@rokkit/chart 1.0.0-next.150 → 1.0.0-next.155

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.
Files changed (90) hide show
  1. package/dist/PlotState.svelte.d.ts +31 -3
  2. package/dist/crossfilter/createCrossFilter.svelte.d.ts +13 -15
  3. package/dist/index.d.ts +6 -1
  4. package/dist/lib/brewing/BoxBrewer.svelte.d.ts +3 -5
  5. package/dist/lib/brewing/QuartileBrewer.svelte.d.ts +9 -0
  6. package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +3 -4
  7. package/dist/lib/brewing/brewer.svelte.d.ts +5 -36
  8. package/dist/lib/brewing/colors.d.ts +10 -1
  9. package/dist/lib/brewing/marks/points.d.ts +17 -2
  10. package/dist/lib/brewing/stats.d.ts +5 -13
  11. package/dist/lib/chart.d.ts +5 -7
  12. package/dist/lib/keyboard-nav.d.ts +15 -0
  13. package/dist/lib/plot/preset.d.ts +1 -1
  14. package/dist/lib/preset.d.ts +30 -0
  15. package/package.json +2 -1
  16. package/src/AnimatedPlot.svelte +375 -206
  17. package/src/Chart.svelte +81 -87
  18. package/src/ChartProvider.svelte +10 -0
  19. package/src/FacetPlot/Panel.svelte +30 -16
  20. package/src/FacetPlot.svelte +100 -76
  21. package/src/Plot/Area.svelte +26 -19
  22. package/src/Plot/Axis.svelte +81 -59
  23. package/src/Plot/Bar.svelte +47 -89
  24. package/src/Plot/Grid.svelte +23 -19
  25. package/src/Plot/Legend.svelte +213 -147
  26. package/src/Plot/Line.svelte +31 -21
  27. package/src/Plot/Point.svelte +35 -22
  28. package/src/Plot/Root.svelte +46 -91
  29. package/src/Plot/Timeline.svelte +82 -82
  30. package/src/Plot/Tooltip.svelte +68 -62
  31. package/src/Plot.svelte +290 -182
  32. package/src/PlotState.svelte.js +339 -267
  33. package/src/Sparkline.svelte +95 -56
  34. package/src/charts/AreaChart.svelte +22 -20
  35. package/src/charts/BarChart.svelte +23 -21
  36. package/src/charts/BoxPlot.svelte +15 -15
  37. package/src/charts/BubbleChart.svelte +17 -17
  38. package/src/charts/LineChart.svelte +20 -20
  39. package/src/charts/PieChart.svelte +30 -20
  40. package/src/charts/ScatterPlot.svelte +20 -19
  41. package/src/charts/ViolinPlot.svelte +15 -15
  42. package/src/crossfilter/CrossFilter.svelte +33 -29
  43. package/src/crossfilter/FilterBar.svelte +17 -25
  44. package/src/crossfilter/FilterHistogram.svelte +290 -0
  45. package/src/crossfilter/FilterSlider.svelte +69 -65
  46. package/src/crossfilter/createCrossFilter.svelte.js +100 -89
  47. package/src/geoms/Arc.svelte +114 -69
  48. package/src/geoms/Area.svelte +67 -39
  49. package/src/geoms/Bar.svelte +184 -126
  50. package/src/geoms/Box.svelte +102 -90
  51. package/src/geoms/LabelPill.svelte +11 -11
  52. package/src/geoms/Line.svelte +110 -87
  53. package/src/geoms/Point.svelte +132 -87
  54. package/src/geoms/Violin.svelte +45 -33
  55. package/src/geoms/lib/areas.js +122 -99
  56. package/src/geoms/lib/bars.js +195 -144
  57. package/src/index.js +21 -14
  58. package/src/lib/brewing/BoxBrewer.svelte.js +8 -50
  59. package/src/lib/brewing/CartesianBrewer.svelte.js +12 -7
  60. package/src/lib/brewing/PieBrewer.svelte.js +5 -5
  61. package/src/lib/brewing/QuartileBrewer.svelte.js +51 -0
  62. package/src/lib/brewing/ViolinBrewer.svelte.js +8 -49
  63. package/src/lib/brewing/brewer.svelte.js +249 -201
  64. package/src/lib/brewing/colors.js +34 -5
  65. package/src/lib/brewing/marks/arcs.js +28 -28
  66. package/src/lib/brewing/marks/areas.js +54 -41
  67. package/src/lib/brewing/marks/bars.js +34 -34
  68. package/src/lib/brewing/marks/boxes.js +51 -51
  69. package/src/lib/brewing/marks/lines.js +37 -30
  70. package/src/lib/brewing/marks/points.js +74 -26
  71. package/src/lib/brewing/marks/violins.js +57 -57
  72. package/src/lib/brewing/patterns.js +25 -11
  73. package/src/lib/brewing/scales.js +20 -20
  74. package/src/lib/brewing/stats.js +40 -28
  75. package/src/lib/brewing/symbols.js +1 -1
  76. package/src/lib/chart.js +12 -4
  77. package/src/lib/keyboard-nav.js +37 -0
  78. package/src/lib/plot/crossfilter.js +5 -5
  79. package/src/lib/plot/facet.js +30 -30
  80. package/src/lib/plot/frames.js +30 -29
  81. package/src/lib/plot/helpers.js +4 -4
  82. package/src/lib/plot/preset.js +48 -34
  83. package/src/lib/plot/scales.js +64 -39
  84. package/src/lib/plot/stat.js +47 -47
  85. package/src/lib/preset.js +41 -0
  86. package/src/patterns/DefinePatterns.svelte +24 -24
  87. package/src/patterns/PatternDef.svelte +1 -1
  88. package/src/patterns/patterns.js +328 -176
  89. package/src/patterns/scale.js +61 -32
  90. package/src/spec/chart-spec.js +64 -21
package/src/Plot.svelte CHANGED
@@ -1,189 +1,297 @@
1
1
  <script>
2
- import { setContext, untrack } from 'svelte'
3
- import { PlotState } from './PlotState.svelte.js'
4
- import Axis from './Plot/Axis.svelte'
5
- import Grid from './Plot/Grid.svelte'
6
- import Legend from './Plot/Legend.svelte'
7
- import Tooltip from './Plot/Tooltip.svelte'
8
- import DefinePatterns from './patterns/DefinePatterns.svelte'
9
- import Bar from './geoms/Bar.svelte'
10
- import Line from './geoms/Line.svelte'
11
- import Area from './geoms/Area.svelte'
12
- import Point from './geoms/Point.svelte'
13
- import Arc from './geoms/Arc.svelte'
14
- import Box from './geoms/Box.svelte'
15
- import Violin from './geoms/Violin.svelte'
16
-
17
- /**
18
- * @type {{
19
- * data?: Object[],
20
- * spec?: import('./lib/plot/types.js').PlotSpec,
21
- * helpers?: import('./lib/plot/types.js').PlotHelpers,
22
- * width?: number,
23
- * height?: number,
24
- * mode?: 'light' | 'dark',
25
- * grid?: boolean,
26
- * axes?: boolean,
27
- * margin?: { top: number, right: number, bottom: number, left: number },
28
- * legend?: boolean,
29
- * title?: string,
30
- * tooltip?: boolean | ((data: Record<string, unknown>) => string),
31
- * children?: import('svelte').Snippet,
32
- * }}
33
- */
34
- let {
35
- data = [],
36
- spec = undefined,
37
- helpers = {},
38
- width = 600,
39
- height = 400,
40
- mode = 'light',
41
- grid = true,
42
- axes = true,
43
- margin = undefined,
44
- legend = false,
45
- title = '',
46
- tooltip = false,
47
- children
48
- } = $props()
49
-
50
- // Create PlotState with initial values and provide as context.
51
- // untrack() suppresses "captures initial value" warnings — intentional:
52
- // the $effect below handles all subsequent reactive updates.
53
- const plotState = untrack(() => new PlotState({
54
- data: spec?.data ?? data,
55
- width: spec?.width ?? width,
56
- height: spec?.height ?? height,
57
- mode,
58
- margin,
59
- channels: spec ? { x: spec.x, y: spec.y, color: spec.color ?? spec.fill } : {},
60
- labels: spec?.labels ?? {},
61
- helpers,
62
- xDomain: spec?.xDomain,
63
- yDomain: spec?.yDomain,
64
- colorDomain: spec?.colorDomain
65
- }))
66
- setContext('plot-state', plotState)
67
-
68
- // Keep state in sync when reactive config changes
69
- $effect(() => {
70
- plotState.update({
71
- data: spec?.data ?? data,
72
- width: spec?.width ?? width,
73
- height: spec?.height ?? height,
74
- mode,
75
- margin,
76
- channels: spec ? { x: spec.x, y: spec.y, color: spec.color ?? spec.fill } : {},
77
- labels: spec?.labels ?? {},
78
- helpers,
79
- xDomain: spec?.xDomain,
80
- yDomain: spec?.yDomain,
81
- colorDomain: spec?.colorDomain
82
- })
83
- })
84
-
85
- const svgWidth = $derived(spec?.width ?? width)
86
- const svgHeight = $derived(spec?.height ?? height)
87
- const showGrid = $derived(spec?.grid ?? grid)
88
- const showLegend = $derived(spec?.legend ?? legend)
89
- const chartTitle = $derived(spec?.title ?? title)
90
-
91
- // Geoms from spec (spec-driven API)
92
- const specGeoms = $derived(spec?.geoms ?? [])
93
-
94
- // Geom component resolver for spec-driven mode
95
- const GEOM_COMPONENTS = { bar: Bar, line: Line, area: Area, point: Point, arc: Arc, box: Box, violin: Violin }
96
-
97
- /**
98
- * @param {string} type
99
- */
100
- function resolveGeomComponent(type) {
101
- return helpers?.geoms?.[type] ?? GEOM_COMPONENTS[type]
102
- }
2
+ import { setContext, getContext, untrack } from 'svelte'
3
+ import { zoom as d3Zoom } from 'd3-zoom'
4
+ import { select } from 'd3-selection'
5
+ import { PlotState } from './PlotState.svelte.js'
6
+ import { defaultPreset } from './lib/preset.js'
7
+ import Axis from './Plot/Axis.svelte'
8
+ import Grid from './Plot/Grid.svelte'
9
+ import Legend from './Plot/Legend.svelte'
10
+ import Tooltip from './Plot/Tooltip.svelte'
11
+ import DefinePatterns from './patterns/DefinePatterns.svelte'
12
+ import Bar from './geoms/Bar.svelte'
13
+ import Line from './geoms/Line.svelte'
14
+ import Area from './geoms/Area.svelte'
15
+ import Point from './geoms/Point.svelte'
16
+ import Arc from './geoms/Arc.svelte'
17
+ import Box from './geoms/Box.svelte'
18
+ import Violin from './geoms/Violin.svelte'
19
+
20
+ /**
21
+ * @type {{
22
+ * data?: Object[],
23
+ * spec?: import('./lib/plot/types.js').PlotSpec,
24
+ * helpers?: import('./lib/plot/types.js').PlotHelpers,
25
+ * width?: number,
26
+ * height?: number,
27
+ * mode?: 'light' | 'dark',
28
+ * grid?: boolean,
29
+ * axes?: boolean,
30
+ * margin?: { top: number, right: number, bottom: number, left: number },
31
+ * legend?: boolean,
32
+ * title?: string,
33
+ * summary?: string,
34
+ * tooltip?: boolean | ((data: Record<string, unknown>) => string),
35
+ * zoom?: boolean,
36
+ * children?: import('svelte').Snippet,
37
+ * }}
38
+ */
39
+ let {
40
+ data = [],
41
+ spec = undefined,
42
+ helpers = {},
43
+ width = 600,
44
+ height = 400,
45
+ mode = 'light',
46
+ grid = true,
47
+ axes = true,
48
+ margin = undefined,
49
+ legend = false,
50
+ title = '',
51
+ summary = '',
52
+ tooltip = false,
53
+ zoom = false,
54
+ xFormat = undefined,
55
+ yFormat = undefined,
56
+ children
57
+ } = $props()
58
+
59
+ const chartPresetCtx = getContext('chart-preset')
60
+ const chartPreset = $derived(chartPresetCtx?.current ?? defaultPreset)
61
+
62
+ // Responsive width: observe container and use actual pixel width for scale calculations
63
+ let svgEl = $state(null)
64
+ let containerEl = $state(null)
65
+ let observedWidth = $state(0)
66
+
67
+ $effect(() => {
68
+ if (!containerEl) return
69
+ const ro = new ResizeObserver((entries) => {
70
+ const w = Math.floor(entries[0].contentRect.width)
71
+ if (w > 0) observedWidth = w
72
+ })
73
+ ro.observe(containerEl)
74
+ return () => ro.disconnect()
75
+ })
76
+
77
+ const effectiveWidth = $derived(observedWidth > 0 ? observedWidth : (spec?.width ?? width))
78
+ const svgHeight = $derived(spec?.height ?? height)
79
+ const showGrid = $derived(spec?.grid ?? grid)
80
+ const showLegend = $derived(spec?.legend ?? legend)
81
+ const chartTitle = $derived(spec?.title ?? title)
82
+ const chartSummary = $derived(spec?.summary ?? summary)
83
+
84
+ // Accessible data table — screen reader fallback
85
+ const tableData = $derived(spec?.data ?? data)
86
+ const tableColumns = $derived.by(() => {
87
+ const cols = [spec?.x, spec?.y, spec?.color ?? spec?.fill].filter(Boolean)
88
+ if (cols.length > 0) return cols
89
+ const first = tableData[0]
90
+ return first ? Object.keys(first) : []
91
+ })
92
+
93
+ function buildPlotConfig() {
94
+ return {
95
+ data: spec?.data ?? data,
96
+ width: effectiveWidth,
97
+ height: spec?.height ?? height,
98
+ mode,
99
+ margin,
100
+ channels: spec ? { x: spec.x, y: spec.y, color: spec.color ?? spec.fill } : {},
101
+ labels: spec?.labels ?? {},
102
+ helpers,
103
+ xDomain: spec?.xDomain,
104
+ yDomain: spec?.yDomain,
105
+ colorDomain: spec?.colorDomain,
106
+ orientation: spec?.orientation,
107
+ chartPreset
108
+ }
109
+ }
110
+
111
+ // Create PlotState with initial values and provide as context.
112
+ // untrack() suppresses "captures initial value" warnings — intentional:
113
+ // the $effect below handles all subsequent reactive updates.
114
+ const plotState = untrack(() => new PlotState(buildPlotConfig()))
115
+ setContext('plot-state', plotState)
116
+
117
+ // Keep state in sync when reactive config changes
118
+ $effect(() => {
119
+ plotState.update(buildPlotConfig())
120
+ })
121
+
122
+ $effect(() => {
123
+ if (!zoom || !svgEl) return
124
+ const zoomBehavior = d3Zoom()
125
+ .scaleExtent([1, 8])
126
+ .on('zoom', (event) => {
127
+ plotState.applyZoom(event.transform)
128
+ })
129
+ const sel = select(svgEl)
130
+ sel.call(zoomBehavior)
131
+ return () => {
132
+ sel.on('.zoom', null)
133
+ plotState.resetZoom()
134
+ }
135
+ })
136
+
137
+ // Geoms from spec (spec-driven API)
138
+ const specGeoms = $derived(spec?.geoms ?? [])
139
+
140
+ // Geom component resolver for spec-driven mode
141
+ const GEOM_COMPONENTS = {
142
+ bar: Bar,
143
+ line: Line,
144
+ area: Area,
145
+ point: Point,
146
+ arc: Arc,
147
+ box: Box,
148
+ violin: Violin
149
+ }
150
+
151
+ /**
152
+ * @param {string} type
153
+ */
154
+ function resolveGeomComponent(type) {
155
+ return helpers?.geoms?.[type] ?? GEOM_COMPONENTS[type]
156
+ }
103
157
  </script>
104
158
 
105
- <div class="plot-root" data-plot-root data-mode={mode}>
106
- {#if chartTitle}
107
- <div class="plot-title" data-plot-title>{chartTitle}</div>
108
- {/if}
109
-
110
- <svg
111
- width={svgWidth}
112
- height={svgHeight}
113
- viewBox="0 0 {svgWidth} {svgHeight}"
114
- role="img"
115
- aria-label={chartTitle || 'Chart visualization'}
116
- >
117
- <!-- SVG pattern defs -->
118
- <DefinePatterns />
119
-
120
- <g
121
- class="plot-canvas"
122
- transform="translate({plotState.margin.left}, {plotState.margin.top})"
123
- data-plot-canvas
124
- >
125
- <!-- Grid (behind everything) -->
126
- {#if showGrid}
127
- <Grid />
128
- {/if}
129
-
130
- <!-- Declarative children (geom components) -->
131
- {@render children?.()}
132
-
133
- <!-- Spec-driven geoms -->
134
- {#each specGeoms as geomSpec}
135
- {@const GeomComponent = resolveGeomComponent(geomSpec.type)}
136
- {#if GeomComponent}
137
- <GeomComponent
138
- x={geomSpec.x ?? spec?.x}
139
- y={geomSpec.y ?? spec?.y}
140
- color={geomSpec.color ?? spec?.color}
141
- fill={geomSpec.fill ?? spec?.fill}
142
- stat={geomSpec.stat}
143
- options={geomSpec.options}
144
- />
145
- {/if}
146
- {/each}
147
-
148
- <!-- Axes -->
149
- {#if axes}
150
- <Axis type="x" label={spec?.labels?.[spec?.x ?? ''] ?? ''} />
151
- <Axis type="y" label={spec?.labels?.[spec?.y ?? ''] ?? ''} />
152
- {/if}
153
- </g>
154
- </svg>
155
-
156
- <!-- Legend (HTML, outside SVG) -->
157
- {#if showLegend}
158
- <Legend labels={spec?.labels ?? {}} />
159
- {/if}
160
-
161
- <!-- Tooltip (HTML, fixed-position overlay) -->
162
- {#if tooltip}
163
- <Tooltip {tooltip} />
164
- {/if}
159
+ <div class="plot-root" data-plot-root data-mode={mode} bind:this={containerEl}>
160
+ {#if chartTitle}
161
+ <div class="plot-title" data-plot-title>{chartTitle}</div>
162
+ {/if}
163
+
164
+ <svg
165
+ bind:this={svgEl}
166
+ width={effectiveWidth}
167
+ height={svgHeight}
168
+ viewBox="0 0 {effectiveWidth} {svgHeight}"
169
+ role="img"
170
+ aria-label={chartTitle || 'Chart visualization'}
171
+ style:cursor={zoom ? 'grab' : undefined}
172
+ >
173
+ {#if chartSummary}
174
+ <desc>{chartSummary}</desc>
175
+ {/if}
176
+ <!-- SVG pattern defs -->
177
+ <DefinePatterns />
178
+
179
+ <g
180
+ class="plot-canvas"
181
+ transform="translate({plotState.margin.left}, {plotState.margin.top})"
182
+ data-plot-canvas
183
+ >
184
+ <!-- Grid (behind everything) -->
185
+ {#if showGrid}
186
+ <Grid />
187
+ {/if}
188
+
189
+ <!-- Declarative children (geom components) -->
190
+ {@render children?.()}
191
+
192
+ <!-- Spec-driven geoms -->
193
+ {#each specGeoms as geomSpec (geomSpec.type)}
194
+ {@const GeomComponent = resolveGeomComponent(geomSpec.type)}
195
+ {#if GeomComponent}
196
+ <GeomComponent
197
+ x={geomSpec.x ?? spec?.x}
198
+ y={geomSpec.y ?? spec?.y}
199
+ color={geomSpec.color ?? spec?.color}
200
+ fill={geomSpec.fill ?? spec?.fill}
201
+ pattern={geomSpec.pattern}
202
+ symbol={geomSpec.symbol}
203
+ stat={geomSpec.stat}
204
+ label={geomSpec.label}
205
+ options={geomSpec.options}
206
+ />
207
+ {/if}
208
+ {/each}
209
+
210
+ <!-- Axes -->
211
+ {#if axes}
212
+ <Axis type="x" label={spec?.labels?.[spec?.x ?? ''] ?? ''} format={xFormat} />
213
+ <Axis type="y" label={spec?.labels?.[spec?.y ?? ''] ?? ''} format={yFormat} />
214
+ {/if}
215
+ </g>
216
+ </svg>
217
+
218
+ <!-- Legend (HTML, outside SVG) -->
219
+ {#if showLegend}
220
+ <Legend labels={spec?.labels ?? {}} />
221
+ {/if}
222
+
223
+ <!-- Tooltip (HTML, fixed-position overlay) -->
224
+ {#if tooltip}
225
+ <Tooltip {tooltip} />
226
+ {/if}
227
+
228
+ <!-- Accessible data table — visually hidden, announces data to screen readers -->
229
+ {#if tableData.length > 0 && tableColumns.length > 0}
230
+ <table class="plot-sr-table" aria-label={chartTitle || 'Chart data'}>
231
+ {#if chartTitle}
232
+ <caption>{chartTitle}</caption>
233
+ {/if}
234
+ <thead>
235
+ <tr>
236
+ {#each tableColumns as col (col)}
237
+ <th scope="col">{col}</th>
238
+ {/each}
239
+ </tr>
240
+ </thead>
241
+ <tbody>
242
+ {#each tableData as row, i (i)}
243
+ <tr>
244
+ {#each tableColumns as col (col)}
245
+ <td>{row[col] ?? ''}</td>
246
+ {/each}
247
+ </tr>
248
+ {/each}
249
+ </tbody>
250
+ </table>
251
+ {/if}
165
252
  </div>
166
253
 
167
254
  <style>
168
- .plot-root {
169
- position: relative;
170
- width: 100%;
171
- height: auto;
172
- }
173
-
174
- svg {
175
- display: block;
176
- overflow: visible;
177
- }
178
-
179
- .plot-canvas {
180
- pointer-events: all;
181
- }
182
-
183
- .plot-title {
184
- font-size: 14px;
185
- font-weight: 600;
186
- text-align: center;
187
- margin-bottom: 4px;
188
- }
255
+ .plot-root {
256
+ position: relative;
257
+ width: 100%;
258
+ height: auto;
259
+ }
260
+
261
+ [data-plot-root][data-mode='light'] {
262
+ color: rgba(var(--color-surface-700, 59, 65, 77), 1);
263
+ }
264
+
265
+ [data-plot-root][data-mode='dark'] {
266
+ color: rgba(var(--color-surface-200, 224, 224, 224), 1);
267
+ }
268
+
269
+ svg {
270
+ display: block;
271
+ overflow: visible;
272
+ }
273
+
274
+ .plot-canvas {
275
+ pointer-events: all;
276
+ }
277
+
278
+ .plot-title {
279
+ font-size: 14px;
280
+ font-weight: 600;
281
+ text-align: center;
282
+ margin-bottom: 4px;
283
+ }
284
+
285
+ /* Visually hidden — in DOM for screen readers, not visible */
286
+ .plot-sr-table {
287
+ position: absolute;
288
+ width: 1px;
289
+ height: 1px;
290
+ padding: 0;
291
+ margin: -1px;
292
+ overflow: hidden;
293
+ clip: rect(0, 0, 0, 0);
294
+ white-space: nowrap;
295
+ border: 0;
296
+ }
189
297
  </style>