Skip to content

Commit

Permalink
[node-square] Implements new square nodes renderer
Browse files Browse the repository at this point in the history
This commit fixes #1452.

Details:
- Adds new basic node renderer @sigma/node-square
- References new module where it's needed (package.json, tsconfig.json
  and docusaurus.config.js)
- Updates documentation to mention that new renderer
  • Loading branch information
jacomyal committed Aug 22, 2024
1 parent 06cec18 commit 7a341c3
Show file tree
Hide file tree
Showing 18 changed files with 425 additions and 2 deletions.
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@
"preconstruct": {
"packages": [
"packages/sigma",
"packages/layer-webgl",
"packages/node-border",
"packages/node-image",
"packages/node-piechart",
"packages/node-square",
"packages/edge-curve",
"packages/layer-leaflet",
"packages/layer-maplibre"
"packages/layer-maplibre",
"packages/layer-webgl"
],
"exports": {
"importConditionDefaultExport": "default"
Expand Down
2 changes: 2 additions & 0 deletions packages/node-square/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
dist
4 changes: 4 additions & 0 deletions packages/node-square/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.gitignore
node_modules
src
tsconfig.json
29 changes: 29 additions & 0 deletions packages/node-square/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Sigma.js - Node square renderer

This package contains a node square renderer for [sigma.js](https://sigmajs.org), as well as a proper

## How to use

Within your application that uses sigma.js, you can use [`@sigma/node-square`](https://www.npmjs.com/package/@sigma/node-square) as following:

```typescript
import { NodeSquareProgram } from "@sigma/node-square";

const graph = new Graph();
graph.addNode("some-node", {
x: 0,
y: 0,
size: 10,
type: "square",
label: "Some node",
color: "blue",
});

const sigma = new Sigma(graph, container, {
nodeProgramClasses: {
square: NodeSquareProgram,
},
});
```

Please check the related [Storybook](https://github.com/jacomyal/sigma.js/tree/main/packages/storybook/stories/node-square) for more advanced examples.
46 changes: 46 additions & 0 deletions packages/node-square/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "@sigma/node-square",
"version": "3.0.0-beta.0",
"description": "A node program that renders nodes as squares for sigma.js",
"main": "dist/sigma-node-square.cjs.js",
"module": "dist/sigma-node-square.esm.js",
"types": "dist/sigma-node-square.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": "Alexis Jacomy",
"url": "http://github.com/jacomyal"
}
],
"license": "MIT",
"preconstruct": {
"entrypoints": [
"index.ts"
]
},
"peerDependencies": {
"sigma": ">=3.0.0-beta.17"
},
"exports": {
".": {
"module": "./dist/sigma-node-square.esm.js",
"import": "./dist/sigma-node-square.cjs.mjs",
"default": "./dist/sigma-node-square.cjs.js"
},
"./package.json": "./package.json"
}
}
2 changes: 2 additions & 0 deletions packages/node-square/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./utils";
export { NodeSquareProgram } from "./program";
61 changes: 61 additions & 0 deletions packages/node-square/src/program.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Attributes } from "graphology-types";
import { NodeProgram, ProgramInfo } from "sigma/rendering";
import { NodeDisplayData, RenderParams } from "sigma/types";
import { floatColor } from "sigma/utils";

import FRAGMENT_SHADER_SOURCE from "./shader-frag";
import VERTEX_SHADER_SOURCE from "./shader-vert";
import { drawSquareNodeHover, drawSquareNodeLabel } from "./utils";

const { UNSIGNED_BYTE, FLOAT } = WebGLRenderingContext;

const UNIFORMS = ["u_sizeRatio", "u_correctionRatio", "u_cameraAngle", "u_matrix"] as const;

const PI = Math.PI;

export class NodeSquareProgram<
N extends Attributes = Attributes,
E extends Attributes = Attributes,
G extends Attributes = Attributes,
> extends NodeProgram<(typeof UNIFORMS)[number], N, E, G> {
drawHover = drawSquareNodeHover;
drawLabel = drawSquareNodeLabel;

getDefinition() {
return {
VERTICES: 6,
VERTEX_SHADER_SOURCE: VERTEX_SHADER_SOURCE,
FRAGMENT_SHADER_SOURCE: FRAGMENT_SHADER_SOURCE,
METHOD: WebGLRenderingContext.TRIANGLES,
UNIFORMS,
ATTRIBUTES: [
{ name: "a_position", size: 2, type: FLOAT },
{ name: "a_size", 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_angle", size: 1, type: FLOAT }],
CONSTANT_DATA: [[PI / 4], [(3 * PI) / 4], [-PI / 4], [(3 * PI) / 4], [-PI / 4], [(-3 * PI) / 4]],
};
}

processVisibleItem(nodeIndex: number, startIndex: number, data: NodeDisplayData) {
const array = this.array;
const color = floatColor(data.color);

array[startIndex++] = data.x;
array[startIndex++] = data.y;
array[startIndex++] = data.size;
array[startIndex++] = color;
array[startIndex++] = nodeIndex;
}

setUniforms(params: RenderParams, { gl, uniformLocations }: ProgramInfo): void {
const { u_sizeRatio, u_correctionRatio, u_cameraAngle, u_matrix } = uniformLocations;

gl.uniform1f(u_sizeRatio, params.sizeRatio);
gl.uniform1f(u_cameraAngle, params.cameraAngle);
gl.uniform1f(u_correctionRatio, params.correctionRatio);
gl.uniformMatrix3fv(u_matrix, false, params.matrix);
}
}
12 changes: 12 additions & 0 deletions packages/node-square/src/shader-frag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// language=GLSL
const SHADER_SOURCE = /*glsl*/ `
precision mediump float;
varying vec4 v_color;
void main(void) {
gl_FragColor = v_color;
}
`;

export default SHADER_SOURCE;
40 changes: 40 additions & 0 deletions packages/node-square/src/shader-vert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// language=GLSL
const SHADER_SOURCE = /*glsl*/ `
attribute vec4 a_id;
attribute vec4 a_color;
attribute vec2 a_position;
attribute float a_size;
attribute float a_angle;
uniform mat3 u_matrix;
uniform float u_sizeRatio;
uniform float u_cameraAngle;
uniform float u_correctionRatio;
varying vec4 v_color;
const float bias = 255.0 / 254.0;
const float sqrt_8 = sqrt(8.0);
void main() {
float size = a_size * u_correctionRatio / u_sizeRatio * sqrt_8;
float angle = a_angle + u_cameraAngle;
vec2 diffVector = size * vec2(cos(angle), sin(angle));
vec2 position = a_position + diffVector;
gl_Position = vec4(
(u_matrix * vec3(position, 1)).xy,
0,
1
);
#ifdef PICKING_MODE
v_color = a_id;
#else
v_color = a_color;
#endif
v_color.a *= bias;
}
`;

export default SHADER_SOURCE;
70 changes: 70 additions & 0 deletions packages/node-square/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Attributes } from "graphology-types";
import { drawDiscNodeLabel } from "sigma/rendering";
import { Settings } from "sigma/settings";
import { NodeDisplayData, PartialButFor } from "sigma/types";

export function drawSquareNodeLabel<
N extends Attributes = Attributes,
E extends Attributes = Attributes,
G extends Attributes = Attributes,
>(
context: CanvasRenderingContext2D,
data: PartialButFor<NodeDisplayData, "x" | "y" | "size" | "label" | "color">,
settings: Settings<N, E, G>,
): void {
return drawDiscNodeLabel<N, E, G>(context, data, settings);
}

export function drawSquareNodeHover<
N extends Attributes = Attributes,
E extends Attributes = Attributes,
G extends Attributes = Attributes,
>(
context: CanvasRenderingContext2D,
data: PartialButFor<NodeDisplayData, "x" | "y" | "size" | "label" | "color">,
settings: Settings<N, E, G>,
): void {
const size = settings.labelSize,
font = settings.labelFont,
weight = settings.labelWeight;

context.font = `${weight} ${size}px ${font}`;

// Then we draw the label background
context.fillStyle = "#FFF";
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.shadowBlur = 8;
context.shadowColor = "#000";

const PADDING = 2;

if (typeof data.label === "string") {
const textWidth = context.measureText(data.label).width,
boxWidth = Math.round(textWidth + 5),
boxHeight = Math.round(size + 2 * PADDING),
radius = Math.max(data.size, size / 2) + PADDING;

context.beginPath();
context.moveTo(data.x + radius, data.y + boxHeight / 2);
context.lineTo(data.x + radius + boxWidth, data.y + boxHeight / 2);
context.lineTo(data.x + radius + boxWidth, data.y - boxHeight / 2);
context.lineTo(data.x + radius, data.y - boxHeight / 2);
context.lineTo(data.x + radius, data.y - radius);
context.lineTo(data.x - radius, data.y - radius);
context.lineTo(data.x - radius, data.y + radius);
context.lineTo(data.x + radius, data.y + radius);
context.moveTo(data.x + radius, data.y + boxHeight / 2);
context.closePath();
context.fill();
} else {
const radius = data.size + PADDING;
context.fillRect(data.x - radius, data.y - radius, radius * 2, radius * 2);
}

context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.shadowBlur = 0;

drawSquareNodeLabel(context, data, settings);
}
26 changes: 26 additions & 0 deletions packages/node-square/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "ESNext", // Specifies the JavaScript version to target when transpiling code.
"useDefineForClassFields": true, // Enables the use of 'define' for class fields.
"lib": ["ES2020", "DOM", "DOM.Iterable"], // Specifies the libraries available for the code.
"module": "ESNext", // Defines the module system to use for code generation.
"skipLibCheck": true, // Skips type checking of declaration files.

/* Bundler mode */
"moduleResolution": "node", // Specifies how modules are resolved when bundling.
"allowSyntheticDefaultImports": true,
"allowImportingTsExtensions": true, // Allows importing TypeScript files with extensions.
"resolveJsonModule": true, // Enables importing JSON modules.
"isolatedModules": true, // Ensures each file is treated as a separate module.
"noEmit": true, // Prevents TypeScript from emitting output files.

/* Linting */
"strict": true, // Enables strict type checking.
"noUnusedLocals": true, // Flags unused local variables.
"noUnusedParameters": true, // Flags unused function parameters.
"noFallthroughCasesInSwitch": true, // Requires handling all cases in a switch statement.
"declaration": true // Generates declaration files for TypeScript.
},
"include": ["src"], // Specifies the directory to include when searching for TypeScript files.
"exclude": ["src/**/__docs__", "src/**/__test__"]
}
13 changes: 13 additions & 0 deletions packages/storybook/stories/node-square/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<style>
html,
body,
#storybook-root,
#sigma-container {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
<div id="sigma-container"></div>
Loading

0 comments on commit 7a341c3

Please sign in to comment.