Skip to content

Commit be13bf6

Browse files
committed
Fix border rendering issues
1 parent 2bae800 commit be13bf6

File tree

4 files changed

+250
-91
lines changed

4 files changed

+250
-91
lines changed

media/explain/borderAndIconDraw.js

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,35 +28,65 @@ function drawNodeIcon(fontSize, color, label) {
2828
return icon;
2929
}
3030

31-
export function drawBorderAndIconForEachExplainNode(cy) {
31+
function isEnoughRoomForBorders(cy, paddingX, paddingY, iconSize, iconGap, windowPadding){
32+
const windowWidth = window.innerWidth
33+
const windowHeight = window.innerHeight;
34+
let widthNeededForAllNodes = 0
35+
let maxHeightNeededForBorder = 0
36+
cy.nodes().forEach(node => {
37+
const nodeW = node.renderedWidth();
38+
const nodeH = node.renderedHeight();
39+
const dimensions = getBorderWidthAndHeight(paddingX, nodeW, paddingY, nodeH, iconSize, iconGap);
40+
widthNeededForAllNodes += dimensions.width
41+
maxHeightNeededForBorder = Math.max(maxHeightNeededForBorder, dimensions.height)
42+
})
43+
return widthNeededForAllNodes < windowWidth - windowPadding * 2 && maxHeightNeededForBorder < windowHeight - windowPadding * 2
44+
}
45+
46+
function isEnoughAreaForBorders(cy, paddingX, paddingY, iconSize, iconGap, windowPadding) {
47+
const windowWidth = window.innerWidth;
48+
const windowHeight = window.innerHeight;
49+
50+
let areaNeededForAllNodes = 0;
51+
let maxHeightNeededForBorder = 0;
52+
let maxWidthNeededForBorder = 0;
53+
54+
cy.nodes().forEach(node => {
55+
const nodeW = node.renderedWidth();
56+
const nodeH = node.renderedHeight();
57+
const dimensions = getBorderWidthAndHeight(paddingX, nodeW, paddingY, nodeH, iconSize, iconGap);
58+
areaNeededForAllNodes += dimensions.width * dimensions.height;
59+
maxHeightNeededForBorder = Math.max(maxHeightNeededForBorder, dimensions.height);
60+
maxWidthNeededForBorder = Math.max(maxWidthNeededForBorder, dimensions.width);
61+
});
62+
63+
const usableWidth = windowWidth - 2 * windowPadding;
64+
const usableHeight = windowHeight - 2 * windowPadding;
65+
66+
const maxCols = Math.floor(usableWidth / maxWidthNeededForBorder);
67+
const maxRows = Math.floor(usableHeight / maxHeightNeededForBorder);
68+
69+
const maxNodesThatCanFit = maxCols * maxRows;
70+
71+
return cy.nodes().length <= maxNodesThatCanFit;
72+
}
73+
74+
export function drawBorderAndIconForEachExplainNode(cy, windowPadding, isVisible) {
3275
const paddingX = 30;
3376
const paddingY = 10;
3477
const iconSize = 50;
3578
const iconGap = 20;
3679
const borderRadius = 10;
3780
const iconColor = "#007acc";
81+
const shouldDrawBorders = isEnoughAreaForBorders(cy, paddingX, paddingY, iconSize, iconGap, windowPadding)
3882

39-
cy.nodes().forEach((node) => {
83+
cy.nodes().forEach(node => {
4084
const nodeW = node.renderedWidth();
4185
const nodeH = node.renderedHeight();
4286
const nodeTopLeft = getNodeTopLeftAbsolute(node, cy);
4387

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-
);
88+
const topLeft = getTopLeftForBorder(nodeTopLeft.x, nodeTopLeft.y, paddingX, paddingY, iconSize, iconGap);
89+
const dimensions = getBorderWidthAndHeight(paddingX, nodeW, paddingY, nodeH, iconSize, iconGap);
6090

6191
const border = document.createElement("div");
6292
border.className = "border";
@@ -70,8 +100,11 @@ export function drawBorderAndIconForEachExplainNode(cy) {
70100
display: "flex",
71101
justifyContent: "center",
72102
paddingTop: `${paddingY}px`,
73-
borderRadius: `${borderRadius}px`,
103+
borderRadius: `${borderRadius}px`
74104
});
105+
if (!shouldDrawBorders || !isVisible){
106+
border.style.borderColor = "transparent"
107+
}
75108

76109
border.appendChild(drawNodeIcon(iconSize, iconColor, node.data().label));
77110
document.body.appendChild(border);

media/explain/explain.css

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,21 @@
3030
.border {
3131
border: 3px solid #007acc;
3232
pointer-events: none;
33-
}
33+
}
34+
35+
.warning-div {
36+
position: fixed;
37+
width: 100%;
38+
top: 50%;
39+
left: 50%;
40+
transform: translate(-50%, -50%);
41+
z-index: 9999;
42+
padding: 2rem;
43+
text-align: center;
44+
}
45+
46+
.warning-div-title {
47+
margin: 0;
48+
font-size: 1.5rem;
49+
color: #007acc;
50+
}

media/explain/explain.js

Lines changed: 136 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,157 @@
11
// @ts-nocheck
2-
import { getTooltipPosition } from "./graphUtils.js";
2+
import {
3+
getTooltipPosition,
4+
showResizeWarning,
5+
isWindowTooSmall,
6+
isEnoughAreaWithoutBorders,
7+
} from "./graphUtils.js";
38
import {
49
deleteAllBorders,
510
drawBorderAndIconForEachExplainNode,
611
} from "./borderAndIconDraw.js";
712

813
const tooltips = window.tooltips;
914
const vscode = window.acquireVsCodeApi();
15+
const GRAPH_PADDING = 50;
1016

11-
// Initialize Cytoscape
12-
const cy = cytoscape({
13-
container: document.getElementById("diagramContainer"),
14-
elements: window.data,
15-
style: [
16-
{
17-
selector: "node",
18-
style: {
19-
padding: "5px",
20-
height: "15px",
21-
width: "label",
22-
"background-color": "var(--vscode-list-activeSelectionBackground)",
23-
color: "var(--vscode-list-activeSelectionForeground)",
24-
label: "data(label)",
25-
"text-valign": "center",
26-
"font-size": "14px",
17+
if (isWindowTooSmall(GRAPH_PADDING)) {
18+
showResizeWarning();
19+
} else {
20+
// Initialize Cytoscape
21+
const cy = cytoscape({
22+
container: document.getElementById("diagramContainer"),
23+
elements: window.data,
24+
zoomingEnabled: false,
25+
userZoomingEnabled: false,
26+
style: [
27+
{
28+
selector: "node",
29+
// style: {
30+
// padding: "5px",
31+
// height: "15px",
32+
// width: "label",
33+
// "background-color": "var(--vscode-list-activeSelectionBackground)",
34+
// color: "var(--vscode-list-activeSelectionForeground)",
35+
// label: "data(label)",
36+
// "text-valign": "center",
37+
// "font-size": "14px",
38+
// },
39+
style: {
40+
padding: "5px",
41+
width: "150px", // fixed width
42+
shape: "roundrectangle",
43+
"background-color": "var(--vscode-list-activeSelectionBackground)",
44+
color: "var(--vscode-list-activeSelectionForeground)",
45+
label: "data(label)",
46+
"text-wrap": "wrap", // enable wrapping
47+
"text-max-width": "150px", // must match or be less than width
48+
"text-valign": "center",
49+
"text-halign": "center",
50+
"font-size": "14px",
51+
"line-height": "1.2",
52+
// "text-overflow-wrap": "anywhere",
53+
},
2754
},
28-
},
29-
{
30-
selector: "edge",
31-
style: {
32-
width: 2,
33-
"line-color": "#5c96bc",
34-
"target-arrow-color": "#5c96bc",
35-
"target-arrow-shape": "triangle",
36-
"curve-style": "bezier",
55+
{
56+
selector: "edge",
57+
style: {
58+
width: 2,
59+
"line-color": "#5c96bc",
60+
"target-arrow-color": "#5c96bc",
61+
"target-arrow-shape": "triangle",
62+
"curve-style": "bezier",
63+
},
3764
},
65+
],
66+
67+
layout: {
68+
name: "grid",
69+
fit: true, // whether to fit the viewport to the graph
70+
padding: GRAPH_PADDING, // padding used on fit
71+
avoidOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space
72+
avoidOverlapPadding: 10, // extra spacing around nodes when avoidOverlap: true
73+
nodeDimensionsIncludeLabels: false, // Excludes the label when calculating node bounding boxes for the layout algorithm
74+
spacingFactor: undefined, // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up
75+
condense: false, // uses all available space on false, uses minimal space on true
76+
rows: undefined, // force num of rows in the grid
77+
cols: undefined, // force num of columns in the grid
78+
position: function (node) {}, // returns { row, col } for element
79+
sort: undefined, // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') }
80+
animate: false, // whether to transition the node positions
81+
animationDuration: 500, // duration of animation in ms if enabled
82+
animationEasing: undefined, // easing of animation if enabled
83+
animateFilter: function (node, i) {
84+
return true;
85+
}, // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts
86+
ready: undefined, // callback on layoutready
87+
stop: undefined, // callback on layoutstop
88+
transform: function (node, position) {
89+
return position;
90+
}, // transform a given node position. Useful for changing flow direction in discrete layouts
3891
},
39-
],
92+
});
4093

41-
layout: {
42-
name: "grid",
43-
padding: 50, // Padding around the graph
44-
spacingFactor: 0.9, // Spacing between nodes
45-
},
46-
});
94+
if (!isEnoughAreaWithoutBorders(cy, GRAPH_PADDING)) {
95+
cy.destroy();
96+
showResizeWarning();
97+
} else {
98+
// When clicked, we display the details for the node in the bottom tree view
99+
cy.on("tap", "node", function (evt) {
100+
const id = evt.target.id();
101+
vscode.postMessage({
102+
command: "selected",
103+
nodeId: id,
104+
});
105+
});
47106

48-
// When clicked, we display the details for the node in the bottom tree view
49-
cy.on("tap", "node", function (evt) {
50-
const id = evt.target.id();
51-
vscode.postMessage({
52-
command: "selected",
53-
nodeId: id,
54-
});
55-
});
107+
// === Tooltip Hover Handler ===
108+
cy.nodes().on("mouseover", (event) => {
109+
const node = event.target;
56110

57-
// === Tooltip Hover Handler ===
58-
cy.nodes().on("mouseover", (event) => {
59-
const node = event.target;
111+
const hoverDiv = document.createElement("pre");
112+
const id = node.id();
113+
const tooltip = tooltips[id];
114+
hoverDiv.innerText = tooltip;
115+
hoverDiv.className = "hover-box";
116+
document.body.appendChild(hoverDiv);
60117

61-
const hoverDiv = document.createElement("pre");
62-
const id = node.id();
63-
const tooltip = tooltips[id];
64-
hoverDiv.innerText = tooltip
65-
hoverDiv.className = "hover-box";
66-
document.body.appendChild(hoverDiv);
118+
function updatePosition() {
119+
const { left, top } = getTooltipPosition(
120+
node,
121+
cy.container(),
122+
hoverDiv
123+
);
124+
hoverDiv.style.left = `${left}px`;
125+
hoverDiv.style.top = `${top}px`;
126+
}
67127

68-
function updatePosition() {
69-
const { left, top } = getTooltipPosition(node, cy.container(), hoverDiv);
70-
hoverDiv.style.left = `${left}px`;
71-
hoverDiv.style.top = `${top}px`;
72-
}
128+
updatePosition();
129+
cy.on("pan zoom resize", updatePosition);
130+
node.on("position", updatePosition);
73131

74-
updatePosition();
75-
cy.on("pan zoom resize", updatePosition);
76-
node.on("position", updatePosition);
132+
node.once("mouseout", () => {
133+
hoverDiv.remove();
134+
cy.off("pan zoom resize", updatePosition);
135+
node.off("position", updatePosition);
136+
});
137+
});
77138

78-
node.once("mouseout", () => {
79-
hoverDiv.remove();
80-
cy.off("pan zoom resize", updatePosition);
81-
node.off("position", updatePosition);
82-
});
83-
});
139+
function isVisibleBorders() {
140+
console.log(window.location.href);
141+
const border = document.querySelector(".border");
142+
if (border !== null) {
143+
return border.style.borderColor !== "transparent";
144+
}
145+
return false;
146+
}
84147

85-
// === Border sync on resize/pan/zoom ===
86-
function redrawBorders() {
87-
deleteAllBorders();
88-
drawBorderAndIconForEachExplainNode(cy);
89-
}
148+
function redrawBorders() {
149+
const showBorder = isVisibleBorders();
150+
deleteAllBorders();
151+
drawBorderAndIconForEachExplainNode(cy, GRAPH_PADDING, showBorder);
152+
}
90153

91-
cy.on("pan zoom resize", redrawBorders);
92-
drawBorderAndIconForEachExplainNode(cy);
154+
cy.on("pan", redrawBorders);
155+
drawBorderAndIconForEachExplainNode(cy, GRAPH_PADDING, true);
156+
}
157+
}

media/explain/graphUtils.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,47 @@ export function getTooltipPosition(node, container, tooltipBox) {
6868

6969
return { left, top };
7070
}
71+
72+
export function showResizeWarning() {
73+
const warningDiv = document.createElement("div");
74+
warningDiv.className = "warning-div"
75+
76+
const h1 = document.createElement("h1");
77+
h1.className = "warning-div-title"
78+
h1.textContent = "Window is too small. Make your window larger and run again.";
79+
80+
warningDiv.appendChild(h1);
81+
document.body.appendChild(warningDiv);
82+
}
83+
84+
export function isEnoughAreaWithoutBorders(cy, windowPadding) {
85+
const windowWidth = window.innerWidth;
86+
const windowHeight = window.innerHeight;
87+
88+
let areaNeededForAllNodes = 0;
89+
let maxHeightNeededForNode = 60;
90+
let maxWidthNeededForNode = 100;
91+
92+
cy.nodes().forEach(node => {
93+
const nodeW = node.renderedWidth();
94+
const nodeH = node.renderedHeight();
95+
areaNeededForAllNodes += nodeW * nodeH
96+
maxHeightNeededForNode = Math.max(maxHeightNeededForNode, nodeH);
97+
maxWidthNeededForNode = Math.max(maxWidthNeededForNode, nodeW);
98+
});
99+
100+
const usableWidth = windowWidth - 2 * windowPadding;
101+
const usableHeight = windowHeight - 2 * windowPadding;
102+
103+
const maxCols = Math.floor(usableWidth / maxWidthNeededForNode);
104+
const maxRows = Math.floor(usableHeight / maxHeightNeededForNode);
105+
106+
const maxNodesThatCanFit = maxCols * maxRows;
107+
108+
return cy.nodes().length <= maxNodesThatCanFit;
109+
}
110+
111+
export function isWindowTooSmall(windowPadding){
112+
const extraRoom = 50
113+
return windowPadding * 2 >= window.innerWidth || windowPadding * 2 + extraRoom >= window.innerHeight
114+
}

0 commit comments

Comments
 (0)