ggaction 0.0.2 → 0.0.4

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 (242) hide show
  1. package/CHANGELOG.md +55 -2
  2. package/README.md +95 -72
  3. package/package.json +15 -5
  4. package/src/actions/boxPlots/components.js +3 -3
  5. package/src/actions/boxPlots/edit.js +277 -0
  6. package/src/actions/boxPlots/index.js +2 -0
  7. package/src/actions/boxPlots/materialize.js +12 -1
  8. package/src/actions/boxPlots/options.js +25 -14
  9. package/src/actions/canvas/actions.js +21 -17
  10. package/src/actions/composition/actions.js +247 -0
  11. package/src/actions/composition/index.js +1 -0
  12. package/src/actions/coordinates/actions.js +2 -5
  13. package/src/actions/data/density.js +7 -1
  14. package/src/actions/data/filter.js +3 -1
  15. package/src/actions/data/regression.js +1 -1
  16. package/src/actions/encodings/appearance.js +12 -1
  17. package/src/actions/encodings/barWidth.js +5 -3
  18. package/src/actions/encodings/color/continuous.js +137 -0
  19. package/src/actions/encodings/color/index.js +151 -0
  20. package/src/actions/encodings/color/layout.js +91 -0
  21. package/src/actions/encodings/color/policy.js +88 -0
  22. package/src/actions/encodings/index.js +4 -2
  23. package/src/actions/encodings/offset.js +91 -73
  24. package/src/actions/encodings/position/apply.js +66 -0
  25. package/src/actions/encodings/position/index.js +130 -0
  26. package/src/actions/encodings/position/policies/arc.js +26 -0
  27. package/src/actions/encodings/position/policies/bar.js +9 -5
  28. package/src/actions/encodings/position/policies/index.js +5 -1
  29. package/src/actions/encodings/position/policies/line.js +20 -3
  30. package/src/actions/encodings/position/resolve.js +58 -9
  31. package/src/actions/encodings/ranged.js +30 -8
  32. package/src/actions/encodings/shared.js +17 -21
  33. package/src/actions/encodings/strokeDash.js +1 -1
  34. package/src/actions/encodings/text.js +73 -0
  35. package/src/actions/errorBands/create.js +37 -29
  36. package/src/actions/errorBands/edit.js +206 -0
  37. package/src/actions/errorBands/index.js +9 -0
  38. package/src/actions/errorBands/options.js +39 -0
  39. package/src/actions/errorBars/create.js +50 -43
  40. package/src/actions/errorBars/edit.js +130 -0
  41. package/src/actions/errorBars/index.js +3 -0
  42. package/src/actions/errorBars/options.js +52 -0
  43. package/src/actions/facets/actions.js +162 -0
  44. package/src/actions/facets/derive.js +200 -0
  45. package/src/actions/facets/guides.js +16 -0
  46. package/src/actions/facets/index.js +1 -0
  47. package/src/actions/facets/replay.js +69 -0
  48. package/src/actions/guides/applicability.js +113 -0
  49. package/src/actions/guides/axes/axes.js +70 -16
  50. package/src/actions/guides/axes/edit.js +126 -0
  51. package/src/actions/guides/axes/index.js +12 -0
  52. package/src/actions/guides/axes/labels.js +22 -19
  53. package/src/actions/guides/axes/remove.js +60 -0
  54. package/src/actions/guides/axes/tickGroups.js +7 -5
  55. package/src/actions/guides/axes/ticks.js +10 -5
  56. package/src/actions/guides/axes/titles.js +8 -6
  57. package/src/actions/guides/grids/grid.js +144 -15
  58. package/src/actions/guides/grids/resolve.js +6 -14
  59. package/src/actions/guides/guides.js +17 -75
  60. package/src/actions/guides/index.js +6 -0
  61. package/src/actions/guides/legends/categorical/actions.js +48 -9
  62. package/src/actions/guides/legends/categorical/components.js +16 -16
  63. package/src/actions/guides/legends/categorical/index.js +14 -14
  64. package/src/actions/guides/legends/categorical/layout.js +7 -3
  65. package/src/actions/guides/legends/categorical/options.js +2 -5
  66. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  67. package/src/actions/guides/legends/categorical/symbols.js +14 -14
  68. package/src/actions/guides/legends/continuous/common.js +7 -7
  69. package/src/actions/guides/legends/continuous/interval.js +6 -6
  70. package/src/actions/guides/legends/continuous/opacity.js +1 -1
  71. package/src/actions/guides/legends/edit.js +4 -26
  72. package/src/actions/guides/legends/focused.js +63 -0
  73. package/src/actions/guides/legends/index.js +4 -0
  74. package/src/actions/guides/legends/remove.js +46 -0
  75. package/src/actions/guides/legends/size.js +13 -15
  76. package/src/actions/guides/legends/target.js +25 -0
  77. package/src/actions/guides/polar/axes/facade.js +219 -0
  78. package/src/actions/guides/polar/axes/index.js +55 -0
  79. package/src/actions/guides/polar/axes/labels.js +188 -0
  80. package/src/actions/guides/polar/axes/lines.js +113 -0
  81. package/src/actions/guides/polar/axes/shared.js +112 -0
  82. package/src/actions/guides/polar/axes/ticks.js +164 -0
  83. package/src/actions/guides/polar/axes/titles.js +165 -0
  84. package/src/actions/guides/polar/grids.js +252 -0
  85. package/src/actions/guides/polar/index.js +2 -0
  86. package/src/actions/guides/polar/resolve.js +269 -0
  87. package/src/actions/guides/tickValues.js +1 -1
  88. package/src/actions/index.js +4 -0
  89. package/src/actions/intervals/resolve.js +1 -1
  90. package/src/actions/marks/arc/actions.js +239 -0
  91. package/src/actions/marks/arc/index.js +1 -0
  92. package/src/actions/marks/{area.js → area/actions.js} +51 -38
  93. package/src/actions/marks/area/index.js +4 -0
  94. package/src/actions/marks/bar/create.js +26 -5
  95. package/src/actions/marks/bar/edit.js +1 -1
  96. package/src/actions/marks/bar/materialize.js +25 -31
  97. package/src/actions/marks/index.js +12 -4
  98. package/src/actions/marks/lifecycle.js +27 -0
  99. package/src/actions/marks/line/actions.js +373 -0
  100. package/src/actions/marks/line/index.js +1 -0
  101. package/src/actions/marks/{point.js → point/actions.js} +67 -108
  102. package/src/actions/marks/point/index.js +1 -0
  103. package/src/actions/marks/rect/actions.js +165 -0
  104. package/src/actions/marks/rect/index.js +1 -0
  105. package/src/actions/marks/remove.js +214 -0
  106. package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
  107. package/src/actions/marks/rule/index.js +1 -0
  108. package/src/actions/marks/shared.js +156 -25
  109. package/src/actions/marks/text/actions.js +192 -0
  110. package/src/actions/marks/text/index.js +1 -0
  111. package/src/actions/primitives/createGraphics.js +13 -6
  112. package/src/actions/primitives/editGraphics.js +10 -3
  113. package/src/actions/primitives/semantic.js +26 -3
  114. package/src/actions/primitives/semanticValue.js +26 -4
  115. package/src/actions/regression/components.js +2 -2
  116. package/src/actions/regression/create.js +19 -2
  117. package/src/actions/regression/edit.js +247 -0
  118. package/src/actions/regression/index.js +3 -0
  119. package/src/actions/regression/resolve.js +3 -1
  120. package/src/actions/scales/consumers/common.js +91 -0
  121. package/src/actions/scales/consumers/families.js +47 -0
  122. package/src/actions/scales/consumers/index.js +41 -0
  123. package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +14 -127
  124. package/src/actions/scales/create.js +1 -1
  125. package/src/actions/scales/definitions.js +15 -6
  126. package/src/actions/scales/edit.js +45 -22
  127. package/src/actions/scales/materialize.js +58 -409
  128. package/src/actions/selection/actions.js +38 -24
  129. package/src/actions/selection/index.js +2 -0
  130. package/src/actions/titles/actions.js +63 -2
  131. package/src/actions/titles/index.js +2 -0
  132. package/src/actions/titles/resolve.js +12 -17
  133. package/src/composition.js +10 -0
  134. package/src/core/ChartProgram.js +49 -65
  135. package/src/core/action.js +10 -0
  136. package/src/core/compositionState.js +169 -0
  137. package/src/core/materializationState.js +65 -0
  138. package/src/core/programState.js +28 -0
  139. package/src/core/validation.js +16 -0
  140. package/src/core/vocabulary.js +154 -4
  141. package/src/grammar/arcs.js +154 -0
  142. package/src/grammar/areaSeries.js +1 -1
  143. package/src/grammar/bars/aggregate.js +3 -3
  144. package/src/grammar/bars/geometry.js +3 -3
  145. package/src/grammar/bars/policy.js +7 -1
  146. package/src/grammar/coordinates.js +3 -7
  147. package/src/grammar/density.js +21 -2
  148. package/src/grammar/facets/dependencies.js +185 -0
  149. package/src/grammar/facets/guides.js +257 -0
  150. package/src/grammar/facets/index.js +122 -0
  151. package/src/grammar/facets/scales.js +254 -0
  152. package/src/grammar/interval.js +2 -96
  153. package/src/grammar/lineSeries.js +112 -4
  154. package/src/grammar/palettes.js +5 -7
  155. package/src/grammar/polar.js +102 -0
  156. package/src/grammar/polarGuides.js +243 -0
  157. package/src/grammar/polarLineCommands.js +43 -0
  158. package/src/grammar/polarPaths.js +161 -0
  159. package/src/grammar/positionCompatibility.js +18 -2
  160. package/src/grammar/rects.js +26 -0
  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 +1 -1
  166. package/src/grammar/scales/color.js +4 -2
  167. package/src/grammar/scales/continuous.js +6 -1
  168. package/src/grammar/scales/fields.js +7 -0
  169. package/src/grammar/scales/ordinal.js +5 -2
  170. package/src/grammar/scales/policies.js +1 -1
  171. package/src/grammar/schemas/graphic.js +1 -1
  172. package/src/grammar/schemas/graphicBounds.js +64 -9
  173. package/src/grammar/schemas/graphicTree.js +44 -10
  174. package/src/grammar/schemas/semanticPath.js +31 -9
  175. package/src/grammar/seriesLayout.js +13 -2
  176. package/src/grammar/statistics/studentT.js +98 -0
  177. package/src/grammar/text.js +71 -0
  178. package/src/grammar/ticks.js +50 -13
  179. package/src/grammar/transforms.js +1 -1
  180. package/src/index.js +1 -0
  181. package/src/layout/composition.js +310 -0
  182. package/src/layout/facets.js +131 -0
  183. package/src/layout/title.js +6 -0
  184. package/src/materialization/bars/aggregate.js +6 -3
  185. package/src/materialization/bars/grouped.js +75 -37
  186. package/src/materialization/bars/histogram.js +1 -1
  187. package/src/materialization/bars/ranged.js +1 -1
  188. package/src/materialization/composition.js +152 -0
  189. package/src/materialization/compositionSnapshot.js +114 -0
  190. package/src/materialization/density.js +1 -1
  191. package/src/materialization/dependencies.js +47 -73
  192. package/src/materialization/encodings.js +14 -28
  193. package/src/materialization/facetGuides/index.js +2 -0
  194. package/src/materialization/facetGuides/legacyCategorical.js +125 -0
  195. package/src/materialization/facetGuides/placement.js +126 -0
  196. package/src/materialization/facetGuides/preparation.js +188 -0
  197. package/src/materialization/facets.js +214 -0
  198. package/src/materialization/graphicHierarchy.js +15 -5
  199. package/src/materialization/guides/resources.js +46 -0
  200. package/src/materialization/marks/capabilities.js +141 -0
  201. package/src/materialization/marks/index.js +105 -0
  202. package/src/materialization/marks/policies.js +87 -0
  203. package/src/materialization/planner.js +105 -12
  204. package/src/materialization/rect.js +138 -0
  205. package/src/materialization/rectConfig.js +38 -0
  206. package/src/materialization/rowEncoding.js +33 -0
  207. package/src/materialization/scaleGuideDependencies.js +94 -0
  208. package/src/materialization/scales/map.js +40 -0
  209. package/src/materialization/scales/policies/arc.js +37 -0
  210. package/src/materialization/scales/policies/bar.js +132 -0
  211. package/src/materialization/scales/policies/series.js +76 -0
  212. package/src/materialization/scales/resolve.js +326 -0
  213. package/src/materialization/selection/items/arc.js +29 -0
  214. package/src/materialization/selection/items/bar.js +255 -0
  215. package/src/materialization/selection/items/common.js +131 -0
  216. package/src/materialization/selection/items/index.js +6 -0
  217. package/src/materialization/selection/items/path.js +59 -0
  218. package/src/materialization/selection/items/point.js +55 -0
  219. package/src/materialization/selection/items/rect.js +23 -0
  220. package/src/materialization/selection/items/rule.js +25 -0
  221. package/src/materialization/selection/policies/arc.js +10 -0
  222. package/src/materialization/selection/policies/area.js +1 -1
  223. package/src/materialization/selection/policies/bar.js +1 -1
  224. package/src/materialization/selection/policies/index.js +5 -1
  225. package/src/materialization/selection/policies/line.js +1 -1
  226. package/src/materialization/selection/policies/point.js +1 -1
  227. package/src/materialization/selection/policies/rect.js +10 -0
  228. package/src/materialization/selection/policies/rule.js +1 -1
  229. package/src/materialization/selection/styles.js +25 -17
  230. package/src/materialization/text.js +156 -0
  231. package/src/renderers/canvas/index.js +68 -22
  232. package/src/renderers/canvas/text.js +14 -1
  233. package/types/extension.d.ts +5 -1
  234. package/types/index.d.ts +56 -1
  235. package/types/program.d.ts +664 -27
  236. package/src/actions/encodings/color.js +0 -329
  237. package/src/actions/encodings/position.js +0 -176
  238. package/src/actions/marks/line.js +0 -248
  239. package/src/grammar/regression.js +0 -578
  240. package/src/grammar/scales.js +0 -1
  241. package/src/materialization/marks.js +0 -110
  242. package/src/materialization/selection/items.js +0 -503
@@ -0,0 +1,131 @@
1
+ import { cloneAndFreeze, isPlainObject } from "../../../core/immutable.js";
2
+ import { MARK_GRAPHIC_PROPERTIES } from "../../../grammar/markSelection.js";
3
+ import { unionConcreteGraphicBounds } from
4
+ "../../../grammar/schemas/graphicBounds.js";
5
+
6
+ export function itemKey(layer, grain, index) {
7
+ return `${layer.id}/${grain}/${index}`;
8
+ }
9
+
10
+ function graphicId(layer, index) {
11
+ return `${layer.id}:${index}`;
12
+ }
13
+
14
+ function encodingValue(row, encoding) {
15
+ if (encoding === undefined) return undefined;
16
+ if (Object.hasOwn(encoding, "field")) return row?.[encoding.field];
17
+ return encoding.datum;
18
+ }
19
+
20
+ export function channelMapFromRow(row, layer) {
21
+ return Object.fromEntries(
22
+ Object.entries(layer.encoding ?? {})
23
+ .map(([channel, encoding]) => [channel, encodingValue(row, encoding)])
24
+ .filter(([, value]) => value !== undefined)
25
+ );
26
+ }
27
+
28
+ export function ownFields(row) {
29
+ return isPlainObject(row) ? { ...row } : {};
30
+ }
31
+
32
+ export function uniqueFields(rows) {
33
+ if (rows.length === 0) return {};
34
+ const fields = new Set(rows.flatMap(row =>
35
+ isPlainObject(row) ? Object.keys(row) : []
36
+ ));
37
+ return Object.fromEntries([...fields].flatMap(field => {
38
+ if (!rows.every(row => isPlainObject(row) && Object.hasOwn(row, field))) {
39
+ return [];
40
+ }
41
+ const value = rows[0][field];
42
+ return rows.every(row => row[field] === value) ? [[field, value]] : [];
43
+ }));
44
+ }
45
+
46
+ function requireResolvedGraphic(program, layer, type) {
47
+ const graphic = program.graphicSpec.objects[layer.id];
48
+ const compatibleCollection = graphic?.type === "collection" &&
49
+ graphic.items?.every(child => child.type === type);
50
+ if (
51
+ (graphic?.type !== type && !compatibleCollection) ||
52
+ !Array.isArray(graphic.items)
53
+ ) {
54
+ throw new Error(
55
+ `Mark "${layer.id}" requires a materialized ${type} collection for selection.`
56
+ );
57
+ }
58
+ return graphic;
59
+ }
60
+
61
+ const SELECTABLE_GRAPHIC_PROPERTIES = new Set(MARK_GRAPHIC_PROPERTIES);
62
+
63
+ export function concreteProperties(properties) {
64
+ return Object.fromEntries(
65
+ Object.entries(properties ?? {}).filter(([key, value]) =>
66
+ SELECTABLE_GRAPHIC_PROPERTIES.has(key) &&
67
+ (Number.isFinite(value) || typeof value === "string")
68
+ )
69
+ );
70
+ }
71
+
72
+ function sharedConcreteProperties(items) {
73
+ if (items.length === 0) return {};
74
+ const shared = concreteProperties(items[0].properties);
75
+ for (const key of Object.keys(shared)) {
76
+ if (!items.every(child => child.properties?.[key] === shared[key])) {
77
+ delete shared[key];
78
+ }
79
+ }
80
+ return shared;
81
+ }
82
+
83
+ function collectionBounds(program, items) {
84
+ const bounds = unionConcreteGraphicBounds(
85
+ program.graphicSpec,
86
+ items.map(item => item.id)
87
+ );
88
+ if (bounds === undefined) return {};
89
+ const { left, top, right, bottom } = bounds;
90
+ return { x: left, y: top, width: right - left, height: bottom - top };
91
+ }
92
+
93
+ export function finalizeItems(program, layer, grain, definitions, graphicType) {
94
+ const graphic = requireResolvedGraphic(program, layer, graphicType);
95
+ const referenced = definitions.flatMap((definition, index) =>
96
+ definition.graphicIndices ?? [index]
97
+ );
98
+ if (
99
+ referenced.some(index => graphic.items[index] === undefined) ||
100
+ (definitions.every(definition => definition.graphicIndices === undefined) &&
101
+ graphic.items.length !== definitions.length)
102
+ ) {
103
+ throw new Error(
104
+ `Mark "${layer.id}" item count does not match its materialized graphics.`
105
+ );
106
+ }
107
+ return Object.freeze(definitions.map((definition, index) => {
108
+ const indices = definition.graphicIndices ?? [index];
109
+ const items = indices.map(childIndex => graphic.items[childIndex]);
110
+ const properties = definition.properties ?? (
111
+ items.length === 1
112
+ ? concreteProperties(items[0].properties)
113
+ : {
114
+ ...sharedConcreteProperties(items),
115
+ ...collectionBounds(program, items)
116
+ }
117
+ );
118
+ return Object.freeze({
119
+ key: definition.key ?? itemKey(layer, grain, index),
120
+ layer: layer.id,
121
+ markType: layer.mark.type,
122
+ fields: cloneAndFreeze(definition.fields),
123
+ channels: cloneAndFreeze(definition.channels),
124
+ properties: cloneAndFreeze(properties),
125
+ members: Object.freeze([...definition.members]),
126
+ graphicIds: Object.freeze(items.map((child, childOffset) =>
127
+ child.id ?? graphicId(layer, indices[childOffset])
128
+ ))
129
+ });
130
+ }));
131
+ }
@@ -0,0 +1,6 @@
1
+ export { resolveAreaItems, resolveLineItems } from "./path.js";
2
+ export { resolveArcItems } from "./arc.js";
3
+ export { resolveBarItems } from "./bar.js";
4
+ export { resolvePointItems } from "./point.js";
5
+ export { resolveRuleItems } from "./rule.js";
6
+ export { resolveRectItems } from "./rect.js";
@@ -0,0 +1,59 @@
1
+ import {
2
+ deriveAreaSeries,
3
+ deriveDensityAreaSeries
4
+ } from "../../../grammar/areaSeries.js";
5
+ import { deriveLineSeries } from "../../../grammar/lineSeries.js";
6
+ import { findUpstreamTransform } from "../../dataProvenance.js";
7
+ import { finalizeItems, uniqueFields } from "./common.js";
8
+
9
+ function rowsForSeries(rows, key) {
10
+ const entries = Object.entries(key);
11
+ return entries.length === 0
12
+ ? rows
13
+ : rows.filter(row => entries.every(([field, value]) => row[field] === value));
14
+ }
15
+
16
+ function seriesDefinitions(layer, rows, series) {
17
+ return series.map(item => {
18
+ const members = rowsForSeries(rows, item.key);
19
+ const fields = { ...uniqueFields(members), ...item.key };
20
+ const channels = Object.fromEntries(
21
+ Object.entries(layer.encoding ?? {}).flatMap(([channel, encoding]) => {
22
+ if (encoding.field === undefined) {
23
+ return Object.hasOwn(encoding, "datum")
24
+ ? [[channel, encoding.datum]]
25
+ : [];
26
+ }
27
+ return Object.hasOwn(fields, encoding.field)
28
+ ? [[channel, fields[encoding.field]]]
29
+ : [];
30
+ })
31
+ );
32
+ return { fields, channels, members };
33
+ });
34
+ }
35
+
36
+ export function resolveLineItems(program, layer, dataset) {
37
+ const derived = deriveLineSeries(dataset.values, layer);
38
+ return finalizeItems(
39
+ program,
40
+ layer,
41
+ "series",
42
+ seriesDefinitions(layer, dataset.values, derived.series),
43
+ "path"
44
+ );
45
+ }
46
+
47
+ export function resolveAreaItems(program, layer, dataset) {
48
+ const transform = findUpstreamTransform(program, dataset, "density");
49
+ const derived = transform === undefined
50
+ ? deriveAreaSeries(dataset.values, layer)
51
+ : deriveDensityAreaSeries(dataset.values, layer, transform);
52
+ return finalizeItems(
53
+ program,
54
+ layer,
55
+ "series",
56
+ seriesDefinitions(layer, dataset.values, derived.series),
57
+ "path"
58
+ );
59
+ }
@@ -0,0 +1,55 @@
1
+ import { selectMarkItemKeys } from "../../../grammar/markSelection.js";
2
+ import {
3
+ channelMapFromRow,
4
+ concreteProperties,
5
+ finalizeItems,
6
+ itemKey,
7
+ ownFields
8
+ } from "./common.js";
9
+
10
+ export function resolvePointItems(program, layer, dataset) {
11
+ const graphic = program.graphicSpec.objects[layer.id];
12
+ const completePosition = (
13
+ layer.encoding?.x?.scale !== undefined &&
14
+ layer.encoding?.y?.scale !== undefined
15
+ ) || (
16
+ layer.encoding?.theta?.scale !== undefined &&
17
+ layer.encoding?.radius?.scale !== undefined
18
+ );
19
+ if (
20
+ !Array.isArray(graphic?.items) ||
21
+ !completePosition ||
22
+ (
23
+ layer.encoding?.size?.scale === undefined &&
24
+ !Number.isFinite(program.markConfigs[layer.id]?.radius)
25
+ )
26
+ ) {
27
+ throw new Error(`Point mark "${layer.id}" is incomplete for selection.`);
28
+ }
29
+ let definitions = dataset.values.map((row, index) => ({
30
+ key: itemKey(layer, "point", index),
31
+ fields: ownFields(row),
32
+ channels: channelMapFromRow(row, layer),
33
+ properties: concreteProperties(graphic.items[index]?.properties),
34
+ members: [row]
35
+ }));
36
+ for (const config of Object.values(
37
+ program.materializationConfigs.highlights ?? {}
38
+ )) {
39
+ if (config.target !== layer.id || config.bringToFront !== true) continue;
40
+ const selection = program.materializationConfigs.selections?.[config.selection];
41
+ if (selection?.target !== layer.id) continue;
42
+ const selected = new Set(selectMarkItemKeys(definitions, selection.selector));
43
+ definitions = [
44
+ ...definitions.filter(definition => !selected.has(definition.key)),
45
+ ...definitions.filter(definition => selected.has(definition.key))
46
+ ];
47
+ }
48
+ return finalizeItems(
49
+ program,
50
+ layer,
51
+ "point",
52
+ definitions,
53
+ program.graphicSpec.objects[layer.id]?.type
54
+ );
55
+ }
@@ -0,0 +1,23 @@
1
+ import { resolveRectRows } from "../../rect.js";
2
+ import {
3
+ concreteProperties,
4
+ finalizeItems,
5
+ itemKey,
6
+ ownFields
7
+ } from "./common.js";
8
+
9
+ export function resolveRectItems(program, layer, dataset) {
10
+ const graphic = program.graphicSpec.objects[layer.id];
11
+ if (graphic?.type !== "rect" || !Array.isArray(graphic.items)) {
12
+ throw new Error(`Rect mark "${layer.id}" is incomplete for selection.`);
13
+ }
14
+ const rows = resolveRectRows(program, layer, dataset);
15
+ const definitions = rows.map((item, graphicIndex) => ({
16
+ key: itemKey(layer, "rect", item.sourceIndex),
17
+ fields: ownFields(item.row),
18
+ channels: item.channels,
19
+ properties: concreteProperties(graphic.items[graphicIndex]?.properties),
20
+ members: [item.row]
21
+ }));
22
+ return finalizeItems(program, layer, "rect", definitions, "rect");
23
+ }
@@ -0,0 +1,25 @@
1
+ import { deriveRuleValues } from "../../../grammar/rules.js";
2
+ import { finalizeItems, ownFields, uniqueFields } from "./common.js";
3
+
4
+ export function resolveRuleItems(program, layer, dataset) {
5
+ const derived = deriveRuleValues(dataset.values, layer);
6
+ const hasField = Object.values(layer.encoding ?? {}).some(encoding =>
7
+ Object.hasOwn(encoding, "field")
8
+ );
9
+ const definitions = Array.from({ length: derived.length }, (_, index) => {
10
+ const members = hasField ? [dataset.values[index]] : dataset.values;
11
+ return {
12
+ fields: hasField
13
+ ? ownFields(dataset.values[index])
14
+ : uniqueFields(dataset.values),
15
+ channels: Object.fromEntries(
16
+ Object.entries(derived.values).map(([channel, values]) => [
17
+ channel,
18
+ values[index]
19
+ ])
20
+ ),
21
+ members
22
+ };
23
+ });
24
+ return finalizeItems(program, layer, "rule", definitions, "line");
25
+ }
@@ -0,0 +1,10 @@
1
+ import { resolveArcItems } from "../items/index.js";
2
+ import { normalizeAreaHighlightStyle } from "../styles.js";
3
+
4
+ export const arcSelectionPolicy = Object.freeze({
5
+ supportedGrains: Object.freeze(["item"]),
6
+ resolveItems: resolveArcItems,
7
+ normalizeHighlightStyle: args => normalizeAreaHighlightStyle(args, "Arc"),
8
+ applyHighlightOp: "applyPathHighlight",
9
+ rematerializeOp: "rematerializeArcMark"
10
+ });
@@ -1,4 +1,4 @@
1
- import { resolveAreaItems } from "../items.js";
1
+ import { resolveAreaItems } from "../items/index.js";
2
2
  import { normalizeAreaHighlightStyle } from "../styles.js";
3
3
 
4
4
  export const areaSelectionPolicy = Object.freeze({
@@ -1,4 +1,4 @@
1
- import { resolveBarItems } from "../items.js";
1
+ import { resolveBarItems } from "../items/index.js";
2
2
  import { normalizeBarHighlightStyle } from "../styles.js";
3
3
 
4
4
  export const barSelectionPolicy = Object.freeze({
@@ -1,17 +1,21 @@
1
1
  import { findDataset } from "../../../selectors/datasets.js";
2
2
  import { requireLayer } from "../../../selectors/layers.js";
3
3
  import { areaSelectionPolicy } from "./area.js";
4
+ import { arcSelectionPolicy } from "./arc.js";
4
5
  import { barSelectionPolicy } from "./bar.js";
5
6
  import { lineSelectionPolicy } from "./line.js";
6
7
  import { pointSelectionPolicy } from "./point.js";
7
8
  import { ruleSelectionPolicy } from "./rule.js";
9
+ import { rectSelectionPolicy } from "./rect.js";
8
10
 
9
11
  const POLICIES = Object.freeze({
12
+ arc: arcSelectionPolicy,
10
13
  area: areaSelectionPolicy,
11
14
  bar: barSelectionPolicy,
12
15
  line: lineSelectionPolicy,
13
16
  point: pointSelectionPolicy,
14
- rule: ruleSelectionPolicy
17
+ rule: ruleSelectionPolicy,
18
+ rect: rectSelectionPolicy
15
19
  });
16
20
 
17
21
  export function findSelectionPolicy(markType) {
@@ -1,4 +1,4 @@
1
- import { resolveLineItems } from "../items.js";
1
+ import { resolveLineItems } from "../items/index.js";
2
2
  import { normalizeStrokeHighlightStyle } from "../styles.js";
3
3
 
4
4
  export const lineSelectionPolicy = Object.freeze({
@@ -1,4 +1,4 @@
1
- import { resolvePointItems } from "../items.js";
1
+ import { resolvePointItems } from "../items/index.js";
2
2
  import { normalizePointHighlightStyle } from "../styles.js";
3
3
 
4
4
  export const pointSelectionPolicy = Object.freeze({
@@ -0,0 +1,10 @@
1
+ import { resolveRectItems } from "../items/index.js";
2
+ import { normalizeRectHighlightStyle } from "../styles.js";
3
+
4
+ export const rectSelectionPolicy = Object.freeze({
5
+ supportedGrains: Object.freeze(["item"]),
6
+ resolveItems: resolveRectItems,
7
+ normalizeHighlightStyle: normalizeRectHighlightStyle,
8
+ applyHighlightOp: "applyRectHighlight",
9
+ rematerializeOp: "rematerializeRectMark"
10
+ });
@@ -1,4 +1,4 @@
1
- import { resolveRuleItems } from "../items.js";
1
+ import { resolveRuleItems } from "../items/index.js";
2
2
  import { normalizeStrokeHighlightStyle } from "../styles.js";
3
3
 
4
4
  export const ruleSelectionPolicy = Object.freeze({
@@ -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,30 +80,30 @@ export function normalizePointHighlightStyle(args) {
80
80
  };
81
81
  }
82
82
 
83
- export function normalizeBarHighlightStyle(args) {
83
+ function normalizeRectangularHighlightStyle(args, mark) {
84
84
  for (const option of ["shape", "size", "offset", "strokeDash"]) {
85
85
  if (args[option] !== undefined) {
86
- throw new Error(`Bar highlight does not support ${option}.`);
86
+ throw new Error(`${mark} highlight does not support ${option}.`);
87
87
  }
88
88
  }
89
89
  if (args.color !== undefined && args.fill !== undefined) {
90
- throw new Error("Bar highlight accepts color or fill, not both.");
90
+ throw new Error(`${mark} highlight accepts color or fill, not both.`);
91
91
  }
92
92
  const fill = validateNonEmptyString(
93
93
  args.fill ?? args.color ?? DEFAULT_COLORS.highlight,
94
- "Bar highlight fill"
94
+ `${mark} highlight fill`
95
95
  );
96
96
  const opacity = args.opacity === undefined
97
97
  ? undefined
98
- : validateUnitInterval(args.opacity, "Bar highlight opacity");
98
+ : validateUnitInterval(args.opacity, `${mark} highlight opacity`);
99
99
  const stroke = args.stroke === undefined
100
100
  ? undefined
101
- : validateNonEmptyString(args.stroke, "Bar highlight stroke");
101
+ : validateNonEmptyString(args.stroke, `${mark} highlight stroke`);
102
102
  const strokeWidth = args.strokeWidth === undefined
103
103
  ? undefined
104
- : validateNonNegativeFinite(args.strokeWidth, "Bar highlight strokeWidth");
104
+ : validateNonNegativeFinite(args.strokeWidth, `${mark} highlight strokeWidth`);
105
105
  if (strokeWidth !== undefined && stroke === undefined) {
106
- throw new Error("Bar highlight strokeWidth requires stroke.");
106
+ throw new Error(`${mark} highlight strokeWidth requires stroke.`);
107
107
  }
108
108
  return {
109
109
  fill,
@@ -113,6 +113,14 @@ export function normalizeBarHighlightStyle(args) {
113
113
  };
114
114
  }
115
115
 
116
+ export function normalizeBarHighlightStyle(args) {
117
+ return normalizeRectangularHighlightStyle(args, "Bar");
118
+ }
119
+
120
+ export function normalizeRectHighlightStyle(args) {
121
+ return normalizeRectangularHighlightStyle(args, "Rect");
122
+ }
123
+
116
124
  function rejectHighlightOptions(args, mark, options) {
117
125
  for (const option of options) {
118
126
  if (args[option] !== undefined) {
@@ -154,28 +162,28 @@ export function normalizeStrokeHighlightStyle(args, mark) {
154
162
  };
155
163
  }
156
164
 
157
- export function normalizeAreaHighlightStyle(args) {
158
- rejectHighlightOptions(args, "Area", ["shape", "size", "strokeDash"]);
165
+ export function normalizeAreaHighlightStyle(args, mark = "Area") {
166
+ rejectHighlightOptions(args, mark, ["shape", "size", "strokeDash"]);
159
167
  if (args.color !== undefined && args.fill !== undefined) {
160
- throw new Error("Area highlight accepts color or fill, not both.");
168
+ throw new Error(`${mark} highlight accepts color or fill, not both.`);
161
169
  }
162
170
  const stroke = args.stroke === undefined
163
171
  ? undefined
164
- : validateNonEmptyString(args.stroke, "Area highlight stroke");
172
+ : validateNonEmptyString(args.stroke, `${mark} highlight stroke`);
165
173
  const strokeWidth = args.strokeWidth === undefined
166
174
  ? undefined
167
- : validateNonNegativeFinite(args.strokeWidth, "Area highlight strokeWidth");
175
+ : validateNonNegativeFinite(args.strokeWidth, `${mark} highlight strokeWidth`);
168
176
  if (strokeWidth !== undefined && stroke === undefined) {
169
- throw new Error("Area highlight strokeWidth requires stroke.");
177
+ throw new Error(`${mark} highlight strokeWidth requires stroke.`);
170
178
  }
171
179
  return {
172
180
  fill: validateNonEmptyString(
173
181
  args.fill ?? args.color ?? DEFAULT_COLORS.highlight,
174
- "Area highlight fill"
182
+ `${mark} highlight fill`
175
183
  ),
176
184
  ...(args.opacity === undefined
177
185
  ? {}
178
- : { opacity: validateUnitInterval(args.opacity, "Area highlight opacity") }),
186
+ : { opacity: validateUnitInterval(args.opacity, `${mark} highlight opacity`) }),
179
187
  ...(stroke === undefined ? {} : { stroke }),
180
188
  ...(strokeWidth === undefined ? {} : { strokeWidth }),
181
189
  offset: normalizeOffset(args.offset)
@@ -0,0 +1,156 @@
1
+ import { resolveBarChannels } from "../grammar/bars/policy.js";
2
+ import { formatTextValue } from "../grammar/text.js";
3
+ import { findDataset } from "../selectors/datasets.js";
4
+ import { findLayer } from "../selectors/layers.js";
5
+ import { resolveMarkItems } from "./selection/policies/index.js";
6
+ import { mapScaleConsumerValues } from "./scales/map.js";
7
+ import { resolveRowEncodingValues } from "./rowEncoding.js";
8
+
9
+ function sourceValue(item, source, field) {
10
+ if (Object.hasOwn(item.fields, field)) return item.fields[field];
11
+ for (const [channel, encoding] of Object.entries(source.encoding ?? {})) {
12
+ if (encoding?.field !== field || item.channels[channel] === undefined) continue;
13
+ if (source.mark.type === "bar" && item.channels[`${channel}2`] !== undefined) {
14
+ return item.channels[`${channel}2`];
15
+ }
16
+ return item.channels[channel];
17
+ }
18
+ if (item.members.length === 1 && Object.hasOwn(item.members[0], field)) {
19
+ return item.members[0][field];
20
+ }
21
+ return undefined;
22
+ }
23
+
24
+ function barAnchor(program, source, item) {
25
+ const channels = resolveBarChannels(source);
26
+ const measure = channels.measure;
27
+ const endpoint = item.channels[`${measure}2`];
28
+ const scale = program.resolvedScales[source.encoding[measure].scale];
29
+ const mapped = endpoint === undefined || scale === undefined
30
+ ? undefined
31
+ : mapScaleConsumerValues([endpoint], scale, measure)[0];
32
+ return measure === "x"
33
+ ? {
34
+ x: mapped ?? item.properties.x + item.properties.width,
35
+ y: item.properties.y + item.properties.height / 2
36
+ }
37
+ : {
38
+ x: item.properties.x + item.properties.width / 2,
39
+ y: mapped ?? item.properties.y
40
+ };
41
+ }
42
+
43
+ function sourceAnchor(program, source, item) {
44
+ if (source.mark.type === "bar") return barAnchor(program, source, item);
45
+ if (source.mark.type === "rect") {
46
+ return {
47
+ x: item.properties.x + item.properties.width / 2,
48
+ y: item.properties.y + item.properties.height / 2
49
+ };
50
+ }
51
+ if (source.mark.type === "rule") {
52
+ return {
53
+ x: item.properties.x2,
54
+ y: item.properties.y2
55
+ };
56
+ }
57
+ return { x: item.properties.x, y: item.properties.y };
58
+ }
59
+
60
+ function contentValue(encoding, rowOrItem, source) {
61
+ return Object.hasOwn(encoding, "field")
62
+ ? source === undefined
63
+ ? rowOrItem[encoding.field]
64
+ : sourceValue(rowOrItem, source, encoding.field)
65
+ : encoding.datum;
66
+ }
67
+
68
+ function concreteItem(config, position, value, format) {
69
+ const text = formatTextValue(value, format);
70
+ const x = position.x;
71
+ const y = position.y;
72
+ if (text === undefined || !Number.isFinite(x) || !Number.isFinite(y)) {
73
+ return undefined;
74
+ }
75
+ return {
76
+ type: "text",
77
+ properties: {
78
+ x: x + config.dx,
79
+ y: y + config.dy,
80
+ text,
81
+ fill: config.fill,
82
+ opacity: config.opacity,
83
+ fontSize: config.fontSize,
84
+ fontFamily: config.fontFamily,
85
+ fontWeight: config.fontWeight,
86
+ textAlign: config.align,
87
+ textBaseline: config.baseline,
88
+ rotation: config.rotation
89
+ }
90
+ };
91
+ }
92
+
93
+ function relativeLuminance(color) {
94
+ if (typeof color !== "string" || !/^#[0-9a-f]{6}$/i.test(color)) {
95
+ return undefined;
96
+ }
97
+ const channels = [1, 3, 5].map(offset =>
98
+ Number.parseInt(color.slice(offset, offset + 2), 16) / 255
99
+ ).map(value => value <= 0.04045
100
+ ? value / 12.92
101
+ : ((value + 0.055) / 1.055) ** 2.4
102
+ );
103
+ return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
104
+ }
105
+
106
+ function sourceTextConfig(config, source, item) {
107
+ if (source.mark.type !== "rect" || config.fillExplicit === true) return config;
108
+ const luminance = relativeLuminance(item.properties.fill);
109
+ if (luminance === undefined) return config;
110
+ return {
111
+ ...config,
112
+ fill: luminance >= 0.26 ? "#0f172a" : "#f8fafc"
113
+ };
114
+ }
115
+
116
+ function resolveSourceTextItems(program, layer, config) {
117
+ const source = findLayer(program, layer.source);
118
+ if (source === undefined) {
119
+ throw new Error(`Text mark "${layer.id}" requires source layer "${layer.source}".`);
120
+ }
121
+ const items = resolveMarkItems(program, source.id);
122
+ return items.flatMap(item => {
123
+ const concrete = concreteItem(
124
+ sourceTextConfig(config, source, item),
125
+ sourceAnchor(program, source, item),
126
+ contentValue(layer.encoding.text, item, source),
127
+ layer.encoding.text.format
128
+ );
129
+ return concrete === undefined ? [] : [concrete];
130
+ });
131
+ }
132
+
133
+ function resolveRowTextItems(program, layer, config) {
134
+ const dataset = findDataset(program, layer.data);
135
+ if (dataset === undefined) {
136
+ throw new Error(`Text mark "${layer.id}" requires an existing dataset.`);
137
+ }
138
+ const x = resolveRowEncodingValues(program, layer, dataset, "x");
139
+ const y = resolveRowEncodingValues(program, layer, dataset, "y");
140
+ if (x === undefined || y === undefined) return [];
141
+ return dataset.values.flatMap((row, index) => {
142
+ const concrete = concreteItem(
143
+ config,
144
+ { x: x[index], y: y[index] },
145
+ contentValue(layer.encoding.text, row),
146
+ layer.encoding.text.format
147
+ );
148
+ return concrete === undefined ? [] : [concrete];
149
+ });
150
+ }
151
+
152
+ export function resolveTextGraphicItems(program, layer, config) {
153
+ return layer.source === undefined
154
+ ? resolveRowTextItems(program, layer, config)
155
+ : resolveSourceTextItems(program, layer, config);
156
+ }