ggaction 0.0.1

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 (222) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +21 -0
  3. package/README.md +108 -0
  4. package/package.json +84 -0
  5. package/src/AGENTS.md +119 -0
  6. package/src/ChartProgram.js +10 -0
  7. package/src/actions/boxPlots/components.js +88 -0
  8. package/src/actions/boxPlots/create.js +84 -0
  9. package/src/actions/boxPlots/index.js +10 -0
  10. package/src/actions/boxPlots/materialize.js +183 -0
  11. package/src/actions/boxPlots/options.js +147 -0
  12. package/src/actions/boxPlots/resolve.js +38 -0
  13. package/src/actions/canvas/actions.js +144 -0
  14. package/src/actions/canvas/index.js +6 -0
  15. package/src/actions/coordinates/actions.js +85 -0
  16. package/src/actions/coordinates/index.js +5 -0
  17. package/src/actions/data/box.js +40 -0
  18. package/src/actions/data/create.js +33 -0
  19. package/src/actions/data/density.js +62 -0
  20. package/src/actions/data/derived.js +52 -0
  21. package/src/actions/data/filter.js +168 -0
  22. package/src/actions/data/index.js +32 -0
  23. package/src/actions/data/interval.js +69 -0
  24. package/src/actions/data/regression.js +71 -0
  25. package/src/actions/data/shared.js +25 -0
  26. package/src/actions/encodings/appearance.js +243 -0
  27. package/src/actions/encodings/barWidth.js +60 -0
  28. package/src/actions/encodings/color.js +329 -0
  29. package/src/actions/encodings/density.js +225 -0
  30. package/src/actions/encodings/histogram.js +50 -0
  31. package/src/actions/encodings/index.js +23 -0
  32. package/src/actions/encodings/offset.js +104 -0
  33. package/src/actions/encodings/position/policies/area.js +24 -0
  34. package/src/actions/encodings/position/policies/bar.js +110 -0
  35. package/src/actions/encodings/position/policies/common.js +17 -0
  36. package/src/actions/encodings/position/policies/index.js +25 -0
  37. package/src/actions/encodings/position/policies/line.js +74 -0
  38. package/src/actions/encodings/position/policies/point.js +19 -0
  39. package/src/actions/encodings/position/policies/rule.js +17 -0
  40. package/src/actions/encodings/position/resolve.js +170 -0
  41. package/src/actions/encodings/position.js +176 -0
  42. package/src/actions/encodings/ranged.js +276 -0
  43. package/src/actions/encodings/ruleAppearance.js +47 -0
  44. package/src/actions/encodings/shared.js +164 -0
  45. package/src/actions/encodings/strokeDash.js +145 -0
  46. package/src/actions/errorBands/components.js +81 -0
  47. package/src/actions/errorBands/create.js +159 -0
  48. package/src/actions/errorBands/index.js +7 -0
  49. package/src/actions/errorBands/resolve.js +28 -0
  50. package/src/actions/errorBars/components.js +62 -0
  51. package/src/actions/errorBars/create.js +144 -0
  52. package/src/actions/errorBars/index.js +7 -0
  53. package/src/actions/errorBars/resolve.js +25 -0
  54. package/src/actions/guides/axes/axes.js +204 -0
  55. package/src/actions/guides/axes/axis.js +131 -0
  56. package/src/actions/guides/axes/index.js +17 -0
  57. package/src/actions/guides/axes/labels.js +231 -0
  58. package/src/actions/guides/axes/lines.js +149 -0
  59. package/src/actions/guides/axes/policy.js +155 -0
  60. package/src/actions/guides/axes/tickGroups.js +149 -0
  61. package/src/actions/guides/axes/ticks.js +179 -0
  62. package/src/actions/guides/axes/titles.js +264 -0
  63. package/src/actions/guides/grids/grid.js +228 -0
  64. package/src/actions/guides/grids/index.js +1 -0
  65. package/src/actions/guides/grids/resolve.js +258 -0
  66. package/src/actions/guides/guides.js +138 -0
  67. package/src/actions/guides/index.js +11 -0
  68. package/src/actions/guides/legends/categorical/actions.js +329 -0
  69. package/src/actions/guides/legends/categorical/components.js +140 -0
  70. package/src/actions/guides/legends/categorical/index.js +49 -0
  71. package/src/actions/guides/legends/categorical/layout.js +445 -0
  72. package/src/actions/guides/legends/categorical/options.js +188 -0
  73. package/src/actions/guides/legends/categorical/recipes.js +120 -0
  74. package/src/actions/guides/legends/categorical/resolve.js +131 -0
  75. package/src/actions/guides/legends/categorical/symbols.js +280 -0
  76. package/src/actions/guides/legends/categorical/validation.js +27 -0
  77. package/src/actions/guides/legends/continuous/common.js +273 -0
  78. package/src/actions/guides/legends/continuous/gradient.js +363 -0
  79. package/src/actions/guides/legends/continuous/index.js +25 -0
  80. package/src/actions/guides/legends/continuous/interval.js +234 -0
  81. package/src/actions/guides/legends/continuous/opacity.js +382 -0
  82. package/src/actions/guides/legends/edit.js +314 -0
  83. package/src/actions/guides/legends/index.js +9 -0
  84. package/src/actions/guides/legends/size.js +220 -0
  85. package/src/actions/guides/tickValues.js +84 -0
  86. package/src/actions/index.js +31 -0
  87. package/src/actions/intervals/resolve.js +352 -0
  88. package/src/actions/marks/area.js +369 -0
  89. package/src/actions/marks/bar/create.js +46 -0
  90. package/src/actions/marks/bar/edit.js +90 -0
  91. package/src/actions/marks/bar/index.js +9 -0
  92. package/src/actions/marks/bar/materialize.js +130 -0
  93. package/src/actions/marks/index.js +13 -0
  94. package/src/actions/marks/line.js +248 -0
  95. package/src/actions/marks/point.js +420 -0
  96. package/src/actions/marks/rule.js +287 -0
  97. package/src/actions/marks/shared.js +112 -0
  98. package/src/actions/primitives/createGraphics.js +163 -0
  99. package/src/actions/primitives/editGraphics.js +208 -0
  100. package/src/actions/primitives/index.js +9 -0
  101. package/src/actions/primitives/semantic.js +168 -0
  102. package/src/actions/primitives/semanticValue.js +182 -0
  103. package/src/actions/regression/components.js +216 -0
  104. package/src/actions/regression/create.js +102 -0
  105. package/src/actions/regression/index.js +23 -0
  106. package/src/actions/regression/resolve.js +57 -0
  107. package/src/actions/scales/consumers.js +283 -0
  108. package/src/actions/scales/create.js +148 -0
  109. package/src/actions/scales/definitions.js +319 -0
  110. package/src/actions/scales/edit.js +290 -0
  111. package/src/actions/scales/index.js +11 -0
  112. package/src/actions/scales/materialize.js +466 -0
  113. package/src/actions/scales/quantitativeColor.js +45 -0
  114. package/src/actions/selection/actions.js +384 -0
  115. package/src/actions/selection/index.js +23 -0
  116. package/src/actions/titles/actions.js +235 -0
  117. package/src/actions/titles/index.js +19 -0
  118. package/src/actions/titles/resolve.js +321 -0
  119. package/src/core/ChartProgram.js +316 -0
  120. package/src/core/action.js +120 -0
  121. package/src/core/identifiers.js +28 -0
  122. package/src/core/immutable.js +94 -0
  123. package/src/core/specs.js +29 -0
  124. package/src/core/textMetrics.js +23 -0
  125. package/src/core/validation.js +54 -0
  126. package/src/core/vocabulary.js +58 -0
  127. package/src/extension.js +2 -0
  128. package/src/grammar/aggregate.js +259 -0
  129. package/src/grammar/areaSeries.js +217 -0
  130. package/src/grammar/bars/aggregate.js +98 -0
  131. package/src/grammar/bars/geometry.js +54 -0
  132. package/src/grammar/bars/policy.js +90 -0
  133. package/src/grammar/boxPlot.js +140 -0
  134. package/src/grammar/coordinates.js +25 -0
  135. package/src/grammar/curveCommands.js +356 -0
  136. package/src/grammar/density.js +296 -0
  137. package/src/grammar/filter.js +169 -0
  138. package/src/grammar/histogram.js +332 -0
  139. package/src/grammar/interval.js +343 -0
  140. package/src/grammar/lineSeries.js +197 -0
  141. package/src/grammar/markFilter.js +46 -0
  142. package/src/grammar/markSelection.js +326 -0
  143. package/src/grammar/palettes.js +208 -0
  144. package/src/grammar/pathCommands.js +86 -0
  145. package/src/grammar/pointShapes.js +164 -0
  146. package/src/grammar/positionCompatibility.js +32 -0
  147. package/src/grammar/regression.js +578 -0
  148. package/src/grammar/ruleAppearance.js +12 -0
  149. package/src/grammar/rules.js +90 -0
  150. package/src/grammar/scales/appearance.js +185 -0
  151. package/src/grammar/scales/color.js +349 -0
  152. package/src/grammar/scales/continuous.js +111 -0
  153. package/src/grammar/scales/definition.js +153 -0
  154. package/src/grammar/scales/discretized.js +159 -0
  155. package/src/grammar/scales/fields.js +133 -0
  156. package/src/grammar/scales/index.js +12 -0
  157. package/src/grammar/scales/mapping.js +27 -0
  158. package/src/grammar/scales/ordinal.js +221 -0
  159. package/src/grammar/scales/policies.js +50 -0
  160. package/src/grammar/scales/temporal.js +53 -0
  161. package/src/grammar/scales/transformed.js +236 -0
  162. package/src/grammar/scales/types.js +102 -0
  163. package/src/grammar/scales/validation.js +66 -0
  164. package/src/grammar/scales.js +1 -0
  165. package/src/grammar/schemas/concreteGraphic.js +73 -0
  166. package/src/grammar/schemas/graphic.js +131 -0
  167. package/src/grammar/schemas/graphicBounds.js +146 -0
  168. package/src/grammar/schemas/graphicTree.js +133 -0
  169. package/src/grammar/schemas/mark.js +4 -0
  170. package/src/grammar/schemas/semanticPath.js +174 -0
  171. package/src/grammar/seriesLayout.js +76 -0
  172. package/src/grammar/ticks.js +149 -0
  173. package/src/grammar/transforms.js +37 -0
  174. package/src/index.js +2 -0
  175. package/src/layout/canvas.js +114 -0
  176. package/src/layout/grid.js +17 -0
  177. package/src/layout/legend.js +51 -0
  178. package/src/layout/text.js +62 -0
  179. package/src/layout/title.js +107 -0
  180. package/src/materialization/bars/aggregate.js +184 -0
  181. package/src/materialization/bars/grouped.js +117 -0
  182. package/src/materialization/bars/histogram.js +163 -0
  183. package/src/materialization/bars/ranged.js +53 -0
  184. package/src/materialization/bars/resolve.js +56 -0
  185. package/src/materialization/dataProvenance.js +15 -0
  186. package/src/materialization/density.js +29 -0
  187. package/src/materialization/dependencies.js +134 -0
  188. package/src/materialization/encodings.js +50 -0
  189. package/src/materialization/graphicHierarchy.js +104 -0
  190. package/src/materialization/legends.js +20 -0
  191. package/src/materialization/marks.js +110 -0
  192. package/src/materialization/planner.js +38 -0
  193. package/src/materialization/selection/filter.js +40 -0
  194. package/src/materialization/selection/items.js +503 -0
  195. package/src/materialization/selection/path.js +37 -0
  196. package/src/materialization/selection/point.js +118 -0
  197. package/src/materialization/selection/policies/area.js +10 -0
  198. package/src/materialization/selection/policies/bar.js +10 -0
  199. package/src/materialization/selection/policies/index.js +40 -0
  200. package/src/materialization/selection/policies/line.js +10 -0
  201. package/src/materialization/selection/policies/point.js +10 -0
  202. package/src/materialization/selection/policies/rule.js +10 -0
  203. package/src/materialization/selection/state.js +67 -0
  204. package/src/materialization/selection/styles.js +183 -0
  205. package/src/renderers/canvas/circle.js +62 -0
  206. package/src/renderers/canvas/index.js +132 -0
  207. package/src/renderers/canvas/line.js +54 -0
  208. package/src/renderers/canvas/path.js +98 -0
  209. package/src/renderers/canvas/rect.js +57 -0
  210. package/src/renderers/canvas/text.js +96 -0
  211. package/src/renderers/canvas/validation.js +23 -0
  212. package/src/renderers/png.js +29 -0
  213. package/src/selectors/coordinates.js +13 -0
  214. package/src/selectors/datasets.js +13 -0
  215. package/src/selectors/index.js +4 -0
  216. package/src/selectors/layers.js +32 -0
  217. package/src/selectors/scales.js +21 -0
  218. package/src/theme/defaults.js +13 -0
  219. package/types/extension.d.ts +14 -0
  220. package/types/index.d.ts +81 -0
  221. package/types/png.d.ts +14 -0
  222. package/types/program.d.ts +1096 -0
@@ -0,0 +1,326 @@
1
+ import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
2
+ import { ENCODING_CHANNELS } from "../core/vocabulary.js";
3
+
4
+ export const MARK_SELECTOR_OPERATORS = Object.freeze([
5
+ "eq",
6
+ "neq",
7
+ "gt",
8
+ "gte",
9
+ "lt",
10
+ "lte",
11
+ "oneOf",
12
+ "range",
13
+ "min",
14
+ "max"
15
+ ]);
16
+
17
+ export const MARK_SELECTOR_GRAINS = Object.freeze(["item", "stack"]);
18
+
19
+ export const MARK_GRAPHIC_PROPERTIES = Object.freeze([
20
+ "x",
21
+ "y",
22
+ "width",
23
+ "height",
24
+ "radius",
25
+ "x1",
26
+ "y1",
27
+ "x2",
28
+ "y2",
29
+ "fill",
30
+ "stroke",
31
+ "strokeWidth",
32
+ "opacity"
33
+ ]);
34
+
35
+ const COMPARISON_OPERATORS = new Set([
36
+ "eq",
37
+ "neq",
38
+ "gt",
39
+ "gte",
40
+ "lt",
41
+ "lte"
42
+ ]);
43
+ const ORDERED_COMPARISON_OPERATORS = new Set(["gt", "gte", "lt", "lte"]);
44
+ const RANK_OPERATORS = new Set(["min", "max"]);
45
+ const SELECTOR_KEYS = Object.freeze([
46
+ "grain",
47
+ "field",
48
+ "channel",
49
+ "property",
50
+ "op",
51
+ "value",
52
+ "values",
53
+ "min",
54
+ "max",
55
+ "inclusive",
56
+ "count",
57
+ "groupBy",
58
+ "ties"
59
+ ]);
60
+
61
+ function validateField(value, label = "Selector field") {
62
+ if (typeof value !== "string" || value.length === 0) {
63
+ throw new TypeError(`${label} must be a non-empty string.`);
64
+ }
65
+ return value;
66
+ }
67
+
68
+ function isOrderedValue(value) {
69
+ return Number.isFinite(value) || typeof value === "string";
70
+ }
71
+
72
+ function validateOrderedValue(value, label) {
73
+ if (!isOrderedValue(value)) {
74
+ throw new TypeError(`${label} must be a finite number or string.`);
75
+ }
76
+ return value;
77
+ }
78
+
79
+ function normalizeGroupBy(value) {
80
+ if (value === undefined) return undefined;
81
+ const fields = Array.isArray(value) ? value : [value];
82
+ if (fields.length === 0) {
83
+ throw new TypeError("Selector groupBy must contain at least one field.");
84
+ }
85
+ const normalized = fields.map(field => validateField(field, "Selector groupBy field"));
86
+ if (new Set(normalized).size !== normalized.length) {
87
+ throw new Error("Selector groupBy fields must be unique.");
88
+ }
89
+ return normalized;
90
+ }
91
+
92
+ export function normalizeMarkSelector(selector) {
93
+ if (!isPlainObject(selector)) {
94
+ throw new TypeError("Mark selector must be a plain object.");
95
+ }
96
+ const unknown = Object.keys(selector).find(key => !SELECTOR_KEYS.includes(key));
97
+ if (unknown !== undefined) {
98
+ throw new Error(`Unknown mark selector option "${unknown}".`);
99
+ }
100
+
101
+ const sources = ["field", "channel", "property"].filter(key =>
102
+ Object.hasOwn(selector, key)
103
+ );
104
+ if (sources.length !== 1) {
105
+ throw new Error(
106
+ "Mark selector requires exactly one of field, channel, or property."
107
+ );
108
+ }
109
+ const source = sources[0];
110
+ const sourceValue = source === "field"
111
+ ? validateField(selector.field)
112
+ : selector[source];
113
+ if (source === "channel" && !ENCODING_CHANNELS.includes(sourceValue)) {
114
+ throw new Error(`Unknown selector channel "${sourceValue}".`);
115
+ }
116
+ if (source === "property" && !MARK_GRAPHIC_PROPERTIES.includes(sourceValue)) {
117
+ throw new Error(`Unknown selector graphic property "${sourceValue}".`);
118
+ }
119
+ const grain = selector.grain ?? "item";
120
+ if (!MARK_SELECTOR_GRAINS.includes(grain)) {
121
+ throw new Error(`Unknown mark selector grain "${grain}".`);
122
+ }
123
+
124
+ if (!MARK_SELECTOR_OPERATORS.includes(selector.op)) {
125
+ throw new Error(`Unknown mark selector operator "${selector.op}".`);
126
+ }
127
+ const op = selector.op;
128
+ const allowedByOperation = RANK_OPERATORS.has(op)
129
+ ? new Set(["grain", source, "op", "count", "groupBy", "ties"])
130
+ : op === "range"
131
+ ? new Set(["grain", source, "op", "min", "max", "inclusive"])
132
+ : op === "oneOf"
133
+ ? new Set(["grain", source, "op", "values"])
134
+ : new Set(["grain", source, "op", "value"]);
135
+ const incompatible = Object.keys(selector).find(key => !allowedByOperation.has(key));
136
+ if (incompatible !== undefined) {
137
+ throw new Error(
138
+ `Mark selector operator "${op}" does not accept "${incompatible}".`
139
+ );
140
+ }
141
+
142
+ if (COMPARISON_OPERATORS.has(op)) {
143
+ if (!Object.hasOwn(selector, "value")) {
144
+ throw new TypeError(`Mark selector operator "${op}" requires value.`);
145
+ }
146
+ if (ORDERED_COMPARISON_OPERATORS.has(op)) {
147
+ validateOrderedValue(selector.value, "Ordered selector value");
148
+ }
149
+ return cloneAndFreeze({ grain, [source]: sourceValue, op, value: selector.value });
150
+ }
151
+
152
+ if (op === "oneOf") {
153
+ if (!Array.isArray(selector.values)) {
154
+ throw new TypeError("Mark selector oneOf values must be an array.");
155
+ }
156
+ return cloneAndFreeze({ grain, [source]: sourceValue, op, values: selector.values });
157
+ }
158
+
159
+ if (op === "range") {
160
+ const min = validateOrderedValue(selector.min, "Selector range min");
161
+ const max = validateOrderedValue(selector.max, "Selector range max");
162
+ if (typeof min !== typeof max) {
163
+ throw new TypeError("Selector range endpoints must have one type.");
164
+ }
165
+ if (min > max) {
166
+ throw new RangeError("Selector range min must not exceed max.");
167
+ }
168
+ if (selector.inclusive !== undefined && typeof selector.inclusive !== "boolean") {
169
+ throw new TypeError("Selector range inclusive must be a boolean.");
170
+ }
171
+ return cloneAndFreeze({
172
+ grain,
173
+ [source]: sourceValue,
174
+ op,
175
+ min,
176
+ max,
177
+ inclusive: selector.inclusive ?? true
178
+ });
179
+ }
180
+
181
+ const count = selector.count ?? 1;
182
+ if (!Number.isInteger(count) || count <= 0) {
183
+ throw new RangeError("Selector count must be a positive integer.");
184
+ }
185
+ const ties = selector.ties ?? "first";
186
+ if (!["first", "all"].includes(ties)) {
187
+ throw new Error(`Unknown selector ties policy "${ties}".`);
188
+ }
189
+ const groupBy = normalizeGroupBy(selector.groupBy);
190
+ return cloneAndFreeze({
191
+ grain,
192
+ [source]: sourceValue,
193
+ op,
194
+ count,
195
+ ...(groupBy === undefined ? {} : { groupBy }),
196
+ ties
197
+ });
198
+ }
199
+
200
+ function validateItems(items) {
201
+ if (!Array.isArray(items)) {
202
+ throw new TypeError("Selectable mark items must be an array.");
203
+ }
204
+ const keys = new Set();
205
+ for (const [index, item] of items.entries()) {
206
+ if (!isPlainObject(item)) {
207
+ throw new TypeError(`Selectable mark item ${index} must be a plain object.`);
208
+ }
209
+ if (typeof item.key !== "string" || item.key.length === 0) {
210
+ throw new TypeError(`Selectable mark item ${index} requires a non-empty key.`);
211
+ }
212
+ if (keys.has(item.key)) {
213
+ throw new Error(`Duplicate selectable mark item key "${item.key}".`);
214
+ }
215
+ keys.add(item.key);
216
+ if (
217
+ !isPlainObject(item.fields) ||
218
+ !isPlainObject(item.channels) ||
219
+ !isPlainObject(item.properties)
220
+ ) {
221
+ throw new TypeError(
222
+ `Selectable mark item "${item.key}" requires field, channel, and property value maps.`
223
+ );
224
+ }
225
+ }
226
+ }
227
+
228
+ function selectorValue(item, selector) {
229
+ if (selector.field !== undefined) return item.fields[selector.field];
230
+ if (selector.channel !== undefined) return item.channels[selector.channel];
231
+ return item.properties[selector.property];
232
+ }
233
+
234
+ function hasSelectorValue(item, selector) {
235
+ const values = selector.field !== undefined
236
+ ? item.fields
237
+ : selector.channel !== undefined
238
+ ? item.channels
239
+ : item.properties;
240
+ const key = selector.field ?? selector.channel ?? selector.property;
241
+ return Object.hasOwn(values, key) && values[key] !== undefined;
242
+ }
243
+
244
+ function comparable(value, operand) {
245
+ return (
246
+ Number.isFinite(value) && Number.isFinite(operand)
247
+ ) || (
248
+ typeof value === "string" && typeof operand === "string"
249
+ );
250
+ }
251
+
252
+ function matches(value, selector) {
253
+ if (selector.op === "eq") return value === selector.value;
254
+ if (selector.op === "neq") return value !== selector.value;
255
+ if (selector.op === "oneOf") return selector.values.includes(value);
256
+ if (selector.op === "range") {
257
+ if (!comparable(value, selector.min) || !comparable(value, selector.max)) {
258
+ return false;
259
+ }
260
+ return selector.inclusive
261
+ ? value >= selector.min && value <= selector.max
262
+ : value > selector.min && value < selector.max;
263
+ }
264
+ if (!comparable(value, selector.value)) return false;
265
+ if (selector.op === "gt") return value > selector.value;
266
+ if (selector.op === "gte") return value >= selector.value;
267
+ if (selector.op === "lt") return value < selector.value;
268
+ return value <= selector.value;
269
+ }
270
+
271
+ function groupKey(item, fields) {
272
+ return JSON.stringify(fields.map(field => item.fields[field]));
273
+ }
274
+
275
+ function rankGroup(items, selector) {
276
+ const eligible = items
277
+ .map((item, order) => ({ item, order, value: selectorValue(item, selector) }))
278
+ .filter(({ value }) => isOrderedValue(value));
279
+ if (eligible.length === 0) return [];
280
+
281
+ const valueType = typeof eligible[0].value;
282
+ const comparableItems = eligible.filter(({ value }) => typeof value === valueType);
283
+ const direction = selector.op === "min" ? 1 : -1;
284
+ comparableItems.sort((left, right) => {
285
+ if (left.value < right.value) return -1 * direction;
286
+ if (left.value > right.value) return 1 * direction;
287
+ return left.order - right.order;
288
+ });
289
+ if (selector.ties === "first" || comparableItems.length <= selector.count) {
290
+ return comparableItems.slice(0, selector.count);
291
+ }
292
+ const boundary = comparableItems[selector.count - 1].value;
293
+ return comparableItems.filter(({ value }, index) =>
294
+ index < selector.count || value === boundary
295
+ );
296
+ }
297
+
298
+ export function selectMarkItemKeys(items, selector) {
299
+ validateItems(items);
300
+ const normalized = normalizeMarkSelector(selector);
301
+ if (!RANK_OPERATORS.has(normalized.op)) {
302
+ return cloneAndFreeze(
303
+ items
304
+ .filter(item =>
305
+ hasSelectorValue(item, normalized) &&
306
+ matches(selectorValue(item, normalized), normalized)
307
+ )
308
+ .map(item => item.key)
309
+ );
310
+ }
311
+
312
+ const groups = new Map();
313
+ for (const item of items) {
314
+ const key = normalized.groupBy === undefined
315
+ ? "all"
316
+ : groupKey(item, normalized.groupBy);
317
+ const group = groups.get(key) ?? [];
318
+ group.push(item);
319
+ groups.set(key, group);
320
+ }
321
+ return cloneAndFreeze(
322
+ [...groups.values()].flatMap(group =>
323
+ rankGroup(group, normalized).map(({ item }) => item.key)
324
+ )
325
+ );
326
+ }
@@ -0,0 +1,208 @@
1
+ import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
2
+ import { validateKeys } from "../core/validation.js";
3
+
4
+ function colors(encoded) {
5
+ return cloneAndFreeze(
6
+ encoded.match(/.{6}/g).map(value => `#${value.toLowerCase()}`)
7
+ );
8
+ }
9
+
10
+ const DISCRETE = Object.freeze({
11
+ accent: colors("7fc97fbeaed4fdc086ffff99386cb0f0027fbf5b17666666"),
12
+ category10: colors("1f77b4ff7f0e2ca02cd627289467bd8c564be377c27f7f7fbcbd2217becf"),
13
+ category20: colors("1f77b4aec7e8ff7f0effbb782ca02c98df8ad62728ff98969467bdc5b0d58c564bc49c94e377c2f7b6d27f7f7fc7c7c7bcbd22dbdb8d17becf9edae5"),
14
+ category20b: colors("393b795254a36b6ecf9c9ede6379398ca252b5cf6bcedb9c8c6d31bd9e39e7ba52e7cb94843c39ad494ad6616be7969c7b4173a55194ce6dbdde9ed6"),
15
+ category20c: colors("3182bd6baed69ecae1c6dbefe6550dfd8d3cfdae6bfdd0a231a35474c476a1d99bc7e9c0756bb19e9ac8bcbddcdadaeb636363969696bdbdbdd9d9d9"),
16
+ observable10: colors("4269d0efb118ff725c6cc5b03ca951ff8ab7a463f297bbf59c6b4e9498a0"),
17
+ dark2: colors("1b9e77d95f027570b3e7298a66a61ee6ab02a6761d666666"),
18
+ paired: colors("a6cee31f78b4b2df8a33a02cfb9a99e31a1cfdbf6fff7f00cab2d66a3d9affff99b15928"),
19
+ pastel1: colors("fbb4aeb3cde3ccebc5decbe4fed9a6ffffcce5d8bdfddaecf2f2f2"),
20
+ pastel2: colors("b3e2cdfdcdaccbd5e8f4cae4e6f5c9fff2aef1e2cccccccc"),
21
+ set1: colors("e41a1c377eb84daf4a984ea3ff7f00ffff33a65628f781bf999999"),
22
+ set2: colors("66c2a5fc8d628da0cbe78ac3a6d854ffd92fe5c494b3b3b3"),
23
+ set3: colors("8dd3c7ffffb3bebadafb807280b1d3fdb462b3de69fccde5d9d9d9bc80bdccebc5ffed6f"),
24
+ tableau10: colors("4c78a8f58518e4575672b7b254a24beeca3bb279a2ff9da69d755dbab0ac"),
25
+ tableau20: colors("4c78a89ecae9f58518ffbf7954a24b88d27ab79a20f2cf5b43989483bcb6e45756ff9d9879706ebab0acd67195fcbfd2b279a2d6a5c99e765fd8b5a5")
26
+ });
27
+
28
+ const CONTINUOUS = Object.freeze({
29
+ blues: colors("cfe1f2bed8eca8cee58fc1de74b2d75ba3cf4592c63181bd206fb2125ca40a4a90"),
30
+ tealblues: colors("bce4d89dd3d181c3cb65b3c245a2b9368fae347da0306a932c5985"),
31
+ teals: colors("bbdfdfa2d4d58ac9c975bcbb61b0af4da5a43799982b8b8c1e7f7f127273006667"),
32
+ greens: colors("d3eecdc0e6baabdda594d3917bc77d60ba6c46ab5e329a512089430e7735036429"),
33
+ browns: colors("eedbbdecca96e9b97ae4a865dc9856d18954c7784cc0673fb85536ad44339f3632"),
34
+ oranges: colors("fdd8b3fdc998fdb87bfda55efc9244f87f2cf06b18e4580bd14904b93d029f3303"),
35
+ reds: colors("fdc9b4fcb49afc9e80fc8767fa7051f6573fec3f2fdc2a25c81b1db21218970b13"),
36
+ purples: colors("e2e1efd4d4e8c4c5e0b4b3d6a3a0cc928ec3827cb97566ae684ea25c3696501f8c"),
37
+ warmgreys: colors("dcd4d0cec5c1c0b8b4b3aaa7a59c9998908c8b827f7e7673726866665c5a59504e"),
38
+ greys: colors("e2e2e2d4d4d4c4c4c4b1b1b19d9d9d8888887575756262624d4d4d3535351e1e1e"),
39
+ viridis: colors("440154470e61481a6c482575472f7d443a834144873d4e8a39568c35608d31688e2d708e2a788e27818e23888e21918d1f988b1fa08822a8842ab07f35b77943bf7154c56866cc5d7ad1518fd744a5db36bcdf27d2e21be9e51afde725"),
40
+ magma: colors("0000040404130b0924150e3720114b2c11603b0f704a107957157e651a80721f817f24828c29819a2e80a8327db6377ac43c75d1426fde4968e95462f1605df76f5cfa7f5efc8f65fe9f6dfeaf78febf84fece91fddea0fcedaffcfdbf"),
41
+ inferno: colors("0000040403130c0826170c3b240c4f330a5f420a68500d6c5d126e6b176e781c6d86216b932667a12b62ae305cbb3755c73e4cd24644dd513ae65c30ed6925f3771af8850ffb9506fca50afcb519fac62df6d645f2e661f3f484fcffa4"),
42
+ plasma: colors("0d088723069033059742039d5002a25d01a66a00a87801a88405a7900da49c179ea72198b12a90ba3488c33d80cb4779d35171da5a69e16462e76e5bed7953f2834cf68f44fa9a3dfca636fdb32ffec029fcce25f9dc24f5ea27f0f921"),
43
+ cividis: colors("00205100235800265d002961012b65042e670831690d346b11366c16396d1c3c6e213f6e26426e2c456e31476e374a6e3c4d6e42506e47536d4c566d51586e555b6e5a5e6e5e616e62646f66676f6a6a706e6d717270717573727976737c79747f7c75827f758682768985778c8877908b78938e789691789a94789e9778a19b78a59e77a9a177aea575b2a874b6ab73bbaf71c0b26fc5b66dc9b96acebd68d3c065d8c462ddc85fe2cb5ce7cf58ebd355f0d652f3da4ff7de4cfae249fce647"),
44
+ turbo: colors("23171b32204a3e2a71453493493eae4b49c54a53d7485ee44569ee4074f53c7ff8378af93295f72e9ff42ba9ef28b3e926bce125c5d925cdcf27d5c629dcbc2de3b232e9a738ee9d3ff39347f68950f9805afc7765fd6e70fe667cfd5e88fc5795fb51a1f84badf545b9f140c5ec3cd0e637dae034e4d931ecd12ef4c92bfac029ffb626ffad24ffa223ff9821ff8d1fff821dff771cfd6c1af76118f05616e84b14df4111d5380fcb2f0dc0260ab61f07ac1805a313029b0f00950c00910b00"),
45
+ bluegreen: colors("d5efedc1e8e0a7ddd18bd2be70c6a958ba9144ad77319c5d2089460e7736036429"),
46
+ bluepurple: colors("ccddecbad0e4a8c2dd9ab0d4919cc98d85be8b6db28a55a6873c99822287730f71"),
47
+ goldgreen: colors("f4d166d5ca60b6c35c98bb597cb25760a6564b9c533f8f4f33834a257740146c36"),
48
+ goldorange: colors("f4d166f8be5cf8aa4cf5983bf3852aef701be2621fd65322c54923b142239e3a26"),
49
+ goldred: colors("f4d166f6be59f9aa51fc964ef6834bee734ae56249db5247cf4244c43141b71d3e"),
50
+ greenblue: colors("d3eecec5e8c3b1e1bb9bd8bb82cec269c2ca51b2cd3c9fc7288abd1675b10b60a1"),
51
+ orangered: colors("fddcaffdcf9bfdc18afdad77fb9562f67d53ee6545e24932d32d1ebf130da70403"),
52
+ purplebluegreen: colors("dbd8eac8cee4b0c3de93b7d872acd1549fc83892bb1c88a3097f8702736b016353"),
53
+ purpleblue: colors("dbdaebc8cee4b1c3de97b7d87bacd15b9fc93a90c01e7fb70b70ab056199045281"),
54
+ purplered: colors("dcc9e2d3b3d7ce9eccd186c0da6bb2e14da0e23189d91e6fc61159ab07498f023a"),
55
+ redpurple: colors("fccfccfcbec0faa9b8f98faff571a5ec539ddb3695c41b8aa908808d0179700174"),
56
+ yellowgreenblue: colors("eff9bddbf1b4bde5b594d5b969c5be45b4c22c9ec02182b82163aa23479c1c3185"),
57
+ yellowgreen: colors("e4f4acd1eca0b9e2949ed68880c97c62bb6e47aa5e3297502083440e723b036034"),
58
+ yelloworangebrown: colors("feeaa1fedd84fecc63feb746fca031f68921eb7215db5e0bc54c05ab3d038f3204"),
59
+ yelloworangered: colors("fee087fed16ffebd59fea849fd903efc7335f9522bee3423de1b20ca0b22af0225"),
60
+ darkblue: colors("3232322d46681a5c930074af008cbf05a7ce25c0dd38daed50f3faffffff"),
61
+ darkgold: colors("3c3c3c584b37725e348c7631ae8b2bcfa424ecc31ef9de30fff184ffffff"),
62
+ darkgreen: colors("3a3a3a215748006f4d048942489e4276b340a6c63dd2d836ffeb2cffffaa"),
63
+ darkmulti: colors("3737371f5287197d8c29a86995ce3fffe800ffffff"),
64
+ darkred: colors("3434347036339e3c38cc4037e75d1eec8620eeab29f0ce32ffeb2c"),
65
+ lightgreyred: colors("efe9e6e1dad7d5cbc8bdb9bbaea9cd967ddc7b43e15f19df4011dc000b"),
66
+ lightgreyteal: colors("e4eaead6dcddc8ced2b7c2c7a6b4bc64b0bf22a6c32295c11f85be1876bc"),
67
+ lightmulti: colors("e0f1f2c4e9d0b0de9fd0e181f6e072f6c053f3993ef77440ef4a3c"),
68
+ lightorange: colors("f2e7daf7d5baf9c499fab184fa9c73f68967ef7860e8645bde515bd43d5b"),
69
+ lighttealblue: colors("e3e9e0c0dccf9aceca7abfc859afc0389fb9328dad2f7ca0276b95255988"),
70
+ blueorange: colors("134b852f78b35da2cb9dcae1d2e5eff2f0ebfce0bafbbf74e8932fc5690d994a07"),
71
+ brownbluegreen: colors("704108a0651ac79548e3c78af3e6c6eef1eac9e9e48ed1c74da79e187a72025147"),
72
+ purplegreen: colors("5b1667834792a67fb6c9aed3e6d6e8eff0efd9efd5aedda971bb75368e490e5e29"),
73
+ pinkyellowgreen: colors("8e0152c0267edd72adf0b3d6faddedf5f3efe1f2cab6de8780bb474f9125276419"),
74
+ purpleorange: colors("4114696647968f83b7b9b4d6dadbebf3eeeafce0bafbbf74e8932fc5690d994a07"),
75
+ redblue: colors("8c0d25bf363adf745ef4ae91fbdbc9f2efeed2e5ef9dcae15da2cb2f78b3134b85"),
76
+ redgrey: colors("8c0d25bf363adf745ef4ae91fcdccbfaf4f1e2e2e2c0c0c0969696646464343434"),
77
+ redyellowblue: colors("a50026d4322cf16e43fcac64fedd90faf8c1dcf1ecabd6e875abd04a74b4313695"),
78
+ redyellowgreen: colors("a50026d4322cf16e43fcac63fedd8df9f7aed7ee8ea4d86e64bc6122964f006837"),
79
+ spectral: colors("9e0142d13c4bf0704afcac63fedd8dfbf8b0e0f3a1a9dda269bda94288b55e4fa2"),
80
+ rainbow: colors("6e40aa883eb1a43db3bf3cafd83fa4ee4395fe4b83ff576eff6659ff7847ff8c38f3a130e2b72fcfcc36bee044aff05b8ff4576ff65b52f6673af27828ea8d1ddfa319d0b81cbecb23abd82f96e03d82e14c6edb5a5dd0664dbf6e40aa"),
81
+ sinebow: colors("ff4040fc582af47218e78d0bd5a703bfbf00a7d5038de70b72f41858fc2a40ff402afc5818f4720be78d03d5a700bfbf03a7d50b8de71872f42a58fc4040ff582afc7218f48d0be7a703d5bf00bfd503a7e70b8df41872fc2a58ff4040")
82
+ });
83
+
84
+ export const PALETTE_NAMES = cloneAndFreeze([
85
+ "accent",
86
+ "category10", "category20", "category20b", "category20c",
87
+ "observable10",
88
+ "dark2", "paired", "pastel1", "pastel2",
89
+ "set1", "set2", "set3",
90
+ "tableau10", "tableau20",
91
+ "blues", "tealblues", "teals", "greens", "browns",
92
+ "oranges", "reds", "purples", "warmgreys", "greys",
93
+ "viridis", "magma", "inferno", "plasma", "cividis", "turbo",
94
+ "bluegreen", "bluepurple",
95
+ "goldgreen", "goldorange", "goldred",
96
+ "greenblue", "orangered",
97
+ "purplebluegreen", "purpleblue", "purplered", "redpurple",
98
+ "yellowgreenblue", "yellowgreen", "yelloworangebrown", "yelloworangered",
99
+ "darkblue", "darkgold", "darkgreen", "darkmulti", "darkred",
100
+ "lightgreyred", "lightgreyteal", "lightmulti", "lightorange", "lighttealblue",
101
+ "blueorange", "brownbluegreen", "purplegreen", "pinkyellowgreen",
102
+ "purpleorange", "redblue", "redgrey",
103
+ "redyellowblue", "redyellowgreen", "spectral",
104
+ "rainbow", "sinebow"
105
+ ]);
106
+
107
+ const PALETTE_SET = new Set(PALETTE_NAMES);
108
+
109
+ export function validatePaletteName(name) {
110
+ if (typeof name !== "string" || !PALETTE_SET.has(name)) {
111
+ throw new Error(`Unknown palette "${name}".`);
112
+ }
113
+ return name;
114
+ }
115
+
116
+ export function normalizePalette(value) {
117
+ if (typeof value === "string") return { name: validatePaletteName(value) };
118
+ if (!isPlainObject(value)) {
119
+ throw new TypeError("Palette must be a name or a plain object.");
120
+ }
121
+ validateKeys(value, ["name", "count", "extent"], "palette");
122
+ const name = validatePaletteName(value.name);
123
+ const normalized = { name };
124
+ if (value.count !== undefined) {
125
+ if (!Number.isInteger(value.count) || value.count <= 0) {
126
+ throw new RangeError("Palette count must be a positive integer.");
127
+ }
128
+ normalized.count = value.count;
129
+ }
130
+ if (value.extent !== undefined) {
131
+ if (DISCRETE[name] !== undefined) {
132
+ throw new Error(`Categorical palette "${name}" does not support extent.`);
133
+ }
134
+ if (
135
+ !Array.isArray(value.extent) ||
136
+ value.extent.length !== 2 ||
137
+ !value.extent.every(item => Number.isFinite(item) && item >= 0 && item <= 1) ||
138
+ value.extent[0] === value.extent[1]
139
+ ) {
140
+ throw new RangeError(
141
+ "Palette extent must contain two distinct values from 0 to 1."
142
+ );
143
+ }
144
+ normalized.extent = cloneAndFreeze(value.extent);
145
+ }
146
+ return cloneAndFreeze(normalized);
147
+ }
148
+
149
+ function channel(hex, offset) {
150
+ return Number.parseInt(hex.slice(offset, offset + 2), 16);
151
+ }
152
+
153
+ function interpolateColor(left, right, amount) {
154
+ const channels = [1, 3, 5].map(offset =>
155
+ Math.round(channel(left, offset) +
156
+ (channel(right, offset) - channel(left, offset)) * amount)
157
+ .toString(16)
158
+ .padStart(2, "0")
159
+ );
160
+ return `#${channels.join("")}`;
161
+ }
162
+
163
+ function sample(colorsValue, position) {
164
+ const bounded = Math.max(0, Math.min(1, position));
165
+ const scaled = bounded * (colorsValue.length - 1);
166
+ const left = Math.floor(scaled);
167
+ const right = Math.min(colorsValue.length - 1, left + 1);
168
+ return interpolateColor(colorsValue[left], colorsValue[right], scaled - left);
169
+ }
170
+
171
+ export function resolvePalette(value, domainCount) {
172
+ const palette = normalizePalette(value);
173
+ const discrete = DISCRETE[palette.name];
174
+ if (discrete !== undefined) {
175
+ const count = palette.count ?? discrete.length;
176
+ return cloneAndFreeze(
177
+ Array.from({ length: count }, (_, index) => discrete[index % discrete.length])
178
+ );
179
+ }
180
+ if (!Number.isInteger(domainCount) || domainCount <= 0) {
181
+ throw new Error("Continuous palette sampling requires a positive domain count.");
182
+ }
183
+ const source = CONTINUOUS[palette.name];
184
+ const count = palette.count ?? domainCount;
185
+ const extent = palette.extent ?? [0, 1];
186
+ return cloneAndFreeze(Array.from({ length: count }, (_, index) => {
187
+ const unit = count === 1 ? 0.5 : index / (count - 1);
188
+ return sample(source, extent[0] + (extent[1] - extent[0]) * unit);
189
+ }));
190
+ }
191
+
192
+ export function resolveContinuousPalette(value, count = 31) {
193
+ if (!Number.isInteger(count) || count < 2) {
194
+ throw new RangeError("Continuous palette count must be an integer of at least 2.");
195
+ }
196
+ const palette = normalizePalette(value);
197
+ if (palette.count !== undefined) {
198
+ throw new Error("Continuous palette does not accept count.");
199
+ }
200
+ const discrete = DISCRETE[palette.name];
201
+ if (discrete !== undefined) return discrete;
202
+ return resolvePalette({ ...palette, count }, count);
203
+ }
204
+
205
+ export function paletteFamily(name) {
206
+ const validated = validatePaletteName(name);
207
+ return DISCRETE[validated] === undefined ? "continuous" : "categorical";
208
+ }
@@ -0,0 +1,86 @@
1
+ import { freezeOwned, isPlainObject } from "../core/immutable.js";
2
+
3
+ const COMMAND_KEYS = Object.freeze({
4
+ M: Object.freeze(["op", "x", "y"]),
5
+ L: Object.freeze(["op", "x", "y"]),
6
+ C: Object.freeze(["op", "x1", "y1", "x2", "y2", "x", "y"]),
7
+ Z: Object.freeze(["op"])
8
+ });
9
+
10
+ function validateCommand(command, index) {
11
+ if (!isPlainObject(command) || !Object.hasOwn(COMMAND_KEYS, command.op)) {
12
+ throw new TypeError(
13
+ `path.commands[${index}] must be an M, L, C, or Z command.`
14
+ );
15
+ }
16
+ const expected = COMMAND_KEYS[command.op];
17
+ const keys = Object.keys(command);
18
+ if (
19
+ keys.length !== expected.length ||
20
+ !expected.every(key => Object.hasOwn(command, key))
21
+ ) {
22
+ throw new TypeError(
23
+ `path.commands[${index}] has invalid properties for ${command.op}.`
24
+ );
25
+ }
26
+ for (const key of expected) {
27
+ if (key !== "op" && !Number.isFinite(command[key])) {
28
+ throw new TypeError(
29
+ `path.commands[${index}].${key} must be a finite number.`
30
+ );
31
+ }
32
+ }
33
+ return command;
34
+ }
35
+
36
+ export function validatePathCommands(commands) {
37
+ if (!Array.isArray(commands) || commands.length < 2) {
38
+ throw new TypeError("path.commands must contain at least two commands.");
39
+ }
40
+ commands.forEach(validateCommand);
41
+ if (commands[0].op !== "M") {
42
+ throw new Error("path.commands must start with M.");
43
+ }
44
+ if (commands.slice(1).some(command => command.op === "M")) {
45
+ throw new Error("path.commands supports one subpath and only one initial M.");
46
+ }
47
+ if (!commands.some(command => command.op === "L" || command.op === "C")) {
48
+ throw new Error("path.commands requires at least one L or C segment.");
49
+ }
50
+ const closeIndex = commands.findIndex(command => command.op === "Z");
51
+ if (closeIndex !== -1 && closeIndex !== commands.length - 1) {
52
+ throw new Error("path.commands Z must be the final command.");
53
+ }
54
+ return commands;
55
+ }
56
+
57
+ function validatePoints(points) {
58
+ if (
59
+ !Array.isArray(points) ||
60
+ points.length < 2 ||
61
+ !points.every(point =>
62
+ isPlainObject(point) &&
63
+ Object.keys(point).length === 2 &&
64
+ Number.isFinite(point.x) &&
65
+ Number.isFinite(point.y)
66
+ )
67
+ ) {
68
+ throw new TypeError(
69
+ "Linear path points must contain at least two finite { x, y } objects."
70
+ );
71
+ }
72
+ return points;
73
+ }
74
+
75
+ export function buildLinearPathCommands(points, { close = false } = {}) {
76
+ validatePoints(points);
77
+ if (typeof close !== "boolean") {
78
+ throw new TypeError("Linear path close must be a boolean.");
79
+ }
80
+ const commands = [
81
+ { op: "M", x: points[0].x, y: points[0].y },
82
+ ...points.slice(1).map(point => ({ op: "L", x: point.x, y: point.y }))
83
+ ];
84
+ if (close) commands.push({ op: "Z" });
85
+ return freezeOwned(commands.map(command => freezeOwned(command)));
86
+ }