Skip to content

Commit e7eb3a8

Browse files
committed
Add precompute and zoom to fit on load
1 parent 5686543 commit e7eb3a8

File tree

6 files changed

+69
-38
lines changed

6 files changed

+69
-38
lines changed

src/browser/modules/Stream/CypherFrame/VisualizationView/VisualizationView.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ LIMIT ${maxNewNeighbours}`
277277
updateStyle={this.props.updateStyle}
278278
getNeighbours={this.getNeighbours.bind(this)}
279279
nodes={this.state.nodes}
280+
autocompleteRelationships={this.props.autoComplete ?? false}
280281
relationships={this.state.relationships}
281282
isFullscreen={this.props.isFullscreen}
282283
assignVisElement={this.props.assignVisElement}
@@ -301,6 +302,7 @@ LIMIT ${maxNewNeighbours}`
301302
DetailsPaneOverride={DetailsPane}
302303
OverviewPaneOverride={OverviewPane}
303304
useGeneratedDefaultColors={false}
305+
initialZoomToFit
304306
/>
305307
</StyledVisContainer>
306308
)

src/neo4j-arc/graph-visualization/GraphVisualizer/Graph/Graph.tsx

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export type GraphProps = {
6161
styleVersion: number
6262
onGraphModelChange: (stats: GraphStats) => void
6363
assignVisElement: (svgElement: any, graphElement: any) => void
64+
autocompleteRelationships: boolean
6465
getAutoCompleteCallback: (
6566
callback: (internalRelationships: BasicRelationship[]) => void
6667
) => void
@@ -105,20 +106,21 @@ export class Graph extends React.Component<GraphProps, GraphState> {
105106

106107
componentDidMount(): void {
107108
const {
108-
nodes,
109-
relationships,
110-
graphStyle,
109+
assignVisElement,
110+
autocompleteRelationships,
111+
getAutoCompleteCallback,
111112
getNodeNeighbours,
113+
graphStyle,
114+
initialZoomToFit,
115+
isFullscreen,
116+
nodes,
117+
onGraphInteraction,
118+
onGraphModelChange,
112119
onItemMouseOver,
113120
onItemSelect,
114-
onGraphModelChange,
115-
onGraphInteraction,
121+
relationships,
116122
setGraph,
117-
getAutoCompleteCallback,
118-
assignVisElement,
119-
isFullscreen,
120-
wheelZoomRequiresModKey,
121-
initialZoomToFit
123+
wheelZoomRequiresModKey
122124
} = this.props
123125

124126
if (!this.svgElement.current) return
@@ -137,7 +139,8 @@ export class Graph extends React.Component<GraphProps, GraphState> {
137139
graph,
138140
graphStyle,
139141
isFullscreen,
140-
wheelZoomRequiresModKey
142+
wheelZoomRequiresModKey,
143+
initialZoomToFit
141144
)
142145

143146
const graphEventHandler = new GraphEventHandlerModel(
@@ -153,30 +156,28 @@ export class Graph extends React.Component<GraphProps, GraphState> {
153156

154157
onGraphModelChange(getGraphStats(graph))
155158
this.visualization.resize(isFullscreen, !!wheelZoomRequiresModKey)
156-
if (initialZoomToFit) {
157-
this.visualization.endInitCallback = () => {
158-
setTimeout(() => {
159-
this.visualization?.zoomByType(ZoomType.FIT)
160-
}, 150)
161-
}
162-
}
163-
this.visualization.init()
164159

165160
if (setGraph) {
166161
setGraph(graph)
167162
}
168-
if (getAutoCompleteCallback) {
163+
if (autocompleteRelationships) {
169164
getAutoCompleteCallback((internalRelationships: BasicRelationship[]) => {
165+
this.visualization?.init()
170166
graph.addInternalRelationships(
171167
mapRelationships(internalRelationships, graph)
172168
)
173169
onGraphModelChange(getGraphStats(graph))
174170
this.visualization?.update({
175171
updateNodes: false,
176-
updateRelationships: true
172+
updateRelationships: true,
173+
restartSimulation: false
177174
})
175+
this.visualization?.precomputeAndStart()
178176
graphEventHandler.onItemMouseOut()
179177
})
178+
} else {
179+
this.visualization?.init()
180+
this.visualization?.precomputeAndStart()
180181
}
181182
if (assignVisElement) {
182183
assignVisElement(this.svgElement.current, this.visualization)

src/neo4j-arc/graph-visualization/GraphVisualizer/Graph/visualization/ForceSimulation.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ import {
3636
FORCE_COLLIDE_RADIUS,
3737
FORCE_LINK_DISTANCE,
3838
LINK_DISTANCE,
39-
PRECOMPUTED_TICKS,
40-
TICKS_PER_RENDER,
39+
MAX_PRECOMPUTED_TICKS,
40+
EXTRA_TICKS_PER_RENDER,
4141
VELOCITY_DECAY
4242
} from '../../../constants'
4343
import { GraphModel } from '../../../models/Graph'
@@ -52,14 +52,15 @@ export class ForceSimulation {
5252
simulation: Simulation<NodeModel, RelationshipModel>
5353
simulationTimeout: null | number = null
5454

55-
constructor(private render: () => void) {
55+
constructor(render: () => void) {
5656
this.simulation = forceSimulation<NodeModel, RelationshipModel>()
5757
.velocityDecay(VELOCITY_DECAY)
5858
.force('charge', forceManyBody().strength(FORCE_CHARGE))
5959
.force('centerX', forceX(0).strength(FORCE_CENTER_X))
6060
.force('centerY', forceY(0).strength(FORCE_CENTER_Y))
61+
.alphaMin(DEFAULT_ALPHA_MIN)
6162
.on('tick', () => {
62-
this.simulation.tick(TICKS_PER_RENDER)
63+
this.simulation.tick(EXTRA_TICKS_PER_RENDER)
6364
render()
6465
})
6566
.stop()
@@ -91,12 +92,26 @@ export class ForceSimulation {
9192
)
9293
}
9394

94-
precompute(): void {
95-
this.simulation.stop().tick(PRECOMPUTED_TICKS)
96-
this.render()
95+
precomputeAndStart(onEnd: () => void = () => undefined): void {
96+
this.simulation.stop()
97+
98+
let precomputeTicks = 0
99+
const start = performance.now()
100+
while (
101+
performance.now() - start < 250 &&
102+
precomputeTicks < MAX_PRECOMPUTED_TICKS
103+
) {
104+
this.simulation.tick(1)
105+
precomputeTicks += 1
106+
if (this.simulation.alpha() <= this.simulation.alphaMin()) {
107+
break
108+
}
109+
}
110+
111+
this.simulation.restart().on('end', onEnd)
97112
}
98113

99114
restart(): void {
100-
this.simulation.alphaMin(DEFAULT_ALPHA_MIN).alpha(DEFAULT_ALPHA).restart()
115+
this.simulation.alpha(DEFAULT_ALPHA).restart()
101116
}
102117
}

src/neo4j-arc/graph-visualization/GraphVisualizer/Graph/visualization/Visualization.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,17 @@ export class Visualization {
7070
// 'canvasClick' event when panning ends.
7171
private draw = false
7272
private isZoomClick = false
73-
public endInitCallback: null | (() => void) = null
7473

7574
constructor(
7675
element: SVGElement,
7776
private measureSize: MeasureSizeFn,
78-
private onZoomEvent: (limitsReached: ZoomLimitsReached) => void,
79-
private onDisplayZoomWheelInfoMessage: () => void,
77+
onZoomEvent: (limitsReached: ZoomLimitsReached) => void,
78+
onDisplayZoomWheelInfoMessage: () => void,
8079
private graph: GraphModel,
8180
public style: GraphStyleModel,
8281
public isFullscreen: boolean,
83-
public wheelZoomRequiresModKey?: boolean
82+
public wheelZoomRequiresModKey?: boolean,
83+
private initialZoomToFit?: boolean
8484
) {
8585
this.root = d3Select(element)
8686

@@ -330,12 +330,23 @@ export class Visualization {
330330

331331
this.updateNodes()
332332
this.updateRelationships()
333-
this.forceSimulation.precompute()
334333

335334
this.adjustZoomMinScaleExtentToFitGraph()
335+
this.setInitialZoom()
336+
}
337+
338+
setInitialZoom(): void {
339+
const count = this.graph.nodes().length
336340

337-
this.endInitCallback && this.endInitCallback()
338-
this.endInitCallback = null
341+
// chosen by *feel* (graph fitting guesstimate)
342+
const scale = -0.02364554 + 1.913 / (1 + (count / 12.7211) ** 0.8156444)
343+
this.zoomBehavior.scaleBy(this.root, Math.max(0, scale))
344+
}
345+
346+
precomputeAndStart(): void {
347+
this.forceSimulation.precomputeAndStart(
348+
() => this.initialZoomToFit && this.zoomByType(ZoomType.FIT)
349+
)
339350
}
340351

341352
update(options: {

src/neo4j-arc/graph-visualization/GraphVisualizer/GraphVisualizer.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ type GraphVisualizerProps = GraphVisualizerDefaultProps & {
8484
OverviewPaneOverride?: React.FC<OverviewPaneProps>
8585
onGraphInteraction?: GraphInteractionCallBack
8686
useGeneratedDefaultColors?: boolean
87+
autocompleteRelationships: boolean
8788
}
8889

8990
type GraphVisualizerState = {
@@ -265,6 +266,7 @@ export class GraphVisualizer extends Component<
265266
onGraphModelChange={this.onGraphModelChange.bind(this)}
266267
assignVisElement={this.props.assignVisElement}
267268
getAutoCompleteCallback={this.props.getAutoCompleteCallback}
269+
autocompleteRelationships={this.props.autocompleteRelationships}
268270
setGraph={this.props.setGraph}
269271
offset={
270272
(this.state.nodePropertiesExpanded ? this.state.width + 8 : 0) + 8

src/neo4j-arc/graph-visualization/constants.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
import { NodeModel } from './models/Node'
2121
import { RelationshipModel } from './models/Relationship'
2222

23-
export const PRECOMPUTED_TICKS = 300
24-
export const TICKS_PER_RENDER = 10
23+
export const MAX_PRECOMPUTED_TICKS = 300
24+
export const EXTRA_TICKS_PER_RENDER = 10
2525

2626
// Friction.
2727
export const VELOCITY_DECAY = 0.4

0 commit comments

Comments
 (0)