Skip to content

Commit a4990ea

Browse files
committed
Add precompute and zoom to fit on load
1 parent 4da3269 commit a4990ea

File tree

6 files changed

+73
-36
lines changed

6 files changed

+73
-36
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
@@ -60,6 +60,7 @@ export type GraphProps = {
6060
styleVersion: number
6161
onGraphModelChange: (stats: GraphStats) => void
6262
assignVisElement: (svgElement: any, graphElement: any) => void
63+
autocompleteRelationships: boolean
6364
getAutoCompleteCallback: (
6465
callback: (internalRelationships: BasicRelationship[]) => void
6566
) => void
@@ -94,20 +95,21 @@ export class Graph extends React.Component<GraphProps, GraphState> {
9495

9596
componentDidMount(): void {
9697
const {
97-
nodes,
98-
relationships,
99-
graphStyle,
98+
assignVisElement,
99+
autocompleteRelationships,
100+
getAutoCompleteCallback,
100101
getNodeNeighbours,
102+
graphStyle,
103+
initialZoomToFit,
104+
isFullscreen,
105+
nodes,
106+
onGraphInteraction,
107+
onGraphModelChange,
101108
onItemMouseOver,
102109
onItemSelect,
103-
onGraphModelChange,
104-
onGraphInteraction,
110+
relationships,
105111
setGraph,
106-
getAutoCompleteCallback,
107-
assignVisElement,
108-
isFullscreen,
109-
wheelZoomRequiresModKey,
110-
initialZoomToFit
112+
wheelZoomRequiresModKey
111113
} = this.props
112114

113115
if (!this.svgElement.current) return
@@ -126,7 +128,8 @@ export class Graph extends React.Component<GraphProps, GraphState> {
126128
graph,
127129
graphStyle,
128130
isFullscreen,
129-
wheelZoomRequiresModKey
131+
wheelZoomRequiresModKey,
132+
initialZoomToFit
130133
)
131134

132135
const graphEventHandler = new GraphEventHandlerModel(
@@ -142,30 +145,28 @@ export class Graph extends React.Component<GraphProps, GraphState> {
142145

143146
onGraphModelChange(getGraphStats(graph))
144147
this.visualization.resize(isFullscreen, !!wheelZoomRequiresModKey)
145-
if (initialZoomToFit) {
146-
this.visualization.endInitCallback = () => {
147-
setTimeout(() => {
148-
this.visualization?.zoomByType(ZoomType.FIT)
149-
}, 150)
150-
}
151-
}
152-
this.visualization.init()
153148

154149
if (setGraph) {
155150
setGraph(graph)
156151
}
157-
if (getAutoCompleteCallback) {
152+
if (autocompleteRelationships) {
158153
getAutoCompleteCallback((internalRelationships: BasicRelationship[]) => {
154+
this.visualization?.init()
159155
graph.addInternalRelationships(
160156
mapRelationships(internalRelationships, graph)
161157
)
162158
onGraphModelChange(getGraphStats(graph))
163159
this.visualization?.update({
164160
updateNodes: false,
165-
updateRelationships: true
161+
updateRelationships: true,
162+
restartSimulation: false
166163
})
164+
this.visualization?.precomputeAndStart()
167165
graphEventHandler.onItemMouseOut()
168166
})
167+
} else {
168+
this.visualization?.init()
169+
this.visualization?.precomputeAndStart()
169170
}
170171
if (assignVisElement) {
171172
assignVisElement(this.svgElement.current, this.visualization)

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

Lines changed: 28 additions & 7 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+
PRECOMPUTED_TICKS as MAX_PRECOMPUTED_TICKS,
40+
EXTRA_TICKS_PER_RENDER,
4141
VELOCITY_DECAY
4242
} from '../../../constants'
4343
import { GraphModel } from '../../../models/Graph'
@@ -51,18 +51,23 @@ const oneRelationshipPerPairOfNodes = (graph: GraphModel) =>
5151
export class ForceSimulation {
5252
simulation: Simulation<NodeModel, RelationshipModel>
5353
simulationTimeout: null | number = null
54+
precomputeStart: number | undefined
55+
precomputeTicks: number
5456

5557
constructor(private render: () => void) {
5658
this.simulation = forceSimulation<NodeModel, RelationshipModel>()
5759
.velocityDecay(VELOCITY_DECAY)
5860
.force('charge', forceManyBody().strength(FORCE_CHARGE))
5961
.force('centerX', forceX(0).strength(FORCE_CENTER_X))
6062
.force('centerY', forceY(0).strength(FORCE_CENTER_Y))
63+
.alphaMin(DEFAULT_ALPHA_MIN)
6164
.on('tick', () => {
62-
this.simulation.tick(TICKS_PER_RENDER)
65+
this.simulation.tick(EXTRA_TICKS_PER_RENDER)
6366
render()
6467
})
6568
.stop()
69+
70+
this.precomputeTicks = 0
6671
}
6772

6873
updateNodes(graph: GraphModel): void {
@@ -91,12 +96,28 @@ export class ForceSimulation {
9196
)
9297
}
9398

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

99120
restart(): void {
100-
this.simulation.alphaMin(DEFAULT_ALPHA_MIN).alpha(DEFAULT_ALPHA).restart()
121+
this.simulation.alpha(DEFAULT_ALPHA).restart()
101122
}
102123
}

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

@@ -338,12 +338,23 @@ export class Visualization {
338338

339339
this.updateNodes()
340340
this.updateRelationships()
341-
this.forceSimulation.precompute()
342341

343342
this.adjustZoomMinScaleExtentToFitGraph()
343+
this.setInitialZoom()
344+
}
345+
346+
setInitialZoom(): void {
347+
const count = this.graph.nodes().length
344348

345-
this.endInitCallback && this.endInitCallback()
346-
this.endInitCallback = null
349+
// chosen by *feel* (graph fitting guesstimate)
350+
const scale = -0.02364554 + 1.913 / (1 + (count / 12.7211) ** 0.8156444)
351+
this.zoomBehavior.scaleBy(this.root, Math.max(0, scale))
352+
}
353+
354+
precomputeAndStart(): void {
355+
this.forceSimulation.precomputeAndStart(
356+
() => this.initialZoomToFit && this.zoomByType(ZoomType.FIT)
357+
)
347358
}
348359

349360
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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { NodeModel } from './models/Node'
2121
import { RelationshipModel } from './models/Relationship'
2222

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

2626
// Friction.
2727
export const VELOCITY_DECAY = 0.4

0 commit comments

Comments
 (0)