@rokkit/chart 1.3.8 → 1.3.10
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/Plot.svelte +4 -1
- package/dist/Plot.svelte.d.ts +2 -0
- package/dist/PlotState.svelte.d.ts +14 -2
- package/dist/PlotState.svelte.js +81 -5
- 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 +18 -5
- package/dist/geoms/lib/bars.d.ts +2 -2
- package/dist/geoms/lib/bars.js +37 -17
- 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 +47 -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 +32 -0
- package/dist/geoms/lib/marks/ribbon.js +80 -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/brewing/colors.d.ts +31 -1
- package/dist/lib/brewing/colors.js +34 -1
- package/dist/lib/brewing/marks/arcs.js +9 -4
- package/dist/lib/brewing/marks/boxes.js +8 -4
- package/dist/lib/brewing/marks/lines.js +13 -2
- package/dist/lib/brewing/marks/points.js +7 -2
- package/dist/lib/brewing/marks/swarm.js +6 -2
- package/dist/lib/brewing/marks/violins.js +8 -4
- 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/Box.svelte
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
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 { buildBoxMarks } from './lib/marks/box.js'
|
|
5
6
|
|
|
6
7
|
type Props = {
|
|
7
8
|
x?: string
|
|
8
9
|
y?: string
|
|
10
|
+
/** Box interior aesthetic (field); defaults to `x`. */
|
|
9
11
|
fill?: string
|
|
12
|
+
/** Outline aesthetic (field); overrides whisker/median/border stroke when set. */
|
|
13
|
+
color?: string
|
|
10
14
|
stat?: string
|
|
11
15
|
/** Offset the box to one half of the band (raincloud/split plots). Default 'center'. */
|
|
12
16
|
side?: 'left' | 'right' | 'center'
|
|
@@ -15,13 +19,14 @@
|
|
|
15
19
|
width?: number
|
|
16
20
|
/** Data field driving a texture fill (from the shared pattern set) instead of a solid fill. */
|
|
17
21
|
pattern?: string
|
|
18
|
-
|
|
22
|
+
/** Fixed box opacity 0–1; defaults to the per-geom preset (box = 0.5). */
|
|
23
|
+
alpha?: number
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
let { x, y, fill, stat = 'boxplot', side = 'center', width, pattern,
|
|
26
|
+
let { x, y, fill, color, stat = 'boxplot', side = 'center', width, pattern, alpha }: Props =
|
|
27
|
+
$props()
|
|
22
28
|
|
|
23
29
|
const plotState = getContext<PlotState>('plot-state')
|
|
24
|
-
let id = $state<string | null>(null)
|
|
25
30
|
|
|
26
31
|
// Fall back to the root Plot channels when the geom's own x/y are omitted, so
|
|
27
32
|
// <Plot.Box> composes under <Plot.Root x y> without silently NaN-ing.
|
|
@@ -30,148 +35,130 @@
|
|
|
30
35
|
// fill ?? effective-x drives the colors map for both box interior and whisker strokes
|
|
31
36
|
const fillChannel = $derived(fill ?? xf)
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
const geom = new GeomState(plotState, () => ({
|
|
39
|
+
type: 'box',
|
|
40
|
+
channels: { x: xf, y: yf, fill: fillChannel, color, pattern },
|
|
41
|
+
stat,
|
|
42
|
+
options: { side, width },
|
|
43
|
+
alpha,
|
|
44
|
+
build: buildBoxMarks
|
|
45
|
+
}))
|
|
46
|
+
onMount(geom.register)
|
|
47
|
+
onDestroy(geom.destroy)
|
|
48
|
+
$effect(geom.sync)
|
|
44
49
|
|
|
45
|
-
$
|
|
46
|
-
if (id)
|
|
47
|
-
plotState.updateGeom(id, { channels: { x: xf, y: yf, color: fillChannel, pattern }, stat })
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
const data = $derived(id ? plotState.geomData(id) : [])
|
|
51
|
-
const xScale = $derived(plotState.xScale)
|
|
52
|
-
const yScale = $derived(plotState.yScale)
|
|
53
|
-
const colors = $derived(plotState.colors)
|
|
54
|
-
const patterns = $derived(plotState.patterns)
|
|
55
|
-
|
|
56
|
-
const boxes = $derived.by(() => {
|
|
57
|
-
if (!data?.length || !xScale || !yScale) return []
|
|
58
|
-
return buildBoxes(data, { x: xf, fill: fillChannel, pattern }, xScale, yScale, colors, {
|
|
59
|
-
side,
|
|
60
|
-
width,
|
|
61
|
-
patterns
|
|
62
|
-
})
|
|
63
|
-
})
|
|
50
|
+
const boxes = $derived(geom.marks)
|
|
64
51
|
</script>
|
|
65
52
|
|
|
66
53
|
{#if boxes.length > 0}
|
|
67
54
|
<g data-plot-geom="box">
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
55
|
+
{#each boxes as box, i (`${String(box.cx)}::${i}`)}
|
|
56
|
+
<!-- Each primitive is placed through plotState.place(band, value) so the whole
|
|
57
|
+
box transposes correctly under orientation='horizontal' (flip). -->
|
|
58
|
+
{@const c1 = plotState.place(box.cx - box.width / 2, box.q3)}
|
|
59
|
+
{@const c2 = plotState.place(box.cx + box.width / 2, box.q1)}
|
|
60
|
+
{@const mA = plotState.place(box.cx - box.width / 2, box.median)}
|
|
61
|
+
{@const mB = plotState.place(box.cx + box.width / 2, box.median)}
|
|
62
|
+
{@const wLoA = plotState.place(box.cx, box.q1)}
|
|
63
|
+
{@const wLoB = plotState.place(box.cx, box.iqr_min)}
|
|
64
|
+
{@const wHiA = plotState.place(box.cx, box.q3)}
|
|
65
|
+
{@const wHiB = plotState.place(box.cx, box.iqr_max)}
|
|
66
|
+
{@const capLoA = plotState.place(box.cx - box.whiskerWidth / 2, box.iqr_min)}
|
|
67
|
+
{@const capLoB = plotState.place(box.cx + box.whiskerWidth / 2, box.iqr_min)}
|
|
68
|
+
{@const capHiA = plotState.place(box.cx - box.whiskerWidth / 2, box.iqr_max)}
|
|
69
|
+
{@const capHiB = plotState.place(box.cx + box.whiskerWidth / 2, box.iqr_max)}
|
|
70
|
+
<!-- Box body (IQR): lighter fill shade -->
|
|
71
|
+
<rect
|
|
72
|
+
x={Math.min(c1.x, c2.x)}
|
|
73
|
+
y={Math.min(c1.y, c2.y)}
|
|
74
|
+
width={Math.abs(c2.x - c1.x)}
|
|
75
|
+
height={Math.abs(c2.y - c1.y)}
|
|
76
|
+
fill={box.fill}
|
|
77
|
+
fill-opacity={box.alpha}
|
|
78
|
+
stroke={box.stroke}
|
|
79
|
+
stroke-width="1"
|
|
80
|
+
data-plot-element="box-body"
|
|
81
|
+
role="graphics-symbol"
|
|
82
|
+
aria-label={`${String(box.data[xf ?? ''] ?? 'box')}: median ${box.data.median}, IQR ${box.data.q1}–${box.data.q3}`}
|
|
83
|
+
onmouseenter={() => plotState.setHovered(box.data)}
|
|
84
|
+
onmouseleave={() => plotState.clearHovered()}
|
|
85
|
+
/>
|
|
86
|
+
{#if box.patternId}
|
|
87
|
+
<!-- Texture fill overlay (matches the box body) -->
|
|
84
88
|
<rect
|
|
85
89
|
x={Math.min(c1.x, c2.x)}
|
|
86
90
|
y={Math.min(c1.y, c2.y)}
|
|
87
91
|
width={Math.abs(c2.x - c1.x)}
|
|
88
92
|
height={Math.abs(c2.y - c1.y)}
|
|
89
|
-
fill={box.
|
|
90
|
-
|
|
93
|
+
fill="url(#{box.patternId})"
|
|
94
|
+
pointer-events="none"
|
|
95
|
+
/>
|
|
96
|
+
{/if}
|
|
97
|
+
<!-- Median line: darker stroke shade -->
|
|
98
|
+
<line
|
|
99
|
+
x1={mA.x}
|
|
100
|
+
y1={mA.y}
|
|
101
|
+
x2={mB.x}
|
|
102
|
+
y2={mB.y}
|
|
103
|
+
stroke={box.stroke}
|
|
104
|
+
stroke-width="2"
|
|
105
|
+
data-plot-element="box-median"
|
|
106
|
+
/>
|
|
107
|
+
<!-- Lower whisker (q1 to iqr_min) -->
|
|
108
|
+
<line
|
|
109
|
+
x1={wLoA.x}
|
|
110
|
+
y1={wLoA.y}
|
|
111
|
+
x2={wLoB.x}
|
|
112
|
+
y2={wLoB.y}
|
|
113
|
+
stroke={box.stroke}
|
|
114
|
+
stroke-width="1"
|
|
115
|
+
data-plot-element="box-whisker"
|
|
116
|
+
/>
|
|
117
|
+
<!-- Upper whisker (q3 to iqr_max) -->
|
|
118
|
+
<line
|
|
119
|
+
x1={wHiA.x}
|
|
120
|
+
y1={wHiA.y}
|
|
121
|
+
x2={wHiB.x}
|
|
122
|
+
y2={wHiB.y}
|
|
123
|
+
stroke={box.stroke}
|
|
124
|
+
stroke-width="1"
|
|
125
|
+
data-plot-element="box-whisker"
|
|
126
|
+
/>
|
|
127
|
+
<!-- Lower whisker cap -->
|
|
128
|
+
<line
|
|
129
|
+
x1={capLoA.x}
|
|
130
|
+
y1={capLoA.y}
|
|
131
|
+
x2={capLoB.x}
|
|
132
|
+
y2={capLoB.y}
|
|
133
|
+
stroke={box.stroke}
|
|
134
|
+
stroke-width="1"
|
|
135
|
+
/>
|
|
136
|
+
<!-- Upper whisker cap -->
|
|
137
|
+
<line
|
|
138
|
+
x1={capHiA.x}
|
|
139
|
+
y1={capHiA.y}
|
|
140
|
+
x2={capHiB.x}
|
|
141
|
+
y2={capHiB.y}
|
|
142
|
+
stroke={box.stroke}
|
|
143
|
+
stroke-width="1"
|
|
144
|
+
/>
|
|
145
|
+
<!-- Outliers: individual points beyond the 1.5·IQR fence -->
|
|
146
|
+
{#each box.outliers as o, oi (`${i}-outlier-${oi}`)}
|
|
147
|
+
{@const op = plotState.place(box.cx, o.cy)}
|
|
148
|
+
<circle
|
|
149
|
+
cx={op.x}
|
|
150
|
+
cy={op.y}
|
|
151
|
+
r="2"
|
|
152
|
+
fill="none"
|
|
91
153
|
stroke={box.stroke}
|
|
92
154
|
stroke-width="1"
|
|
93
|
-
data-plot-element="box-
|
|
155
|
+
data-plot-element="box-outlier"
|
|
94
156
|
role="graphics-symbol"
|
|
95
|
-
aria-label={
|
|
96
|
-
onmouseenter={() => plotState.setHovered(box.data)}
|
|
157
|
+
aria-label={`outlier: ${o.value}`}
|
|
158
|
+
onmouseenter={() => plotState.setHovered({ ...box.data, value: o.value })}
|
|
97
159
|
onmouseleave={() => plotState.clearHovered()}
|
|
98
160
|
/>
|
|
99
|
-
{#if box.patternId}
|
|
100
|
-
<!-- Texture fill overlay (matches the box body) -->
|
|
101
|
-
<rect
|
|
102
|
-
x={Math.min(c1.x, c2.x)}
|
|
103
|
-
y={Math.min(c1.y, c2.y)}
|
|
104
|
-
width={Math.abs(c2.x - c1.x)}
|
|
105
|
-
height={Math.abs(c2.y - c1.y)}
|
|
106
|
-
fill="url(#{box.patternId})"
|
|
107
|
-
pointer-events="none"
|
|
108
|
-
/>
|
|
109
|
-
{/if}
|
|
110
|
-
<!-- Median line: darker stroke shade -->
|
|
111
|
-
<line
|
|
112
|
-
x1={mA.x}
|
|
113
|
-
y1={mA.y}
|
|
114
|
-
x2={mB.x}
|
|
115
|
-
y2={mB.y}
|
|
116
|
-
stroke={box.stroke}
|
|
117
|
-
stroke-width="2"
|
|
118
|
-
data-plot-element="box-median"
|
|
119
|
-
/>
|
|
120
|
-
<!-- Lower whisker (q1 to iqr_min) -->
|
|
121
|
-
<line
|
|
122
|
-
x1={wLoA.x}
|
|
123
|
-
y1={wLoA.y}
|
|
124
|
-
x2={wLoB.x}
|
|
125
|
-
y2={wLoB.y}
|
|
126
|
-
stroke={box.stroke}
|
|
127
|
-
stroke-width="1"
|
|
128
|
-
data-plot-element="box-whisker"
|
|
129
|
-
/>
|
|
130
|
-
<!-- Upper whisker (q3 to iqr_max) -->
|
|
131
|
-
<line
|
|
132
|
-
x1={wHiA.x}
|
|
133
|
-
y1={wHiA.y}
|
|
134
|
-
x2={wHiB.x}
|
|
135
|
-
y2={wHiB.y}
|
|
136
|
-
stroke={box.stroke}
|
|
137
|
-
stroke-width="1"
|
|
138
|
-
data-plot-element="box-whisker"
|
|
139
|
-
/>
|
|
140
|
-
<!-- Lower whisker cap -->
|
|
141
|
-
<line
|
|
142
|
-
x1={capLoA.x}
|
|
143
|
-
y1={capLoA.y}
|
|
144
|
-
x2={capLoB.x}
|
|
145
|
-
y2={capLoB.y}
|
|
146
|
-
stroke={box.stroke}
|
|
147
|
-
stroke-width="1"
|
|
148
|
-
/>
|
|
149
|
-
<!-- Upper whisker cap -->
|
|
150
|
-
<line
|
|
151
|
-
x1={capHiA.x}
|
|
152
|
-
y1={capHiA.y}
|
|
153
|
-
x2={capHiB.x}
|
|
154
|
-
y2={capHiB.y}
|
|
155
|
-
stroke={box.stroke}
|
|
156
|
-
stroke-width="1"
|
|
157
|
-
/>
|
|
158
|
-
<!-- Outliers: individual points beyond the 1.5·IQR fence -->
|
|
159
|
-
{#each box.outliers as o, oi (`${i}-outlier-${oi}`)}
|
|
160
|
-
{@const op = plotState.place(box.cx, o.cy)}
|
|
161
|
-
<circle
|
|
162
|
-
cx={op.x}
|
|
163
|
-
cy={op.y}
|
|
164
|
-
r="2"
|
|
165
|
-
fill="none"
|
|
166
|
-
stroke={box.stroke}
|
|
167
|
-
stroke-width="1"
|
|
168
|
-
data-plot-element="box-outlier"
|
|
169
|
-
role="graphics-symbol"
|
|
170
|
-
aria-label={`outlier: ${o.value}`}
|
|
171
|
-
onmouseenter={() => plotState.setHovered({ ...box.data, value: o.value })}
|
|
172
|
-
onmouseleave={() => plotState.clearHovered()}
|
|
173
|
-
/>
|
|
174
|
-
{/each}
|
|
175
161
|
{/each}
|
|
162
|
+
{/each}
|
|
176
163
|
</g>
|
|
177
164
|
{/if}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
type Props = {
|
|
2
2
|
x?: string;
|
|
3
3
|
y?: string;
|
|
4
|
+
/** Box interior aesthetic (field); defaults to `x`. */
|
|
4
5
|
fill?: string;
|
|
6
|
+
/** Outline aesthetic (field); overrides whisker/median/border stroke when set. */
|
|
7
|
+
color?: string;
|
|
5
8
|
stat?: string;
|
|
6
9
|
/** Offset the box to one half of the band (raincloud/split plots). Default 'center'. */
|
|
7
10
|
side?: 'left' | 'right' | 'center';
|
|
@@ -10,9 +13,8 @@ type Props = {
|
|
|
10
13
|
width?: number;
|
|
11
14
|
/** Data field driving a texture fill (from the shared pattern set) instead of a solid fill. */
|
|
12
15
|
pattern?: string;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
};
|
|
16
|
+
/** Fixed box opacity 0–1; defaults to the per-geom preset (box = 0.5). */
|
|
17
|
+
alpha?: number;
|
|
16
18
|
};
|
|
17
19
|
declare const Box: import("svelte").Component<Props, {}, "">;
|
|
18
20
|
type Box = ReturnType<typeof Box>;
|
|
@@ -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,92 +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
|
-
const b1 = plotState.place(xPos, bodyTop)
|
|
86
|
-
const b2 = plotState.place(xPos + bodyWidth, bodyBottom)
|
|
87
|
-
const wa = plotState.place(xPos + bodyWidth / 2, yScale(highVal))
|
|
88
|
-
const wb = plotState.place(xPos + bodyWidth / 2, yScale(lowVal))
|
|
89
|
-
return {
|
|
90
|
-
key: `${d[x ?? '']}-${i}`,
|
|
91
|
-
// Body
|
|
92
|
-
bodyX: Math.min(b1.x, b2.x),
|
|
93
|
-
bodyY: Math.min(b1.y, b2.y),
|
|
94
|
-
bodyWidth: Math.abs(b2.x - b1.x),
|
|
95
|
-
bodyHeight: Math.max(1, Math.abs(b2.y - b1.y)),
|
|
96
|
-
fill: isUp ? upColor : downColor,
|
|
97
|
-
// Wick
|
|
98
|
-
wickX1: wa.x,
|
|
99
|
-
wickY1: wa.y,
|
|
100
|
-
wickX2: wb.x,
|
|
101
|
-
wickY2: wb.y,
|
|
102
|
-
data: d
|
|
103
|
-
}
|
|
104
|
-
})
|
|
105
|
-
})
|
|
48
|
+
const candles = $derived(geom.marks)
|
|
106
49
|
</script>
|
|
107
50
|
|
|
108
51
|
{#if candles.length > 0}
|
|
@@ -115,6 +58,7 @@
|
|
|
115
58
|
x2={c.wickX2}
|
|
116
59
|
y2={c.wickY2}
|
|
117
60
|
stroke={c.fill}
|
|
61
|
+
stroke-opacity={c.alpha}
|
|
118
62
|
stroke-width={wickWidth}
|
|
119
63
|
data-plot-element="wick"
|
|
120
64
|
/>
|
|
@@ -125,12 +69,13 @@
|
|
|
125
69
|
width={c.bodyWidth}
|
|
126
70
|
height={c.bodyHeight}
|
|
127
71
|
fill={c.fill}
|
|
72
|
+
fill-opacity={c.alpha}
|
|
128
73
|
data-plot-element="candle"
|
|
129
74
|
role="img"
|
|
130
75
|
onmouseenter={() => plotState.setHovered(c.data)}
|
|
131
76
|
onmouseleave={() => plotState.clearHovered()}
|
|
132
77
|
>
|
|
133
|
-
<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>
|
|
134
79
|
</rect>
|
|
135
80
|
{/each}
|
|
136
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>
|