Skip to content

Commit

Permalink
[sigma] Allows updating programs
Browse files Browse the repository at this point in the history
This fixes #1423.

Details:
- Adds (un)?register(Node|Edge)Program methods
- Updates handleSettingsUpdate so that it can take an oldSettings input,
  and check when programs are updated
- Updates updateSetting to use setSetting internally
- Updates setSetting to give handleSettingsUpdate an oldSettings input
- Adds some unit tests for settings management
  • Loading branch information
jacomyal committed Jun 18, 2024
1 parent 23e5373 commit 1d86d8e
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 19 deletions.
133 changes: 114 additions & 19 deletions packages/sigma/src/sigma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,20 +216,15 @@ export default class Sigma<

// Loading programs
for (const type in this.settings.nodeProgramClasses) {
const NodeProgramClass = this.settings.nodeProgramClasses[type];
this.nodePrograms[type] = new NodeProgramClass(this.webGLContexts.nodes, this.frameBuffers.nodes, this);

let NodeHoverProgram = NodeProgramClass;
if (type in this.settings.nodeHoverProgramClasses) {
NodeHoverProgram = this.settings.nodeHoverProgramClasses[type];
}

this.nodeHoverPrograms[type] = new NodeHoverProgram(this.webGLContexts.hoverNodes, null, this);
this.registerNodeProgram(
type,
this.settings.nodeProgramClasses[type],
this.settings.nodeHoverProgramClasses[type],
);
}

for (const type in this.settings.edgeProgramClasses) {
const EdgeProgramClass = this.settings.edgeProgramClasses[type];
this.edgePrograms[type] = new EdgeProgramClass(this.webGLContexts.edges, this.frameBuffers.edges, this);
this.registerEdgeProgram(type, this.settings.edgeProgramClasses[type]);
}

// Initializing the camera
Expand Down Expand Up @@ -302,6 +297,74 @@ export default class Sigma<
return this;
}

/**
* Internal function used to register a node program
*
* @param {string} key - The program's key, matching the related nodes "type" values.
* @param {NodeProgramType} NodeProgramClass - A nodes program class.
* @param {NodeProgramType?} NodeHoverProgram - A nodes program class to render hovered nodes (optional).
* @return {Sigma}
*/
private registerNodeProgram(
key: string,
NodeProgramClass: NodeProgramType<N, E, G>,
NodeHoverProgram?: NodeProgramType<N, E, G>,
): this {
if (this.nodePrograms[key]) this.nodePrograms[key].kill();
if (this.nodeHoverPrograms[key]) this.nodeHoverPrograms[key].kill();
this.nodePrograms[key] = new NodeProgramClass(this.webGLContexts.nodes, this.frameBuffers.nodes, this);
this.nodeHoverPrograms[key] = new (NodeHoverProgram || NodeProgramClass)(this.webGLContexts.hoverNodes, null, this);
return this;
}

/**
* Internal function used to register an edge program
*
* @param {string} key - The program's key, matching the related edges "type" values.
* @param {EdgeProgramType} EdgeProgramClass - An edges program class.
* @return {Sigma}
*/
private registerEdgeProgram(key: string, EdgeProgramClass: EdgeProgramType<N, E, G>): this {
if (this.edgePrograms[key]) this.edgePrograms[key].kill();
this.edgePrograms[key] = new EdgeProgramClass(this.webGLContexts.edges, this.frameBuffers.edges, this);
return this;
}

/**
* Internal function used to unregister a node program
*
* @param {string} key - The program's key, matching the related nodes "type" values.
* @return {Sigma}
*/
private unregisterNodeProgram(key: string): this {
if (this.nodePrograms[key]) {
const { [key]: program, ...programs } = this.nodePrograms;
program.kill();
this.nodePrograms = programs;
}
if (this.nodeHoverPrograms[key]) {
const { [key]: program, ...programs } = this.nodeHoverPrograms;
program.kill();
this.nodePrograms = programs;
}
return this;
}

/**
* Internal function used to unregister an edge program
*
* @param {string} key - The program's key, matching the related edges "type" values.
* @return {Sigma}
*/
private unregisterEdgeProgram(key: string): this {
if (this.edgePrograms[key]) {
const { [key]: program, ...programs } = this.edgePrograms;
program.kill();
this.edgePrograms = programs;
}
return this;
}

/**
* Internal function used to create a WebGL context and add the relevant DOM
* elements.
Expand Down Expand Up @@ -859,11 +922,45 @@ export default class Sigma<
* Method that backports potential settings updates where it's needed.
* @private
*/
private handleSettingsUpdate(): this {
this.camera.minRatio = this.settings.minCameraRatio;
this.camera.maxRatio = this.settings.maxCameraRatio;
private handleSettingsUpdate(oldSettings?: Settings<N, E, G>): this {
const settings = this.settings;

this.camera.minRatio = settings.minCameraRatio;
this.camera.maxRatio = settings.maxCameraRatio;
this.camera.setState(this.camera.validateState(this.camera.getState()));

if (oldSettings) {
// Check edge programs:
if (oldSettings.edgeProgramClasses !== settings.edgeProgramClasses) {
for (const type in settings.edgeProgramClasses) {
if (settings.edgeProgramClasses[type] !== oldSettings.edgeProgramClasses[type]) {
this.registerEdgeProgram(type, settings.edgeProgramClasses[type]);
}
}
for (const type in oldSettings.edgeProgramClasses) {
if (!settings.edgeProgramClasses[type]) this.unregisterEdgeProgram(type);
}
}

// Check node programs:
if (
oldSettings.nodeProgramClasses !== settings.nodeProgramClasses ||
oldSettings.nodeHoverProgramClasses !== settings.nodeHoverProgramClasses
) {
for (const type in settings.nodeProgramClasses) {
if (
settings.nodeProgramClasses[type] !== oldSettings.nodeProgramClasses[type] ||
settings.nodeHoverProgramClasses[type] !== oldSettings.nodeHoverProgramClasses[type]
) {
this.registerNodeProgram(type, settings.nodeProgramClasses[type], settings.nodeHoverProgramClasses[type]);
}
}
for (const type in oldSettings.nodeProgramClasses) {
if (!settings.nodeProgramClasses[type]) this.unregisterNodeProgram(type);
}
}
}

return this;
}

Expand Down Expand Up @@ -1631,9 +1728,10 @@ export default class Sigma<
* @return {Sigma}
*/
setSetting<K extends keyof Settings<N, E, G>>(key: K, value: Settings<N, E, G>[K]): this {
const oldValues = { ...this.settings };
this.settings[key] = value;
validateSettings(this.settings);
this.handleSettingsUpdate();
this.handleSettingsUpdate(oldValues);
this.scheduleRefresh();
return this;
}
Expand All @@ -1650,10 +1748,7 @@ export default class Sigma<
key: K,
updater: (value: Settings<N, E, G>[K]) => Settings<N, E, G>[K],
): this {
this.settings[key] = updater(this.settings[key]);
validateSettings(this.settings);
this.handleSettingsUpdate();
this.scheduleRefresh();
this.setSetting(key, updater(this.settings[key]));
return this;
}

Expand Down
46 changes: 46 additions & 0 deletions packages/test/unit/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Graph from "graphology";
import Sigma from "sigma";
import { NodePointProgram } from "sigma/rendering";
import { createElement } from "sigma/utils";
import { afterEach, beforeEach, describe, expect, test } from "vitest";

interface SigmaTestContext {
sigma: Sigma;
}

beforeEach<SigmaTestContext>(async (context) => {
const graph = new Graph();
graph.addNode("a", { type: "circle", x: 0, y: 0 });
graph.addNode("b", { type: "circle", x: 10, y: 10 });
graph.addEdge("a", "b", { type: "arrow" });
const container = createElement("div", { width: "100px", height: "100px" });
document.body.append(container);
context.sigma = new Sigma(graph, container);
});

afterEach<SigmaTestContext>(async ({ sigma }) => {
sigma.kill();
sigma.getContainer().remove();
});

describe("Sigma settings management", () => {
test<SigmaTestContext>("it should refresh when settings are updated", async ({ sigma }) => {
let count = 0;
sigma.on("beforeRender", () => count++);

expect(count).toEqual(0);
sigma.setSetting("minEdgeThickness", 10);
await new Promise((resolve) => window.setTimeout(resolve, 0));

expect(count).toEqual(1);
});

test<SigmaTestContext>("it should update programs when they're updated", ({ sigma }) => {
sigma.setSetting("nodeProgramClasses", { point: NodePointProgram });
expect(() => sigma.refresh()).toThrow();

const graph = sigma.getGraph();
graph.forEachNode((node) => graph.setNodeAttribute(node, "type", "point"));
expect(() => sigma.refresh()).not.toThrow();
});
});

0 comments on commit 1d86d8e

Please sign in to comment.