Skip to content

Commit

Permalink
Ooooh, get graph working
Browse files Browse the repository at this point in the history
  • Loading branch information
StefanieJaeger committed Dec 20, 2023
1 parent 0d0e730 commit 95be4fd
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 85 deletions.
28 changes: 26 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
"chart.js": "^4.4.0",
"chartjs-chart-graph": "^4.2.6",
"chartjs-plugin-datalabels": "^2.2.0",
"cytoscape": "^3.28.0",
"vue": "^3.2.47",
"vue-router": "^4.1.6",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@types/cytoscape": "^3.19.16",
"@types/node": "^20.3.3",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
Expand Down
28 changes: 17 additions & 11 deletions src/components/module-dependency-graph/ModuleDependencyDropdown.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
<template>
<!-- <div class="has-dropdown is-hoverable">
<a>Module</a>
<div class="navbar-dropdown">
<a v-for="module in unselectedModules" :key="module.id" class="navbar-item" @click="onModuleSelected(module.id)"
v-text="module.name" />
</div>
</div> -->

<div class="is-flex is-align-content-space-evenly is-justify-content-left">
<label class="is-flex is-flex-direction-column is-justify-content-center" for="modules-select">

Check warning on line 3 in src/components/module-dependency-graph/ModuleDependencyDropdown.vue

View workflow job for this annotation

GitHub Actions / Build

'for' should be on a new line
<p>Module</p>
Expand All @@ -20,17 +12,30 @@
</div>
</div>

<div>
<div class="is-flex mt-2">
<template v-for="module in selectedModules" :key="module.id">

Check warning on line 16 in src/components/module-dependency-graph/ModuleDependencyDropdown.vue

View workflow job for this annotation

GitHub Actions / Build

':key' should be on a new line
<div style="background-color: pink; margin-right: 1em;">{{ module.name }} <button
@click="onModuleDeselected(module.id)">X</button></div>
<div class="selected-module p-2" :style="{ 'background-color': getCategoryColorForModule(module), 'margin-right': '1em' }">

Check failure on line 17 in src/components/module-dependency-graph/ModuleDependencyDropdown.vue

View workflow job for this annotation

GitHub Actions / Build

This line has a length of 129. Maximum allowed is 120

Check warning on line 17 in src/components/module-dependency-graph/ModuleDependencyDropdown.vue

View workflow job for this annotation

GitHub Actions / Build

':style' should be on a new line
{{ module.name }}
<button
class="delete-button delete is-pulled-right ml-1"
type="button"
@click="onModuleDeselected(module.id)">X</button>

Check warning on line 22 in src/components/module-dependency-graph/ModuleDependencyDropdown.vue

View workflow job for this annotation

GitHub Actions / Build

Expected 1 line break before closing bracket, but no line breaks found
</div>
</template>
</div>
</template>

<style>
.selected-module {
color: white;
border-radius: 5px;
}
</style>

<script lang="ts">
import { defineComponent } from 'vue';
import type { Module } from '../../helpers/types';
import { getCategoryColorForModule } from '../../helpers/color-helper';
export default defineComponent({
name: 'ModuleDependencyDropdown',
Expand All @@ -57,6 +62,7 @@ export default defineComponent({
},
},
methods: {
getCategoryColorForModule,
onModuleSelected(moduleId: string) {
console.log('onModuleSelected', moduleId);
const module = this.unselectedModules.find(f => f.id === moduleId);
Expand Down
177 changes: 106 additions & 71 deletions src/components/module-dependency-graph/ModuleDependencyGraph.vue
Original file line number Diff line number Diff line change
@@ -1,100 +1,135 @@
<template>
<canvas id="chart" />
<div class="cy-wrapper">
<div id="cy"></div>
</div>
</template>

<style>
.cy-wrapper {
width: 100%;
height: 100%;
position: absolute;
}
#cy {
width: 100%;
height: 100%;
position: relative;
margin: auto;
background: #ececec;
}
</style>

<script lang="ts">
import { Chart, LinearScale, PointElement, type ChartOptions, type ChartTypeRegistry, type ChartConfiguration } from 'chart.js/auto';
import { defineComponent } from 'vue';
import { ForceDirectedGraphController, EdgeLine } from 'chartjs-chart-graph';
import ChartDataLabels from 'chartjs-plugin-datalabels';
import type { Module } from '../../helpers/types';
type Node = { id: string, displayName: string };
type Link = { source: string, target: string };
Chart.register(ForceDirectedGraphController, EdgeLine, LinearScale, PointElement, ChartDataLabels);
// todo: labels (should not hide arrow)
// todo: no animation (faster render)
// todo: keep things closer together (way too speard apart)
const chartOptions: ChartOptions<'forceDirectedGraph' | ChartDataLabels> = {
tree: { orientation: 'radial' },
layout: { padding: 20 },
plugins: {
datalabels: {
display: (_: ChartDataLabels.Context) => {
return true;
},
align: (context: ChartDataLabels.Context) => {
const index = context.dataIndex;
const value = context.dataset.data[index] as { angle: number };
return (-value.angle / Math.PI) * 180;
},
rotation: (context: ChartDataLabels.Context) => {
const index = context.dataIndex;
const value = context.dataset.data[index] as { angle: number };
return (-value.angle / Math.PI) * 180;
},
backgroundColor: 'white',
formatter: (value: { displayName: string }) => {
return value.displayName;
},
},
},
};
import cytoscape from 'cytoscape';
import { getCategoryColorForModule } from '../../helpers/color-helper';
export default defineComponent({
name: 'ModuleDependencyGraph',
props: {
modules: {
modulesToDisplay: {
type: Array<Module>,
required: true,
},
selectedModules: {
type: Array<Module>,
required: true,
},
},
data() {
return {
chart: null as any | null,
};
cy: null as any | null,

Check failure on line 43 in src/components/module-dependency-graph/ModuleDependencyGraph.vue

View workflow job for this annotation

GitHub Actions / Build

Unexpected any. Specify a different type
}
},
mounted() {
this.create();
},
watch: {
modules: {
modulesToDisplay: {
deep: true,
immediate: false,
handler(newValue: Module[]) {
const nodes = [...newValue.map(module => ({ id: module.id, displayName: module.name }))];
let links = [...newValue.flatMap(module => module.recommendedModuleIds.map(recommendedId => ({ source: recommendedId, target: module.id })) ?? [])];
links = links.filter(link => nodes.some(node => node.id === link.source) && nodes.some(node => node.id === link.target));
console.log({ nodes, links });
this.createChart(nodes, links);
handler() {
this.create();
},
},
},
methods: {
createChart(nodes: Node[], links: Link[]) {
if (this.chart) {
this.chart.destroy();
}
const canvas = (document.getElementById('chart') as HTMLCanvasElement).getContext('2d') as CanvasRenderingContext2D;
const config: ChartConfiguration<'forceDirectedGraph' | ChartDataLabels> = {
type: ForceDirectedGraphController.id,
buildData(): { nodes: { data: Node }[], edges: { data: Edge }[] } {
const nodes: { data: Node }[] = [...this.modulesToDisplay.map(module => ({
data: {
labels: nodes.map((d) => d.id),
datasets: [{
pointBackgroundColor: 'steelblue',
pointRadius: 5,
directed: true,
data: nodes,
edges: links,
}]
},
options: chartOptions,
plugins: [ChartDataLabels],
};
this.chart = new Chart(canvas, config);
id: module.id,
label: module.name,
categoryColor: getCategoryColorForModule(module),
}
}))];
let edges = [...this.modulesToDisplay.flatMap(module =>
module.recommendedModuleIds.map(recId =>
({ data: { source: recId, target: module.id } })
) ?? [])];
edges = edges.filter(edge =>
edge.data.source &&
edge.data.target &&
nodes.some(node => node.data.id === edge.data.source) &&
nodes.some(node => node.data.id === edge.data.target));
console.log({ nodes, edges });
return { nodes, edges };
},
create() {
const { nodes, edges } = this.buildData();
this.cy = cytoscape({
container: document.getElementById('cy'),
elements: {
nodes,
edges
},
zoomingEnabled: false,
style: [
{
selector: "node[label]",
style: {
"label": "data(label)",
}
},
{
selector: "edge[label]",
style: {
"label": "data(label)",
"width": 3
}
},
{
selector: "edge",
style: {
"width": 1,
"target-arrow-shape": "triangle",
"curve-style": "straight",
}
},
{
selector: 'node[categoryColor]',
style: {
"background-color": "data(categoryColor)"
}
}
],
});
}
},
});
type Node = {
id: string,
label: string,
categoryColor: string,
};
type Edge = {
source: string,
target: string,
};
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</div>
<div class="columns">
<div class="column">
<ModuleDependencyGraph :modules="modulesForGraph"></ModuleDependencyGraph>
<ModuleDependencyGraph :selectedModules="selectedModules" :modulesToDisplay="modulesForGraph"></ModuleDependencyGraph>

Check failure on line 14 in src/components/module-dependency-graph/ModuleDependencyPage.vue

View workflow job for this annotation

GitHub Actions / Build

This line has a length of 128. Maximum allowed is 120
</div>
</div>
</template>
Expand All @@ -35,6 +35,7 @@ export default defineComponent({
return {
modulesForGraph: [] as Module[],
allModules: [] as Module[],
selectedModules: [] as Module[],
}
},
async mounted() {
Expand All @@ -57,6 +58,7 @@ export default defineComponent({
}
this.modulesForGraph = [...selectedModules, ...recommended, ...recommending].filter((value, index, array) => array.indexOf(value) === index);

Check failure on line 60 in src/components/module-dependency-graph/ModuleDependencyPage.vue

View workflow job for this annotation

GitHub Actions / Build

This line has a length of 147. Maximum allowed is 120
this.selectedModules = selectedModules;
},
getRecommendedModulesForModule(module: Module, visited: Module[]): Module[] {
const recommended = module.recommendedModuleIds.map(m => this.allModules.find(f => f.id === m));
Expand Down

0 comments on commit 95be4fd

Please sign in to comment.