Skip to content

Commit 6239302

Browse files
authored
feat(graph-viewer): add dag layout configuration controls (visgl#359)
1 parent 45b0da9 commit 6239302

5 files changed

Lines changed: 611 additions & 53 deletions

File tree

examples/graph-layers/graph-viewer/app.tsx

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ import {
3434
D3DagLayout
3535
} from '@deck.gl-community/graph-layers';
3636

37-
import {ControlPanel, ExampleDefinition, LayoutType} from './control-panel';
37+
import {ControlPanel} from './control-panel';
38+
import type {LayoutType, ExampleDefinition} from './layout-options';
3839
import {CollapseControls} from './collapse-controls';
3940
import {StylesheetEditor} from './stylesheet-editor';
4041
import {DEFAULT_EXAMPLE, EXAMPLES} from './examples';
@@ -110,18 +111,30 @@ export function App(props) {
110111
const [selectedExample, setSelectedExample] = useState<ExampleDefinition | undefined>(DEFAULT_EXAMPLE);
111112
const [selectedLayout, setSelectedLayout] = useState<LayoutType>(DEFAULT_LAYOUT);
112113
const [collapseEnabled, setCollapseEnabled] = useState(true);
114+
const [layoutOverrides, setLayoutOverrides] = useState<
115+
Partial<Record<LayoutType, Record<string, unknown>>>
116+
>({});
113117
const [dagChainSummary, setDagChainSummary] = useState<
114118
{chainIds: string[]; collapsedIds: string[]}
115119
| null>(null);
116120

117121
const graphData = useMemo(() => selectedExample?.data(), [selectedExample]);
118-
const layoutOptions = useMemo(
119-
() =>
120-
selectedExample && selectedLayout && graphData
121-
? selectedExample.getLayoutOptions?.(selectedLayout, graphData)
122-
: undefined,
123-
[selectedExample, selectedLayout, graphData]
124-
);
122+
const layoutOptions = useMemo(() => {
123+
if (!selectedExample || !selectedLayout) {
124+
return undefined;
125+
}
126+
127+
const baseOptions = graphData
128+
? selectedExample.getLayoutOptions?.(selectedLayout, graphData)
129+
: undefined;
130+
const overrides = layoutOverrides[selectedLayout];
131+
132+
if (baseOptions && overrides) {
133+
return {...baseOptions, ...overrides};
134+
}
135+
136+
return overrides ?? baseOptions;
137+
}, [selectedExample, selectedLayout, graphData, layoutOverrides]);
125138
const graph = useMemo(() => (graphData ? JSONLoader({json: graphData}) : null), [graphData]);
126139
const layout = useMemo(() => {
127140
if (!selectedLayout) {
@@ -345,6 +358,13 @@ export function App(props) {
345358
setSelectedLayout(layoutType);
346359
}, []);
347360

361+
const handleApplyLayoutOptions = useCallback(
362+
(layoutType: LayoutType, options: Record<string, unknown>) => {
363+
setLayoutOverrides((current) => ({...current, [layoutType]: options}));
364+
},
365+
[]
366+
);
367+
348368
return (
349369
<div
350370
style={{
@@ -435,6 +455,8 @@ export function App(props) {
435455
examples={EXAMPLES}
436456
defaultExample={DEFAULT_EXAMPLE}
437457
onExampleChange={handleExampleChange}
458+
layoutOptions={layoutOptions}
459+
onLayoutOptionsApply={handleApplyLayoutOptions}
438460
>
439461
<>
440462
{isDagLayout ? (

examples/graph-layers/graph-viewer/control-panel.tsx

Lines changed: 45 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,25 @@
44

55
import React, {useCallback, useEffect, useMemo, useState} from 'react';
66
import type {ReactNode} from 'react';
7-
import type {GraphLayerProps} from '@deck.gl-community/graph-layers';
8-
9-
export type LayoutType =
10-
| 'd3-force-layout'
11-
| 'gpu-force-layout'
12-
| 'simple-layout'
13-
| 'radial-layout'
14-
| 'hive-plot-layout'
15-
| 'force-multi-graph-layout'
16-
| 'd3-dag-layout';
17-
18-
export type ExampleStyles = NonNullable<GraphLayerProps['stylesheet']>;
19-
20-
export type ExampleDefinition = {
21-
name: string;
22-
description: string;
23-
data: () => {nodes: unknown[]; edges: unknown[]};
24-
/** First listed layout is the default */
25-
layouts: LayoutType[];
26-
layoutDescriptions: Record<LayoutType, string>;
27-
style: ExampleStyles;
28-
getLayoutOptions?: (
29-
layout: LayoutType,
30-
data: {nodes: unknown[]; edges: unknown[]}
31-
) => Record<string, unknown> | undefined;
32-
};
7+
import type {ExampleDefinition, LayoutType} from './layout-options';
8+
import {LAYOUT_LABELS} from './layout-options';
9+
import {LayoutOptionsPanel} from './layout-options-panel';
3310

3411
type ControlPanelProps = {
3512
examples: ExampleDefinition[];
3613
defaultExample?: ExampleDefinition;
3714
onExampleChange: (example: ExampleDefinition, layout: LayoutType) => void;
3815
children?: ReactNode;
39-
};
40-
41-
const LAYOUT_LABELS: Record<LayoutType, string> = {
42-
'd3-force-layout': 'D3 Force Layout',
43-
'gpu-force-layout': 'GPU Force Layout',
44-
'simple-layout': 'Simple Layout',
45-
'radial-layout': 'Radial Layout',
46-
'hive-plot-layout': 'Hive Plot Layout',
47-
'force-multi-graph-layout': 'Force Multi-Graph Layout',
48-
'd3-dag-layout': 'D3 DAG Layout',
16+
layoutOptions?: Record<string, unknown>;
17+
onLayoutOptionsApply?: (layout: LayoutType, options: Record<string, unknown>) => void;
4918
};
5019

5120
export function ControlPanel({
5221
examples,
5322
defaultExample,
5423
onExampleChange,
24+
layoutOptions,
25+
onLayoutOptionsApply,
5526
children
5627
}: ControlPanelProps) {
5728
const resolveExampleIndex = useCallback(
@@ -74,6 +45,7 @@ export function ControlPanel({
7445
const [selectedLayout, setSelectedLayout] = useState<LayoutType | undefined>(
7546
availableLayouts[0]
7647
);
48+
7749
useEffect(() => {
7850
if (!availableLayouts.length) {
7951
setSelectedLayout(undefined);
@@ -122,6 +94,19 @@ export function ControlPanel({
12294
return selectedExample.layoutDescriptions[selectedLayout];
12395
}, [selectedExample, selectedLayout]);
12496

97+
const styleJson = useMemo(() => {
98+
const styles = selectedExample?.style;
99+
if (!styles) {
100+
return '';
101+
}
102+
103+
return JSON.stringify(
104+
styles,
105+
(_key, value) => (typeof value === 'function' ? value.toString() : value),
106+
2
107+
);
108+
}, [selectedExample]);
109+
125110
if (!examples.length) {
126111
return null;
127112
}
@@ -172,6 +157,25 @@ export function ControlPanel({
172157
</option>
173158
))}
174159
</select>
160+
</div>
161+
{datasetDescription ? (
162+
<section style={{fontSize: '0.875rem', lineHeight: 1.5, color: '#334155'}}>
163+
<h3 style={{margin: '0 0 0.25rem', fontSize: '0.875rem', fontWeight: 600, color: '#0f172a'}}>
164+
Dataset overview
165+
</h3>
166+
<p style={{margin: 0}}>{datasetDescription}</p>
167+
</section>
168+
) : null}
169+
<div
170+
style={{
171+
display: 'grid',
172+
gridTemplateColumns: 'auto 1fr',
173+
gridAutoRows: 'auto',
174+
columnGap: '0.75rem',
175+
rowGap: '0.75rem',
176+
alignItems: 'center'
177+
}}
178+
>
175179
<label htmlFor="graph-viewer-layout" style={{fontSize: '0.875rem', fontWeight: 600, color: '#0f172a'}}>
176180
Layout
177181
</label>
@@ -197,14 +201,11 @@ export function ControlPanel({
197201
))}
198202
</select>
199203
</div>
200-
{datasetDescription ? (
201-
<section style={{fontSize: '0.875rem', lineHeight: 1.5, color: '#334155'}}>
202-
<h3 style={{margin: '0 0 0.25rem', fontSize: '0.875rem', fontWeight: 600, color: '#0f172a'}}>
203-
Dataset overview
204-
</h3>
205-
<p style={{margin: 0}}>{datasetDescription}</p>
206-
</section>
207-
) : null}
204+
<LayoutOptionsPanel
205+
layout={selectedLayout}
206+
appliedOptions={layoutOptions}
207+
onApply={onLayoutOptionsApply}
208+
/>
208209
{children ? (
209210
<section
210211
style={{

examples/graph-layers/graph-viewer/examples.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// Copyright (c) vis.gl contributors
44

55
import {SAMPLE_GRAPH_DATASETS} from '../../../modules/graph-layers/test/data/graphs/sample-datasets';
6-
import type {ExampleDefinition, ExampleStyles, LayoutType} from './control-panel';
6+
import type {ExampleDefinition, ExampleStyles, LayoutType} from './layout-options';
77
import witsRaw from '../../../modules/graph-layers/test/data/examples/wits.json';
88
import sampleMultiGraph from './sample-multi-graph.json';
99

0 commit comments

Comments
 (0)