ggaction 0.0.4 → 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 +30 -0
- package/README.md +35 -13
- package/package.json +18 -7
- 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/window.js +50 -0
- package/src/actions/encodings/color/index.js +17 -1
- package/src/actions/encodings/color/layout.js +8 -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 +6 -0
- 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/index.js +15 -2
- 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 +24 -2
- package/src/actions/encodings/ranged.js +3 -1
- package/src/actions/encodings/ruleAppearance.js +88 -10
- package/src/actions/facets/actions.js +1 -2
- package/src/actions/facets/derive.js +33 -3
- 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 +11 -6
- package/src/actions/guides/axes/parallel/resolve.js +86 -0
- package/src/actions/guides/axes/parallel.js +228 -0
- package/src/actions/guides/axes/titles.js +11 -8
- package/src/actions/guides/guides.js +60 -5
- package/src/actions/guides/legends/categorical/actions.js +48 -11
- package/src/actions/guides/legends/edit.js +5 -0
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/remove.js +4 -11
- package/src/actions/guides/legends/strokeWidth.js +170 -0
- package/src/actions/index.js +6 -0
- package/src/actions/marks/area/actions.js +60 -136
- package/src/actions/marks/area/materialize.js +163 -0
- package/src/actions/marks/index.js +5 -1
- package/src/actions/marks/line/actions.js +99 -80
- 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 +3 -0
- package/src/actions/marks/remove.js +13 -2
- package/src/actions/marks/rule/actions.js +25 -3
- package/src/actions/marks/text/actions.js +19 -4
- 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/edit.js +19 -21
- package/src/actions/scales/consumers/common.js +18 -0
- package/src/actions/scales/consumers/families.js +14 -3
- package/src/actions/scales/definitions.js +30 -0
- package/src/actions/scales/edit.js +6 -204
- package/src/actions/scales/editPolicy.js +214 -0
- package/src/actions/scales/materialize.js +1 -1
- 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/textMetrics.js +47 -0
- package/src/core/vocabulary.js +7 -2
- package/src/grammar/arcs.js +49 -12
- package/src/grammar/areaSeries.js +41 -5
- 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 +252 -26
- package/src/grammar/facets/dependencies.js +3 -11
- package/src/grammar/facets/index.js +3 -1
- package/src/grammar/gradientProfile.js +213 -0
- package/src/grammar/horizon.js +455 -0
- package/src/grammar/jitter.js +197 -0
- package/src/grammar/lineSeries.js +69 -0
- package/src/grammar/paint.js +88 -0
- package/src/grammar/parallelCoordinates.js +213 -0
- package/src/grammar/pathOrder.js +35 -0
- package/src/grammar/pointShapes.js +24 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/appearance.js +21 -0
- 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/transforms.js +100 -11
- 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/dataProvenance.js +56 -2
- package/src/materialization/dependencies.js +9 -14
- 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 +21 -1
- package/src/materialization/marks/index.js +3 -0
- package/src/materialization/marks/pathOrder.js +71 -0
- package/src/materialization/marks/policies.js +6 -2
- package/src/materialization/scaleGuideDependencies.js +8 -0
- package/src/materialization/scales/resolve.js +2 -0
- 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 +20 -9
- 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/selectors/datasets.js +4 -0
- package/src/theme/defaults.js +4 -0
- package/types/extension.d.ts +6 -0
- package/types/index.d.ts +76 -0
- package/types/program.d.ts +632 -10
|
@@ -5,6 +5,9 @@ import { normalizeOptions } from "./options.js";
|
|
|
5
5
|
import { findLayer } from "../../../../selectors/layers.js";
|
|
6
6
|
import { findSemanticScale } from "../../../../selectors/scales.js";
|
|
7
7
|
import { isSizeLegendPoint } from "../size.js";
|
|
8
|
+
import { isStrokeWidthLegendLayer } from "../strokeWidth.js";
|
|
9
|
+
import { legendResourcePolicies } from
|
|
10
|
+
"../../../../materialization/guides/resources.js";
|
|
8
11
|
import {
|
|
9
12
|
resolveCurrentDefinition,
|
|
10
13
|
resolveDefinition,
|
|
@@ -80,17 +83,13 @@ export const rematerializeLegend = action(
|
|
|
80
83
|
).some(highlight => highlight.target === config.target);
|
|
81
84
|
if (hasHighlight) next = next.rematerializeLegendHighlights();
|
|
82
85
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
next = next.rematerializeIntervalLegend();
|
|
91
|
-
}
|
|
92
|
-
if (this.guideConfigs.legend?.opacity !== undefined) {
|
|
93
|
-
next = next.rematerializeOpacityLegend();
|
|
86
|
+
for (const policy of legendResourcePolicies()) {
|
|
87
|
+
if (
|
|
88
|
+
policy.rematerializeOp !== undefined &&
|
|
89
|
+
this.guideConfigs.legend?.[policy.kind] !== undefined
|
|
90
|
+
) {
|
|
91
|
+
next = next[policy.rematerializeOp]();
|
|
92
|
+
}
|
|
94
93
|
}
|
|
95
94
|
return next;
|
|
96
95
|
}
|
|
@@ -177,6 +176,8 @@ export const createLegend = action(
|
|
|
177
176
|
throw new TypeError("createLegend channels must be an array.");
|
|
178
177
|
}
|
|
179
178
|
const explicitSize = channels?.length === 1 && channels[0] === "size";
|
|
179
|
+
const explicitStrokeWidth = channels?.length === 1 &&
|
|
180
|
+
channels[0] === "strokeWidth";
|
|
180
181
|
const requestedSizeLayer = args.target === undefined
|
|
181
182
|
? undefined
|
|
182
183
|
: findLayer(this, args.target);
|
|
@@ -216,6 +217,42 @@ export const createLegend = action(
|
|
|
216
217
|
...(count === undefined ? {} : { count })
|
|
217
218
|
});
|
|
218
219
|
}
|
|
220
|
+
const strokeWidthCandidates = this.semanticSpec.layers.filter(
|
|
221
|
+
isStrokeWidthLegendLayer
|
|
222
|
+
);
|
|
223
|
+
const requestedStrokeWidthLayer = args.target === undefined
|
|
224
|
+
? undefined
|
|
225
|
+
: findLayer(this, args.target);
|
|
226
|
+
const hasOtherStrokeWidthLegendCandidate = this.semanticSpec.layers.some(
|
|
227
|
+
layer => ["color", "shape", "strokeDash", "size", "opacity"].some(
|
|
228
|
+
channel => layer.encoding?.[channel]?.scale !== undefined
|
|
229
|
+
)
|
|
230
|
+
);
|
|
231
|
+
const inferredStrokeWidth = channels === undefined && (
|
|
232
|
+
(args.target !== undefined &&
|
|
233
|
+
isStrokeWidthLegendLayer(requestedStrokeWidthLayer) &&
|
|
234
|
+
!hasNonSizeLegendEncoding(requestedStrokeWidthLayer)) ||
|
|
235
|
+
(args.target === undefined && strokeWidthCandidates.length === 1 &&
|
|
236
|
+
!hasOtherStrokeWidthLegendCandidate)
|
|
237
|
+
);
|
|
238
|
+
if (explicitStrokeWidth || inferredStrokeWidth) {
|
|
239
|
+
const { target, count, position, channels: _channels, ...unsupported } = args;
|
|
240
|
+
const unsupportedKeys = Object.keys(unsupported);
|
|
241
|
+
if (unsupportedKeys.length > 0) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`Standalone stroke-width legend does not support option "${unsupportedKeys[0]}".`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
if (position !== undefined && position !== "right") {
|
|
247
|
+
throw new Error(
|
|
248
|
+
'Standalone stroke-width legends currently require position "right".'
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
return this.createStrokeWidthLegend({
|
|
252
|
+
...(target === undefined ? {} : { target }),
|
|
253
|
+
...(count === undefined ? {} : { count })
|
|
254
|
+
});
|
|
255
|
+
}
|
|
219
256
|
const opacityCandidates = this.semanticSpec.layers.filter(layer =>
|
|
220
257
|
layer.mark?.type === "point" && layer.encoding?.opacity?.scale !== undefined
|
|
221
258
|
);
|
|
@@ -284,6 +284,11 @@ export const editLegend = action(
|
|
|
284
284
|
if (configs.interval?.target === target) {
|
|
285
285
|
return editInterval(this, configs.interval, args);
|
|
286
286
|
}
|
|
287
|
+
if (configs.strokeWidth?.target === target) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
"editLegend does not yet support stroke-width legends; edit the owning scale instead."
|
|
290
|
+
);
|
|
291
|
+
}
|
|
287
292
|
const continuousKind = ["gradient", "opacity"].find(
|
|
288
293
|
kind => configs[kind]?.target === target
|
|
289
294
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { registerCategoricalLegendActions } from "./categorical/index.js";
|
|
2
2
|
import { registerContinuousLegendActions } from "./continuous/index.js";
|
|
3
3
|
import { registerSizeLegendActions } from "./size.js";
|
|
4
|
+
import { registerStrokeWidthLegendActions } from "./strokeWidth.js";
|
|
4
5
|
import { registerFocusedLegendActions } from "./focused.js";
|
|
5
6
|
import { removeLegend } from "./remove.js";
|
|
6
7
|
|
|
@@ -8,6 +9,7 @@ export function registerLegendActions(ProgramClass) {
|
|
|
8
9
|
registerCategoricalLegendActions(ProgramClass);
|
|
9
10
|
registerContinuousLegendActions(ProgramClass);
|
|
10
11
|
registerSizeLegendActions(ProgramClass);
|
|
12
|
+
registerStrokeWidthLegendActions(ProgramClass);
|
|
11
13
|
registerFocusedLegendActions(ProgramClass);
|
|
12
14
|
ProgramClass.prototype.removeLegend = removeLegend;
|
|
13
15
|
}
|
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
import { action } from "../../../core/action.js";
|
|
2
2
|
import { validateKeys } from "../../../core/validation.js";
|
|
3
|
-
import { legendGraphicIds } from
|
|
3
|
+
import { legendGraphicIds, legendResourcePolicy } from
|
|
4
4
|
"../../../materialization/guides/resources.js";
|
|
5
5
|
import { resolveLegendTarget } from "./target.js";
|
|
6
6
|
|
|
7
7
|
const OPTIONS = Object.freeze(["target"]);
|
|
8
8
|
|
|
9
|
-
const SEMANTIC_KIND = Object.freeze({
|
|
10
|
-
series: "series",
|
|
11
|
-
color: "color",
|
|
12
|
-
size: "size",
|
|
13
|
-
gradient: "color",
|
|
14
|
-
interval: "color",
|
|
15
|
-
opacity: "opacity"
|
|
16
|
-
});
|
|
17
|
-
|
|
18
9
|
export const removeLegend = action(
|
|
19
10
|
{ op: "removeLegend", description: "Remove every legend block owned by one mark." },
|
|
20
11
|
function (args = {}) {
|
|
@@ -23,7 +14,9 @@ export const removeLegend = action(
|
|
|
23
14
|
const kinds = Object.entries(this.guideConfigs.legend ?? {})
|
|
24
15
|
.filter(([, config]) => config?.target === target)
|
|
25
16
|
.map(([kind]) => kind);
|
|
26
|
-
const semanticKinds = new Set(
|
|
17
|
+
const semanticKinds = new Set(
|
|
18
|
+
kinds.map(kind => legendResourcePolicy(kind).semanticKind)
|
|
19
|
+
);
|
|
27
20
|
let next = this;
|
|
28
21
|
for (const kind of semanticKinds) {
|
|
29
22
|
if (next.semanticSpec.guides.legend?.[kind] !== undefined) {
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateKeys } from "../../../core/validation.js";
|
|
3
|
+
import { mapContinuousScaleValues } from "../../../grammar/scales/index.js";
|
|
4
|
+
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
5
|
+
import { resolveLegendGraphicPlacement } from
|
|
6
|
+
"../../../materialization/graphicHierarchy.js";
|
|
7
|
+
import { findLayer } from "../../../selectors/layers.js";
|
|
8
|
+
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
9
|
+
"../../../theme/defaults.js";
|
|
10
|
+
|
|
11
|
+
const OPTIONS = Object.freeze(["target", "count"]);
|
|
12
|
+
|
|
13
|
+
export function isStrokeWidthLegendLayer(layer) {
|
|
14
|
+
return ["line", "rule"].includes(layer?.mark?.type) &&
|
|
15
|
+
layer.encoding?.strokeWidth?.scale !== undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function resolveLayer(program, requested) {
|
|
19
|
+
const candidates = program.semanticSpec.layers.filter(isStrokeWidthLegendLayer);
|
|
20
|
+
const layer = requested === undefined
|
|
21
|
+
? candidates.length === 1 ? candidates[0] : undefined
|
|
22
|
+
: (() => {
|
|
23
|
+
const candidate = findLayer(program, requested);
|
|
24
|
+
return candidates.includes(candidate) ? candidate : undefined;
|
|
25
|
+
})();
|
|
26
|
+
if (layer === undefined) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
requested === undefined
|
|
29
|
+
? "Stroke-width legend requires one eligible line or rule mark or an explicit target."
|
|
30
|
+
: `Unknown stroke-width legend target "${requested}".`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return layer;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function requireScale(program, id) {
|
|
37
|
+
const scale = program.resolvedScales[id];
|
|
38
|
+
if (scale === undefined || !["linear", "log", "pow", "sqrt", "symlog"].includes(scale.type)) {
|
|
39
|
+
throw new Error(`Stroke-width legend requires resolved quantitative scale "${id}".`);
|
|
40
|
+
}
|
|
41
|
+
return scale;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function styleText(program, id, { title = false } = {}) {
|
|
45
|
+
return program
|
|
46
|
+
.editGraphics({ target: id, property: "fill", value: title
|
|
47
|
+
? DEFAULT_COLORS.strongText
|
|
48
|
+
: DEFAULT_COLORS.text })
|
|
49
|
+
.editGraphics({ target: id, property: "fontSize", value: title ? 13 : 12 })
|
|
50
|
+
.editGraphics({ target: id, property: "fontFamily", value: DEFAULT_FONT_FAMILY })
|
|
51
|
+
.editGraphics({ target: id, property: "fontWeight", value: title ? 600 : "normal" })
|
|
52
|
+
.editGraphics({ target: id, property: "textAlign", value: "left" })
|
|
53
|
+
.editGraphics({ target: id, property: "textBaseline", value: "middle" });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const rematerializeStrokeWidthLegend = action(
|
|
57
|
+
{
|
|
58
|
+
op: "rematerializeStrokeWidthLegend",
|
|
59
|
+
description: "Rematerialize a quantitative stroke-width legend."
|
|
60
|
+
},
|
|
61
|
+
function (args = {}) {
|
|
62
|
+
validateKeys(args, [], "rematerializeStrokeWidthLegend");
|
|
63
|
+
const config = this.guideConfigs.legend?.strokeWidth;
|
|
64
|
+
if (config === undefined) {
|
|
65
|
+
throw new Error("Stroke-width legend requires stored configuration.");
|
|
66
|
+
}
|
|
67
|
+
const layer = findLayer(this, config.target);
|
|
68
|
+
const encoding = layer?.encoding?.strokeWidth;
|
|
69
|
+
if (encoding?.scale === undefined) {
|
|
70
|
+
throw new Error("Stroke-width legend target requires a strokeWidth encoding.");
|
|
71
|
+
}
|
|
72
|
+
const scale = requireScale(this, encoding.scale);
|
|
73
|
+
const plot = resolveGraphicBounds(this);
|
|
74
|
+
if (plot === undefined) {
|
|
75
|
+
throw new Error("Stroke-width legend requires resolved plot bounds.");
|
|
76
|
+
}
|
|
77
|
+
const values = Array.from(
|
|
78
|
+
{ length: config.count },
|
|
79
|
+
(_, index) => scale.domain[0] +
|
|
80
|
+
index / (config.count - 1) * (scale.domain[1] - scale.domain[0])
|
|
81
|
+
);
|
|
82
|
+
const widths = mapContinuousScaleValues(values, scale);
|
|
83
|
+
const originX = plot.x + plot.width + 30;
|
|
84
|
+
const titleY = plot.y + 28;
|
|
85
|
+
const y = values.map((_, index) => titleY + 34 + index * 32);
|
|
86
|
+
const title = config.inferredTitle === true ? encoding.field : config.title;
|
|
87
|
+
let next = this
|
|
88
|
+
.editSemantic({ property: "guide.legend.strokeWidth.scale", value: encoding.scale })
|
|
89
|
+
.editSemantic({ property: "guide.legend.strokeWidth.title", value: title })
|
|
90
|
+
._withLegendConfig("strokeWidth", {
|
|
91
|
+
...config,
|
|
92
|
+
scale: encoding.scale,
|
|
93
|
+
title,
|
|
94
|
+
domain: scale.domain
|
|
95
|
+
})
|
|
96
|
+
.editGraphics({ target: "strokeWidthLegendSymbols", property: "length", value: values.length })
|
|
97
|
+
.editGraphics({ target: "strokeWidthLegendSymbols", property: "x1", value: values.map(() => originX) })
|
|
98
|
+
.editGraphics({ target: "strokeWidthLegendSymbols", property: "x2", value: values.map(() => originX + 32) })
|
|
99
|
+
.editGraphics({ target: "strokeWidthLegendSymbols", property: "y1", value: y })
|
|
100
|
+
.editGraphics({ target: "strokeWidthLegendSymbols", property: "y2", value: y })
|
|
101
|
+
.editGraphics({ target: "strokeWidthLegendSymbols", property: "stroke", value: DEFAULT_COLORS.mark })
|
|
102
|
+
.editGraphics({ target: "strokeWidthLegendSymbols", property: "strokeWidth", value: widths })
|
|
103
|
+
.editGraphics({ target: "strokeWidthLegendLabels", property: "length", value: values.length })
|
|
104
|
+
.editGraphics({ target: "strokeWidthLegendLabels", property: "x", value: values.map(() => originX + 44) })
|
|
105
|
+
.editGraphics({ target: "strokeWidthLegendLabels", property: "y", value: y })
|
|
106
|
+
.editGraphics({
|
|
107
|
+
target: "strokeWidthLegendLabels",
|
|
108
|
+
property: "text",
|
|
109
|
+
value: values.map(value => String(+value.toPrecision(3)))
|
|
110
|
+
})
|
|
111
|
+
.editGraphics({ target: "strokeWidthLegendTitle", property: "x", value: originX })
|
|
112
|
+
.editGraphics({ target: "strokeWidthLegendTitle", property: "y", value: titleY })
|
|
113
|
+
.editGraphics({ target: "strokeWidthLegendTitle", property: "text", value: title });
|
|
114
|
+
next = styleText(next, "strokeWidthLegendLabels");
|
|
115
|
+
return styleText(next, "strokeWidthLegendTitle", { title: true });
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
export const createStrokeWidthLegend = action(
|
|
120
|
+
{
|
|
121
|
+
op: "createStrokeWidthLegend",
|
|
122
|
+
description: "Create a quantitative stroke-width legend."
|
|
123
|
+
},
|
|
124
|
+
function (args = {}) {
|
|
125
|
+
validateKeys(args, OPTIONS, "createStrokeWidthLegend");
|
|
126
|
+
const layer = resolveLayer(this, args.target);
|
|
127
|
+
const encoding = layer.encoding.strokeWidth;
|
|
128
|
+
requireScale(this, encoding.scale);
|
|
129
|
+
const count = args.count ?? 5;
|
|
130
|
+
if (!Number.isInteger(count) || count < 2) {
|
|
131
|
+
throw new RangeError(
|
|
132
|
+
"Stroke-width legend count must be an integer of at least 2."
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return this
|
|
136
|
+
.editSemantic({ property: "guide.legend.strokeWidth.scale", value: encoding.scale })
|
|
137
|
+
.editSemantic({ property: "guide.legend.strokeWidth.title", value: encoding.field })
|
|
138
|
+
._withLegendConfig("strokeWidth", {
|
|
139
|
+
target: layer.id,
|
|
140
|
+
scale: encoding.scale,
|
|
141
|
+
title: encoding.field,
|
|
142
|
+
inferredTitle: true,
|
|
143
|
+
count
|
|
144
|
+
})
|
|
145
|
+
.createGraphics({
|
|
146
|
+
id: "strokeWidthLegendSymbols",
|
|
147
|
+
type: "line",
|
|
148
|
+
length: count,
|
|
149
|
+
...resolveLegendGraphicPlacement(this)
|
|
150
|
+
})
|
|
151
|
+
.createGraphics({
|
|
152
|
+
id: "strokeWidthLegendLabels",
|
|
153
|
+
type: "text",
|
|
154
|
+
length: count,
|
|
155
|
+
...resolveLegendGraphicPlacement(this)
|
|
156
|
+
})
|
|
157
|
+
.createGraphics({
|
|
158
|
+
id: "strokeWidthLegendTitle",
|
|
159
|
+
type: "text",
|
|
160
|
+
...resolveLegendGraphicPlacement(this)
|
|
161
|
+
})
|
|
162
|
+
.rematerializeStrokeWidthLegend();
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
export function registerStrokeWidthLegendActions(ProgramClass) {
|
|
167
|
+
ProgramClass.prototype.createStrokeWidthLegend = createStrokeWidthLegend;
|
|
168
|
+
ProgramClass.prototype.rematerializeStrokeWidthLegend =
|
|
169
|
+
rematerializeStrokeWidthLegend;
|
|
170
|
+
}
|
package/src/actions/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { registerCanvasActions } from "./canvas/index.js";
|
|
2
|
+
import { registerChartActions } from "./charts/index.js";
|
|
2
3
|
import { registerCoordinateActions } from "./coordinates/index.js";
|
|
3
4
|
import { registerCompositionActions } from "./composition/index.js";
|
|
4
5
|
import { registerDataActions } from "./data/index.js";
|
|
@@ -8,12 +9,14 @@ import { registerErrorBarActions } from "./errorBars/index.js";
|
|
|
8
9
|
import { registerFacetActions } from "./facets/index.js";
|
|
9
10
|
import { registerBoxPlotActions } from "./boxPlots/index.js";
|
|
10
11
|
import { registerGuideActions } from "./guides/index.js";
|
|
12
|
+
import { registerGradientPlotActions } from "./gradientPlots/index.js";
|
|
11
13
|
import { registerMarkActions } from "./marks/index.js";
|
|
12
14
|
import { registerPrimitiveActions } from "./primitives/index.js";
|
|
13
15
|
import { registerRegressionActions } from "./regression/index.js";
|
|
14
16
|
import { registerScaleActions } from "./scales/index.js";
|
|
15
17
|
import { registerTitleActions } from "./titles/index.js";
|
|
16
18
|
import { registerSelectionActions } from "./selection/index.js";
|
|
19
|
+
import { registerViolinPlotActions } from "./violinPlots/index.js";
|
|
17
20
|
|
|
18
21
|
export function registerActions(ProgramClass) {
|
|
19
22
|
registerPrimitiveActions(ProgramClass);
|
|
@@ -31,5 +34,8 @@ export function registerActions(ProgramClass) {
|
|
|
31
34
|
registerErrorBarActions(ProgramClass);
|
|
32
35
|
registerErrorBandActions(ProgramClass);
|
|
33
36
|
registerBoxPlotActions(ProgramClass);
|
|
37
|
+
registerGradientPlotActions(ProgramClass);
|
|
38
|
+
registerViolinPlotActions(ProgramClass);
|
|
39
|
+
registerChartActions(ProgramClass);
|
|
34
40
|
registerSelectionActions(ProgramClass);
|
|
35
41
|
}
|
|
@@ -5,12 +5,6 @@ import {
|
|
|
5
5
|
validateNonNegativeFinite,
|
|
6
6
|
validateUnitInterval
|
|
7
7
|
} from "../../../core/validation.js";
|
|
8
|
-
import {
|
|
9
|
-
deriveAreaSeries,
|
|
10
|
-
deriveDensityAreaSeries,
|
|
11
|
-
layoutDensityAreaSeries
|
|
12
|
-
} from "../../../grammar/areaSeries.js";
|
|
13
|
-
import { mapContinuousScaleValues, mapOrdinalValues } from "../../../grammar/scales/index.js";
|
|
14
8
|
import {
|
|
15
9
|
assertMarkAvailable,
|
|
16
10
|
applyLayeredMarkInheritance,
|
|
@@ -23,17 +17,14 @@ import {
|
|
|
23
17
|
import { DEFAULT_COLORS } from "../../../theme/defaults.js";
|
|
24
18
|
import { findDataset } from "../../../selectors/datasets.js";
|
|
25
19
|
import { findLayer } from "../../../selectors/layers.js";
|
|
26
|
-
import {
|
|
27
|
-
buildAreaCurvePathCommands,
|
|
28
|
-
validateCurveInterpolation
|
|
29
|
-
} from "../../../grammar/curveCommands.js";
|
|
30
|
-
import { buildLinearPathCommands } from "../../../grammar/pathCommands.js";
|
|
20
|
+
import { validateCurveInterpolation } from "../../../grammar/curveCommands.js";
|
|
31
21
|
import { resolveEligibleLayer } from "../../../selectors/layers.js";
|
|
32
22
|
import { canMaterializeArea } from "../../../materialization/marks/index.js";
|
|
33
23
|
import { findUpstreamTransform } from "../../../materialization/dataProvenance.js";
|
|
34
24
|
import { resolveMarkGraphicPlacement } from
|
|
35
25
|
"../../../materialization/graphicHierarchy.js";
|
|
36
26
|
import { rematerializeHighlightBaseline } from "../lifecycle.js";
|
|
27
|
+
import { resolveAreaMaterialization } from "./materialize.js";
|
|
37
28
|
|
|
38
29
|
const CREATE_OPTIONS = Object.freeze([
|
|
39
30
|
"id", "data", "fill", "opacity", "stroke", "strokeWidth", "curve"
|
|
@@ -42,6 +33,7 @@ const EDIT_OPTIONS = Object.freeze([
|
|
|
42
33
|
"target", "fill", "opacity", "stroke", "strokeWidth", "curve"
|
|
43
34
|
]);
|
|
44
35
|
const REMATERIALIZE_OPTIONS = Object.freeze(["id", "scales"]);
|
|
36
|
+
const STROKE_FROM_FILL_OPTIONS = Object.freeze(["id", "strokeWidth"]);
|
|
45
37
|
|
|
46
38
|
export function validateAreaCreateOutline(args, operation = "createAreaMark") {
|
|
47
39
|
if (Object.hasOwn(args, "strokeWidth") && !Object.hasOwn(args, "stroke")) {
|
|
@@ -103,6 +95,35 @@ const createAreaMark = action(
|
|
|
103
95
|
}
|
|
104
96
|
);
|
|
105
97
|
|
|
98
|
+
const configureAreaStrokeFromFill = action(
|
|
99
|
+
{
|
|
100
|
+
op: "configureAreaStrokeFromFill",
|
|
101
|
+
description: "Use each area path fill as its outline color."
|
|
102
|
+
},
|
|
103
|
+
function (args = {}) {
|
|
104
|
+
validateMarkOptions(
|
|
105
|
+
args,
|
|
106
|
+
STROKE_FROM_FILL_OPTIONS,
|
|
107
|
+
"configureAreaStrokeFromFill"
|
|
108
|
+
);
|
|
109
|
+
const id = validateUserId(args.id, "Area mark id");
|
|
110
|
+
const layer = findLayer(this, id);
|
|
111
|
+
if (layer?.mark?.type !== "area") {
|
|
112
|
+
throw new Error(`Unknown area mark "${id}".`);
|
|
113
|
+
}
|
|
114
|
+
const { stroke: _stroke, ...config } = this.markConfigs[id] ?? {};
|
|
115
|
+
void _stroke;
|
|
116
|
+
return this._withMarkConfig(id, {
|
|
117
|
+
...config,
|
|
118
|
+
strokeFromFill: true,
|
|
119
|
+
strokeWidth: validateNonNegativeFinite(
|
|
120
|
+
args.strokeWidth ?? 1,
|
|
121
|
+
"Area strokeWidth"
|
|
122
|
+
)
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
106
127
|
const rematerializeAreaMark = action(
|
|
107
128
|
{
|
|
108
129
|
op: "rematerializeAreaMark",
|
|
@@ -149,14 +170,7 @@ const rematerializeAreaMark = action(
|
|
|
149
170
|
: `Density area mark "${id}" requires x and y scales.`
|
|
150
171
|
);
|
|
151
172
|
}
|
|
152
|
-
const rawDerived = densityTransform === undefined
|
|
153
|
-
? deriveAreaSeries(dataset.values, layer)
|
|
154
|
-
: deriveDensityAreaSeries(dataset.values, layer, densityTransform);
|
|
155
173
|
const colorEncoding = layer.encoding?.color;
|
|
156
|
-
const layout = colorEncoding?.layout ?? "overlay";
|
|
157
|
-
const derived = densityTransform === undefined
|
|
158
|
-
? rawDerived
|
|
159
|
-
: layoutDensityAreaSeries(rawDerived, layout);
|
|
160
174
|
let resolved = args.scales === false
|
|
161
175
|
? this
|
|
162
176
|
: this
|
|
@@ -165,122 +179,17 @@ const rematerializeAreaMark = action(
|
|
|
165
179
|
if (args.scales !== false && colorEncoding?.scale !== undefined) {
|
|
166
180
|
resolved = resolved.rematerializeScale({ id: colorEncoding.scale });
|
|
167
181
|
}
|
|
168
|
-
const xScale = resolved.resolvedScales[xScaleId];
|
|
169
|
-
const yScale = resolved.resolvedScales[yScaleId];
|
|
170
|
-
const densityScale = densityTransform === undefined
|
|
171
|
-
? undefined
|
|
172
|
-
: derived.mode === "y-density" ? yScale : xScale;
|
|
173
|
-
if (
|
|
174
|
-
densityScale !== undefined &&
|
|
175
|
-
(densityScale.domain[0] > 0 || densityScale.domain[1] < 0)
|
|
176
|
-
) {
|
|
177
|
-
throw new Error(`Density area mark "${id}" requires a scale domain containing zero.`);
|
|
178
|
-
}
|
|
179
|
-
const paths = derived.series.map(series => {
|
|
180
|
-
const curve = this.markConfigs[id]?.curve ?? "linear";
|
|
181
|
-
if (densityTransform === undefined && derived.orientation === "horizontal") {
|
|
182
|
-
const y = mapContinuousScaleValues(
|
|
183
|
-
series.values.map(value => value.y),
|
|
184
|
-
yScale
|
|
185
|
-
);
|
|
186
|
-
const lower = mapContinuousScaleValues(
|
|
187
|
-
series.values.map(value => value.x),
|
|
188
|
-
xScale
|
|
189
|
-
);
|
|
190
|
-
const upper = mapContinuousScaleValues(
|
|
191
|
-
series.values.map(value => value.x2),
|
|
192
|
-
xScale
|
|
193
|
-
);
|
|
194
|
-
return buildAreaCurvePathCommands(
|
|
195
|
-
y.map((value, index) => ({ x: lower[index], y: value })),
|
|
196
|
-
y.map((value, index) => ({ x: upper[index], y: value })),
|
|
197
|
-
curve,
|
|
198
|
-
{ independentAxis: "y" }
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
if (densityTransform !== undefined && derived.mode === "x-density") {
|
|
202
|
-
const y = mapContinuousScaleValues(
|
|
203
|
-
series.values.map(value => value.y),
|
|
204
|
-
yScale
|
|
205
|
-
);
|
|
206
|
-
const upper = mapContinuousScaleValues(
|
|
207
|
-
series.values.map(value => value.x),
|
|
208
|
-
xScale
|
|
209
|
-
);
|
|
210
|
-
const baseline = mapContinuousScaleValues(
|
|
211
|
-
[0],
|
|
212
|
-
densityScale
|
|
213
|
-
)[0];
|
|
214
|
-
if (curve === "linear") {
|
|
215
|
-
return buildLinearPathCommands([
|
|
216
|
-
{ x: baseline, y: y[0] },
|
|
217
|
-
...y.map((value, index) => ({ x: upper[index], y: value })),
|
|
218
|
-
{ x: baseline, y: y.at(-1) }
|
|
219
|
-
], { close: true });
|
|
220
|
-
}
|
|
221
|
-
return buildAreaCurvePathCommands(
|
|
222
|
-
y.map(value => ({ x: baseline, y: value })),
|
|
223
|
-
y.map((value, index) => ({ x: upper[index], y: value })),
|
|
224
|
-
curve,
|
|
225
|
-
{ independentAxis: "y" }
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
const x = mapContinuousScaleValues(
|
|
229
|
-
series.values.map(value => value.x),
|
|
230
|
-
xScale
|
|
231
|
-
);
|
|
232
|
-
const lowerValues = densityTransform === undefined
|
|
233
|
-
? series.values.map(value => value.y)
|
|
234
|
-
: derived.mode === "y-density"
|
|
235
|
-
? series.values.map(value => value.lower)
|
|
236
|
-
: series.values.map(value => value.y);
|
|
237
|
-
const lower = mapContinuousScaleValues(
|
|
238
|
-
lowerValues,
|
|
239
|
-
yScale
|
|
240
|
-
);
|
|
241
|
-
const upperValues = densityTransform === undefined
|
|
242
|
-
? series.values.map(value => value.y2)
|
|
243
|
-
: series.values.map(value => value.upper);
|
|
244
|
-
const upper = mapContinuousScaleValues(
|
|
245
|
-
upperValues,
|
|
246
|
-
yScale
|
|
247
|
-
);
|
|
248
|
-
if (densityTransform !== undefined && layout === "overlay") {
|
|
249
|
-
const baseline = mapContinuousScaleValues(
|
|
250
|
-
[0],
|
|
251
|
-
yScale
|
|
252
|
-
)[0];
|
|
253
|
-
if (curve === "linear") {
|
|
254
|
-
return buildLinearPathCommands([
|
|
255
|
-
{ x: x[0], y: baseline },
|
|
256
|
-
...x.map((value, index) => ({ x: value, y: upper[index] })),
|
|
257
|
-
{ x: x.at(-1), y: baseline }
|
|
258
|
-
], { close: true });
|
|
259
|
-
}
|
|
260
|
-
return buildAreaCurvePathCommands(
|
|
261
|
-
x.map(value => ({ x: value, y: baseline })),
|
|
262
|
-
x.map((value, index) => ({ x: value, y: upper[index] })),
|
|
263
|
-
curve
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
return buildAreaCurvePathCommands(
|
|
267
|
-
x.map((value, index) => ({ x: value, y: lower[index] })),
|
|
268
|
-
x.map((value, index) => ({ x: value, y: upper[index] })),
|
|
269
|
-
curve
|
|
270
|
-
);
|
|
271
|
-
});
|
|
272
182
|
const config = this.markConfigs[id];
|
|
273
|
-
const fills =
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
:
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
resolved.resolvedScales[colorEncoding.scale].range
|
|
281
|
-
);
|
|
183
|
+
const { paths, fills } = resolveAreaMaterialization({
|
|
184
|
+
rows: dataset.values,
|
|
185
|
+
layer,
|
|
186
|
+
densityTransform,
|
|
187
|
+
resolvedScales: resolved.resolvedScales,
|
|
188
|
+
config
|
|
189
|
+
});
|
|
282
190
|
const existingChildren = resolved.graphicSpec.objects[id].items ?? [];
|
|
283
|
-
const
|
|
191
|
+
const hasOutline = config.stroke !== undefined || config.strokeFromFill === true;
|
|
192
|
+
const removesOutline = !hasOutline && existingChildren.some(
|
|
284
193
|
child => child.properties.stroke !== undefined
|
|
285
194
|
);
|
|
286
195
|
let next = removesOutline
|
|
@@ -292,9 +201,13 @@ const rematerializeAreaMark = action(
|
|
|
292
201
|
.editGraphics({ target: id, property: "commands", value: paths })
|
|
293
202
|
.editGraphics({ target: id, property: "fill", value: fills })
|
|
294
203
|
.editGraphics({ target: id, property: "opacity", value: config.opacity });
|
|
295
|
-
if (
|
|
204
|
+
if (hasOutline) {
|
|
296
205
|
next = next
|
|
297
|
-
.editGraphics({
|
|
206
|
+
.editGraphics({
|
|
207
|
+
target: id,
|
|
208
|
+
property: "stroke",
|
|
209
|
+
value: config.strokeFromFill === true ? fills : config.stroke
|
|
210
|
+
})
|
|
298
211
|
.editGraphics({
|
|
299
212
|
target: id,
|
|
300
213
|
property: "strokeWidth",
|
|
@@ -347,12 +260,21 @@ const editAreaMark = action(
|
|
|
347
260
|
}
|
|
348
261
|
if (Object.hasOwn(args, "stroke")) {
|
|
349
262
|
if (args.stroke === false) {
|
|
350
|
-
const {
|
|
263
|
+
const {
|
|
264
|
+
stroke: removedStroke,
|
|
265
|
+
strokeFromFill: removedStrokeFromFill,
|
|
266
|
+
strokeWidth: removedWidth,
|
|
267
|
+
...rest
|
|
268
|
+
} = config;
|
|
351
269
|
void removedStroke;
|
|
270
|
+
void removedStrokeFromFill;
|
|
352
271
|
void removedWidth;
|
|
353
272
|
config = rest;
|
|
354
273
|
} else {
|
|
355
|
-
const hadStroke = config.stroke !== undefined;
|
|
274
|
+
const hadStroke = config.stroke !== undefined || config.strokeFromFill === true;
|
|
275
|
+
const { strokeFromFill: _strokeFromFill, ...withoutFillStroke } = config;
|
|
276
|
+
void _strokeFromFill;
|
|
277
|
+
config = withoutFillStroke;
|
|
356
278
|
config.stroke = validateNonEmptyString(args.stroke, "Area stroke");
|
|
357
279
|
config.strokeWidth = Object.hasOwn(args, "strokeWidth")
|
|
358
280
|
? validateNonNegativeFinite(args.strokeWidth, "Area strokeWidth")
|
|
@@ -377,6 +299,8 @@ const editAreaMark = action(
|
|
|
377
299
|
|
|
378
300
|
export function registerAreaMarkActions(ProgramClass) {
|
|
379
301
|
ProgramClass.prototype.createAreaMark = createAreaMark;
|
|
302
|
+
ProgramClass.prototype.configureAreaStrokeFromFill =
|
|
303
|
+
configureAreaStrokeFromFill;
|
|
380
304
|
ProgramClass.prototype.editAreaMark = editAreaMark;
|
|
381
305
|
ProgramClass.prototype.rematerializeAreaMark = rematerializeAreaMark;
|
|
382
306
|
}
|