@rokkit/chart 1.3.15 → 1.4.1
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.svelte +25 -9
- package/dist/PlotState.svelte.d.ts +1 -0
- package/dist/PlotState.svelte.js +19 -8
- package/dist/Spark.svelte +165 -0
- package/dist/Spark.svelte.d.ts +31 -0
- package/dist/SparkState.svelte.d.ts +138 -0
- package/dist/SparkState.svelte.js +331 -0
- package/dist/Sparkline.svelte +62 -237
- package/dist/Sparkline.svelte.d.ts +2 -0
- package/dist/charts/RadarChart.svelte +106 -0
- package/dist/charts/RadarChart.svelte.d.ts +43 -0
- package/dist/geoms/Area.svelte +24 -9
- package/dist/geoms/Area.svelte.d.ts +2 -0
- package/dist/geoms/Bar.svelte +9 -16
- package/dist/geoms/Highlight.svelte +61 -40
- package/dist/geoms/Line.svelte +10 -9
- package/dist/geoms/Point.svelte +9 -15
- package/dist/geoms/Radar.svelte +352 -0
- package/dist/geoms/Radar.svelte.d.ts +42 -0
- package/dist/geoms/Trend.svelte +46 -23
- package/dist/geoms/lib/aesthetics.d.ts +14 -0
- package/dist/geoms/lib/aesthetics.js +21 -0
- package/dist/geoms/lib/areas.d.ts +9 -2
- package/dist/geoms/lib/areas.js +53 -8
- package/dist/geoms/lib/marks/area.d.ts +3 -0
- package/dist/geoms/lib/marks/area.js +11 -2
- package/dist/geoms/lib/marks/radar.d.ts +68 -0
- package/dist/geoms/lib/marks/radar.js +153 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -1
- package/dist/lib/brewing/marks/boxes.js +19 -30
- package/dist/lib/brewing/polar.d.ts +444 -0
- package/dist/lib/brewing/polar.js +645 -0
- package/dist/lib/highlight.js +84 -33
- package/dist/lib/plot/scales.js +8 -5
- package/dist/lib/preset.d.ts +1 -0
- package/dist/lib/preset.js +2 -1
- package/dist/lib/select.d.ts +25 -11
- package/dist/lib/select.js +14 -7
- package/dist/lib/trend.js +55 -39
- package/package.json +1 -1
- package/dist/examples/BarChartExample.svelte +0 -78
- package/dist/examples/BarChartExample.svelte.d.ts +0 -18
- package/dist/lib/brewing/types.d.ts +0 -162
- package/dist/lib/brewing/types.js +0 -73
- package/dist/lib/xscale.d.ts +0 -11
- package/dist/lib/xscale.js +0 -31
|
@@ -21,6 +21,8 @@ type Props = {
|
|
|
21
21
|
baseline?: number;
|
|
22
22
|
highlight?: HighlightSelector | HighlightSelector[];
|
|
23
23
|
trend?: TrendMethod | TrendMethod[];
|
|
24
|
+
/** Accessible name, forwarded to `Spark`. Omitted → generated from the series. */
|
|
25
|
+
label?: string;
|
|
24
26
|
};
|
|
25
27
|
declare const Sparkline: import("svelte").Component<Props, {}, "">;
|
|
26
28
|
type Sparkline = ReturnType<typeof Sparkline>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Plot from '../Plot.svelte'
|
|
3
|
+
import Radar from '../geoms/Radar.svelte'
|
|
4
|
+
|
|
5
|
+
type Row = Record<string, unknown>
|
|
6
|
+
|
|
7
|
+
type AxisSpec = {
|
|
8
|
+
key: string
|
|
9
|
+
label?: string
|
|
10
|
+
unit?: string
|
|
11
|
+
domain?: [number, number]
|
|
12
|
+
ticks?: number
|
|
13
|
+
tickLabels?: string[]
|
|
14
|
+
format?: (value: number) => unknown
|
|
15
|
+
weight?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type Props = {
|
|
19
|
+
data?: Row[]
|
|
20
|
+
/** Field naming which axis each row belongs to. */
|
|
21
|
+
axis?: string
|
|
22
|
+
/** Field holding the plotted magnitude. */
|
|
23
|
+
value?: string
|
|
24
|
+
/** Field distinguishing one polygon from another. */
|
|
25
|
+
series?: string
|
|
26
|
+
/** Axis order and per-axis config — bare names, full specs, or a mix. */
|
|
27
|
+
axes?: (string | AxisSpec)[]
|
|
28
|
+
pattern?: string
|
|
29
|
+
grid?: boolean
|
|
30
|
+
rings?: number
|
|
31
|
+
sharedDomain?: boolean
|
|
32
|
+
radiusScale?: 'linear' | 'sqrt' | 'auto'
|
|
33
|
+
alpha?: number
|
|
34
|
+
stat?: string
|
|
35
|
+
width?: number
|
|
36
|
+
height?: number
|
|
37
|
+
mode?: 'light' | 'dark'
|
|
38
|
+
title?: string
|
|
39
|
+
tooltip?: boolean | ((data: Row) => string)
|
|
40
|
+
/** Omit to let it default: on for 2+ series, off for one. */
|
|
41
|
+
legend?: boolean
|
|
42
|
+
/** Plot-level selection: receives the `buildSelectDetail` shape, not a bare row —
|
|
43
|
+
* same as AreaChart/BarChart. The geom's own `onselect` (a row) is reached by
|
|
44
|
+
* composing `<Plot><Radar onselect /></Plot>` directly. */
|
|
45
|
+
onselect?: (detail: unknown) => void
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let {
|
|
49
|
+
data = [],
|
|
50
|
+
axis = undefined,
|
|
51
|
+
value = undefined,
|
|
52
|
+
series = undefined,
|
|
53
|
+
axes = undefined,
|
|
54
|
+
pattern = undefined,
|
|
55
|
+
grid = true,
|
|
56
|
+
rings = undefined,
|
|
57
|
+
sharedDomain = undefined,
|
|
58
|
+
radiusScale = undefined,
|
|
59
|
+
alpha = undefined,
|
|
60
|
+
stat = 'identity',
|
|
61
|
+
width = 400,
|
|
62
|
+
height = 400,
|
|
63
|
+
mode = undefined,
|
|
64
|
+
title = undefined,
|
|
65
|
+
tooltip = false,
|
|
66
|
+
legend = undefined,
|
|
67
|
+
onselect = undefined
|
|
68
|
+
}: Props = $props()
|
|
69
|
+
|
|
70
|
+
// ⚠ Diverges from PieChart, which defaults `legend` to false. Radar fills are washed out
|
|
71
|
+
// (preset alpha 0.25) and they overlap, so where they intersect the blend is a colour
|
|
72
|
+
// that belongs to neither series — matching a polygon to its series by colour alone is
|
|
73
|
+
// unreliable. A legend is therefore on by default as soon as there is more than one
|
|
74
|
+
// series. Still fully overridable in both directions.
|
|
75
|
+
//
|
|
76
|
+
// With no `series` channel there is exactly one polygon by construction, so the count
|
|
77
|
+
// cannot exceed one and the legend is simply off — no need to distinguish empty data
|
|
78
|
+
// from one implicit series.
|
|
79
|
+
const multiSeries = $derived(series ? new Set(data.map((row) => row[series])).size > 1 : false)
|
|
80
|
+
const showLegend = $derived(legend ?? multiSeries)
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<Plot
|
|
84
|
+
{data}
|
|
85
|
+
{width}
|
|
86
|
+
{height}
|
|
87
|
+
{mode}
|
|
88
|
+
{title}
|
|
89
|
+
grid={false}
|
|
90
|
+
axes={false}
|
|
91
|
+
margin={{ top: 10, right: 10, bottom: 10, left: 10 }}
|
|
92
|
+
legend={showLegend}
|
|
93
|
+
{tooltip}
|
|
94
|
+
{onselect}
|
|
95
|
+
>
|
|
96
|
+
<Radar
|
|
97
|
+
{axis}
|
|
98
|
+
{value}
|
|
99
|
+
{series}
|
|
100
|
+
{axes}
|
|
101
|
+
{pattern}
|
|
102
|
+
{alpha}
|
|
103
|
+
{stat}
|
|
104
|
+
options={{ grid, rings, sharedDomain, radiusScale }}
|
|
105
|
+
/>
|
|
106
|
+
</Plot>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
type Row = Record<string, unknown>;
|
|
2
|
+
type AxisSpec = {
|
|
3
|
+
key: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
unit?: string;
|
|
6
|
+
domain?: [number, number];
|
|
7
|
+
ticks?: number;
|
|
8
|
+
tickLabels?: string[];
|
|
9
|
+
format?: (value: number) => unknown;
|
|
10
|
+
weight?: number;
|
|
11
|
+
};
|
|
12
|
+
type Props = {
|
|
13
|
+
data?: Row[];
|
|
14
|
+
/** Field naming which axis each row belongs to. */
|
|
15
|
+
axis?: string;
|
|
16
|
+
/** Field holding the plotted magnitude. */
|
|
17
|
+
value?: string;
|
|
18
|
+
/** Field distinguishing one polygon from another. */
|
|
19
|
+
series?: string;
|
|
20
|
+
/** Axis order and per-axis config — bare names, full specs, or a mix. */
|
|
21
|
+
axes?: (string | AxisSpec)[];
|
|
22
|
+
pattern?: string;
|
|
23
|
+
grid?: boolean;
|
|
24
|
+
rings?: number;
|
|
25
|
+
sharedDomain?: boolean;
|
|
26
|
+
radiusScale?: 'linear' | 'sqrt' | 'auto';
|
|
27
|
+
alpha?: number;
|
|
28
|
+
stat?: string;
|
|
29
|
+
width?: number;
|
|
30
|
+
height?: number;
|
|
31
|
+
mode?: 'light' | 'dark';
|
|
32
|
+
title?: string;
|
|
33
|
+
tooltip?: boolean | ((data: Row) => string);
|
|
34
|
+
/** Omit to let it default: on for 2+ series, off for one. */
|
|
35
|
+
legend?: boolean;
|
|
36
|
+
/** Plot-level selection: receives the `buildSelectDetail` shape, not a bare row —
|
|
37
|
+
* same as AreaChart/BarChart. The geom's own `onselect` (a row) is reached by
|
|
38
|
+
* composing `<Plot><Radar onselect /></Plot>` directly. */
|
|
39
|
+
onselect?: (detail: unknown) => void;
|
|
40
|
+
};
|
|
41
|
+
declare const RadarChart: import("svelte").Component<Props, {}, "">;
|
|
42
|
+
type RadarChart = ReturnType<typeof RadarChart>;
|
|
43
|
+
export default RadarChart;
|
package/dist/geoms/Area.svelte
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
/** @deprecated use `position="stack"` — kept as an alias. */
|
|
11
11
|
stack?: boolean
|
|
12
12
|
curve?: 'linear' | 'smooth' | 'step'
|
|
13
|
+
/** Data-space value to anchor/split the fill at; splits the area into above/below segments. */
|
|
14
|
+
baseline?: number
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
type Props = {
|
|
@@ -40,7 +42,12 @@
|
|
|
40
42
|
type: 'area',
|
|
41
43
|
channels: { x, y, color, fill, pattern },
|
|
42
44
|
stat,
|
|
43
|
-
options: {
|
|
45
|
+
options: {
|
|
46
|
+
stack: options?.stack ?? false,
|
|
47
|
+
position: resolvedPosition,
|
|
48
|
+
curve: options?.curve,
|
|
49
|
+
baseline: options?.baseline
|
|
50
|
+
},
|
|
44
51
|
alpha,
|
|
45
52
|
build: buildAreaMarks
|
|
46
53
|
}))
|
|
@@ -59,14 +66,14 @@
|
|
|
59
66
|
function selectPoint(d: Row, event: MouseEvent | KeyboardEvent) {
|
|
60
67
|
if (plotState.interactive)
|
|
61
68
|
plotState.handleSelect(
|
|
62
|
-
buildSelectDetail(
|
|
63
|
-
d,
|
|
64
|
-
plotState.data.indexOf(d),
|
|
65
|
-
{ x, y },
|
|
66
|
-
'area',
|
|
67
|
-
seriesField ? d[seriesField] : undefined,
|
|
69
|
+
buildSelectDetail({
|
|
70
|
+
datum: d,
|
|
71
|
+
index: plotState.data.indexOf(d),
|
|
72
|
+
channels: { x, y },
|
|
73
|
+
geom: 'area',
|
|
74
|
+
series: seriesField ? d[seriesField] : undefined,
|
|
68
75
|
event
|
|
69
|
-
)
|
|
76
|
+
})
|
|
70
77
|
)
|
|
71
78
|
}
|
|
72
79
|
</script>
|
|
@@ -80,9 +87,17 @@
|
|
|
80
87
|
fill-opacity={seg.patternId ? 1 : seg.alpha}
|
|
81
88
|
stroke={seg.stroke ?? 'none'}
|
|
82
89
|
data-plot-element="area"
|
|
90
|
+
data-plot-area
|
|
91
|
+
data-plot-area-sign={seg.sign}
|
|
83
92
|
/>
|
|
84
93
|
{#if seg.patternId}
|
|
85
|
-
<path
|
|
94
|
+
<path
|
|
95
|
+
d={seg.d}
|
|
96
|
+
fill="url(#{seg.patternId})"
|
|
97
|
+
data-plot-element="area"
|
|
98
|
+
data-plot-area
|
|
99
|
+
data-plot-area-sign={seg.sign}
|
|
100
|
+
/>
|
|
86
101
|
{/if}
|
|
87
102
|
{/each}
|
|
88
103
|
<!-- Invisible hit circles for tooltip: one per data point -->
|
|
@@ -2,6 +2,8 @@ type Options = {
|
|
|
2
2
|
/** @deprecated use `position="stack"` — kept as an alias. */
|
|
3
3
|
stack?: boolean;
|
|
4
4
|
curve?: 'linear' | 'smooth' | 'step';
|
|
5
|
+
/** Data-space value to anchor/split the fill at; splits the area into above/below segments. */
|
|
6
|
+
baseline?: number;
|
|
5
7
|
};
|
|
6
8
|
type Props = {
|
|
7
9
|
x?: string;
|
package/dist/geoms/Bar.svelte
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { createCrossFilter } from '../crossfilter/createCrossFilter.svelte.js'
|
|
5
5
|
import { GeomState } from './lib/GeomState.svelte.js'
|
|
6
6
|
import { buildBarMarks } from './lib/marks/bar.js'
|
|
7
|
+
import { resolveLabel } from './lib/aesthetics.js'
|
|
7
8
|
import { keyboardNav } from '../lib/keyboard-nav.js'
|
|
8
9
|
import { buildSelectDetail } from '../lib/select.js'
|
|
9
10
|
import LabelPill from './LabelPill.svelte'
|
|
@@ -60,14 +61,6 @@
|
|
|
60
61
|
// `options.stack: true` is the back-compat alias for position='stack'.
|
|
61
62
|
const resolvedPosition = $derived(position ?? (options?.stack ? 'stack' : 'dodge'))
|
|
62
63
|
|
|
63
|
-
function resolveLabel(data: Row, defaultField: string | undefined): string | null {
|
|
64
|
-
if (!label) return null
|
|
65
|
-
if (label === true) return String((defaultField ? data[defaultField] : undefined) ?? '')
|
|
66
|
-
if (typeof label === 'function') return String(label(data) ?? '')
|
|
67
|
-
if (typeof label === 'string') return String(data[label] ?? '')
|
|
68
|
-
return null
|
|
69
|
-
}
|
|
70
|
-
|
|
71
64
|
/**
|
|
72
65
|
* Pick white or dark text based on perceived luminance of a hex fill color.
|
|
73
66
|
*/
|
|
@@ -126,14 +119,14 @@
|
|
|
126
119
|
function selectBar(bar: { data: Row }, event: MouseEvent | KeyboardEvent) {
|
|
127
120
|
if (plotState.interactive)
|
|
128
121
|
plotState.handleSelect(
|
|
129
|
-
buildSelectDetail(
|
|
130
|
-
bar.data,
|
|
131
|
-
plotState.data.indexOf(bar.data),
|
|
132
|
-
{ x, y },
|
|
133
|
-
'bar',
|
|
134
|
-
seriesField ? bar.data[seriesField] : undefined,
|
|
122
|
+
buildSelectDetail({
|
|
123
|
+
datum: bar.data,
|
|
124
|
+
index: plotState.data.indexOf(bar.data),
|
|
125
|
+
channels: { x, y },
|
|
126
|
+
geom: 'bar',
|
|
127
|
+
series: seriesField ? bar.data[seriesField] : undefined,
|
|
135
128
|
event
|
|
136
|
-
)
|
|
129
|
+
})
|
|
137
130
|
)
|
|
138
131
|
}
|
|
139
132
|
|
|
@@ -198,7 +191,7 @@
|
|
|
198
191
|
/>
|
|
199
192
|
{/if}
|
|
200
193
|
{#if label}
|
|
201
|
-
{@const text = resolveLabel(bar.data, effectiveOrientation === 'horizontal' ? x : y)}
|
|
194
|
+
{@const text = resolveLabel(label, bar.data, effectiveOrientation === 'horizontal' ? x : y)}
|
|
202
195
|
{#if text}
|
|
203
196
|
{#if effectiveOrientation === 'horizontal'}
|
|
204
197
|
{#if options.labelInside}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
4
|
import { resolveHighlight } from '../lib/highlight.js'
|
|
5
5
|
import { scalePos } from '../lib/scale.js'
|
|
6
|
+
import { resolveLabel } from './lib/aesthetics.js'
|
|
6
7
|
|
|
7
8
|
type Row = Record<string, unknown>
|
|
8
9
|
type Props = {
|
|
@@ -15,49 +16,69 @@
|
|
|
15
16
|
|
|
16
17
|
const state = getContext<PlotState>('plot-state')
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
19
|
+
type Mark = { key: string; cx: number; cy: number; row: Row; selected: boolean }
|
|
20
|
+
/** Everything both mark builders need, resolved once per derivation. */
|
|
21
|
+
type Ctx = {
|
|
22
|
+
rows: Row[]
|
|
23
|
+
selectedRows: Row[]
|
|
24
|
+
// Scales come from PlotState (untyped JS), so they stay loose here.
|
|
25
|
+
xs: (v: unknown) => number
|
|
26
|
+
ys: (v: number) => number
|
|
27
|
+
x: string
|
|
28
|
+
y: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const hasScales = (s: PlotState | undefined) => Boolean(s?.xScale && s?.yScale)
|
|
32
|
+
|
|
33
|
+
/** Null until the plot has both scales and both channels — nothing can be placed before then. */
|
|
34
|
+
const ctx = $derived.by((): Ctx | null => {
|
|
35
|
+
if (!hasScales(state) || !x || !y) return null
|
|
36
|
+
return {
|
|
37
|
+
rows: state.data ?? [],
|
|
38
|
+
selectedRows: state.selectedRows ?? [],
|
|
39
|
+
xs: state.xScale,
|
|
40
|
+
ys: state.yScale,
|
|
41
|
+
x,
|
|
42
|
+
y
|
|
41
43
|
}
|
|
42
|
-
selectedRows.forEach((row, j) => {
|
|
43
|
-
if (seen.has(row)) return
|
|
44
|
-
out.push({
|
|
45
|
-
key: `s${j}`,
|
|
46
|
-
cx: scalePos(xs, row[x]),
|
|
47
|
-
cy: ys(Number(row[y])),
|
|
48
|
-
row,
|
|
49
|
-
selected: true
|
|
50
|
-
})
|
|
51
|
-
})
|
|
52
|
-
return out.filter((m) => Number.isFinite(m.cx) && Number.isFinite(m.cy))
|
|
53
44
|
})
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
46
|
+
const place = (c: Ctx, row: Row) => ({
|
|
47
|
+
cx: scalePos(c.xs, row[c.x]),
|
|
48
|
+
cy: c.ys(Number(row[c.y]))
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
/** Marks for the declarative `highlight` selector. */
|
|
52
|
+
function highlightMarks(c: Ctx, selector: Props['highlight']): Mark[] {
|
|
53
|
+
if (selector === null || selector === undefined) return []
|
|
54
|
+
const selected = new Set(c.selectedRows)
|
|
55
|
+
return resolveHighlight(c.rows, selector, { y: c.y }).map((i) => ({
|
|
56
|
+
key: `h${i}`,
|
|
57
|
+
...place(c, c.rows[i]),
|
|
58
|
+
row: c.rows[i],
|
|
59
|
+
// A statically-highlighted point that is also selected renders as
|
|
60
|
+
// selected — selection is the stronger, interactive state.
|
|
61
|
+
selected: selected.has(c.rows[i])
|
|
62
|
+
}))
|
|
60
63
|
}
|
|
64
|
+
|
|
65
|
+
/** Marks for interactive selection, minus any row a highlight mark already covers. */
|
|
66
|
+
function selectionMarks(c: Ctx, covered: Set<Row>): Mark[] {
|
|
67
|
+
return c.selectedRows
|
|
68
|
+
.map((row, j) => ({ row, j }))
|
|
69
|
+
.filter(({ row }) => !covered.has(row))
|
|
70
|
+
.map(({ row, j }) => ({ key: `s${j}`, ...place(c, row), row, selected: true }))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const isPlaced = (m: Mark) => Number.isFinite(m.cx) && Number.isFinite(m.cy)
|
|
74
|
+
|
|
75
|
+
const marks = $derived.by(() => {
|
|
76
|
+
if (!ctx) return []
|
|
77
|
+
const highlighted = highlightMarks(ctx, highlight)
|
|
78
|
+
// Derived from the marks just built, so "already emitted" has one source of truth.
|
|
79
|
+
const selected = selectionMarks(ctx, new Set(highlighted.map((m) => m.row)))
|
|
80
|
+
return [...highlighted, ...selected].filter(isPlaced)
|
|
81
|
+
})
|
|
61
82
|
</script>
|
|
62
83
|
|
|
63
84
|
{#if marks.length}
|
|
@@ -70,7 +91,7 @@
|
|
|
70
91
|
data-plot-selected={m.selected ? 'true' : undefined}
|
|
71
92
|
/>
|
|
72
93
|
{#if label}
|
|
73
|
-
{@const text = resolveLabel(m.row)}
|
|
94
|
+
{@const text = resolveLabel(label, m.row, y)}
|
|
74
95
|
{#if text}
|
|
75
96
|
<text x={m.cx} y={m.cy} dy="-8" text-anchor="middle" data-plot-highlight-label
|
|
76
97
|
>{text}</text
|
package/dist/geoms/Line.svelte
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
4
|
import { GeomState } from './lib/GeomState.svelte.js'
|
|
5
5
|
import { buildLineMarks } from './lib/marks/line.js'
|
|
6
|
+
import { resolveLabel } from './lib/aesthetics.js'
|
|
6
7
|
import { buildSymbolPath } from '../lib/brewing/marks/points.js'
|
|
7
8
|
import { keyboardNav } from '../lib/keyboard-nav.js'
|
|
8
9
|
import { buildSelectDetail } from '../lib/select.js'
|
|
@@ -47,13 +48,6 @@
|
|
|
47
48
|
keyboard = false
|
|
48
49
|
}: Props = $props()
|
|
49
50
|
|
|
50
|
-
function resolveLabel(data: Row): string | null {
|
|
51
|
-
if (!label) return null
|
|
52
|
-
if (label === true) return String((y ? data[y] : undefined) ?? '')
|
|
53
|
-
if (typeof label === 'function') return String(label(data) ?? '')
|
|
54
|
-
return typeof label === 'string' ? String(data[label] ?? '') : null
|
|
55
|
-
}
|
|
56
|
-
|
|
57
51
|
const plotState = getContext<PlotState>('plot-state')
|
|
58
52
|
|
|
59
53
|
const geom = new GeomState(plotState, () => ({
|
|
@@ -81,7 +75,14 @@
|
|
|
81
75
|
onselect?.(pt.data)
|
|
82
76
|
if (plotState.interactive)
|
|
83
77
|
plotState.handleSelect(
|
|
84
|
-
buildSelectDetail(
|
|
78
|
+
buildSelectDetail({
|
|
79
|
+
datum: pt.data,
|
|
80
|
+
index: plotState.data.indexOf(pt.data),
|
|
81
|
+
channels: { x, y },
|
|
82
|
+
geom: 'line',
|
|
83
|
+
series: seg?.key,
|
|
84
|
+
event
|
|
85
|
+
})
|
|
85
86
|
)
|
|
86
87
|
}
|
|
87
88
|
</script>
|
|
@@ -113,7 +114,7 @@
|
|
|
113
114
|
{/if}
|
|
114
115
|
{#if label}
|
|
115
116
|
{#each seg.points as pt (`label::${pt.i}`)}
|
|
116
|
-
{@const text = resolveLabel(pt.data)}
|
|
117
|
+
{@const text = resolveLabel(label, pt.data, y)}
|
|
117
118
|
{#if text}
|
|
118
119
|
<LabelPill
|
|
119
120
|
x={pt.x + (options.labelOffset?.x ?? 0)}
|
package/dist/geoms/Point.svelte
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
4
|
import { GeomState } from './lib/GeomState.svelte.js'
|
|
5
5
|
import { buildPointMarks } from './lib/marks/point.js'
|
|
6
|
+
import { resolveLabel } from './lib/aesthetics.js'
|
|
6
7
|
import { keyboardNav } from '../lib/keyboard-nav.js'
|
|
7
8
|
import { buildSelectDetail } from '../lib/select.js'
|
|
8
9
|
import LabelPill from './LabelPill.svelte'
|
|
@@ -49,13 +50,6 @@
|
|
|
49
50
|
keyboard = false
|
|
50
51
|
}: Props = $props()
|
|
51
52
|
|
|
52
|
-
function resolveLabel(data: Row): string | null {
|
|
53
|
-
if (!label) return null
|
|
54
|
-
if (label === true) return String((y ? data[y] : undefined) ?? '')
|
|
55
|
-
if (typeof label === 'function') return String(label(data) ?? '')
|
|
56
|
-
return typeof label === 'string' ? String(data[label] ?? '') : null
|
|
57
|
-
}
|
|
58
|
-
|
|
59
53
|
const plotState = getContext<PlotState>('plot-state')
|
|
60
54
|
|
|
61
55
|
// All plumbing (register/data/scales/colors) + mark building (geometry + fill/color/alpha)
|
|
@@ -80,14 +74,14 @@
|
|
|
80
74
|
onselect?.(pt.data)
|
|
81
75
|
if (plotState.interactive)
|
|
82
76
|
plotState.handleSelect(
|
|
83
|
-
buildSelectDetail(
|
|
84
|
-
pt.data,
|
|
85
|
-
plotState.data.indexOf(pt.data),
|
|
86
|
-
{ x, y },
|
|
87
|
-
'point',
|
|
88
|
-
seriesField ? pt.data[seriesField] : undefined,
|
|
77
|
+
buildSelectDetail({
|
|
78
|
+
datum: pt.data,
|
|
79
|
+
index: plotState.data.indexOf(pt.data),
|
|
80
|
+
channels: { x, y },
|
|
81
|
+
geom: 'point',
|
|
82
|
+
series: seriesField ? pt.data[seriesField] : undefined,
|
|
89
83
|
event
|
|
90
|
-
)
|
|
84
|
+
})
|
|
91
85
|
)
|
|
92
86
|
}
|
|
93
87
|
</script>
|
|
@@ -141,7 +135,7 @@
|
|
|
141
135
|
/>
|
|
142
136
|
{/if}
|
|
143
137
|
{#if label}
|
|
144
|
-
{@const text = resolveLabel(pt.data)}
|
|
138
|
+
{@const text = resolveLabel(label, pt.data, y)}
|
|
145
139
|
{#if text}
|
|
146
140
|
<LabelPill
|
|
147
141
|
x={pt.cx + (options.labelOffset?.x ?? 0)}
|