@rokkit/chart 1.3.8 → 1.3.9
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/Chart.svelte +50 -64
- package/dist/Plot/Root.svelte +4 -1
- package/dist/Plot/Root.svelte.d.ts +2 -0
- package/dist/Plot/index.d.ts +1 -1
- package/dist/Plot/index.js +1 -1
- package/dist/PlotState.svelte.d.ts +14 -2
- package/dist/PlotState.svelte.js +46 -4
- package/dist/charts/AreaChart.svelte +8 -1
- package/dist/charts/AreaChart.svelte.d.ts +5 -0
- package/dist/charts/BarChart.svelte +8 -1
- package/dist/charts/BarChart.svelte.d.ts +5 -0
- package/dist/charts/BoxPlot.svelte +3 -1
- package/dist/charts/BoxPlot.svelte.d.ts +1 -0
- package/dist/charts/BubbleChart.svelte +3 -1
- package/dist/charts/BubbleChart.svelte.d.ts +1 -0
- package/dist/charts/LineChart.svelte +3 -1
- package/dist/charts/LineChart.svelte.d.ts +1 -0
- package/dist/charts/PieChart.svelte +4 -2
- package/dist/charts/PieChart.svelte.d.ts +1 -0
- package/dist/charts/ScatterPlot.svelte +3 -1
- package/dist/charts/ScatterPlot.svelte.d.ts +1 -0
- package/dist/charts/ViolinPlot.svelte +3 -1
- package/dist/charts/ViolinPlot.svelte.d.ts +1 -0
- package/dist/geoms/Arc.svelte +21 -55
- package/dist/geoms/Arc.svelte.d.ts +4 -4
- package/dist/geoms/Area.svelte +45 -44
- package/dist/geoms/Area.svelte.d.ts +8 -1
- package/dist/geoms/Bar.svelte +33 -56
- package/dist/geoms/Bar.svelte.d.ts +9 -0
- package/dist/geoms/Box.svelte +118 -131
- package/dist/geoms/Box.svelte.d.ts +5 -3
- package/dist/geoms/Candlestick.svelte +22 -77
- package/dist/geoms/Candlestick.svelte.d.ts +2 -0
- package/dist/geoms/Heatmap.svelte +21 -66
- package/dist/geoms/Heatmap.svelte.d.ts +2 -0
- package/dist/geoms/Hexbin.svelte +20 -100
- package/dist/geoms/Hexbin.svelte.d.ts +2 -0
- package/dist/geoms/Jitter.svelte +21 -36
- package/dist/geoms/Jitter.svelte.d.ts +5 -3
- package/dist/geoms/Line.svelte +27 -31
- package/dist/geoms/Line.svelte.d.ts +5 -0
- package/dist/geoms/Point.svelte +31 -66
- package/dist/geoms/Point.svelte.d.ts +5 -1
- package/dist/geoms/Ribbon.svelte +30 -124
- package/dist/geoms/Ribbon.svelte.d.ts +2 -0
- package/dist/geoms/Rule.svelte +5 -1
- package/dist/geoms/Rule.svelte.d.ts +3 -0
- package/dist/geoms/Violin.svelte +21 -40
- package/dist/geoms/Violin.svelte.d.ts +5 -3
- package/dist/geoms/Waterfall.svelte +19 -80
- package/dist/geoms/Waterfall.svelte.d.ts +2 -0
- package/dist/geoms/lib/GeomState.svelte.d.ts +51 -0
- package/dist/geoms/lib/GeomState.svelte.js +79 -0
- package/dist/geoms/lib/aesthetics.d.ts +36 -0
- package/dist/geoms/lib/aesthetics.js +45 -0
- package/dist/geoms/lib/areas.d.ts +1 -1
- package/dist/geoms/lib/areas.js +4 -2
- package/dist/geoms/lib/bars.d.ts +2 -2
- package/dist/geoms/lib/bars.js +13 -8
- package/dist/geoms/lib/marks/arc.d.ts +24 -0
- package/dist/geoms/lib/marks/arc.js +28 -0
- package/dist/geoms/lib/marks/area.d.ts +23 -0
- package/dist/geoms/lib/marks/area.js +38 -0
- package/dist/geoms/lib/marks/bar.d.ts +16 -0
- package/dist/geoms/lib/marks/bar.js +43 -0
- package/dist/geoms/lib/marks/box.d.ts +14 -0
- package/dist/geoms/lib/marks/box.js +30 -0
- package/dist/geoms/lib/marks/candlestick.d.ts +27 -0
- package/dist/geoms/lib/marks/candlestick.js +52 -0
- package/dist/geoms/lib/marks/heatmap.d.ts +24 -0
- package/dist/geoms/lib/marks/heatmap.js +41 -0
- package/dist/geoms/lib/marks/hexbin.d.ts +29 -0
- package/dist/geoms/lib/marks/hexbin.js +62 -0
- package/dist/geoms/lib/marks/jitter.d.ts +21 -0
- package/dist/geoms/lib/marks/jitter.js +36 -0
- package/dist/geoms/lib/marks/line.d.ts +26 -0
- package/dist/geoms/lib/marks/line.js +25 -0
- package/dist/geoms/lib/marks/point.d.ts +25 -0
- package/dist/geoms/lib/marks/point.js +58 -0
- package/dist/geoms/lib/marks/ribbon.d.ts +31 -0
- package/dist/geoms/lib/marks/ribbon.js +77 -0
- package/dist/geoms/lib/marks/violin.d.ts +13 -0
- package/dist/geoms/lib/marks/violin.js +31 -0
- package/dist/geoms/lib/marks/waterfall.d.ts +25 -0
- package/dist/geoms/lib/marks/waterfall.js +58 -0
- package/dist/index.d.ts +1 -8
- package/dist/index.js +1 -8
- package/dist/lib/preset.d.ts +11 -1
- package/dist/lib/preset.js +16 -4
- package/package.json +1 -1
- package/dist/Plot/Arc.svelte +0 -30
- package/dist/Plot/Arc.svelte.d.ts +0 -18
- package/dist/lib/brewer.d.ts +0 -9
- package/dist/lib/brewer.js +0 -25
- package/dist/lib/brewing/BoxBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/BoxBrewer.svelte.js +0 -14
- package/dist/lib/brewing/CartesianBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/CartesianBrewer.svelte.js +0 -21
- package/dist/lib/brewing/PieBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/PieBrewer.svelte.js +0 -14
- package/dist/lib/brewing/QuartileBrewer.svelte.d.ts +0 -9
- package/dist/lib/brewing/QuartileBrewer.svelte.js +0 -51
- package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/ViolinBrewer.svelte.js +0 -14
- package/dist/lib/brewing/axes.svelte.d.ts +0 -66
- package/dist/lib/brewing/axes.svelte.js +0 -270
- package/dist/lib/brewing/bars.svelte.d.ts +0 -56
- package/dist/lib/brewing/bars.svelte.js +0 -201
- package/dist/lib/brewing/brewer.svelte.d.ts +0 -124
- package/dist/lib/brewing/brewer.svelte.js +0 -277
- package/dist/lib/brewing/dimensions.svelte.d.ts +0 -35
- package/dist/lib/brewing/dimensions.svelte.js +0 -56
- package/dist/lib/brewing/index.svelte.d.ts +0 -118
- package/dist/lib/brewing/index.svelte.js +0 -205
- package/dist/lib/brewing/legends.svelte.d.ts +0 -48
- package/dist/lib/brewing/legends.svelte.js +0 -137
- package/dist/lib/brewing/marks/areas.d.ts +0 -31
- package/dist/lib/brewing/marks/areas.js +0 -72
- package/dist/lib/brewing/marks/bars.d.ts +0 -1
- package/dist/lib/brewing/marks/bars.js +0 -49
- package/dist/lib/brewing/scales.svelte.d.ts +0 -24
- package/dist/lib/brewing/scales.svelte.js +0 -82
- package/dist/lib/context.d.ts +0 -13
- package/dist/lib/context.js +0 -131
- package/dist/lib/plot/chartProps.d.ts +0 -177
- package/dist/lib/plot/chartProps.js +0 -76
- package/dist/lib/scales.svelte.d.ts +0 -35
- package/dist/lib/scales.svelte.js +0 -151
- package/dist/lib/ticks.d.ts +0 -36
- package/dist/lib/ticks.js +0 -46
package/dist/geoms/Hexbin.svelte
CHANGED
|
@@ -1,120 +1,39 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
3
|
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
type Scale = (value: unknown) => number | undefined
|
|
7
|
-
type Bin = { cx: number; cy: number; count: number; points: Row[] }
|
|
4
|
+
import { GeomState } from './lib/GeomState.svelte.js'
|
|
5
|
+
import { buildHexbinMarks, hexPath } from './lib/marks/hexbin.js'
|
|
8
6
|
|
|
9
7
|
type Props = {
|
|
10
8
|
x?: string
|
|
11
9
|
y?: string
|
|
12
10
|
color?: string
|
|
13
11
|
fill?: string
|
|
12
|
+
/** Fixed hex opacity 0–1; defaults to the per-geom preset (hexbin = 1). */
|
|
13
|
+
alpha?: number
|
|
14
14
|
stat?: string
|
|
15
15
|
options?: { radius?: number }
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
let {
|
|
19
|
-
x,
|
|
20
|
-
y,
|
|
21
|
-
color = undefined,
|
|
22
|
-
fill: fillProp = undefined,
|
|
23
|
-
stat = 'identity',
|
|
24
|
-
options = {}
|
|
25
|
-
}: Props = $props()
|
|
18
|
+
let { x, y, color, fill, alpha, stat = 'identity', options = {} }: Props = $props()
|
|
26
19
|
|
|
20
|
+
const colorField = $derived(fill ?? color)
|
|
27
21
|
const radius = $derived(options.radius ?? 20)
|
|
28
|
-
|
|
29
22
|
const plotState = getContext<PlotState>('plot-state')
|
|
30
|
-
let id = $state<string | null>(null)
|
|
31
|
-
|
|
32
|
-
onMount(() => {
|
|
33
|
-
id = plotState.registerGeom({
|
|
34
|
-
type: 'hexbin',
|
|
35
|
-
channels: { x, y, color: fillProp ?? color },
|
|
36
|
-
stat
|
|
37
|
-
})
|
|
38
|
-
})
|
|
39
|
-
onDestroy(() => {
|
|
40
|
-
if (id) plotState.unregisterGeom(id)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
$effect(() => {
|
|
44
|
-
if (id)
|
|
45
|
-
plotState.updateGeom(id, {
|
|
46
|
-
channels: { x, y, color: fillProp ?? color },
|
|
47
|
-
stat
|
|
48
|
-
})
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
const data = $derived(id ? plotState.geomData(id) : [])
|
|
52
|
-
const xScale = $derived(plotState.xScale)
|
|
53
|
-
const yScale = $derived(plotState.yScale)
|
|
54
|
-
const continuousColor = $derived(plotState.continuousColorScale)
|
|
55
|
-
|
|
56
|
-
// Hex binning: group data points into hexagonal cells
|
|
57
|
-
// eslint-disable-next-line max-params, complexity
|
|
58
|
-
function hexBin(
|
|
59
|
-
rows: Row[],
|
|
60
|
-
xField: string | undefined,
|
|
61
|
-
yField: string | undefined,
|
|
62
|
-
xScale: Scale,
|
|
63
|
-
yScale: Scale,
|
|
64
|
-
r: number
|
|
65
|
-
): Bin[] {
|
|
66
|
-
const dx = r * 2 * Math.sin(Math.PI / 3) // horizontal spacing
|
|
67
|
-
const dy = r * 1.5 // vertical spacing
|
|
68
|
-
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
69
|
-
const bins = new Map<string, Bin>()
|
|
70
|
-
|
|
71
|
-
for (const d of rows) {
|
|
72
|
-
const px = xScale(d[xField ?? ''])
|
|
73
|
-
const py = yScale(d[yField ?? ''])
|
|
74
|
-
if (px === null || px === undefined || py === null || py === undefined) continue
|
|
75
|
-
|
|
76
|
-
// Find nearest hex center
|
|
77
|
-
const col = Math.round(px / dx)
|
|
78
|
-
const row = Math.round(py / dy)
|
|
79
|
-
const cx = col * dx + (row % 2 ? dx / 2 : 0)
|
|
80
|
-
const cy = row * dy
|
|
81
|
-
const key = `${col},${row}`
|
|
82
|
-
|
|
83
|
-
let bin = bins.get(key)
|
|
84
|
-
if (!bin) {
|
|
85
|
-
bin = { cx, cy, count: 0, points: [] }
|
|
86
|
-
bins.set(key, bin)
|
|
87
|
-
}
|
|
88
|
-
bin.count++
|
|
89
|
-
bin.points.push(d)
|
|
90
|
-
}
|
|
91
|
-
return [...bins.values()]
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Build hexagon path for a given radius
|
|
95
|
-
function hexPath(r: number) {
|
|
96
|
-
const angles = [0, 1, 2, 3, 4, 5].map((i) => ((i * 60 - 30) * Math.PI) / 180)
|
|
97
|
-
return `${angles.map((a, i) => `${i === 0 ? 'M' : 'L'}${r * Math.cos(a)},${r * Math.sin(a)}`).join('') }Z`
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const hexes = $derived.by(() => {
|
|
101
|
-
if (!data?.length || !xScale || !yScale) return []
|
|
102
|
-
const bins = hexBin(data, x, y, xScale, yScale, radius)
|
|
103
|
-
const maxCount = Math.max(1, ...bins.map((b) => b.count))
|
|
104
|
-
const colorScale = continuousColor?.scale
|
|
105
|
-
|
|
106
|
-
return bins.map((bin, i) => ({
|
|
107
|
-
key: `hex-${i}`,
|
|
108
|
-
cx: bin.cx,
|
|
109
|
-
cy: bin.cy,
|
|
110
|
-
count: bin.count,
|
|
111
|
-
fill: colorScale
|
|
112
|
-
? colorScale(bin.count)
|
|
113
|
-
: `rgba(66, 133, 244, ${0.1 + 0.9 * (bin.count / maxCount)})`,
|
|
114
|
-
data: { count: bin.count, x: bin.cx, y: bin.cy }
|
|
115
|
-
}))
|
|
116
|
-
})
|
|
117
23
|
|
|
24
|
+
const geom = new GeomState(plotState, () => ({
|
|
25
|
+
type: 'hexbin',
|
|
26
|
+
channels: { x, y, color: colorField },
|
|
27
|
+
stat,
|
|
28
|
+
options,
|
|
29
|
+
alpha,
|
|
30
|
+
build: buildHexbinMarks
|
|
31
|
+
}))
|
|
32
|
+
onMount(geom.register)
|
|
33
|
+
onDestroy(geom.destroy)
|
|
34
|
+
$effect(geom.sync)
|
|
35
|
+
|
|
36
|
+
const hexes = $derived(geom.marks)
|
|
118
37
|
const hex = $derived(hexPath(radius))
|
|
119
38
|
</script>
|
|
120
39
|
|
|
@@ -125,6 +44,7 @@
|
|
|
125
44
|
transform="translate({h.cx},{h.cy})"
|
|
126
45
|
d={hex}
|
|
127
46
|
fill={h.fill}
|
|
47
|
+
fill-opacity={h.alpha}
|
|
128
48
|
stroke="white"
|
|
129
49
|
stroke-width="0.5"
|
|
130
50
|
data-plot-element="hex"
|
package/dist/geoms/Jitter.svelte
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
3
|
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
|
-
import {
|
|
4
|
+
import { GeomState } from './lib/GeomState.svelte.js'
|
|
5
|
+
import { buildJitterMarks } from './lib/marks/jitter.js'
|
|
5
6
|
|
|
6
7
|
type Props = {
|
|
7
8
|
x?: string
|
|
8
9
|
y?: string
|
|
10
|
+
/** Point interior aesthetic (field); defaults to `x`. */
|
|
9
11
|
fill?: string
|
|
12
|
+
/** Outline aesthetic (field); overrides the point stroke when set. */
|
|
13
|
+
color?: string
|
|
10
14
|
r?: number
|
|
11
15
|
method?: 'jitter' | 'swarm'
|
|
12
16
|
/** Confine points to one half of the band (raincloud/split plots). Default 'center'. */
|
|
13
17
|
side?: 'left' | 'right' | 'center'
|
|
14
|
-
|
|
18
|
+
/** Fixed point opacity 0–1; defaults to the per-geom preset (jitter = 0.8). */
|
|
19
|
+
alpha?: number
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
let { x, y, fill, r = 2, method = 'jitter', side = 'center',
|
|
22
|
+
let { x, y, fill, color, r = 2, method = 'jitter', side = 'center', alpha }: Props = $props()
|
|
18
23
|
|
|
19
24
|
const plotState = getContext<PlotState>('plot-state')
|
|
20
|
-
let id = $state<string | null>(null)
|
|
21
25
|
|
|
22
26
|
// Fall back to the root Plot channels when the geom omits x/y (composes under
|
|
23
27
|
// <Plot.Root x y> without silently NaN-ing).
|
|
@@ -26,38 +30,19 @@
|
|
|
26
30
|
// fill ?? effective-x drives the color lookup
|
|
27
31
|
const fillChannel = $derived(fill ?? xf)
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
const geom = new GeomState(plotState, () => ({
|
|
34
|
+
type: 'jitter',
|
|
35
|
+
channels: { x: xf, y: yf, fill: fillChannel, color },
|
|
36
|
+
stat: 'identity',
|
|
37
|
+
options: { method, r, side },
|
|
38
|
+
alpha,
|
|
39
|
+
build: buildJitterMarks
|
|
40
|
+
}))
|
|
41
|
+
onMount(geom.register)
|
|
42
|
+
onDestroy(geom.destroy)
|
|
43
|
+
$effect(geom.sync)
|
|
40
44
|
|
|
41
|
-
$
|
|
42
|
-
if (id) plotState.updateGeom(id, { channels: { x: xf, y: yf, color: fillChannel }, stat: 'identity' })
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
const data = $derived(id ? plotState.geomData(id) : [])
|
|
46
|
-
const xScale = $derived(plotState.xScale)
|
|
47
|
-
const yScale = $derived(plotState.yScale)
|
|
48
|
-
const colors = $derived(plotState.colors)
|
|
49
|
-
|
|
50
|
-
const points = $derived.by(() => {
|
|
51
|
-
if (!data?.length || !xScale || !yScale) return []
|
|
52
|
-
const raw = buildSwarm(data, { x: xf, y: yf, fill: fillChannel }, xScale, yScale, colors, { method, r, side })
|
|
53
|
-
if (!plotState.isFlipped) return raw
|
|
54
|
-
// Horizontal: the band axis (swarm spread) stands up on screen-Y and the value
|
|
55
|
-
// axis runs along screen-X — swap the computed coords through place().
|
|
56
|
-
return raw.map((p) => {
|
|
57
|
-
const s = plotState.place(p.cx, p.cy)
|
|
58
|
-
return { ...p, cx: s.x, cy: s.y }
|
|
59
|
-
})
|
|
60
|
-
})
|
|
45
|
+
const points = $derived(geom.marks)
|
|
61
46
|
</script>
|
|
62
47
|
|
|
63
48
|
{#if points.length > 0}
|
|
@@ -68,7 +53,7 @@
|
|
|
68
53
|
cy={pt.cy}
|
|
69
54
|
{r}
|
|
70
55
|
fill={pt.fill}
|
|
71
|
-
fill-opacity={
|
|
56
|
+
fill-opacity={pt.alpha}
|
|
72
57
|
stroke={pt.stroke}
|
|
73
58
|
stroke-width="0.5"
|
|
74
59
|
data-plot-element="jitter-point"
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
type Props = {
|
|
2
2
|
x?: string;
|
|
3
3
|
y?: string;
|
|
4
|
+
/** Point interior aesthetic (field); defaults to `x`. */
|
|
4
5
|
fill?: string;
|
|
6
|
+
/** Outline aesthetic (field); overrides the point stroke when set. */
|
|
7
|
+
color?: string;
|
|
5
8
|
r?: number;
|
|
6
9
|
method?: 'jitter' | 'swarm';
|
|
7
10
|
/** Confine points to one half of the band (raincloud/split plots). Default 'center'. */
|
|
8
11
|
side?: 'left' | 'right' | 'center';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
12
|
+
/** Fixed point opacity 0–1; defaults to the per-geom preset (jitter = 0.8). */
|
|
13
|
+
alpha?: number;
|
|
12
14
|
};
|
|
13
15
|
declare const Jitter: import("svelte").Component<Props, {}, "">;
|
|
14
16
|
type Jitter = ReturnType<typeof Jitter>;
|
package/dist/geoms/Line.svelte
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
3
|
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
|
-
import {
|
|
4
|
+
import { GeomState } from './lib/GeomState.svelte.js'
|
|
5
|
+
import { buildLineMarks } from './lib/marks/line.js'
|
|
5
6
|
import { buildSymbolPath } from '../lib/brewing/marks/points.js'
|
|
6
7
|
import { keyboardNav } from '../lib/keyboard-nav.js'
|
|
7
8
|
import { buildSelectDetail } from '../lib/select.js'
|
|
@@ -18,8 +19,13 @@
|
|
|
18
19
|
type Props = {
|
|
19
20
|
x?: string
|
|
20
21
|
y?: string
|
|
22
|
+
/** Line/stroke aesthetic (field). */
|
|
21
23
|
color?: string
|
|
24
|
+
/** Accepted as an alias for `color` (a line has no interior). */
|
|
25
|
+
fill?: string
|
|
22
26
|
symbol?: string
|
|
27
|
+
/** Fixed line opacity 0–1; defaults to the per-geom preset (line = 1). */
|
|
28
|
+
alpha?: number
|
|
23
29
|
label?: boolean | string | ((data: Row) => unknown)
|
|
24
30
|
stat?: string
|
|
25
31
|
options?: Options
|
|
@@ -31,7 +37,9 @@
|
|
|
31
37
|
x,
|
|
32
38
|
y,
|
|
33
39
|
color,
|
|
40
|
+
fill,
|
|
34
41
|
symbol: symbolField,
|
|
42
|
+
alpha,
|
|
35
43
|
label = false,
|
|
36
44
|
stat = 'identity',
|
|
37
45
|
options = {},
|
|
@@ -47,7 +55,23 @@
|
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
const plotState = getContext<PlotState>('plot-state')
|
|
50
|
-
|
|
58
|
+
|
|
59
|
+
const geom = new GeomState(plotState, () => ({
|
|
60
|
+
type: 'line',
|
|
61
|
+
channels: { x, y, color, fill, symbol: symbolField },
|
|
62
|
+
stat,
|
|
63
|
+
options,
|
|
64
|
+
alpha,
|
|
65
|
+
build: buildLineMarks
|
|
66
|
+
}))
|
|
67
|
+
onMount(geom.register)
|
|
68
|
+
onDestroy(geom.destroy)
|
|
69
|
+
$effect(geom.sync)
|
|
70
|
+
|
|
71
|
+
const lines = $derived(geom.marks)
|
|
72
|
+
// Markers are a rendering detail resolved from the shared symbol scale.
|
|
73
|
+
const symbolMap = $derived(plotState.symbols)
|
|
74
|
+
const markerRadius = $derived(options.markerRadius ?? 4)
|
|
51
75
|
|
|
52
76
|
function selectPoint(
|
|
53
77
|
pt: { data: Row },
|
|
@@ -60,35 +84,6 @@
|
|
|
60
84
|
buildSelectDetail(pt.data, plotState.data.indexOf(pt.data), { x, y }, 'line', seg?.key, event)
|
|
61
85
|
)
|
|
62
86
|
}
|
|
63
|
-
|
|
64
|
-
onMount(() => {
|
|
65
|
-
id = plotState.registerGeom({
|
|
66
|
-
type: 'line',
|
|
67
|
-
channels: { x, y, color, symbol: symbolField },
|
|
68
|
-
stat,
|
|
69
|
-
options
|
|
70
|
-
})
|
|
71
|
-
})
|
|
72
|
-
onDestroy(() => {
|
|
73
|
-
if (id) plotState.unregisterGeom(id)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
$effect(() => {
|
|
77
|
-
if (id) plotState.updateGeom(id, { channels: { x, y, color, symbol: symbolField }, stat })
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
const data = $derived(id ? plotState.geomData(id) : [])
|
|
81
|
-
const xScale = $derived(plotState.xScale)
|
|
82
|
-
const yScale = $derived(plotState.yScale)
|
|
83
|
-
const colors = $derived(plotState.colors)
|
|
84
|
-
const symbolMap = $derived(plotState.symbols)
|
|
85
|
-
|
|
86
|
-
const lines = $derived.by(() => {
|
|
87
|
-
if (!data?.length || !xScale || !yScale) return []
|
|
88
|
-
return buildLines(data, { x, y, color }, xScale, yScale, colors, options.curve, plotState.place.bind(plotState))
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
const markerRadius = $derived(options.markerRadius ?? 4)
|
|
92
87
|
</script>
|
|
93
88
|
|
|
94
89
|
{#if lines.length > 0}
|
|
@@ -101,6 +96,7 @@
|
|
|
101
96
|
stroke-width={options.strokeWidth ?? 2}
|
|
102
97
|
stroke-linejoin="round"
|
|
103
98
|
stroke-linecap="round"
|
|
99
|
+
stroke-opacity={seg.alpha}
|
|
104
100
|
data-plot-element="line"
|
|
105
101
|
/>
|
|
106
102
|
{#if symbolField && symbolMap}
|
|
@@ -11,8 +11,13 @@ type Options = {
|
|
|
11
11
|
type Props = {
|
|
12
12
|
x?: string;
|
|
13
13
|
y?: string;
|
|
14
|
+
/** Line/stroke aesthetic (field). */
|
|
14
15
|
color?: string;
|
|
16
|
+
/** Accepted as an alias for `color` (a line has no interior). */
|
|
17
|
+
fill?: string;
|
|
15
18
|
symbol?: string;
|
|
19
|
+
/** Fixed line opacity 0–1; defaults to the per-geom preset (line = 1). */
|
|
20
|
+
alpha?: number;
|
|
16
21
|
label?: boolean | string | ((data: Row) => unknown);
|
|
17
22
|
stat?: string;
|
|
18
23
|
options?: Options;
|
package/dist/geoms/Point.svelte
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
|
-
import { scaleSqrt } from 'd3-scale'
|
|
4
3
|
import type { PlotState } from '../PlotState.svelte.js'
|
|
5
|
-
import {
|
|
4
|
+
import { GeomState } from './lib/GeomState.svelte.js'
|
|
5
|
+
import { buildPointMarks } from './lib/marks/point.js'
|
|
6
6
|
import { keyboardNav } from '../lib/keyboard-nav.js'
|
|
7
7
|
import { buildSelectDetail } from '../lib/select.js'
|
|
8
8
|
import LabelPill from './LabelPill.svelte'
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
minRadius?: number
|
|
13
13
|
maxRadius?: number
|
|
14
14
|
radius?: number
|
|
15
|
-
opacity?: number
|
|
16
15
|
jitter?: { width?: number; height?: number } | null
|
|
17
16
|
labelOffset?: { x?: number; y?: number }
|
|
18
17
|
}
|
|
@@ -20,9 +19,14 @@
|
|
|
20
19
|
type Props = {
|
|
21
20
|
x?: string
|
|
22
21
|
y?: string
|
|
22
|
+
/** Outline / point-stroke aesthetic (field). */
|
|
23
23
|
color?: string
|
|
24
|
+
/** Interior aesthetic (field); falls back to `color` when omitted. */
|
|
25
|
+
fill?: string
|
|
24
26
|
size?: string
|
|
25
27
|
symbol?: string
|
|
28
|
+
/** Fixed mark opacity 0–1; defaults to the per-geom preset (point = 0.8). */
|
|
29
|
+
alpha?: number
|
|
26
30
|
label?: boolean | string | ((data: Row) => unknown)
|
|
27
31
|
stat?: string
|
|
28
32
|
options?: Options
|
|
@@ -34,8 +38,10 @@
|
|
|
34
38
|
x,
|
|
35
39
|
y,
|
|
36
40
|
color,
|
|
41
|
+
fill,
|
|
37
42
|
size,
|
|
38
43
|
symbol: symbolField,
|
|
44
|
+
alpha,
|
|
39
45
|
label = false,
|
|
40
46
|
stat = 'identity',
|
|
41
47
|
options = {},
|
|
@@ -51,7 +57,24 @@
|
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
const plotState = getContext<PlotState>('plot-state')
|
|
54
|
-
|
|
60
|
+
|
|
61
|
+
// All plumbing (register/data/scales/colors) + mark building (geometry + fill/color/alpha)
|
|
62
|
+
// lives in GeomState + buildPointMarks. This component only renders + handles interactivity.
|
|
63
|
+
const geom = new GeomState(plotState, () => ({
|
|
64
|
+
type: 'point',
|
|
65
|
+
channels: { x, y, color, fill, size, symbol: symbolField },
|
|
66
|
+
stat,
|
|
67
|
+
options,
|
|
68
|
+
alpha,
|
|
69
|
+
build: buildPointMarks
|
|
70
|
+
}))
|
|
71
|
+
onMount(geom.register)
|
|
72
|
+
onDestroy(geom.destroy)
|
|
73
|
+
$effect(geom.sync)
|
|
74
|
+
|
|
75
|
+
const points = $derived(geom.marks)
|
|
76
|
+
|
|
77
|
+
const seriesField = $derived(fill ?? color)
|
|
55
78
|
|
|
56
79
|
function selectPoint(pt: { data: Row }, event: MouseEvent | KeyboardEvent) {
|
|
57
80
|
onselect?.(pt.data)
|
|
@@ -62,75 +85,17 @@
|
|
|
62
85
|
plotState.data.indexOf(pt.data),
|
|
63
86
|
{ x, y },
|
|
64
87
|
'point',
|
|
65
|
-
|
|
88
|
+
seriesField ? pt.data[seriesField] : undefined,
|
|
66
89
|
event
|
|
67
90
|
)
|
|
68
91
|
)
|
|
69
92
|
}
|
|
70
|
-
|
|
71
|
-
onMount(() => {
|
|
72
|
-
id = plotState.registerGeom({
|
|
73
|
-
type: 'point',
|
|
74
|
-
channels: { x, y, color, size, symbol: symbolField },
|
|
75
|
-
stat,
|
|
76
|
-
options
|
|
77
|
-
})
|
|
78
|
-
})
|
|
79
|
-
onDestroy(() => {
|
|
80
|
-
if (id) plotState.unregisterGeom(id)
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
$effect(() => {
|
|
84
|
-
if (id) plotState.updateGeom(id, { channels: { x, y, color, size, symbol: symbolField }, stat })
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
const data = $derived(id ? plotState.geomData(id) : [])
|
|
88
|
-
const xScale = $derived(plotState.xScale)
|
|
89
|
-
const yScale = $derived(plotState.yScale)
|
|
90
|
-
const colors = $derived(plotState.colors)
|
|
91
|
-
const symbolMap = $derived(plotState.symbols)
|
|
92
|
-
|
|
93
|
-
function buildSizeScale() {
|
|
94
|
-
if (!size || !data?.length) return null
|
|
95
|
-
const vals = data.map((d) => Number(d[size])).filter((v) => !isNaN(v))
|
|
96
|
-
if (!vals.length) return null
|
|
97
|
-
const minVal = Math.min(...vals)
|
|
98
|
-
const maxVal = Math.max(...vals)
|
|
99
|
-
const minRadius = options.minRadius ?? 3
|
|
100
|
-
const maxRadius = options.maxRadius ?? 20
|
|
101
|
-
return scaleSqrt().domain([minVal, maxVal]).range([minRadius, maxRadius])
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const sizeScale = $derived.by(() => buildSizeScale())
|
|
105
|
-
|
|
106
|
-
const defaultRadius = $derived(options.radius ?? 4)
|
|
107
|
-
|
|
108
|
-
const points = $derived.by(() => {
|
|
109
|
-
if (!data?.length || !xScale || !yScale) return []
|
|
110
|
-
const raw = buildPoints(
|
|
111
|
-
data,
|
|
112
|
-
{ x, y, color, size, symbol: symbolField },
|
|
113
|
-
xScale,
|
|
114
|
-
yScale,
|
|
115
|
-
colors,
|
|
116
|
-
sizeScale,
|
|
117
|
-
symbolMap,
|
|
118
|
-
defaultRadius,
|
|
119
|
-
options?.jitter ?? null
|
|
120
|
-
)
|
|
121
|
-
if (!plotState.isFlipped) return raw
|
|
122
|
-
// Horizontal: swap each point's screen coords (value → screen-X, category → screen-Y).
|
|
123
|
-
return raw.map((p) => {
|
|
124
|
-
const s = plotState.place(p.cx, p.cy)
|
|
125
|
-
return { ...p, cx: s.x, cy: s.y }
|
|
126
|
-
})
|
|
127
|
-
})
|
|
128
93
|
</script>
|
|
129
94
|
|
|
130
95
|
{#if points.length > 0}
|
|
131
96
|
<g data-plot-geom="point">
|
|
132
97
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
133
|
-
{#each points as pt
|
|
98
|
+
{#each points as pt (pt.key)}
|
|
134
99
|
{#if pt.symbolPath}
|
|
135
100
|
<path
|
|
136
101
|
transform="translate({pt.cx},{pt.cy})"
|
|
@@ -138,7 +103,7 @@
|
|
|
138
103
|
fill={pt.fill}
|
|
139
104
|
stroke={pt.stroke}
|
|
140
105
|
stroke-width="1"
|
|
141
|
-
fill-opacity={
|
|
106
|
+
fill-opacity={pt.alpha}
|
|
142
107
|
data-plot-element="point"
|
|
143
108
|
role={onselect || keyboard || plotState.interactive ? 'button' : 'graphics-symbol'}
|
|
144
109
|
tabindex={onselect || keyboard || plotState.interactive ? 0 : undefined}
|
|
@@ -160,7 +125,7 @@
|
|
|
160
125
|
fill={pt.fill}
|
|
161
126
|
stroke={pt.stroke}
|
|
162
127
|
stroke-width="1"
|
|
163
|
-
fill-opacity={
|
|
128
|
+
fill-opacity={pt.alpha}
|
|
164
129
|
data-plot-element="point"
|
|
165
130
|
role={onselect || keyboard || plotState.interactive ? 'button' : 'graphics-symbol'}
|
|
166
131
|
tabindex={onselect || keyboard || plotState.interactive ? 0 : undefined}
|
|
@@ -3,7 +3,6 @@ type Options = {
|
|
|
3
3
|
minRadius?: number;
|
|
4
4
|
maxRadius?: number;
|
|
5
5
|
radius?: number;
|
|
6
|
-
opacity?: number;
|
|
7
6
|
jitter?: {
|
|
8
7
|
width?: number;
|
|
9
8
|
height?: number;
|
|
@@ -16,9 +15,14 @@ type Options = {
|
|
|
16
15
|
type Props = {
|
|
17
16
|
x?: string;
|
|
18
17
|
y?: string;
|
|
18
|
+
/** Outline / point-stroke aesthetic (field). */
|
|
19
19
|
color?: string;
|
|
20
|
+
/** Interior aesthetic (field); falls back to `color` when omitted. */
|
|
21
|
+
fill?: string;
|
|
20
22
|
size?: string;
|
|
21
23
|
symbol?: string;
|
|
24
|
+
/** Fixed mark opacity 0–1; defaults to the per-geom preset (point = 0.8). */
|
|
25
|
+
alpha?: number;
|
|
22
26
|
label?: boolean | string | ((data: Row) => unknown);
|
|
23
27
|
stat?: string;
|
|
24
28
|
options?: Options;
|