ggaction 0.0.3 → 0.0.5

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 (223) hide show
  1. package/CHANGELOG.md +58 -3
  2. package/README.md +117 -83
  3. package/package.json +27 -11
  4. package/src/actions/boxPlots/create.js +13 -7
  5. package/src/actions/boxPlots/edit.js +31 -35
  6. package/src/actions/boxPlots/materialize.js +3 -0
  7. package/src/actions/boxPlots/options.js +8 -1
  8. package/src/actions/boxPlots/resolve.js +10 -2
  9. package/src/actions/charts/bar.js +50 -0
  10. package/src/actions/charts/heatmap.js +205 -0
  11. package/src/actions/charts/histogram.js +58 -0
  12. package/src/actions/charts/index.js +15 -0
  13. package/src/actions/charts/line.js +69 -0
  14. package/src/actions/charts/parallel.js +76 -0
  15. package/src/actions/charts/scatter.js +56 -0
  16. package/src/actions/charts/shared.js +121 -0
  17. package/src/actions/coordinates/parallel.js +50 -0
  18. package/src/actions/data/bin2d.js +137 -0
  19. package/src/actions/data/density.js +54 -13
  20. package/src/actions/data/derived.js +22 -0
  21. package/src/actions/data/filter.js +5 -0
  22. package/src/actions/data/gradientProfile.js +65 -0
  23. package/src/actions/data/horizon.js +71 -0
  24. package/src/actions/data/index.js +29 -2
  25. package/src/actions/data/regression.js +1 -1
  26. package/src/actions/data/window.js +50 -0
  27. package/src/actions/encodings/appearance.js +1 -1
  28. package/src/actions/encodings/color/continuous.js +137 -0
  29. package/src/actions/encodings/color/index.js +167 -0
  30. package/src/actions/encodings/color/layout.js +99 -0
  31. package/src/actions/encodings/color/policy.js +88 -0
  32. package/src/actions/encodings/density/resolve.js +109 -0
  33. package/src/actions/encodings/density.js +193 -88
  34. package/src/actions/encodings/horizon/resolve.js +283 -0
  35. package/src/actions/encodings/horizon.js +364 -0
  36. package/src/actions/encodings/index.js +8 -2
  37. package/src/actions/encodings/offset.js +1 -1
  38. package/src/actions/encodings/parallel.js +115 -0
  39. package/src/actions/encodings/pathOrder.js +131 -0
  40. package/src/actions/encodings/position/apply.js +73 -4
  41. package/src/actions/encodings/{position.js → position/index.js} +21 -8
  42. package/src/actions/encodings/position/policies/arc.js +27 -4
  43. package/src/actions/encodings/position/policies/area.js +33 -2
  44. package/src/actions/encodings/position/policies/index.js +7 -2
  45. package/src/actions/encodings/position/policies/line.js +18 -2
  46. package/src/actions/encodings/position/resolve.js +25 -3
  47. package/src/actions/encodings/ranged.js +5 -3
  48. package/src/actions/encodings/ruleAppearance.js +88 -10
  49. package/src/actions/encodings/strokeDash.js +1 -1
  50. package/src/actions/encodings/text.js +1 -1
  51. package/src/actions/errorBands/options.js +1 -1
  52. package/src/actions/errorBars/options.js +1 -1
  53. package/src/actions/facets/actions.js +2 -3
  54. package/src/actions/facets/derive.js +35 -5
  55. package/src/actions/facets/guides.js +1 -1
  56. package/src/actions/facets/replay.js +7 -36
  57. package/src/actions/gradientPlots/components.js +177 -0
  58. package/src/actions/gradientPlots/create.js +96 -0
  59. package/src/actions/gradientPlots/edit.js +153 -0
  60. package/src/actions/gradientPlots/index.js +24 -0
  61. package/src/actions/gradientPlots/materialize.js +287 -0
  62. package/src/actions/gradientPlots/options.js +120 -0
  63. package/src/actions/gradientPlots/paint.js +107 -0
  64. package/src/actions/gradientPlots/rebind.js +51 -0
  65. package/src/actions/gradientPlots/resolve.js +81 -0
  66. package/src/actions/guides/applicability.js +44 -17
  67. package/src/actions/guides/axes/axes.js +37 -3
  68. package/src/actions/guides/axes/index.js +2 -0
  69. package/src/actions/guides/axes/labels.js +12 -7
  70. package/src/actions/guides/axes/parallel/resolve.js +86 -0
  71. package/src/actions/guides/axes/parallel.js +228 -0
  72. package/src/actions/guides/axes/ticks.js +1 -1
  73. package/src/actions/guides/axes/titles.js +12 -9
  74. package/src/actions/guides/grids/resolve.js +1 -1
  75. package/src/actions/guides/guides.js +60 -5
  76. package/src/actions/guides/legends/categorical/actions.js +48 -11
  77. package/src/actions/guides/legends/categorical/layout.js +4 -2
  78. package/src/actions/guides/legends/continuous/interval.js +1 -1
  79. package/src/actions/guides/legends/continuous/opacity.js +1 -1
  80. package/src/actions/guides/legends/edit.js +7 -20
  81. package/src/actions/guides/legends/index.js +2 -0
  82. package/src/actions/guides/legends/remove.js +6 -31
  83. package/src/actions/guides/legends/size.js +1 -1
  84. package/src/actions/guides/legends/strokeWidth.js +170 -0
  85. package/src/actions/guides/legends/target.js +25 -0
  86. package/src/actions/guides/polar/{axes.js → axes/index.js} +5 -5
  87. package/src/actions/guides/polar/index.js +1 -1
  88. package/src/actions/guides/polar/resolve.js +1 -1
  89. package/src/actions/guides/tickValues.js +1 -1
  90. package/src/actions/index.js +6 -0
  91. package/src/actions/intervals/resolve.js +1 -1
  92. package/src/actions/marks/arc/actions.js +2 -2
  93. package/src/actions/marks/area/actions.js +61 -137
  94. package/src/actions/marks/area/materialize.js +163 -0
  95. package/src/actions/marks/bar/edit.js +1 -1
  96. package/src/actions/marks/index.js +5 -1
  97. package/src/actions/marks/line/actions.js +100 -81
  98. package/src/actions/marks/line/materialize.js +146 -0
  99. package/src/actions/marks/point/create.js +76 -0
  100. package/src/actions/marks/point/edit.js +85 -0
  101. package/src/actions/marks/point/index.js +11 -1
  102. package/src/actions/marks/point/jitter.js +72 -0
  103. package/src/actions/marks/point/{actions.js → materialize.js} +175 -170
  104. package/src/actions/marks/rect/actions.js +4 -1
  105. package/src/actions/marks/remove.js +13 -2
  106. package/src/actions/marks/rule/actions.js +26 -4
  107. package/src/actions/marks/shared.js +1 -1
  108. package/src/actions/marks/text/actions.js +20 -5
  109. package/src/actions/marks/text/index.js +7 -1
  110. package/src/actions/marks/text/layout.js +321 -0
  111. package/src/actions/primitives/semanticValidation/dataset.js +22 -0
  112. package/src/actions/primitives/semanticValidation/guide.js +43 -0
  113. package/src/actions/primitives/semanticValidation/index.js +23 -0
  114. package/src/actions/primitives/semanticValidation/layer.js +74 -0
  115. package/src/actions/primitives/semanticValidation/scale.js +74 -0
  116. package/src/actions/primitives/semanticValidation/shared.js +5 -0
  117. package/src/actions/primitives/semanticValue.js +1 -204
  118. package/src/actions/regression/components.js +1 -1
  119. package/src/actions/regression/create.js +1 -1
  120. package/src/actions/regression/edit.js +20 -22
  121. package/src/actions/scales/consumers/common.js +109 -0
  122. package/src/actions/scales/consumers/families.js +58 -0
  123. package/src/actions/scales/consumers/index.js +41 -0
  124. package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +10 -132
  125. package/src/actions/scales/create.js +1 -1
  126. package/src/actions/scales/definitions.js +32 -2
  127. package/src/actions/scales/edit.js +8 -206
  128. package/src/actions/scales/editPolicy.js +214 -0
  129. package/src/actions/scales/materialize.js +3 -3
  130. package/src/actions/scales/patch.js +26 -0
  131. package/src/actions/selection/actions.js +28 -3
  132. package/src/actions/violinPlots/create.js +198 -0
  133. package/src/actions/violinPlots/index.js +5 -0
  134. package/src/core/ChartProgram.js +22 -236
  135. package/src/core/compositionState.js +169 -0
  136. package/src/core/materializationState.js +65 -0
  137. package/src/core/programState.js +28 -0
  138. package/src/core/textMetrics.js +47 -0
  139. package/src/core/vocabulary.js +7 -2
  140. package/src/grammar/arcs.js +50 -13
  141. package/src/grammar/areaSeries.js +42 -6
  142. package/src/grammar/bars/aggregate.js +1 -1
  143. package/src/grammar/bin2d.js +333 -0
  144. package/src/grammar/categoricalDensity.js +192 -0
  145. package/src/grammar/coordinates.js +1 -1
  146. package/src/grammar/curveCommands.js +3 -1
  147. package/src/grammar/density.js +253 -27
  148. package/src/grammar/facets/dependencies.js +3 -11
  149. package/src/grammar/{facets.js → facets/index.js} +8 -6
  150. package/src/grammar/gradientProfile.js +213 -0
  151. package/src/grammar/horizon.js +455 -0
  152. package/src/grammar/interval.js +2 -96
  153. package/src/grammar/jitter.js +197 -0
  154. package/src/grammar/lineSeries.js +70 -1
  155. package/src/grammar/paint.js +88 -0
  156. package/src/grammar/palettes.js +5 -7
  157. package/src/grammar/parallelCoordinates.js +213 -0
  158. package/src/grammar/pathOrder.js +35 -0
  159. package/src/grammar/pointShapes.js +24 -0
  160. package/src/grammar/polarLineCommands.js +1 -1
  161. package/src/grammar/regression/derive.js +97 -0
  162. package/src/grammar/regression/index.js +22 -0
  163. package/src/grammar/regression/models.js +282 -0
  164. package/src/grammar/regression/parameters.js +106 -0
  165. package/src/grammar/rules.js +2 -1
  166. package/src/grammar/scales/appearance.js +21 -0
  167. package/src/grammar/scales/color.js +4 -2
  168. package/src/grammar/schemas/concreteGraphic.js +11 -1
  169. package/src/grammar/schemas/graphicBounds.js +19 -37
  170. package/src/grammar/schemas/semanticPath.js +17 -2
  171. package/src/grammar/statistics/studentT.js +98 -0
  172. package/src/grammar/transforms.js +101 -12
  173. package/src/grammar/window.js +339 -0
  174. package/src/layout/labels.js +270 -0
  175. package/src/layout/text.js +4 -1
  176. package/src/layout/title.js +10 -20
  177. package/src/materialization/bars/aggregate.js +1 -1
  178. package/src/materialization/bars/grouped.js +1 -1
  179. package/src/materialization/bars/histogram.js +1 -1
  180. package/src/materialization/bars/ranged.js +1 -1
  181. package/src/materialization/dataProvenance.js +56 -2
  182. package/src/materialization/density.js +1 -1
  183. package/src/materialization/dependencies.js +15 -101
  184. package/src/materialization/encodings.js +2 -2
  185. package/src/materialization/facetGuides/index.js +2 -0
  186. package/src/materialization/facetGuides/legacyCategorical.js +125 -0
  187. package/src/materialization/facetGuides/placement.js +126 -0
  188. package/src/materialization/facetGuides/preparation.js +188 -0
  189. package/src/materialization/facets.js +1 -1
  190. package/src/materialization/guides/resources.js +74 -24
  191. package/src/materialization/horizon.js +29 -0
  192. package/src/materialization/layout.js +15 -0
  193. package/src/materialization/marks/capabilities.js +161 -0
  194. package/src/materialization/marks/index.js +108 -0
  195. package/src/materialization/marks/pathOrder.js +71 -0
  196. package/src/materialization/marks/policies.js +91 -0
  197. package/src/materialization/rect.js +1 -1
  198. package/src/materialization/rowEncoding.js +1 -1
  199. package/src/materialization/scaleGuideDependencies.js +102 -0
  200. package/src/materialization/scales/map.js +1 -1
  201. package/src/materialization/scales/policies/series.js +1 -1
  202. package/src/materialization/scales/resolve.js +3 -1
  203. package/src/materialization/selection/items/bar.js +1 -1
  204. package/src/materialization/selection/items/path.js +27 -1
  205. package/src/materialization/selection/items/point.js +1 -5
  206. package/src/materialization/selection/items/rect.js +21 -0
  207. package/src/materialization/selection/styles.js +21 -10
  208. package/src/materialization/text.js +12 -4
  209. package/src/renderers/canvas/fill.js +33 -0
  210. package/src/renderers/canvas/index.js +7 -0
  211. package/src/renderers/canvas/path.js +7 -4
  212. package/src/renderers/canvas/rect.js +9 -3
  213. package/src/renderers/canvas/text.js +14 -1
  214. package/src/selectors/datasets.js +4 -0
  215. package/src/theme/defaults.js +4 -0
  216. package/types/extension.d.ts +10 -1
  217. package/types/index.d.ts +76 -0
  218. package/types/program.d.ts +632 -10
  219. package/src/actions/encodings/color.js +0 -424
  220. package/src/grammar/regression.js +0 -578
  221. package/src/grammar/scales.js +0 -1
  222. package/src/materialization/facetGuides.js +0 -426
  223. package/src/materialization/marks.js +0 -316
@@ -1,17 +1,18 @@
1
1
  import { freezeOwned } from "../../core/immutable.js";
2
2
  import { resolveHistogramBins } from "../../grammar/histogram.js";
3
- import { readQuantitativeField } from "../../grammar/scales.js";
3
+ import { readQuantitativeField } from "../../grammar/scales/index.js";
4
4
  import { BAR_GRAINS, resolveBarGrain } from "../../grammar/bars/policy.js";
5
5
  import { resolveFacetScaleDomains } from "../../grammar/facets/scales.js";
6
6
  import {
7
7
  applyMaterializationPlan,
8
8
  planScaleGuideRematerialization
9
9
  } from "../../materialization/dependencies.js";
10
- import { getMarkMaterializationStep } from "../../materialization/marks.js";
10
+ import { getMarkMaterializationStep } from "../../materialization/marks/index.js";
11
11
  import { buildMaterializationPlan } from "../../materialization/planner.js";
12
12
  import { requireDataset } from "../../selectors/datasets.js";
13
13
  import { requireLayer } from "../../selectors/layers.js";
14
14
  import { findSemanticScale } from "../../selectors/scales.js";
15
+ import { findTransformPolicy } from "../../grammar/transforms.js";
15
16
 
16
17
  function sharedHistogramBoundaries(program) {
17
18
  const boundaries = new Map();
@@ -59,6 +60,23 @@ function replayDatasetId(cell, owner) {
59
60
  return `${cell.id}-${owner}-data`;
60
61
  }
61
62
 
63
+ function rebindReplayedGradientPlots(child, base, datasets) {
64
+ let next = child;
65
+ for (const [id, markConfig] of Object.entries(base.markConfigs)) {
66
+ const gradientPlot = markConfig.gradientPlot;
67
+ if (gradientPlot?.materialized !== true) continue;
68
+ const profile = datasets.get(gradientPlot.profileId);
69
+ const source = datasets.get(gradientPlot.source);
70
+ if (profile === undefined || source === undefined) {
71
+ throw new Error(
72
+ `Facet gradient plot "${id}" is missing replayed profile dependencies.`
73
+ );
74
+ }
75
+ next = next.rebindGradientPlotProfile({ id, profile, source });
76
+ }
77
+ return next;
78
+ }
79
+
62
80
  function cellMaterializationPlan(program) {
63
81
  const scaleIds = [...new Set(program.semanticSpec.layers.flatMap(layer =>
64
82
  Object.values(layer.encoding ?? {})
@@ -72,7 +90,13 @@ function cellMaterializationPlan(program) {
72
90
  return buildMaterializationPlan({ scales });
73
91
  }
74
92
 
75
- function deriveCellProgram(base, definition, cell, histogramBoundaries) {
93
+ function deriveCellProgram(
94
+ base,
95
+ definition,
96
+ cell,
97
+ histogramBoundaries,
98
+ scales
99
+ ) {
76
100
  let child = base.filterData({
77
101
  id: cell.data,
78
102
  source: definition.data,
@@ -88,10 +112,15 @@ function deriveCellProgram(base, definition, cell, histogramBoundaries) {
88
112
  );
89
113
  }
90
114
  const id = replayDatasetId(cell, replay.id);
115
+ const policy = findTransformPolicy(replay.transform.type);
116
+ const transform = policy?.facetReplayTransform?.(
117
+ replay.transform,
118
+ { scales }
119
+ ) ?? replay.transform;
91
120
  child = child.replayDerivedData({
92
121
  id,
93
122
  source,
94
- transform: replay.transform
123
+ transform
95
124
  });
96
125
  datasets.set(replay.id, id);
97
126
  }
@@ -109,6 +138,7 @@ function deriveCellProgram(base, definition, cell, histogramBoundaries) {
109
138
  histogramBoundaries.get(layer.id)
110
139
  );
111
140
  }
141
+ child = rebindReplayedGradientPlots(child, base, datasets);
112
142
  return applyMaterializationPlan(child, cellMaterializationPlan(child));
113
143
  }
114
144
 
@@ -163,7 +193,7 @@ export function deriveFacetChildren(
163
193
  : new Map();
164
194
  const independentlyResolved = Object.fromEntries(definition.cells.map(cell => [
165
195
  cell.id,
166
- deriveCellProgram(template, definition, cell, bins)
196
+ deriveCellProgram(template, definition, cell, bins, resolutionRequest)
167
197
  ]));
168
198
  const resolution = resolveFacetScaleDomains(
169
199
  template.semanticSpec,
@@ -1,7 +1,7 @@
1
1
  import { action } from "../../core/action.js";
2
2
  import { validateKeys } from "../../core/validation.js";
3
3
  import { applyFacetGuideComposition } from
4
- "../../materialization/facetGuides.js";
4
+ "../../materialization/facetGuides/index.js";
5
5
 
6
6
  export const composeFacetGuides = action(
7
7
  {
@@ -2,33 +2,25 @@ import { action } from "../../core/action.js";
2
2
  import { isPlainObject } from "../../core/immutable.js";
3
3
  import { validateUserId } from "../../core/identifiers.js";
4
4
  import { validateKeys } from "../../core/validation.js";
5
+ import { findTransformPolicy } from "../../grammar/transforms.js";
5
6
  import { hasDataset } from "../../selectors/datasets.js";
6
- import { requireLayer } from "../../selectors/layers.js";
7
7
 
8
8
  const REPLAY_OPTIONS = Object.freeze(["id", "source", "transform"]);
9
- const REBIND_OPTIONS = Object.freeze(["id", "data"]);
10
- const MATERIALIZERS = Object.freeze({
11
- filter: "materializeFilteredData",
12
- regression: "materializeRegressionData",
13
- density: "materializeDensityData",
14
- interval: "materializeIntervalData",
15
- boxSummary: "materializeBoxSummaryData",
16
- boxOutlier: "materializeBoxOutlierData"
17
- });
18
9
 
19
10
  function requestedTransform(transform) {
20
11
  if (!isPlainObject(transform)) {
21
12
  throw new TypeError("replayDerivedData transform must be a plain object.");
22
13
  }
23
- const materialize = MATERIALIZERS[transform.type];
24
- if (materialize === undefined) {
14
+ const policy = findTransformPolicy(transform.type);
15
+ if (policy?.facetTopology === undefined) {
25
16
  throw new Error(
26
17
  `replayDerivedData does not support transform "${transform.type ?? "unknown"}".`
27
18
  );
28
19
  }
29
- if (transform.type !== "density") return { transform, materialize };
30
- const { resolved: _resolved, ...requested } = transform;
31
- return { transform: requested, materialize };
20
+ return {
21
+ transform: policy.replayTransform?.(transform) ?? transform,
22
+ materialize: policy.materializeOp
23
+ };
32
24
  }
33
25
 
34
26
  export const replayDerivedData = action(
@@ -46,24 +38,3 @@ export const replayDerivedData = action(
46
38
  [resolved.materialize]({ id });
47
39
  }
48
40
  );
49
-
50
- export const rebindLayerData = action(
51
- {
52
- op: "rebindLayerData",
53
- description: "Rebind one facet-cell layer to its replayed dataset."
54
- },
55
- function (args = {}) {
56
- validateKeys(args, REBIND_OPTIONS, "rebindLayerData");
57
- const id = validateUserId(args.id, "Facet layer id");
58
- const data = validateUserId(args.data, "Facet layer dataset id");
59
- requireLayer(this, id);
60
- if (!hasDataset(this, data)) {
61
- throw new Error(`Facet layer dataset "${data}" does not exist.`);
62
- }
63
- return this.editSemantic({
64
- property: `layer[${id}].data`,
65
- value: data
66
- });
67
- }
68
- );
69
-
@@ -0,0 +1,177 @@
1
+ import { action } from "../../core/action.js";
2
+ import { validateKeys } from "../../core/validation.js";
3
+ import { GRADIENT_PROFILE_FIELDS } from "../../grammar/gradientProfile.js";
4
+ import { resolveGraphicBounds } from "../../layout/canvas.js";
5
+ import { findLayer } from "../../selectors/layers.js";
6
+ import { createDensityLegendPaint } from "./paint.js";
7
+
8
+ const CENTER_OPTIONS = Object.freeze([
9
+ "id", "owner", "data", "category", "categoryType", "coordinate",
10
+ "categoryScale", "measureScale", "orientation", "size", "stroke",
11
+ "strokeWidth"
12
+ ]);
13
+ const LEGEND_OPTIONS = Object.freeze(["owner", "title", "position"]);
14
+
15
+ export const createGradientPlotCenter = action(
16
+ {
17
+ op: "createGradientPlotCenter",
18
+ description: "Create the optional center rule for one gradient plot."
19
+ },
20
+ function (args = {}) {
21
+ validateKeys(args, CENTER_OPTIONS, "createGradientPlotCenter");
22
+ const categoryAction = args.orientation === "vertical" ? "encodeX" : "encodeY";
23
+ const measureAction = args.orientation === "vertical" ? "encodeY" : "encodeX";
24
+ const spanOrientation = args.orientation === "vertical" ? "horizontal" : "vertical";
25
+ let next = this.createRuleMark({ id: args.id, data: args.data });
26
+ next = next[categoryAction]({
27
+ target: args.id,
28
+ field: args.category,
29
+ fieldType: args.categoryType,
30
+ coordinate: args.coordinate,
31
+ scale: { id: args.categoryScale }
32
+ });
33
+ next = next[measureAction]({
34
+ target: args.id,
35
+ field: GRADIENT_PROFILE_FIELDS.center,
36
+ fieldType: "quantitative",
37
+ coordinate: args.coordinate,
38
+ scale: { id: args.measureScale }
39
+ });
40
+ return next
41
+ .encodeStroke({ target: args.id, value: args.stroke })
42
+ .encodeStrokeWidth({ target: args.id, value: args.strokeWidth })
43
+ .materializeRuleSpan({
44
+ id: args.id,
45
+ orientation: spanOrientation,
46
+ size: args.size
47
+ });
48
+ }
49
+ );
50
+
51
+ function legendIds(owner) {
52
+ return {
53
+ strip: `${owner}DensityLegend`,
54
+ labels: `${owner}DensityLegendLabels`,
55
+ title: `${owner}DensityLegendTitle`
56
+ };
57
+ }
58
+
59
+ function requireLegendOwner(program, owner) {
60
+ const layer = findLayer(program, owner);
61
+ const config = program.markConfigs[owner]?.gradientPlot;
62
+ if (layer === undefined || config?.materialized !== true) {
63
+ throw new Error(`Unknown gradient-plot legend owner "${owner}".`);
64
+ }
65
+ return { layer, config };
66
+ }
67
+
68
+ export const rematerializeGradientPlotLegend = action(
69
+ {
70
+ op: "rematerializeGradientPlotLegend",
71
+ description: "Rematerialize one gradient plot density legend."
72
+ },
73
+ function ({ owner } = {}) {
74
+ const { config } = requireLegendOwner(this, owner);
75
+ const ids = legendIds(owner);
76
+ const bounds = resolveGraphicBounds(this);
77
+ const position = config.guides.legend.position;
78
+ if (position !== "right") {
79
+ throw new Error('Gradient plot density legend currently requires position "right".');
80
+ }
81
+ const x = bounds.x + bounds.width + 50;
82
+ const y = bounds.y + 50;
83
+ const width = 22;
84
+ 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" });
114
+ }
115
+ );
116
+
117
+ export const createGradientPlotLegend = action(
118
+ {
119
+ op: "createGradientPlotLegend",
120
+ description: "Create the neutral density legend owned by one gradient plot."
121
+ },
122
+ function (args = {}) {
123
+ validateKeys(args, LEGEND_OPTIONS, "createGradientPlotLegend");
124
+ requireLegendOwner(this, args.owner);
125
+ if (args.position !== undefined && args.position !== "right") {
126
+ throw new Error('Gradient plot density legend currently requires position "right".');
127
+ }
128
+ if (args.title !== undefined && (typeof args.title !== "string" || args.title.length === 0)) {
129
+ throw new TypeError("Gradient plot density legend title must be a non-empty string.");
130
+ }
131
+ const ids = legendIds(args.owner);
132
+ 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" });
151
+ next = next._withMarkConfig(args.owner, {
152
+ ...next.markConfigs[args.owner],
153
+ gradientPlot: {
154
+ ...next.markConfigs[args.owner].gradientPlot,
155
+ guides: {
156
+ ...next.markConfigs[args.owner].gradientPlot.guides,
157
+ legend: {
158
+ title: args.title ?? "Relative density",
159
+ position: args.position ?? "right"
160
+ }
161
+ }
162
+ }
163
+ });
164
+ return next.rematerializeGradientPlotLegend({ owner: args.owner });
165
+ }
166
+ );
167
+
168
+ export function removeGradientPlotLegend(program, owner) {
169
+ const ids = legendIds(owner);
170
+ let next = program;
171
+ for (const id of Object.values(ids)) {
172
+ if (next.graphicSpec.objects[id] !== undefined) {
173
+ next = next.editGraphics({ target: id, remove: true });
174
+ }
175
+ }
176
+ return next;
177
+ }
@@ -0,0 +1,96 @@
1
+ import { action } from "../../core/action.js";
2
+ import { validateKeys } from "../../core/validation.js";
3
+ import { findDataset } from "../../selectors/datasets.js";
4
+ import { findSemanticScale } from "../../selectors/scales.js";
5
+ import {
6
+ GRADIENT_PLOT_OPTIONS,
7
+ gradientEncodingArgs,
8
+ resolveGradientAppearance,
9
+ resolveGradientCenter,
10
+ resolveGradientDensity,
11
+ resolveGradientGuides,
12
+ resolveGradientPosition,
13
+ resolveGradientWidth
14
+ } from "./options.js";
15
+ import {
16
+ normalizeGradientPositionTypes,
17
+ resolveGradientPlotId,
18
+ resolveGradientSourceLayer
19
+ } from "./resolve.js";
20
+
21
+ export const createGradientPlot = action(
22
+ {
23
+ op: "createGradientPlot",
24
+ description: "Create a categorical density gradient plot."
25
+ },
26
+ function (args = {}) {
27
+ validateKeys(args, GRADIENT_PLOT_OPTIONS, "createGradientPlot");
28
+ const id = resolveGradientPlotId(this, args.id);
29
+ const source = resolveGradientSourceLayer(this, args.target);
30
+ const data = args.data ?? source?.data ?? this.context.currentData ??
31
+ (this.semanticSpec.datasets.length === 1
32
+ ? this.semanticSpec.datasets[0].id
33
+ : undefined);
34
+ if (findDataset(this, data) === undefined) {
35
+ throw new Error("createGradientPlot requires data or one inferable dataset.");
36
+ }
37
+ const positions = normalizeGradientPositionTypes(
38
+ resolveGradientPosition(args.x, "x") ?? source?.encoding?.x,
39
+ resolveGradientPosition(args.y, "y") ?? source?.encoding?.y
40
+ );
41
+ if (
42
+ positions.x !== undefined && positions.y !== undefined &&
43
+ positions.orientation === undefined
44
+ ) {
45
+ throw new Error(
46
+ "createGradientPlot requires one categorical axis and one quantitative axis."
47
+ );
48
+ }
49
+ const density = resolveGradientDensity(args.density);
50
+ const width = resolveGradientWidth(args.width);
51
+ const gradient = resolveGradientAppearance(args.gradient);
52
+ const center = resolveGradientCenter(args.center);
53
+ const guides = resolveGradientGuides(args.guides);
54
+ const category = positions.orientation === "vertical"
55
+ ? positions.x
56
+ : positions.orientation === "horizontal"
57
+ ? positions.y
58
+ : undefined;
59
+ const categoryScaleId = typeof category?.scale === "string"
60
+ ? category.scale
61
+ : category?.scale?.id;
62
+ const categoryScale = findSemanticScale(this, categoryScaleId);
63
+ let next = categoryScale?.type === "point"
64
+ ? this.editScale({ id: categoryScaleId, type: "band" })
65
+ : this;
66
+ next = next.createRectMark({ id, data })._withMarkConfig(id, {
67
+ ...this.markConfigs[id],
68
+ gradientPlot: {
69
+ materialized: false,
70
+ density,
71
+ width,
72
+ gradient,
73
+ center,
74
+ guides
75
+ },
76
+ itemFilterable: false,
77
+ stroke: false,
78
+ strokeWidth: 0
79
+ });
80
+ if (positions.x !== undefined) {
81
+ next = next.encodeX({
82
+ ...gradientEncodingArgs(positions.x),
83
+ target: id,
84
+ coordinate: args.coordinate ?? source?.coordinate
85
+ });
86
+ }
87
+ if (positions.y !== undefined) {
88
+ next = next.encodeY({
89
+ ...gradientEncodingArgs(positions.y),
90
+ target: id,
91
+ coordinate: args.coordinate ?? source?.coordinate
92
+ });
93
+ }
94
+ return next.materializeGradientPlot({ id });
95
+ }
96
+ );
@@ -0,0 +1,153 @@
1
+ import { action } from "../../core/action.js";
2
+ import { isPlainObject } from "../../core/immutable.js";
3
+ import { validateKeys } from "../../core/validation.js";
4
+ import { planDerivedDataRevision } from "../../materialization/dataProvenance.js";
5
+ import { findDataset } from "../../selectors/datasets.js";
6
+ import { findLayer } from "../../selectors/layers.js";
7
+ import { removeGradientPlotLegend } from "./components.js";
8
+ import {
9
+ resolveGradientAppearance,
10
+ resolveGradientCenter,
11
+ resolveGradientDensity,
12
+ resolveGradientWidth
13
+ } from "./options.js";
14
+ import { resolveGradientOwner } from "./resolve.js";
15
+
16
+ const OPTIONS = Object.freeze([
17
+ "target", "density", "width", "gradient", "center"
18
+ ]);
19
+
20
+ function patch(value, label) {
21
+ if (!isPlainObject(value)) {
22
+ throw new TypeError(`editGradientPlot ${label} must be a plain object.`);
23
+ }
24
+ return value;
25
+ }
26
+
27
+ function removeCenter(program, id) {
28
+ let next = program;
29
+ if (findLayer(next, id) !== undefined) {
30
+ next = next.editSemantic({ property: `layer[${id}]`, remove: true });
31
+ }
32
+ if (next.graphicSpec.objects[id] !== undefined) {
33
+ next = next.editGraphics({ target: id, remove: true });
34
+ }
35
+ return next._withoutMaterializationConfig(["marks", id]);
36
+ }
37
+
38
+ export const editGradientPlot = action(
39
+ {
40
+ op: "editGradientPlot",
41
+ description: "Edit one stable categorical gradient plot."
42
+ },
43
+ function (args = {}) {
44
+ validateKeys(args, OPTIONS, "editGradientPlot");
45
+ if (!OPTIONS.slice(1).some(key => Object.hasOwn(args, key))) {
46
+ throw new Error("editGradientPlot requires at least one gradient-plot option.");
47
+ }
48
+ const owner = resolveGradientOwner(this, args.target, "editGradientPlot");
49
+ const current = this.markConfigs[owner.id].gradientPlot;
50
+ const density = Object.hasOwn(args, "density")
51
+ ? resolveGradientDensity(patch(args.density, "density"), current.density, "editGradientPlot")
52
+ : current.density;
53
+ const width = Object.hasOwn(args, "width")
54
+ ? resolveGradientWidth(patch(args.width, "width"), current.width, "editGradientPlot")
55
+ : current.width;
56
+ const gradient = Object.hasOwn(args, "gradient")
57
+ ? resolveGradientAppearance(
58
+ patch(args.gradient, "gradient"),
59
+ current.gradient,
60
+ "editGradientPlot"
61
+ )
62
+ : current.gradient;
63
+ const center = Object.hasOwn(args, "center")
64
+ ? resolveGradientCenter(
65
+ args.center === false ? false : patch(args.center, "center"),
66
+ current.center === false ? undefined : current.center,
67
+ "editGradientPlot"
68
+ )
69
+ : current.center;
70
+ const densityChanged = JSON.stringify(density) !== JSON.stringify(current.density);
71
+ const centerTypeChanged = center !== false && current.center !== false &&
72
+ center.type !== current.center.type;
73
+ const statistical = densityChanged || centerTypeChanged;
74
+ let next = this;
75
+ let profileId = current.profileId;
76
+ if (statistical) {
77
+ const consumers = [owner.id, ...(findLayer(next, current.centerId) ? [current.centerId] : [])];
78
+ const revision = planDerivedDataRevision(next, {
79
+ owner: owner.id,
80
+ role: "ProfileData",
81
+ previous: current.profileId,
82
+ consumers
83
+ });
84
+ profileId = revision.id;
85
+ next = next.createGradientProfileData({
86
+ id: profileId,
87
+ source: current.source,
88
+ category: current.category,
89
+ field: current.measure,
90
+ ...density,
91
+ center: center === false ? "median" : center.type
92
+ });
93
+ for (const rebind of revision.rebinds) next = next.rebindLayerData(rebind);
94
+ next = next.releaseDerivedData(revision.release);
95
+ }
96
+ const profile = findDataset(next, profileId);
97
+ next = next._withMarkConfig(owner.id, {
98
+ ...next.markConfigs[owner.id],
99
+ gradientPlot: {
100
+ ...current,
101
+ density,
102
+ width,
103
+ gradient,
104
+ center,
105
+ profileId,
106
+ intensityDomain: profile.transform[0].resolved.intensityDomain
107
+ }
108
+ });
109
+ const hadCenter = findLayer(next, current.centerId) !== undefined;
110
+ if (center === false && hadCenter) {
111
+ next = removeCenter(next, current.centerId);
112
+ } else if (center !== false && !hadCenter) {
113
+ const categoryEncoding = owner.encoding[current.orientation === "vertical" ? "x" : "y"];
114
+ const measureEncoding = owner.encoding[current.orientation === "vertical" ? "y" : "x"];
115
+ next = next.createGradientPlotCenter({
116
+ id: current.centerId,
117
+ owner: owner.id,
118
+ data: profileId,
119
+ category: current.category,
120
+ categoryType: current.categoryType,
121
+ coordinate: owner.coordinate,
122
+ categoryScale: categoryEncoding.scale,
123
+ measureScale: measureEncoding.scale,
124
+ orientation: current.orientation,
125
+ size: 1,
126
+ stroke: center.stroke,
127
+ strokeWidth: center.strokeWidth
128
+ });
129
+ } else if (center !== false) {
130
+ next = next
131
+ .encodeStroke({ target: current.centerId, value: center.stroke })
132
+ .encodeStrokeWidth({ target: current.centerId, value: center.strokeWidth });
133
+ }
134
+ next = next.materializeGradientPlotFill({ id: owner.id });
135
+ if (center !== false && findLayer(next, current.centerId) !== undefined) {
136
+ const categoryChannel = current.orientation === "vertical" ? "x" : "y";
137
+ const categoryScale = next.resolvedScales[owner.encoding[categoryChannel].scale];
138
+ next = next.materializeRuleSpan({
139
+ id: current.centerId,
140
+ orientation: current.orientation === "vertical" ? "horizontal" : "vertical",
141
+ size: Math.max(1, categoryScale.bandwidth * width.band - 16)
142
+ });
143
+ }
144
+ if (current.guides?.legend !== false && current.guides?.legend !== undefined) {
145
+ next = removeGradientPlotLegend(next, owner.id)
146
+ .createGradientPlotLegend({
147
+ owner: owner.id,
148
+ ...current.guides.legend
149
+ });
150
+ }
151
+ return next._withContext({ currentMark: owner.id, currentData: current.source });
152
+ }
153
+ );
@@ -0,0 +1,24 @@
1
+ import {
2
+ createGradientPlotCenter,
3
+ createGradientPlotLegend,
4
+ rematerializeGradientPlotLegend
5
+ } from "./components.js";
6
+ import { createGradientPlot } from "./create.js";
7
+ import { editGradientPlot } from "./edit.js";
8
+ import {
9
+ materializeGradientPlot,
10
+ materializeGradientPlotFill
11
+ } from "./materialize.js";
12
+ import { rebindGradientPlotProfile } from "./rebind.js";
13
+
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;
24
+ }