@rokkit/chart 1.1.18 → 1.1.19

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,9 +1,34 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { getContext, onMount, onDestroy } from 'svelte'
3
+ import type { PlotState } from '../PlotState.svelte.js'
4
+ import type { createCrossFilter } from '../crossfilter/createCrossFilter.svelte.js'
3
5
  import { buildGroupedBars, buildStackedBars, buildHorizontalBars } from './lib/bars.js'
4
6
  import { keyboardNav } from '../lib/keyboard-nav.js'
5
7
  import LabelPill from './LabelPill.svelte'
6
8
 
9
+ type Row = Record<string, unknown>
10
+ type CrossFilter = ReturnType<typeof createCrossFilter>
11
+ type Options = {
12
+ stack?: boolean
13
+ orientation?: 'horizontal' | 'vertical'
14
+ labelInside?: boolean
15
+ labelOffset?: number
16
+ }
17
+
18
+ type Props = {
19
+ x?: string
20
+ y?: string
21
+ color?: string
22
+ fill?: string
23
+ pattern?: string
24
+ label?: boolean | string | ((data: Row) => unknown)
25
+ stat?: string
26
+ options?: Options
27
+ filterable?: boolean
28
+ onselect?: (data: Row) => void
29
+ keyboard?: boolean
30
+ }
31
+
7
32
  let {
8
33
  x,
9
34
  y,
@@ -16,19 +41,14 @@
16
41
  filterable = false,
17
42
  onselect = undefined,
18
43
  keyboard = false
19
- } = $props()
44
+ }: Props = $props()
20
45
 
21
46
  // `fill` is accepted as an alias for `color` (consistent with Arc.svelte)
22
47
  const colorChannel = $derived(fillProp ?? color)
23
48
 
24
- /**
25
- * @param {Record<string, unknown>} data
26
- * @param {string} defaultField
27
- * @returns {string | null}
28
- */
29
- function resolveLabel(data, defaultField) {
49
+ function resolveLabel(data: Row, defaultField: string | undefined): string | null {
30
50
  if (!label) return null
31
- if (label === true) return String(data[defaultField] ?? '')
51
+ if (label === true) return String((defaultField ? data[defaultField] : undefined) ?? '')
32
52
  if (typeof label === 'function') return String(label(data) ?? '')
33
53
  if (typeof label === 'string') return String(data[label] ?? '')
34
54
  return null
@@ -36,10 +56,8 @@
36
56
 
37
57
  /**
38
58
  * Pick white or dark text based on perceived luminance of a hex fill color.
39
- * @param {string | undefined} hex
40
- * @returns {string}
41
59
  */
42
- function contrastColor(hex) {
60
+ function contrastColor(hex: string | undefined): string {
43
61
  if (!hex || !hex.startsWith('#') || hex.length < 7) return 'white'
44
62
  const r = parseInt(hex.slice(1, 3), 16) / 255
45
63
  const g = parseInt(hex.slice(3, 5), 16) / 255
@@ -47,9 +65,9 @@
47
65
  return 0.299 * r + 0.587 * g + 0.114 * b > 0.55 ? '#333' : 'white'
48
66
  }
49
67
 
50
- const plotState = getContext('plot-state')
51
- const cf = getContext('crossfilter')
52
- let id = $state(null)
68
+ const plotState = getContext<PlotState>('plot-state')
69
+ const cf = getContext<CrossFilter | undefined>('crossfilter')
70
+ let id = $state<string | null>(null)
53
71
 
54
72
  onMount(() => {
55
73
  id = plotState.registerGeom({
@@ -92,8 +110,7 @@
92
110
  return buildGroupedBars(data, channels, xScale, yScale, colors, innerHeight, patterns)
93
111
  })
94
112
 
95
- /** @type {Record<string, boolean>} */
96
- let dimmedByKey = $state({})
113
+ let dimmedByKey = $state<Record<string, boolean>>({})
97
114
 
98
115
  $effect(() => {
99
116
  if (!cf) {
@@ -104,7 +121,7 @@
104
121
  // Reading it here establishes a reactive dependency so the effect re-runs
105
122
  // whenever any filter changes — including changes from sibling FilterBars.
106
123
  void cf.version
107
- const next = /** @type {Record<string, boolean>} */ ({})
124
+ const next: Record<string, boolean> = {}
108
125
  for (const bar of bars) {
109
126
  const dimmedByX = x ? cf.isDimmed(x, bar.data[x]) : false
110
127
  const dimmedByY = y ? cf.isDimmed(y, bar.data[y]) : false
@@ -113,7 +130,7 @@
113
130
  dimmedByKey = next
114
131
  })
115
132
 
116
- function handleBarClick(barX) {
133
+ function handleBarClick(barX: unknown) {
117
134
  if (!filterable || !x || !cf) return
118
135
  cf.toggleCategorical(x, barX)
119
136
  }
@@ -132,8 +149,8 @@
132
149
  stroke={bar.stroke ?? 'none'}
133
150
  stroke-width={bar.stroke ? 0.5 : 0}
134
151
  data-plot-element="bar"
135
- data-plot-value={bar.data[y]}
136
- data-plot-category={bar.data[x]}
152
+ data-plot-value={bar.data[y ?? '']}
153
+ data-plot-category={bar.data[x ?? '']}
137
154
  data-dimmed={dimmedByKey[bar.key] ? true : undefined}
138
155
  style:cursor={filterable || onselect ? 'pointer' : undefined}
139
156
  onclick={filterable && x ? () => { handleBarClick(bar.data[x]); onselect?.(bar.data) } : onselect ? () => onselect(bar.data) : undefined}
@@ -143,11 +160,11 @@
143
160
  role={filterable || onselect || keyboard ? 'button' : 'graphics-symbol'}
144
161
  tabindex={filterable || onselect || keyboard ? 0 : undefined}
145
162
  use:keyboardNav={keyboard}
146
- aria-label="{bar.data[x]}: {bar.data[y]}"
163
+ aria-label="{bar.data[x ?? '']}: {bar.data[y ?? '']}"
147
164
  onmouseenter={() => plotState.setHovered(bar.data)}
148
165
  onmouseleave={() => plotState.clearHovered()}
149
166
  >
150
- <title>{bar.data[x]}: {bar.data[y]}</title>
167
+ <title>{bar.data[x ?? '']}: {bar.data[y ?? '']}</title>
151
168
  </rect>
152
169
  {#if bar.patternId}
153
170
  <rect
@@ -1,11 +1,20 @@
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 { buildBoxes } from '../lib/brewing/marks/boxes.js'
4
5
 
5
- let { x, y, fill, stat = 'boxplot', options = {} } = $props()
6
+ type Props = {
7
+ x?: string
8
+ y?: string
9
+ fill?: string
10
+ stat?: string
11
+ options?: { opacity?: number }
12
+ }
6
13
 
7
- const plotState = getContext('plot-state')
8
- let id = $state(null)
14
+ let { x, y, fill, stat = 'boxplot', options = {} }: Props = $props()
15
+
16
+ const plotState = getContext<PlotState>('plot-state')
17
+ let id = $state<string | null>(null)
9
18
 
10
19
  // fill ?? x drives the colors map for both box interior and whisker strokes
11
20
  const fillChannel = $derived(fill ?? x)
@@ -1,5 +1,25 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { getContext, onMount, onDestroy } from 'svelte'
3
+ import type { PlotState } from '../PlotState.svelte.js'
4
+
5
+ type Options = {
6
+ open?: string
7
+ high?: string
8
+ low?: string
9
+ close?: string
10
+ upColor?: string
11
+ downColor?: string
12
+ wickWidth?: number
13
+ }
14
+
15
+ type Props = {
16
+ x?: string
17
+ y?: string
18
+ color?: string
19
+ fill?: string
20
+ stat?: string
21
+ options?: Options
22
+ }
3
23
 
4
24
  let {
5
25
  x,
@@ -8,7 +28,7 @@
8
28
  fill: fillProp = undefined,
9
29
  stat = 'identity',
10
30
  options = {}
11
- } = $props()
31
+ }: Props = $props()
12
32
 
13
33
  const open = $derived(options.open ?? 'open')
14
34
  const high = $derived(options.high ?? 'high')
@@ -18,8 +38,8 @@
18
38
  const downColor = $derived(options.downColor ?? '#ef4444')
19
39
  const wickWidth = $derived(options.wickWidth ?? 1)
20
40
 
21
- const plotState = getContext('plot-state')
22
- let id = $state(null)
41
+ const plotState = getContext<PlotState>('plot-state')
42
+ let id = $state<string | null>(null)
23
43
 
24
44
  onMount(() => {
25
45
  // Register with y pointing to 'high' so the y scale covers the full range
@@ -52,7 +72,7 @@
52
72
  const bodyOffset = (bw - bodyWidth) / 2
53
73
 
54
74
  return data.map((d, i) => {
55
- const xPos = (xScale(d[x]) ?? 0) + bodyOffset
75
+ const xPos = (xScale(d[x ?? '']) ?? 0) + bodyOffset
56
76
  const openVal = Number(d[open])
57
77
  const closeVal = Number(d[close])
58
78
  const highVal = Number(d[high])
@@ -62,7 +82,7 @@
62
82
  const bodyBottom = yScale(isUp ? openVal : closeVal)
63
83
 
64
84
  return {
65
- key: `${d[x]}-${i}`,
85
+ key: `${d[x ?? '']}-${i}`,
66
86
  // Body
67
87
  bodyX: xPos,
68
88
  bodyY: bodyTop,
@@ -104,7 +124,7 @@
104
124
  onmouseenter={() => plotState.setHovered(c.data)}
105
125
  onmouseleave={() => plotState.clearHovered()}
106
126
  >
107
- <title>{c.data[x]}: O={c.data[open]} H={c.data[high]} L={c.data[low]} C={c.data[close]}</title>
127
+ <title>{c.data[x ?? '']}: O={c.data[open]} H={c.data[high]} L={c.data[low]} C={c.data[close]}</title>
108
128
  </rect>
109
129
  {/each}
110
130
  </g>
@@ -1,5 +1,15 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { getContext, onMount, onDestroy } from 'svelte'
3
+ import type { PlotState } from '../PlotState.svelte.js'
4
+
5
+ type Props = {
6
+ x?: string
7
+ y?: string
8
+ color?: string
9
+ fill?: string
10
+ stat?: string
11
+ options?: { rounded?: number }
12
+ }
3
13
 
4
14
  let {
5
15
  x,
@@ -8,12 +18,12 @@
8
18
  fill: fillProp,
9
19
  stat = 'identity',
10
20
  options = {}
11
- } = $props()
21
+ }: Props = $props()
12
22
 
13
23
  const colorChannel = $derived(fillProp ?? color)
14
24
 
15
- const plotState = getContext('plot-state')
16
- let id = $state(null)
25
+ const plotState = getContext<PlotState>('plot-state')
26
+ let id = $state<string | null>(null)
17
27
 
18
28
  onMount(() => {
19
29
  id = plotState.registerGeom({
@@ -47,15 +57,16 @@
47
57
  const bwY = typeof yScale.bandwidth === 'function' ? yScale.bandwidth() : 0
48
58
 
49
59
  return data.map((d, i) => {
50
- const xVal = d[x]
51
- const yVal = d[y]
60
+ const xVal = d[x ?? '']
61
+ const yVal = d[y ?? '']
52
62
  const colorVal = colorChannel ? d[colorChannel] : null
53
63
  let cellFill = '#ccc'
54
64
 
55
65
  if (continuousColor) {
56
66
  cellFill = continuousColor.scale(Number(colorVal))
57
- } else if (categoricalColors?.has(colorVal)) {
58
- cellFill = categoricalColors.get(colorVal).fill
67
+ } else {
68
+ const entry = categoricalColors?.get(colorVal)
69
+ if (entry) cellFill = entry.fill
59
70
  }
60
71
 
61
72
  return {
@@ -86,7 +97,7 @@
86
97
  onmouseenter={() => plotState.setHovered(cell.data)}
87
98
  onmouseleave={() => plotState.clearHovered()}
88
99
  >
89
- <title>{cell.data[x]}, {cell.data[y]}: {colorChannel ? cell.data[colorChannel] : ''}</title>
100
+ <title>{cell.data[x ?? '']}, {cell.data[y ?? '']}: {colorChannel ? cell.data[colorChannel] : ''}</title>
90
101
  </rect>
91
102
  {/each}
92
103
  </g>
@@ -1,5 +1,19 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { getContext, onMount, onDestroy } from 'svelte'
3
+ import type { PlotState } from '../PlotState.svelte.js'
4
+
5
+ type Row = Record<string, unknown>
6
+ type Scale = (value: unknown) => number | undefined
7
+ type Bin = { cx: number; cy: number; count: number; points: Row[] }
8
+
9
+ type Props = {
10
+ x?: string
11
+ y?: string
12
+ color?: string
13
+ fill?: string
14
+ stat?: string
15
+ options?: { radius?: number }
16
+ }
3
17
 
4
18
  let {
5
19
  x,
@@ -8,12 +22,12 @@
8
22
  fill: fillProp = undefined,
9
23
  stat = 'identity',
10
24
  options = {}
11
- } = $props()
25
+ }: Props = $props()
12
26
 
13
27
  const radius = $derived(options.radius ?? 20)
14
28
 
15
- const plotState = getContext('plot-state')
16
- let id = $state(null)
29
+ const plotState = getContext<PlotState>('plot-state')
30
+ let id = $state<string | null>(null)
17
31
 
18
32
  onMount(() => {
19
33
  id = plotState.registerGeom({
@@ -41,15 +55,22 @@
41
55
 
42
56
  // Hex binning: group data points into hexagonal cells
43
57
  // eslint-disable-next-line max-params, complexity
44
- function hexBin(data, xField, yField, xScale, yScale, r) {
58
+ function hexBin(
59
+ rows: Row[],
60
+ xField: string | undefined,
61
+ yField: string | undefined,
62
+ xScale: Scale,
63
+ yScale: Scale,
64
+ r: number
65
+ ): Bin[] {
45
66
  const dx = r * 2 * Math.sin(Math.PI / 3) // horizontal spacing
46
67
  const dy = r * 1.5 // vertical spacing
47
68
  // eslint-disable-next-line svelte/prefer-svelte-reactivity
48
- const bins = new Map()
69
+ const bins = new Map<string, Bin>()
49
70
 
50
- for (const d of data) {
51
- const px = xScale(d[xField])
52
- const py = yScale(d[yField])
71
+ for (const d of rows) {
72
+ const px = xScale(d[xField ?? ''])
73
+ const py = yScale(d[yField ?? ''])
53
74
  if (px === null || px === undefined || py === null || py === undefined) continue
54
75
 
55
76
  // Find nearest hex center
@@ -59,10 +80,11 @@
59
80
  const cy = row * dy
60
81
  const key = `${col},${row}`
61
82
 
62
- if (!bins.has(key)) {
63
- bins.set(key, { cx, cy, count: 0, points: [] })
83
+ let bin = bins.get(key)
84
+ if (!bin) {
85
+ bin = { cx, cy, count: 0, points: [] }
86
+ bins.set(key, bin)
64
87
  }
65
- const bin = bins.get(key)
66
88
  bin.count++
67
89
  bin.points.push(d)
68
90
  }
@@ -70,7 +92,7 @@
70
92
  }
71
93
 
72
94
  // Build hexagon path for a given radius
73
- function hexPath(r) {
95
+ function hexPath(r: number) {
74
96
  const angles = [0, 1, 2, 3, 4, 5].map((i) => ((i * 60 - 30) * Math.PI) / 180)
75
97
  return `${angles.map((a, i) => `${i === 0 ? 'M' : 'L'}${r * Math.cos(a)},${r * Math.sin(a)}`).join('') }Z`
76
98
  }
@@ -1,6 +1,12 @@
1
- <script>
2
- /** @type {{ x: number, y: number, text: string, color?: string }} */
3
- let { x, y, text, color = '#333' } = $props()
1
+ <script lang="ts">
2
+ type Props = {
3
+ x: number
4
+ y: number
5
+ text: string
6
+ color?: string
7
+ }
8
+
9
+ let { x, y, text, color = '#333' }: Props = $props()
4
10
 
5
11
  const w = $derived(Math.max(36, String(text).length * 7 + 12))
6
12
  </script>
@@ -1,10 +1,31 @@
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 { buildLines } from '../lib/brewing/marks/lines.js'
4
5
  import { buildSymbolPath } from '../lib/brewing/marks/points.js'
5
6
  import { keyboardNav } from '../lib/keyboard-nav.js'
6
7
  import LabelPill from './LabelPill.svelte'
7
8
 
9
+ type Row = Record<string, unknown>
10
+ type Options = {
11
+ curve?: 'linear' | 'smooth' | 'step'
12
+ strokeWidth?: number
13
+ markerRadius?: number
14
+ labelOffset?: { x?: number; y?: number }
15
+ }
16
+
17
+ type Props = {
18
+ x?: string
19
+ y?: string
20
+ color?: string
21
+ symbol?: string
22
+ label?: boolean | string | ((data: Row) => unknown)
23
+ stat?: string
24
+ options?: Options
25
+ onselect?: (data: Row) => void
26
+ keyboard?: boolean
27
+ }
28
+
8
29
  let {
9
30
  x,
10
31
  y,
@@ -15,21 +36,17 @@
15
36
  options = {},
16
37
  onselect = undefined,
17
38
  keyboard = false
18
- } = $props()
39
+ }: Props = $props()
19
40
 
20
- /**
21
- * @param {Record<string, unknown>} data
22
- * @returns {string | null}
23
- */
24
- function resolveLabel(data) {
41
+ function resolveLabel(data: Row): string | null {
25
42
  if (!label) return null
26
- if (label === true) return String(data[y] ?? '')
43
+ if (label === true) return String((y ? data[y] : undefined) ?? '')
27
44
  if (typeof label === 'function') return String(label(data) ?? '')
28
45
  return typeof label === 'string' ? String(data[label] ?? '') : null
29
46
  }
30
47
 
31
- const plotState = getContext('plot-state')
32
- let id = $state(null)
48
+ const plotState = getContext<PlotState>('plot-state')
49
+ let id = $state<string | null>(null)
33
50
 
34
51
  onMount(() => {
35
52
  id = plotState.registerGeom({
@@ -1,10 +1,34 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { getContext, onMount, onDestroy } from 'svelte'
3
3
  import { scaleSqrt } from 'd3-scale'
4
+ import type { PlotState } from '../PlotState.svelte.js'
4
5
  import { buildPoints } from '../lib/brewing/marks/points.js'
5
6
  import { keyboardNav } from '../lib/keyboard-nav.js'
6
7
  import LabelPill from './LabelPill.svelte'
7
8
 
9
+ type Row = Record<string, unknown>
10
+ type Options = {
11
+ minRadius?: number
12
+ maxRadius?: number
13
+ radius?: number
14
+ opacity?: number
15
+ jitter?: { width?: number; height?: number } | null
16
+ labelOffset?: { x?: number; y?: number }
17
+ }
18
+
19
+ type Props = {
20
+ x?: string
21
+ y?: string
22
+ color?: string
23
+ size?: string
24
+ symbol?: string
25
+ label?: boolean | string | ((data: Row) => unknown)
26
+ stat?: string
27
+ options?: Options
28
+ onselect?: (data: Row) => void
29
+ keyboard?: boolean
30
+ }
31
+
8
32
  let {
9
33
  x,
10
34
  y,
@@ -16,21 +40,17 @@
16
40
  options = {},
17
41
  onselect = undefined,
18
42
  keyboard = false
19
- } = $props()
43
+ }: Props = $props()
20
44
 
21
- /**
22
- * @param {Record<string, unknown>} data
23
- * @returns {string | null}
24
- */
25
- function resolveLabel(data) {
45
+ function resolveLabel(data: Row): string | null {
26
46
  if (!label) return null
27
- if (label === true) return String(data[y] ?? '')
47
+ if (label === true) return String((y ? data[y] : undefined) ?? '')
28
48
  if (typeof label === 'function') return String(label(data) ?? '')
29
49
  return typeof label === 'string' ? String(data[label] ?? '') : null
30
50
  }
31
51
 
32
- const plotState = getContext('plot-state')
33
- let id = $state(null)
52
+ const plotState = getContext<PlotState>('plot-state')
53
+ let id = $state<string | null>(null)
34
54
 
35
55
  onMount(() => {
36
56
  id = plotState.registerGeom({
@@ -88,7 +108,7 @@
88
108
  {#if points.length > 0}
89
109
  <g data-plot-geom="point">
90
110
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
91
- {#each points as pt, i (`${i}::${pt.data[x]}::${pt.data[y]}`)}
111
+ {#each points as pt, i (`${i}::${pt.data[x ?? '']}::${pt.data[y ?? '']}`)}
92
112
  {#if pt.symbolPath}
93
113
  <path
94
114
  transform="translate({pt.cx},{pt.cy})"
@@ -101,7 +121,7 @@
101
121
  role={onselect || keyboard ? 'button' : 'graphics-symbol'}
102
122
  tabindex={onselect || keyboard ? 0 : undefined}
103
123
  style:cursor={onselect ? 'pointer' : undefined}
104
- aria-label="{pt.data[x]}, {pt.data[y]}"
124
+ aria-label="{pt.data[x ?? '']}, {pt.data[y ?? '']}"
105
125
  onmouseenter={() => plotState.setHovered(pt.data)}
106
126
  onmouseleave={() => plotState.clearHovered()}
107
127
  onclick={onselect ? () => onselect(pt.data) : undefined}
@@ -121,7 +141,7 @@
121
141
  role={onselect || keyboard ? 'button' : 'graphics-symbol'}
122
142
  tabindex={onselect || keyboard ? 0 : undefined}
123
143
  style:cursor={onselect ? 'pointer' : undefined}
124
- aria-label="{pt.data[x]}, {pt.data[y]}"
144
+ aria-label="{pt.data[x ?? '']}, {pt.data[y ?? '']}"
125
145
  onmouseenter={() => plotState.setHovered(pt.data)}
126
146
  onmouseleave={() => plotState.clearHovered()}
127
147
  onclick={onselect ? () => onselect(pt.data) : undefined}
@@ -1,5 +1,23 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { getContext, onMount, onDestroy } from 'svelte'
3
+ import type { PlotState } from '../PlotState.svelte.js'
4
+
5
+ type Node = { name: string; y: number; height: number }
6
+
7
+ type Options = {
8
+ source?: string
9
+ target?: string
10
+ value?: string
11
+ }
12
+
13
+ type Props = {
14
+ x?: string
15
+ y?: string
16
+ color?: string
17
+ fill?: string
18
+ stat?: string
19
+ options?: Options
20
+ }
3
21
 
4
22
  let {
5
23
  x = undefined,
@@ -8,15 +26,15 @@
8
26
  fill: fillProp = undefined,
9
27
  stat = 'identity',
10
28
  options = {}
11
- } = $props()
29
+ }: Props = $props()
12
30
 
13
31
  const sourceField = $derived(options.source ?? 'source')
14
32
  const targetField = $derived(options.target ?? 'target')
15
33
  const valueField = $derived(options.value ?? 'value')
16
34
  const colorChannel = $derived(fillProp ?? color)
17
35
 
18
- const plotState = getContext('plot-state')
19
- let id = $state(null)
36
+ const plotState = getContext<PlotState>('plot-state')
37
+ let id = $state<string | null>(null)
20
38
 
21
39
  onMount(() => {
22
40
  id = plotState.registerGeom({
@@ -57,9 +75,9 @@
57
75
 
58
76
  // Compute node positions
59
77
  // eslint-disable-next-line svelte/prefer-svelte-reactivity
60
- const sourceMap = new Map()
78
+ const sourceMap = new Map<string, number>()
61
79
  // eslint-disable-next-line svelte/prefer-svelte-reactivity
62
- const targetMap = new Map()
80
+ const targetMap = new Map<string, number>()
63
81
  for (const f of flows) {
64
82
  sourceMap.set(f.source, (sourceMap.get(f.source) ?? 0) + f.value)
65
83
  targetMap.set(f.target, (targetMap.get(f.target) ?? 0) + f.value)
@@ -72,7 +90,7 @@
72
90
 
73
91
  // Position source nodes on left
74
92
  let sourceY = 0
75
- const sourceNodes = []
93
+ const sourceNodes: Node[] = []
76
94
  for (const [name, value] of sourceMap) {
77
95
  const h = (value / totalSource) * availHeight
78
96
  sourceNodes.push({ name, y: sourceY, height: h })
@@ -81,7 +99,7 @@
81
99
 
82
100
  // Position target nodes on right
83
101
  let targetY = 0
84
- const targetNodes = []
102
+ const targetNodes: Node[] = []
85
103
  for (const [name, value] of targetMap) {
86
104
  const h = (value / totalTarget) * availHeight
87
105
  targetNodes.push({ name, y: targetY, height: h })
@@ -1,11 +1,20 @@
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 { buildViolins } from '../lib/brewing/marks/violins.js'
4
5
 
5
- let { x, y, fill, stat = 'boxplot', options = {} } = $props()
6
+ type Props = {
7
+ x?: string
8
+ y?: string
9
+ fill?: string
10
+ stat?: string
11
+ options?: { opacity?: number }
12
+ }
6
13
 
7
- const plotState = getContext('plot-state')
8
- let id = $state(null)
14
+ let { x, y, fill, stat = 'boxplot', options = {} }: Props = $props()
15
+
16
+ const plotState = getContext<PlotState>('plot-state')
17
+ let id = $state<string | null>(null)
9
18
 
10
19
  // fill ?? x drives the colors map for both violin interior and outline
11
20
  const fillChannel = $derived(fill ?? x)