Skip to content

Commit 423a553

Browse files
committed
[edge-curve] Adds EdgeCurvedDoubleArrowProgram
Details: - Adds arrowHead.extremity in CreateEdgeCurveProgramOptions, to deal with arrow heads target side (as it was already implemented), source side, or both - Updates the rest of the program to handle source arrow heads, with or without target-side arrow heads - Exports a new EdgeCurvedDoubleArrowProgram already built program - Adds a new "Arrow heads" story to showcase this feature, as well as the straight double arrows
1 parent f0187cb commit 423a553

File tree

7 files changed

+162
-15
lines changed

7 files changed

+162
-15
lines changed

packages/edge-curve/src/factory.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export default function createEdgeCurveProgram<
2020
...(inputOptions || {}),
2121
};
2222
const { arrowHead, curvatureAttribute, drawLabel } = options as CreateEdgeCurveProgramOptions<N, E, G>;
23+
const hasTargetArrowHead = arrowHead?.extremity === "target" || arrowHead?.extremity === "both";
24+
const hasSourceArrowHead = arrowHead?.extremity === "source" || arrowHead?.extremity === "both";
2325
const UNIFORMS = [
2426
"u_matrix",
2527
"u_sizeRatio",
@@ -43,7 +45,8 @@ export default function createEdgeCurveProgram<
4345
ATTRIBUTES: [
4446
{ name: "a_source", size: 2, type: FLOAT },
4547
{ name: "a_target", size: 2, type: FLOAT },
46-
...(arrowHead ? [{ name: "a_targetSize", size: 1, type: FLOAT }] : []),
48+
...(hasTargetArrowHead ? [{ name: "a_targetSize", size: 1, type: FLOAT }] : []),
49+
...(hasSourceArrowHead ? [{ name: "a_sourceSize", size: 1, type: FLOAT }] : []),
4750
{ name: "a_thickness", size: 1, type: FLOAT },
4851
{ name: "a_curvature", size: 1, type: FLOAT },
4952
{ name: "a_color", size: 4, type: UNSIGNED_BYTE, normalized: true },
@@ -86,7 +89,8 @@ export default function createEdgeCurveProgram<
8689
array[startIndex++] = y1;
8790
array[startIndex++] = x2;
8891
array[startIndex++] = y2;
89-
if (arrowHead) array[startIndex++] = targetData.size;
92+
if (hasTargetArrowHead) array[startIndex++] = targetData.size;
93+
if (hasSourceArrowHead) array[startIndex++] = sourceData.size;
9094
array[startIndex++] = thickness;
9195
array[startIndex++] = curvature;
9296
array[startIndex++] = color;

packages/edge-curve/src/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,10 @@ export default EdgeCurveProgram;
1818
export const EdgeCurvedArrowProgram: EdgeProgramType = createEdgeCurveProgram({
1919
arrowHead: DEFAULT_EDGE_ARROW_HEAD_PROGRAM_OPTIONS,
2020
});
21+
22+
export const EdgeCurvedDoubleArrowProgram: EdgeProgramType = createEdgeCurveProgram({
23+
arrowHead: {
24+
...DEFAULT_EDGE_ARROW_HEAD_PROGRAM_OPTIONS,
25+
extremity: "both",
26+
},
27+
});

packages/edge-curve/src/shader-frag.ts

+31-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { CreateEdgeCurveProgramOptions } from "./utils";
22

33
export default function getFragmentShader({ arrowHead }: CreateEdgeCurveProgramOptions) {
4+
const hasTargetArrowHead = arrowHead?.extremity === "target" || arrowHead?.extremity === "both";
5+
const hasSourceArrowHead = arrowHead?.extremity === "source" || arrowHead?.extremity === "both";
6+
47
// language=GLSL
58
const SHADER = /*glsl*/ `
69
precision highp float;
@@ -12,11 +15,22 @@ varying vec2 v_cpA;
1215
varying vec2 v_cpB;
1316
varying vec2 v_cpC;
1417
${
15-
arrowHead
18+
hasTargetArrowHead
1619
? `
1720
varying float v_targetSize;
18-
varying vec2 v_targetPoint;
19-
21+
varying vec2 v_targetPoint;`
22+
: ""
23+
}
24+
${
25+
hasSourceArrowHead
26+
? `
27+
varying float v_sourceSize;
28+
varying vec2 v_sourcePoint;`
29+
: ""
30+
}
31+
${
32+
arrowHead
33+
? `
2034
uniform float u_lengthToThicknessRatio;
2135
uniform float u_widenessToThicknessRatio;`
2236
: ""
@@ -49,12 +63,22 @@ void main(void) {
4963
float dist = distToQuadraticBezierCurve(gl_FragCoord.xy, v_cpA, v_cpB, v_cpC);
5064
float thickness = v_thickness;
5165
${
52-
arrowHead
66+
hasTargetArrowHead
5367
? `
5468
float distToTarget = length(gl_FragCoord.xy - v_targetPoint);
55-
float arrowLength = v_targetSize + thickness * u_lengthToThicknessRatio;
56-
if (distToTarget < arrowLength) {
57-
thickness = (distToTarget - v_targetSize) / (arrowLength - v_targetSize) * u_widenessToThicknessRatio * thickness;
69+
float targetArrowLength = v_targetSize + thickness * u_lengthToThicknessRatio;
70+
if (distToTarget < targetArrowLength) {
71+
thickness = (distToTarget - v_targetSize) / (targetArrowLength - v_targetSize) * u_widenessToThicknessRatio * thickness;
72+
}`
73+
: ""
74+
}
75+
${
76+
hasSourceArrowHead
77+
? `
78+
float distToSource = length(gl_FragCoord.xy - v_sourcePoint);
79+
float sourceArrowLength = v_sourceSize + thickness * u_lengthToThicknessRatio;
80+
if (distToSource < sourceArrowLength) {
81+
thickness = (distToSource - v_sourceSize) / (sourceArrowLength - v_sourceSize) * u_widenessToThicknessRatio * thickness;
5882
}`
5983
: ""
6084
}

packages/edge-curve/src/shader-vert.ts

+29-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { CreateEdgeCurveProgramOptions } from "./utils";
22

33
export default function getVertexShader({ arrowHead }: CreateEdgeCurveProgramOptions) {
4+
const hasTargetArrowHead = arrowHead?.extremity === "target" || arrowHead?.extremity === "both";
5+
const hasSourceArrowHead = arrowHead?.extremity === "source" || arrowHead?.extremity === "both";
6+
47
// language=GLSL
58
const SHADER = /*glsl*/ `
69
attribute vec4 a_id;
@@ -11,7 +14,8 @@ attribute vec2 a_source;
1114
attribute vec2 a_target;
1215
attribute float a_current;
1316
attribute float a_curvature;
14-
${arrowHead ? "attribute float a_targetSize;\n" : ""}
17+
${hasTargetArrowHead ? "attribute float a_targetSize;\n" : ""}
18+
${hasSourceArrowHead ? "attribute float a_sourceSize;\n" : ""}
1519
1620
uniform mat3 u_matrix;
1721
uniform float u_sizeRatio;
@@ -27,12 +31,23 @@ varying vec2 v_cpA;
2731
varying vec2 v_cpB;
2832
varying vec2 v_cpC;
2933
${
30-
arrowHead
34+
hasTargetArrowHead
3135
? `
3236
varying float v_targetSize;
33-
varying vec2 v_targetPoint;
34-
uniform float u_widenessToThicknessRatio;
35-
`
37+
varying vec2 v_targetPoint;`
38+
: ""
39+
}
40+
${
41+
hasSourceArrowHead
42+
? `
43+
varying float v_sourceSize;
44+
varying vec2 v_sourcePoint;`
45+
: ""
46+
}
47+
${
48+
arrowHead
49+
? `
50+
uniform float u_widenessToThicknessRatio;`
3651
: ""
3752
}
3853
@@ -92,13 +107,21 @@ void main() {
92107
gl_Position = vec4(position, 0, 1);
93108
94109
${
95-
arrowHead
110+
hasTargetArrowHead
96111
? `
97112
v_targetSize = a_targetSize * u_pixelRatio / u_sizeRatio;
98113
v_targetPoint = viewportTarget;
99114
`
100115
: ""
101116
}
117+
${
118+
hasSourceArrowHead
119+
? `
120+
v_sourceSize = a_sourceSize * u_pixelRatio / u_sizeRatio;
121+
v_sourcePoint = viewportSource;
122+
`
123+
: ""
124+
}
102125
103126
#ifdef PICKING_MODE
104127
// For picking mode, we use the ID as the color:

packages/edge-curve/src/utils.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export type CreateEdgeCurveProgramOptions<
1313
curvatureAttribute: string;
1414
defaultCurvature: number;
1515
arrowHead: null | {
16+
extremity: "target" | "source" | "both";
1617
lengthToThicknessRatio: number;
1718
widenessToThicknessRatio: number;
1819
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* This story is here to showcase straight and curved arrows and double-arrows
3+
* renderers.
4+
*/
5+
import EdgeCurveProgram, { EdgeCurvedArrowProgram, EdgeCurvedDoubleArrowProgram } from "@sigma/edge-curve";
6+
import { MultiGraph } from "graphology";
7+
import Sigma from "sigma";
8+
import { EdgeArrowProgram, EdgeDoubleArrowProgram, EdgeRectangleProgram } from "sigma/rendering";
9+
10+
export default () => {
11+
const container = document.getElementById("sigma-container") as HTMLElement;
12+
13+
// Create a graph, with various parallel edges:
14+
const graph = new MultiGraph();
15+
16+
graph.addNode("a", { x: 0, y: 0, size: 10, label: "Alexandra" });
17+
graph.addNode("b", { x: 1, y: -1, size: 20, label: "Bastian" });
18+
graph.addNode("c", { x: 3, y: -2, size: 10, label: "Charles" });
19+
graph.addNode("d", { x: 1, y: -3, size: 10, label: "Dorothea" });
20+
graph.addNode("e", { x: 3, y: -4, size: 20, label: "Ernestine" });
21+
graph.addNode("f", { x: 4, y: -5, size: 10, label: "Fabian" });
22+
23+
graph.addEdge("a", "b", { size: 5 });
24+
graph.addEdge("b", "c", { size: 6, curved: true });
25+
graph.addEdge("b", "d", { size: 5 });
26+
graph.addEdge("c", "b", { size: 5, curved: true });
27+
graph.addEdge("c", "e", { size: 9 });
28+
graph.addEdge("d", "c", { size: 5, curved: true });
29+
graph.addEdge("d", "e", { size: 5, curved: true });
30+
graph.addEdge("e", "d", { size: 4, curved: true });
31+
graph.addEdge("f", "e", { size: 7, curved: true });
32+
33+
const renderer = new Sigma(graph, container, {
34+
allowInvalidContainer: true,
35+
defaultEdgeType: "straightNoArrow",
36+
renderEdgeLabels: true,
37+
edgeProgramClasses: {
38+
straightNoArrow: EdgeRectangleProgram,
39+
curvedNoArrow: EdgeCurveProgram,
40+
straightArrow: EdgeArrowProgram,
41+
curvedArrow: EdgeCurvedArrowProgram,
42+
straightDoubleArrow: EdgeDoubleArrowProgram,
43+
curvedDoubleArrow: EdgeCurvedDoubleArrowProgram,
44+
},
45+
});
46+
47+
// Add a form to play with arrow heads sides:
48+
const select = document.createElement("select") as HTMLSelectElement;
49+
select.style.fontFamily = "sans-serif";
50+
select.style.position = "absolute";
51+
select.style.top = "10px";
52+
select.style.right = "10px";
53+
select.style.padding = "10px";
54+
select.innerHTML = `
55+
<option value="NoArrow">No arrow</option>
56+
<option value="Arrow">Arrows</option>
57+
<option value="DoubleArrow">Double-sided arrows</option>
58+
`;
59+
select.value = "Arrow";
60+
document.body.append(select);
61+
62+
const refreshEdgeTypes = () => {
63+
const suffix = select.value;
64+
graph.forEachEdge((edge, { curved }) =>
65+
graph.setEdgeAttribute(edge, "type", `${curved ? "curved" : "straight"}${suffix}`),
66+
);
67+
};
68+
refreshEdgeTypes();
69+
select.addEventListener("change", refreshEdgeTypes);
70+
71+
return () => {
72+
renderer.kill();
73+
};
74+
};

packages/storybook/stories/3-additional-packages/edge-curve/stories.ts

+14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { Meta, StoryObj } from "@storybook/web-components";
22

33
import { wrapStory } from "../../utils";
4+
import arrowHeadsPlay from "./arrow-heads";
5+
import arrowHeadsSource from "./arrow-heads?raw";
46
import basicPlay from "./basic";
57
import basicSource from "./basic?raw";
68
import template from "./index.html?raw";
@@ -66,3 +68,15 @@ export const parallelEdges: Story = {
6668
},
6769
},
6870
};
71+
72+
export const arrowHeads: Story = {
73+
name: "Arrow heads",
74+
render: () => template,
75+
play: wrapStory(arrowHeadsPlay),
76+
args: {},
77+
parameters: {
78+
storySource: {
79+
source: arrowHeadsSource,
80+
},
81+
},
82+
};

0 commit comments

Comments
 (0)