ggaction 0.0.6 → 0.0.8
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 +43 -0
- package/README.md +77 -14
- package/package.json +19 -2
- package/src/BasicChartProgram.js +10 -0
- package/src/actions/basic.js +21 -0
- package/src/actions/canvas/actions.js +42 -0
- package/src/actions/canvas/index.js +5 -1
- package/src/actions/categoryOrder/index.js +111 -0
- package/src/actions/charts/basic.js +13 -0
- package/src/actions/data/basic.js +11 -0
- package/src/actions/data/basicBin2d.js +43 -0
- package/src/actions/data/bin2d.js +1 -26
- package/src/actions/data/bin2dMaterialize.js +29 -0
- package/src/actions/data/index.js +6 -0
- package/src/actions/data/timeUnit.js +48 -0
- package/src/actions/encodings/angle.js +77 -0
- package/src/actions/encodings/appearance.js +6 -2
- package/src/actions/encodings/basic.js +21 -0
- package/src/actions/encodings/color/index.js +17 -13
- package/src/actions/encodings/color/layout.js +2 -0
- package/src/actions/encodings/color/policy.js +3 -0
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/position/index.js +5 -1
- package/src/actions/encodings/position/policies/area.js +40 -2
- package/src/actions/encodings/position/policies/bar.js +6 -0
- package/src/actions/encodings/position/policies/index.js +2 -0
- package/src/actions/encodings/position/policies/tick.js +19 -0
- package/src/actions/encodings/ranged.js +6 -2
- package/src/actions/encodings/remove.js +1 -1
- package/src/actions/guides/axes/index.js +10 -2
- package/src/actions/guides/basic.js +19 -0
- package/src/actions/guides/grids/grid.js +7 -3
- package/src/actions/guides/grids/index.js +1 -1
- package/src/actions/guides/legends/categorical/actions.js +23 -6
- package/src/actions/guides/legends/categorical/index.js +14 -2
- package/src/actions/guides/legends/continuous/common.js +17 -5
- package/src/actions/guides/legends/continuous/gradient.js +12 -8
- package/src/actions/guides/legends/continuous/index.js +6 -2
- package/src/actions/guides/legends/continuous/opacity.js +70 -17
- package/src/actions/guides/legends/edit.js +13 -8
- package/src/actions/guides/legends/lane.js +468 -0
- package/src/actions/guides/legends/remove.js +3 -7
- package/src/actions/index.js +2 -0
- package/src/actions/marks/area/actions.js +3 -1
- package/src/actions/marks/area/materialize.js +12 -3
- package/src/actions/marks/bar/index.js +5 -1
- package/src/actions/marks/basic.js +11 -0
- package/src/actions/marks/index.js +2 -0
- package/src/actions/marks/line/actions.js +5 -1
- package/src/actions/marks/line/index.js +4 -1
- package/src/actions/marks/point/index.js +5 -1
- package/src/actions/marks/point/materialize.js +17 -5
- package/src/actions/marks/rect/actions.js +5 -1
- package/src/actions/marks/rect/index.js +4 -1
- package/src/actions/marks/tick/actions.js +225 -0
- package/src/actions/marks/tick/index.js +1 -0
- package/src/actions/primitives/semanticValidation/layer.js +2 -0
- package/src/actions/scales/consumers/index.js +8 -0
- package/src/actions/scales/consumers/seriesLayout.js +22 -2
- package/src/actions/scales/index.js +5 -1
- package/src/actions/scales/materialize.js +2 -0
- package/src/actions/selection/actions.js +5 -2
- package/src/basic.js +2 -0
- package/src/core/vocabulary.js +7 -3
- package/src/grammar/areaSeries.js +112 -2
- package/src/grammar/categoryOrder.js +138 -0
- package/src/grammar/direction.js +46 -0
- package/src/grammar/facets/index.js +1 -1
- package/src/grammar/pointShapes.js +34 -6
- package/src/grammar/positionCompatibility.js +4 -0
- package/src/grammar/schemas/semanticPath.js +2 -1
- package/src/grammar/seriesLayout.js +10 -2
- package/src/grammar/timeUnit.js +108 -0
- package/src/grammar/transforms.js +6 -0
- package/src/grammar/window.js +73 -7
- package/src/layout/legendLane.js +338 -0
- package/src/materialization/marks/capabilities.js +9 -0
- package/src/materialization/marks/index.js +2 -1
- package/src/materialization/marks/policies.js +13 -0
- package/src/materialization/scales/policies/series.js +9 -0
- package/src/materialization/scales/resolve.js +25 -3
- package/src/materialization/selection/items/index.js +1 -0
- package/src/materialization/selection/items/path.js +4 -1
- package/src/materialization/selection/items/tick.js +42 -0
- package/src/materialization/selection/policies/index.js +2 -0
- package/src/materialization/selection/policies/tick.js +10 -0
- package/src/renderers/canvas/index.js +96 -60
- package/src/renderers/canvas/text.js +3 -14
- package/src/renderers/pdf.js +112 -0
- package/src/renderers/svg.js +559 -0
- package/src/renderers/text.js +11 -0
- package/types/basic.d.ts +107 -0
- package/types/index.d.ts +10 -0
- package/types/pdf.d.ts +26 -0
- package/types/program.d.ts +96 -3
- package/types/svg.d.ts +11 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
3
|
+
import { validateKeys } from "../../core/validation.js";
|
|
4
|
+
import {
|
|
5
|
+
deriveTimeUnitRows,
|
|
6
|
+
normalizeTimeUnitTransform
|
|
7
|
+
} from "../../grammar/timeUnit.js";
|
|
8
|
+
import { MATERIALIZE_OPTIONS, requireDerivedDataset } from "./shared.js";
|
|
9
|
+
|
|
10
|
+
const OPTIONS = Object.freeze(["id", "source", "field", "unit", "as"]);
|
|
11
|
+
|
|
12
|
+
export const materializeTimeUnitData = action(
|
|
13
|
+
{
|
|
14
|
+
op: "materializeTimeUnitData",
|
|
15
|
+
description: "Materialize one immutable UTC time-unit dataset."
|
|
16
|
+
},
|
|
17
|
+
function (args = {}) {
|
|
18
|
+
validateKeys(args, MATERIALIZE_OPTIONS, "materializeTimeUnitData");
|
|
19
|
+
const { id, source, transform } = requireDerivedDataset(
|
|
20
|
+
this,
|
|
21
|
+
args.id,
|
|
22
|
+
"timeUnit"
|
|
23
|
+
);
|
|
24
|
+
return this.editSemantic({
|
|
25
|
+
property: `dataset[${id}].values`,
|
|
26
|
+
value: deriveTimeUnitRows(source.values, transform)
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
export const createTimeUnitData = action(
|
|
32
|
+
{
|
|
33
|
+
op: "createTimeUnitData",
|
|
34
|
+
description: "Create immutable UTC calendar bucket values."
|
|
35
|
+
},
|
|
36
|
+
function (args = {}) {
|
|
37
|
+
validateKeys(args, OPTIONS, "createTimeUnitData");
|
|
38
|
+
const id = validateUserId(args.id, "Time-unit dataset id");
|
|
39
|
+
const source = validateUserId(
|
|
40
|
+
args.source ?? this.context.currentData,
|
|
41
|
+
"Source dataset id"
|
|
42
|
+
);
|
|
43
|
+
const transform = normalizeTimeUnitTransform(args);
|
|
44
|
+
return this
|
|
45
|
+
.createDerivedData({ id, source, transform: [transform] })
|
|
46
|
+
.materializeTimeUnitData({ id });
|
|
47
|
+
}
|
|
48
|
+
);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { readQuantitativeField } from "../../grammar/scales/index.js";
|
|
3
|
+
import { resolveTarget, validateOptions } from "./shared.js";
|
|
4
|
+
|
|
5
|
+
const OPTIONS = Object.freeze(["target", "value", "field", "fieldType"]);
|
|
6
|
+
|
|
7
|
+
function rematerialize(program, layer, target) {
|
|
8
|
+
return layer.mark.type === "tick"
|
|
9
|
+
? program.rematerializeTickMark({ id: target })
|
|
10
|
+
: program.rematerializePointMark({ id: target });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const encodeAngle = action(
|
|
14
|
+
{
|
|
15
|
+
op: "encodeAngle",
|
|
16
|
+
description: "Assign direct clockwise degree rotation to point or Tick glyphs."
|
|
17
|
+
},
|
|
18
|
+
function (args = {}) {
|
|
19
|
+
validateOptions(args, OPTIONS, "encodeAngle");
|
|
20
|
+
const hasValue = Object.hasOwn(args, "value");
|
|
21
|
+
const hasField = Object.hasOwn(args, "field");
|
|
22
|
+
if (hasValue === hasField) {
|
|
23
|
+
throw new Error("encodeAngle requires exactly one of value or field.");
|
|
24
|
+
}
|
|
25
|
+
if (hasValue && args.fieldType !== undefined) {
|
|
26
|
+
throw new Error("Constant angle does not accept fieldType.");
|
|
27
|
+
}
|
|
28
|
+
const { id: target, dataset, layer } = resolveTarget(
|
|
29
|
+
this,
|
|
30
|
+
args.target,
|
|
31
|
+
["point", "tick"],
|
|
32
|
+
"point or Tick mark"
|
|
33
|
+
);
|
|
34
|
+
if (hasValue && !Number.isFinite(args.value)) {
|
|
35
|
+
throw new TypeError("encodeAngle value must be finite degrees.");
|
|
36
|
+
}
|
|
37
|
+
if (hasField) {
|
|
38
|
+
const fieldType = args.fieldType ?? "quantitative";
|
|
39
|
+
if (fieldType !== "quantitative") {
|
|
40
|
+
throw new Error("encodeAngle requires a quantitative field.");
|
|
41
|
+
}
|
|
42
|
+
readQuantitativeField(dataset.values, args.field);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const previous = layer.encoding?.angle;
|
|
46
|
+
let next = this;
|
|
47
|
+
for (const property of ["datum", "field", "fieldType"]) {
|
|
48
|
+
if (Object.hasOwn(previous ?? {}, property)) {
|
|
49
|
+
next = next.editSemantic({
|
|
50
|
+
property: `layer[${target}].encoding.angle.${property}`,
|
|
51
|
+
remove: true
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (hasValue) {
|
|
56
|
+
next = next.editSemantic({
|
|
57
|
+
property: `layer[${target}].encoding.angle.datum`,
|
|
58
|
+
value: args.value
|
|
59
|
+
});
|
|
60
|
+
} else {
|
|
61
|
+
next = next
|
|
62
|
+
.editSemantic({
|
|
63
|
+
property: `layer[${target}].encoding.angle.field`,
|
|
64
|
+
value: args.field
|
|
65
|
+
})
|
|
66
|
+
.editSemantic({
|
|
67
|
+
property: `layer[${target}].encoding.angle.fieldType`,
|
|
68
|
+
value: "quantitative"
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return rematerialize(next, layer, target);
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
export function registerAngleEncodingAction(ProgramClass) {
|
|
76
|
+
ProgramClass.prototype.encodeAngle = encodeAngle;
|
|
77
|
+
}
|
|
@@ -277,12 +277,16 @@ const encodeOpacity = action(
|
|
|
277
277
|
}
|
|
278
278
|
);
|
|
279
279
|
|
|
280
|
+
export function registerBasicAppearanceEncodingActions(ProgramClass) {
|
|
281
|
+
ProgramClass.prototype.encodeSize = encodeSize;
|
|
282
|
+
ProgramClass.prototype.encodeShape = encodeShape;
|
|
283
|
+
}
|
|
284
|
+
|
|
280
285
|
export function registerAppearanceEncodingAction(ProgramClass) {
|
|
286
|
+
registerBasicAppearanceEncodingActions(ProgramClass);
|
|
281
287
|
ProgramClass.prototype.encodeRadius = encodeRadius;
|
|
282
288
|
ProgramClass.prototype.encodePointRadius = encodePointRadius;
|
|
283
289
|
ProgramClass.prototype.removePointRadius = removePointRadius;
|
|
284
|
-
ProgramClass.prototype.encodeSize = encodeSize;
|
|
285
|
-
ProgramClass.prototype.encodeShape = encodeShape;
|
|
286
290
|
ProgramClass.prototype.encodeOpacity = encodeOpacity;
|
|
287
291
|
ProgramClass.prototype.clearOpacityEncoding = clearOpacityEncoding;
|
|
288
292
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { registerBasicAppearanceEncodingActions } from "./appearance.js";
|
|
2
|
+
import { registerBarWidthEncodingAction } from "./barWidth.js";
|
|
3
|
+
import { registerColorEncodingAction } from "./color/index.js";
|
|
4
|
+
import { registerHistogramEncodingAction } from "./histogram.js";
|
|
5
|
+
import { registerOffsetEncodingAction } from "./offset.js";
|
|
6
|
+
import {
|
|
7
|
+
registerCartesianPositionEncodingActions
|
|
8
|
+
} from "./position/index.js";
|
|
9
|
+
import { registerBasicRangedEncodingActions } from "./ranged.js";
|
|
10
|
+
import { registerStrokeDashEncodingActions } from "./strokeDash.js";
|
|
11
|
+
|
|
12
|
+
export function registerBasicEncodingActions(ProgramClass) {
|
|
13
|
+
registerCartesianPositionEncodingActions(ProgramClass);
|
|
14
|
+
registerBasicRangedEncodingActions(ProgramClass);
|
|
15
|
+
registerColorEncodingAction(ProgramClass);
|
|
16
|
+
registerBasicAppearanceEncodingActions(ProgramClass);
|
|
17
|
+
registerStrokeDashEncodingActions(ProgramClass);
|
|
18
|
+
registerOffsetEncodingAction(ProgramClass);
|
|
19
|
+
registerHistogramEncodingAction(ProgramClass);
|
|
20
|
+
registerBarWidthEncodingAction(ProgramClass);
|
|
21
|
+
}
|
|
@@ -70,8 +70,18 @@ const encodeColor = action(
|
|
|
70
70
|
const horizonColor = horizonTransform !== undefined &&
|
|
71
71
|
horizonTransform.as?.color === args.field &&
|
|
72
72
|
layer.encoding?.group?.field === horizonTransform.as?.group;
|
|
73
|
+
const barGrain = resolveBarGrain(layer);
|
|
74
|
+
if (layer.mark.type === "bar" && barGrain === undefined) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
"Bar color encoding requires a complete histogram encoding or a complete ordinal aggregate encoding."
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const layout = resolveColorLayout(layer, args.layout, barGrain);
|
|
80
|
+
const createsCenterGroup = layer.mark.type === "area" &&
|
|
81
|
+
layout === "center" && layer.encoding?.group?.field === undefined;
|
|
73
82
|
if (
|
|
74
83
|
layer.mark.type === "area" &&
|
|
84
|
+
!createsCenterGroup &&
|
|
75
85
|
(layer.encoding?.group?.field === undefined ||
|
|
76
86
|
layer.encoding.group.field !== args.field) &&
|
|
77
87
|
!densitySeriesFields.includes(args.field) &&
|
|
@@ -82,14 +92,6 @@ const encodeColor = action(
|
|
|
82
92
|
);
|
|
83
93
|
}
|
|
84
94
|
validateLineSeriesCompatibility(layer, "color", args.field);
|
|
85
|
-
|
|
86
|
-
const barGrain = resolveBarGrain(layer);
|
|
87
|
-
if (layer.mark.type === "bar" && barGrain === undefined) {
|
|
88
|
-
throw new Error(
|
|
89
|
-
"Bar color encoding requires a complete histogram encoding or a complete ordinal aggregate encoding."
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
const layout = resolveColorLayout(layer, args.layout, barGrain);
|
|
93
95
|
const requestedScale = resolveReassignmentScaleOptions(
|
|
94
96
|
layer.encoding?.color,
|
|
95
97
|
resolveColorScaleOptions(args)
|
|
@@ -112,11 +114,13 @@ const encodeColor = action(
|
|
|
112
114
|
readNominalField(dataset.values, args.field);
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
let next =
|
|
116
|
-
.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
117
|
+
let next = createsCenterGroup
|
|
118
|
+
? this.encodeGroup({ target, field: args.field })
|
|
119
|
+
: this;
|
|
120
|
+
next = next.editSemantic({
|
|
121
|
+
property: `layer[${target}].encoding.color.field`,
|
|
122
|
+
value: args.field
|
|
123
|
+
})
|
|
120
124
|
.editSemantic({
|
|
121
125
|
property: `layer[${target}].encoding.color.fieldType`,
|
|
122
126
|
value: fieldType
|
|
@@ -74,6 +74,9 @@ export function resolveColorLayout(layer, requested, barGrain) {
|
|
|
74
74
|
if (layer.mark.type === "area" && layout === "group") {
|
|
75
75
|
throw new Error('Area color layout does not support "group".');
|
|
76
76
|
}
|
|
77
|
+
if (layer.mark.type === "bar" && layout === "center") {
|
|
78
|
+
throw new Error("Centered bars are not supported.");
|
|
79
|
+
}
|
|
77
80
|
if (
|
|
78
81
|
layer.mark.type === "arc" &&
|
|
79
82
|
layout !== undefined &&
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { registerAppearanceEncodingAction } from "./appearance.js";
|
|
2
|
+
import { registerAngleEncodingAction } from "./angle.js";
|
|
2
3
|
import { registerBarWidthEncodingAction } from "./barWidth.js";
|
|
3
4
|
import { registerColorEncodingAction } from "./color/index.js";
|
|
4
5
|
import { registerDensityEncodingAction } from "./density.js";
|
|
@@ -26,6 +27,7 @@ export function registerEncodingActions(ProgramClass) {
|
|
|
26
27
|
registerColorEncodingAction(ProgramClass);
|
|
27
28
|
registerStrokeDashEncodingActions(ProgramClass);
|
|
28
29
|
registerAppearanceEncodingAction(ProgramClass);
|
|
30
|
+
registerAngleEncodingAction(ProgramClass);
|
|
29
31
|
registerRuleAppearanceEncodingActions(ProgramClass);
|
|
30
32
|
registerTextEncodingAction(ProgramClass);
|
|
31
33
|
registerParallelEncodingAction(ProgramClass);
|
|
@@ -135,9 +135,13 @@ const encodeR = action(
|
|
|
135
135
|
}
|
|
136
136
|
);
|
|
137
137
|
|
|
138
|
-
export function
|
|
138
|
+
export function registerCartesianPositionEncodingActions(ProgramClass) {
|
|
139
139
|
ProgramClass.prototype.encodeX = encodeX;
|
|
140
140
|
ProgramClass.prototype.encodeY = encodeY;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function registerPositionEncodingActions(ProgramClass) {
|
|
144
|
+
registerCartesianPositionEncodingActions(ProgramClass);
|
|
141
145
|
ProgramClass.prototype.encodeTheta = encodeTheta;
|
|
142
146
|
ProgramClass.prototype.encodeR = encodeR;
|
|
143
147
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { validateStack, emptyPositionPolicy } from "./common.js";
|
|
2
|
+
import { deriveCenteredAreaSeries } from "../../../../grammar/areaSeries.js";
|
|
3
|
+
import { readQuantitativeField } from "../../../../grammar/scales/index.js";
|
|
2
4
|
|
|
3
5
|
export function isCategoricalDensityPosition({
|
|
4
6
|
layer,
|
|
@@ -15,7 +17,14 @@ export function isCategoricalDensityPosition({
|
|
|
15
17
|
density.placement.categoryField === field;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
export function resolveAreaPositionPolicy({
|
|
20
|
+
export function resolveAreaPositionPolicy({
|
|
21
|
+
dataset,
|
|
22
|
+
layer,
|
|
23
|
+
channel,
|
|
24
|
+
args,
|
|
25
|
+
fieldType,
|
|
26
|
+
field
|
|
27
|
+
}) {
|
|
19
28
|
const density = dataset.transform?.find(transform => transform.type === "density");
|
|
20
29
|
if (["nominal", "ordinal"].includes(fieldType)) {
|
|
21
30
|
if (
|
|
@@ -41,6 +50,35 @@ export function resolveAreaPositionPolicy({ dataset, channel, args, fieldType, f
|
|
|
41
50
|
throw new Error("Area position encoding does not support aggregate or bin.");
|
|
42
51
|
}
|
|
43
52
|
if (args.stack === undefined) return emptyPositionPolicy();
|
|
53
|
+
const stack = validateStack(args.stack, "Area y encoding");
|
|
54
|
+
if (stack === "center") {
|
|
55
|
+
if (channel !== "y") {
|
|
56
|
+
throw new Error("Area center stack requires a y encoding.");
|
|
57
|
+
}
|
|
58
|
+
if (layer.encoding?.group?.fieldType !== "nominal") {
|
|
59
|
+
throw new Error("Area center stack requires a nominal group encoding.");
|
|
60
|
+
}
|
|
61
|
+
if (
|
|
62
|
+
layer.encoding?.color?.layout !== undefined &&
|
|
63
|
+
layer.encoding.color.layout !== "center"
|
|
64
|
+
) {
|
|
65
|
+
throw new Error("Area center stack requires a matching center color layout.");
|
|
66
|
+
}
|
|
67
|
+
const values = readQuantitativeField(dataset.values, field);
|
|
68
|
+
if (values.some(value => value < 0)) {
|
|
69
|
+
throw new RangeError("Area center stack requires non-negative values.");
|
|
70
|
+
}
|
|
71
|
+
if (layer.encoding?.x !== undefined) {
|
|
72
|
+
deriveCenteredAreaSeries(dataset.values, {
|
|
73
|
+
...layer,
|
|
74
|
+
encoding: {
|
|
75
|
+
...layer.encoding,
|
|
76
|
+
y: { field, fieldType, stack: "center" }
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return { bin: undefined, aggregate: undefined, stack };
|
|
81
|
+
}
|
|
44
82
|
if (
|
|
45
83
|
channel !== "y" ||
|
|
46
84
|
density === undefined
|
|
@@ -50,6 +88,6 @@ export function resolveAreaPositionPolicy({ dataset, channel, args, fieldType, f
|
|
|
50
88
|
return {
|
|
51
89
|
bin: undefined,
|
|
52
90
|
aggregate: undefined,
|
|
53
|
-
stack
|
|
91
|
+
stack
|
|
54
92
|
};
|
|
55
93
|
}
|
|
@@ -52,6 +52,9 @@ export function resolveBarPositionPolicy({
|
|
|
52
52
|
throw new Error('Histogram bar y aggregate must be "count".');
|
|
53
53
|
}
|
|
54
54
|
stack = validateStack(stack, "Histogram bar y encoding");
|
|
55
|
+
if (stack === "center") {
|
|
56
|
+
throw new Error("Centered bars are not supported.");
|
|
57
|
+
}
|
|
55
58
|
} else if (fieldType === "quantitative") {
|
|
56
59
|
if (args.bin !== undefined) {
|
|
57
60
|
throw new Error(
|
|
@@ -80,6 +83,9 @@ export function resolveBarPositionPolicy({
|
|
|
80
83
|
aggregate = validateAggregate(aggregate);
|
|
81
84
|
validateAggregateFieldType(aggregate, fieldType);
|
|
82
85
|
stack = validateStack(stack, `Bar ${channel} encoding`);
|
|
86
|
+
if (stack === "center") {
|
|
87
|
+
throw new Error("Centered bars are not supported.");
|
|
88
|
+
}
|
|
83
89
|
} else {
|
|
84
90
|
throw new Error(
|
|
85
91
|
"Bar position requires quantitative, temporal, ordinal, or nominal fields."
|
|
@@ -9,6 +9,7 @@ import { resolveBarPositionPolicy } from "./bar.js";
|
|
|
9
9
|
import { resolveLinePositionPolicy } from "./line.js";
|
|
10
10
|
import { resolvePointPositionPolicy } from "./point.js";
|
|
11
11
|
import { resolveRulePositionPolicy } from "./rule.js";
|
|
12
|
+
import { resolveTickPositionPolicy } from "./tick.js";
|
|
12
13
|
|
|
13
14
|
const POSITION_POLICIES = Object.freeze({
|
|
14
15
|
area: resolveAreaPositionPolicy,
|
|
@@ -17,6 +18,7 @@ const POSITION_POLICIES = Object.freeze({
|
|
|
17
18
|
line: resolveLinePositionPolicy,
|
|
18
19
|
point: resolvePointPositionPolicy,
|
|
19
20
|
rule: resolveRulePositionPolicy,
|
|
21
|
+
tick: resolveTickPositionPolicy,
|
|
20
22
|
text: resolvePointPositionPolicy,
|
|
21
23
|
rect: resolvePointPositionPolicy
|
|
22
24
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { emptyPositionPolicy } from "./common.js";
|
|
2
|
+
|
|
3
|
+
export function resolveTickPositionPolicy({ args, fieldType }) {
|
|
4
|
+
if (!["quantitative", "temporal", "ordinal", "nominal"].includes(fieldType)) {
|
|
5
|
+
throw new Error(
|
|
6
|
+
"Tick position encoding requires quantitative, temporal, ordinal, or nominal fields."
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
if (args.aggregate !== undefined) {
|
|
10
|
+
throw new Error("Tick position encoding does not support aggregate.");
|
|
11
|
+
}
|
|
12
|
+
if (args.bin !== undefined) {
|
|
13
|
+
throw new Error("Tick position encoding does not support bin.");
|
|
14
|
+
}
|
|
15
|
+
if (args.stack !== undefined) {
|
|
16
|
+
throw new Error("Tick position encoding does not support stack.");
|
|
17
|
+
}
|
|
18
|
+
return emptyPositionPolicy();
|
|
19
|
+
}
|
|
@@ -291,10 +291,14 @@ const encodeGroup = action(
|
|
|
291
291
|
}
|
|
292
292
|
);
|
|
293
293
|
|
|
294
|
-
export function
|
|
294
|
+
export function registerBasicRangedEncodingActions(ProgramClass) {
|
|
295
295
|
ProgramClass.prototype.encodeX2 = encodeX2;
|
|
296
296
|
ProgramClass.prototype.encodeY2 = encodeY2;
|
|
297
|
+
ProgramClass.prototype.encodeGroup = encodeGroup;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function registerRangedEncodingActions(ProgramClass) {
|
|
301
|
+
registerBasicRangedEncodingActions(ProgramClass);
|
|
297
302
|
ProgramClass.prototype.encodeXRange = encodeXRange;
|
|
298
303
|
ProgramClass.prototype.encodeYRange = encodeYRange;
|
|
299
|
-
ProgramClass.prototype.encodeGroup = encodeGroup;
|
|
300
304
|
}
|
|
@@ -21,7 +21,7 @@ const OPTIONS = Object.freeze(["target", "channel"]);
|
|
|
21
21
|
const REMOVABLE_CHANNELS = Object.freeze([
|
|
22
22
|
"x", "y", "x2", "y2", "xOffset", "yOffset", "theta", "radius",
|
|
23
23
|
"color", "strokeDash", "strokeWidth", "size", "shape", "group",
|
|
24
|
-
"opacity", "text"
|
|
24
|
+
"angle", "opacity", "text"
|
|
25
25
|
]);
|
|
26
26
|
const REMOVE_AXIS = Object.freeze({
|
|
27
27
|
x: "removeXAxis",
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "./remove.js";
|
|
15
15
|
import { registerParallelAxisActions } from "./parallel.js";
|
|
16
16
|
|
|
17
|
-
export function
|
|
17
|
+
export function registerBasicCartesianAxisActions(ProgramClass) {
|
|
18
18
|
registerAxisLineActions(ProgramClass);
|
|
19
19
|
registerAxisTickActions(ProgramClass);
|
|
20
20
|
registerAxisLabelActions(ProgramClass);
|
|
@@ -22,10 +22,18 @@ export function registerGuideAxisActions(ProgramClass) {
|
|
|
22
22
|
registerAxisTitleActions(ProgramClass);
|
|
23
23
|
registerAxisActions(ProgramClass);
|
|
24
24
|
registerAxisCollectionActions(ProgramClass);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function registerCartesianAxisActions(ProgramClass) {
|
|
28
|
+
registerBasicCartesianAxisActions(ProgramClass);
|
|
25
29
|
registerCompleteAxisEditActions(ProgramClass);
|
|
26
|
-
registerParallelAxisActions(ProgramClass);
|
|
27
30
|
ProgramClass.prototype.removeXAxis = removeXAxis;
|
|
28
31
|
ProgramClass.prototype.removeYAxis = removeYAxis;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function registerGuideAxisActions(ProgramClass) {
|
|
35
|
+
registerCartesianAxisActions(ProgramClass);
|
|
36
|
+
registerParallelAxisActions(ProgramClass);
|
|
29
37
|
ProgramClass.prototype.removeThetaAxis = removeThetaAxis;
|
|
30
38
|
ProgramClass.prototype.removeRadialAxis = removeRadialAxis;
|
|
31
39
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { registerBasicCartesianAxisActions } from "./axes/index.js";
|
|
2
|
+
import { registerGuideCollectionActions } from "./guides.js";
|
|
3
|
+
import { registerBasicGridActions } from "./grids/index.js";
|
|
4
|
+
import {
|
|
5
|
+
registerBasicCategoricalLegendActions
|
|
6
|
+
} from "./legends/categorical/index.js";
|
|
7
|
+
import {
|
|
8
|
+
registerGradientLegendActions
|
|
9
|
+
} from "./legends/continuous/index.js";
|
|
10
|
+
import { registerSizeLegendActions } from "./legends/size.js";
|
|
11
|
+
|
|
12
|
+
export function registerBasicGuideActions(ProgramClass) {
|
|
13
|
+
registerBasicCartesianAxisActions(ProgramClass);
|
|
14
|
+
registerBasicCategoricalLegendActions(ProgramClass);
|
|
15
|
+
registerGradientLegendActions(ProgramClass);
|
|
16
|
+
registerSizeLegendActions(ProgramClass);
|
|
17
|
+
registerBasicGridActions(ProgramClass);
|
|
18
|
+
registerGuideCollectionActions(ProgramClass);
|
|
19
|
+
}
|
|
@@ -343,13 +343,17 @@ const removeGrid = action(
|
|
|
343
343
|
);
|
|
344
344
|
|
|
345
345
|
export function registerGridActions(ProgramClass) {
|
|
346
|
-
ProgramClass
|
|
347
|
-
ProgramClass.prototype.createHorizontalGrid = createHorizontalGrid;
|
|
348
|
-
ProgramClass.prototype.createVerticalGrid = createVerticalGrid;
|
|
346
|
+
registerBasicGridActions(ProgramClass);
|
|
349
347
|
ProgramClass.prototype.editHorizontalGrid = editHorizontalGrid;
|
|
350
348
|
ProgramClass.prototype.editVerticalGrid = editVerticalGrid;
|
|
351
349
|
ProgramClass.prototype.editGrid = editGrid;
|
|
352
350
|
ProgramClass.prototype.removeGrid = removeGrid;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function registerBasicGridActions(ProgramClass) {
|
|
354
|
+
ProgramClass.prototype.createGrid = createGrid;
|
|
355
|
+
ProgramClass.prototype.createHorizontalGrid = createHorizontalGrid;
|
|
356
|
+
ProgramClass.prototype.createVerticalGrid = createVerticalGrid;
|
|
353
357
|
ProgramClass.prototype.rematerializeGrid = rematerializeGrid;
|
|
354
358
|
ProgramClass.prototype.rematerializeHorizontalGrid =
|
|
355
359
|
rematerializeHorizontalGrid;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { registerGridActions } from "./grid.js";
|
|
1
|
+
export { registerBasicGridActions, registerGridActions } from "./grid.js";
|
|
@@ -8,6 +8,11 @@ import { isSizeLegendPoint } from "../size.js";
|
|
|
8
8
|
import { isStrokeWidthLegendLayer } from "../strokeWidth.js";
|
|
9
9
|
import { legendResourcePolicies } from
|
|
10
10
|
"../../../../materialization/guides/resources.js";
|
|
11
|
+
import {
|
|
12
|
+
hasMultiHorizontalLegendLane,
|
|
13
|
+
hasMultiLegendLane,
|
|
14
|
+
hasMultiSideLegendLane
|
|
15
|
+
} from "../lane.js";
|
|
11
16
|
import {
|
|
12
17
|
resolveCurrentDefinition,
|
|
13
18
|
resolveDefinition,
|
|
@@ -91,6 +96,12 @@ export const rematerializeLegend = action(
|
|
|
91
96
|
next = next[policy.rematerializeOp]();
|
|
92
97
|
}
|
|
93
98
|
}
|
|
99
|
+
if (hasMultiSideLegendLane(next)) {
|
|
100
|
+
next = next.rematerializeSideLegendLane();
|
|
101
|
+
}
|
|
102
|
+
if (hasMultiHorizontalLegendLane(next)) {
|
|
103
|
+
next = next.rematerializeHorizontalLegendLane();
|
|
104
|
+
}
|
|
94
105
|
return next;
|
|
95
106
|
}
|
|
96
107
|
);
|
|
@@ -212,10 +223,11 @@ export const createLegend = action(
|
|
|
212
223
|
if (position !== undefined && position !== "right") {
|
|
213
224
|
throw new Error('Standalone size legends currently require position "right".');
|
|
214
225
|
}
|
|
215
|
-
|
|
226
|
+
const next = this.createSizeLegend({
|
|
216
227
|
...(target === undefined ? {} : { target }),
|
|
217
228
|
...(count === undefined ? {} : { count })
|
|
218
229
|
});
|
|
230
|
+
return hasMultiLegendLane(next) ? next.rematerializeLegend() : next;
|
|
219
231
|
}
|
|
220
232
|
const strokeWidthCandidates = this.semanticSpec.layers.filter(
|
|
221
233
|
isStrokeWidthLegendLayer
|
|
@@ -248,10 +260,11 @@ export const createLegend = action(
|
|
|
248
260
|
'Standalone stroke-width legends currently require position "right".'
|
|
249
261
|
);
|
|
250
262
|
}
|
|
251
|
-
|
|
263
|
+
const next = this.createStrokeWidthLegend({
|
|
252
264
|
...(target === undefined ? {} : { target }),
|
|
253
265
|
...(count === undefined ? {} : { count })
|
|
254
266
|
});
|
|
267
|
+
return hasMultiLegendLane(next) ? next.rematerializeLegend() : next;
|
|
255
268
|
}
|
|
256
269
|
const opacityCandidates = this.semanticSpec.layers.filter(layer =>
|
|
257
270
|
layer.mark?.type === "point" && layer.encoding?.opacity?.scale !== undefined
|
|
@@ -266,7 +279,8 @@ export const createLegend = action(
|
|
|
266
279
|
(channels === undefined && opacityCandidates.length === 1 &&
|
|
267
280
|
!hasOtherLegendCandidate)
|
|
268
281
|
) {
|
|
269
|
-
|
|
282
|
+
const next = this.createOpacityLegend(args);
|
|
283
|
+
return hasMultiLegendLane(next) ? next.rematerializeLegend() : next;
|
|
270
284
|
}
|
|
271
285
|
const continuousColorCandidates = this.semanticSpec.layers.filter(layer => {
|
|
272
286
|
const encoding = ["point", "bar", "rect"].includes(layer.mark?.type)
|
|
@@ -287,7 +301,8 @@ export const createLegend = action(
|
|
|
287
301
|
(channels?.length === 1 && channels[0] === "color" && continuousColor) ||
|
|
288
302
|
(channels === undefined && continuousColor)
|
|
289
303
|
) {
|
|
290
|
-
|
|
304
|
+
const next = this.createGradientLegend(args);
|
|
305
|
+
return hasMultiLegendLane(next) ? next.rematerializeLegend() : next;
|
|
291
306
|
}
|
|
292
307
|
const intervalColorCandidates = this.semanticSpec.layers.filter(layer => {
|
|
293
308
|
const encoding = layer.mark?.type === "point" ? layer.encoding?.color : undefined;
|
|
@@ -307,7 +322,8 @@ export const createLegend = action(
|
|
|
307
322
|
intervalColorCandidates.length > 0) ||
|
|
308
323
|
(channels === undefined && intervalColorCandidates.length > 0)
|
|
309
324
|
) {
|
|
310
|
-
|
|
325
|
+
const next = this.createIntervalLegend(args);
|
|
326
|
+
return hasMultiLegendLane(next) ? next.rematerializeLegend() : next;
|
|
311
327
|
}
|
|
312
328
|
const wantsShape = channels?.includes("shape") === true;
|
|
313
329
|
const pointCandidates = this.semanticSpec.layers.filter(layer =>
|
|
@@ -383,7 +399,8 @@ export const createLegend = action(
|
|
|
383
399
|
}
|
|
384
400
|
return next;
|
|
385
401
|
}
|
|
386
|
-
|
|
402
|
+
const next = this.createCategoricalLegend(args);
|
|
403
|
+
return hasMultiLegendLane(next) ? next.rematerializeLegend() : next;
|
|
387
404
|
}
|
|
388
405
|
);
|
|
389
406
|
|
|
@@ -24,10 +24,13 @@ import {
|
|
|
24
24
|
rematerializeLegendHighlights
|
|
25
25
|
} from "./symbols.js";
|
|
26
26
|
import { editLegend } from "../edit.js";
|
|
27
|
+
import {
|
|
28
|
+
rematerializeHorizontalLegendLane,
|
|
29
|
+
rematerializeSideLegendLane
|
|
30
|
+
} from "../lane.js";
|
|
27
31
|
|
|
28
|
-
export function
|
|
32
|
+
export function registerBasicCategoricalLegendActions(ProgramClass) {
|
|
29
33
|
ProgramClass.prototype.createLegend = createLegend;
|
|
30
|
-
ProgramClass.prototype.editLegend = editLegend;
|
|
31
34
|
ProgramClass.prototype.createCategoricalLegend = createCategoricalLegend;
|
|
32
35
|
ProgramClass.prototype.removeCategoricalLegend = removeCategoricalLegend;
|
|
33
36
|
ProgramClass.prototype.createLegendBackground = createLegendBackground;
|
|
@@ -46,4 +49,13 @@ export function registerCategoricalLegendActions(ProgramClass) {
|
|
|
46
49
|
ProgramClass.prototype.createLegendTitle = createLegendTitle;
|
|
47
50
|
ProgramClass.prototype.rematerializeLegendTitle = rematerializeLegendTitle;
|
|
48
51
|
ProgramClass.prototype.rematerializeLegend = rematerializeLegend;
|
|
52
|
+
ProgramClass.prototype.rematerializeSideLegendLane =
|
|
53
|
+
rematerializeSideLegendLane;
|
|
54
|
+
ProgramClass.prototype.rematerializeHorizontalLegendLane =
|
|
55
|
+
rematerializeHorizontalLegendLane;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function registerCategoricalLegendActions(ProgramClass) {
|
|
59
|
+
registerBasicCategoricalLegendActions(ProgramClass);
|
|
60
|
+
ProgramClass.prototype.editLegend = editLegend;
|
|
49
61
|
}
|
|
@@ -105,12 +105,21 @@ export function normalizeContinuousLegend(args, kind) {
|
|
|
105
105
|
}
|
|
106
106
|
const offset = args.offset ?? 30;
|
|
107
107
|
validateNonNegative(offset, "Legend offset");
|
|
108
|
-
const
|
|
108
|
+
const titlePosition = args.titlePosition ?? "top";
|
|
109
|
+
if (!["top", "left"].includes(titlePosition)) {
|
|
110
|
+
throw new Error(`Unsupported legend titlePosition "${titlePosition}".`);
|
|
111
|
+
}
|
|
112
|
+
if (
|
|
113
|
+
titlePosition === "left" &&
|
|
114
|
+
(!["top", "bottom"].includes(position) || kind !== "opacity")
|
|
115
|
+
) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
"Only horizontal opacity legends support left titlePosition."
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const itemGap = args.itemGap ?? (titlePosition === "left" ? 20 : 28);
|
|
109
121
|
validatePositive(itemGap, "Legend itemGap");
|
|
110
122
|
if (args.title !== undefined) validateNonEmptyString(args.title, "Legend title");
|
|
111
|
-
if (args.titlePosition !== undefined && args.titlePosition !== "top") {
|
|
112
|
-
throw new Error("Continuous legends currently require top titlePosition.");
|
|
113
|
-
}
|
|
114
123
|
if (kind === "gradient") {
|
|
115
124
|
for (const key of ["symbol", "columns", "direction", "itemGap"]) {
|
|
116
125
|
if (Object.hasOwn(args, key)) {
|
|
@@ -135,7 +144,9 @@ export function normalizeContinuousLegend(args, kind) {
|
|
|
135
144
|
labels: normalizeLegendTextOptions(
|
|
136
145
|
args.labels,
|
|
137
146
|
"createLegend.labels",
|
|
138
|
-
|
|
147
|
+
titlePosition === "left"
|
|
148
|
+
? { ...DEFAULT_LABELS, offset: 8 }
|
|
149
|
+
: DEFAULT_LABELS
|
|
139
150
|
),
|
|
140
151
|
titleStyle: normalizeLegendTextOptions(
|
|
141
152
|
args.titleStyle,
|
|
@@ -143,6 +154,7 @@ export function normalizeContinuousLegend(args, kind) {
|
|
|
143
154
|
DEFAULT_TITLE
|
|
144
155
|
),
|
|
145
156
|
itemGap,
|
|
157
|
+
titlePosition,
|
|
146
158
|
border: normalizeBorder(args.border)
|
|
147
159
|
};
|
|
148
160
|
}
|