ggaction 0.0.16 → 0.0.17

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 (83) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +2 -1
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/package.json +8 -2
  7. package/src/actions/data/bin.js +1 -1
  8. package/src/actions/data/create.js +18 -5
  9. package/src/actions/data/density.js +1 -1
  10. package/src/actions/data/derived.js +15 -1
  11. package/src/actions/data/edit.js +19 -6
  12. package/src/actions/data/filter.js +15 -4
  13. package/src/actions/data/index.js +3 -0
  14. package/src/actions/data/interval.js +2 -0
  15. package/src/actions/data/regression.js +4 -1
  16. package/src/actions/data/revise.js +37 -5
  17. package/src/actions/data/shared.js +30 -2
  18. package/src/actions/data/sort.js +21 -0
  19. package/src/actions/data/summary.js +1 -1
  20. package/src/actions/encodings/angle.js +4 -1
  21. package/src/actions/encodings/appearance.js +6 -2
  22. package/src/actions/encodings/color/continuous.js +4 -1
  23. package/src/actions/encodings/color/index.js +4 -1
  24. package/src/actions/encodings/offset.js +4 -1
  25. package/src/actions/encodings/position/resolve.js +6 -1
  26. package/src/actions/encodings/ranged.js +6 -1
  27. package/src/actions/encodings/ruleAppearance.js +4 -1
  28. package/src/actions/encodings/stroke.js +4 -1
  29. package/src/actions/encodings/strokeDash.js +6 -1
  30. package/src/actions/encodings/text.js +6 -1
  31. package/src/actions/marks/bar/create.js +8 -1
  32. package/src/actions/marks/bar/edit.js +12 -5
  33. package/src/actions/marks/point/create.js +8 -1
  34. package/src/actions/marks/point/edit.js +12 -5
  35. package/src/actions/marks/point/materialize.js +4 -2
  36. package/src/actions/marks/rect/actions.js +23 -8
  37. package/src/actions/marks/rule/actions.js +24 -8
  38. package/src/actions/marks/text/actions.js +25 -6
  39. package/src/actions/marks/tick/actions.js +23 -8
  40. package/src/actions/primitives/semanticAction.js +37 -3
  41. package/src/actions/primitives/semanticValidation/dataset.js +13 -1
  42. package/src/actions/primitives/semanticValidation/layer.js +22 -2
  43. package/src/actions/primitives/semanticValidation/scale.js +6 -0
  44. package/src/actions/regression/create.js +25 -4
  45. package/src/actions/regression/edit.js +47 -12
  46. package/src/actions/regression/follow.js +22 -0
  47. package/src/actions/scales/channels.js +11 -11
  48. package/src/actions/scales/consumers/common.js +2 -1
  49. package/src/actions/scales/create.js +2 -1
  50. package/src/actions/scales/definitions.js +1 -1
  51. package/src/actions/scales/edit.js +1 -1
  52. package/src/actions/scales/parallel.js +1 -1
  53. package/src/actions/scales/patch.js +1 -1
  54. package/src/actions/scales/preview.js +28 -1
  55. package/src/actions/theme/reconcile.js +2 -0
  56. package/src/core/action.js +3 -2
  57. package/src/core/programState.js +5 -1
  58. package/src/grammar/bin.js +39 -10
  59. package/src/grammar/datasetSchema.js +386 -0
  60. package/src/grammar/density.js +53 -5
  61. package/src/grammar/filter.js +133 -39
  62. package/src/grammar/interval.js +32 -1
  63. package/src/grammar/itemMissing.js +59 -0
  64. package/src/grammar/regression/derive.js +57 -16
  65. package/src/grammar/regression/index.js +1 -0
  66. package/src/grammar/regression/models.js +51 -22
  67. package/src/grammar/regression/parameters.js +56 -4
  68. package/src/grammar/scales/definition.js +7 -0
  69. package/src/grammar/schemas/semanticPath.js +3 -2
  70. package/src/grammar/sort.js +88 -0
  71. package/src/grammar/summary.js +117 -19
  72. package/src/grammar/transformTopology.js +1 -0
  73. package/src/grammar/transforms.js +21 -11
  74. package/src/inspection.js +495 -0
  75. package/src/inspectionDescriptors.js +3 -0
  76. package/src/materialization/bars/resolve.js +4 -2
  77. package/src/materialization/revisionIdentity.js +19 -0
  78. package/src/materialization/scales/resolve.js +67 -4
  79. package/src/materialization/text.js +4 -2
  80. package/src/persistence.js +79 -6
  81. package/src/version.js +1 -1
  82. package/types/inspection.d.ts +17 -0
  83. package/types/program.d.ts +147 -27
@@ -48,6 +48,7 @@ import {
48
48
  import { createCompleteData, materializeCompleteData } from "./complete.js";
49
49
  import { createImputedData, materializeImputedData } from "./impute.js";
50
50
  import { createStackData, materializeStackData } from "./stack.js";
51
+ import { createSortedData, materializeSortedData } from "./sort.js";
51
52
  import { EDIT_DERIVED_DATA_ACTIONS } from "./edit.js";
52
53
  import { createBoxSummaryData, createBoxOutlierData, materializeBoxSummaryData, materializeBoxOutlierData } from "./box.js";
53
54
 
@@ -96,6 +97,8 @@ export function registerDataActions(ProgramClass) {
96
97
  materializeImputedData,
97
98
  createStackData,
98
99
  materializeStackData,
100
+ createSortedData,
101
+ materializeSortedData,
99
102
  createTimeUnitData,
100
103
  materializeTimeUnitData,
101
104
  createBin2DData,
@@ -13,6 +13,7 @@ const OPTIONS = Object.freeze([
13
13
  "extent",
14
14
  "method",
15
15
  "level",
16
+ "missing",
16
17
  "as"
17
18
  ]);
18
19
 
@@ -42,6 +43,7 @@ export const createIntervalData = /* @__PURE__ */ derivedCreator(
42
43
  extent: args.extent,
43
44
  method: args.method,
44
45
  level: args.level,
46
+ missing: args.missing,
45
47
  as
46
48
  });
47
49
  },
@@ -11,6 +11,7 @@ import { derivedMaterializer } from "./shared.js";
11
11
  const OPTIONS = Object.freeze([
12
12
  "id", "source", "x", "y", "groupBy", "method", "degree", "span",
13
13
  "confidenceMethod", "level", "confidence", "interval"
14
+ , "predict", "missing"
14
15
  ]);
15
16
 
16
17
  export const materializeRegressionData = /* @__PURE__ */ derivedMaterializer(
@@ -27,7 +28,9 @@ export const materializeRegressionData = /* @__PURE__ */ derivedMaterializer(
27
28
  confidenceMethod: transform.confidenceMethod,
28
29
  level: transform.level,
29
30
  confidence: transform.confidence,
30
- interval: transform.interval
31
+ interval: transform.interval,
32
+ predict: transform.predict
33
+ , missing: transform.missing
31
34
  })
32
35
  );
33
36
 
@@ -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
 
@@ -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
+ );
@@ -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.");
@@ -25,6 +25,8 @@ import {
25
25
  setEncodingProperties,
26
26
  validateOptions
27
27
  } from "./shared.js";
28
+ import { applyRequestedItemMissingPolicy } from
29
+ "../../grammar/itemMissing.js";
28
30
 
29
31
  const RADIUS_OPTIONS = Object.freeze(["value", "target"]);
30
32
  const REMOVE_RADIUS_OPTIONS = Object.freeze(["target"]);
@@ -34,13 +36,14 @@ const OPACITY_OPTIONS = Object.freeze([
34
36
  const FIELD_OPTIONS = Object.freeze(["field", "target", "fieldType", "scale"]);
35
37
  function encodeAppearanceField(program, channel, args, operation) {
36
38
  validateOptions(args, FIELD_OPTIONS, operation);
37
- const { id: target, dataset, layer } = resolveTarget(
39
+ const { id: target, dataset: sourceDataset, layer } = resolveTarget(
38
40
  program,
39
41
  args.target,
40
42
  ["point"],
41
43
  "point mark"
42
44
  );
43
45
  const expectedFieldType = channel === "shape" ? "nominal" : "quantitative";
46
+ const dataset = applyRequestedItemMissingPolicy(layer, sourceDataset, args.field);
44
47
  const fieldType = args.fieldType ?? expectedFieldType;
45
48
  if (fieldType !== expectedFieldType) {
46
49
  throw new Error(`${operation} requires a ${expectedFieldType} field.`);
@@ -196,7 +199,7 @@ const encodeOpacity = /* @__PURE__ */ action(
196
199
  if (hasValue && (args.fieldType !== undefined || args.scale !== undefined)) {
197
200
  throw new Error("Constant opacity does not accept fieldType or scale.");
198
201
  }
199
- const { id: target, dataset, layer } = resolveTarget(
202
+ const { id: target, dataset: sourceDataset, layer } = resolveTarget(
200
203
  this,
201
204
  args.target,
202
205
  ["point", "rule", "line"],
@@ -222,6 +225,7 @@ const encodeOpacity = /* @__PURE__ */ action(
222
225
  : next.rematerializePointMark({ id: target });
223
226
  return applyDetachedScaleRematerialization(materialized, [layer]);
224
227
  }
228
+ const dataset = applyRequestedItemMissingPolicy(layer, sourceDataset, args.field);
225
229
  const fieldType = args.fieldType ?? "quantitative";
226
230
  if (fieldType !== "quantitative") {
227
231
  throw new Error("encodeOpacity requires a quantitative field.");
@@ -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
42
  ["point", "bar", "rect"],
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
66
  ["point", "line", "bar", "area", "arc", "rect"],
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"
@@ -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);
@@ -13,6 +13,8 @@ import {
13
13
  validateFieldType,
14
14
  validatePositionChannel
15
15
  } from "../../../grammar/scales/index.js";
16
+ import { applyRequestedItemMissingPolicy } from
17
+ "../../../grammar/itemMissing.js";
16
18
  import { resolvePositionScaleDefinition } from "../../scales/definitions.js";
17
19
  import { findCoordinate } from "../../../selectors/coordinates.js";
18
20
  import {
@@ -92,7 +94,7 @@ function resolveCoordinate(program, channel, layer, requestedId) {
92
94
  export function resolvePositionEncoding(program, channel, args, operation) {
93
95
  validateOptions(args, POSITION_ENCODING_OPTIONS, operation);
94
96
  validatePositionChannel(channel);
95
- const { id: target, dataset, layer } = resolveTarget(
97
+ const { id: target, dataset: sourceDataset, layer } = resolveTarget(
96
98
  program,
97
99
  args.target,
98
100
  getPositionChannelDefinition(channel).markTypes
@@ -157,6 +159,9 @@ export function resolvePositionEncoding(program, channel, args, operation) {
157
159
  xEncoding?.bin !== undefined && args.field === undefined
158
160
  ? xEncoding.field
159
161
  : args.field;
162
+ const dataset = hasField
163
+ ? applyRequestedItemMissingPolicy(layer, sourceDataset, field)
164
+ : applyRequestedItemMissingPolicy(layer, sourceDataset);
160
165
  const datum = args.datum;
161
166
  const temporalUnit = resolveTemporalUnit({ ...args, ...(hasDatum ? {} : { field }) }, fieldType, previous);
162
167
  const usesField = !countRadius && (!["rule", "area", "rect", "text", "point", "tick"].includes(layer.mark.type) || hasField);
@@ -14,6 +14,8 @@ import { deriveAreaSeries } from "../../grammar/areaSeries.js";
14
14
  import { resolvePositionEncoding } from "./position/resolve.js";
15
15
  import { resolveScalePreview } from "../scales/preview.js";
16
16
  import { findUpstreamTransform } from "../../materialization/dataProvenance.js";
17
+ import { applyRequestedItemMissingPolicy } from
18
+ "../../grammar/itemMissing.js";
17
19
 
18
20
  const SECONDARY_OPTIONS = Object.freeze([
19
21
  "field", "datum", "target", "fieldType", "scale", "coordinate", "temporalUnit"
@@ -46,7 +48,7 @@ function validateSecondaryField(dataset, field, fieldType, temporalUnit) {
46
48
 
47
49
  function encodeSecondaryPosition(program, channel, args, operation, types) {
48
50
  validateOptions(args, SECONDARY_OPTIONS, operation);
49
- const { id: target, dataset, layer } = resolveTarget(
51
+ const { id: target, dataset: sourceDataset, layer } = resolveTarget(
50
52
  program,
51
53
  args.target,
52
54
  types,
@@ -72,6 +74,9 @@ function encodeSecondaryPosition(program, channel, args, operation, types) {
72
74
  const rect = layer.mark.type === "rect";
73
75
  const hasField = Object.hasOwn(args, "field");
74
76
  const hasDatum = Object.hasOwn(args, "datum");
77
+ const dataset = hasField
78
+ ? applyRequestedItemMissingPolicy(layer, sourceDataset, args.field)
79
+ : applyRequestedItemMissingPolicy(layer, sourceDataset);
75
80
  if ((rule || area || rect) && hasField === hasDatum) {
76
81
  throw new Error(`${operation} requires exactly one of field or datum for a ${layer.mark.type} mark.`);
77
82
  }
@@ -14,6 +14,8 @@ import {
14
14
  import { findLayer } from "../../selectors/layers.js";
15
15
  import { validatePathSeriesAppearance } from "../../grammar/pathSeries.js";
16
16
  import { assertEncodingSelectionCompatibility } from "../../materialization/selection/compatibility.js";
17
+ import { applyRequestedItemMissingPolicy } from
18
+ "../../grammar/itemMissing.js";
17
19
 
18
20
  const WIDTH_OPTIONS = Object.freeze([
19
21
  "target", "value", "field", "fieldType", "scale"
@@ -57,12 +59,13 @@ const encodeStrokeWidth = /* @__PURE__ */ action(
57
59
  next = next.rematerializeRuleMark({ id });
58
60
  return applyDetachedScaleRematerialization(next, [layer]);
59
61
  }
60
- const { id, dataset, layer } = resolveTarget(
62
+ const { id, dataset: sourceDataset, layer } = resolveTarget(
61
63
  this,
62
64
  args.target,
63
65
  ["rule", "line"],
64
66
  "rule or line mark"
65
67
  );
68
+ const dataset = applyRequestedItemMissingPolicy(layer, sourceDataset, args.field);
66
69
  const fieldType = args.fieldType ?? "quantitative";
67
70
  if (fieldType !== "quantitative") {
68
71
  throw new Error("encodeStrokeWidth requires a quantitative field.");
@@ -26,6 +26,8 @@ import {
26
26
  setEncodingProperties,
27
27
  validateOptions
28
28
  } from "./shared.js";
29
+ import { applyRequestedItemMissingPolicy } from
30
+ "../../grammar/itemMissing.js";
29
31
 
30
32
  const OPTIONS = Object.freeze([
31
33
  "target", "value", "field", "fieldType", "temporalUnit", "scale"
@@ -122,7 +124,7 @@ export const encodeStroke = /* @__PURE__ */ action(
122
124
  "Constant stroke does not accept fieldType, temporalUnit, or scale."
123
125
  );
124
126
  }
125
- const { id: target, dataset, layer } = resolveTarget(
127
+ const { id: target, dataset: sourceDataset, layer } = resolveTarget(
126
128
  this,
127
129
  args.target,
128
130
  MARK_TYPES,
@@ -155,6 +157,7 @@ export const encodeStroke = /* @__PURE__ */ action(
155
157
  }
156
158
 
157
159
  const fieldType = args.fieldType ?? "nominal";
160
+ const dataset = applyRequestedItemMissingPolicy(layer, sourceDataset, args.field);
158
161
  const previous = layer.encoding?.stroke;
159
162
  const temporalUnit = resolveTemporalUnit(args, fieldType, previous);
160
163
  const requestedScale = resolveReassignmentScaleOptions(
@@ -15,6 +15,8 @@ import {
15
15
  } from "./shared.js";
16
16
  import { findLayer } from "../../selectors/layers.js";
17
17
  import { validatePathSeriesAppearance } from "../../grammar/pathSeries.js";
18
+ import { applyRequestedItemMissingPolicy } from
19
+ "../../grammar/itemMissing.js";
18
20
 
19
21
  const STROKE_DASH_ENCODING_OPTIONS = Object.freeze([
20
22
  "field", "value", "target", "fieldType", "scale"
@@ -73,12 +75,15 @@ const encodeStrokeDash = /* @__PURE__ */ action(
73
75
  if (hasValue && (args.fieldType !== undefined || args.scale !== undefined)) {
74
76
  throw new Error("Constant stroke dash does not accept fieldType or scale.");
75
77
  }
76
- const { id: target, dataset, layer } = resolveTarget(
78
+ const { id: target, dataset: sourceDataset, layer } = resolveTarget(
77
79
  this,
78
80
  args.target,
79
81
  ["line", "rule"],
80
82
  "line or rule mark"
81
83
  );
84
+ const dataset = hasField
85
+ ? applyRequestedItemMissingPolicy(layer, sourceDataset, args.field)
86
+ : sourceDataset;
82
87
  if (hasValue) {
83
88
  normalizeStrokeDashPattern(args.value);
84
89
  validatePathSeriesAppearance(dataset.values, {
@@ -9,6 +9,8 @@ import {
9
9
  setEncodingProperties,
10
10
  validateOptions
11
11
  } from "./shared.js";
12
+ import { applyRequestedItemMissingPolicy } from
13
+ "../../grammar/itemMissing.js";
12
14
 
13
15
  const OPTIONS = Object.freeze(["target", "field", "value", "content", "normalizeBy", "format"]);
14
16
 
@@ -41,12 +43,15 @@ export const encodeText = /* @__PURE__ */ action(
41
43
  if (Object.hasOwn(args, "normalizeBy") && (!hasContent || args.content !== "share")) {
42
44
  throw new Error("Text normalizeBy is only supported with share content.");
43
45
  }
44
- const { id: target, dataset, layer } = resolveTarget(
46
+ const { id: target, dataset: sourceDataset, layer } = resolveTarget(
45
47
  this,
46
48
  args.target,
47
49
  ["text"],
48
50
  "text mark"
49
51
  );
52
+ const dataset = hasField
53
+ ? applyRequestedItemMissingPolicy(layer, sourceDataset, args.field)
54
+ : applyRequestedItemMissingPolicy(layer, sourceDataset);
50
55
  const previous = layer.encoding?.text;
51
56
  const format = validateTextFormat(args.format ?? previous?.format ?? "auto");
52
57
  const content = hasContent
@@ -14,9 +14,10 @@ import { requestedRectStyleDetails } from
14
14
  "../../../grammar/roundedRect.js";
15
15
  import { STROKE_STYLE_PROPERTIES } from
16
16
  "../../../grammar/strokeStyle.js";
17
+ import { validateItemMissing } from "../../../grammar/itemMissing.js";
17
18
 
18
19
  const CREATE_OPTIONS = Object.freeze([
19
- "id", "data", "fill", "opacity", "stroke", "strokeWidth",
20
+ "id", "data", "missing", "fill", "opacity", "stroke", "strokeWidth",
20
21
  "cornerRadius", ...STROKE_STYLE_PROPERTIES
21
22
  ]);
22
23
 
@@ -52,6 +53,12 @@ export const createBarMark = /* @__PURE__ */ action(
52
53
  property: `layer[${id}].data`,
53
54
  value: data
54
55
  });
56
+ if (Object.hasOwn(args, "missing")) {
57
+ created = created.editSemantic({
58
+ property: `layer[${id}].mark.missing`,
59
+ value: validateItemMissing(args.missing, "Bar missing")
60
+ });
61
+ }
55
62
  created = applyLayeredMarkInheritance(created, id, inherited);
56
63
  created = created
57
64
  .createGraphics({
@@ -14,9 +14,10 @@ import { requestedRectStyleDetails } from
14
14
  import { STROKE_STYLE_PROPERTIES } from
15
15
  "../../../grammar/strokeStyle.js";
16
16
  import { rematerializeExistingLegend } from "../../encodings/shared.js";
17
+ import { validateItemMissing } from "../../../grammar/itemMissing.js";
17
18
 
18
19
  const EDIT_OPTIONS = Object.freeze([
19
- "target", "fill", "opacity", "stroke", "strokeWidth", "cornerRadius",
20
+ "target", "missing", "fill", "opacity", "stroke", "strokeWidth", "cornerRadius",
20
21
  ...STROKE_STYLE_PROPERTIES
21
22
  ]);
22
23
 
@@ -29,12 +30,12 @@ export const editBarMark = /* @__PURE__ */ action(
29
30
  validateMarkOptions(args, EDIT_OPTIONS, "editBarMark");
30
31
  const styleDetails = requestedRectStyleDetails(args, "editBarMark");
31
32
  const changes = [
32
- "fill", "opacity", "stroke", "strokeWidth", "cornerRadius",
33
+ "missing", "fill", "opacity", "stroke", "strokeWidth", "cornerRadius",
33
34
  ...STROKE_STYLE_PROPERTIES
34
35
  ];
35
36
  if (!changes.some(key => Object.hasOwn(args, key))) {
36
37
  throw new Error(
37
- "editBarMark requires fill, opacity, stroke, or strokeWidth."
38
+ "editBarMark requires fill, opacity, stroke, or strokeWidth; missing is also editable."
38
39
  );
39
40
  }
40
41
  const requested = Object.hasOwn(args, "target")
@@ -61,7 +62,13 @@ export const editBarMark = /* @__PURE__ */ action(
61
62
  );
62
63
  }
63
64
 
64
- const config = { ...this.markConfigs[layer.id] };
65
+ const semantic = Object.hasOwn(args, "missing")
66
+ ? this.editSemantic({
67
+ property: `layer[${layer.id}].mark.missing`,
68
+ value: validateItemMissing(args.missing, "Bar missing")
69
+ })
70
+ : this;
71
+ const config = { ...semantic.markConfigs[layer.id] };
65
72
  const appearance = { ...config.barAppearance };
66
73
  Object.assign(appearance, styleDetails);
67
74
  if (Object.hasOwn(args, "fill")) {
@@ -95,7 +102,7 @@ export const editBarMark = /* @__PURE__ */ action(
95
102
  );
96
103
  }
97
104
 
98
- const next = this._withMarkConfig(layer.id, {
105
+ const next = semantic._withMarkConfig(layer.id, {
99
106
  ...config,
100
107
  barAppearance: appearance
101
108
  });
@@ -16,9 +16,10 @@ import {
16
16
  resolveMarkId,
17
17
  validateMarkOptions
18
18
  } from "../shared.js";
19
+ import { validateItemMissing } from "../../../grammar/itemMissing.js";
19
20
 
20
21
  const OPTIONS = Object.freeze([
21
- "id", "data", "shape", "fill", "opacity", "stroke", "strokeWidth",
22
+ "id", "data", "missing", "shape", "fill", "opacity", "stroke", "strokeWidth",
22
23
  ...STROKE_STYLE_PROPERTIES
23
24
  ]);
24
25
 
@@ -61,6 +62,12 @@ export const createPointMark = /* @__PURE__ */ action(
61
62
  property: `layer[${id}].data`,
62
63
  value: data
63
64
  });
65
+ if (Object.hasOwn(args, "missing")) {
66
+ next = next.editSemantic({
67
+ property: `layer[${id}].mark.missing`,
68
+ value: validateItemMissing(args.missing, "Point missing")
69
+ });
70
+ }
64
71
  next = applyLayeredMarkInheritance(next, id, inherited)
65
72
  .createGraphics({
66
73
  id,