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,66 @@
|
|
|
1
|
+
function replaceAlternateBinding(program, target, channel, previous, usesField) {
|
|
2
|
+
if (previous === undefined) return program;
|
|
3
|
+
const alternate = usesField ? "datum" : "field";
|
|
4
|
+
return Object.hasOwn(previous, alternate)
|
|
5
|
+
? program.editSemantic({
|
|
6
|
+
property: `layer[${target}].encoding.${channel}.${alternate}`,
|
|
7
|
+
remove: true
|
|
8
|
+
})
|
|
9
|
+
: program;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function applyBin(program, target, channel, previous, bin) {
|
|
13
|
+
if (bin === undefined) return program;
|
|
14
|
+
const [mode] = Object.keys(bin);
|
|
15
|
+
let next = program;
|
|
16
|
+
for (const previousMode of Object.keys(previous?.bin ?? {})) {
|
|
17
|
+
if (previousMode === mode) continue;
|
|
18
|
+
next = next.editSemantic({
|
|
19
|
+
property: `layer[${target}].encoding.${channel}.bin.${previousMode}`,
|
|
20
|
+
remove: true
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return next.editSemantic({
|
|
24
|
+
property: `layer[${target}].encoding.${channel}.bin.${mode}`,
|
|
25
|
+
value: bin[mode]
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function applyPositionSemantics(program, {
|
|
30
|
+
target,
|
|
31
|
+
channel,
|
|
32
|
+
previous,
|
|
33
|
+
field,
|
|
34
|
+
datum,
|
|
35
|
+
hasField,
|
|
36
|
+
fieldType,
|
|
37
|
+
bin,
|
|
38
|
+
aggregate,
|
|
39
|
+
stack
|
|
40
|
+
}) {
|
|
41
|
+
let next = replaceAlternateBinding(
|
|
42
|
+
program,
|
|
43
|
+
target,
|
|
44
|
+
channel,
|
|
45
|
+
previous,
|
|
46
|
+
hasField
|
|
47
|
+
)
|
|
48
|
+
.editSemantic({
|
|
49
|
+
property: `layer[${target}].encoding.${channel}.${hasField ? "field" : "datum"}`,
|
|
50
|
+
value: hasField ? field : datum
|
|
51
|
+
})
|
|
52
|
+
.editSemantic({
|
|
53
|
+
property: `layer[${target}].encoding.${channel}.fieldType`,
|
|
54
|
+
value: fieldType
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
next = applyBin(next, target, channel, previous, bin);
|
|
58
|
+
for (const [property, value] of Object.entries({ aggregate, stack })) {
|
|
59
|
+
if (value === undefined) continue;
|
|
60
|
+
next = next.editSemantic({
|
|
61
|
+
property: `layer[${target}].encoding.${channel}.${property}`,
|
|
62
|
+
value
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return next;
|
|
66
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import {
|
|
3
|
+
getPositionEncodingMaterializationSteps
|
|
4
|
+
} from "../../../materialization/marks/index.js";
|
|
5
|
+
import { resolvePositionEncoding } from "./resolve.js";
|
|
6
|
+
import { findLayer } from "../../../selectors/layers.js";
|
|
7
|
+
import { applyPositionSemantics } from "./apply.js";
|
|
8
|
+
import {
|
|
9
|
+
applyEncodingScale,
|
|
10
|
+
rebindPositionGuides
|
|
11
|
+
} from "../shared.js";
|
|
12
|
+
|
|
13
|
+
function encodePosition(program, channel, args, operation) {
|
|
14
|
+
const {
|
|
15
|
+
target,
|
|
16
|
+
layer,
|
|
17
|
+
previous,
|
|
18
|
+
requestedScale,
|
|
19
|
+
field,
|
|
20
|
+
datum,
|
|
21
|
+
hasField,
|
|
22
|
+
fieldType,
|
|
23
|
+
scale,
|
|
24
|
+
coordinate,
|
|
25
|
+
bin,
|
|
26
|
+
aggregate,
|
|
27
|
+
stack
|
|
28
|
+
} = resolvePositionEncoding(program, channel, args, operation);
|
|
29
|
+
|
|
30
|
+
let next = program
|
|
31
|
+
.createCoordinate({
|
|
32
|
+
id: coordinate.id,
|
|
33
|
+
type: coordinate.type,
|
|
34
|
+
layers: [target]
|
|
35
|
+
});
|
|
36
|
+
next = applyPositionSemantics(next, {
|
|
37
|
+
target,
|
|
38
|
+
channel,
|
|
39
|
+
previous,
|
|
40
|
+
field,
|
|
41
|
+
datum,
|
|
42
|
+
hasField,
|
|
43
|
+
fieldType,
|
|
44
|
+
bin,
|
|
45
|
+
aggregate,
|
|
46
|
+
stack
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
next = next.editSemantic({
|
|
50
|
+
property: `layer[${target}].encoding.${channel}.scale`,
|
|
51
|
+
value: scale.id
|
|
52
|
+
});
|
|
53
|
+
next = applyEncodingScale(next, scale, requestedScale, {
|
|
54
|
+
reassignment: previous?.scale === scale.id
|
|
55
|
+
});
|
|
56
|
+
next = rebindPositionGuides(
|
|
57
|
+
next,
|
|
58
|
+
channel,
|
|
59
|
+
previous?.scale,
|
|
60
|
+
scale.id,
|
|
61
|
+
target
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (layer.mark.type === "bar") {
|
|
65
|
+
const updated = findLayer(next, target);
|
|
66
|
+
const pendingBox = next.markConfigs[target]?.boxPlot;
|
|
67
|
+
if (pendingBox !== undefined && !pendingBox.materialized) {
|
|
68
|
+
return updated.encoding?.x !== undefined && updated.encoding?.y !== undefined
|
|
69
|
+
? next.materializeBoxPlot({ id: target })
|
|
70
|
+
: next;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const updated = findLayer(next, target);
|
|
75
|
+
for (const step of getPositionEncodingMaterializationSteps(
|
|
76
|
+
next,
|
|
77
|
+
updated,
|
|
78
|
+
scale.id
|
|
79
|
+
)) {
|
|
80
|
+
next = next[step.op](step.args);
|
|
81
|
+
}
|
|
82
|
+
return next;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const encodeX = action(
|
|
86
|
+
{
|
|
87
|
+
op: "encodeX",
|
|
88
|
+
description: "Encode a field as horizontal position."
|
|
89
|
+
},
|
|
90
|
+
function (args = {}) {
|
|
91
|
+
return encodePosition(this, "x", args, "encodeX");
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const encodeY = action(
|
|
96
|
+
{
|
|
97
|
+
op: "encodeY",
|
|
98
|
+
description: "Encode a field as vertical position."
|
|
99
|
+
},
|
|
100
|
+
function (args = {}) {
|
|
101
|
+
return encodePosition(this, "y", args, "encodeY");
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const encodeTheta = action(
|
|
106
|
+
{
|
|
107
|
+
op: "encodeTheta",
|
|
108
|
+
description: "Encode a field as Polar angle in clockwise degrees."
|
|
109
|
+
},
|
|
110
|
+
function (args = {}) {
|
|
111
|
+
return encodePosition(this, "theta", args, "encodeTheta");
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const encodeR = action(
|
|
116
|
+
{
|
|
117
|
+
op: "encodeR",
|
|
118
|
+
description: "Encode a quantitative field as Polar radius."
|
|
119
|
+
},
|
|
120
|
+
function (args = {}) {
|
|
121
|
+
return encodePosition(this, "radius", args, "encodeR");
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
export function registerPositionEncodingActions(ProgramClass) {
|
|
126
|
+
ProgramClass.prototype.encodeX = encodeX;
|
|
127
|
+
ProgramClass.prototype.encodeY = encodeY;
|
|
128
|
+
ProgramClass.prototype.encodeTheta = encodeTheta;
|
|
129
|
+
ProgramClass.prototype.encodeR = encodeR;
|
|
130
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { validateAggregate } from "../../../../grammar/aggregate.js";
|
|
2
|
+
import { emptyPositionPolicy } from "./common.js";
|
|
3
|
+
|
|
4
|
+
export function resolveArcPositionPolicy({ channel, args, fieldType }) {
|
|
5
|
+
if (args.bin !== undefined) {
|
|
6
|
+
throw new Error(`Arc ${channel} encoding does not support bin.`);
|
|
7
|
+
}
|
|
8
|
+
if (args.stack !== undefined) {
|
|
9
|
+
throw new Error(`Arc ${channel} encoding does not support stack.`);
|
|
10
|
+
}
|
|
11
|
+
if (channel === "radius") {
|
|
12
|
+
if (args.aggregate !== undefined) {
|
|
13
|
+
throw new Error("Arc radius encoding does not support aggregate.");
|
|
14
|
+
}
|
|
15
|
+
return emptyPositionPolicy();
|
|
16
|
+
}
|
|
17
|
+
if (!["ordinal", "nominal"].includes(fieldType)) {
|
|
18
|
+
throw new Error("Arc theta encoding requires an ordinal or nominal field.");
|
|
19
|
+
}
|
|
20
|
+
if (args.aggregate === undefined) return emptyPositionPolicy();
|
|
21
|
+
const aggregate = validateAggregate(args.aggregate);
|
|
22
|
+
if (aggregate !== "count") {
|
|
23
|
+
throw new Error('Arc theta aggregate currently supports only "count".');
|
|
24
|
+
}
|
|
25
|
+
return { bin: undefined, aggregate, stack: undefined };
|
|
26
|
+
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
} from "../../../../grammar/aggregate.js";
|
|
5
5
|
import {
|
|
6
6
|
BAR_ORIENTATIONS,
|
|
7
|
+
resolveBarOffsetChannel,
|
|
7
8
|
resolveBarOrientation
|
|
8
9
|
} from "../../../../grammar/bars/policy.js";
|
|
9
10
|
import { resolveBin, validateStack } from "./common.js";
|
|
@@ -98,12 +99,15 @@ export function resolveBarPositionPolicy({
|
|
|
98
99
|
`Bar ${channel} encoding requires a quantitative field opposite a categorical position.`
|
|
99
100
|
);
|
|
100
101
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
const expectedOffset = resolveBarOffsetChannel(candidate);
|
|
103
|
+
const incompatibleOffset = orientation === BAR_ORIENTATIONS.horizontal
|
|
104
|
+
? "xOffset"
|
|
105
|
+
: orientation === BAR_ORIENTATIONS.vertical
|
|
106
|
+
? "yOffset"
|
|
107
|
+
: undefined;
|
|
108
|
+
if (incompatibleOffset !== undefined && candidate.encoding?.[incompatibleOffset] !== undefined) {
|
|
105
109
|
throw new Error(
|
|
106
|
-
|
|
110
|
+
`${orientation} bars require ${expectedOffset}; remove the incompatible ${incompatibleOffset} encoding.`
|
|
107
111
|
);
|
|
108
112
|
}
|
|
109
113
|
return { bin, aggregate, stack };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { validatePositionFieldCompatibility } from
|
|
2
2
|
"../../../../grammar/positionCompatibility.js";
|
|
3
3
|
import { resolveAreaPositionPolicy } from "./area.js";
|
|
4
|
+
import { resolveArcPositionPolicy } from "./arc.js";
|
|
4
5
|
import { resolveBarPositionPolicy } from "./bar.js";
|
|
5
6
|
import { resolveLinePositionPolicy } from "./line.js";
|
|
6
7
|
import { resolvePointPositionPolicy } from "./point.js";
|
|
@@ -8,10 +9,13 @@ import { resolveRulePositionPolicy } from "./rule.js";
|
|
|
8
9
|
|
|
9
10
|
const POSITION_POLICIES = Object.freeze({
|
|
10
11
|
area: resolveAreaPositionPolicy,
|
|
12
|
+
arc: resolveArcPositionPolicy,
|
|
11
13
|
bar: resolveBarPositionPolicy,
|
|
12
14
|
line: resolveLinePositionPolicy,
|
|
13
15
|
point: resolvePointPositionPolicy,
|
|
14
|
-
rule: resolveRulePositionPolicy
|
|
16
|
+
rule: resolveRulePositionPolicy,
|
|
17
|
+
text: resolvePointPositionPolicy,
|
|
18
|
+
rect: resolvePointPositionPolicy
|
|
15
19
|
});
|
|
16
20
|
|
|
17
21
|
export function resolveMarkPositionPolicy(context) {
|
|
@@ -14,22 +14,39 @@ function rejectSharedOptions(args, channel) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function resolveLinePositionPolicy({
|
|
17
|
+
program,
|
|
17
18
|
layer,
|
|
18
19
|
dataset,
|
|
19
20
|
channel,
|
|
20
21
|
args,
|
|
21
22
|
fieldType
|
|
22
23
|
}) {
|
|
24
|
+
const polar = ["theta", "radius"].includes(channel);
|
|
25
|
+
const config = program.markConfigs[layer.id] ?? {};
|
|
26
|
+
if (polar) {
|
|
27
|
+
if (config.curve !== undefined && config.curve !== "linear") {
|
|
28
|
+
throw new Error("Polar line position currently requires curve \"linear\".");
|
|
29
|
+
}
|
|
30
|
+
rejectSharedOptions(args, channel);
|
|
31
|
+
if (args.aggregate !== undefined) {
|
|
32
|
+
throw new Error(`Line ${channel} encoding does not support aggregate.`);
|
|
33
|
+
}
|
|
34
|
+
return emptyPositionPolicy();
|
|
35
|
+
}
|
|
36
|
+
if (config.closed === true) {
|
|
37
|
+
throw new Error("Line closed requires theta/radius Polar position encodings.");
|
|
38
|
+
}
|
|
23
39
|
const regression = dataset.transform?.some(item => item.type === "regression");
|
|
24
40
|
const interval = dataset.transform?.some(item => item.type === "interval");
|
|
25
41
|
|
|
26
42
|
if (channel === "x") {
|
|
27
|
-
const
|
|
28
|
-
layer.encoding?.y?.
|
|
43
|
+
const directPair =
|
|
44
|
+
layer.encoding?.y?.aggregate === undefined &&
|
|
45
|
+
["quantitative", "temporal"].includes(layer.encoding?.y?.fieldType) &&
|
|
29
46
|
fieldType === "quantitative";
|
|
30
47
|
if (
|
|
31
48
|
fieldType !== "temporal" &&
|
|
32
|
-
!((regression || interval ||
|
|
49
|
+
!((regression || interval || directPair) && fieldType === "quantitative")
|
|
33
50
|
) {
|
|
34
51
|
throw new Error(
|
|
35
52
|
"Line x encoding requires a temporal field or a compatible derived quantitative field."
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
readTemporalField,
|
|
8
8
|
validateFieldType,
|
|
9
9
|
validatePositionChannel
|
|
10
|
-
} from "../../../grammar/scales.js";
|
|
10
|
+
} from "../../../grammar/scales/index.js";
|
|
11
11
|
import { resolvePositionScaleDefinition } from "../../scales/definitions.js";
|
|
12
12
|
import { findCoordinate } from "../../../selectors/coordinates.js";
|
|
13
13
|
import {
|
|
@@ -22,12 +22,29 @@ import {
|
|
|
22
22
|
} from "../../../grammar/aggregate.js";
|
|
23
23
|
import { normalizeRuleDatum } from "../../../grammar/rules.js";
|
|
24
24
|
import { resolveMarkPositionPolicy } from "./policies/index.js";
|
|
25
|
+
import {
|
|
26
|
+
getPositionChannelDefinition,
|
|
27
|
+
positionChannelsForFamily
|
|
28
|
+
} from "../../../core/vocabulary.js";
|
|
25
29
|
|
|
26
30
|
const POSITION_ENCODING_OPTIONS = Object.freeze([
|
|
27
31
|
"field", "datum", "target", "fieldType", "scale", "coordinate",
|
|
28
32
|
"aggregate", "bin", "stack"
|
|
29
33
|
]);
|
|
30
34
|
|
|
35
|
+
function validateCoordinateFamily(layer, channel, operation) {
|
|
36
|
+
const family = getPositionChannelDefinition(channel).family;
|
|
37
|
+
const incompatible = positionChannelsForFamily(
|
|
38
|
+
family === "polar" ? "cartesian" : "polar"
|
|
39
|
+
);
|
|
40
|
+
const existing = incompatible.filter(name => layer.encoding?.[name] !== undefined);
|
|
41
|
+
if (existing.length > 0) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`${operation} cannot mix ${channel} with ${existing.join("/")} position encodings on layer "${layer.id}".`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
31
48
|
function resolveCoordinate(program, channel, layer, requestedId) {
|
|
32
49
|
const defaults = getPositionCoordinateDefaults(channel);
|
|
33
50
|
const existingId = layer.coordinate;
|
|
@@ -35,7 +52,19 @@ function resolveCoordinate(program, channel, layer, requestedId) {
|
|
|
35
52
|
if (existingId !== undefined && requestedId !== undefined && existingId !== requestedId) {
|
|
36
53
|
throw new Error(`Layer "${layer.id}" already uses coordinate "${existingId}".`);
|
|
37
54
|
}
|
|
38
|
-
const
|
|
55
|
+
const compatible = program.semanticSpec.coordinates.filter(
|
|
56
|
+
coordinate => coordinate.type === defaults.type
|
|
57
|
+
);
|
|
58
|
+
if (
|
|
59
|
+
existingId === undefined &&
|
|
60
|
+
requestedId === undefined &&
|
|
61
|
+
compatible.length > 1
|
|
62
|
+
) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`${channel} encoding requires coordinate when multiple ${defaults.type} coordinates are available.`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const id = existingId ?? requestedId ?? compatible[0]?.id ?? defaults.id;
|
|
39
68
|
const coordinate = findCoordinate(program, id);
|
|
40
69
|
if (coordinate !== undefined && coordinate.type !== defaults.type) {
|
|
41
70
|
throw new Error(
|
|
@@ -51,8 +80,9 @@ export function resolvePositionEncoding(program, channel, args, operation) {
|
|
|
51
80
|
const { id: target, dataset, layer } = resolveTarget(
|
|
52
81
|
program,
|
|
53
82
|
args.target,
|
|
54
|
-
|
|
83
|
+
getPositionChannelDefinition(channel).markTypes
|
|
55
84
|
);
|
|
85
|
+
validateCoordinateFamily(layer, channel, operation);
|
|
56
86
|
const hasField = Object.hasOwn(args, "field");
|
|
57
87
|
const hasDatum = Object.hasOwn(args, "datum");
|
|
58
88
|
if (layer.mark.type === "rule") {
|
|
@@ -66,8 +96,11 @@ export function resolvePositionEncoding(program, channel, args, operation) {
|
|
|
66
96
|
throw new Error(`${operation} does not support datum for a ${layer.mark.type} mark.`);
|
|
67
97
|
}
|
|
68
98
|
const previous = layer.encoding?.[channel];
|
|
69
|
-
const requestedFieldType =
|
|
70
|
-
|
|
99
|
+
const requestedFieldType = args.fieldType ?? previous?.fieldType ?? (
|
|
100
|
+
layer.mark.type === "arc" && channel === "theta" && args.aggregate === "count"
|
|
101
|
+
? "nominal"
|
|
102
|
+
: "quantitative"
|
|
103
|
+
);
|
|
71
104
|
const fieldType = requestedFieldType === "nominal"
|
|
72
105
|
? "nominal"
|
|
73
106
|
: validateFieldType(requestedFieldType);
|
|
@@ -98,7 +131,9 @@ export function resolvePositionEncoding(program, channel, args, operation) {
|
|
|
98
131
|
throw new TypeError(`${operation} field must be a non-empty string.`);
|
|
99
132
|
}
|
|
100
133
|
|
|
101
|
-
const aggregateOutput = isAggregate(policy.aggregate)
|
|
134
|
+
const aggregateOutput = isAggregate(policy.aggregate) && !(
|
|
135
|
+
layer.mark.type === "arc" && channel === "theta"
|
|
136
|
+
);
|
|
102
137
|
const requestedScale = resolveReassignmentScaleOptions(
|
|
103
138
|
previous,
|
|
104
139
|
Object.hasOwn(args, "scale") ? args.scale : {}
|
|
@@ -119,9 +154,21 @@ export function resolvePositionEncoding(program, channel, args, operation) {
|
|
|
119
154
|
? { nice: true }
|
|
120
155
|
: { discreteType: "band" }
|
|
121
156
|
: ["ordinal", "nominal"].includes(fieldType)
|
|
122
|
-
? {
|
|
157
|
+
? {
|
|
158
|
+
discreteType: ["arc", "rect"].includes(layer.mark.type)
|
|
159
|
+
? "band"
|
|
160
|
+
: "point"
|
|
161
|
+
}
|
|
123
162
|
: {}
|
|
124
163
|
);
|
|
164
|
+
if (
|
|
165
|
+
layer.mark.type === "arc" &&
|
|
166
|
+
channel === "theta" &&
|
|
167
|
+
["ordinal", "nominal"].includes(fieldType) &&
|
|
168
|
+
scale.type !== "band"
|
|
169
|
+
) {
|
|
170
|
+
throw new Error("Categorical arc theta position requires a band scale.");
|
|
171
|
+
}
|
|
125
172
|
if (Object.hasOwn(scale, "unknown") && layer.mark.type !== "point") {
|
|
126
173
|
throw new Error(
|
|
127
174
|
"Position scale unknown currently requires a row-owned point mark."
|
|
@@ -140,6 +187,8 @@ export function resolvePositionEncoding(program, channel, args, operation) {
|
|
|
140
187
|
throw new TypeError(`Field "${field}" must contain a finite number at row ${index}.`);
|
|
141
188
|
}
|
|
142
189
|
}
|
|
190
|
+
} else if (layer.mark.type === "rect") {
|
|
191
|
+
readScaleField(dataset.values, field, fieldType, { allowUnknown: true });
|
|
143
192
|
} else if (Object.hasOwn(scale, "unknown")) {
|
|
144
193
|
readScaleField(dataset.values, field, fieldType, { allowUnknown: true });
|
|
145
194
|
} else if (fieldType === "temporal") readTemporalField(dataset.values, field);
|
|
@@ -148,11 +197,11 @@ export function resolvePositionEncoding(program, channel, args, operation) {
|
|
|
148
197
|
} else readQuantitativeField(dataset.values, field);
|
|
149
198
|
|
|
150
199
|
if (
|
|
151
|
-
layer.mark.type
|
|
200
|
+
["bar", "rect"].includes(layer.mark.type) &&
|
|
152
201
|
["ordinal", "nominal"].includes(fieldType) &&
|
|
153
202
|
scale.type === "point"
|
|
154
203
|
) {
|
|
155
|
-
throw new Error(
|
|
204
|
+
throw new Error(`Categorical ${layer.mark.type} positions require a band scale.`);
|
|
156
205
|
}
|
|
157
206
|
return {
|
|
158
207
|
target,
|
|
@@ -4,14 +4,15 @@ import { isPlainObject } from "../../core/immutable.js";
|
|
|
4
4
|
import {
|
|
5
5
|
readNominalField,
|
|
6
6
|
readQuantitativeField,
|
|
7
|
+
readScaleField,
|
|
7
8
|
readTemporalField,
|
|
8
9
|
validateSemanticFieldType
|
|
9
|
-
} from "../../grammar/scales.js";
|
|
10
|
+
} from "../../grammar/scales/index.js";
|
|
10
11
|
import { normalizeRuleDatum } from "../../grammar/rules.js";
|
|
11
12
|
import {
|
|
12
13
|
canMaterializeArea,
|
|
13
14
|
canMaterializeLine
|
|
14
|
-
} from "../../materialization/marks.js";
|
|
15
|
+
} from "../../materialization/marks/index.js";
|
|
15
16
|
import { findLayer } from "../../selectors/layers.js";
|
|
16
17
|
import {
|
|
17
18
|
resolveTarget,
|
|
@@ -85,16 +86,35 @@ function encodeSecondaryPosition(program, channel, args, operation, types) {
|
|
|
85
86
|
}
|
|
86
87
|
const fieldType = rule
|
|
87
88
|
? validateSemanticFieldType(args.fieldType)
|
|
88
|
-
:
|
|
89
|
-
|
|
89
|
+
: layer.mark.type === "rect"
|
|
90
|
+
? args.fieldType ?? primary.fieldType
|
|
91
|
+
: args.fieldType ?? "quantitative";
|
|
92
|
+
if (
|
|
93
|
+
!rule &&
|
|
94
|
+
layer.mark.type !== "rect" &&
|
|
95
|
+
fieldType !== "quantitative"
|
|
96
|
+
) {
|
|
90
97
|
throw new Error(`${operation} requires a quantitative field.`);
|
|
91
98
|
}
|
|
99
|
+
if (
|
|
100
|
+
layer.mark.type === "rect" &&
|
|
101
|
+
!["quantitative", "temporal"].includes(fieldType)
|
|
102
|
+
) {
|
|
103
|
+
throw new Error(`${operation} requires a quantitative or temporal rect field.`);
|
|
104
|
+
}
|
|
105
|
+
if (layer.mark.type === "rect" && fieldType !== primary.fieldType) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`${operation} fieldType must match the primary ${primaryChannel} fieldType.`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
92
110
|
if (rule && fieldType !== primary.fieldType) {
|
|
93
111
|
throw new Error(
|
|
94
112
|
`${operation} fieldType must match the primary ${primaryChannel} fieldType.`
|
|
95
113
|
);
|
|
96
114
|
}
|
|
97
|
-
if (hasField
|
|
115
|
+
if (hasField && layer.mark.type === "rect") {
|
|
116
|
+
readScaleField(dataset.values, args.field, fieldType, { allowUnknown: true });
|
|
117
|
+
} else if (hasField) validateSecondaryField(dataset, args.field, fieldType);
|
|
98
118
|
else normalizeRuleDatum(args.datum, fieldType, channel);
|
|
99
119
|
|
|
100
120
|
const previous = layer.encoding?.[channel];
|
|
@@ -135,7 +155,9 @@ function encodeSecondaryPosition(program, channel, args, operation, types) {
|
|
|
135
155
|
if (rule) return next.rematerializeRuleMark({ id: target });
|
|
136
156
|
return layer.mark.type === "bar"
|
|
137
157
|
? next.rematerializeBarMark({ id: target })
|
|
138
|
-
:
|
|
158
|
+
: layer.mark.type === "rect"
|
|
159
|
+
? next.rematerializeRectMark({ id: target })
|
|
160
|
+
: next.rematerializeAreaMark({ id: target });
|
|
139
161
|
}
|
|
140
162
|
|
|
141
163
|
const encodeX2 = action(
|
|
@@ -149,7 +171,7 @@ const encodeX2 = action(
|
|
|
149
171
|
"x2",
|
|
150
172
|
args,
|
|
151
173
|
"encodeX2",
|
|
152
|
-
["area", "rule", "bar"]
|
|
174
|
+
["area", "rule", "bar", "rect"]
|
|
153
175
|
);
|
|
154
176
|
}
|
|
155
177
|
);
|
|
@@ -165,7 +187,7 @@ const encodeY2 = action(
|
|
|
165
187
|
"y2",
|
|
166
188
|
args,
|
|
167
189
|
"encodeY2",
|
|
168
|
-
["area", "rule", "bar"]
|
|
190
|
+
["area", "rule", "bar", "rect"]
|
|
169
191
|
);
|
|
170
192
|
}
|
|
171
193
|
);
|
|
@@ -3,10 +3,14 @@ import { findDataset } from "../../selectors/datasets.js";
|
|
|
3
3
|
import { resolveEligibleLayer } from "../../selectors/layers.js";
|
|
4
4
|
import { hasMaterializedLegend } from "../../materialization/legends.js";
|
|
5
5
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
6
|
-
import {
|
|
6
|
+
import { validateOptionObject } from "../../core/validation.js";
|
|
7
|
+
import {
|
|
8
|
+
getMarkGraphicTypes,
|
|
9
|
+
getPositionChannelDefinition
|
|
10
|
+
} from "../../core/vocabulary.js";
|
|
7
11
|
|
|
8
12
|
export function validateOptions(args, supported, operation) {
|
|
9
|
-
|
|
13
|
+
validateOptionObject(args, supported, operation);
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
export function validateLineSeriesCompatibility(layer, channel, field) {
|
|
@@ -65,10 +69,12 @@ export function rebindPositionGuides(
|
|
|
65
69
|
nextScale,
|
|
66
70
|
target
|
|
67
71
|
) {
|
|
72
|
+
const definition = getPositionChannelDefinition(channel);
|
|
73
|
+
if (definition?.guideChannel === undefined) return program;
|
|
68
74
|
if (previousScale === undefined || previousScale === nextScale) return program;
|
|
69
75
|
|
|
70
|
-
const axis = program.semanticSpec.guides.axis?.[
|
|
71
|
-
const direction =
|
|
76
|
+
const axis = program.semanticSpec.guides.axis?.[definition.guideChannel];
|
|
77
|
+
const direction = definition.gridDirection;
|
|
72
78
|
const grid = program.semanticSpec.guides.grid?.[direction];
|
|
73
79
|
const ownsAxis = axis?.scale === previousScale;
|
|
74
80
|
const ownsGrid = grid?.scale === previousScale;
|
|
@@ -86,13 +92,13 @@ export function rebindPositionGuides(
|
|
|
86
92
|
let next = program;
|
|
87
93
|
if (ownsAxis) {
|
|
88
94
|
next = next.editSemantic({
|
|
89
|
-
property: `guide.axis.${
|
|
95
|
+
property: `guide.axis.${definition.guideChannel}.scale`,
|
|
90
96
|
value: nextScale
|
|
91
97
|
});
|
|
92
|
-
for (const component of ["ticks", "labels", "title"]) {
|
|
93
|
-
const config = next.guideConfigs.axis?.[
|
|
98
|
+
for (const component of ["line", "ticks", "labels", "title"]) {
|
|
99
|
+
const config = next.guideConfigs.axis?.[definition.guideChannel]?.[component];
|
|
94
100
|
if (config?.scale === previousScale) {
|
|
95
|
-
next = next._withGuideConfig(
|
|
101
|
+
next = next._withGuideConfig(definition.guideChannel, component, {
|
|
96
102
|
...config,
|
|
97
103
|
scale: nextScale
|
|
98
104
|
});
|
|
@@ -141,22 +147,12 @@ export function resolveTarget(
|
|
|
141
147
|
throw new Error(`Mark "${id}" requires an existing dataset.`);
|
|
142
148
|
}
|
|
143
149
|
|
|
144
|
-
const expectedGraphic =
|
|
145
|
-
point: ["circle", "rect", "collection"],
|
|
146
|
-
line: "path",
|
|
147
|
-
bar: "rect",
|
|
148
|
-
area: "path",
|
|
149
|
-
rule: "line"
|
|
150
|
-
}[layer.mark.type];
|
|
150
|
+
const expectedGraphic = getMarkGraphicTypes(layer.mark.type);
|
|
151
151
|
|
|
152
152
|
const graphicType = program.graphicSpec.objects[id]?.type;
|
|
153
|
-
const matches =
|
|
154
|
-
? expectedGraphic.includes(graphicType)
|
|
155
|
-
: graphicType === expectedGraphic;
|
|
153
|
+
const matches = expectedGraphic?.includes(graphicType) === true;
|
|
156
154
|
if (!matches) {
|
|
157
|
-
const label =
|
|
158
|
-
? expectedGraphic.join(" or ")
|
|
159
|
-
: expectedGraphic;
|
|
155
|
+
const label = expectedGraphic?.join(" or ") ?? "supported";
|
|
160
156
|
throw new Error(`Mark "${id}" requires ${label} graphics.`);
|
|
161
157
|
}
|
|
162
158
|
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
normalizeStrokeDashPattern,
|
|
4
4
|
readNominalField,
|
|
5
5
|
validateNominalFieldType
|
|
6
|
-
} from "../../grammar/scales.js";
|
|
6
|
+
} from "../../grammar/scales/index.js";
|
|
7
7
|
import { resolveStrokeDashScaleDefinition } from "../scales/definitions.js";
|
|
8
8
|
import { applyMaterializationPlan } from "../../materialization/dependencies.js";
|
|
9
9
|
import { planEncodingRematerialization } from "../../materialization/encodings.js";
|