Skip to content

Commit fd10b09

Browse files
authored
Merge pull request #301 from JohT/feature/graph-visualization-with-graphviz
Graph Visualization with GraphViz
2 parents d7f9c67 + 0839c1f commit fd10b09

17 files changed

+551
-30
lines changed

.github/workflows/java-code-analysis.yml

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,6 @@ jobs:
6969
distribution: 'adopt'
7070
java-version: ${{ matrix.java }}
7171

72-
- name: Setup Node.js for Graph Visualization
73-
uses: actions/setup-node@v4
74-
with:
75-
node-version-file: 'graph-visualization/.nvmrc'
76-
77-
- name: Install Node packages for Graph Visualization
78-
working-directory: graph-visualization
79-
run: npm ci
80-
8172
- name: Setup Cache for Conda package manager Miniforge
8273
uses: actions/cache@v4
8374
env:
@@ -142,7 +133,7 @@ jobs:
142133
if: failure()
143134
uses: actions/upload-artifact@v4
144135
with:
145-
name: code-analysis-logs-java-${{ matrix.java }}-python-${{ matrix.python }}-miniforge-${{ matrix.miniforge }}
136+
name: java-code-analysis-logs-java-${{ matrix.java }}-python-${{ matrix.python }}-miniforge-${{ matrix.miniforge }}
146137
path: |
147138
./temp/**/runtime/*
148139
./temp/**/reports/*
@@ -153,8 +144,8 @@ jobs:
153144
if: success()
154145
uses: actions/upload-artifact@v4
155146
with:
156-
name: code-report-results-java-${{ matrix.java }}-python-${{ matrix.python }}-miniforge-${{ matrix.miniforge }}
157-
path: ./results
147+
name: java-code-analysis-results-java-${{ matrix.java }}-python-${{ matrix.python }}-miniforge-${{ matrix.miniforge }}
148+
path: ./temp/**/reports/*
158149
if-no-files-found: error
159150
retention-days: 5
160151

@@ -164,7 +155,7 @@ jobs:
164155
#- name: Archive exported database
165156
# uses: actions/upload-artifact@v3
166157
# with:
167-
# name: code-report-database-export-${{ matrix.java }}-python-${{ matrix.python }}-miniforge-${{ matrix.miniforge }}
158+
# name: java-code-analysis-database-export-${{ matrix.java }}-python-${{ matrix.python }}-miniforge-${{ matrix.miniforge }}
168159
# path: ./temp/**/import
169160
# if-no-files-found: error
170161
# retention-days: 5

.github/workflows/typescript-code-analysis.yml

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,6 @@ jobs:
6969
distribution: 'adopt'
7070
java-version: ${{ matrix.java }}
7171

72-
- name: Setup Node.js for Graph Visualization
73-
uses: actions/setup-node@v4
74-
with:
75-
node-version-file: 'graph-visualization/.nvmrc'
76-
77-
- name: Install Node packages for Graph Visualization
78-
working-directory: graph-visualization
79-
run: npm ci
80-
8172
- name: Setup Cache for Conda package manager Miniforge
8273
uses: actions/cache@v4
8374
env:
@@ -156,7 +147,7 @@ jobs:
156147
if: failure()
157148
uses: actions/upload-artifact@v4
158149
with:
159-
name: code-analysis-logs-java-${{ matrix.java }}-python-${{ matrix.python }}-miniforge-${{ matrix.miniforge }}
150+
name: typescript-code-analysis-logs-java-${{ matrix.java }}-python-${{ matrix.python }}-miniforge-${{ matrix.miniforge }}
160151
path: |
161152
./temp/**/runtime/*
162153
./temp/**/reports/*
@@ -167,8 +158,8 @@ jobs:
167158
if: success()
168159
uses: actions/upload-artifact@v4
169160
with:
170-
name: code-report-results-java-${{ matrix.java }}-python-${{ matrix.python }}-miniforge-${{ matrix.miniforge }}
171-
path: ./results
161+
name: typescript-code-analysis-results-java-${{ matrix.java }}-python-${{ matrix.python }}-miniforge-${{ matrix.miniforge }}
162+
path: ./temp/**/reports/*
172163
if-no-files-found: error
173164
retention-days: 5
174165

@@ -178,7 +169,7 @@ jobs:
178169
#- name: Archive exported database
179170
# uses: actions/upload-artifact@v3
180171
# with:
181-
# name: code-report-database-export-${{ matrix.java }}-python-${{ matrix.python }}-miniforge-${{ matrix.miniforge }}
172+
# name: typescript-code-analysis-database-export-${{ matrix.java }}-python-${{ matrix.python }}-miniforge-${{ matrix.miniforge }}
182173
# path: ./temp/**/import
183174
# if-no-files-found: error
184175
# retention-days: 5

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ The [Code Structure Analysis Pipeline](./.github/workflows/java-code-analysis.ym
124124
- [openTSNE](https://github.com/pavlin-policar/openTSNE)
125125
- [wordcloud](https://github.com/amueller/word_cloud)
126126
- [Graph Visualization](./graph-visualization/README.md) uses [node.js](https://nodejs.org/de) and the dependencies listed in [package.json](./graph-visualization/package.json).
127+
- [HPCC-Systems (High Performance Computing Cluster) Web-Assembly (JavaScript)](https://github.com/hpcc-systems/hpcc-js-wasm) containing a wrapper for GraphViz to visualize graph structures.
128+
- [GraphViz](https://gitlab.com/graphviz/graphviz) for CLI Graph Visualization
127129
- [Check links in markdown documentation (GitHub workflow)](./.github/workflows/check-links-in-documentation.yml) uses [markdown-link-check](https://github.com/tcort/markdown-link-check).
128130

129131
**Big shout-out** 📣 to all the creators and contributors of these great libraries 👍. Projects like this wouldn't be possible without them. Feel free to [create an issue](https://github.com/JohT/code-graph-analysis-pipeline/issues/new/choose) if something is missing or wrong in the list.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// List of all Java Artifacts and their dependencies with build levels for GraphViz Visualization
2+
3+
MATCH (sourceForStatistics:Java:Artifact)-[dependencyForStatistics:DEPENDS_ON]->(targetForStatistics:Java:Artifact)
4+
WHERE sourceForStatistics.maxDistanceFromSource IS NOT NULL
5+
AND targetForStatistics.maxDistanceFromSource IS NOT NULL
6+
WITH min(dependencyForStatistics.weight) AS minWeight
7+
,max(dependencyForStatistics.weight) AS maxWeight
8+
,max(targetForStatistics.maxDistanceFromSource) AS maxLevel
9+
MATCH (source:Java:Artifact)-[dependency:DEPENDS_ON]->(target:Java:Artifact)
10+
WHERE source.maxDistanceFromSource IS NOT NULL
11+
AND target.maxDistanceFromSource IS NOT NULL
12+
WITH *, toFloat(dependency.weight - minWeight) / toFloat(maxWeight - minWeight) AS normalizedWeight
13+
WITH *, round((normalizedWeight * 5) + 1, 2) AS penWidth
14+
WITH *, source.name + "\\n(level " + coalesce(source.maxDistanceFromSource + "/" + maxLevel, "?") + ")" AS fullSourceName
15+
WITH *, target.name + "\\n(level " + coalesce(target.maxDistanceFromSource + "/" + maxLevel, "?") + ")" AS fullTargetName
16+
WITH *, "\" -> \"" + fullTargetName
17+
+ "\" [label = " + dependency.weight + ";"
18+
+ " penwidth = " + penWidth + ";"
19+
+ " ];" AS graphVizDotNotationEdge
20+
WITH *, "\"" + fullSourceName + coalesce(graphVizDotNotationEdge, "\" [];") AS graphVizDotNotationLine
21+
ORDER BY dependency.weight DESC, target.maxDistanceFromSource DESC
22+
RETURN graphVizDotNotationLine
23+
//Debugging
24+
//,source.name AS sourceName
25+
//,target.name AS targetName
26+
//,penWidth
27+
//,normalizedWeight
28+
//,dependency.weight AS weight
29+
//,minWeight
30+
//,maxWeight
31+
LIMIT 440
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// List of all Typescript modules and their dependencies with build levels for GraphViz Visualization
2+
3+
MATCH (sourceForStatistics:TS:Module)-[dependencyForStatistics:DEPENDS_ON]->(targetForStatistics:TS:Module)
4+
WHERE sourceForStatistics.maxDistanceFromSource IS NOT NULL
5+
AND targetForStatistics.maxDistanceFromSource IS NOT NULL
6+
WITH min(dependencyForStatistics.weight) AS minWeight
7+
,max(dependencyForStatistics.weight) AS maxWeight
8+
,max(targetForStatistics.maxDistanceFromSource) AS maxLevel
9+
MATCH (source:TS:Module)-[dependency:DEPENDS_ON]->(target:TS:Module)
10+
WHERE source.maxDistanceFromSource IS NOT NULL
11+
AND target.maxDistanceFromSource IS NOT NULL
12+
WITH *, toFloat(dependency.cardinality - minWeight) / toFloat(maxWeight - minWeight) AS normalizedWeight
13+
WITH *, round((normalizedWeight * 5) + 1, 2) AS penWidth
14+
WITH *, source.rootProjectName + "\\n" + source.name + "\\n(level " + coalesce(source.maxDistanceFromSource + "/" + maxLevel, "?") + ")" AS fullSourceName
15+
WITH *, target.rootProjectName + "\\n" + target.name + "\\n(level " + coalesce(target.maxDistanceFromSource + "/" + maxLevel, "?") + ")" AS fullTargetName
16+
WITH *, "\" -> \"" + fullTargetName
17+
+ "\" [label = " + dependency.cardinality + ";"
18+
+ " penwidth = " + penWidth + ";"
19+
+ " ];" AS graphVizDotNotationEdge
20+
WITH *, "\"" + fullSourceName + coalesce(graphVizDotNotationEdge, "\" [];") AS graphVizDotNotationLine
21+
ORDER BY dependency.weight DESC, target.maxDistanceFromSource DESC
22+
RETURN graphVizDotNotationLine
23+
//Debugging
24+
//,source.name AS sourceName
25+
//,target.name AS targetName
26+
//,penWidth
27+
//,normalizedWeight
28+
//,dependency.cardinality AS weight
29+
//,minWeight
30+
//,maxWeight
31+
LIMIT 440
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Path Finding - Longest path - Stream - List all dependencies for nodes contributing to longest paths and highlight those paths in the Visualization with GraphViz.
2+
3+
// Gather global statistics about dependency weights and levels for normalization and node details
4+
MATCH (sourceNodeForStatistics)-[dependencyForStatistics:DEPENDS_ON]->(targetNodeForStatistics)
5+
WHERE $dependencies_projection_node IN LABELS(sourceNodeForStatistics)
6+
AND $dependencies_projection_node IN LABELS(targetNodeForStatistics)
7+
WITH min(dependencyForStatistics[$dependencies_projection_weight_property]) AS minWeight
8+
,max(dependencyForStatistics[$dependencies_projection_weight_property]) AS maxWeight
9+
,max(targetNodeForStatistics.maxDistanceFromSource) AS maxLevel
10+
WITH *, 1.0 / toFloat(maxWeight - minWeight) AS weightNormalizationFactor
11+
WITH { minWeight: minWeight, maxLevel: maxLevel, weightNormalizationFactor: weightNormalizationFactor } AS statistics
12+
// -> Main call to execute "longest path" algorithm
13+
CALL gds.dag.longestPath.stream($dependencies_projection + '-cleaned')
14+
YIELD index, totalCost, path
15+
WITH *
16+
// Sort longest paths by their length descending and - if equal - by their index ascending
17+
ORDER BY totalCost DESC, index ASC
18+
// Only take the top 50 longest paths as a compromise between performance and visualization content
19+
LIMIT 50
20+
// Collect all results of the longest path search as well as all nodes of the longest paths
21+
WITH statistics
22+
,collect({index: index, distance: toInteger(totalCost), path: path}) AS longestPaths
23+
,collect(nodes(path)) AS allLongestPathNodes
24+
// Flatten and deduplicate the list of all nodes that contribute to at least one longest path
25+
UNWIND allLongestPathNodes AS longestPathNodes
26+
UNWIND longestPathNodes AS longestPathNode
27+
WITH statistics
28+
,longestPaths
29+
,collect(DISTINCT longestPathNode) AS allDistinctLongestPathNodes
30+
// Iterate over all longest paths
31+
UNWIND longestPaths AS longestPath
32+
WITH statistics
33+
,longestPaths, allDistinctLongestPathNodes
34+
,[ singleRelationship IN relationships(longestPath.path) | [startNode(singleRelationship), endNode(singleRelationship)] ] AS allLongestPathStartAndEndNodeTuples
35+
,[ singleRelationship IN relationships(longestPaths[0].path) | [startNode(singleRelationship), endNode(singleRelationship)] ] AS longestPathStartAndEndNodeTuples
36+
,longestPath.index AS index
37+
,longestPath.distance AS distance
38+
// -> Main query of all dependencies of nodes contributing to the longest paths
39+
MATCH (source)-[dependency:DEPENDS_ON]->(target)
40+
WHERE $dependencies_projection_node IN labels(source)
41+
AND $dependencies_projection_node IN labels(target)
42+
// Dependent nodes need to be part of at least one longest paths
43+
AND (source IN allDistinctLongestPathNodes AND target IN allDistinctLongestPathNodes)
44+
WITH statistics.maxLevel AS maxLevel
45+
,statistics.minWeight AS minWeight
46+
,statistics.weightNormalizationFactor AS weightNormalizationFactor
47+
,count(index) AS numberOfLongestPathsPassing
48+
,max(distance) AS lengthOfLongestPathPassing
49+
,dependency
50+
,source
51+
,target
52+
// If there is at least one longest path passing through the dependency then "contributesToALongestPath" is true
53+
,([source, target] IN allLongestPathStartAndEndNodeTuples) AS contributesToALongestPath
54+
,([source, target] IN longestPathStartAndEndNodeTuples) AS isPartOfLongestPath
55+
WITH *, dependency[$dependencies_projection_weight_property] AS weight
56+
WITH *, toFloat(weight - minWeight) * weightNormalizationFactor AS normalizedWeight
57+
WITH *, round((normalizedWeight * 5) + 1, 2) AS penWidth
58+
WITH *, source.name + "\\n(level " + source.maxDistanceFromSource + "/" + maxLevel + ")" AS startNodeTitle
59+
WITH *, target.name + "\\n(level " + target.maxDistanceFromSource + "/" + maxLevel + ")" AS endNodeTitle
60+
// The longest path will be highlighted in red.
61+
WITH *, CASE WHEN isPartOfLongestPath THEN "; color=\"red\""
62+
// Dependencies contributing to the longest path will be highlighted in dark orange.
63+
WHEN contributesToALongestPath THEN "; color=\"darkorange\""
64+
ELSE "" END AS edgeColor
65+
// Prepare the GraphViz edge attributes for the visualization
66+
WITH *, "[label=" + weight + "; penwidth=" + penWidth + edgeColor + "; ];" AS graphVizEdgeAttributes
67+
// Assemble the final GraphViz DOT notation line for the edge representing the current dependency
68+
WITH *, "\"" + startNodeTitle + "\" -> \"" + endNodeTitle + "\" " + graphVizEdgeAttributes AS graphVizDotNotationLine
69+
RETURN DISTINCT graphVizDotNotationLine
70+
// Debugging
71+
// ,source.name
72+
// ,target.name
73+
// ,numberOfLongestPathsPassing
74+
// ,lengthOfLongestPathPassing
75+
// ,contributesToALongestPath
76+
// ,isPartOfLongestPath
77+
LIMIT 440
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Path Finding - Longest path - Stream - Find the top 100 dependencies contributing to the longest paths for Visualization with GraphViz
2+
3+
MATCH (sourceNodeForStatistics)-[dependencyForStatistics:DEPENDS_ON]->(targetNodeForStatistics)
4+
WHERE $dependencies_projection_node IN LABELS(sourceNodeForStatistics)
5+
AND $dependencies_projection_node IN LABELS(targetNodeForStatistics)
6+
WITH min(dependencyForStatistics[$dependencies_projection_weight_property]) AS minWeight
7+
,max(dependencyForStatistics[$dependencies_projection_weight_property]) AS maxWeight
8+
,max(targetNodeForStatistics.maxDistanceFromSource) AS maxLevel
9+
WITH *, 1.0 / toFloat(maxWeight - minWeight) AS weightNormalizationFactor
10+
CALL gds.dag.longestPath.stream($dependencies_projection + '-cleaned')
11+
YIELD index, totalCost, path
12+
WITH *, toInteger(totalCost) AS distance
13+
ORDER BY distance DESC, index ASC
14+
UNWIND relationships(path) AS pathRelationship
15+
WITH *
16+
,startNode(pathRelationship) AS startNode
17+
,endNode(pathRelationship) AS endNode
18+
MATCH (startNode)-[dependency:DEPENDS_ON]->(endNode)
19+
WITH *, dependency[$dependencies_projection_weight_property] AS weight
20+
WITH *, toFloat(weight - minWeight) * weightNormalizationFactor AS normalizedWeight
21+
WITH *, round((normalizedWeight * 5) + 1, 2) AS penWidth
22+
WITH *, startNode.name + "\\n(level " + startNode.maxDistanceFromSource + "/" + maxLevel + ")" AS startNodeTitle
23+
WITH *, endNode.name + "\\n(level " + endNode.maxDistanceFromSource + "/" + maxLevel + ")" AS endNodeTitle
24+
WITH *, "[label=" + weight + "; penwidth=" + penWidth + "; ];" AS graphVizEdgeAttributes
25+
WITH *, "\"" + startNodeTitle + "\" -> \"" + endNodeTitle + "\" " + graphVizEdgeAttributes AS graphVizDotNotationLine
26+
RETURN graphVizDotNotationLine
27+
// Debugging
28+
// RETURN startNode.name AS startNodeName
29+
// ,endNode.name AS endNodeName
30+
// ,dependency[$dependencies_projection_weight_property] AS dependencyWeight
31+
// ,max(distance) AS partOfLongestPathLength
32+
// ,count(DISTINCT index) AS partOfLongestPathCounts
33+
// ,startNode.maxDistanceFromSource AS startNodeLevel
34+
// ,endNode.maxDistanceFromSource AS endNodeLevel
35+
LIMIT 100

cypher/Path_Finding/Set_Parameters.cypher

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Example on how to set the parameters for centrality in this case for Packages and PageRank
1+
// Example on how to set the parameters for path finding in this case for Packages and PageRank
22

33
:params {
44
"dependencies_projection": "package-path-finding",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Example on how to set the parameters for topological sort in this case for Java Artifacts and Node Similarity
2+
3+
:params {
4+
"dependencies_projection": "artifact-topology",
5+
"dependencies_projection_node": "Artifact",
6+
"dependencies_projection_weight_property": "weight"
7+
}

graph-visualization/DEPRECATED.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Render Graph Visualizations (Deprecated)
2+
3+
:warning: **This package is deprecated and might get removed in future.** :warning:
4+
5+
Visualizations are now done using [GraphViz](https://graphviz.org). Please use the new script [visualizeQueryResults.sh](./../scripts/visualization/visualizeQueryResults.sh) like it is done in [InternalDependenciesVisualization.sh](./../scripts/reports/InternalDependenciesVisualization.sh) to create Graph Visualizations.

0 commit comments

Comments
 (0)