ggaction 0.0.3 → 0.0.5
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 +58 -3
- package/README.md +117 -83
- package/package.json +27 -11
- package/src/actions/boxPlots/create.js +13 -7
- package/src/actions/boxPlots/edit.js +31 -35
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +8 -1
- package/src/actions/boxPlots/resolve.js +10 -2
- package/src/actions/charts/bar.js +50 -0
- package/src/actions/charts/heatmap.js +205 -0
- package/src/actions/charts/histogram.js +58 -0
- package/src/actions/charts/index.js +15 -0
- package/src/actions/charts/line.js +69 -0
- package/src/actions/charts/parallel.js +76 -0
- package/src/actions/charts/scatter.js +56 -0
- package/src/actions/charts/shared.js +121 -0
- package/src/actions/coordinates/parallel.js +50 -0
- package/src/actions/data/bin2d.js +137 -0
- package/src/actions/data/density.js +54 -13
- package/src/actions/data/derived.js +22 -0
- package/src/actions/data/filter.js +5 -0
- package/src/actions/data/gradientProfile.js +65 -0
- package/src/actions/data/horizon.js +71 -0
- package/src/actions/data/index.js +29 -2
- package/src/actions/data/regression.js +1 -1
- package/src/actions/data/window.js +50 -0
- package/src/actions/encodings/appearance.js +1 -1
- package/src/actions/encodings/color/continuous.js +137 -0
- package/src/actions/encodings/color/index.js +167 -0
- package/src/actions/encodings/color/layout.js +99 -0
- package/src/actions/encodings/color/policy.js +88 -0
- package/src/actions/encodings/density/resolve.js +109 -0
- package/src/actions/encodings/density.js +193 -88
- package/src/actions/encodings/horizon/resolve.js +283 -0
- package/src/actions/encodings/horizon.js +364 -0
- package/src/actions/encodings/index.js +8 -2
- package/src/actions/encodings/offset.js +1 -1
- package/src/actions/encodings/parallel.js +115 -0
- package/src/actions/encodings/pathOrder.js +131 -0
- package/src/actions/encodings/position/apply.js +73 -4
- package/src/actions/encodings/{position.js → position/index.js} +21 -8
- package/src/actions/encodings/position/policies/arc.js +27 -4
- package/src/actions/encodings/position/policies/area.js +33 -2
- package/src/actions/encodings/position/policies/index.js +7 -2
- package/src/actions/encodings/position/policies/line.js +18 -2
- package/src/actions/encodings/position/resolve.js +25 -3
- package/src/actions/encodings/ranged.js +5 -3
- package/src/actions/encodings/ruleAppearance.js +88 -10
- package/src/actions/encodings/strokeDash.js +1 -1
- package/src/actions/encodings/text.js +1 -1
- package/src/actions/errorBands/options.js +1 -1
- package/src/actions/errorBars/options.js +1 -1
- package/src/actions/facets/actions.js +2 -3
- package/src/actions/facets/derive.js +35 -5
- package/src/actions/facets/guides.js +1 -1
- package/src/actions/facets/replay.js +7 -36
- package/src/actions/gradientPlots/components.js +177 -0
- package/src/actions/gradientPlots/create.js +96 -0
- package/src/actions/gradientPlots/edit.js +153 -0
- package/src/actions/gradientPlots/index.js +24 -0
- package/src/actions/gradientPlots/materialize.js +287 -0
- package/src/actions/gradientPlots/options.js +120 -0
- package/src/actions/gradientPlots/paint.js +107 -0
- package/src/actions/gradientPlots/rebind.js +51 -0
- package/src/actions/gradientPlots/resolve.js +81 -0
- package/src/actions/guides/applicability.js +44 -17
- package/src/actions/guides/axes/axes.js +37 -3
- package/src/actions/guides/axes/index.js +2 -0
- package/src/actions/guides/axes/labels.js +12 -7
- package/src/actions/guides/axes/parallel/resolve.js +86 -0
- package/src/actions/guides/axes/parallel.js +228 -0
- package/src/actions/guides/axes/ticks.js +1 -1
- package/src/actions/guides/axes/titles.js +12 -9
- package/src/actions/guides/grids/resolve.js +1 -1
- package/src/actions/guides/guides.js +60 -5
- package/src/actions/guides/legends/categorical/actions.js +48 -11
- package/src/actions/guides/legends/categorical/layout.js +4 -2
- package/src/actions/guides/legends/continuous/interval.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +1 -1
- package/src/actions/guides/legends/edit.js +7 -20
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/remove.js +6 -31
- package/src/actions/guides/legends/size.js +1 -1
- package/src/actions/guides/legends/strokeWidth.js +170 -0
- package/src/actions/guides/legends/target.js +25 -0
- package/src/actions/guides/polar/{axes.js → axes/index.js} +5 -5
- package/src/actions/guides/polar/index.js +1 -1
- package/src/actions/guides/polar/resolve.js +1 -1
- package/src/actions/guides/tickValues.js +1 -1
- package/src/actions/index.js +6 -0
- package/src/actions/intervals/resolve.js +1 -1
- package/src/actions/marks/arc/actions.js +2 -2
- package/src/actions/marks/area/actions.js +61 -137
- package/src/actions/marks/area/materialize.js +163 -0
- package/src/actions/marks/bar/edit.js +1 -1
- package/src/actions/marks/index.js +5 -1
- package/src/actions/marks/line/actions.js +100 -81
- package/src/actions/marks/line/materialize.js +146 -0
- package/src/actions/marks/point/create.js +76 -0
- package/src/actions/marks/point/edit.js +85 -0
- package/src/actions/marks/point/index.js +11 -1
- package/src/actions/marks/point/jitter.js +72 -0
- package/src/actions/marks/point/{actions.js → materialize.js} +175 -170
- package/src/actions/marks/rect/actions.js +4 -1
- package/src/actions/marks/remove.js +13 -2
- package/src/actions/marks/rule/actions.js +26 -4
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +20 -5
- package/src/actions/marks/text/index.js +7 -1
- package/src/actions/marks/text/layout.js +321 -0
- package/src/actions/primitives/semanticValidation/dataset.js +22 -0
- package/src/actions/primitives/semanticValidation/guide.js +43 -0
- package/src/actions/primitives/semanticValidation/index.js +23 -0
- package/src/actions/primitives/semanticValidation/layer.js +74 -0
- package/src/actions/primitives/semanticValidation/scale.js +74 -0
- package/src/actions/primitives/semanticValidation/shared.js +5 -0
- package/src/actions/primitives/semanticValue.js +1 -204
- package/src/actions/regression/components.js +1 -1
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +20 -22
- package/src/actions/scales/consumers/common.js +109 -0
- package/src/actions/scales/consumers/families.js +58 -0
- package/src/actions/scales/consumers/index.js +41 -0
- package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +10 -132
- package/src/actions/scales/create.js +1 -1
- package/src/actions/scales/definitions.js +32 -2
- package/src/actions/scales/edit.js +8 -206
- package/src/actions/scales/editPolicy.js +214 -0
- package/src/actions/scales/materialize.js +3 -3
- package/src/actions/scales/patch.js +26 -0
- package/src/actions/selection/actions.js +28 -3
- package/src/actions/violinPlots/create.js +198 -0
- package/src/actions/violinPlots/index.js +5 -0
- package/src/core/ChartProgram.js +22 -236
- package/src/core/compositionState.js +169 -0
- package/src/core/materializationState.js +65 -0
- package/src/core/programState.js +28 -0
- package/src/core/textMetrics.js +47 -0
- package/src/core/vocabulary.js +7 -2
- package/src/grammar/arcs.js +50 -13
- package/src/grammar/areaSeries.js +42 -6
- package/src/grammar/bars/aggregate.js +1 -1
- package/src/grammar/bin2d.js +333 -0
- package/src/grammar/categoricalDensity.js +192 -0
- package/src/grammar/coordinates.js +1 -1
- package/src/grammar/curveCommands.js +3 -1
- package/src/grammar/density.js +253 -27
- package/src/grammar/facets/dependencies.js +3 -11
- package/src/grammar/{facets.js → facets/index.js} +8 -6
- package/src/grammar/gradientProfile.js +213 -0
- package/src/grammar/horizon.js +455 -0
- package/src/grammar/interval.js +2 -96
- package/src/grammar/jitter.js +197 -0
- package/src/grammar/lineSeries.js +70 -1
- package/src/grammar/paint.js +88 -0
- package/src/grammar/palettes.js +5 -7
- package/src/grammar/parallelCoordinates.js +213 -0
- package/src/grammar/pathOrder.js +35 -0
- package/src/grammar/pointShapes.js +24 -0
- package/src/grammar/polarLineCommands.js +1 -1
- 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 +2 -1
- package/src/grammar/scales/appearance.js +21 -0
- package/src/grammar/scales/color.js +4 -2
- package/src/grammar/schemas/concreteGraphic.js +11 -1
- package/src/grammar/schemas/graphicBounds.js +19 -37
- package/src/grammar/schemas/semanticPath.js +17 -2
- package/src/grammar/statistics/studentT.js +98 -0
- package/src/grammar/transforms.js +101 -12
- package/src/grammar/window.js +339 -0
- package/src/layout/labels.js +270 -0
- package/src/layout/text.js +4 -1
- package/src/layout/title.js +10 -20
- package/src/materialization/bars/aggregate.js +1 -1
- package/src/materialization/bars/grouped.js +1 -1
- package/src/materialization/bars/histogram.js +1 -1
- package/src/materialization/bars/ranged.js +1 -1
- package/src/materialization/dataProvenance.js +56 -2
- package/src/materialization/density.js +1 -1
- package/src/materialization/dependencies.js +15 -101
- package/src/materialization/encodings.js +2 -2
- 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 +1 -1
- package/src/materialization/guides/resources.js +74 -24
- package/src/materialization/horizon.js +29 -0
- package/src/materialization/layout.js +15 -0
- package/src/materialization/marks/capabilities.js +161 -0
- package/src/materialization/marks/index.js +108 -0
- package/src/materialization/marks/pathOrder.js +71 -0
- package/src/materialization/marks/policies.js +91 -0
- package/src/materialization/rect.js +1 -1
- package/src/materialization/rowEncoding.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +102 -0
- package/src/materialization/scales/map.js +1 -1
- package/src/materialization/scales/policies/series.js +1 -1
- package/src/materialization/scales/resolve.js +3 -1
- package/src/materialization/selection/items/bar.js +1 -1
- package/src/materialization/selection/items/path.js +27 -1
- package/src/materialization/selection/items/point.js +1 -5
- package/src/materialization/selection/items/rect.js +21 -0
- package/src/materialization/selection/styles.js +21 -10
- package/src/materialization/text.js +12 -4
- package/src/renderers/canvas/fill.js +33 -0
- package/src/renderers/canvas/index.js +7 -0
- package/src/renderers/canvas/path.js +7 -4
- package/src/renderers/canvas/rect.js +9 -3
- package/src/renderers/canvas/text.js +14 -1
- package/src/selectors/datasets.js +4 -0
- package/src/theme/defaults.js +4 -0
- package/types/extension.d.ts +10 -1
- package/types/index.d.ts +76 -0
- package/types/program.d.ts +632 -10
- package/src/actions/encodings/color.js +0 -424
- package/src/grammar/regression.js +0 -578
- package/src/grammar/scales.js +0 -1
- package/src/materialization/facetGuides.js +0 -426
- package/src/materialization/marks.js +0 -316
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readQuantitativeField,
|
|
3
|
+
readScaleField,
|
|
4
|
+
readTemporalField
|
|
5
|
+
} from "../../../grammar/scales/index.js";
|
|
6
|
+
import {
|
|
7
|
+
BAR_GRAINS,
|
|
8
|
+
resolveBarChannels,
|
|
9
|
+
resolveBarGrain
|
|
10
|
+
} from "../../../grammar/bars/policy.js";
|
|
11
|
+
import {
|
|
12
|
+
validateAggregate,
|
|
13
|
+
validateAggregateFieldType,
|
|
14
|
+
validateAggregateFieldValues
|
|
15
|
+
} from "../../../grammar/aggregate.js";
|
|
16
|
+
import {
|
|
17
|
+
resolveQuantitativeColorScaleDefinition
|
|
18
|
+
} from "../../scales/definitions.js";
|
|
19
|
+
import {
|
|
20
|
+
resolveReassignmentScaleOptions,
|
|
21
|
+
resolveTarget
|
|
22
|
+
} from "../shared.js";
|
|
23
|
+
import { applyMaterializationPlan } from "../../../materialization/dependencies.js";
|
|
24
|
+
import {
|
|
25
|
+
planEncodingRematerialization
|
|
26
|
+
} from "../../../materialization/encodings.js";
|
|
27
|
+
import {
|
|
28
|
+
assertNoConstantColor,
|
|
29
|
+
resolveColorScaleOptions
|
|
30
|
+
} from "./policy.js";
|
|
31
|
+
|
|
32
|
+
export function encodeContinuousColor(program, args) {
|
|
33
|
+
if (!["quantitative", "temporal"].includes(args.fieldType)) {
|
|
34
|
+
throw new Error(`Unsupported color field type "${args.fieldType}".`);
|
|
35
|
+
}
|
|
36
|
+
if (args.layout !== undefined) {
|
|
37
|
+
throw new Error("Continuous color does not support layout.");
|
|
38
|
+
}
|
|
39
|
+
const { id: target, dataset, layer } = resolveTarget(
|
|
40
|
+
program,
|
|
41
|
+
args.target,
|
|
42
|
+
["point", "bar", "rect"],
|
|
43
|
+
"continuous color mark"
|
|
44
|
+
);
|
|
45
|
+
assertNoConstantColor(program, layer);
|
|
46
|
+
const requestedScale = resolveReassignmentScaleOptions(
|
|
47
|
+
layer.encoding?.color,
|
|
48
|
+
resolveColorScaleOptions(args)
|
|
49
|
+
);
|
|
50
|
+
const scale = resolveQuantitativeColorScaleDefinition(
|
|
51
|
+
program,
|
|
52
|
+
args.fieldType,
|
|
53
|
+
requestedScale
|
|
54
|
+
);
|
|
55
|
+
if (Object.hasOwn(scale, "unknown") && layer.mark.type !== "point") {
|
|
56
|
+
throw new Error(
|
|
57
|
+
"Continuous color scale unknown currently requires a row-owned point mark."
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (
|
|
61
|
+
layer.mark.type === "bar" &&
|
|
62
|
+
["nominal", "ordinal"].includes(layer.encoding?.color?.fieldType)
|
|
63
|
+
) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
"Continuous bar color cannot replace an existing nominal color layout."
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
let aggregate;
|
|
69
|
+
if (["point", "rect"].includes(layer.mark.type)) {
|
|
70
|
+
if (args.aggregate !== undefined) {
|
|
71
|
+
throw new Error(`${layer.mark.type} continuous color does not support aggregate.`);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
if (args.fieldType !== "quantitative") {
|
|
75
|
+
throw new Error("Aggregate bar color currently requires a quantitative field.");
|
|
76
|
+
}
|
|
77
|
+
if (resolveBarGrain(layer) !== BAR_GRAINS.aggregate) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
"Continuous bar color requires a complete categorical aggregate bar."
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const channels = resolveBarChannels(layer);
|
|
83
|
+
const measure = layer.encoding?.[channels.measure];
|
|
84
|
+
aggregate = args.aggregate ?? (
|
|
85
|
+
measure?.field === args.field ? measure.aggregate : undefined
|
|
86
|
+
);
|
|
87
|
+
if (aggregate === undefined) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
"Continuous bar color requires aggregate when its field differs from the measure field."
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
aggregate = validateAggregate(aggregate);
|
|
93
|
+
validateAggregateFieldType(aggregate, args.fieldType);
|
|
94
|
+
validateAggregateFieldValues(dataset.values, args.field, args.fieldType);
|
|
95
|
+
}
|
|
96
|
+
if (layer.mark.type === "rect") {
|
|
97
|
+
readScaleField(dataset.values, args.field, args.fieldType, {
|
|
98
|
+
allowUnknown: true
|
|
99
|
+
});
|
|
100
|
+
} else if (Object.hasOwn(scale, "unknown")) {
|
|
101
|
+
readScaleField(dataset.values, args.field, args.fieldType, {
|
|
102
|
+
allowUnknown: true
|
|
103
|
+
});
|
|
104
|
+
} else if (args.fieldType === "temporal") {
|
|
105
|
+
readTemporalField(dataset.values, args.field);
|
|
106
|
+
} else {
|
|
107
|
+
readQuantitativeField(dataset.values, args.field);
|
|
108
|
+
}
|
|
109
|
+
const next = program
|
|
110
|
+
.editSemantic({
|
|
111
|
+
property: `layer[${target}].encoding.color.field`,
|
|
112
|
+
value: args.field
|
|
113
|
+
})
|
|
114
|
+
.editSemantic({
|
|
115
|
+
property: `layer[${target}].encoding.color.fieldType`,
|
|
116
|
+
value: args.fieldType
|
|
117
|
+
})
|
|
118
|
+
.editSemantic({
|
|
119
|
+
property: `layer[${target}].encoding.color.scale`,
|
|
120
|
+
value: scale.id
|
|
121
|
+
});
|
|
122
|
+
const encoded = aggregate === undefined
|
|
123
|
+
? next
|
|
124
|
+
: next.editSemantic({
|
|
125
|
+
property: `layer[${target}].encoding.color.aggregate`,
|
|
126
|
+
value: aggregate
|
|
127
|
+
});
|
|
128
|
+
const scaled = encoded.setQuantitativeColorScale(scale);
|
|
129
|
+
return applyMaterializationPlan(
|
|
130
|
+
scaled,
|
|
131
|
+
planEncodingRematerialization(scaled, {
|
|
132
|
+
target,
|
|
133
|
+
channel: "color",
|
|
134
|
+
scale: scale.id
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import {
|
|
3
|
+
readNominalField,
|
|
4
|
+
readScaleField,
|
|
5
|
+
validateCategoricalFieldType
|
|
6
|
+
} from "../../../grammar/scales/index.js";
|
|
7
|
+
import {
|
|
8
|
+
resolveBarGrain
|
|
9
|
+
} from "../../../grammar/bars/policy.js";
|
|
10
|
+
import { resolveColorScaleDefinition } from "../../scales/definitions.js";
|
|
11
|
+
import {
|
|
12
|
+
applyEncodingScale,
|
|
13
|
+
resolveReassignmentScaleOptions,
|
|
14
|
+
resolveTarget,
|
|
15
|
+
validateLineSeriesCompatibility,
|
|
16
|
+
validateOptions
|
|
17
|
+
} from "../shared.js";
|
|
18
|
+
import { applyMaterializationPlan } from "../../../materialization/dependencies.js";
|
|
19
|
+
import {
|
|
20
|
+
planEncodingRematerialization
|
|
21
|
+
} from "../../../materialization/encodings.js";
|
|
22
|
+
import { findUpstreamTransform } from
|
|
23
|
+
"../../../materialization/dataProvenance.js";
|
|
24
|
+
import { encodeContinuousColor } from "./continuous.js";
|
|
25
|
+
import {
|
|
26
|
+
applyColorLayoutCompanion,
|
|
27
|
+
preSynchronizeGroupedOffset
|
|
28
|
+
} from "./layout.js";
|
|
29
|
+
import {
|
|
30
|
+
assertNoConstantColor,
|
|
31
|
+
COLOR_ENCODING_OPTIONS,
|
|
32
|
+
resolveColorLayout,
|
|
33
|
+
resolveColorScaleOptions
|
|
34
|
+
} from "./policy.js";
|
|
35
|
+
|
|
36
|
+
const encodeColor = action(
|
|
37
|
+
{
|
|
38
|
+
op: "encodeColor",
|
|
39
|
+
description: "Encode a field as graphical color."
|
|
40
|
+
},
|
|
41
|
+
function (args = {}) {
|
|
42
|
+
validateOptions(args, COLOR_ENCODING_OPTIONS, "encodeColor");
|
|
43
|
+
const requestedFieldType = args.fieldType ?? "nominal";
|
|
44
|
+
if (!["nominal", "ordinal"].includes(requestedFieldType)) {
|
|
45
|
+
return encodeContinuousColor(this, {
|
|
46
|
+
...args,
|
|
47
|
+
fieldType: requestedFieldType
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (args.aggregate !== undefined) {
|
|
51
|
+
throw new Error("Categorical color does not support aggregate.");
|
|
52
|
+
}
|
|
53
|
+
const fieldType = validateCategoricalFieldType(requestedFieldType);
|
|
54
|
+
const { id: target, dataset, layer } = resolveTarget(
|
|
55
|
+
this,
|
|
56
|
+
args.target,
|
|
57
|
+
["point", "line", "bar", "area", "arc", "rect"],
|
|
58
|
+
"color mark"
|
|
59
|
+
);
|
|
60
|
+
assertNoConstantColor(this, layer);
|
|
61
|
+
const densityTransform = dataset.transform?.length === 1 &&
|
|
62
|
+
dataset.transform[0].type === "density"
|
|
63
|
+
? dataset.transform[0]
|
|
64
|
+
: undefined;
|
|
65
|
+
const densitySeriesFields = densityTransform?.placement?.type === "category"
|
|
66
|
+
? [densityTransform.groupBy, densityTransform.placement.split?.field]
|
|
67
|
+
.filter(Boolean)
|
|
68
|
+
: [];
|
|
69
|
+
const horizonTransform = findUpstreamTransform(this, dataset, "horizon");
|
|
70
|
+
const horizonColor = horizonTransform !== undefined &&
|
|
71
|
+
horizonTransform.as?.color === args.field &&
|
|
72
|
+
layer.encoding?.group?.field === horizonTransform.as?.group;
|
|
73
|
+
if (
|
|
74
|
+
layer.mark.type === "area" &&
|
|
75
|
+
(layer.encoding?.group?.field === undefined ||
|
|
76
|
+
layer.encoding.group.field !== args.field) &&
|
|
77
|
+
!densitySeriesFields.includes(args.field) &&
|
|
78
|
+
!horizonColor
|
|
79
|
+
) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
"Area color encoding must match an existing group encoding."
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
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
|
+
const requestedScale = resolveReassignmentScaleOptions(
|
|
94
|
+
layer.encoding?.color,
|
|
95
|
+
resolveColorScaleOptions(args)
|
|
96
|
+
);
|
|
97
|
+
const scale = resolveColorScaleDefinition(this, requestedScale);
|
|
98
|
+
if (Object.hasOwn(scale, "unknown") && layer.mark.type !== "point") {
|
|
99
|
+
throw new Error(
|
|
100
|
+
"Categorical color scale unknown currently requires a row-owned point mark."
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (layer.mark.type === "rect") {
|
|
104
|
+
readScaleField(dataset.values, args.field, fieldType, {
|
|
105
|
+
allowUnknown: true
|
|
106
|
+
});
|
|
107
|
+
} else if (Object.hasOwn(scale, "unknown")) {
|
|
108
|
+
readScaleField(dataset.values, args.field, fieldType, {
|
|
109
|
+
allowUnknown: true
|
|
110
|
+
});
|
|
111
|
+
} else {
|
|
112
|
+
readNominalField(dataset.values, args.field);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let next = this
|
|
116
|
+
.editSemantic({
|
|
117
|
+
property: `layer[${target}].encoding.color.field`,
|
|
118
|
+
value: args.field
|
|
119
|
+
})
|
|
120
|
+
.editSemantic({
|
|
121
|
+
property: `layer[${target}].encoding.color.fieldType`,
|
|
122
|
+
value: fieldType
|
|
123
|
+
})
|
|
124
|
+
.editSemantic({
|
|
125
|
+
property: `layer[${target}].encoding.color.scale`,
|
|
126
|
+
value: scale.id
|
|
127
|
+
});
|
|
128
|
+
if (layout !== undefined) {
|
|
129
|
+
next = next.editSemantic({
|
|
130
|
+
property: `layer[${target}].encoding.color.layout`,
|
|
131
|
+
value: layout
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
next = preSynchronizeGroupedOffset(next, {
|
|
135
|
+
target,
|
|
136
|
+
layer,
|
|
137
|
+
layout,
|
|
138
|
+
field: args.field,
|
|
139
|
+
fieldType,
|
|
140
|
+
scale,
|
|
141
|
+
requestedScale
|
|
142
|
+
});
|
|
143
|
+
next = applyEncodingScale(next, scale, requestedScale, {
|
|
144
|
+
reassignment: layer.encoding?.color?.scale === scale.id
|
|
145
|
+
});
|
|
146
|
+
next = applyColorLayoutCompanion(next, {
|
|
147
|
+
target,
|
|
148
|
+
layer,
|
|
149
|
+
layout,
|
|
150
|
+
scale,
|
|
151
|
+
field: args.field
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return applyMaterializationPlan(
|
|
155
|
+
next,
|
|
156
|
+
planEncodingRematerialization(next, {
|
|
157
|
+
target,
|
|
158
|
+
channel: "color",
|
|
159
|
+
scale: scale.id
|
|
160
|
+
})
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
export function registerColorEncodingAction(ProgramClass) {
|
|
166
|
+
ProgramClass.prototype.encodeColor = encodeColor;
|
|
167
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BAR_GRAINS,
|
|
3
|
+
resolveBarChannels,
|
|
4
|
+
resolveBarGrain,
|
|
5
|
+
resolveBarOffsetChannel
|
|
6
|
+
} from "../../../grammar/bars/policy.js";
|
|
7
|
+
import { resolveOffsetScaleDefinition } from "../../scales/definitions.js";
|
|
8
|
+
import { isRangedArea } from "./policy.js";
|
|
9
|
+
|
|
10
|
+
export function applyColorLayoutCompanion(
|
|
11
|
+
program,
|
|
12
|
+
{ target, layer, layout, scale, field }
|
|
13
|
+
) {
|
|
14
|
+
if (layout === undefined || layer.mark.type === "arc") return program;
|
|
15
|
+
if (
|
|
16
|
+
layer.mark.type === "bar" &&
|
|
17
|
+
resolveBarGrain(layer) === BAR_GRAINS.ranged
|
|
18
|
+
) {
|
|
19
|
+
return program;
|
|
20
|
+
}
|
|
21
|
+
if (
|
|
22
|
+
layer.mark.type === "area" &&
|
|
23
|
+
[layer.encoding?.x, layer.encoding?.y].some(
|
|
24
|
+
encoding => ["nominal", "ordinal"].includes(encoding?.fieldType)
|
|
25
|
+
)
|
|
26
|
+
) {
|
|
27
|
+
return program;
|
|
28
|
+
}
|
|
29
|
+
if (isRangedArea(layer)) return program;
|
|
30
|
+
const channels = layer.mark.type === "bar"
|
|
31
|
+
? resolveBarChannels(layer)
|
|
32
|
+
: undefined;
|
|
33
|
+
const measureChannel = channels?.measure ?? "y";
|
|
34
|
+
const measure = layer.encoding?.[measureChannel];
|
|
35
|
+
if (measure?.field === undefined || measure.scale === undefined) {
|
|
36
|
+
throw new Error(`Color layout on mark "${target}" requires a measure encoding.`);
|
|
37
|
+
}
|
|
38
|
+
const stack = layout === "fill"
|
|
39
|
+
? "normalize"
|
|
40
|
+
: layout === "overlay" || layout === "group"
|
|
41
|
+
? null
|
|
42
|
+
: "zero";
|
|
43
|
+
let next = program;
|
|
44
|
+
if (layout === "group") {
|
|
45
|
+
const offsetChannel = resolveBarOffsetChannel(layer);
|
|
46
|
+
const method = offsetChannel === "xOffset" ? "encodeXOffset" : "encodeYOffset";
|
|
47
|
+
next = next[method]({
|
|
48
|
+
field,
|
|
49
|
+
target,
|
|
50
|
+
scale: {
|
|
51
|
+
...(layer.encoding?.[offsetChannel]?.scale === undefined
|
|
52
|
+
? {}
|
|
53
|
+
: { id: layer.encoding[offsetChannel].scale }),
|
|
54
|
+
domain: scale.domain
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return next[measureChannel === "x" ? "encodeX" : "encodeY"]({
|
|
59
|
+
target,
|
|
60
|
+
field: measure.field,
|
|
61
|
+
fieldType: measure.fieldType,
|
|
62
|
+
...(measure.aggregate === undefined ? {} : { aggregate: measure.aggregate }),
|
|
63
|
+
stack
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function preSynchronizeGroupedOffset(
|
|
68
|
+
program,
|
|
69
|
+
{ target, layer, layout, field, fieldType, scale, requestedScale }
|
|
70
|
+
) {
|
|
71
|
+
if (
|
|
72
|
+
layer.mark.type !== "bar" ||
|
|
73
|
+
layout !== "group" ||
|
|
74
|
+
layer.encoding?.color?.scale !== scale.id ||
|
|
75
|
+
!Object.keys(requestedScale).some(key => key !== "id")
|
|
76
|
+
) {
|
|
77
|
+
return program;
|
|
78
|
+
}
|
|
79
|
+
const offsetChannel = resolveBarOffsetChannel(layer);
|
|
80
|
+
const offset = layer.encoding?.[offsetChannel];
|
|
81
|
+
if (offset?.scale === undefined) return program;
|
|
82
|
+
const offsetScale = resolveOffsetScaleDefinition(program, {
|
|
83
|
+
id: offset.scale,
|
|
84
|
+
domain: scale.domain
|
|
85
|
+
}, offsetChannel);
|
|
86
|
+
return program
|
|
87
|
+
.editSemantic({
|
|
88
|
+
property: `layer[${target}].encoding.${offsetChannel}.field`,
|
|
89
|
+
value: field
|
|
90
|
+
})
|
|
91
|
+
.editSemantic({
|
|
92
|
+
property: `layer[${target}].encoding.${offsetChannel}.fieldType`,
|
|
93
|
+
value: fieldType
|
|
94
|
+
})
|
|
95
|
+
.editSemantic({
|
|
96
|
+
property: `scale[${offsetScale.id}].domain`,
|
|
97
|
+
value: offsetScale.domain
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BAR_GRAINS,
|
|
3
|
+
inferBarColorLayout
|
|
4
|
+
} from "../../../grammar/bars/policy.js";
|
|
5
|
+
import { validateColorLayout } from "../../../grammar/seriesLayout.js";
|
|
6
|
+
|
|
7
|
+
export const COLOR_ENCODING_OPTIONS = Object.freeze([
|
|
8
|
+
"field", "target", "fieldType", "scale", "palette", "layout", "aggregate"
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
export function resolveColorScaleOptions(args) {
|
|
12
|
+
const options = args.scale ?? {};
|
|
13
|
+
if (args.palette === undefined) return options;
|
|
14
|
+
if (options.palette !== undefined || options.range !== undefined) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"encodeColor palette cannot be combined with scale.palette or scale.range."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return { ...options, palette: args.palette };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function assertNoConstantColor(program, layer) {
|
|
23
|
+
const config = program.markConfigs[layer.id];
|
|
24
|
+
const hasConstant = {
|
|
25
|
+
point: config?.fill !== undefined,
|
|
26
|
+
line: config?.stroke !== undefined,
|
|
27
|
+
bar: config?.barAppearance?.fill !== undefined,
|
|
28
|
+
arc: config?.fill !== undefined,
|
|
29
|
+
rect: config?.fillExplicit === true
|
|
30
|
+
}[layer.mark.type] ?? false;
|
|
31
|
+
if (hasConstant) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`encodeColor cannot replace constant appearance on ${layer.mark.type} mark "${layer.id}".`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isRangedArea(layer) {
|
|
39
|
+
return layer.mark.type === "area" && (
|
|
40
|
+
layer.encoding?.x2 !== undefined ||
|
|
41
|
+
layer.encoding?.y2 !== undefined
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function resolveColorLayout(layer, requested, barGrain) {
|
|
46
|
+
const existing = layer.encoding?.color === undefined
|
|
47
|
+
? undefined
|
|
48
|
+
: layer.encoding.color.layout ?? (
|
|
49
|
+
layer.mark.type === "bar"
|
|
50
|
+
? inferBarColorLayout(layer)
|
|
51
|
+
: layer.mark.type === "area"
|
|
52
|
+
? "overlay"
|
|
53
|
+
: undefined
|
|
54
|
+
);
|
|
55
|
+
if (requested !== undefined) validateColorLayout(requested);
|
|
56
|
+
if (existing !== undefined && requested !== undefined && requested !== existing) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Color layout transition from "${existing}" to "${requested}" is not supported.`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const layout = requested ?? existing ?? (
|
|
62
|
+
layer.mark.type === "bar"
|
|
63
|
+
? barGrain === BAR_GRAINS.histogram
|
|
64
|
+
? "stack"
|
|
65
|
+
: barGrain === BAR_GRAINS.ranged ? "overlay" : "group"
|
|
66
|
+
: layer.mark.type === "area"
|
|
67
|
+
? "overlay"
|
|
68
|
+
: undefined
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (["point", "line", "rect"].includes(layer.mark.type) && layout !== undefined) {
|
|
72
|
+
throw new Error(`Color layout is not supported for ${layer.mark.type} marks.`);
|
|
73
|
+
}
|
|
74
|
+
if (layer.mark.type === "area" && layout === "group") {
|
|
75
|
+
throw new Error('Area color layout does not support "group".');
|
|
76
|
+
}
|
|
77
|
+
if (
|
|
78
|
+
layer.mark.type === "arc" &&
|
|
79
|
+
layout !== undefined &&
|
|
80
|
+
layout !== "overlay"
|
|
81
|
+
) {
|
|
82
|
+
throw new Error('Arc color layout currently supports only "overlay".');
|
|
83
|
+
}
|
|
84
|
+
if (isRangedArea(layer) && layout !== "overlay") {
|
|
85
|
+
throw new Error('Ranged area color encoding supports only "overlay" layout.');
|
|
86
|
+
}
|
|
87
|
+
return layout;
|
|
88
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { isPlainObject } from "../../../core/immutable.js";
|
|
2
|
+
import { findSemanticScale } from "../../../selectors/scales.js";
|
|
3
|
+
|
|
4
|
+
export function requireDensityField(value, label) {
|
|
5
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
6
|
+
throw new TypeError(`${label} must be a non-empty string.`);
|
|
7
|
+
}
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveDensityScaleOptions(value, defaults, label) {
|
|
12
|
+
if (value !== undefined && !isPlainObject(value)) {
|
|
13
|
+
throw new TypeError(`${label} must be a plain object.`);
|
|
14
|
+
}
|
|
15
|
+
return { ...defaults, ...(value ?? {}) };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolveDensityCategoryScaleOptions(value, fallbackId) {
|
|
19
|
+
const scale = resolveDensityScaleOptions(
|
|
20
|
+
value,
|
|
21
|
+
{ type: "band" },
|
|
22
|
+
"Density placement scale"
|
|
23
|
+
);
|
|
24
|
+
if ((scale.type ?? "band") !== "band") {
|
|
25
|
+
throw new Error('Density category placement requires scale type "band".');
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
...(fallbackId === undefined ? {} : { id: fallbackId }),
|
|
29
|
+
...scale,
|
|
30
|
+
type: "band"
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function valueScaleForTransition(program, layer, transform) {
|
|
35
|
+
const densityChannel = transform.placement?.channel ?? (
|
|
36
|
+
layer.encoding.x.field === transform.as[1] ? "x" : "y"
|
|
37
|
+
);
|
|
38
|
+
const valueChannel = densityChannel === "x" ? "y" : "x";
|
|
39
|
+
return findSemanticScale(program, layer.encoding[valueChannel].scale);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function resolveDensityTransitionScaleDefinitions(
|
|
43
|
+
program,
|
|
44
|
+
layer,
|
|
45
|
+
transform,
|
|
46
|
+
placement,
|
|
47
|
+
rawPlacement
|
|
48
|
+
) {
|
|
49
|
+
const valueScale = valueScaleForTransition(program, layer, transform);
|
|
50
|
+
if (valueScale === undefined) {
|
|
51
|
+
throw new Error(`Density area "${layer.id}" requires its value scale.`);
|
|
52
|
+
}
|
|
53
|
+
const { id: _valueId, range: _valueRange, ...valueDefinition } = valueScale;
|
|
54
|
+
void _valueId;
|
|
55
|
+
void _valueRange;
|
|
56
|
+
const valueChannel = placement?.channel === "x" ? "y" : "x";
|
|
57
|
+
const companionChannel = valueChannel === "x" ? "y" : "x";
|
|
58
|
+
const companionDefinition = placement === undefined
|
|
59
|
+
? { type: "linear", domain: "auto", range: "auto", nice: true, zero: true }
|
|
60
|
+
: resolveDensityCategoryScaleOptions(rawPlacement.scale);
|
|
61
|
+
return {
|
|
62
|
+
[valueChannel]: { ...valueDefinition, range: "auto" },
|
|
63
|
+
[companionChannel]: companionDefinition
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function resolveDensityPositionDefinition({
|
|
68
|
+
layer,
|
|
69
|
+
output,
|
|
70
|
+
groupBy,
|
|
71
|
+
placement,
|
|
72
|
+
coordinate,
|
|
73
|
+
valueScale,
|
|
74
|
+
densityScale,
|
|
75
|
+
placementScale
|
|
76
|
+
}) {
|
|
77
|
+
if (placement === undefined) {
|
|
78
|
+
const xIsDensity = layer.densityChannel === "x";
|
|
79
|
+
return {
|
|
80
|
+
x: {
|
|
81
|
+
field: output[xIsDensity ? 1 : 0],
|
|
82
|
+
fieldType: "quantitative",
|
|
83
|
+
scale: xIsDensity ? densityScale : valueScale,
|
|
84
|
+
coordinate
|
|
85
|
+
},
|
|
86
|
+
y: {
|
|
87
|
+
field: output[xIsDensity ? 0 : 1],
|
|
88
|
+
fieldType: "quantitative",
|
|
89
|
+
scale: xIsDensity ? valueScale : densityScale,
|
|
90
|
+
coordinate
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const category = {
|
|
95
|
+
field: placement.categoryField,
|
|
96
|
+
fieldType: "nominal",
|
|
97
|
+
scale: placementScale,
|
|
98
|
+
coordinate
|
|
99
|
+
};
|
|
100
|
+
const value = {
|
|
101
|
+
field: output[0],
|
|
102
|
+
fieldType: "quantitative",
|
|
103
|
+
scale: valueScale,
|
|
104
|
+
coordinate
|
|
105
|
+
};
|
|
106
|
+
return placement.channel === "x"
|
|
107
|
+
? { x: category, y: value }
|
|
108
|
+
: { x: value, y: category };
|
|
109
|
+
}
|