Skip to content

Commit f8c0bbb

Browse files
authored
Factor out functions into modules (#15)
1 parent 0f40fd3 commit f8c0bbb

File tree

3 files changed

+213
-211
lines changed

3 files changed

+213
-211
lines changed

js/helpers.js

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import { Logger } from "./logger";
2+
3+
export function getLegendElements(graphvizInstance, $) {
4+
const legendNodes = [];
5+
const legendEdges = [];
6+
7+
graphvizInstance.nodes().each(function () {
8+
const $node = $(this);
9+
if ($node.attr("data-name").startsWith("legend_")) {
10+
legendNodes.push($node[0]);
11+
}
12+
});
13+
14+
graphvizInstance.edges().each(function () {
15+
const $edge = $(this);
16+
if ($edge.attr("data-name").startsWith("legend_")) {
17+
legendEdges.push($edge[0]);
18+
}
19+
});
20+
return { legendNodes: $(legendNodes), legendEdges: $(legendEdges) };
21+
}
22+
23+
function _findEdges(text, searchFunction, graphvizInstance, $) {
24+
const $set = $();
25+
graphvizInstance.edges().each((index, edge) => {
26+
if (edge.textContent && searchFunction(text, edge.textContent)) {
27+
$set.push(edge);
28+
}
29+
});
30+
return $set;
31+
}
32+
33+
function _findNodes(text, searchFunction, nodeName, nodeLabel, graphvizInstance, $) {
34+
const $set = $();
35+
const nodes = graphvizInstance.nodesByName();
36+
37+
for (const [nodeID, node] of Object.entries(nodes)) {
38+
if (
39+
(nodeName && searchFunction(text, nodeID)) ||
40+
(nodeLabel && node.textContent && searchFunction(text, node.textContent))
41+
) {
42+
$set.push(node);
43+
}
44+
}
45+
return $set;
46+
}
47+
48+
export function search(text, searchObject, graphvizInstance, $) {
49+
let searchFunction;
50+
51+
switch (searchObject.type) {
52+
case "exact":
53+
searchFunction = (search, str) => str.trim() === search.trim();
54+
break;
55+
case "included":
56+
searchFunction = (search, str) => {
57+
const searchStr = searchObject.case === "insensitive" ? search.toLowerCase() : search;
58+
const valStr = searchObject.case === "insensitive" ? str.toLowerCase() : str;
59+
return valStr.indexOf(searchStr) !== -1;
60+
};
61+
break;
62+
case "regex":
63+
searchFunction = (search, str) => {
64+
const regex = new RegExp(search, searchObject.case === "insensitive" ? "i" : undefined);
65+
return !!str.trim().match(regex);
66+
};
67+
break;
68+
}
69+
70+
let $edges = $();
71+
if (searchObject.edgeLabel) {
72+
$edges = _findEdges(text, searchFunction, graphvizInstance, $);
73+
}
74+
75+
let $nodes = $();
76+
if (searchObject.nodeLabel || searchObject.nodeName) {
77+
$nodes = _findNodes(
78+
text,
79+
searchFunction,
80+
searchObject.nodeName,
81+
searchObject.nodeLabel,
82+
graphvizInstance,
83+
$
84+
);
85+
}
86+
return { nodes: $nodes, edges: $edges };
87+
}
88+
89+
function _getConnectedNodes(nodeSet, mode, graphvizInstance) {
90+
let resultSet = $().add(nodeSet);
91+
const nodes = graphvizInstance.nodesByName();
92+
93+
nodeSet.each((i, el) => {
94+
if (mode === "single") {
95+
resultSet = resultSet.add(el);
96+
} else if (el.className.baseVal === "edge") {
97+
const [startNode, endNode] = $(el).data("name").split("->");
98+
if ((mode === "bidirectional" || mode === "upstream") && startNode) {
99+
resultSet = resultSet
100+
.add(nodes[startNode])
101+
.add(graphvizInstance.linkedTo(nodes[startNode], true));
102+
}
103+
if ((mode === "bidirectional" || mode === "downstream") && endNode) {
104+
resultSet = resultSet
105+
.add(nodes[endNode])
106+
.add(graphvizInstance.linkedFrom(nodes[endNode], true));
107+
}
108+
} else {
109+
if (mode === "bidirectional" || mode === "upstream") {
110+
resultSet = resultSet.add(graphvizInstance.linkedTo(el, true));
111+
}
112+
if (mode === "bidirectional" || mode === "downstream") {
113+
resultSet = resultSet.add(graphvizInstance.linkedFrom(el, true));
114+
}
115+
}
116+
});
117+
return resultSet;
118+
}
119+
120+
function _highlightSelection(graphvizInstance, currentSelection, $) {
121+
let highlightedNodes = $();
122+
let highlightedEdges = $();
123+
124+
currentSelection.forEach((selection) => {
125+
const nodes = _getConnectedNodes(selection.set, selection.direction, graphvizInstance);
126+
highlightedNodes = highlightedNodes.add(nodes);
127+
});
128+
129+
const { legendNodes, legendEdges } = getLegendElements(graphvizInstance, $);
130+
highlightedNodes = highlightedNodes.add(legendNodes);
131+
highlightedEdges = highlightedEdges.add(legendEdges);
132+
133+
graphvizInstance.highlight(highlightedNodes, highlightedEdges);
134+
graphvizInstance.bringToFront(highlightedNodes);
135+
graphvizInstance.bringToFront(highlightedEdges);
136+
}
137+
138+
export function handleGraphvizSvgEvents(graphvizInstance, $, currentSelection, getSelectedDirection) {
139+
// Add hover event listeners for edges
140+
Logger.debug("Initializing graph events");
141+
graphvizInstance.edges().each(function () {
142+
const $edge = $(this);
143+
144+
// Store the original stroke width, with a fallback to "1"
145+
const originalStroke = $edge.attr("stroke-width") || "1";
146+
$edge.data("original-stroke", originalStroke);
147+
148+
$edge.on("mouseenter", function () {
149+
// Highlight edge by making the stroke width thicker
150+
$(this).find("path").attr("stroke-width", "3");
151+
// Highlight edge label by making the text visible
152+
$(this).find("text").attr("fill", "black");
153+
});
154+
155+
$edge.on("mouseleave", function () {
156+
// Revert edge highlight by restoring the original stroke color
157+
const originalStroke = $(this).data("original-stroke");
158+
$(this).find("path").attr("stroke-width", originalStroke);
159+
// Revert edge label highlight by making the text transparent
160+
$(this).find("text").attr("fill", "transparent");
161+
});
162+
});
163+
Logger.debug("Edge event handlers attached");
164+
165+
// Add event listeners for nodes
166+
graphvizInstance.nodes().click(function (event) {
167+
const nodeSet = $().add(this);
168+
const selectionObject = {
169+
set: nodeSet,
170+
direction: getSelectedDirection(),
171+
};
172+
if (event.ctrlKey || event.metaKey || event.shiftKey) {
173+
currentSelection.push(selectionObject);
174+
} else {
175+
currentSelection.splice(0, currentSelection.length, selectionObject);
176+
}
177+
178+
_highlightSelection(graphvizInstance, currentSelection, $);
179+
});
180+
Logger.debug("Node click handlers attached");
181+
182+
// Add a keydown event listener for escape key to reset highlights
183+
$(document).keydown(function (event) {
184+
if (event.keyCode === 27) {
185+
// Escape key
186+
graphvizInstance.highlight();
187+
}
188+
});
189+
Logger.debug("Keyboard handlers attached");
190+
}

js/logger.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export const Logger = {
2+
DEBUG: false, // Can be controlled via environment or initialization
3+
4+
debug(...args) {
5+
if (this.DEBUG) {
6+
console.debug("🔍 [DEBUG]", ...args);
7+
}
8+
},
9+
10+
info(...args) {
11+
console.info("ℹ️ [INFO]", ...args);
12+
},
13+
14+
warn(...args) {
15+
console.warn("⚠️ [WARN]", ...args);
16+
},
17+
18+
error(...args) {
19+
console.error("❌ [ERROR]", ...args);
20+
},
21+
};

0 commit comments

Comments
 (0)