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
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
+
import {
|
|
4
|
+
validateNonEmptyString,
|
|
5
|
+
validateNonNegativeFinite,
|
|
6
|
+
validateUnitInterval
|
|
7
|
+
} from "../../../core/validation.js";
|
|
8
|
+
import { validatePointShape } from "../../../grammar/pointShapes.js";
|
|
9
|
+
import { findLayer } from "../../../selectors/layers.js";
|
|
10
|
+
import { rematerializeExistingLegend } from "../../encodings/shared.js";
|
|
11
|
+
import { validateMarkOptions } from "../shared.js";
|
|
12
|
+
|
|
13
|
+
const OPTIONS = Object.freeze([
|
|
14
|
+
"target", "shape", "fill", "opacity", "stroke", "strokeWidth"
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
export const editPointMark = action(
|
|
18
|
+
{
|
|
19
|
+
op: "editPointMark",
|
|
20
|
+
description: "Edit constant point-mark appearance."
|
|
21
|
+
},
|
|
22
|
+
function (args = {}) {
|
|
23
|
+
validateMarkOptions(args, OPTIONS, "editPointMark");
|
|
24
|
+
const editable = ["shape", "fill", "opacity", "stroke", "strokeWidth"];
|
|
25
|
+
if (!editable.some(property => Object.hasOwn(args, property))) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
"editPointMark requires shape, fill, opacity, stroke, or strokeWidth."
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
const candidates = this.semanticSpec.layers.filter(
|
|
31
|
+
layer => layer.mark?.type === "point"
|
|
32
|
+
);
|
|
33
|
+
const current = findLayer(this, this.context.currentMark);
|
|
34
|
+
const requested = args.target ??
|
|
35
|
+
(current?.mark?.type === "point" ? current.id : undefined);
|
|
36
|
+
const inferred = requested ?? (candidates.length === 1
|
|
37
|
+
? candidates[0].id
|
|
38
|
+
: undefined);
|
|
39
|
+
const id = validateUserId(inferred, "Point mark id");
|
|
40
|
+
const layer = findLayer(this, id);
|
|
41
|
+
if (layer?.mark?.type !== "point") {
|
|
42
|
+
throw new Error(`Unknown point mark "${id}".`);
|
|
43
|
+
}
|
|
44
|
+
if (Object.hasOwn(args, "shape") && layer.encoding?.shape !== undefined) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
"editPointMark shape cannot be combined with a shape encoding."
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
"editPointMark fill cannot be combined with a color encoding."
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const config = { ...this.markConfigs[id] };
|
|
55
|
+
if (Object.hasOwn(args, "shape")) {
|
|
56
|
+
config.shape = validatePointShape(args.shape);
|
|
57
|
+
}
|
|
58
|
+
if (Object.hasOwn(args, "fill")) {
|
|
59
|
+
config.fill = validateNonEmptyString(args.fill, "Point fill");
|
|
60
|
+
}
|
|
61
|
+
if (Object.hasOwn(args, "opacity")) {
|
|
62
|
+
config.opacity = validateUnitInterval(args.opacity, "Point opacity");
|
|
63
|
+
}
|
|
64
|
+
if (Object.hasOwn(args, "stroke")) {
|
|
65
|
+
config.stroke = validateNonEmptyString(args.stroke, "Point stroke");
|
|
66
|
+
config.strokeWidth ??= 1;
|
|
67
|
+
}
|
|
68
|
+
if (Object.hasOwn(args, "strokeWidth")) {
|
|
69
|
+
if (config.stroke === undefined) {
|
|
70
|
+
throw new Error("Point strokeWidth requires an active stroke.");
|
|
71
|
+
}
|
|
72
|
+
config.strokeWidth = validateNonNegativeFinite(
|
|
73
|
+
args.strokeWidth,
|
|
74
|
+
"Point strokeWidth"
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const next = this
|
|
78
|
+
._withMarkConfig(id, config)
|
|
79
|
+
.rematerializePointMark({ id });
|
|
80
|
+
const legend = next.guideConfigs.legend?.series;
|
|
81
|
+
return legend?.target === id && legend.channels.includes("shape")
|
|
82
|
+
? rematerializeExistingLegend(next)
|
|
83
|
+
: next;
|
|
84
|
+
}
|
|
85
|
+
);
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { createPointMark } from "./create.js";
|
|
2
|
+
import { editPointMark } from "./edit.js";
|
|
3
|
+
import { rematerializePointMark } from "./materialize.js";
|
|
4
|
+
|
|
5
|
+
export { registerPointJitterActions } from "./jitter.js";
|
|
6
|
+
|
|
7
|
+
export function registerPointMarkActions(ProgramClass) {
|
|
8
|
+
ProgramClass.prototype.createPointMark = createPointMark;
|
|
9
|
+
ProgramClass.prototype.editPointMark = editPointMark;
|
|
10
|
+
ProgramClass.prototype.rematerializePointMark = rematerializePointMark;
|
|
11
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
+
import { validateKeys } from "../../../core/validation.js";
|
|
4
|
+
import { normalizePointJitterPolicy } from "../../../grammar/jitter.js";
|
|
5
|
+
import { findCoordinate } from "../../../selectors/coordinates.js";
|
|
6
|
+
import { resolveEligibleLayer } from "../../../selectors/layers.js";
|
|
7
|
+
|
|
8
|
+
const JITTER_OPTIONS = Object.freeze([
|
|
9
|
+
"target", "channel", "maxOffset", "seed", "key"
|
|
10
|
+
]);
|
|
11
|
+
const REMOVE_OPTIONS = Object.freeze(["target"]);
|
|
12
|
+
|
|
13
|
+
function isCartesianPoint(program, layer, channel) {
|
|
14
|
+
return layer.mark?.type === "point" &&
|
|
15
|
+
layer.encoding?.[channel]?.scale !== undefined &&
|
|
16
|
+
layer.encoding?.[channel === "x" ? "y" : "x"]?.scale !== undefined &&
|
|
17
|
+
findCoordinate(program, layer.coordinate)?.type === "cartesian";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resolveJitterTarget(program, target, channel) {
|
|
21
|
+
const requested = target === undefined
|
|
22
|
+
? undefined
|
|
23
|
+
: validateUserId(target, "Point jitter target");
|
|
24
|
+
return resolveEligibleLayer(program, {
|
|
25
|
+
target: requested,
|
|
26
|
+
predicate: layer => isCartesianPoint(program, layer, channel),
|
|
27
|
+
label: "point jitter"
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const jitterPoints = action(
|
|
32
|
+
{
|
|
33
|
+
op: "jitterPoints",
|
|
34
|
+
description: "Apply deterministic bounded graphical jitter to a point mark."
|
|
35
|
+
},
|
|
36
|
+
function (args = {}) {
|
|
37
|
+
validateKeys(args, JITTER_OPTIONS, "jitterPoints");
|
|
38
|
+
const policy = normalizePointJitterPolicy(args);
|
|
39
|
+
const layer = resolveJitterTarget(this, args.target, policy.channel);
|
|
40
|
+
return this
|
|
41
|
+
._withMaterializationConfig(["jitters", layer.id], policy)
|
|
42
|
+
.rematerializePointMark({ id: layer.id });
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export const removeJitter = action(
|
|
47
|
+
{
|
|
48
|
+
op: "removeJitter",
|
|
49
|
+
description: "Remove point jitter and restore semantic scale positions."
|
|
50
|
+
},
|
|
51
|
+
function (args = {}) {
|
|
52
|
+
validateKeys(args, REMOVE_OPTIONS, "removeJitter");
|
|
53
|
+
const requested = args.target === undefined
|
|
54
|
+
? undefined
|
|
55
|
+
: validateUserId(args.target, "Point jitter target");
|
|
56
|
+
const layer = resolveEligibleLayer(this, {
|
|
57
|
+
target: requested,
|
|
58
|
+
predicate: candidate =>
|
|
59
|
+
candidate.mark?.type === "point" &&
|
|
60
|
+
this.materializationConfigs.jitters?.[candidate.id] !== undefined,
|
|
61
|
+
label: "point jitter"
|
|
62
|
+
});
|
|
63
|
+
return this
|
|
64
|
+
._withoutMaterializationConfig(["jitters", layer.id])
|
|
65
|
+
.rematerializePointMark({ id: layer.id });
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
export function registerPointJitterActions(ProgramClass) {
|
|
70
|
+
ProgramClass.prototype.jitterPoints = jitterPoints;
|
|
71
|
+
ProgramClass.prototype.removeJitter = removeJitter;
|
|
72
|
+
}
|
|
@@ -1,103 +1,32 @@
|
|
|
1
1
|
import { action } from "../../../core/action.js";
|
|
2
2
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
-
import {
|
|
4
|
-
validateNonEmptyString,
|
|
5
|
-
validateNonNegativeFinite,
|
|
6
|
-
validateUnitInterval
|
|
7
|
-
} from "../../../core/validation.js";
|
|
8
3
|
import { getPointGraphicType } from "../../../grammar/schemas/mark.js";
|
|
9
4
|
import {
|
|
10
5
|
createPointShapeGraphic,
|
|
6
|
+
resolvePointShapeExtent,
|
|
11
7
|
validatePointShape
|
|
12
8
|
} from "../../../grammar/pointShapes.js";
|
|
9
|
+
import {
|
|
10
|
+
canonicalJitterScalar,
|
|
11
|
+
normalizePointJitterPolicy,
|
|
12
|
+
resolvePointJitter
|
|
13
|
+
} from "../../../grammar/jitter.js";
|
|
13
14
|
import { polarToCartesian, resolvePolarFrame } from "../../../grammar/polar.js";
|
|
14
15
|
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
16
|
+
import { validateMarkOptions } from "../shared.js";
|
|
15
17
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
resolveLayeredMarkInheritance,
|
|
20
|
-
resolveMarkId,
|
|
21
|
-
resolveMarkData,
|
|
22
|
-
validateMarkOptions
|
|
23
|
-
} from "../shared.js";
|
|
24
|
-
import { DEFAULT_COLORS } from "../../../theme/defaults.js";
|
|
18
|
+
DEFAULT_COLORS,
|
|
19
|
+
DEFAULT_POINT_RADIUS
|
|
20
|
+
} from "../../../theme/defaults.js";
|
|
25
21
|
import { findDataset } from "../../../selectors/datasets.js";
|
|
26
22
|
import { findLayer } from "../../../selectors/layers.js";
|
|
27
|
-
import { rematerializeExistingLegend } from "../../encodings/shared.js";
|
|
28
|
-
import { resolveMarkGraphicPlacement } from
|
|
29
|
-
"../../../materialization/graphicHierarchy.js";
|
|
30
23
|
import { rematerializeHighlightBaseline } from "../lifecycle.js";
|
|
31
24
|
import { resolveRowEncodingValues } from
|
|
32
25
|
"../../../materialization/rowEncoding.js";
|
|
33
26
|
|
|
34
|
-
const POINT_MARK_OPTIONS = Object.freeze([
|
|
35
|
-
"id", "data", "shape", "fill", "opacity", "stroke", "strokeWidth"
|
|
36
|
-
]);
|
|
37
|
-
const EDIT_POINT_OPTIONS = Object.freeze([
|
|
38
|
-
"target", "shape", "fill", "opacity", "stroke", "strokeWidth"
|
|
39
|
-
]);
|
|
40
27
|
const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
|
|
41
28
|
const DEFAULT_POINT_FILL = DEFAULT_COLORS.mark;
|
|
42
29
|
|
|
43
|
-
const createPointMark = action(
|
|
44
|
-
{
|
|
45
|
-
op: "createPointMark",
|
|
46
|
-
description: "Create a semantic point mark and concrete point graphics."
|
|
47
|
-
},
|
|
48
|
-
function (args = {}) {
|
|
49
|
-
validateMarkOptions(args, POINT_MARK_OPTIONS, "createPointMark");
|
|
50
|
-
const id = resolveMarkId(this, args.id, {
|
|
51
|
-
defaultId: "point",
|
|
52
|
-
label: "Point mark id",
|
|
53
|
-
markType: "point",
|
|
54
|
-
operation: "createPointMark"
|
|
55
|
-
});
|
|
56
|
-
const inherited = resolveLayeredMarkInheritance(this, args, "point");
|
|
57
|
-
const { data, dataset } = resolveMarkData(this, {
|
|
58
|
-
...args,
|
|
59
|
-
...(args.data === undefined &&
|
|
60
|
-
this.context.currentData === undefined &&
|
|
61
|
-
inherited?.data !== undefined
|
|
62
|
-
? { data: inherited.data }
|
|
63
|
-
: {})
|
|
64
|
-
});
|
|
65
|
-
const shape = Object.hasOwn(args, "shape") ? args.shape : "circle";
|
|
66
|
-
validatePointShape(shape);
|
|
67
|
-
const resolvedType = getPointGraphicType(shape);
|
|
68
|
-
const graphicType = resolvedType === "path" ? "circle" : resolvedType;
|
|
69
|
-
|
|
70
|
-
assertMarkAvailable(this, id);
|
|
71
|
-
|
|
72
|
-
let next = this
|
|
73
|
-
.editSemantic({
|
|
74
|
-
property: `layer[${id}].mark.type`,
|
|
75
|
-
value: "point"
|
|
76
|
-
})
|
|
77
|
-
.editSemantic({
|
|
78
|
-
property: `layer[${id}].data`,
|
|
79
|
-
value: data
|
|
80
|
-
});
|
|
81
|
-
next = applyLayeredMarkInheritance(next, id, inherited)
|
|
82
|
-
.createGraphics({
|
|
83
|
-
id,
|
|
84
|
-
type: graphicType,
|
|
85
|
-
length: dataset.values.length,
|
|
86
|
-
...resolveMarkGraphicPlacement(next, { data, markType: "point" })
|
|
87
|
-
})
|
|
88
|
-
._withMarkConfig(id, { shape });
|
|
89
|
-
const created = materializeInheritedMark(next, id);
|
|
90
|
-
const appearance = Object.fromEntries(
|
|
91
|
-
["fill", "opacity", "stroke", "strokeWidth"]
|
|
92
|
-
.filter(property => Object.hasOwn(args, property))
|
|
93
|
-
.map(property => [property, args[property]])
|
|
94
|
-
);
|
|
95
|
-
return Object.keys(appearance).length === 0
|
|
96
|
-
? created
|
|
97
|
-
: created.editPointMark({ target: id, ...appearance });
|
|
98
|
-
}
|
|
99
|
-
);
|
|
100
|
-
|
|
101
30
|
function resolvePointPositions(program, layer, dataset) {
|
|
102
31
|
const hasPolar = layer.encoding?.theta !== undefined ||
|
|
103
32
|
layer.encoding?.radius !== undefined;
|
|
@@ -154,7 +83,122 @@ function existingArea(child, parentType) {
|
|
|
154
83
|
return undefined;
|
|
155
84
|
}
|
|
156
85
|
|
|
157
|
-
|
|
86
|
+
function resolvedPointArea(area, index, config, existingChildren, graphicType) {
|
|
87
|
+
return area?.[index] ?? (
|
|
88
|
+
config.radius === undefined
|
|
89
|
+
? existingArea(existingChildren[index], graphicType) ??
|
|
90
|
+
Math.PI * DEFAULT_POINT_RADIUS ** 2
|
|
91
|
+
: Math.PI * config.radius ** 2
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function resolveJitterEntries({
|
|
96
|
+
policy,
|
|
97
|
+
dataset,
|
|
98
|
+
x,
|
|
99
|
+
y,
|
|
100
|
+
area,
|
|
101
|
+
shapes,
|
|
102
|
+
config,
|
|
103
|
+
graphic,
|
|
104
|
+
existingChildren
|
|
105
|
+
}) {
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
return dataset.values.flatMap((row, index) => {
|
|
108
|
+
if (!Number.isFinite(x?.[index]) || !Number.isFinite(y?.[index])) return [];
|
|
109
|
+
const identity = policy.key === undefined ? index : row[policy.key];
|
|
110
|
+
const canonical = canonicalJitterScalar(
|
|
111
|
+
identity,
|
|
112
|
+
policy.key === undefined
|
|
113
|
+
? "Point jitter source index"
|
|
114
|
+
: `Point jitter key field "${policy.key}"`
|
|
115
|
+
);
|
|
116
|
+
if (seen.has(canonical)) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Point jitter key field "${policy.key}" must be unique for materialized items.`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
seen.add(canonical);
|
|
122
|
+
const resolvedArea = resolvedPointArea(
|
|
123
|
+
area,
|
|
124
|
+
index,
|
|
125
|
+
config,
|
|
126
|
+
existingChildren,
|
|
127
|
+
graphic.type
|
|
128
|
+
);
|
|
129
|
+
const extent = resolvePointShapeExtent({
|
|
130
|
+
shape: shapes[index],
|
|
131
|
+
area: resolvedArea,
|
|
132
|
+
strokeWidth: config.stroke === undefined ? 0 : config.strokeWidth ?? 1
|
|
133
|
+
});
|
|
134
|
+
return [{
|
|
135
|
+
index,
|
|
136
|
+
identity,
|
|
137
|
+
base: policy.channel === "x" ? x[index] : y[index],
|
|
138
|
+
halfExtent: extent[policy.channel]
|
|
139
|
+
}];
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function applyPointJitter(program, {
|
|
144
|
+
id,
|
|
145
|
+
layer,
|
|
146
|
+
dataset,
|
|
147
|
+
x,
|
|
148
|
+
y,
|
|
149
|
+
area,
|
|
150
|
+
shapes,
|
|
151
|
+
config,
|
|
152
|
+
graphic,
|
|
153
|
+
existingChildren
|
|
154
|
+
}) {
|
|
155
|
+
const stored = program.materializationConfigs.jitters?.[id];
|
|
156
|
+
if (stored === undefined) return { program, x, y };
|
|
157
|
+
const policy = normalizePointJitterPolicy(stored);
|
|
158
|
+
const encoding = layer.encoding?.[policy.channel];
|
|
159
|
+
const scale = program.resolvedScales[encoding?.scale];
|
|
160
|
+
if (scale === undefined) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`Point jitter on "${id}" requires a resolved ${policy.channel} scale.`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
const bounds = resolveGraphicBounds(program);
|
|
166
|
+
const entries = resolveJitterEntries({
|
|
167
|
+
policy,
|
|
168
|
+
dataset,
|
|
169
|
+
x,
|
|
170
|
+
y,
|
|
171
|
+
area,
|
|
172
|
+
shapes,
|
|
173
|
+
config,
|
|
174
|
+
graphic,
|
|
175
|
+
existingChildren
|
|
176
|
+
});
|
|
177
|
+
const resolution = resolvePointJitter({
|
|
178
|
+
target: id,
|
|
179
|
+
policy,
|
|
180
|
+
scale,
|
|
181
|
+
entries,
|
|
182
|
+
plotMinimum: policy.channel === "x" ? bounds.x : bounds.y,
|
|
183
|
+
plotMaximum: policy.channel === "x"
|
|
184
|
+
? bounds.x + bounds.width
|
|
185
|
+
: bounds.y + bounds.height
|
|
186
|
+
});
|
|
187
|
+
const values = [...(policy.channel === "x" ? x : y)];
|
|
188
|
+
for (const item of resolution.items) {
|
|
189
|
+
values[item.index] += item.finalOffset;
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
program: program._withMaterializationConfig(["jitters", id], {
|
|
193
|
+
...policy,
|
|
194
|
+
resolved: resolution
|
|
195
|
+
}),
|
|
196
|
+
x: policy.channel === "x" ? values : x,
|
|
197
|
+
y: policy.channel === "y" ? values : y
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export const rematerializePointMark = action(
|
|
158
202
|
{
|
|
159
203
|
op: "rematerializePointMark",
|
|
160
204
|
description: "Recompute concrete point geometry and appearance."
|
|
@@ -183,7 +227,7 @@ const rematerializePointMark = action(
|
|
|
183
227
|
throw new Error(`Point mark "${id}" requires an existing dataset.`);
|
|
184
228
|
}
|
|
185
229
|
|
|
186
|
-
const
|
|
230
|
+
const positions = resolvePointPositions(this, layer, dataset);
|
|
187
231
|
const mappedFill = resolveRowEncodingValues(this, layer, dataset, "color");
|
|
188
232
|
const area = resolveRowEncodingValues(this, layer, dataset, "size");
|
|
189
233
|
const encodedShape = resolveRowEncodingValues(this, layer, dataset, "shape");
|
|
@@ -197,6 +241,19 @@ const rematerializePointMark = action(
|
|
|
197
241
|
encodedShape !== undefined ||
|
|
198
242
|
graphic.type === "collection" ||
|
|
199
243
|
getPointGraphicType(constantShape) !== graphic.type;
|
|
244
|
+
const jittered = applyPointJitter(this, {
|
|
245
|
+
id,
|
|
246
|
+
layer,
|
|
247
|
+
dataset,
|
|
248
|
+
x: positions.x,
|
|
249
|
+
y: positions.y,
|
|
250
|
+
area,
|
|
251
|
+
shapes,
|
|
252
|
+
config,
|
|
253
|
+
graphic,
|
|
254
|
+
existingChildren
|
|
255
|
+
});
|
|
256
|
+
const { x, y } = jittered;
|
|
200
257
|
|
|
201
258
|
if (requiresMixedCollection) {
|
|
202
259
|
const items = dataset.values.map((_, index) => {
|
|
@@ -205,11 +262,13 @@ const rematerializePointMark = action(
|
|
|
205
262
|
const color = mappedFill?.[index] ?? config.fill ??
|
|
206
263
|
existing.fill ?? DEFAULT_POINT_FILL;
|
|
207
264
|
const opacity = encodedOpacity?.[index] ?? config.opacity ?? existing.opacity;
|
|
208
|
-
const resolvedArea =
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
265
|
+
const resolvedArea = resolvedPointArea(
|
|
266
|
+
area,
|
|
267
|
+
index,
|
|
268
|
+
config,
|
|
269
|
+
existingChildren,
|
|
270
|
+
graphic.type
|
|
271
|
+
);
|
|
213
272
|
const centerX = x?.[index] ?? existing.x;
|
|
214
273
|
const centerY = y?.[index] ?? existing.y;
|
|
215
274
|
if (
|
|
@@ -237,12 +296,16 @@ const rematerializePointMark = action(
|
|
|
237
296
|
opacity
|
|
238
297
|
});
|
|
239
298
|
});
|
|
240
|
-
return
|
|
299
|
+
return jittered.program.editGraphics({
|
|
300
|
+
target: id,
|
|
301
|
+
property: "items",
|
|
302
|
+
value: items
|
|
303
|
+
});
|
|
241
304
|
}
|
|
242
305
|
|
|
243
306
|
let next = graphic.items.length === dataset.values.length
|
|
244
|
-
?
|
|
245
|
-
:
|
|
307
|
+
? jittered.program
|
|
308
|
+
: jittered.program.editGraphics({
|
|
246
309
|
target: id,
|
|
247
310
|
property: "length",
|
|
248
311
|
value: dataset.values.length
|
|
@@ -254,14 +317,32 @@ const rematerializePointMark = action(
|
|
|
254
317
|
}
|
|
255
318
|
if (graphic.type === "circle") {
|
|
256
319
|
const radii = area === undefined
|
|
257
|
-
? config.radius
|
|
320
|
+
? config.radius ?? (
|
|
321
|
+
x !== undefined && y !== undefined
|
|
322
|
+
? dataset.values.map((_, index) => {
|
|
323
|
+
const existing = existingChildren[index]?.properties?.radius;
|
|
324
|
+
return Number.isFinite(existing)
|
|
325
|
+
? existing
|
|
326
|
+
: DEFAULT_POINT_RADIUS;
|
|
327
|
+
})
|
|
328
|
+
: undefined
|
|
329
|
+
)
|
|
258
330
|
: area.map(value => Math.sqrt(value / Math.PI));
|
|
259
331
|
if (radii !== undefined) {
|
|
260
332
|
next = next.editGraphics({ target: id, property: "radius", value: radii });
|
|
261
333
|
}
|
|
262
|
-
} else if (
|
|
334
|
+
} else if (
|
|
335
|
+
area !== undefined ||
|
|
336
|
+
config.radius !== undefined ||
|
|
337
|
+
(x !== undefined && y !== undefined)
|
|
338
|
+
) {
|
|
263
339
|
const sides = area === undefined
|
|
264
|
-
?
|
|
340
|
+
? dataset.values.map((_, index) => {
|
|
341
|
+
const existing = existingArea(existingChildren[index], graphic.type);
|
|
342
|
+
return Math.sqrt(
|
|
343
|
+
existing ?? Math.PI * (config.radius ?? DEFAULT_POINT_RADIUS) ** 2
|
|
344
|
+
);
|
|
345
|
+
})
|
|
265
346
|
: area.map(value => Math.sqrt(value));
|
|
266
347
|
const centersX = x ?? existingChildren.map(child => child.properties.x);
|
|
267
348
|
const centersY = y ?? existingChildren.map(child => child.properties.y);
|
|
@@ -301,79 +382,3 @@ const rematerializePointMark = action(
|
|
|
301
382
|
return next;
|
|
302
383
|
}
|
|
303
384
|
);
|
|
304
|
-
|
|
305
|
-
const editPointMark = action(
|
|
306
|
-
{
|
|
307
|
-
op: "editPointMark",
|
|
308
|
-
description: "Edit constant point-mark appearance."
|
|
309
|
-
},
|
|
310
|
-
function (args = {}) {
|
|
311
|
-
validateMarkOptions(args, EDIT_POINT_OPTIONS, "editPointMark");
|
|
312
|
-
const editable = ["shape", "fill", "opacity", "stroke", "strokeWidth"];
|
|
313
|
-
if (!editable.some(property => Object.hasOwn(args, property))) {
|
|
314
|
-
throw new Error(
|
|
315
|
-
"editPointMark requires shape, fill, opacity, stroke, or strokeWidth."
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
const candidates = this.semanticSpec.layers.filter(
|
|
319
|
-
layer => layer.mark?.type === "point"
|
|
320
|
-
);
|
|
321
|
-
const current = findLayer(this, this.context.currentMark);
|
|
322
|
-
const requested = args.target ??
|
|
323
|
-
(current?.mark?.type === "point" ? current.id : undefined);
|
|
324
|
-
const inferred = requested ?? (candidates.length === 1
|
|
325
|
-
? candidates[0].id
|
|
326
|
-
: undefined);
|
|
327
|
-
const id = validateUserId(inferred, "Point mark id");
|
|
328
|
-
const layer = findLayer(this, id);
|
|
329
|
-
if (layer?.mark?.type !== "point") {
|
|
330
|
-
throw new Error(`Unknown point mark "${id}".`);
|
|
331
|
-
}
|
|
332
|
-
if (Object.hasOwn(args, "shape") && layer.encoding?.shape !== undefined) {
|
|
333
|
-
throw new Error(
|
|
334
|
-
"editPointMark shape cannot be combined with a shape encoding."
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
|
|
338
|
-
throw new Error(
|
|
339
|
-
"editPointMark fill cannot be combined with a color encoding."
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
const config = { ...this.markConfigs[id] };
|
|
343
|
-
if (Object.hasOwn(args, "shape")) {
|
|
344
|
-
config.shape = validatePointShape(args.shape);
|
|
345
|
-
}
|
|
346
|
-
if (Object.hasOwn(args, "fill")) {
|
|
347
|
-
config.fill = validateNonEmptyString(args.fill, "Point fill");
|
|
348
|
-
}
|
|
349
|
-
if (Object.hasOwn(args, "opacity")) {
|
|
350
|
-
config.opacity = validateUnitInterval(args.opacity, "Point opacity");
|
|
351
|
-
}
|
|
352
|
-
if (Object.hasOwn(args, "stroke")) {
|
|
353
|
-
config.stroke = validateNonEmptyString(args.stroke, "Point stroke");
|
|
354
|
-
config.strokeWidth ??= 1;
|
|
355
|
-
}
|
|
356
|
-
if (Object.hasOwn(args, "strokeWidth")) {
|
|
357
|
-
if (config.stroke === undefined) {
|
|
358
|
-
throw new Error("Point strokeWidth requires an active stroke.");
|
|
359
|
-
}
|
|
360
|
-
config.strokeWidth = validateNonNegativeFinite(
|
|
361
|
-
args.strokeWidth,
|
|
362
|
-
"Point strokeWidth"
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
const next = this
|
|
366
|
-
._withMarkConfig(id, config)
|
|
367
|
-
.rematerializePointMark({ id });
|
|
368
|
-
const legend = next.guideConfigs.legend?.series;
|
|
369
|
-
return legend?.target === id && legend.channels.includes("shape")
|
|
370
|
-
? rematerializeExistingLegend(next)
|
|
371
|
-
: next;
|
|
372
|
-
}
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
export function registerPointMarkActions(ProgramClass) {
|
|
376
|
-
ProgramClass.prototype.createPointMark = createPointMark;
|
|
377
|
-
ProgramClass.prototype.editPointMark = editPointMark;
|
|
378
|
-
ProgramClass.prototype.rematerializePointMark = rematerializePointMark;
|
|
379
|
-
}
|
|
@@ -102,6 +102,9 @@ const rematerializeRectMark = action(
|
|
|
102
102
|
if (graphic?.type !== "rect" || !Array.isArray(graphic.items)) {
|
|
103
103
|
throw new Error(`Rect mark "${id}" requires rect collection graphics.`);
|
|
104
104
|
}
|
|
105
|
+
if (this.markConfigs[id]?.gradientPlot?.materialized === true) {
|
|
106
|
+
return this.materializeGradientPlotFill({ id });
|
|
107
|
+
}
|
|
105
108
|
const dataset = findDataset(this, layer.data);
|
|
106
109
|
if (dataset === undefined) {
|
|
107
110
|
throw new Error(`Rect mark "${id}" requires an existing dataset.`);
|
|
@@ -27,7 +27,8 @@ function ownedChildren(program, id) {
|
|
|
27
27
|
config.regression?.lineId,
|
|
28
28
|
config.boxPlot?.whiskerId,
|
|
29
29
|
config.boxPlot?.medianId,
|
|
30
|
-
config.boxPlot?.outlierId
|
|
30
|
+
config.boxPlot?.outlierId,
|
|
31
|
+
config.gradientPlot?.centerId
|
|
31
32
|
].concat(
|
|
32
33
|
program.semanticSpec.layers
|
|
33
34
|
.filter(layer => layer.source === id)
|
|
@@ -88,7 +89,8 @@ function ownedDerivedData(program, ids) {
|
|
|
88
89
|
config.errorBand?.data,
|
|
89
90
|
config.regression?.dataId,
|
|
90
91
|
config.boxPlot?.summaryId,
|
|
91
|
-
config.boxPlot?.outlierDataId
|
|
92
|
+
config.boxPlot?.outlierDataId,
|
|
93
|
+
config.gradientPlot?.profileId
|
|
92
94
|
]) {
|
|
93
95
|
if (candidate !== undefined) candidates.add(candidate);
|
|
94
96
|
}
|
|
@@ -189,6 +191,13 @@ export const removeMark = action(
|
|
|
189
191
|
const selectionCleanup = cleanupSelectionState(next, ids);
|
|
190
192
|
next = selectionCleanup.program;
|
|
191
193
|
for (const id of ids) {
|
|
194
|
+
const labelLayout = next.materializationConfigs.labelLayouts?.[id];
|
|
195
|
+
if (
|
|
196
|
+
labelLayout?.leaderId !== undefined &&
|
|
197
|
+
next.graphicSpec.objects[labelLayout.leaderId] !== undefined
|
|
198
|
+
) {
|
|
199
|
+
next = next.editGraphics({ target: labelLayout.leaderId, remove: true });
|
|
200
|
+
}
|
|
192
201
|
if (findLayer(next, id) !== undefined) {
|
|
193
202
|
next = next.editSemantic({ property: `layer[${id}]`, remove: true });
|
|
194
203
|
}
|
|
@@ -196,6 +205,8 @@ export const removeMark = action(
|
|
|
196
205
|
next = next.editGraphics({ target: id, remove: true });
|
|
197
206
|
}
|
|
198
207
|
next = next._withoutMaterializationConfig(["marks", id]);
|
|
208
|
+
next = next._withoutMaterializationConfig(["jitters", id]);
|
|
209
|
+
next = next._withoutMaterializationConfig(["labelLayouts", id]);
|
|
199
210
|
}
|
|
200
211
|
|
|
201
212
|
next = cleanupPositionGuides(next, positionScales);
|