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,46 @@
|
|
|
1
|
+
const AXIS_COMPONENTS = Object.freeze(["Line", "Ticks", "Labels", "Title"]);
|
|
2
|
+
|
|
3
|
+
const CATEGORICAL_COMPONENTS = Object.freeze([
|
|
4
|
+
"Symbols", "SymbolLines", "SymbolPoints", "SymbolSwatches",
|
|
5
|
+
"Labels", "Title", "Background"
|
|
6
|
+
]);
|
|
7
|
+
|
|
8
|
+
const LEGEND_GRAPHICS = Object.freeze({
|
|
9
|
+
series: Object.freeze(CATEGORICAL_COMPONENTS.map(
|
|
10
|
+
component => `seriesLegend${component}`
|
|
11
|
+
)),
|
|
12
|
+
color: Object.freeze(CATEGORICAL_COMPONENTS.map(
|
|
13
|
+
component => `colorLegend${component}`
|
|
14
|
+
)),
|
|
15
|
+
size: Object.freeze([
|
|
16
|
+
"sizeLegendSymbols", "sizeLegendLabels", "sizeLegendTitle"
|
|
17
|
+
]),
|
|
18
|
+
gradient: Object.freeze([
|
|
19
|
+
"colorGradientBackground", "colorGradientStrips", "colorGradientTicks",
|
|
20
|
+
"colorGradientLabels", "colorGradientTitle"
|
|
21
|
+
]),
|
|
22
|
+
interval: Object.freeze([
|
|
23
|
+
"colorLegendSymbols", "colorLegendLabels", "colorLegendTitle"
|
|
24
|
+
]),
|
|
25
|
+
opacity: Object.freeze([
|
|
26
|
+
"opacityLegendBackground", "opacityLegendSymbols", "opacityLegendLabels",
|
|
27
|
+
"opacityLegendTitle"
|
|
28
|
+
])
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export function axisGraphicIds(channel) {
|
|
32
|
+
const prefix = channel === "radius" ? "radial" : channel;
|
|
33
|
+
return AXIS_COMPONENTS.map(component => `${prefix}Axis${component}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function legendGraphicIds(kind) {
|
|
37
|
+
const ids = LEGEND_GRAPHICS[kind];
|
|
38
|
+
if (ids === undefined) {
|
|
39
|
+
throw new Error(`Unknown legend graphic kind "${kind}".`);
|
|
40
|
+
}
|
|
41
|
+
return ids;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function allLegendGraphicIds(kinds) {
|
|
45
|
+
return [...new Set(kinds.flatMap(legendGraphicIds))];
|
|
46
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { findDataset } from "../../selectors/datasets.js";
|
|
2
|
+
import { findLayer } from "../../selectors/layers.js";
|
|
3
|
+
import { isAggregate } from "../../grammar/aggregate.js";
|
|
4
|
+
import {
|
|
5
|
+
BAR_GRAINS,
|
|
6
|
+
resolveBarColorLayout,
|
|
7
|
+
resolveBarOffsetChannel,
|
|
8
|
+
resolveBarGrain
|
|
9
|
+
} from "../../grammar/bars/policy.js";
|
|
10
|
+
import { resolveRuleMode } from "../../grammar/rules.js";
|
|
11
|
+
import { findUpstreamTransform } from "../dataProvenance.js";
|
|
12
|
+
import { resolveRectMode } from "../../grammar/rects.js";
|
|
13
|
+
|
|
14
|
+
function hasCartesianPositionScales(layer) {
|
|
15
|
+
return (
|
|
16
|
+
layer.encoding?.x?.scale !== undefined &&
|
|
17
|
+
layer.encoding?.y?.scale !== undefined
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function hasPolarPositionScales(layer) {
|
|
22
|
+
return (
|
|
23
|
+
layer.encoding?.theta?.scale !== undefined &&
|
|
24
|
+
layer.encoding?.radius?.scale !== undefined
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function canMaterializePoint(_program, layer) {
|
|
29
|
+
return layer.mark?.type === "point" && (
|
|
30
|
+
hasCartesianPositionScales(layer) || hasPolarPositionScales(layer)
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function canMaterializeLine(program, layer) {
|
|
35
|
+
const dataset = findDataset(program, layer.data);
|
|
36
|
+
const interval = dataset?.transform?.some(item => item.type === "interval");
|
|
37
|
+
if (hasPolarPositionScales(layer)) {
|
|
38
|
+
return (
|
|
39
|
+
layer.mark?.type === "line" &&
|
|
40
|
+
["quantitative", "temporal", "ordinal", "nominal"].includes(
|
|
41
|
+
layer.encoding.theta.fieldType
|
|
42
|
+
) &&
|
|
43
|
+
layer.encoding.radius.fieldType === "quantitative"
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return (
|
|
47
|
+
layer.mark?.type === "line" &&
|
|
48
|
+
hasCartesianPositionScales(layer) &&
|
|
49
|
+
(isAggregate(layer.encoding.y.aggregate) ||
|
|
50
|
+
(layer.encoding.x.fieldType === "quantitative" &&
|
|
51
|
+
layer.encoding.y.fieldType === "quantitative") ||
|
|
52
|
+
((interval || layer.encoding.y.aggregate === undefined) &&
|
|
53
|
+
["quantitative", "temporal"].includes(layer.encoding.x.fieldType) &&
|
|
54
|
+
["quantitative", "temporal"].includes(layer.encoding.y.fieldType) &&
|
|
55
|
+
layer.encoding.x.fieldType !== layer.encoding.y.fieldType))
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function canMaterializeArea(program, layer) {
|
|
60
|
+
if (layer.mark?.type !== "area" || !hasCartesianPositionScales(layer)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const dataset = findDataset(program, layer.data);
|
|
64
|
+
const densityTransform = findUpstreamTransform(program, dataset, "density");
|
|
65
|
+
const completeDensity =
|
|
66
|
+
densityTransform !== undefined &&
|
|
67
|
+
(densityTransform.groupBy === undefined ||
|
|
68
|
+
layer.encoding?.group?.field === densityTransform.groupBy);
|
|
69
|
+
return (
|
|
70
|
+
completeDensity ||
|
|
71
|
+
layer.encoding?.y2?.scale === layer.encoding.y.scale ||
|
|
72
|
+
layer.encoding?.x2?.scale === layer.encoding.x.scale
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function canMaterializeArc(_program, layer) {
|
|
77
|
+
if (
|
|
78
|
+
layer.mark?.type !== "arc" ||
|
|
79
|
+
layer.encoding?.theta?.scale === undefined ||
|
|
80
|
+
!["nominal", "ordinal"].includes(layer.encoding.theta.fieldType)
|
|
81
|
+
) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
if (layer.encoding.theta.aggregate === "count") {
|
|
85
|
+
return layer.encoding?.radius === undefined;
|
|
86
|
+
}
|
|
87
|
+
return (
|
|
88
|
+
layer.encoding.theta.aggregate === undefined &&
|
|
89
|
+
layer.encoding?.radius?.scale !== undefined &&
|
|
90
|
+
layer.encoding.radius.fieldType === "quantitative"
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function canMaterializeBar(program, layer) {
|
|
95
|
+
if (layer.mark?.type !== "bar" || !hasCartesianPositionScales(layer)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const grain = resolveBarGrain(layer);
|
|
99
|
+
if (![BAR_GRAINS.histogram, BAR_GRAINS.aggregate, BAR_GRAINS.ranged].includes(grain)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
if (
|
|
103
|
+
grain === BAR_GRAINS.aggregate &&
|
|
104
|
+
resolveBarColorLayout(layer) === "group"
|
|
105
|
+
) {
|
|
106
|
+
const offsetChannel = resolveBarOffsetChannel(layer);
|
|
107
|
+
return (
|
|
108
|
+
layer.encoding?.color?.field !== undefined &&
|
|
109
|
+
layer.encoding?.[offsetChannel]?.field === layer.encoding.color.field &&
|
|
110
|
+
layer.encoding[offsetChannel].scale !== undefined
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function canMaterializeRule(program, layer) {
|
|
117
|
+
const fixedSpan = program.markConfigs[layer.id]?.fixedSpan;
|
|
118
|
+
const boxSpanOwner = program.markConfigs[layer.id]?.boxSpanOwner;
|
|
119
|
+
return layer.mark?.type === "rule" && (
|
|
120
|
+
resolveRuleMode(layer) !== undefined ||
|
|
121
|
+
(boxSpanOwner !== undefined && hasCartesianPositionScales(layer)) ||
|
|
122
|
+
(fixedSpan !== undefined && hasCartesianPositionScales(layer))
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function canMaterializeText(program, layer) {
|
|
127
|
+
if (layer.mark?.type !== "text" || layer.encoding?.text === undefined) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
if (layer.source !== undefined) {
|
|
131
|
+
const source = findLayer(program, layer.source);
|
|
132
|
+
return source !== undefined &&
|
|
133
|
+
["point", "bar", "rule", "rect"].includes(source.mark?.type) &&
|
|
134
|
+
Array.isArray(program.graphicSpec.objects[source.id]?.items);
|
|
135
|
+
}
|
|
136
|
+
return hasCartesianPositionScales(layer);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function canMaterializeRect(_program, layer) {
|
|
140
|
+
return resolveRectMode(layer) !== undefined;
|
|
141
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { POSITION_CHANNELS } from "../../core/vocabulary.js";
|
|
2
|
+
import { getMarkMaterializationPolicy } from "./policies.js";
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
canMaterializeArc,
|
|
6
|
+
canMaterializeArea,
|
|
7
|
+
canMaterializeBar,
|
|
8
|
+
canMaterializeLine,
|
|
9
|
+
canMaterializePoint,
|
|
10
|
+
canMaterializeRect,
|
|
11
|
+
canMaterializeRule,
|
|
12
|
+
canMaterializeText
|
|
13
|
+
} from "./capabilities.js";
|
|
14
|
+
|
|
15
|
+
export function getMarkRematerializationStep(layer) {
|
|
16
|
+
const policy = getMarkMaterializationPolicy(layer);
|
|
17
|
+
return policy === undefined
|
|
18
|
+
? undefined
|
|
19
|
+
: { op: policy.op, args: { id: layer.id } };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getMarkMaterializationStep(program, layer) {
|
|
23
|
+
const policy = getMarkMaterializationPolicy(layer);
|
|
24
|
+
if (policy === undefined || !policy.canMaterialize(program, layer)) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return getMarkRematerializationStep(layer);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getSourceDependentMarkSteps(program, sourceId) {
|
|
31
|
+
return program.semanticSpec.layers.flatMap(layer =>
|
|
32
|
+
layer.source === sourceId &&
|
|
33
|
+
getMarkMaterializationPolicy(layer)?.sourceDependent === true
|
|
34
|
+
? [getMarkMaterializationStep(program, layer)].filter(
|
|
35
|
+
step => step !== undefined
|
|
36
|
+
)
|
|
37
|
+
: []
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getPositionEncodingMaterializationSteps(program, layer, scaleId) {
|
|
42
|
+
const policy = getMarkMaterializationPolicy(layer);
|
|
43
|
+
if (policy === undefined) return [];
|
|
44
|
+
const scale = { op: "rematerializeScale", args: { id: scaleId } };
|
|
45
|
+
const complete = policy.canMaterialize(program, layer);
|
|
46
|
+
const mark = getMarkRematerializationStep(layer);
|
|
47
|
+
if (!complete) {
|
|
48
|
+
if (policy.positionEncoding.incomplete === "scale") return [scale];
|
|
49
|
+
return policy.positionEncoding.scaleFirst ? [scale, mark] : [mark];
|
|
50
|
+
}
|
|
51
|
+
return policy.positionEncoding.scaleFirst ? [scale, mark] : [mark];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getScaleConsumerMaterializationMode(layer, channel) {
|
|
55
|
+
const policy = getMarkMaterializationPolicy(layer);
|
|
56
|
+
if (policy === undefined) return "direct";
|
|
57
|
+
if (POSITION_CHANNELS.includes(channel)) {
|
|
58
|
+
return policy.scaleApplication.position ?? policy.scaleApplication.default;
|
|
59
|
+
}
|
|
60
|
+
if (policy.scaleApplication.deferredChannels?.includes(channel)) {
|
|
61
|
+
return "defer";
|
|
62
|
+
}
|
|
63
|
+
return policy.scaleApplication.default;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function canDeferScaleConsumerApplication(layer) {
|
|
67
|
+
return getMarkMaterializationPolicy(layer)?.scaleApplication.deferWithMark === true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getExistingMarkRematerializationStep(program, layer) {
|
|
71
|
+
const policy = getMarkMaterializationPolicy(layer);
|
|
72
|
+
if (
|
|
73
|
+
policy?.rematerializeIncompleteExisting !== true ||
|
|
74
|
+
program.graphicSpec.objects[layer.id] === undefined
|
|
75
|
+
) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return getMarkRematerializationStep(layer);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function getEncodingMaterializationStages(program, layer, channel, scale) {
|
|
82
|
+
const policy = getMarkMaterializationPolicy(layer);
|
|
83
|
+
if (policy === undefined) return { scales: [], marks: [] };
|
|
84
|
+
const scales = policy.encoding?.scaleFirst === true && scale !== undefined
|
|
85
|
+
? [{
|
|
86
|
+
op: "rematerializeScale",
|
|
87
|
+
args: { id: scale, guides: false, marks: false }
|
|
88
|
+
}]
|
|
89
|
+
: [];
|
|
90
|
+
const shared = policy.encoding?.sharedChannels?.includes(channel) === true &&
|
|
91
|
+
scale !== undefined;
|
|
92
|
+
const candidates = shared
|
|
93
|
+
? program.semanticSpec.layers.filter(candidate =>
|
|
94
|
+
candidate.mark?.type === layer.mark?.type &&
|
|
95
|
+
candidate.encoding?.[channel]?.scale === scale
|
|
96
|
+
)
|
|
97
|
+
: [layer];
|
|
98
|
+
const marks = candidates.flatMap(candidate => {
|
|
99
|
+
const step = policy.encoding?.completeOnly === true
|
|
100
|
+
? getMarkMaterializationStep(program, candidate)
|
|
101
|
+
: getMarkRematerializationStep(candidate);
|
|
102
|
+
return step === undefined ? [] : [step];
|
|
103
|
+
});
|
|
104
|
+
return { scales, marks };
|
|
105
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canMaterializeArc,
|
|
3
|
+
canMaterializeArea,
|
|
4
|
+
canMaterializeBar,
|
|
5
|
+
canMaterializeLine,
|
|
6
|
+
canMaterializePoint,
|
|
7
|
+
canMaterializeRect,
|
|
8
|
+
canMaterializeRule,
|
|
9
|
+
canMaterializeText
|
|
10
|
+
} from "./capabilities.js";
|
|
11
|
+
|
|
12
|
+
const MARK_MATERIALIZATION_POLICIES = Object.freeze({
|
|
13
|
+
point: Object.freeze({
|
|
14
|
+
canMaterialize: canMaterializePoint,
|
|
15
|
+
op: "rematerializePointMark",
|
|
16
|
+
positionEncoding: Object.freeze({ incomplete: "mark", scaleFirst: true }),
|
|
17
|
+
encoding: Object.freeze({ scaleFirst: true }),
|
|
18
|
+
scaleApplication: Object.freeze({
|
|
19
|
+
deferWithMark: true,
|
|
20
|
+
position: "rematerialize",
|
|
21
|
+
deferredChannels: Object.freeze(["size", "shape"]),
|
|
22
|
+
default: "direct"
|
|
23
|
+
}),
|
|
24
|
+
rematerializeIncompleteExisting: true
|
|
25
|
+
}),
|
|
26
|
+
line: Object.freeze({
|
|
27
|
+
canMaterialize: canMaterializeLine,
|
|
28
|
+
op: "rematerializeLineMark",
|
|
29
|
+
positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: false }),
|
|
30
|
+
scaleApplication: Object.freeze({ default: "defer" })
|
|
31
|
+
}),
|
|
32
|
+
area: Object.freeze({
|
|
33
|
+
canMaterialize: canMaterializeArea,
|
|
34
|
+
op: "rematerializeAreaMark",
|
|
35
|
+
positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: true }),
|
|
36
|
+
encoding: Object.freeze({ sharedChannels: Object.freeze(["color"]) }),
|
|
37
|
+
scaleApplication: Object.freeze({ default: "defer" })
|
|
38
|
+
}),
|
|
39
|
+
arc: Object.freeze({
|
|
40
|
+
canMaterialize: canMaterializeArc,
|
|
41
|
+
op: "rematerializeArcMark",
|
|
42
|
+
positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: false }),
|
|
43
|
+
encoding: Object.freeze({ completeOnly: true }),
|
|
44
|
+
scaleApplication: Object.freeze({ default: "defer" })
|
|
45
|
+
}),
|
|
46
|
+
bar: Object.freeze({
|
|
47
|
+
canMaterialize: canMaterializeBar,
|
|
48
|
+
op: "rematerializeBarMark",
|
|
49
|
+
positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: false }),
|
|
50
|
+
scaleApplication: Object.freeze({ default: "defer" })
|
|
51
|
+
}),
|
|
52
|
+
rule: Object.freeze({
|
|
53
|
+
canMaterialize: canMaterializeRule,
|
|
54
|
+
op: "rematerializeRuleMark",
|
|
55
|
+
positionEncoding: Object.freeze({ incomplete: "mark", scaleFirst: false }),
|
|
56
|
+
scaleApplication: Object.freeze({ default: "defer" })
|
|
57
|
+
}),
|
|
58
|
+
text: Object.freeze({
|
|
59
|
+
canMaterialize: canMaterializeText,
|
|
60
|
+
op: "rematerializeTextMark",
|
|
61
|
+
positionEncoding: Object.freeze({ incomplete: "mark", scaleFirst: true }),
|
|
62
|
+
encoding: Object.freeze({ scaleFirst: true }),
|
|
63
|
+
scaleApplication: Object.freeze({
|
|
64
|
+
deferWithMark: true,
|
|
65
|
+
position: "rematerialize",
|
|
66
|
+
default: "defer"
|
|
67
|
+
}),
|
|
68
|
+
rematerializeIncompleteExisting: true,
|
|
69
|
+
sourceDependent: true
|
|
70
|
+
}),
|
|
71
|
+
rect: Object.freeze({
|
|
72
|
+
canMaterialize: canMaterializeRect,
|
|
73
|
+
op: "rematerializeRectMark",
|
|
74
|
+
positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: true }),
|
|
75
|
+
encoding: Object.freeze({ scaleFirst: true }),
|
|
76
|
+
scaleApplication: Object.freeze({
|
|
77
|
+
deferWithMark: true,
|
|
78
|
+
position: "rematerialize",
|
|
79
|
+
default: "defer"
|
|
80
|
+
}),
|
|
81
|
+
rematerializeIncompleteExisting: true
|
|
82
|
+
})
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export function getMarkMaterializationPolicy(layer) {
|
|
86
|
+
return MARK_MATERIALIZATION_POLICIES[layer.mark?.type];
|
|
87
|
+
}
|
|
@@ -6,30 +6,123 @@ const STAGES = Object.freeze([
|
|
|
6
6
|
"highlights"
|
|
7
7
|
]);
|
|
8
8
|
|
|
9
|
+
const STAGE_SET = new Set(STAGES);
|
|
10
|
+
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
if (value === null || typeof value !== "object") return false;
|
|
13
|
+
const prototype = Object.getPrototypeOf(value);
|
|
14
|
+
return prototype === Object.prototype || prototype === null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function clonePlanValue(value, path) {
|
|
18
|
+
if (
|
|
19
|
+
value === null ||
|
|
20
|
+
typeof value === "string" ||
|
|
21
|
+
typeof value === "boolean"
|
|
22
|
+
) {
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
return Object.freeze(value.map((item, index) =>
|
|
28
|
+
clonePlanValue(item, `${path}[${index}]`)
|
|
29
|
+
));
|
|
30
|
+
}
|
|
31
|
+
if (isPlainObject(value)) {
|
|
32
|
+
const clone = {};
|
|
33
|
+
for (const key of Object.keys(value)) {
|
|
34
|
+
if (value[key] === undefined) continue;
|
|
35
|
+
clone[key] = clonePlanValue(value[key], `${path}.${key}`);
|
|
36
|
+
}
|
|
37
|
+
return Object.freeze(clone);
|
|
38
|
+
}
|
|
39
|
+
throw new TypeError(
|
|
40
|
+
`${path} must contain only finite JSON-compatible values.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function canonicalValue(value) {
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
return `[${value.map(canonicalValue).join(",")}]`;
|
|
47
|
+
}
|
|
48
|
+
if (isPlainObject(value)) {
|
|
49
|
+
return `{${Object.keys(value).sort().map(key =>
|
|
50
|
+
`${JSON.stringify(key)}:${canonicalValue(value[key])}`
|
|
51
|
+
).join(",")}}`;
|
|
52
|
+
}
|
|
53
|
+
return JSON.stringify(value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeStep(step, index) {
|
|
57
|
+
if (!isPlainObject(step)) {
|
|
58
|
+
throw new TypeError(`Materialization step ${index} must be a plain object.`);
|
|
59
|
+
}
|
|
60
|
+
if (typeof step.op !== "string" || step.op.length === 0) {
|
|
61
|
+
throw new TypeError(
|
|
62
|
+
`Materialization step ${index} op must be a non-empty string.`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
if (step.args !== undefined && !isPlainObject(step.args)) {
|
|
66
|
+
throw new TypeError(
|
|
67
|
+
`Materialization step ${index} args must be a plain object.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return Object.freeze({
|
|
71
|
+
op: step.op,
|
|
72
|
+
...(step.args === undefined
|
|
73
|
+
? {}
|
|
74
|
+
: { args: clonePlanValue(step.args, `Materialization step ${index} args`) })
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeSteps(steps) {
|
|
79
|
+
if (!Array.isArray(steps)) {
|
|
80
|
+
throw new TypeError("Materialization plan stages must be arrays.");
|
|
81
|
+
}
|
|
82
|
+
return steps
|
|
83
|
+
.filter(step => step !== undefined)
|
|
84
|
+
.map((step, index) => normalizeStep(step, index));
|
|
85
|
+
}
|
|
86
|
+
|
|
9
87
|
function stepKey(step) {
|
|
10
|
-
return JSON.stringify(
|
|
88
|
+
return `${JSON.stringify(step.op)}:${canonicalValue(step.args ?? null)}`;
|
|
11
89
|
}
|
|
12
90
|
|
|
13
|
-
|
|
91
|
+
function deduplicateSteps(steps) {
|
|
14
92
|
const plan = [];
|
|
15
93
|
const seen = new Set();
|
|
94
|
+
for (const step of steps) {
|
|
95
|
+
const key = stepKey(step);
|
|
96
|
+
if (seen.has(key)) continue;
|
|
97
|
+
seen.add(key);
|
|
98
|
+
plan.push(step);
|
|
99
|
+
}
|
|
100
|
+
return Object.freeze(plan);
|
|
101
|
+
}
|
|
16
102
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
103
|
+
export function buildMaterializationPlan(stages = {}) {
|
|
104
|
+
if (!isPlainObject(stages)) {
|
|
105
|
+
throw new TypeError("Materialization plan stages must be a plain object.");
|
|
106
|
+
}
|
|
107
|
+
for (const stage of Object.keys(stages)) {
|
|
108
|
+
if (!STAGE_SET.has(stage)) {
|
|
109
|
+
throw new Error(`Unknown materialization stage "${stage}".`);
|
|
24
110
|
}
|
|
25
111
|
}
|
|
26
|
-
|
|
27
|
-
|
|
112
|
+
return deduplicateSteps(STAGES.flatMap(stage =>
|
|
113
|
+
normalizeSteps(stages[stage] ?? [])
|
|
114
|
+
));
|
|
28
115
|
}
|
|
29
116
|
|
|
30
117
|
export function applyMaterializationPlan(program, plan) {
|
|
118
|
+
const steps = deduplicateSteps(normalizeSteps(plan));
|
|
31
119
|
let next = program;
|
|
32
|
-
for (const step of
|
|
120
|
+
for (const step of steps) {
|
|
121
|
+
if (typeof next?.[step.op] !== "function") {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Materialization operation "${step.op}" is not available on the program.`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
33
126
|
next = step.args === undefined
|
|
34
127
|
? next[step.op]()
|
|
35
128
|
: next[step.op](step.args);
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mapOrdinalPositionValues,
|
|
3
|
+
readScaleField
|
|
4
|
+
} from "../grammar/scales/index.js";
|
|
5
|
+
import { RECT_MODES, resolveRectMode } from "../grammar/rects.js";
|
|
6
|
+
import { DEFAULT_RECT_MARK } from "./rectConfig.js";
|
|
7
|
+
import { mapScaleConsumerValues } from "./scales/map.js";
|
|
8
|
+
|
|
9
|
+
function optionalValues(rows, encoding) {
|
|
10
|
+
return readScaleField(rows, encoding.field, encoding.fieldType, {
|
|
11
|
+
allowUnknown: true
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function requiredChannels(layer, mode) {
|
|
16
|
+
return mode === RECT_MODES.ranged
|
|
17
|
+
? ["x", "x2", "y", "y2", ...(layer.encoding?.color ? ["color"] : [])]
|
|
18
|
+
: ["x", "y", ...(layer.encoding?.color ? ["color"] : [])];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolveRectConsumerValues(layer, dataset, channel) {
|
|
22
|
+
const requested = optionalValues(dataset.values, layer.encoding[channel]);
|
|
23
|
+
const mode = resolveRectMode(layer);
|
|
24
|
+
if (mode === undefined) return requested;
|
|
25
|
+
const complete = requiredChannels(layer, mode).map(candidate =>
|
|
26
|
+
optionalValues(dataset.values, layer.encoding[candidate])
|
|
27
|
+
);
|
|
28
|
+
return requested.map((value, index) =>
|
|
29
|
+
complete.every(values => values[index] !== undefined) ? value : undefined
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function mapOptional(values, scale, channel, categorical = false) {
|
|
34
|
+
return values.map(value => {
|
|
35
|
+
if (value === undefined) return undefined;
|
|
36
|
+
return categorical
|
|
37
|
+
? mapOrdinalPositionValues([value], scale)[0]
|
|
38
|
+
: mapScaleConsumerValues([value], scale, channel)[0];
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function mappedEncoding(program, layer, dataset, channel) {
|
|
43
|
+
const encoding = layer.encoding[channel];
|
|
44
|
+
const scale = program.resolvedScales[encoding.scale];
|
|
45
|
+
if (scale === undefined) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Rect mark "${layer.id}" requires resolved ${channel} scale "${encoding.scale}".`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const values = resolveRectConsumerValues(layer, dataset, channel);
|
|
51
|
+
const categorical = ["nominal", "ordinal"].includes(encoding.fieldType) &&
|
|
52
|
+
["x", "y"].includes(channel);
|
|
53
|
+
return {
|
|
54
|
+
values: mapOptional(values, scale, channel, categorical),
|
|
55
|
+
scale
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function appearance(config, fill) {
|
|
60
|
+
return {
|
|
61
|
+
fill,
|
|
62
|
+
opacity: config.opacity,
|
|
63
|
+
stroke: config.stroke === false ? "transparent" : config.stroke,
|
|
64
|
+
strokeWidth: config.stroke === false ? 0 : config.strokeWidth
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function resolveRectRows(program, layer, dataset) {
|
|
69
|
+
const mode = resolveRectMode(layer);
|
|
70
|
+
if (mode === undefined) return [];
|
|
71
|
+
const x = mappedEncoding(program, layer, dataset, "x");
|
|
72
|
+
const y = mappedEncoding(program, layer, dataset, "y");
|
|
73
|
+
const x2 = mode === RECT_MODES.ranged
|
|
74
|
+
? mappedEncoding(program, layer, dataset, "x2")
|
|
75
|
+
: undefined;
|
|
76
|
+
const y2 = mode === RECT_MODES.ranged
|
|
77
|
+
? mappedEncoding(program, layer, dataset, "y2")
|
|
78
|
+
: undefined;
|
|
79
|
+
const color = layer.encoding?.color === undefined
|
|
80
|
+
? undefined
|
|
81
|
+
: mappedEncoding(program, layer, dataset, "color").values;
|
|
82
|
+
const config = program.markConfigs[layer.id] ?? DEFAULT_RECT_MARK;
|
|
83
|
+
|
|
84
|
+
return dataset.values.flatMap((row, index) => {
|
|
85
|
+
const fill = color === undefined ? config.fill : color[index];
|
|
86
|
+
if (
|
|
87
|
+
!Number.isFinite(x.values[index]) ||
|
|
88
|
+
!Number.isFinite(y.values[index]) ||
|
|
89
|
+
typeof fill !== "string"
|
|
90
|
+
) return [];
|
|
91
|
+
|
|
92
|
+
let geometry;
|
|
93
|
+
if (mode === RECT_MODES.discrete) {
|
|
94
|
+
const width = x.scale.bandwidth;
|
|
95
|
+
const height = y.scale.bandwidth;
|
|
96
|
+
if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
geometry = {
|
|
100
|
+
x: x.values[index] - width / 2,
|
|
101
|
+
y: y.values[index] - height / 2,
|
|
102
|
+
width,
|
|
103
|
+
height
|
|
104
|
+
};
|
|
105
|
+
} else {
|
|
106
|
+
const endX = x2.values[index];
|
|
107
|
+
const endY = y2.values[index];
|
|
108
|
+
if (!Number.isFinite(endX) || !Number.isFinite(endY)) return [];
|
|
109
|
+
geometry = {
|
|
110
|
+
x: Math.min(x.values[index], endX),
|
|
111
|
+
y: Math.min(y.values[index], endY),
|
|
112
|
+
width: Math.abs(endX - x.values[index]),
|
|
113
|
+
height: Math.abs(endY - y.values[index])
|
|
114
|
+
};
|
|
115
|
+
if (geometry.width <= 0 || geometry.height <= 0) return [];
|
|
116
|
+
}
|
|
117
|
+
return [{
|
|
118
|
+
row,
|
|
119
|
+
sourceIndex: index,
|
|
120
|
+
channels: Object.fromEntries(
|
|
121
|
+
["x", "y", "x2", "y2", "color"].flatMap(channel => {
|
|
122
|
+
const encoding = layer.encoding?.[channel];
|
|
123
|
+
return encoding?.field === undefined
|
|
124
|
+
? []
|
|
125
|
+
: [[channel, row[encoding.field]]];
|
|
126
|
+
})
|
|
127
|
+
),
|
|
128
|
+
properties: { ...geometry, ...appearance(config, fill) }
|
|
129
|
+
}];
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function resolveRectGraphicItems(program, layer, dataset) {
|
|
134
|
+
return resolveRectRows(program, layer, dataset).map(item => ({
|
|
135
|
+
type: "rect",
|
|
136
|
+
properties: item.properties
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateNonEmptyString,
|
|
3
|
+
validateNonNegativeFinite,
|
|
4
|
+
validateUnitInterval
|
|
5
|
+
} from "../core/validation.js";
|
|
6
|
+
import { DEFAULT_COLORS } from "../theme/defaults.js";
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_RECT_MARK = Object.freeze({
|
|
9
|
+
fill: DEFAULT_COLORS.mark,
|
|
10
|
+
opacity: 1,
|
|
11
|
+
stroke: "white",
|
|
12
|
+
strokeWidth: 1
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export function normalizeRectMarkConfig(patch, previous = DEFAULT_RECT_MARK) {
|
|
16
|
+
const result = { ...previous };
|
|
17
|
+
if (Object.hasOwn(patch, "fill")) {
|
|
18
|
+
result.fill = validateNonEmptyString(patch.fill, "Rect fill");
|
|
19
|
+
}
|
|
20
|
+
if (Object.hasOwn(patch, "opacity")) {
|
|
21
|
+
result.opacity = validateUnitInterval(patch.opacity, "Rect opacity");
|
|
22
|
+
}
|
|
23
|
+
if (Object.hasOwn(patch, "stroke")) {
|
|
24
|
+
result.stroke = patch.stroke === false
|
|
25
|
+
? false
|
|
26
|
+
: validateNonEmptyString(patch.stroke, "Rect stroke");
|
|
27
|
+
}
|
|
28
|
+
if (Object.hasOwn(patch, "strokeWidth")) {
|
|
29
|
+
if (result.stroke === false) {
|
|
30
|
+
throw new Error("Rect strokeWidth requires an active stroke.");
|
|
31
|
+
}
|
|
32
|
+
result.strokeWidth = validateNonNegativeFinite(
|
|
33
|
+
patch.strokeWidth,
|
|
34
|
+
"Rect strokeWidth"
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return Object.freeze(result);
|
|
38
|
+
}
|