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

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 (90) hide show
  1. package/dist/PlotState.svelte.d.ts +31 -3
  2. package/dist/crossfilter/createCrossFilter.svelte.d.ts +13 -15
  3. package/dist/index.d.ts +6 -1
  4. package/dist/lib/brewing/BoxBrewer.svelte.d.ts +3 -5
  5. package/dist/lib/brewing/QuartileBrewer.svelte.d.ts +9 -0
  6. package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +3 -4
  7. package/dist/lib/brewing/brewer.svelte.d.ts +5 -36
  8. package/dist/lib/brewing/colors.d.ts +10 -1
  9. package/dist/lib/brewing/marks/points.d.ts +17 -2
  10. package/dist/lib/brewing/stats.d.ts +5 -13
  11. package/dist/lib/chart.d.ts +5 -7
  12. package/dist/lib/keyboard-nav.d.ts +15 -0
  13. package/dist/lib/plot/preset.d.ts +1 -1
  14. package/dist/lib/preset.d.ts +30 -0
  15. package/package.json +2 -1
  16. package/src/AnimatedPlot.svelte +375 -206
  17. package/src/Chart.svelte +81 -87
  18. package/src/ChartProvider.svelte +10 -0
  19. package/src/FacetPlot/Panel.svelte +30 -16
  20. package/src/FacetPlot.svelte +100 -76
  21. package/src/Plot/Area.svelte +26 -19
  22. package/src/Plot/Axis.svelte +81 -59
  23. package/src/Plot/Bar.svelte +47 -89
  24. package/src/Plot/Grid.svelte +23 -19
  25. package/src/Plot/Legend.svelte +213 -147
  26. package/src/Plot/Line.svelte +31 -21
  27. package/src/Plot/Point.svelte +35 -22
  28. package/src/Plot/Root.svelte +46 -91
  29. package/src/Plot/Timeline.svelte +82 -82
  30. package/src/Plot/Tooltip.svelte +68 -62
  31. package/src/Plot.svelte +290 -182
  32. package/src/PlotState.svelte.js +339 -267
  33. package/src/Sparkline.svelte +95 -56
  34. package/src/charts/AreaChart.svelte +22 -20
  35. package/src/charts/BarChart.svelte +23 -21
  36. package/src/charts/BoxPlot.svelte +15 -15
  37. package/src/charts/BubbleChart.svelte +17 -17
  38. package/src/charts/LineChart.svelte +20 -20
  39. package/src/charts/PieChart.svelte +30 -20
  40. package/src/charts/ScatterPlot.svelte +20 -19
  41. package/src/charts/ViolinPlot.svelte +15 -15
  42. package/src/crossfilter/CrossFilter.svelte +33 -29
  43. package/src/crossfilter/FilterBar.svelte +17 -25
  44. package/src/crossfilter/FilterHistogram.svelte +290 -0
  45. package/src/crossfilter/FilterSlider.svelte +69 -65
  46. package/src/crossfilter/createCrossFilter.svelte.js +100 -89
  47. package/src/geoms/Arc.svelte +114 -69
  48. package/src/geoms/Area.svelte +67 -39
  49. package/src/geoms/Bar.svelte +184 -126
  50. package/src/geoms/Box.svelte +102 -90
  51. package/src/geoms/LabelPill.svelte +11 -11
  52. package/src/geoms/Line.svelte +110 -87
  53. package/src/geoms/Point.svelte +132 -87
  54. package/src/geoms/Violin.svelte +45 -33
  55. package/src/geoms/lib/areas.js +122 -99
  56. package/src/geoms/lib/bars.js +195 -144
  57. package/src/index.js +21 -14
  58. package/src/lib/brewing/BoxBrewer.svelte.js +8 -50
  59. package/src/lib/brewing/CartesianBrewer.svelte.js +12 -7
  60. package/src/lib/brewing/PieBrewer.svelte.js +5 -5
  61. package/src/lib/brewing/QuartileBrewer.svelte.js +51 -0
  62. package/src/lib/brewing/ViolinBrewer.svelte.js +8 -49
  63. package/src/lib/brewing/brewer.svelte.js +249 -201
  64. package/src/lib/brewing/colors.js +34 -5
  65. package/src/lib/brewing/marks/arcs.js +28 -28
  66. package/src/lib/brewing/marks/areas.js +54 -41
  67. package/src/lib/brewing/marks/bars.js +34 -34
  68. package/src/lib/brewing/marks/boxes.js +51 -51
  69. package/src/lib/brewing/marks/lines.js +37 -30
  70. package/src/lib/brewing/marks/points.js +74 -26
  71. package/src/lib/brewing/marks/violins.js +57 -57
  72. package/src/lib/brewing/patterns.js +25 -11
  73. package/src/lib/brewing/scales.js +20 -20
  74. package/src/lib/brewing/stats.js +40 -28
  75. package/src/lib/brewing/symbols.js +1 -1
  76. package/src/lib/chart.js +12 -4
  77. package/src/lib/keyboard-nav.js +37 -0
  78. package/src/lib/plot/crossfilter.js +5 -5
  79. package/src/lib/plot/facet.js +30 -30
  80. package/src/lib/plot/frames.js +30 -29
  81. package/src/lib/plot/helpers.js +4 -4
  82. package/src/lib/plot/preset.js +48 -34
  83. package/src/lib/plot/scales.js +64 -39
  84. package/src/lib/plot/stat.js +47 -47
  85. package/src/lib/preset.js +41 -0
  86. package/src/patterns/DefinePatterns.svelte +24 -24
  87. package/src/patterns/PatternDef.svelte +1 -1
  88. package/src/patterns/patterns.js +328 -176
  89. package/src/patterns/scale.js +61 -32
  90. package/src/spec/chart-spec.js +64 -21
@@ -29,180 +29,332 @@
29
29
 
30
30
  /** @type {Record<string, PatternMark[]>} */
31
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
- { type: 'path', d: [['M', 0, 0.3], ['A', 0.6, 0.5, 0, 0, 0, 1, 0.3]] }
77
- ],
78
-
79
- waves: [
80
- { type: 'path', d: [['M', 0, 0.5], ['L', 0.25, 0], ['L', 0.75, 1], ['L', 1, 0.5]] }
81
- ],
82
-
83
- // ── Circle-based ────────────────────────────────────────────────────────────
84
-
85
- // Small filled dots in an X pattern across the tile
86
- dots: [
87
- { type: 'circle', cx: 0.2, cy: 0.2, r: 0.08, fill: true },
88
- { type: 'circle', cx: 0.4, cy: 0.4, r: 0.08, fill: true },
89
- { type: 'circle', cx: 0.6, cy: 0.6, r: 0.08, fill: true },
90
- { type: 'circle', cx: 0.8, cy: 0.8, r: 0.08, fill: true },
91
- { type: 'circle', cx: 0.8, cy: 0.2, r: 0.08, fill: true },
92
- { type: 'circle', cx: 0.6, cy: 0.4, r: 0.08, fill: true },
93
- { type: 'circle', cx: 0.4, cy: 0.6, r: 0.08, fill: true },
94
- { type: 'circle', cx: 0.2, cy: 0.8, r: 0.08, fill: true }
95
- ],
96
-
97
- // Outlined diamond (rotated square)
98
- lattice: [
99
- { type: 'polygon', points: [[0.5, 0.22], [0.78, 0.5], [0.5, 0.78], [0.22, 0.5]] }
100
- ],
101
-
102
- // Fish-scale geometry as stroke only same circles as petals, no fill
103
- scales: [
104
- {
105
- type: 'path',
106
- d: [
107
- ['M', -0.7071, 0], ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0], ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
108
- ['M', 0.2929, 0], ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0], ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
109
- ['M', -0.2071, 0.5], ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0], ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
110
- ['M', -0.7071, 1], ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0], ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
111
- ['M', 0.2929, 1], ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0], ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
112
- ]
113
- }
114
- ],
115
-
116
- // Filled circles at tile corners + center (5-point dice pattern)
117
- circles: [
118
- { type: 'circle', cx: 0.5, cy: 0.5, r: 0.2, fill: true },
119
- { type: 'circle', cx: 0, cy: 0, r: 0.2, fill: true },
120
- { type: 'circle', cx: 1, cy: 0, r: 0.2, fill: true },
121
- { type: 'circle', cx: 0, cy: 1, r: 0.2, fill: true },
122
- { type: 'circle', cx: 1, cy: 1, r: 0.2, fill: true }
123
- ],
124
-
125
- // Diagonal cross strokes with a filled circle at the center
126
- pip: [
127
- { type: 'line', x1: 0, y1: 0, x2: 1, y2: 1 },
128
- { type: 'line', x1: 0, y1: 1, x2: 1, y2: 0 },
129
- { type: 'circle', cx: 0.5, cy: 0.5, r: 0.22, fill: true }
130
- ],
131
-
132
- // Two outlined circles + two filled circles in a 2×2 grid
133
- rings: [
134
- { type: 'circle', cx: 0.25, cy: 0.25, r: 0.2 },
135
- { type: 'circle', cx: 0.75, cy: 0.75, r: 0.2 },
136
- { type: 'circle', cx: 0.25, cy: 0.75, r: 0.2, fill: true },
137
- { type: 'circle', cx: 0.75, cy: 0.25, r: 0.2, fill: true }
138
- ],
139
-
140
- // Fish-scale: circles centered at corners + tile center, r = 0.5√2 so each
141
- // circle passes through its adjacent neighbors' centers. Even-odd fill produces
142
- // the scallop/petal pattern where overlapping regions alternate filled/clear.
143
- petals: [
144
- {
145
- type: 'path', fill: true, fillRule: 'evenodd',
146
- d: [
147
- // center (0, 0) r=0.7071 leftmost = (-0.7071, 0)
148
- ['M', -0.7071, 0], ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0], ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
149
- // center (1, 0) leftmost = (0.2929, 0)
150
- ['M', 0.2929, 0], ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0], ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
151
- // center (0.5, 0.5) leftmost = (-0.2071, 0.5)
152
- ['M', -0.2071, 0.5], ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0], ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
153
- // center (0, 1) leftmost = (-0.7071, 1)
154
- ['M', -0.7071, 1], ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0], ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
155
- // center (1, 1) leftmost = (0.2929, 1)
156
- ['M', 0.2929, 1], ['a', 0.7071, 0.7071, 0, 1, 0, 1.4142, 0], ['a', 0.7071, 0.7071, 0, 1, 0, -1.4142, 0],
157
- ]
158
- }
159
- ],
160
-
161
- // ── Polygon-based ───────────────────────────────────────────────────────────
162
-
163
- // Three filled triangles tiling the corners + center of the tile
164
- triangles: [
165
- { type: 'polygon', points: [[0, 0.5], [0.5, 1], [0, 1]], fill: true },
166
- { type: 'polygon', points: [[0.5, 0], [0, 0], [0, 0.5]], fill: true },
167
- { type: 'polygon', points: [[1, 0], [0.5, 0.5], [1, 1]], fill: true }
168
- ],
169
-
170
- // Two filled triangles scattered at opposite corners
171
- shards: [
172
- { type: 'polygon', points: [[0.25, 0.017], [0, 0.417], [0.5, 0.417]], fill: true },
173
- { type: 'polygon', points: [[0.5, 0.583], [1, 0.583], [0.75, 0.983]], fill: true }
174
- ],
175
-
176
- // Diagonal corner split — two-tone polygon division
177
- wedge: [
178
- { type: 'polygon', points: [[1, 1], [0.45, 1], [0, 0.55], [0, 0]], fill: true },
179
- { type: 'polygon', points: [[1, 0], [0, 0], [1, 1]], fill: true, fillOpacity: 0.55 }
180
- ],
181
-
182
- // Two triangles pointing inward from top and bottom edges
183
- argyle: [
184
- { type: 'polygon', points: [[1, 0], [0.5, 0.5], [0, 0]], fill: true },
185
- { type: 'polygon', points: [[1, 1], [0.5, 0.5], [0, 1]], fill: true }
186
- ],
187
-
188
- // triangles pattern rotated 90° clockwise: (x,y) → (1−y, x)
189
- chevrons: [
190
- { type: 'polygon', points: [[0, 0], [0.5, 0], [0, 0.5]], fill: true },
191
- { type: 'polygon', points: [[0.5, 0], [1, 0], [1, 0.5]], fill: true },
192
- { type: 'polygon', points: [[0, 1], [1, 1], [0.5, 0.5]], fill: true }
193
- ],
194
-
195
- // ── Rect-based ──────────────────────────────────────────────────────────────
196
-
197
- checkerboard: [
198
- { type: 'rect', x: 0, y: 0, w: 0.5, h: 0.5, fill: true },
199
- { type: 'rect', x: 0.5, y: 0.5, w: 0.5, h: 0.5, fill: true }
200
- ],
201
-
202
- // Quarter-circle filled shape (bottom-left corner) with two accent dots
203
- shell: [
204
- { type: 'path', d: [['M', 0, 1], ['V', 0], ['H', 0.6], ['V', 0.4], ['A', 0.6, 0.6, 0, 0, 1, 0, 1]], fill: true },
205
- { type: 'circle', cx: 0.798, cy: 0.298, r: 0.081, fill: true },
206
- { type: 'circle', cx: 0.798, cy: 0.702, r: 0.081, fill: true }
207
- ],
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
+ ]
208
360
  }
@@ -24,11 +24,20 @@
24
24
  * @returns {(string|number)[]}
25
25
  */
26
26
  function scalePathCmd(cmd, size) {
27
- const [op, ...args] = cmd
28
- if (op === 'A' || op === 'a') {
29
- return [op, args[0] * size, args[1] * size, args[2], args[3], args[4], args[5] * size, args[6] * size]
30
- }
31
- return [op, ...args.map((v) => (typeof v === 'number' ? v * size : v))]
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))]
32
41
  }
33
42
 
34
43
  /**
@@ -39,22 +48,34 @@ function scalePathCmd(cmd, size) {
39
48
  * @returns {object}
40
49
  */
41
50
  export function scaleMark(mark, size) {
42
- switch (mark.type) {
43
- case 'line':
44
- return { ...mark, x1: mark.x1 * size, y1: mark.y1 * size, x2: mark.x2 * size, y2: mark.y2 * size }
45
- case 'circle':
46
- return { ...mark, cx: mark.cx * size, cy: mark.cy * size, r: mark.r * size }
47
- case 'rect': {
48
- const { w, h, ...rest } = mark
49
- return { ...rest, x: mark.x * size, y: mark.y * size, width: w * size, height: h * size }
50
- }
51
- case 'polygon':
52
- return { ...mark, points: mark.points.map(([x, y]) => `${x * size},${y * size}`).join(' ') }
53
- case 'path':
54
- return { ...mark, d: mark.d.map((cmd) => scalePathCmd(cmd, size)).map((cmd) => cmd.join(' ')).join(' ') }
55
- default:
56
- return mark
57
- }
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
+ }
58
79
  }
59
80
 
60
81
  /**
@@ -71,17 +92,25 @@ export function scaleMark(mark, size) {
71
92
  * @returns {{ type: string, attrs: object }}
72
93
  */
73
94
  export function resolveMarkAttrs(scaledMark, { fill, stroke, thickness }) {
74
- const { type, fill: isFill, strokeWidth, fillOpacity, opacity, fillRule, ...geometry } = scaledMark
95
+ const {
96
+ type,
97
+ fill: isFill,
98
+ strokeWidth,
99
+ fillOpacity,
100
+ opacity,
101
+ fillRule,
102
+ ...geometry
103
+ } = scaledMark
75
104
 
76
- const attrs = {
77
- ...geometry,
78
- fill: isFill ? fill : 'none',
79
- stroke: isFill ? 'none' : stroke,
80
- 'stroke-width': isFill ? 0 : (strokeWidth ?? thickness),
81
- }
82
- if (fillOpacity !== null && fillOpacity !== undefined) attrs['fill-opacity'] = fillOpacity
83
- if (opacity !== null && opacity !== undefined) attrs.opacity = opacity
84
- if (fillRule !== null && fillRule !== undefined) attrs['fill-rule'] = fillRule
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
85
114
 
86
- return { type, attrs }
115
+ return { type, attrs }
87
116
  }