@rokkit/chart 1.3.7 → 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/AnimatedPlot.svelte +26 -8
- package/dist/AnimatedPlot.svelte.d.ts +5 -0
- package/dist/Chart.svelte +51 -65
- package/dist/FacetPlot.svelte +1 -1
- package/dist/Plot/Axis.svelte +19 -10
- package/dist/Plot/Root.svelte +67 -46
- package/dist/Plot/Root.svelte.d.ts +29 -2
- package/dist/Plot/Timeline.svelte +39 -13
- package/dist/Plot/index.d.ts +5 -5
- package/dist/Plot/index.js +5 -5
- package/dist/Plot.svelte +95 -178
- package/dist/Plot.svelte.d.ts +4 -0
- package/dist/PlotState.svelte.d.ts +29 -1
- package/dist/PlotState.svelte.js +163 -19
- package/dist/PlotSurface.svelte +162 -0
- package/dist/PlotSurface.svelte.d.ts +25 -0
- package/dist/charts/AreaChart.svelte +12 -2
- package/dist/charts/AreaChart.svelte.d.ts +6 -0
- package/dist/charts/BarChart.svelte +40 -4
- package/dist/charts/BarChart.svelte.d.ts +15 -1
- package/dist/charts/BoxPlot.svelte +7 -3
- package/dist/charts/BoxPlot.svelte.d.ts +2 -0
- package/dist/charts/BubbleChart.svelte +37 -4
- package/dist/charts/BubbleChart.svelte.d.ts +12 -1
- package/dist/charts/LineChart.svelte +4 -2
- package/dist/charts/LineChart.svelte.d.ts +1 -0
- package/dist/charts/PieChart.svelte +5 -3
- package/dist/charts/PieChart.svelte.d.ts +1 -0
- package/dist/charts/ScatterPlot.svelte +35 -4
- package/dist/charts/ScatterPlot.svelte.d.ts +11 -1
- package/dist/charts/ViolinPlot.svelte +7 -3
- package/dist/charts/ViolinPlot.svelte.d.ts +2 -0
- package/dist/examples/BarChartExample.svelte +1 -1
- package/dist/geoms/Arc.svelte +21 -54
- 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 -45
- package/dist/geoms/Bar.svelte.d.ts +9 -0
- package/dist/geoms/Box.svelte +103 -61
- package/dist/geoms/Box.svelte.d.ts +12 -3
- package/dist/geoms/Candlestick.svelte +26 -75
- 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 +67 -0
- package/dist/geoms/Jitter.svelte.d.ts +17 -0
- package/dist/geoms/Line.svelte +27 -31
- package/dist/geoms/Line.svelte.d.ts +5 -0
- package/dist/geoms/Point.svelte +31 -60
- 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 +117 -0
- package/dist/geoms/Rule.svelte.d.ts +19 -0
- package/dist/geoms/Violin.svelte +36 -31
- package/dist/geoms/Violin.svelte.d.ts +10 -3
- package/dist/geoms/Waterfall.svelte +25 -79
- 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 +6 -2
- package/dist/geoms/lib/areas.js +20 -12
- package/dist/geoms/lib/bars.d.ts +18 -3
- package/dist/geoms/lib/bars.js +80 -101
- 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 +25 -12
- package/dist/index.js +26 -13
- package/dist/lib/brewing/marks/arcs.js +7 -1
- package/dist/lib/brewing/marks/boxes.d.ts +11 -2
- package/dist/lib/brewing/marks/boxes.js +32 -11
- package/dist/lib/brewing/marks/lines.d.ts +4 -1
- package/dist/lib/brewing/marks/lines.js +18 -15
- package/dist/lib/brewing/marks/swarm.d.ts +32 -0
- package/dist/lib/brewing/marks/swarm.js +125 -0
- package/dist/lib/brewing/marks/violins.d.ts +4 -20
- package/dist/lib/brewing/marks/violins.js +41 -25
- package/dist/lib/brewing/stats.d.ts +3 -1
- package/dist/lib/brewing/stats.js +28 -11
- package/dist/lib/plot/scales.js +12 -7
- package/dist/lib/plot/types.d.ts +9 -1
- package/dist/lib/plot/types.js +3 -1
- 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/Plot/Area.svelte +0 -54
- package/dist/Plot/Area.svelte.d.ts +0 -14
- package/dist/Plot/Bar.svelte +0 -69
- package/dist/Plot/Bar.svelte.d.ts +0 -11
- package/dist/Plot/Line.svelte +0 -54
- package/dist/Plot/Line.svelte.d.ts +0 -13
- package/dist/Plot/Point.svelte +0 -58
- package/dist/Plot/Point.svelte.d.ts +0 -13
- 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
|
@@ -1,6 +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 { GeomState } from './lib/GeomState.svelte.js'
|
|
5
|
+
import { buildCandleMarks } from './lib/marks/candlestick.js'
|
|
4
6
|
|
|
5
7
|
type Options = {
|
|
6
8
|
open?: string
|
|
@@ -17,86 +19,33 @@
|
|
|
17
19
|
y?: string
|
|
18
20
|
color?: string
|
|
19
21
|
fill?: string
|
|
22
|
+
/** Fixed opacity 0–1; defaults to the per-geom preset (candlestick = 1). */
|
|
23
|
+
alpha?: number
|
|
20
24
|
stat?: string
|
|
21
25
|
options?: Options
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
let {
|
|
25
|
-
x,
|
|
26
|
-
y: _y = undefined,
|
|
27
|
-
color = undefined,
|
|
28
|
-
fill: fillProp = undefined,
|
|
29
|
-
stat = 'identity',
|
|
30
|
-
options = {}
|
|
31
|
-
}: Props = $props()
|
|
28
|
+
let { x, y: _y = undefined, color, fill, alpha, stat = 'identity', options = {} }: Props = $props()
|
|
32
29
|
|
|
33
|
-
const
|
|
30
|
+
const colorField = $derived(fill ?? color)
|
|
34
31
|
const high = $derived(options.high ?? 'high')
|
|
35
|
-
const low = $derived(options.low ?? 'low')
|
|
36
|
-
const close = $derived(options.close ?? 'close')
|
|
37
|
-
const upColor = $derived(options.upColor ?? '#22c55e')
|
|
38
|
-
const downColor = $derived(options.downColor ?? '#ef4444')
|
|
39
32
|
const wickWidth = $derived(options.wickWidth ?? 1)
|
|
40
|
-
|
|
41
33
|
const plotState = getContext<PlotState>('plot-state')
|
|
42
|
-
let id = $state<string | null>(null)
|
|
43
|
-
|
|
44
|
-
onMount(() => {
|
|
45
|
-
// Register with y pointing to 'high' so the y scale covers the full range
|
|
46
|
-
id = plotState.registerGeom({
|
|
47
|
-
type: 'candlestick',
|
|
48
|
-
channels: { x, y: high, color: fillProp ?? color },
|
|
49
|
-
stat
|
|
50
|
-
})
|
|
51
|
-
})
|
|
52
|
-
onDestroy(() => {
|
|
53
|
-
if (id) plotState.unregisterGeom(id)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
$effect(() => {
|
|
57
|
-
if (id)
|
|
58
|
-
plotState.updateGeom(id, {
|
|
59
|
-
channels: { x, y: high, color: fillProp ?? color },
|
|
60
|
-
stat
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
const data = $derived(id ? plotState.geomData(id) : [])
|
|
65
|
-
const xScale = $derived(plotState.xScale)
|
|
66
|
-
const yScale = $derived(plotState.yScale)
|
|
67
|
-
|
|
68
|
-
const candles = $derived.by(() => {
|
|
69
|
-
if (!data?.length || !xScale || !yScale) return []
|
|
70
|
-
const bw = typeof xScale.bandwidth === 'function' ? xScale.bandwidth() : 10
|
|
71
|
-
const bodyWidth = bw * 0.6
|
|
72
|
-
const bodyOffset = (bw - bodyWidth) / 2
|
|
73
34
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
35
|
+
// Register with y pointing to 'high' so the y scale covers the full range.
|
|
36
|
+
const geom = new GeomState(plotState, () => ({
|
|
37
|
+
type: 'candlestick',
|
|
38
|
+
channels: { x, y: high, color: colorField },
|
|
39
|
+
stat,
|
|
40
|
+
options,
|
|
41
|
+
alpha,
|
|
42
|
+
build: buildCandleMarks
|
|
43
|
+
}))
|
|
44
|
+
onMount(geom.register)
|
|
45
|
+
onDestroy(geom.destroy)
|
|
46
|
+
$effect(geom.sync)
|
|
83
47
|
|
|
84
|
-
|
|
85
|
-
key: `${d[x ?? '']}-${i}`,
|
|
86
|
-
// Body
|
|
87
|
-
bodyX: xPos,
|
|
88
|
-
bodyY: bodyTop,
|
|
89
|
-
bodyWidth,
|
|
90
|
-
bodyHeight: Math.max(1, bodyBottom - bodyTop),
|
|
91
|
-
fill: isUp ? upColor : downColor,
|
|
92
|
-
// Wick
|
|
93
|
-
wickX: xPos + bodyWidth / 2,
|
|
94
|
-
wickTop: yScale(highVal),
|
|
95
|
-
wickBottom: yScale(lowVal),
|
|
96
|
-
data: d
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
})
|
|
48
|
+
const candles = $derived(geom.marks)
|
|
100
49
|
</script>
|
|
101
50
|
|
|
102
51
|
{#if candles.length > 0}
|
|
@@ -104,11 +53,12 @@
|
|
|
104
53
|
{#each candles as c (c.key)}
|
|
105
54
|
<!-- Wick (high to low) -->
|
|
106
55
|
<line
|
|
107
|
-
x1={c.
|
|
108
|
-
y1={c.
|
|
109
|
-
x2={c.
|
|
110
|
-
y2={c.
|
|
56
|
+
x1={c.wickX1}
|
|
57
|
+
y1={c.wickY1}
|
|
58
|
+
x2={c.wickX2}
|
|
59
|
+
y2={c.wickY2}
|
|
111
60
|
stroke={c.fill}
|
|
61
|
+
stroke-opacity={c.alpha}
|
|
112
62
|
stroke-width={wickWidth}
|
|
113
63
|
data-plot-element="wick"
|
|
114
64
|
/>
|
|
@@ -119,12 +69,13 @@
|
|
|
119
69
|
width={c.bodyWidth}
|
|
120
70
|
height={c.bodyHeight}
|
|
121
71
|
fill={c.fill}
|
|
72
|
+
fill-opacity={c.alpha}
|
|
122
73
|
data-plot-element="candle"
|
|
123
74
|
role="img"
|
|
124
75
|
onmouseenter={() => plotState.setHovered(c.data)}
|
|
125
76
|
onmouseleave={() => plotState.clearHovered()}
|
|
126
77
|
>
|
|
127
|
-
<title>{c.data[x ?? '']}: O={c.data[open]} H={c.data[high]} L={c.data[low]} C={c.data[close]}</title>
|
|
78
|
+
<title>{c.data[x ?? '']}: O={c.data[options.open ?? 'open']} H={c.data[options.high ?? 'high']} L={c.data[options.low ?? 'low']} C={c.data[options.close ?? 'close']}</title>
|
|
128
79
|
</rect>
|
|
129
80
|
{/each}
|
|
130
81
|
</g>
|
|
@@ -1,85 +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
|
+
import { GeomState } from './lib/GeomState.svelte.js'
|
|
5
|
+
import { buildHeatmapMarks } from './lib/marks/heatmap.js'
|
|
4
6
|
|
|
5
7
|
type Props = {
|
|
6
8
|
x?: string
|
|
7
9
|
y?: string
|
|
8
10
|
color?: string
|
|
9
11
|
fill?: string
|
|
12
|
+
/** Fixed cell opacity 0–1; defaults to the per-geom preset (heatmap = 1). */
|
|
13
|
+
alpha?: number
|
|
10
14
|
stat?: string
|
|
11
15
|
options?: { rounded?: number }
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
let {
|
|
15
|
-
x,
|
|
16
|
-
y,
|
|
17
|
-
color,
|
|
18
|
-
fill: fillProp,
|
|
19
|
-
stat = 'identity',
|
|
20
|
-
options = {}
|
|
21
|
-
}: Props = $props()
|
|
22
|
-
|
|
23
|
-
const colorChannel = $derived(fillProp ?? color)
|
|
18
|
+
let { x, y, color, fill, alpha, stat = 'identity', options = {} }: Props = $props()
|
|
24
19
|
|
|
20
|
+
const colorField = $derived(fill ?? color)
|
|
25
21
|
const plotState = getContext<PlotState>('plot-state')
|
|
26
|
-
let id = $state<string | null>(null)
|
|
27
|
-
|
|
28
|
-
onMount(() => {
|
|
29
|
-
id = plotState.registerGeom({
|
|
30
|
-
type: 'heatmap',
|
|
31
|
-
channels: { x, y, color: colorChannel },
|
|
32
|
-
stat
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
onDestroy(() => {
|
|
36
|
-
if (id) plotState.unregisterGeom(id)
|
|
37
|
-
})
|
|
38
22
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
23
|
+
const geom = new GeomState(plotState, () => ({
|
|
24
|
+
type: 'heatmap',
|
|
25
|
+
channels: { x, y, color: colorField },
|
|
26
|
+
stat,
|
|
27
|
+
options,
|
|
28
|
+
alpha,
|
|
29
|
+
build: buildHeatmapMarks
|
|
30
|
+
}))
|
|
31
|
+
onMount(geom.register)
|
|
32
|
+
onDestroy(geom.destroy)
|
|
33
|
+
$effect(geom.sync)
|
|
34
|
+
|
|
35
|
+
const cells = $derived(geom.marks)
|
|
52
36
|
const rx = $derived(options.rounded ?? 0)
|
|
53
|
-
|
|
54
|
-
const cells = $derived.by(() => {
|
|
55
|
-
if (!data?.length || !xScale || !yScale) return []
|
|
56
|
-
const bwX = typeof xScale.bandwidth === 'function' ? xScale.bandwidth() : 0
|
|
57
|
-
const bwY = typeof yScale.bandwidth === 'function' ? yScale.bandwidth() : 0
|
|
58
|
-
|
|
59
|
-
return data.map((d, i) => {
|
|
60
|
-
const xVal = d[x ?? '']
|
|
61
|
-
const yVal = d[y ?? '']
|
|
62
|
-
const colorVal = colorChannel ? d[colorChannel] : null
|
|
63
|
-
let cellFill = '#ccc'
|
|
64
|
-
|
|
65
|
-
if (continuousColor) {
|
|
66
|
-
cellFill = continuousColor.scale(Number(colorVal))
|
|
67
|
-
} else {
|
|
68
|
-
const entry = categoricalColors?.get(colorVal)
|
|
69
|
-
if (entry) cellFill = entry.fill
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
key: `${xVal}-${yVal}-${i}`,
|
|
74
|
-
x: xScale(xVal) ?? 0,
|
|
75
|
-
y: yScale(yVal) ?? 0,
|
|
76
|
-
width: bwX,
|
|
77
|
-
height: bwY,
|
|
78
|
-
fill: cellFill,
|
|
79
|
-
data: d
|
|
80
|
-
}
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
37
|
</script>
|
|
84
38
|
|
|
85
39
|
{#if cells.length > 0}
|
|
@@ -91,13 +45,14 @@
|
|
|
91
45
|
width={Math.max(0, cell.width)}
|
|
92
46
|
height={Math.max(0, cell.height)}
|
|
93
47
|
fill={cell.fill}
|
|
48
|
+
fill-opacity={cell.alpha}
|
|
94
49
|
rx={rx}
|
|
95
50
|
data-plot-element="cell"
|
|
96
51
|
role="img"
|
|
97
52
|
onmouseenter={() => plotState.setHovered(cell.data)}
|
|
98
53
|
onmouseleave={() => plotState.clearHovered()}
|
|
99
54
|
>
|
|
100
|
-
<title>{cell.data[x ?? '']}, {cell.data[y ?? '']}: {
|
|
55
|
+
<title>{cell.data[x ?? '']}, {cell.data[y ?? '']}: {colorField ? cell.data[colorField] : ''}</title>
|
|
101
56
|
</rect>
|
|
102
57
|
{/each}
|
|
103
58
|
</g>
|
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"
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
|
+
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
|
+
import { GeomState } from './lib/GeomState.svelte.js'
|
|
5
|
+
import { buildJitterMarks } from './lib/marks/jitter.js'
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
x?: string
|
|
9
|
+
y?: string
|
|
10
|
+
/** Point interior aesthetic (field); defaults to `x`. */
|
|
11
|
+
fill?: string
|
|
12
|
+
/** Outline aesthetic (field); overrides the point stroke when set. */
|
|
13
|
+
color?: string
|
|
14
|
+
r?: number
|
|
15
|
+
method?: 'jitter' | 'swarm'
|
|
16
|
+
/** Confine points to one half of the band (raincloud/split plots). Default 'center'. */
|
|
17
|
+
side?: 'left' | 'right' | 'center'
|
|
18
|
+
/** Fixed point opacity 0–1; defaults to the per-geom preset (jitter = 0.8). */
|
|
19
|
+
alpha?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let { x, y, fill, color, r = 2, method = 'jitter', side = 'center', alpha }: Props = $props()
|
|
23
|
+
|
|
24
|
+
const plotState = getContext<PlotState>('plot-state')
|
|
25
|
+
|
|
26
|
+
// Fall back to the root Plot channels when the geom omits x/y (composes under
|
|
27
|
+
// <Plot.Root x y> without silently NaN-ing).
|
|
28
|
+
const xf = $derived(x ?? plotState.channels?.x)
|
|
29
|
+
const yf = $derived(y ?? plotState.channels?.y)
|
|
30
|
+
// fill ?? effective-x drives the color lookup
|
|
31
|
+
const fillChannel = $derived(fill ?? xf)
|
|
32
|
+
|
|
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)
|
|
44
|
+
|
|
45
|
+
const points = $derived(geom.marks)
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
{#if points.length > 0}
|
|
49
|
+
<g data-plot-geom="jitter">
|
|
50
|
+
{#each points as pt, i (`${String(pt.cx)}::${i}`)}
|
|
51
|
+
<circle
|
|
52
|
+
cx={pt.cx}
|
|
53
|
+
cy={pt.cy}
|
|
54
|
+
{r}
|
|
55
|
+
fill={pt.fill}
|
|
56
|
+
fill-opacity={pt.alpha}
|
|
57
|
+
stroke={pt.stroke}
|
|
58
|
+
stroke-width="0.5"
|
|
59
|
+
data-plot-element="jitter-point"
|
|
60
|
+
role="graphics-symbol"
|
|
61
|
+
aria-label={`(${String(pt.data[xf ?? ''])}, ${String(pt.data[yf ?? ''])})`}
|
|
62
|
+
onmouseenter={() => plotState.setHovered(pt.data)}
|
|
63
|
+
onmouseleave={() => plotState.clearHovered()}
|
|
64
|
+
/>
|
|
65
|
+
{/each}
|
|
66
|
+
</g>
|
|
67
|
+
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
x?: string;
|
|
3
|
+
y?: string;
|
|
4
|
+
/** Point interior aesthetic (field); defaults to `x`. */
|
|
5
|
+
fill?: string;
|
|
6
|
+
/** Outline aesthetic (field); overrides the point stroke when set. */
|
|
7
|
+
color?: string;
|
|
8
|
+
r?: number;
|
|
9
|
+
method?: 'jitter' | 'swarm';
|
|
10
|
+
/** Confine points to one half of the band (raincloud/split plots). Default 'center'. */
|
|
11
|
+
side?: 'left' | 'right' | 'center';
|
|
12
|
+
/** Fixed point opacity 0–1; defaults to the per-geom preset (jitter = 0.8). */
|
|
13
|
+
alpha?: number;
|
|
14
|
+
};
|
|
15
|
+
declare const Jitter: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type Jitter = ReturnType<typeof Jitter>;
|
|
17
|
+
export default 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)
|
|
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;
|