@rokkit/chart 1.3.6 → 1.3.7
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/dist/Plot/Axis.svelte +2 -0
- package/dist/Plot/Grid.svelte +32 -13
- package/dist/Plot/Grid.svelte.d.ts +6 -1
- package/dist/Plot.svelte +51 -4
- package/dist/Plot.svelte.d.ts +14 -2
- package/dist/PlotState.svelte.d.ts +7 -0
- package/dist/PlotState.svelte.js +37 -0
- package/dist/charts/AreaChart.svelte +33 -3
- package/dist/charts/AreaChart.svelte.d.ts +11 -2
- package/dist/charts/LineChart.svelte +34 -3
- package/dist/charts/LineChart.svelte.d.ts +11 -2
- package/dist/geoms/Area.svelte +18 -1
- package/dist/geoms/Bar.svelte +40 -7
- package/dist/geoms/Highlight.svelte +105 -0
- package/dist/geoms/Highlight.svelte.d.ts +10 -0
- package/dist/geoms/Line.svelte +25 -8
- package/dist/geoms/Point.svelte +30 -10
- package/dist/geoms/Trend.svelte +66 -0
- package/dist/geoms/Trend.svelte.d.ts +12 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -1
- package/dist/lib/brewing/brewer.svelte.d.ts +1 -0
- package/dist/lib/brewing/marks/lines.d.ts +2 -1
- package/dist/lib/brewing/marks/lines.js +11 -7
- package/dist/lib/highlight.d.ts +10 -0
- package/dist/lib/highlight.js +43 -0
- package/dist/lib/scale.d.ts +10 -0
- package/dist/lib/scale.js +14 -0
- package/dist/lib/select.d.ts +22 -0
- package/dist/lib/select.js +22 -0
- package/dist/lib/trend.d.ts +27 -0
- package/dist/lib/trend.js +113 -0
- package/dist/patterns/README.md +1 -1
- package/package.json +1 -1
package/dist/Plot/Axis.svelte
CHANGED
package/dist/Plot/Grid.svelte
CHANGED
|
@@ -2,26 +2,44 @@
|
|
|
2
2
|
import { getContext } from 'svelte'
|
|
3
3
|
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
4
|
|
|
5
|
+
type Props = {
|
|
6
|
+
lines?: 'auto' | 'x' | 'y' | 'both'
|
|
7
|
+
xTicks?: number
|
|
8
|
+
yTicks?: number
|
|
9
|
+
}
|
|
10
|
+
let { lines = 'auto', xTicks = 6, yTicks = 6 }: Props = $props()
|
|
11
|
+
|
|
5
12
|
const state = getContext<PlotState>('plot-state')
|
|
6
13
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
if (!s || typeof s.bandwidth !== 'function') return []
|
|
10
|
-
return s.domain().map((val: unknown) => ({ pos: (s(val) ?? 0) + s.bandwidth() / 2 }))
|
|
11
|
-
})
|
|
14
|
+
const isBand = (s: unknown) =>
|
|
15
|
+
!!s && typeof (s as { bandwidth?: unknown }).bandwidth === 'function'
|
|
12
16
|
|
|
13
|
-
const
|
|
17
|
+
const showX = $derived(
|
|
18
|
+
lines === 'x' || lines === 'both' || (lines === 'auto' && isBand(state.xScale))
|
|
19
|
+
)
|
|
20
|
+
const showY = $derived(lines === 'auto' || lines === 'y' || lines === 'both')
|
|
21
|
+
|
|
22
|
+
const yLines = $derived.by(() => {
|
|
14
23
|
const s = state.yScale
|
|
15
|
-
if (!s || typeof s.ticks !== 'function') return []
|
|
16
|
-
return s.ticks(
|
|
24
|
+
if (!showY || !s || typeof s.ticks !== 'function') return []
|
|
25
|
+
return s.ticks(yTicks).map((val: number) => ({ pos: s(val) }))
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const xLines = $derived.by(() => {
|
|
29
|
+
const s = state.xScale
|
|
30
|
+
if (!showX || !s) return []
|
|
31
|
+
if (isBand(s))
|
|
32
|
+
return s.domain().map((val: unknown) => ({ pos: (s(val) ?? 0) + s.bandwidth() / 2 }))
|
|
33
|
+
if (typeof s.ticks !== 'function') return []
|
|
34
|
+
return s.ticks(xTicks).map((val: number) => ({ pos: s(val) }))
|
|
17
35
|
})
|
|
18
36
|
</script>
|
|
19
37
|
|
|
20
38
|
<g class="grid" data-plot-grid>
|
|
21
|
-
{#each
|
|
22
|
-
<line x1="0" y1={line.pos} x2={state.innerWidth} y2={line.pos} data-plot-grid-line />
|
|
39
|
+
{#each yLines as line (line.pos)}
|
|
40
|
+
<line x1="0" y1={line.pos} x2={state.innerWidth} y2={line.pos} data-plot-grid-line="y" />
|
|
23
41
|
{/each}
|
|
24
|
-
{#each
|
|
42
|
+
{#each xLines as line (line.pos)}
|
|
25
43
|
<line x1={line.pos} y1="0" x2={line.pos} y2={state.innerHeight} data-plot-grid-line="x" />
|
|
26
44
|
{/each}
|
|
27
45
|
</g>
|
|
@@ -29,7 +47,8 @@
|
|
|
29
47
|
<style>
|
|
30
48
|
[data-plot-grid-line] {
|
|
31
49
|
stroke: var(--chart-grid-color, currentColor);
|
|
32
|
-
|
|
33
|
-
stroke-dasharray: 2 4;
|
|
50
|
+
stroke-width: var(--chart-grid-width, 1);
|
|
51
|
+
stroke-dasharray: var(--chart-grid-dash, 2 4);
|
|
52
|
+
opacity: var(--chart-grid-opacity, 0.15);
|
|
34
53
|
}
|
|
35
54
|
</style>
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
type Props = {
|
|
2
|
+
lines?: 'auto' | 'x' | 'y' | 'both';
|
|
3
|
+
xTicks?: number;
|
|
4
|
+
yTicks?: number;
|
|
5
|
+
};
|
|
6
|
+
declare const Grid: import("svelte").Component<Props, {}, "">;
|
|
2
7
|
type Grid = ReturnType<typeof Grid>;
|
|
3
8
|
export default Grid;
|
package/dist/Plot.svelte
CHANGED
|
@@ -21,8 +21,11 @@
|
|
|
21
21
|
import Waterfall from './geoms/Waterfall.svelte'
|
|
22
22
|
import Hexbin from './geoms/Hexbin.svelte'
|
|
23
23
|
import Ribbon from './geoms/Ribbon.svelte'
|
|
24
|
+
import Highlight from './geoms/Highlight.svelte'
|
|
25
|
+
import Trend from './geoms/Trend.svelte'
|
|
24
26
|
|
|
25
27
|
type Row = Record<string, unknown>
|
|
28
|
+
type Method = string | number | { type: string; [k: string]: unknown }
|
|
26
29
|
type Margin = { top: number; right: number; bottom: number; left: number }
|
|
27
30
|
type ChartPresetCtx = { current: typeof defaultPreset }
|
|
28
31
|
|
|
@@ -33,7 +36,7 @@
|
|
|
33
36
|
width?: number
|
|
34
37
|
height?: number
|
|
35
38
|
mode?: 'light' | 'dark'
|
|
36
|
-
grid?: boolean
|
|
39
|
+
grid?: boolean | 'x' | 'y' | 'both'
|
|
37
40
|
axes?: boolean
|
|
38
41
|
margin?: Margin
|
|
39
42
|
legend?: boolean
|
|
@@ -46,6 +49,14 @@
|
|
|
46
49
|
xTicks?: number
|
|
47
50
|
yTicks?: number
|
|
48
51
|
minorTicks?: boolean
|
|
52
|
+
x?: string
|
|
53
|
+
y?: string
|
|
54
|
+
highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean)
|
|
55
|
+
label?: boolean | string | ((row: Row) => unknown)
|
|
56
|
+
trend?: Method | Method[]
|
|
57
|
+
onselect?: (detail: unknown) => void
|
|
58
|
+
selectable?: boolean
|
|
59
|
+
selected?: Row[]
|
|
49
60
|
children?: Snippet
|
|
50
61
|
}
|
|
51
62
|
|
|
@@ -69,6 +80,14 @@
|
|
|
69
80
|
xTicks = undefined,
|
|
70
81
|
yTicks = undefined,
|
|
71
82
|
minorTicks = false,
|
|
83
|
+
x = undefined,
|
|
84
|
+
y = undefined,
|
|
85
|
+
highlight = undefined,
|
|
86
|
+
label = false,
|
|
87
|
+
trend = undefined,
|
|
88
|
+
onselect = undefined,
|
|
89
|
+
selectable = false,
|
|
90
|
+
selected = $bindable([]),
|
|
72
91
|
children
|
|
73
92
|
}: Props = $props()
|
|
74
93
|
|
|
@@ -92,7 +111,11 @@
|
|
|
92
111
|
|
|
93
112
|
const effectiveWidth = $derived(observedWidth > 0 ? observedWidth : (spec?.width ?? width))
|
|
94
113
|
const svgHeight = $derived(spec?.height ?? height)
|
|
95
|
-
const
|
|
114
|
+
const gridValue = $derived(spec?.grid ?? grid)
|
|
115
|
+
const showGrid = $derived(gridValue !== false)
|
|
116
|
+
const gridLines = $derived(
|
|
117
|
+
gridValue === true || gridValue === false ? 'auto' : gridValue
|
|
118
|
+
)
|
|
96
119
|
const showLegend = $derived(spec?.legend ?? legend)
|
|
97
120
|
const chartTitle = $derived(spec?.title ?? title)
|
|
98
121
|
const chartSummary = $derived(spec?.summary ?? summary)
|
|
@@ -125,7 +148,9 @@
|
|
|
125
148
|
colorScheme: spec?.colorScheme,
|
|
126
149
|
colorMidpoint: spec?.colorMidpoint,
|
|
127
150
|
orientation: spec?.orientation,
|
|
128
|
-
chartPreset
|
|
151
|
+
chartPreset,
|
|
152
|
+
onselect,
|
|
153
|
+
selectable
|
|
129
154
|
}
|
|
130
155
|
}
|
|
131
156
|
|
|
@@ -140,6 +165,17 @@
|
|
|
140
165
|
plotState.update(buildPlotConfig())
|
|
141
166
|
})
|
|
142
167
|
|
|
168
|
+
// external control → state
|
|
169
|
+
$effect(() => {
|
|
170
|
+
plotState.setSelected(selected)
|
|
171
|
+
})
|
|
172
|
+
// clicks (state) → prop, guarded to avoid an update loop
|
|
173
|
+
$effect(() => {
|
|
174
|
+
const rows = plotState.selectedRows
|
|
175
|
+
const same = rows.length === selected.length && rows.every((r, i) => r === selected[i])
|
|
176
|
+
if (!same) selected = rows
|
|
177
|
+
})
|
|
178
|
+
|
|
143
179
|
$effect(() => {
|
|
144
180
|
if (!zoom || !svgEl) return
|
|
145
181
|
// d3-zoom/d3-selection are client-only; load them dynamically so they are
|
|
@@ -169,6 +205,9 @@
|
|
|
169
205
|
// Geoms from spec (spec-driven API)
|
|
170
206
|
const specGeoms = $derived(spec?.geoms ?? [])
|
|
171
207
|
|
|
208
|
+
const overlayX = $derived(spec?.x ?? x)
|
|
209
|
+
const overlayY = $derived(spec?.y ?? y)
|
|
210
|
+
|
|
172
211
|
// Geom component resolver for spec-driven mode
|
|
173
212
|
const GEOM_COMPONENTS = {
|
|
174
213
|
bar: Bar,
|
|
@@ -217,7 +256,7 @@
|
|
|
217
256
|
>
|
|
218
257
|
<!-- Grid (behind everything) -->
|
|
219
258
|
{#if showGrid}
|
|
220
|
-
<Grid />
|
|
259
|
+
<Grid lines={gridLines} {xTicks} {yTicks} />
|
|
221
260
|
{/if}
|
|
222
261
|
|
|
223
262
|
<!-- Declarative children (geom components) -->
|
|
@@ -250,6 +289,14 @@
|
|
|
250
289
|
<Axis type="x" label={spec?.labels?.[spec?.x ?? ''] ?? ''} format={xFormat} ticks={xTicks} {minorTicks} />
|
|
251
290
|
<Axis type="y" label={spec?.labels?.[spec?.y ?? ''] ?? ''} format={yFormat} ticks={yTicks} {minorTicks} />
|
|
252
291
|
{/if}
|
|
292
|
+
|
|
293
|
+
{#if trend !== null && trend !== undefined}
|
|
294
|
+
<Trend x={overlayX} y={overlayY} {trend} />
|
|
295
|
+
{/if}
|
|
296
|
+
|
|
297
|
+
{#if (highlight !== null && highlight !== undefined) || selectable || selected.length}
|
|
298
|
+
<Highlight x={overlayX} y={overlayY} {highlight} {label} />
|
|
299
|
+
{/if}
|
|
253
300
|
</g>
|
|
254
301
|
</svg>
|
|
255
302
|
|
package/dist/Plot.svelte.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { PlotSpec, PlotHelpers } from './lib/plot/types.js';
|
|
3
3
|
type Row = Record<string, unknown>;
|
|
4
|
+
type Method = string | number | {
|
|
5
|
+
type: string;
|
|
6
|
+
[k: string]: unknown;
|
|
7
|
+
};
|
|
4
8
|
type Margin = {
|
|
5
9
|
top: number;
|
|
6
10
|
right: number;
|
|
@@ -14,7 +18,7 @@ type Props = {
|
|
|
14
18
|
width?: number;
|
|
15
19
|
height?: number;
|
|
16
20
|
mode?: 'light' | 'dark';
|
|
17
|
-
grid?: boolean;
|
|
21
|
+
grid?: boolean | 'x' | 'y' | 'both';
|
|
18
22
|
axes?: boolean;
|
|
19
23
|
margin?: Margin;
|
|
20
24
|
legend?: boolean;
|
|
@@ -27,8 +31,16 @@ type Props = {
|
|
|
27
31
|
xTicks?: number;
|
|
28
32
|
yTicks?: number;
|
|
29
33
|
minorTicks?: boolean;
|
|
34
|
+
x?: string;
|
|
35
|
+
y?: string;
|
|
36
|
+
highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean);
|
|
37
|
+
label?: boolean | string | ((row: Row) => unknown);
|
|
38
|
+
trend?: Method | Method[];
|
|
39
|
+
onselect?: (detail: unknown) => void;
|
|
40
|
+
selectable?: boolean;
|
|
41
|
+
selected?: Row[];
|
|
30
42
|
children?: Snippet;
|
|
31
43
|
};
|
|
32
|
-
declare const Plot: import("svelte").Component<Props, {}, "">;
|
|
44
|
+
declare const Plot: import("svelte").Component<Props, {}, "selected">;
|
|
33
45
|
type Plot = ReturnType<typeof Plot>;
|
|
34
46
|
export default Plot;
|
|
@@ -40,6 +40,7 @@ export class PlotState {
|
|
|
40
40
|
patterns: any;
|
|
41
41
|
symbols: any;
|
|
42
42
|
};
|
|
43
|
+
get data(): any[];
|
|
43
44
|
get margin(): {
|
|
44
45
|
top: number;
|
|
45
46
|
right: number;
|
|
@@ -71,8 +72,14 @@ export class PlotState {
|
|
|
71
72
|
symbols: string[];
|
|
72
73
|
};
|
|
73
74
|
get hovered(): null;
|
|
75
|
+
get interactive(): boolean;
|
|
76
|
+
get selectedRows(): any[];
|
|
74
77
|
setHovered(data: any): void;
|
|
75
78
|
clearHovered(): void;
|
|
79
|
+
isSelected(row: any): boolean;
|
|
80
|
+
setSelected(rows: any): void;
|
|
81
|
+
clearSelected(): void;
|
|
82
|
+
handleSelect(detail: any): void;
|
|
76
83
|
applyZoom(transform: any): void;
|
|
77
84
|
resetZoom(): void;
|
|
78
85
|
#private;
|
package/dist/PlotState.svelte.js
CHANGED
|
@@ -39,6 +39,9 @@ export class PlotState {
|
|
|
39
39
|
#mode = $state('light')
|
|
40
40
|
#chartPreset = $state(defaultPreset)
|
|
41
41
|
#hovered = $state(null)
|
|
42
|
+
#selected = $state(new SvelteSet())
|
|
43
|
+
#onselect = $state(undefined)
|
|
44
|
+
#selectable = $state(false)
|
|
42
45
|
#orientationOverride = $state(undefined)
|
|
43
46
|
|
|
44
47
|
axisOrigin = $state([undefined, undefined])
|
|
@@ -274,6 +277,9 @@ export class PlotState {
|
|
|
274
277
|
this.#height = config.height ?? 400
|
|
275
278
|
this.#mode = config.mode ?? 'light'
|
|
276
279
|
this.#chartPreset = config.chartPreset ?? defaultPreset
|
|
280
|
+
this.#onselect = config.onselect
|
|
281
|
+
this.#selectable = config.selectable ?? false
|
|
282
|
+
if (config.selected) this.#selected = new SvelteSet(config.selected)
|
|
277
283
|
this.#axisOffset = config.axisOffset ?? 0
|
|
278
284
|
this.#marginOverride = config.margin ?? undefined
|
|
279
285
|
this.#orientationOverride = config.orientation ?? undefined
|
|
@@ -298,6 +304,8 @@ export class PlotState {
|
|
|
298
304
|
if (config.height !== undefined) this.#height = config.height
|
|
299
305
|
if (config.mode !== undefined) this.#mode = config.mode
|
|
300
306
|
if (config.chartPreset !== undefined) this.#chartPreset = config.chartPreset
|
|
307
|
+
if (config.onselect !== undefined) this.#onselect = config.onselect
|
|
308
|
+
if (config.selectable !== undefined) this.#selectable = config.selectable
|
|
301
309
|
if (config.axisOffset !== undefined) this.#axisOffset = config.axisOffset
|
|
302
310
|
this.#marginOverride = config.margin ?? undefined
|
|
303
311
|
this.#orientationOverride = config.orientation ?? undefined
|
|
@@ -345,6 +353,13 @@ export class PlotState {
|
|
|
345
353
|
return resolvePreset(this.#presetName, this.#helpers)
|
|
346
354
|
}
|
|
347
355
|
|
|
356
|
+
get data() {
|
|
357
|
+
// Return #rawData (not #data): geoms read their rows via geomData() which
|
|
358
|
+
// returns #rawData, so overlays and index lookups (plotState.data.indexOf(datum))
|
|
359
|
+
// must use the SAME proxy identity. #data and #rawData proxy the same source
|
|
360
|
+
// array separately, so their elements are not ===. Same content, aligned identity.
|
|
361
|
+
return this.#rawData
|
|
362
|
+
}
|
|
348
363
|
get margin() {
|
|
349
364
|
return this.#effectiveMargin
|
|
350
365
|
}
|
|
@@ -363,6 +378,12 @@ export class PlotState {
|
|
|
363
378
|
get hovered() {
|
|
364
379
|
return this.#hovered
|
|
365
380
|
}
|
|
381
|
+
get interactive() {
|
|
382
|
+
return Boolean(this.#onselect) || this.#selectable
|
|
383
|
+
}
|
|
384
|
+
get selectedRows() {
|
|
385
|
+
return [...this.#selected]
|
|
386
|
+
}
|
|
366
387
|
|
|
367
388
|
setHovered(data) {
|
|
368
389
|
this.#hovered = data
|
|
@@ -370,6 +391,22 @@ export class PlotState {
|
|
|
370
391
|
clearHovered() {
|
|
371
392
|
this.#hovered = null
|
|
372
393
|
}
|
|
394
|
+
isSelected(row) {
|
|
395
|
+
return this.#selected.has(row)
|
|
396
|
+
}
|
|
397
|
+
setSelected(rows) {
|
|
398
|
+
this.#selected = new SvelteSet(rows ?? [])
|
|
399
|
+
}
|
|
400
|
+
clearSelected() {
|
|
401
|
+
this.#selected = new SvelteSet()
|
|
402
|
+
}
|
|
403
|
+
handleSelect(detail) {
|
|
404
|
+
this.#onselect?.(detail)
|
|
405
|
+
if (this.#selectable && detail?.datum !== undefined) {
|
|
406
|
+
if (this.#selected.has(detail.datum)) this.#selected.delete(detail.datum)
|
|
407
|
+
else this.#selected.add(detail.datum)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
373
410
|
|
|
374
411
|
applyZoom(transform) {
|
|
375
412
|
this.#zoomTransform = transform
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import Area from '../geoms/Area.svelte'
|
|
4
4
|
|
|
5
5
|
type Row = Record<string, unknown>
|
|
6
|
+
type Method = string | number | { type: string; [k: string]: unknown }
|
|
6
7
|
type Format = (v: unknown) => string
|
|
7
8
|
|
|
8
9
|
type Props = {
|
|
@@ -17,13 +18,18 @@
|
|
|
17
18
|
width?: number
|
|
18
19
|
height?: number
|
|
19
20
|
mode?: 'light' | 'dark'
|
|
20
|
-
grid?: boolean
|
|
21
|
+
grid?: boolean | 'x' | 'y' | 'both'
|
|
21
22
|
legend?: boolean
|
|
23
|
+
highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean)
|
|
24
|
+
trend?: Method | Method[]
|
|
22
25
|
xFormat?: Format
|
|
23
26
|
yFormat?: Format
|
|
24
27
|
xTicks?: number
|
|
25
28
|
yTicks?: number
|
|
26
29
|
minorTicks?: boolean
|
|
30
|
+
onselect?: (detail: unknown) => void
|
|
31
|
+
selectable?: boolean
|
|
32
|
+
selected?: Row[]
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
let {
|
|
@@ -40,14 +46,38 @@
|
|
|
40
46
|
mode = 'light',
|
|
41
47
|
grid = true,
|
|
42
48
|
legend = false,
|
|
49
|
+
highlight = undefined,
|
|
50
|
+
trend = undefined,
|
|
43
51
|
xFormat = undefined,
|
|
44
52
|
yFormat = undefined,
|
|
45
53
|
xTicks = undefined,
|
|
46
54
|
yTicks = undefined,
|
|
47
|
-
minorTicks = false
|
|
55
|
+
minorTicks = false,
|
|
56
|
+
onselect = undefined,
|
|
57
|
+
selectable = false,
|
|
58
|
+
selected = $bindable([])
|
|
48
59
|
}: Props = $props()
|
|
49
60
|
</script>
|
|
50
61
|
|
|
51
|
-
<Plot
|
|
62
|
+
<Plot
|
|
63
|
+
{data}
|
|
64
|
+
{x}
|
|
65
|
+
{y}
|
|
66
|
+
{width}
|
|
67
|
+
{height}
|
|
68
|
+
{mode}
|
|
69
|
+
{grid}
|
|
70
|
+
{legend}
|
|
71
|
+
{highlight}
|
|
72
|
+
{trend}
|
|
73
|
+
{xFormat}
|
|
74
|
+
{yFormat}
|
|
75
|
+
{xTicks}
|
|
76
|
+
{yTicks}
|
|
77
|
+
{minorTicks}
|
|
78
|
+
{onselect}
|
|
79
|
+
{selectable}
|
|
80
|
+
bind:selected
|
|
81
|
+
>
|
|
52
82
|
<Area {x} {y} color={fill} {pattern} {stat} options={{ curve, stack }} />
|
|
53
83
|
</Plot>
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
type Row = Record<string, unknown>;
|
|
2
|
+
type Method = string | number | {
|
|
3
|
+
type: string;
|
|
4
|
+
[k: string]: unknown;
|
|
5
|
+
};
|
|
2
6
|
type Format = (v: unknown) => string;
|
|
3
7
|
type Props = {
|
|
4
8
|
data?: Row[];
|
|
@@ -12,14 +16,19 @@ type Props = {
|
|
|
12
16
|
width?: number;
|
|
13
17
|
height?: number;
|
|
14
18
|
mode?: 'light' | 'dark';
|
|
15
|
-
grid?: boolean;
|
|
19
|
+
grid?: boolean | 'x' | 'y' | 'both';
|
|
16
20
|
legend?: boolean;
|
|
21
|
+
highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean);
|
|
22
|
+
trend?: Method | Method[];
|
|
17
23
|
xFormat?: Format;
|
|
18
24
|
yFormat?: Format;
|
|
19
25
|
xTicks?: number;
|
|
20
26
|
yTicks?: number;
|
|
21
27
|
minorTicks?: boolean;
|
|
28
|
+
onselect?: (detail: unknown) => void;
|
|
29
|
+
selectable?: boolean;
|
|
30
|
+
selected?: Row[];
|
|
22
31
|
};
|
|
23
|
-
declare const AreaChart: import("svelte").Component<Props, {}, "">;
|
|
32
|
+
declare const AreaChart: import("svelte").Component<Props, {}, "selected">;
|
|
24
33
|
type AreaChart = ReturnType<typeof AreaChart>;
|
|
25
34
|
export default AreaChart;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import Line from '../geoms/Line.svelte'
|
|
4
4
|
|
|
5
5
|
type Row = Record<string, unknown>
|
|
6
|
+
type Method = string | number | { type: string; [k: string]: unknown }
|
|
6
7
|
type Format = (v: unknown) => string
|
|
7
8
|
|
|
8
9
|
type Props = {
|
|
@@ -18,13 +19,18 @@
|
|
|
18
19
|
width?: number
|
|
19
20
|
height?: number
|
|
20
21
|
mode?: 'light' | 'dark'
|
|
21
|
-
grid?: boolean
|
|
22
|
+
grid?: boolean | 'x' | 'y' | 'both'
|
|
22
23
|
legend?: boolean
|
|
24
|
+
highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean)
|
|
25
|
+
trend?: Method | Method[]
|
|
23
26
|
xFormat?: Format
|
|
24
27
|
yFormat?: Format
|
|
25
28
|
xTicks?: number
|
|
26
29
|
yTicks?: number
|
|
27
30
|
minorTicks?: boolean
|
|
31
|
+
onselect?: (detail: unknown) => void
|
|
32
|
+
selectable?: boolean
|
|
33
|
+
selected?: Row[]
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
let {
|
|
@@ -42,14 +48,39 @@
|
|
|
42
48
|
mode = 'light',
|
|
43
49
|
grid = true,
|
|
44
50
|
legend = false,
|
|
51
|
+
highlight = undefined,
|
|
52
|
+
trend = undefined,
|
|
45
53
|
xFormat = undefined,
|
|
46
54
|
yFormat = undefined,
|
|
47
55
|
xTicks = undefined,
|
|
48
56
|
yTicks = undefined,
|
|
49
|
-
minorTicks = false
|
|
57
|
+
minorTicks = false,
|
|
58
|
+
onselect = undefined,
|
|
59
|
+
selectable = false,
|
|
60
|
+
selected = $bindable([])
|
|
50
61
|
}: Props = $props()
|
|
51
62
|
</script>
|
|
52
63
|
|
|
53
|
-
<Plot
|
|
64
|
+
<Plot
|
|
65
|
+
{data}
|
|
66
|
+
{x}
|
|
67
|
+
{y}
|
|
68
|
+
{width}
|
|
69
|
+
{height}
|
|
70
|
+
{mode}
|
|
71
|
+
{grid}
|
|
72
|
+
{legend}
|
|
73
|
+
{tooltip}
|
|
74
|
+
{highlight}
|
|
75
|
+
{trend}
|
|
76
|
+
{xFormat}
|
|
77
|
+
{yFormat}
|
|
78
|
+
{xTicks}
|
|
79
|
+
{yTicks}
|
|
80
|
+
{minorTicks}
|
|
81
|
+
{onselect}
|
|
82
|
+
{selectable}
|
|
83
|
+
bind:selected
|
|
84
|
+
>
|
|
54
85
|
<Line {x} {y} {color} {symbol} {label} {stat} options={{ curve }} />
|
|
55
86
|
</Plot>
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
type Row = Record<string, unknown>;
|
|
2
|
+
type Method = string | number | {
|
|
3
|
+
type: string;
|
|
4
|
+
[k: string]: unknown;
|
|
5
|
+
};
|
|
2
6
|
type Format = (v: unknown) => string;
|
|
3
7
|
type Props = {
|
|
4
8
|
data?: Row[];
|
|
@@ -13,14 +17,19 @@ type Props = {
|
|
|
13
17
|
width?: number;
|
|
14
18
|
height?: number;
|
|
15
19
|
mode?: 'light' | 'dark';
|
|
16
|
-
grid?: boolean;
|
|
20
|
+
grid?: boolean | 'x' | 'y' | 'both';
|
|
17
21
|
legend?: boolean;
|
|
22
|
+
highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean);
|
|
23
|
+
trend?: Method | Method[];
|
|
18
24
|
xFormat?: Format;
|
|
19
25
|
yFormat?: Format;
|
|
20
26
|
xTicks?: number;
|
|
21
27
|
yTicks?: number;
|
|
22
28
|
minorTicks?: boolean;
|
|
29
|
+
onselect?: (detail: unknown) => void;
|
|
30
|
+
selectable?: boolean;
|
|
31
|
+
selected?: Row[];
|
|
23
32
|
};
|
|
24
|
-
declare const LineChart: import("svelte").Component<Props, {}, "">;
|
|
33
|
+
declare const LineChart: import("svelte").Component<Props, {}, "selected">;
|
|
25
34
|
type LineChart = ReturnType<typeof LineChart>;
|
|
26
35
|
export default LineChart;
|
package/dist/geoms/Area.svelte
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
3
|
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
4
|
import { buildAreas, buildStackedAreas } from './lib/areas.js'
|
|
5
|
+
import { buildSelectDetail } from '../lib/select.js'
|
|
6
|
+
|
|
7
|
+
type Row = Record<string, unknown>
|
|
5
8
|
|
|
6
9
|
type Options = {
|
|
7
10
|
stack?: boolean
|
|
@@ -23,6 +26,13 @@
|
|
|
23
26
|
const plotState = getContext<PlotState>('plot-state')
|
|
24
27
|
let id = $state<string | null>(null)
|
|
25
28
|
|
|
29
|
+
function selectPoint(d: Row, event: MouseEvent | KeyboardEvent) {
|
|
30
|
+
if (plotState.interactive)
|
|
31
|
+
plotState.handleSelect(
|
|
32
|
+
buildSelectDetail(d, plotState.data.indexOf(d), { x, y }, 'area', color ? d[color] : undefined, event)
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
onMount(() => {
|
|
27
37
|
id = plotState.registerGeom({
|
|
28
38
|
type: 'area',
|
|
@@ -79,16 +89,23 @@
|
|
|
79
89
|
{#each data as d, i (`hover::${i}`)}
|
|
80
90
|
{@const px = typeof xScale?.bandwidth === 'function' ? (xScale(d[x]) ?? 0) + xScale.bandwidth() / 2 : (xScale?.(d[x]) ?? 0)}
|
|
81
91
|
{@const py = yScale?.(d[y]) ?? 0}
|
|
92
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
82
93
|
<circle
|
|
83
94
|
cx={px}
|
|
84
95
|
cy={py}
|
|
85
96
|
r="8"
|
|
86
97
|
fill="transparent"
|
|
87
98
|
stroke="none"
|
|
88
|
-
role=
|
|
99
|
+
role={plotState.interactive ? 'button' : 'presentation'}
|
|
100
|
+
tabindex={plotState.interactive ? 0 : undefined}
|
|
101
|
+
style:cursor={plotState.interactive ? 'pointer' : undefined}
|
|
89
102
|
data-plot-element="area-hover"
|
|
90
103
|
onmouseenter={() => plotState.setHovered(d)}
|
|
91
104
|
onmouseleave={() => plotState.clearHovered()}
|
|
105
|
+
onclick={plotState.interactive ? (e) => selectPoint(d, e) : undefined}
|
|
106
|
+
onkeydown={plotState.interactive
|
|
107
|
+
? (e) => (e.key === 'Enter' || e.key === ' ') && selectPoint(d, e)
|
|
108
|
+
: undefined}
|
|
92
109
|
/>
|
|
93
110
|
{/each}
|
|
94
111
|
{/if}
|
package/dist/geoms/Bar.svelte
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { createCrossFilter } from '../crossfilter/createCrossFilter.svelte.js'
|
|
5
5
|
import { buildGroupedBars, buildStackedBars, buildHorizontalBars } from './lib/bars.js'
|
|
6
6
|
import { keyboardNav } from '../lib/keyboard-nav.js'
|
|
7
|
+
import { buildSelectDetail } from '../lib/select.js'
|
|
7
8
|
import LabelPill from './LabelPill.svelte'
|
|
8
9
|
|
|
9
10
|
type Row = Record<string, unknown>
|
|
@@ -134,6 +135,36 @@
|
|
|
134
135
|
if (!filterable || !x || !cf) return
|
|
135
136
|
cf.toggleCategorical(x, barX)
|
|
136
137
|
}
|
|
138
|
+
|
|
139
|
+
function selectBar(bar: { data: Row }, event: MouseEvent | KeyboardEvent) {
|
|
140
|
+
if (plotState.interactive)
|
|
141
|
+
plotState.handleSelect(
|
|
142
|
+
buildSelectDetail(
|
|
143
|
+
bar.data,
|
|
144
|
+
plotState.data.indexOf(bar.data),
|
|
145
|
+
{ x, y },
|
|
146
|
+
'bar',
|
|
147
|
+
colorChannel ? bar.data[colorChannel] : undefined,
|
|
148
|
+
event
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function handleBarActivate(bar: { data: Row }, event: MouseEvent) {
|
|
154
|
+
if (filterable && x) handleBarClick(bar.data[x])
|
|
155
|
+
onselect?.(bar.data)
|
|
156
|
+
selectBar(bar, event)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function handleBarKeyActivate(bar: { data: Row }, event: KeyboardEvent) {
|
|
160
|
+
if (event.key !== 'Enter' && event.key !== ' ') return
|
|
161
|
+
if (filterable && x) {
|
|
162
|
+
handleBarClick(bar.data[x])
|
|
163
|
+
} else {
|
|
164
|
+
onselect?.(bar.data)
|
|
165
|
+
}
|
|
166
|
+
selectBar(bar, event)
|
|
167
|
+
}
|
|
137
168
|
</script>
|
|
138
169
|
|
|
139
170
|
{#if bars.length > 0}
|
|
@@ -152,13 +183,15 @@
|
|
|
152
183
|
data-plot-value={bar.data[y ?? '']}
|
|
153
184
|
data-plot-category={bar.data[x ?? '']}
|
|
154
185
|
data-dimmed={dimmedByKey[bar.key] ? true : undefined}
|
|
155
|
-
style:cursor={filterable || onselect ? 'pointer' : undefined}
|
|
156
|
-
onclick={filterable
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
186
|
+
style:cursor={filterable || onselect || plotState.interactive ? 'pointer' : undefined}
|
|
187
|
+
onclick={filterable || onselect || keyboard || plotState.interactive
|
|
188
|
+
? (e) => handleBarActivate(bar, e)
|
|
189
|
+
: undefined}
|
|
190
|
+
onkeydown={filterable || onselect || keyboard || plotState.interactive
|
|
191
|
+
? (e) => handleBarKeyActivate(bar, e)
|
|
192
|
+
: undefined}
|
|
193
|
+
role={filterable || onselect || keyboard || plotState.interactive ? 'button' : 'graphics-symbol'}
|
|
194
|
+
tabindex={filterable || onselect || keyboard || plotState.interactive ? 0 : undefined}
|
|
162
195
|
use:keyboardNav={keyboard}
|
|
163
196
|
aria-label="{bar.data[x ?? '']}: {bar.data[y ?? '']}"
|
|
164
197
|
onmouseenter={() => plotState.setHovered(bar.data)}
|