diff --git a/packages/sigma/src/rendering/program.ts b/packages/sigma/src/rendering/program.ts index e8175f381..f882751a1 100644 --- a/packages/sigma/src/rendering/program.ts +++ b/packages/sigma/src/rendering/program.ts @@ -22,6 +22,7 @@ import { } from "./utils"; const PICKING_PREFIX = `#define PICKING_MODE\n`; +const Z_INDEXING_PREFIX = `#define Z_INDEXING\n`; const SIZE_FACTOR_PER_ATTRIBUTE_TYPE: Record = { [WebGL2RenderingContext.BOOL]: 1, @@ -76,6 +77,7 @@ export abstract class Program< pickProgram: ProgramInfo | null; isInstanced: boolean; + isZIndexing: boolean; abstract getDefinition(): ProgramDefinition | InstancedProgramDefinition; @@ -84,11 +86,16 @@ export abstract class Program< pickingBuffer: WebGLFramebuffer | null, renderer: Sigma, ) { + this.renderer = renderer; + this.isZIndexing = !!renderer.getSetting("zIndex"); + + const zIndexingPrefix = this.isZIndexing ? Z_INDEXING_PREFIX : ""; + // Reading and caching program definition const def = this.getDefinition(); this.VERTICES = def.VERTICES; - this.VERTEX_SHADER_SOURCE = def.VERTEX_SHADER_SOURCE; - this.FRAGMENT_SHADER_SOURCE = def.FRAGMENT_SHADER_SOURCE; + this.VERTEX_SHADER_SOURCE = zIndexingPrefix + def.VERTEX_SHADER_SOURCE; + this.FRAGMENT_SHADER_SOURCE = zIndexingPrefix + def.FRAGMENT_SHADER_SOURCE; this.UNIFORMS = def.UNIFORMS; this.ATTRIBUTES = def.ATTRIBUTES; this.METHOD = def.METHOD; @@ -102,14 +109,19 @@ export abstract class Program< this.STRIDE = this.VERTICES * this.ATTRIBUTES_ITEMS_COUNT; // Members - this.renderer = renderer; - this.normalProgram = this.getProgramInfo("normal", gl, def.VERTEX_SHADER_SOURCE, def.FRAGMENT_SHADER_SOURCE, null); + this.normalProgram = this.getProgramInfo( + "normal", + gl, + this.VERTEX_SHADER_SOURCE, + this.FRAGMENT_SHADER_SOURCE, + null, + ); this.pickProgram = pickingBuffer ? this.getProgramInfo( "pick", gl, - PICKING_PREFIX + def.VERTEX_SHADER_SOURCE, - PICKING_PREFIX + def.FRAGMENT_SHADER_SOURCE, + PICKING_PREFIX + this.VERTEX_SHADER_SOURCE, + PICKING_PREFIX + this.FRAGMENT_SHADER_SOURCE, pickingBuffer, ) : null; diff --git a/packages/sigma/src/sigma.ts b/packages/sigma/src/sigma.ts index 9d2458920..dce75b924 100644 --- a/packages/sigma/src/sigma.ts +++ b/packages/sigma/src/sigma.ts @@ -143,8 +143,8 @@ export default class Sigma< private nodesWithForcedLabels: Set = new Set(); private edgesWithForcedLabels: Set = new Set(); private nodeExtent: { x: Extent; y: Extent } = { x: [0, 1], y: [0, 1] }; - private nodeZExtent: [number, number] = [Infinity, -Infinity]; - private edgeZExtent: [number, number] = [Infinity, -Infinity]; + private nodeDepths: Record = {}; + private edgeDepths: Record = {}; private matrix: Float32Array = identity(); private invMatrix: Float32Array = identity(); @@ -631,13 +631,13 @@ export default class Sigma< private bindGraphHandlers(): this { const graph = this.graph; - const LAYOUT_IMPACTING_FIELDS = new Set(["x", "y", "zIndex", "type"]); + const LAYOUT_IMPACTING_FIELDS = new Set(["x", "y", "type"]); this.activeListeners.eachNodeAttributesUpdatedGraphUpdate = (e: { hints?: { attributes?: string[] } }) => { const updatedFields = e.hints?.attributes; // we process all nodes this.graph.forEachNode((node) => this.updateNode(node)); - // if coord, type or zIndex have changed, we need to schedule a render + // if coord or type have changed, we need to schedule a render // (zIndex for the programIndex) const layoutChanged = !updatedFields || updatedFields.some((f) => LAYOUT_IMPACTING_FIELDS.has(f)); this.refresh({ partialGraph: { nodes: graph.nodes() }, skipIndexation: !layoutChanged, schedule: true }); @@ -647,7 +647,7 @@ export default class Sigma< const updatedFields = e.hints?.attributes; // we process all edges this.graph.forEachEdge((edge) => this.updateEdge(edge)); - const layoutChanged = updatedFields && ["zIndex", "type"].some((f) => updatedFields?.includes(f)); + const layoutChanged = updatedFields?.includes("type"); this.refresh({ partialGraph: { edges: graph.edges() }, skipIndexation: !layoutChanged, schedule: true }); }; @@ -816,7 +816,7 @@ export default class Sigma< const itemIDsIndex: typeof this.itemIDsIndex = {}; let incrID = 1; - let nodes = graph.nodes(); + const nodes = graph.nodes(); // Do some indexation on the whole graph for (let i = 0, l = nodes.length; i < l; i++) { @@ -849,13 +849,16 @@ export default class Sigma< } // Order nodes by zIndex before to add them to program - if (this.settings.zIndex && this.nodeZExtent[0] !== this.nodeZExtent[1]) - nodes = zIndexOrdering( - this.nodeZExtent, + this.nodeDepths = {}; + if (this.settings.zIndex) { + const sortedNodes = zIndexOrdering( (node: string): number => this.nodeDataCache[node].zIndex, - nodes, + nodes.slice(0), ); + for (let i = 0, l = sortedNodes.length; i < l; i++) this.nodeDepths[sortedNodes[i]] = l - 1 - i; + } + // Add data to programs for (let i = 0, l = nodes.length; i < l; i++) { const node = nodes[i]; @@ -873,7 +876,7 @@ export default class Sigma< // const edgesPerPrograms: Record = {}; - let edges = graph.edges(); + const edges = graph.edges(); // Allocate memory to programs for (let i = 0, l = edges.length; i < l; i++) { @@ -883,13 +886,16 @@ export default class Sigma< } // Order edges by zIndex before to add them to program - if (this.settings.zIndex && this.edgeZExtent[0] !== this.edgeZExtent[1]) - edges = zIndexOrdering( - this.edgeZExtent, + this.edgeDepths = {}; + if (this.settings.zIndex) { + const sortedEdges = zIndexOrdering( (edge: string): number => this.edgeDataCache[edge].zIndex, - edges, + edges.slice(0), ); + for (let i = 0, l = sortedEdges.length; i < l; i++) this.edgeDepths[sortedEdges[i]] = l - 1 - i; + } + for (const type in this.edgePrograms) { if (!hasOwnProperty.call(this.edgePrograms, type)) { throw new Error(`Sigma: could not find a suitable program for edge type "${type}"!`); @@ -930,8 +936,10 @@ export default class Sigma< this.camera.setState(this.camera.validateState(this.camera.getState())); if (oldSettings) { + const zIndexingUpdated = !!oldSettings.zIndex !== !!settings.zIndex; + // Check edge programs: - if (oldSettings.edgeProgramClasses !== settings.edgeProgramClasses) { + if (zIndexingUpdated || oldSettings.edgeProgramClasses !== settings.edgeProgramClasses) { for (const type in settings.edgeProgramClasses) { if (settings.edgeProgramClasses[type] !== oldSettings.edgeProgramClasses[type]) { this.registerEdgeProgram(type, settings.edgeProgramClasses[type]); @@ -944,6 +952,7 @@ export default class Sigma< // Check node programs: if ( + zIndexingUpdated || oldSettings.nodeProgramClasses !== settings.nodeProgramClasses || oldSettings.nodeHoverProgramClasses !== settings.nodeHoverProgramClasses ) { @@ -1194,6 +1203,8 @@ export default class Sigma< program.render({ matrix: this.matrix, + maxEdgesDepth: this.graph.size + 1, + maxNodesDepth: this.graph.order + 1, width: this.width, height: this.height, pixelRatio: this.pixelRatio, @@ -1291,6 +1302,8 @@ export default class Sigma< const params: RenderParams = { matrix: this.matrix, + maxEdgesDepth: this.graph.size + 1, + maxNodesDepth: this.graph.order + 1, width: this.width, height: this.height, pixelRatio: this.pixelRatio, @@ -1356,12 +1369,6 @@ export default class Sigma< // update this.highlightedNodes.delete(key); if (data.highlighted && !data.hidden) this.highlightedNodes.add(key); - - // zIndex - if (this.settings.zIndex) { - if (data.zIndex < this.nodeZExtent[0]) this.nodeZExtent[0] = data.zIndex; - if (data.zIndex > this.nodeZExtent[1]) this.nodeZExtent[1] = data.zIndex; - } } /** @@ -1387,7 +1394,7 @@ export default class Sigma< delete this.nodeDataCache[key]; // Remove from node program index delete this.nodeProgramIndex[key]; - // Remove from higlighted nodes + // Remove from highlighted nodes this.highlightedNodes.delete(key); // Remove from hovered if (this.hoveredNode === key) this.hoveredNode = null; @@ -1417,12 +1424,6 @@ export default class Sigma< // update this.edgesWithForcedLabels.delete(key); if (data.forceLabel && !data.hidden) this.edgesWithForcedLabels.add(key); - - // Check zIndex - if (this.settings.zIndex) { - if (data.zIndex < this.edgeZExtent[0]) this.edgeZExtent[0] = data.zIndex; - if (data.zIndex > this.edgeZExtent[1]) this.edgeZExtent[1] = data.zIndex; - } } /** @@ -1461,7 +1462,6 @@ export default class Sigma< this.nodeDataCache = {}; this.edgeProgramIndex = {}; this.nodesWithForcedLabels = new Set(); - this.nodeZExtent = [Infinity, -Infinity]; } /** @@ -1472,7 +1472,6 @@ export default class Sigma< this.edgeDataCache = {}; this.edgeProgramIndex = {}; this.edgesWithForcedLabels = new Set(); - this.edgeZExtent = [Infinity, -Infinity]; } /** @@ -1524,7 +1523,7 @@ export default class Sigma< const data = this.nodeDataCache[node]; const nodeProgram = this.nodePrograms[data.type]; if (!nodeProgram) throw new Error(`Sigma: could not find a suitable program for node type "${data.type}"!`); - nodeProgram.process(fingerprint, position, data); + nodeProgram.process(fingerprint, position, { ...data, zIndex: this.nodeDepths[node] }); // Saving program index this.nodeProgramIndex[node] = position; } @@ -1543,7 +1542,7 @@ export default class Sigma< const extremities = this.graph.extremities(edge), sourceData = this.nodeDataCache[extremities[0]], targetData = this.nodeDataCache[extremities[1]]; - edgeProgram.process(fingerprint, position, sourceData, targetData, data); + edgeProgram.process(fingerprint, position, sourceData, targetData, { ...data, zIndex: this.edgeDepths[edge] }); // Saving program index this.edgeProgramIndex[edge] = position; } diff --git a/packages/sigma/src/types.ts b/packages/sigma/src/types.ts index 2e32ecad4..f1b4d7dbe 100644 --- a/packages/sigma/src/types.ts +++ b/packages/sigma/src/types.ts @@ -92,6 +92,8 @@ export interface RenderParams { downSizingRatio: number; minEdgeThickness: number; antiAliasingFeather: number; + maxNodesDepth: number; + maxEdgesDepth: number; } /** diff --git a/packages/sigma/src/utils/misc.ts b/packages/sigma/src/utils/misc.ts index 2d3da75c8..c9be21865 100644 --- a/packages/sigma/src/utils/misc.ts +++ b/packages/sigma/src/utils/misc.ts @@ -1,4 +1,4 @@ -import { Extent, PlainObject } from "../types"; +import { PlainObject } from "../types"; /** * Function used to create DOM elements easily. @@ -35,10 +35,9 @@ export function getPixelRatio(): number { } /** - * Function ordering the given elements in reverse z-order so they drawn - * the correct way. + * Function ordering the given elements in reverse z-order, so they are drawn the correct way. */ -export function zIndexOrdering(_extent: Extent, getter: (e: T) => number, elements: Array): Array { +export function zIndexOrdering(getter: (e: T) => number, elements: Array): Array { // If k is > n, we'll use a standard sort return elements.sort(function (a, b) { const zA = getter(a) || 0,