import { LabelRenderer } from '../renderers/labels/LabelRenderer'; import { CircleRenderer } from '../renderers/shapes/CircleRenderer'; import { RectangleRenderer } from '../renderers/shapes/RectangleRenderer'; import { ShapeRenderer } from '../renderers/shapes/ShapeRenderer'; import { NodeStyle } from '../styles/NodeStyle'; import { isCircleStyle, isRectangleStyle } from '../styles/ShapeStyle'; import { parseLabelStyles, parseShapeStyles } from '../styles/StyleParsers'; import { Node } from '../types/Node'; class NodeManager { private _nodes: Map; private _styles: NodeStyle[]; private _labels: LabelRenderer[]; private _shapes: ShapeRenderer[]; constructor () { this._nodes = new Map(); this._labels = []; this._shapes = []; this._styles = []; } labelRenderers (): LabelRenderer[] { return this._labels; } nodes (): Node[]; nodes (nodes: (Node | string)[]): this; nodes (nodes?: (Node | string)[]): Node[] | this { // Return the array nodes if (!nodes) return Array.from(this._nodes.values()); // Clear the node map this._nodes = new Map(); // Make sure all nodes are node objects const nodeobjs = nodes.map(node => { if (typeof node === 'string') { return { id: node, x: 0, y: 0 } } return node; }); // Add all nodes to node map nodeobjs.forEach(node => { if (this._nodes.has(node.id)) throw Error(`Duplicate node ${node.id}`); this._nodes.set(node.id, node); }); return this; } shapeRenderers (): ShapeRenderer[] { return this._shapes; } setStyles (styles: NodeStyle[]): this { this._styles = styles; return this; } update (): void { // Recalculate the renderers this._shapes = this._calculateShapeRenderers(); this._labels = this._calculateLabelRenderers(); } private _calculateLabelRenderers (): LabelRenderer[] { // An array to store renderers const renderers: LabelRenderer[] = []; // Create a map of node IDs to nodes const nodemap = new Map(this._nodes); // Flatten styles and build renderers this._styles.forEach(style => { parseLabelStyles(style).forEach((nodes, labelStyle) => { // Create the renderer let labelRenderer = new LabelRenderer(labelStyle); // Extract nodes that haven't already been assigned to a renderer const available: Node[] = extractNodes(nodes, nodemap); // Set the nodes that the renderer will render labelRenderer.nodes(available); // Add the renderer to the list renderers.push(labelRenderer); }); }); // Create a default renderer for those not yet assigned if (nodemap.size > 0) { const defaultRenderer = new LabelRenderer(); const remaining = Array.from(nodemap.values()); defaultRenderer.nodes(remaining); renderers.push(defaultRenderer); } return renderers; } private _calculateShapeRenderers (): ShapeRenderer[] { // An array to store renderers const renderers: ShapeRenderer[] = []; // Create a map of node IDs to nodes const nodemap = new Map(this._nodes); // Flatten styles and build renderers this._styles.forEach(style => { parseShapeStyles(style).forEach((nodes, shapeStyle) => { // Create the node renderer let shapeRenderer: ShapeRenderer; if (isCircleStyle(shapeStyle)) shapeRenderer = new CircleRenderer(shapeStyle); else if (isRectangleStyle(shapeStyle)) shapeRenderer = new RectangleRenderer(shapeStyle); else shapeRenderer = new CircleRenderer(shapeStyle); // Extract nodes that haven't already been assigned to a renderer const available: Node[] = extractNodes(nodes, nodemap); // Set the nodes that the renderer will render shapeRenderer.nodes(available); // Add the renderer to the list renderers.push(shapeRenderer); }); }); // Create a default renderer for those not yet assigned if (nodemap.size > 0) { const defaultRenderer = new CircleRenderer(); const remaining = Array.from(nodemap.values()); defaultRenderer.nodes(remaining); renderers.push(defaultRenderer); } return renderers; } } function extractNodes (names: string[], nodemap: Map): Node[] { const available: Node[] = []; names.forEach(name => { if (nodemap.has(name)) { available.push(nodemap.get(name)!); nodemap.delete(name); } }); return available; } export { NodeManager }