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

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 (222) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +150 -46
  3. package/dist/Plot/index.d.ts +9 -0
  4. package/dist/PlotState.svelte.d.ts +49 -0
  5. package/dist/crossfilter/createCrossFilter.svelte.d.ts +13 -0
  6. package/dist/elements/index.d.ts +6 -0
  7. package/dist/geoms/lib/areas.d.ts +52 -0
  8. package/dist/geoms/lib/bars.d.ts +3 -0
  9. package/dist/index.d.ts +51 -0
  10. package/dist/lib/brewer.d.ts +9 -0
  11. package/dist/lib/brewing/BoxBrewer.svelte.d.ts +10 -0
  12. package/dist/lib/brewing/CartesianBrewer.svelte.d.ts +8 -0
  13. package/dist/lib/brewing/PieBrewer.svelte.d.ts +8 -0
  14. package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +9 -0
  15. package/dist/lib/brewing/axes.svelte.d.ts +66 -0
  16. package/dist/lib/brewing/bars.svelte.d.ts +56 -0
  17. package/dist/lib/brewing/brewer.svelte.d.ts +114 -0
  18. package/dist/lib/brewing/colors.d.ts +17 -0
  19. package/dist/lib/brewing/dimensions.svelte.d.ts +35 -0
  20. package/dist/lib/brewing/index.svelte.d.ts +118 -0
  21. package/dist/lib/brewing/legends.svelte.d.ts +48 -0
  22. package/dist/lib/brewing/marks/arcs.d.ts +17 -0
  23. package/dist/lib/brewing/marks/areas.d.ts +31 -0
  24. package/dist/lib/brewing/marks/bars.d.ts +1 -0
  25. package/dist/lib/brewing/marks/boxes.d.ts +24 -0
  26. package/dist/lib/brewing/marks/lines.d.ts +24 -0
  27. package/dist/lib/brewing/marks/points.d.ts +40 -0
  28. package/dist/lib/brewing/marks/violins.d.ts +20 -0
  29. package/dist/lib/brewing/patterns.d.ts +14 -0
  30. package/dist/lib/brewing/scales.d.ts +28 -0
  31. package/dist/lib/brewing/scales.svelte.d.ts +24 -0
  32. package/dist/lib/brewing/stats.d.ts +23 -0
  33. package/dist/lib/brewing/symbols.d.ts +7 -0
  34. package/dist/lib/brewing/types.d.ts +162 -0
  35. package/dist/lib/chart.d.ts +38 -0
  36. package/dist/lib/context.d.ts +13 -0
  37. package/dist/lib/grid.d.ts +72 -0
  38. package/dist/lib/plot/chartProps.d.ts +177 -0
  39. package/dist/lib/plot/crossfilter.d.ts +13 -0
  40. package/dist/lib/plot/facet.d.ts +24 -0
  41. package/dist/lib/plot/frames.d.ts +47 -0
  42. package/dist/lib/plot/helpers.d.ts +3 -0
  43. package/dist/lib/plot/preset.d.ts +29 -0
  44. package/dist/lib/plot/scales.d.ts +5 -0
  45. package/dist/lib/plot/stat.d.ts +32 -0
  46. package/dist/lib/plot/types.d.ts +89 -0
  47. package/dist/lib/scales.svelte.d.ts +35 -0
  48. package/dist/lib/swatch.d.ts +12 -0
  49. package/dist/lib/ticks.d.ts +36 -0
  50. package/dist/lib/utils.d.ts +61 -0
  51. package/dist/lib/xscale.d.ts +11 -0
  52. package/dist/patterns/index.d.ts +4 -0
  53. package/dist/patterns/patterns.d.ts +72 -0
  54. package/dist/patterns/scale.d.ts +30 -0
  55. package/dist/symbols/constants/index.d.ts +1 -0
  56. package/dist/symbols/index.d.ts +5 -0
  57. package/package.json +41 -45
  58. package/src/AnimatedPlot.svelte +215 -0
  59. package/src/Chart.svelte +98 -0
  60. package/src/FacetPlot/Panel.svelte +23 -0
  61. package/src/FacetPlot.svelte +90 -0
  62. package/src/Plot/Arc.svelte +29 -0
  63. package/src/Plot/Area.svelte +25 -0
  64. package/src/Plot/Axis.svelte +73 -0
  65. package/src/Plot/Bar.svelte +96 -0
  66. package/src/Plot/Grid.svelte +30 -0
  67. package/src/Plot/Legend.svelte +167 -0
  68. package/src/Plot/Line.svelte +27 -0
  69. package/src/Plot/Point.svelte +27 -0
  70. package/src/Plot/Root.svelte +107 -0
  71. package/src/Plot/Timeline.svelte +95 -0
  72. package/src/Plot/Tooltip.svelte +81 -0
  73. package/src/Plot/index.js +9 -0
  74. package/src/Plot.svelte +181 -0
  75. package/src/PlotState.svelte.js +277 -0
  76. package/src/Sparkline.svelte +69 -0
  77. package/src/Symbol.svelte +21 -0
  78. package/src/Texture.svelte +18 -0
  79. package/src/charts/AreaChart.svelte +25 -0
  80. package/src/charts/BarChart.svelte +26 -0
  81. package/src/charts/BoxPlot.svelte +21 -0
  82. package/src/charts/BubbleChart.svelte +23 -0
  83. package/src/charts/LineChart.svelte +26 -0
  84. package/src/charts/PieChart.svelte +25 -0
  85. package/src/charts/ScatterPlot.svelte +25 -0
  86. package/src/charts/ViolinPlot.svelte +21 -0
  87. package/src/crossfilter/CrossFilter.svelte +38 -0
  88. package/src/crossfilter/FilterBar.svelte +32 -0
  89. package/src/crossfilter/FilterSlider.svelte +79 -0
  90. package/src/crossfilter/createCrossFilter.svelte.js +120 -0
  91. package/src/elements/Bar.svelte +22 -24
  92. package/src/elements/ColorRamp.svelte +20 -22
  93. package/src/elements/ContinuousLegend.svelte +20 -17
  94. package/src/elements/DefinePatterns.svelte +24 -0
  95. package/src/elements/DiscreteLegend.svelte +15 -15
  96. package/src/elements/Label.svelte +4 -8
  97. package/src/elements/SymbolGrid.svelte +22 -0
  98. package/src/elements/index.js +6 -0
  99. package/src/examples/BarChartExample.svelte +81 -0
  100. package/src/geoms/Arc.svelte +81 -0
  101. package/src/geoms/Area.svelte +50 -0
  102. package/src/geoms/Bar.svelte +142 -0
  103. package/src/geoms/Box.svelte +103 -0
  104. package/src/geoms/LabelPill.svelte +17 -0
  105. package/src/geoms/Line.svelte +99 -0
  106. package/src/geoms/Point.svelte +105 -0
  107. package/src/geoms/Violin.svelte +46 -0
  108. package/src/geoms/lib/areas.js +131 -0
  109. package/src/geoms/lib/bars.js +172 -0
  110. package/src/index.js +67 -16
  111. package/src/lib/brewer.js +25 -0
  112. package/src/lib/brewing/BoxBrewer.svelte.js +56 -0
  113. package/src/lib/brewing/CartesianBrewer.svelte.js +17 -0
  114. package/src/lib/brewing/PieBrewer.svelte.js +14 -0
  115. package/src/lib/brewing/ViolinBrewer.svelte.js +55 -0
  116. package/src/lib/brewing/axes.svelte.js +270 -0
  117. package/src/lib/brewing/bars.svelte.js +201 -0
  118. package/src/lib/brewing/brewer.svelte.js +230 -0
  119. package/src/lib/brewing/colors.js +22 -0
  120. package/src/lib/brewing/dimensions.svelte.js +56 -0
  121. package/src/lib/brewing/index.svelte.js +205 -0
  122. package/src/lib/brewing/legends.svelte.js +137 -0
  123. package/src/lib/brewing/marks/arcs.js +43 -0
  124. package/src/lib/brewing/marks/areas.js +59 -0
  125. package/src/lib/brewing/marks/bars.js +49 -0
  126. package/src/lib/brewing/marks/boxes.js +75 -0
  127. package/src/lib/brewing/marks/lines.js +48 -0
  128. package/src/lib/brewing/marks/points.js +57 -0
  129. package/src/lib/brewing/marks/violins.js +90 -0
  130. package/src/lib/brewing/patterns.js +31 -0
  131. package/src/lib/brewing/scales.js +51 -0
  132. package/src/lib/brewing/scales.svelte.js +82 -0
  133. package/src/lib/brewing/stats.js +66 -0
  134. package/src/lib/brewing/symbols.js +10 -0
  135. package/src/lib/brewing/types.js +73 -0
  136. package/src/lib/chart.js +220 -0
  137. package/src/lib/context.js +131 -0
  138. package/src/lib/grid.js +85 -0
  139. package/src/lib/plot/chartProps.js +76 -0
  140. package/src/lib/plot/crossfilter.js +16 -0
  141. package/src/lib/plot/facet.js +58 -0
  142. package/src/lib/plot/frames.js +80 -0
  143. package/src/lib/plot/helpers.js +14 -0
  144. package/src/lib/plot/preset.js +53 -0
  145. package/src/lib/plot/scales.js +56 -0
  146. package/src/lib/plot/stat.js +92 -0
  147. package/src/lib/plot/types.js +65 -0
  148. package/src/lib/scales.svelte.js +151 -0
  149. package/src/lib/swatch.js +13 -0
  150. package/src/lib/ticks.js +46 -0
  151. package/src/lib/utils.js +111 -118
  152. package/src/lib/xscale.js +31 -0
  153. package/src/patterns/DefinePatterns.svelte +32 -0
  154. package/src/patterns/PatternDef.svelte +27 -0
  155. package/src/patterns/index.js +4 -0
  156. package/src/patterns/patterns.js +208 -0
  157. package/src/patterns/scale.js +87 -0
  158. package/src/spec/chart-spec.js +29 -0
  159. package/src/symbols/RoundedSquare.svelte +33 -0
  160. package/src/symbols/Shape.svelte +37 -0
  161. package/src/symbols/constants/index.js +4 -0
  162. package/src/symbols/index.js +9 -0
  163. package/src/symbols/outline.svelte +60 -0
  164. package/src/symbols/solid.svelte +60 -0
  165. package/src/chart/FacetGrid.svelte +0 -51
  166. package/src/chart/Grid.svelte +0 -34
  167. package/src/chart/Legend.svelte +0 -16
  168. package/src/chart/PatternDefs.svelte +0 -13
  169. package/src/chart/Swatch.svelte +0 -93
  170. package/src/chart/SwatchButton.svelte +0 -29
  171. package/src/chart/SwatchGrid.svelte +0 -55
  172. package/src/chart/Symbol.svelte +0 -37
  173. package/src/chart/Texture.svelte +0 -16
  174. package/src/chart/TexturedShape.svelte +0 -27
  175. package/src/chart/TimelapseChart.svelte +0 -97
  176. package/src/chart/Timer.svelte +0 -27
  177. package/src/chart.js +0 -9
  178. package/src/components/charts/Axis.svelte +0 -66
  179. package/src/components/charts/Chart.svelte +0 -35
  180. package/src/components/index.js +0 -23
  181. package/src/components/lib/axis.js +0 -0
  182. package/src/components/lib/chart.js +0 -187
  183. package/src/components/lib/color.js +0 -327
  184. package/src/components/lib/funnel.js +0 -204
  185. package/src/components/lib/index.js +0 -19
  186. package/src/components/lib/pattern.js +0 -190
  187. package/src/components/lib/rollup.js +0 -55
  188. package/src/components/lib/shape.js +0 -199
  189. package/src/components/lib/summary.js +0 -145
  190. package/src/components/lib/theme.js +0 -23
  191. package/src/components/lib/timer.js +0 -41
  192. package/src/components/lib/utils.js +0 -165
  193. package/src/components/plots/BarPlot.svelte +0 -36
  194. package/src/components/plots/BoxPlot.svelte +0 -54
  195. package/src/components/plots/ScatterPlot.svelte +0 -30
  196. package/src/components/store.js +0 -70
  197. package/src/constants.js +0 -66
  198. package/src/elements/PatternDefs.svelte +0 -13
  199. package/src/elements/PatternMask.svelte +0 -20
  200. package/src/elements/Symbol.svelte +0 -38
  201. package/src/elements/Tooltip.svelte +0 -23
  202. package/src/funnel.svelte +0 -35
  203. package/src/geom.js +0 -105
  204. package/src/lib/axis.js +0 -75
  205. package/src/lib/colors.js +0 -32
  206. package/src/lib/geom.js +0 -4
  207. package/src/lib/shapes.js +0 -144
  208. package/src/lib/timer.js +0 -44
  209. package/src/lookup.js +0 -29
  210. package/src/plots/BarPlot.svelte +0 -55
  211. package/src/plots/BoxPlot.svelte +0 -0
  212. package/src/plots/FunnelPlot.svelte +0 -33
  213. package/src/plots/HeatMap.svelte +0 -5
  214. package/src/plots/HeatMapCalendar.svelte +0 -129
  215. package/src/plots/LinePlot.svelte +0 -55
  216. package/src/plots/Plot.svelte +0 -25
  217. package/src/plots/RankBarPlot.svelte +0 -38
  218. package/src/plots/ScatterPlot.svelte +0 -20
  219. package/src/plots/ViolinPlot.svelte +0 -11
  220. package/src/plots/heatmap.js +0 -70
  221. package/src/plots/index.js +0 -10
  222. package/src/swatch.js +0 -11
@@ -0,0 +1,79 @@
1
+ <script>
2
+ import { getContext } from 'svelte'
3
+
4
+ /**
5
+ * Dual range slider for a continuous crossfilter dimension.
6
+ * NOTE: Interim implementation using HTML range inputs.
7
+ * The spec calls for a Plot+Point+brush architecture, deferred until brush geom is implemented.
8
+ */
9
+ let { field, min, max, step = 0.1, label = '' } = $props()
10
+
11
+ const cf = getContext('crossfilter')
12
+
13
+ // Initialize from props; $effect keeps in sync when min/max change
14
+ let low = $state(0)
15
+ let high = $state(100)
16
+
17
+ $effect(() => {
18
+ low = min ?? 0
19
+ high = max ?? 100
20
+ })
21
+
22
+ function handleLow(e) {
23
+ low = Math.min(Number(e.currentTarget.value), high)
24
+ cf?.setRange(field, [low, high])
25
+ }
26
+
27
+ function handleHigh(e) {
28
+ high = Math.max(Number(e.currentTarget.value), low)
29
+ cf?.setRange(field, [low, high])
30
+ }
31
+ </script>
32
+
33
+ <div data-filter-slider data-filter-field={field}>
34
+ {#if label}
35
+ <span data-filter-slider-label>{label}</span>
36
+ {/if}
37
+ <div data-filter-slider-inputs>
38
+ <input
39
+ type="range"
40
+ {min} {max} {step}
41
+ value={low}
42
+ oninput={handleLow}
43
+ aria-label="Minimum {label || field}"
44
+ data-filter-slider-low
45
+ />
46
+ <input
47
+ type="range"
48
+ {min} {max} {step}
49
+ value={high}
50
+ oninput={handleHigh}
51
+ aria-label="Maximum {label || field}"
52
+ data-filter-slider-high
53
+ />
54
+ </div>
55
+ <div data-filter-slider-display>
56
+ {low} – {high}
57
+ </div>
58
+ </div>
59
+
60
+ <style>
61
+ [data-filter-slider] {
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: 4px;
65
+ font-size: 12px;
66
+ }
67
+ [data-filter-slider-inputs] {
68
+ display: flex;
69
+ flex-direction: column;
70
+ gap: 2px;
71
+ }
72
+ [data-filter-slider-label] {
73
+ font-weight: 600;
74
+ }
75
+ [data-filter-slider-display] {
76
+ color: currentColor;
77
+ opacity: 0.7;
78
+ }
79
+ </style>
@@ -0,0 +1,120 @@
1
+ import { SvelteMap, SvelteSet } from 'svelte/reactivity'
2
+
3
+ /**
4
+ * Creates a reactive cross-filter state object.
5
+ *
6
+ * Filter values follow the spec type:
7
+ * FilterState = Map<string, Set<unknown> | [number, number]>
8
+ * - categorical: raw Set of selected values
9
+ * - continuous: [min, max] tuple
10
+ *
11
+ * Exposes a `filters` getter so CrossFilter.svelte can bind to current state.
12
+ * Exposes a `version` counter that increments on every mutation, giving
13
+ * components a simple reactive signal to watch for filter changes.
14
+ *
15
+ * @returns {CrossFilter}
16
+ */
17
+
18
+ function toggleCategoricalInMap(filters, dimension, value) {
19
+ const existing = filters.get(dimension)
20
+ const set = existing instanceof Set ? new SvelteSet(existing) : new SvelteSet()
21
+ if (set.has(value)) {
22
+ set.delete(value)
23
+ } else {
24
+ set.add(value)
25
+ }
26
+ if (set.size === 0) {
27
+ filters.delete(dimension)
28
+ } else {
29
+ filters.set(dimension, set)
30
+ }
31
+ }
32
+
33
+ export function createCrossFilter() {
34
+ // Map<dimension, Set<unknown> | [number, number]>
35
+ const filters = new SvelteMap()
36
+
37
+ // Simple counter incremented on every mutation. Components read cf.version
38
+ // inside $effect to reactively recompute when any filter changes.
39
+ let version = $state(0)
40
+
41
+ /**
42
+ * Returns true if any filter is active on this dimension.
43
+ * @param {string} dimension
44
+ */
45
+ function isFiltered(dimension) {
46
+ const f = filters.get(dimension)
47
+ if (!f) return false
48
+ if (f instanceof Set) return f.size > 0
49
+ return true // range: always active if present
50
+ }
51
+
52
+ /**
53
+ * Returns true if a value on this dimension is NOT in the active filter.
54
+ * Returns false when no filter is active on this dimension.
55
+ *
56
+ * @param {string} dimension
57
+ * @param {unknown} value
58
+ */
59
+ function isDimmed(dimension, value) {
60
+ const f = filters.get(dimension)
61
+ if (!f) return false
62
+ if (f instanceof Set) {
63
+ return !f.has(value)
64
+ }
65
+ // [min, max] range
66
+ const [lo, hi] = f
67
+ return Number(value) < lo || Number(value) > hi
68
+ }
69
+
70
+ /**
71
+ * Toggles a categorical value for a dimension.
72
+ * Adds when absent, removes when present.
73
+ * Clears the dimension filter when the last value is removed.
74
+ *
75
+ * @param {string} dimension
76
+ * @param {unknown} value
77
+ */
78
+ function toggleCategorical(dimension, value) {
79
+ toggleCategoricalInMap(filters, dimension, value)
80
+ version++
81
+ }
82
+
83
+ /**
84
+ * Sets a continuous range filter for a dimension.
85
+ * @param {string} dimension
86
+ * @param {[number, number]} range
87
+ */
88
+ function setRange(dimension, range) {
89
+ filters.set(dimension, [range[0], range[1]])
90
+ version++
91
+ }
92
+
93
+ /**
94
+ * Clears the filter for a single dimension.
95
+ * @param {string} dimension
96
+ */
97
+ function clearFilter(dimension) {
98
+ filters.delete(dimension)
99
+ version++
100
+ }
101
+
102
+ /** Clears all active filters. */
103
+ function clearAll() {
104
+ filters.clear()
105
+ version++
106
+ }
107
+
108
+ return {
109
+ /** @readonly — reactive Map of current filter state */
110
+ get filters() { return filters },
111
+ /** @readonly — increments on every mutation; read inside $effect to react to any filter change */
112
+ get version() { return version },
113
+ isFiltered,
114
+ isDimmed,
115
+ toggleCategorical,
116
+ setRange,
117
+ clearFilter,
118
+ clearAll
119
+ }
120
+ }
@@ -1,35 +1,33 @@
1
1
  <script>
2
2
  import { format } from 'd3-format'
3
+ import { get } from 'svelte/store'
3
4
  import Label from './Label.svelte'
4
5
 
5
- export let rank
6
- export let value
7
- export let name
8
- export let formatString = '.1%'
9
- export let scales
10
- export let height = 60
11
- export let fill
12
- export let spaceBetween = 5
6
+ let {
7
+ rank,
8
+ value,
9
+ name,
10
+ formatString = '.1%',
11
+ scales,
12
+ height = 60,
13
+ fill,
14
+ spaceBetween = 5
15
+ } = $props()
13
16
 
14
17
  const textHeight = 16
15
18
  const charWidth = 12
16
- $: y = rank * (height + spaceBetween)
17
- $: width = $scales.x(value)
19
+ let y = $derived(rank * (height + spaceBetween))
20
+ let width = $derived(get(scales).x(value))
18
21
 
19
- $: textWidth = name.length * charWidth
20
- $: textOffset = width <= textWidth ? width + charWidth : width
21
- $: textAnchor = textOffset > width ? 'start' : 'end'
22
+ let textWidth = $derived(name.length * charWidth)
23
+ let textOffset = $derived(width <= textWidth ? width + charWidth : width)
24
+ let textAnchor = $derived(textOffset > width ? 'start' : 'end')
22
25
 
23
- $: formattedValue = format(formatString)(value)
26
+ let formattedValue = $derived(format(formatString)(value))
27
+ let xOrigin = $derived(get(scales).x(0))
24
28
  </script>
25
29
 
26
- <rect x={$scales.x(0)} {y} {width} {height} {fill} opacity={0.5} />
27
- <rect x={$scales.x(0)} {y} width={5} {height} {fill} />
28
- <Label x={width} y={y + textHeight + 8} anchor={textAnchor} label={name} />
29
- <Label
30
- x={width}
31
- y={y + height - 14}
32
- anchor={textAnchor}
33
- label={formattedValue}
34
- small
35
- />
30
+ <rect x={xOrigin} {y} {width} {height} {fill} opacity={0.5} />
31
+ <rect x={xOrigin} {y} width={5} {height} {fill} />
32
+ <Label x={width} y={y + textHeight + 8} anchor={textAnchor} text={name} />
33
+ <Label x={width} y={y + height - 14} anchor={textAnchor} text={formattedValue} small />
@@ -1,39 +1,37 @@
1
1
  <script>
2
2
  import { scaleLinear } from 'd3-scale'
3
- import { uniqueId } from '../lib/utils'
3
+ import { id as uniqueId } from '@rokkit/core'
4
4
 
5
- export let x = 0
6
- export let y = 0
7
- export let textSize = 5
8
- export let height = 10
9
- export let width = 100
10
- export let tickCount = 5
11
- export let scale
5
+ let { x = 0, y = 0, textSize = 5, height = 10, width = 100, tickCount = 5, scale } = $props()
12
6
 
13
- $: scaleTicks = scaleLinear()
14
- .range([x, x + width])
15
- .domain(scale.domain())
16
- $: scalePercent = scaleLinear().range([0, 100]).domain(scale.domain())
17
- $: ticks = scale.ticks
18
- .apply(scale, [tickCount])
19
- .map((d) => ({ x: scaleTicks(d), value: d }))
7
+ let scaleTicks = $derived(
8
+ scaleLinear()
9
+ .range([x, x + width])
10
+ .domain(scale.domain())
11
+ )
12
+ let scalePercent = $derived(scaleLinear().range([0, 100]).domain(scale.domain()))
13
+ let ticks = $derived(
14
+ scale.ticks.apply(scale, [tickCount]).map((d) => ({ x: scaleTicks(d), value: d }))
15
+ )
20
16
 
21
- $: colors = ticks.map(({ value }) => ({
22
- color: scale(value),
23
- offset: `${scalePercent(value)}%`,
24
- }))
25
- $: id = uniqueId('legend-')
17
+ let colors = $derived(
18
+ ticks.map(({ value }) => ({
19
+ color: scale(value),
20
+ offset: `${scalePercent(value)}%`
21
+ }))
22
+ )
23
+ let id = $state(uniqueId('legend-'))
26
24
  </script>
27
25
 
28
26
  <defs>
29
27
  <linearGradient {id}>
30
- {#each colors as { color, offset }}
28
+ {#each colors as { color, offset }, index (index)}
31
29
  <stop stop-color={color} {offset} />
32
30
  {/each}
33
31
  </linearGradient>
34
32
  </defs>
35
33
  <rect {x} y={y + height} {width} {height} fill="url(#{id})" />
36
- {#each ticks as { x, value }}
34
+ {#each ticks as { x, value }, index (index)}
37
35
  <line x1={x} y1={y + (2 * height) / 3} x2={x} y2={y + height * 2} />
38
36
  <text {x} y={y + height / 2} font-size={textSize}>{value}</text>
39
37
  {/each}
@@ -1,24 +1,27 @@
1
1
  <script>
2
2
  import { scaleLinear } from 'd3-scale'
3
- import { uniqueId } from '../components/lib/utils'
4
3
 
5
- export let x = 0
6
- export let y = 0
7
- export let textSize = 5
8
- export let height = 10
9
- export let width = 100
10
- export let tickCount = 5
11
- export let scale
4
+ let {
5
+ x = 0,
6
+ y = 0,
7
+ textSize = 5,
8
+ height = 10,
9
+ width = 100,
10
+ tickCount = 5,
11
+ scale,
12
+ id = 'legend'
13
+ } = $props()
12
14
 
13
- $: scaleTicks = scaleLinear()
14
- .range([x, x + 100])
15
- .domain(scale.domain())
16
- $: ticks = scale.ticks
17
- .apply(scale, [tickCount])
18
- .map((d) => ({ x: scaleTicks(d), label: d }))
15
+ let scaleTicks = $derived(
16
+ scaleLinear()
17
+ .range([x, x + 100])
18
+ .domain(scale.domain())
19
+ )
20
+ let ticks = $derived(
21
+ scale.ticks.apply(scale, [tickCount]).map((d) => ({ x: scaleTicks(d), label: d }))
22
+ )
19
23
 
20
- $: colors = scale.range()
21
- $: id = uniqueId('legend-')
24
+ let colors = $derived(scale.range())
22
25
  </script>
23
26
 
24
27
  <defs>
@@ -28,7 +31,7 @@
28
31
  </linearGradient>
29
32
  </defs>
30
33
  <rect {x} y={y + height} {width} {height} fill="url(#{id})" />
31
- {#each ticks as { x, label }}
34
+ {#each ticks as { x, label }, index (index)}
32
35
  <line x1={x} y1={y + (2 * height) / 3} x2={x} y2={y + height * 2} />
33
36
  <text {x} y={y + height / 2} font-size={textSize}>{label}</text>
34
37
  {/each}
@@ -0,0 +1,24 @@
1
+ <script>
2
+ import { uniq } from 'ramda'
3
+
4
+ let {
5
+ size = 10,
6
+ patternUnits = 'userSpaceOnUse',
7
+ /** @type {Array<import('./types').Pattern>} */
8
+ patterns = []
9
+ } = $props()
10
+
11
+ let names = $derived(uniq(patterns.map(({ id }) => id)))
12
+ </script>
13
+
14
+ {#if names.length < patterns.length}
15
+ <error> Patterns should be an array and should have unique names for each pattern </error>
16
+ {:else if patterns.length > 0}
17
+ <defs>
18
+ {#each patterns as { id, component: Component, fill, stroke }, index (index)}
19
+ <pattern {id} {patternUnits} width={size} height={size}>
20
+ <Component {size} {fill} {stroke} />
21
+ </pattern>
22
+ {/each}
23
+ </defs>
24
+ {/if}
@@ -1,22 +1,22 @@
1
1
  <script>
2
- export let x = 0
3
- export let y = 0
4
- export let textSize = 5
5
- export let size = 10
6
- export let space = 2
7
- export let padding = 5
8
- export let scale
9
- export let tickCount
2
+ let {
3
+ x = 0,
4
+ y = 0,
5
+ textSize = 5,
6
+ size = 10,
7
+ space = 2,
8
+ padding = 5,
9
+ scale,
10
+ tickCount = 10
11
+ } = $props()
10
12
 
11
- $: sizeWithSpace = size + space
12
- $: ticks = scale.ticks.apply(scale, [tickCount])
13
+ let sizeWithSpace = $derived(size + space)
14
+ let ticks = $derived(scale.ticks.apply(scale, [tickCount]))
13
15
  </script>
14
16
 
15
- {#each ticks as tick, i}
16
- <text
17
- x={x + padding + i * sizeWithSpace + size / 2}
18
- y={y + size / 2}
19
- font-size={textSize}>{tick}</text
17
+ {#each ticks as tick, i (i)}
18
+ <text x={x + padding + i * sizeWithSpace + size / 2} y={y + size / 2} font-size={textSize}
19
+ >{tick}</text
20
20
  >
21
21
  <rect
22
22
  x={x + padding + i * sizeWithSpace}
@@ -1,12 +1,8 @@
1
1
  <script>
2
- export let small = false
3
- export let label
4
- export let angle = 0
5
- export let anchor = 'middle'
6
- export let x
7
- export let y
2
+ let { x, y, text, angle = 0, small = false, anchor = 'middle' } = $props()
8
3
 
9
- $: transform = `translate(${x},${y}) rotate(${angle})`
4
+ let transform = $derived(`translate(${x},${y}) rotate(${angle})`)
5
+ let validAnchor = $derived(['start', 'middle', 'end'].includes(anchor) ? anchor : 'middle')
10
6
  </script>
11
7
 
12
- <text class="label" class:small text-anchor={anchor} {transform}>{label}</text>
8
+ <text class="label" class:small text-anchor={validAnchor} {transform}>{text}</text>
@@ -0,0 +1,22 @@
1
+ <script>
2
+ import { swatch } from '../lib/swatch'
3
+ import { swatchGrid } from '../lib/grid'
4
+ import Symbol from '../Symbol.svelte'
5
+
6
+ let { base = 'teal', size = 4, shade = 600 } = $props()
7
+
8
+ let grid = $derived(swatchGrid(swatch.keys.symbol.length, size, 10))
9
+ </script>
10
+
11
+ <svg viewBox="0 0 {grid.width} {grid.height}">
12
+ {#each grid.data as { x, y, r }, index (index)}
13
+ <Symbol
14
+ {x}
15
+ {y}
16
+ size={r * 2}
17
+ name={swatch.keys.symbol[index]}
18
+ fill={swatch.palette[base][shade]}
19
+ stroke={swatch.palette[base][shade]}
20
+ />
21
+ {/each}
22
+ </svg>
@@ -0,0 +1,6 @@
1
+ export { default as Bar } from './Bar.svelte'
2
+ export { default as ColorRamp } from './ColorRamp.svelte'
3
+ export { default as ContinuousLegend } from './ContinuousLegend.svelte'
4
+ export { default as Label } from './Label.svelte'
5
+ export { default as DefinePatterns } from './DefinePatterns.svelte'
6
+ export { default as SymbolGrid } from './SymbolGrid.svelte'
@@ -0,0 +1,81 @@
1
+ <script>
2
+ import { Plot } from '../index.js'
3
+ import { dataset } from '@rokkit/data'
4
+
5
+ // Sample data
6
+ const sampleData = [
7
+ { model: 'Model A', name: 'Product 1', category: 'Electronics', count: 45 },
8
+ { model: 'Model B', name: 'Product 2', category: 'Clothing', count: 32 },
9
+ { model: 'Model C', name: 'Product 3', category: 'Electronics', count: 62 },
10
+ { model: 'Model D', name: 'Product 4', category: 'Home', count: 28 },
11
+ { model: 'Model E', name: 'Product 5', category: 'Electronics', count: 53 },
12
+ { model: 'Model F', name: 'Product 6', category: 'Clothing', count: 24 },
13
+ { model: 'Model G', name: 'Product 7', category: 'Home', count: 35 }
14
+ ]
15
+
16
+ // Use the dataset class to process the data
17
+ const data = dataset(sampleData)
18
+ .groupBy('category')
19
+ .summarize('name', { count: (values) => values.length })
20
+ .rollup()
21
+
22
+ // Chart dimensions
23
+ const width = 600
24
+ const height = 400
25
+ const margin = { top: 20, right: 100, bottom: 60, left: 60 }
26
+
27
+ // Click handler for bars
28
+ function handleBarClick(item) {
29
+ console.log('Bar clicked:', item)
30
+ alert(`Clicked on ${item.category} with count ${item.count}`)
31
+ }
32
+ </script>
33
+
34
+ <div class="example">
35
+ <h2>Bar Chart Example</h2>
36
+
37
+ <div class="chart-wrapper">
38
+ <Plot.Root {data} {width} {height} {margin} fill="category">
39
+ <Plot.Grid direction="y" lineStyle="dashed" />
40
+ <Plot.Axis type="x" field="category" label="Product Category" />
41
+ <Plot.Axis type="y" field="count" label="Number of Products" />
42
+ <Plot.Bar x="category" y="count" fill="category" onClick={handleBarClick} />
43
+ <Plot.Legend title="Categories" />
44
+ </Plot.Root>
45
+ </div>
46
+
47
+ <div class="code-example">
48
+ <h3>Example Code:</h3>
49
+ <pre>
50
+ <!-- &lt;script&gt;
51
+ import { Plot } from '@rokkit/chart';
52
+ import { dataset } from '@rokkit/data';
53
+
54
+ // Sample data
55
+ const sampleData = [
56
+ { model: 'Model A', name: 'Product 1', category: 'Electronics', count: 45 },
57
+ { model: 'Model B', name: 'Product 2', category: 'Clothing', count: 32 },
58
+ { model: 'Model C', name: 'Product 3', category: 'Electronics', count: 62 },
59
+ { model: 'Model D', name: 'Product 4', category: 'Home', count: 28 },
60
+ { model: 'Model E', name: 'Product 5', category: 'Electronics', count: 53 },
61
+ { model: 'Model F', name: 'Product 6', category: 'Clothing', count: 24 },
62
+ { model: 'Model G', name: 'Product 7', category: 'Home', count: 35 },
63
+ ];
64
+
65
+ // Use the dataset class to process the data
66
+ const data = dataset(sampleData)
67
+ .groupBy('category')
68
+ .summarize('name', { count: values => values.length })
69
+ .rollup();
70
+ &lt;/script&gt;
71
+
72
+ &lt;Plot.Root {data} width={600} height={400} margin={{ top: 20, right: 100, bottom: 60, left: 60 }} fill="category"&gt;
73
+ &lt;Plot.Grid direction="y" lineStyle="dashed" /&gt;
74
+ &lt;Plot.Axis type="x" field="category" label="Product Category" /&gt;
75
+ &lt;Plot.Axis type="y" field="count" label="Number of Products" /&gt;
76
+ &lt;Plot.Bar x="category" y="count" fill="category" onClick={handleBarClick} /&gt;
77
+ &lt;Plot.Legend title="Categories" /&gt;
78
+ &lt;/Plot.Root&gt; -->
79
+ </pre>
80
+ </div>
81
+ </div>
@@ -0,0 +1,81 @@
1
+ <script>
2
+ import { getContext, onMount, onDestroy } from 'svelte'
3
+ import { buildArcs } from '../lib/brewing/marks/arcs.js'
4
+
5
+ /**
6
+ * `fill` is the primary prop name; `color` is accepted as an alias for
7
+ * spec-driven usage (Plot.svelte passes `color` to all geoms generically).
8
+ * @type {{ theta?: string, fill?: string, color?: string, pattern?: string, stat?: string, labelFn?: (data: Record<string, unknown>) => string, options?: { innerRadius?: number } }}
9
+ */
10
+ let { theta, fill, color, pattern, labelFn = undefined, stat = 'identity', options = {} } = $props()
11
+
12
+ const fillField = $derived(fill ?? color)
13
+
14
+ const plotState = getContext('plot-state')
15
+ let id = $state(null)
16
+
17
+ onMount(() => {
18
+ id = plotState.registerGeom({ type: 'arc', channels: { color: fillField, y: theta, pattern }, stat, options })
19
+ })
20
+ onDestroy(() => { if (id) plotState.unregisterGeom(id) })
21
+
22
+ $effect(() => {
23
+ if (id) plotState.updateGeom(id, { channels: { color: fillField, y: theta, pattern }, stat })
24
+ })
25
+
26
+ const data = $derived(id ? plotState.geomData(id) : [])
27
+ const colors = $derived(plotState.colors)
28
+ const patterns = $derived(plotState.patterns)
29
+ const w = $derived(plotState.innerWidth)
30
+ const h = $derived(plotState.innerHeight)
31
+
32
+ const arcs = $derived.by(() => {
33
+ if (!data?.length) return []
34
+ // Guard: skip until data catches up after a fill-field change.
35
+ // When fillField changes, the $effect updates the geom asynchronously, but
36
+ // this derived runs first with stale data whose rows don't have the new
37
+ // field — causing all keys to be undefined (duplicate key error).
38
+ if (fillField && !(fillField in data[0])) return []
39
+ const innerRadius = (options.innerRadius ?? 0) * Math.min(w, h) / 2
40
+ return buildArcs(data, { color: fillField, y: theta, pattern }, colors, w, h, { innerRadius }, patterns)
41
+ })
42
+ </script>
43
+
44
+ {#if arcs.length > 0}
45
+ <g
46
+ data-plot-geom="arc"
47
+ transform="translate({w / 2}, {h / 2})"
48
+ >
49
+ {#each arcs as arc (arc.key)}
50
+ <path
51
+ d={arc.d}
52
+ fill={arc.fill}
53
+ stroke={arc.stroke}
54
+ stroke-width="1"
55
+ role="presentation"
56
+ data-plot-element="arc"
57
+ onmouseenter={() => plotState.setHovered({ ...arc.data, '%': `${arc.pct}%` })}
58
+ onmouseleave={() => plotState.clearHovered()}
59
+ />
60
+ {#if arc.patternId}
61
+ <path d={arc.d} fill="url(#{arc.patternId})" stroke={arc.stroke} stroke-width="1" pointer-events="none" data-plot-element="arc" />
62
+ {/if}
63
+ {#if arc.pct >= 5}
64
+ {@const labelText = labelFn ? String(labelFn(arc.data) ?? '') : `${arc.pct}%`}
65
+ {#if labelText}
66
+ {@const lw = Math.max(36, labelText.length * 7 + 12)}
67
+ <g transform="translate({arc.centroid[0]},{arc.centroid[1]})" pointer-events="none" data-plot-element="arc-label">
68
+ <rect x={-lw / 2} y="-9" width={lw} height="18" rx="4" fill="white" fill-opacity="0.82" />
69
+ <text
70
+ text-anchor="middle"
71
+ dominant-baseline="central"
72
+ font-size="11"
73
+ font-weight="600"
74
+ fill={arc.stroke}
75
+ >{labelText}</text>
76
+ </g>
77
+ {/if}
78
+ {/if}
79
+ {/each}
80
+ </g>
81
+ {/if}