@rokkit/chart 1.0.0-next.147 → 1.0.0-next.149

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 (157) hide show
  1. package/dist/Plot/index.d.ts +4 -0
  2. package/dist/PlotState.svelte.d.ts +47 -0
  3. package/dist/crossfilter/createCrossFilter.svelte.d.ts +15 -0
  4. package/dist/geoms/lib/areas.d.ts +52 -0
  5. package/dist/geoms/lib/bars.d.ts +3 -0
  6. package/dist/index.d.ts +38 -1
  7. package/dist/lib/brewing/BoxBrewer.svelte.d.ts +10 -0
  8. package/dist/lib/brewing/CartesianBrewer.svelte.d.ts +8 -0
  9. package/dist/lib/brewing/PieBrewer.svelte.d.ts +8 -0
  10. package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +9 -0
  11. package/dist/lib/brewing/brewer.svelte.d.ts +145 -0
  12. package/dist/lib/brewing/colors.d.ts +17 -0
  13. package/dist/lib/brewing/marks/arcs.d.ts +17 -0
  14. package/dist/lib/brewing/marks/areas.d.ts +31 -0
  15. package/dist/lib/brewing/marks/bars.d.ts +1 -0
  16. package/dist/lib/brewing/marks/boxes.d.ts +24 -0
  17. package/dist/lib/brewing/marks/lines.d.ts +24 -0
  18. package/dist/lib/brewing/marks/points.d.ts +40 -0
  19. package/dist/lib/brewing/marks/violins.d.ts +20 -0
  20. package/dist/lib/brewing/patterns.d.ts +14 -0
  21. package/dist/lib/brewing/scales.d.ts +28 -0
  22. package/dist/lib/brewing/stats.d.ts +31 -0
  23. package/dist/lib/brewing/symbols.d.ts +7 -0
  24. package/dist/lib/plot/chartProps.d.ts +177 -0
  25. package/dist/lib/plot/crossfilter.d.ts +13 -0
  26. package/dist/lib/plot/facet.d.ts +24 -0
  27. package/dist/lib/plot/frames.d.ts +47 -0
  28. package/dist/lib/plot/helpers.d.ts +3 -0
  29. package/dist/lib/plot/preset.d.ts +29 -0
  30. package/dist/lib/plot/scales.d.ts +5 -0
  31. package/dist/lib/plot/stat.d.ts +32 -0
  32. package/dist/lib/plot/types.d.ts +89 -0
  33. package/dist/lib/scales.svelte.d.ts +1 -1
  34. package/dist/lib/swatch.d.ts +12 -0
  35. package/dist/lib/utils.d.ts +1 -0
  36. package/dist/lib/xscale.d.ts +11 -0
  37. package/dist/patterns/index.d.ts +4 -9
  38. package/dist/patterns/patterns.d.ts +72 -0
  39. package/dist/patterns/scale.d.ts +30 -0
  40. package/package.json +9 -3
  41. package/src/AnimatedPlot.svelte +214 -0
  42. package/src/Chart.svelte +101 -0
  43. package/src/FacetPlot/Panel.svelte +23 -0
  44. package/src/FacetPlot.svelte +90 -0
  45. package/src/Plot/Arc.svelte +29 -0
  46. package/src/Plot/Area.svelte +25 -0
  47. package/src/Plot/Axis.svelte +62 -84
  48. package/src/Plot/Grid.svelte +20 -58
  49. package/src/Plot/Legend.svelte +160 -120
  50. package/src/Plot/Line.svelte +27 -0
  51. package/src/Plot/Point.svelte +27 -0
  52. package/src/Plot/Timeline.svelte +95 -0
  53. package/src/Plot/Tooltip.svelte +81 -0
  54. package/src/Plot/index.js +4 -0
  55. package/src/Plot.svelte +189 -0
  56. package/src/PlotState.svelte.js +278 -0
  57. package/src/Sparkline.svelte +69 -0
  58. package/src/charts/AreaChart.svelte +25 -0
  59. package/src/charts/BarChart.svelte +26 -0
  60. package/src/charts/BoxPlot.svelte +21 -0
  61. package/src/charts/BubbleChart.svelte +23 -0
  62. package/src/charts/LineChart.svelte +26 -0
  63. package/src/charts/PieChart.svelte +25 -0
  64. package/src/charts/ScatterPlot.svelte +25 -0
  65. package/src/charts/ViolinPlot.svelte +21 -0
  66. package/src/crossfilter/CrossFilter.svelte +38 -0
  67. package/src/crossfilter/FilterBar.svelte +32 -0
  68. package/src/crossfilter/FilterSlider.svelte +79 -0
  69. package/src/crossfilter/createCrossFilter.svelte.js +113 -0
  70. package/src/elements/SymbolGrid.svelte +6 -7
  71. package/src/geoms/Arc.svelte +81 -0
  72. package/src/geoms/Area.svelte +50 -0
  73. package/src/geoms/Bar.svelte +142 -0
  74. package/src/geoms/Box.svelte +101 -0
  75. package/src/geoms/LabelPill.svelte +17 -0
  76. package/src/geoms/Line.svelte +100 -0
  77. package/src/geoms/Point.svelte +100 -0
  78. package/src/geoms/Violin.svelte +44 -0
  79. package/src/geoms/lib/areas.js +131 -0
  80. package/src/geoms/lib/bars.js +172 -0
  81. package/src/index.js +52 -3
  82. package/src/lib/brewing/BoxBrewer.svelte.js +56 -0
  83. package/src/lib/brewing/CartesianBrewer.svelte.js +16 -0
  84. package/src/lib/brewing/PieBrewer.svelte.js +14 -0
  85. package/src/lib/brewing/ViolinBrewer.svelte.js +55 -0
  86. package/src/lib/brewing/brewer.svelte.js +229 -0
  87. package/src/lib/brewing/colors.js +22 -0
  88. package/src/lib/brewing/marks/arcs.js +43 -0
  89. package/src/lib/brewing/marks/areas.js +59 -0
  90. package/src/lib/brewing/marks/bars.js +49 -0
  91. package/src/lib/brewing/marks/boxes.js +75 -0
  92. package/src/lib/brewing/marks/lines.js +48 -0
  93. package/src/lib/brewing/marks/points.js +57 -0
  94. package/src/lib/brewing/marks/violins.js +90 -0
  95. package/src/lib/brewing/patterns.js +31 -0
  96. package/src/lib/brewing/scales.js +51 -0
  97. package/src/lib/brewing/scales.svelte.js +2 -26
  98. package/src/lib/brewing/stats.js +62 -0
  99. package/src/lib/brewing/symbols.js +10 -0
  100. package/src/lib/plot/chartProps.js +76 -0
  101. package/src/lib/plot/crossfilter.js +16 -0
  102. package/src/lib/plot/facet.js +58 -0
  103. package/src/lib/plot/frames.js +80 -0
  104. package/src/lib/plot/helpers.js +14 -0
  105. package/src/lib/plot/preset.js +53 -0
  106. package/src/lib/plot/scales.js +56 -0
  107. package/src/lib/plot/stat.js +92 -0
  108. package/src/lib/plot/types.js +65 -0
  109. package/src/lib/scales.svelte.js +2 -26
  110. package/src/lib/swatch.js +13 -0
  111. package/src/lib/utils.js +9 -0
  112. package/src/lib/xscale.js +31 -0
  113. package/src/patterns/DefinePatterns.svelte +32 -0
  114. package/src/patterns/PatternDef.svelte +27 -0
  115. package/src/patterns/index.js +4 -14
  116. package/src/patterns/patterns.js +208 -0
  117. package/src/patterns/scale.js +87 -0
  118. package/src/spec/chart-spec.js +29 -0
  119. package/src/symbols/Shape.svelte +1 -1
  120. package/src/symbols/constants/index.js +1 -1
  121. package/dist/old_lib/index.d.ts +0 -4
  122. package/dist/old_lib/plots.d.ts +0 -3
  123. package/dist/old_lib/swatch.d.ts +0 -285
  124. package/dist/old_lib/utils.d.ts +0 -1
  125. package/dist/patterns/paths/constants.d.ts +0 -1
  126. package/dist/template/constants.d.ts +0 -43
  127. package/dist/template/shapes/index.d.ts +0 -4
  128. package/src/old_lib/index.js +0 -4
  129. package/src/old_lib/plots.js +0 -27
  130. package/src/old_lib/swatch.js +0 -16
  131. package/src/old_lib/utils.js +0 -8
  132. package/src/patterns/Brick.svelte +0 -15
  133. package/src/patterns/Circles.svelte +0 -18
  134. package/src/patterns/CrossHatch.svelte +0 -12
  135. package/src/patterns/CurvedWave.svelte +0 -7
  136. package/src/patterns/Dots.svelte +0 -20
  137. package/src/patterns/OutlineCircles.svelte +0 -13
  138. package/src/patterns/Tile.svelte +0 -16
  139. package/src/patterns/Triangles.svelte +0 -13
  140. package/src/patterns/Waves.svelte +0 -9
  141. package/src/patterns/paths/NamedPattern.svelte +0 -9
  142. package/src/patterns/paths/constants.js +0 -4
  143. package/src/template/Texture.svelte +0 -13
  144. package/src/template/constants.js +0 -43
  145. package/src/template/shapes/Circles.svelte +0 -15
  146. package/src/template/shapes/Lines.svelte +0 -16
  147. package/src/template/shapes/Path.svelte +0 -9
  148. package/src/template/shapes/Polygons.svelte +0 -15
  149. package/src/template/shapes/index.js +0 -4
  150. /package/dist/{old_lib → lib}/brewer.d.ts +0 -0
  151. /package/dist/{old_lib → lib}/chart.d.ts +0 -0
  152. /package/dist/{old_lib → lib}/grid.d.ts +0 -0
  153. /package/dist/{old_lib → lib}/ticks.d.ts +0 -0
  154. /package/src/{old_lib → lib}/brewer.js +0 -0
  155. /package/src/{old_lib → lib}/chart.js +0 -0
  156. /package/src/{old_lib → lib}/grid.js +0 -0
  157. /package/src/{old_lib → lib}/ticks.js +0 -0
@@ -0,0 +1,214 @@
1
+ <script>
2
+ import { onMount, onDestroy } from 'svelte'
3
+ import { extractFrames, completeFrames, computeStaticDomains } from './lib/plot/frames.js'
4
+ import { applyGeomStat } from './lib/plot/stat.js'
5
+ import Timeline from './Plot/Timeline.svelte'
6
+ import PlotChart from './Plot.svelte'
7
+
8
+ /**
9
+ * @type {{
10
+ * data: Object[],
11
+ * animate: { by: string, duration?: number, loop?: boolean },
12
+ * x?: string,
13
+ * y?: string,
14
+ * color?: string,
15
+ * geoms?: import('./lib/plot/types.js').GeomSpec[],
16
+ * helpers?: import('./lib/plot/types.js').PlotHelpers,
17
+ * width?: number,
18
+ * height?: number,
19
+ * mode?: 'light' | 'dark',
20
+ * grid?: boolean,
21
+ * legend?: boolean,
22
+ * children?: import('svelte').Snippet
23
+ * }}
24
+ */
25
+ let {
26
+ data = [],
27
+ animate,
28
+ x,
29
+ y,
30
+ color,
31
+ geoms = [],
32
+ helpers = {},
33
+ width = 600,
34
+ height = 400,
35
+ mode = 'light',
36
+ grid = true,
37
+ legend = false,
38
+ children
39
+ } = $props()
40
+
41
+ // Pre-aggregate and complete frames when any geom has a non-identity stat:
42
+ // 1. applyGeomStat: aggregate data by (x, color?, by) → one row per combination
43
+ // 2. completeFrames: alignBy(by) ensures all frame values appear for every (x, color?)
44
+ // group, filling missing rows with y=0 so bars animate smoothly
45
+ // Geoms are returned with stat: 'identity' so PlotChart renders the pre-aggregated values as-is.
46
+ const prepared = $derived.by(() => {
47
+ const firstNonIdentity = geoms.find((g) => g.stat && g.stat !== 'identity')
48
+ if (!firstNonIdentity) return { data, geoms }
49
+
50
+ const aggChannels = { y }
51
+ if (x) aggChannels.x = x
52
+ if (color) aggChannels.color = color
53
+ aggChannels.frame = animate.by // 'frame' is not a value channel, so it becomes a group-by
54
+
55
+ const aggregated = applyGeomStat(
56
+ data,
57
+ { stat: firstNonIdentity.stat, channels: aggChannels },
58
+ helpers
59
+ )
60
+ const completeData = completeFrames(aggregated, { x, y, color }, animate.by)
61
+
62
+ return { data: completeData, geoms: geoms.map((g) => ({ ...g, stat: 'identity' })) }
63
+ })
64
+
65
+ // Extract frames and compute stable domains from the full prepared dataset
66
+ const rawFrames = $derived(extractFrames(prepared.data, animate.by))
67
+ const frameKeys = $derived([...rawFrames.keys()])
68
+
69
+ const channels = $derived({ x, y, color })
70
+ const staticDomains = $derived(
71
+ x && y ? computeStaticDomains(prepared.data, channels) : { xDomain: undefined, yDomain: undefined }
72
+ )
73
+
74
+ // Playback state
75
+ let currentIndex = $state(0)
76
+ let playing = $state(false)
77
+ let speed = $state(1)
78
+
79
+ // Current frame data — already complete (all x/color combos present)
80
+ const currentFrameData = $derived.by(() => {
81
+ const key = frameKeys[currentIndex]
82
+ return rawFrames.get(key) ?? []
83
+ })
84
+
85
+ // Reduced motion preference
86
+ let prefersReducedMotion = $state(false)
87
+ onMount(() => {
88
+ if (typeof window.matchMedia !== 'function') return
89
+ const mq = window.matchMedia('(prefers-reduced-motion: reduce)')
90
+ prefersReducedMotion = mq.matches
91
+ const handler = (e) => { prefersReducedMotion = e.matches }
92
+ mq.addEventListener('change', handler)
93
+ return () => mq.removeEventListener('change', handler)
94
+ })
95
+
96
+ // Animation loop
97
+ const baseDuration = $derived(animate.duration ?? 800)
98
+ const msPerFrame = $derived(Math.round(baseDuration / speed))
99
+ let lastTime = 0
100
+ let rafId = 0
101
+
102
+ function tick(time) {
103
+ if (!playing) return
104
+ if (time - lastTime >= msPerFrame) {
105
+ lastTime = time
106
+ currentIndex = currentIndex + 1
107
+ if (currentIndex >= frameKeys.length) {
108
+ if (animate.loop ?? false) {
109
+ currentIndex = 0
110
+ } else {
111
+ playing = false
112
+ return
113
+ }
114
+ }
115
+ }
116
+ rafId = requestAnimationFrame(tick)
117
+ }
118
+
119
+ $effect(() => {
120
+ // Reading msPerFrame here makes it a tracked dependency — effect re-runs on speed changes,
121
+ // which resets lastTime=0 to re-anchor frame pacing.
122
+ const _ms = msPerFrame
123
+ if (playing && !prefersReducedMotion) {
124
+ lastTime = 0
125
+ rafId = requestAnimationFrame(tick)
126
+ } else {
127
+ cancelAnimationFrame(rafId)
128
+ }
129
+ return () => cancelAnimationFrame(rafId)
130
+ })
131
+
132
+ // Reduced motion: step frames on interval instead
133
+ let reducedInterval = 0
134
+ $effect(() => {
135
+ if (!playing || !prefersReducedMotion) {
136
+ clearInterval(reducedInterval)
137
+ return
138
+ }
139
+ reducedInterval = setInterval(() => {
140
+ currentIndex = currentIndex + 1
141
+ if (currentIndex >= frameKeys.length) {
142
+ if (animate.loop ?? false) currentIndex = 0
143
+ else { playing = false; clearInterval(reducedInterval) }
144
+ }
145
+ }, msPerFrame)
146
+ return () => clearInterval(reducedInterval)
147
+ })
148
+
149
+ onDestroy(() => {
150
+ cancelAnimationFrame(rafId)
151
+ clearInterval(reducedInterval)
152
+ })
153
+
154
+ $effect(() => {
155
+ const len = frameKeys.length
156
+ if (currentIndex >= len && len > 0) {
157
+ currentIndex = len - 1
158
+ playing = false
159
+ } else if (len === 0) {
160
+ currentIndex = 0
161
+ playing = false
162
+ }
163
+ })
164
+
165
+ function handlePlay() { playing = true }
166
+ function handlePause() { playing = false }
167
+ function handleScrub(index) {
168
+ playing = false
169
+ currentIndex = index
170
+ }
171
+ function handleSpeed(s) { speed = s }
172
+
173
+ // Build spec for the current frame, with static domain overrides
174
+ const frameSpec = $derived({
175
+ data: currentFrameData,
176
+ x, y, color,
177
+ geoms: prepared.geoms,
178
+ xDomain: staticDomains.xDomain,
179
+ yDomain: staticDomains.yDomain
180
+ })
181
+ </script>
182
+
183
+ <div data-plot-animated>
184
+ <PlotChart
185
+ spec={frameSpec}
186
+ {helpers}
187
+ {width}
188
+ {height}
189
+ {mode}
190
+ {grid}
191
+ {legend}
192
+ >
193
+ {@render children?.()}
194
+ </PlotChart>
195
+
196
+ <Timeline
197
+ {frameKeys}
198
+ {currentIndex}
199
+ {playing}
200
+ {speed}
201
+ onplay={handlePlay}
202
+ onpause={handlePause}
203
+ onscrub={handleScrub}
204
+ onspeed={handleSpeed}
205
+ />
206
+ </div>
207
+
208
+ <style>
209
+ [data-plot-animated] {
210
+ display: flex;
211
+ flex-direction: column;
212
+ width: 100%;
213
+ }
214
+ </style>
@@ -0,0 +1,101 @@
1
+ <script>
2
+ import { setContext } from 'svelte'
3
+ import { ChartBrewer } from './lib/brewing/brewer.svelte.js'
4
+
5
+ /**
6
+ * @type {{
7
+ * spec?: import('./spec/chart-spec.js').ChartSpec,
8
+ * data?: Object[],
9
+ * x?: string,
10
+ * y?: string,
11
+ * color?: string,
12
+ * pattern?: string,
13
+ * fill?: string,
14
+ * size?: string,
15
+ * label?: string,
16
+ * symbol?: string,
17
+ * width?: number,
18
+ * height?: number,
19
+ * mode?: 'light' | 'dark',
20
+ * children?: import('svelte').Snippet
21
+ * }}
22
+ */
23
+ let {
24
+ spec = undefined,
25
+ data = [],
26
+ x = undefined,
27
+ y = undefined,
28
+ color = undefined,
29
+ pattern = undefined,
30
+ fill = undefined,
31
+ size = undefined,
32
+ label = undefined,
33
+ symbol = undefined,
34
+ width = 600,
35
+ height = 400,
36
+ mode = 'light',
37
+ children
38
+ } = $props()
39
+
40
+ const brewer = new ChartBrewer()
41
+ setContext('chart-brewer', brewer)
42
+
43
+ $effect(() => {
44
+ if (spec) {
45
+ brewer.update({
46
+ data: spec.data,
47
+ channels: spec.channels,
48
+ width,
49
+ height,
50
+ mode,
51
+ layers: spec.layers
52
+ })
53
+ } else {
54
+ const channels = {}
55
+ if (x) channels.x = x
56
+ if (y) channels.y = y
57
+ if (color) channels.color = color
58
+ if (pattern) channels.pattern = pattern
59
+ if (fill) channels.fill = fill
60
+ if (size) channels.size = size
61
+ if (label) channels.label = label
62
+ if (symbol) channels.symbol = symbol
63
+ brewer.update({ data, channels, width, height, mode })
64
+ }
65
+ })
66
+
67
+ </script>
68
+
69
+ <div class="chart-container" data-chart-root>
70
+ <svg
71
+ {width}
72
+ {height}
73
+ viewBox="0 0 {width} {height}"
74
+ role="img"
75
+ aria-label="Chart visualization"
76
+ >
77
+ <g
78
+ class="chart-area"
79
+ data-chart-canvas
80
+ >
81
+ {@render children?.()}
82
+ </g>
83
+ </svg>
84
+ </div>
85
+
86
+ <style>
87
+ .chart-container {
88
+ position: relative;
89
+ width: 100%;
90
+ height: auto;
91
+ }
92
+
93
+ svg {
94
+ display: block;
95
+ overflow: visible;
96
+ }
97
+
98
+ .chart-area {
99
+ pointer-events: all;
100
+ }
101
+ </style>
@@ -0,0 +1,23 @@
1
+ <script>
2
+ import PlotChart from '../Plot.svelte'
3
+
4
+ let {
5
+ data, x, y, color, geoms = [], helpers = {},
6
+ width, height, mode, grid, legend,
7
+ xDomain, yDomain, colorDomain,
8
+ children
9
+ } = $props()
10
+
11
+ // Build spec with domain overrides so PlotState uses them
12
+ const spec = $derived({
13
+ data, x, y, color,
14
+ geoms,
15
+ xDomain,
16
+ yDomain,
17
+ colorDomain
18
+ })
19
+ </script>
20
+
21
+ <PlotChart {spec} {helpers} {width} {height} {mode} {grid} {legend}>
22
+ {@render children?.()}
23
+ </PlotChart>
@@ -0,0 +1,90 @@
1
+ <script>
2
+ import { splitByField, getFacetDomains } from './lib/plot/facet.js'
3
+ import { distinct } from './lib/brewing/colors.js'
4
+ import PlotPanel from './FacetPlot/Panel.svelte'
5
+
6
+ /**
7
+ * @type {{
8
+ * data: Object[],
9
+ * facet: { by: string, cols?: number, scales?: 'fixed'|'free'|'free_x'|'free_y' },
10
+ * x?: string,
11
+ * y?: string,
12
+ * color?: string,
13
+ * geoms?: import('./lib/plot/types.js').GeomSpec[],
14
+ * helpers?: import('./lib/plot/types.js').PlotHelpers,
15
+ * panelWidth?: number,
16
+ * panelHeight?: number,
17
+ * width?: number,
18
+ * height?: number,
19
+ * mode?: 'light' | 'dark',
20
+ * grid?: boolean,
21
+ * legend?: boolean,
22
+ * children?: import('svelte').Snippet
23
+ * }}
24
+ */
25
+ let {
26
+ data = [],
27
+ facet,
28
+ x,
29
+ y,
30
+ fill,
31
+ color,
32
+ geoms = [],
33
+ helpers = {},
34
+ panelWidth,
35
+ panelHeight,
36
+ width = 900,
37
+ height = 300,
38
+ mode = 'light',
39
+ grid = true,
40
+ legend = false,
41
+ children
42
+ } = $props()
43
+
44
+ // `fill` is accepted as an alias for `color` (bar/area semantics vs line/point)
45
+ const colorChannel = $derived(fill ?? color)
46
+
47
+ const panels = $derived(splitByField(data, facet.by))
48
+ const scales = $derived(facet.scales ?? 'fixed')
49
+ const domains = $derived(
50
+ x && y ? getFacetDomains(panels, { x, y }, scales) : new Map()
51
+ )
52
+
53
+ // Global color domain ensures the same value maps to the same color in every panel.
54
+ const colorDomain = $derived(colorChannel ? distinct(data, colorChannel) : undefined)
55
+
56
+ const cols = $derived(facet.cols ?? Math.min(panels.size, 3))
57
+ const pw = $derived(panelWidth ?? Math.floor(width / cols))
58
+ const ph = $derived(panelHeight ?? height)
59
+ </script>
60
+
61
+ <div data-facet-grid style:--facet-cols={cols}>
62
+ {#each [...panels.entries()] as [facetValue, panelData] (`${facetValue}`)}
63
+ <div data-facet-panel data-facet-value={facetValue}>
64
+ <div data-facet-title>{facetValue}</div>
65
+ <PlotPanel
66
+ data={panelData}
67
+ {x} {y} color={colorChannel}
68
+ {geoms} {helpers}
69
+ width={pw}
70
+ height={ph}
71
+ {mode} {grid}
72
+ legend={false}
73
+ xDomain={domains.get(facetValue)?.xDomain}
74
+ yDomain={domains.get(facetValue)?.yDomain}
75
+ {colorDomain}
76
+ >
77
+ <!-- Render caller-supplied geoms inside every panel (each gets its own PlotState context) -->
78
+ {@render children?.()}
79
+ </PlotPanel>
80
+ </div>
81
+ {/each}
82
+ </div>
83
+
84
+ <!-- Single shared legend outside the grid -->
85
+ {#if legend}
86
+ <div data-facet-legend>
87
+ <!-- Legend content rendered by first panel; simplified for now -->
88
+ </div>
89
+ {/if}
90
+
@@ -0,0 +1,29 @@
1
+ <script>
2
+ import { getContext } from 'svelte'
3
+
4
+ const brewer = getContext('chart-brewer')
5
+ </script>
6
+
7
+ {#if brewer && brewer.arcs && brewer.arcs.length > 0}
8
+ <g
9
+ class="chart-arcs"
10
+ data-plot-type="arc"
11
+ transform="translate({brewer.width / 2}, {brewer.height / 2})"
12
+ >
13
+ {#each brewer.arcs as arc (arc.key)}
14
+ <path
15
+ d={arc.d}
16
+ fill={arc.fill}
17
+ stroke={arc.stroke}
18
+ stroke-width="1"
19
+ data-plot-element="arc"
20
+ />
21
+ {/each}
22
+ </g>
23
+ {/if}
24
+
25
+ <style>
26
+ .chart-arcs {
27
+ pointer-events: none;
28
+ }
29
+ </style>
@@ -0,0 +1,25 @@
1
+ <script>
2
+ import { getContext } from 'svelte'
3
+
4
+ const brewer = getContext('chart-brewer')
5
+ </script>
6
+
7
+ {#if brewer && brewer.areas && brewer.areas.length > 0}
8
+ <g class="chart-areas" data-plot-type="area">
9
+ {#each brewer.areas as seg (seg.key ?? seg.d)}
10
+ <path
11
+ d={seg.d}
12
+ fill={seg.fill}
13
+ stroke={seg.stroke}
14
+ opacity="0.7"
15
+ data-plot-element="area"
16
+ />
17
+ {/each}
18
+ </g>
19
+ {/if}
20
+
21
+ <style>
22
+ .chart-areas {
23
+ pointer-events: none;
24
+ }
25
+ </style>
@@ -1,95 +1,73 @@
1
1
  <script>
2
- import { getContext } from 'svelte'
3
- import { createXAxis, createYAxis, createTickAttributes } from '../lib/brewing/axes.svelte.js'
2
+ import { getContext } from 'svelte'
4
3
 
5
- let {
6
- type = 'x',
7
- field = null,
8
- label = '',
9
- ticks = null,
10
- tickFormat = null,
11
- grid = false
12
- } = $props()
4
+ /** @type {'x' | 'y'} */
5
+ let { type = 'x', label = '' } = $props()
13
6
 
14
- // Get brewer from context
15
- const brewer = getContext('chart-brewer')
7
+ const state = getContext('plot-state')
16
8
 
17
- // Set field mappings in the brewer
18
- $effect(() => {
19
- if (field) {
20
- brewer.setFields({
21
- [type]: field
22
- })
9
+ const xTicks = $derived.by(() => {
10
+ const s = state.xScale
11
+ if (!s) return []
12
+ if (typeof s.bandwidth === 'function') {
13
+ return s.domain().map((val) => ({
14
+ value: val,
15
+ pos: (s(val) ?? 0) + s.bandwidth() / 2
16
+ }))
17
+ }
18
+ return s.ticks(6).map((val) => ({ value: val, pos: s(val) }))
19
+ })
23
20
 
24
- // Ensure scales are updated
25
- brewer.createScales()
26
- }
27
- })
21
+ const yTicks = $derived.by(() => {
22
+ const s = state.yScale
23
+ if (!s) return []
24
+ if (typeof s.bandwidth === 'function') {
25
+ return s.domain().map((val) => ({
26
+ value: val,
27
+ pos: (s(val) ?? 0) + s.bandwidth() / 2
28
+ }))
29
+ }
30
+ return s.ticks(6).map((val) => ({ value: val, pos: s(val) }))
31
+ })
28
32
 
29
- // Compute axis data whenever scales change
30
- let axisData = $derived(
31
- type === 'x'
32
- ? brewer.createXAxis({ tickCount: ticks, tickFormat, label })
33
- : brewer.createYAxis({ tickCount: ticks, tickFormat, label })
34
- )
33
+ const xTransform = $derived(`translate(0, ${state.xAxisY ?? state.innerHeight})`)
34
+ const yTransform = $derived(`translate(${state.yAxisX ?? 0}, 0)`)
35
35
  </script>
36
36
 
37
- <g class="axis {type}-axis" transform={axisData.transform} data-plot-axis={type}>
38
- {#if axisData.ticks.length > 0}
39
- <!-- Axis line -->
40
- <line
41
- data-plot-axis-line
42
- x1={type === 'x' ? 0 : 0}
43
- y1={type === 'x' ? 0 : 0}
44
- x2={type === 'x' ? brewer.getDimensions().innerWidth : 0}
45
- y2={type === 'x' ? 0 : brewer.getDimensions().innerHeight}
46
- stroke="currentColor"
47
- />
48
-
49
- <!-- Ticks -->
50
- {#each axisData.ticks as tick, index (index)}
51
- {@const attrs = createTickAttributes(tick, type)}
52
- <g {...attrs}>
53
- <line
54
- x1="0"
55
- y1="0"
56
- x2={type === 'x' ? 0 : -6}
57
- y2={type === 'x' ? 6 : 0}
58
- stroke="currentColor"
59
- />
60
- <text x={type === 'x' ? 0 : -9} y={type === 'x' ? 9 : 0} data-plot-tick-label>
61
- {tick.formattedValue}
62
- </text>
63
- </g>
64
- {/each}
65
-
66
- <!-- Axis label -->
67
- {#if label}
68
- <text
69
- class="axis-label {type}-axis-label"
70
- transform={axisData.labelTransform}
71
- text-anchor="middle"
72
- data-plot-axis-label
73
- >
74
- {label}
75
- </text>
76
- {/if}
77
- {/if}
78
- </g>
37
+ {#if type === 'x'}
38
+ <g class="axis x-axis" transform={xTransform} data-plot-axis="x">
39
+ <line x1="0" y1="0" x2={state.innerWidth} y2="0" data-plot-axis-line />
40
+ {#each xTicks as tick (tick.value)}
41
+ <g transform="translate({tick.pos}, 0)" data-plot-tick>
42
+ <line x1="0" y1="0" x2="0" y2="6" stroke="currentColor" />
43
+ <text x="0" y="9" text-anchor="middle" dominant-baseline="hanging" data-plot-tick-label>
44
+ {tick.value}
45
+ </text>
46
+ </g>
47
+ {/each}
48
+ {#if label}
49
+ <text x={state.innerWidth / 2} y="36" text-anchor="middle" class="axis-label" data-plot-axis-label>{label}</text>
50
+ {/if}
51
+ </g>
52
+ {:else}
53
+ <g class="axis y-axis" transform={yTransform} data-plot-axis="y">
54
+ <line x1="0" y1="0" x2="0" y2={state.innerHeight} data-plot-axis-line />
55
+ {#each yTicks as tick (tick.value)}
56
+ <g transform="translate(0, {tick.pos})" data-plot-tick>
57
+ <line x1="-6" y1="0" x2="0" y2="0" stroke="currentColor" />
58
+ <text x="-9" y="0" text-anchor="end" dominant-baseline="middle" data-plot-tick-label>
59
+ {tick.value}
60
+ </text>
61
+ </g>
62
+ {/each}
63
+ {#if label}
64
+ <text transform="rotate(-90)" x={-(state.innerHeight / 2)} y="-40" text-anchor="middle" class="axis-label" data-plot-axis-label>{label}</text>
65
+ {/if}
66
+ </g>
67
+ {/if}
79
68
 
80
69
  <style>
81
- .axis {
82
- font-size: 12px;
83
- }
84
-
85
- .axis-label {
86
- font-size: 14px;
87
- font-weight: 500;
88
- fill: currentColor;
89
- }
90
-
91
- [data-plot-tick-label] {
92
- font-size: 11px;
93
- fill: currentColor;
94
- }
70
+ .axis { font-size: 11px; fill: currentColor; stroke: currentColor; }
71
+ .axis-label { font-size: 13px; font-weight: 500; }
72
+ [data-plot-axis-line] { stroke: currentColor; }
95
73
  </style>