Skip to content

Commit 48febf2

Browse files
committed
fixup! Add graph visualizations to anomaly detection
1 parent a6fbec1 commit 48febf2

File tree

8 files changed

+207
-25
lines changed

8 files changed

+207
-25
lines changed

domains/anomaly-detection/graphs/TopAuthority.cypher

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Anomaly Detection Graphs: Find top nodes marked as "Authority" including their incoming dependencies and output them in Graphviz format.
1+
// Anomaly Detection Graphs: Find top nodes marked as "Authority" including their incoming and outgoing dependencies, sizes based on PageRank and thick outline for nodes with high Page Rank to Article Rank difference in Graphviz format.
22

33
// Step 1: Query overall statistics, e.g. min/max weight for later normalization
44
MATCH (sourceForStatistics)-[dependencyForStatistics:DEPENDS_ON]->(targetForStatistics)
@@ -13,24 +13,24 @@
1313
WITH maxWeight
1414
,pageToArticleRankThreshold
1515
,central
16-
,"Top Rank #" + $projection_node_rank + " " + $projection_language + " " + $projection_node_label + " Authority: " AS graphLabel
16+
,"Top Rank #" + $projection_node_rank + " " + $projection_language + " " + $projection_node_label + " Authority\\n" AS graphLabel
1717
,coalesce(central.fqn, central.globalFqn, central.fileName, central.signature, central.name) AS targetName
1818
,[] AS graphVizOutput
19-
WITH *, replace(replace(targetName, '.', '.\\n'), '/', '/\\n') AS targetNameSplit
20-
WITH *, targetNameSplit + "\\n(authority #" + central.anomalyAuthorityRank + ")" AS centralNodeLabel
2119
WITH *, graphVizOutput + ["graph [label=\"" + graphLabel + targetName + "\\n\\n\"];"] AS graphVizOutput
20+
WITH *, "🏛️ authority #" + central.anomalyAuthorityRank + "\\n" + central.name AS centralNodeLabel
2221
WITH *, graphVizOutput + ["central [label=\"" + centralNodeLabel + "\"];"] AS graphVizOutput
2322
// Step 3: Query direct incoming dependencies to the central node
2423
MATCH (source)-[dependency:DEPENDS_ON]->(central)
2524
WHERE $projection_node_label IN labels(source)
2625
AND source.outgoingDependencies > 0
2726
ORDER BY dependency.weight DESC, source.name ASC
28-
LIMIT 40
27+
LIMIT 60
2928
WITH *, coalesce(dependency.weight25PercentInterfaces, dependency.weight, 1) AS weight
3029
WITH *, round((toFloat(weight) / toFloat(maxWeight) * 2.5) + 0.4, 1.0) AS penWidth
3130
WITH *, "label=" + weight + "; weight=" + weight + "; penwidth=" + penWidth AS edgeAttributes
3231
WITH *, CASE WHEN source.centralityPageRankToArticleRankDifference > pageToArticleRankThreshold THEN 5 ELSE 2 END AS scaledNodeBorder
3332
WITH *, round(source.centralityPageRankNormalized * 0.66 + 0.2, 3) AS scaledNodeSize
33+
WITH *, coalesce(scaledNodeSize, 0.5) AS scaledNodeSize
3434
WITH *, "penwidth = " + scaledNodeBorder + "; " AS directInBorder
3535
WITH *, "height = " + scaledNodeSize + "; " AS directInSize
3636
WITH *, "\"" + source.name + "\" [" + directInBorder + directInSize + "]; " AS directInNode
@@ -46,14 +46,15 @@
4646
WHERE $projection_node_label IN labels(source)
4747
AND source.incomingDependencies > 0
4848
ORDER BY dependency.weight DESC, source.name ASC
49-
LIMIT 40
49+
LIMIT 60
5050
WITH *, coalesce(dependency.weight25PercentInterfaces, dependency.weight, 1) AS weight
5151
WITH *, round((toFloat(weight) / toFloat(maxWeight) * 2.5) + 0.4, 1.0) AS penWidth
5252
WITH *, "label=" + weight + "; weight=" + weight + "; penwidth=" + penWidth AS edgeAttributes
5353
// Use a lighter color for the target nodes of outgoing dependencies from the central node and their edges
5454
WITH *, edgeAttributes + "; color = 5" AS edgeAttributes
5555
WITH *, CASE WHEN source.centralityPageRankToArticleRankDifference > pageToArticleRankThreshold THEN 5 ELSE 2 END AS scaledNodeBorder
5656
WITH *, round(source.centralityPageRankNormalized * 0.66 + 0.2, 3) AS scaledNodeSize
57+
WITH *, coalesce(scaledNodeSize, 0.5) AS scaledNodeSize
5758
WITH *, "penwidth = " + scaledNodeBorder + "; " AS directOutBorder
5859
WITH *, "height = " + scaledNodeSize + "; " AS directOutSize
5960
WITH *, "color = 5; fillcolor = 1; " AS directOutColors
@@ -80,7 +81,7 @@
8081
,directDependentNode
8182
,dependency
8283
,collect(anotherDirectDependentNode)[0] AS firstLinkedDependentNode
83-
LIMIT 80
84+
LIMIT 120
8485
WITH *, coalesce(dependency.weight25PercentInterfaces, dependency.weight, 1) AS weight
8586
// Use a fixed small pen width for secondary dependencies for better visibility of the more important direct dependency
8687
WITH *, "label=" + weight + "; weight=" + weight + "; penwidth=0.3" AS edgeAttributes

domains/anomaly-detection/graphs/TopBottleneck.cypher

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,18 @@
1111
AND central.anomalyBottleneckRank = toInteger($projection_node_rank)
1212
WITH maxWeight
1313
,central
14-
,"Top Rank #" + $projection_node_rank + " " + $projection_language + " " + $projection_node_label + " Bottleneck: " AS graphLabel
14+
,"Top Rank #" + $projection_node_rank + " " + $projection_language + " " + $projection_node_label + " Bottleneck\\n" AS graphLabel
1515
,coalesce(central.fqn, central.globalFqn, central.fileName, central.signature, central.name) AS targetName
1616
,[] AS graphVizOutput
17-
WITH *, replace(replace(targetName, '.', '.\\n'), '/', '/\\n') AS targetNameSplit
18-
WITH *, targetNameSplit + "\\n(bottleneck #" + central.anomalyBottleneckRank + ")" AS centralNodeLabel
1917
WITH *, graphVizOutput + ["graph [label=\"" + graphLabel + targetName + "\\n\\n\"];"] AS graphVizOutput
18+
WITH *, "🔒 bottleneck #" + central.anomalyBottleneckRank + "\\n" + central.name AS centralNodeLabel
2019
WITH *, graphVizOutput + ["central [label=\"" + centralNodeLabel + "\"];"] AS graphVizOutput
2120
// Step 3: Query direct incoming dependencies to the central node
2221
MATCH (source)-[dependency:DEPENDS_ON]->(central)
2322
WHERE $projection_node_label IN labels(source)
2423
AND source.outgoingDependencies > 0
2524
ORDER BY dependency.weight DESC, source.name ASC
26-
LIMIT 30
25+
LIMIT 60
2726
WITH *, coalesce(dependency.weight25PercentInterfaces, dependency.weight, 1) AS weight
2827
WITH *, round((toFloat(weight) / toFloat(maxWeight) * 2.5) + 0.4, 1.0) AS penWidth
2928
WITH *, "label=" + weight + "; weight=" + weight + "; penwidth=" + penWidth AS edgeAttributes
@@ -38,7 +37,7 @@
3837
WHERE $projection_node_label IN labels(source)
3938
AND source.incomingDependencies > 0
4039
ORDER BY dependency.weight DESC, source.name ASC
41-
LIMIT 30
40+
LIMIT 60
4241
WITH *, coalesce(dependency.weight25PercentInterfaces, dependency.weight, 1) AS weight
4342
WITH *, round((toFloat(weight) / toFloat(maxWeight) * 2.5) + 0.4, 1.0) AS penWidth
4443
WITH *, "label=" + weight + "; weight=" + weight + "; penwidth=" + penWidth AS edgeAttributes
@@ -65,7 +64,7 @@
6564
,directDependentNode
6665
,dependency
6766
,collect(anotherDirectDependentNode)[0] AS firstLinkedDependentNode
68-
LIMIT 60
67+
LIMIT 120
6968
WITH *, coalesce(dependency.weight25PercentInterfaces, dependency.weight, 1) AS weight
7069
// Use a fixed small pen width for secondary dependencies for better visibility of the more important direct dependency
7170
WITH *, "label=" + weight + "; weight=" + weight + "; penwidth=0.3" AS edgeAttributes
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Anomaly Detection Graphs: Find top nodes marked as "Bridge" including their incoming and outgoing dependencies and output them in Graphviz format.
2+
3+
// Step 1: Query overall statistics, e.g. min/max weight for later normalization
4+
MATCH (sourceForStatistics)-[dependencyForStatistics:DEPENDS_ON]->(targetForStatistics)
5+
WHERE $projection_node_label IN labels(sourceForStatistics)
6+
AND $projection_node_label IN labels(targetForStatistics)
7+
WITH max(coalesce(dependencyForStatistics.weight25PercentInterfaces, dependencyForStatistics.weight)) AS maxWeight
8+
// Step 2: Query selected central node
9+
MATCH (central)
10+
WHERE $projection_node_label IN labels(central)
11+
AND central.anomalyBridgeRank = toInteger($projection_node_rank)
12+
WITH maxWeight
13+
,central
14+
,"Top Rank #" + $projection_node_rank + " " + $projection_language + " " + $projection_node_label + " Bridge\\n" AS graphLabel
15+
,coalesce(central.fqn, central.globalFqn, central.fileName, central.signature, central.name) AS targetName
16+
,[] AS graphVizOutput
17+
WITH *, graphVizOutput + ["graph [label=\"" + graphLabel + targetName + "\\n\\n\"];"] AS graphVizOutput
18+
WITH *, "🌉 bridge #" + central.anomalyBridgeRank + "\\n" + central.name AS centralNodeLabel
19+
WITH *, graphVizOutput + ["central [label=\"" + centralNodeLabel + "\"];"] AS graphVizOutput
20+
// Step 3: Query direct incoming dependencies to the central node
21+
MATCH (source)-[dependency:DEPENDS_ON]->(central)
22+
WHERE $projection_node_label IN labels(source)
23+
AND source.outgoingDependencies > 0
24+
ORDER BY dependency.weight DESC, source.name ASC
25+
LIMIT 60
26+
WITH *, coalesce(dependency.weight25PercentInterfaces, dependency.weight, 1) AS weight
27+
WITH *, round((toFloat(weight) / toFloat(maxWeight) * 2.5) + 0.4, 1.0) AS penWidth
28+
WITH *, "label=" + weight + "; weight=" + weight + "; penwidth=" + penWidth AS edgeAttributes
29+
WITH maxWeight
30+
,central
31+
,graphVizOutput
32+
,collect(source) AS incomingDependencyNodes
33+
,collect("\"" + source.name + "\" -> central [" + edgeAttributes + "];") AS directInEdges
34+
WITH *, graphVizOutput + directInEdges AS graphVizOutput
35+
// Step 4: Query direct outgoing dependencies from the central node
36+
MATCH (source)<-[dependency:DEPENDS_ON]-(central)
37+
WHERE $projection_node_label IN labels(source)
38+
AND source.incomingDependencies > 0
39+
ORDER BY dependency.weight DESC, source.name ASC
40+
LIMIT 60
41+
WITH *, coalesce(dependency.weight25PercentInterfaces, dependency.weight, 1) AS weight
42+
WITH *, round((toFloat(weight) / toFloat(maxWeight) * 2.5) + 0.4, 1.0) AS penWidth
43+
WITH *, "label=" + weight + "; weight=" + weight + "; penwidth=" + penWidth AS edgeAttributes
44+
// Use a lighter color for the target nodes of outgoing dependencies from the central node and their edges
45+
WITH *, edgeAttributes + "; color = 5" AS edgeAttributes
46+
WITH *, "\"" + source.name + "\" [color = 5; fillcolor = 1;]; " AS directOutNode
47+
WITH maxWeight
48+
,central
49+
,graphVizOutput
50+
,incomingDependencyNodes
51+
,collect(source) AS outgoingDependencyNodes
52+
,collect(directOutNode + "central -> \"" + source.name + "\" [" + edgeAttributes + "];") AS directOutEdges
53+
WITH *, graphVizOutput + directOutEdges AS graphVizOutput
54+
WITH *, incomingDependencyNodes + outgoingDependencyNodes AS directDependentNodes
55+
// Step 5: Query dependencies between direct dependencies outside the central node
56+
UNWIND directDependentNodes AS directDependentNode
57+
MATCH (directDependentNode)-[dependency:DEPENDS_ON]->(anotherDirectDependentNode)
58+
WHERE anotherDirectDependentNode IN directDependentNodes
59+
AND anotherDirectDependentNode <> directDependentNode
60+
ORDER BY dependency.weight DESC, directDependentNode.name ASC
61+
WITH maxWeight
62+
,central
63+
,graphVizOutput
64+
,directDependentNode
65+
,dependency
66+
,collect(anotherDirectDependentNode)[0] AS firstLinkedDependentNode
67+
LIMIT 140
68+
WITH *, coalesce(dependency.weight25PercentInterfaces, dependency.weight, 1) AS weight
69+
// Use a fixed small pen width for secondary dependencies for better visibility of the more important direct dependency
70+
WITH *, "label=" + weight + "; weight=" + weight + "; penwidth=0.3" AS edgeAttributes
71+
// Use a light color for secondary dependency edges
72+
WITH *, edgeAttributes + "; color = 3" AS edgeAttributes
73+
WITH *, "\"" + directDependentNode.name + "\" -> \"" + firstLinkedDependentNode.name + "\"" AS directDependenciesEdge
74+
WITH *, collect(directDependenciesEdge + " [" + edgeAttributes + "]") AS directDependenciesEdges
75+
WITH *, graphVizOutput + directDependenciesEdges AS graphVizOutput
76+
UNWIND graphVizOutput AS graphVizOutputLine
77+
RETURN DISTINCT graphVizOutputLine

domains/anomaly-detection/graphs/TopCentral.template.gv

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ strict digraph top_central_template {
88
graph [fontname = "Helvetica,Arial,sans-serif"; labelloc = "t";];
99
node [colorscheme = "bugn9"; color = 6; fillcolor = 3;]; # Alternative: color = "0.58 0.75 0.75"; fillcolor = "0.58 0.15 0.99"
1010
edge [colorscheme = "bugn9"; color = 7; ]; # Alternative: color = "0.58 0.75 0.85";
11-
node [fontsize = 8; style = "filled"; margin = "0.05,0.05"];
11+
node [fontsize = 10; style = "filled"; margin = "0.001,0.001"];
1212
edge [fontsize = 4; arrowsize = "0.4";];
1313

1414
central [shape = "doublecircle"; margin = "0.00001,0.00001";];
15-
central [fontsize = 10;];
15+
central [fontsize = 14;];
1616
central [color = 7; fillcolor = 5; penwidth = 3;]; # color = "0.52 0.7 0.7"; fillcolor = "0.52 0.4 0.9"
1717

1818
limit_hint [color = 7; fillcolor = 5; penwidth = 2;] # color = "0.52 0.7 0.7"; fillcolor = "0.52 0.4 0.9"
19-
limit_hint [shape = "note"; fontsize = 10]
19+
limit_hint [shape = "note"; fontsize = 10;]
2020
limit_hint [label = "limited\nnode count";]
2121
limit_hint -> central [dir = "back"; arrowtail = "inv"]; // Signals that the number of edges might have been limited
2222

domains/anomaly-detection/graphs/TopHub.cypher

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,18 @@
1111
AND central.anomalyHubRank = toInteger($projection_node_rank)
1212
WITH maxWeight
1313
,central
14-
,"Top Rank #" + $projection_node_rank + " " + $projection_language + " " + $projection_node_label + " Hub: " AS graphLabel
14+
,"Top Rank #" + $projection_node_rank + " " + $projection_language + " " + $projection_node_label + " Hub\\n" AS graphLabel
1515
,coalesce(central.fqn, central.globalFqn, central.fileName, central.signature, central.name) AS targetName
1616
,[] AS graphVizOutput
17-
WITH *, replace(replace(targetName, '.', '.\\n'), '/', '/\\n') AS targetNameSplit
18-
WITH *, targetNameSplit + "\\n(hub #" + central.anomalyHubRank + ")" AS centralNodeLabel
1917
WITH *, graphVizOutput + ["graph [label=\"" + graphLabel + targetName + "\\n\\n\"];"] AS graphVizOutput
18+
WITH *, "🎡 hub #" + central.anomalyHubRank + "\\n" + central.name AS centralNodeLabel
2019
WITH *, graphVizOutput + ["central [label=\"" + centralNodeLabel + "\"];"] AS graphVizOutput
2120
// Step 3: Query direct incoming dependencies to the central node
2221
MATCH (source)-[dependency:DEPENDS_ON]->(central)
2322
WHERE $projection_node_label IN labels(source)
2423
AND source.outgoingDependencies > 0
2524
ORDER BY dependency.weight DESC, source.name ASC
26-
LIMIT 70
25+
LIMIT 60
2726
WITH *, coalesce(dependency.weight25PercentInterfaces, dependency.weight, 1) AS weight
2827
WITH *, round((toFloat(weight) / toFloat(maxWeight) * 2.5) + 0.4, 1.0) AS penWidth
2928
WITH *, "label=" + weight + "; weight=" + weight + "; penwidth=" + penWidth AS edgeAttributes

0 commit comments

Comments
 (0)