@rokkit/chart 1.1.18 → 1.1.19
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.d.ts +35 -0
- package/dist/Chart.svelte.d.ts +21 -0
- package/dist/ChartExporter.svelte.d.ts +22 -0
- package/dist/ChartLegend.svelte.d.ts +30 -0
- package/dist/ChartProvider.svelte.d.ts +10 -0
- package/dist/FacetPlot/Panel.svelte.d.ts +22 -0
- package/dist/FacetPlot.svelte.d.ts +32 -0
- package/dist/Plot/Arc.svelte.d.ts +18 -0
- package/dist/Plot/Area.svelte.d.ts +14 -0
- package/dist/Plot/Axis.svelte.d.ts +13 -0
- package/dist/Plot/Bar.svelte.d.ts +11 -0
- package/dist/Plot/Grid.svelte.d.ts +3 -0
- package/dist/Plot/Legend.svelte.d.ts +6 -0
- package/dist/Plot/Line.svelte.d.ts +13 -0
- package/dist/Plot/Point.svelte.d.ts +13 -0
- package/dist/Plot/Root.svelte.d.ts +21 -0
- package/dist/Plot/Timeline.svelte.d.ts +13 -0
- package/dist/Plot/Tooltip.svelte.d.ts +7 -0
- package/dist/Plot.svelte.d.ts +34 -0
- package/dist/PlotState.svelte.d.ts +0 -3
- package/dist/Sparkline.svelte.d.ts +16 -0
- package/dist/Symbol.svelte.d.ts +12 -0
- package/dist/Texture.svelte.d.ts +12 -0
- package/dist/charts/AreaChart.svelte.d.ts +25 -0
- package/dist/charts/BarChart.svelte.d.ts +26 -0
- package/dist/charts/BoxPlot.svelte.d.ts +18 -0
- package/dist/charts/BubbleChart.svelte.d.ts +23 -0
- package/dist/charts/LineChart.svelte.d.ts +26 -0
- package/dist/charts/PieChart.svelte.d.ts +19 -0
- package/dist/charts/ScatterPlot.svelte.d.ts +29 -0
- package/dist/charts/ViolinPlot.svelte.d.ts +18 -0
- package/dist/crossfilter/CrossFilter.svelte.d.ts +12 -0
- package/dist/crossfilter/FilterBar.svelte.d.ts +12 -0
- package/dist/crossfilter/FilterHistogram.svelte.d.ts +19 -0
- package/dist/crossfilter/FilterSlider.svelte.d.ts +15 -0
- package/dist/elements/Bar.svelte.d.ts +17 -0
- package/dist/elements/ColorRamp.svelte.d.ts +17 -0
- package/dist/elements/ContinuousLegend.svelte.d.ts +18 -0
- package/dist/elements/DefinePatterns.svelte.d.ts +21 -0
- package/dist/elements/DiscreteLegend.svelte.d.ts +17 -0
- package/dist/elements/Label.svelte.d.ts +11 -0
- package/dist/elements/SymbolGrid.svelte.d.ts +8 -0
- package/dist/examples/BarChartExample.svelte.d.ts +18 -0
- package/dist/geoms/Arc.svelte.d.ts +20 -0
- package/dist/geoms/Area.svelte.d.ts +16 -0
- package/dist/geoms/Bar.svelte.d.ts +23 -0
- package/dist/geoms/Box.svelte.d.ts +12 -0
- package/dist/geoms/Candlestick.svelte.d.ts +20 -0
- package/dist/geoms/Heatmap.svelte.d.ts +13 -0
- package/dist/geoms/Hexbin.svelte.d.ts +13 -0
- package/dist/geoms/LabelPill.svelte.d.ts +9 -0
- package/dist/geoms/Line.svelte.d.ts +24 -0
- package/dist/geoms/Point.svelte.d.ts +30 -0
- package/dist/geoms/Ribbon.svelte.d.ts +16 -0
- package/dist/geoms/Violin.svelte.d.ts +12 -0
- package/dist/geoms/Waterfall.svelte.d.ts +18 -0
- package/dist/geoms/lib/areas.d.ts +5 -3
- package/dist/lib/brewing/brewer.svelte.d.ts +24 -15
- package/dist/lib/brewing/marks/arcs.d.ts +16 -6
- package/dist/lib/brewing/marks/areas.d.ts +2 -2
- package/dist/lib/brewing/marks/boxes.d.ts +3 -3
- package/dist/lib/brewing/marks/lines.d.ts +6 -6
- package/dist/lib/brewing/marks/points.d.ts +13 -12
- package/dist/lib/brewing/marks/violins.d.ts +3 -3
- package/dist/lib/brewing/scales.d.ts +3 -3
- package/dist/lib/chart.d.ts +2 -2
- package/dist/lib/keyboard-nav.d.ts +1 -1
- package/dist/lib/plot/facet.d.ts +2 -2
- package/dist/lib/plot/frames.d.ts +9 -9
- package/dist/lib/plot/scales.d.ts +3 -3
- package/dist/lib/plot/types.d.ts +7 -4
- package/dist/lib/scales.svelte.d.ts +2 -2
- package/dist/lib/utils.d.ts +7 -1
- package/dist/lib/xscale.d.ts +1 -1
- package/dist/patterns/DefinePatterns.svelte.d.ts +7 -0
- package/dist/patterns/PatternDef.svelte.d.ts +11 -0
- package/dist/spec/chart-spec.d.ts +22 -0
- package/dist/symbols/RoundedSquare.svelte.d.ts +11 -0
- package/dist/symbols/Shape.svelte.d.ts +18 -0
- package/dist/symbols/constants/index.d.ts +1 -1
- package/dist/symbols/index.d.ts +2 -2
- package/dist/symbols/outline.svelte.d.ts +18 -0
- package/dist/symbols/solid.svelte.d.ts +18 -0
- package/package.json +22 -3
- package/src/AnimatedPlot.svelte +49 -43
- package/src/Chart.svelte +34 -22
- package/src/ChartExporter.svelte +19 -16
- package/src/ChartLegend.svelte +24 -20
- package/src/ChartProvider.svelte +10 -2
- package/src/FacetPlot/Panel.svelte +23 -3
- package/src/FacetPlot.svelte +29 -26
- package/src/Plot/Arc.svelte +3 -2
- package/src/Plot/Area.svelte +30 -8
- package/src/Plot/Axis.svelte +26 -45
- package/src/Plot/Bar.svelte +20 -5
- package/src/Plot/Grid.svelte +5 -4
- package/src/Plot/Legend.svelte +8 -4
- package/src/Plot/Line.svelte +24 -7
- package/src/Plot/Point.svelte +24 -6
- package/src/Plot/Root.svelte +17 -2
- package/src/Plot/Timeline.svelte +13 -14
- package/src/Plot/Tooltip.svelte +12 -6
- package/src/Plot.svelte +41 -32
- package/src/PlotState.svelte.js +6 -2
- package/src/Sparkline.svelte +22 -8
- package/src/Symbol.svelte +15 -5
- package/src/Texture.svelte +12 -2
- package/src/charts/AreaChart.svelte +26 -3
- package/src/charts/BarChart.svelte +27 -3
- package/src/charts/BoxPlot.svelte +19 -3
- package/src/charts/BubbleChart.svelte +24 -3
- package/src/charts/LineChart.svelte +27 -3
- package/src/charts/PieChart.svelte +20 -3
- package/src/charts/ScatterPlot.svelte +27 -3
- package/src/charts/ViolinPlot.svelte +19 -3
- package/src/crossfilter/CrossFilter.svelte +13 -10
- package/src/crossfilter/FilterBar.svelte +14 -3
- package/src/crossfilter/FilterHistogram.svelte +49 -24
- package/src/crossfilter/FilterSlider.svelte +16 -5
- package/src/elements/Bar.svelte +16 -2
- package/src/elements/ColorRamp.svelte +28 -4
- package/src/elements/ContinuousLegend.svelte +20 -3
- package/src/elements/DefinePatterns.svelte +23 -7
- package/src/elements/DiscreteLegend.svelte +18 -2
- package/src/elements/Label.svelte +11 -2
- package/src/elements/SymbolGrid.svelte +13 -4
- package/src/examples/BarChartExample.svelte +12 -15
- package/src/geoms/Arc.svelte +18 -5
- package/src/geoms/Area.svelte +38 -20
- package/src/geoms/Bar.svelte +40 -23
- package/src/geoms/Box.svelte +13 -4
- package/src/geoms/Candlestick.svelte +27 -7
- package/src/geoms/Heatmap.svelte +20 -9
- package/src/geoms/Hexbin.svelte +35 -13
- package/src/geoms/LabelPill.svelte +9 -3
- package/src/geoms/Line.svelte +27 -10
- package/src/geoms/Point.svelte +33 -13
- package/src/geoms/Ribbon.svelte +26 -8
- package/src/geoms/Violin.svelte +13 -4
- package/src/geoms/Waterfall.svelte +32 -12
- package/src/geoms/lib/areas.js +2 -2
- package/src/lib/brewing/marks/arcs.js +3 -2
- package/src/lib/brewing/marks/boxes.js +1 -1
- package/src/lib/brewing/marks/lines.js +2 -2
- package/src/lib/brewing/marks/points.js +3 -2
- package/src/lib/brewing/marks/violins.js +1 -1
- package/src/lib/plot/facet.js +1 -1
- package/src/lib/plot/frames.js +4 -4
- package/src/lib/plot/types.js +6 -3
- package/src/lib/utils.js +7 -1
- package/src/patterns/DefinePatterns.svelte +9 -4
- package/src/patterns/PatternDef.svelte +11 -3
- package/src/symbols/RoundedSquare.svelte +15 -4
- package/src/symbols/Shape.svelte +21 -3
- package/src/symbols/outline.svelte +2 -0
- package/src/symbols/solid.svelte +2 -0
package/src/geoms/Bar.svelte
CHANGED
|
@@ -1,9 +1,34 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script lang="ts">
|
|
2
2
|
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
|
+
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
|
+
import type { createCrossFilter } from '../crossfilter/createCrossFilter.svelte.js'
|
|
3
5
|
import { buildGroupedBars, buildStackedBars, buildHorizontalBars } from './lib/bars.js'
|
|
4
6
|
import { keyboardNav } from '../lib/keyboard-nav.js'
|
|
5
7
|
import LabelPill from './LabelPill.svelte'
|
|
6
8
|
|
|
9
|
+
type Row = Record<string, unknown>
|
|
10
|
+
type CrossFilter = ReturnType<typeof createCrossFilter>
|
|
11
|
+
type Options = {
|
|
12
|
+
stack?: boolean
|
|
13
|
+
orientation?: 'horizontal' | 'vertical'
|
|
14
|
+
labelInside?: boolean
|
|
15
|
+
labelOffset?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type Props = {
|
|
19
|
+
x?: string
|
|
20
|
+
y?: string
|
|
21
|
+
color?: string
|
|
22
|
+
fill?: string
|
|
23
|
+
pattern?: string
|
|
24
|
+
label?: boolean | string | ((data: Row) => unknown)
|
|
25
|
+
stat?: string
|
|
26
|
+
options?: Options
|
|
27
|
+
filterable?: boolean
|
|
28
|
+
onselect?: (data: Row) => void
|
|
29
|
+
keyboard?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
7
32
|
let {
|
|
8
33
|
x,
|
|
9
34
|
y,
|
|
@@ -16,19 +41,14 @@
|
|
|
16
41
|
filterable = false,
|
|
17
42
|
onselect = undefined,
|
|
18
43
|
keyboard = false
|
|
19
|
-
} = $props()
|
|
44
|
+
}: Props = $props()
|
|
20
45
|
|
|
21
46
|
// `fill` is accepted as an alias for `color` (consistent with Arc.svelte)
|
|
22
47
|
const colorChannel = $derived(fillProp ?? color)
|
|
23
48
|
|
|
24
|
-
|
|
25
|
-
* @param {Record<string, unknown>} data
|
|
26
|
-
* @param {string} defaultField
|
|
27
|
-
* @returns {string | null}
|
|
28
|
-
*/
|
|
29
|
-
function resolveLabel(data, defaultField) {
|
|
49
|
+
function resolveLabel(data: Row, defaultField: string | undefined): string | null {
|
|
30
50
|
if (!label) return null
|
|
31
|
-
if (label === true) return String(data[defaultField] ?? '')
|
|
51
|
+
if (label === true) return String((defaultField ? data[defaultField] : undefined) ?? '')
|
|
32
52
|
if (typeof label === 'function') return String(label(data) ?? '')
|
|
33
53
|
if (typeof label === 'string') return String(data[label] ?? '')
|
|
34
54
|
return null
|
|
@@ -36,10 +56,8 @@
|
|
|
36
56
|
|
|
37
57
|
/**
|
|
38
58
|
* Pick white or dark text based on perceived luminance of a hex fill color.
|
|
39
|
-
* @param {string | undefined} hex
|
|
40
|
-
* @returns {string}
|
|
41
59
|
*/
|
|
42
|
-
function contrastColor(hex) {
|
|
60
|
+
function contrastColor(hex: string | undefined): string {
|
|
43
61
|
if (!hex || !hex.startsWith('#') || hex.length < 7) return 'white'
|
|
44
62
|
const r = parseInt(hex.slice(1, 3), 16) / 255
|
|
45
63
|
const g = parseInt(hex.slice(3, 5), 16) / 255
|
|
@@ -47,9 +65,9 @@
|
|
|
47
65
|
return 0.299 * r + 0.587 * g + 0.114 * b > 0.55 ? '#333' : 'white'
|
|
48
66
|
}
|
|
49
67
|
|
|
50
|
-
const plotState = getContext('plot-state')
|
|
51
|
-
const cf = getContext('crossfilter')
|
|
52
|
-
let id = $state(null)
|
|
68
|
+
const plotState = getContext<PlotState>('plot-state')
|
|
69
|
+
const cf = getContext<CrossFilter | undefined>('crossfilter')
|
|
70
|
+
let id = $state<string | null>(null)
|
|
53
71
|
|
|
54
72
|
onMount(() => {
|
|
55
73
|
id = plotState.registerGeom({
|
|
@@ -92,8 +110,7 @@
|
|
|
92
110
|
return buildGroupedBars(data, channels, xScale, yScale, colors, innerHeight, patterns)
|
|
93
111
|
})
|
|
94
112
|
|
|
95
|
-
|
|
96
|
-
let dimmedByKey = $state({})
|
|
113
|
+
let dimmedByKey = $state<Record<string, boolean>>({})
|
|
97
114
|
|
|
98
115
|
$effect(() => {
|
|
99
116
|
if (!cf) {
|
|
@@ -104,7 +121,7 @@
|
|
|
104
121
|
// Reading it here establishes a reactive dependency so the effect re-runs
|
|
105
122
|
// whenever any filter changes — including changes from sibling FilterBars.
|
|
106
123
|
void cf.version
|
|
107
|
-
const next
|
|
124
|
+
const next: Record<string, boolean> = {}
|
|
108
125
|
for (const bar of bars) {
|
|
109
126
|
const dimmedByX = x ? cf.isDimmed(x, bar.data[x]) : false
|
|
110
127
|
const dimmedByY = y ? cf.isDimmed(y, bar.data[y]) : false
|
|
@@ -113,7 +130,7 @@
|
|
|
113
130
|
dimmedByKey = next
|
|
114
131
|
})
|
|
115
132
|
|
|
116
|
-
function handleBarClick(barX) {
|
|
133
|
+
function handleBarClick(barX: unknown) {
|
|
117
134
|
if (!filterable || !x || !cf) return
|
|
118
135
|
cf.toggleCategorical(x, barX)
|
|
119
136
|
}
|
|
@@ -132,8 +149,8 @@
|
|
|
132
149
|
stroke={bar.stroke ?? 'none'}
|
|
133
150
|
stroke-width={bar.stroke ? 0.5 : 0}
|
|
134
151
|
data-plot-element="bar"
|
|
135
|
-
data-plot-value={bar.data[y]}
|
|
136
|
-
data-plot-category={bar.data[x]}
|
|
152
|
+
data-plot-value={bar.data[y ?? '']}
|
|
153
|
+
data-plot-category={bar.data[x ?? '']}
|
|
137
154
|
data-dimmed={dimmedByKey[bar.key] ? true : undefined}
|
|
138
155
|
style:cursor={filterable || onselect ? 'pointer' : undefined}
|
|
139
156
|
onclick={filterable && x ? () => { handleBarClick(bar.data[x]); onselect?.(bar.data) } : onselect ? () => onselect(bar.data) : undefined}
|
|
@@ -143,11 +160,11 @@
|
|
|
143
160
|
role={filterable || onselect || keyboard ? 'button' : 'graphics-symbol'}
|
|
144
161
|
tabindex={filterable || onselect || keyboard ? 0 : undefined}
|
|
145
162
|
use:keyboardNav={keyboard}
|
|
146
|
-
aria-label="{bar.data[x]}: {bar.data[y]}"
|
|
163
|
+
aria-label="{bar.data[x ?? '']}: {bar.data[y ?? '']}"
|
|
147
164
|
onmouseenter={() => plotState.setHovered(bar.data)}
|
|
148
165
|
onmouseleave={() => plotState.clearHovered()}
|
|
149
166
|
>
|
|
150
|
-
<title>{bar.data[x]}: {bar.data[y]}</title>
|
|
167
|
+
<title>{bar.data[x ?? '']}: {bar.data[y ?? '']}</title>
|
|
151
168
|
</rect>
|
|
152
169
|
{#if bar.patternId}
|
|
153
170
|
<rect
|
package/src/geoms/Box.svelte
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script lang="ts">
|
|
2
2
|
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
|
+
import type { PlotState } from '../PlotState.svelte.js'
|
|
3
4
|
import { buildBoxes } from '../lib/brewing/marks/boxes.js'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
type Props = {
|
|
7
|
+
x?: string
|
|
8
|
+
y?: string
|
|
9
|
+
fill?: string
|
|
10
|
+
stat?: string
|
|
11
|
+
options?: { opacity?: number }
|
|
12
|
+
}
|
|
6
13
|
|
|
7
|
-
|
|
8
|
-
|
|
14
|
+
let { x, y, fill, stat = 'boxplot', options = {} }: Props = $props()
|
|
15
|
+
|
|
16
|
+
const plotState = getContext<PlotState>('plot-state')
|
|
17
|
+
let id = $state<string | null>(null)
|
|
9
18
|
|
|
10
19
|
// fill ?? x drives the colors map for both box interior and whisker strokes
|
|
11
20
|
const fillChannel = $derived(fill ?? x)
|
|
@@ -1,5 +1,25 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script lang="ts">
|
|
2
2
|
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
|
+
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
|
+
|
|
5
|
+
type Options = {
|
|
6
|
+
open?: string
|
|
7
|
+
high?: string
|
|
8
|
+
low?: string
|
|
9
|
+
close?: string
|
|
10
|
+
upColor?: string
|
|
11
|
+
downColor?: string
|
|
12
|
+
wickWidth?: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type Props = {
|
|
16
|
+
x?: string
|
|
17
|
+
y?: string
|
|
18
|
+
color?: string
|
|
19
|
+
fill?: string
|
|
20
|
+
stat?: string
|
|
21
|
+
options?: Options
|
|
22
|
+
}
|
|
3
23
|
|
|
4
24
|
let {
|
|
5
25
|
x,
|
|
@@ -8,7 +28,7 @@
|
|
|
8
28
|
fill: fillProp = undefined,
|
|
9
29
|
stat = 'identity',
|
|
10
30
|
options = {}
|
|
11
|
-
} = $props()
|
|
31
|
+
}: Props = $props()
|
|
12
32
|
|
|
13
33
|
const open = $derived(options.open ?? 'open')
|
|
14
34
|
const high = $derived(options.high ?? 'high')
|
|
@@ -18,8 +38,8 @@
|
|
|
18
38
|
const downColor = $derived(options.downColor ?? '#ef4444')
|
|
19
39
|
const wickWidth = $derived(options.wickWidth ?? 1)
|
|
20
40
|
|
|
21
|
-
const plotState = getContext('plot-state')
|
|
22
|
-
let id = $state(null)
|
|
41
|
+
const plotState = getContext<PlotState>('plot-state')
|
|
42
|
+
let id = $state<string | null>(null)
|
|
23
43
|
|
|
24
44
|
onMount(() => {
|
|
25
45
|
// Register with y pointing to 'high' so the y scale covers the full range
|
|
@@ -52,7 +72,7 @@
|
|
|
52
72
|
const bodyOffset = (bw - bodyWidth) / 2
|
|
53
73
|
|
|
54
74
|
return data.map((d, i) => {
|
|
55
|
-
const xPos = (xScale(d[x]) ?? 0) + bodyOffset
|
|
75
|
+
const xPos = (xScale(d[x ?? '']) ?? 0) + bodyOffset
|
|
56
76
|
const openVal = Number(d[open])
|
|
57
77
|
const closeVal = Number(d[close])
|
|
58
78
|
const highVal = Number(d[high])
|
|
@@ -62,7 +82,7 @@
|
|
|
62
82
|
const bodyBottom = yScale(isUp ? openVal : closeVal)
|
|
63
83
|
|
|
64
84
|
return {
|
|
65
|
-
key: `${d[x]}-${i}`,
|
|
85
|
+
key: `${d[x ?? '']}-${i}`,
|
|
66
86
|
// Body
|
|
67
87
|
bodyX: xPos,
|
|
68
88
|
bodyY: bodyTop,
|
|
@@ -104,7 +124,7 @@
|
|
|
104
124
|
onmouseenter={() => plotState.setHovered(c.data)}
|
|
105
125
|
onmouseleave={() => plotState.clearHovered()}
|
|
106
126
|
>
|
|
107
|
-
<title>{c.data[x]}: O={c.data[open]} H={c.data[high]} L={c.data[low]} C={c.data[close]}</title>
|
|
127
|
+
<title>{c.data[x ?? '']}: O={c.data[open]} H={c.data[high]} L={c.data[low]} C={c.data[close]}</title>
|
|
108
128
|
</rect>
|
|
109
129
|
{/each}
|
|
110
130
|
</g>
|
package/src/geoms/Heatmap.svelte
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script lang="ts">
|
|
2
2
|
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
|
+
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
x?: string
|
|
7
|
+
y?: string
|
|
8
|
+
color?: string
|
|
9
|
+
fill?: string
|
|
10
|
+
stat?: string
|
|
11
|
+
options?: { rounded?: number }
|
|
12
|
+
}
|
|
3
13
|
|
|
4
14
|
let {
|
|
5
15
|
x,
|
|
@@ -8,12 +18,12 @@
|
|
|
8
18
|
fill: fillProp,
|
|
9
19
|
stat = 'identity',
|
|
10
20
|
options = {}
|
|
11
|
-
} = $props()
|
|
21
|
+
}: Props = $props()
|
|
12
22
|
|
|
13
23
|
const colorChannel = $derived(fillProp ?? color)
|
|
14
24
|
|
|
15
|
-
const plotState = getContext('plot-state')
|
|
16
|
-
let id = $state(null)
|
|
25
|
+
const plotState = getContext<PlotState>('plot-state')
|
|
26
|
+
let id = $state<string | null>(null)
|
|
17
27
|
|
|
18
28
|
onMount(() => {
|
|
19
29
|
id = plotState.registerGeom({
|
|
@@ -47,15 +57,16 @@
|
|
|
47
57
|
const bwY = typeof yScale.bandwidth === 'function' ? yScale.bandwidth() : 0
|
|
48
58
|
|
|
49
59
|
return data.map((d, i) => {
|
|
50
|
-
const xVal = d[x]
|
|
51
|
-
const yVal = d[y]
|
|
60
|
+
const xVal = d[x ?? '']
|
|
61
|
+
const yVal = d[y ?? '']
|
|
52
62
|
const colorVal = colorChannel ? d[colorChannel] : null
|
|
53
63
|
let cellFill = '#ccc'
|
|
54
64
|
|
|
55
65
|
if (continuousColor) {
|
|
56
66
|
cellFill = continuousColor.scale(Number(colorVal))
|
|
57
|
-
} else
|
|
58
|
-
|
|
67
|
+
} else {
|
|
68
|
+
const entry = categoricalColors?.get(colorVal)
|
|
69
|
+
if (entry) cellFill = entry.fill
|
|
59
70
|
}
|
|
60
71
|
|
|
61
72
|
return {
|
|
@@ -86,7 +97,7 @@
|
|
|
86
97
|
onmouseenter={() => plotState.setHovered(cell.data)}
|
|
87
98
|
onmouseleave={() => plotState.clearHovered()}
|
|
88
99
|
>
|
|
89
|
-
<title>{cell.data[x]}, {cell.data[y]}: {colorChannel ? cell.data[colorChannel] : ''}</title>
|
|
100
|
+
<title>{cell.data[x ?? '']}, {cell.data[y ?? '']}: {colorChannel ? cell.data[colorChannel] : ''}</title>
|
|
90
101
|
</rect>
|
|
91
102
|
{/each}
|
|
92
103
|
</g>
|
package/src/geoms/Hexbin.svelte
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script lang="ts">
|
|
2
2
|
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
|
+
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
|
+
|
|
5
|
+
type Row = Record<string, unknown>
|
|
6
|
+
type Scale = (value: unknown) => number | undefined
|
|
7
|
+
type Bin = { cx: number; cy: number; count: number; points: Row[] }
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
x?: string
|
|
11
|
+
y?: string
|
|
12
|
+
color?: string
|
|
13
|
+
fill?: string
|
|
14
|
+
stat?: string
|
|
15
|
+
options?: { radius?: number }
|
|
16
|
+
}
|
|
3
17
|
|
|
4
18
|
let {
|
|
5
19
|
x,
|
|
@@ -8,12 +22,12 @@
|
|
|
8
22
|
fill: fillProp = undefined,
|
|
9
23
|
stat = 'identity',
|
|
10
24
|
options = {}
|
|
11
|
-
} = $props()
|
|
25
|
+
}: Props = $props()
|
|
12
26
|
|
|
13
27
|
const radius = $derived(options.radius ?? 20)
|
|
14
28
|
|
|
15
|
-
const plotState = getContext('plot-state')
|
|
16
|
-
let id = $state(null)
|
|
29
|
+
const plotState = getContext<PlotState>('plot-state')
|
|
30
|
+
let id = $state<string | null>(null)
|
|
17
31
|
|
|
18
32
|
onMount(() => {
|
|
19
33
|
id = plotState.registerGeom({
|
|
@@ -41,15 +55,22 @@
|
|
|
41
55
|
|
|
42
56
|
// Hex binning: group data points into hexagonal cells
|
|
43
57
|
// eslint-disable-next-line max-params, complexity
|
|
44
|
-
function hexBin(
|
|
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[] {
|
|
45
66
|
const dx = r * 2 * Math.sin(Math.PI / 3) // horizontal spacing
|
|
46
67
|
const dy = r * 1.5 // vertical spacing
|
|
47
68
|
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
48
|
-
const bins = new Map()
|
|
69
|
+
const bins = new Map<string, Bin>()
|
|
49
70
|
|
|
50
|
-
for (const d of
|
|
51
|
-
const px = xScale(d[xField])
|
|
52
|
-
const py = yScale(d[yField])
|
|
71
|
+
for (const d of rows) {
|
|
72
|
+
const px = xScale(d[xField ?? ''])
|
|
73
|
+
const py = yScale(d[yField ?? ''])
|
|
53
74
|
if (px === null || px === undefined || py === null || py === undefined) continue
|
|
54
75
|
|
|
55
76
|
// Find nearest hex center
|
|
@@ -59,10 +80,11 @@
|
|
|
59
80
|
const cy = row * dy
|
|
60
81
|
const key = `${col},${row}`
|
|
61
82
|
|
|
62
|
-
|
|
63
|
-
|
|
83
|
+
let bin = bins.get(key)
|
|
84
|
+
if (!bin) {
|
|
85
|
+
bin = { cx, cy, count: 0, points: [] }
|
|
86
|
+
bins.set(key, bin)
|
|
64
87
|
}
|
|
65
|
-
const bin = bins.get(key)
|
|
66
88
|
bin.count++
|
|
67
89
|
bin.points.push(d)
|
|
68
90
|
}
|
|
@@ -70,7 +92,7 @@
|
|
|
70
92
|
}
|
|
71
93
|
|
|
72
94
|
// Build hexagon path for a given radius
|
|
73
|
-
function hexPath(r) {
|
|
95
|
+
function hexPath(r: number) {
|
|
74
96
|
const angles = [0, 1, 2, 3, 4, 5].map((i) => ((i * 60 - 30) * Math.PI) / 180)
|
|
75
97
|
return `${angles.map((a, i) => `${i === 0 ? 'M' : 'L'}${r * Math.cos(a)},${r * Math.sin(a)}`).join('') }Z`
|
|
76
98
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
type Props = {
|
|
3
|
+
x: number
|
|
4
|
+
y: number
|
|
5
|
+
text: string
|
|
6
|
+
color?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { x, y, text, color = '#333' }: Props = $props()
|
|
4
10
|
|
|
5
11
|
const w = $derived(Math.max(36, String(text).length * 7 + 12))
|
|
6
12
|
</script>
|
package/src/geoms/Line.svelte
CHANGED
|
@@ -1,10 +1,31 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script lang="ts">
|
|
2
2
|
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
|
+
import type { PlotState } from '../PlotState.svelte.js'
|
|
3
4
|
import { buildLines } from '../lib/brewing/marks/lines.js'
|
|
4
5
|
import { buildSymbolPath } from '../lib/brewing/marks/points.js'
|
|
5
6
|
import { keyboardNav } from '../lib/keyboard-nav.js'
|
|
6
7
|
import LabelPill from './LabelPill.svelte'
|
|
7
8
|
|
|
9
|
+
type Row = Record<string, unknown>
|
|
10
|
+
type Options = {
|
|
11
|
+
curve?: 'linear' | 'smooth' | 'step'
|
|
12
|
+
strokeWidth?: number
|
|
13
|
+
markerRadius?: number
|
|
14
|
+
labelOffset?: { x?: number; y?: number }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type Props = {
|
|
18
|
+
x?: string
|
|
19
|
+
y?: string
|
|
20
|
+
color?: string
|
|
21
|
+
symbol?: string
|
|
22
|
+
label?: boolean | string | ((data: Row) => unknown)
|
|
23
|
+
stat?: string
|
|
24
|
+
options?: Options
|
|
25
|
+
onselect?: (data: Row) => void
|
|
26
|
+
keyboard?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
8
29
|
let {
|
|
9
30
|
x,
|
|
10
31
|
y,
|
|
@@ -15,21 +36,17 @@
|
|
|
15
36
|
options = {},
|
|
16
37
|
onselect = undefined,
|
|
17
38
|
keyboard = false
|
|
18
|
-
} = $props()
|
|
39
|
+
}: Props = $props()
|
|
19
40
|
|
|
20
|
-
|
|
21
|
-
* @param {Record<string, unknown>} data
|
|
22
|
-
* @returns {string | null}
|
|
23
|
-
*/
|
|
24
|
-
function resolveLabel(data) {
|
|
41
|
+
function resolveLabel(data: Row): string | null {
|
|
25
42
|
if (!label) return null
|
|
26
|
-
if (label === true) return String(data[y] ?? '')
|
|
43
|
+
if (label === true) return String((y ? data[y] : undefined) ?? '')
|
|
27
44
|
if (typeof label === 'function') return String(label(data) ?? '')
|
|
28
45
|
return typeof label === 'string' ? String(data[label] ?? '') : null
|
|
29
46
|
}
|
|
30
47
|
|
|
31
|
-
const plotState = getContext('plot-state')
|
|
32
|
-
let id = $state(null)
|
|
48
|
+
const plotState = getContext<PlotState>('plot-state')
|
|
49
|
+
let id = $state<string | null>(null)
|
|
33
50
|
|
|
34
51
|
onMount(() => {
|
|
35
52
|
id = plotState.registerGeom({
|
package/src/geoms/Point.svelte
CHANGED
|
@@ -1,10 +1,34 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script lang="ts">
|
|
2
2
|
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
3
|
import { scaleSqrt } from 'd3-scale'
|
|
4
|
+
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
5
|
import { buildPoints } from '../lib/brewing/marks/points.js'
|
|
5
6
|
import { keyboardNav } from '../lib/keyboard-nav.js'
|
|
6
7
|
import LabelPill from './LabelPill.svelte'
|
|
7
8
|
|
|
9
|
+
type Row = Record<string, unknown>
|
|
10
|
+
type Options = {
|
|
11
|
+
minRadius?: number
|
|
12
|
+
maxRadius?: number
|
|
13
|
+
radius?: number
|
|
14
|
+
opacity?: number
|
|
15
|
+
jitter?: { width?: number; height?: number } | null
|
|
16
|
+
labelOffset?: { x?: number; y?: number }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type Props = {
|
|
20
|
+
x?: string
|
|
21
|
+
y?: string
|
|
22
|
+
color?: string
|
|
23
|
+
size?: string
|
|
24
|
+
symbol?: string
|
|
25
|
+
label?: boolean | string | ((data: Row) => unknown)
|
|
26
|
+
stat?: string
|
|
27
|
+
options?: Options
|
|
28
|
+
onselect?: (data: Row) => void
|
|
29
|
+
keyboard?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
8
32
|
let {
|
|
9
33
|
x,
|
|
10
34
|
y,
|
|
@@ -16,21 +40,17 @@
|
|
|
16
40
|
options = {},
|
|
17
41
|
onselect = undefined,
|
|
18
42
|
keyboard = false
|
|
19
|
-
} = $props()
|
|
43
|
+
}: Props = $props()
|
|
20
44
|
|
|
21
|
-
|
|
22
|
-
* @param {Record<string, unknown>} data
|
|
23
|
-
* @returns {string | null}
|
|
24
|
-
*/
|
|
25
|
-
function resolveLabel(data) {
|
|
45
|
+
function resolveLabel(data: Row): string | null {
|
|
26
46
|
if (!label) return null
|
|
27
|
-
if (label === true) return String(data[y] ?? '')
|
|
47
|
+
if (label === true) return String((y ? data[y] : undefined) ?? '')
|
|
28
48
|
if (typeof label === 'function') return String(label(data) ?? '')
|
|
29
49
|
return typeof label === 'string' ? String(data[label] ?? '') : null
|
|
30
50
|
}
|
|
31
51
|
|
|
32
|
-
const plotState = getContext('plot-state')
|
|
33
|
-
let id = $state(null)
|
|
52
|
+
const plotState = getContext<PlotState>('plot-state')
|
|
53
|
+
let id = $state<string | null>(null)
|
|
34
54
|
|
|
35
55
|
onMount(() => {
|
|
36
56
|
id = plotState.registerGeom({
|
|
@@ -88,7 +108,7 @@
|
|
|
88
108
|
{#if points.length > 0}
|
|
89
109
|
<g data-plot-geom="point">
|
|
90
110
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
91
|
-
{#each points as pt, i (`${i}::${pt.data[x]}::${pt.data[y]}`)}
|
|
111
|
+
{#each points as pt, i (`${i}::${pt.data[x ?? '']}::${pt.data[y ?? '']}`)}
|
|
92
112
|
{#if pt.symbolPath}
|
|
93
113
|
<path
|
|
94
114
|
transform="translate({pt.cx},{pt.cy})"
|
|
@@ -101,7 +121,7 @@
|
|
|
101
121
|
role={onselect || keyboard ? 'button' : 'graphics-symbol'}
|
|
102
122
|
tabindex={onselect || keyboard ? 0 : undefined}
|
|
103
123
|
style:cursor={onselect ? 'pointer' : undefined}
|
|
104
|
-
aria-label="{pt.data[x]}, {pt.data[y]}"
|
|
124
|
+
aria-label="{pt.data[x ?? '']}, {pt.data[y ?? '']}"
|
|
105
125
|
onmouseenter={() => plotState.setHovered(pt.data)}
|
|
106
126
|
onmouseleave={() => plotState.clearHovered()}
|
|
107
127
|
onclick={onselect ? () => onselect(pt.data) : undefined}
|
|
@@ -121,7 +141,7 @@
|
|
|
121
141
|
role={onselect || keyboard ? 'button' : 'graphics-symbol'}
|
|
122
142
|
tabindex={onselect || keyboard ? 0 : undefined}
|
|
123
143
|
style:cursor={onselect ? 'pointer' : undefined}
|
|
124
|
-
aria-label="{pt.data[x]}, {pt.data[y]}"
|
|
144
|
+
aria-label="{pt.data[x ?? '']}, {pt.data[y ?? '']}"
|
|
125
145
|
onmouseenter={() => plotState.setHovered(pt.data)}
|
|
126
146
|
onmouseleave={() => plotState.clearHovered()}
|
|
127
147
|
onclick={onselect ? () => onselect(pt.data) : undefined}
|
package/src/geoms/Ribbon.svelte
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script lang="ts">
|
|
2
2
|
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
|
+
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
|
+
|
|
5
|
+
type Node = { name: string; y: number; height: number }
|
|
6
|
+
|
|
7
|
+
type Options = {
|
|
8
|
+
source?: string
|
|
9
|
+
target?: string
|
|
10
|
+
value?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
x?: string
|
|
15
|
+
y?: string
|
|
16
|
+
color?: string
|
|
17
|
+
fill?: string
|
|
18
|
+
stat?: string
|
|
19
|
+
options?: Options
|
|
20
|
+
}
|
|
3
21
|
|
|
4
22
|
let {
|
|
5
23
|
x = undefined,
|
|
@@ -8,15 +26,15 @@
|
|
|
8
26
|
fill: fillProp = undefined,
|
|
9
27
|
stat = 'identity',
|
|
10
28
|
options = {}
|
|
11
|
-
} = $props()
|
|
29
|
+
}: Props = $props()
|
|
12
30
|
|
|
13
31
|
const sourceField = $derived(options.source ?? 'source')
|
|
14
32
|
const targetField = $derived(options.target ?? 'target')
|
|
15
33
|
const valueField = $derived(options.value ?? 'value')
|
|
16
34
|
const colorChannel = $derived(fillProp ?? color)
|
|
17
35
|
|
|
18
|
-
const plotState = getContext('plot-state')
|
|
19
|
-
let id = $state(null)
|
|
36
|
+
const plotState = getContext<PlotState>('plot-state')
|
|
37
|
+
let id = $state<string | null>(null)
|
|
20
38
|
|
|
21
39
|
onMount(() => {
|
|
22
40
|
id = plotState.registerGeom({
|
|
@@ -57,9 +75,9 @@
|
|
|
57
75
|
|
|
58
76
|
// Compute node positions
|
|
59
77
|
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
60
|
-
const sourceMap = new Map()
|
|
78
|
+
const sourceMap = new Map<string, number>()
|
|
61
79
|
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
62
|
-
const targetMap = new Map()
|
|
80
|
+
const targetMap = new Map<string, number>()
|
|
63
81
|
for (const f of flows) {
|
|
64
82
|
sourceMap.set(f.source, (sourceMap.get(f.source) ?? 0) + f.value)
|
|
65
83
|
targetMap.set(f.target, (targetMap.get(f.target) ?? 0) + f.value)
|
|
@@ -72,7 +90,7 @@
|
|
|
72
90
|
|
|
73
91
|
// Position source nodes on left
|
|
74
92
|
let sourceY = 0
|
|
75
|
-
const sourceNodes = []
|
|
93
|
+
const sourceNodes: Node[] = []
|
|
76
94
|
for (const [name, value] of sourceMap) {
|
|
77
95
|
const h = (value / totalSource) * availHeight
|
|
78
96
|
sourceNodes.push({ name, y: sourceY, height: h })
|
|
@@ -81,7 +99,7 @@
|
|
|
81
99
|
|
|
82
100
|
// Position target nodes on right
|
|
83
101
|
let targetY = 0
|
|
84
|
-
const targetNodes = []
|
|
102
|
+
const targetNodes: Node[] = []
|
|
85
103
|
for (const [name, value] of targetMap) {
|
|
86
104
|
const h = (value / totalTarget) * availHeight
|
|
87
105
|
targetNodes.push({ name, y: targetY, height: h })
|
package/src/geoms/Violin.svelte
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script lang="ts">
|
|
2
2
|
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
|
+
import type { PlotState } from '../PlotState.svelte.js'
|
|
3
4
|
import { buildViolins } from '../lib/brewing/marks/violins.js'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
type Props = {
|
|
7
|
+
x?: string
|
|
8
|
+
y?: string
|
|
9
|
+
fill?: string
|
|
10
|
+
stat?: string
|
|
11
|
+
options?: { opacity?: number }
|
|
12
|
+
}
|
|
6
13
|
|
|
7
|
-
|
|
8
|
-
|
|
14
|
+
let { x, y, fill, stat = 'boxplot', options = {} }: Props = $props()
|
|
15
|
+
|
|
16
|
+
const plotState = getContext<PlotState>('plot-state')
|
|
17
|
+
let id = $state<string | null>(null)
|
|
9
18
|
|
|
10
19
|
// fill ?? x drives the colors map for both violin interior and outline
|
|
11
20
|
const fillChannel = $derived(fill ?? x)
|