@visuallyjs/browser-ui-svelte 1.2.6 → 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AreaChartComponent.svelte +57 -31
- package/BarChartComponent.svelte +55 -32
- package/BubbleChartComponent.svelte +55 -33
- package/ColumnChartComponent.svelte +55 -33
- package/EdgePropertyMappingsInspector.svelte +69 -0
- package/EdgeTypePickerComponent.svelte +4 -3
- package/LineChartComponent.svelte +56 -32
- package/PieChartComponent.svelte +55 -35
- package/SankeyChartComponent.svelte +57 -29
- package/ScatterChartComponent.svelte +55 -33
- package/ShapePropertiesInspector.svelte +193 -0
- package/XYChartComponent.svelte +55 -36
- package/components.d.ts +2 -0
- package/components.js +2 -0
- package/definitions.d.ts +42 -7
- package/package.json +1 -1
package/PieChartComponent.svelte
CHANGED
|
@@ -1,51 +1,71 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { PieChart, type BrowserElement, type PieChartOptions } from "@visuallyjs/browser-ui";
|
|
3
|
+
import type { PieChartComponentProps } from "./charts/definitions";
|
|
4
|
+
import { useVisuallyJsModel } from "./surface-store";
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
const { options, className = "", data, url, dataSourceFilter }: PieChartComponentProps = $props();
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {useVisuallyJsModel} from "./surface-store";
|
|
8
|
+
let container: BrowserElement;
|
|
9
|
+
let chart: PieChart | undefined = $state();
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
let {options, className = "", data, url, dataSourceFilter }:PieChartComponentProps = $props();
|
|
12
|
-
|
|
13
|
-
let chart:PieChart;
|
|
14
|
-
|
|
15
|
-
export function getChart() {
|
|
16
|
-
return chart;
|
|
11
|
+
export function getChart() {
|
|
12
|
+
return chart;
|
|
17
13
|
}
|
|
18
14
|
|
|
15
|
+
// A single, unified effect to manage the chart's entire lifecycle.
|
|
16
|
+
// This effect re-runs when props that require recreation, like `options`, change.
|
|
19
17
|
$effect(() => {
|
|
20
|
-
if (
|
|
21
|
-
chart.setDataSourceFilter(dataSourceFilter)
|
|
22
|
-
}
|
|
23
|
-
})
|
|
18
|
+
if (!container) return;
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
let chartInstance: PieChart;
|
|
21
|
+
const chartOpts: PieChartOptions = { ...options, dataSourceFilter };
|
|
22
|
+
let isDestroyed = false;
|
|
27
23
|
|
|
28
|
-
if
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
// Initialize the chart. This can be async if we need to fetch a model.
|
|
25
|
+
if (url == null && data == null) {
|
|
26
|
+
useVisuallyJsModel().then(m => {
|
|
27
|
+
if (!isDestroyed) { // Ensure component hasn't been destroyed in the meantime
|
|
28
|
+
chartOpts.dataSource = m;
|
|
29
|
+
chartInstance = new PieChart(container, chartOpts);
|
|
30
|
+
chart = chartInstance;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
// Synchronous initialization with `data` or `url` props.
|
|
35
|
+
chartInstance = new PieChart(container, chartOpts);
|
|
36
|
+
chart = chartInstance;
|
|
32
37
|
}
|
|
33
|
-
}
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
// This nested effect runs ONLY when the `data` prop changes.
|
|
40
|
+
// It loads the new data into the existing chart without recreating it.
|
|
41
|
+
$effect(() => {
|
|
42
|
+
if (chartInstance && data != null) {
|
|
43
|
+
chartInstance.load({ data });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
// This nested effect handles updates to the `url` prop.
|
|
48
|
+
$effect(() => {
|
|
49
|
+
if (chartInstance && url != null) {
|
|
50
|
+
chartInstance.load({ url });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
38
53
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
// This nested effect handles updates to the filter.
|
|
55
|
+
$effect(() => {
|
|
56
|
+
if (chartInstance && dataSourceFilter != null) {
|
|
57
|
+
chartInstance.setDataSourceFilter(dataSourceFilter);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// The cleanup function for the main effect. It runs when the component
|
|
62
|
+
// is unmounted or when `options` change, ensuring the old chart is destroyed.
|
|
63
|
+
return () => {
|
|
64
|
+
isDestroyed = true;
|
|
65
|
+
chartInstance?.destroy();
|
|
66
|
+
chart = undefined;
|
|
67
|
+
};
|
|
68
|
+
});
|
|
49
69
|
|
|
50
70
|
</script>
|
|
51
71
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
|
|
3
|
-
import { onMount } from "svelte";
|
|
4
|
-
|
|
5
3
|
import {
|
|
6
4
|
SankeyChart,
|
|
7
5
|
type BrowserElement,
|
|
@@ -16,49 +14,79 @@
|
|
|
16
14
|
|
|
17
15
|
let chart:SankeyChart|null = $state(null);
|
|
18
16
|
|
|
19
|
-
$effect(() => {
|
|
20
|
-
if (pivot != null && chart != null) {
|
|
21
|
-
chart.pivot(pivot as string)
|
|
22
|
-
}
|
|
23
|
-
})
|
|
24
|
-
|
|
25
17
|
export function getChart() {
|
|
26
18
|
return chart;
|
|
27
19
|
}
|
|
28
20
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
chart = new SankeyChart(container, opts)
|
|
32
|
-
|
|
33
|
-
if (url != null) {
|
|
34
|
-
chart.load({url});
|
|
35
|
-
} else if (csvData != null) {
|
|
36
|
-
chart.load({csvData});
|
|
37
|
-
} else if (jsonData != null) {
|
|
38
|
-
chart.load({jsonData})
|
|
39
|
-
}
|
|
40
|
-
}
|
|
21
|
+
$effect(() => {
|
|
22
|
+
if (!container) return;
|
|
41
23
|
|
|
24
|
+
let chartInstance: SankeyChart;
|
|
25
|
+
const _options: SankeyOptions = { ...options };
|
|
42
26
|
|
|
43
|
-
onMount(async () => {
|
|
44
|
-
const _options:SankeyOptions = Object.assign({}, options)
|
|
45
27
|
if (interactive != null) {
|
|
46
|
-
_options.interactive = interactive
|
|
28
|
+
_options.interactive = interactive;
|
|
47
29
|
}
|
|
48
30
|
if (pivot != null) {
|
|
49
|
-
_options.pivot = pivot
|
|
31
|
+
_options.pivot = pivot;
|
|
50
32
|
}
|
|
51
33
|
|
|
34
|
+
let isDestroyed = false;
|
|
35
|
+
|
|
36
|
+
const setupChart = () => {
|
|
37
|
+
chartInstance = new SankeyChart(container, _options);
|
|
38
|
+
chart = chartInstance;
|
|
39
|
+
|
|
40
|
+
if (url != null) {
|
|
41
|
+
chartInstance.load({url});
|
|
42
|
+
} else if (csvData != null) {
|
|
43
|
+
chartInstance.load({csvData});
|
|
44
|
+
} else if (jsonData != null) {
|
|
45
|
+
chartInstance.load({jsonData})
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
52
49
|
if (url == null && csvData == null && jsonData == null) {
|
|
53
50
|
useVisuallyJsModel().then(m => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
if (!isDestroyed) {
|
|
52
|
+
_options.dataSource = m;
|
|
53
|
+
setupChart();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
57
56
|
} else {
|
|
58
|
-
|
|
57
|
+
setupChart();
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
$effect(() => {
|
|
61
|
+
if (chartInstance && pivot != null) {
|
|
62
|
+
chartInstance.pivot(pivot as string);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
$effect(() => {
|
|
67
|
+
if (chartInstance && url != null) {
|
|
68
|
+
chartInstance.load({ url });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
$effect(() => {
|
|
73
|
+
if (chartInstance && csvData != null) {
|
|
74
|
+
chartInstance.load({ csvData });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
$effect(() => {
|
|
79
|
+
if (chartInstance && jsonData != null) {
|
|
80
|
+
chartInstance.load({ jsonData });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return () => {
|
|
85
|
+
isDestroyed = true;
|
|
86
|
+
chartInstance?.destroy();
|
|
87
|
+
chart = null;
|
|
88
|
+
};
|
|
89
|
+
});
|
|
62
90
|
|
|
63
91
|
</script>
|
|
64
92
|
|
|
@@ -1,49 +1,71 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { ScatterChart, type BrowserElement, type ScatterChartOptions } from "@visuallyjs/browser-ui";
|
|
3
|
+
import type { ScatterChartComponentProps } from "./charts/definitions";
|
|
4
|
+
import { useVisuallyJsModel } from "./surface-store";
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
const { options, className = "", data, url, dataSourceFilter }: ScatterChartComponentProps = $props();
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {useVisuallyJsModel} from "./surface-store";
|
|
8
|
+
let container: BrowserElement;
|
|
9
|
+
let chart: ScatterChart | undefined = $state();
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// ScatterChartOptions
|
|
12
|
-
let {options, className = "", data, url, dataSourceFilter }:ScatterChartComponentProps = $props();
|
|
13
|
-
|
|
14
|
-
let chart:ScatterChart;
|
|
15
|
-
|
|
16
|
-
export function getChart() {
|
|
17
|
-
return chart;
|
|
11
|
+
export function getChart() {
|
|
12
|
+
return chart;
|
|
18
13
|
}
|
|
19
14
|
|
|
15
|
+
// A single, unified effect to manage the chart's entire lifecycle.
|
|
16
|
+
// This effect re-runs when props that require recreation, like `options`, change.
|
|
20
17
|
$effect(() => {
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
function init(opts:ScatterChartOptions) {
|
|
27
|
-
chart = new ScatterChart(container, opts)
|
|
28
|
-
if (url != null) {
|
|
29
|
-
chart.load({url});
|
|
30
|
-
} else if (data != null) {
|
|
31
|
-
chart.load({data});
|
|
32
|
-
}
|
|
33
|
-
}
|
|
18
|
+
if (!container) return;
|
|
19
|
+
|
|
20
|
+
let chartInstance: ScatterChart;
|
|
21
|
+
const chartOpts: ScatterChartOptions = { ...options, dataSourceFilter };
|
|
22
|
+
let isDestroyed = false;
|
|
34
23
|
|
|
35
|
-
|
|
36
|
-
const opts:ScatterChartOptions = Object.assign({dataSourceFilter}, options)
|
|
24
|
+
// Initialize the chart. This can be async if we need to fetch a model.
|
|
37
25
|
if (url == null && data == null) {
|
|
38
26
|
useVisuallyJsModel().then(m => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
27
|
+
if (!isDestroyed) { // Ensure component hasn't been destroyed in the meantime
|
|
28
|
+
chartOpts.dataSource = m;
|
|
29
|
+
chartInstance = new ScatterChart(container, chartOpts);
|
|
30
|
+
chart = chartInstance;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
42
33
|
} else {
|
|
43
|
-
|
|
34
|
+
// Synchronous initialization with `data` or `url` props.
|
|
35
|
+
chartInstance = new ScatterChart(container, chartOpts);
|
|
36
|
+
chart = chartInstance;
|
|
44
37
|
}
|
|
45
|
-
})
|
|
46
38
|
|
|
39
|
+
// This nested effect runs ONLY when the `data` prop changes.
|
|
40
|
+
// It loads the new data into the existing chart without recreating it.
|
|
41
|
+
$effect(() => {
|
|
42
|
+
if (chartInstance && data != null) {
|
|
43
|
+
chartInstance.load({ data });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// This nested effect handles updates to the `url` prop.
|
|
48
|
+
$effect(() => {
|
|
49
|
+
if (chartInstance && url != null) {
|
|
50
|
+
chartInstance.load({ url });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// This nested effect handles updates to the filter.
|
|
55
|
+
$effect(() => {
|
|
56
|
+
if (chartInstance && dataSourceFilter != null) {
|
|
57
|
+
chartInstance.setDataSourceFilter(dataSourceFilter);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// The cleanup function for the main effect. It runs when the component
|
|
62
|
+
// is unmounted or when `options` change, ensuring the old chart is destroyed.
|
|
63
|
+
return () => {
|
|
64
|
+
isDestroyed = true;
|
|
65
|
+
chartInstance?.destroy();
|
|
66
|
+
chart = undefined;
|
|
67
|
+
};
|
|
68
|
+
});
|
|
47
69
|
|
|
48
70
|
</script>
|
|
49
71
|
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
isNode,
|
|
4
|
+
isString,
|
|
5
|
+
EVENT_CHANGE,
|
|
6
|
+
type ShapePropertyValueDefinition,
|
|
7
|
+
type Vertex,
|
|
8
|
+
type Inspector,
|
|
9
|
+
log
|
|
10
|
+
} from "@visuallyjs/browser-ui";
|
|
11
|
+
import {onDestroy, onMount} from "svelte";
|
|
12
|
+
import type {ShapePropertiesInspectorProps} from "./definitions";
|
|
13
|
+
import {getInspectorContext} from "./inspector-store";
|
|
14
|
+
import ColorPickerComponent from "./ColorPickerComponent.svelte";
|
|
15
|
+
|
|
16
|
+
const MAX_RADIOS = 7;
|
|
17
|
+
const TRUE_FALSE = ["True", "False"]
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
vertex,
|
|
21
|
+
maxRadios = MAX_RADIOS
|
|
22
|
+
}: ShapePropertiesInspectorProps = $props()
|
|
23
|
+
|
|
24
|
+
let inspector: Inspector | null = $state(null)
|
|
25
|
+
let shapeDef = $state(null)
|
|
26
|
+
let inspectorRevision = $state(0)
|
|
27
|
+
let inspectorChangeListener: (() => void) | null = null
|
|
28
|
+
|
|
29
|
+
function getBooleanLabel(prop: any, value: boolean) {
|
|
30
|
+
const labels = prop.booleanLabels || TRUE_FALSE
|
|
31
|
+
return labels[value ? 0 : 1]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getShapePropertyValueLabel(v: string | ShapePropertyValueDefinition): string {
|
|
35
|
+
return isString(v) ? v : (v as ShapePropertyValueDefinition).label
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getShapePropertyValueValue(v: string | ShapePropertyValueDefinition): string {
|
|
39
|
+
return isString(v) ? v : (v as ShapePropertyValueDefinition).value
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getPropertyDisplayValue(prop: any): string {
|
|
43
|
+
// Reading this state makes the rendered value refresh on inspector changes.
|
|
44
|
+
inspectorRevision
|
|
45
|
+
let value: any = inspector?.getValue(prop.id)
|
|
46
|
+
if (value == null && vertex) {
|
|
47
|
+
value = prop.id.split(".").reduce((current: any, part: string) => current == null ? current : current[part], vertex.data)
|
|
48
|
+
}
|
|
49
|
+
if (value == null) {
|
|
50
|
+
value = prop.defaultValue
|
|
51
|
+
}
|
|
52
|
+
if (value == null) {
|
|
53
|
+
return ""
|
|
54
|
+
}
|
|
55
|
+
if (typeof value === "object") {
|
|
56
|
+
return JSON.stringify(value) || ""
|
|
57
|
+
}
|
|
58
|
+
return String(value)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveShapeDef(insp: Inspector, v: Vertex) {
|
|
62
|
+
if (!insp || !v || !isNode(v)) return null
|
|
63
|
+
const {type, category} = v.data;
|
|
64
|
+
if (!type || !category) return null;
|
|
65
|
+
|
|
66
|
+
const shapeLibrary = insp.$ui.getShapeLibrary();
|
|
67
|
+
const shapeSet = shapeLibrary.getShapeSet(category);
|
|
68
|
+
if (!shapeSet) return null;
|
|
69
|
+
return shapeSet.shapes.find(s => s.type === type)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
onMount(() => {
|
|
73
|
+
const ic = getInspectorContext(true)
|
|
74
|
+
if (ic == null) {
|
|
75
|
+
log(`WARN: ShapePropertiesInspector cannot find an inspector context; cannot mount.`)
|
|
76
|
+
} else {
|
|
77
|
+
ic.listen(i => {
|
|
78
|
+
if (inspector && inspectorChangeListener) {
|
|
79
|
+
inspector.unbind(EVENT_CHANGE, inspectorChangeListener)
|
|
80
|
+
}
|
|
81
|
+
inspector = i
|
|
82
|
+
inspectorChangeListener = () => inspectorRevision++
|
|
83
|
+
inspector.bind(EVENT_CHANGE, inspectorChangeListener)
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
onDestroy(() => {
|
|
89
|
+
if (inspector && inspectorChangeListener) {
|
|
90
|
+
inspector.unbind(EVENT_CHANGE, inspectorChangeListener)
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
$effect(() => {
|
|
95
|
+
if (inspector && vertex) {
|
|
96
|
+
shapeDef = resolveShapeDef(inspector, vertex)
|
|
97
|
+
} else {
|
|
98
|
+
shapeDef = null
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
{#if shapeDef && shapeDef.properties}
|
|
105
|
+
{#each shapeDef.properties as prop (prop.id)}
|
|
106
|
+
{@const isBoolean = prop.type === 'boolean'}
|
|
107
|
+
{@const hasValues = prop.values && prop.values.length > 0}
|
|
108
|
+
{@const useRadio = hasValues && prop.editor !== 'select' && prop.values.length <= maxRadios}
|
|
109
|
+
|
|
110
|
+
<div class="vjs-inspector-field">
|
|
111
|
+
{#if prop.readOnly || !isBoolean || prop.editor === 'select' || prop.editor === 'radio'}
|
|
112
|
+
<label class="vjs-inspector-property-label">{prop.label || prop.id}</label>
|
|
113
|
+
{/if}
|
|
114
|
+
|
|
115
|
+
{#if prop.readOnly}
|
|
116
|
+
<output class="vjs-inspector-property-value">{getPropertyDisplayValue(prop)}</output>
|
|
117
|
+
{:else if prop.type === 'string'}
|
|
118
|
+
{#if hasValues}
|
|
119
|
+
{#if useRadio}
|
|
120
|
+
<div class="vjs-radio-group">
|
|
121
|
+
{#each prop.values as v (getShapePropertyValueLabel(v))}
|
|
122
|
+
<label>
|
|
123
|
+
<input type="radio" name={prop.id} vjs-att={prop.id} value={getShapePropertyValueValue(v)} /> {getShapePropertyValueLabel(v)}
|
|
124
|
+
</label>
|
|
125
|
+
{/each}
|
|
126
|
+
</div>
|
|
127
|
+
{:else}
|
|
128
|
+
<select vjs-att={prop.id}>
|
|
129
|
+
{#each prop.values as v (getShapePropertyValueLabel(v))}
|
|
130
|
+
<option value={getShapePropertyValueValue(v)}>{getShapePropertyValueLabel(v)}</option>
|
|
131
|
+
{/each}
|
|
132
|
+
</select>
|
|
133
|
+
{/if}
|
|
134
|
+
{:else}
|
|
135
|
+
<input type="text" vjs-att={prop.id} placeholder={prop.description} />
|
|
136
|
+
{/if}
|
|
137
|
+
{:else if prop.type === 'text'}
|
|
138
|
+
<textarea rows="5" vjs-att={prop.id} placeholder={prop.description}></textarea>
|
|
139
|
+
{:else if prop.type === 'color'}
|
|
140
|
+
<ColorPickerComponent propertyName={prop.id} />
|
|
141
|
+
{:else}
|
|
142
|
+
{#if prop.type === 'number'}
|
|
143
|
+
{#if hasValues}
|
|
144
|
+
{#if useRadio}
|
|
145
|
+
<div class="vjs-radio-group">
|
|
146
|
+
{#each prop.values as v (getShapePropertyValueLabel(v))}
|
|
147
|
+
<label>
|
|
148
|
+
<input type="radio" name={prop.id} vjs-att={prop.id} value={getShapePropertyValueValue(v)} /> {getShapePropertyValueLabel(v)}
|
|
149
|
+
</label>
|
|
150
|
+
{/each}
|
|
151
|
+
</div>
|
|
152
|
+
{:else}
|
|
153
|
+
<select vjs-att={prop.id}>
|
|
154
|
+
{#each prop.values as v (getShapePropertyValueLabel(v))}
|
|
155
|
+
<option value={getShapePropertyValueValue(v)}>{getShapePropertyValueLabel(v)}</option>
|
|
156
|
+
{/each}
|
|
157
|
+
</select>
|
|
158
|
+
{/if}
|
|
159
|
+
{:else}
|
|
160
|
+
<input type="number" vjs-att={prop.id} placeholder={prop.description} min={prop.min} max={prop.max} />
|
|
161
|
+
{/if}
|
|
162
|
+
{:else}
|
|
163
|
+
{#if isBoolean}
|
|
164
|
+
{#if prop.editor === 'select'}
|
|
165
|
+
<select vjs-att={prop.id} vjs-datatype="boolean">
|
|
166
|
+
<option value=""></option>
|
|
167
|
+
<option value="true">{getBooleanLabel(prop, true)}</option>
|
|
168
|
+
<option value="false">{getBooleanLabel(prop, false)}</option>
|
|
169
|
+
</select>
|
|
170
|
+
{:else}
|
|
171
|
+
{#if prop.editor === 'radio'}
|
|
172
|
+
<div class="vjs-radio-group">
|
|
173
|
+
<label><input type="radio" name={prop.id} vjs-att={prop.id} vjs-datatype="boolean" value="true" /><span>{getBooleanLabel(prop, true)}</span></label>
|
|
174
|
+
<label><input type="radio" name={prop.id} vjs-att={prop.id} vjs-datatype="boolean" value="false" /><span>{getBooleanLabel(prop, false)}</span></label>
|
|
175
|
+
</div>
|
|
176
|
+
{:else}
|
|
177
|
+
<label>
|
|
178
|
+
<input type="checkbox" vjs-datatype="boolean" vjs-att={prop.id} /> {prop.label || prop.id}
|
|
179
|
+
</label>
|
|
180
|
+
{/if}
|
|
181
|
+
{/if}
|
|
182
|
+
{:else}
|
|
183
|
+
<input type="text" vjs-att={prop.id} placeholder={prop.description} />
|
|
184
|
+
{/if}
|
|
185
|
+
{/if}
|
|
186
|
+
{/if}
|
|
187
|
+
|
|
188
|
+
{#if prop.description}
|
|
189
|
+
<div class="vjs-field-desc">{prop.description}</div>
|
|
190
|
+
{/if}
|
|
191
|
+
</div>
|
|
192
|
+
{/each}
|
|
193
|
+
{/if}
|
package/XYChartComponent.svelte
CHANGED
|
@@ -1,52 +1,71 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { CategoryValueChart, type BrowserElement, type CategoryValueChartOptions } from "@visuallyjs/browser-ui";
|
|
3
|
+
import type { XYChartComponentProps } from "./charts/definitions";
|
|
4
|
+
import { useVisuallyJsModel } from "./surface-store";
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
const { options, className = "", data, url, dataSourceFilter }: XYChartComponentProps = $props();
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
type CategoryValueChartOptions
|
|
8
|
-
} from "@visuallyjs/browser-ui"
|
|
9
|
-
import {type XYChartComponentProps} from "./charts/definitions";
|
|
10
|
-
import {useVisuallyJsModel} from "./surface-store";
|
|
8
|
+
let container: BrowserElement;
|
|
9
|
+
let chart: CategoryValueChart | undefined = $state();
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// XYChartOptions
|
|
15
|
-
let {options, className = "", data, url, dataSourceFilter }:XYChartComponentProps = $props()
|
|
16
|
-
|
|
17
|
-
let chart:CategoryValueChart
|
|
18
|
-
|
|
19
|
-
export function getChart() {
|
|
20
|
-
return chart;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function init(opts:CategoryValueChartOptions) {
|
|
24
|
-
chart = new CategoryValueChart(container, opts)
|
|
25
|
-
if (url != null) {
|
|
26
|
-
chart.load({url});
|
|
27
|
-
} else if (data != null) {
|
|
28
|
-
chart.load({data});
|
|
29
|
-
}
|
|
11
|
+
export function getChart() {
|
|
12
|
+
return chart;
|
|
30
13
|
}
|
|
31
14
|
|
|
15
|
+
// A single, unified effect to manage the chart's entire lifecycle.
|
|
16
|
+
// This effect re-runs when props that require recreation, like `options`, change.
|
|
32
17
|
$effect(() => {
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
if (!container) return;
|
|
19
|
+
|
|
20
|
+
let chartInstance: CategoryValueChart;
|
|
21
|
+
const chartOpts: CategoryValueChartOptions = { ...options, dataSourceFilter };
|
|
22
|
+
let isDestroyed = false;
|
|
37
23
|
|
|
38
|
-
|
|
39
|
-
const opts:CategoryValueChartOptions = Object.assign({}, options)
|
|
24
|
+
// Initialize the chart. This can be async if we need to fetch a model.
|
|
40
25
|
if (url == null && data == null) {
|
|
41
26
|
useVisuallyJsModel().then(m => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
27
|
+
if (!isDestroyed) { // Ensure component hasn't been destroyed in the meantime
|
|
28
|
+
chartOpts.dataSource = m;
|
|
29
|
+
chartInstance = new CategoryValueChart(container, chartOpts);
|
|
30
|
+
chart = chartInstance;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
45
33
|
} else {
|
|
46
|
-
|
|
34
|
+
// Synchronous initialization with `data` or `url` props.
|
|
35
|
+
chartInstance = new CategoryValueChart(container, chartOpts);
|
|
36
|
+
chart = chartInstance;
|
|
47
37
|
}
|
|
48
|
-
})
|
|
49
38
|
|
|
39
|
+
// This nested effect runs ONLY when the `data` prop changes.
|
|
40
|
+
// It loads the new data into the existing chart without recreating it.
|
|
41
|
+
$effect(() => {
|
|
42
|
+
if (chartInstance && data != null) {
|
|
43
|
+
chartInstance.load({ data });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// This nested effect handles updates to the `url` prop.
|
|
48
|
+
$effect(() => {
|
|
49
|
+
if (chartInstance && url != null) {
|
|
50
|
+
chartInstance.load({ url });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// This nested effect handles updates to the filter.
|
|
55
|
+
$effect(() => {
|
|
56
|
+
if (chartInstance && dataSourceFilter != null) {
|
|
57
|
+
chartInstance.setDataSourceFilter(dataSourceFilter);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// The cleanup function for the main effect. It runs when the component
|
|
62
|
+
// is unmounted or when `options` change, ensuring the old chart is destroyed.
|
|
63
|
+
return () => {
|
|
64
|
+
isDestroyed = true;
|
|
65
|
+
chartInstance?.destroy();
|
|
66
|
+
chart = undefined;
|
|
67
|
+
};
|
|
68
|
+
});
|
|
50
69
|
|
|
51
70
|
</script>
|
|
52
71
|
|
package/components.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export { default as ShapePaletteComponent } from "./ShapePaletteComponent.svelte
|
|
|
14
14
|
export { default as DiagramPaletteComponent } from "./DiagramPaletteComponent.svelte";
|
|
15
15
|
export { default as ShapeComponent } from "./ShapeComponent.svelte";
|
|
16
16
|
export { default as EdgeTypePickerComponent } from "./EdgeTypePickerComponent.svelte";
|
|
17
|
+
export { default as EdgePropertyMappingsInspector } from "./EdgePropertyMappingsInspector.svelte";
|
|
18
|
+
export { default as ShapePropertiesInspector } from "./ShapePropertiesInspector.svelte";
|
|
17
19
|
export { default as ColorPickerComponent } from "./ColorPickerComponent.svelte";
|
|
18
20
|
export { default as SurfacePopup } from "./SurfacePopup.svelte";
|
|
19
21
|
export { default as ColumnChartComponent } from "./ColumnChartComponent.svelte";
|
package/components.js
CHANGED
|
@@ -14,6 +14,8 @@ export { default as ShapePaletteComponent } from "./ShapePaletteComponent.svelte
|
|
|
14
14
|
export { default as DiagramPaletteComponent } from "./DiagramPaletteComponent.svelte";
|
|
15
15
|
export { default as ShapeComponent } from "./ShapeComponent.svelte";
|
|
16
16
|
export { default as EdgeTypePickerComponent } from "./EdgeTypePickerComponent.svelte";
|
|
17
|
+
export { default as EdgePropertyMappingsInspector } from "./EdgePropertyMappingsInspector.svelte";
|
|
18
|
+
export { default as ShapePropertiesInspector } from "./ShapePropertiesInspector.svelte";
|
|
17
19
|
export { default as ColorPickerComponent } from "./ColorPickerComponent.svelte";
|
|
18
20
|
export { default as SurfacePopup } from "./SurfacePopup.svelte";
|
|
19
21
|
export { default as ColumnChartComponent } from "./ColumnChartComponent.svelte";
|