@rokkit/chart 1.1.18 → 1.2.0

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 (156) hide show
  1. package/dist/AnimatedPlot.svelte.d.ts +35 -0
  2. package/dist/Chart.svelte.d.ts +21 -0
  3. package/dist/ChartExporter.svelte.d.ts +22 -0
  4. package/dist/ChartLegend.svelte.d.ts +30 -0
  5. package/dist/ChartProvider.svelte.d.ts +10 -0
  6. package/dist/FacetPlot/Panel.svelte.d.ts +22 -0
  7. package/dist/FacetPlot.svelte.d.ts +32 -0
  8. package/dist/Plot/Arc.svelte.d.ts +18 -0
  9. package/dist/Plot/Area.svelte.d.ts +14 -0
  10. package/dist/Plot/Axis.svelte.d.ts +13 -0
  11. package/dist/Plot/Bar.svelte.d.ts +11 -0
  12. package/dist/Plot/Grid.svelte.d.ts +3 -0
  13. package/dist/Plot/Legend.svelte.d.ts +6 -0
  14. package/dist/Plot/Line.svelte.d.ts +13 -0
  15. package/dist/Plot/Point.svelte.d.ts +13 -0
  16. package/dist/Plot/Root.svelte.d.ts +21 -0
  17. package/dist/Plot/Timeline.svelte.d.ts +13 -0
  18. package/dist/Plot/Tooltip.svelte.d.ts +7 -0
  19. package/dist/Plot.svelte.d.ts +34 -0
  20. package/dist/PlotState.svelte.d.ts +0 -3
  21. package/dist/Sparkline.svelte.d.ts +16 -0
  22. package/dist/Symbol.svelte.d.ts +12 -0
  23. package/dist/Texture.svelte.d.ts +12 -0
  24. package/dist/charts/AreaChart.svelte.d.ts +25 -0
  25. package/dist/charts/BarChart.svelte.d.ts +26 -0
  26. package/dist/charts/BoxPlot.svelte.d.ts +18 -0
  27. package/dist/charts/BubbleChart.svelte.d.ts +23 -0
  28. package/dist/charts/LineChart.svelte.d.ts +26 -0
  29. package/dist/charts/PieChart.svelte.d.ts +19 -0
  30. package/dist/charts/ScatterPlot.svelte.d.ts +29 -0
  31. package/dist/charts/ViolinPlot.svelte.d.ts +18 -0
  32. package/dist/crossfilter/CrossFilter.svelte.d.ts +12 -0
  33. package/dist/crossfilter/FilterBar.svelte.d.ts +12 -0
  34. package/dist/crossfilter/FilterHistogram.svelte.d.ts +19 -0
  35. package/dist/crossfilter/FilterSlider.svelte.d.ts +15 -0
  36. package/dist/elements/Bar.svelte.d.ts +17 -0
  37. package/dist/elements/ColorRamp.svelte.d.ts +17 -0
  38. package/dist/elements/ContinuousLegend.svelte.d.ts +18 -0
  39. package/dist/elements/DefinePatterns.svelte.d.ts +21 -0
  40. package/dist/elements/DiscreteLegend.svelte.d.ts +17 -0
  41. package/dist/elements/Label.svelte.d.ts +11 -0
  42. package/dist/elements/SymbolGrid.svelte.d.ts +8 -0
  43. package/dist/examples/BarChartExample.svelte.d.ts +18 -0
  44. package/dist/geoms/Arc.svelte.d.ts +20 -0
  45. package/dist/geoms/Area.svelte.d.ts +16 -0
  46. package/dist/geoms/Bar.svelte.d.ts +23 -0
  47. package/dist/geoms/Box.svelte.d.ts +12 -0
  48. package/dist/geoms/Candlestick.svelte.d.ts +20 -0
  49. package/dist/geoms/Heatmap.svelte.d.ts +13 -0
  50. package/dist/geoms/Hexbin.svelte.d.ts +13 -0
  51. package/dist/geoms/LabelPill.svelte.d.ts +9 -0
  52. package/dist/geoms/Line.svelte.d.ts +24 -0
  53. package/dist/geoms/Point.svelte.d.ts +30 -0
  54. package/dist/geoms/Ribbon.svelte.d.ts +16 -0
  55. package/dist/geoms/Violin.svelte.d.ts +12 -0
  56. package/dist/geoms/Waterfall.svelte.d.ts +18 -0
  57. package/dist/geoms/lib/areas.d.ts +5 -3
  58. package/dist/lib/brewing/brewer.svelte.d.ts +24 -15
  59. package/dist/lib/brewing/marks/arcs.d.ts +16 -6
  60. package/dist/lib/brewing/marks/areas.d.ts +2 -2
  61. package/dist/lib/brewing/marks/boxes.d.ts +3 -3
  62. package/dist/lib/brewing/marks/lines.d.ts +6 -6
  63. package/dist/lib/brewing/marks/points.d.ts +13 -12
  64. package/dist/lib/brewing/marks/violins.d.ts +3 -3
  65. package/dist/lib/brewing/scales.d.ts +3 -3
  66. package/dist/lib/chart.d.ts +2 -2
  67. package/dist/lib/keyboard-nav.d.ts +1 -1
  68. package/dist/lib/plot/facet.d.ts +2 -2
  69. package/dist/lib/plot/frames.d.ts +9 -9
  70. package/dist/lib/plot/scales.d.ts +3 -3
  71. package/dist/lib/plot/types.d.ts +7 -4
  72. package/dist/lib/scales.svelte.d.ts +2 -2
  73. package/dist/lib/utils.d.ts +7 -1
  74. package/dist/lib/xscale.d.ts +1 -1
  75. package/dist/patterns/DefinePatterns.svelte.d.ts +7 -0
  76. package/dist/patterns/PatternDef.svelte.d.ts +11 -0
  77. package/dist/spec/chart-spec.d.ts +22 -0
  78. package/dist/symbols/RoundedSquare.svelte.d.ts +11 -0
  79. package/dist/symbols/Shape.svelte.d.ts +18 -0
  80. package/dist/symbols/constants/index.d.ts +1 -1
  81. package/dist/symbols/index.d.ts +2 -2
  82. package/dist/symbols/outline.svelte.d.ts +18 -0
  83. package/dist/symbols/solid.svelte.d.ts +18 -0
  84. package/package.json +22 -3
  85. package/src/AnimatedPlot.svelte +49 -43
  86. package/src/Chart.svelte +34 -22
  87. package/src/ChartExporter.svelte +19 -16
  88. package/src/ChartLegend.svelte +24 -20
  89. package/src/ChartProvider.svelte +10 -2
  90. package/src/FacetPlot/Panel.svelte +23 -3
  91. package/src/FacetPlot.svelte +29 -26
  92. package/src/Plot/Arc.svelte +3 -2
  93. package/src/Plot/Area.svelte +30 -8
  94. package/src/Plot/Axis.svelte +26 -45
  95. package/src/Plot/Bar.svelte +20 -5
  96. package/src/Plot/Grid.svelte +5 -4
  97. package/src/Plot/Legend.svelte +8 -4
  98. package/src/Plot/Line.svelte +24 -7
  99. package/src/Plot/Point.svelte +24 -6
  100. package/src/Plot/Root.svelte +17 -2
  101. package/src/Plot/Timeline.svelte +13 -14
  102. package/src/Plot/Tooltip.svelte +12 -6
  103. package/src/Plot.svelte +41 -32
  104. package/src/PlotState.svelte.js +6 -2
  105. package/src/Sparkline.svelte +22 -8
  106. package/src/Symbol.svelte +15 -5
  107. package/src/Texture.svelte +12 -2
  108. package/src/charts/AreaChart.svelte +26 -3
  109. package/src/charts/BarChart.svelte +27 -3
  110. package/src/charts/BoxPlot.svelte +19 -3
  111. package/src/charts/BubbleChart.svelte +24 -3
  112. package/src/charts/LineChart.svelte +27 -3
  113. package/src/charts/PieChart.svelte +20 -3
  114. package/src/charts/ScatterPlot.svelte +27 -3
  115. package/src/charts/ViolinPlot.svelte +19 -3
  116. package/src/crossfilter/CrossFilter.svelte +13 -10
  117. package/src/crossfilter/FilterBar.svelte +14 -3
  118. package/src/crossfilter/FilterHistogram.svelte +49 -24
  119. package/src/crossfilter/FilterSlider.svelte +16 -5
  120. package/src/elements/Bar.svelte +16 -2
  121. package/src/elements/ColorRamp.svelte +28 -4
  122. package/src/elements/ContinuousLegend.svelte +20 -3
  123. package/src/elements/DefinePatterns.svelte +23 -7
  124. package/src/elements/DiscreteLegend.svelte +18 -2
  125. package/src/elements/Label.svelte +11 -2
  126. package/src/elements/SymbolGrid.svelte +13 -4
  127. package/src/examples/BarChartExample.svelte +12 -15
  128. package/src/geoms/Arc.svelte +18 -5
  129. package/src/geoms/Area.svelte +38 -20
  130. package/src/geoms/Bar.svelte +40 -23
  131. package/src/geoms/Box.svelte +13 -4
  132. package/src/geoms/Candlestick.svelte +27 -7
  133. package/src/geoms/Heatmap.svelte +20 -9
  134. package/src/geoms/Hexbin.svelte +35 -13
  135. package/src/geoms/LabelPill.svelte +9 -3
  136. package/src/geoms/Line.svelte +27 -10
  137. package/src/geoms/Point.svelte +33 -13
  138. package/src/geoms/Ribbon.svelte +26 -8
  139. package/src/geoms/Violin.svelte +13 -4
  140. package/src/geoms/Waterfall.svelte +32 -12
  141. package/src/geoms/lib/areas.js +2 -2
  142. package/src/lib/brewing/marks/arcs.js +3 -2
  143. package/src/lib/brewing/marks/boxes.js +1 -1
  144. package/src/lib/brewing/marks/lines.js +2 -2
  145. package/src/lib/brewing/marks/points.js +3 -2
  146. package/src/lib/brewing/marks/violins.js +1 -1
  147. package/src/lib/plot/facet.js +1 -1
  148. package/src/lib/plot/frames.js +4 -4
  149. package/src/lib/plot/types.js +6 -3
  150. package/src/lib/utils.js +7 -1
  151. package/src/patterns/DefinePatterns.svelte +9 -4
  152. package/src/patterns/PatternDef.svelte +11 -3
  153. package/src/symbols/RoundedSquare.svelte +15 -4
  154. package/src/symbols/Shape.svelte +21 -3
  155. package/src/symbols/outline.svelte +2 -0
  156. package/src/symbols/solid.svelte +2 -0
@@ -1,21 +1,24 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { setContext, untrack } from 'svelte'
3
+ import type { Snippet } from 'svelte'
3
4
  import { createCrossFilter } from './createCrossFilter.svelte.js'
4
5
 
5
- /**
6
- * @type {{
7
- * crossfilter?: ReturnType<typeof createCrossFilter>,
8
- * mode?: 'dim' | 'hide',
9
- * filters?: import('./createCrossFilter.svelte.js').FilterState,
10
- * children?: import('svelte').Snippet
11
- * }}
12
- */
6
+ type CrossFilter = ReturnType<typeof createCrossFilter>
7
+ type FilterState = CrossFilter['filters']
8
+
9
+ type Props = {
10
+ crossfilter?: CrossFilter
11
+ mode?: 'dim' | 'hide'
12
+ filters?: FilterState
13
+ children?: Snippet
14
+ }
15
+
13
16
  let {
14
17
  crossfilter: externalCf = undefined,
15
18
  mode = 'dim',
16
19
  filters = $bindable(),
17
20
  children
18
- } = $props()
21
+ }: Props = $props()
19
22
 
20
23
  // Use an externally provided instance (spec/helpers API) or create one internally.
21
24
  // untrack() suppresses "captures initial value" warning — intentional: the cf
@@ -1,6 +1,17 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import PlotChart from '../Plot.svelte'
3
3
  import Bar from '../geoms/Bar.svelte'
4
+ import type { PlotSpec } from '../lib/plot/types.js'
5
+
6
+ type Props = {
7
+ data?: Record<string, unknown>[]
8
+ field?: string
9
+ valueField?: string
10
+ stat?: string
11
+ width?: number
12
+ height?: number
13
+ mode?: 'light' | 'dark'
14
+ }
4
15
 
5
16
  let {
6
17
  data = [],
@@ -10,9 +21,9 @@
10
21
  width = 300,
11
22
  height = 120,
12
23
  mode = 'light'
13
- } = $props()
24
+ }: Props = $props()
14
25
 
15
- const spec = $derived({
26
+ const spec = $derived<PlotSpec>({
16
27
  x: field,
17
28
  y: valueField
18
29
  })
@@ -1,15 +1,29 @@
1
- <script>
2
- // @ts-nocheck
1
+ <script lang="ts">
3
2
  import { getContext } from 'svelte'
4
- import { bin, extent, max } from 'd3-array'
3
+ import { bin, extent, max, type Bin } from 'd3-array'
5
4
  import { scaleLinear } from 'd3-scale'
6
5
  import { format } from 'd3-format'
6
+ import type { createCrossFilter } from './createCrossFilter.svelte.js'
7
+
8
+ type Row = Record<string, unknown>
9
+ type CrossFilter = ReturnType<typeof createCrossFilter>
7
10
 
8
11
  /**
9
12
  * Interactive histogram with brush selection for crossfilter range dimensions.
10
13
  * Renders bins as bars; mouse drag selects a range and calls cf.setRange().
11
14
  * Click without dragging clears the filter.
12
15
  */
16
+ type Props = {
17
+ data?: Row[]
18
+ field?: string
19
+ label?: string
20
+ bins?: number
21
+ width?: number
22
+ height?: number
23
+ color?: string
24
+ dimColor?: string
25
+ }
26
+
13
27
  let {
14
28
  data = [],
15
29
  field = '',
@@ -19,9 +33,9 @@
19
33
  height = 120,
20
34
  color = 'rgb(var(--color-primary, 249 115 22))',
21
35
  dimColor = 'rgb(var(--color-surface-z3, 203 213 225))'
22
- } = $props()
36
+ }: Props = $props()
23
37
 
24
- const cf = getContext('crossfilter')
38
+ const cf = getContext<CrossFilter | undefined>('crossfilter')
25
39
 
26
40
  const margin = { top: 8, right: 10, bottom: 22, left: 32 }
27
41
 
@@ -30,12 +44,16 @@
30
44
 
31
45
  // Compute histogram bins from data
32
46
  const binner = $derived(
33
- bin()
34
- .value((d) => d[field])
47
+ bin<Row, number>()
48
+ .value((d) => Number(d[field]))
35
49
  .thresholds(binCount)
36
50
  )
37
- const binned = $derived(data.length > 0 ? binner(data) : [])
38
- const domainExtent = $derived(data.length > 0 ? extent(data, (d) => d[field]) : [0, 1])
51
+ const binned = $derived<Bin<Row, number>[]>(data.length > 0 ? binner(data) : [])
52
+ const domainExtent = $derived.by<[number, number]>(() => {
53
+ if (data.length === 0) return [0, 1]
54
+ const [lo, hi] = extent(data, (d) => Number(d[field]))
55
+ return [lo ?? 0, hi ?? 1]
56
+ })
39
57
 
40
58
  // Scales
41
59
  const xScale = $derived(
@@ -53,12 +71,17 @@
53
71
  : scaleLinear().domain([0, 1]).range([innerHeight, 0])
54
72
  )
55
73
 
56
- // Active range from crossfilter
57
- const activeRange = $derived(cf?.filters?.get(field))
74
+ // Active range from crossfilter — the histogram only operates on continuous
75
+ // [min, max] dimensions, so ignore categorical Set filters.
76
+ const activeRange = $derived.by<[number, number] | null>(() => {
77
+ const f = cf?.filters?.get(field)
78
+ if (Array.isArray(f) && f.length === 2) return [f[0], f[1]]
79
+ return null
80
+ })
58
81
 
59
82
  // Brush state
60
- let brushStartPx = $state(null)
61
- let brushEndPx = $state(null)
83
+ let brushStartPx = $state<number | null>(null)
84
+ let brushEndPx = $state<number | null>(null)
62
85
  let brushing = $state(false)
63
86
 
64
87
  // Brush rect in pixel space
@@ -77,32 +100,34 @@
77
100
  return { x: lo, width: Math.max(1, hi - lo) }
78
101
  })
79
102
 
80
- function pixelToValue(px) {
103
+ function pixelToValue(px: number) {
81
104
  return xScale.invert(Math.max(0, Math.min(innerWidth, px)))
82
105
  }
83
106
 
84
- function getLocalX(e) {
107
+ function getLocalX(e: MouseEvent & { currentTarget: Element }) {
85
108
  const svgEl = e.currentTarget.closest('svg')
109
+ if (!svgEl) return 0
86
110
  const rect = svgEl.getBoundingClientRect()
87
111
  return e.clientX - rect.left - margin.left
88
112
  }
89
113
 
90
- function onMouseDown(e) {
114
+ function onMouseDown(e: MouseEvent & { currentTarget: Element }) {
91
115
  brushStartPx = getLocalX(e)
92
116
  brushEndPx = brushStartPx
93
117
  brushing = true
94
118
  }
95
119
 
96
- function onMouseMove(e) {
120
+ function onMouseMove(e: MouseEvent & { currentTarget: Element }) {
97
121
  if (!brushing) return
98
122
  brushEndPx = getLocalX(e)
99
123
  }
100
124
 
101
125
  function onMouseUp() {
102
- if (!brushing) return
126
+ if (!brushing || brushStartPx === null) return
103
127
  brushing = false
104
- const lo = Math.min(brushStartPx, brushEndPx)
105
- const hi = Math.max(brushStartPx, brushEndPx)
128
+ const end = brushEndPx ?? brushStartPx
129
+ const lo = Math.min(brushStartPx, end)
130
+ const hi = Math.max(brushStartPx, end)
106
131
  if (hi - lo < 3) {
107
132
  // Treat as click — clear filter
108
133
  cf?.clearFilter(field)
@@ -118,10 +143,10 @@
118
143
  }
119
144
 
120
145
  // Check if a bin is within active range
121
- function binInRange(b) {
146
+ function binInRange(b: Bin<Row, number>) {
122
147
  if (!activeRange) return true
123
148
  const [lo, hi] = activeRange
124
- return b.x1 > lo && b.x0 < hi
149
+ return (b.x1 ?? 0) > lo && (b.x0 ?? 0) < hi
125
150
  }
126
151
 
127
152
  // Y-axis tick values (2-3 ticks)
@@ -195,8 +220,8 @@
195
220
 
196
221
  <!-- Bars -->
197
222
  {#each binned as b (b.x0)}
198
- {@const bx = xScale(b.x0) + 1}
199
- {@const bw = Math.max(0, xScale(b.x1) - xScale(b.x0) - 1)}
223
+ {@const bx = xScale(b.x0 ?? 0) + 1}
224
+ {@const bw = Math.max(0, xScale(b.x1 ?? 0) - xScale(b.x0 ?? 0) - 1)}
200
225
  {@const by = yScale(b.length)}
201
226
  {@const bh = innerHeight - by}
202
227
  <rect
@@ -1,14 +1,25 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { getContext } from 'svelte'
3
+ import type { createCrossFilter } from './createCrossFilter.svelte.js'
4
+
5
+ type CrossFilter = ReturnType<typeof createCrossFilter>
3
6
 
4
7
  /**
5
8
  * Dual range slider for a continuous crossfilter dimension.
6
9
  * NOTE: Interim implementation using HTML range inputs.
7
10
  * The spec calls for a Plot+Point+brush architecture, deferred until brush geom is implemented.
8
11
  */
9
- let { field, min, max, step = 0.1, label = '' } = $props()
12
+ type Props = {
13
+ field: string
14
+ min?: number
15
+ max?: number
16
+ step?: number
17
+ label?: string
18
+ }
19
+
20
+ let { field, min, max, step = 0.1, label = '' }: Props = $props()
10
21
 
11
- const cf = getContext('crossfilter')
22
+ const cf = getContext<CrossFilter | undefined>('crossfilter')
12
23
 
13
24
  // Initialize from props; $effect keeps in sync when min/max change
14
25
  let low = $state(0)
@@ -19,12 +30,12 @@
19
30
  high = max ?? 100
20
31
  })
21
32
 
22
- function handleLow(e) {
33
+ function handleLow(e: Event & { currentTarget: HTMLInputElement }) {
23
34
  low = Math.min(Number(e.currentTarget.value), high)
24
35
  cf?.setRange(field, [low, high])
25
36
  }
26
37
 
27
- function handleHigh(e) {
38
+ function handleHigh(e: Event & { currentTarget: HTMLInputElement }) {
28
39
  high = Math.max(Number(e.currentTarget.value), low)
29
40
  cf?.setRange(field, [low, high])
30
41
  }
@@ -1,8 +1,22 @@
1
- <script>
1
+ <script lang="ts">
2
+ import type { Readable } from 'svelte/store'
2
3
  import { format } from 'd3-format'
3
4
  import { get } from 'svelte/store'
4
5
  import Label from './Label.svelte'
5
6
 
7
+ type Scales = { x: (value: number) => number }
8
+
9
+ type Props = {
10
+ rank: number
11
+ value: number
12
+ name: string
13
+ formatString?: string
14
+ scales: Readable<Scales>
15
+ height?: number
16
+ fill?: string
17
+ spaceBetween?: number
18
+ }
19
+
6
20
  let {
7
21
  rank,
8
22
  value,
@@ -12,7 +26,7 @@
12
26
  height = 60,
13
27
  fill,
14
28
  spaceBetween = 5
15
- } = $props()
29
+ }: Props = $props()
16
30
 
17
31
  const textHeight = 16
18
32
  const charWidth = 12
@@ -1,15 +1,39 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { scaleLinear } from 'd3-scale'
3
3
  import { id as uniqueId } from '@rokkit/core'
4
4
 
5
- let { x = 0, y = 0, textSize = 5, height = 10, width = 100, tickCount = 5, scale } = $props()
5
+ type ColorScale = {
6
+ (value: number): string
7
+ domain(): number[]
8
+ ticks(count?: number): number[]
9
+ }
10
+
11
+ type Props = {
12
+ x?: number
13
+ y?: number
14
+ textSize?: number
15
+ height?: number
16
+ width?: number
17
+ tickCount?: number
18
+ scale: ColorScale
19
+ }
20
+
21
+ let {
22
+ x = 0,
23
+ y = 0,
24
+ textSize = 5,
25
+ height = 10,
26
+ width = 100,
27
+ tickCount = 5,
28
+ scale
29
+ }: Props = $props()
6
30
 
7
31
  let scaleTicks = $derived(
8
- scaleLinear()
32
+ scaleLinear<number>()
9
33
  .range([x, x + width])
10
34
  .domain(scale.domain())
11
35
  )
12
- let scalePercent = $derived(scaleLinear().range([0, 100]).domain(scale.domain()))
36
+ let scalePercent = $derived(scaleLinear<number>().range([0, 100]).domain(scale.domain()))
13
37
  let ticks = $derived(
14
38
  scale.ticks.apply(scale, [tickCount]).map((d) => ({ x: scaleTicks(d), value: d }))
15
39
  )
@@ -1,6 +1,23 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { scaleLinear } from 'd3-scale'
3
3
 
4
+ type ColorScale = {
5
+ (value: number): string
6
+ domain(): number[]
7
+ ticks?(count?: number): number[]
8
+ }
9
+
10
+ type Props = {
11
+ x?: number
12
+ y?: number
13
+ textSize?: number
14
+ height?: number
15
+ width?: number
16
+ tickCount?: number
17
+ scale: ColorScale
18
+ id?: string
19
+ }
20
+
4
21
  let {
5
22
  x = 0,
6
23
  y = 0,
@@ -10,10 +27,10 @@
10
27
  tickCount = 5,
11
28
  scale,
12
29
  id = 'legend'
13
- } = $props()
30
+ }: Props = $props()
14
31
 
15
32
  let scaleTicks = $derived(
16
- scaleLinear()
33
+ scaleLinear<number>()
17
34
  .range([x, x + width])
18
35
  .domain(scale.domain())
19
36
  )
@@ -1,12 +1,28 @@
1
- <script>
1
+ <script lang="ts">
2
+ import type { Component } from 'svelte'
3
+ import type { SVGAttributes } from 'svelte/elements'
2
4
  import { uniq } from 'ramda'
3
5
 
4
- let {
5
- size = 10,
6
- patternUnits = 'userSpaceOnUse',
7
- /** @type {Array<import('./types').Pattern>} */
8
- patterns = []
9
- } = $props()
6
+ type PatternComponentProps = {
7
+ size?: number
8
+ fill?: string
9
+ stroke?: string
10
+ }
11
+
12
+ type Pattern = {
13
+ id: string
14
+ component: Component<PatternComponentProps>
15
+ fill?: string
16
+ stroke?: string
17
+ }
18
+
19
+ type Props = {
20
+ size?: number
21
+ patternUnits?: SVGAttributes<SVGPatternElement>['patternUnits']
22
+ patterns?: Pattern[]
23
+ }
24
+
25
+ let { size = 10, patternUnits = 'userSpaceOnUse', patterns = [] }: Props = $props()
10
26
 
11
27
  let names = $derived(uniq(patterns.map(({ id }) => id)))
12
28
  </script>
@@ -1,4 +1,20 @@
1
- <script>
1
+ <script lang="ts">
2
+ type ColorScale = {
3
+ (value: number): string
4
+ ticks(count?: number): number[]
5
+ }
6
+
7
+ type Props = {
8
+ x?: number
9
+ y?: number
10
+ textSize?: number
11
+ size?: number
12
+ space?: number
13
+ padding?: number
14
+ scale: ColorScale
15
+ tickCount?: number
16
+ }
17
+
2
18
  let {
3
19
  x = 0,
4
20
  y = 0,
@@ -8,7 +24,7 @@
8
24
  padding = 5,
9
25
  scale,
10
26
  tickCount = 10
11
- } = $props()
27
+ }: Props = $props()
12
28
 
13
29
  let sizeWithSpace = $derived(size + space)
14
30
  let ticks = $derived(scale.ticks.apply(scale, [tickCount]))
@@ -1,5 +1,14 @@
1
- <script>
2
- let { x, y, text, angle = 0, small = false, anchor = 'middle' } = $props()
1
+ <script lang="ts">
2
+ type Props = {
3
+ x: number
4
+ y: number
5
+ text: string
6
+ angle?: number
7
+ small?: boolean
8
+ anchor?: string
9
+ }
10
+
11
+ let { x, y, text, angle = 0, small = false, anchor = 'middle' }: Props = $props()
3
12
 
4
13
  let transform = $derived(`translate(${x},${y}) rotate(${angle})`)
5
14
  let validAnchor = $derived(['start', 'middle', 'end'].includes(anchor) ? anchor : 'middle')
@@ -1,11 +1,20 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { swatch } from '../lib/swatch'
3
3
  import { swatchGrid } from '../lib/grid'
4
4
  import Symbol from '../Symbol.svelte'
5
5
 
6
- let { base = 'teal', size = 4, shade = 600 } = $props()
6
+ type Props = {
7
+ base?: string
8
+ size?: number
9
+ shade?: number
10
+ }
11
+
12
+ let { base = 'teal', size = 4, shade = 600 }: Props = $props()
7
13
 
8
14
  let grid = $derived(swatchGrid(swatch.keys.symbol.length, size, 10))
15
+
16
+ const palette: Record<string, Record<string, string>> = swatch.palette
17
+ let color = $derived(palette[base]?.[shade])
9
18
  </script>
10
19
 
11
20
  <svg viewBox="0 0 {grid.width} {grid.height}">
@@ -15,8 +24,8 @@
15
24
  {y}
16
25
  size={r * 2}
17
26
  name={swatch.keys.symbol[index]}
18
- fill={swatch.palette[base][shade]}
19
- stroke={swatch.palette[base][shade]}
27
+ fill={color}
28
+ stroke={color}
20
29
  />
21
30
  {/each}
22
31
  </svg>
@@ -1,7 +1,9 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { Plot } from '../index.js'
3
3
  import { dataset } from '@rokkit/data'
4
4
 
5
+ type Row = Record<string, unknown>
6
+
5
7
  // Sample data
6
8
  const sampleData = [
7
9
  { model: 'Model A', name: 'Product 1', category: 'Electronics', count: 45 },
@@ -14,33 +16,28 @@
14
16
  ]
15
17
 
16
18
  // Use the dataset class to process the data
17
- const data = dataset(sampleData)
19
+ const data: Row[] = dataset(sampleData)
18
20
  .groupBy('category')
19
- .summarize('name', { count: (values) => values.length })
21
+ .summarize('name', { count: (values: unknown[]) => values.length })
20
22
  .rollup()
23
+ .select()
21
24
 
22
25
  // Chart dimensions
23
26
  const width = 600
24
27
  const height = 400
25
28
  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
29
  </script>
33
30
 
34
31
  <div class="example">
35
32
  <h2>Bar Chart Example</h2>
36
33
 
37
34
  <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" />
35
+ <Plot.Root {data} x="category" y="count" color="category" {width} {height} {margin}>
36
+ <Plot.Grid />
37
+ <Plot.Axis type="x" label="Product Category" />
38
+ <Plot.Axis type="y" label="Number of Products" />
39
+ <Plot.Bar {data} x="category" y="count" fill="category" />
40
+ <Plot.Legend labels={{ category: 'Categories' }} />
44
41
  </Plot.Root>
45
42
  </div>
46
43
 
@@ -1,12 +1,25 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { getContext, onMount, onDestroy } from 'svelte'
3
+ import type { PlotState } from '../PlotState.svelte.js'
3
4
  import { buildArcs } from '../lib/brewing/marks/arcs.js'
4
5
 
6
+ type Row = Record<string, unknown>
7
+
5
8
  /**
6
9
  * `fill` is the primary prop name; `color` is accepted as an alias for
7
10
  * 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
11
  */
12
+ type Props = {
13
+ theta?: string
14
+ fill?: string
15
+ color?: string
16
+ pattern?: string
17
+ stat?: string
18
+ labelFn?: (data: Row) => string
19
+ options?: { innerRadius?: number }
20
+ onselect?: (data: Row) => void
21
+ }
22
+
10
23
  let {
11
24
  theta,
12
25
  fill,
@@ -16,12 +29,12 @@
16
29
  stat = 'identity',
17
30
  options = {},
18
31
  onselect = undefined
19
- } = $props()
32
+ }: Props = $props()
20
33
 
21
34
  const fillField = $derived(fill ?? color)
22
35
 
23
- const plotState = getContext('plot-state')
24
- let id = $state(null)
36
+ const plotState = getContext<PlotState>('plot-state')
37
+ let id = $state<string | null>(null)
25
38
 
26
39
  onMount(() => {
27
40
  id = plotState.registerGeom({
@@ -1,11 +1,27 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { getContext, onMount, onDestroy } from 'svelte'
3
+ import type { PlotState } from '../PlotState.svelte.js'
3
4
  import { buildAreas, buildStackedAreas } from './lib/areas.js'
4
5
 
5
- let { x, y, color, pattern, stat = 'identity', options = {} } = $props()
6
+ type Options = {
7
+ stack?: boolean
8
+ curve?: 'linear' | 'smooth' | 'step'
9
+ opacity?: number
10
+ }
6
11
 
7
- const plotState = getContext('plot-state')
8
- let id = $state(null)
12
+ type Props = {
13
+ x?: string
14
+ y?: string
15
+ color?: string
16
+ pattern?: string
17
+ stat?: string
18
+ options?: Options
19
+ }
20
+
21
+ let { x, y, color, pattern, stat = 'identity', options = {} }: Props = $props()
22
+
23
+ const plotState = getContext<PlotState>('plot-state')
24
+ let id = $state<string | null>(null)
9
25
 
10
26
  onMount(() => {
11
27
  id = plotState.registerGeom({
@@ -35,7 +51,7 @@
35
51
  const patterns = $derived(plotState.patterns)
36
52
 
37
53
  const areas = $derived.by(() => {
38
- if (!data?.length || !xScale || !yScale) return []
54
+ if (!data?.length || !xScale || !yScale || !x || !y) return []
39
55
  const channels = { x, y, color, pattern }
40
56
  if (options.stack) {
41
57
  return buildStackedAreas(data, channels, xScale, yScale, colors, options.curve, patterns)
@@ -59,20 +75,22 @@
59
75
  {/if}
60
76
  {/each}
61
77
  <!-- Invisible hit circles for tooltip: one per data point -->
62
- {#each data as d, i (`hover::${i}`)}
63
- {@const px = typeof xScale?.bandwidth === 'function' ? (xScale(d[x]) ?? 0) + xScale.bandwidth() / 2 : (xScale?.(d[x]) ?? 0)}
64
- {@const py = yScale?.(d[y]) ?? 0}
65
- <circle
66
- cx={px}
67
- cy={py}
68
- r="8"
69
- fill="transparent"
70
- stroke="none"
71
- role="presentation"
72
- data-plot-element="area-hover"
73
- onmouseenter={() => plotState.setHovered(d)}
74
- onmouseleave={() => plotState.clearHovered()}
75
- />
76
- {/each}
78
+ {#if x && y}
79
+ {#each data as d, i (`hover::${i}`)}
80
+ {@const px = typeof xScale?.bandwidth === 'function' ? (xScale(d[x]) ?? 0) + xScale.bandwidth() / 2 : (xScale?.(d[x]) ?? 0)}
81
+ {@const py = yScale?.(d[y]) ?? 0}
82
+ <circle
83
+ cx={px}
84
+ cy={py}
85
+ r="8"
86
+ fill="transparent"
87
+ stroke="none"
88
+ role="presentation"
89
+ data-plot-element="area-hover"
90
+ onmouseenter={() => plotState.setHovered(d)}
91
+ onmouseleave={() => plotState.clearHovered()}
92
+ />
93
+ {/each}
94
+ {/if}
77
95
  </g>
78
96
  {/if}