@rokkit/chart 1.0.0-next.151 → 1.0.0-next.158

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 (86) hide show
  1. package/dist/PlotState.svelte.d.ts +26 -0
  2. package/dist/index.d.ts +6 -1
  3. package/dist/lib/brewing/BoxBrewer.svelte.d.ts +3 -5
  4. package/dist/lib/brewing/QuartileBrewer.svelte.d.ts +9 -0
  5. package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +3 -4
  6. package/dist/lib/brewing/colors.d.ts +10 -1
  7. package/dist/lib/brewing/marks/points.d.ts +17 -2
  8. package/dist/lib/keyboard-nav.d.ts +15 -0
  9. package/dist/lib/plot/preset.d.ts +1 -1
  10. package/dist/lib/preset.d.ts +30 -0
  11. package/package.json +2 -1
  12. package/src/AnimatedPlot.svelte +375 -207
  13. package/src/Chart.svelte +81 -84
  14. package/src/ChartProvider.svelte +10 -0
  15. package/src/FacetPlot/Panel.svelte +30 -16
  16. package/src/FacetPlot.svelte +100 -76
  17. package/src/Plot/Area.svelte +26 -19
  18. package/src/Plot/Axis.svelte +81 -59
  19. package/src/Plot/Bar.svelte +47 -89
  20. package/src/Plot/Grid.svelte +23 -19
  21. package/src/Plot/Legend.svelte +213 -147
  22. package/src/Plot/Line.svelte +31 -21
  23. package/src/Plot/Point.svelte +35 -22
  24. package/src/Plot/Root.svelte +46 -91
  25. package/src/Plot/Timeline.svelte +82 -82
  26. package/src/Plot/Tooltip.svelte +68 -62
  27. package/src/Plot.svelte +290 -174
  28. package/src/PlotState.svelte.js +338 -265
  29. package/src/Sparkline.svelte +95 -56
  30. package/src/charts/AreaChart.svelte +22 -20
  31. package/src/charts/BarChart.svelte +23 -21
  32. package/src/charts/BoxPlot.svelte +15 -15
  33. package/src/charts/BubbleChart.svelte +17 -17
  34. package/src/charts/LineChart.svelte +20 -20
  35. package/src/charts/PieChart.svelte +30 -20
  36. package/src/charts/ScatterPlot.svelte +20 -19
  37. package/src/charts/ViolinPlot.svelte +15 -15
  38. package/src/crossfilter/CrossFilter.svelte +33 -29
  39. package/src/crossfilter/FilterBar.svelte +17 -25
  40. package/src/crossfilter/FilterHistogram.svelte +290 -0
  41. package/src/crossfilter/FilterSlider.svelte +69 -65
  42. package/src/crossfilter/createCrossFilter.svelte.js +94 -90
  43. package/src/geoms/Arc.svelte +114 -69
  44. package/src/geoms/Area.svelte +67 -39
  45. package/src/geoms/Bar.svelte +184 -126
  46. package/src/geoms/Box.svelte +101 -91
  47. package/src/geoms/LabelPill.svelte +11 -11
  48. package/src/geoms/Line.svelte +110 -86
  49. package/src/geoms/Point.svelte +130 -90
  50. package/src/geoms/Violin.svelte +51 -41
  51. package/src/geoms/lib/areas.js +122 -99
  52. package/src/geoms/lib/bars.js +195 -144
  53. package/src/index.js +21 -14
  54. package/src/lib/brewing/BoxBrewer.svelte.js +8 -50
  55. package/src/lib/brewing/CartesianBrewer.svelte.js +11 -7
  56. package/src/lib/brewing/PieBrewer.svelte.js +5 -5
  57. package/src/lib/brewing/QuartileBrewer.svelte.js +51 -0
  58. package/src/lib/brewing/ViolinBrewer.svelte.js +8 -49
  59. package/src/lib/brewing/brewer.svelte.js +242 -195
  60. package/src/lib/brewing/colors.js +34 -5
  61. package/src/lib/brewing/marks/arcs.js +28 -28
  62. package/src/lib/brewing/marks/areas.js +54 -41
  63. package/src/lib/brewing/marks/bars.js +34 -34
  64. package/src/lib/brewing/marks/boxes.js +51 -51
  65. package/src/lib/brewing/marks/lines.js +37 -30
  66. package/src/lib/brewing/marks/points.js +74 -26
  67. package/src/lib/brewing/marks/violins.js +57 -57
  68. package/src/lib/brewing/patterns.js +25 -11
  69. package/src/lib/brewing/scales.js +17 -17
  70. package/src/lib/brewing/stats.js +37 -29
  71. package/src/lib/brewing/symbols.js +1 -1
  72. package/src/lib/chart.js +2 -1
  73. package/src/lib/keyboard-nav.js +37 -0
  74. package/src/lib/plot/crossfilter.js +5 -5
  75. package/src/lib/plot/facet.js +30 -30
  76. package/src/lib/plot/frames.js +30 -29
  77. package/src/lib/plot/helpers.js +4 -4
  78. package/src/lib/plot/preset.js +48 -34
  79. package/src/lib/plot/scales.js +64 -39
  80. package/src/lib/plot/stat.js +47 -47
  81. package/src/lib/preset.js +41 -0
  82. package/src/patterns/DefinePatterns.svelte +24 -24
  83. package/src/patterns/README.md +3 -0
  84. package/src/patterns/patterns.js +328 -176
  85. package/src/patterns/scale.js +61 -32
  86. package/src/spec/chart-spec.js +64 -21
@@ -1,27 +1,40 @@
1
1
  <script>
2
2
  import { getContext } from 'svelte'
3
3
 
4
- const brewer = getContext('chart-brewer')
5
- </script>
4
+ let {
5
+ data = [],
6
+ x = undefined,
7
+ y = undefined,
8
+ r = 4,
9
+ fill = 'steelblue',
10
+ stroke = 'white',
11
+ strokeWidth = 1
12
+ } = $props()
13
+
14
+ const state = getContext('plot-state')
6
15
 
7
- {#if brewer && brewer.points && brewer.points.length > 0}
8
- <g class="chart-points" data-plot-type="point">
9
- {#each brewer.points as pt, i (i)}
10
- <circle
11
- cx={pt.cx}
12
- cy={pt.cy}
13
- r={pt.r}
14
- fill={pt.fill}
15
- stroke={pt.stroke}
16
- stroke-width="1"
17
- data-plot-element="point"
18
- />
19
- {/each}
20
- </g>
21
- {/if}
16
+ const points = $derived.by(() => {
17
+ if (!state?.xScale || !state?.yScale || !data?.length) return []
18
+ return data
19
+ .map((d) => ({
20
+ cx: state.xScale(x ? d[x] : d) ?? null,
21
+ cy: state.yScale(y ? d[y] : d) ?? null,
22
+ label: `(${x ? d[x] : d}, ${y ? d[y] : d})`
23
+ }))
24
+ .filter((p) => p.cx !== null && p.cy !== null)
25
+ })
26
+ </script>
22
27
 
23
- <style>
24
- .chart-points {
25
- pointer-events: none;
26
- }
27
- </style>
28
+ {#each points as pt, i (i)}
29
+ <circle
30
+ cx={pt.cx}
31
+ cy={pt.cy}
32
+ {r}
33
+ {fill}
34
+ {stroke}
35
+ stroke-width={strokeWidth}
36
+ data-plot-element="point"
37
+ role="graphics-symbol"
38
+ aria-label={pt.label}
39
+ />
40
+ {/each}
@@ -1,107 +1,62 @@
1
1
  <script>
2
- import { setContext } from 'svelte'
3
- import { ChartBrewer } from '../lib/brewing/index.svelte.js'
2
+ import { setContext, untrack } from 'svelte'
3
+ import { PlotState } from '../PlotState.svelte.js'
4
+ import { defaultPreset } from '../lib/preset.js'
4
5
 
5
6
  let {
6
7
  data = [],
8
+ x = undefined,
9
+ y = undefined,
10
+ color = undefined,
7
11
  width = 600,
8
12
  height = 400,
9
- margin = { top: 20, right: 30, bottom: 40, left: 50 },
10
- fill = null,
11
- responsive = true,
12
- animationDuration = 300,
13
+ margin = undefined,
14
+ mode = 'light',
13
15
  children
14
16
  } = $props()
15
17
 
16
- // Create chart brewer instance
17
- let brewer = $state(new ChartBrewer())
18
-
19
- $effect(() => {
20
- brewer.setDimensions({ width, height, margin })
21
- })
22
-
23
- // Chart dimensions derived from brewer
24
- let dimensions = $derived(brewer.getDimensions())
25
-
26
- // Process data
27
- $effect(() => {
28
- // If data has a select method (dataset object), call it to get actual data
29
- const chartData = data.select && typeof data.select === 'function' ? data.select() : data
30
-
31
- // Update brewer with data and fields
32
- brewer.setData(chartData)
33
- brewer.setFields({ color: fill })
34
-
35
- // Create scales after setting data
36
- brewer.createScales()
37
- })
38
-
39
- // Provide chart context to child components
40
- setContext('chart-brewer', brewer)
41
-
42
- // Handle responsive behavior
43
- let container
44
-
45
- $effect(() => {
46
- if (!responsive || !container || !document) return
47
-
48
- const resizeObserver = new ResizeObserver((entries) => {
49
- const entry = entries[0]
50
- if (!entry) return
51
-
52
- const containerWidth = entry.contentRect.width
53
- const aspectRatio = height / width
54
-
55
- // Update chart dimensions while maintaining aspect ratio
56
- brewer.setDimensions({
57
- width: containerWidth,
58
- height: containerWidth * aspectRatio
18
+ const plotState = untrack(
19
+ () =>
20
+ new PlotState({
21
+ data,
22
+ channels: { x, y, color },
23
+ width,
24
+ height,
25
+ margin,
26
+ mode,
27
+ chartPreset: defaultPreset
59
28
  })
29
+ )
60
30
 
61
- // Update scales after dimensions change
62
- brewer.createScales()
31
+ $effect(() => {
32
+ plotState.update({
33
+ data,
34
+ channels: { x, y, color },
35
+ width,
36
+ height,
37
+ margin,
38
+ mode,
39
+ chartPreset: defaultPreset
63
40
  })
64
-
65
- // Start observing container size
66
- resizeObserver.observe(container)
67
-
68
- return () => {
69
- resizeObserver.disconnect()
70
- }
71
41
  })
72
- </script>
73
42
 
74
- <div class="chart-container" bind:this={container} data-plot-root>
75
- <svg
76
- width={dimensions.width}
77
- height={dimensions.height}
78
- viewBox="0 0 {dimensions.width} {dimensions.height}"
79
- role="img"
80
- aria-label="Chart visualization"
81
- >
82
- <g
83
- class="chart-area"
84
- transform="translate({dimensions.margin.left}, {dimensions.margin.top})"
85
- data-plot-canvas
86
- >
87
- {@render children?.()}
88
- </g>
89
- </svg>
90
- </div>
43
+ setContext('plot-state', plotState)
91
44
 
92
- <style>
93
- .chart-container {
94
- position: relative;
95
- width: 100%;
96
- height: auto;
97
- }
98
-
99
- svg {
100
- display: block;
101
- overflow: visible;
102
- }
45
+ const svgWidth = $derived(plotState.innerWidth + (margin?.left ?? 50) + (margin?.right ?? 30))
46
+ const svgHeight = $derived(plotState.innerHeight + (margin?.top ?? 20) + (margin?.bottom ?? 40))
47
+ const marginLeft = $derived(margin?.left ?? 50)
48
+ const marginTop = $derived(margin?.top ?? 20)
49
+ </script>
103
50
 
104
- .chart-area {
105
- pointer-events: all;
106
- }
107
- </style>
51
+ <svg
52
+ {width}
53
+ {height}
54
+ viewBox="0 0 {svgWidth} {svgHeight}"
55
+ role="img"
56
+ aria-label="Chart visualization"
57
+ data-plot-root
58
+ >
59
+ <g transform="translate({marginLeft}, {marginTop})" data-plot-canvas>
60
+ {@render children?.()}
61
+ </g>
62
+ </svg>
@@ -1,95 +1,95 @@
1
1
  <script>
2
- /**
3
- * @type {{
4
- * frameKeys: unknown[],
5
- * currentIndex: number,
6
- * playing: boolean,
7
- * speed: number,
8
- * onplay: () => void,
9
- * onpause: () => void,
10
- * onscrub: (index: number) => void,
11
- * onspeed: (speed: number) => void
12
- * }}
13
- */
14
- let {
15
- frameKeys = [],
16
- currentIndex = 0,
17
- playing = false,
18
- speed = 1,
19
- onplay,
20
- onpause,
21
- onscrub,
22
- onspeed
23
- } = $props()
2
+ /**
3
+ * @type {{
4
+ * frameKeys: unknown[],
5
+ * currentIndex: number,
6
+ * playing: boolean,
7
+ * speed: number,
8
+ * onplay: () => void,
9
+ * onpause: () => void,
10
+ * onscrub: (index: number) => void,
11
+ * onspeed: (speed: number) => void
12
+ * }}
13
+ */
14
+ let {
15
+ frameKeys = [],
16
+ currentIndex = 0,
17
+ playing = false,
18
+ speed = 1,
19
+ onplay,
20
+ onpause,
21
+ onscrub,
22
+ onspeed
23
+ } = $props()
24
24
 
25
- const safeIndex = $derived(
26
- frameKeys.length === 0 ? 0 : Math.min(currentIndex, frameKeys.length - 1)
27
- )
25
+ const safeIndex = $derived(
26
+ frameKeys.length === 0 ? 0 : Math.min(currentIndex, frameKeys.length - 1)
27
+ )
28
28
 
29
- const SPEEDS = [0.5, 1, 1.5, 2, 4]
29
+ const SPEEDS = [0.5, 1, 1.5, 2, 4]
30
30
  </script>
31
31
 
32
32
  <div class="timeline" data-plot-timeline>
33
- <!-- Play / Pause -->
34
- <button
35
- class="play-pause"
36
- aria-label={playing ? 'Pause' : 'Play'}
37
- onclick={() => playing ? onpause?.() : onplay?.()}
38
- disabled={frameKeys.length === 0}
39
- data-plot-timeline-playpause
40
- >
41
- {playing ? '⏸' : '▶'}
42
- </button>
33
+ <!-- Play / Pause -->
34
+ <button
35
+ class="play-pause"
36
+ aria-label={playing ? 'Pause' : 'Play'}
37
+ onclick={() => (playing ? onpause?.() : onplay?.())}
38
+ disabled={frameKeys.length === 0}
39
+ data-plot-timeline-playpause
40
+ >
41
+ {playing ? '⏸' : '▶'}
42
+ </button>
43
43
 
44
- <!-- Frame label -->
45
- <span class="frame-label" data-plot-timeline-label>{frameKeys[safeIndex] ?? ''}</span>
44
+ <!-- Frame label -->
45
+ <span class="frame-label" data-plot-timeline-label>{frameKeys[safeIndex] ?? ''}</span>
46
46
 
47
- <!-- Scrub slider -->
48
- <input
49
- type="range"
50
- min="0"
51
- max={Math.max(0, frameKeys.length - 1)}
52
- value={safeIndex}
53
- disabled={frameKeys.length === 0}
54
- class="scrub"
55
- aria-label="Animation timeline"
56
- oninput={(e) => onscrub?.(Number(e.currentTarget.value))}
57
- data-plot-timeline-scrub
58
- />
47
+ <!-- Scrub slider -->
48
+ <input
49
+ type="range"
50
+ min="0"
51
+ max={Math.max(0, frameKeys.length - 1)}
52
+ value={safeIndex}
53
+ disabled={frameKeys.length === 0}
54
+ class="scrub"
55
+ aria-label="Animation timeline"
56
+ oninput={(e) => onscrub?.(Number(e.currentTarget.value))}
57
+ data-plot-timeline-scrub
58
+ />
59
59
 
60
- <!-- Speed selector -->
61
- <select
62
- aria-label="Playback speed"
63
- value={speed}
64
- onchange={(e) => onspeed?.(Number(e.currentTarget.value))}
65
- data-plot-timeline-speed
66
- >
67
- {#each SPEEDS as s (s)}
68
- <option value={s}>{s}×</option>
69
- {/each}
70
- </select>
60
+ <!-- Speed selector -->
61
+ <select
62
+ aria-label="Playback speed"
63
+ value={speed}
64
+ onchange={(e) => onspeed?.(Number(e.currentTarget.value))}
65
+ data-plot-timeline-speed
66
+ >
67
+ {#each SPEEDS as s (s)}
68
+ <option value={s}>{s}×</option>
69
+ {/each}
70
+ </select>
71
71
  </div>
72
72
 
73
73
  <style>
74
- .timeline {
75
- display: flex;
76
- align-items: center;
77
- gap: 8px;
78
- padding: 8px 0;
79
- font-size: 12px;
80
- }
81
- .play-pause {
82
- font-size: 16px;
83
- cursor: pointer;
84
- background: none;
85
- border: none;
86
- padding: 0;
87
- }
88
- .scrub {
89
- flex: 1;
90
- }
91
- .frame-label {
92
- min-width: 4ch;
93
- text-align: right;
94
- }
74
+ .timeline {
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 8px;
78
+ padding: 8px 0;
79
+ font-size: 12px;
80
+ }
81
+ .play-pause {
82
+ font-size: 16px;
83
+ cursor: pointer;
84
+ background: none;
85
+ border: none;
86
+ padding: 0;
87
+ }
88
+ .scrub {
89
+ flex: 1;
90
+ }
91
+ .frame-label {
92
+ min-width: 4ch;
93
+ text-align: right;
94
+ }
95
95
  </style>
@@ -1,81 +1,87 @@
1
1
  <script>
2
- import { getContext } from 'svelte'
2
+ import { getContext } from 'svelte'
3
3
 
4
- /** @type {{ tooltip?: boolean | ((data: Record<string, unknown>) => string) }} */
5
- let { tooltip = true } = $props()
4
+ /** @type {{ tooltip?: boolean | ((data: Record<string, unknown>) => string) }} */
5
+ let { tooltip = true } = $props()
6
6
 
7
- const plotState = getContext('plot-state')
7
+ const plotState = getContext('plot-state')
8
8
 
9
- let mouseX = $state(0)
10
- let mouseY = $state(0)
9
+ let mouseX = $state(0)
10
+ let mouseY = $state(0)
11
11
 
12
- $effect(() => {
13
- function onMove(e) { mouseX = e.clientX; mouseY = e.clientY }
14
- window.addEventListener('mousemove', onMove)
15
- return () => window.removeEventListener('mousemove', onMove)
16
- })
12
+ $effect(() => {
13
+ function onMove(e) {
14
+ mouseX = e.clientX
15
+ mouseY = e.clientY
16
+ }
17
+ window.addEventListener('mousemove', onMove)
18
+ return () => window.removeEventListener('mousemove', onMove)
19
+ })
17
20
 
18
- const hovered = $derived(plotState.hovered)
19
- const mode = $derived(plotState.mode)
21
+ const hovered = $derived(plotState.hovered)
22
+ const mode = $derived(plotState.mode)
20
23
 
21
- function formatDefault(data) {
22
- return Object.entries(data)
23
- .filter(([, v]) => v !== undefined && v !== null)
24
- .map(([k, v]) => `<div class="tt-row"><span class="tt-key">${k}</span><span class="tt-val">${v}</span></div>`)
25
- .join('')
26
- }
24
+ function formatDefault(data) {
25
+ return Object.entries(data)
26
+ .filter(([, v]) => v !== undefined && v !== null)
27
+ .map(
28
+ ([k, v]) =>
29
+ `<div class="tt-row"><span class="tt-key">${k}</span><span class="tt-val">${v}</span></div>`
30
+ )
31
+ .join('')
32
+ }
27
33
 
28
- const content = $derived.by(() => {
29
- if (!hovered) return ''
30
- if (typeof tooltip === 'function') return String(tooltip(hovered) ?? '')
31
- return formatDefault(hovered)
32
- })
34
+ const content = $derived.by(() => {
35
+ if (!hovered) return ''
36
+ if (typeof tooltip === 'function') return String(tooltip(hovered) ?? '')
37
+ return formatDefault(hovered)
38
+ })
33
39
  </script>
34
40
 
35
41
  {#if hovered && content}
36
- <div
37
- class="plot-tooltip"
38
- data-mode={mode}
39
- style:left="{mouseX + 14}px"
40
- style:top="{mouseY - 8}px"
41
- >
42
- {@html content}
43
- </div>
42
+ <div
43
+ class="plot-tooltip"
44
+ data-mode={mode}
45
+ style:left="{mouseX + 14}px"
46
+ style:top="{mouseY - 8}px"
47
+ >
48
+ {@html content}
49
+ </div>
44
50
  {/if}
45
51
 
46
52
  <style>
47
- .plot-tooltip {
48
- position: fixed;
49
- pointer-events: none;
50
- z-index: 1000;
51
- padding: 8px 10px;
52
- border-radius: 6px;
53
- font-size: 12px;
54
- line-height: 1.5;
55
- white-space: nowrap;
56
- box-shadow: 0 4px 12px rgb(0 0 0 / 0.12);
57
- background: #ffffff;
58
- border: 1px solid #e2e8f0;
59
- color: #1e293b;
60
- }
53
+ .plot-tooltip {
54
+ position: fixed;
55
+ pointer-events: none;
56
+ z-index: 1000;
57
+ padding: 8px 10px;
58
+ border-radius: 6px;
59
+ font-size: 12px;
60
+ line-height: 1.5;
61
+ white-space: nowrap;
62
+ box-shadow: 0 4px 12px rgb(0 0 0 / 0.12);
63
+ background: #ffffff;
64
+ border: 1px solid #e2e8f0;
65
+ color: #1e293b;
66
+ }
61
67
 
62
- .plot-tooltip[data-mode="dark"] {
63
- background: #1e293b;
64
- border-color: #334155;
65
- color: #f1f5f9;
66
- }
68
+ .plot-tooltip[data-mode='dark'] {
69
+ background: #1e293b;
70
+ border-color: #334155;
71
+ color: #f1f5f9;
72
+ }
67
73
 
68
- :global(.tt-row) {
69
- display: flex;
70
- gap: 10px;
71
- justify-content: space-between;
72
- }
74
+ :global(.tt-row) {
75
+ display: flex;
76
+ gap: 10px;
77
+ justify-content: space-between;
78
+ }
73
79
 
74
- :global(.tt-key) {
75
- opacity: 0.65;
76
- }
80
+ :global(.tt-key) {
81
+ opacity: 0.65;
82
+ }
77
83
 
78
- :global(.tt-val) {
79
- font-weight: 600;
80
- }
84
+ :global(.tt-val) {
85
+ font-weight: 600;
86
+ }
81
87
  </style>