From e4e6755932ac3db4f1eec3d8562dbd03d00d2eb5 Mon Sep 17 00:00:00 2001 From: Andrew Simachev Date: Wed, 1 Nov 2023 11:01:48 +0100 Subject: [PATCH 1/4] JS-3184: local graph --- dist/workers/graph.js | 33 ++++++++++++++++-------- dist/workers/lib/util.js | 4 +++ src/json/text.json | 1 + src/ts/component/menu/graph/settings.tsx | 1 + src/ts/store/common.ts | 2 ++ 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/dist/workers/graph.js b/dist/workers/graph.js index 608f344344..4a3983bb4e 100644 --- a/dist/workers/graph.js +++ b/dist/workers/graph.js @@ -104,7 +104,7 @@ init = (param) => { // Center initially on root node setTimeout(() => { - root = getNodeById(data.rootId); + const root = getNodeById(data.rootId); let x = width / 2; let y = height / 2; @@ -202,11 +202,13 @@ initForces = () => { }; updateForces = () => { - let old = getNodeMap(); + const old = getNodeMap(); edges = util.objectCopy(data.edges); nodes = util.objectCopy(data.nodes); + const root = getNodeById(data.rootId); + // Filter links if (!settings.link) { edges = edges.filter(d => d.type != EdgeType.Link); @@ -217,6 +219,14 @@ updateForces = () => { edges = edges.filter(d => d.type != EdgeType.Relation); }; + if (settings.local && root) { + edges = edges.filter(d => (d.source == root.id) || (d.target == root.id)); + + const nodeIds = util.arrayUnique([].concat(edges.map(d => d.source)).concat(edges.map(d => d.target))); + + nodes = nodes.filter(d => nodeIds.includes(d.id)); + }; + let map = getNodeMap(); edges = edges.filter(d => map.get(d.source) && map.get(d.target)); @@ -258,17 +268,18 @@ updateForces = () => { }; updateSettings = (param) => { - const needUpdate = (param.link != settings.link) || - (param.relation != settings.relation) || - (param.orphan != settings.orphan); + const updateKeys = [ 'link', 'relation', 'orphan', 'local' ]; + + let needUpdate = false; + for (let key of updateKeys) { + if (param[key] != settings[key]) { + needUpdate = true; + break; + }; + }; settings = Object.assign(settings, param); - - if (needUpdate) { - updateForces(); - } else { - redraw(); - }; + needUpdate ? updateForces() : redraw(); }; updateTheme = ({ theme }) => { diff --git a/dist/workers/lib/util.js b/dist/workers/lib/util.js index 6ccf9a0417..9d2cca2928 100644 --- a/dist/workers/lib/util.js +++ b/dist/workers/lib/util.js @@ -78,4 +78,8 @@ class Util { this.ctx.restore(); }; + arrayUnique (a) { + return [ ...new Set(a) ]; + }; + }; \ No newline at end of file diff --git a/src/json/text.json b/src/json/text.json index 235c2f5aa6..37c3d3fc8f 100644 --- a/src/json/text.json +++ b/src/json/text.json @@ -1168,6 +1168,7 @@ "menuGraphSettingsLinks": "Links", "menuGraphSettingsRelations": "Relations", "menuGraphSettingsUnlinkedObjects": "Unlinked Objects", + "menuGraphSettingsLocal": "Local graph", "menuHelpWhatsNew": "What's New", "menuHelpShortcut": "Keyboard Shortcuts", diff --git a/src/ts/component/menu/graph/settings.tsx b/src/ts/component/menu/graph/settings.tsx index 14efe0f4c7..47a9bd66cc 100644 --- a/src/ts/component/menu/graph/settings.tsx +++ b/src/ts/component/menu/graph/settings.tsx @@ -99,6 +99,7 @@ const MenuGraphSettings = observer(class MenuGraphSettings extends React.Compone { id: 'link', name: translate('menuGraphSettingsLinks') }, { id: 'relation', name: translate('menuGraphSettingsRelations') }, { id: 'orphan', name: translate('menuGraphSettingsUnlinkedObjects') }, + { id: 'local', name: translate('menuGraphSettingsLocal') }, ] } ]; diff --git a/src/ts/store/common.ts b/src/ts/store/common.ts index 9e4f098e02..f039aed0f1 100644 --- a/src/ts/store/common.ts +++ b/src/ts/store/common.ts @@ -17,6 +17,7 @@ interface Graph { label: boolean; relation: boolean; link: boolean; + local: boolean; filter: string; }; @@ -62,6 +63,7 @@ class CommonStore { label: true, relation: true, link: true, + local: false, filter: '', }; From 5d460e91985558224d1c686eda880332361d03f3 Mon Sep 17 00:00:00 2001 From: Andrew Simachev Date: Fri, 10 Nov 2023 13:16:07 +0100 Subject: [PATCH 2/4] local graph --- dist/workers/graph.js | 80 +++++++++++++++++++-------------- src/ts/component/util/graph.tsx | 12 +++-- 2 files changed, 55 insertions(+), 37 deletions(-) diff --git a/dist/workers/graph.js b/dist/workers/graph.js index 4b2aca0bee..7b05c27d2e 100644 --- a/dist/workers/graph.js +++ b/dist/workers/graph.js @@ -70,7 +70,9 @@ let edgeMap = new Map(); let hoverAlpha = 0.2; let fontFamily = 'Helvetica, san-serif'; let timeoutHover = 0; +let rootId = ''; let root = null; +let paused = false; addEventListener('message', ({ data }) => { if (this[data.id]) { @@ -82,7 +84,7 @@ init = (param) => { data = param; canvas = data.canvas; settings = data.settings; - + rootId = data.rootId; ctx = canvas.getContext('2d'); util.ctx = ctx; @@ -99,12 +101,12 @@ init = (param) => { initForces(); - simulation.on('tick', () => { redraw(); }); + simulation.on('tick', () => redraw()); simulation.tick(100); // Center initially on root node setTimeout(() => { - const root = getNodeById(data.rootId); + root = getNodeById(rootId); let x = width / 2; let y = height / 2; @@ -207,8 +209,6 @@ updateForces = () => { edges = util.objectCopy(data.edges); nodes = util.objectCopy(data.nodes); - const root = getNodeById(data.rootId); - // Filter links if (!settings.link) { edges = edges.filter(d => d.type != EdgeType.Link); @@ -219,11 +219,11 @@ updateForces = () => { edges = edges.filter(d => d.type != EdgeType.Relation); }; - if (settings.local && root) { - edges = edges.filter(d => (d.source == root.id) || (d.target == root.id)); + // Filte local only edges + if (settings.local) { + edges = edges.filter(d => (d.source == rootId) || (d.target == rootId)); const nodeIds = util.arrayUnique([].concat(edges.map(d => d.source)).concat(edges.map(d => d.target))); - nodes = nodes.filter(d => nodeIds.includes(d.id)); }; @@ -247,7 +247,9 @@ updateForces = () => { edges = edges.filter(d => map.get(d.source) && map.get(d.target)); // Shallow copy to disable mutations - nodes = nodes.map(d => Object.assign(old.get(d.id) || {}, d)); + nodes = nodes.map(d => { + return Object.assign(old.get(d.id) || { x: width / 2, y: width / 2 }, d); + }); edges = edges.map(d => Object.assign({}, d)); simulation.nodes(nodes); @@ -263,8 +265,7 @@ updateForces = () => { edgeMap.set(d.id, [].concat(sources).concat(targets)); }); - simulation.alpha(1).restart(); - redraw(); + simulation.alpha(0.1).restart(); }; updateSettings = (param) => { @@ -300,7 +301,7 @@ draw = (t) => { ctx.font = getFont(); edges.forEach(d => { - drawLine(d, radius, radius * 1.3, settings.marker && d.isDouble, settings.marker); + drawEdge(d, radius, radius * 1.3, settings.marker && d.isDouble, settings.marker); }); nodes.forEach(d => { @@ -314,10 +315,12 @@ draw = (t) => { redraw = () => { cancelAnimationFrame(frame); - frame = requestAnimationFrame(draw); + if (!paused) { + frame = requestAnimationFrame(draw); + }; }; -drawLine = (d, arrowWidth, arrowHeight, arrowStart, arrowEnd) => { +drawEdge = (d, arrowWidth, arrowHeight, arrowStart, arrowEnd) => { const x1 = d.source.x; const y1 = d.source.y; const r1 = getRadius(d.source); @@ -588,6 +591,22 @@ onSelect = ({ x, y, selectRelated }) => { }; }; +onSetRootId = ({ x, y }) => { + const d = getNodeByCoords(x, y); + if (d) { + this.setRootId({ rootId: d.id }); + }; +}; + +onSetEdges = (param) => { + data.edges = param.edges; + updateForces(); +}; + +onSetSelected = ({ ids }) => { + selected = ids; +}; + onMouseMove = ({ x, y }) => { const active = nodes.find(d => d.isOver); const d = getNodeByCoords(x, y); @@ -629,12 +648,11 @@ onContextMenu = ({ x, y }) => { const d = getNodeByCoords(x, y); if (!d) { send('onContextSpaceClick', { x, y }); - return; + } else { + send('onContextMenu', { node: d, x, y }); + d.isOver = true; + redraw(); }; - - d.isOver = true; - send('onContextMenu', { node: d, x, y }); - redraw(); }; onAddNode = ({ target, sourceId }) => { @@ -681,18 +699,10 @@ onRemoveNode = ({ ids }) => { redraw(); }; -onSetEdges = (param) => { - data.edges = param.edges; +setRootId = (param) => { + rootId = param.rootId; + root = getNodeById(rootId); - updateForces(); -}; - -onSetSelected = ({ ids }) => { - selected = ids; -}; - -onSetRootId = ({ rootId }) => { - root = nodes.find(d => d.id == rootId); if (!root) { return; }; @@ -707,12 +717,14 @@ onSetRootId = ({ rootId }) => { transform = Object.assign(transform, coords); redraw(); }) - .onComplete(() => { - send('onTransform', { ...transform }); - }) + .onComplete(() => send('onTransform', { ...transform })) .start(); - redraw(); + if (settings.local) { + updateForces(); + } else { + redraw(); + }; }; restart = (alpha) => { diff --git a/src/ts/component/util/graph.tsx b/src/ts/component/util/graph.tsx index c8bf33bc0e..b45c9ec6c7 100644 --- a/src/ts/component/util/graph.tsx +++ b/src/ts/component/util/graph.tsx @@ -83,7 +83,7 @@ const Graph = observer(class Graph extends React.Component { this.unbind(); win.on('updateGraphSettings.graph', () => { this.updateSettings(); }); - win.on('updateGraphRoot.graph', (e: any, data: any) => { this.setRootId(data.id); }); + win.on('updateGraphRoot.graph', (e: any, data: any) => this.setRootId(data.id)); win.on('updateTheme.graph', () => { this.send('updateTheme', { theme: commonStore.getThemeClass() }); }); }; @@ -138,8 +138,14 @@ const Graph = observer(class Graph extends React.Component { .call(this.zoom) .call(this.zoom.transform, d3.zoomIdentity.translate(0, 0).scale(1.5)) .on('click', (e: any) => { + const { local } = commonStore.graph; const [ x, y ] = d3.pointer(e); - this.send(e.shiftKey ? 'onSelect' : 'onClick', { x, y }); + + if (local) { + this.send('onSetRootId', { x, y }); + } else { + this.send(e.shiftKey ? 'onSelect' : 'onClick', { x, y }); + }; }) .on('dblclick', (e: any) => { if (e.shiftKey) { @@ -464,7 +470,7 @@ const Graph = observer(class Graph extends React.Component { }; setRootId (id: string) { - this.send('onSetRootId', { rootId: id }); + this.send('setRootId', { rootId: id }); }; send (id: string, param: any, transfer?: any[]) { From 3e5188a96abd7fbcad7023195f999c140f486c99 Mon Sep 17 00:00:00 2001 From: Andrew Simachev Date: Sat, 11 Nov 2023 17:10:36 +0100 Subject: [PATCH 3/4] fix nodeIds --- dist/workers/graph.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/workers/graph.js b/dist/workers/graph.js index 7b05c27d2e..760d3a1e99 100644 --- a/dist/workers/graph.js +++ b/dist/workers/graph.js @@ -223,7 +223,7 @@ updateForces = () => { if (settings.local) { edges = edges.filter(d => (d.source == rootId) || (d.target == rootId)); - const nodeIds = util.arrayUnique([].concat(edges.map(d => d.source)).concat(edges.map(d => d.target))); + const nodeIds = util.arrayUnique([ rootId ].concat(edges.map(d => d.source)).concat(edges.map(d => d.target))); nodes = nodes.filter(d => nodeIds.includes(d.id)); }; From 9637bce969c99275a299c65d8cd92eca040f17ca Mon Sep 17 00:00:00 2001 From: Andrew Simachev Date: Sat, 11 Nov 2023 17:27:01 +0100 Subject: [PATCH 4/4] fix graph centering --- dist/workers/graph.js | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/dist/workers/graph.js b/dist/workers/graph.js index 760d3a1e99..76c0731891 100644 --- a/dist/workers/graph.js +++ b/dist/workers/graph.js @@ -15,6 +15,7 @@ const util = new Util(); // CONSTANTS const transformThreshold = 1.5; +const delayFocus = 1000; const ObjectLayout = { Human: 1, @@ -104,22 +105,7 @@ init = (param) => { simulation.on('tick', () => redraw()); simulation.tick(100); - // Center initially on root node - setTimeout(() => { - root = getNodeById(rootId); - - let x = width / 2; - let y = height / 2; - - if (root) { - x = root.x; - y = root.y; - }; - - transform = Object.assign(transform, getCenter(x, y)); - send('onTransform', { ...transform }); - redraw(); - }, 100); + setTimeout(() => this.setRootId({ rootId }), 100); }; initTheme = (theme) => { @@ -265,22 +251,34 @@ updateForces = () => { edgeMap.set(d.id, [].concat(sources).concat(targets)); }); - simulation.alpha(0.1).restart(); + simulation.alpha(1).restart(); + redraw(); }; updateSettings = (param) => { const updateKeys = [ 'link', 'relation', 'orphan', 'local' ]; let needUpdate = false; + let needFocus = false; + for (let key of updateKeys) { if (param[key] != settings[key]) { needUpdate = true; + + if (key == 'local') { + needFocus = true; + }; + break; }; }; settings = Object.assign(settings, param); needUpdate ? updateForces() : redraw(); + + if (needFocus) { + setTimeout(() => this.setRootId({ rootId }), delayFocus); + }; }; updateTheme = ({ theme }) => { @@ -696,7 +694,6 @@ onRemoveNode = ({ ids }) => { data.edges = data.edges.filter(d => !ids.includes(d.source.id) && !ids.includes(d.target.id)); updateForces(); - redraw(); }; setRootId = (param) => {