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
@@ -0,0 +1,102 @@
1
+ import { POSITION_ENCODING_CHANNELS } from "../core/vocabulary.js";
2
+ import { materializedLegendUsesScale } from "./legends.js";
3
+ import { buildMaterializationPlan } from "./planner.js";
4
+
5
+ const CARTESIAN_AXES = Object.freeze([
6
+ Object.freeze({ channel: "x", prefix: "X", lineObject: "xAxisLine" }),
7
+ Object.freeze({ channel: "y", prefix: "Y", lineObject: "yAxisLine" })
8
+ ]);
9
+ const POLAR_AXES = Object.freeze([
10
+ Object.freeze({ channel: "theta", prefix: "Theta" }),
11
+ Object.freeze({ channel: "radius", prefix: "Radial" })
12
+ ]);
13
+ const GRIDS = Object.freeze([
14
+ Object.freeze({ role: "horizontal", op: "rematerializeHorizontalGrid" }),
15
+ Object.freeze({ role: "vertical", op: "rematerializeVerticalGrid" }),
16
+ Object.freeze({ role: "theta", op: "rematerializeThetaGrid" }),
17
+ Object.freeze({ role: "radial", op: "rematerializeRadialGrid" })
18
+ ]);
19
+
20
+ function usesPositionalScale(program, id) {
21
+ return program.semanticSpec.layers.some(layer =>
22
+ POSITION_ENCODING_CHANNELS.some(
23
+ channel => layer.encoding?.[channel]?.scale === id
24
+ ) || (layer.encoding?.parallel?.dimensions ?? []).some(
25
+ dimension => dimension.scale === id
26
+ )
27
+ );
28
+ }
29
+
30
+ function usesRadialScale(program, id) {
31
+ return program.semanticSpec.layers.some(layer =>
32
+ layer.encoding?.radius?.scale === id
33
+ );
34
+ }
35
+
36
+ function semanticGuideUsesScale(program, id) {
37
+ return [
38
+ ...CARTESIAN_AXES.map(({ channel }) =>
39
+ program.semanticSpec.guides.axis?.[channel]
40
+ ),
41
+ ...POLAR_AXES.map(({ channel }) =>
42
+ program.semanticSpec.guides.axis?.[channel]
43
+ ),
44
+ ...GRIDS.map(({ role }) => program.semanticSpec.guides.grid?.[role])
45
+ ].some(guide => guide?.scale === id);
46
+ }
47
+
48
+ export function needsCanvasScaleRematerialization(program, scale) {
49
+ return (
50
+ (scale.range === "auto" ||
51
+ usesRadialScale(program, scale.id) ||
52
+ semanticGuideUsesScale(program, scale.id)) &&
53
+ program.resolvedScales[scale.id] !== undefined &&
54
+ usesPositionalScale(program, scale.id)
55
+ );
56
+ }
57
+
58
+ function componentEditOperation(prefix, component) {
59
+ const suffix = component[0].toUpperCase() + component.slice(1);
60
+ return `edit${prefix}Axis${suffix}`;
61
+ }
62
+
63
+ export function planScaleGuideRematerialization(program, id) {
64
+ const guides = [];
65
+ const objects = program.graphicSpec?.objects ?? {};
66
+ const guideConfigs = program.guideConfigs ?? {};
67
+ if (guideConfigs.axis?.parallel?.axes?.scales?.includes(id)) {
68
+ guides.push({
69
+ op: "rematerializeParallelAxes",
70
+ args: { target: guideConfigs.axis.parallel.axes.target }
71
+ });
72
+ }
73
+ for (const axis of CARTESIAN_AXES) {
74
+ if (
75
+ objects[axis.lineObject] &&
76
+ program.semanticSpec.guides.axis?.[axis.channel]?.scale === id
77
+ ) {
78
+ guides.push({ op: componentEditOperation(axis.prefix, "line") });
79
+ }
80
+ for (const component of ["ticks", "labels", "title"]) {
81
+ if (guideConfigs.axis?.[axis.channel]?.[component]?.scale === id) {
82
+ guides.push({ op: componentEditOperation(axis.prefix, component) });
83
+ }
84
+ }
85
+ }
86
+ for (const axis of POLAR_AXES) {
87
+ for (const component of ["line", "ticks", "labels", "title"]) {
88
+ if (guideConfigs.axis?.[axis.channel]?.[component]?.scale === id) {
89
+ guides.push({ op: componentEditOperation(axis.prefix, component) });
90
+ }
91
+ }
92
+ }
93
+ for (const grid of GRIDS) {
94
+ if (guideConfigs.grid?.[grid.role]?.scale === id) {
95
+ guides.push({ op: grid.op });
96
+ }
97
+ }
98
+ if (materializedLegendUsesScale(program, id)) {
99
+ guides.push({ op: "rematerializeLegend" });
100
+ }
101
+ return buildMaterializationPlan({ guides });
102
+ }
@@ -6,7 +6,7 @@ import {
6
6
  mapDiscretizedColors,
7
7
  mapOrdinalValues,
8
8
  mapSequentialColors
9
- } from "../../grammar/scales.js";
9
+ } from "../../grammar/scales/index.js";
10
10
 
11
11
  export function mapScaleConsumerValues(values, resolvedScale, channel) {
12
12
  if (channel === "color" && isDiscretizedColorScaleType(resolvedScale.type)) {
@@ -2,7 +2,7 @@ import {
2
2
  isTransformedScaleType,
3
3
  resolveContinuousDomain,
4
4
  resolveTransformedDomain
5
- } from "../../../grammar/scales.js";
5
+ } from "../../../grammar/scales/index.js";
6
6
 
7
7
  export function resolveSeriesLayoutDomain({
8
8
  id,
@@ -18,13 +18,14 @@ import {
18
18
  resolveShapeRange,
19
19
  resolveSizeRange,
20
20
  resolveStrokeDashRange,
21
+ resolveStrokeWidthRange,
21
22
  resolveTransformedDomain,
22
23
  SCALE_ROLES,
23
24
  validateLinearScaleType,
24
25
  validateOrdinalScaleType,
25
26
  validateScaleTypeForRole,
26
27
  validateTimeScaleType
27
- } from "../../grammar/scales.js";
28
+ } from "../../grammar/scales/index.js";
28
29
  import { resolveArcAutoPositionRange } from "./policies/arc.js";
29
30
  import {
30
31
  resolveBinnedBarDomain,
@@ -92,6 +93,7 @@ function resolveRange({
92
93
  if (channel === "shape") return resolveShapeRange(scale.range);
93
94
  if (channel === "size") return resolveSizeRange(scale.range);
94
95
  if (channel === "opacity") return resolveOpacityRange(scale.range);
96
+ if (channel === "strokeWidth") return resolveStrokeWidthRange(scale.range);
95
97
  if (isOrdinalOffset) return undefined;
96
98
  return resolveArcAutoPositionRange({
97
99
  consumers,
@@ -10,7 +10,7 @@ import { layoutSeriesPartition } from "../../../grammar/seriesLayout.js";
10
10
  import {
11
11
  readNominalField,
12
12
  readTemporalField
13
- } from "../../../grammar/scales.js";
13
+ } from "../../../grammar/scales/index.js";
14
14
  import { findSemanticScale } from "../../../selectors/scales.js";
15
15
  import { deriveHistogramSegments } from "../../bars/histogram.js";
16
16
  import {
@@ -4,7 +4,12 @@ import {
4
4
  } from "../../../grammar/areaSeries.js";
5
5
  import { deriveLineSeries } from "../../../grammar/lineSeries.js";
6
6
  import { findUpstreamTransform } from "../../dataProvenance.js";
7
- import { finalizeItems, uniqueFields } from "./common.js";
7
+ import {
8
+ channelMapFromRow,
9
+ finalizeItems,
10
+ ownFields,
11
+ uniqueFields
12
+ } from "./common.js";
8
13
 
9
14
  function rowsForSeries(rows, key) {
10
15
  const entries = Object.entries(key);
@@ -34,6 +39,27 @@ function seriesDefinitions(layer, rows, series) {
34
39
  }
35
40
 
36
41
  export function resolveLineItems(program, layer, dataset) {
42
+ if (layer.encoding?.parallel !== undefined) {
43
+ const parallel = layer.encoding.parallel;
44
+ const definitions = dataset.values.flatMap((row, index) => {
45
+ const incomplete = parallel.dimensions.some(dimension => {
46
+ const value = row[dimension.field];
47
+ return dimension.fieldType === "quantitative"
48
+ ? !Number.isFinite(value)
49
+ : typeof value !== "string" && !Number.isFinite(value);
50
+ });
51
+ if (incomplete && parallel.missing === "drop-row") return [];
52
+ return [{
53
+ key: parallel.key === undefined
54
+ ? `${layer.id}/row/source:${index}`
55
+ : `${layer.id}/row/${String(row[parallel.key])}`,
56
+ fields: ownFields(row),
57
+ channels: channelMapFromRow(row, layer),
58
+ members: [row]
59
+ }];
60
+ });
61
+ return finalizeItems(program, layer, "row", definitions, "path");
62
+ }
37
63
  const derived = deriveLineSeries(dataset.values, layer);
38
64
  return finalizeItems(
39
65
  program,
@@ -18,11 +18,7 @@ export function resolvePointItems(program, layer, dataset) {
18
18
  );
19
19
  if (
20
20
  !Array.isArray(graphic?.items) ||
21
- !completePosition ||
22
- (
23
- layer.encoding?.size?.scale === undefined &&
24
- !Number.isFinite(program.markConfigs[layer.id]?.radius)
25
- )
21
+ !completePosition
26
22
  ) {
27
23
  throw new Error(`Point mark "${layer.id}" is incomplete for selection.`);
28
24
  }
@@ -1,16 +1,37 @@
1
1
  import { resolveRectRows } from "../../rect.js";
2
2
  import {
3
+ channelMapFromRow,
3
4
  concreteProperties,
4
5
  finalizeItems,
5
6
  itemKey,
6
7
  ownFields
7
8
  } from "./common.js";
8
9
 
10
+ function resolveGradientPlotItems(program, layer, dataset) {
11
+ const graphic = program.graphicSpec.objects[layer.id];
12
+ return finalizeItems(
13
+ program,
14
+ layer,
15
+ "rect",
16
+ dataset.values.map((row, index) => ({
17
+ key: itemKey(layer, "rect", index),
18
+ fields: ownFields(row),
19
+ channels: channelMapFromRow(row, layer),
20
+ properties: concreteProperties(graphic.items[index]?.properties),
21
+ members: [row]
22
+ })),
23
+ "rect"
24
+ );
25
+ }
26
+
9
27
  export function resolveRectItems(program, layer, dataset) {
10
28
  const graphic = program.graphicSpec.objects[layer.id];
11
29
  if (graphic?.type !== "rect" || !Array.isArray(graphic.items)) {
12
30
  throw new Error(`Rect mark "${layer.id}" is incomplete for selection.`);
13
31
  }
32
+ if (program.markConfigs[layer.id]?.gradientPlot?.materialized === true) {
33
+ return resolveGradientPlotItems(program, layer, dataset);
34
+ }
14
35
  const rows = resolveRectRows(program, layer, dataset);
15
36
  const definitions = rows.map((item, graphicIndex) => ({
16
37
  key: itemKey(layer, "rect", item.sourceIndex),
@@ -7,7 +7,7 @@ import {
7
7
  validateUnitInterval
8
8
  } from "../../core/validation.js";
9
9
  import { validatePointShape } from "../../grammar/pointShapes.js";
10
- import { normalizeStrokeDashPattern } from "../../grammar/scales.js";
10
+ import { normalizeStrokeDashPattern } from "../../grammar/scales/index.js";
11
11
  import { DEFAULT_COLORS } from "../../theme/defaults.js";
12
12
 
13
13
  const DEFAULT_POINT_SIZE = 2;
@@ -80,19 +80,19 @@ export function normalizePointHighlightStyle(args) {
80
80
  };
81
81
  }
82
82
 
83
- function normalizeRectangularHighlightStyle(args, mark) {
84
- for (const option of ["shape", "size", "offset", "strokeDash"]) {
83
+ function normalizeRectangularHighlightStyle(args, mark, { offset = false } = {}) {
84
+ for (const option of ["shape", "size", "strokeDash"]) {
85
85
  if (args[option] !== undefined) {
86
86
  throw new Error(`${mark} highlight does not support ${option}.`);
87
87
  }
88
88
  }
89
+ if (!offset && args.offset !== undefined) {
90
+ throw new Error(`${mark} highlight does not support offset.`);
91
+ }
89
92
  if (args.color !== undefined && args.fill !== undefined) {
90
93
  throw new Error(`${mark} highlight accepts color or fill, not both.`);
91
94
  }
92
- const fill = validateNonEmptyString(
93
- args.fill ?? args.color ?? DEFAULT_COLORS.highlight,
94
- `${mark} highlight fill`
95
- );
95
+ const explicitFill = args.fill ?? args.color;
96
96
  const opacity = args.opacity === undefined
97
97
  ? undefined
98
98
  : validateUnitInterval(args.opacity, `${mark} highlight opacity`);
@@ -105,11 +105,22 @@ function normalizeRectangularHighlightStyle(args, mark) {
105
105
  if (strokeWidth !== undefined && stroke === undefined) {
106
106
  throw new Error(`${mark} highlight strokeWidth requires stroke.`);
107
107
  }
108
+ const hasAppearanceOverride = opacity !== undefined || stroke !== undefined ||
109
+ strokeWidth !== undefined || args.offset !== undefined;
110
+ const fill = explicitFill === undefined && hasAppearanceOverride
111
+ ? undefined
112
+ : validateNonEmptyString(
113
+ explicitFill ?? DEFAULT_COLORS.highlight,
114
+ `${mark} highlight fill`
115
+ );
108
116
  return {
109
- fill,
117
+ ...(fill === undefined ? {} : { fill }),
110
118
  ...(opacity === undefined ? {} : { opacity }),
111
119
  ...(stroke === undefined ? {} : { stroke }),
112
- ...(strokeWidth === undefined ? {} : { strokeWidth })
120
+ ...(strokeWidth === undefined ? {} : { strokeWidth }),
121
+ ...(offset && args.offset !== undefined
122
+ ? { offset: normalizeOffset(args.offset) }
123
+ : {})
113
124
  };
114
125
  }
115
126
 
@@ -118,7 +129,7 @@ export function normalizeBarHighlightStyle(args) {
118
129
  }
119
130
 
120
131
  export function normalizeRectHighlightStyle(args) {
121
- return normalizeRectangularHighlightStyle(args, "Rect");
132
+ return normalizeRectangularHighlightStyle(args, "Rect", { offset: true });
122
133
  }
123
134
 
124
135
  function rejectHighlightOptions(args, mark, options) {
@@ -120,13 +120,14 @@ function resolveSourceTextItems(program, layer, config) {
120
120
  }
121
121
  const items = resolveMarkItems(program, source.id);
122
122
  return items.flatMap(item => {
123
+ const anchor = sourceAnchor(program, source, item);
123
124
  const concrete = concreteItem(
124
125
  sourceTextConfig(config, source, item),
125
- sourceAnchor(program, source, item),
126
+ anchor,
126
127
  contentValue(layer.encoding.text, item, source),
127
128
  layer.encoding.text.format
128
129
  );
129
- return concrete === undefined ? [] : [concrete];
130
+ return concrete === undefined ? [] : [{ graphic: concrete, anchor }];
130
131
  });
131
132
  }
132
133
 
@@ -145,12 +146,19 @@ function resolveRowTextItems(program, layer, config) {
145
146
  contentValue(layer.encoding.text, row),
146
147
  layer.encoding.text.format
147
148
  );
148
- return concrete === undefined ? [] : [concrete];
149
+ return concrete === undefined
150
+ ? []
151
+ : [{ graphic: concrete, anchor: { x: x[index], y: y[index] } }];
149
152
  });
150
153
  }
151
154
 
152
- export function resolveTextGraphicItems(program, layer, config) {
155
+ export function resolveTextGraphicEntries(program, layer, config) {
153
156
  return layer.source === undefined
154
157
  ? resolveRowTextItems(program, layer, config)
155
158
  : resolveSourceTextItems(program, layer, config);
156
159
  }
160
+
161
+ export function resolveTextGraphicItems(program, layer, config) {
162
+ return resolveTextGraphicEntries(program, layer, config)
163
+ .map(entry => entry.graphic);
164
+ }
@@ -0,0 +1,33 @@
1
+ import {
2
+ isLinearGradientPaint,
3
+ resolveLinearGradientCoordinates,
4
+ validateFillPaint
5
+ } from "../../grammar/paint.js";
6
+
7
+ export function applyCanvasFill(context, fill, bounds, graphicId) {
8
+ validateFillPaint(fill, `${graphicId}.fill`);
9
+ if (!isLinearGradientPaint(fill)) {
10
+ context.fillStyle = fill;
11
+ return fill;
12
+ }
13
+ if (typeof context.createLinearGradient !== "function") {
14
+ throw new TypeError(
15
+ `Graphic "${graphicId}" requires Canvas context createLinearGradient().`
16
+ );
17
+ }
18
+ const coordinates = resolveLinearGradientCoordinates(fill, bounds);
19
+ const gradient = context.createLinearGradient(
20
+ coordinates.from.x,
21
+ coordinates.from.y,
22
+ coordinates.to.x,
23
+ coordinates.to.y
24
+ );
25
+ if (gradient === null || typeof gradient !== "object" || typeof gradient.addColorStop !== "function") {
26
+ throw new TypeError(
27
+ `Graphic "${graphicId}" requires a Canvas linear gradient with addColorStop().`
28
+ );
29
+ }
30
+ for (const stop of fill.stops) gradient.addColorStop(stop.offset, stop.color);
31
+ context.fillStyle = gradient;
32
+ return gradient;
33
+ }
@@ -120,6 +120,13 @@ export function render(program, context, { pixelRatio = 1 } = {}) {
120
120
 
121
121
  context.canvas.width = Math.round(width * pixelRatio);
122
122
  context.canvas.height = Math.round(height * pixelRatio);
123
+ if (
124
+ context.canvas.style !== null &&
125
+ typeof context.canvas.style === "object"
126
+ ) {
127
+ context.canvas.style.width = `${width}px`;
128
+ context.canvas.style.height = `${height}px`;
129
+ }
123
130
  context.save();
124
131
 
125
132
  try {
@@ -1,6 +1,8 @@
1
1
  import { requireFiniteProperty } from "./validation.js";
2
2
  import { validateConcreteGraphicProperties } from
3
3
  "../../grammar/schemas/concreteGraphic.js";
4
+ import { resolvePathCommandBounds } from "../../grammar/schemas/graphicBounds.js";
5
+ import { applyCanvasFill } from "./fill.js";
4
6
 
5
7
  function drawPath(context, child, collectionId) {
6
8
  const properties = child.properties ?? {};
@@ -19,9 +21,6 @@ function drawPath(context, child, collectionId) {
19
21
  if (!hasFill && !hasStroke) {
20
22
  throw new Error(`Graphic "${graphicId}" requires a fill or stroke property.`);
21
23
  }
22
- if (hasFill && typeof properties.fill !== "string") {
23
- throw new Error(`Graphic "${graphicId}" requires a string fill property.`);
24
- }
25
24
  if (hasFill && commands.at(-1).op !== "Z") {
26
25
  throw new Error(`Graphic "${graphicId}" requires a final Z command when filled.`);
27
26
  }
@@ -75,7 +74,11 @@ function drawPath(context, child, collectionId) {
75
74
  }
76
75
 
77
76
  if (hasFill) {
78
- context.fillStyle = properties.fill;
77
+ const bounds = resolvePathCommandBounds(commands);
78
+ if (bounds === undefined) {
79
+ throw new Error(`Graphic "${graphicId}" requires finite path fill bounds.`);
80
+ }
81
+ applyCanvasFill(context, properties.fill, bounds, graphicId);
79
82
  context.fill();
80
83
  }
81
84
  if (hasStroke) {
@@ -1,6 +1,7 @@
1
1
  import { requireFiniteProperty } from "./validation.js";
2
2
  import { validateConcreteGraphicProperties } from
3
3
  "../../grammar/schemas/concreteGraphic.js";
4
+ import { applyCanvasFill } from "./fill.js";
4
5
 
5
6
  function drawRect(context, child, collectionId) {
6
7
  const properties = child.properties ?? {};
@@ -21,8 +22,8 @@ function drawRect(context, child, collectionId) {
21
22
  `Graphic "${graphicId}" requires non-negative rect dimensions and strokeWidth.`
22
23
  );
23
24
  }
24
- if (typeof properties.fill !== "string") {
25
- throw new Error(`Graphic "${graphicId}" requires a string fill property.`);
25
+ if (properties.fill === undefined) {
26
+ throw new Error(`Graphic "${graphicId}" requires a fill property.`);
26
27
  }
27
28
  if (typeof properties.stroke !== "string") {
28
29
  throw new Error(`Graphic "${graphicId}" requires a string stroke property.`);
@@ -34,7 +35,12 @@ function drawRect(context, child, collectionId) {
34
35
  }
35
36
 
36
37
  context.globalAlpha = opacity;
37
- context.fillStyle = properties.fill;
38
+ applyCanvasFill(context, properties.fill, {
39
+ left: x,
40
+ right: x + width,
41
+ top: y,
42
+ bottom: y + height
43
+ }, graphicId);
38
44
  context.fillRect(x, y, width, height);
39
45
  context.strokeStyle = properties.stroke;
40
46
  context.lineWidth = strokeWidth;
@@ -2,6 +2,18 @@ import { requireFiniteProperty, requireStringProperty } from "./validation.js";
2
2
  import { validateConcreteGraphicProperties } from
3
3
  "../../grammar/schemas/concreteGraphic.js";
4
4
 
5
+ const MIN_CANVAS_NUMERIC_FONT_WEIGHT = 100;
6
+ const MAX_CANVAS_NUMERIC_FONT_WEIGHT = 900;
7
+
8
+ export function normalizeCanvasFontWeight(fontWeight) {
9
+ if (typeof fontWeight !== "number") return fontWeight;
10
+ const rounded = Math.round(fontWeight / 100) * 100;
11
+ return Math.min(
12
+ MAX_CANVAS_NUMERIC_FONT_WEIGHT,
13
+ Math.max(MIN_CANVAS_NUMERIC_FONT_WEIGHT, rounded)
14
+ );
15
+ }
16
+
5
17
  function drawText(context, child, collectionId) {
6
18
  const properties = child.properties ?? {};
7
19
  const graphicId = child.id ?? collectionId;
@@ -73,7 +85,8 @@ function drawText(context, child, collectionId) {
73
85
  try {
74
86
  context.fillStyle = fill;
75
87
  context.globalAlpha = opacity;
76
- context.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
88
+ const canvasFontWeight = normalizeCanvasFontWeight(fontWeight);
89
+ context.font = `${canvasFontWeight} ${fontSize}px ${fontFamily}`;
77
90
  context.textAlign = textAlign;
78
91
  context.textBaseline = textBaseline;
79
92
  context.translate(x, y);
@@ -6,6 +6,10 @@ export function hasDataset(program, id) {
6
6
  return findDataset(program, id) !== undefined;
7
7
  }
8
8
 
9
+ export function findDatasetConsumer(program, source) {
10
+ return program.semanticSpec.datasets.find(dataset => dataset.source === source);
11
+ }
12
+
9
13
  export function requireDataset(program, id, label = `Dataset "${id}"`) {
10
14
  const dataset = findDataset(program, id);
11
15
  if (dataset === undefined) throw new Error(`${label} does not exist.`);
@@ -1,10 +1,14 @@
1
1
  export const DEFAULT_FONT_FAMILY = "sans-serif";
2
2
 
3
+ export const DEFAULT_POINT_RADIUS = 3;
4
+
3
5
  export const DEFAULT_COLORS = Object.freeze({
4
6
  mark: "#4c78a8",
5
7
  text: "#334155",
6
8
  strongText: "#0f172a",
7
9
  mutedText: "#64748b",
10
+ axis: "#475569",
11
+ axisTitle: "#1e293b",
8
12
  grid: "#e2e8f0",
9
13
  border: "#cbd5e1",
10
14
  sizeSymbol: "#94a3b8",
@@ -2,6 +2,12 @@ import type { ActionOptions } from "./program.js";
2
2
  import { ChartProgram } from "./program.js";
3
3
 
4
4
  export { ChartProgram };
5
+ export type {
6
+ FillPaint,
7
+ LinearGradientPaint,
8
+ LinearGradientPoint,
9
+ LinearGradientStop
10
+ } from "./program.js";
5
11
 
6
12
  export interface ActionMetadata {
7
13
  op: string;
@@ -12,4 +18,7 @@ export interface ActionMetadata {
12
18
  export function action<TOptions extends ActionOptions = ActionOptions>(
13
19
  metadata: ActionMetadata,
14
20
  implementation: (this: ChartProgram, options: TOptions) => ChartProgram
15
- ): (this: ChartProgram, options?: TOptions) => ChartProgram;
21
+ ): <TProgram extends ChartProgram>(
22
+ this: TProgram,
23
+ options?: TOptions
24
+ ) => TProgram;