Skip to content

Commit fd62b7c

Browse files
committed
Added in borders
1 parent efa3996 commit fd62b7c

File tree

5 files changed

+252
-56
lines changed

5 files changed

+252
-56
lines changed

src/views/cytoscape/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ export class CytoscapeGraph {
151151
<link href="${codiconsUri}" rel="stylesheet" />
152152
<script src="${cytoscapeUri}"></script>
153153
<script src="${cytoscapeHtmlLabelUri}"></script>
154-
<script src="${explainUri}" defer></script>
154+
<script type="module" src="${explainUri}" defer></script>
155155
<script>
156156
window.data = ${data};
157157
window.iconMap = ${iconMap}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import {
2+
getNodeTopLeftAbsolute,
3+
getTopLeftForBorder,
4+
getBorderWidthAndHeight,
5+
} from "./graphUtils.js";
6+
7+
export function deleteAllBorders() {
8+
document.querySelectorAll(".border").forEach((el) => el.remove());
9+
}
10+
11+
// @ts-ignore
12+
const iconMap = window.iconMap;
13+
14+
const getCodiconClass = (label) => {
15+
const className = iconMap[label];
16+
return className !== undefined ? `codicon-${className}` : "";
17+
};
18+
19+
function drawNodeIcon(fontSize, color, label) {
20+
const icon = document.createElement("i");
21+
const codiconClass = getCodiconClass(label);
22+
icon.className = `codicon ${codiconClass}`;
23+
Object.assign(icon.style, {
24+
fontSize: `${fontSize}px`,
25+
color,
26+
height: "fit-content",
27+
});
28+
return icon;
29+
}
30+
31+
export function drawBorderAndIconForEachExplainNode(cy) {
32+
const paddingX = 30;
33+
const paddingY = 10;
34+
const iconSize = 50;
35+
const iconGap = 20;
36+
const borderRadius = 10;
37+
const iconColor = "#007acc";
38+
39+
cy.nodes().forEach((node) => {
40+
const nodeW = node.renderedWidth();
41+
const nodeH = node.renderedHeight();
42+
const nodeTopLeft = getNodeTopLeftAbsolute(node, cy);
43+
44+
const topLeft = getTopLeftForBorder(
45+
nodeTopLeft.x,
46+
nodeTopLeft.y,
47+
paddingX,
48+
paddingY,
49+
iconSize,
50+
iconGap
51+
);
52+
const dimensions = getBorderWidthAndHeight(
53+
paddingX,
54+
nodeW,
55+
paddingY,
56+
nodeH,
57+
iconSize,
58+
iconGap
59+
);
60+
61+
const border = document.createElement("div");
62+
border.className = "border";
63+
64+
Object.assign(border.style, {
65+
width: `${dimensions.width}px`,
66+
height: `${dimensions.height}px`,
67+
position: "absolute",
68+
top: `${topLeft.y}px`,
69+
left: `${topLeft.x}px`,
70+
display: "flex",
71+
justifyContent: "center",
72+
paddingTop: `${paddingY}px`,
73+
borderRadius: `${borderRadius}px`,
74+
});
75+
76+
border.appendChild(drawNodeIcon(iconSize, iconColor, node.data().label));
77+
document.body.appendChild(border);
78+
});
79+
}

src/views/cytoscape/media/explain.css

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
.codicon {
1+
/* .codicon {
22
font-size: 30px !important;
33
color: #007acc;
44
margin-bottom: 8px;
5-
/* margin-right: 8px; */
6-
}
5+
} */
76

87
.diagram-container {
98
position: absolute;
@@ -31,4 +30,9 @@
3130
border: 1px solid #aaa;
3231
padding: 4px 8px;
3332
color: black;
33+
}
34+
35+
.border {
36+
border: 3px solid #007acc;
37+
pointer-events: none;
3438
}

src/views/cytoscape/media/explain.js

Lines changed: 94 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
// Initialize Cytoscape
2-
3-
// @ts-ignore
4-
const iconMap = window.iconMap;
2+
// main.js
3+
import { getTooltipPosition } from "./graphUtils.js";
4+
import {
5+
deleteAllBorders,
6+
drawBorderAndIconForEachExplainNode,
7+
} from "./borderDraw.js";
8+
// // @ts-ignore
9+
// const iconMap = window.iconMap;
510
// @ts-ignore
611
const tooltips = window.tooltips;
712
// @ts-ignore
@@ -56,67 +61,95 @@ cy.on("tap", "node", function (evt) {
5661
});
5762
});
5863

59-
const getCodiconClass = (label) => {
60-
const className = iconMap[label];
61-
return className !== undefined ? `codicon-${className}` : "";
62-
};
63-
64-
cy.nodeHtmlLabel([
65-
{
66-
query: ".l1",
67-
valign: "top",
68-
halign: "center",
69-
valignBox: "top",
70-
halignBox: "center",
71-
tpl: function (data) {
72-
const className = getCodiconClass(data.label);
73-
return `<div><div class="icon"><i class="codicon ${className}"></i></div>`;
74-
},
75-
},
76-
]);
77-
64+
// const getCodiconClass = (label) => {
65+
// const className = iconMap[label];
66+
// return className !== undefined ? `codicon-${className}` : "";
67+
// };
68+
69+
// cy.nodeHtmlLabel([
70+
// {
71+
// query: ".l1",
72+
// valign: "top",
73+
// halign: "center",
74+
// valignBox: "top",
75+
// halignBox: "center",
76+
// tpl: function (data) {
77+
// const className = getCodiconClass(data.label);
78+
// return `<div><div class="icon"><i class="codicon ${className}"></i></div>`;
79+
// },
80+
// },
81+
// ]);
82+
83+
// cy.nodes().on("mouseover", (event) => {
84+
// const node = event.target;
85+
// const id = node.id();
86+
// const tooltip = tooltips[id];
87+
// const hoverDiv = document.createElement("pre");
88+
// hoverDiv.innerText = tooltip;
89+
// hoverDiv.className = "hover-box";
90+
// document.body.appendChild(hoverDiv);
91+
92+
// function updatePosition() {
93+
// const { x, y } = node.renderedPosition(); // center of node
94+
// const containerRect = cy.container().getBoundingClientRect(); // Cytoscape canvas
95+
// const boxRect = hoverDiv.getBoundingClientRect(); // Tooltip box
96+
// const boxWidth = boxRect.width;
97+
// const boxHeight = boxRect.height;
98+
99+
// const nodeTopY = y - node.renderedOuterHeight() / 2;
100+
// const offset = 20;
101+
102+
// let top = nodeTopY + containerRect.top - boxHeight - offset;
103+
// let left = x + containerRect.left - boxWidth / 2;
104+
105+
// // Constrain to visible area
106+
// const viewportWidth = window.innerWidth;
107+
// const viewportHeight = window.innerHeight;
108+
109+
// // Keep inside horizontal bounds
110+
// if (left < 4) left = 4;
111+
// if (left + boxWidth > viewportWidth - 4) {
112+
// left = viewportWidth - boxWidth - 4;
113+
// }
114+
115+
// // If tooltip would be cut off vertically, show it *below* the node instead
116+
// if (top < 4) {
117+
// top = y + containerRect.top + node.renderedOuterHeight() / 2;
118+
// }
119+
120+
// hoverDiv.style.left = `${left}px`;
121+
// hoverDiv.style.top = `${top}px`;
122+
// }
123+
124+
// updatePosition(); // initial position
125+
// cy.on("pan zoom resize", updatePosition);
126+
// node.on("position", updatePosition);
127+
128+
// node.once("mouseout", () => {
129+
// hoverDiv.remove();
130+
// cy.off("pan zoom resize", updatePosition);
131+
// node.off("position", updatePosition);
132+
// });
133+
// });
134+
135+
// === Tooltip Hover Handler ===
78136
cy.nodes().on("mouseover", (event) => {
79137
const node = event.target;
138+
139+
const hoverDiv = document.createElement("pre");
80140
const id = node.id();
81141
const tooltip = tooltips[id];
82-
const hoverDiv = document.createElement("pre");
83-
hoverDiv.innerText = tooltip;
142+
hoverDiv.innerText = tooltip
84143
hoverDiv.className = "hover-box";
85144
document.body.appendChild(hoverDiv);
86145

87146
function updatePosition() {
88-
const { x, y } = node.renderedPosition(); // center of node
89-
const containerRect = cy.container().getBoundingClientRect(); // Cytoscape canvas
90-
const boxRect = hoverDiv.getBoundingClientRect(); // Tooltip box
91-
const boxWidth = boxRect.width;
92-
const boxHeight = boxRect.height;
93-
94-
const nodeTopY = y - node.renderedOuterHeight() / 2;
95-
const offset = 20;
96-
97-
let top = nodeTopY + containerRect.top - boxHeight - offset;
98-
let left = x + containerRect.left - boxWidth / 2;
99-
100-
// Constrain to visible area
101-
const viewportWidth = window.innerWidth;
102-
const viewportHeight = window.innerHeight;
103-
104-
// Keep inside horizontal bounds
105-
if (left < 4) left = 4;
106-
if (left + boxWidth > viewportWidth - 4) {
107-
left = viewportWidth - boxWidth - 4;
108-
}
109-
110-
// If tooltip would be cut off vertically, show it *below* the node instead
111-
if (top < 4) {
112-
top = y + containerRect.top + node.renderedOuterHeight() / 2;
113-
}
114-
147+
const { left, top } = getTooltipPosition(node, cy.container(), hoverDiv);
115148
hoverDiv.style.left = `${left}px`;
116149
hoverDiv.style.top = `${top}px`;
117150
}
118151

119-
updatePosition(); // initial position
152+
updatePosition();
120153
cy.on("pan zoom resize", updatePosition);
121154
node.on("position", updatePosition);
122155

@@ -126,3 +159,12 @@ cy.nodes().on("mouseover", (event) => {
126159
node.off("position", updatePosition);
127160
});
128161
});
162+
163+
// === Border sync on resize/pan/zoom ===
164+
function redrawBorders() {
165+
deleteAllBorders();
166+
drawBorderAndIconForEachExplainNode(cy);
167+
}
168+
169+
cy.on("pan zoom resize", redrawBorders);
170+
drawBorderAndIconForEachExplainNode(cy);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
export const MIN_DISTANCE_TO_VIEWPORT = 4;
2+
export const TOOLTIP_OFFSET = 20;
3+
4+
// === Node Position Utilities ===
5+
6+
export function getNodeTopLeftAbsolute(node, cy) {
7+
const containerRect = cy.container().getBoundingClientRect();
8+
const pos = node.position();
9+
const zoom = cy.zoom();
10+
const pan = cy.pan();
11+
12+
const renderedX = pos.x * zoom + pan.x;
13+
const renderedY = pos.y * zoom + pan.y;
14+
15+
return {
16+
x: containerRect.left + renderedX - node.renderedWidth() / 2,
17+
y: containerRect.top + renderedY - node.renderedHeight() / 2,
18+
};
19+
}
20+
21+
// === Border Geometry Utilities ===
22+
23+
export function getTopLeftForBorder(x, y, padX, padY, iconH, iconGap) {
24+
return {
25+
x: x - padX - 2, // slight adjustment
26+
y: y - padY - iconH - iconGap,
27+
};
28+
}
29+
30+
export function getBorderWidthAndHeight(
31+
padX,
32+
nodeW,
33+
padY,
34+
nodeH,
35+
iconH,
36+
iconGap
37+
) {
38+
return {
39+
width: padX * 2 + nodeW,
40+
height: padY * 2 + iconH + iconGap + nodeH,
41+
};
42+
}
43+
44+
// === Tooltip Position Utility ===
45+
46+
export function getTooltipPosition(node, container, tooltipBox) {
47+
const { x, y } = node.renderedPosition();
48+
const containerRect = container.getBoundingClientRect();
49+
const boxRect = tooltipBox.getBoundingClientRect();
50+
51+
let left = x + containerRect.left - boxRect.width / 2;
52+
let top =
53+
y -
54+
node.renderedOuterHeight() / 2 +
55+
containerRect.top -
56+
boxRect.height -
57+
TOOLTIP_OFFSET;
58+
59+
// Prevent overflow
60+
if (left < MIN_DISTANCE_TO_VIEWPORT) left = MIN_DISTANCE_TO_VIEWPORT;
61+
if (left + boxRect.width > window.innerWidth - MIN_DISTANCE_TO_VIEWPORT) {
62+
left = window.innerWidth - boxRect.width - MIN_DISTANCE_TO_VIEWPORT;
63+
}
64+
65+
if (top < MIN_DISTANCE_TO_VIEWPORT) {
66+
top =
67+
y + containerRect.top + node.renderedOuterHeight() / 2 + TOOLTIP_OFFSET;
68+
}
69+
70+
return { left, top };
71+
}

0 commit comments

Comments
 (0)