@rokkit/chart 1.0.0-next.16 → 1.0.0-next.161

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 (173) hide show
  1. package/README.md +150 -46
  2. package/package.json +42 -45
  3. package/src/AnimatedPlot.svelte +383 -0
  4. package/src/Chart.svelte +95 -0
  5. package/src/ChartProvider.svelte +10 -0
  6. package/src/FacetPlot/Panel.svelte +37 -0
  7. package/src/FacetPlot.svelte +114 -0
  8. package/src/Plot/Arc.svelte +29 -0
  9. package/src/Plot/Area.svelte +32 -0
  10. package/src/Plot/Axis.svelte +95 -0
  11. package/src/Plot/Bar.svelte +54 -0
  12. package/src/Plot/Grid.svelte +34 -0
  13. package/src/Plot/Legend.svelte +233 -0
  14. package/src/Plot/Line.svelte +37 -0
  15. package/src/Plot/Point.svelte +40 -0
  16. package/src/Plot/Root.svelte +62 -0
  17. package/src/Plot/Timeline.svelte +95 -0
  18. package/src/Plot/Tooltip.svelte +87 -0
  19. package/src/Plot/index.js +9 -0
  20. package/src/Plot.svelte +297 -0
  21. package/src/PlotState.svelte.js +350 -0
  22. package/src/Sparkline.svelte +108 -0
  23. package/src/Symbol.svelte +21 -0
  24. package/src/Texture.svelte +18 -0
  25. package/src/charts/AreaChart.svelte +27 -0
  26. package/src/charts/BarChart.svelte +28 -0
  27. package/src/charts/BoxPlot.svelte +21 -0
  28. package/src/charts/BubbleChart.svelte +23 -0
  29. package/src/charts/LineChart.svelte +26 -0
  30. package/src/charts/PieChart.svelte +35 -0
  31. package/src/charts/ScatterPlot.svelte +26 -0
  32. package/src/charts/ViolinPlot.svelte +21 -0
  33. package/src/crossfilter/CrossFilter.svelte +42 -0
  34. package/src/crossfilter/FilterBar.svelte +24 -0
  35. package/src/crossfilter/FilterHistogram.svelte +290 -0
  36. package/src/crossfilter/FilterSlider.svelte +83 -0
  37. package/src/crossfilter/createCrossFilter.svelte.js +124 -0
  38. package/src/elements/Bar.svelte +22 -24
  39. package/src/elements/ColorRamp.svelte +20 -22
  40. package/src/elements/ContinuousLegend.svelte +20 -17
  41. package/src/elements/DefinePatterns.svelte +24 -0
  42. package/src/elements/DiscreteLegend.svelte +15 -15
  43. package/src/elements/Label.svelte +4 -8
  44. package/src/elements/SymbolGrid.svelte +22 -0
  45. package/src/elements/index.js +6 -0
  46. package/src/examples/BarChartExample.svelte +81 -0
  47. package/src/geoms/Arc.svelte +126 -0
  48. package/src/geoms/Area.svelte +78 -0
  49. package/src/geoms/Bar.svelte +200 -0
  50. package/src/geoms/Box.svelte +113 -0
  51. package/src/geoms/LabelPill.svelte +17 -0
  52. package/src/geoms/Line.svelte +123 -0
  53. package/src/geoms/Point.svelte +145 -0
  54. package/src/geoms/Violin.svelte +56 -0
  55. package/src/geoms/lib/areas.js +154 -0
  56. package/src/geoms/lib/bars.js +223 -0
  57. package/src/index.js +74 -16
  58. package/src/lib/brewer.js +25 -0
  59. package/src/lib/brewing/BoxBrewer.svelte.js +14 -0
  60. package/src/lib/brewing/CartesianBrewer.svelte.js +21 -0
  61. package/src/lib/brewing/PieBrewer.svelte.js +14 -0
  62. package/src/lib/brewing/QuartileBrewer.svelte.js +51 -0
  63. package/src/lib/brewing/ViolinBrewer.svelte.js +14 -0
  64. package/src/lib/brewing/axes.svelte.js +270 -0
  65. package/src/lib/brewing/bars.svelte.js +201 -0
  66. package/src/lib/brewing/brewer.svelte.js +277 -0
  67. package/src/lib/brewing/colors.js +51 -0
  68. package/src/lib/brewing/dimensions.svelte.js +56 -0
  69. package/src/lib/brewing/index.svelte.js +205 -0
  70. package/src/lib/brewing/legends.svelte.js +137 -0
  71. package/src/lib/brewing/marks/arcs.js +43 -0
  72. package/src/lib/brewing/marks/areas.js +72 -0
  73. package/src/lib/brewing/marks/bars.js +49 -0
  74. package/src/lib/brewing/marks/boxes.js +75 -0
  75. package/src/lib/brewing/marks/lines.js +55 -0
  76. package/src/lib/brewing/marks/points.js +105 -0
  77. package/src/lib/brewing/marks/violins.js +90 -0
  78. package/src/lib/brewing/patterns.js +45 -0
  79. package/src/lib/brewing/scales.js +51 -0
  80. package/src/lib/brewing/scales.svelte.js +82 -0
  81. package/src/lib/brewing/stats.js +74 -0
  82. package/src/lib/brewing/symbols.js +10 -0
  83. package/src/lib/brewing/types.js +73 -0
  84. package/src/lib/chart.js +221 -0
  85. package/src/lib/context.js +131 -0
  86. package/src/lib/grid.js +85 -0
  87. package/src/lib/keyboard-nav.js +37 -0
  88. package/src/lib/plot/chartProps.js +76 -0
  89. package/src/lib/plot/crossfilter.js +16 -0
  90. package/src/lib/plot/facet.js +58 -0
  91. package/src/lib/plot/frames.js +81 -0
  92. package/src/lib/plot/helpers.js +14 -0
  93. package/src/lib/plot/preset.js +67 -0
  94. package/src/lib/plot/scales.js +81 -0
  95. package/src/lib/plot/stat.js +92 -0
  96. package/src/lib/plot/types.js +65 -0
  97. package/src/lib/preset.js +41 -0
  98. package/src/lib/scales.svelte.js +151 -0
  99. package/src/lib/swatch.js +13 -0
  100. package/src/lib/ticks.js +46 -0
  101. package/src/lib/utils.js +111 -118
  102. package/src/lib/xscale.js +31 -0
  103. package/src/patterns/DefinePatterns.svelte +32 -0
  104. package/src/patterns/PatternDef.svelte +27 -0
  105. package/src/patterns/index.js +4 -0
  106. package/src/patterns/patterns.js +360 -0
  107. package/src/patterns/scale.js +116 -0
  108. package/src/spec/chart-spec.js +72 -0
  109. package/src/symbols/RoundedSquare.svelte +33 -0
  110. package/src/symbols/Shape.svelte +37 -0
  111. package/src/symbols/constants/index.js +4 -0
  112. package/src/symbols/index.js +9 -0
  113. package/src/symbols/outline.svelte +60 -0
  114. package/src/symbols/solid.svelte +60 -0
  115. package/LICENSE +0 -21
  116. package/src/chart/FacetGrid.svelte +0 -51
  117. package/src/chart/Grid.svelte +0 -34
  118. package/src/chart/Legend.svelte +0 -16
  119. package/src/chart/PatternDefs.svelte +0 -13
  120. package/src/chart/Swatch.svelte +0 -93
  121. package/src/chart/SwatchButton.svelte +0 -29
  122. package/src/chart/SwatchGrid.svelte +0 -55
  123. package/src/chart/Symbol.svelte +0 -37
  124. package/src/chart/Texture.svelte +0 -16
  125. package/src/chart/TexturedShape.svelte +0 -27
  126. package/src/chart/TimelapseChart.svelte +0 -97
  127. package/src/chart/Timer.svelte +0 -27
  128. package/src/chart.js +0 -9
  129. package/src/components/charts/Axis.svelte +0 -66
  130. package/src/components/charts/Chart.svelte +0 -35
  131. package/src/components/index.js +0 -23
  132. package/src/components/lib/axis.js +0 -0
  133. package/src/components/lib/chart.js +0 -187
  134. package/src/components/lib/color.js +0 -327
  135. package/src/components/lib/funnel.js +0 -204
  136. package/src/components/lib/index.js +0 -19
  137. package/src/components/lib/pattern.js +0 -190
  138. package/src/components/lib/rollup.js +0 -55
  139. package/src/components/lib/shape.js +0 -199
  140. package/src/components/lib/summary.js +0 -145
  141. package/src/components/lib/theme.js +0 -23
  142. package/src/components/lib/timer.js +0 -41
  143. package/src/components/lib/utils.js +0 -165
  144. package/src/components/plots/BarPlot.svelte +0 -36
  145. package/src/components/plots/BoxPlot.svelte +0 -54
  146. package/src/components/plots/ScatterPlot.svelte +0 -30
  147. package/src/components/store.js +0 -70
  148. package/src/constants.js +0 -66
  149. package/src/elements/PatternDefs.svelte +0 -13
  150. package/src/elements/PatternMask.svelte +0 -20
  151. package/src/elements/Symbol.svelte +0 -38
  152. package/src/elements/Tooltip.svelte +0 -23
  153. package/src/funnel.svelte +0 -35
  154. package/src/geom.js +0 -105
  155. package/src/lib/axis.js +0 -75
  156. package/src/lib/colors.js +0 -32
  157. package/src/lib/geom.js +0 -4
  158. package/src/lib/shapes.js +0 -144
  159. package/src/lib/timer.js +0 -44
  160. package/src/lookup.js +0 -29
  161. package/src/plots/BarPlot.svelte +0 -55
  162. package/src/plots/BoxPlot.svelte +0 -0
  163. package/src/plots/FunnelPlot.svelte +0 -33
  164. package/src/plots/HeatMap.svelte +0 -5
  165. package/src/plots/HeatMapCalendar.svelte +0 -129
  166. package/src/plots/LinePlot.svelte +0 -55
  167. package/src/plots/Plot.svelte +0 -25
  168. package/src/plots/RankBarPlot.svelte +0 -38
  169. package/src/plots/ScatterPlot.svelte +0 -20
  170. package/src/plots/ViolinPlot.svelte +0 -11
  171. package/src/plots/heatmap.js +0 -70
  172. package/src/plots/index.js +0 -10
  173. package/src/swatch.js +0 -11
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Unified pattern library.
3
+ *
4
+ * Each entry is an array of mark descriptors. All geometry is in normalized
5
+ * 0–1 coordinates; the renderer scales by `size` at paint time.
6
+ *
7
+ * Mark types
8
+ * ----------
9
+ * circle { cx, cy, r }
10
+ * line { x1, y1, x2, y2 } coordinates may exceed 0–1 for seamless tiling
11
+ * polygon { points: [x,y][] }
12
+ * rect { x, y, w, h }
13
+ * path { d: [cmd, ...args][] } SVG path commands as nested arrays (H/V take one arg)
14
+ *
15
+ * Appearance
16
+ * ----------
17
+ * fill: true → element uses fill color (stroke: none)
18
+ * fill: false → element uses stroke color (fill: none) ← default
19
+ * fillOpacity: n → applied as fill-opacity on filled marks
20
+ * opacity: n → applied as opacity on any mark
21
+ */
22
+
23
+ /** @typedef {{ type: 'circle', cx: number, cy: number, r: number, fill?: boolean, fillOpacity?: number, opacity?: number }} CircleMark */
24
+ /** @typedef {{ type: 'line', x1: number, y1: number, x2: number, y2: number, fill?: false, strokeWidth?: number }} LineMark */
25
+ /** @typedef {{ type: 'polygon', points: [number,number][], fill?: boolean, fillOpacity?: number, opacity?: number }} PolygonMark */
26
+ /** @typedef {{ type: 'rect', x: number, y: number, w: number, h: number, fill?: boolean, fillOpacity?: number, opacity?: number }} RectMark */
27
+ /** @typedef {{ type: 'path', d: (string|number)[][], fill?: boolean, fillOpacity?: number, opacity?: number }} PathMark */
28
+ /** @typedef {CircleMark | LineMark | PolygonMark | RectMark | PathMark} PatternMark */
29
+
30
+ /** @type {Record<string, PatternMark[]>} */
31
+ export const PATTERNS = {
32
+ // ── Line-based ──────────────────────────────────────────────────────────────
33
+
34
+ brick: [
35
+ { type: 'line', x1: 0, y1: 0.25, x2: 0.5, y2: 0.25 },
36
+ { type: 'line', x1: 0.5, y1: 0.75, x2: 1, y2: 0.75 },
37
+ { type: 'line', x1: 0, y1: 0, x2: 0, y2: 1 },
38
+ { type: 'line', x1: 1, y1: 0, x2: 1, y2: 1 },
39
+ { type: 'line', x1: 0.5, y1: 0, x2: 0.5, y2: 1 }
40
+ ],
41
+
42
+ hatch: [
43
+ { type: 'line', x1: 0, y1: 0.25, x2: 1, y2: 0.25 },
44
+ { type: 'line', x1: 0, y1: 0.5, x2: 1, y2: 0.5 },
45
+ { type: 'line', x1: 0, y1: 0.75, x2: 1, y2: 0.75 },
46
+ { type: 'line', x1: 0.25, y1: 0, x2: 0.25, y2: 1 },
47
+ { type: 'line', x1: 0.5, y1: 0, x2: 0.5, y2: 1 },
48
+ { type: 'line', x1: 0.75, y1: 0, x2: 0.75, y2: 1 }
49
+ ],
50
+
51
+ // Coordinates intentionally exceed 0–1 so lines tile seamlessly at tile edges
52
+ diagonal: [
53
+ { type: 'line', x1: -0.5, y1: 0.5, x2: 0.5, y2: -0.5 },
54
+ { type: 'line', x1: 0, y1: 1, x2: 1, y2: 0 },
55
+ { type: 'line', x1: 0.5, y1: 1.5, x2: 1.5, y2: 0.5 }
56
+ ],
57
+
58
+ // Two sets of diagonals crossing to form a diamond lattice
59
+ diamonds: [
60
+ { type: 'line', x1: 0, y1: 1, x2: 1, y2: 0 },
61
+ { type: 'line', x1: -0.5, y1: 0.5, x2: 0.5, y2: -0.5 },
62
+ { type: 'line', x1: 0.5, y1: 1.5, x2: 1.5, y2: 0.5 },
63
+ { type: 'line', x1: 0, y1: 0, x2: 1, y2: 1 },
64
+ { type: 'line', x1: -0.5, y1: 0.5, x2: 0.5, y2: 1.5 },
65
+ { type: 'line', x1: 0.5, y1: -0.5, x2: 1.5, y2: 0.5 }
66
+ ],
67
+
68
+ tile: [
69
+ { type: 'line', x1: 0, y1: 0.5, x2: 1, y2: 0.5 },
70
+ { type: 'line', x1: 0.5, y1: 0, x2: 0.5, y2: 1 }
71
+ ],
72
+
73
+ // ── Path-based ──────────────────────────────────────────────────────────────
74
+
75
+ swell: [
76
+ {
77
+ type: 'path',
78
+ d: [
79
+ ['M', 0, 0.3],
80
+ ['A', 0.6, 0.5, 0, 0, 0, 1, 0.3]
81
+ ]
82
+ }
83
+ ],
84
+
85
+ waves: [
86
+ {
87
+ type: 'path',
88
+ d: [
89
+ ['M', 0, 0.5],
90
+ ['L', 0.25, 0],
91
+ ['L', 0.75, 1],
92
+ ['L', 1, 0.5]
93
+ ]
94
+ }
95
+ ],
96
+
97
+ // ── Circle-based ────────────────────────────────────────────────────────────
98
+
99
+ // Small filled dots in an X pattern across the tile
100
+ dots: [
101
+ { type: 'circle', cx: 0.2, cy: 0.2, r: 0.08, fill: true },
102
+ { type: 'circle', cx: 0.4, cy: 0.4, r: 0.08, fill: true },
103
+ { type: 'circle', cx: 0.6, cy: 0.6, r: 0.08, fill: true },
104
+ { type: 'circle', cx: 0.8, cy: 0.8, r: 0.08, fill: true },
105
+ { type: 'circle', cx: 0.8, cy: 0.2, r: 0.08, fill: true },
106
+ { type: 'circle', cx: 0.6, cy: 0.4, r: 0.08, fill: true },
107
+ { type: 'circle', cx: 0.4, cy: 0.6, r: 0.08, fill: true },
108
+ { type: 'circle', cx: 0.2, cy: 0.8, r: 0.08, fill: true }
109
+ ],
110
+
111
+ // Outlined diamond (rotated square)
112
+ lattice: [
113
+ {
114
+ type: 'polygon',
115
+ points: [
116
+ [0.5, 0.22],
117
+ [0.78, 0.5],
118
+ [0.5, 0.78],
119
+ [0.22, 0.5]
120
+ ]
121
+ }
122
+ ],
123
+
124
+ // Fish-scale geometry as stroke only — same circles as petals, no fill
125
+ scales: [
126
+ {
127
+ type: 'path',
128
+ d: [
129
+ ['M', -0.7071, 0],
130
+ ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0],
131
+ ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
132
+ ['M', 0.2929, 0],
133
+ ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0],
134
+ ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
135
+ ['M', -0.2071, 0.5],
136
+ ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0],
137
+ ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
138
+ ['M', -0.7071, 1],
139
+ ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0],
140
+ ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
141
+ ['M', 0.2929, 1],
142
+ ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0],
143
+ ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0]
144
+ ]
145
+ }
146
+ ],
147
+
148
+ // Filled circles at tile corners + center (5-point dice pattern)
149
+ circles: [
150
+ { type: 'circle', cx: 0.5, cy: 0.5, r: 0.2, fill: true },
151
+ { type: 'circle', cx: 0, cy: 0, r: 0.2, fill: true },
152
+ { type: 'circle', cx: 1, cy: 0, r: 0.2, fill: true },
153
+ { type: 'circle', cx: 0, cy: 1, r: 0.2, fill: true },
154
+ { type: 'circle', cx: 1, cy: 1, r: 0.2, fill: true }
155
+ ],
156
+
157
+ // Diagonal cross strokes with a filled circle at the center
158
+ pip: [
159
+ { type: 'line', x1: 0, y1: 0, x2: 1, y2: 1 },
160
+ { type: 'line', x1: 0, y1: 1, x2: 1, y2: 0 },
161
+ { type: 'circle', cx: 0.5, cy: 0.5, r: 0.22, fill: true }
162
+ ],
163
+
164
+ // Two outlined circles + two filled circles in a 2×2 grid
165
+ rings: [
166
+ { type: 'circle', cx: 0.25, cy: 0.25, r: 0.2 },
167
+ { type: 'circle', cx: 0.75, cy: 0.75, r: 0.2 },
168
+ { type: 'circle', cx: 0.25, cy: 0.75, r: 0.2, fill: true },
169
+ { type: 'circle', cx: 0.75, cy: 0.25, r: 0.2, fill: true }
170
+ ],
171
+
172
+ // Fish-scale: circles centered at corners + tile center, r = 0.5√2 so each
173
+ // circle passes through its adjacent neighbors' centers. Even-odd fill produces
174
+ // the scallop/petal pattern where overlapping regions alternate filled/clear.
175
+ petals: [
176
+ {
177
+ type: 'path',
178
+ fill: true,
179
+ fillRule: 'evenodd',
180
+ d: [
181
+ // center (0, 0) r=0.7071 leftmost = (-0.7071, 0)
182
+ ['M', -0.7071, 0],
183
+ ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0],
184
+ ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
185
+ // center (1, 0) leftmost = (0.2929, 0)
186
+ ['M', 0.2929, 0],
187
+ ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0],
188
+ ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
189
+ // center (0.5, 0.5) leftmost = (-0.2071, 0.5)
190
+ ['M', -0.2071, 0.5],
191
+ ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0],
192
+ ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
193
+ // center (0, 1) leftmost = (-0.7071, 1)
194
+ ['M', -0.7071, 1],
195
+ ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0],
196
+ ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
197
+ // center (1, 1) leftmost = (0.2929, 1)
198
+ ['M', 0.2929, 1],
199
+ ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0],
200
+ ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0]
201
+ ]
202
+ }
203
+ ],
204
+
205
+ // ── Polygon-based ───────────────────────────────────────────────────────────
206
+
207
+ // Three filled triangles tiling the corners + center of the tile
208
+ triangles: [
209
+ {
210
+ type: 'polygon',
211
+ points: [
212
+ [0, 0.5],
213
+ [0.5, 1],
214
+ [0, 1]
215
+ ],
216
+ fill: true
217
+ },
218
+ {
219
+ type: 'polygon',
220
+ points: [
221
+ [0.5, 0],
222
+ [0, 0],
223
+ [0, 0.5]
224
+ ],
225
+ fill: true
226
+ },
227
+ {
228
+ type: 'polygon',
229
+ points: [
230
+ [1, 0],
231
+ [0.5, 0.5],
232
+ [1, 1]
233
+ ],
234
+ fill: true
235
+ }
236
+ ],
237
+
238
+ // Two filled triangles scattered at opposite corners
239
+ shards: [
240
+ {
241
+ type: 'polygon',
242
+ points: [
243
+ [0.25, 0.017],
244
+ [0, 0.417],
245
+ [0.5, 0.417]
246
+ ],
247
+ fill: true
248
+ },
249
+ {
250
+ type: 'polygon',
251
+ points: [
252
+ [0.5, 0.583],
253
+ [1, 0.583],
254
+ [0.75, 0.983]
255
+ ],
256
+ fill: true
257
+ }
258
+ ],
259
+
260
+ // Diagonal corner split — two-tone polygon division
261
+ wedge: [
262
+ {
263
+ type: 'polygon',
264
+ points: [
265
+ [1, 1],
266
+ [0.45, 1],
267
+ [0, 0.55],
268
+ [0, 0]
269
+ ],
270
+ fill: true
271
+ },
272
+ {
273
+ type: 'polygon',
274
+ points: [
275
+ [1, 0],
276
+ [0, 0],
277
+ [1, 1]
278
+ ],
279
+ fill: true,
280
+ fillOpacity: 0.55
281
+ }
282
+ ],
283
+
284
+ // Two triangles pointing inward from top and bottom edges
285
+ argyle: [
286
+ {
287
+ type: 'polygon',
288
+ points: [
289
+ [1, 0],
290
+ [0.5, 0.5],
291
+ [0, 0]
292
+ ],
293
+ fill: true
294
+ },
295
+ {
296
+ type: 'polygon',
297
+ points: [
298
+ [1, 1],
299
+ [0.5, 0.5],
300
+ [0, 1]
301
+ ],
302
+ fill: true
303
+ }
304
+ ],
305
+
306
+ // triangles pattern rotated 90° clockwise: (x,y) → (1−y, x)
307
+ chevrons: [
308
+ {
309
+ type: 'polygon',
310
+ points: [
311
+ [0, 0],
312
+ [0.5, 0],
313
+ [0, 0.5]
314
+ ],
315
+ fill: true
316
+ },
317
+ {
318
+ type: 'polygon',
319
+ points: [
320
+ [0.5, 0],
321
+ [1, 0],
322
+ [1, 0.5]
323
+ ],
324
+ fill: true
325
+ },
326
+ {
327
+ type: 'polygon',
328
+ points: [
329
+ [0, 1],
330
+ [1, 1],
331
+ [0.5, 0.5]
332
+ ],
333
+ fill: true
334
+ }
335
+ ],
336
+
337
+ // ── Rect-based ──────────────────────────────────────────────────────────────
338
+
339
+ checkerboard: [
340
+ { type: 'rect', x: 0, y: 0, w: 0.5, h: 0.5, fill: true },
341
+ { type: 'rect', x: 0.5, y: 0.5, w: 0.5, h: 0.5, fill: true }
342
+ ],
343
+
344
+ // Quarter-circle filled shape (bottom-left corner) with two accent dots
345
+ shell: [
346
+ {
347
+ type: 'path',
348
+ d: [
349
+ ['M', 0, 1],
350
+ ['V', 0],
351
+ ['H', 0.6],
352
+ ['V', 0.4],
353
+ ['A', 0.6, 0.6, 0, 0, 1, 0, 1]
354
+ ],
355
+ fill: true
356
+ },
357
+ { type: 'circle', cx: 0.798, cy: 0.298, r: 0.081, fill: true },
358
+ { type: 'circle', cx: 0.798, cy: 0.702, r: 0.081, fill: true }
359
+ ]
360
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Mark geometry scaling and attribute resolution utilities.
3
+ *
4
+ * All pattern coordinates are stored normalized 0–1. `scaleMark` multiplies
5
+ * every geometric value by `size`. `resolveMarkAttrs` then folds in the
6
+ * runtime fill/stroke colors and produces a plain object whose keys are
7
+ * valid SVG attribute names, ready for spread (`{...attrs}`).
8
+ *
9
+ * Path `A` (arc) commands: indices 2, 3, 4 are xRotation, largeArcFlag, and
10
+ * sweepFlag — NOT coordinates — and are never scaled.
11
+ *
12
+ * Source → SVG name mappings applied by scaleMark:
13
+ * rect w → width, h → height
14
+ * polygon points [[x,y]…] → SVG points string
15
+ * path d [[cmd,…]…] → SVG d string
16
+ */
17
+
18
+ /** @import { PatternMark } from './patterns.js' */
19
+
20
+ /**
21
+ * Scale a single SVG path command by `size`.
22
+ * @param {(string|number)[]} cmd
23
+ * @param {number} size
24
+ * @returns {(string|number)[]}
25
+ */
26
+ function scalePathCmd(cmd, size) {
27
+ const [op, ...args] = cmd
28
+ if (op === 'A' || op === 'a') {
29
+ return [
30
+ op,
31
+ args[0] * size,
32
+ args[1] * size,
33
+ args[2],
34
+ args[3],
35
+ args[4],
36
+ args[5] * size,
37
+ args[6] * size
38
+ ]
39
+ }
40
+ return [op, ...args.map((v) => (typeof v === 'number' ? v * size : v))]
41
+ }
42
+
43
+ /**
44
+ * Scale all geometric coordinates in a mark by `size`.
45
+ * Renames rect `w`/`h` to `width`/`height` and converts polygon/path to strings.
46
+ * @param {PatternMark} mark
47
+ * @param {number} size
48
+ * @returns {object}
49
+ */
50
+ export function scaleMark(mark, size) {
51
+ switch (mark.type) {
52
+ case 'line':
53
+ return {
54
+ ...mark,
55
+ x1: mark.x1 * size,
56
+ y1: mark.y1 * size,
57
+ x2: mark.x2 * size,
58
+ y2: mark.y2 * size
59
+ }
60
+ case 'circle':
61
+ return { ...mark, cx: mark.cx * size, cy: mark.cy * size, r: mark.r * size }
62
+ case 'rect': {
63
+ const { w, h, ...rest } = mark
64
+ return { ...rest, x: mark.x * size, y: mark.y * size, width: w * size, height: h * size }
65
+ }
66
+ case 'polygon':
67
+ return { ...mark, points: mark.points.map(([x, y]) => `${x * size},${y * size}`).join(' ') }
68
+ case 'path':
69
+ return {
70
+ ...mark,
71
+ d: mark.d
72
+ .map((cmd) => scalePathCmd(cmd, size))
73
+ .map((cmd) => cmd.join(' '))
74
+ .join(' ')
75
+ }
76
+ default:
77
+ return mark
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Resolve a scaled mark into SVG-ready `{ type, attrs }`.
83
+ * `attrs` can be spread directly onto the SVG element.
84
+ *
85
+ * Per-mark overrides respected:
86
+ * mark.strokeWidth → `stroke-width` (takes precedence over `thickness`)
87
+ * mark.fillOpacity → `fill-opacity`
88
+ * mark.opacity → `opacity`
89
+ *
90
+ * @param {object} scaledMark Output of scaleMark
91
+ * @param {{ fill: string, stroke: string, thickness: number }} appearance
92
+ * @returns {{ type: string, attrs: object }}
93
+ */
94
+ export function resolveMarkAttrs(scaledMark, { fill, stroke, thickness }) {
95
+ const {
96
+ type,
97
+ fill: isFill,
98
+ strokeWidth,
99
+ fillOpacity,
100
+ opacity,
101
+ fillRule,
102
+ ...geometry
103
+ } = scaledMark
104
+
105
+ const attrs = {
106
+ ...geometry,
107
+ fill: isFill ? fill : 'none',
108
+ stroke: isFill ? 'none' : stroke,
109
+ 'stroke-width': isFill ? 0 : (strokeWidth ?? thickness)
110
+ }
111
+ if (fillOpacity !== null && fillOpacity !== undefined) attrs['fill-opacity'] = fillOpacity
112
+ if (opacity !== null && opacity !== undefined) attrs.opacity = opacity
113
+ if (fillRule !== null && fillRule !== undefined) attrs['fill-rule'] = fillRule
114
+
115
+ return { type, attrs }
116
+ }
@@ -0,0 +1,72 @@
1
+ export class ChartSpec {
2
+ constructor(data) {
3
+ this.data = data ?? []
4
+ this.channels = {}
5
+ this.layers = []
6
+ this.options = {}
7
+ }
8
+
9
+ x(f) {
10
+ this.channels.x = f
11
+ return this
12
+ }
13
+ y(f) {
14
+ this.channels.y = f
15
+ return this
16
+ }
17
+ color(f) {
18
+ this.channels.color = f
19
+ return this
20
+ }
21
+ pattern(f) {
22
+ this.channels.pattern = f
23
+ return this
24
+ }
25
+ aes(ch) {
26
+ Object.assign(this.channels, ch)
27
+ return this
28
+ }
29
+
30
+ bar(opts = {}) {
31
+ this.layers.push({ type: 'bar', ...opts })
32
+ return this
33
+ }
34
+ line(opts = {}) {
35
+ this.layers.push({ type: 'line', ...opts })
36
+ return this
37
+ }
38
+ area(opts = {}) {
39
+ this.layers.push({ type: 'area', ...opts })
40
+ return this
41
+ }
42
+ arc(opts = {}) {
43
+ this.layers.push({ type: 'arc', ...opts })
44
+ return this
45
+ }
46
+ point(opts = {}) {
47
+ this.layers.push({ type: 'point', ...opts })
48
+ return this
49
+ }
50
+
51
+ grid(opts = {}) {
52
+ this.options.grid = opts
53
+ return this
54
+ }
55
+ legend(opts = {}) {
56
+ this.options.legend = opts
57
+ return this
58
+ }
59
+ axis(type, opts = {}) {
60
+ this.options[`axis_${type}`] = opts
61
+ return this
62
+ }
63
+ size(w, h) {
64
+ this.options.width = w
65
+ this.options.height = h
66
+ return this
67
+ }
68
+ }
69
+
70
+ export function chart(data, channels = {}) {
71
+ return new ChartSpec(data).aes(channels)
72
+ }
@@ -0,0 +1,33 @@
1
+ <script>
2
+ let {
3
+ x = 0,
4
+ y = 0,
5
+ size = 1,
6
+ fill = 'currentColor',
7
+ stroke = 'currentColor',
8
+ onclick,
9
+ onmouseover,
10
+ onmouseleave,
11
+ onfocus,
12
+ ...restProps
13
+ } = $props()
14
+
15
+ let r = $derived(size * 3.534)
16
+ let props = $derived({ rx: r * 0.1, ry: r * 0.1, ...restProps })
17
+ </script>
18
+
19
+ <rect
20
+ x={x - r}
21
+ y={y - r}
22
+ width={r * 2}
23
+ height={r * 2}
24
+ {fill}
25
+ {stroke}
26
+ {...props}
27
+ role="button"
28
+ {onclick}
29
+ {onmouseover}
30
+ {onmouseleave}
31
+ {onfocus}
32
+ tabindex="0"
33
+ />
@@ -0,0 +1,37 @@
1
+ <script>
2
+ import { namedShapes } from './constants'
3
+
4
+ let {
5
+ x = 0,
6
+ y = 0,
7
+ size = 1,
8
+ fill = 'none',
9
+ stroke = 'currentColor',
10
+ thickness = 1,
11
+ name = 'circle',
12
+ onclick,
13
+ onmouseover,
14
+ onmouseleave,
15
+ onfocus,
16
+ onblur
17
+ } = $props()
18
+
19
+ let d = $derived(name in namedShapes ? namedShapes[name](size) : namedShapes['circle'](size))
20
+ </script>
21
+
22
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
23
+ <path
24
+ {d}
25
+ {fill}
26
+ {stroke}
27
+ transform="translate({x},{y})"
28
+ stroke-width={thickness}
29
+ fill-rule="nonzero"
30
+ role="button"
31
+ {onclick}
32
+ {onmouseover}
33
+ {onmouseleave}
34
+ {onfocus}
35
+ {onblur}
36
+ tabindex="0"
37
+ />
@@ -0,0 +1,4 @@
1
+ import { scaledPathCollection } from '../../lib/utils'
2
+ import paths from './shapes.json' with { type: 'json' }
3
+
4
+ export const namedShapes = scaledPathCollection(paths)
@@ -0,0 +1,9 @@
1
+ import { namedShapes } from './constants'
2
+ import { default as Shape } from './Shape.svelte'
3
+ import { default as RoundedSquare } from './RoundedSquare.svelte'
4
+
5
+ export const shapes = [...Object.keys(namedShapes), 'rounded-square']
6
+ export const components = {
7
+ default: Shape,
8
+ 'rounded-square': RoundedSquare
9
+ }
@@ -0,0 +1,60 @@
1
+ <svg
2
+ class="plot"
3
+ fill="currentColor"
4
+ font-family="system-ui, sans-serif"
5
+ font-size="10"
6
+ text-anchor="middle"
7
+ width="688"
8
+ height="60"
9
+ viewBox="0 0 688 60"
10
+ >
11
+ <style>
12
+ :where(.plot) {
13
+ --plot-background: white;
14
+ display: block;
15
+ height: auto;
16
+ height: intrinsic;
17
+ max-width: 100%;
18
+ }
19
+ :where(.plot text),
20
+ :where(.plot tspan) {
21
+ white-space: pre;
22
+ }
23
+ </style>
24
+ <g aria-label="x-axis tick" fill="none" stroke="currentColor"
25
+ ><path transform="translate(68,30)" d="M0,0L0,6"></path><path
26
+ transform="translate(160,30)"
27
+ d="M0,0L0,6"
28
+ ></path><path transform="translate(252,30)" d="M0,0L0,6"></path><path
29
+ transform="translate(344,30)"
30
+ d="M0,0L0,6"
31
+ ></path><path transform="translate(436,30)" d="M0,0L0,6"></path><path
32
+ transform="translate(528,30)"
33
+ d="M0,0L0,6"
34
+ ></path><path transform="translate(620,30)" d="M0,0L0,6"></path></g
35
+ ><g aria-label="x-axis tick label" transform="translate(0,9)"
36
+ ><text y="0.71em" transform="translate(68,30)">asterisk</text><text
37
+ y="0.71em"
38
+ transform="translate(160,30)">circle</text
39
+ ><text y="0.71em" transform="translate(252,30)">diamond2</text><text
40
+ y="0.71em"
41
+ transform="translate(344,30)">plus</text
42
+ ><text y="0.71em" transform="translate(436,30)">square2</text><text
43
+ y="0.71em"
44
+ transform="translate(528,30)">times</text
45
+ ><text y="0.71em" transform="translate(620,30)">triangle2</text></g
46
+ ><g aria-label="dot" fill="none" stroke="currentColor" stroke-width="1.5"
47
+ ><path transform="translate(160,15.25)" d="M4.5,0A4.5,4.5,0,1,1,-4.5,0A4.5,4.5,0,1,1,4.5,0"
48
+ ></path><path transform="translate(344,15.25)" d="M-6.873,0L6.873,0M0,6.873L0,-6.873"
49
+ ></path><path transform="translate(528,15.25)" d="M-4.87,-4.87L4.87,4.87M-4.87,4.87L4.87,-4.87"
50
+ ></path><path transform="translate(620,15.25)" d="M0,-5.443L4.714,2.721L-4.714,2.721Z"
51
+ ></path><path
52
+ transform="translate(68,15.25)"
53
+ d="M0,4.769L0,-4.769M-4.13,-2.384L4.13,2.384M-4.13,2.384L4.13,-2.384"
54
+ ></path><path
55
+ transform="translate(436,15.25)"
56
+ d="M3.534,3.534L3.534,-3.534L-3.534,-3.534L-3.534,3.534Z"
57
+ ></path><path transform="translate(252,15.25)" d="M0,-4.995L4.995,0L0,4.995L-4.995,0Z"
58
+ ></path></g
59
+ ></svg
60
+ >