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

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 (223) 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 +47 -0
  5. package/dist/crossfilter/createCrossFilter.svelte.d.ts +15 -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 +145 -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 +31 -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 +40 -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 +214 -0
  59. package/src/Chart.svelte +101 -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 +189 -0
  75. package/src/PlotState.svelte.js +278 -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 +113 -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 +101 -0
  104. package/src/geoms/LabelPill.svelte +17 -0
  105. package/src/geoms/Line.svelte +100 -0
  106. package/src/geoms/Point.svelte +100 -0
  107. package/src/geoms/Violin.svelte +44 -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 +16 -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 +229 -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 +62 -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 +213 -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/README.md +3 -0
  156. package/src/patterns/index.js +4 -0
  157. package/src/patterns/patterns.js +208 -0
  158. package/src/patterns/scale.js +87 -0
  159. package/src/spec/chart-spec.js +29 -0
  160. package/src/symbols/RoundedSquare.svelte +33 -0
  161. package/src/symbols/Shape.svelte +37 -0
  162. package/src/symbols/constants/index.js +4 -0
  163. package/src/symbols/index.js +9 -0
  164. package/src/symbols/outline.svelte +60 -0
  165. package/src/symbols/solid.svelte +60 -0
  166. package/src/chart/FacetGrid.svelte +0 -51
  167. package/src/chart/Grid.svelte +0 -34
  168. package/src/chart/Legend.svelte +0 -16
  169. package/src/chart/PatternDefs.svelte +0 -13
  170. package/src/chart/Swatch.svelte +0 -93
  171. package/src/chart/SwatchButton.svelte +0 -29
  172. package/src/chart/SwatchGrid.svelte +0 -55
  173. package/src/chart/Symbol.svelte +0 -37
  174. package/src/chart/Texture.svelte +0 -16
  175. package/src/chart/TexturedShape.svelte +0 -27
  176. package/src/chart/TimelapseChart.svelte +0 -97
  177. package/src/chart/Timer.svelte +0 -27
  178. package/src/chart.js +0 -9
  179. package/src/components/charts/Axis.svelte +0 -66
  180. package/src/components/charts/Chart.svelte +0 -35
  181. package/src/components/index.js +0 -23
  182. package/src/components/lib/axis.js +0 -0
  183. package/src/components/lib/chart.js +0 -187
  184. package/src/components/lib/color.js +0 -327
  185. package/src/components/lib/funnel.js +0 -204
  186. package/src/components/lib/index.js +0 -19
  187. package/src/components/lib/pattern.js +0 -190
  188. package/src/components/lib/rollup.js +0 -55
  189. package/src/components/lib/shape.js +0 -199
  190. package/src/components/lib/summary.js +0 -145
  191. package/src/components/lib/theme.js +0 -23
  192. package/src/components/lib/timer.js +0 -41
  193. package/src/components/lib/utils.js +0 -165
  194. package/src/components/plots/BarPlot.svelte +0 -36
  195. package/src/components/plots/BoxPlot.svelte +0 -54
  196. package/src/components/plots/ScatterPlot.svelte +0 -30
  197. package/src/components/store.js +0 -70
  198. package/src/constants.js +0 -66
  199. package/src/elements/PatternDefs.svelte +0 -13
  200. package/src/elements/PatternMask.svelte +0 -20
  201. package/src/elements/Symbol.svelte +0 -38
  202. package/src/elements/Tooltip.svelte +0 -23
  203. package/src/funnel.svelte +0 -35
  204. package/src/geom.js +0 -105
  205. package/src/lib/axis.js +0 -75
  206. package/src/lib/colors.js +0 -32
  207. package/src/lib/geom.js +0 -4
  208. package/src/lib/shapes.js +0 -144
  209. package/src/lib/timer.js +0 -44
  210. package/src/lookup.js +0 -29
  211. package/src/plots/BarPlot.svelte +0 -55
  212. package/src/plots/BoxPlot.svelte +0 -0
  213. package/src/plots/FunnelPlot.svelte +0 -33
  214. package/src/plots/HeatMap.svelte +0 -5
  215. package/src/plots/HeatMapCalendar.svelte +0 -129
  216. package/src/plots/LinePlot.svelte +0 -55
  217. package/src/plots/Plot.svelte +0 -25
  218. package/src/plots/RankBarPlot.svelte +0 -38
  219. package/src/plots/ScatterPlot.svelte +0 -20
  220. package/src/plots/ViolinPlot.svelte +0 -11
  221. package/src/plots/heatmap.js +0 -70
  222. package/src/plots/index.js +0 -10
  223. 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,113 @@
1
+ /**
2
+ * Creates a reactive cross-filter state object.
3
+ *
4
+ * Filter values follow the spec type:
5
+ * FilterState = Map<string, Set<unknown> | [number, number]>
6
+ * - categorical: raw Set of selected values
7
+ * - continuous: [min, max] tuple
8
+ *
9
+ * Exposes a `filters` getter so CrossFilter.svelte can bind to current state.
10
+ * Exposes a `version` counter that increments on every mutation, giving
11
+ * components a simple reactive signal to watch for filter changes.
12
+ *
13
+ * @returns {CrossFilter}
14
+ */
15
+ export function createCrossFilter() {
16
+ // Map<dimension, Set<unknown> | [number, number]>
17
+ const filters = $state(new Map())
18
+
19
+ // Simple counter incremented on every mutation. Components read cf.version
20
+ // inside $effect to reactively recompute when any filter changes.
21
+ let version = $state(0)
22
+
23
+ /**
24
+ * Returns true if any filter is active on this dimension.
25
+ * @param {string} dimension
26
+ */
27
+ function isFiltered(dimension) {
28
+ const f = filters.get(dimension)
29
+ if (!f) return false
30
+ if (f instanceof Set) return f.size > 0
31
+ return true // range: always active if present
32
+ }
33
+
34
+ /**
35
+ * Returns true if a value on this dimension is NOT in the active filter.
36
+ * Returns false when no filter is active on this dimension.
37
+ *
38
+ * @param {string} dimension
39
+ * @param {unknown} value
40
+ */
41
+ function isDimmed(dimension, value) {
42
+ const f = filters.get(dimension)
43
+ if (!f) return false
44
+ if (f instanceof Set) {
45
+ return !f.has(value)
46
+ }
47
+ // [min, max] range
48
+ const [lo, hi] = f
49
+ return Number(value) < lo || Number(value) > hi
50
+ }
51
+
52
+ /**
53
+ * Toggles a categorical value for a dimension.
54
+ * Adds when absent, removes when present.
55
+ * Clears the dimension filter when the last value is removed.
56
+ *
57
+ * @param {string} dimension
58
+ * @param {unknown} value
59
+ */
60
+ function toggleCategorical(dimension, value) {
61
+ const existing = filters.get(dimension)
62
+ const set = existing instanceof Set ? new Set(existing) : new Set()
63
+ if (set.has(value)) {
64
+ set.delete(value)
65
+ } else {
66
+ set.add(value)
67
+ }
68
+ if (set.size === 0) {
69
+ filters.delete(dimension)
70
+ } else {
71
+ filters.set(dimension, set)
72
+ }
73
+ version++
74
+ }
75
+
76
+ /**
77
+ * Sets a continuous range filter for a dimension.
78
+ * @param {string} dimension
79
+ * @param {[number, number]} range
80
+ */
81
+ function setRange(dimension, range) {
82
+ filters.set(dimension, [range[0], range[1]])
83
+ version++
84
+ }
85
+
86
+ /**
87
+ * Clears the filter for a single dimension.
88
+ * @param {string} dimension
89
+ */
90
+ function clearFilter(dimension) {
91
+ filters.delete(dimension)
92
+ version++
93
+ }
94
+
95
+ /** Clears all active filters. */
96
+ function clearAll() {
97
+ filters.clear()
98
+ version++
99
+ }
100
+
101
+ return {
102
+ /** @readonly — reactive Map of current filter state */
103
+ get filters() { return filters },
104
+ /** @readonly — increments on every mutation; read inside $effect to react to any filter change */
105
+ get version() { return version },
106
+ isFiltered,
107
+ isDimmed,
108
+ toggleCategorical,
109
+ setRange,
110
+ clearFilter,
111
+ clearAll
112
+ }
113
+ }
@@ -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}