ggaction 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +6 -2
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/intent-taxonomy.json +1 -1
  6. package/knowledge/mcp-resources.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +3 -3
  9. package/src/BasicChartProgram.js +1 -1
  10. package/src/accessibility.js +3 -2
  11. package/src/actions/boxPlots/create.js +14 -4
  12. package/src/actions/boxPlots/edit.js +39 -33
  13. package/src/actions/boxPlots/materialize.js +7 -3
  14. package/src/actions/boxPlots/options.js +37 -11
  15. package/src/actions/canvas/actions.js +70 -95
  16. package/src/actions/canvas/fitting.js +20 -1
  17. package/src/actions/canvas/index.js +3 -0
  18. package/src/actions/canvas/plotBorder.js +28 -0
  19. package/src/actions/charts/area.js +2 -2
  20. package/src/actions/charts/bar.js +34 -7
  21. package/src/actions/charts/ecdf.js +4 -18
  22. package/src/actions/charts/endpoints.js +4 -18
  23. package/src/actions/charts/heatmap.js +8 -7
  24. package/src/actions/charts/index.js +2 -0
  25. package/src/actions/charts/interval-regression.js +2 -2
  26. package/src/actions/charts/line.js +1 -1
  27. package/src/actions/charts/pie.js +3 -47
  28. package/src/actions/charts/radial.js +34 -12
  29. package/src/actions/charts/scatter.js +16 -2
  30. package/src/actions/charts/shared.js +2 -2
  31. package/src/actions/charts/text.js +35 -0
  32. package/src/actions/composition/actions.js +6 -2
  33. package/src/actions/data/box.js +2 -1
  34. package/src/actions/data/derived.js +11 -1
  35. package/src/actions/data/edit.js +3 -1
  36. package/src/actions/data/index.js +4 -0
  37. package/src/actions/data/regression.js +25 -16
  38. package/src/actions/data/regressionStatistics.js +73 -0
  39. package/src/actions/data/shared.js +8 -4
  40. package/src/actions/data/stack.js +3 -1
  41. package/src/actions/distributions/revision.js +24 -18
  42. package/src/actions/encodings/appearance.js +37 -29
  43. package/src/actions/encodings/barWidth.js +36 -11
  44. package/src/actions/encodings/channels.js +0 -1
  45. package/src/actions/encodings/color/continuous.js +1 -1
  46. package/src/actions/encodings/color/index.js +1 -1
  47. package/src/actions/encodings/color/policy.js +10 -3
  48. package/src/actions/encodings/density/resolve.js +1 -6
  49. package/src/actions/encodings/position/policies/arc.js +12 -10
  50. package/src/actions/encodings/position/policies/area.js +3 -3
  51. package/src/actions/encodings/position/policies/bar.js +15 -6
  52. package/src/actions/encodings/position/policies/line.js +17 -6
  53. package/src/actions/encodings/position/resolve.js +3 -2
  54. package/src/actions/encodings/ranged.js +17 -0
  55. package/src/actions/encodings/remove.js +14 -8
  56. package/src/actions/encodings/seriesLayout.js +1 -0
  57. package/src/actions/encodings/shared.js +26 -24
  58. package/src/actions/encodings/text.js +6 -3
  59. package/src/actions/errorBands/create.js +2 -2
  60. package/src/actions/errorBands/edit.js +2 -1
  61. package/src/actions/facets/actions.js +176 -247
  62. package/src/actions/facets/guides.js +2 -1
  63. package/src/actions/gradientPlots/resolve.js +2 -17
  64. package/src/actions/guides/applicability.js +1 -1
  65. package/src/actions/guides/axes/axes.js +30 -11
  66. package/src/actions/guides/axes/axis.js +72 -10
  67. package/src/actions/guides/axes/components.js +13 -13
  68. package/src/actions/guides/axes/edit.js +36 -24
  69. package/src/actions/guides/axes/labels.js +155 -71
  70. package/src/actions/guides/axes/lines.js +27 -24
  71. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  72. package/src/actions/guides/axes/parallel/policy.js +8 -7
  73. package/src/actions/guides/axes/remove.js +13 -8
  74. package/src/actions/guides/axes/tickGroups.js +11 -7
  75. package/src/actions/guides/axes/ticks.js +36 -24
  76. package/src/actions/guides/axes/titles.js +107 -69
  77. package/src/actions/guides/basic.js +2 -0
  78. package/src/actions/guides/facadeAxes.js +33 -24
  79. package/src/actions/guides/facadeLegend.js +12 -4
  80. package/src/actions/guides/grids/grid.js +8 -2
  81. package/src/actions/guides/grids/resolve.js +102 -58
  82. package/src/actions/guides/legends/blocks.js +46 -20
  83. package/src/actions/guides/legends/categorical/actions.js +95 -19
  84. package/src/actions/guides/legends/categorical/components.js +15 -18
  85. package/src/actions/guides/legends/categorical/index.js +8 -0
  86. package/src/actions/guides/legends/categorical/layout.js +45 -20
  87. package/src/actions/guides/legends/categorical/options.js +39 -22
  88. package/src/actions/guides/legends/categorical/recipes.js +7 -3
  89. package/src/actions/guides/legends/categorical/resolve.js +11 -4
  90. package/src/actions/guides/legends/categorical/symbols.js +38 -18
  91. package/src/actions/guides/legends/categorical/validation.js +1 -6
  92. package/src/actions/guides/legends/colorSize.js +110 -0
  93. package/src/actions/guides/legends/combine.js +37 -0
  94. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  95. package/src/actions/guides/legends/continuous/common.js +1 -447
  96. package/src/actions/guides/legends/continuous/gradient.js +73 -46
  97. package/src/actions/guides/legends/continuous/interval.js +4 -4
  98. package/src/actions/guides/legends/continuous/opacity.js +33 -21
  99. package/src/actions/guides/legends/creation.js +2 -0
  100. package/src/actions/guides/legends/edit.js +1 -604
  101. package/src/actions/guides/legends/focused.js +8 -7
  102. package/src/actions/guides/legends/group.js +1 -0
  103. package/src/actions/guides/legends/lane.js +1 -516
  104. package/src/actions/guides/legends/lifecycle.js +2 -1
  105. package/src/actions/guides/legends/remove.js +9 -3
  106. package/src/actions/guides/legends/sampling.js +1 -1
  107. package/src/actions/guides/legends/size.js +65 -47
  108. package/src/actions/guides/legends/strokeWidth.js +13 -29
  109. package/src/actions/guides/legends/target.js +2 -79
  110. package/src/actions/guides/legends/transition.js +44 -11
  111. package/src/actions/guides/polar/axes/labels.js +4 -2
  112. package/src/actions/guides/polar/axes/titles.js +7 -5
  113. package/src/actions/guides/polar/resolve.js +6 -14
  114. package/src/actions/guides/tickValues.js +8 -5
  115. package/src/actions/index.js +16 -16
  116. package/src/actions/marks/arc/actions.js +3 -10
  117. package/src/actions/marks/area/actions.js +8 -5
  118. package/src/actions/marks/area/materialize.js +8 -9
  119. package/src/actions/marks/bar/create.js +10 -4
  120. package/src/actions/marks/bar/edit.js +29 -14
  121. package/src/actions/marks/bar/materialize.js +15 -7
  122. package/src/actions/marks/line/actions.js +11 -4
  123. package/src/actions/marks/line/materialize.js +9 -5
  124. package/src/actions/marks/point/edit.js +1 -1
  125. package/src/actions/marks/point/materialize.js +5 -1
  126. package/src/actions/marks/rect/actions.js +3 -3
  127. package/src/actions/marks/remove.js +12 -10
  128. package/src/actions/marks/rule/actions.js +7 -4
  129. package/src/actions/marks/text/actions.js +17 -5
  130. package/src/actions/marks/text/layout.js +3 -0
  131. package/src/actions/primitives/createGraphics.js +3 -2
  132. package/src/actions/primitives/editGraphics.js +15 -11
  133. package/src/actions/primitives/graphicProperties.js +1 -6
  134. package/src/actions/primitives/semanticAction.js +27 -1
  135. package/src/actions/primitives/semanticValidation/guide.js +11 -1
  136. package/src/actions/primitives/semanticValidation/layer.js +8 -0
  137. package/src/actions/regression/create.js +1 -1
  138. package/src/actions/regression/edit.js +18 -6
  139. package/src/actions/regression/resolve.js +2 -6
  140. package/src/actions/scales/channels.js +9 -4
  141. package/src/actions/scales/consumers/families.js +16 -5
  142. package/src/actions/scales/create.js +10 -5
  143. package/src/actions/scales/definitions.js +1 -425
  144. package/src/actions/scales/edit.js +3 -3
  145. package/src/actions/scales/editPolicy.js +12 -3
  146. package/src/actions/scales/materialize.js +1 -1
  147. package/src/actions/scales/patch.js +1 -1
  148. package/src/actions/scales/preview.js +21 -1
  149. package/src/actions/scales/quantitativeColor.js +8 -3
  150. package/src/actions/selection/actions.js +1 -13
  151. package/src/actions/theme/reconcile.js +126 -45
  152. package/src/actions/theme/state.js +12 -0
  153. package/src/actions/titles/actions.js +5 -18
  154. package/src/actions/titles/resolve.js +41 -19
  155. package/src/actions/violinPlots/edit.js +3 -20
  156. package/src/core/compositionState.js +6 -3
  157. package/src/core/font.js +9 -2
  158. package/src/core/graphicProperties.js +1 -0
  159. package/src/core/optionRequirements.js +1 -0
  160. package/src/core/programState.js +4 -0
  161. package/src/core/resourceReferences.js +5 -2
  162. package/src/core/textMetricProfile.js +3 -2
  163. package/src/core/textMetrics.js +41 -4
  164. package/src/core/validation.js +35 -0
  165. package/src/core/vocabulary.js +7 -0
  166. package/src/grammar/aggregate.js +4 -18
  167. package/src/grammar/arcs.js +1 -386
  168. package/src/grammar/areaPaint.js +10 -0
  169. package/src/grammar/areaSeries.js +18 -10
  170. package/src/grammar/axisTextPlacement.js +1 -0
  171. package/src/grammar/bars/geometry.js +30 -3
  172. package/src/grammar/bars/policy.js +12 -1
  173. package/src/grammar/bin.js +2 -12
  174. package/src/grammar/bin2d.js +1 -6
  175. package/src/grammar/boxPlot.js +56 -9
  176. package/src/grammar/categoryOrder.js +1 -6
  177. package/src/grammar/colorSizeLegend.js +39 -0
  178. package/src/grammar/complete.js +15 -19
  179. package/src/grammar/computed.js +115 -26
  180. package/src/grammar/curveCommands.js +29 -16
  181. package/src/grammar/datasetSchema.js +13 -9
  182. package/src/grammar/displayLabels.js +36 -0
  183. package/src/grammar/ecdf.js +2 -9
  184. package/src/grammar/facets/dependencies.js +6 -2
  185. package/src/grammar/facets/guides.js +58 -30
  186. package/src/grammar/facets/index.js +2 -5
  187. package/src/grammar/facets/legendIntent.js +65 -0
  188. package/src/grammar/fold.js +2 -12
  189. package/src/grammar/gradientProfile.js +1 -6
  190. package/src/grammar/guideDash.js +1 -0
  191. package/src/grammar/impute.js +21 -48
  192. package/src/grammar/interval.js +2 -9
  193. package/src/grammar/legendBlocks.js +1 -0
  194. package/src/grammar/legendBorder.js +9 -0
  195. package/src/grammar/legendClip.js +19 -0
  196. package/src/grammar/legendCombinations.js +45 -0
  197. package/src/grammar/legendGradient.js +27 -0
  198. package/src/grammar/legendGroups.js +1 -0
  199. package/src/grammar/legendOpacity.js +6 -0
  200. package/src/grammar/legendTitleStyle.js +22 -0
  201. package/src/grammar/lineSeries.js +32 -17
  202. package/src/grammar/markLabels.js +5 -2
  203. package/src/grammar/normalize.js +4 -54
  204. package/src/grammar/numeric.js +1 -290
  205. package/src/grammar/parallelCoordinates.js +1 -6
  206. package/src/grammar/parseTime.js +41 -0
  207. package/src/grammar/pathCommands.js +11 -4
  208. package/src/grammar/pointShapes.js +16 -2
  209. package/src/grammar/polar.js +1 -184
  210. package/src/grammar/positionCompatibility.js +3 -3
  211. package/src/grammar/regression/derive.js +16 -2
  212. package/src/grammar/regression/models.js +142 -54
  213. package/src/grammar/regression/parameters.js +23 -4
  214. package/src/grammar/regression/statistics.js +64 -0
  215. package/src/grammar/roundedRect.js +42 -54
  216. package/src/grammar/scales/appearance.js +1 -206
  217. package/src/grammar/scales/color.js +1 -382
  218. package/src/grammar/scales/colorConsumers.js +5 -2
  219. package/src/grammar/scales/definition.js +1 -197
  220. package/src/grammar/scales/discretized.js +1 -195
  221. package/src/grammar/scales/size.js +1 -456
  222. package/src/grammar/scales/temporal.js +2 -13
  223. package/src/grammar/scales/transformed.js +1 -383
  224. package/src/grammar/scales/types.js +1 -1
  225. package/src/grammar/scales/validation.js +1 -66
  226. package/src/grammar/schemas/concreteGraphic.js +14 -0
  227. package/src/grammar/schemas/graphic.js +6 -0
  228. package/src/grammar/schemas/graphicBounds.js +31 -7
  229. package/src/grammar/schemas/semanticPath.js +19 -3
  230. package/src/grammar/stack.js +2 -10
  231. package/src/grammar/statisticalReference.js +1 -6
  232. package/src/grammar/summary.js +4 -27
  233. package/src/grammar/text.js +19 -0
  234. package/src/grammar/timeUnit.js +22 -11
  235. package/src/grammar/transformKeys.js +47 -0
  236. package/src/grammar/transformTopology.js +1 -0
  237. package/src/grammar/transforms.js +15 -2
  238. package/src/grammar/valueFormat.js +17 -5
  239. package/src/grammar/weightedStatistics.js +1 -6
  240. package/src/grammar/window.js +4 -58
  241. package/src/inspectionDescriptors.js +1 -1
  242. package/src/layout/canvas.js +1 -110
  243. package/src/layout/facets.js +1 -244
  244. package/src/layout/guideCollisions.js +9 -1
  245. package/src/layout/labels.js +1 -0
  246. package/src/layout/legend.js +13 -0
  247. package/src/layout/legendGroup.js +1 -0
  248. package/src/layout/legendItems.js +59 -15
  249. package/src/layout/legendLane.js +1 -441
  250. package/src/layout/legendText.js +1 -0
  251. package/src/layout/title.js +35 -13
  252. package/src/materialization/bars/aggregate.js +11 -5
  253. package/src/materialization/bars/histogram.js +4 -1
  254. package/src/materialization/bars/ranged.js +19 -6
  255. package/src/materialization/bars/resolve.js +22 -1
  256. package/src/materialization/compositionSnapshot.js +2 -7
  257. package/src/materialization/coordinateBounds.js +21 -0
  258. package/src/materialization/dependencies.js +4 -44
  259. package/src/materialization/facetGuides/edit.js +30 -0
  260. package/src/materialization/facetGuides/layout.js +1 -0
  261. package/src/materialization/facetGuides/owner.js +125 -0
  262. package/src/materialization/facetGuides/ownerLayout.js +71 -0
  263. package/src/materialization/facetGuides/placement.js +71 -14
  264. package/src/materialization/facetGuides/preparation.js +22 -7
  265. package/src/materialization/facetHeaders.js +1 -347
  266. package/src/materialization/facets.js +1 -185
  267. package/src/materialization/graphicHierarchy.js +5 -1
  268. package/src/materialization/graphicTranslation.js +73 -0
  269. package/src/materialization/guides/layout.js +1 -57
  270. package/src/materialization/guides/resources.js +1 -123
  271. package/src/materialization/layout.js +53 -0
  272. package/src/materialization/legendBorder.js +28 -0
  273. package/src/materialization/legendClip.js +76 -0
  274. package/src/materialization/legendGroups.js +1 -0
  275. package/src/materialization/legendText.js +1 -0
  276. package/src/materialization/legends.js +2 -2
  277. package/src/materialization/marks/capabilities.js +5 -6
  278. package/src/materialization/rect.js +1 -1
  279. package/src/materialization/scaleGuideDependencies.js +14 -15
  280. package/src/materialization/scales/map.js +6 -0
  281. package/src/materialization/scales/policies/arc.js +8 -7
  282. package/src/materialization/scales/policies/bar.js +19 -7
  283. package/src/materialization/scales/policies/series.js +2 -1
  284. package/src/materialization/scales/resolve.js +7 -3
  285. package/src/materialization/selection/items/arc.js +1 -1
  286. package/src/materialization/selection/items/bar.js +7 -6
  287. package/src/materialization/selection/items/path.js +7 -6
  288. package/src/materialization/selection/items/rect.js +2 -2
  289. package/src/materialization/text.js +13 -4
  290. package/src/materialization/textLines.js +15 -0
  291. package/src/materialization/typography.js +10 -2
  292. package/src/persistence/codec.js +37 -6
  293. package/src/persistence/validation.js +220 -0
  294. package/src/persistence.js +19 -6
  295. package/src/renderers/canvas/circle.js +6 -3
  296. package/src/renderers/canvas/fill.js +7 -1
  297. package/src/renderers/canvas/index.js +22 -11
  298. package/src/renderers/canvas/isolated.js +166 -0
  299. package/src/renderers/canvas/limits.js +3 -0
  300. package/src/renderers/canvas/line.js +1 -0
  301. package/src/renderers/canvas/path.js +1 -1
  302. package/src/renderers/canvas/rect.js +11 -8
  303. package/src/renderers/canvas/stroke.js +2 -1
  304. package/src/renderers/canvas/text.js +5 -3
  305. package/src/renderers/pdf.js +2 -2
  306. package/src/renderers/png.js +2 -2
  307. package/src/renderers/svg.js +14 -4
  308. package/src/selectors/axes.js +1 -0
  309. package/src/selectors/graphicCapacity.js +38 -0
  310. package/src/selectors/layers.js +19 -0
  311. package/src/theme/defaults.js +8 -2
  312. package/src/version.js +1 -1
  313. package/types/basic.d.ts +3 -0
  314. package/types/index.d.ts +12 -0
  315. package/types/program.d.ts +536 -129
@@ -0,0 +1,45 @@
1
+ import { sameOrderedValues, validateGeneratedItemLimit } from "../core/validation.js";
2
+ import { mapOrdinalValues } from "./scales/index.js";
3
+ import { readNominalField } from "./scales/fields.js";
4
+ import { resolveLegendOrderDomain } from "./categoryOrder.js";
5
+
6
+ export function isSizeShapeChannels(channels) {
7
+ return Array.isArray(channels) && channels.length === 2 &&
8
+ channels.includes('shape') && channels.includes('size');
9
+ }
10
+ export function validateLegendCombine(value) {
11
+ if(value===undefined)return;
12
+ if (!['separate','size-shape','color-size'].includes(value)) throw new Error('Legend combine must be separate, size-shape, or color-size.');
13
+ }
14
+ export function validateMappedSizeShapeSymbol(symbol) {
15
+ if(symbol===undefined||symbol==='auto')return;
16
+ const layers=symbol.layers??[];
17
+ if(layers.some(layer=>layer.type==='point'&&(Object.hasOwn(layer,'shape')||Object.hasOwn(layer,'size')))) {
18
+ throw new Error('Combined size-shape symbols cannot override mapped shape or size.');
19
+ }
20
+ }
21
+
22
+ export function resolveSizeShapeMapping({layer,scales,order,rows}) {
23
+ const shape=layer?.encoding?.shape,size=layer?.encoding?.size;
24
+ const shapeScale=scales[shape?.scale],sizeScale=scales[size?.scale];
25
+ if(layer?.mark?.type!=="point"||shape?.field!==size?.field||
26
+ !["nominal","ordinal"].includes(size?.fieldType)||shapeScale?.type!=="ordinal"||
27
+ sizeScale?.type!=="ordinal"||!sameOrderedValues(shapeScale.domain,sizeScale.domain)) {
28
+ throw new Error("Combined size-shape requires one categorical point field and identical ordered domains.");
29
+ }
30
+ validateGeneratedItemLimit(shapeScale.domain.length,"Categorical legend item count");
31
+ if(shapeScale.domain.length===0||sizeScale.range.some(area=>!Number.isFinite(area)||area<0)) {
32
+ throw new Error("Combined size-shape requires a nonempty domain and finite nonnegative areas.");
33
+ }
34
+ let linked;
35
+ if(order?.channel!==undefined){
36
+ const position=layer.encoding[order.channel];
37
+ if(!["nominal","ordinal"].includes(position?.fieldType)||position.field!==shape.field) {
38
+ throw new Error("Linked legend order requires the same categorical field on its target.");
39
+ }
40
+ linked=scales[position.scale]?.domain;
41
+ }
42
+ const domain=resolveLegendOrderDomain(shapeScale.domain,order,linked,
43
+ order?.values===undefined?undefined:readNominalField(rows,shape.field));
44
+ return {domain,sizeAreas:mapOrdinalValues(domain,sizeScale.domain,sizeScale.range)};
45
+ }
@@ -0,0 +1,27 @@
1
+ import { validateOptionObject, validatePositiveFinite, validateUnitInterval } from "../core/validation.js";
2
+
3
+ export const DEFAULT_LEGEND_GRADIENT = Object.freeze({ length: 120, thickness: 12 });
4
+ const OPTIONS = Object.freeze(["length", "thickness", "opacity"]);
5
+
6
+ export function validateLegendGradient(value, label = "Legend gradient") {
7
+ validateOptionObject(value, OPTIONS, label);
8
+ validatePositiveFinite(value.length, `${label} length`);
9
+ validatePositiveFinite(value.thickness, `${label} thickness`);
10
+ if (Object.hasOwn(value, "opacity")) validateUnitInterval(value.opacity, `${label} opacity`);
11
+ }
12
+
13
+ export function normalizeLegendGradient(patch, previous = DEFAULT_LEGEND_GRADIENT) {
14
+ if (patch !== undefined) validateOptionObject(patch, OPTIONS, "Legend gradient");
15
+ const value = { ...previous, ...patch };
16
+ validateLegendGradient(value);
17
+ return value;
18
+ }
19
+
20
+
21
+ export function normalizeGradientDirection(value, label = "Gradient legend direction") {
22
+ if (value === undefined) return undefined;
23
+ if (value !== "horizontal" && value !== "vertical") {
24
+ throw new Error(`${label} must be horizontal or vertical.`);
25
+ }
26
+ return value;
27
+ }
@@ -0,0 +1 @@
1
+ import{validateOptionObject as o,validateNonNegativeFinite as d}from"../core/validation.js";import{validateUserId as l}from"../core/identifiers.js";import{PLOT_LEGEND_POSITIONS as i,LEGEND_POSITIONS as a,LEGEND_BLOCK_LAYOUTS as f}from"../core/vocabulary.js";function c(n){o(n,void 0,"Legend groups");for(const[t,r]of Object.entries(n)){if(l(t,"Legend group target"),o(r,["placement","blockLayout","inferredOffset"],"Legend group"),!f.includes(r.blockLayout))throw new Error("Legend group blockLayout must be row, column or wrap.");if(r.inferredOffset!==void 0&&typeof r.inferredOffset!="boolean")throw new TypeError("Legend group inferredOffset must be boolean.");const e=r.placement;if(o(e,["at","position","align","offset"],"Legend group placement"),Object.hasOwn(e,"at")){if(Object.keys(e).length!==1)throw new Error("Legend group at cannot coexist with an edge anchor.");if(o(e.at,["x","y"],"Legend group at"),!Number.isFinite(e.at.x)||!Number.isFinite(e.at.y))throw new Error("Legend group at requires finite x and y.")}else{if(Object.keys(e).length!==3||!a.includes(e.position))throw new Error("Legend group requires a complete edge placement.");if(!["left","center","right"].includes(e.align))throw new Error("Unsupported legend group alignment.");if((i.includes(e.position)||["left","right"].includes(e.position))&&e.align!=="center")throw new Error("Side and in-plot legend groups require center alignment.");d(e.offset,"Legend group offset")}}return n}function s(n){const t=Object.values(n?.blockOverrides??{}).map(r=>r.position).filter(r=>r!==void 0);if(t.length>1)throw new Error("Legend family has ambiguous local positions.");if(t.length&&!a.includes(t[0]))throw new Error("Invalid local legend position.");return t[0]}function m(n){const t=s(n);if(t!==void 0)return{position:t,align:["top","bottom"].includes(t)?n.align??"center":"center",offset:i.includes(t)?8:n.offset??(n.channels===void 0?30:8),inferredOffset:n.inferredOffset??!0}}export{m as localLegendPlacement,s as localLegendPosition,c as validateLegendGroupState};
@@ -0,0 +1,6 @@
1
+ export function validateLegendLayerOpacity(value) {
2
+ if(value!==undefined&&(!Number.isFinite(value)||value<0||value>1)) {
3
+ throw new RangeError("Legend symbol layer opacity must be finite and between 0 and 1.");
4
+ }
5
+ return value;
6
+ }
@@ -0,0 +1,22 @@
1
+ import { validateOptionObject } from "../core/validation.js";
2
+
3
+ export const LEGEND_TITLE_STYLE_KEYS = Object.freeze([
4
+ "color", "fontSize", "fontFamily", "fontWeight", "align"
5
+ ]);
6
+
7
+ export function validateLegendTitleAlignment(style, titlePosition = "top") {
8
+ if (style?.align === undefined) {
9
+ if (style !== undefined && Object.hasOwn(style, "align")) throw new TypeError("Legend title align requires left, center or right.");
10
+ return;
11
+ }
12
+ if (!["left", "center", "right"].includes(style.align)) throw new Error("Legend title align requires left, center or right.");
13
+ if (titlePosition === "left") throw new Error("Inline legend titles do not accept title alignment.");
14
+ }
15
+
16
+ export function mergeLegendTitleStyle(previous, patch, { edit = false } = {}) {
17
+ if (patch !== undefined) validateOptionObject(patch, LEGEND_TITLE_STYLE_KEYS, "Legend titleStyle");
18
+ const result = { ...previous, ...patch };
19
+ if (edit && patch?.align === "auto") delete result.align;
20
+ validateLegendTitleAlignment(result);
21
+ return result;
22
+ }
@@ -29,7 +29,7 @@ function requireLineEncoding(layer) {
29
29
  const y = layer.encoding?.y;
30
30
 
31
31
  if (x === undefined) {
32
- throw new Error(`Line mark "${layer.id}" requires a temporal x encoding.`);
32
+ throw new Error(`Line mark "${layer.id}" requires an x encoding.`);
33
33
  }
34
34
 
35
35
  const binnedAggregate =
@@ -37,10 +37,10 @@ function requireLineEncoding(layer) {
37
37
  x.bin !== undefined &&
38
38
  isAggregate(y?.aggregate);
39
39
  const aggregateMode =
40
- (x?.fieldType === "temporal" && isAggregate(y?.aggregate)) ||
40
+ (["temporal", "nominal", "ordinal"].includes(x?.fieldType) && isAggregate(y?.aggregate)) ||
41
41
  binnedAggregate;
42
- const directQuantitative =
43
- x?.fieldType === "quantitative" &&
42
+ const directNumericY =
43
+ ["quantitative", "nominal", "ordinal"].includes(x?.fieldType) &&
44
44
  x.bin === undefined &&
45
45
  y?.fieldType === "quantitative" &&
46
46
  y.aggregate === undefined;
@@ -48,14 +48,14 @@ function requireLineEncoding(layer) {
48
48
  y?.aggregate === undefined &&
49
49
  ((x?.fieldType === "temporal" && y?.fieldType === "quantitative") ||
50
50
  (x?.fieldType === "quantitative" && y?.fieldType === "temporal"));
51
- if (!aggregateMode && !directQuantitative && !directTemporal) {
51
+ if (!aggregateMode && !directNumericY && !directTemporal) {
52
52
  if (x?.fieldType === "temporal") {
53
53
  throw new Error(
54
54
  `Line mark "${layer.id}" requires a supported aggregate y encoding.`
55
55
  );
56
56
  }
57
57
  throw new Error(
58
- `Line mark "${layer.id}" requires temporal/aggregate or direct quantitative x/y encodings.`
58
+ `Line mark "${layer.id}" requires categorical/temporal aggregate or compatible direct x/y encodings.`
59
59
  );
60
60
  }
61
61
  if (aggregateMode) {
@@ -66,7 +66,7 @@ function requireLineEncoding(layer) {
66
66
  y,
67
67
  isAggregate: aggregateMode,
68
68
  binnedAggregate,
69
- directRows: directTemporal || directQuantitative,
69
+ directRows: directTemporal || directNumericY,
70
70
  directTemporal
71
71
  };
72
72
  }
@@ -90,9 +90,17 @@ function groupedSeries(groups, fields, dimensions) {
90
90
  return series;
91
91
  }
92
92
 
93
- function freezeCartesianSeries(series) {
93
+ function freezeCartesianSeries(series, categoryOrder) {
94
+ const observedX = series.flatMap(item => item.values.map(value => value.x));
95
+ const observedSet = new Set(observedX);
96
+ if (categoryOrder !== undefined) {
97
+ const domain = new Set(categoryOrder);
98
+ if (observedX.some(value => !domain.has(value))) {
99
+ throw new Error("Line categorical x domain must contain every observed vertex.");
100
+ }
101
+ }
94
102
  return cloneAndFreeze({
95
- xValues: series.flatMap(item => item.values.map(value => value.x)),
103
+ xValues: categoryOrder === undefined ? observedX : categoryOrder.filter(value => observedSet.has(value)),
96
104
  yValues: series.flatMap(item => item.values.map(value => value.y)),
97
105
  series
98
106
  });
@@ -122,7 +130,9 @@ function deriveCartesianLineSeries(rows, layer, options = {}) {
122
130
  if (isAggregate) {
123
131
  validateAggregateFieldValues(rows, y.field, y.fieldType);
124
132
  }
125
- const xValues = x.fieldType === "temporal"
133
+ const categorical = ["nominal", "ordinal"].includes(x.fieldType);
134
+ const xValues = categorical ? readNominalField(rows, x.field)
135
+ : x.fieldType === "temporal"
126
136
  ? readTemporalField(rows, x.field, x.temporalUnit)
127
137
  : readQuantitativeField(rows, x.field);
128
138
  const yValues = isAggregate
@@ -130,6 +140,11 @@ function deriveCartesianLineSeries(rows, layer, options = {}) {
130
140
  : y.fieldType === "temporal"
131
141
  ? readTemporalField(rows, y.field, y.temporalUnit)
132
142
  : readQuantitativeField(rows, y.field);
143
+ const categoryOrder = categorical ? options.xDomain ?? [...new Set(xValues)] : undefined;
144
+ const ranks = new Map(categoryOrder?.map((value, index) => [value, index]));
145
+ const compareX = categorical
146
+ ? (left, right) => ranks.get(left.x) - ranks.get(right.x)
147
+ : (left, right) => left.x - right.x;
133
148
  const seriesFields = validatePathSeriesAppearance(rows, layer);
134
149
  const binBoundaries = binnedAggregate
135
150
  ? options.xBinBoundaries ?? resolveLineBins(rows, layer).boundaries
@@ -159,14 +174,14 @@ function deriveCartesianLineSeries(rows, layer, options = {}) {
159
174
  item.orderValues,
160
175
  pathOrder.order
161
176
  );
162
- return values.length < 2 ? [] : [{ key: item.key, values }];
177
+ return [{ key: item.key, values }];
163
178
  });
164
179
  if (series.length === 0) {
165
180
  throw new Error(
166
181
  `Line series on mark "${layer.id}" requires at least two ordered points.`
167
182
  );
168
183
  }
169
- return freezeCartesianSeries(series);
184
+ return freezeCartesianSeries(series, categoryOrder);
170
185
  }
171
186
 
172
187
  if (directRows) {
@@ -181,16 +196,16 @@ function deriveCartesianLineSeries(rows, layer, options = {}) {
181
196
  const orderBy = directTemporal && y.fieldType === "temporal" ? "y" : "x";
182
197
  const series = [...groups.values()].flatMap(item => {
183
198
  const values = item.values.sort(
184
- (left, right) => left[orderBy] - right[orderBy]
199
+ orderBy === "x" ? compareX : (left, right) => left.y - right.y
185
200
  );
186
- return values.length < 2 ? [] : [{ key: item.key, values }];
201
+ return [{ key: item.key, values }];
187
202
  });
188
203
  if (series.length === 0) {
189
204
  throw new Error(
190
205
  `Line series on mark "${layer.id}" requires at least two direct points.`
191
206
  );
192
207
  }
193
- return freezeCartesianSeries(series);
208
+ return freezeCartesianSeries(series, categoryOrder);
194
209
  }
195
210
  const aggregateGroups = new Map();
196
211
 
@@ -241,7 +256,7 @@ function deriveCartesianLineSeries(rows, layer, options = {}) {
241
256
  }
242
257
 
243
258
  const series = [...seriesGroups.values()].flatMap(item => {
244
- const values = item.values.sort((left, right) => left.x - right.x);
259
+ const values = item.values.sort(compareX);
245
260
 
246
261
  if (values.length < 2) {
247
262
  return [];
@@ -256,7 +271,7 @@ function deriveCartesianLineSeries(rows, layer, options = {}) {
256
271
  );
257
272
  }
258
273
 
259
- return freezeCartesianSeries(series);
274
+ return freezeCartesianSeries(series, categoryOrder);
260
275
  }
261
276
 
262
277
  function requirePolarLineEncoding(layer) {
@@ -35,7 +35,8 @@ export function normalizeMarkLabelContent(source, { content, normalizeBy }) {
35
35
  }
36
36
  if (source.mark.type === "bar") {
37
37
  const grain = resolveBarGrain(source);
38
- if (grain === BAR_GRAINS.ranged) {
38
+ if (grain === BAR_GRAINS.ranged || grain === BAR_GRAINS.centered &&
39
+ source.encoding[`${resolveBarChannels(source).measure}2`] !== undefined) {
39
40
  throw new Error("Ranged Bar text requires an explicit field or value.");
40
41
  }
41
42
  if (content === "category" && grain === BAR_GRAINS.histogram) {
@@ -63,10 +64,12 @@ function itemValue(source, item) {
63
64
  );
64
65
  }
65
66
  const measure = source.encoding[resolveBarChannels(source).measure];
67
+ if (resolveBarGrain(source) === BAR_GRAINS.centered) return item.channels[`${resolveBarChannels(source).measure}2`];
66
68
  return measure.aggregate === "count" ? item.members.length
67
69
  : aggregateRows(item.members, measure.field, measure.aggregate);
68
70
  }
69
- if (source.encoding?.radius !== undefined) return item.channels.radius;
71
+ if (source.encoding?.radius !== undefined && source.encoding.theta.fieldType !== "quantitative" &&
72
+ source.encoding.theta.aggregate === undefined) return item.channels.radius;
70
73
  const theta = source.encoding.theta;
71
74
  if (theta.fieldType === "quantitative") return item.channels.theta;
72
75
  return theta.aggregate === "count" ? item.members.length
@@ -1,3 +1,6 @@
1
+ import { scalarKey, normalizeSortBy, validateSortBy } from "./transformKeys.js";
2
+ import { rejectUnknownProperties as rejectUnknownKeys } from "../core/validation.js";
3
+ import { requireStringValue as requireField } from "../core/validation.js";
1
4
  import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
2
5
  import {
3
6
  inverseLerp,
@@ -15,23 +18,9 @@ const METHODS = Object.freeze([
15
18
  "share", "zscore", "minmax", "index", "change", "percentChange"
16
19
  ]);
17
20
  const ZERO_POLICIES = Object.freeze(["error", "null", "zero"]);
18
- const SORT_KEYS = Object.freeze(["field", "order"]);
19
- const ORDERS = Object.freeze(["ascending", "descending"]);
20
21
  const BASELINE_KEYS = Object.freeze(["position", "value"]);
21
22
  const BASELINE_METHODS = new Set(["index", "change", "percentChange"]);
22
23
 
23
- function rejectUnknownKeys(value, supported, label) {
24
- const unknown = Object.keys(value).find(key => !supported.includes(key));
25
- if (unknown !== undefined) throw new Error(`Unknown ${label} property "${unknown}".`);
26
- }
27
-
28
- function requireField(value, label) {
29
- if (typeof value !== "string" || value.length === 0) {
30
- throw new TypeError(`${label} must be a non-empty string.`);
31
- }
32
- return value;
33
- }
34
-
35
24
  function normalizeGroupBy(value) {
36
25
  if (value === undefined) return [];
37
26
  return Array.isArray(value) ? [...value] : [value];
@@ -48,33 +37,6 @@ function validateGroupBy(groupBy) {
48
37
  }
49
38
  }
50
39
 
51
- function normalizeSortBy(value) {
52
- if (value === undefined) return [];
53
- if (!Array.isArray(value)) return value;
54
- return value.map(sort => isPlainObject(sort)
55
- ? { ...sort, order: sort.order ?? "ascending" }
56
- : sort
57
- );
58
- }
59
-
60
- function validateSortBy(sortBy) {
61
- if (!Array.isArray(sortBy)) throw new TypeError("Normalize sortBy must be an array.");
62
- const fields = [];
63
- sortBy.forEach((sort, index) => {
64
- if (!isPlainObject(sort)) {
65
- throw new TypeError(`Normalize sortBy[${index}] must be a plain object.`);
66
- }
67
- rejectUnknownKeys(sort, SORT_KEYS, `normalize sortBy[${index}]`);
68
- fields.push(requireField(sort.field, `Normalize sortBy[${index}].field`));
69
- if (!ORDERS.includes(sort.order)) {
70
- throw new Error(`Unsupported normalize sort order "${sort.order}".`);
71
- }
72
- });
73
- if (new Set(fields).size !== fields.length) {
74
- throw new Error("Normalize sortBy fields must be unique.");
75
- }
76
- }
77
-
78
40
  function normalizeBaseline(value) {
79
41
  if (!isPlainObject(value)) return value;
80
42
  return { ...value };
@@ -136,7 +98,7 @@ export function validateNormalizeTransform(transform) {
136
98
  throw new Error("Normalize zeroDenominator is not available for change.");
137
99
  }
138
100
  if (BASELINE_METHODS.has(transform.method)) {
139
- validateSortBy(transform.sortBy);
101
+ validateSortBy(transform.sortBy, "Normalize");
140
102
  validateBaseline(transform.baseline, transform.sortBy);
141
103
  } else {
142
104
  if (hasBaseline) {
@@ -184,18 +146,6 @@ export function normalizeNormalizeTransform(args = {}) {
184
146
  return cloneAndFreeze(transform);
185
147
  }
186
148
 
187
- function scalarKey(value, label) {
188
- if (value === null) return "null";
189
- if (typeof value === "string") return `string:${value.length}:${value}`;
190
- if (typeof value === "boolean") return `boolean:${value}`;
191
- if (typeof value === "number" && Number.isFinite(value)) {
192
- return `number:${Object.is(value, -0) ? 0 : value}`;
193
- }
194
- throw new TypeError(
195
- `${label} must contain null, strings, booleans, or finite numbers.`
196
- );
197
- }
198
-
199
149
  function compareScalar(left, right, label) {
200
150
  const leftMissing = left === null || left === undefined;
201
151
  const rightMissing = right === null || right === undefined;
@@ -1,290 +1 @@
1
- export const NICE_FACTORS = Object.freeze([1, 2, 3, 5]);
2
-
3
- function finiteArray(values, label) {
4
- if (!Array.isArray(values) || !values.every(Number.isFinite)) {
5
- throw new TypeError(`${label} must contain finite numbers.`);
6
- }
7
- }
8
-
9
- function rangeError(label) {
10
- return new RangeError(`${label} is outside the finite numeric range.`);
11
- }
12
-
13
- function compensatedSum(values, scale = 1) {
14
- let total = 0;
15
- let correction = 0;
16
- for (const source of values) {
17
- const value = source / scale;
18
- const next = total + value;
19
- correction += Math.abs(total) >= Math.abs(value)
20
- ? total - next + value
21
- : value - next + total;
22
- total = next;
23
- }
24
- return total + correction;
25
- }
26
-
27
- export function maximumMagnitude(values) {
28
- let maximum = 0;
29
- for (const value of values) maximum = Math.max(maximum, Math.abs(value));
30
- return maximum;
31
- }
32
-
33
- export function normalizedFiniteSum(values, label = "Numeric values") {
34
- finiteArray(values, label);
35
- const scale = maximumMagnitude(values) || 1;
36
- return { scale, total: compensatedSum(values, scale) };
37
- }
38
-
39
- export function requireFiniteResult(value, label = "Numeric result") {
40
- if (!Number.isFinite(value)) throw rangeError(label);
41
- return value === 0 ? 0 : value;
42
- }
43
-
44
- export function restoreFiniteScale(value, scale, label = "Numeric result") {
45
- if (!Number.isFinite(value) || !Number.isFinite(scale) || scale < 0) {
46
- throw rangeError(label);
47
- }
48
- return requireFiniteResult(value * scale, label);
49
- }
50
-
51
- function stableReducedSum(values, label, divisor = 1) {
52
- finiteArray(values, label);
53
- const ordinary = values.reduce((sum, value) => sum + value, 0);
54
- if (Number.isFinite(ordinary)) {
55
- return ordinary === 0 ? 0 : ordinary / divisor;
56
- }
57
- const scale = maximumMagnitude(values) || 1;
58
- return restoreFiniteScale(
59
- compensatedSum(values, scale) / divisor, scale, label
60
- );
61
- }
62
-
63
- export function stableFiniteSum(values, label = "Numeric sum") {
64
- return stableReducedSum(values, label);
65
- }
66
-
67
- export function stableFinitePrefixSums(values, label = "Numeric sum") {
68
- const normalized = normalizedFiniteSum(values, label);
69
- let ordinary = 0;
70
- let sum = 0;
71
- let correction = 0;
72
- return values.map(value => {
73
- ordinary += value;
74
- const scaled = value / normalized.scale;
75
- const next = sum + scaled;
76
- correction += Math.abs(sum) >= Math.abs(scaled)
77
- ? sum - next + scaled
78
- : scaled - next + sum;
79
- sum = next;
80
- return Number.isFinite(ordinary)
81
- ? ordinary === 0 ? 0 : ordinary
82
- : restoreFiniteScale(sum + correction, normalized.scale, label);
83
- });
84
- }
85
-
86
- export function stableFiniteMean(values, label = "Numeric mean") {
87
- finiteArray(values, label);
88
- if (values.length === 0) {
89
- throw new RangeError(`${label} requires at least one value.`);
90
- }
91
- return stableReducedSum(values, label, values.length);
92
- }
93
-
94
- export function stableFiniteSquareSum(values, label = "Numeric square sum") {
95
- finiteArray(values, label);
96
- const ordinary = values.reduce((sum, value) => sum + value ** 2, 0);
97
- if (Number.isFinite(ordinary)) return ordinary;
98
- const scale = maximumMagnitude(values);
99
- if (scale === 0) return 0;
100
- const normalized = values.reduce(
101
- (sum, value) => sum + (value / scale) ** 2,
102
- 0
103
- );
104
- const magnitude = restoreFiniteScale(Math.sqrt(normalized), scale, label);
105
- return requireFiniteResult(magnitude ** 2, label);
106
- }
107
-
108
- export function stableFiniteDeviation(values, {
109
- sample = false,
110
- divisor = 1,
111
- label = "Numeric deviation"
112
- } = {}) {
113
- if (!Number.isFinite(divisor) || divisor <= 0) {
114
- throw new RangeError(`${label} divisor must be positive and finite.`);
115
- }
116
- const denominator = values.length - (sample ? 1 : 0);
117
- if (denominator <= 0) {
118
- throw new RangeError(`${label} requires more values.`);
119
- }
120
- const mean = stableFiniteMean(values, `${label} mean`);
121
- const differences = values.map(value => value - mean);
122
- const squared = differences.reduce(
123
- (sum, difference) => sum + difference ** 2,
124
- 0
125
- );
126
- if (Number.isFinite(squared)) {
127
- return {
128
- mean,
129
- deviation: Math.sqrt(squared / denominator) / divisor,
130
- squared
131
- };
132
- }
133
- const finiteDifferences = differences.every(Number.isFinite);
134
- const scale = finiteDifferences
135
- ? maximumMagnitude(differences)
136
- : Math.max(Math.abs(mean), maximumMagnitude(values));
137
- if (scale === 0) return { mean, deviation: 0 };
138
- const normalizedSquares = values.reduce((sum, value, index) => {
139
- const difference = finiteDifferences
140
- ? differences[index] / scale
141
- : value / scale - mean / scale;
142
- return sum + difference ** 2;
143
- }, 0);
144
- const deviation = restoreFiniteScale(
145
- Math.sqrt(normalizedSquares / denominator) / divisor,
146
- scale,
147
- label
148
- );
149
- return { mean, deviation };
150
- }
151
-
152
- export function numericExtent(values) {
153
- let minimum = Infinity;
154
- let maximum = -Infinity;
155
- for (const value of values) {
156
- minimum = Math.min(minimum, value);
157
- maximum = Math.max(maximum, value);
158
- }
159
- return [minimum, maximum];
160
- }
161
-
162
- export function normalizeNumericRange(start, end) {
163
- const magnitude = Math.max(Math.abs(start), Math.abs(end));
164
- if (magnitude === 0) {
165
- return { scale: 1, start: 0, end: 0, span: 0 };
166
- }
167
-
168
- const decimalScale = 10 ** Math.floor(Math.log10(magnitude));
169
- const scale = Number.isFinite(decimalScale) && decimalScale > 0
170
- ? decimalScale
171
- : magnitude;
172
- start /= scale;
173
- end /= scale;
174
- return {
175
- scale,
176
- start,
177
- end,
178
- span: end - start
179
- };
180
- }
181
-
182
- export function niceNumericStep(
183
- span,
184
- count = 5,
185
- factors = NICE_FACTORS
186
- ) {
187
- if (!Number.isFinite(span) || span <= 0) {
188
- throw new RangeError("Nice numeric span must be positive and finite.");
189
- }
190
- const rough = span / Math.max(1, count);
191
- const power = 10 ** Math.floor(Math.log10(rough));
192
- const fraction = rough / power;
193
- const factor = factors.find(candidate => candidate >= fraction) ?? 10;
194
- return factor * power;
195
- }
196
-
197
- export function alignNumericStep(value, step, direction) {
198
- let quotient = value / step;
199
- const nearest = Math.round(quotient);
200
- if (Math.abs(quotient - nearest) <= 1e-10) quotient = nearest;
201
- return (direction === "floor" ? Math.floor(quotient) : Math.ceil(quotient)) * step;
202
- }
203
-
204
- export function cleanNumericValue(value, resolution) {
205
- if (!Number.isFinite(value) || value === 0) return value === 0 ? 0 : value;
206
- const rounded = Number(value.toPrecision(15));
207
- return Number.isFinite(resolution) && resolution > 0 &&
208
- Math.abs(rounded - value) <= resolution * 1e-10
209
- ? rounded
210
- : value;
211
- }
212
-
213
- export function inverseLerp(value, start, end) {
214
- if (value === start) return 0;
215
- if (value === end) return 1;
216
-
217
- const span = end - start;
218
- const offset = value - start;
219
- if (Number.isFinite(span) && span !== 0 && Number.isFinite(offset)) {
220
- return offset / span;
221
- }
222
-
223
- const scale = Math.max(Math.abs(value), Math.abs(start), Math.abs(end));
224
- return (value / scale - start / scale) / (end / scale - start / scale);
225
- }
226
-
227
- export function interpolateNumber(start, end, proportion) {
228
- let result = proportion === 0 ? start : proportion === 1 ? end : undefined;
229
- if (result === undefined) {
230
- const span = end - start;
231
- if (Number.isFinite(span)) {
232
- result = start + proportion * span;
233
- } else {
234
- const scale = Math.max(Math.abs(start), Math.abs(end));
235
- result = scale * (
236
- start / scale * (1 - proportion) + end / scale * proportion
237
- );
238
- }
239
- }
240
- return requireFiniteResult(result, "Numeric interpolation");
241
- }
242
-
243
- export function finiteMidpoint(start, end) {
244
- const sum = start + end;
245
- const result = Number.isFinite(sum) ? sum / 2 : start / 2 + end / 2;
246
- return requireFiniteResult(result, "Numeric midpoint");
247
- }
248
-
249
- export function sampleNumericRange(start, end, count, label = "Numeric range") {
250
- const resolution = Math.abs((end - start) / (count - 1));
251
- const values = [];
252
- const direction = Math.sign(end - start);
253
- for (let index = 0; index < count; index += 1) {
254
- const value = cleanNumericValue(
255
- interpolateNumber(start, end, index / (count - 1)),
256
- resolution
257
- );
258
- if (
259
- !Number.isFinite(value) ||
260
- (direction !== 0 && index > 0 &&
261
- direction * (value - values[index - 1]) <= 0)
262
- ) {
263
- throw new RangeError(`${label} cannot represent ${count} distinct samples.`);
264
- }
265
- values.push(value);
266
- }
267
- return values;
268
- }
269
-
270
- export function formatDistinctNumericSamples(values, initialFormat) {
271
- let labels = values.map(initialFormat ?? (value =>
272
- String(+value.toPrecision(3))
273
- ));
274
- const distinct = () => labels.every((label, index) =>
275
- index === 0 || values[index] === values[index - 1] ||
276
- label !== labels[index - 1]
277
- );
278
- for (let precision = 4; !distinct() && precision <= 17; precision += 1) {
279
- labels = values.map(value => String(+value.toPrecision(precision)));
280
- }
281
- return distinct() ? labels : values.map(String);
282
- }
283
-
284
- export function stableDecimal(value, places = 12) {
285
- if (value === 0) return 0;
286
- const threshold = 10 ** -places;
287
- return Math.abs(value) < threshold
288
- ? Number(value.toPrecision(places))
289
- : Number(value.toFixed(places));
290
- }
1
+ const q=Object.freeze([1,2,3,5]);function h(t,e){if(!Array.isArray(t)||!t.every(Number.isFinite))throw new TypeError(`${e} must contain finite numbers.`)}function p(t){return new RangeError(`${t} is outside the finite numeric range.`)}function x(t,e=1){let n=0,r=0;for(const i of t){const o=i/e,c=n+o;r+=Math.abs(n)>=Math.abs(o)?n-c+o:o-c+n,n=c}return n+r}function f(t){let e=0;for(const n of t)e=Math.max(e,Math.abs(n));return e}function y(t,e="Numeric values"){h(t,e);const n=f(t)||1;return{scale:n,total:x(t,n)}}function b(t,e="Numeric result"){if(!Number.isFinite(t))throw p(e);return t===0?0:t}function N(t,e,n="Numeric result"){if(!Number.isFinite(t)||!Number.isFinite(e)||e<0)throw p(n);return b(t*e,n)}function d(t,e,n=1){h(t,e);const r=t.reduce((o,c)=>o+c,0);if(Number.isFinite(r))return r===0?0:r/n;const i=f(t)||1;return N(x(t,i)/n,i,e)}function $(t,e="Numeric sum"){return d(t,e)}function z(t,e="Numeric sum"){const n=y(t,e);let r=0,i=0,o=0;return t.map(c=>{r+=c;const u=c/n.scale,s=i+u;return o+=Math.abs(i)>=Math.abs(u)?i-s+u:u-s+i,i=s,Number.isFinite(r)?r===0?0:r:N(i+o,n.scale,e)})}function R(t,e="Numeric mean"){if(h(t,e),t.length===0)throw new RangeError(`${e} requires at least one value.`);return d(t,e,t.length)}function P(t,e="Numeric square sum"){h(t,e);const n=t.reduce((c,u)=>c+u**2,0);if(Number.isFinite(n))return n;const r=f(t);if(r===0)return 0;const i=t.reduce((c,u)=>c+(u/r)**2,0),o=N(Math.sqrt(i),r,e);return b(o**2,e)}function A(t,{sample:e=!1,divisor:n=1,label:r="Numeric deviation"}={}){if(!Number.isFinite(n)||n<=0)throw new RangeError(`${r} divisor must be positive and finite.`);const i=t.length-(e?1:0);if(i<=0)throw new RangeError(`${r} requires more values.`);const o=R(t,`${r} mean`),c=t.map(a=>a-o),u=c.reduce((a,M)=>a+M**2,0);if(Number.isFinite(u))return{mean:o,deviation:Math.sqrt(u/i)/n,squared:u};const s=c.every(Number.isFinite),m=s?f(c):Math.max(Math.abs(o),f(t));if(m===0)return{mean:o,deviation:0};const g=t.reduce((a,M,S)=>{const w=s?c[S]/m:M/m-o/m;return a+w**2},0),l=N(Math.sqrt(g/i)/n,m,r);return{mean:o,deviation:l}}function D(t,e){const n=(t.length-1)*e,r=Math.floor(n),i=Math.ceil(n);return F(t[r],t[i],n-r)}function I(t){let e=1/0,n=-1/0;for(const r of t)e=Math.min(e,r),n=Math.max(n,r);return[e,n]}function C(t,e){const n=Math.max(Math.abs(t),Math.abs(e));if(n===0)return{scale:1,start:0,end:0,span:0};const r=10**Math.floor(Math.log10(n)),i=Number.isFinite(r)&&r>0?r:n;return t/=i,e/=i,{scale:i,start:t,end:e,span:e-t}}function O(t,e=5,n=q){if(!Number.isFinite(t)||t<=0)throw new RangeError("Nice numeric span must be positive and finite.");const r=t/Math.max(1,e),i=10**Math.floor(Math.log10(r)),o=r/i;return(n.find(u=>u>=o)??10)*i}function T(t,e,n){let r=t/e;const i=Math.round(r);return Math.abs(r-i)<=1e-10&&(r=i),(n==="floor"?Math.floor(r):Math.ceil(r))*e}function E(t,e){if(!Number.isFinite(t)||t===0)return t===0?0:t;const n=Number(t.toPrecision(15));return Number.isFinite(e)&&e>0&&Math.abs(n-t)<=e*1e-10?n:t}function j(t,e,n){if(t===e)return 0;if(t===n)return 1;const r=n-e,i=t-e;if(Number.isFinite(r)&&r!==0&&Number.isFinite(i))return i/r;const o=Math.max(Math.abs(t),Math.abs(e),Math.abs(n));return(t/o-e/o)/(n/o-e/o)}function F(t,e,n){let r=n===0?t:n===1?e:void 0;if(r===void 0){const i=e-t;if(Number.isFinite(i))r=t+n*i;else{const o=Math.max(Math.abs(t),Math.abs(e));r=o*(t/o*(1-n)+e/o*n)}}return b(r,"Numeric interpolation")}function L(t,e){const n=t+e,r=Number.isFinite(n)?n/2:t/2+e/2;return b(r,"Numeric midpoint")}function V(t,e,n,r="Numeric range"){const i=Math.abs((e-t)/(n-1)),o=[],c=Math.sign(e-t);for(let u=0;u<n;u+=1){const s=E(F(t,e,u/(n-1)),i);if(!Number.isFinite(s)||c!==0&&u>0&&c*(s-o[u-1])<=0)throw new RangeError(`${r} cannot represent ${n} distinct samples.`);o.push(s)}return o}function _(t,e){let n=t.map(e??(i=>String(+i.toPrecision(3))));const r=()=>n.every((i,o)=>o===0||t[o]===t[o-1]||i!==n[o-1]);for(let i=4;!r()&&i<=17;i+=1)n=t.map(o=>String(+o.toPrecision(i)));return r()?n:t.map(String)}function k(t,e=12){if(t===0)return 0;const n=10**-e;return Math.abs(t)<n?Number(t.toPrecision(e)):Number(t.toFixed(e))}export{q as NICE_FACTORS,T as alignNumericStep,E as cleanNumericValue,L as finiteMidpoint,_ as formatDistinctNumericSamples,F as interpolateNumber,j as inverseLerp,f as maximumMagnitude,O as niceNumericStep,C as normalizeNumericRange,y as normalizedFiniteSum,I as numericExtent,D as quantileSorted,b as requireFiniteResult,N as restoreFiniteScale,V as sampleNumericRange,k as stableDecimal,A as stableFiniteDeviation,R as stableFiniteMean,z as stableFinitePrefixSums,P as stableFiniteSquareSum,$ as stableFiniteSum};