ggaction 0.0.9 → 0.0.11

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 (222) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +5 -4
  3. package/knowledge/action-card.schema.json +1 -0
  4. package/knowledge/action-cards.json +1 -10941
  5. package/knowledge/action-cards.schema.json +1 -0
  6. package/knowledge/extension-authoring.md +248 -0
  7. package/knowledge/intent-taxonomy.json +1 -183
  8. package/knowledge/intent-taxonomy.schema.json +1 -0
  9. package/knowledge/mcp-resources.json +1 -95
  10. package/knowledge/mcp-resources.schema.json +1 -0
  11. package/knowledge/task-packet.schema.json +1 -159
  12. package/knowledge/task-resolver.js +1 -1230
  13. package/package.json +19 -2
  14. package/src/actions/boxPlots/edit.js +45 -298
  15. package/src/actions/boxPlots/options.js +46 -77
  16. package/src/actions/boxPlots/resolve.js +1 -1
  17. package/src/actions/charts/bar.js +3 -0
  18. package/src/actions/charts/heatmap.js +73 -83
  19. package/src/actions/charts/index.js +1 -1
  20. package/src/actions/charts/parallel.js +35 -1
  21. package/src/actions/charts/shared.js +20 -0
  22. package/src/actions/composition/actions.js +46 -49
  23. package/src/actions/data/bin2d.js +105 -2
  24. package/src/actions/data/box.js +17 -17
  25. package/src/actions/data/density.js +42 -78
  26. package/src/actions/data/gradientProfile.js +22 -52
  27. package/src/actions/data/horizon.js +16 -41
  28. package/src/actions/data/index.js +10 -10
  29. package/src/actions/data/interval.js +16 -38
  30. package/src/actions/data/intervalEdit.js +55 -0
  31. package/src/actions/data/regression.js +7 -17
  32. package/src/actions/data/shared.js +47 -0
  33. package/src/actions/data/timeUnit.js +14 -38
  34. package/src/actions/data/window.js +14 -38
  35. package/src/actions/distributions/revision.js +154 -34
  36. package/src/actions/encodings/angle.js +9 -10
  37. package/src/actions/encodings/appearance.js +17 -51
  38. package/src/actions/encodings/density/resolve.js +3 -1
  39. package/src/actions/encodings/density.js +1 -15
  40. package/src/actions/encodings/horizon/resolve.js +13 -3
  41. package/src/actions/encodings/horizon.js +8 -7
  42. package/src/actions/encodings/index.js +9 -9
  43. package/src/actions/encodings/offset.js +8 -24
  44. package/src/actions/encodings/parallel.js +9 -10
  45. package/src/actions/encodings/pathOrder.js +6 -13
  46. package/src/actions/encodings/position/policies/index.js +4 -4
  47. package/src/actions/encodings/position/policies/line.js +47 -17
  48. package/src/actions/encodings/position/resolve.js +24 -5
  49. package/src/actions/encodings/ranged.js +18 -23
  50. package/src/actions/encodings/remove.js +15 -25
  51. package/src/actions/encodings/ruleAppearance.js +9 -24
  52. package/src/actions/encodings/shared.js +37 -0
  53. package/src/actions/encodings/strokeDash.js +8 -23
  54. package/src/actions/encodings/text.js +9 -10
  55. package/src/actions/errorBands/create.js +105 -39
  56. package/src/actions/errorBands/edit.js +89 -93
  57. package/src/actions/errorBands/index.js +1 -2
  58. package/src/actions/errorBars/create.js +78 -27
  59. package/src/actions/errorBars/edit.js +81 -48
  60. package/src/actions/errorBars/index.js +1 -2
  61. package/src/actions/facets/actions.js +45 -69
  62. package/src/actions/facets/derive.js +38 -53
  63. package/src/actions/facets/replay.js +0 -1
  64. package/src/actions/gradientPlots/components.js +59 -48
  65. package/src/actions/gradientPlots/edit.js +35 -216
  66. package/src/actions/gradientPlots/index.js +11 -10
  67. package/src/actions/gradientPlots/materialize.js +21 -35
  68. package/src/actions/gradientPlots/options.js +5 -7
  69. package/src/actions/gradientPlots/paint.js +9 -6
  70. package/src/actions/gradientPlots/resolve.js +1 -1
  71. package/src/actions/guides/axes/index.js +3 -3
  72. package/src/actions/guides/axes/labels.js +68 -26
  73. package/src/actions/guides/axes/lines.js +22 -2
  74. package/src/actions/guides/axes/policy.js +28 -8
  75. package/src/actions/guides/axes/ticks.js +27 -9
  76. package/src/actions/guides/axes/titles.js +113 -65
  77. package/src/actions/guides/grids/resolve.js +14 -0
  78. package/src/actions/guides/guides.js +2 -4
  79. package/src/actions/guides/index.js +2 -2
  80. package/src/actions/guides/legends/categorical/actions.js +44 -68
  81. package/src/actions/guides/legends/categorical/components.js +6 -1
  82. package/src/actions/guides/legends/categorical/index.js +2 -2
  83. package/src/actions/guides/legends/categorical/layout.js +148 -175
  84. package/src/actions/guides/legends/categorical/recipes.js +5 -0
  85. package/src/actions/guides/legends/categorical/resolve.js +8 -1
  86. package/src/actions/guides/legends/continuous/common.js +117 -66
  87. package/src/actions/guides/legends/continuous/gradient.js +38 -37
  88. package/src/actions/guides/legends/continuous/interval.js +97 -76
  89. package/src/actions/guides/legends/continuous/opacity.js +28 -38
  90. package/src/actions/guides/legends/edit.js +14 -1
  91. package/src/actions/guides/legends/index.js +1 -1
  92. package/src/actions/guides/legends/lane.js +2 -1
  93. package/src/actions/guides/legends/remove.js +8 -0
  94. package/src/actions/guides/legends/size.js +36 -43
  95. package/src/actions/guides/legends/strokeWidth.js +20 -26
  96. package/src/actions/guides/polar/axes/labels.js +17 -1
  97. package/src/actions/guides/polar/axes/shared.js +7 -1
  98. package/src/actions/guides/polar/axes/titles.js +19 -2
  99. package/src/actions/guides/polar/grids.js +4 -0
  100. package/src/actions/guides/polar/resolve.js +6 -0
  101. package/src/actions/index.js +13 -13
  102. package/src/actions/intervals/resolve.js +87 -117
  103. package/src/actions/marks/arc/actions.js +31 -36
  104. package/src/actions/marks/area/actions.js +23 -31
  105. package/src/actions/marks/index.js +6 -6
  106. package/src/actions/marks/line/actions.js +33 -52
  107. package/src/actions/marks/line/materialize.js +7 -5
  108. package/src/actions/marks/point/materialize.js +5 -3
  109. package/src/actions/marks/remove.js +21 -0
  110. package/src/actions/marks/rule/actions.js +53 -53
  111. package/src/actions/marks/shared.js +8 -4
  112. package/src/actions/marks/text/actions.js +14 -6
  113. package/src/actions/marks/text/layout.js +21 -45
  114. package/src/actions/marks/tick/actions.js +22 -21
  115. package/src/actions/primitives/createGraphics.js +4 -0
  116. package/src/actions/primitives/editGraphics.js +4 -0
  117. package/src/actions/primitives/index.js +3 -3
  118. package/src/actions/primitives/semantic.js +1 -1
  119. package/src/actions/primitives/semanticValidation/index.js +2 -2
  120. package/src/actions/primitives/semanticValidation/layer.js +1 -1
  121. package/src/actions/regression/edit.js +3 -43
  122. package/src/actions/scales/consumers/families.js +24 -4
  123. package/src/actions/scales/consumers/index.js +1 -1
  124. package/src/actions/scales/consumers/seriesLayout.js +17 -0
  125. package/src/actions/scales/definitions.js +100 -103
  126. package/src/actions/scales/materialize.js +5 -0
  127. package/src/actions/selection/actions.js +79 -121
  128. package/src/actions/titles/actions.js +48 -94
  129. package/src/actions/titles/resolve.js +14 -27
  130. package/src/actions/violinPlots/create.js +37 -44
  131. package/src/core/action.js +20 -5
  132. package/src/core/extensionRegistry.js +104 -0
  133. package/src/core/textMetrics.js +74 -37
  134. package/src/core/validation.js +18 -0
  135. package/src/extension.js +8 -1
  136. package/src/grammar/aggregate.js +48 -28
  137. package/src/grammar/arcs.js +58 -11
  138. package/src/grammar/areaSeries.js +14 -16
  139. package/src/grammar/bin2d.js +37 -23
  140. package/src/grammar/boxPlot.js +14 -13
  141. package/src/grammar/categoricalDensity.js +14 -14
  142. package/src/grammar/categoryOrder.js +8 -14
  143. package/src/grammar/curveCommands.js +87 -20
  144. package/src/grammar/density.js +162 -141
  145. package/src/grammar/facets/index.js +14 -0
  146. package/src/grammar/facets/scales.js +7 -4
  147. package/src/grammar/gradientProfile.js +12 -15
  148. package/src/grammar/histogram.js +145 -83
  149. package/src/grammar/horizon.js +60 -23
  150. package/src/grammar/interval.js +14 -13
  151. package/src/grammar/lineSeries.js +91 -61
  152. package/src/grammar/numeric.js +290 -0
  153. package/src/grammar/paint.js +3 -4
  154. package/src/grammar/palettes.js +21 -32
  155. package/src/grammar/pathCommands.js +6 -0
  156. package/src/grammar/polarPaths.js +5 -5
  157. package/src/grammar/regression/derive.js +48 -9
  158. package/src/grammar/regression/models.js +248 -69
  159. package/src/grammar/regression/parameters.js +8 -0
  160. package/src/grammar/scales/color.js +35 -26
  161. package/src/grammar/scales/continuous.js +71 -24
  162. package/src/grammar/scales/definition.js +1 -1
  163. package/src/grammar/scales/discretized.js +42 -12
  164. package/src/grammar/scales/fields.js +21 -4
  165. package/src/grammar/scales/ordinal.js +117 -61
  166. package/src/grammar/scales/temporal.js +40 -11
  167. package/src/grammar/scales/transformed.js +199 -52
  168. package/src/grammar/schemas/graphicBounds.js +270 -79
  169. package/src/grammar/seriesLayout.js +50 -18
  170. package/src/grammar/ticks.js +123 -53
  171. package/src/grammar/timeUnit.js +18 -8
  172. package/src/grammar/transforms.js +4 -1
  173. package/src/grammar/window.js +70 -57
  174. package/src/index.js +1 -1
  175. package/src/layout/canvas.js +4 -8
  176. package/src/layout/composition.js +39 -40
  177. package/src/layout/facets.js +10 -14
  178. package/src/layout/labels.js +91 -15
  179. package/src/layout/legend.js +22 -20
  180. package/src/layout/legendLane.js +54 -40
  181. package/src/layout/title.js +7 -7
  182. package/src/materialization/bars/aggregate.js +14 -40
  183. package/src/materialization/bars/grouped.js +7 -18
  184. package/src/materialization/bars/histogram.js +2 -19
  185. package/src/materialization/bars/ranged.js +20 -15
  186. package/src/materialization/bars/resolve.js +21 -0
  187. package/src/materialization/facetGuides/legacyCategorical.js +2 -1
  188. package/src/materialization/facetGuides/placement.js +34 -19
  189. package/src/materialization/facetGuides/preparation.js +9 -5
  190. package/src/materialization/facets.js +39 -27
  191. package/src/materialization/guides/resources.js +2 -1
  192. package/src/materialization/marks/capabilities.js +5 -2
  193. package/src/materialization/marks/index.js +24 -4
  194. package/src/materialization/scales/policies/bar.js +61 -59
  195. package/src/materialization/scales/policies/binnedPosition.js +34 -0
  196. package/src/materialization/scales/policies/series.js +13 -7
  197. package/src/materialization/scales/resolve.js +4 -5
  198. package/src/materialization/selection/items/arc.js +16 -2
  199. package/src/materialization/selection/items/bar.js +4 -2
  200. package/src/materialization/selection/items/common.js +21 -1
  201. package/src/materialization/selection/items/path.js +19 -2
  202. package/src/materialization/selection/items/point.js +4 -16
  203. package/src/materialization/selection/items/tick.js +13 -21
  204. package/src/materialization/selection/point.js +11 -4
  205. package/src/materialization/selection/styles.js +19 -27
  206. package/src/materialization/text.js +4 -1
  207. package/src/mcp/adapter.js +8 -3
  208. package/src/renderers/canvas/fill.js +2 -2
  209. package/src/renderers/canvas/index.js +35 -2
  210. package/src/renderers/canvas/native.js +160 -0
  211. package/src/renderers/pdf.js +14 -0
  212. package/src/renderers/svg.js +37 -4
  213. package/types/extension.d.ts +19 -0
  214. package/types/index.d.ts +3 -0
  215. package/types/program.d.ts +509 -131
  216. package/types/svg.d.ts +1 -0
  217. package/src/actions/errorBands/components.js +0 -81
  218. package/src/actions/errorBands/options.js +0 -39
  219. package/src/actions/errorBands/resolve.js +0 -28
  220. package/src/actions/errorBars/components.js +0 -62
  221. package/src/actions/errorBars/options.js +0 -58
  222. package/src/actions/errorBars/resolve.js +0 -25
@@ -1,12 +1,67 @@
1
1
  import { isPlainObject } from "../../core/immutable.js";
2
+ import { validateUserId } from "../../core/identifiers.js";
2
3
  import { validateKeys } from "../../core/validation.js";
3
4
  import { normalizeIntervalParameters } from "../../grammar/interval.js";
4
5
  import { planDerivedDataRevision } from
5
6
  "../../materialization/dataProvenance.js";
6
7
  import { findDataset } from "../../selectors/datasets.js";
8
+ import { resolveEligibleLayer } from "../../selectors/layers.js";
7
9
 
8
10
  const STATISTICS_OPTIONS = Object.freeze(["center", "extent", "level"]);
9
11
 
12
+ export function ownOptions(value, options) {
13
+ return Object.fromEntries(
14
+ options.filter(key => Object.hasOwn(value, key)).map(key => [key, value[key]])
15
+ );
16
+ }
17
+
18
+ export function resolveIntervalOwner(program, requested, {
19
+ idLabel,
20
+ label,
21
+ mark,
22
+ config
23
+ }) {
24
+ const target = requested === undefined
25
+ ? undefined
26
+ : validateUserId(requested, idLabel);
27
+ return resolveEligibleLayer(program, {
28
+ target,
29
+ label,
30
+ predicate: layer =>
31
+ layer.mark?.type === mark &&
32
+ program.markConfigs[layer.id]?.[config] !== undefined
33
+ });
34
+ }
35
+
36
+ export function createResolvedIntervalData(program, resolved) {
37
+ if (resolved.interval.mode !== "statistical") return program;
38
+ return program.createIntervalData({
39
+ id: resolved.dataId,
40
+ source: resolved.source,
41
+ field: resolved.interval.field,
42
+ groupBy: resolved.groupBy,
43
+ center: resolved.interval.center,
44
+ extent: resolved.interval.extent,
45
+ level: resolved.interval.level,
46
+ as: resolved.fields
47
+ });
48
+ }
49
+
50
+ export function applyIntervalRevision(program, interval) {
51
+ if (!interval.changed) return program;
52
+ let next = program.createIntervalData(interval.dataArgs);
53
+ for (const rebind of interval.revision.rebinds) {
54
+ next = next.rebindLayerData(rebind);
55
+ }
56
+ return next;
57
+ }
58
+
59
+ export function releaseIntervalRevision(program, interval) {
60
+ return interval.changed
61
+ ? program.releaseDerivedData(interval.revision.release)
62
+ : program;
63
+ }
64
+
10
65
  export function planIntervalEdit(program, {
11
66
  owner,
12
67
  data,
@@ -5,23 +5,18 @@ import {
5
5
  deriveRegression,
6
6
  normalizeRegressionParameters
7
7
  } from "../../grammar/regression/index.js";
8
- import { MATERIALIZE_OPTIONS, requireDerivedDataset } from "./shared.js";
8
+ import { derivedMaterializer } from "./shared.js";
9
9
 
10
10
  const OPTIONS = Object.freeze([
11
11
  "id", "source", "x", "y", "groupBy", "method", "degree", "span",
12
12
  "confidence", "interval"
13
13
  ]);
14
14
 
15
- export const materializeRegressionData = action(
16
- { op: "materializeRegressionData", description: "Materialize one regression derived dataset." },
17
- function (args = {}) {
18
- validateKeys(args, MATERIALIZE_OPTIONS, "materializeRegressionData");
19
- const { id, source, transform } = requireDerivedDataset(
20
- this,
21
- args.id,
22
- "regression"
23
- );
24
- const result = deriveRegression(source.values, {
15
+ export const materializeRegressionData = derivedMaterializer(
16
+ "materializeRegressionData",
17
+ "Materialize one regression derived dataset.",
18
+ "regression",
19
+ (values, transform) => deriveRegression(values, {
25
20
  x: transform.x,
26
21
  y: transform.y,
27
22
  groupBy: transform.groupBy,
@@ -30,12 +25,7 @@ export const materializeRegressionData = action(
30
25
  span: transform.span,
31
26
  confidence: transform.confidence,
32
27
  interval: transform.interval
33
- });
34
- return this.editSemantic({
35
- property: `dataset[${id}].values`,
36
- value: result.values
37
- });
38
- }
28
+ })
39
29
  );
40
30
 
41
31
  export const createRegressionData = action(
@@ -1,4 +1,6 @@
1
+ import { action } from "../../core/action.js";
1
2
  import { validateUserId } from "../../core/identifiers.js";
3
+ import { validateKeys } from "../../core/validation.js";
2
4
  import { findDataset } from "../../selectors/datasets.js";
3
5
 
4
6
  export const MATERIALIZE_OPTIONS = Object.freeze(["id"]);
@@ -23,3 +25,48 @@ export function requireDerivedDataset(program, id, type) {
23
25
  }
24
26
  return { id: validatedId, dataset, source, transform: dataset.transform[0] };
25
27
  }
28
+
29
+ export function derivedMaterializer(op, description, type, derive, resolve) {
30
+ return action({ op, description }, function (args = {}) {
31
+ validateKeys(args, MATERIALIZE_OPTIONS, op);
32
+ const { id, source, transform } = requireDerivedDataset(
33
+ this,
34
+ args.id,
35
+ type
36
+ );
37
+ const result = derive(source.values, transform);
38
+ const program = resolve === undefined
39
+ ? this
40
+ : this.editSemantic({
41
+ property: `dataset[${id}].transform`,
42
+ value: resolve(result, transform)
43
+ });
44
+ return program.editSemantic({
45
+ property: `dataset[${id}].values`,
46
+ value: Array.isArray(result) ? result : result.values
47
+ });
48
+ });
49
+ }
50
+
51
+ export function derivedCreator(
52
+ op,
53
+ description,
54
+ options,
55
+ idLabel,
56
+ sourceLabel,
57
+ transform,
58
+ materialize,
59
+ requireSource = false
60
+ ) {
61
+ return action({ op, description }, function (args = {}) {
62
+ validateKeys(args, options, op);
63
+ const id = validateUserId(args.id, idLabel);
64
+ const source = validateUserId(
65
+ requireSource ? args.source : args.source ?? this.context.currentData,
66
+ sourceLabel
67
+ );
68
+ return this
69
+ .createDerivedData({ id, source, transform: [transform(args, id)] })
70
+ [materialize]({ id });
71
+ });
72
+ }
@@ -1,48 +1,24 @@
1
- import { action } from "../../core/action.js";
2
- import { validateUserId } from "../../core/identifiers.js";
3
- import { validateKeys } from "../../core/validation.js";
4
1
  import {
5
2
  deriveTimeUnitRows,
6
3
  normalizeTimeUnitTransform
7
4
  } from "../../grammar/timeUnit.js";
8
- import { MATERIALIZE_OPTIONS, requireDerivedDataset } from "./shared.js";
5
+ import { derivedCreator, derivedMaterializer } from "./shared.js";
9
6
 
10
7
  const OPTIONS = Object.freeze(["id", "source", "field", "unit", "as"]);
11
8
 
12
- export const materializeTimeUnitData = action(
13
- {
14
- op: "materializeTimeUnitData",
15
- description: "Materialize one immutable UTC time-unit dataset."
16
- },
17
- function (args = {}) {
18
- validateKeys(args, MATERIALIZE_OPTIONS, "materializeTimeUnitData");
19
- const { id, source, transform } = requireDerivedDataset(
20
- this,
21
- args.id,
22
- "timeUnit"
23
- );
24
- return this.editSemantic({
25
- property: `dataset[${id}].values`,
26
- value: deriveTimeUnitRows(source.values, transform)
27
- });
28
- }
9
+ export const materializeTimeUnitData = derivedMaterializer(
10
+ "materializeTimeUnitData",
11
+ "Materialize one immutable UTC time-unit dataset.",
12
+ "timeUnit",
13
+ deriveTimeUnitRows
29
14
  );
30
15
 
31
- export const createTimeUnitData = action(
32
- {
33
- op: "createTimeUnitData",
34
- description: "Create immutable UTC calendar bucket values."
35
- },
36
- function (args = {}) {
37
- validateKeys(args, OPTIONS, "createTimeUnitData");
38
- const id = validateUserId(args.id, "Time-unit dataset id");
39
- const source = validateUserId(
40
- args.source ?? this.context.currentData,
41
- "Source dataset id"
42
- );
43
- const transform = normalizeTimeUnitTransform(args);
44
- return this
45
- .createDerivedData({ id, source, transform: [transform] })
46
- .materializeTimeUnitData({ id });
47
- }
16
+ export const createTimeUnitData = derivedCreator(
17
+ "createTimeUnitData",
18
+ "Create immutable UTC calendar bucket values.",
19
+ OPTIONS,
20
+ "Time-unit dataset id",
21
+ "Source dataset id",
22
+ normalizeTimeUnitTransform,
23
+ "materializeTimeUnitData"
48
24
  );
@@ -1,50 +1,26 @@
1
- import { action } from "../../core/action.js";
2
- import { validateUserId } from "../../core/identifiers.js";
3
- import { validateKeys } from "../../core/validation.js";
4
1
  import {
5
2
  deriveWindowRows,
6
3
  normalizeWindowTransform
7
4
  } from "../../grammar/window.js";
8
- import { MATERIALIZE_OPTIONS, requireDerivedDataset } from "./shared.js";
5
+ import { derivedCreator, derivedMaterializer } from "./shared.js";
9
6
 
10
7
  const OPTIONS = Object.freeze([
11
8
  "id", "source", "partitionBy", "sortBy", "operations"
12
9
  ]);
13
10
 
14
- export const materializeWindowData = action(
15
- {
16
- op: "materializeWindowData",
17
- description: "Materialize one immutable window-derived dataset."
18
- },
19
- function (args = {}) {
20
- validateKeys(args, MATERIALIZE_OPTIONS, "materializeWindowData");
21
- const { id, source, transform } = requireDerivedDataset(
22
- this,
23
- args.id,
24
- "window"
25
- );
26
- return this.editSemantic({
27
- property: `dataset[${id}].values`,
28
- value: deriveWindowRows(source.values, transform)
29
- });
30
- }
11
+ export const materializeWindowData = derivedMaterializer(
12
+ "materializeWindowData",
13
+ "Materialize one immutable window-derived dataset.",
14
+ "window",
15
+ deriveWindowRows
31
16
  );
32
17
 
33
- export const createWindowData = action(
34
- {
35
- op: "createWindowData",
36
- description: "Create immutable partitioned window values."
37
- },
38
- function (args = {}) {
39
- validateKeys(args, OPTIONS, "createWindowData");
40
- const id = validateUserId(args.id, "Window dataset id");
41
- const source = validateUserId(
42
- args.source ?? this.context.currentData,
43
- "Source dataset id"
44
- );
45
- const transform = normalizeWindowTransform(args);
46
- return this
47
- .createDerivedData({ id, source, transform: [transform] })
48
- .materializeWindowData({ id });
49
- }
18
+ export const createWindowData = derivedCreator(
19
+ "createWindowData",
20
+ "Create immutable partitioned window values.",
21
+ OPTIONS,
22
+ "Window dataset id",
23
+ "Source dataset id",
24
+ normalizeWindowTransform,
25
+ "materializeWindowData"
50
26
  );
@@ -5,6 +5,28 @@ import { resolvePositionScaleDefinition } from "../scales/definitions.js";
5
5
 
6
6
  const POSITION_CHANNELS = Object.freeze(["x", "y", "x2", "y2"]);
7
7
 
8
+ export function currentDistributionPositions(owner, current) {
9
+ const vertical = current.orientation === "vertical";
10
+ const categoryEncoding = owner.encoding[vertical ? "x" : "y"];
11
+ const measureEncoding = owner.encoding[vertical ? "y" : "x"];
12
+ const category = {
13
+ field: current.category,
14
+ fieldType: current.categoryType ?? categoryEncoding.fieldType,
15
+ scale: categoryEncoding.scale
16
+ };
17
+ const measure = {
18
+ field: current.measure,
19
+ fieldType: "quantitative",
20
+ scale: measureEncoding.scale
21
+ };
22
+ return {
23
+ x: vertical ? category : measure,
24
+ y: vertical ? measure : category,
25
+ categoryScale: category.scale,
26
+ measureScale: measure.scale
27
+ };
28
+ }
29
+
8
30
  export function resolveDistributionScalePlan(program, {
9
31
  channel,
10
32
  fieldType,
@@ -35,32 +57,81 @@ export function resolveDistributionScalePlan(program, {
35
57
  };
36
58
  }
37
59
 
60
+ export function resolveDistributionRoles(program, owner, current, args, {
61
+ operation,
62
+ resolvePosition,
63
+ normalize,
64
+ quantitativeDefaults,
65
+ defaultFieldType = false
66
+ }) {
67
+ const previous = currentDistributionPositions(owner, current);
68
+ const position = channel => {
69
+ let value = Object.hasOwn(args, channel)
70
+ ? resolvePosition(args[channel], channel, operation)
71
+ : previous[channel];
72
+ if (typeof value?.field !== "string" || value.field.length === 0) {
73
+ throw new TypeError(`${operation} ${channel} field must be a non-empty string.`);
74
+ }
75
+ if (defaultFieldType) {
76
+ value = { fieldType: value.fieldType ?? "quantitative", ...value };
77
+ }
78
+ return value;
79
+ };
80
+ const positions = normalize(position("x"), position("y"));
81
+ if (positions.orientation === undefined) {
82
+ throw new Error(
83
+ `${operation} requires one categorical axis and one quantitative axis.`
84
+ );
85
+ }
86
+ const vertical = positions.orientation === "vertical";
87
+ const plan = channel => resolveDistributionScalePlan(program, {
88
+ channel,
89
+ fieldType: positions[channel].fieldType,
90
+ requested: positions[channel].scale,
91
+ fallback: previous[vertical === (channel === "x")
92
+ ? "categoryScale"
93
+ : "measureScale"],
94
+ defaults: ["ordinal", "nominal"].includes(positions[channel].fieldType)
95
+ ? { discreteType: "band" }
96
+ : quantitativeDefaults
97
+ });
98
+ const xScale = plan("x");
99
+ const yScale = plan("y");
100
+ return {
101
+ orientation: positions.orientation,
102
+ x: { ...positions.x, scale: xScale.id },
103
+ y: { ...positions.y, scale: yScale.id },
104
+ xScale,
105
+ yScale,
106
+ category: vertical ? positions.x.field : positions.y.field,
107
+ categoryType: vertical ? positions.x.fieldType : positions.y.fieldType,
108
+ measure: vertical ? positions.y.field : positions.x.field,
109
+ previous
110
+ };
111
+ }
112
+
113
+ function setCartesianProperties(program, id, channel, properties) {
114
+ for (const [property, value] of Object.entries(properties)) {
115
+ program = program.editSemantic({
116
+ property: `layer[${id}].encoding.${channel}.${property}`,
117
+ value
118
+ });
119
+ }
120
+ return program;
121
+ }
122
+
38
123
  export function setCartesianPosition(program, id, channel, {
39
124
  field,
40
125
  fieldType,
41
126
  scale,
42
127
  title
43
128
  }) {
44
- let next = program
45
- .editSemantic({
46
- property: `layer[${id}].encoding.${channel}.field`,
47
- value: field
48
- })
49
- .editSemantic({
50
- property: `layer[${id}].encoding.${channel}.fieldType`,
51
- value: fieldType
52
- })
53
- .editSemantic({
54
- property: `layer[${id}].encoding.${channel}.scale`,
55
- value: scale
56
- });
57
- if (title !== undefined) {
58
- next = next.editSemantic({
59
- property: `layer[${id}].encoding.${channel}.title`,
60
- value: title
61
- });
62
- }
63
- return next;
129
+ return setCartesianProperties(program, id, channel, {
130
+ field,
131
+ fieldType,
132
+ scale,
133
+ ...(title === undefined ? {} : { title })
134
+ });
64
135
  }
65
136
 
66
137
  export function setCartesianRange(
@@ -72,24 +143,73 @@ export function setCartesianRange(
72
143
  scale,
73
144
  title
74
145
  ) {
75
- return setCartesianPosition(program, id, channel, {
146
+ program = setCartesianPosition(program, id, channel, {
76
147
  field: lower,
77
148
  fieldType: "quantitative",
78
149
  scale,
79
150
  title
80
- })
81
- .editSemantic({
82
- property: `layer[${id}].encoding.${channel}2.field`,
83
- value: upper
84
- })
85
- .editSemantic({
86
- property: `layer[${id}].encoding.${channel}2.fieldType`,
87
- value: "quantitative"
88
- })
89
- .editSemantic({
90
- property: `layer[${id}].encoding.${channel}2.scale`,
91
- value: scale
92
- });
151
+ });
152
+ return setCartesianProperties(program, id, `${channel}2`, {
153
+ field: upper,
154
+ fieldType: "quantitative",
155
+ scale
156
+ });
157
+ }
158
+
159
+ export function updateDistributionPositions(
160
+ program,
161
+ owner,
162
+ current,
163
+ candidate,
164
+ { owned, lower, upper, title, update }
165
+ ) {
166
+ assertDistributionScaleHandoff(program, {
167
+ owned,
168
+ oldXScale: candidate.previous.x.scale,
169
+ oldYScale: candidate.previous.y.scale,
170
+ newXScale: candidate.xScale.id,
171
+ newYScale: candidate.yScale.id
172
+ });
173
+ let next = program;
174
+ for (const id of owned) next = clearCartesianPositions(next, id);
175
+ for (const scale of [candidate.xScale, candidate.yScale]) {
176
+ if (scale.create) next = next.createScale(scale.definition);
177
+ }
178
+ const vertical = candidate.orientation === "vertical";
179
+ const axes = {
180
+ category: vertical ? candidate.x : candidate.y,
181
+ measure: vertical ? candidate.y : candidate.x,
182
+ categoryChannel: vertical ? "x" : "y",
183
+ measureChannel: vertical ? "y" : "x"
184
+ };
185
+ next = setCartesianPosition(next, owner.id, axes.categoryChannel, {
186
+ field: candidate.category,
187
+ fieldType: candidate.categoryType,
188
+ scale: axes.category.scale
189
+ });
190
+ next = setCartesianRange(
191
+ next, owner.id, axes.measureChannel, lower, upper, axes.measure.scale, title
192
+ );
193
+ next = update(next, axes);
194
+ for (const id of new Set([candidate.xScale.id, candidate.yScale.id])) {
195
+ next = next.rematerializeScale({ id, marks: false, guides: false });
196
+ }
197
+ next = rebindDistributionGuides(next, {
198
+ oldXScale: candidate.previous.x.scale,
199
+ oldYScale: candidate.previous.y.scale,
200
+ newXScale: candidate.xScale.id,
201
+ newYScale: candidate.yScale.id,
202
+ oldXTitle: candidate.previous.x.field,
203
+ oldYTitle: candidate.previous.y.field,
204
+ newXTitle: candidate.x.field,
205
+ newYTitle: candidate.y.field,
206
+ oldMeasureChannel: current.orientation === "vertical" ? "y" : "x",
207
+ newMeasureChannel: axes.measureChannel
208
+ });
209
+ for (const scale of [candidate.xScale, candidate.yScale]) {
210
+ if (scale.edit !== undefined) next = next.editScale(scale.edit);
211
+ }
212
+ return next;
93
213
  }
94
214
 
95
215
  function axisMethods(channel) {
@@ -1,6 +1,10 @@
1
1
  import { action } from "../../core/action.js";
2
2
  import { readQuantitativeField } from "../../grammar/scales/index.js";
3
- import { resolveTarget, validateOptions } from "./shared.js";
3
+ import {
4
+ resolveTarget,
5
+ setEncodingProperties,
6
+ validateOptions
7
+ } from "./shared.js";
4
8
 
5
9
  const OPTIONS = Object.freeze(["target", "value", "field", "fieldType"]);
6
10
 
@@ -58,15 +62,10 @@ export const encodeAngle = action(
58
62
  value: args.value
59
63
  });
60
64
  } else {
61
- next = next
62
- .editSemantic({
63
- property: `layer[${target}].encoding.angle.field`,
64
- value: args.field
65
- })
66
- .editSemantic({
67
- property: `layer[${target}].encoding.angle.fieldType`,
68
- value: "quantitative"
69
- });
65
+ next = setEncodingProperties(next, target, "angle", {
66
+ field: args.field,
67
+ fieldType: "quantitative"
68
+ });
70
69
  }
71
70
  return rematerialize(next, layer, target);
72
71
  }
@@ -13,12 +13,13 @@ import {
13
13
  findLayer,
14
14
  resolveEligibleLayer
15
15
  } from "../../selectors/layers.js";
16
- import { applyMaterializationPlan } from "../../materialization/dependencies.js";
17
- import { planEncodingRematerialization } from "../../materialization/encodings.js";
18
16
  import {
19
17
  applyEncodingScale,
18
+ clearMarkGraphic,
19
+ rematerializeEncoding,
20
20
  resolveReassignmentScaleOptions,
21
21
  resolveTarget,
22
+ setEncodingProperties,
22
23
  validateOptions
23
24
  } from "./shared.js";
24
25
 
@@ -64,30 +65,15 @@ function encodeAppearanceField(program, channel, args, operation) {
64
65
  readQuantitativeField(dataset.values, args.field);
65
66
  }
66
67
 
67
- let next = program
68
- .editSemantic({
69
- property: `layer[${target}].encoding.${channel}.field`,
70
- value: args.field
71
- })
72
- .editSemantic({
73
- property: `layer[${target}].encoding.${channel}.fieldType`,
74
- value: fieldType
75
- })
76
- .editSemantic({
77
- property: `layer[${target}].encoding.${channel}.scale`,
78
- value: scale.id
79
- });
68
+ let next = setEncodingProperties(program, target, channel, {
69
+ field: args.field,
70
+ fieldType,
71
+ scale: scale.id
72
+ });
80
73
  next = applyEncodingScale(next, scale, requestedScale, {
81
74
  reassignment: previous?.scale === scale.id
82
75
  });
83
- return applyMaterializationPlan(
84
- next,
85
- planEncodingRematerialization(next, {
86
- target,
87
- channel,
88
- scale: scale.id
89
- })
90
- );
76
+ return rematerializeEncoding(next, target, channel, scale.id);
91
77
  }
92
78
 
93
79
  const encodeRadius = action(
@@ -152,12 +138,7 @@ const removePointRadius = action(
152
138
  });
153
139
  const next = this
154
140
  ._withoutMaterializationConfig(["marks", layer.id, "radius"]);
155
- const graphic = next.graphicSpec.objects[layer.id];
156
- const baseline = graphic === undefined
157
- ? next
158
- : graphic.type === "collection"
159
- ? next.editGraphics({ target: layer.id, property: "items", value: [] })
160
- : next.editGraphics({ target: layer.id, property: "length", value: 0 });
141
+ const baseline = clearMarkGraphic(next, layer.id);
161
142
  return baseline.rematerializePointMark({ id: layer.id });
162
143
  }
163
144
  );
@@ -249,31 +230,16 @@ const encodeOpacity = action(
249
230
  }
250
231
  const { opacity, ...config } = this.markConfigs[target] ?? {};
251
232
  void opacity;
252
- let next = this
253
- ._withoutMaterializationConfig(["marks", target, "opacity"])
254
- .editSemantic({
255
- property: `layer[${target}].encoding.opacity.field`,
256
- value: args.field
257
- })
258
- .editSemantic({
259
- property: `layer[${target}].encoding.opacity.fieldType`,
260
- value: fieldType
261
- })
262
- .editSemantic({
263
- property: `layer[${target}].encoding.opacity.scale`,
264
- value: scale.id
265
- })
233
+ let next = setEncodingProperties(
234
+ this._withoutMaterializationConfig(["marks", target, "opacity"]),
235
+ target,
236
+ "opacity",
237
+ { field: args.field, fieldType, scale: scale.id }
238
+ );
266
239
  next = applyEncodingScale(next, scale, requestedScale, {
267
240
  reassignment: previous?.scale === scale.id
268
241
  });
269
- return applyMaterializationPlan(
270
- next,
271
- planEncodingRematerialization(next, {
272
- target,
273
- channel: "opacity",
274
- scale: scale.id
275
- })
276
- );
242
+ return rematerializeEncoding(next, target, "opacity", scale.id);
277
243
  }
278
244
  );
279
245
 
@@ -12,7 +12,9 @@ export function resolveDensityScaleOptions(value, defaults, label) {
12
12
  if (value !== undefined && !isPlainObject(value)) {
13
13
  throw new TypeError(`${label} must be a plain object.`);
14
14
  }
15
- return { ...defaults, ...(value ?? {}) };
15
+ const scale = { ...defaults, ...(value ?? {}) };
16
+ if (value?.type === "log" && value.zero === undefined) delete scale.zero;
17
+ return scale;
16
18
  }
17
19
 
18
20
  export function resolveDensityCategoryScaleOptions(value, fallbackId) {