ggaction 0.0.18 → 0.0.20

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 (164) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +6 -2
  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 +1 -1
  7. package/src/actions/canvas/fitting.js +6 -2
  8. package/src/actions/charts/heatmap.js +4 -3
  9. package/src/actions/data/derived.js +11 -1
  10. package/src/actions/data/edit.js +2 -0
  11. package/src/actions/data/index.js +4 -0
  12. package/src/actions/data/regression.js +24 -16
  13. package/src/actions/data/regressionStatistics.js +73 -0
  14. package/src/actions/data/shared.js +5 -2
  15. package/src/actions/distributions/revision.js +24 -18
  16. package/src/actions/encodings/color/continuous.js +7 -1
  17. package/src/actions/encodings/remove.js +11 -6
  18. package/src/actions/encodings/shared.js +26 -24
  19. package/src/actions/facets/actions.js +18 -7
  20. package/src/actions/facets/guides.js +2 -1
  21. package/src/actions/guides/axes/axes.js +30 -11
  22. package/src/actions/guides/axes/axis.js +58 -8
  23. package/src/actions/guides/axes/components.js +13 -13
  24. package/src/actions/guides/axes/edit.js +36 -24
  25. package/src/actions/guides/axes/labels.js +37 -27
  26. package/src/actions/guides/axes/lines.js +22 -20
  27. package/src/actions/guides/axes/remove.js +10 -6
  28. package/src/actions/guides/axes/tickGroups.js +8 -7
  29. package/src/actions/guides/axes/ticks.js +31 -22
  30. package/src/actions/guides/axes/titles.js +53 -60
  31. package/src/actions/guides/facadeAxes.js +33 -24
  32. package/src/actions/guides/facadeLegend.js +3 -3
  33. package/src/actions/guides/grids/grid.js +1 -1
  34. package/src/actions/guides/grids/resolve.js +48 -51
  35. package/src/actions/guides/legends/blocks.js +43 -19
  36. package/src/actions/guides/legends/categorical/actions.js +32 -9
  37. package/src/actions/guides/legends/categorical/components.js +3 -10
  38. package/src/actions/guides/legends/categorical/index.js +2 -0
  39. package/src/actions/guides/legends/categorical/layout.js +9 -13
  40. package/src/actions/guides/legends/categorical/options.js +8 -2
  41. package/src/actions/guides/legends/categorical/recipes.js +7 -3
  42. package/src/actions/guides/legends/categorical/resolve.js +9 -2
  43. package/src/actions/guides/legends/categorical/symbols.js +22 -8
  44. package/src/actions/guides/legends/colorSize.js +110 -0
  45. package/src/actions/guides/legends/combine.js +37 -0
  46. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -1
  47. package/src/actions/guides/legends/continuous/common.js +1 -1
  48. package/src/actions/guides/legends/continuous/gradient.js +27 -13
  49. package/src/actions/guides/legends/continuous/opacity.js +16 -11
  50. package/src/actions/guides/legends/creation.js +2 -0
  51. package/src/actions/guides/legends/edit.js +1 -1
  52. package/src/actions/guides/legends/focused.js +6 -5
  53. package/src/actions/guides/legends/lane.js +1 -1
  54. package/src/actions/guides/legends/lifecycle.js +2 -1
  55. package/src/actions/guides/legends/remove.js +6 -3
  56. package/src/actions/guides/legends/size.js +16 -6
  57. package/src/actions/guides/legends/strokeWidth.js +2 -0
  58. package/src/actions/guides/legends/target.js +2 -79
  59. package/src/actions/guides/legends/transition.js +24 -5
  60. package/src/actions/guides/tickValues.js +8 -5
  61. package/src/actions/marks/area/materialize.js +1 -190
  62. package/src/actions/marks/bar/edit.js +23 -12
  63. package/src/actions/marks/bar/materialize.js +6 -1
  64. package/src/actions/marks/point/materialize.js +5 -1
  65. package/src/actions/marks/remove.js +10 -10
  66. package/src/actions/primitives/createGraphics.js +3 -2
  67. package/src/actions/primitives/editGraphics.js +15 -11
  68. package/src/actions/primitives/graphicProperties.js +1 -6
  69. package/src/actions/primitives/semanticAction.js +27 -1
  70. package/src/actions/primitives/semanticValidation/guide.js +11 -1
  71. package/src/actions/primitives/semanticValidation/layer.js +4 -0
  72. package/src/actions/regression/edit.js +15 -3
  73. package/src/actions/scales/channels.js +2 -2
  74. package/src/actions/scales/consumers/common.js +7 -1
  75. package/src/actions/scales/create.js +3 -3
  76. package/src/actions/scales/definitions.js +1 -1
  77. package/src/actions/scales/edit.js +1 -1
  78. package/src/actions/scales/editPolicy.js +3 -0
  79. package/src/actions/scales/patch.js +1 -1
  80. package/src/actions/scales/quantitativeColor.js +8 -3
  81. package/src/actions/theme/reconcile.js +50 -23
  82. package/src/core/compositionState.js +2 -2
  83. package/src/core/graphicProperties.js +1 -0
  84. package/src/core/programState.js +4 -0
  85. package/src/core/resourceReferences.js +2 -2
  86. package/src/core/validation.js +9 -0
  87. package/src/core/vocabulary.js +3 -0
  88. package/src/grammar/aggregate.js +3 -2
  89. package/src/grammar/areaSeries.js +1 -415
  90. package/src/grammar/axisTextPlacement.js +1 -0
  91. package/src/grammar/colorSizeLegend.js +39 -0
  92. package/src/grammar/computed.js +23 -1
  93. package/src/grammar/datasetSchema.js +10 -2
  94. package/src/grammar/displayLabels.js +8 -0
  95. package/src/grammar/facets/guides.js +54 -31
  96. package/src/grammar/facets/legendIntent.js +65 -0
  97. package/src/grammar/guideDash.js +1 -0
  98. package/src/grammar/interval.js +22 -6
  99. package/src/grammar/legendBlocks.js +1 -0
  100. package/src/grammar/legendBorder.js +9 -0
  101. package/src/grammar/legendClip.js +19 -0
  102. package/src/grammar/legendCombinations.js +45 -0
  103. package/src/grammar/legendGradient.js +9 -0
  104. package/src/grammar/legendGroups.js +1 -1
  105. package/src/grammar/legendOpacity.js +6 -0
  106. package/src/grammar/pathSeries.js +2 -2
  107. package/src/grammar/pointShapes.js +16 -2
  108. package/src/grammar/regression/derive.js +9 -1
  109. package/src/grammar/regression/models.js +60 -0
  110. package/src/grammar/regression/parameters.js +9 -1
  111. package/src/grammar/regression/statistics.js +64 -0
  112. package/src/grammar/scales/color.js +1 -1
  113. package/src/grammar/scales/colorConsumers.js +6 -3
  114. package/src/grammar/scales/definition.js +1 -1
  115. package/src/grammar/scales/types.js +1 -1
  116. package/src/grammar/schemas/concreteGraphic.js +3 -0
  117. package/src/grammar/schemas/graphic.js +3 -0
  118. package/src/grammar/schemas/graphicBounds.js +31 -7
  119. package/src/grammar/schemas/semanticPath.js +17 -2
  120. package/src/grammar/statistics/confidenceInterval.js +1 -101
  121. package/src/grammar/summary.js +1 -320
  122. package/src/grammar/timeUnit.js +6 -4
  123. package/src/grammar/transformTopology.js +1 -0
  124. package/src/grammar/transforms.js +11 -1
  125. package/src/inspectionDescriptors.js +1 -1
  126. package/src/layout/guideCollisions.js +9 -1
  127. package/src/layout/legendGroup.js +1 -1
  128. package/src/materialization/compositionSnapshot.js +2 -7
  129. package/src/materialization/coordinateBounds.js +17 -4
  130. package/src/materialization/facetGuides/edit.js +30 -0
  131. package/src/materialization/facetGuides/layout.js +1 -1
  132. package/src/materialization/facetGuides/owner.js +125 -0
  133. package/src/materialization/facetGuides/ownerLayout.js +71 -0
  134. package/src/materialization/facetGuides/placement.js +52 -6
  135. package/src/materialization/facetGuides/preparation.js +7 -0
  136. package/src/materialization/facets.js +1 -1
  137. package/src/materialization/graphicHierarchy.js +5 -1
  138. package/src/materialization/graphicTranslation.js +73 -0
  139. package/src/materialization/guides/layout.js +1 -1
  140. package/src/materialization/guides/resources.js +1 -1
  141. package/src/materialization/legendBorder.js +28 -0
  142. package/src/materialization/legendClip.js +76 -0
  143. package/src/materialization/legendGroups.js +1 -1
  144. package/src/materialization/legends.js +1 -1
  145. package/src/materialization/marks/grouping.js +1 -1
  146. package/src/materialization/scaleGuideDependencies.js +13 -14
  147. package/src/materialization/scales/map.js +3 -0
  148. package/src/materialization/scales/resolve.js +2 -0
  149. package/src/materialization/typography.js +10 -2
  150. package/src/persistence/validation.js +174 -1
  151. package/src/persistence.js +1 -1
  152. package/src/renderers/canvas/index.js +20 -10
  153. package/src/renderers/canvas/isolated.js +166 -0
  154. package/src/renderers/canvas/limits.js +3 -0
  155. package/src/renderers/canvas/rect.js +1 -1
  156. package/src/renderers/pdf.js +2 -2
  157. package/src/renderers/png.js +2 -2
  158. package/src/renderers/svg.js +10 -3
  159. package/src/selectors/axes.js +1 -0
  160. package/src/selectors/graphicCapacity.js +38 -0
  161. package/src/version.js +1 -1
  162. package/types/basic.d.ts +2 -0
  163. package/types/index.d.ts +7 -0
  164. package/types/program.d.ts +220 -64
@@ -1,190 +1 @@
1
- import {
2
- deriveCenteredAreaSeries,
3
- deriveAreaSeries,
4
- deriveDensityAreaSeries,
5
- layoutDensityAreaSeries
6
- } from "../../../grammar/areaSeries.js";
7
- import {
8
- mapContinuousScaleValues,
9
- mapOrdinalPositionValues,
10
- mapOrdinalValues
11
- } from "../../../grammar/scales/index.js";
12
- import { mapScaleConsumerValues } from
13
- "../../../materialization/scales/map.js";
14
- import { buildAreaCurvePathCommands } from
15
- "../../../grammar/curveCommands.js";
16
- import { buildLinearPathCommands } from
17
- "../../../grammar/pathCommands.js";
18
- import { buildCategoricalDensityPaths } from
19
- "../../../grammar/categoricalDensity.js";
20
- import { derivePathSeriesFieldValues } from "../../../grammar/pathSeries.js";
21
-
22
- function resolveAreaPaths({
23
- derived,
24
- densityTransform,
25
- categoricalDensity,
26
- xScale,
27
- yScale,
28
- densityScale,
29
- layout,
30
- curve
31
- }) {
32
- if (categoricalDensity) {
33
- return buildCategoricalDensityPaths(derived, {
34
- categoryScale: densityTransform.placement.channel === "x" ? xScale : yScale,
35
- valueScale: densityTransform.placement.channel === "x" ? yScale : xScale,
36
- curve
37
- });
38
- }
39
- return derived.series.map(series => {
40
- if (densityTransform === undefined && derived.orientation === "horizontal") {
41
- const y = (["point", "band"].includes(yScale.type) ? mapOrdinalPositionValues : mapContinuousScaleValues)(
42
- series.values.map(value => value.y), yScale
43
- );
44
- const lower = mapContinuousScaleValues(
45
- series.values.map(value => value.x),
46
- xScale
47
- );
48
- const upper = mapContinuousScaleValues(
49
- series.values.map(value => value.x2),
50
- xScale
51
- );
52
- return buildAreaCurvePathCommands(
53
- y.map((value, index) => ({ x: lower[index], y: value })),
54
- y.map((value, index) => ({ x: upper[index], y: value })),
55
- curve,
56
- { independentAxis: "y" }
57
- );
58
- }
59
- if (densityTransform !== undefined && derived.mode === "x-density") {
60
- const y = (["point", "band"].includes(yScale.type) ? mapOrdinalPositionValues : mapContinuousScaleValues)(
61
- series.values.map(value => value.y), yScale
62
- );
63
- const upper = mapContinuousScaleValues(
64
- series.values.map(value => value.x),
65
- xScale
66
- );
67
- const baseline = mapContinuousScaleValues([0], densityScale)[0];
68
- if (curve === "linear") {
69
- return buildLinearPathCommands([
70
- { x: baseline, y: y[0] },
71
- ...y.map((value, index) => ({ x: upper[index], y: value })),
72
- { x: baseline, y: y.at(-1) }
73
- ], { close: true });
74
- }
75
- return buildAreaCurvePathCommands(
76
- y.map(value => ({ x: baseline, y: value })),
77
- y.map((value, index) => ({ x: upper[index], y: value })),
78
- curve,
79
- { independentAxis: "y" }
80
- );
81
- }
82
- const x = (["point", "band"].includes(xScale.type) ? mapOrdinalPositionValues : mapContinuousScaleValues)(
83
- series.values.map(value => value.x),
84
- xScale
85
- );
86
- const lowerValues = densityTransform === undefined
87
- ? derived.mode === "y-center"
88
- ? series.values.map(value => value.lower)
89
- : series.values.map(value => value.y)
90
- : derived.mode === "y-density"
91
- ? series.values.map(value => value.lower)
92
- : series.values.map(value => value.y);
93
- const lower = mapContinuousScaleValues(lowerValues, yScale);
94
- const upperValues = densityTransform === undefined
95
- ? derived.mode === "y-center"
96
- ? series.values.map(value => value.upper)
97
- : series.values.map(value => value.y2)
98
- : series.values.map(value => value.upper);
99
- const upper = mapContinuousScaleValues(upperValues, yScale);
100
- if (densityTransform !== undefined && layout === "overlay") {
101
- const baseline = mapContinuousScaleValues([0], yScale)[0];
102
- if (curve === "linear") {
103
- return buildLinearPathCommands([
104
- { x: x[0], y: baseline },
105
- ...x.map((value, index) => ({ x: value, y: upper[index] })),
106
- { x: x.at(-1), y: baseline }
107
- ], { close: true });
108
- }
109
- return buildAreaCurvePathCommands(
110
- x.map(value => ({ x: value, y: baseline })),
111
- x.map((value, index) => ({ x: value, y: upper[index] })),
112
- curve
113
- );
114
- }
115
- return buildAreaCurvePathCommands(
116
- x.map((value, index) => ({ x: value, y: lower[index] })),
117
- x.map((value, index) => ({ x: value, y: upper[index] })),
118
- curve
119
- );
120
- });
121
- }
122
-
123
- export function resolveAreaMaterialization({
124
- rows,
125
- layer,
126
- densityTransform,
127
- resolvedScales,
128
- config
129
- }) {
130
- const xScale = resolvedScales[layer.encoding.x.scale];
131
- const yScale = resolvedScales[layer.encoding.y.scale];
132
- const centered = densityTransform === undefined &&
133
- layer.encoding?.y?.stack === "center";
134
- const rawDerived = densityTransform === undefined
135
- ? centered
136
- ? deriveCenteredAreaSeries(rows, layer, { xDomain: xScale.domain })
137
- : deriveAreaSeries(rows, layer, { xDomain: xScale.domain, yDomain: yScale.domain })
138
- : deriveDensityAreaSeries(rows, layer, densityTransform);
139
- const colorEncoding = layer.encoding?.color;
140
- const layout = layer.layout?.mode ?? colorEncoding?.layout ?? "overlay";
141
- const categoricalDensity = densityTransform?.placement?.type === "category";
142
- const derived = densityTransform === undefined || categoricalDensity
143
- ? rawDerived
144
- : layoutDensityAreaSeries(rawDerived, layout);
145
- const densityScale = densityTransform === undefined || categoricalDensity
146
- ? undefined
147
- : derived.mode === "y-density" ? yScale : xScale;
148
- if (
149
- densityScale !== undefined &&
150
- (densityScale.domain[0] > 0 || densityScale.domain[1] < 0)
151
- ) {
152
- throw new Error(`Density area mark "${layer.id}" requires a scale domain containing zero.`);
153
- }
154
- const paths = resolveAreaPaths({
155
- derived,
156
- densityTransform,
157
- categoricalDensity,
158
- xScale,
159
- yScale,
160
- densityScale,
161
- layout,
162
- curve: config.curve ?? "linear"
163
- });
164
- const fills = config.errorBand?.fill !== undefined
165
- ? paths.map(() => config.errorBand.fill)
166
- : colorEncoding?.scale === undefined
167
- ? paths.map(() => config.fill)
168
- : mapOrdinalValues(
169
- densityTransform !== undefined || centered
170
- ? derived.series.map(series => series.key[colorEncoding.field])
171
- : derivePathSeriesFieldValues(rows, derived.series, colorEncoding.field, "color"),
172
- resolvedScales[colorEncoding.scale].domain,
173
- resolvedScales[colorEncoding.scale].range
174
- );
175
- const strokeEncoding = layer.encoding?.stroke;
176
- const strokes = strokeEncoding?.scale === undefined
177
- ? undefined
178
- : mapScaleConsumerValues(
179
- derivePathSeriesFieldValues(
180
- rows,
181
- derived.series,
182
- strokeEncoding.field,
183
- "stroke",
184
- strokeEncoding
185
- ),
186
- resolvedScales[strokeEncoding.scale],
187
- "stroke"
188
- );
189
- return { paths, fills, strokes };
190
- }
1
+ import{deriveCenteredAreaSeries as P,deriveAreaSeries as V,deriveDensityAreaSeries as w,layoutDensityAreaSeries as z}from"../../../grammar/areaSeries.js";import{mapContinuousScaleValues as c,mapOrdinalPositionValues as D}from"../../../grammar/scales/index.js";import{mapScaleConsumerValues as b}from"../../../materialization/scales/map.js";import{buildAreaCurvePathCommands as A}from"../../../grammar/curveCommands.js";import{buildLinearPathCommands as k}from"../../../grammar/pathCommands.js";import{buildCategoricalDensityPaths as E}from"../../../grammar/categoricalDensity.js";import{derivePathSeriesFieldValues as C}from"../../../grammar/pathSeries.js";function q({derived:d,densityTransform:a,categoricalDensity:m,xScale:l,yScale:u,densityScale:x,layout:v,curve:r}){return m?E(d,{categoryScale:a.placement.channel==="x"?l:u,valueScale:a.placement.channel==="x"?u:l,curve:r}):d.series.map(o=>{if(a===void 0&&d.orientation==="horizontal"){const e=(["point","band"].includes(u.type)?D:c)(o.values.map(n=>n.y),u),t=c(o.values.map(n=>n.x),l),p=c(o.values.map(n=>n.x2),l);return A(e.map((n,s)=>({x:t[s],y:n})),e.map((n,s)=>({x:p[s],y:n})),r,{independentAxis:"y"})}if(a!==void 0&&d.mode==="x-density"){const e=(["point","band"].includes(u.type)?D:c)(o.values.map(n=>n.y),u),t=c(o.values.map(n=>n.x),l),p=c([0],x)[0];return r==="linear"?k([{x:p,y:e[0]},...e.map((n,s)=>({x:t[s],y:n})),{x:p,y:e.at(-1)}],{close:!0}):A(e.map(n=>({x:p,y:n})),e.map((n,s)=>({x:t[s],y:n})),r,{independentAxis:"y"})}const i=(["point","band"].includes(l.type)?D:c)(o.values.map(e=>e.x),l),g=a===void 0?d.mode==="y-center"?o.values.map(e=>e.lower):o.values.map(e=>e.y):d.mode==="y-density"?o.values.map(e=>e.lower):o.values.map(e=>e.y),h=c(g,u),y=a===void 0?d.mode==="y-center"?o.values.map(e=>e.upper):o.values.map(e=>e.y2):o.values.map(e=>e.upper),f=c(y,u);if(a!==void 0&&v==="overlay"){const e=c([0],u)[0];return r==="linear"?k([{x:i[0],y:e},...i.map((t,p)=>({x:t,y:f[p]})),{x:i.at(-1),y:e}],{close:!0}):A(i.map(t=>({x:t,y:e})),i.map((t,p)=>({x:t,y:f[p]})),r)}return A(i.map((e,t)=>({x:e,y:h[t]})),i.map((e,t)=>({x:e,y:f[t]})),r)})}function G({rows:d,layer:a,densityTransform:m,resolvedScales:l,config:u}){const x=l[a.encoding.x.scale],v=l[a.encoding.y.scale],r=m===void 0&&a.encoding?.y?.stack==="center",o=m===void 0?r?P(d,a,{xDomain:x.domain}):V(d,a,{xDomain:x.domain,yDomain:v.domain}):w(d,a,m),i=a.encoding?.color,g=a.layout?.mode??i?.layout??"overlay",h=m?.placement?.type==="category",y=m===void 0||h?o:z(o,g),f=m===void 0||h?void 0:y.mode==="y-density"?v:x;if(f!==void 0&&(f.domain[0]>0||f.domain[1]<0))throw new Error(`Density area mark "${a.id}" requires a scale domain containing zero.`);const e=q({derived:y,densityTransform:m,categoricalDensity:h,xScale:x,yScale:v,densityScale:f,layout:g,curve:u.curve??"linear"}),t=u.errorBand?.fill!==void 0?e.map(()=>u.errorBand.fill):i?.scale===void 0?e.map(()=>u.fill):b(i.fieldType!=="quantitative"&&(m!==void 0||r)?y.series.map(s=>s.key[i.field]):C(d,y.series,i.field,"color",i),l[i.scale],"color"),p=a.encoding?.stroke,n=p?.scale===void 0?void 0:b(C(d,y.series,p.field,"stroke",p),l[p.scale],"stroke");return{paths:e,fills:t,strokes:n}}export{G as resolveAreaMaterialization};
@@ -18,20 +18,20 @@ import { validateFillPaint } from "../../../grammar/paint.js";
18
18
  import { validateItemMissing } from "../../../grammar/itemMissing.js";
19
19
 
20
20
  const EDIT_OPTIONS = Object.freeze([
21
- "target", "missing", "fill", "opacity", "stroke", "strokeWidth", ...RECT_RADIUS_PROPERTIES,
21
+ "target", "missing", "blend", "fill", "opacity", "stroke", "strokeWidth", ...RECT_RADIUS_PROPERTIES,
22
22
  ...STROKE_STYLE_PROPERTIES
23
23
  ]);
24
24
 
25
25
  export const editBarMark = /* @__PURE__ */ action(
26
26
  {
27
27
  op: "editBarMark",
28
- description: "Edit whole-bar fill, opacity, and outline appearance."
28
+ description: "Edit whole-bar fill, opacity, outline, and compositing appearance."
29
29
  },
30
30
  function (args = {}) {
31
31
  validateMarkOptions(args, EDIT_OPTIONS, "editBarMark");
32
32
  const styleDetails = requestedRectStyleDetails(args, "editBarMark");
33
33
  const changes = [
34
- "missing", "fill", "opacity", "stroke", "strokeWidth", ...RECT_RADIUS_PROPERTIES,
34
+ "missing", "blend", "fill", "opacity", "stroke", "strokeWidth", ...RECT_RADIUS_PROPERTIES,
35
35
  ...STROKE_STYLE_PROPERTIES
36
36
  ];
37
37
  if (!changes.some(key => Object.hasOwn(args, key))) {
@@ -66,13 +66,12 @@ export const editBarMark = /* @__PURE__ */ action(
66
66
  );
67
67
  }
68
68
 
69
- const semantic = Object.hasOwn(args, "missing")
70
- ? this.editSemantic({
71
- property: `layer[${layer.id}].mark.missing`,
72
- value: validateItemMissing(args.missing, "Bar missing")
73
- })
74
- : this;
75
- const config = { ...semantic.markConfigs[layer.id] };
69
+ if (Object.hasOwn(args, "blend") && !["normal", "difference"].includes(args.blend)) {
70
+ throw new Error("Bar blend must be normal or difference.");
71
+ }
72
+ const missing = Object.hasOwn(args, "missing")
73
+ ? validateItemMissing(args.missing, "Bar missing") : undefined;
74
+ const config = { ...this.markConfigs[layer.id] };
76
75
  const appearance = { ...config.barAppearance };
77
76
  Object.assign(appearance, styleDetails);
78
77
  if (Object.hasOwn(args, "fill")) {
@@ -106,10 +105,22 @@ export const editBarMark = /* @__PURE__ */ action(
106
105
  );
107
106
  }
108
107
 
109
- const next = semantic._withMarkConfig(layer.id, {
108
+ let semantic = missing === undefined ? this : this.editSemantic({
109
+ property: `layer[${layer.id}].mark.missing`, value: missing
110
+ });
111
+ if (Object.hasOwn(args, "blend")) {
112
+ if (args.blend === "difference") {
113
+ semantic = semantic.editSemantic({property: `layer[${layer.id}].mark.blend`, value: "difference"});
114
+ } else if (layer.mark.blend !== undefined) {
115
+ semantic = semantic.editSemantic({property: `layer[${layer.id}].mark.blend`, remove: true});
116
+ }
117
+ }
118
+ const editsAppearance = changes.some(key => key !== "missing" && key !== "blend" &&
119
+ Object.hasOwn(args, key));
120
+ const next = editsAppearance ? semantic._withMarkConfig(layer.id, {
110
121
  ...config,
111
122
  barAppearance: appearance
112
- });
123
+ }) : semantic;
113
124
  const materialized = canMaterializeBar(next, layer)
114
125
  ? next.rematerializeBarMark({ id: layer.id })
115
126
  : next;
@@ -1,3 +1,4 @@
1
+ import { findLayer } from "../../../selectors/layers.js";
1
2
  import { editGraphicProperties } from "../../primitives/graphicProperties.js";
2
3
  import { action } from "../../../core/action.js";
3
4
  import { validateUserId } from "../../../core/identifiers.js";
@@ -20,9 +21,13 @@ const REMATERIALIZE_OPTIONS = Object.freeze(["id", "scales"]);
20
21
 
21
22
  function editRectangles(program, id, rectangles) {
22
23
  const appearance = program.markConfigs[id]?.barAppearance ?? {};
24
+ const blend = findLayer(program, id)?.mark?.blend;
25
+ if (blend !== undefined) rectangles = rectangles.map(rect => ({...rect, blend}));
23
26
  const hasShapeStyle = RECT_STYLE_PROPERTIES
24
27
  .some(property => Object.hasOwn(appearance, property));
25
- if (hasShapeStyle) {
28
+ const previousBlend = program.graphicSpec.objects[id]?.items?.some(item =>
29
+ item.properties?.blend !== undefined);
30
+ if (hasShapeStyle || blend !== undefined || previousBlend) {
26
31
  const radius = appearance;
27
32
  return replaceGraphicItems(
28
33
  program,
@@ -1,3 +1,4 @@
1
+ import { validateGraphicItemLimit } from "../../../core/validation.js";
1
2
  import { editGraphicProperties } from "../../primitives/graphicProperties.js";
2
3
  import { action } from "../../../core/action.js";
3
4
  import { validateUserId } from "../../../core/identifiers.js";
@@ -29,6 +30,8 @@ import {
29
30
  } from "../../../theme/defaults.js";
30
31
  import { findDataset } from "../../../selectors/datasets.js";
31
32
  import { findLayer } from "../../../selectors/layers.js";
33
+ import { resolveGraphicItemCapacity } from "../../../selectors/graphicCapacity.js";
34
+ import { getMarkGraphicTypes } from "../../../core/vocabulary.js";
32
35
  import { canMaterializePoint } from "../../../materialization/marks/capabilities.js";
33
36
  import { rematerializeHighlightBaseline } from "../lifecycle.js";
34
37
  import {
@@ -335,13 +338,14 @@ export const rematerializePointMark = /* @__PURE__ */ action(
335
338
  if (layer?.mark?.type !== "point") {
336
339
  throw new Error(`Unknown point mark "${id}".`);
337
340
  }
338
- if (!["circle", "rect", "path", "collection"].includes(graphic?.type)) {
341
+ if (!getMarkGraphicTypes("point").includes(graphic?.type)) {
339
342
  throw new Error(`Point mark "${id}" requires point graphics.`);
340
343
  }
341
344
  const sourceDataset = findDataset(resolved, layer.data);
342
345
  if (sourceDataset === undefined) {
343
346
  throw new Error(`Point mark "${id}" requires an existing dataset.`);
344
347
  }
348
+ validateGraphicItemLimit(sourceDataset.values.length, resolveGraphicItemCapacity(resolved, { id, label: "Point mark item count" }));
345
349
  const dataset = applyItemMissingPolicy(layer, sourceDataset);
346
350
 
347
351
  if (graphic.items.length === 0 && !canMaterializePoint(resolved, layer)) {
@@ -1,3 +1,4 @@
1
+ import { cartesianAxisOwners, axisTargetArgs } from "../../selectors/axes.js";
1
2
  import { ownedChildren, ownership } from "../../selectors/markOwners.js";
2
3
  import { isSourceOwnedText } from "../../grammar/text.js";
3
4
  import { closedAction } from "../../core/action.js";
@@ -183,16 +184,15 @@ function cleanupPositionGuides(program, scales) {
183
184
  let next = program;
184
185
  for (const channel of POSITION_CHANNELS) {
185
186
  const definition = getPositionChannelDefinition(channel);
186
- const axis = next.semanticSpec.guides.axis?.[channel];
187
- if (
188
- axis !== undefined &&
189
- scales[channel].has(axis.scale) &&
190
- !hasScaleConsumer(next, channel, axis.scale)
191
- ) {
192
- next = next[REMOVE_AXIS[channel]]({
193
- scale: axis.scale,
194
- ...(axis.coordinate === undefined ? {} : { coordinate: axis.coordinate })
195
- });
187
+ const owners = ["x", "y"].includes(channel)
188
+ ? cartesianAxisOwners(next).filter(owner => owner.channel === channel)
189
+ : [{ id: channel, channel, semantic: next.semanticSpec.guides.axis?.[channel] }];
190
+ for (const owner of owners) {
191
+ const axis = owner.semantic;
192
+ if (axis !== undefined && scales[channel].has(axis.scale) && !hasScaleConsumer(next, channel, axis.scale)) {
193
+ next = next[REMOVE_AXIS[channel]]({ ...axisTargetArgs(owner.id, channel), scale: axis.scale,
194
+ ...(axis.coordinate === undefined ? {} : { coordinate: axis.coordinate }) });
195
+ }
196
196
  }
197
197
  const grid = next.semanticSpec.guides.grid?.[definition.gridDirection];
198
198
  if (
@@ -1,7 +1,8 @@
1
+ import { validateGraphicItemLimit } from "../../core/validation.js";
1
2
  import { closedAction } from "../../core/action.js";
2
3
  import { validateUserId } from "../../core/identifiers.js";
3
4
  import { freezeOwned } from "../../core/immutable.js";
4
- import { validateGeneratedItemLimit } from "../../core/validation.js";
5
+ import { resolveGraphicItemCapacity } from "../../selectors/graphicCapacity.js";
5
6
  import {
6
7
  isDrawableGraphicType,
7
8
  isGraphicContainerType,
@@ -118,7 +119,7 @@ const createGraphics = /* @__PURE__ */ closedAction(
118
119
  throw new TypeError("createGraphics length must be a non-negative integer.");
119
120
  }
120
121
  if (length !== undefined) {
121
- validateGeneratedItemLimit(length, "createGraphics length");
122
+ validateGraphicItemLimit(length, resolveGraphicItemCapacity(this, { id, label: "createGraphics length", type: validatedType }));
122
123
  }
123
124
  if (validatedType === "collection" && length !== undefined) {
124
125
  throw new Error("Heterogeneous collections use editGraphics items instead of length.");
@@ -1,6 +1,7 @@
1
+ import { validateGraphicItemLimit } from "../../core/validation.js";
1
2
  import { closedAction } from "../../core/action.js";
2
3
  import { cloneAndFreeze, freezeOwned, isPlainObject } from "../../core/immutable.js";
3
- import { validateGeneratedItemLimit } from "../../core/validation.js";
4
+ import { resolveGraphicItemCapacity } from "../../selectors/graphicCapacity.js";
4
5
  import {
5
6
  isPrimitiveDrawableGraphicType,
6
7
  isStructuralGraphicType,
@@ -43,14 +44,14 @@ function validateCollectionItem(item, ownerId, index) {
43
44
  });
44
45
  }
45
46
 
46
- function replaceCollectionItems(value, id) {
47
+ function replaceCollectionItems(value, id, program) {
47
48
  if (!Array.isArray(value)) throw new TypeError("collection.items must be an array.");
48
- validateGeneratedItemLimit(value.length, "collection.items length");
49
+ validateGraphicItemLimit(value.length, resolveGraphicItemCapacity(program, { id, label: "collection.items length" }));
49
50
  return freezeOwned(value.map((item, index) => validateCollectionItem(item, id, index)));
50
51
  }
51
52
 
52
- function replaceDrawableItems(value, id, type) {
53
- const items = replaceCollectionItems(value, id);
53
+ function replaceDrawableItems(value, id, type, program) {
54
+ const items = replaceCollectionItems(value, id, program);
54
55
  if (!items.every(item => item.type === type)) {
55
56
  return freezeOwned({ type: "collection", items });
56
57
  }
@@ -102,15 +103,15 @@ function editGeneratedItem(owner, itemIndex, property, value) {
102
103
  return freezeOwned({ ...owner, items: freezeOwned(items) });
103
104
  }
104
105
 
105
- function editItems(owner, property, value, id) {
106
+ function editItems(owner, property, value, id, program) {
106
107
  if (owner.type === "collection" && property === "items") {
107
- return freezeOwned({ ...owner, items: replaceCollectionItems(value, id) });
108
+ return freezeOwned({ ...owner, items: replaceCollectionItems(value, id, program) });
108
109
  }
109
110
  if (property === "length") {
110
111
  if (!Number.isInteger(value) || value < 0) {
111
112
  throw new TypeError("Graphic collection length must be a non-negative integer.");
112
113
  }
113
- validateGeneratedItemLimit(value, "Graphic collection length");
114
+ validateGraphicItemLimit(value, resolveGraphicItemCapacity(program, { id, label: "Graphic collection length" }));
114
115
  const items = owner.items.slice(0, value);
115
116
  for (let index = items.length; index < value; index += 1) {
116
117
  if (owner.type === "collection") {
@@ -189,6 +190,9 @@ const editGraphics = /* @__PURE__ */ closedAction(
189
190
 
190
191
  const ownerId = found.kind === "item" ? found.ownerId : found.id;
191
192
  const owner = found.kind === "item" ? found.owner : found.object;
193
+ if (owner.items !== undefined) validateGraphicItemLimit(owner.items.length, resolveGraphicItemCapacity(this, {
194
+ id: ownerId, label: "Graphic collection length"
195
+ }));
192
196
  const replacesDrawableItems =
193
197
  found.kind === "object" && property === "items" &&
194
198
  owner.items !== undefined && owner.type !== "collection";
@@ -199,7 +203,7 @@ const editGraphics = /* @__PURE__ */ closedAction(
199
203
  : owner.type, property);
200
204
  let updated;
201
205
  if (replacesDrawableItems) {
202
- updated = replaceDrawableItems(value, ownerId, owner.type);
206
+ updated = replaceDrawableItems(value, ownerId, owner.type, this);
203
207
  } else if (found.kind === "item") {
204
208
  if (validatedProperty === "length" || validatedProperty === "items") {
205
209
  throw new Error(`${validatedProperty} can only target an owning graphic.`);
@@ -212,12 +216,12 @@ const editGraphics = /* @__PURE__ */ closedAction(
212
216
  properties: setGraphicProperty(owner.properties, validatedProperty, value)
213
217
  });
214
218
  } else if (owner.items !== undefined) {
215
- updated = editItems(owner, validatedProperty, value, ownerId);
219
+ updated = editItems(owner, validatedProperty, value, ownerId, this);
216
220
  } else if (validatedProperty === "length") {
217
221
  if (!Number.isInteger(value) || value < 0) {
218
222
  throw new TypeError("Graphic collection length must be a non-negative integer.");
219
223
  }
220
- validateGeneratedItemLimit(value, "Graphic collection length");
224
+ validateGraphicItemLimit(value, resolveGraphicItemCapacity(this, { id: ownerId, label: "Graphic collection length" }));
221
225
  updated = freezeOwned({
222
226
  type: owner.type,
223
227
  items: freezeOwned(Array.from({ length: value }, (_, index) => freezeOwned({
@@ -1,6 +1 @@
1
- export function editGraphicProperties(program, target, properties) {
2
- for (const [property, value] of Object.entries(properties)) {
3
- program = program.editGraphics({ target, property, value });
4
- }
5
- return program;
6
- }
1
+ export { editGraphicProperties } from "../../core/graphicProperties.js";
@@ -57,7 +57,7 @@ function updateEntity(spec, parsed, value) {
57
57
  source.schema,
58
58
  value[0],
59
59
  undefined,
60
- { sourceId: source.id, ownerId: parsed.id }
60
+ { sourceId: source.id, ownerId: parsed.id, sourceTransform: source.transform?.[0] }
61
61
  ));
62
62
  }
63
63
  }
@@ -72,6 +72,7 @@ function updateEntity(spec, parsed, value) {
72
72
  const schema = source?.schema !== undefined && current.transform?.length === 1
73
73
  ? deriveTransformSchema(source.schema, current.transform[0], value, {
74
74
  sourceId: source.id,
75
+ sourceTransform: source.transform?.[0],
75
76
  ownerId: parsed.id
76
77
  })
77
78
  : inferDatasetSchema(value);
@@ -225,6 +226,31 @@ export function withoutPreviewLayerEncodings(program, { id, channels }) {
225
226
  return program._clone({ semanticSpec });
226
227
  }
227
228
 
229
+ // Composition materialization may retain this native two-scale guide meaning.
230
+ // Use the existing semantic primitives on a private unit adapter; this does not
231
+ // relax the public composition guard or accept arbitrary semantic paths.
232
+ export function synchronizeFacetColorSizeMeaning(program, meaning) {
233
+ if (program.compositionSpec?.type !== "facet") {
234
+ throw new Error("Color-size parent meaning requires a facet composition.");
235
+ }
236
+ if (meaning !== undefined && (!isPlainObject(meaning) ||
237
+ Object.keys(meaning).some(key => !["colorScale", "sizeScale", "title"].includes(key)))) {
238
+ throw new Error("Color-size parent meaning requires its native scale bindings and title.");
239
+ }
240
+ let unit = new program.constructor({ semanticSpec: program.semanticSpec, graphicSpec: program.graphicSpec,
241
+ resolvedScales: program.resolvedScales, materializationConfigs: program.materializationConfigs,
242
+ context: program.context, trace: program.trace, actionStack: program.actionStack,
243
+ actionSequence: program._actionSequence });
244
+ if (unit.semanticSpec.guides.legend?.colorSize !== undefined) {
245
+ unit = unit.editSemantic({ property: "guide.legend.colorSize", remove: true });
246
+ }
247
+ if (meaning !== undefined) for (const [property, value] of Object.entries(meaning)) {
248
+ unit = unit.editSemantic({ property: `guide.legend.colorSize.${property}`, value });
249
+ }
250
+ return program._clone({ semanticSpec: unit.semanticSpec, context: unit.context,
251
+ trace: unit.trace, actionStack: unit.actionStack, actionSequence: unit._actionSequence });
252
+ }
253
+
228
254
  export function createSemanticPrimitiveAction(validateSemanticValue, planNamedRemoval) {
229
255
  return closedAction(
230
256
  {
@@ -1,3 +1,4 @@
1
+ import {isSizeShapeChannels} from "../../../grammar/legendCombinations.js";
1
2
  import { normalizeLegendOrder } from "../../../grammar/categoryOrder.js";
2
3
  import { validateUserId } from "../../../core/identifiers.js";
3
4
  import { CATEGORICAL_LEGEND_CHANNELS } from "../../../core/vocabulary.js";
@@ -16,7 +17,7 @@ function validateLegend(property, value) {
16
17
  throw new Error(`Legend ${property} must not contain duplicates.`);
17
18
  }
18
19
  if (property === "channels") {
19
- if (!value.every(channel => CATEGORICAL_LEGEND_CHANNELS.includes(channel))) {
20
+ if (!isSizeShapeChannels(value) && !value.every(channel => CATEGORICAL_LEGEND_CHANNELS.includes(channel))) {
20
21
  throw new Error("Legend channels support only color, strokeDash, and shape.");
21
22
  }
22
23
  return;
@@ -26,6 +27,11 @@ function validateLegend(property, value) {
26
27
 
27
28
  export function validateGuideSemanticValue(_program, parsed, value) {
28
29
  const property = parsed.path.at(-1);
30
+ if (parsed.id === "legend.colorSize") {
31
+ if(property === "title") validateNonEmptySemanticString(value,"Legend title");
32
+ else validateUserId(value,"Legend scale id");
33
+ return;
34
+ }
29
35
  if (parsed.id === "axis.parallel" && property === "titles") {
30
36
  validateParallelAxisTitles(value);
31
37
  return;
@@ -44,6 +50,10 @@ export function validateGuideSemanticValue(_program, parsed, value) {
44
50
  throw new TypeError("Parallel axis scales must contain at least two ids.");
45
51
  }
46
52
  value.forEach(id => validateUserId(id, "Parallel axis scale id"));
53
+ } else if (parsed.id.startsWith("axis.") && property === "channel") {
54
+ if (!["x", "y"].includes(value)) throw new Error("Named Cartesian axis channel requires x or y.");
55
+ } else if (parsed.id.startsWith("axis.") && property === "position") {
56
+ if (!["top", "bottom", "left", "right"].includes(value)) throw new Error("Named Cartesian axis position requires a physical side.");
47
57
  } else if (property === "title") {
48
58
  validateNonEmptySemanticString(value, "Axis title");
49
59
  } else {
@@ -70,6 +70,10 @@ export function validateLayerSemanticValue(
70
70
  !["vertical", "horizontal"].includes(value))) {
71
71
  throw new Error("Bar orientation must be vertical or horizontal.");
72
72
  }
73
+ if (property === "mark.blend" &&
74
+ (findLayer(program, parsed.id)?.mark?.type !== "bar" || value !== "difference")) {
75
+ throw new Error("Bar blend must be difference; remove the optional intent for normal painting.");
76
+ }
73
77
  if (property === "derivedBindings.regression") {
74
78
  if (!value || typeof value !== "object" || Array.isArray(value) ||
75
79
  Object.keys(value).length !== 2 || value.mode !== "follow" ||
@@ -19,6 +19,7 @@ import { findLayer } from "../../selectors/layers.js";
19
19
  import { removeOwnedMark } from "../marks/remove.js";
20
20
  import { removeOwnedColorLegends } from "../guides/legends/remove.js";
21
21
  import { requireRegressionField } from "./resolve.js";
22
+ import { buildSourceRevisionPlan, applyRevisionPlan } from "../data/edit.js";
22
23
 
23
24
  const OPTIONS = Object.freeze([
24
25
  "target", "data", "x", "y", "groupBy", "method", "degree", "span", "robustIterations",
@@ -128,7 +129,7 @@ function resolveParameters(previous, args) {
128
129
  raw[key] = previous[key];
129
130
  }
130
131
  }
131
- } else if (method !== "loess" && previous.method !== "loess" && args.interval !== false) {
132
+ } else if (!["loess", "exp"].includes(method) && !["loess", "exp"].includes(previous.method) && args.interval !== false) {
132
133
  for (const key of ["confidenceMethod", "level", "interval"]) {
133
134
  if (
134
135
  !Object.hasOwn(raw, key) &&
@@ -295,6 +296,11 @@ export const editRegression = /* @__PURE__ */ closedAction(
295
296
  const changesParameters = JSON.stringify(parameters) !==
296
297
  JSON.stringify(current.parameters);
297
298
  const changesStatistics = changesProvenance || changesParameters;
299
+ const hasStatistics = this.semanticSpec.datasets.some(dataset =>
300
+ dataset.source === current.dataId && dataset.transform?.[0]?.type === "regressionStatistics");
301
+ if (hasStatistics && parameters.method !== "linear") {
302
+ throw new Error("Remove dependent regression statistics before changing the linear fit method.");
303
+ }
298
304
  const bandPatch = Object.hasOwn(args, "band")
299
305
  ? validateBandPatch(args.band)
300
306
  : undefined;
@@ -302,14 +308,16 @@ export const editRegression = /* @__PURE__ */ closedAction(
302
308
  ? validateLinePatch(args.line)
303
309
  : undefined;
304
310
  if ((parameters.method === "loess" || parameters.interval === false) && bandPatch !== undefined && bandPatch !== false) {
305
- throw new Error("LOESS regression does not support a band object.");
311
+ throw new Error(parameters.method === "loess"
312
+ ? "LOESS regression does not support a band object."
313
+ : "Regression interval false cannot include a band object.");
306
314
  }
307
315
 
308
316
  const hadBand = current.bandId !== undefined;
309
317
  const wantsBand = parameters.method !== "loess" && parameters.interval !== false &&
310
318
  (bandPatch === false
311
319
  ? false
312
- : hadBand || bandPatch !== undefined || current.parameters.method === "loess");
320
+ : hadBand || bandPatch !== undefined || ["loess", "exp"].includes(current.parameters.method));
313
321
  const revision = changesStatistics
314
322
  ? planDerivedDataRevision(this, {
315
323
  owner: owner.id,
@@ -321,6 +329,8 @@ export const editRegression = /* @__PURE__ */ closedAction(
321
329
  ]
322
330
  })
323
331
  : undefined;
332
+ const statisticsRevision = changesStatistics && hasStatistics
333
+ ? buildSourceRevisionPlan(this, current.dataId, revision.id) : undefined;
324
334
 
325
335
  const applyEdit = program => {
326
336
  let dataId = current.dataId;
@@ -412,6 +422,8 @@ export const editRegression = /* @__PURE__ */ closedAction(
412
422
  }
413
423
  });
414
424
  if (changesStatistics) {
425
+ if (statisticsRevision !== undefined) next = applyRevisionPlan(next, undefined,
426
+ statisticsRevision, { retain: new Set([current.dataId]) });
415
427
  next = next.releaseDerivedData(revision.release);
416
428
  }
417
429
  const binding = findLayer(next, owner.id)?.derivedBindings?.regression;
@@ -42,7 +42,7 @@ const DEFINITIONS = Object.freeze({
42
42
  channel: "color",
43
43
  options: Object.freeze([
44
44
  "type", "domain", "range", "emptyDomain", "clamp", "reverse", "palette",
45
- "interpolate", "midpoint", "base", "constant", "unknown"
45
+ "interpolate", "whitePoint", "gamma", "midpoint", "base", "exponent", "constant", "unknown"
46
46
  ])
47
47
  }),
48
48
  editStrokeScale: Object.freeze({
@@ -50,7 +50,7 @@ const DEFINITIONS = Object.freeze({
50
50
  targetOnly: true,
51
51
  options: Object.freeze([
52
52
  "type", "domain", "range", "emptyDomain", "clamp", "reverse", "palette",
53
- "interpolate", "midpoint", "base", "constant", "unknown"
53
+ "interpolate", "whitePoint", "gamma", "midpoint", "base", "exponent", "constant", "unknown"
54
54
  ])
55
55
  }),
56
56
  editSizeScale: Object.freeze({