-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[edge-curve] Drafts @sigma/edge-curve package
This commit drafts the new edge-curve renderer, based on previous work by @Yomguithereal, in his sigma-experiments. It compiles, but it does not work as intended yet. TODO: - Fix missing part of the curve - Add custom canvas label renderer that follows the curvature
- Loading branch information
Showing
16 changed files
with
3,172 additions
and
6 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.gitignore | ||
node_modules | ||
src | ||
tsconfig.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
{ | ||
"name": "@sigma/edge-curve", | ||
"version": "3.0.0-beta.1", | ||
"description": "An edge program that renders edges as curves for sigma.js", | ||
"main": "dist/sigma-edge-curve.cjs.js", | ||
"module": "dist/sigma-edge-curve.esm.js", | ||
"types": "dist/sigma-edge-curve.cjs.d.ts", | ||
"files": [ | ||
"/dist" | ||
], | ||
"sideEffects": false, | ||
"homepage": "https://www.sigmajs.org", | ||
"bugs": "http://github.com/jacomyal/sigma.js/issues", | ||
"repository": { | ||
"type": "git", | ||
"url": "http://github.com/jacomyal/sigma.js.git" | ||
}, | ||
"keywords": [ | ||
"graph", | ||
"graphology", | ||
"sigma" | ||
], | ||
"contributors": [ | ||
{ | ||
"name": "Guillaume Plique", | ||
"url": "http://github.com/Yomguithereal" | ||
}, | ||
{ | ||
"name": "Alexis Jacomy", | ||
"url": "http://github.com/jacomyal" | ||
} | ||
], | ||
"license": "MIT", | ||
"preconstruct": { | ||
"entrypoints": [ | ||
"index.ts" | ||
] | ||
}, | ||
"peerDependencies": { | ||
"sigma": ">=3.0.0-beta.10" | ||
}, | ||
"exports": { | ||
".": { | ||
"module": "./dist/sigma-edge-curve.esm.js", | ||
"import": "./dist/sigma-edge-curve.cjs.mjs", | ||
"default": "./dist/sigma-edge-curve.cjs.js" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { Attributes } from "graphology-types"; | ||
import { EdgeProgram, ProgramInfo } from "sigma/rendering"; | ||
import { EdgeDisplayData, NodeDisplayData, RenderParams } from "sigma/types"; | ||
import { floatColor } from "sigma/utils"; | ||
|
||
import FRAGMENT_SHADER_SOURCE from "./shader-frag"; | ||
import VERTEX_SHADER_SOURCE from "./shader-vert"; | ||
|
||
const { UNSIGNED_BYTE, FLOAT } = WebGLRenderingContext; | ||
|
||
const UNIFORMS = ["u_matrix", "u_sizeRatio", "u_dimensions", "u_pixelRatio"] as const; | ||
|
||
const DEFAULT_EDGE_CURVATURE = 0.25; | ||
|
||
export default class EdgeCurveProgram< | ||
N extends Attributes = Attributes, | ||
E extends Attributes = Attributes, | ||
G extends Attributes = Attributes, | ||
> extends EdgeProgram<(typeof UNIFORMS)[number], N, E, G> { | ||
getDefinition() { | ||
return { | ||
VERTICES: 4, | ||
VERTEX_SHADER_SOURCE, | ||
FRAGMENT_SHADER_SOURCE, | ||
METHOD: WebGLRenderingContext.TRIANGLES, | ||
UNIFORMS, | ||
ATTRIBUTES: [ | ||
{ name: "a_source", size: 2, type: FLOAT }, | ||
{ name: "a_target", size: 2, type: FLOAT }, | ||
{ name: "a_thickness", size: 1, type: FLOAT }, | ||
{ name: "a_curvature", size: 1, type: FLOAT }, | ||
{ name: "a_color", size: 4, type: UNSIGNED_BYTE, normalized: true }, | ||
{ name: "a_id", size: 4, type: UNSIGNED_BYTE, normalized: true }, | ||
], | ||
CONSTANT_ATTRIBUTES: [ | ||
{ name: "a_current", size: 1, type: FLOAT }, // TODO: could optimize to bool | ||
{ name: "a_direction", size: 1, type: FLOAT }, // TODO: could optimize to byte | ||
], | ||
CONSTANT_DATA: [ | ||
[0, 1], | ||
[0, -1], | ||
[1, 1], | ||
[1, -1], | ||
], | ||
}; | ||
} | ||
|
||
processVisibleItem( | ||
edgeIndex: number, | ||
startIndex: number, | ||
sourceData: NodeDisplayData, | ||
targetData: NodeDisplayData, | ||
data: EdgeDisplayData & { curvature?: number }, | ||
) { | ||
const thickness = data.size || 1; | ||
const x1 = sourceData.x; | ||
const y1 = sourceData.y; | ||
const x2 = targetData.x; | ||
const y2 = targetData.y; | ||
const color = floatColor(data.color); | ||
const curvature = typeof data.curvature === "number" ? data.curvature : DEFAULT_EDGE_CURVATURE; | ||
|
||
const array = this.array; | ||
|
||
// First point | ||
array[startIndex++] = x1; | ||
array[startIndex++] = y1; | ||
array[startIndex++] = x2; | ||
array[startIndex++] = y2; | ||
array[startIndex++] = thickness; | ||
array[startIndex++] = curvature; | ||
array[startIndex++] = color; | ||
array[startIndex++] = edgeIndex; | ||
} | ||
|
||
setUniforms(params: RenderParams, { gl, uniformLocations }: ProgramInfo): void { | ||
const { u_matrix, u_pixelRatio, u_sizeRatio, u_dimensions } = uniformLocations; | ||
|
||
gl.uniformMatrix3fv(u_matrix, false, params.matrix); | ||
gl.uniform1f(u_pixelRatio, params.pixelRatio); | ||
gl.uniform1f(u_sizeRatio, params.sizeRatio); | ||
gl.uniform2f(u_dimensions, params.width * params.pixelRatio, params.height * params.pixelRatio); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// language=GLSL | ||
const SHADER_SOURCE = /*glsl*/ ` | ||
precision mediump float; | ||
varying vec4 v_color; | ||
varying float v_thickness; | ||
varying vec2 v_cpA; | ||
varying vec2 v_cpB; | ||
varying vec2 v_cpC; | ||
float det(vec2 a, vec2 b) { | ||
return a.x * b.y - b.x * a.y; | ||
} | ||
vec2 get_distance_vector(vec2 b0, vec2 b1, vec2 b2) { | ||
float a = det(b0, b2), b = 2.0 * det(b1, b0), d = 2.0 * det(b2, b1); | ||
float f = b * d - a * a; | ||
vec2 d21 = b2 - b1, d10 = b1 - b0, d20 = b2 - b0; | ||
vec2 gf = 2.0 * (b * d21 + d * d10 + a * d20); | ||
gf = vec2(gf.y, -gf.x); | ||
vec2 pp = -f * gf / dot(gf, gf); | ||
vec2 d0p = b0 - pp; | ||
float ap = det(d0p, d20), bp = 2.0 * det(d10, d0p); | ||
float t = clamp((ap + bp) / (2.0 * a + b + d), 0.0, 1.0); | ||
return mix(mix(b0, b1, t), mix(b1, b2, t), t); | ||
} | ||
float distToQuadraticBezierCurve(vec2 p, vec2 b0, vec2 b1, vec2 b2) { | ||
return length(get_distance_vector(b0 - p, b1 - p, b2 - p)); | ||
} | ||
const float epsilon = 0.7; | ||
const vec4 transparent = vec4(0.0, 0.0, 0.0, 0.0); | ||
void main(void) { | ||
float dist = distToQuadraticBezierCurve(gl_FragCoord.xy, v_cpA, v_cpB, v_cpC); | ||
if (dist < v_thickness + epsilon) { | ||
float inCurve = 1.0 - smoothstep(v_thickness - epsilon, v_thickness + epsilon, dist); | ||
gl_FragColor = inCurve * vec4(v_color.rgb * v_color.a, v_color.a); | ||
} else { | ||
gl_FragColor = transparent; | ||
} | ||
} | ||
`; | ||
|
||
export default SHADER_SOURCE; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// language=GLSL | ||
const VERTEX_SHADER_SOURCE = /*glsl*/ ` | ||
attribute vec4 a_color; | ||
attribute float a_direction; | ||
attribute float a_thickness; | ||
attribute vec2 a_source; | ||
attribute vec2 a_target; | ||
attribute float a_current; | ||
attribute float a_curvature; | ||
uniform mat3 u_matrix; | ||
uniform float u_sizeRatio; | ||
uniform float u_pixelRatio; | ||
uniform vec2 u_dimensions; | ||
varying vec4 v_color; | ||
varying float v_thickness; | ||
varying vec2 v_cpA; | ||
varying vec2 v_cpB; | ||
varying vec2 v_cpC; | ||
const float bias = 255.0 / 254.0; | ||
const float epsilon = 0.7; | ||
const float minThickness = 0.3; | ||
vec2 clipspaceToViewport(vec2 pos, vec2 dimensions) { | ||
return vec2( | ||
(pos.x + 1.0) * dimensions.x / 2.0, | ||
(pos.y + 1.0) * dimensions.y / 2.0 | ||
); | ||
} | ||
vec2 viewportToClipspace(vec2 pos, vec2 dimensions) { | ||
return vec2( | ||
pos.x / dimensions.x * 2.0 - 1.0, | ||
pos.y / dimensions.y * 2.0 - 1.0 | ||
); | ||
} | ||
void main() { | ||
// Selecting the correct position | ||
// Branchless "position = a_source if a_current == 1.0 else a_target" | ||
vec2 position = a_source * max(0.0, a_current) + a_target * max(0.0, 1.0 - a_current); | ||
position = (u_matrix * vec3(position, 1)).xy; | ||
vec2 source = (u_matrix * vec3(a_source, 1)).xy; | ||
vec2 target = (u_matrix * vec3(a_target, 1)).xy; | ||
vec2 viewportPosition = clipspaceToViewport(position, u_dimensions); | ||
vec2 viewportSource = clipspaceToViewport(source, u_dimensions); | ||
vec2 viewportTarget = clipspaceToViewport(target, u_dimensions); | ||
vec2 delta = viewportTarget.xy - viewportSource.xy; | ||
float len = length(delta); | ||
vec2 normal = vec2(-delta.y, delta.x) * a_direction; | ||
vec2 unitNormal = normal / len; | ||
float boundingBoxThickness = len * a_curvature; | ||
float curveThickness = max(minThickness, a_thickness / 2.0 / u_sizeRatio * u_pixelRatio); | ||
v_thickness = curveThickness; | ||
v_cpA = viewportSource; | ||
v_cpB = 0.5 * (viewportSource + viewportTarget) + unitNormal * a_direction * boundingBoxThickness; | ||
v_cpC = viewportTarget; | ||
vec2 viewportOffsetPosition = ( | ||
viewportPosition + | ||
unitNormal * (boundingBoxThickness / 2.0 + curveThickness + epsilon) * | ||
max(0.0, a_direction) // NOTE: cutting the bounding box in half to avoid overdraw | ||
); | ||
position = viewportToClipspace(viewportOffsetPosition, u_dimensions); | ||
gl_Position = vec4(position, 0, 1); | ||
#ifdef PICKING_MODE | ||
// For picking mode, we use the ID as the color: | ||
v_color = a_id; | ||
#else | ||
// For normal mode, we use the color: | ||
v_color = a_color; | ||
#endif | ||
v_color.a *= bias; | ||
} | ||
`; | ||
|
||
export default VERTEX_SHADER_SOURCE; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { Meta, StoryObj } from "@storybook/html"; | ||
import Graph from "graphology"; | ||
import Sigma from "sigma"; | ||
|
||
import EdgeCurveProgram from "../index.ts"; | ||
import data from "./data.json"; | ||
import "./stage.css"; | ||
|
||
const createPictogramsStage = () => { | ||
const stage = document.createElement("div"); | ||
stage.classList.add("stage"); | ||
|
||
const graph = new Graph(); | ||
graph.import(data); | ||
|
||
new Sigma(graph, stage, { | ||
allowInvalidContainer: true, | ||
defaultEdgeType: "curve", | ||
edgeProgramClasses: { | ||
curve: EdgeCurveProgram, | ||
}, | ||
}); | ||
|
||
return stage; | ||
}; | ||
|
||
const meta: Meta<typeof createPictogramsStage> = { | ||
title: "edge-curve", | ||
render: () => createPictogramsStage(), | ||
parameters: { | ||
layout: "fullscreen", | ||
}, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof createPictogramsStage>; | ||
|
||
export const ComparisonExample: Story = { | ||
name: "All features at once", | ||
}; |
Oops, something went wrong.