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.
- package/CHANGELOG.md +55 -2
- package/README.md +95 -72
- package/package.json +15 -5
- package/src/actions/boxPlots/components.js +3 -3
- package/src/actions/boxPlots/edit.js +277 -0
- package/src/actions/boxPlots/index.js +2 -0
- package/src/actions/boxPlots/materialize.js +12 -1
- package/src/actions/boxPlots/options.js +25 -14
- package/src/actions/canvas/actions.js +21 -17
- package/src/actions/composition/actions.js +247 -0
- package/src/actions/composition/index.js +1 -0
- package/src/actions/coordinates/actions.js +2 -5
- package/src/actions/data/density.js +7 -1
- package/src/actions/data/filter.js +3 -1
- package/src/actions/data/regression.js +1 -1
- package/src/actions/encodings/appearance.js +12 -1
- package/src/actions/encodings/barWidth.js +5 -3
- package/src/actions/encodings/color/continuous.js +137 -0
- package/src/actions/encodings/color/index.js +151 -0
- package/src/actions/encodings/color/layout.js +91 -0
- package/src/actions/encodings/color/policy.js +88 -0
- package/src/actions/encodings/index.js +4 -2
- package/src/actions/encodings/offset.js +91 -73
- package/src/actions/encodings/position/apply.js +66 -0
- package/src/actions/encodings/position/index.js +130 -0
- package/src/actions/encodings/position/policies/arc.js +26 -0
- package/src/actions/encodings/position/policies/bar.js +9 -5
- package/src/actions/encodings/position/policies/index.js +5 -1
- package/src/actions/encodings/position/policies/line.js +20 -3
- package/src/actions/encodings/position/resolve.js +58 -9
- package/src/actions/encodings/ranged.js +30 -8
- package/src/actions/encodings/shared.js +17 -21
- package/src/actions/encodings/strokeDash.js +1 -1
- package/src/actions/encodings/text.js +73 -0
- package/src/actions/errorBands/create.js +37 -29
- package/src/actions/errorBands/edit.js +206 -0
- package/src/actions/errorBands/index.js +9 -0
- package/src/actions/errorBands/options.js +39 -0
- package/src/actions/errorBars/create.js +50 -43
- package/src/actions/errorBars/edit.js +130 -0
- package/src/actions/errorBars/index.js +3 -0
- package/src/actions/errorBars/options.js +52 -0
- package/src/actions/facets/actions.js +162 -0
- package/src/actions/facets/derive.js +200 -0
- package/src/actions/facets/guides.js +16 -0
- package/src/actions/facets/index.js +1 -0
- package/src/actions/facets/replay.js +69 -0
- package/src/actions/guides/applicability.js +113 -0
- package/src/actions/guides/axes/axes.js +70 -16
- package/src/actions/guides/axes/edit.js +126 -0
- package/src/actions/guides/axes/index.js +12 -0
- package/src/actions/guides/axes/labels.js +22 -19
- package/src/actions/guides/axes/remove.js +60 -0
- package/src/actions/guides/axes/tickGroups.js +7 -5
- package/src/actions/guides/axes/ticks.js +10 -5
- package/src/actions/guides/axes/titles.js +8 -6
- package/src/actions/guides/grids/grid.js +144 -15
- package/src/actions/guides/grids/resolve.js +6 -14
- package/src/actions/guides/guides.js +17 -75
- package/src/actions/guides/index.js +6 -0
- package/src/actions/guides/legends/categorical/actions.js +48 -9
- package/src/actions/guides/legends/categorical/components.js +16 -16
- package/src/actions/guides/legends/categorical/index.js +14 -14
- package/src/actions/guides/legends/categorical/layout.js +7 -3
- package/src/actions/guides/legends/categorical/options.js +2 -5
- package/src/actions/guides/legends/categorical/resolve.js +2 -2
- package/src/actions/guides/legends/categorical/symbols.js +14 -14
- package/src/actions/guides/legends/continuous/common.js +7 -7
- package/src/actions/guides/legends/continuous/interval.js +6 -6
- package/src/actions/guides/legends/continuous/opacity.js +1 -1
- package/src/actions/guides/legends/edit.js +4 -26
- package/src/actions/guides/legends/focused.js +63 -0
- package/src/actions/guides/legends/index.js +4 -0
- package/src/actions/guides/legends/remove.js +46 -0
- package/src/actions/guides/legends/size.js +13 -15
- package/src/actions/guides/legends/target.js +25 -0
- package/src/actions/guides/polar/axes/facade.js +219 -0
- package/src/actions/guides/polar/axes/index.js +55 -0
- package/src/actions/guides/polar/axes/labels.js +188 -0
- package/src/actions/guides/polar/axes/lines.js +113 -0
- package/src/actions/guides/polar/axes/shared.js +112 -0
- package/src/actions/guides/polar/axes/ticks.js +164 -0
- package/src/actions/guides/polar/axes/titles.js +165 -0
- package/src/actions/guides/polar/grids.js +252 -0
- package/src/actions/guides/polar/index.js +2 -0
- package/src/actions/guides/polar/resolve.js +269 -0
- package/src/actions/guides/tickValues.js +1 -1
- package/src/actions/index.js +4 -0
- package/src/actions/intervals/resolve.js +1 -1
- package/src/actions/marks/arc/actions.js +239 -0
- package/src/actions/marks/arc/index.js +1 -0
- package/src/actions/marks/{area.js → area/actions.js} +51 -38
- package/src/actions/marks/area/index.js +4 -0
- package/src/actions/marks/bar/create.js +26 -5
- package/src/actions/marks/bar/edit.js +1 -1
- package/src/actions/marks/bar/materialize.js +25 -31
- package/src/actions/marks/index.js +12 -4
- package/src/actions/marks/lifecycle.js +27 -0
- package/src/actions/marks/line/actions.js +373 -0
- package/src/actions/marks/line/index.js +1 -0
- package/src/actions/marks/{point.js → point/actions.js} +67 -108
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/rect/actions.js +165 -0
- package/src/actions/marks/rect/index.js +1 -0
- package/src/actions/marks/remove.js +214 -0
- package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
- package/src/actions/marks/rule/index.js +1 -0
- package/src/actions/marks/shared.js +156 -25
- package/src/actions/marks/text/actions.js +192 -0
- package/src/actions/marks/text/index.js +1 -0
- package/src/actions/primitives/createGraphics.js +13 -6
- package/src/actions/primitives/editGraphics.js +10 -3
- package/src/actions/primitives/semantic.js +26 -3
- package/src/actions/primitives/semanticValue.js +26 -4
- package/src/actions/regression/components.js +2 -2
- package/src/actions/regression/create.js +19 -2
- package/src/actions/regression/edit.js +247 -0
- package/src/actions/regression/index.js +3 -0
- package/src/actions/regression/resolve.js +3 -1
- package/src/actions/scales/consumers/common.js +91 -0
- package/src/actions/scales/consumers/families.js +47 -0
- package/src/actions/scales/consumers/index.js +41 -0
- package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +14 -127
- package/src/actions/scales/create.js +1 -1
- package/src/actions/scales/definitions.js +15 -6
- package/src/actions/scales/edit.js +45 -22
- package/src/actions/scales/materialize.js +58 -409
- package/src/actions/selection/actions.js +38 -24
- package/src/actions/selection/index.js +2 -0
- package/src/actions/titles/actions.js +63 -2
- package/src/actions/titles/index.js +2 -0
- package/src/actions/titles/resolve.js +12 -17
- package/src/composition.js +10 -0
- package/src/core/ChartProgram.js +49 -65
- package/src/core/action.js +10 -0
- package/src/core/compositionState.js +169 -0
- package/src/core/materializationState.js +65 -0
- package/src/core/programState.js +28 -0
- package/src/core/validation.js +16 -0
- package/src/core/vocabulary.js +154 -4
- package/src/grammar/arcs.js +154 -0
- package/src/grammar/areaSeries.js +1 -1
- package/src/grammar/bars/aggregate.js +3 -3
- package/src/grammar/bars/geometry.js +3 -3
- package/src/grammar/bars/policy.js +7 -1
- package/src/grammar/coordinates.js +3 -7
- package/src/grammar/density.js +21 -2
- package/src/grammar/facets/dependencies.js +185 -0
- package/src/grammar/facets/guides.js +257 -0
- package/src/grammar/facets/index.js +122 -0
- package/src/grammar/facets/scales.js +254 -0
- package/src/grammar/interval.js +2 -96
- package/src/grammar/lineSeries.js +112 -4
- package/src/grammar/palettes.js +5 -7
- package/src/grammar/polar.js +102 -0
- package/src/grammar/polarGuides.js +243 -0
- package/src/grammar/polarLineCommands.js +43 -0
- package/src/grammar/polarPaths.js +161 -0
- package/src/grammar/positionCompatibility.js +18 -2
- package/src/grammar/rects.js +26 -0
- package/src/grammar/regression/derive.js +97 -0
- package/src/grammar/regression/index.js +22 -0
- package/src/grammar/regression/models.js +282 -0
- package/src/grammar/regression/parameters.js +106 -0
- package/src/grammar/rules.js +1 -1
- package/src/grammar/scales/color.js +4 -2
- package/src/grammar/scales/continuous.js +6 -1
- package/src/grammar/scales/fields.js +7 -0
- package/src/grammar/scales/ordinal.js +5 -2
- package/src/grammar/scales/policies.js +1 -1
- package/src/grammar/schemas/graphic.js +1 -1
- package/src/grammar/schemas/graphicBounds.js +64 -9
- package/src/grammar/schemas/graphicTree.js +44 -10
- package/src/grammar/schemas/semanticPath.js +31 -9
- package/src/grammar/seriesLayout.js +13 -2
- package/src/grammar/statistics/studentT.js +98 -0
- package/src/grammar/text.js +71 -0
- package/src/grammar/ticks.js +50 -13
- package/src/grammar/transforms.js +1 -1
- package/src/index.js +1 -0
- package/src/layout/composition.js +310 -0
- package/src/layout/facets.js +131 -0
- package/src/layout/title.js +6 -0
- package/src/materialization/bars/aggregate.js +6 -3
- package/src/materialization/bars/grouped.js +75 -37
- package/src/materialization/bars/histogram.js +1 -1
- package/src/materialization/bars/ranged.js +1 -1
- package/src/materialization/composition.js +152 -0
- package/src/materialization/compositionSnapshot.js +114 -0
- package/src/materialization/density.js +1 -1
- package/src/materialization/dependencies.js +47 -73
- package/src/materialization/encodings.js +14 -28
- package/src/materialization/facetGuides/index.js +2 -0
- package/src/materialization/facetGuides/legacyCategorical.js +125 -0
- package/src/materialization/facetGuides/placement.js +126 -0
- package/src/materialization/facetGuides/preparation.js +188 -0
- package/src/materialization/facets.js +214 -0
- package/src/materialization/graphicHierarchy.js +15 -5
- package/src/materialization/guides/resources.js +46 -0
- package/src/materialization/marks/capabilities.js +141 -0
- package/src/materialization/marks/index.js +105 -0
- package/src/materialization/marks/policies.js +87 -0
- package/src/materialization/planner.js +105 -12
- package/src/materialization/rect.js +138 -0
- package/src/materialization/rectConfig.js +38 -0
- package/src/materialization/rowEncoding.js +33 -0
- package/src/materialization/scaleGuideDependencies.js +94 -0
- package/src/materialization/scales/map.js +40 -0
- package/src/materialization/scales/policies/arc.js +37 -0
- package/src/materialization/scales/policies/bar.js +132 -0
- package/src/materialization/scales/policies/series.js +76 -0
- package/src/materialization/scales/resolve.js +326 -0
- package/src/materialization/selection/items/arc.js +29 -0
- package/src/materialization/selection/items/bar.js +255 -0
- package/src/materialization/selection/items/common.js +131 -0
- package/src/materialization/selection/items/index.js +6 -0
- package/src/materialization/selection/items/path.js +59 -0
- package/src/materialization/selection/items/point.js +55 -0
- package/src/materialization/selection/items/rect.js +23 -0
- package/src/materialization/selection/items/rule.js +25 -0
- package/src/materialization/selection/policies/arc.js +10 -0
- package/src/materialization/selection/policies/area.js +1 -1
- package/src/materialization/selection/policies/bar.js +1 -1
- package/src/materialization/selection/policies/index.js +5 -1
- package/src/materialization/selection/policies/line.js +1 -1
- package/src/materialization/selection/policies/point.js +1 -1
- package/src/materialization/selection/policies/rect.js +10 -0
- package/src/materialization/selection/policies/rule.js +1 -1
- package/src/materialization/selection/styles.js +25 -17
- package/src/materialization/text.js +156 -0
- package/src/renderers/canvas/index.js +68 -22
- package/src/renderers/canvas/text.js +14 -1
- package/types/extension.d.ts +5 -1
- package/types/index.d.ts +56 -1
- package/types/program.d.ts +664 -27
- package/src/actions/encodings/color.js +0 -329
- package/src/actions/encodings/position.js +0 -176
- package/src/actions/marks/line.js +0 -248
- package/src/grammar/regression.js +0 -578
- package/src/grammar/scales.js +0 -1
- package/src/materialization/marks.js +0 -110
- 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,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) {
|
|
@@ -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
|
+
});
|
|
@@ -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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
94
|
+
`${mark} highlight fill`
|
|
95
95
|
);
|
|
96
96
|
const opacity = args.opacity === undefined
|
|
97
97
|
? undefined
|
|
98
|
-
: validateUnitInterval(args.opacity,
|
|
98
|
+
: validateUnitInterval(args.opacity, `${mark} highlight opacity`);
|
|
99
99
|
const stroke = args.stroke === undefined
|
|
100
100
|
? undefined
|
|
101
|
-
: validateNonEmptyString(args.stroke,
|
|
101
|
+
: validateNonEmptyString(args.stroke, `${mark} highlight stroke`);
|
|
102
102
|
const strokeWidth = args.strokeWidth === undefined
|
|
103
103
|
? undefined
|
|
104
|
-
: validateNonNegativeFinite(args.strokeWidth,
|
|
104
|
+
: validateNonNegativeFinite(args.strokeWidth, `${mark} highlight strokeWidth`);
|
|
105
105
|
if (strokeWidth !== undefined && stroke === undefined) {
|
|
106
|
-
throw new Error(
|
|
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,
|
|
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(
|
|
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,
|
|
172
|
+
: validateNonEmptyString(args.stroke, `${mark} highlight stroke`);
|
|
165
173
|
const strokeWidth = args.strokeWidth === undefined
|
|
166
174
|
? undefined
|
|
167
|
-
: validateNonNegativeFinite(args.strokeWidth,
|
|
175
|
+
: validateNonNegativeFinite(args.strokeWidth, `${mark} highlight strokeWidth`);
|
|
168
176
|
if (strokeWidth !== undefined && stroke === undefined) {
|
|
169
|
-
throw new Error(
|
|
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
|
-
|
|
182
|
+
`${mark} highlight fill`
|
|
175
183
|
),
|
|
176
184
|
...(args.opacity === undefined
|
|
177
185
|
? {}
|
|
178
|
-
: { opacity: validateUnitInterval(args.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
|
+
}
|