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
@@ -65,6 +65,14 @@ function requireLegendOwner(program, owner) {
65
65
  return { layer, config };
66
66
  }
67
67
 
68
+ function editProperties(program, target, properties) {
69
+ let next = program;
70
+ for (const [property, value] of Object.entries(properties)) {
71
+ next = next.editGraphics({ target, property, value });
72
+ }
73
+ return next;
74
+ }
75
+
68
76
  export const rematerializeGradientPlotLegend = action(
69
77
  {
70
78
  op: "rematerializeGradientPlotLegend",
@@ -82,35 +90,33 @@ export const rematerializeGradientPlotLegend = action(
82
90
  const y = bounds.y + 50;
83
91
  const width = 22;
84
92
  const height = Math.min(170, Math.max(80, bounds.height - 110));
85
- return this
86
- .editGraphics({ target: ids.strip, property: "x", value: x })
87
- .editGraphics({ target: ids.strip, property: "y", value: y })
88
- .editGraphics({ target: ids.strip, property: "width", value: width })
89
- .editGraphics({ target: ids.strip, property: "height", value: height })
90
- .editGraphics({
91
- target: ids.strip,
92
- property: "fill",
93
- value: createDensityLegendPaint(config.gradient.opacity)
94
- })
95
- .editGraphics({ target: ids.strip, property: "stroke", value: "#cbd5e1" })
96
- .editGraphics({ target: ids.strip, property: "strokeWidth", value: 1 })
97
- .editGraphics({ target: ids.labels, property: "x", value: x + width + 10 })
98
- .editGraphics({ target: ids.labels, property: "y", value: [y + height, y] })
99
- .editGraphics({ target: ids.labels, property: "text", value: ["Low", "High"] })
100
- .editGraphics({ target: ids.labels, property: "fill", value: "#64748b" })
101
- .editGraphics({ target: ids.labels, property: "fontSize", value: 11 })
102
- .editGraphics({ target: ids.labels, property: "fontFamily", value: "sans-serif" })
103
- .editGraphics({ target: ids.labels, property: "textAlign", value: "left" })
104
- .editGraphics({ target: ids.labels, property: "textBaseline", value: "middle" })
105
- .editGraphics({ target: ids.title, property: "x", value: x })
106
- .editGraphics({ target: ids.title, property: "y", value: y - 18 })
107
- .editGraphics({ target: ids.title, property: "text", value: config.guides.legend.title })
108
- .editGraphics({ target: ids.title, property: "fill", value: "#334155" })
109
- .editGraphics({ target: ids.title, property: "fontSize", value: 12 })
110
- .editGraphics({ target: ids.title, property: "fontFamily", value: "sans-serif" })
111
- .editGraphics({ target: ids.title, property: "fontWeight", value: 600 })
112
- .editGraphics({ target: ids.title, property: "textAlign", value: "left" })
113
- .editGraphics({ target: ids.title, property: "textBaseline", value: "middle" });
93
+ let next = editProperties(this, ids.strip, {
94
+ x, y, width, height,
95
+ fill: createDensityLegendPaint(config.gradient.opacity),
96
+ stroke: "#cbd5e1",
97
+ strokeWidth: 1
98
+ });
99
+ next = editProperties(next, ids.labels, {
100
+ x: x + width + 10,
101
+ y: [y + height, y],
102
+ text: ["Low", "High"],
103
+ fill: "#64748b",
104
+ fontSize: 11,
105
+ fontFamily: "sans-serif",
106
+ textAlign: "left",
107
+ textBaseline: "middle"
108
+ });
109
+ return editProperties(next, ids.title, {
110
+ x,
111
+ y: y - 18,
112
+ text: config.guides.legend.title,
113
+ fill: "#334155",
114
+ fontSize: 12,
115
+ fontFamily: "sans-serif",
116
+ fontWeight: 600,
117
+ textAlign: "left",
118
+ textBaseline: "middle"
119
+ });
114
120
  }
115
121
  );
116
122
 
@@ -130,24 +136,29 @@ export const createGradientPlotLegend = action(
130
136
  }
131
137
  const ids = legendIds(args.owner);
132
138
  const densityScale = `${args.owner}Density`;
133
- let next = this
134
- .editSemantic({ property: `scale[${densityScale}].type`, value: "sequential" })
135
- .editSemantic({
136
- property: `scale[${densityScale}].domain`,
137
- value: this.markConfigs[args.owner].gradientPlot.intensityDomain
138
- })
139
- .editSemantic({
140
- property: `scale[${densityScale}].range`,
141
- value: { palette: { name: "greys" } }
142
- })
143
- .editSemantic({ property: "guide.legend.color.scale", value: densityScale })
144
- .editSemantic({
145
- property: "guide.legend.color.title",
146
- value: args.title ?? "Relative density"
147
- })
148
- .createGraphics({ id: ids.strip, type: "rect", parent: "canvas" })
149
- .createGraphics({ id: ids.labels, type: "text", length: 2, parent: "canvas" })
150
- .createGraphics({ id: ids.title, type: "text", parent: "canvas" });
139
+ const title = args.title ?? "Relative density";
140
+ let next = this;
141
+ for (const [property, value] of [
142
+ [`scale[${densityScale}].type`, "sequential"],
143
+ [`scale[${densityScale}].domain`, this.markConfigs[args.owner].gradientPlot.intensityDomain],
144
+ [`scale[${densityScale}].range`, { palette: { name: "greys" } }],
145
+ ["guide.legend.color.scale", densityScale],
146
+ ["guide.legend.color.title", title]
147
+ ]) {
148
+ next = next.editSemantic({ property, value });
149
+ }
150
+ for (const [id, type, length] of [
151
+ [ids.strip, "rect"],
152
+ [ids.labels, "text", 2],
153
+ [ids.title, "text"]
154
+ ]) {
155
+ next = next.createGraphics({
156
+ id,
157
+ type,
158
+ ...(length === undefined ? {} : { length }),
159
+ parent: "canvas"
160
+ });
161
+ }
151
162
  next = next._withMarkConfig(args.owner, {
152
163
  ...next.markConfigs[args.owner],
153
164
  gradientPlot: {
@@ -155,7 +166,7 @@ export const createGradientPlotLegend = action(
155
166
  guides: {
156
167
  ...next.markConfigs[args.owner].gradientPlot.guides,
157
168
  legend: {
158
- title: args.title ?? "Relative density",
169
+ title,
159
170
  position: args.position ?? "right"
160
171
  }
161
172
  }
@@ -5,15 +5,13 @@ import { validateKeys } from "../../core/validation.js";
5
5
  import { planDerivedDataRevision } from "../../materialization/dataProvenance.js";
6
6
  import { findDataset } from "../../selectors/datasets.js";
7
7
  import { findLayer } from "../../selectors/layers.js";
8
+ import { removeOwnedMark } from "../marks/remove.js";
8
9
  import { GRADIENT_PROFILE_FIELDS } from "../../grammar/gradientProfile.js";
9
10
  import { removeGradientPlotLegend } from "./components.js";
10
11
  import {
11
- assertDistributionScaleHandoff,
12
- clearCartesianPositions,
13
- rebindDistributionGuides,
14
- resolveDistributionScalePlan,
12
+ resolveDistributionRoles,
15
13
  setCartesianPosition,
16
- setCartesianRange
14
+ updateDistributionPositions
17
15
  } from "../distributions/revision.js";
18
16
  import {
19
17
  resolveGradientAppearance,
@@ -39,140 +37,20 @@ function patch(value, label) {
39
37
  }
40
38
 
41
39
  function removeCenter(program, id) {
42
- let next = program;
43
- const selections = Object.entries(
44
- program.materializationConfigs.selections ?? {}
45
- ).filter(([, config]) => config.target === id).map(([selection]) => selection);
46
- for (const [highlight, config] of Object.entries(
47
- program.materializationConfigs.highlights ?? {}
48
- )) {
49
- if (config.target === id || selections.includes(config.selection)) {
50
- next = next._withoutMaterializationConfig(["highlights", highlight]);
51
- }
52
- }
53
- for (const selection of selections) {
54
- next = next._withoutMaterializationConfig(["selections", selection]);
55
- }
56
- if (findLayer(next, id) !== undefined) {
57
- next = next.editSemantic({ property: `layer[${id}]`, remove: true });
58
- }
59
- if (next.graphicSpec.objects[id] !== undefined) {
60
- next = next.editGraphics({ target: id, remove: true });
61
- }
62
- return next
63
- ._withoutMaterializationConfig(["marks", id])
64
- ._withContext({
65
- ...(program.context.currentMark === id ? { currentMark: undefined } : {}),
66
- ...(selections.includes(program.context.currentSelection)
67
- ? { currentSelection: undefined }
68
- : {})
69
- });
70
- }
71
-
72
- function currentGradientPositions(owner, current) {
73
- const categoryChannel = current.orientation === "vertical" ? "x" : "y";
74
- const measureChannel = current.orientation === "vertical" ? "y" : "x";
75
- const categoryEncoding = owner.encoding[categoryChannel];
76
- const measureEncoding = owner.encoding[measureChannel];
77
- return {
78
- x: current.orientation === "vertical"
79
- ? {
80
- field: current.category,
81
- fieldType: current.categoryType,
82
- scale: categoryEncoding.scale
83
- }
84
- : {
85
- field: current.measure,
86
- fieldType: "quantitative",
87
- scale: measureEncoding.scale
88
- },
89
- y: current.orientation === "vertical"
90
- ? {
91
- field: current.measure,
92
- fieldType: "quantitative",
93
- scale: measureEncoding.scale
94
- }
95
- : {
96
- field: current.category,
97
- fieldType: current.categoryType,
98
- scale: categoryEncoding.scale
99
- },
100
- categoryScale: categoryEncoding.scale,
101
- measureScale: measureEncoding.scale
102
- };
103
- }
104
-
105
- function requirePositionField(position, label) {
106
- if (typeof position?.field !== "string" || position.field.length === 0) {
107
- throw new TypeError(`${label} field must be a non-empty string.`);
108
- }
109
- return position;
40
+ return removeOwnedMark(program, id, true);
110
41
  }
111
42
 
112
43
  function roleCandidate(program, owner, current, args) {
113
- const previous = currentGradientPositions(owner, current);
114
- const positions = normalizeGradientPositionTypes(
115
- requirePositionField(
116
- Object.hasOwn(args, "x")
117
- ? resolveGradientPosition(args.x, "x", "editGradientPlot")
118
- : previous.x,
119
- "editGradientPlot x"
120
- ),
121
- requirePositionField(
122
- Object.hasOwn(args, "y")
123
- ? resolveGradientPosition(args.y, "y", "editGradientPlot")
124
- : previous.y,
125
- "editGradientPlot y"
126
- )
127
- );
128
- if (positions.orientation === undefined) {
129
- throw new Error(
130
- "editGradientPlot requires one categorical axis and one quantitative axis."
131
- );
132
- }
133
- const xRoleScale = positions.orientation === "vertical"
134
- ? previous.categoryScale
135
- : previous.measureScale;
136
- const yRoleScale = positions.orientation === "vertical"
137
- ? previous.measureScale
138
- : previous.categoryScale;
139
- const xScale = resolveDistributionScalePlan(program, {
140
- channel: "x",
141
- fieldType: positions.x.fieldType,
142
- requested: positions.x.scale,
143
- fallback: xRoleScale,
144
- defaults: ["ordinal", "nominal"].includes(positions.x.fieldType)
145
- ? { discreteType: "band" }
146
- : {}
147
- });
148
- const yScale = resolveDistributionScalePlan(program, {
149
- channel: "y",
150
- fieldType: positions.y.fieldType,
151
- requested: positions.y.scale,
152
- fallback: yRoleScale,
153
- defaults: ["ordinal", "nominal"].includes(positions.y.fieldType)
154
- ? { discreteType: "band" }
155
- : {}
156
- });
157
44
  return {
158
45
  source: Object.hasOwn(args, "data")
159
46
  ? validateUserId(args.data, "Gradient-plot data id")
160
47
  : current.source,
161
- orientation: positions.orientation,
162
- x: { ...positions.x, scale: xScale.id },
163
- y: { ...positions.y, scale: yScale.id },
164
- xScale,
165
- yScale,
166
- category: positions.orientation === "vertical"
167
- ? positions.x.field
168
- : positions.y.field,
169
- categoryType: positions.orientation === "vertical"
170
- ? positions.x.fieldType
171
- : positions.y.fieldType,
172
- measure: positions.orientation === "vertical"
173
- ? positions.y.field
174
- : positions.x.field,
175
- previous
48
+ ...resolveDistributionRoles(program, owner, current, args, {
49
+ operation: "editGradientPlot",
50
+ resolvePosition: resolveGradientPosition,
51
+ normalize: normalizeGradientPositionTypes,
52
+ quantitativeDefaults: {}
53
+ })
176
54
  };
177
55
  }
178
56
 
@@ -190,46 +68,27 @@ function sameRoleCandidate(current, candidate) {
190
68
 
191
69
  function updateGradientPositions(program, owner, current, candidate, hasCenter) {
192
70
  const owned = [owner.id, ...(hasCenter ? [current.centerId] : [])];
193
- assertDistributionScaleHandoff(program, {
194
- owned,
195
- oldXScale: candidate.previous.x.scale,
196
- oldYScale: candidate.previous.y.scale,
197
- newXScale: candidate.xScale.id,
198
- newYScale: candidate.yScale.id
199
- });
200
- let next = program;
201
- for (const id of owned) next = clearCartesianPositions(next, id);
202
- for (const scale of [candidate.xScale, candidate.yScale]) {
203
- if (scale.create) next = next.createScale(scale.definition);
204
- }
205
- const category = candidate.orientation === "vertical" ? candidate.x : candidate.y;
206
- const measure = candidate.orientation === "vertical" ? candidate.y : candidate.x;
207
- const categoryChannel = candidate.orientation === "vertical" ? "x" : "y";
208
- const measureChannel = candidate.orientation === "vertical" ? "y" : "x";
209
- next = setCartesianPosition(next, owner.id, categoryChannel, {
210
- field: candidate.category,
211
- fieldType: candidate.categoryType,
212
- scale: category.scale
213
- });
214
- next = setCartesianRange(
215
- next,
216
- owner.id,
217
- measureChannel,
218
- GRADIENT_PROFILE_FIELDS.lower,
219
- GRADIENT_PROFILE_FIELDS.upper,
220
- measure.scale
221
- );
222
- if (hasCenter) {
223
- next = setCartesianPosition(next, current.centerId, categoryChannel, {
71
+ return updateDistributionPositions(
72
+ program,
73
+ owner,
74
+ current,
75
+ candidate,
76
+ {
77
+ owned,
78
+ lower: GRADIENT_PROFILE_FIELDS.lower,
79
+ upper: GRADIENT_PROFILE_FIELDS.upper,
80
+ update(next, { category, measure, categoryChannel, measureChannel }) {
81
+ if (!hasCenter) return next;
82
+ next = setCartesianPosition(next, current.centerId, categoryChannel, {
224
83
  field: candidate.category,
225
84
  fieldType: candidate.categoryType,
226
85
  scale: category.scale
227
86
  });
228
- next = setCartesianPosition(next, current.centerId, measureChannel, {
87
+ return setCartesianPosition(next, current.centerId, measureChannel, {
229
88
  field: GRADIENT_PROFILE_FIELDS.center,
230
89
  fieldType: "quantitative",
231
90
  scale: measure.scale
232
- })._withMarkConfig(current.centerId, {
91
+ })._withMarkConfig(current.centerId, {
233
92
  ...next.markConfigs[current.centerId],
234
93
  fixedSpan: {
235
94
  ...next.markConfigs[current.centerId].fixedSpan,
@@ -237,27 +96,10 @@ function updateGradientPositions(program, owner, current, candidate, hasCenter)
237
96
  ? "horizontal"
238
97
  : "vertical"
239
98
  }
240
- });
241
- }
242
- for (const id of new Set([candidate.xScale.id, candidate.yScale.id])) {
243
- next = next.rematerializeScale({ id, marks: false, guides: false });
244
- }
245
- next = rebindDistributionGuides(next, {
246
- oldXScale: candidate.previous.x.scale,
247
- oldYScale: candidate.previous.y.scale,
248
- newXScale: candidate.xScale.id,
249
- newYScale: candidate.yScale.id,
250
- oldXTitle: candidate.previous.x.field,
251
- oldYTitle: candidate.previous.y.field,
252
- newXTitle: candidate.x.field,
253
- newYTitle: candidate.y.field,
254
- oldMeasureChannel: current.orientation === "vertical" ? "y" : "x",
255
- newMeasureChannel: candidate.orientation === "vertical" ? "y" : "x"
256
- });
257
- for (const scale of [candidate.xScale, candidate.yScale]) {
258
- if (scale.edit !== undefined) next = next.editScale(scale.edit);
259
- }
260
- return next;
99
+ });
100
+ }
101
+ }
102
+ );
261
103
  }
262
104
 
263
105
  export const editGradientPlot = action(
@@ -301,7 +143,10 @@ export const editGradientPlot = action(
301
143
  key => Object.hasOwn(args, key)
302
144
  );
303
145
  const changesRoles = !sameRoleCandidate(current, candidate);
304
- if (changesRoles) {
146
+ const statistical = JSON.stringify(density) !== JSON.stringify(current.density) ||
147
+ center !== false && current.center !== false &&
148
+ center.type !== current.center.type;
149
+ if (changesRoles || statistical) {
305
150
  const hadCenter = findLayer(this, current.centerId) !== undefined;
306
151
  const revision = planDerivedDataRevision(this, {
307
152
  owner: owner.id,
@@ -404,7 +249,6 @@ export const editGradientPlot = action(
404
249
  currentData: candidate.source
405
250
  });
406
251
  };
407
- applyEdit(this);
408
252
  return applyEdit(this);
409
253
  }
410
254
  if (roleRequested && !OPTIONS.slice(4).some(
@@ -415,33 +259,8 @@ export const editGradientPlot = action(
415
259
  currentData: current.source
416
260
  });
417
261
  }
418
- const densityChanged = JSON.stringify(density) !== JSON.stringify(current.density);
419
- const centerTypeChanged = center !== false && current.center !== false &&
420
- center.type !== current.center.type;
421
- const statistical = densityChanged || centerTypeChanged;
422
262
  let next = this;
423
- let profileId = current.profileId;
424
- if (statistical) {
425
- const consumers = [owner.id, ...(findLayer(next, current.centerId) ? [current.centerId] : [])];
426
- const revision = planDerivedDataRevision(next, {
427
- owner: owner.id,
428
- role: "ProfileData",
429
- previous: current.profileId,
430
- consumers
431
- });
432
- profileId = revision.id;
433
- next = next.createGradientProfileData({
434
- id: profileId,
435
- source: current.source,
436
- category: current.category,
437
- field: current.measure,
438
- ...density,
439
- center: center === false ? "median" : center.type
440
- });
441
- for (const rebind of revision.rebinds) next = next.rebindLayerData(rebind);
442
- next = next.releaseDerivedData(revision.release);
443
- }
444
- const profile = findDataset(next, profileId);
263
+ const profile = findDataset(next, current.profileId);
445
264
  next = next._withMarkConfig(owner.id, {
446
265
  ...next.markConfigs[owner.id],
447
266
  gradientPlot: {
@@ -450,7 +269,7 @@ export const editGradientPlot = action(
450
269
  width,
451
270
  gradient,
452
271
  center,
453
- profileId,
272
+ profileId: current.profileId,
454
273
  intensityDomain: profile.transform[0].resolved.intensityDomain
455
274
  }
456
275
  });
@@ -463,7 +282,7 @@ export const editGradientPlot = action(
463
282
  next = next.createGradientPlotCenter({
464
283
  id: current.centerId,
465
284
  owner: owner.id,
466
- data: profileId,
285
+ data: current.profileId,
467
286
  category: current.category,
468
287
  categoryType: current.categoryType,
469
288
  coordinate: owner.coordinate,
@@ -5,20 +5,21 @@ import {
5
5
  } from "./components.js";
6
6
  import { createGradientPlot } from "./create.js";
7
7
  import { editGradientPlot } from "./edit.js";
8
+ import { rebindGradientPlotProfile } from "./rebind.js";
8
9
  import {
9
10
  materializeGradientPlot,
10
11
  materializeGradientPlotFill
11
12
  } from "./materialize.js";
12
- import { rebindGradientPlotProfile } from "./rebind.js";
13
13
 
14
14
  export function registerGradientPlotActions(ProgramClass) {
15
- ProgramClass.prototype.createGradientPlot = createGradientPlot;
16
- ProgramClass.prototype.editGradientPlot = editGradientPlot;
17
- ProgramClass.prototype.materializeGradientPlot = materializeGradientPlot;
18
- ProgramClass.prototype.materializeGradientPlotFill = materializeGradientPlotFill;
19
- ProgramClass.prototype.createGradientPlotCenter = createGradientPlotCenter;
20
- ProgramClass.prototype.createGradientPlotLegend = createGradientPlotLegend;
21
- ProgramClass.prototype.rematerializeGradientPlotLegend =
22
- rematerializeGradientPlotLegend;
23
- ProgramClass.prototype.rebindGradientPlotProfile = rebindGradientPlotProfile;
15
+ Object.assign(ProgramClass.prototype, {
16
+ createGradientPlot,
17
+ editGradientPlot,
18
+ materializeGradientPlot,
19
+ materializeGradientPlotFill,
20
+ createGradientPlotCenter,
21
+ createGradientPlotLegend,
22
+ rematerializeGradientPlotLegend,
23
+ rebindGradientPlotProfile
24
+ });
24
25
  }
@@ -23,10 +23,6 @@ function position(values, scale) {
23
23
  : mapContinuousScaleValues(values, scale);
24
24
  }
25
25
 
26
- function profileValue(row, key) {
27
- return row[GRADIENT_PROFILE_FIELDS[key]];
28
- }
29
-
30
26
  function axisWithTitle(option, text) {
31
27
  if (option === false) return false;
32
28
  const axis = option ?? {};
@@ -56,20 +52,22 @@ function gradientAxesOptions(config) {
56
52
  }
57
53
 
58
54
  function bodyItems(program, layer, dataset, config) {
59
- const categoryChannel = config.orientation === "vertical" ? "x" : "y";
60
- const measureChannel = config.orientation === "vertical" ? "y" : "x";
55
+ const vertical = config.orientation === "vertical";
56
+ const categoryChannel = vertical ? "x" : "y";
57
+ const measureChannel = vertical ? "y" : "x";
61
58
  const categoryEncoding = layer.encoding[categoryChannel];
62
59
  const measureEncoding = layer.encoding[measureChannel];
63
60
  const categoryScale = program.resolvedScales[categoryEncoding.scale];
64
61
  const measureScale = program.resolvedScales[measureEncoding.scale];
65
- const categories = dataset.values.map(row => row[config.category]);
62
+ const rows = dataset.values;
63
+ const categories = rows.map(row => row[config.category]);
66
64
  const centers = position(categories, categoryScale);
67
65
  const lower = position(
68
- dataset.values.map(row => profileValue(row, "lower")),
66
+ rows.map(row => row[GRADIENT_PROFILE_FIELDS.lower]),
69
67
  measureScale
70
68
  );
71
69
  const upper = position(
72
- dataset.values.map(row => profileValue(row, "upper")),
70
+ rows.map(row => row[GRADIENT_PROFILE_FIELDS.upper]),
73
71
  measureScale
74
72
  );
75
73
  const categoryColors = resolveGradientPlotCategoryColors(
@@ -81,10 +79,10 @@ function bodyItems(program, layer, dataset, config) {
81
79
  if (!Number.isFinite(thickness) || thickness <= 0) {
82
80
  throw new Error(`Gradient plot "${layer.id}" requires a positive band width.`);
83
81
  }
84
- return dataset.values.map((row, index) => {
82
+ return rows.map((row, index) => {
85
83
  const a = lower[index];
86
84
  const b = upper[index];
87
- const geometry = config.orientation === "vertical"
85
+ const geometry = vertical
88
86
  ? {
89
87
  x: centers[index] - thickness / 2,
90
88
  y: Math.min(a, b),
@@ -102,8 +100,8 @@ function bodyItems(program, layer, dataset, config) {
102
100
  properties: {
103
101
  ...geometry,
104
102
  fill: createGradientPlotPaint({
105
- values: profileValue(row, "values"),
106
- intensities: profileValue(row, "intensities")
103
+ values: row[GRADIENT_PROFILE_FIELDS.values],
104
+ intensities: row[GRADIENT_PROFILE_FIELDS.intensities]
107
105
  }, {
108
106
  orientation: config.orientation,
109
107
  valueScale: measureScale,
@@ -223,28 +221,16 @@ export const materializeGradientPlot = action(
223
221
  }
224
222
  });
225
223
  const measureChannel = orientation === "vertical" ? "y" : "x";
226
- const secondaryChannel = `${measureChannel}2`;
227
- next = next
228
- .editSemantic({
229
- property: `layer[${owner}].encoding.${measureChannel}.field`,
230
- value: GRADIENT_PROFILE_FIELDS.lower
231
- })
232
- .editSemantic({
233
- property: `layer[${owner}].encoding.${measureChannel}.fieldType`,
234
- value: "quantitative"
235
- })
236
- .editSemantic({
237
- property: `layer[${owner}].encoding.${secondaryChannel}.field`,
238
- value: GRADIENT_PROFILE_FIELDS.upper
239
- })
240
- .editSemantic({
241
- property: `layer[${owner}].encoding.${secondaryChannel}.fieldType`,
242
- value: "quantitative"
243
- })
244
- .editSemantic({
245
- property: `layer[${owner}].encoding.${secondaryChannel}.scale`,
246
- value: measure.scale
247
- });
224
+ const path = `layer[${owner}].encoding.`;
225
+ for (const [property, value] of [
226
+ [`${path}${measureChannel}.field`, GRADIENT_PROFILE_FIELDS.lower],
227
+ [`${path}${measureChannel}.fieldType`, "quantitative"],
228
+ [`${path}${measureChannel}2.field`, GRADIENT_PROFILE_FIELDS.upper],
229
+ [`${path}${measureChannel}2.fieldType`, "quantitative"],
230
+ [`${path}${measureChannel}2.scale`, measure.scale]
231
+ ]) {
232
+ next = next.editSemantic({ property, value });
233
+ }
248
234
  next = next.materializeGradientPlotFill({ id: owner });
249
235
  if (config.center !== false) {
250
236
  const categoryScale = next.resolvedScales[category.scale];
@@ -1,8 +1,7 @@
1
- import { isPlainObject } from "../../core/immutable.js";
2
1
  import {
3
- validateKeys,
4
2
  validateNonEmptyString,
5
3
  validateNonNegativeFinite,
4
+ validateOptionObject,
6
5
  validateUnitInterval
7
6
  } from "../../core/validation.js";
8
7
  import { normalizePalette } from "../../grammar/palettes.js";
@@ -14,11 +13,10 @@ export const GRADIENT_PLOT_OPTIONS = Object.freeze([
14
13
 
15
14
  function plain(value, keys, label, operation) {
16
15
  if (value === undefined) return {};
17
- if (!isPlainObject(value)) {
18
- throw new TypeError(`${operation} ${label} must be a plain object.`);
19
- }
20
- validateKeys(value, keys, `${operation} ${label}`);
21
- return value;
16
+ const context = `${operation} ${label}`;
17
+ return validateOptionObject(value, keys, context, {
18
+ plainObjectMessage: `${context} must be a plain object.`
19
+ });
22
20
  }
23
21
 
24
22
  export function resolveGradientPosition(value, label, operation = "createGradientPlot") {
@@ -1,12 +1,12 @@
1
1
  import { resolveContinuousPalette } from "../../grammar/palettes.js";
2
+ import { inverseLerp } from "../../grammar/numeric.js";
2
3
  import { mapScaleConsumerValues } from "../../materialization/scales/map.js";
3
4
 
4
5
  function parseHex(value) {
5
6
  const match = value.match(/^#([0-9a-f]{6})$/i);
6
7
  if (!match) throw new Error(`Gradient plot requires a six-digit hex color, received "${value}".`);
7
- return [0, 2, 4].map(offset =>
8
- Number.parseInt(match[1].slice(offset, offset + 2), 16)
9
- );
8
+ const color = Number.parseInt(match[1], 16);
9
+ return [color >> 16, color >> 8 & 255, color & 255];
10
10
  }
11
11
 
12
12
  function interpolate(left, right, amount) {
@@ -23,8 +23,11 @@ function paletteColor(colors, amount) {
23
23
  const bounded = Math.max(0, Math.min(1, amount));
24
24
  const scaled = bounded * (colors.length - 1);
25
25
  const left = Math.floor(scaled);
26
- const right = Math.min(colors.length - 1, left + 1);
27
- return interpolate(parseHex(colors[left]), parseHex(colors[right]), scaled - left);
26
+ return interpolate(
27
+ parseHex(colors[left]),
28
+ parseHex(colors[Math.min(colors.length - 1, left + 1)]),
29
+ scaled - left
30
+ );
28
31
  }
29
32
 
30
33
  function categoricalRamp(base) {
@@ -75,7 +78,7 @@ export function createGradientPlotPaint(profile, {
75
78
  const stops = values.map((_, index) => {
76
79
  const fraction = intensities[index] / maximum;
77
80
  const offset = Number(
78
- ((mapped[index] - startPosition) / (endPosition - startPosition)).toFixed(12)
81
+ inverseLerp(mapped[index], startPosition, endPosition).toFixed(12)
79
82
  );
80
83
  return {
81
84
  offset: Object.is(offset, -0) ? 0 : offset,