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
@@ -6,16 +6,48 @@ import { requireLayer } from "../../selectors/layers.js";
6
6
  import { applyRevisionPlan, buildSourceRevisionPlan } from "./edit.js";
7
7
  import { resolveStoredSelection } from "../../materialization/selection/state.js";
8
8
  import { withGuideLayoutTransaction } from "../../materialization/guides/layout.js";
9
+ import { assertFieldsAvailable } from "../../grammar/datasetSchema.js";
9
10
 
10
11
  let reviseFacet;
11
12
 
13
+ function layerInputFields(layer) {
14
+ const fields = [];
15
+ const add = value => {
16
+ if (typeof value === "string" && value.length > 0) fields.push(value);
17
+ };
18
+ for (const encoding of Object.values(layer.encoding ?? {})) {
19
+ if (!encoding || typeof encoding !== "object") continue;
20
+ add(encoding.field);
21
+ add(encoding.weight);
22
+ add(encoding.key);
23
+ add(encoding.pathOrder?.field);
24
+ add(encoding.categoryOrder?.by?.field);
25
+ for (const field of encoding.fields ?? []) add(field);
26
+ for (const dimension of encoding.dimensions ?? []) add(dimension.field);
27
+ }
28
+ return [...new Set(fields)];
29
+ }
30
+
12
31
  export function registerFacetDataRevision(handler) {
13
32
  reviseFacet = handler;
14
33
  }
15
34
 
16
- export function reviseUnitData(program, { source, id, values }) {
35
+ export function reviseUnitData(program, { source, id, values, schema }) {
17
36
  const plan = buildSourceRevisionPlan(program, source, id);
18
- const created = program.createData({ id, values });
37
+ const original = requireDataset(program, source);
38
+ const inheritedSchema = schema ?? (original.schema?.origin === "declared"
39
+ ? { fields: original.schema.fields.map(({ name, storageType, nullable, optional }) => ({
40
+ name, storageType, nullable, optional
41
+ })) }
42
+ : undefined);
43
+ const created = program.createData({ id, values, ...(inheritedSchema === undefined ? {} : { schema: inheritedSchema }) });
44
+ const revisedSource = requireDataset(created, id);
45
+ for (const layer of program.semanticSpec.layers.filter(layer => layer.data === source)) {
46
+ assertFieldsAvailable(revisedSource.schema, layerInputFields(layer), {
47
+ data: id,
48
+ operation: "reviseData"
49
+ });
50
+ }
19
51
  const next = withGuideLayoutTransaction(created, candidate =>
20
52
  applyRevisionPlan(candidate, undefined, plan, { retain: new Set([source]) }));
21
53
  for (const [selection, config] of Object.entries(next.materializationConfigs.selections ?? {})) {
@@ -27,7 +59,7 @@ export function reviseUnitData(program, { source, id, values }) {
27
59
  }
28
60
 
29
61
  export const reviseData = /* @__PURE__ */ closedAction(
30
- { op: "reviseData", description: "Revise source data and its dependent chart.", scope: "any" }, ["source", "id", "values"],
62
+ { op: "reviseData", description: "Revise source data and its dependent chart.", scope: "any" }, ["source", "id", "values", "schema"],
31
63
  function (args = {}) {
32
64
  const source = validateUserId(args.source, "Source dataset id");
33
65
  const id = validateUserId(args.id, "Revision dataset id");
@@ -39,8 +71,8 @@ export const reviseData = /* @__PURE__ */ closedAction(
39
71
  throw new Error(`Source "${source}" must be a materialized original dataset.`);
40
72
  }
41
73
  const apply = this.compositionSpec === undefined
42
- ? program => reviseUnitData(program, { source, id, values: args.values }).program
43
- : program => reviseFacet(program, { source, id, values: args.values });
74
+ ? program => reviseUnitData(program, { source, id, values: args.values, schema: args.schema }).program
75
+ : program => reviseFacet(program, { source, id, values: args.values, schema: args.schema });
44
76
  // The entire dependent materialization must succeed before returning a revision.
45
77
  apply(this);
46
78
  return apply(this);
@@ -5,6 +5,12 @@ import {
5
5
  findDataset,
6
6
  resolveDatasetReference
7
7
  } from "../../selectors/datasets.js";
8
+ import {
9
+ assertFieldsAvailable,
10
+ deriveTransformSchema,
11
+ transformInputFields,
12
+ validateRowsAgainstSchema
13
+ } from "../../grammar/datasetSchema.js";
8
14
 
9
15
  export const MATERIALIZE_OPTIONS = Object.freeze(["id"]);
10
16
 
@@ -37,17 +43,39 @@ export function derivedMaterializer(op, description, type, derive, resolve) {
37
43
  args.id,
38
44
  type
39
45
  );
46
+ assertFieldsAvailable(source.schema, transformInputFields(transform), {
47
+ data: source.id,
48
+ operation: op
49
+ });
40
50
  const result = derive(source.values, transform);
51
+ const values = Array.isArray(result) ? result : result.values;
52
+ const schema = deriveTransformSchema(source.schema, transform, values, {
53
+ sourceId: source.id,
54
+ ownerId: id
55
+ });
56
+ validateRowsAgainstSchema(values, schema, `Derived dataset "${id}"`);
41
57
  const program = resolve === undefined
42
58
  ? this
43
59
  : this.editSemantic({
44
60
  property: `dataset[${id}].transform`,
45
61
  value: resolve(result, transform)
46
62
  });
47
- return program.editSemantic({
63
+ let next = program.editSemantic({
48
64
  property: `dataset[${id}].values`,
49
- value: Array.isArray(result) ? result : result.values
65
+ value: values
50
66
  });
67
+ if (!Array.isArray(result) && result.report !== undefined) {
68
+ const report = {
69
+ ...result.report,
70
+ owner: { kind: "data", id },
71
+ inputs: [{ kind: "data", id: source.id }]
72
+ };
73
+ next = next._withMaterializationConfig(
74
+ ["calculations", "datasets", id],
75
+ report
76
+ );
77
+ }
78
+ return next;
51
79
  });
52
80
  }
53
81
 
@@ -59,11 +87,12 @@ export function derivedCreator(
59
87
  sourceLabel,
60
88
  transform,
61
89
  materialize,
62
- requireSource = false
90
+ requireSource = false,
91
+ defaultId
63
92
  ) {
64
93
  return action({ op, description }, function (args = {}) {
65
94
  validateKeys(args, options, op);
66
- const id = validateUserId(args.id, idLabel);
95
+ const id = validateUserId(args.id === undefined ? defaultId : args.id, idLabel);
67
96
  const requestedSource = validateUserId(
68
97
  requireSource ? args.source : args.source ?? this.context.currentData,
69
98
  sourceLabel
@@ -0,0 +1,21 @@
1
+ import { deriveSortedRows, normalizeSortTransform } from "../../grammar/sort.js";
2
+ import { derivedCreator, derivedMaterializer } from "./shared.js";
3
+
4
+ const OPTIONS = Object.freeze(["id", "source", "sortBy"]);
5
+
6
+ export const materializeSortedData = /* @__PURE__ */ derivedMaterializer(
7
+ "materializeSortedData",
8
+ "Materialize one immutable stable sorted dataset.",
9
+ "sort",
10
+ deriveSortedRows
11
+ );
12
+
13
+ export const createSortedData = /* @__PURE__ */ derivedCreator(
14
+ "createSortedData",
15
+ "Create reusable rows in a stable explicit order.",
16
+ OPTIONS,
17
+ "Sorted dataset id",
18
+ "Sorted source dataset id",
19
+ normalizeSortTransform,
20
+ "materializeSortedData"
21
+ );
@@ -22,5 +22,7 @@ export const createStackData = /* @__PURE__ */ derivedCreator(
22
22
  "Stack dataset id",
23
23
  "Stack source dataset id",
24
24
  normalizeStackTransform,
25
- "materializeStackData"
25
+ "materializeStackData",
26
+ false,
27
+ "stacked"
26
28
  );
@@ -5,7 +5,7 @@ import {
5
5
  import { derivedCreator, derivedMaterializer } from "./shared.js";
6
6
 
7
7
  const OPTIONS = Object.freeze([
8
- "id", "source", "groupBy", "aggregates", "members", "weight"
8
+ "id", "source", "groupBy", "aggregates", "members", "weight", "missing", "empty"
9
9
  ]);
10
10
 
11
11
  export const materializeSummaryData = /* @__PURE__ */ derivedMaterializer(
@@ -5,6 +5,8 @@ import {
5
5
  setEncodingProperties,
6
6
  validateOptions
7
7
  } from "./shared.js";
8
+ import { applyRequestedItemMissingPolicy } from
9
+ "../../grammar/itemMissing.js";
8
10
 
9
11
  const OPTIONS = Object.freeze(["target", "value", "field", "fieldType"]);
10
12
 
@@ -29,7 +31,7 @@ export const encodeAngle = /* @__PURE__ */ action(
29
31
  if (hasValue && args.fieldType !== undefined) {
30
32
  throw new Error("Constant angle does not accept fieldType.");
31
33
  }
32
- const { id: target, dataset, layer } = resolveTarget(
34
+ const { id: target, dataset: sourceDataset, layer } = resolveTarget(
33
35
  this,
34
36
  args.target,
35
37
  ["point", "tick"],
@@ -39,6 +41,7 @@ export const encodeAngle = /* @__PURE__ */ action(
39
41
  throw new TypeError("encodeAngle value must be finite degrees.");
40
42
  }
41
43
  if (hasField) {
44
+ const dataset = applyRequestedItemMissingPolicy(layer, sourceDataset, args.field);
42
45
  const fieldType = args.fieldType ?? "quantitative";
43
46
  if (fieldType !== "quantitative") {
44
47
  throw new Error("encodeAngle requires a quantitative field.");
@@ -1,9 +1,8 @@
1
+ import { canMaterializeBar } from "../../materialization/marks/index.js";
1
2
  import { action } from "../../core/action.js";
2
3
  import { validatePathSeriesAppearance } from "../../grammar/pathSeries.js";
3
4
  import { assertEncodingSelectionCompatibility } from "../../materialization/selection/compatibility.js";
4
5
  import {
5
- readNominalField,
6
- readQuantitativeField,
7
6
  readScaleField,
8
7
  validateOpacityValue
9
8
  } from "../../grammar/scales/index.js";
@@ -25,6 +24,8 @@ import {
25
24
  setEncodingProperties,
26
25
  validateOptions
27
26
  } from "./shared.js";
27
+ import { applyRequestedItemMissingPolicy } from
28
+ "../../grammar/itemMissing.js";
28
29
 
29
30
  const RADIUS_OPTIONS = Object.freeze(["value", "target"]);
30
31
  const REMOVE_RADIUS_OPTIONS = Object.freeze(["target"]);
@@ -34,16 +35,17 @@ const OPACITY_OPTIONS = Object.freeze([
34
35
  const FIELD_OPTIONS = Object.freeze(["field", "target", "fieldType", "scale"]);
35
36
  function encodeAppearanceField(program, channel, args, operation) {
36
37
  validateOptions(args, FIELD_OPTIONS, operation);
37
- const { id: target, dataset, layer } = resolveTarget(
38
+ const { id: target, dataset: sourceDataset, layer } = resolveTarget(
38
39
  program,
39
40
  args.target,
40
41
  ["point"],
41
42
  "point mark"
42
43
  );
43
- const expectedFieldType = channel === "shape" ? "nominal" : "quantitative";
44
- const fieldType = args.fieldType ?? expectedFieldType;
45
- if (fieldType !== expectedFieldType) {
46
- throw new Error(`${operation} requires a ${expectedFieldType} field.`);
44
+ const fieldTypes = channel === "shape" ? ["nominal"] : ["quantitative", "nominal", "ordinal"];
45
+ const dataset = applyRequestedItemMissingPolicy(layer, sourceDataset, args.field);
46
+ const fieldType = args.fieldType ?? fieldTypes[0];
47
+ if (!fieldTypes.includes(fieldType)) {
48
+ throw new Error(`${operation} requires a ${fieldTypes.join(" or ")} field.`);
47
49
  }
48
50
  if (channel === "size" && program.markConfigs[target]?.radius !== undefined) {
49
51
  throw new Error("encodeSize cannot be combined with a constant radius.");
@@ -56,17 +58,12 @@ function encodeAppearanceField(program, channel, args, operation) {
56
58
  const scale = resolveAppearanceScaleDefinition(
57
59
  program,
58
60
  channel,
59
- requestedScale
61
+ requestedScale,
62
+ fieldType
60
63
  );
61
- if (Object.hasOwn(scale, "unknown")) {
62
- readScaleField(dataset.values, args.field, fieldType, {
63
- allowUnknown: true
64
- });
65
- } else if (channel === "shape") {
66
- readNominalField(dataset.values, args.field);
67
- } else {
68
- readQuantitativeField(dataset.values, args.field);
69
- }
64
+ readScaleField(dataset.values, args.field, fieldType, {
65
+ allowUnknown: Object.hasOwn(scale, "unknown")
66
+ });
70
67
 
71
68
  let next = setEncodingProperties(program, target, channel, {
72
69
  field: args.field,
@@ -149,7 +146,7 @@ const removePointRadius = /* @__PURE__ */ action(
149
146
  const encodeSize = /* @__PURE__ */ action(
150
147
  {
151
148
  op: "encodeSize",
152
- description: "Encode a quantitative field as equal-area point size."
149
+ description: "Encode a field as equal-area point size."
153
150
  },
154
151
  function (args = {}) {
155
152
  return encodeAppearanceField(this, "size", args, "encodeSize");
@@ -181,6 +178,17 @@ const clearOpacityEncoding = /* @__PURE__ */ action(
181
178
  }
182
179
  );
183
180
 
181
+ function withoutConstantOpacity(program, target) {
182
+ const { opacity, ...config } = program.markConfigs[target] ?? {};
183
+ void opacity;
184
+ if (config.barAppearance?.opacity !== undefined) {
185
+ const { opacity: barOpacity, ...appearance } = config.barAppearance;
186
+ void barOpacity;
187
+ config.barAppearance = appearance;
188
+ }
189
+ return program._withMarkConfig(target, config);
190
+ }
191
+
184
192
  const encodeOpacity = /* @__PURE__ */ action(
185
193
  {
186
194
  op: "encodeOpacity",
@@ -196,17 +204,16 @@ const encodeOpacity = /* @__PURE__ */ action(
196
204
  if (hasValue && (args.fieldType !== undefined || args.scale !== undefined)) {
197
205
  throw new Error("Constant opacity does not accept fieldType or scale.");
198
206
  }
199
- const { id: target, dataset, layer } = resolveTarget(
207
+ const { id: target, dataset: sourceDataset, layer } = resolveTarget(
200
208
  this,
201
209
  args.target,
202
- ["point", "rule", "line"],
203
- "point, rule or line mark"
210
+ ["point", "rule", "line", "bar"],
211
+ "point, rule, line or bar mark"
204
212
  );
205
213
  if (hasValue) {
206
214
  validateOpacityValue(args.value, "encodeOpacity");
207
215
  assertEncodingSelectionCompatibility(this, target, ["opacity"]);
208
- const { opacity, ...config } = this.markConfigs[target] ?? {};
209
- void opacity;
216
+ const config = withoutConstantOpacity(this, target).markConfigs[target];
210
217
  const withoutLegend = this.guideConfigs.legend?.opacity?.target === target
211
218
  ? this.removeOpacityLegend()
212
219
  : this;
@@ -217,11 +224,17 @@ const encodeOpacity = /* @__PURE__ */ action(
217
224
  if (layer.mark.type === "line") {
218
225
  return rematerializeEncoding(next, target, "opacity", undefined, layer);
219
226
  }
227
+ if (layer.mark.type === "bar") {
228
+ return canMaterializeBar(next, findLayer(next, target))
229
+ ? applyDetachedScaleRematerialization(next.rematerializeBarMark({ id: target }), [layer])
230
+ : next;
231
+ }
220
232
  const materialized = layer.mark.type === "rule"
221
233
  ? next.rematerializeRuleMark({ id: target })
222
234
  : next.rematerializePointMark({ id: target });
223
235
  return applyDetachedScaleRematerialization(materialized, [layer]);
224
236
  }
237
+ const dataset = applyRequestedItemMissingPolicy(layer, sourceDataset, args.field);
225
238
  const fieldType = args.fieldType ?? "quantitative";
226
239
  if (fieldType !== "quantitative") {
227
240
  throw new Error("encodeOpacity requires a quantitative field.");
@@ -240,17 +253,13 @@ const encodeOpacity = /* @__PURE__ */ action(
240
253
  ...layer, encoding: { ...layer.encoding, opacity: { field: args.field, fieldType } }
241
254
  });
242
255
  }
243
- if (Object.hasOwn(scale, "unknown")) {
244
- readScaleField(dataset.values, args.field, fieldType, {
245
- allowUnknown: true
246
- });
247
- } else {
248
- readQuantitativeField(dataset.values, args.field);
249
- }
256
+ readScaleField(dataset.values, args.field, fieldType, {
257
+ allowUnknown: Object.hasOwn(scale, "unknown")
258
+ });
250
259
  const { opacity, ...config } = this.markConfigs[target] ?? {};
251
260
  void opacity;
252
261
  let next = setEncodingProperties(
253
- this._withoutMaterializationConfig(["marks", target, "opacity"]),
262
+ withoutConstantOpacity(this, target),
254
263
  target,
255
264
  "opacity",
256
265
  { field: args.field, fieldType, scale: scale.id }
@@ -258,6 +267,9 @@ const encodeOpacity = /* @__PURE__ */ action(
258
267
  next = applyEncodingScale(next, scale, requestedScale, {
259
268
  reassignment: previous?.scale === scale.id
260
269
  });
270
+ if (layer.mark.type === "bar" && !canMaterializeBar(next, findLayer(next, target))) {
271
+ return next.rematerializeScale({ id: scale.id });
272
+ }
261
273
  return rematerializeEncoding(next, target, "opacity", scale.id, layer);
262
274
  }
263
275
  );
@@ -4,11 +4,33 @@ import {
4
4
  BAR_GRAINS,
5
5
  resolveBarColorLayout,
6
6
  resolveBarOffsetChannel,
7
+ resolveBarChannels,
7
8
  resolveBarGrain
8
9
  } from "../../grammar/bars/policy.js";
9
10
  import { normalizeBarWidth } from "../../grammar/bars/geometry.js";
11
+ import { applyMaterializationPlan } from "../../materialization/dependencies.js";
12
+ import {
13
+ getScaleConsumerMarkSteps,
14
+ getSourceDependentMarkSteps
15
+ } from "../../materialization/marks/index.js";
16
+
17
+ function rematerializeWidth(program, target, layer, width) {
18
+ const next = program._withMarkConfig(target, {
19
+ ...program.markConfigs[target], barWidth: width
20
+ }).rematerializeBarMark({ id: target });
21
+ const category = resolveBarChannels(layer)?.category;
22
+ const encoding = layer.encoding?.[category];
23
+ if (encoding?.fieldType !== "temporal") return next;
24
+ const marks = getScaleConsumerMarkSteps(next, [encoding.scale])
25
+ .filter(step => step.args.id !== target);
26
+ return applyMaterializationPlan(next, [
27
+ ...marks,
28
+ ...[target, ...marks.map(step => step.args.id)]
29
+ .flatMap(id => getSourceDependentMarkSteps(next, id))
30
+ ]);
31
+ }
10
32
 
11
- const OPTIONS = Object.freeze(["band", "pixels", "target"]);
33
+ const OPTIONS = Object.freeze(["band", "pixels", "interval", "target"]);
12
34
 
13
35
  const encodeBarWidth = /* @__PURE__ */ action(
14
36
  {
@@ -26,22 +48,30 @@ const encodeBarWidth = /* @__PURE__ */ action(
26
48
  const layout = resolveBarColorLayout(layer);
27
49
  const width = normalizeBarWidth(
28
50
  args,
29
- this.markConfigs[target]?.barWidth
51
+ this.markConfigs[target]?.barWidth ?? (layer.mark.orientation === undefined ? undefined : { pixels: 5 })
30
52
  );
31
53
  const grouped = layout === "group";
32
54
  const offsetChannel = resolveBarOffsetChannel(layer);
33
55
  const grain = resolveBarGrain(layer);
56
+ if (layer.mark.orientation !== undefined && width.band !== undefined) {
57
+ throw new Error("Numeric-center bars require pixel width.");
58
+ }
34
59
  if (grain === undefined && this.markConfigs[target]?.boxPlot === undefined && (
35
60
  layer.encoding?.x === undefined || layer.encoding?.y === undefined
36
61
  )) {
37
62
  return this._withMarkConfig(target, { ...this.markConfigs[target], barWidth: width });
38
63
  }
64
+ if (width.interval !== undefined && grain !== undefined) {
65
+ const category = resolveBarChannels(layer)?.category;
66
+ if (grain !== BAR_GRAINS.aggregate || grouped || layer.encoding?.[category]?.fieldType !== "temporal") {
67
+ throw new Error("Bar interval width requires ungrouped aggregate temporal categories.");
68
+ }
69
+ }
39
70
  if (grain === BAR_GRAINS.histogram) {
40
71
  throw new Error("encodeBarWidth requires an aggregate or ranged category slot, not histogram bins.");
41
72
  }
42
- if (grain === BAR_GRAINS.ranged) {
43
- return this._withMarkConfig(target, { ...this.markConfigs[target], barWidth: width })
44
- .rematerializeBarMark({ id: target });
73
+ if ([BAR_GRAINS.ranged, BAR_GRAINS.centered].includes(grain)) {
74
+ return rematerializeWidth(this, target, layer, width);
45
75
  }
46
76
  if (
47
77
  grain !== BAR_GRAINS.aggregate ||
@@ -56,12 +86,7 @@ const encodeBarWidth = /* @__PURE__ */ action(
56
86
  );
57
87
  }
58
88
 
59
- return this
60
- ._withMarkConfig(target, {
61
- ...this.markConfigs[target],
62
- barWidth: width
63
- })
64
- .rematerializeBarMark({ id: target });
89
+ return rematerializeWidth(this, target, layer, width);
65
90
  }
66
91
  );
67
92
 
@@ -155,7 +155,6 @@ function resolveScaleOnly(program, id) {
155
155
  }
156
156
  const consumers = findScaleConsumers(program, id);
157
157
  if (
158
- semanticScale.radialMapping !== undefined &&
159
158
  consumers.length > 0 &&
160
159
  consumers.every(isPendingMeasuredRadiusConsumer)
161
160
  ) {
@@ -26,6 +26,8 @@ import {
26
26
  } from "./policy.js";
27
27
 
28
28
  import { applyTemporalUnit } from "../temporal.js";
29
+ import { applyRequestedItemMissingPolicy } from
30
+ "../../../grammar/itemMissing.js";
29
31
 
30
32
  export function encodeContinuousColor(program, args) {
31
33
  if (!["quantitative", "temporal"].includes(args.fieldType)) {
@@ -34,12 +36,13 @@ export function encodeContinuousColor(program, args) {
34
36
  if (args.layout !== undefined) {
35
37
  throw new Error("Continuous color does not support layout.");
36
38
  }
37
- const { id: target, dataset, layer } = resolveTarget(
39
+ const { id: target, dataset: sourceDataset, layer } = resolveTarget(
38
40
  program,
39
41
  args.target,
40
- ["point", "bar", "rect"],
42
+ ["point", "bar", "rect", "text"],
41
43
  "continuous color mark"
42
44
  );
45
+ const dataset = applyRequestedItemMissingPolicy(layer, sourceDataset, args.field);
43
46
  assertNoConstantColor(program, layer);
44
47
  const temporalUnit = resolveTemporalUnit(args, args.fieldType, layer.encoding?.color);
45
48
  const requestedScale = resolveReassignmentScaleOptions(
@@ -38,6 +38,8 @@ import {
38
38
  } from "./policy.js";
39
39
 
40
40
  import { applyTemporalUnit } from "../temporal.js";
41
+ import { applyRequestedItemMissingPolicy } from
42
+ "../../../grammar/itemMissing.js";
41
43
 
42
44
  const encodeColor = /* @__PURE__ */ action(
43
45
  {
@@ -58,12 +60,13 @@ const encodeColor = /* @__PURE__ */ action(
58
60
  }
59
61
  const fieldType = validateCategoricalFieldType(requestedFieldType);
60
62
  resolveTemporalUnit(args, fieldType);
61
- const { id: target, dataset, layer } = resolveTarget(
63
+ const { id: target, dataset: sourceDataset, layer } = resolveTarget(
62
64
  this,
63
65
  args.target,
64
- ["point", "line", "bar", "area", "arc", "rect"],
66
+ ["point", "line", "bar", "area", "arc", "rect", "text"],
65
67
  "color mark"
66
68
  );
69
+ const dataset = applyRequestedItemMissingPolicy(layer, sourceDataset, args.field);
67
70
  assertNoConstantColor(this, layer);
68
71
  const densityTransform = dataset.transform?.length === 1 &&
69
72
  dataset.transform[0].type === "density"
@@ -20,6 +20,9 @@ export function resolveColorScaleOptions(args) {
20
20
  }
21
21
 
22
22
  export function assertNoConstantColor(program, layer) {
23
+ if (layer.mark.type === "text" && layer.source !== undefined) {
24
+ throw new Error("Source-owned text uses inheritColor or constant fill; field color requires row-backed text.");
25
+ }
23
26
  const config = program.markConfigs[layer.id];
24
27
  const hasConstant = {
25
28
  point: config?.fill !== undefined,
@@ -27,7 +30,8 @@ export function assertNoConstantColor(program, layer) {
27
30
  area: config?.errorBand?.fill !== undefined,
28
31
  bar: config?.barAppearance?.fill !== undefined,
29
32
  arc: config?.fill !== undefined,
30
- rect: config?.fillExplicit === true
33
+ rect: config?.fillExplicit === true,
34
+ text: config?.fillExplicit === true
31
35
  }[layer.mark.type] ?? false;
32
36
  if (hasConstant) {
33
37
  throw new Error(
@@ -58,15 +62,18 @@ export function resolveColorLayout(layer, requested, barGrain) {
58
62
  layer.mark.type === "bar"
59
63
  ? barGrain === BAR_GRAINS.histogram
60
64
  ? "stack"
61
- : barGrain === BAR_GRAINS.ranged ? "overlay" : "group"
65
+ : [BAR_GRAINS.ranged, BAR_GRAINS.centered].includes(barGrain) ? "overlay" : "group"
62
66
  : layer.mark.type === "area"
63
67
  ? "overlay"
64
68
  : undefined
65
69
  );
66
70
 
67
- if (["point", "line", "rect"].includes(layer.mark.type) && layout !== undefined) {
71
+ if (["point", "line", "rect", "text"].includes(layer.mark.type) && layout !== undefined) {
68
72
  throw new Error(`Color layout is not supported for ${layer.mark.type} marks.`);
69
73
  }
74
+ if (barGrain === BAR_GRAINS.centered && layout !== "overlay") {
75
+ throw new Error("Numeric-center bars support only overlay layout.");
76
+ }
70
77
  if (layer.mark.type === "area" && layout === "group") {
71
78
  throw new Error('Area color layout does not support "group".');
72
79
  }
@@ -1,12 +1,7 @@
1
+ export { requireStringValue as requireDensityField } from "../../../core/validation.js";
1
2
  import { isPlainObject } from "../../../core/immutable.js";
2
3
  import { findSemanticScale } from "../../../selectors/scales.js";
3
4
 
4
- export function requireDensityField(value, label) {
5
- if (typeof value !== "string" || value.length === 0) {
6
- throw new TypeError(`${label} must be a non-empty string.`);
7
- }
8
- return value;
9
- }
10
5
 
11
6
  export function resolveDensityScaleOptions(value, defaults, label) {
12
7
  if (value !== undefined && !isPlainObject(value)) {
@@ -18,6 +18,8 @@ import {
18
18
  resolveBarChannels,
19
19
  resolveBarGrain
20
20
  } from "../../grammar/bars/policy.js";
21
+ import { applyRequestedItemMissingPolicy } from
22
+ "../../grammar/itemMissing.js";
21
23
 
22
24
  const ENCODING_OPTIONS = Object.freeze([
23
25
  "field", "target", "fieldType", "scale", "paddingInner", "paddingOuter"
@@ -33,12 +35,13 @@ function createOffsetEncoding(channel) {
33
35
  function (args = {}) {
34
36
  validateOptions(args, ENCODING_OPTIONS, operation);
35
37
  const fieldType = validateCategoricalFieldType(args.fieldType ?? "nominal");
36
- const { id: target, dataset, layer } = resolveTarget(
38
+ const { id: target, dataset: sourceDataset, layer } = resolveTarget(
37
39
  this,
38
40
  args.target,
39
41
  ["bar", "point", "rule"],
40
42
  "offset-compatible mark"
41
43
  );
44
+ const dataset = applyRequestedItemMissingPolicy(layer, sourceDataset, args.field);
42
45
 
43
46
  if (layer.mark.type === "bar") {
44
47
  const channels = resolveBarChannels(layer);
@@ -11,11 +11,6 @@ export function resolveArcPositionPolicy({ channel, args, fieldType, layer }) {
11
11
  throw new Error(`Arc ${channel} encoding does not support stack.`);
12
12
  }
13
13
  if (channel === "radius") {
14
- if (layer.encoding?.theta?.fieldType === "quantitative") {
15
- throw new Error(
16
- "Arc radius encoding cannot be combined with quantitative theta."
17
- );
18
- }
19
14
  if (args.mapping !== undefined) {
20
15
  validateRadialMapping(args.mapping);
21
16
  if (fieldType !== "quantitative" || !["count", "sum"].includes(args.aggregate)) {
@@ -30,7 +25,16 @@ export function resolveArcPositionPolicy({ channel, args, fieldType, layer }) {
30
25
  return { ...emptyPositionPolicy(), aggregate: args.aggregate };
31
26
  }
32
27
  if (args.aggregate !== undefined) {
33
- throw new Error("Arc radius encoding does not support aggregate.");
28
+ if (!["count", "sum"].includes(args.aggregate)) throw new Error("Arc radius aggregate supports count or sum.");
29
+ const theta = layer.encoding?.theta;
30
+ if (theta !== undefined && !["count", "sum"].includes(theta.aggregate)) {
31
+ throw new Error("Grouped arc radius requires aggregated categorical theta.");
32
+ }
33
+ if (args.aggregate === "count" && Object.hasOwn(args, "field")) {
34
+ throw new Error("Arc radius count does not accept a field.");
35
+ }
36
+ if (args.weight !== undefined) throw new Error("Arc radius encoding does not support weight.");
37
+ return { ...emptyPositionPolicy(), aggregate: args.aggregate };
34
38
  }
35
39
  if (args.weight !== undefined) {
36
40
  throw new Error("Arc radius encoding does not support weight.");
@@ -38,10 +42,8 @@ export function resolveArcPositionPolicy({ channel, args, fieldType, layer }) {
38
42
  return emptyPositionPolicy();
39
43
  }
40
44
  if (fieldType === "quantitative") {
41
- if (layer.encoding?.radius !== undefined) {
42
- throw new Error(
43
- "Quantitative arc theta cannot be combined with radius encoding."
44
- );
45
+ if (layer.encoding?.radius?.aggregate !== undefined) {
46
+ throw new Error("Grouped arc radius requires aggregated categorical theta.");
45
47
  }
46
48
  if (args.aggregate !== undefined) {
47
49
  throw new Error("Quantitative arc theta does not support aggregate.");
@@ -26,16 +26,16 @@ export function resolveAreaPositionPolicy({
26
26
  const density = dataset.transform?.find(transform => transform.type === "density");
27
27
  if (["nominal", "ordinal"].includes(fieldType)) {
28
28
  if (
29
- density?.placement?.type !== "category" ||
29
+ density !== undefined && (density.placement?.type !== "category" ||
30
30
  density.placement.channel !== channel ||
31
- density.placement.categoryField !== field
31
+ density.placement.categoryField !== field)
32
32
  ) {
33
33
  throw new Error(
34
34
  "Categorical area position requires a matching category density placement."
35
35
  );
36
36
  }
37
37
  if (args.aggregate !== undefined || args.bin !== undefined || args.stack !== undefined) {
38
- throw new Error("Categorical density position does not support aggregate, bin, or stack.");
38
+ throw new Error("Categorical area position does not support aggregate, bin, or stack.");
39
39
  }
40
40
  return emptyPositionPolicy();
41
41
  }