ggaction 0.0.10 → 0.0.12

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 (224) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +5 -3
  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/intent-taxonomy.json +1 -183
  7. package/knowledge/intent-taxonomy.schema.json +1 -0
  8. package/knowledge/mcp-resources.json +1 -95
  9. package/knowledge/mcp-resources.schema.json +1 -0
  10. package/knowledge/task-packet.schema.json +1 -159
  11. package/knowledge/task-resolver.js +1 -1230
  12. package/package.json +18 -2
  13. package/src/actions/boxPlots/edit.js +45 -298
  14. package/src/actions/boxPlots/options.js +46 -77
  15. package/src/actions/boxPlots/resolve.js +1 -1
  16. package/src/actions/charts/bar.js +3 -0
  17. package/src/actions/charts/heatmap.js +73 -83
  18. package/src/actions/charts/index.js +1 -1
  19. package/src/actions/charts/parallel.js +35 -1
  20. package/src/actions/charts/shared.js +20 -0
  21. package/src/actions/composition/actions.js +46 -49
  22. package/src/actions/data/bin2d.js +105 -2
  23. package/src/actions/data/box.js +17 -17
  24. package/src/actions/data/density.js +42 -78
  25. package/src/actions/data/gradientProfile.js +22 -52
  26. package/src/actions/data/horizon.js +16 -41
  27. package/src/actions/data/index.js +10 -10
  28. package/src/actions/data/interval.js +16 -38
  29. package/src/actions/data/intervalEdit.js +55 -0
  30. package/src/actions/data/regression.js +7 -17
  31. package/src/actions/data/shared.js +47 -0
  32. package/src/actions/data/timeUnit.js +14 -38
  33. package/src/actions/data/window.js +14 -38
  34. package/src/actions/distributions/revision.js +154 -34
  35. package/src/actions/encodings/angle.js +9 -10
  36. package/src/actions/encodings/appearance.js +17 -51
  37. package/src/actions/encodings/density/resolve.js +3 -1
  38. package/src/actions/encodings/density.js +1 -15
  39. package/src/actions/encodings/horizon/resolve.js +13 -3
  40. package/src/actions/encodings/horizon.js +8 -7
  41. package/src/actions/encodings/index.js +9 -9
  42. package/src/actions/encodings/offset.js +41 -45
  43. package/src/actions/encodings/parallel.js +9 -10
  44. package/src/actions/encodings/pathOrder.js +6 -13
  45. package/src/actions/encodings/position/apply.js +30 -0
  46. package/src/actions/encodings/position/policies/arc.js +23 -2
  47. package/src/actions/encodings/position/policies/index.js +4 -4
  48. package/src/actions/encodings/position/policies/line.js +47 -17
  49. package/src/actions/encodings/position/resolve.js +36 -7
  50. package/src/actions/encodings/ranged.js +18 -23
  51. package/src/actions/encodings/remove.js +15 -25
  52. package/src/actions/encodings/ruleAppearance.js +9 -24
  53. package/src/actions/encodings/shared.js +37 -0
  54. package/src/actions/encodings/strokeDash.js +8 -23
  55. package/src/actions/encodings/text.js +9 -10
  56. package/src/actions/errorBands/create.js +105 -39
  57. package/src/actions/errorBands/edit.js +89 -93
  58. package/src/actions/errorBands/index.js +1 -2
  59. package/src/actions/errorBars/create.js +212 -30
  60. package/src/actions/errorBars/edit.js +91 -48
  61. package/src/actions/errorBars/index.js +1 -2
  62. package/src/actions/facets/actions.js +45 -69
  63. package/src/actions/facets/derive.js +38 -53
  64. package/src/actions/facets/replay.js +0 -1
  65. package/src/actions/gradientPlots/components.js +59 -48
  66. package/src/actions/gradientPlots/edit.js +35 -216
  67. package/src/actions/gradientPlots/index.js +11 -10
  68. package/src/actions/gradientPlots/materialize.js +21 -35
  69. package/src/actions/gradientPlots/options.js +5 -7
  70. package/src/actions/gradientPlots/paint.js +9 -6
  71. package/src/actions/gradientPlots/resolve.js +1 -1
  72. package/src/actions/guides/axes/index.js +3 -3
  73. package/src/actions/guides/axes/labels.js +68 -26
  74. package/src/actions/guides/axes/lines.js +22 -2
  75. package/src/actions/guides/axes/policy.js +28 -8
  76. package/src/actions/guides/axes/ticks.js +27 -9
  77. package/src/actions/guides/axes/titles.js +113 -65
  78. package/src/actions/guides/grids/resolve.js +14 -0
  79. package/src/actions/guides/guides.js +2 -4
  80. package/src/actions/guides/index.js +2 -2
  81. package/src/actions/guides/legends/categorical/actions.js +44 -68
  82. package/src/actions/guides/legends/categorical/components.js +6 -1
  83. package/src/actions/guides/legends/categorical/index.js +2 -2
  84. package/src/actions/guides/legends/categorical/layout.js +148 -175
  85. package/src/actions/guides/legends/categorical/recipes.js +5 -0
  86. package/src/actions/guides/legends/categorical/resolve.js +8 -1
  87. package/src/actions/guides/legends/continuous/common.js +117 -66
  88. package/src/actions/guides/legends/continuous/gradient.js +38 -37
  89. package/src/actions/guides/legends/continuous/interval.js +97 -76
  90. package/src/actions/guides/legends/continuous/opacity.js +28 -38
  91. package/src/actions/guides/legends/edit.js +14 -1
  92. package/src/actions/guides/legends/index.js +1 -1
  93. package/src/actions/guides/legends/lane.js +2 -1
  94. package/src/actions/guides/legends/remove.js +8 -0
  95. package/src/actions/guides/legends/size.js +36 -43
  96. package/src/actions/guides/legends/strokeWidth.js +20 -26
  97. package/src/actions/guides/polar/axes/labels.js +17 -1
  98. package/src/actions/guides/polar/axes/shared.js +7 -1
  99. package/src/actions/guides/polar/axes/titles.js +19 -2
  100. package/src/actions/guides/polar/grids.js +4 -0
  101. package/src/actions/guides/polar/resolve.js +6 -0
  102. package/src/actions/index.js +13 -13
  103. package/src/actions/intervals/resolve.js +87 -117
  104. package/src/actions/marks/arc/actions.js +31 -36
  105. package/src/actions/marks/area/actions.js +23 -31
  106. package/src/actions/marks/index.js +6 -6
  107. package/src/actions/marks/line/actions.js +33 -52
  108. package/src/actions/marks/line/materialize.js +7 -5
  109. package/src/actions/marks/point/materialize.js +38 -16
  110. package/src/actions/marks/remove.js +21 -0
  111. package/src/actions/marks/rule/actions.js +79 -54
  112. package/src/actions/marks/shared.js +8 -4
  113. package/src/actions/marks/text/actions.js +14 -6
  114. package/src/actions/marks/text/layout.js +21 -45
  115. package/src/actions/marks/tick/actions.js +22 -21
  116. package/src/actions/primitives/createGraphics.js +4 -0
  117. package/src/actions/primitives/editGraphics.js +4 -0
  118. package/src/actions/primitives/index.js +3 -3
  119. package/src/actions/primitives/semantic.js +1 -1
  120. package/src/actions/primitives/semanticValidation/index.js +2 -2
  121. package/src/actions/primitives/semanticValidation/layer.js +1 -1
  122. package/src/actions/regression/edit.js +3 -43
  123. package/src/actions/scales/consumers/families.js +24 -4
  124. package/src/actions/scales/consumers/index.js +1 -1
  125. package/src/actions/scales/consumers/seriesLayout.js +17 -0
  126. package/src/actions/scales/definitions.js +100 -103
  127. package/src/actions/scales/materialize.js +5 -0
  128. package/src/actions/selection/actions.js +79 -121
  129. package/src/actions/titles/actions.js +48 -94
  130. package/src/actions/titles/resolve.js +14 -27
  131. package/src/actions/violinPlots/create.js +37 -44
  132. package/src/core/textMetrics.js +74 -37
  133. package/src/core/validation.js +18 -0
  134. package/src/core/vocabulary.js +2 -2
  135. package/src/grammar/aggregate.js +48 -28
  136. package/src/grammar/arcs.js +170 -32
  137. package/src/grammar/areaSeries.js +14 -16
  138. package/src/grammar/bin2d.js +37 -23
  139. package/src/grammar/boxPlot.js +14 -13
  140. package/src/grammar/categoricalDensity.js +14 -14
  141. package/src/grammar/categoryOrder.js +8 -14
  142. package/src/grammar/curveCommands.js +87 -20
  143. package/src/grammar/density.js +162 -141
  144. package/src/grammar/facets/index.js +14 -0
  145. package/src/grammar/facets/scales.js +7 -4
  146. package/src/grammar/gradientProfile.js +12 -15
  147. package/src/grammar/histogram.js +145 -83
  148. package/src/grammar/horizon.js +60 -23
  149. package/src/grammar/interval.js +14 -13
  150. package/src/grammar/lineSeries.js +91 -61
  151. package/src/grammar/numeric.js +290 -0
  152. package/src/grammar/paint.js +3 -4
  153. package/src/grammar/palettes.js +21 -32
  154. package/src/grammar/pathCommands.js +6 -0
  155. package/src/grammar/polarPaths.js +5 -5
  156. package/src/grammar/positionCompatibility.js +1 -1
  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/rules.js +2 -0
  161. package/src/grammar/scales/color.js +35 -26
  162. package/src/grammar/scales/continuous.js +71 -24
  163. package/src/grammar/scales/definition.js +1 -1
  164. package/src/grammar/scales/discretized.js +42 -12
  165. package/src/grammar/scales/fields.js +21 -4
  166. package/src/grammar/scales/ordinal.js +139 -60
  167. package/src/grammar/scales/temporal.js +40 -11
  168. package/src/grammar/scales/transformed.js +199 -52
  169. package/src/grammar/schemas/graphicBounds.js +270 -79
  170. package/src/grammar/seriesLayout.js +50 -18
  171. package/src/grammar/ticks.js +123 -53
  172. package/src/grammar/timeUnit.js +18 -8
  173. package/src/grammar/transforms.js +4 -1
  174. package/src/grammar/window.js +70 -57
  175. package/src/index.js +1 -1
  176. package/src/layout/canvas.js +4 -8
  177. package/src/layout/composition.js +39 -40
  178. package/src/layout/facets.js +55 -30
  179. package/src/layout/labels.js +91 -15
  180. package/src/layout/legend.js +22 -20
  181. package/src/layout/legendLane.js +54 -40
  182. package/src/layout/title.js +7 -7
  183. package/src/materialization/bars/aggregate.js +14 -40
  184. package/src/materialization/bars/grouped.js +7 -18
  185. package/src/materialization/bars/histogram.js +2 -19
  186. package/src/materialization/bars/ranged.js +20 -15
  187. package/src/materialization/bars/resolve.js +21 -0
  188. package/src/materialization/facetGuides/legacyCategorical.js +9 -2
  189. package/src/materialization/facetGuides/placement.js +49 -19
  190. package/src/materialization/facetGuides/preparation.js +35 -6
  191. package/src/materialization/facets.js +42 -28
  192. package/src/materialization/guides/resources.js +2 -1
  193. package/src/materialization/marks/capabilities.js +15 -4
  194. package/src/materialization/marks/index.js +29 -6
  195. package/src/materialization/rowEncoding.js +57 -1
  196. package/src/materialization/scales/policies/bar.js +61 -102
  197. package/src/materialization/scales/policies/binnedPosition.js +34 -0
  198. package/src/materialization/scales/policies/offset.js +54 -0
  199. package/src/materialization/scales/policies/series.js +13 -7
  200. package/src/materialization/scales/resolve.js +6 -9
  201. package/src/materialization/selection/items/arc.js +16 -2
  202. package/src/materialization/selection/items/bar.js +4 -2
  203. package/src/materialization/selection/items/common.js +21 -1
  204. package/src/materialization/selection/items/path.js +19 -2
  205. package/src/materialization/selection/items/point.js +4 -16
  206. package/src/materialization/selection/items/tick.js +13 -21
  207. package/src/materialization/selection/point.js +11 -4
  208. package/src/materialization/selection/styles.js +19 -27
  209. package/src/materialization/text.js +4 -1
  210. package/src/mcp/adapter.js +8 -3
  211. package/src/renderers/canvas/fill.js +2 -2
  212. package/src/renderers/canvas/index.js +35 -2
  213. package/src/renderers/canvas/native.js +160 -0
  214. package/src/renderers/pdf.js +14 -0
  215. package/src/renderers/svg.js +37 -4
  216. package/types/index.d.ts +3 -0
  217. package/types/program.d.ts +523 -131
  218. package/types/svg.d.ts +1 -0
  219. package/src/actions/errorBands/components.js +0 -81
  220. package/src/actions/errorBands/options.js +0 -39
  221. package/src/actions/errorBands/resolve.js +0 -28
  222. package/src/actions/errorBars/components.js +0 -62
  223. package/src/actions/errorBars/options.js +0 -58
  224. package/src/actions/errorBars/resolve.js +0 -25
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ggaction",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "Build charts through immutable, traceable graphical actions.",
5
5
  "license": "MIT",
6
6
  "author": "Hyeon Jeon",
@@ -25,9 +25,13 @@
25
25
  "src/",
26
26
  "!src/**/AGENTS.md",
27
27
  "knowledge/action-cards.json",
28
+ "knowledge/action-card.schema.json",
29
+ "knowledge/action-cards.schema.json",
28
30
  "knowledge/extension-authoring.md",
29
31
  "knowledge/intent-taxonomy.json",
32
+ "knowledge/intent-taxonomy.schema.json",
30
33
  "knowledge/mcp-resources.json",
34
+ "knowledge/mcp-resources.schema.json",
31
35
  "knowledge/task-packet.schema.json",
32
36
  "knowledge/task-resolver.js",
33
37
  "types/",
@@ -72,6 +76,7 @@
72
76
  "test:charts": "node scripts/run-tests.js charts",
73
77
  "test:gates": "node scripts/run-tests.js gates",
74
78
  "test:docs": "node scripts/run-tests.js docs",
79
+ "test:realistic": "node scripts/run-tests.js realistic",
75
80
  "test:browser": "node scripts/run-tests.js browser",
76
81
  "test:docs:built": "node scripts/check-built-docs.js _site",
77
82
  "test:docs:browser": "node scripts/test-built-docs.js _site",
@@ -82,12 +87,19 @@
82
87
  "artifacts:clean": "node test/support/artifacts.js",
83
88
  "artifacts:gallery": "node scripts/generate-artifact-gallery.js",
84
89
  "artifacts:gallery:test": "node scripts/test-artifact-gallery.js",
90
+ "datasets:sync": "node scripts/sync-dataset-corpus.js --all",
91
+ "scenarios:smoke": "node scripts/run-generated-scenarios.js --mode=smoke --no-tidytuesday",
92
+ "scenarios:deep": "node scripts/run-generated-scenarios.js --mode=deep",
93
+ "scenarios:realistic": "node --expose-gc scripts/run-realistic-scenarios.js",
94
+ "scenarios:realistic:audit": "node --expose-gc scripts/run-realistic-scenarios.js --no-artifacts",
85
95
  "docs:images": "node scripts/generate-doc-images.js",
86
96
  "docs:llms": "node scripts/generate-llm-docs.js",
87
97
  "docs:capabilities": "node scripts/generate-doc-capabilities.js",
88
98
  "docs:capabilities:check": "node scripts/generate-doc-capabilities.js --check",
89
99
  "docs:actions": "node scripts/generate-doc-action-metadata.js",
90
100
  "docs:actions:check": "node scripts/generate-doc-action-metadata.js --check",
101
+ "docs:machine": "node scripts/generate-doc-machine-artifacts.js",
102
+ "docs:machine:check": "node scripts/generate-doc-machine-artifacts.js --check",
91
103
  "docs:search": "node scripts/generate-doc-search-index.js",
92
104
  "docs:search:check": "node scripts/generate-doc-search-index.js --check",
93
105
  "docs:reference": "node scripts/generate-doc-action-reference.js",
@@ -99,11 +111,13 @@
99
111
  "docs:signatures:check": "node scripts/generate-doc-signatures.js --check",
100
112
  "examples:index": "node scripts/generate-examples-readme.js",
101
113
  "examples:index:check": "node scripts/generate-examples-readme.js --check",
102
- "docs:generate": "npm run examples:index && npm run docs:actions && npm run docs:reference && npm run docs:signatures && npm run docs:capabilities && npm run docs:metadata && npm run docs:search && npm run docs:images && npm run docs:llms",
114
+ "docs:generate": "npm run examples:index && npm run docs:reference && npm run contracts:cards && npm run docs:actions && npm run docs:signatures && npm run docs:capabilities && npm run docs:metadata && npm run docs:search && npm run docs:images && npm run docs:machine && npm run docs:llms",
103
115
  "docs:build": "bundle exec jekyll build --source docs --destination _site --baseurl /ggaction",
104
116
  "docs:verify": "npm run docs:preflight && npm run docs:generate && npm run test:docs && npm run docs:build && npm run test:docs:built && npm run test:docs:browser",
105
117
  "contracts:catalog": "node scripts/generate-action-catalog.js",
106
118
  "contracts:catalog:check": "node scripts/generate-action-catalog.js --check",
119
+ "contracts:cards": "node scripts/generate-action-cards.js",
120
+ "contracts:cards:check": "node scripts/generate-action-cards.js --check",
107
121
  "package:check": "node scripts/package-artifact.js --check",
108
122
  "package:pack": "node scripts/package-artifact.js --pack",
109
123
  "package:bundle": "node scripts/measure-browser-bundle.js",
@@ -124,7 +138,9 @@
124
138
  },
125
139
  "devDependencies": {
126
140
  "@axe-core/playwright": "^4.12.1",
141
+ "ajv": "^8.20.0",
127
142
  "es-module-lexer": "^1.7.0",
143
+ "esbuild": "0.28.2",
128
144
  "gifenc": "^1.0.3",
129
145
  "playwright": "^1.61.1",
130
146
  "typescript": "^7.0.2",
@@ -8,13 +8,12 @@ import { planDerivedDataRevision } from
8
8
  "../../materialization/dataProvenance.js";
9
9
  import { findDataset } from "../../selectors/datasets.js";
10
10
  import { findLayer } from "../../selectors/layers.js";
11
+ import { removeOwnedMark } from "../marks/remove.js";
11
12
  import {
12
- assertDistributionScaleHandoff,
13
- clearCartesianPositions,
14
- rebindDistributionGuides,
15
- resolveDistributionScalePlan,
13
+ resolveDistributionRoles,
16
14
  setCartesianPosition,
17
- setCartesianRange
15
+ setCartesianRange,
16
+ updateDistributionPositions
18
17
  } from "../distributions/revision.js";
19
18
  import {
20
19
  resolveBoxAppearance,
@@ -73,130 +72,18 @@ function resolveEditedWhisker(current, value) {
73
72
  return resolveBoxWhisker(candidate, "editBoxPlot");
74
73
  }
75
74
 
76
- function removeOwnedMark(program, id) {
77
- const selections = Object.entries(
78
- program.materializationConfigs.selections ?? {}
79
- ).filter(([, config]) => config.target === id).map(([selection]) => selection);
80
- let next = program;
81
- for (const [highlight, config] of Object.entries(
82
- program.materializationConfigs.highlights ?? {}
83
- )) {
84
- if (config.target === id || selections.includes(config.selection)) {
85
- next = next._withoutMaterializationConfig(["highlights", highlight]);
86
- }
87
- }
88
- for (const selection of selections) {
89
- next = next._withoutMaterializationConfig(["selections", selection]);
90
- }
91
- return next
92
- .editSemantic({ property: `layer[${id}]`, remove: true })
93
- .editGraphics({ target: id, remove: true })
94
- ._withoutMaterializationConfig(["marks", id])
95
- ._withContext({
96
- ...(program.context.currentMark === id ? { currentMark: undefined } : {}),
97
- ...(selections.includes(program.context.currentSelection)
98
- ? { currentSelection: undefined }
99
- : {})
100
- });
101
- }
102
-
103
- function currentBoxPositions(owner, current) {
104
- const categoryChannel = current.orientation === "vertical" ? "x" : "y";
105
- const measureChannel = current.orientation === "vertical" ? "y" : "x";
106
- const categoryEncoding = owner.encoding[categoryChannel];
107
- const measureEncoding = owner.encoding[measureChannel];
108
- return {
109
- x: current.orientation === "vertical"
110
- ? {
111
- field: current.category,
112
- fieldType: categoryEncoding.fieldType,
113
- scale: categoryEncoding.scale
114
- }
115
- : {
116
- field: current.measure,
117
- fieldType: "quantitative",
118
- scale: measureEncoding.scale
119
- },
120
- y: current.orientation === "vertical"
121
- ? {
122
- field: current.measure,
123
- fieldType: "quantitative",
124
- scale: measureEncoding.scale
125
- }
126
- : {
127
- field: current.category,
128
- fieldType: categoryEncoding.fieldType,
129
- scale: categoryEncoding.scale
130
- },
131
- categoryScale: categoryEncoding.scale,
132
- measureScale: measureEncoding.scale
133
- };
134
- }
135
-
136
- function requirePositionField(position, label) {
137
- if (typeof position.field !== "string" || position.field.length === 0) {
138
- throw new TypeError(`${label} field must be a non-empty string.`);
139
- }
140
- return { fieldType: position.fieldType ?? "quantitative", ...position };
141
- }
142
-
143
75
  function roleCandidate(program, owner, current, args) {
144
- const previous = currentBoxPositions(owner, current);
145
- const x = requirePositionField(
146
- Object.hasOwn(args, "x")
147
- ? resolveBoxPosition(args.x, "x", "editBoxPlot")
148
- : previous.x,
149
- "editBoxPlot x"
150
- );
151
- const y = requirePositionField(
152
- Object.hasOwn(args, "y")
153
- ? resolveBoxPosition(args.y, "y", "editBoxPlot")
154
- : previous.y,
155
- "editBoxPlot y"
156
- );
157
- const orientation = resolveBoxOrientation(x, y);
158
- if (orientation === undefined) {
159
- throw new Error(
160
- "editBoxPlot requires one categorical axis and one quantitative axis."
161
- );
162
- }
163
- const xRoleScale = orientation === "vertical"
164
- ? previous.categoryScale
165
- : previous.measureScale;
166
- const yRoleScale = orientation === "vertical"
167
- ? previous.measureScale
168
- : previous.categoryScale;
169
- const xScale = resolveDistributionScalePlan(program, {
170
- channel: "x",
171
- fieldType: x.fieldType,
172
- requested: x.scale,
173
- fallback: xRoleScale,
174
- defaults: ["ordinal", "nominal"].includes(x.fieldType)
175
- ? { discreteType: "band" }
176
- : { nice: true, zero: false }
177
- });
178
- const yScale = resolveDistributionScalePlan(program, {
179
- channel: "y",
180
- fieldType: y.fieldType,
181
- requested: y.scale,
182
- fallback: yRoleScale,
183
- defaults: ["ordinal", "nominal"].includes(y.fieldType)
184
- ? { discreteType: "band" }
185
- : { nice: true, zero: false }
186
- });
187
76
  return {
188
77
  source: Object.hasOwn(args, "data")
189
78
  ? validateUserId(args.data, "Box-plot data id")
190
79
  : current.source,
191
- orientation,
192
- x: { ...x, scale: xScale.id },
193
- y: { ...y, scale: yScale.id },
194
- xScale,
195
- yScale,
196
- category: orientation === "vertical" ? x.field : y.field,
197
- categoryType: orientation === "vertical" ? x.fieldType : y.fieldType,
198
- measure: orientation === "vertical" ? y.field : x.field,
199
- previous
80
+ ...resolveDistributionRoles(program, owner, current, args, {
81
+ operation: "editBoxPlot",
82
+ resolvePosition: resolveBoxPosition,
83
+ normalize: (x, y) => ({ x, y, orientation: resolveBoxOrientation(x, y) }),
84
+ quantitativeDefaults: { nice: true, zero: false },
85
+ defaultFieldType: true
86
+ })
200
87
  };
201
88
  }
202
89
 
@@ -223,37 +110,18 @@ function updateBoxPositions(program, owner, current, candidate, {
223
110
  ].filter(Boolean);
224
111
  const owned = [owner.id, current.whiskerId, ...capIds, current.medianId,
225
112
  ...(findLayer(program, current.outlierId) ? [current.outlierId] : [])];
226
- assertDistributionScaleHandoff(program, {
227
- owned,
228
- oldXScale: candidate.previous.x.scale,
229
- oldYScale: candidate.previous.y.scale,
230
- newXScale: candidate.xScale.id,
231
- newYScale: candidate.yScale.id
232
- });
233
- let next = program;
234
- for (const id of owned) next = clearCartesianPositions(next, id);
235
- for (const scale of [candidate.xScale, candidate.yScale]) {
236
- if (scale.create) next = next.createScale(scale.definition);
237
- }
238
- const category = candidate.orientation === "vertical" ? candidate.x : candidate.y;
239
- const measure = candidate.orientation === "vertical" ? candidate.y : candidate.x;
240
- const categoryChannel = candidate.orientation === "vertical" ? "x" : "y";
241
- const measureChannel = candidate.orientation === "vertical" ? "y" : "x";
242
- next = setCartesianPosition(next, owner.id, categoryChannel, {
243
- field: category.field,
244
- fieldType: category.fieldType,
245
- scale: category.scale
246
- });
247
- next = setCartesianRange(
248
- next,
249
- owner.id,
250
- measureChannel,
251
- BOX_FIELDS.q1,
252
- BOX_FIELDS.q3,
253
- measure.scale,
254
- candidate.measure
255
- );
256
- next = next._withMarkConfig(current.whiskerId, {
113
+ let next = updateDistributionPositions(
114
+ program,
115
+ owner,
116
+ current,
117
+ candidate,
118
+ {
119
+ owned,
120
+ lower: BOX_FIELDS.q1,
121
+ upper: BOX_FIELDS.q3,
122
+ title: candidate.measure,
123
+ update(next, { category, measure, categoryChannel, measureChannel }) {
124
+ next = next._withMarkConfig(current.whiskerId, {
257
125
  ...whiskerConfig,
258
126
  errorBar: {
259
127
  ...whiskerConfig.errorBar,
@@ -265,13 +133,13 @@ function updateBoxPositions(program, owner, current, candidate, {
265
133
  positionScale: category.scale,
266
134
  intervalScale: measure.scale
267
135
  }
268
- });
269
- next = setCartesianPosition(next, current.whiskerId, categoryChannel, {
136
+ });
137
+ next = setCartesianPosition(next, current.whiskerId, categoryChannel, {
270
138
  field: category.field,
271
139
  fieldType: category.fieldType,
272
140
  scale: category.scale
273
141
  });
274
- next = setCartesianRange(
142
+ next = setCartesianRange(
275
143
  next,
276
144
  current.whiskerId,
277
145
  measureChannel,
@@ -279,8 +147,8 @@ function updateBoxPositions(program, owner, current, candidate, {
279
147
  BOX_FIELDS.upperWhisker,
280
148
  measure.scale,
281
149
  candidate.measure
282
- );
283
- for (const [index, capId] of capIds.entries()) {
150
+ );
151
+ for (const [index, capId] of capIds.entries()) {
284
152
  const field = index === 0 ? BOX_FIELDS.lowerWhisker : BOX_FIELDS.upperWhisker;
285
153
  next = setCartesianPosition(next, capId, categoryChannel, {
286
154
  field: category.field,
@@ -300,18 +168,18 @@ function updateBoxPositions(program, owner, current, candidate, {
300
168
  : "vertical"
301
169
  }
302
170
  });
303
- }
304
- next = setCartesianPosition(next, current.medianId, categoryChannel, {
171
+ }
172
+ next = setCartesianPosition(next, current.medianId, categoryChannel, {
305
173
  field: category.field,
306
174
  fieldType: category.fieldType,
307
175
  scale: category.scale
308
176
  });
309
- next = setCartesianPosition(next, current.medianId, measureChannel, {
177
+ next = setCartesianPosition(next, current.medianId, measureChannel, {
310
178
  field: BOX_FIELDS.median,
311
179
  fieldType: "quantitative",
312
180
  scale: measure.scale
313
181
  });
314
- if (hasOutliers && findLayer(next, current.outlierId) !== undefined) {
182
+ if (hasOutliers && findLayer(next, current.outlierId) !== undefined) {
315
183
  next = setCartesianPosition(next, current.outlierId, categoryChannel, {
316
184
  field: category.field,
317
185
  fieldType: category.fieldType,
@@ -322,25 +190,11 @@ function updateBoxPositions(program, owner, current, candidate, {
322
190
  fieldType: "quantitative",
323
191
  scale: measure.scale
324
192
  });
325
- }
326
- for (const id of new Set([candidate.xScale.id, candidate.yScale.id])) {
327
- next = next.rematerializeScale({ id, marks: false, guides: false });
328
- }
329
- next = rebindDistributionGuides(next, {
330
- oldXScale: candidate.previous.x.scale,
331
- oldYScale: candidate.previous.y.scale,
332
- newXScale: candidate.xScale.id,
333
- newYScale: candidate.yScale.id,
334
- oldXTitle: candidate.previous.x.field,
335
- oldYTitle: candidate.previous.y.field,
336
- newXTitle: candidate.x.field,
337
- newYTitle: candidate.y.field,
338
- oldMeasureChannel: current.orientation === "vertical" ? "y" : "x",
339
- newMeasureChannel: candidate.orientation === "vertical" ? "y" : "x"
340
- });
341
- for (const scale of [candidate.xScale, candidate.yScale]) {
342
- if (scale.edit !== undefined) next = next.editScale(scale.edit);
343
- }
193
+ }
194
+ return next;
195
+ }
196
+ }
197
+ );
344
198
  next = next.rematerializeBarMark({ id: owner.id })
345
199
  .rematerializeErrorBar({ id: current.whiskerId });
346
200
  for (const capId of capIds) {
@@ -413,7 +267,9 @@ export const editBoxPlot = action(
413
267
  key => Object.hasOwn(args, key)
414
268
  );
415
269
  const changesRoles = !sameRoleCandidate(current, candidate);
416
- if (changesRoles) {
270
+ const revisesData = JSON.stringify(whisker) !== JSON.stringify(current.whisker) ||
271
+ outliers !== current.outliers;
272
+ if (changesRoles || revisesData) {
417
273
  const derived = deriveBoxData(
418
274
  sourceDataset.values,
419
275
  normalizeBoxTransform({
@@ -556,7 +412,6 @@ export const editBoxPlot = action(
556
412
  currentData: candidate.source
557
413
  });
558
414
  };
559
- applyEdit(this);
560
415
  return applyEdit(this);
561
416
  }
562
417
  if (roleRequested && !OPTIONS.slice(4).some(
@@ -568,110 +423,11 @@ export const editBoxPlot = action(
568
423
  });
569
424
  }
570
425
 
571
- const revisesData = JSON.stringify(whisker) !== JSON.stringify(current.whisker) ||
572
- outliers !== current.outliers;
573
- const changesBox = Object.hasOwn(args, "box") ||
574
- Object.hasOwn(args, "width") || revisesData;
575
- const changesMedian = Object.hasOwn(args, "median") ||
576
- Object.hasOwn(args, "width") || revisesData;
577
- const changesOutlier = Object.hasOwn(args, "outlier") || revisesData;
578
- const categoryEncoding = current.orientation === "vertical"
579
- ? owner.encoding.x
580
- : owner.encoding.y;
581
- const measureEncoding = current.orientation === "vertical"
582
- ? owner.encoding.y
583
- : owner.encoding.x;
584
- let summaryId = current.summaryId;
585
- let outlierDataId = current.outlierDataId;
426
+ const changesBox = Object.hasOwn(args, "box") || Object.hasOwn(args, "width");
427
+ const changesMedian = Object.hasOwn(args, "median") || Object.hasOwn(args, "width");
428
+ const changesOutlier = Object.hasOwn(args, "outlier");
586
429
  let next = this;
587
430
 
588
- const sourceRows = findDataset(this, current.source).values;
589
- const derived = deriveBoxData(sourceRows, normalizeBoxTransform({
590
- type: "boxSummary",
591
- category: current.category,
592
- field: current.measure,
593
- whisker: whisker.type,
594
- ...(whisker.factor === undefined ? {} : { factor: whisker.factor })
595
- }));
596
- const hasOutliers = outliers && whisker.type === "tukey" &&
597
- derived.outliers.length > 0;
598
-
599
- if (revisesData) {
600
- const whiskerConfig = next.markConfigs[current.whiskerId];
601
- const capIds = [
602
- whiskerConfig.errorBar.lowerCapId,
603
- whiskerConfig.errorBar.upperCapId
604
- ].filter(id => id !== undefined);
605
- const summaryRevision = planDerivedDataRevision(this, {
606
- owner: owner.id,
607
- role: "SummaryData",
608
- previous: current.summaryId,
609
- consumers: [owner.id, current.whiskerId, ...capIds, current.medianId]
610
- });
611
- summaryId = summaryRevision.id;
612
- next = next.createBoxSummaryData({
613
- id: summaryId,
614
- source: current.source,
615
- category: current.category,
616
- field: current.measure,
617
- whisker: whisker.type,
618
- ...(whisker.factor === undefined ? {} : { factor: whisker.factor })
619
- });
620
- if (hasOutliers) {
621
- const hadOutlierLayer = findLayer(next, current.outlierId) !== undefined;
622
- const outlierRevision = planDerivedDataRevision(this, {
623
- owner: owner.id,
624
- role: "OutlierData",
625
- ...(current.outlierDataId === undefined
626
- ? {}
627
- : { previous: current.outlierDataId }),
628
- consumers: hadOutlierLayer ? [current.outlierId] : []
629
- });
630
- outlierDataId = outlierRevision.id;
631
- next = next.createBoxOutlierData({
632
- id: outlierDataId,
633
- source: current.source,
634
- category: current.category,
635
- field: current.measure,
636
- whisker: whisker.type,
637
- factor: whisker.factor
638
- });
639
- for (const rebind of outlierRevision.rebinds) {
640
- next = next.rebindLayerData(rebind);
641
- }
642
- } else {
643
- outlierDataId = undefined;
644
- }
645
-
646
- for (const rebind of summaryRevision.rebinds) {
647
- next = next.rebindLayerData(rebind);
648
- }
649
- next = next._withMarkConfig(current.whiskerId, {
650
- ...whiskerConfig,
651
- errorBar: { ...whiskerConfig.errorBar, data: summaryId }
652
- });
653
-
654
- const hadOutlierLayer = findLayer(next, current.outlierId) !== undefined;
655
- if (hadOutlierLayer && !hasOutliers) {
656
- next = removeOwnedMark(next, current.outlierId);
657
- } else if (!hadOutlierLayer && hasOutliers) {
658
- next = next.createBoxOutliers({
659
- id: current.outlierId,
660
- data: outlierDataId,
661
- category: current.category,
662
- categoryType: categoryEncoding.fieldType,
663
- measure: current.measure,
664
- orientation: current.orientation,
665
- coordinate: owner.coordinate,
666
- categoryScale: categoryEncoding.scale,
667
- measureScale: measureEncoding.scale,
668
- shape: outlier.shape,
669
- radius: outlier.radius,
670
- opacity: outlier.opacity
671
- });
672
- }
673
- }
674
-
675
431
  next = next._withMarkConfig(owner.id, {
676
432
  ...next.markConfigs[owner.id],
677
433
  boxPlot: {
@@ -682,8 +438,8 @@ export const editBoxPlot = action(
682
438
  box,
683
439
  median,
684
440
  outlier,
685
- summaryId,
686
- outlierDataId
441
+ summaryId: current.summaryId,
442
+ outlierDataId: current.outlierDataId
687
443
  },
688
444
  barWidth: { band: width },
689
445
  fill: box.fill,
@@ -693,9 +449,6 @@ export const editBoxPlot = action(
693
449
  });
694
450
 
695
451
  if (changesBox) next = next.rematerializeBarMark({ id: owner.id });
696
- if (revisesData) {
697
- next = next.rematerializeErrorBar({ id: current.whiskerId });
698
- }
699
452
  if (changesMedian) {
700
453
  next = next
701
454
  .encodeStroke({ target: current.medianId, value: median.stroke })
@@ -714,12 +467,6 @@ export const editBoxPlot = action(
714
467
  .encodeRadius({ target: current.outlierId, value: outlier.radius });
715
468
  }
716
469
 
717
- if (revisesData) {
718
- next = next.releaseDerivedData({ id: current.summaryId });
719
- if (current.outlierDataId !== undefined) {
720
- next = next.releaseDerivedData({ id: current.outlierDataId });
721
- }
722
- }
723
470
  return next._withContext({ currentMark: owner.id, currentData: current.source });
724
471
  }
725
472
  );