ggaction 0.0.16 → 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 (299) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +3 -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 +10 -4
  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/bin.js +1 -1
  34. package/src/actions/data/box.js +2 -1
  35. package/src/actions/data/create.js +18 -5
  36. package/src/actions/data/density.js +1 -1
  37. package/src/actions/data/derived.js +15 -1
  38. package/src/actions/data/edit.js +20 -7
  39. package/src/actions/data/filter.js +15 -4
  40. package/src/actions/data/index.js +3 -0
  41. package/src/actions/data/interval.js +2 -0
  42. package/src/actions/data/regression.js +6 -2
  43. package/src/actions/data/revise.js +37 -5
  44. package/src/actions/data/shared.js +33 -4
  45. package/src/actions/data/sort.js +21 -0
  46. package/src/actions/data/stack.js +3 -1
  47. package/src/actions/data/summary.js +1 -1
  48. package/src/actions/encodings/angle.js +4 -1
  49. package/src/actions/encodings/appearance.js +43 -31
  50. package/src/actions/encodings/barWidth.js +36 -11
  51. package/src/actions/encodings/channels.js +0 -1
  52. package/src/actions/encodings/color/continuous.js +5 -2
  53. package/src/actions/encodings/color/index.js +5 -2
  54. package/src/actions/encodings/color/policy.js +10 -3
  55. package/src/actions/encodings/density/resolve.js +1 -6
  56. package/src/actions/encodings/offset.js +4 -1
  57. package/src/actions/encodings/position/policies/arc.js +12 -10
  58. package/src/actions/encodings/position/policies/area.js +3 -3
  59. package/src/actions/encodings/position/policies/bar.js +15 -6
  60. package/src/actions/encodings/position/policies/line.js +17 -6
  61. package/src/actions/encodings/position/resolve.js +9 -3
  62. package/src/actions/encodings/ranged.js +23 -1
  63. package/src/actions/encodings/remove.js +3 -2
  64. package/src/actions/encodings/ruleAppearance.js +4 -1
  65. package/src/actions/encodings/seriesLayout.js +1 -0
  66. package/src/actions/encodings/stroke.js +4 -1
  67. package/src/actions/encodings/strokeDash.js +6 -1
  68. package/src/actions/encodings/text.js +12 -4
  69. package/src/actions/errorBands/create.js +2 -2
  70. package/src/actions/errorBands/edit.js +2 -1
  71. package/src/actions/facets/actions.js +158 -240
  72. package/src/actions/gradientPlots/resolve.js +2 -17
  73. package/src/actions/guides/applicability.js +1 -1
  74. package/src/actions/guides/axes/axis.js +16 -4
  75. package/src/actions/guides/axes/edit.js +4 -4
  76. package/src/actions/guides/axes/labels.js +120 -46
  77. package/src/actions/guides/axes/lines.js +6 -5
  78. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  79. package/src/actions/guides/axes/parallel/policy.js +8 -7
  80. package/src/actions/guides/axes/remove.js +3 -2
  81. package/src/actions/guides/axes/tickGroups.js +5 -2
  82. package/src/actions/guides/axes/ticks.js +6 -3
  83. package/src/actions/guides/axes/titles.js +71 -26
  84. package/src/actions/guides/basic.js +2 -0
  85. package/src/actions/guides/facadeLegend.js +10 -2
  86. package/src/actions/guides/grids/grid.js +7 -1
  87. package/src/actions/guides/grids/resolve.js +59 -12
  88. package/src/actions/guides/legends/blocks.js +3 -1
  89. package/src/actions/guides/legends/categorical/actions.js +70 -17
  90. package/src/actions/guides/legends/categorical/components.js +12 -8
  91. package/src/actions/guides/legends/categorical/index.js +6 -0
  92. package/src/actions/guides/legends/categorical/layout.js +37 -8
  93. package/src/actions/guides/legends/categorical/options.js +31 -20
  94. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  95. package/src/actions/guides/legends/categorical/symbols.js +18 -12
  96. package/src/actions/guides/legends/categorical/validation.js +1 -6
  97. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  98. package/src/actions/guides/legends/continuous/common.js +1 -447
  99. package/src/actions/guides/legends/continuous/gradient.js +47 -34
  100. package/src/actions/guides/legends/continuous/interval.js +4 -4
  101. package/src/actions/guides/legends/continuous/opacity.js +19 -12
  102. package/src/actions/guides/legends/edit.js +1 -604
  103. package/src/actions/guides/legends/focused.js +2 -2
  104. package/src/actions/guides/legends/group.js +1 -0
  105. package/src/actions/guides/legends/lane.js +1 -516
  106. package/src/actions/guides/legends/remove.js +3 -0
  107. package/src/actions/guides/legends/sampling.js +1 -1
  108. package/src/actions/guides/legends/size.js +51 -43
  109. package/src/actions/guides/legends/strokeWidth.js +11 -29
  110. package/src/actions/guides/legends/target.js +3 -3
  111. package/src/actions/guides/legends/transition.js +21 -7
  112. package/src/actions/guides/polar/axes/labels.js +4 -2
  113. package/src/actions/guides/polar/axes/titles.js +7 -5
  114. package/src/actions/guides/polar/resolve.js +6 -14
  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 +17 -4
  120. package/src/actions/marks/bar/edit.js +18 -7
  121. package/src/actions/marks/bar/materialize.js +9 -6
  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/create.js +8 -1
  125. package/src/actions/marks/point/edit.js +13 -6
  126. package/src/actions/marks/point/materialize.js +4 -2
  127. package/src/actions/marks/rect/actions.js +26 -11
  128. package/src/actions/marks/remove.js +2 -0
  129. package/src/actions/marks/rule/actions.js +31 -12
  130. package/src/actions/marks/text/actions.js +42 -11
  131. package/src/actions/marks/text/layout.js +3 -0
  132. package/src/actions/marks/tick/actions.js +23 -8
  133. package/src/actions/primitives/semanticAction.js +37 -3
  134. package/src/actions/primitives/semanticValidation/dataset.js +13 -1
  135. package/src/actions/primitives/semanticValidation/layer.js +26 -2
  136. package/src/actions/primitives/semanticValidation/scale.js +6 -0
  137. package/src/actions/regression/create.js +26 -5
  138. package/src/actions/regression/edit.js +50 -15
  139. package/src/actions/regression/follow.js +22 -0
  140. package/src/actions/regression/resolve.js +2 -6
  141. package/src/actions/scales/channels.js +20 -15
  142. package/src/actions/scales/consumers/common.js +2 -1
  143. package/src/actions/scales/consumers/families.js +16 -5
  144. package/src/actions/scales/create.js +10 -4
  145. package/src/actions/scales/definitions.js +1 -425
  146. package/src/actions/scales/edit.js +3 -3
  147. package/src/actions/scales/editPolicy.js +9 -3
  148. package/src/actions/scales/materialize.js +1 -1
  149. package/src/actions/scales/parallel.js +1 -1
  150. package/src/actions/scales/patch.js +1 -1
  151. package/src/actions/scales/preview.js +49 -2
  152. package/src/actions/scales/quantitativeColor.js +3 -3
  153. package/src/actions/selection/actions.js +1 -13
  154. package/src/actions/theme/reconcile.js +83 -27
  155. package/src/actions/theme/state.js +12 -0
  156. package/src/actions/titles/actions.js +5 -18
  157. package/src/actions/titles/resolve.js +41 -19
  158. package/src/actions/violinPlots/edit.js +3 -20
  159. package/src/core/action.js +3 -2
  160. package/src/core/compositionState.js +4 -1
  161. package/src/core/font.js +9 -2
  162. package/src/core/optionRequirements.js +1 -0
  163. package/src/core/programState.js +5 -1
  164. package/src/core/resourceReferences.js +3 -0
  165. package/src/core/textMetricProfile.js +3 -2
  166. package/src/core/textMetrics.js +41 -4
  167. package/src/core/validation.js +26 -0
  168. package/src/core/vocabulary.js +4 -0
  169. package/src/grammar/aggregate.js +4 -18
  170. package/src/grammar/arcs.js +1 -386
  171. package/src/grammar/areaPaint.js +10 -0
  172. package/src/grammar/areaSeries.js +18 -10
  173. package/src/grammar/bars/geometry.js +30 -3
  174. package/src/grammar/bars/policy.js +12 -1
  175. package/src/grammar/bin.js +41 -22
  176. package/src/grammar/bin2d.js +1 -6
  177. package/src/grammar/boxPlot.js +56 -9
  178. package/src/grammar/categoryOrder.js +1 -6
  179. package/src/grammar/complete.js +15 -19
  180. package/src/grammar/computed.js +92 -25
  181. package/src/grammar/curveCommands.js +29 -16
  182. package/src/grammar/datasetSchema.js +382 -0
  183. package/src/grammar/density.js +53 -5
  184. package/src/grammar/displayLabels.js +28 -0
  185. package/src/grammar/ecdf.js +2 -9
  186. package/src/grammar/facets/dependencies.js +6 -2
  187. package/src/grammar/facets/guides.js +5 -0
  188. package/src/grammar/facets/index.js +2 -5
  189. package/src/grammar/filter.js +133 -39
  190. package/src/grammar/fold.js +2 -12
  191. package/src/grammar/gradientProfile.js +1 -6
  192. package/src/grammar/impute.js +21 -48
  193. package/src/grammar/interval.js +34 -10
  194. package/src/grammar/itemMissing.js +59 -0
  195. package/src/grammar/legendGradient.js +18 -0
  196. package/src/grammar/legendGroups.js +1 -0
  197. package/src/grammar/legendTitleStyle.js +22 -0
  198. package/src/grammar/lineSeries.js +32 -17
  199. package/src/grammar/markLabels.js +5 -2
  200. package/src/grammar/normalize.js +4 -54
  201. package/src/grammar/numeric.js +1 -290
  202. package/src/grammar/parallelCoordinates.js +1 -6
  203. package/src/grammar/parseTime.js +41 -0
  204. package/src/grammar/pathCommands.js +11 -4
  205. package/src/grammar/polar.js +1 -184
  206. package/src/grammar/positionCompatibility.js +3 -3
  207. package/src/grammar/regression/derive.js +63 -16
  208. package/src/grammar/regression/index.js +1 -0
  209. package/src/grammar/regression/models.js +116 -59
  210. package/src/grammar/regression/parameters.js +69 -6
  211. package/src/grammar/roundedRect.js +42 -54
  212. package/src/grammar/scales/appearance.js +1 -206
  213. package/src/grammar/scales/color.js +1 -382
  214. package/src/grammar/scales/colorConsumers.js +5 -2
  215. package/src/grammar/scales/definition.js +1 -190
  216. package/src/grammar/scales/discretized.js +1 -195
  217. package/src/grammar/scales/size.js +1 -456
  218. package/src/grammar/scales/temporal.js +2 -13
  219. package/src/grammar/scales/transformed.js +1 -383
  220. package/src/grammar/scales/types.js +1 -1
  221. package/src/grammar/scales/validation.js +1 -66
  222. package/src/grammar/schemas/concreteGraphic.js +11 -0
  223. package/src/grammar/schemas/graphic.js +3 -0
  224. package/src/grammar/schemas/semanticPath.js +5 -3
  225. package/src/grammar/sort.js +88 -0
  226. package/src/grammar/stack.js +2 -10
  227. package/src/grammar/statisticalReference.js +1 -6
  228. package/src/grammar/summary.js +120 -45
  229. package/src/grammar/text.js +19 -0
  230. package/src/grammar/timeUnit.js +16 -7
  231. package/src/grammar/transformKeys.js +47 -0
  232. package/src/grammar/transformTopology.js +1 -0
  233. package/src/grammar/transforms.js +25 -12
  234. package/src/grammar/valueFormat.js +17 -5
  235. package/src/grammar/weightedStatistics.js +1 -6
  236. package/src/grammar/window.js +4 -58
  237. package/src/inspection.js +495 -0
  238. package/src/inspectionDescriptors.js +3 -0
  239. package/src/layout/canvas.js +1 -110
  240. package/src/layout/facets.js +1 -244
  241. package/src/layout/labels.js +1 -0
  242. package/src/layout/legend.js +13 -0
  243. package/src/layout/legendGroup.js +1 -0
  244. package/src/layout/legendItems.js +59 -15
  245. package/src/layout/legendLane.js +1 -441
  246. package/src/layout/legendText.js +1 -0
  247. package/src/layout/title.js +35 -13
  248. package/src/materialization/bars/aggregate.js +11 -5
  249. package/src/materialization/bars/histogram.js +4 -1
  250. package/src/materialization/bars/ranged.js +19 -6
  251. package/src/materialization/bars/resolve.js +26 -3
  252. package/src/materialization/coordinateBounds.js +8 -0
  253. package/src/materialization/dependencies.js +4 -44
  254. package/src/materialization/facetGuides/layout.js +1 -0
  255. package/src/materialization/facetGuides/placement.js +19 -8
  256. package/src/materialization/facetGuides/preparation.js +15 -7
  257. package/src/materialization/facetHeaders.js +1 -347
  258. package/src/materialization/facets.js +1 -185
  259. package/src/materialization/graphicHierarchy.js +1 -1
  260. package/src/materialization/guides/layout.js +1 -57
  261. package/src/materialization/guides/resources.js +1 -123
  262. package/src/materialization/layout.js +53 -0
  263. package/src/materialization/legendGroups.js +1 -0
  264. package/src/materialization/legendText.js +1 -0
  265. package/src/materialization/legends.js +1 -1
  266. package/src/materialization/marks/capabilities.js +5 -6
  267. package/src/materialization/rect.js +1 -1
  268. package/src/materialization/revisionIdentity.js +19 -0
  269. package/src/materialization/scaleGuideDependencies.js +1 -1
  270. package/src/materialization/scales/map.js +3 -0
  271. package/src/materialization/scales/policies/arc.js +8 -7
  272. package/src/materialization/scales/policies/bar.js +19 -7
  273. package/src/materialization/scales/policies/series.js +2 -1
  274. package/src/materialization/scales/resolve.js +72 -7
  275. package/src/materialization/selection/items/arc.js +1 -1
  276. package/src/materialization/selection/items/bar.js +7 -6
  277. package/src/materialization/selection/items/path.js +7 -6
  278. package/src/materialization/selection/items/rect.js +2 -2
  279. package/src/materialization/text.js +17 -6
  280. package/src/materialization/textLines.js +15 -0
  281. package/src/persistence/codec.js +37 -6
  282. package/src/persistence/validation.js +47 -0
  283. package/src/persistence.js +94 -8
  284. package/src/renderers/canvas/circle.js +6 -3
  285. package/src/renderers/canvas/fill.js +7 -1
  286. package/src/renderers/canvas/index.js +2 -1
  287. package/src/renderers/canvas/line.js +1 -0
  288. package/src/renderers/canvas/path.js +1 -1
  289. package/src/renderers/canvas/rect.js +11 -8
  290. package/src/renderers/canvas/stroke.js +2 -1
  291. package/src/renderers/canvas/text.js +5 -3
  292. package/src/renderers/svg.js +4 -1
  293. package/src/selectors/layers.js +19 -0
  294. package/src/theme/defaults.js +8 -2
  295. package/src/version.js +1 -1
  296. package/types/basic.d.ts +1 -0
  297. package/types/index.d.ts +7 -0
  298. package/types/inspection.d.ts +17 -0
  299. package/types/program.d.ts +494 -112
@@ -1,3 +1,4 @@
1
+ import { boxBarWidth, boxWhiskerAppearance } from "./options.js";
1
2
  import { applyFacadeGuides } from "../charts/shared.js";
2
3
  import { action } from "../../core/action.js";
3
4
  import { validateUserId } from "../../core/identifiers.js";
@@ -60,7 +61,7 @@ export const materializeBoxPlot = /* @__PURE__ */ action(
60
61
  medianId,
61
62
  outlierId
62
63
  },
63
- barWidth: { band: config.width },
64
+ barWidth: boxBarWidth(config.width),
64
65
  fill: config.box.fill,
65
66
  opacity: config.box.opacity,
66
67
  stroke: config.box.stroke,
@@ -73,6 +74,7 @@ export const materializeBoxPlot = /* @__PURE__ */ action(
73
74
  source,
74
75
  category: category.field,
75
76
  field: measure.field,
77
+ ...(config.summary === undefined ? {} : { summary: config.summary }),
76
78
  whisker: config.whisker.type,
77
79
  ...(config.whisker.factor === undefined
78
80
  ? {}
@@ -144,7 +146,8 @@ export const materializeBoxPlot = /* @__PURE__ */ action(
144
146
  }),
145
147
  coordinate: layer.coordinate,
146
148
  stroke: "#111111",
147
- strokeWidth: 1.5
149
+ strokeWidth: 1.5,
150
+ ...boxWhiskerAppearance(config.whisker)
148
151
  });
149
152
  next = next
150
153
  .editSemantic({
@@ -156,7 +159,8 @@ export const materializeBoxPlot = /* @__PURE__ */ action(
156
159
  id: ownerId,
157
160
  type: "rect",
158
161
  length: 0,
159
- ...resolvePlotGraphicPlacement(next)
162
+ ...resolvePlotGraphicPlacement(next, next.graphicSpec.objects.plotBorder === undefined
163
+ ? {} : { before: "plotBorder" })
160
164
  })
161
165
  .rematerializeBarMark({ id: ownerId })
162
166
  .createBoxMedian({
@@ -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;