ggaction 0.0.17 → 0.0.18

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 (263) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +2 -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 +16 -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 +4 -4
  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/edit.js +1 -1
  35. package/src/actions/data/regression.js +2 -1
  36. package/src/actions/data/shared.js +3 -2
  37. package/src/actions/data/stack.js +3 -1
  38. package/src/actions/encodings/appearance.js +37 -29
  39. package/src/actions/encodings/barWidth.js +36 -11
  40. package/src/actions/encodings/channels.js +0 -1
  41. package/src/actions/encodings/color/continuous.js +1 -1
  42. package/src/actions/encodings/color/index.js +1 -1
  43. package/src/actions/encodings/color/policy.js +10 -3
  44. package/src/actions/encodings/density/resolve.js +1 -6
  45. package/src/actions/encodings/position/policies/arc.js +12 -10
  46. package/src/actions/encodings/position/policies/area.js +3 -3
  47. package/src/actions/encodings/position/policies/bar.js +15 -6
  48. package/src/actions/encodings/position/policies/line.js +17 -6
  49. package/src/actions/encodings/position/resolve.js +3 -2
  50. package/src/actions/encodings/ranged.js +17 -0
  51. package/src/actions/encodings/remove.js +3 -2
  52. package/src/actions/encodings/seriesLayout.js +1 -0
  53. package/src/actions/encodings/text.js +6 -3
  54. package/src/actions/errorBands/create.js +2 -2
  55. package/src/actions/errorBands/edit.js +2 -1
  56. package/src/actions/facets/actions.js +158 -240
  57. package/src/actions/gradientPlots/resolve.js +2 -17
  58. package/src/actions/guides/applicability.js +1 -1
  59. package/src/actions/guides/axes/axis.js +16 -4
  60. package/src/actions/guides/axes/edit.js +4 -4
  61. package/src/actions/guides/axes/labels.js +120 -46
  62. package/src/actions/guides/axes/lines.js +6 -5
  63. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  64. package/src/actions/guides/axes/parallel/policy.js +8 -7
  65. package/src/actions/guides/axes/remove.js +3 -2
  66. package/src/actions/guides/axes/tickGroups.js +5 -2
  67. package/src/actions/guides/axes/ticks.js +6 -3
  68. package/src/actions/guides/axes/titles.js +71 -26
  69. package/src/actions/guides/basic.js +2 -0
  70. package/src/actions/guides/facadeLegend.js +10 -2
  71. package/src/actions/guides/grids/grid.js +7 -1
  72. package/src/actions/guides/grids/resolve.js +59 -12
  73. package/src/actions/guides/legends/blocks.js +3 -1
  74. package/src/actions/guides/legends/categorical/actions.js +70 -17
  75. package/src/actions/guides/legends/categorical/components.js +12 -8
  76. package/src/actions/guides/legends/categorical/index.js +6 -0
  77. package/src/actions/guides/legends/categorical/layout.js +37 -8
  78. package/src/actions/guides/legends/categorical/options.js +31 -20
  79. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  80. package/src/actions/guides/legends/categorical/symbols.js +18 -12
  81. package/src/actions/guides/legends/categorical/validation.js +1 -6
  82. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  83. package/src/actions/guides/legends/continuous/common.js +1 -447
  84. package/src/actions/guides/legends/continuous/gradient.js +47 -34
  85. package/src/actions/guides/legends/continuous/interval.js +4 -4
  86. package/src/actions/guides/legends/continuous/opacity.js +19 -12
  87. package/src/actions/guides/legends/edit.js +1 -604
  88. package/src/actions/guides/legends/focused.js +2 -2
  89. package/src/actions/guides/legends/group.js +1 -0
  90. package/src/actions/guides/legends/lane.js +1 -516
  91. package/src/actions/guides/legends/remove.js +3 -0
  92. package/src/actions/guides/legends/sampling.js +1 -1
  93. package/src/actions/guides/legends/size.js +51 -43
  94. package/src/actions/guides/legends/strokeWidth.js +11 -29
  95. package/src/actions/guides/legends/target.js +3 -3
  96. package/src/actions/guides/legends/transition.js +21 -7
  97. package/src/actions/guides/polar/axes/labels.js +4 -2
  98. package/src/actions/guides/polar/axes/titles.js +7 -5
  99. package/src/actions/guides/polar/resolve.js +6 -14
  100. package/src/actions/index.js +16 -16
  101. package/src/actions/marks/arc/actions.js +3 -10
  102. package/src/actions/marks/area/actions.js +8 -5
  103. package/src/actions/marks/area/materialize.js +8 -9
  104. package/src/actions/marks/bar/create.js +10 -4
  105. package/src/actions/marks/bar/edit.js +8 -4
  106. package/src/actions/marks/bar/materialize.js +9 -6
  107. package/src/actions/marks/line/actions.js +11 -4
  108. package/src/actions/marks/line/materialize.js +9 -5
  109. package/src/actions/marks/point/edit.js +1 -1
  110. package/src/actions/marks/rect/actions.js +3 -3
  111. package/src/actions/marks/remove.js +2 -0
  112. package/src/actions/marks/rule/actions.js +7 -4
  113. package/src/actions/marks/text/actions.js +17 -5
  114. package/src/actions/marks/text/layout.js +3 -0
  115. package/src/actions/primitives/semanticValidation/layer.js +4 -0
  116. package/src/actions/regression/create.js +1 -1
  117. package/src/actions/regression/edit.js +3 -3
  118. package/src/actions/regression/resolve.js +2 -6
  119. package/src/actions/scales/channels.js +9 -4
  120. package/src/actions/scales/consumers/families.js +16 -5
  121. package/src/actions/scales/create.js +8 -3
  122. package/src/actions/scales/definitions.js +1 -425
  123. package/src/actions/scales/edit.js +2 -2
  124. package/src/actions/scales/editPolicy.js +9 -3
  125. package/src/actions/scales/materialize.js +1 -1
  126. package/src/actions/scales/preview.js +21 -1
  127. package/src/actions/scales/quantitativeColor.js +3 -3
  128. package/src/actions/selection/actions.js +1 -13
  129. package/src/actions/theme/reconcile.js +81 -27
  130. package/src/actions/theme/state.js +12 -0
  131. package/src/actions/titles/actions.js +5 -18
  132. package/src/actions/titles/resolve.js +41 -19
  133. package/src/actions/violinPlots/edit.js +3 -20
  134. package/src/core/compositionState.js +4 -1
  135. package/src/core/font.js +9 -2
  136. package/src/core/optionRequirements.js +1 -0
  137. package/src/core/resourceReferences.js +3 -0
  138. package/src/core/textMetricProfile.js +3 -2
  139. package/src/core/textMetrics.js +41 -4
  140. package/src/core/validation.js +26 -0
  141. package/src/core/vocabulary.js +4 -0
  142. package/src/grammar/aggregate.js +4 -18
  143. package/src/grammar/arcs.js +1 -386
  144. package/src/grammar/areaPaint.js +10 -0
  145. package/src/grammar/areaSeries.js +18 -10
  146. package/src/grammar/bars/geometry.js +30 -3
  147. package/src/grammar/bars/policy.js +12 -1
  148. package/src/grammar/bin.js +2 -12
  149. package/src/grammar/bin2d.js +1 -6
  150. package/src/grammar/boxPlot.js +56 -9
  151. package/src/grammar/categoryOrder.js +1 -6
  152. package/src/grammar/complete.js +15 -19
  153. package/src/grammar/computed.js +92 -25
  154. package/src/grammar/curveCommands.js +29 -16
  155. package/src/grammar/datasetSchema.js +3 -7
  156. package/src/grammar/displayLabels.js +28 -0
  157. package/src/grammar/ecdf.js +2 -9
  158. package/src/grammar/facets/dependencies.js +6 -2
  159. package/src/grammar/facets/guides.js +5 -0
  160. package/src/grammar/facets/index.js +2 -5
  161. package/src/grammar/fold.js +2 -12
  162. package/src/grammar/gradientProfile.js +1 -6
  163. package/src/grammar/impute.js +21 -48
  164. package/src/grammar/interval.js +2 -9
  165. package/src/grammar/legendGradient.js +18 -0
  166. package/src/grammar/legendGroups.js +1 -0
  167. package/src/grammar/legendTitleStyle.js +22 -0
  168. package/src/grammar/lineSeries.js +32 -17
  169. package/src/grammar/markLabels.js +5 -2
  170. package/src/grammar/normalize.js +4 -54
  171. package/src/grammar/numeric.js +1 -290
  172. package/src/grammar/parallelCoordinates.js +1 -6
  173. package/src/grammar/parseTime.js +41 -0
  174. package/src/grammar/pathCommands.js +11 -4
  175. package/src/grammar/polar.js +1 -184
  176. package/src/grammar/positionCompatibility.js +3 -3
  177. package/src/grammar/regression/derive.js +7 -1
  178. package/src/grammar/regression/models.js +83 -55
  179. package/src/grammar/regression/parameters.js +14 -3
  180. package/src/grammar/roundedRect.js +42 -54
  181. package/src/grammar/scales/appearance.js +1 -206
  182. package/src/grammar/scales/color.js +1 -382
  183. package/src/grammar/scales/colorConsumers.js +5 -2
  184. package/src/grammar/scales/definition.js +1 -197
  185. package/src/grammar/scales/discretized.js +1 -195
  186. package/src/grammar/scales/size.js +1 -456
  187. package/src/grammar/scales/temporal.js +2 -13
  188. package/src/grammar/scales/transformed.js +1 -383
  189. package/src/grammar/scales/types.js +1 -1
  190. package/src/grammar/scales/validation.js +1 -66
  191. package/src/grammar/schemas/concreteGraphic.js +11 -0
  192. package/src/grammar/schemas/graphic.js +3 -0
  193. package/src/grammar/schemas/semanticPath.js +2 -1
  194. package/src/grammar/stack.js +2 -10
  195. package/src/grammar/statisticalReference.js +1 -6
  196. package/src/grammar/summary.js +3 -26
  197. package/src/grammar/text.js +19 -0
  198. package/src/grammar/timeUnit.js +16 -7
  199. package/src/grammar/transformKeys.js +47 -0
  200. package/src/grammar/transforms.js +4 -1
  201. package/src/grammar/valueFormat.js +17 -5
  202. package/src/grammar/weightedStatistics.js +1 -6
  203. package/src/grammar/window.js +4 -58
  204. package/src/inspectionDescriptors.js +1 -1
  205. package/src/layout/canvas.js +1 -110
  206. package/src/layout/facets.js +1 -244
  207. package/src/layout/labels.js +1 -0
  208. package/src/layout/legend.js +13 -0
  209. package/src/layout/legendGroup.js +1 -0
  210. package/src/layout/legendItems.js +59 -15
  211. package/src/layout/legendLane.js +1 -441
  212. package/src/layout/legendText.js +1 -0
  213. package/src/layout/title.js +35 -13
  214. package/src/materialization/bars/aggregate.js +11 -5
  215. package/src/materialization/bars/histogram.js +4 -1
  216. package/src/materialization/bars/ranged.js +19 -6
  217. package/src/materialization/bars/resolve.js +22 -1
  218. package/src/materialization/coordinateBounds.js +8 -0
  219. package/src/materialization/dependencies.js +4 -44
  220. package/src/materialization/facetGuides/layout.js +1 -0
  221. package/src/materialization/facetGuides/placement.js +19 -8
  222. package/src/materialization/facetGuides/preparation.js +15 -7
  223. package/src/materialization/facetHeaders.js +1 -347
  224. package/src/materialization/facets.js +1 -185
  225. package/src/materialization/graphicHierarchy.js +1 -1
  226. package/src/materialization/guides/layout.js +1 -57
  227. package/src/materialization/guides/resources.js +1 -123
  228. package/src/materialization/layout.js +53 -0
  229. package/src/materialization/legendGroups.js +1 -0
  230. package/src/materialization/legendText.js +1 -0
  231. package/src/materialization/legends.js +1 -1
  232. package/src/materialization/marks/capabilities.js +5 -6
  233. package/src/materialization/rect.js +1 -1
  234. package/src/materialization/scaleGuideDependencies.js +1 -1
  235. package/src/materialization/scales/map.js +3 -0
  236. package/src/materialization/scales/policies/arc.js +8 -7
  237. package/src/materialization/scales/policies/bar.js +19 -7
  238. package/src/materialization/scales/policies/series.js +2 -1
  239. package/src/materialization/scales/resolve.js +5 -3
  240. package/src/materialization/selection/items/arc.js +1 -1
  241. package/src/materialization/selection/items/bar.js +7 -6
  242. package/src/materialization/selection/items/path.js +7 -6
  243. package/src/materialization/selection/items/rect.js +2 -2
  244. package/src/materialization/text.js +13 -4
  245. package/src/materialization/textLines.js +15 -0
  246. package/src/persistence/codec.js +37 -6
  247. package/src/persistence/validation.js +47 -0
  248. package/src/persistence.js +18 -5
  249. package/src/renderers/canvas/circle.js +6 -3
  250. package/src/renderers/canvas/fill.js +7 -1
  251. package/src/renderers/canvas/index.js +2 -1
  252. package/src/renderers/canvas/line.js +1 -0
  253. package/src/renderers/canvas/path.js +1 -1
  254. package/src/renderers/canvas/rect.js +11 -8
  255. package/src/renderers/canvas/stroke.js +2 -1
  256. package/src/renderers/canvas/text.js +5 -3
  257. package/src/renderers/svg.js +4 -1
  258. package/src/selectors/layers.js +19 -0
  259. package/src/theme/defaults.js +8 -2
  260. package/src/version.js +1 -1
  261. package/types/basic.d.ts +1 -0
  262. package/types/index.d.ts +7 -0
  263. package/types/program.d.ts +347 -85
@@ -15,12 +15,13 @@ import {
15
15
  requestedStrokeDetails,
16
16
  STROKE_STYLE_PROPERTIES
17
17
  } from "../../grammar/strokeStyle.js";
18
+ import { ERROR_BAR_APPEARANCE_OPTIONS, resolveErrorBarAppearance } from "../errorBars/edit.js";
18
19
  import { DEFAULT_COLORS } from "../../theme/defaults.js";
19
20
  import { normalizeGuides } from "../charts/shared.js";
20
21
 
21
22
  export const BOX_PLOT_OPTIONS = Object.freeze([
22
23
  "id", "target", "data", "x", "y", "coordinate", "whisker",
23
- "width", "outliers", "box", "median", "outlier", "guides"
24
+ "width", "summary", "outliers", "box", "median", "outlier", "guides"
24
25
  ]);
25
26
 
26
27
  const DEFAULT_BOX = {
@@ -97,15 +98,20 @@ export function resolveBoxGuides(value) {
97
98
  : normalizeGuides(value, "createBoxPlot");
98
99
  }
99
100
 
100
- export function resolveBoxWhisker(value, operation = "createBoxPlot") {
101
- if (value === undefined) return Object.freeze({ type: "tukey", factor: 1.5 });
101
+ export function resolveBoxWhisker(value, operation = "createBoxPlot", defaultType = "tukey") {
102
+ if (value === undefined) return Object.freeze(defaultType === "tukey" ? { type: "tukey", factor: 1.5 } : { type: defaultType });
102
103
  const options = plainOptions(
103
104
  value,
104
- ["type", "factor"],
105
+ ["type", "factor", ...ERROR_BAR_APPEARANCE_OPTIONS],
105
106
  "whisker",
106
107
  operation
107
108
  );
108
- const type = options.type ?? "tukey";
109
+ const requestedAppearance = boxWhiskerAppearance(options);
110
+ const appearance = Object.keys(requestedAppearance).length === 0 ? {} : resolveErrorBarAppearance(requestedAppearance, {
111
+ defaults: { caps: true, capSize: 8, stroke: "#111111", strokeWidth: 1.5, strokeDash: "solid", opacity: 1 }, operation
112
+ });
113
+ const style = Object.fromEntries(Object.keys(requestedAppearance).map(key => [key, appearance[key]]));
114
+ const type = options.type ?? defaultType;
109
115
  if (!["tukey", "minmax"].includes(type)) {
110
116
  throw new Error(`Unsupported createBoxPlot whisker type "${type}".`);
111
117
  }
@@ -113,7 +119,7 @@ export function resolveBoxWhisker(value, operation = "createBoxPlot") {
113
119
  if (options.factor !== undefined) {
114
120
  throw new Error("createBoxPlot minmax whiskers do not accept factor.");
115
121
  }
116
- return Object.freeze({ type });
122
+ return Object.freeze({ type, ...style });
117
123
  }
118
124
  const factor = options.factor ?? 1.5;
119
125
  if (!Number.isFinite(factor) || factor <= 0) {
@@ -121,11 +127,15 @@ export function resolveBoxWhisker(value, operation = "createBoxPlot") {
121
127
  "createBoxPlot whisker factor must be positive and finite."
122
128
  );
123
129
  }
124
- return Object.freeze({ type, factor });
130
+ return Object.freeze({ type, factor, ...style });
125
131
  }
126
132
 
127
133
  export function resolveBoxWidth(value, operation = "createBoxPlot") {
128
- const options = plainOptions(value, ["band"], "width", operation);
134
+ const options = plainOptions(value, ["band", "pixels"], "width", operation);
135
+ if (options.pixels !== undefined) {
136
+ if (options.band !== undefined) throw new Error(`${operation} width accepts band or pixels, not both.`);
137
+ return Object.freeze({ pixels: validatePositiveFinite(options.pixels, `${operation} width.pixels`) });
138
+ }
129
139
  const band = options.band ?? 0.7;
130
140
  if (!Number.isFinite(band) || band <= 0 || band >= 1) {
131
141
  throw new RangeError(
@@ -135,6 +145,15 @@ export function resolveBoxWidth(value, operation = "createBoxPlot") {
135
145
  return band;
136
146
  }
137
147
 
148
+ export function boxBarWidth(width) {
149
+ return typeof width === "number" ? { band: width } : width;
150
+ }
151
+
152
+ export function boxWhiskerAppearance(whisker) {
153
+ return Object.fromEntries(ERROR_BAR_APPEARANCE_OPTIONS.filter(key => Object.hasOwn(whisker, key))
154
+ .map(key => [key, whisker[key]]));
155
+ }
156
+
138
157
  export function resolveBoxAppearance(value, operation = "createBoxPlot") {
139
158
  return resolveAppearance(
140
159
  value,
@@ -146,13 +165,20 @@ export function resolveBoxAppearance(value, operation = "createBoxPlot") {
146
165
  }
147
166
 
148
167
  export function resolveBoxMedianAppearance(value, operation = "createBoxPlot") {
149
- return resolveAppearance(
150
- value,
168
+ const options = value === undefined ? {} : value;
169
+ if (!isPlainObject(options)) throw new TypeError(`${operation} median must be a plain object.`);
170
+ const { width, ...appearance } = options;
171
+ if (width !== undefined && width !== "auto") {
172
+ plainOptions(width, ["pixels"], "median.width", operation);
173
+ validatePositiveFinite(width.pixels, `${operation} median.width.pixels`);
174
+ }
175
+ return Object.freeze({ ...resolveAppearance(
176
+ appearance,
151
177
  DEFAULT_MEDIAN,
152
178
  "median",
153
179
  operation,
154
180
  requestedStrokeDetails
155
- );
181
+ ), ...(width === undefined || width === "auto" ? {} : { width: Object.freeze({ pixels: width.pixels }) }) });
156
182
  }
157
183
 
158
184
  export function resolveBoxOutlierAppearance(value, operation = "createBoxPlot") {
@@ -1,8 +1,11 @@
1
1
  import { action } from "../../core/action.js";
2
+ import { findLayer } from "../../selectors/layers.js";
2
3
  import {
3
4
  DEFAULT_CANVAS,
4
5
  DEFAULT_MARGIN,
5
6
  normalizeMargin,
7
+ normalizePlotBorder,
8
+ resolveCanvasPlot,
6
9
  validateCanvasState
7
10
  } from "../../layout/canvas.js";
8
11
  import { cloneAndFreeze } from "../../core/immutable.js";
@@ -22,7 +25,9 @@ const CANVAS_OPTIONS = Object.freeze([
22
25
  "width",
23
26
  "height",
24
27
  "background",
25
- "margin"
28
+ "margin",
29
+ "plot",
30
+ "plotBorder"
26
31
  ]);
27
32
 
28
33
  function validateOptions(args, operation, { allowEmpty = false } = {}) {
@@ -39,34 +44,60 @@ function requireCanvas(program) {
39
44
  return canvas;
40
45
  }
41
46
 
42
- function resolveCanvasState(program, args) {
43
- const canvas = requireCanvas(program);
44
- const canvasConfig = program.materializationConfigs.canvas;
45
- const baseMargin = canvasConfig?.margin ?? DEFAULT_MARGIN;
46
- const baseSize = canvasConfig?.size ?? { width: "explicit", height: "explicit" };
47
+ function resolveCanvasState(properties, args, config = {}) {
48
+ const baseMargin = config.margin ?? DEFAULT_MARGIN;
49
+ const baseSize = config.size ?? { width: "explicit", height: "explicit" };
47
50
  const state = {
48
- width: Object.hasOwn(args, "width")
49
- ? args.width
50
- : canvas.properties.width,
51
- height: Object.hasOwn(args, "height")
52
- ? args.height
53
- : canvas.properties.height,
54
- background: Object.hasOwn(args, "background")
55
- ? args.background
56
- : canvas.properties.background,
57
- margin: Object.hasOwn(args, "margin")
58
- ? normalizeMargin(args.margin, baseMargin)
59
- : baseMargin,
51
+ width: properties.width, height: properties.height, background: properties.background,
52
+ ...args,
53
+ margin: Object.hasOwn(args, "margin") ? normalizeMargin(args.margin, baseMargin) : baseMargin,
54
+ plotBorder: normalizePlotBorder(Object.hasOwn(args, "plotBorder") ? args.plotBorder : (config.plotBorder ?? false)),
60
55
  size: {
61
56
  width: Object.hasOwn(args, "width") ? "explicit" : baseSize.width,
62
57
  height: Object.hasOwn(args, "height") ? "explicit" : baseSize.height
63
58
  }
64
59
  };
65
-
60
+ delete state.plot;
61
+ const plot = resolveCanvasPlot(args, config.plot);
62
+ if (plot !== undefined) {
63
+ state.plot = plot;
64
+ state.width = plot.width + state.margin.left + state.margin.right;
65
+ state.height = plot.height + state.margin.top + state.margin.bottom;
66
+ state.size = { width: "explicit", height: "explicit" };
67
+ }
66
68
  validateCanvasState(state);
67
69
  return cloneAndFreeze(state);
68
70
  }
69
71
 
72
+ function applyCanvasState(program, args, state) {
73
+ let next = program;
74
+ for (const property of ["width", "height", "background"]) {
75
+ if (Object.hasOwn(args, property) || state.plot !== undefined && property !== "background") {
76
+ next = next.editGraphics({ target: CANVAS_GRAPHIC_ID, property, value: state[property] });
77
+ }
78
+ }
79
+ return next._withCanvasConfig({ margin: state.margin, size: state.size,
80
+ ...(state.plot === undefined ? {} : { plot: state.plot }),
81
+ ...(state.plotBorder === false ? {} : { plotBorder: state.plotBorder }) });
82
+ }
83
+
84
+ function assertBorderAvailable(program, state) {
85
+ if (state.plotBorder !== false && program.materializationConfigs.canvas?.plotBorder === undefined &&
86
+ (program.graphicSpec.objects.plotBorder !== undefined || findLayer(program, "plotBorder") !== undefined)) {
87
+ throw new Error('Canvas plotBorder requires the reserved graphic id "plotBorder".');
88
+ }
89
+ }
90
+
91
+ function createHierarchy(program) {
92
+ return program.createGraphics({ id: CANVAS_GRAPHIC_ID, type: "canvas" })
93
+ .createGraphics({ id: PLOT_GRAPHIC_ID, type: "collection", parent: CANVAS_GRAPHIC_ID });
94
+ }
95
+
96
+ function initialSize(args) {
97
+ return Object.fromEntries(["width", "height"].map(key =>
98
+ [key, args.plot || Object.hasOwn(args, key) ? "explicit" : "auto"]));
99
+ }
100
+
70
101
  export const editCanvas = /* @__PURE__ */ action(
71
102
  {
72
103
  op: "editCanvas",
@@ -74,30 +105,25 @@ export const editCanvas = /* @__PURE__ */ action(
74
105
  },
75
106
  function (args = {}) {
76
107
  validateOptions(args, "editCanvas");
77
- const state = resolveCanvasState(this, args);
108
+ const state = resolveCanvasState(requireCanvas(this).properties, args, this.materializationConfigs.canvas);
109
+ assertBorderAvailable(this, state);
78
110
  let next = this;
79
-
80
- for (const property of ["width", "height", "background"]) {
81
- if (Object.hasOwn(args, property)) {
82
- next = next.editGraphics({
83
- target: "canvas",
84
- property,
85
- value: state[property]
86
- });
87
- }
111
+ if (state.plotBorder === false && this.materializationConfigs.canvas?.plotBorder !== undefined) {
112
+ next = next.editGraphics({ target: "plotBorder", remove: true });
88
113
  }
89
-
90
- next = next._withCanvasConfig({ margin: state.margin, size: state.size });
114
+ next = applyCanvasState(next, args, state);
91
115
 
92
116
  if (
93
117
  Object.hasOwn(args, "width") ||
94
118
  Object.hasOwn(args, "height") ||
95
- Object.hasOwn(args, "margin")
119
+ Object.hasOwn(args, "margin") || Object.hasOwn(args, "plot")
96
120
  ) {
97
121
  next = applyMaterializationPlan(
98
122
  next,
99
123
  planCanvasRematerialization(next)
100
124
  );
125
+ } else if (state.plotBorder !== false && Object.hasOwn(args, "plotBorder")) {
126
+ next = next.rematerializePlotBorder();
101
127
  }
102
128
 
103
129
  return next;
@@ -114,36 +140,14 @@ export const createCanvas = /* @__PURE__ */ action(
114
140
 
115
141
  assertCanvasHierarchyAvailable(this);
116
142
 
117
- const options = {
118
- width: Object.hasOwn(args, "width")
119
- ? args.width
120
- : DEFAULT_CANVAS.width,
121
- height: Object.hasOwn(args, "height")
122
- ? args.height
123
- : DEFAULT_CANVAS.height,
124
- background: Object.hasOwn(args, "background")
125
- ? args.background
126
- : DEFAULT_CANVAS.background,
127
- margin: Object.hasOwn(args, "margin")
128
- ? args.margin
129
- : DEFAULT_CANVAS.margin
130
- };
131
-
132
- const next = this
133
- .createGraphics({ id: CANVAS_GRAPHIC_ID, type: "canvas" })
134
- .createGraphics({
135
- id: PLOT_GRAPHIC_ID,
136
- type: "collection",
137
- parent: CANVAS_GRAPHIC_ID
138
- })
139
- .editCanvas(options);
140
- return next._withCanvasConfig({
141
- ...next.materializationConfigs.canvas,
142
- size: {
143
- width: Object.hasOwn(args, "width") ? "explicit" : "auto",
144
- height: Object.hasOwn(args, "height") ? "explicit" : "auto"
145
- }
146
- });
143
+ const plot = resolveCanvasPlot(args);
144
+ const options = { ...DEFAULT_CANVAS, ...args };
145
+ if (plot !== undefined) {
146
+ delete options.width;
147
+ delete options.height;
148
+ }
149
+ const next = createHierarchy(this).editCanvas(options);
150
+ return next._withCanvasConfig({ ...next.materializationConfigs.canvas, size: initialSize(args) });
147
151
  }
148
152
  );
149
153
 
@@ -155,38 +159,9 @@ export const createBasicCanvas = /* @__PURE__ */ action(
155
159
  function (args = {}) {
156
160
  validateOptions(args, "createCanvas", { allowEmpty: true });
157
161
  assertCanvasHierarchyAvailable(this);
158
- const state = {
159
- width: Object.hasOwn(args, "width") ? args.width : DEFAULT_CANVAS.width,
160
- height: Object.hasOwn(args, "height") ? args.height : DEFAULT_CANVAS.height,
161
- background: Object.hasOwn(args, "background")
162
- ? args.background
163
- : DEFAULT_CANVAS.background,
164
- margin: Object.hasOwn(args, "margin")
165
- ? normalizeMargin(args.margin, DEFAULT_MARGIN)
166
- : DEFAULT_MARGIN,
167
- size: {
168
- width: Object.hasOwn(args, "width") ? "explicit" : "auto",
169
- height: Object.hasOwn(args, "height") ? "explicit" : "auto"
170
- }
171
- };
172
- validateCanvasState(state);
173
- let next = this
174
- .createGraphics({
175
- id: CANVAS_GRAPHIC_ID,
176
- type: "canvas"
177
- })
178
- .createGraphics({
179
- id: PLOT_GRAPHIC_ID,
180
- type: "collection",
181
- parent: CANVAS_GRAPHIC_ID
182
- });
183
- for (const property of ["width", "height", "background"]) {
184
- next = next.editGraphics({
185
- target: CANVAS_GRAPHIC_ID,
186
- property,
187
- value: state[property]
188
- });
189
- }
190
- return next._withCanvasConfig({ margin: state.margin, size: state.size });
162
+ const state = resolveCanvasState(DEFAULT_CANVAS, args, { size: initialSize(args) });
163
+ assertBorderAvailable(this, state);
164
+ const next = applyCanvasState(createHierarchy(this), state, state);
165
+ return state.plotBorder === false ? next : next.rematerializePlotBorder();
191
166
  }
192
167
  );
@@ -7,6 +7,8 @@ import {
7
7
  import { resolveGraphicBounds } from "../../layout/canvas.js";
8
8
  import { findCanvasGraphic } from
9
9
  "../../materialization/graphicHierarchy.js";
10
+ import { resolveConcreteGraphicBounds } from "../../grammar/schemas/graphicBounds.js";
11
+ import { textBoundsFitCanvas } from "../../core/textMetrics.js";
10
12
 
11
13
  const OPTIONS = Object.freeze([
12
14
  "padding",
@@ -45,7 +47,17 @@ function normalizeOptions(args) {
45
47
 
46
48
  function probe(program, margin) {
47
49
  try {
48
- return { ok: true, program: program.editCanvas({ margin }) };
50
+ const next = program.editCanvas({ margin });
51
+ const title = next.guideConfigs.axis?.y?.title;
52
+ if (title?.inferredOffset && Math.abs(Math.cos(title.rotation)) < 1e-10) {
53
+ const bounds = resolveConcreteGraphicBounds(next.graphicSpec, "yAxisTitle", next.materializationConfigs.textMetrics);
54
+ const edge = title.position === "right" ? "right" : "left";
55
+ const reserve = title.fontSize / 4 * (edge === "right" ? 1 : -1);
56
+ if (!textBoundsFitCanvas({...bounds, [edge]: bounds[edge] + reserve}, findCanvasGraphic(next).properties)) {
57
+ return {ok:false, message:"Fitting retains inferred axis-title ink clearance."};
58
+ }
59
+ }
60
+ return { ok: true, program: next };
49
61
  } catch (error) {
50
62
  return {
51
63
  ok: false,
@@ -171,6 +183,9 @@ export const fitCanvas = /* @__PURE__ */ action(
171
183
  this.materializationConfigs.canvas?.margin === undefined) {
172
184
  throw new Error("fitCanvas requires an existing Canvas.");
173
185
  }
186
+ if (this.materializationConfigs.canvas.plot !== undefined) {
187
+ throw new Error("fitCanvas requires fixed outer dimensions; disable Canvas plot sizing first.");
188
+ }
174
189
  const previous = this.materializationConfigs.fitting;
175
190
  if (samePolicy(previous?.policy, policy) &&
176
191
  previous?.result?.signature === layoutSignature(this)) {
@@ -1,12 +1,15 @@
1
1
  import { createBasicCanvas, createCanvas, editCanvas } from "./actions.js";
2
2
  import { fitCanvas } from "./fitting.js";
3
+ import { rematerializePlotBorder } from "./plotBorder.js";
3
4
 
4
5
  export function registerCanvasActions(ProgramClass) {
5
6
  ProgramClass.prototype.editCanvas = editCanvas;
6
7
  ProgramClass.prototype.createCanvas = createCanvas;
7
8
  ProgramClass.prototype.fitCanvas = fitCanvas;
9
+ ProgramClass.prototype.rematerializePlotBorder = rematerializePlotBorder;
8
10
  }
9
11
 
10
12
  export function registerBasicCanvasActions(ProgramClass) {
11
13
  ProgramClass.prototype.createCanvas = createBasicCanvas;
14
+ ProgramClass.prototype.rematerializePlotBorder = rematerializePlotBorder;
12
15
  }
@@ -0,0 +1,28 @@
1
+ import { action } from "../../core/action.js";
2
+ import { noOptions } from "../../core/validation.js";
3
+ import { resolveGraphicBounds } from "../../layout/canvas.js";
4
+ import { resolvePlotGraphicPlacement } from "../../materialization/graphicHierarchy.js";
5
+
6
+ export const rematerializePlotBorder = /* @__PURE__ */ action(
7
+ { op: "rematerializePlotBorder", description: "Materialize the optional inset plot border." },
8
+ function (args = {}) {
9
+ noOptions(args, "rematerializePlotBorder");
10
+ const border = this.materializationConfigs.canvas?.plotBorder;
11
+ if (border === undefined) return this;
12
+ const plot = resolveGraphicBounds(this);
13
+ const half = border.lineWidth / 2;
14
+ const axis = this.graphicSpec.objects["plot-main"].children?.find(id =>
15
+ /^(?:(?:x|y|theta|radial)Axis(?:Line|Ticks|Labels|Title)|parallelAxis(?:Lines|Ticks|Labels|Titles))$/.test(id));
16
+ let next = this.graphicSpec.objects.plotBorder === undefined
17
+ ? this.createGraphics({ id: "plotBorder", type: "rect",
18
+ ...resolvePlotGraphicPlacement(this, axis === undefined ? {} : { before: axis }) })
19
+ : this;
20
+ const properties = { x: plot.x + half, y: plot.y + half,
21
+ width: plot.width - border.lineWidth, height: plot.height - border.lineWidth,
22
+ fill: "transparent", stroke: border.color, strokeWidth: border.lineWidth };
23
+ for (const [property, value] of Object.entries(properties)) {
24
+ next = next.editGraphics({ target: "plotBorder", property, value });
25
+ }
26
+ return next;
27
+ }
28
+ );
@@ -21,8 +21,8 @@ export const createAreaPlot = /* @__PURE__ */ action({ op: OPERATION, descriptio
21
21
  const independentChannel = valueChannel === "y" ? "x" : "y";
22
22
  const independent = omitUndefinedOptions(normalizeFieldEncoding(args[independentChannel], `${OPERATION} ${independentChannel}`));
23
23
  validateFacadeOptions(independent, ["field", "fieldType", "temporalUnit", "scale"], `${OPERATION} independent position`);
24
- if (independent.fieldType !== undefined && !["quantitative", "temporal"].includes(independent.fieldType)) {
25
- throw new Error("createAreaPlot independent position must be quantitative or temporal.");
24
+ if (independent.fieldType !== undefined && !["quantitative", "temporal", "nominal", "ordinal"].includes(independent.fieldType)) {
25
+ throw new Error("createAreaPlot independent position must be quantitative, temporal, or categorical.");
26
26
  }
27
27
  const measurement = omitUndefinedOptions(normalizeFieldEncoding(args[valueChannel], `${OPERATION} ${valueChannel}`));
28
28
  const ranged = Object.hasOwn(measurement, "lower") || Object.hasOwn(measurement, "upper");
@@ -17,12 +17,26 @@ import {
17
17
  } from "./shared.js";
18
18
 
19
19
  const OPTIONS = Object.freeze([
20
- "id", "data", "coordinate", "x", "y", "color", "width", "bar", "guides"
20
+ "id", "data", "coordinate", "orientation", "x", "y", "color", "width", "bar", "guides"
21
21
  ]);
22
22
  const BAR_OPTIONS = Object.freeze([
23
23
  "fill", "opacity", "stroke", "strokeWidth", ...RECT_STYLE_PROPERTIES
24
24
  ]);
25
25
 
26
+ function normalizeBarPosition(value, label) {
27
+ const encoding = normalizeFieldEncoding(value, label);
28
+ if (!Object.hasOwn(encoding, "lower") && !Object.hasOwn(encoding, "upper")) return encoding;
29
+ validateFacadeOptions(encoding, ["lower", "upper", "fieldType", "scale"], label);
30
+ if (typeof encoding.lower !== "string" || !encoding.lower.length ||
31
+ typeof encoding.upper !== "string" || !encoding.upper.length) {
32
+ throw new TypeError(`${label} range requires lower and upper field names.`);
33
+ }
34
+ if (encoding.fieldType !== undefined && encoding.fieldType !== "quantitative") {
35
+ throw new Error(`${label} range must be quantitative.`);
36
+ }
37
+ return { ...encoding, fieldType: "quantitative" };
38
+ }
39
+
26
40
  export const createBarPlot = /* @__PURE__ */ action(
27
41
  {
28
42
  op: "createBarPlot",
@@ -36,18 +50,31 @@ export const createBarPlot = /* @__PURE__ */ action(
36
50
  });
37
51
  const data = resolveFacadeData(this, args.data, "createBarPlot");
38
52
  const bar = normalizeAppearance(args.bar, BAR_OPTIONS, "createBarPlot bar");
39
- const x = normalizeFieldEncoding(args.x, "createBarPlot x");
40
- const y = normalizeFieldEncoding(args.y, "createBarPlot y");
53
+ const x = normalizeBarPosition(args.x, "createBarPlot x");
54
+ const y = normalizeBarPosition(args.y, "createBarPlot y");
55
+ const xRange = Object.hasOwn(x, "lower");
56
+ const yRange = Object.hasOwn(y, "lower");
57
+ if (xRange && yRange) throw new Error("createBarPlot accepts only one range channel.");
41
58
  x.fieldType = inferFacadeFieldType(this, data, x, "createBarPlot x");
42
59
  y.fieldType = inferFacadeFieldType(this, data, y, "createBarPlot y");
43
60
  const color = normalizeEncoding(args.color, "createBarPlot color");
44
61
  const width = normalizeTargetOptions(args.width, "createBarPlot width");
45
62
  const guides = normalizeGuides(args.guides, "createBarPlot");
63
+ const numeric = x.fieldType === "quantitative" && y.fieldType === "quantitative" &&
64
+ x.bin === undefined && y.bin === undefined && x.aggregate === undefined && y.aggregate === undefined;
65
+ if (args.orientation !== undefined && !numeric) throw new Error("createBarPlot orientation requires raw numeric positions.");
66
+ const orientation = numeric ? args.orientation ?? (xRange ? "horizontal" : "vertical") : undefined;
67
+ if (numeric && ((xRange && orientation !== "horizontal") || (yRange && orientation !== "vertical"))) {
68
+ throw new Error("createBarPlot range must match its numeric measure orientation.");
69
+ }
46
70
 
47
- const positions = isBarCategoryEncoding(y)
48
- ? [["encodeY", y], ["encodeX", x]]
49
- : [["encodeX", x], ["encodeY", y]];
50
- let next = this.createBarMark({ id, data, ...bar });
71
+ if (!numeric && ((xRange && !isBarCategoryEncoding(y)) || (yRange && !isBarCategoryEncoding(x)))) {
72
+ throw new Error("createBarPlot range requires a categorical or temporal opposite position.");
73
+ }
74
+ const positions = isBarCategoryEncoding(y) || orientation === "horizontal"
75
+ ? [["encodeY", y], [xRange ? "encodeXRange" : "encodeX", x]]
76
+ : [["encodeX", x], [yRange ? "encodeYRange" : "encodeY", y]];
77
+ let next = this.createBarMark({ id, data, ...bar, ...(orientation === undefined ? {} : { orientation }) });
51
78
  for (const [operation, encoding] of positions) {
52
79
  next = next[operation](positionArgs(encoding, {
53
80
  target: id, coordinate: args.coordinate
@@ -1,5 +1,6 @@
1
- import { action } from "../../core/action.js";
2
1
  import { validateUserId } from "../../core/identifiers.js";
2
+ import { action } from "../../core/action.js";
3
+ import { resolveConfiguredOwner } from "../../selectors/layers.js";
3
4
  import { validateNonEmptyString, validateOptionObject } from "../../core/validation.js";
4
5
  import { normalizeGroupFields } from "../../grammar/pathSeries.js";
5
6
  import { STROKE_STYLE_PROPERTIES } from "../../grammar/strokeStyle.js";
@@ -55,22 +56,7 @@ function normalizeOutputs(value, id, operation) {
55
56
  return { ...value };
56
57
  }
57
58
 
58
- function resolveOwner(program, requested) {
59
- const eligible = program.semanticSpec.layers.filter(
60
- layer => program.markConfigs[layer.id]?.ecdfPlot !== undefined
61
- );
62
- if (requested !== undefined) {
63
- const id = validateUserId(requested, "ECDF-plot owner id");
64
- const owner = eligible.find(layer => layer.id === id);
65
- if (owner === undefined) throw new Error(`Unknown ECDF-plot owner "${id}".`);
66
- return owner;
67
- }
68
- const current = eligible.find(layer => layer.id === program.context.currentMark);
69
- if (current !== undefined) return current;
70
- if (eligible.length === 1) return eligible[0];
71
- if (eligible.length === 0) throw new Error("editECDFPlot requires an ECDF plot.");
72
- throw new Error("editECDFPlot target is ambiguous; provide target.");
73
- }
59
+
74
60
 
75
61
  export const createECDFPlot = /* @__PURE__ */ action(
76
62
  {
@@ -169,7 +155,7 @@ export const editECDFPlot = /* @__PURE__ */ action(
169
155
  if (changes.length === 0) {
170
156
  throw new Error("editECDFPlot requires at least one ECDF option.");
171
157
  }
172
- const owner = resolveOwner(this, args.target);
158
+ const owner = resolveConfiguredOwner(this, args.target, { config: "ecdfPlot", operation: "editECDFPlot", kind: "ECDF", article: "an" }, validateUserId);
173
159
  const current = this.markConfigs[owner.id].ecdfPlot;
174
160
  const revised = { ...current.options };
175
161
  for (const key of changes) {
@@ -1,7 +1,8 @@
1
+ import { validateUserId } from "../../core/identifiers.js";
1
2
  import { action } from "../../core/action.js";
2
3
  import { STROKE_STYLE_PROPERTIES } from "../../grammar/strokeStyle.js";
3
4
  import { isPlainObject } from "../../core/immutable.js";
4
- import { validateUserId } from "../../core/identifiers.js";
5
+ import { resolveConfiguredOwner } from "../../selectors/layers.js";
5
6
  import { isNominalValue } from "../../grammar/scales/fields.js";
6
7
  import {
7
8
  validateNonEmptyString,
@@ -400,22 +401,7 @@ export const createDumbbellPlot = /* @__PURE__ */ action(
400
401
  }
401
402
  );
402
403
 
403
- function resolveEndpointOwner(program, requested) {
404
- const eligible = program.semanticSpec.layers.filter(
405
- layer => program.markConfigs[layer.id]?.endpointPlot !== undefined
406
- );
407
- if (requested !== undefined) {
408
- const id = validateUserId(requested, "Endpoint-plot owner id");
409
- const owner = eligible.find(layer => layer.id === id);
410
- if (owner === undefined) throw new Error(`Unknown endpoint-plot owner "${id}".`);
411
- return owner;
412
- }
413
- const current = eligible.find(layer => layer.id === program.context.currentMark);
414
- if (current !== undefined) return current;
415
- if (eligible.length === 1) return eligible[0];
416
- if (eligible.length === 0) throw new Error("editEndpointPlot requires an endpoint plot.");
417
- throw new Error("editEndpointPlot target is ambiguous; provide target.");
418
- }
404
+
419
405
 
420
406
  const KIND_METHOD = Object.freeze({
421
407
  dot: "createDotPlot",
@@ -457,7 +443,7 @@ export const editEndpointPlot = /* @__PURE__ */ action(
457
443
  if (changes.length === 0) {
458
444
  throw new Error("editEndpointPlot requires at least one endpoint-plot option.");
459
445
  }
460
- const owner = resolveEndpointOwner(this, args.target);
446
+ const owner = resolveConfiguredOwner(this, args.target, { config: "endpointPlot", operation: "editEndpointPlot", kind: "endpoint", article: "an" }, validateUserId);
461
447
  const current = this.markConfigs[owner.id].endpointPlot;
462
448
  const allowed = KIND_ROLES[current.kind];
463
449
  const invalid = changes.find(key => !allowed.has(key));
@@ -153,12 +153,12 @@ export const createHeatmap = /* @__PURE__ */ action(
153
153
  if (args.bin === undefined) {
154
154
  const x = normalizePosition(args.x, "x", true);
155
155
  const y = normalizePosition(args.y, "y", true);
156
- const color = normalizeFieldEncoding(args.color, `${OPERATION} color`);
157
- const next = this
156
+ const color = args.color === undefined ? undefined : normalizeFieldEncoding(args.color, `${OPERATION} color`);
157
+ let next = this
158
158
  .createRectMark({ id, data, ...rect })
159
159
  .encodeX(positionArgs(x, { target: id, coordinate: args.coordinate }))
160
- .encodeY(positionArgs(y, { target: id, coordinate: args.coordinate }))
161
- .encodeColor(targetArgs(color, id));
160
+ .encodeY(positionArgs(y, { target: id, coordinate: args.coordinate }));
161
+ if (color !== undefined) next = next.encodeColor(targetArgs(color, id));
162
162
  return applyFacadeGuides(next, guides, id);
163
163
  }
164
164
 
@@ -5,6 +5,7 @@ import { createBarPlot } from "./bar.js";
5
5
  import { createHistogram } from "./histogram.js";
6
6
  import { createLinePlot } from "./line.js";
7
7
  import { createScatterPlot } from "./scatter.js";
8
+ import { createTextPlot } from "./text.js";
8
9
  import { createParallelCoordinates } from "./parallel.js";
9
10
  import { createPiePlot } from "./pie.js";
10
11
  import { createDensityPlot } from "./density.js";
@@ -28,6 +29,7 @@ export function registerChartActions(ProgramClass) {
28
29
  ProgramClass.prototype.createHeatmap = createHeatmap;
29
30
  ProgramClass.prototype.createHistogram = createHistogram;
30
31
  ProgramClass.prototype.createScatterPlot = createScatterPlot;
32
+ ProgramClass.prototype.createTextPlot = createTextPlot;
31
33
  ProgramClass.prototype.createLinePlot = createLinePlot;
32
34
  ProgramClass.prototype.createParallelCoordinates = createParallelCoordinates;
33
35
  ProgramClass.prototype.createPiePlot = createPiePlot;
@@ -135,7 +135,7 @@ export const createIntervalPlot = /* @__PURE__ */ action(
135
135
 
136
136
  const REGRESSION_OPTIONS = Object.freeze([
137
137
  "id", "data", "coordinate", "x", "y", "color", "size", "shape", "point",
138
- "groupBy", "method", "degree", "span", "confidenceMethod", "level", "confidence",
138
+ "groupBy", "method", "degree", "span", "robustIterations", "confidenceMethod", "level", "confidence",
139
139
  "interval", "band", "line", "guides"
140
140
  ]);
141
141
 
@@ -163,7 +163,7 @@ export const createRegressionPlot = /* @__PURE__ */ action(
163
163
  guides: false
164
164
  });
165
165
  const regression = Object.fromEntries([
166
- "groupBy", "method", "degree", "span", "confidenceMethod", "level",
166
+ "groupBy", "method", "degree", "span", "robustIterations", "confidenceMethod", "level",
167
167
  "confidence", "interval", "band", "line"
168
168
  ].filter(key => Object.hasOwn(args, key)).map(key => [key, args[key]]));
169
169
  next = next.createRegression({ target: id, ...regression });
@@ -20,7 +20,7 @@ const OPTIONS = Object.freeze([
20
20
  "strokeDash", "line", "guides"
21
21
  ]);
22
22
  const LINE_OPTIONS = Object.freeze([
23
- "strokeWidth", "curve", "stroke", "opacity", "closed",
23
+ "strokeWidth", "curve", "tension", "stroke", "opacity", "closed",
24
24
  ...STROKE_STYLE_PROPERTIES
25
25
  ]);
26
26