Skip to content

Commit 3f54f3f

Browse files
authored
Merge pull request #37 from anAcc22/dev
feat: support edge coloring (#36)
2 parents 3f3a436 + ab3b9a3 commit 3f54f3f

10 files changed

Lines changed: 441 additions & 202 deletions

File tree

src/App.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ function App() {
5959
localStorage.getItem("nodeBorderWidthHalf") !== null
6060
? Number.parseFloat(localStorage.getItem("nodeBorderWidthHalf")!)
6161
: 1,
62+
edgeBorderWidthHalf:
63+
localStorage.getItem("edgeBorderWidthHalf") !== null
64+
? Number.parseFloat(localStorage.getItem("edgeBorderWidthHalf")!)
65+
: 1,
6266
edgeLength:
6367
localStorage.getItem("edgeLength") !== null
6468
? Number.parseFloat(localStorage.getItem("edgeLength")!)
@@ -166,12 +170,31 @@ function App() {
166170
px-2 py-1 justify-between items-center hover:border-border-hover
167171
z-20 bg-block group h-9"
168172
>
173+
<svg
174+
xmlns="http://www.w3.org/2000/svg"
175+
fill="none"
176+
viewBox="0 0 24 24"
177+
strokeWidth="1.5"
178+
stroke="currentColor"
179+
className="size-5 mr-1"
180+
>
181+
<path
182+
strokeLinecap="round"
183+
strokeLinejoin="round"
184+
d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 0 0-2.456 2.456ZM16.894 20.567 16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394-1.183.394a2.25 2.25 0 0 0-1.423 1.423Z"
185+
/>
186+
</svg>
169187
{settings.language == "en" ? "Changelog" : "更新记录"}
170188
<div
171189
className="absolute border-2 text-sm px-2 py-1 border-border-hover
172-
rounded-lg bg-block left-0 top-8 w-100 invisible
190+
rounded-lg bg-block -left-2 top-8 w-100 invisible
173191
group-hover:visible max-h-28 no-scrollbar overflow-scroll"
174192
>
193+
<p>15 Aug 2025</p>
194+
<ul className="list-disc list-inside">
195+
<li>Support edge coloring</li>
196+
</ul>
197+
<hr className="border-dashed border-border" />
175198
<p>5 June 2025</p>
176199
<ul className="list-disc list-inside">
177200
<li>Improve annotation experience</li>

src/components/AppearanceSettings.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ export function AppearanceSettings({ settings, setSettings }: Props) {
104104
/>
105105

106106
<h4 className="font-semibold">
107-
{settings.language == "en" ? "Line Thickness" : "线条粗细"}
107+
{settings.language == "en"
108+
? "Line Thickness (Node)"
109+
: "线条粗细 (节点)"}
108110
</h4>
109111
<input
110112
type="range"
@@ -135,6 +137,40 @@ export function AppearanceSettings({ settings, setSettings }: Props) {
135137
}}
136138
/>
137139

140+
<h4 className="font-semibold">
141+
{settings.language == "en"
142+
? "Line Thickness (Edge)"
143+
: "线条粗细 (边)"}
144+
</h4>
145+
<input
146+
type="range"
147+
min={0}
148+
max={1.5}
149+
step={0.1}
150+
value={settings.edgeBorderWidthHalf - 1}
151+
className="range appearance-none outline-none bg-slider h-1 w-5/6
152+
self-center rounded-full cursor-ew-resize
153+
[&::-webkit-slider-thumb]:appearance-none
154+
[&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4
155+
[&::-webkit-slider-thumb]:border-none
156+
[&::-webkit-slider-thumb]:bg-slider-thumb
157+
[&::-webkit-slider-thumb]:rounded-full
158+
[&::-moz-range-thumb]:bg-slider-thumb [&::-moz-range-thumb]:w-4
159+
[&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:border-none
160+
[&::-moz-range-thumb]:rounded-full"
161+
onChange={(e) => {
162+
const newBorderWidthHalf = 1 + Number.parseFloat(e.target.value);
163+
setSettings({
164+
...settings,
165+
edgeBorderWidthHalf: newBorderWidthHalf,
166+
});
167+
localStorage.setItem(
168+
"edgeBorderWidthHalf",
169+
newBorderWidthHalf.toString(),
170+
);
171+
}}
172+
/>
173+
138174
<h4 className="font-semibold">
139175
{settings.language == "en" ? "Edge Length" : "边的长度"}
140176
</h4>

src/components/GraphCanvas.tsx

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ export function GraphCanvas({
347347
hover:border-border-hover hover:bg-bg-tab-hover rounded-md
348348
w-7 h-7 items-center justify-center active:bg-tab-active`
349349
}
350-
title={settings.language == "en" ? "Node" : "节点"}
350+
title={settings.language == "en" ? "Paint" : ""}
351351
onClick={() => {
352352
setSettings({
353353
...settings,
@@ -356,19 +356,31 @@ export function GraphCanvas({
356356
}}
357357
>
358358
<svg
359+
fill="#000000"
360+
version="1.1"
361+
id="Layer_1"
362+
xmlns="http://www.w3.org/2000/svg"
359363
width="800px"
360364
height="800px"
361-
viewBox="0 0 24 24"
362-
fill="none"
363-
xmlns="http://www.w3.org/2000/svg"
364-
className="stroke-text w-3/4 h-3/4 -rotate-45"
365+
viewBox="0 0 100 100"
366+
enable-background="new 0 0 100 100"
367+
className="fill-text"
365368
>
366-
<path
367-
d="M11.0605 2.93203C11.3983 2.68689 11.5672 2.56432 11.7518 2.51696C11.9148 2.47514 12.0858 2.47514 12.2488 2.51696C12.4334 2.56432 12.6023 2.68689 12.9401 2.93203L21.0586 8.82396C21.397 9.06956 21.5663 9.19235 21.6686 9.3535C21.7589 9.49579 21.8119 9.65862 21.8224 9.82684C21.8344 10.0174 21.7697 10.2162 21.6404 10.6138L18.5401 20.1449C18.4109 20.5421 18.3463 20.7407 18.2247 20.8876C18.1173 21.0173 17.979 21.1178 17.8224 21.1798C17.6451 21.25 17.4362 21.25 17.0186 21.25H6.98203C6.56437 21.25 6.35554 21.25 6.17822 21.1798C6.02164 21.1178 5.88325 21.0173 5.77589 20.8876C5.65429 20.7407 5.58969 20.5421 5.4605 20.1449L2.36021 10.6138C2.23086 10.2162 2.16619 10.0174 2.17817 9.82684C2.18874 9.65862 2.24166 9.49579 2.33202 9.3535C2.43434 9.19235 2.60355 9.06956 2.94196 8.82396L11.0605 2.93203Z"
368-
strokeWidth="2"
369-
strokeLinecap="round"
370-
strokeLinejoin="round"
371-
/>
369+
<g>
370+
<path
371+
d="M83.095,47.878c-0.001-0.001-0.003-0.002-0.004-0.003l-32.088-32.09l-0.001,0l0,0c-0.984-0.984-2.578-0.984-3.562,0
372+
l-5.26,5.26L30.934,9.799c-0.903-0.943-2.166-1.535-3.571-1.534c-2.743-0.001-4.966,2.231-4.964,4.986
373+
c0,1.426,0.603,2.703,1.558,3.612l11.203,11.205L7.883,55.344c0,0,0,0,0,0c-0.984,0.983-0.984,2.578,0,3.562l32.091,32.092
374+
c0.984,0.984,2.579,0.984,3.562,0l0.001-0.001L83.095,51.44C84.078,50.456,84.078,48.861,83.095,47.878z M63.391,57.106H20.233
375+
l29.003-29.004l21.579,21.58L63.391,57.106z"
376+
/>
377+
<path
378+
d="M91.073,73.735l-5.97-10.339c-0.031-0.058-0.061-0.117-0.098-0.171L84.99,63.2l-0.004,0.002
379+
c-0.302-0.418-0.788-0.69-1.351-0.69c-0.508,0-0.952,0.231-1.256,0.588l-0.016-0.009l-0.059,0.103
380+
c-0.086,0.116-0.162,0.239-0.217,0.375l-5.835,10.105c-1.144,1.535-1.829,3.432-1.829,5.493c0,5.09,4.124,9.217,9.216,9.217
381+
c5.093,0,9.217-4.127,9.217-9.217C92.856,77.133,92.189,75.26,91.073,73.735z"
382+
/>
383+
</g>
372384
</svg>
373385
</button>
374386
<button

src/components/GraphPalette.tsx

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,11 @@ export function GraphPalette({ settings, setSettings }: Props) {
216216
<div
217217
className={
218218
settings.markColor === 35
219-
? `h-4 w-4 hover:drop-shadow-sm bg-palette-raw rounded-full
220-
outline outline-2 outline-offset-2 transition ease-in-out
221-
hover:scale-150 duration-100 outline-palette-raw`
222-
: `h-4 w-4 hover:drop-shadow-sm bg-palette-raw rounded-full
223-
transition ease-in-out hover:scale-150 duration-100
219+
? `h-4 w-4 hover:drop-shadow-sm bg-palette-raw rounded-full hidden
220+
xl:block outline outline-2 outline-offset-2 transition
221+
ease-in-out hover:scale-150 duration-100 outline-palette-raw`
222+
: `h-4 w-4 hover:drop-shadow-sm bg-palette-raw rounded-full hidden
223+
xl:block transition ease-in-out hover:scale-150 duration-100
224224
hover:border-text`
225225
}
226226
onClick={() => setSettings({ ...settings, markColor: 35 })}
@@ -241,11 +241,11 @@ export function GraphPalette({ settings, setSettings }: Props) {
241241
className={
242242
settings.markColor === 39
243243
? `h-4 w-4 hover:drop-shadow-sm bg-palette-moon rounded-full
244-
outline outline-2 outline-offset-2 transition ease-in-out
245-
hover:scale-150 duration-100 outline-palette-moon`
244+
hidden xl:block outline outline-2 outline-offset-2 transition
245+
ease-in-out hover:scale-150 duration-100 outline-palette-moon`
246246
: `h-4 w-4 hover:drop-shadow-sm bg-palette-moon rounded-full
247-
transition ease-in-out hover:scale-150 duration-100
248-
hover:border-text`
247+
hidden xl:block transition ease-in-out hover:scale-150
248+
duration-100 hover:border-text`
249249
}
250250
onClick={() => setSettings({ ...settings, markColor: 39 })}
251251
></div>
@@ -471,11 +471,12 @@ export function GraphPalette({ settings, setSettings }: Props) {
471471
className={
472472
settings.markColor === 36
473473
? `h-4 w-4 hover:drop-shadow-sm bg-palette-raw-dull rounded-full
474-
outline outline-2 outline-offset-2 transition ease-in-out
475-
hover:scale-150 duration-100 outline-palette-raw-dull`
474+
hidden xl:block outline outline-2 outline-offset-2 transition
475+
ease-in-out hover:scale-150 duration-100
476+
outline-palette-raw-dull`
476477
: `h-4 w-4 hover:drop-shadow-sm bg-palette-raw-dull rounded-full
477-
transition ease-in-out hover:scale-150 duration-100
478-
hover:border-text`
478+
hidden xl:block transition ease-in-out hover:scale-150
479+
duration-100 hover:border-text`
479480
}
480481
onClick={() => setSettings({ ...settings, markColor: 36 })}
481482
></div>
@@ -496,11 +497,12 @@ export function GraphPalette({ settings, setSettings }: Props) {
496497
className={
497498
settings.markColor === 40
498499
? `h-4 w-4 hover:drop-shadow-sm bg-palette-moon-dull rounded-full
499-
outline outline-2 outline-offset-2 transition ease-in-out
500-
hover:scale-150 duration-100 outline-palette-moon-dull`
500+
hidden xl:block outline outline-2 outline-offset-2 transition
501+
ease-in-out hover:scale-150 duration-100
502+
outline-palette-moon-dull`
501503
: `h-4 w-4 hover:drop-shadow-sm bg-palette-moon-dull rounded-full
502-
transition ease-in-out hover:scale-150 duration-100
503-
hover:border-text`
504+
hidden xl:block transition ease-in-out hover:scale-150
505+
duration-100 hover:border-text`
504506
}
505507
onClick={() => setSettings({ ...settings, markColor: 40 })}
506508
></div>

src/components/animateGraph.ts

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,9 @@ const EDGE_LABEL_LIGHT = "hsl(30, 50%, 40%)";
109109
const NODE_LABEL_LIGHT = "hsl(30, 80%, 50%)";
110110
const NODE_LABEL_OUTLINE_LIGHT = "hsl(10, 2%, 70%)";
111111

112-
const STROKE_COLOR_DARK = "hsl(0, 0%, 90%)";
113-
const TEXT_COLOR_DARK = "hsl(0, 0%, 90%)";
114-
const EDGE_COLOR_DARK = "hsl(0, 0%, 90%)";
112+
const STROKE_COLOR_DARK = "hsl(0, 0%, 98%)";
113+
const TEXT_COLOR_DARK = "hsl(0, 0%, 98%)";
114+
const EDGE_COLOR_DARK = "hsl(0, 0%, 98%)";
115115
const EDGE_LABEL_DARK = "hsl(30, 70%, 60%)";
116116
const NODE_LABEL_DARK = "hsl(30, 100%, 50%)";
117117
const NODE_LABEL_OUTLINE_DARK = "hsl(10, 2%, 30%)";
@@ -151,9 +151,11 @@ const FILL_COLORS_DARK = [
151151
const FILL_COLORS_LENGTH = 10;
152152

153153
let prevMS = performance.now();
154+
let latestColorChangeMS = performance.now();
154155

155156
let nodeRadius = 16;
156157
let nodeBorderWidthHalf = 1;
158+
let edgeBorderWidthHalf = 1;
157159

158160
let nodeLabelColor = NODE_LABEL_LIGHT;
159161
let nodeLabelOutlineColor = NODE_LABEL_OUTLINE_LIGHT;
@@ -196,6 +198,7 @@ let settings: Settings = {
196198
nodeRadius: 15,
197199
fontSize: 15,
198200
nodeBorderWidthHalf: 15,
201+
edgeBorderWidthHalf: 15,
199202
edgeLength: 10,
200203
edgeLabelSeparation: 10,
201204
penThickness: 1,
@@ -228,8 +231,11 @@ let nodeLabels = new Map<string, string>();
228231
let labelOffset = 0;
229232

230233
let draggedNodes: string[] = [];
234+
let coloredEdge: string | undefined = undefined;
235+
let prevColoredEdgeMS = 0;
231236

232237
let edges: string[] = [];
238+
let edgeMap = new Map<string, number | undefined>();
233239
let edgeToPos = new Map<string, number>();
234240
let edgeLabels = new Map<string, string>();
235241

@@ -291,6 +297,7 @@ function updateNodes(graphNodes: string[]): void {
291297
function updateEdges(graphEdges: string[]): void {
292298
edges = graphEdges;
293299
edgeToPos.clear();
300+
294301
for (const e of edges) {
295302
const [u, v, rStr] = e.split(" ");
296303
const eBase = [u, v].join(" ");
@@ -301,6 +308,12 @@ function updateEdges(graphEdges: string[]): void {
301308
edgeToPos.set(eBase, Math.max(rNum, edgeToPos.get(eBase)!));
302309
}
303310
}
311+
312+
edgeMap.forEach((_, e) => {
313+
if (!edges.includes(e)) {
314+
edgeMap.delete(e);
315+
}
316+
});
304317
}
305318

306319
function updateVelocities() {
@@ -492,6 +505,7 @@ function buildSettings(): void {
492505

493506
nodeRadius = settings.nodeRadius;
494507
nodeBorderWidthHalf = settings.nodeBorderWidthHalf;
508+
edgeBorderWidthHalf = settings.edgeBorderWidthHalf;
495509
nodeDist = settings.edgeLength + 2 * nodeRadius;
496510

497511
labelOffset = settings.labelOffset;
@@ -797,9 +811,22 @@ function renderEdges(renderer: GraphRenderer) {
797811
renderer.setLineDash([2, 10]);
798812
}
799813

814+
if (!edgeMap.has(e)) edgeMap.set(e, undefined);
815+
816+
let finalColor = edgeColor;
817+
818+
if (edgeMap.get(e)! !== undefined) {
819+
const idx = edgeMap.get(e)!;
820+
const color = settings.darkMode
821+
? FILL_PALETTE_DARK[idx]
822+
: FILL_PALETTE_LIGHT[idx];
823+
824+
finalColor = color;
825+
}
826+
800827
renderer.strokeStyle = strokeColor;
801828

802-
let thickness = nodeBorderWidthHalf;
829+
let thickness = edgeBorderWidthHalf;
803830

804831
if (
805832
localStorage.getItem("isEdgeNumeric") === "true" &&
@@ -810,15 +837,38 @@ function renderEdges(renderer: GraphRenderer) {
810837
thickness *= 2;
811838
}
812839

840+
let isSelected = false;
841+
813842
if (
814843
settings.showBridges &&
815844
bridgeMap !== undefined &&
816845
edrMax === 0 &&
817846
bridgeMap.get(eBase)
818847
) {
819-
drawBridge(renderer, pt1, pt2, thickness, nodeRadius, edgeColor);
848+
isSelected = drawBridge(
849+
renderer,
850+
pt1,
851+
pt2,
852+
thickness,
853+
nodeRadius,
854+
finalColor,
855+
mousePos,
856+
);
820857
} else {
821-
drawLine(renderer, pt1, pt2, edr, thickness, edgeColor);
858+
isSelected = drawLine(
859+
renderer,
860+
pt1,
861+
pt2,
862+
edr,
863+
thickness,
864+
finalColor,
865+
mousePos,
866+
);
867+
}
868+
869+
if (isSelected) {
870+
coloredEdge = e;
871+
prevColoredEdgeMS = performance.now();
822872
}
823873

824874
renderer.setLineDash([]);
@@ -1184,14 +1234,24 @@ export function animateGraph(
11841234
canvas.addEventListener("pointerup", (event) => {
11851235
event.preventDefault();
11861236
const curMS = performance.now();
1187-
if (curMS - prevMS <= 200 && draggedNodes.length) {
1188-
const u = draggedNodes[0];
1189-
const sel = nodeMap.get(u)!.selected;
1190-
if (settings.markedNodes) nodeMap.get(u)!.selected = !sel;
1191-
if (settings.markColor === 2) {
1192-
nodeMap.get(u)!.markColor = undefined;
1193-
} else if (settings.markColor >= 3) {
1194-
nodeMap.get(u)!.markColor = settings.markColor;
1237+
if (curMS - prevMS <= 200 && curMS - latestColorChangeMS > 200) {
1238+
if (draggedNodes.length) {
1239+
const u = draggedNodes[0];
1240+
const sel = nodeMap.get(u)!.selected;
1241+
if (settings.markedNodes) nodeMap.get(u)!.selected = !sel;
1242+
if (settings.markColor === 2) {
1243+
nodeMap.get(u)!.markColor = undefined;
1244+
} else if (settings.markColor >= 3) {
1245+
nodeMap.get(u)!.markColor = settings.markColor;
1246+
}
1247+
latestColorChangeMS = performance.now();
1248+
} else if (coloredEdge) {
1249+
if (settings.markColor === 2) {
1250+
edgeMap.set(coloredEdge, undefined);
1251+
} else if (settings.markColor >= 3) {
1252+
edgeMap.set(coloredEdge, settings.markColor);
1253+
}
1254+
latestColorChangeMS = performance.now();
11951255
}
11961256
}
11971257
draggedNodes = [];
@@ -1234,6 +1294,10 @@ export function animateGraph(
12341294
renderEraseIndicator(indicatorRenderer);
12351295
renderPenIndicator(indicatorRenderer);
12361296

1297+
if (performance.now() - prevColoredEdgeMS > 200) {
1298+
coloredEdge = undefined;
1299+
}
1300+
12371301
if (!settings.lockMode) {
12381302
updateVelocities();
12391303
}

0 commit comments

Comments
 (0)