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,58 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateNonEmptyString } from "../../core/validation.js";
|
|
3
|
+
import {
|
|
4
|
+
applyFacadeGuides,
|
|
5
|
+
normalizeAppearance,
|
|
6
|
+
normalizeEncoding,
|
|
7
|
+
normalizeGuides,
|
|
8
|
+
resolveFacadeData,
|
|
9
|
+
resolveFacadeId,
|
|
10
|
+
targetArgs,
|
|
11
|
+
validateFacadeOptions
|
|
12
|
+
} from "./shared.js";
|
|
13
|
+
|
|
14
|
+
const OPTIONS = Object.freeze([
|
|
15
|
+
"id", "data", "coordinate", "field", "maxBins", "binStep",
|
|
16
|
+
"binBoundaries", "stack", "xScale", "yScale", "color", "bar", "guides"
|
|
17
|
+
]);
|
|
18
|
+
const BAR_OPTIONS = Object.freeze([
|
|
19
|
+
"fill", "opacity", "stroke", "strokeWidth"
|
|
20
|
+
]);
|
|
21
|
+
const HISTOGRAM_OPTIONS = Object.freeze([
|
|
22
|
+
"maxBins", "binStep", "binBoundaries", "stack", "xScale", "yScale"
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
export const createHistogram = action(
|
|
26
|
+
{
|
|
27
|
+
op: "createHistogram",
|
|
28
|
+
description: "Create a binned count histogram from existing chart data."
|
|
29
|
+
},
|
|
30
|
+
function (args = {}) {
|
|
31
|
+
validateFacadeOptions(args, OPTIONS, "createHistogram");
|
|
32
|
+
const id = resolveFacadeId(this, args.id, {
|
|
33
|
+
defaultId: "histogram",
|
|
34
|
+
operation: "createHistogram"
|
|
35
|
+
});
|
|
36
|
+
const data = resolveFacadeData(this, args.data, "createHistogram");
|
|
37
|
+
const field = validateNonEmptyString(args.field, "createHistogram field");
|
|
38
|
+
const bar = normalizeAppearance(args.bar, BAR_OPTIONS, "createHistogram bar");
|
|
39
|
+
const color = normalizeEncoding(args.color, "createHistogram color");
|
|
40
|
+
const guides = normalizeGuides(args.guides, "createHistogram");
|
|
41
|
+
const histogram = Object.fromEntries(
|
|
42
|
+
HISTOGRAM_OPTIONS
|
|
43
|
+
.filter(key => Object.hasOwn(args, key))
|
|
44
|
+
.map(key => [key, args[key]])
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
let next = this
|
|
48
|
+
.createBarMark({ id, data, ...bar })
|
|
49
|
+
.encodeHistogram({
|
|
50
|
+
field,
|
|
51
|
+
target: id,
|
|
52
|
+
...(args.coordinate === undefined ? {} : { coordinate: args.coordinate }),
|
|
53
|
+
...histogram
|
|
54
|
+
});
|
|
55
|
+
if (color !== undefined) next = next.encodeColor(targetArgs(color, id));
|
|
56
|
+
return applyFacadeGuides(next, guides);
|
|
57
|
+
}
|
|
58
|
+
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createBarPlot } from "./bar.js";
|
|
2
|
+
import { createHeatmap } from "./heatmap.js";
|
|
3
|
+
import { createHistogram } from "./histogram.js";
|
|
4
|
+
import { createLinePlot } from "./line.js";
|
|
5
|
+
import { createScatterPlot } from "./scatter.js";
|
|
6
|
+
import { createParallelCoordinates } from "./parallel.js";
|
|
7
|
+
|
|
8
|
+
export function registerChartActions(ProgramClass) {
|
|
9
|
+
ProgramClass.prototype.createBarPlot = createBarPlot;
|
|
10
|
+
ProgramClass.prototype.createHeatmap = createHeatmap;
|
|
11
|
+
ProgramClass.prototype.createHistogram = createHistogram;
|
|
12
|
+
ProgramClass.prototype.createScatterPlot = createScatterPlot;
|
|
13
|
+
ProgramClass.prototype.createLinePlot = createLinePlot;
|
|
14
|
+
ProgramClass.prototype.createParallelCoordinates = createParallelCoordinates;
|
|
15
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateNonEmptyString } from "../../core/validation.js";
|
|
3
|
+
import {
|
|
4
|
+
applyFacadeGuides,
|
|
5
|
+
normalizeAppearance,
|
|
6
|
+
normalizeEncoding,
|
|
7
|
+
normalizeFieldEncoding,
|
|
8
|
+
normalizeGuides,
|
|
9
|
+
normalizeStrokeDashEncoding,
|
|
10
|
+
positionArgs,
|
|
11
|
+
resolveFacadeData,
|
|
12
|
+
resolveFacadeId,
|
|
13
|
+
targetArgs,
|
|
14
|
+
validateFacadeOptions
|
|
15
|
+
} from "./shared.js";
|
|
16
|
+
|
|
17
|
+
const OPTIONS = Object.freeze([
|
|
18
|
+
"id", "data", "coordinate", "x", "y", "color", "groupBy",
|
|
19
|
+
"strokeDash", "line", "guides"
|
|
20
|
+
]);
|
|
21
|
+
const LINE_OPTIONS = Object.freeze([
|
|
22
|
+
"strokeWidth", "curve", "stroke", "opacity", "closed"
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
export const createLinePlot = action(
|
|
26
|
+
{
|
|
27
|
+
op: "createLinePlot",
|
|
28
|
+
description: "Create a Cartesian line plot from existing chart data."
|
|
29
|
+
},
|
|
30
|
+
function (args = {}) {
|
|
31
|
+
validateFacadeOptions(args, OPTIONS, "createLinePlot");
|
|
32
|
+
const id = resolveFacadeId(this, args.id, {
|
|
33
|
+
defaultId: "linePlot",
|
|
34
|
+
operation: "createLinePlot"
|
|
35
|
+
});
|
|
36
|
+
const data = resolveFacadeData(this, args.data, "createLinePlot");
|
|
37
|
+
const line = normalizeAppearance(
|
|
38
|
+
args.line,
|
|
39
|
+
LINE_OPTIONS,
|
|
40
|
+
"createLinePlot line"
|
|
41
|
+
);
|
|
42
|
+
if (line.closed === true) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
"createLinePlot is Cartesian; closed lines require Polar line authoring."
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
const x = normalizeFieldEncoding(args.x, "createLinePlot x");
|
|
48
|
+
const y = normalizeFieldEncoding(args.y, "createLinePlot y");
|
|
49
|
+
const color = normalizeEncoding(args.color, "createLinePlot color");
|
|
50
|
+
const strokeDash = normalizeStrokeDashEncoding(args.strokeDash);
|
|
51
|
+
const groupBy = args.groupBy === undefined
|
|
52
|
+
? undefined
|
|
53
|
+
: validateNonEmptyString(args.groupBy, "createLinePlot groupBy");
|
|
54
|
+
const guides = normalizeGuides(args.guides, "createLinePlot");
|
|
55
|
+
|
|
56
|
+
let next = this
|
|
57
|
+
.createLineMark({ id, data, ...line })
|
|
58
|
+
.encodeX(positionArgs(x, { target: id, coordinate: args.coordinate }))
|
|
59
|
+
.encodeY(positionArgs(y, { target: id, coordinate: args.coordinate }));
|
|
60
|
+
if (color !== undefined) next = next.encodeColor(targetArgs(color, id));
|
|
61
|
+
if (groupBy !== undefined) {
|
|
62
|
+
next = next.encodeGroup({ field: groupBy, target: id });
|
|
63
|
+
}
|
|
64
|
+
if (strokeDash !== undefined) {
|
|
65
|
+
next = next.encodeStrokeDash(targetArgs(strokeDash, id));
|
|
66
|
+
}
|
|
67
|
+
return applyFacadeGuides(next, guides);
|
|
68
|
+
}
|
|
69
|
+
);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { resolveParallelCoordinate } from "../coordinates/parallel.js";
|
|
3
|
+
import {
|
|
4
|
+
applyFacadeGuides,
|
|
5
|
+
normalizeAppearance,
|
|
6
|
+
normalizeEncoding,
|
|
7
|
+
normalizeGuides,
|
|
8
|
+
normalizeStrokeDashEncoding,
|
|
9
|
+
resolveFacadeData,
|
|
10
|
+
resolveFacadeId,
|
|
11
|
+
targetArgs,
|
|
12
|
+
validateFacadeOptions
|
|
13
|
+
} from "./shared.js";
|
|
14
|
+
|
|
15
|
+
const OPTIONS = Object.freeze([
|
|
16
|
+
"id", "data", "coordinate", "dimensions", "key", "missing",
|
|
17
|
+
"color", "strokeDash", "line", "guides"
|
|
18
|
+
]);
|
|
19
|
+
const LINE_OPTIONS = Object.freeze([
|
|
20
|
+
"strokeWidth", "stroke", "opacity", "curve", "closed"
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export const createParallelCoordinates = action(
|
|
24
|
+
{
|
|
25
|
+
op: "createParallelCoordinates",
|
|
26
|
+
description: "Create a complete Parallel-coordinates chart."
|
|
27
|
+
},
|
|
28
|
+
function (args = {}) {
|
|
29
|
+
validateFacadeOptions(args, OPTIONS, "createParallelCoordinates");
|
|
30
|
+
const id = resolveFacadeId(this, args.id, {
|
|
31
|
+
defaultId: "parallelCoordinates",
|
|
32
|
+
operation: "createParallelCoordinates"
|
|
33
|
+
});
|
|
34
|
+
const data = resolveFacadeData(this, args.data, "createParallelCoordinates");
|
|
35
|
+
const coordinate = resolveParallelCoordinate(this, {
|
|
36
|
+
requested: args.coordinate,
|
|
37
|
+
operation: "createParallelCoordinates",
|
|
38
|
+
useCurrent: true
|
|
39
|
+
}).id;
|
|
40
|
+
const line = normalizeAppearance(
|
|
41
|
+
args.line,
|
|
42
|
+
LINE_OPTIONS,
|
|
43
|
+
"createParallelCoordinates line"
|
|
44
|
+
);
|
|
45
|
+
if (line.closed === true || (line.curve !== undefined && line.curve !== "linear")) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
"createParallelCoordinates requires a linear open line."
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const color = normalizeEncoding(
|
|
51
|
+
args.color,
|
|
52
|
+
"createParallelCoordinates color"
|
|
53
|
+
);
|
|
54
|
+
const strokeDash = normalizeStrokeDashEncoding(
|
|
55
|
+
args.strokeDash,
|
|
56
|
+
"createParallelCoordinates strokeDash"
|
|
57
|
+
);
|
|
58
|
+
const guides = normalizeGuides(args.guides, "createParallelCoordinates");
|
|
59
|
+
|
|
60
|
+
let next = this
|
|
61
|
+
.createCoordinate({ id: coordinate, type: "parallel" })
|
|
62
|
+
.createLineMark({ id, data, ...line })
|
|
63
|
+
.encodeParallelCoordinates({
|
|
64
|
+
target: id,
|
|
65
|
+
coordinate,
|
|
66
|
+
dimensions: args.dimensions,
|
|
67
|
+
...(args.key === undefined ? {} : { key: args.key }),
|
|
68
|
+
...(args.missing === undefined ? {} : { missing: args.missing })
|
|
69
|
+
});
|
|
70
|
+
if (color !== undefined) next = next.encodeColor(targetArgs(color, id));
|
|
71
|
+
if (strokeDash !== undefined) {
|
|
72
|
+
next = next.encodeStrokeDash(targetArgs(strokeDash, id));
|
|
73
|
+
}
|
|
74
|
+
return applyFacadeGuides(next, guides);
|
|
75
|
+
}
|
|
76
|
+
);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import {
|
|
3
|
+
applyFacadeGuides,
|
|
4
|
+
normalizeAppearance,
|
|
5
|
+
normalizeEncoding,
|
|
6
|
+
normalizeFieldEncoding,
|
|
7
|
+
normalizeGuides,
|
|
8
|
+
positionArgs,
|
|
9
|
+
resolveFacadeData,
|
|
10
|
+
resolveFacadeId,
|
|
11
|
+
targetArgs,
|
|
12
|
+
validateFacadeOptions
|
|
13
|
+
} from "./shared.js";
|
|
14
|
+
|
|
15
|
+
const OPTIONS = Object.freeze([
|
|
16
|
+
"id", "data", "coordinate", "x", "y", "color", "size", "shape",
|
|
17
|
+
"point", "guides"
|
|
18
|
+
]);
|
|
19
|
+
const POINT_OPTIONS = Object.freeze([
|
|
20
|
+
"shape", "fill", "opacity", "stroke", "strokeWidth"
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export const createScatterPlot = action(
|
|
24
|
+
{
|
|
25
|
+
op: "createScatterPlot",
|
|
26
|
+
description: "Create a Cartesian scatter plot from existing chart data."
|
|
27
|
+
},
|
|
28
|
+
function (args = {}) {
|
|
29
|
+
validateFacadeOptions(args, OPTIONS, "createScatterPlot");
|
|
30
|
+
const id = resolveFacadeId(this, args.id, {
|
|
31
|
+
defaultId: "scatterPlot",
|
|
32
|
+
operation: "createScatterPlot"
|
|
33
|
+
});
|
|
34
|
+
const data = resolveFacadeData(this, args.data, "createScatterPlot");
|
|
35
|
+
const point = normalizeAppearance(
|
|
36
|
+
args.point,
|
|
37
|
+
POINT_OPTIONS,
|
|
38
|
+
"createScatterPlot point"
|
|
39
|
+
);
|
|
40
|
+
const x = normalizeFieldEncoding(args.x, "createScatterPlot x");
|
|
41
|
+
const y = normalizeFieldEncoding(args.y, "createScatterPlot y");
|
|
42
|
+
const color = normalizeEncoding(args.color, "createScatterPlot color");
|
|
43
|
+
const size = normalizeEncoding(args.size, "createScatterPlot size");
|
|
44
|
+
const shape = normalizeEncoding(args.shape, "createScatterPlot shape");
|
|
45
|
+
const guides = normalizeGuides(args.guides, "createScatterPlot");
|
|
46
|
+
|
|
47
|
+
let next = this
|
|
48
|
+
.createPointMark({ id, data, ...point })
|
|
49
|
+
.encodeX(positionArgs(x, { target: id, coordinate: args.coordinate }))
|
|
50
|
+
.encodeY(positionArgs(y, { target: id, coordinate: args.coordinate }));
|
|
51
|
+
if (color !== undefined) next = next.encodeColor(targetArgs(color, id));
|
|
52
|
+
if (size !== undefined) next = next.encodeSize(targetArgs(size, id));
|
|
53
|
+
if (shape !== undefined) next = next.encodeShape(targetArgs(shape, id));
|
|
54
|
+
return applyFacadeGuides(next, guides);
|
|
55
|
+
}
|
|
56
|
+
);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { resolveOptionalUserId, validateUserId } from "../../core/identifiers.js";
|
|
2
|
+
import { isPlainObject } from "../../core/immutable.js";
|
|
3
|
+
import { validateOptionObject } from "../../core/validation.js";
|
|
4
|
+
import { findDataset } from "../../selectors/datasets.js";
|
|
5
|
+
import { hasLayer } from "../../selectors/layers.js";
|
|
6
|
+
|
|
7
|
+
export function validateFacadeOptions(args, supported, operation) {
|
|
8
|
+
return validateOptionObject(args, supported, operation);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveFacadeData(program, requested, operation) {
|
|
12
|
+
if (requested !== undefined) {
|
|
13
|
+
const id = validateUserId(requested, `${operation} dataset id`);
|
|
14
|
+
if (findDataset(program, id) === undefined) {
|
|
15
|
+
throw new Error(`Unknown dataset "${id}".`);
|
|
16
|
+
}
|
|
17
|
+
return id;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const current = program.context.currentData;
|
|
21
|
+
if (current !== undefined && findDataset(program, current) !== undefined) {
|
|
22
|
+
return current;
|
|
23
|
+
}
|
|
24
|
+
const datasets = program.semanticSpec.datasets;
|
|
25
|
+
if (datasets.length === 1) return datasets[0].id;
|
|
26
|
+
if (datasets.length > 1) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`${operation} requires data when multiple datasets are available.`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`${operation} requires data or one inferable dataset.`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveFacadeId(program, requested, {
|
|
35
|
+
defaultId,
|
|
36
|
+
operation
|
|
37
|
+
}) {
|
|
38
|
+
return resolveOptionalUserId(requested, {
|
|
39
|
+
defaultId,
|
|
40
|
+
label: `${operation} id`,
|
|
41
|
+
operation,
|
|
42
|
+
ambiguous: hasLayer(program, defaultId) ||
|
|
43
|
+
program.graphicSpec.objects[defaultId] !== undefined
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function normalizeFieldEncoding(value, label) {
|
|
48
|
+
if (typeof value === "string") return { field: value };
|
|
49
|
+
if (!isPlainObject(value)) {
|
|
50
|
+
throw new TypeError(`${label} must be a field string or a plain object.`);
|
|
51
|
+
}
|
|
52
|
+
if (Object.hasOwn(value, "target") || Object.hasOwn(value, "coordinate")) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`${label} target and coordinate are owned by the chart facade.`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return { ...value };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function normalizeEncoding(value, label) {
|
|
61
|
+
if (value === undefined) return undefined;
|
|
62
|
+
return normalizeFieldEncoding(value, label);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function normalizeStrokeDashEncoding(
|
|
66
|
+
value,
|
|
67
|
+
label = "createLinePlot strokeDash"
|
|
68
|
+
) {
|
|
69
|
+
if (value === undefined) return undefined;
|
|
70
|
+
if (!isPlainObject(value)) {
|
|
71
|
+
throw new TypeError(
|
|
72
|
+
`${label} must be an encodeStrokeDash option object.`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (Object.hasOwn(value, "target")) {
|
|
76
|
+
throw new Error(`${label} target is owned by the chart facade.`);
|
|
77
|
+
}
|
|
78
|
+
return { ...value };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function normalizeAppearance(value, supported, label) {
|
|
82
|
+
if (value === undefined) return {};
|
|
83
|
+
validateOptionObject(value, supported, label);
|
|
84
|
+
return { ...value };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function normalizeTargetOptions(value, label) {
|
|
88
|
+
if (value === undefined) return undefined;
|
|
89
|
+
if (!isPlainObject(value)) {
|
|
90
|
+
throw new TypeError(`${label} must be a plain object.`);
|
|
91
|
+
}
|
|
92
|
+
if (Object.hasOwn(value, "target")) {
|
|
93
|
+
throw new Error(`${label} target is owned by the chart facade.`);
|
|
94
|
+
}
|
|
95
|
+
return { ...value };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function normalizeGuides(value, operation) {
|
|
99
|
+
if (value === false) return false;
|
|
100
|
+
if (value === undefined) return {};
|
|
101
|
+
if (!isPlainObject(value)) {
|
|
102
|
+
throw new TypeError(`${operation} guides must be false or a plain object.`);
|
|
103
|
+
}
|
|
104
|
+
return { ...value };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function positionArgs(encoding, { target, coordinate }) {
|
|
108
|
+
return {
|
|
109
|
+
...encoding,
|
|
110
|
+
target,
|
|
111
|
+
...(coordinate === undefined ? {} : { coordinate })
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function targetArgs(encoding, target) {
|
|
116
|
+
return { ...encoding, target };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function applyFacadeGuides(program, guides) {
|
|
120
|
+
return guides === false ? program : program.createGuides(guides);
|
|
121
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
2
|
+
import { findCoordinate } from "../../selectors/coordinates.js";
|
|
3
|
+
|
|
4
|
+
export function resolveParallelCoordinate(program, {
|
|
5
|
+
requested,
|
|
6
|
+
layer,
|
|
7
|
+
operation,
|
|
8
|
+
useCurrent = false
|
|
9
|
+
}) {
|
|
10
|
+
const explicit = requested === undefined
|
|
11
|
+
? undefined
|
|
12
|
+
: validateUserId(requested, "Parallel coordinate id");
|
|
13
|
+
if (layer?.coordinate !== undefined) {
|
|
14
|
+
if (explicit !== undefined && explicit !== layer.coordinate) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Layer "${layer.id}" already uses coordinate "${layer.coordinate}".`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const coordinate = findCoordinate(program, layer.coordinate);
|
|
20
|
+
if (coordinate?.type !== "parallel") {
|
|
21
|
+
throw new Error(`Coordinate "${layer.coordinate}" is not Parallel.`);
|
|
22
|
+
}
|
|
23
|
+
return { id: layer.coordinate, create: false };
|
|
24
|
+
}
|
|
25
|
+
if (explicit !== undefined) {
|
|
26
|
+
const coordinate = findCoordinate(program, explicit);
|
|
27
|
+
if (coordinate !== undefined && coordinate.type !== "parallel") {
|
|
28
|
+
throw new Error(`Coordinate "${explicit}" is not Parallel.`);
|
|
29
|
+
}
|
|
30
|
+
return { id: explicit, create: coordinate === undefined };
|
|
31
|
+
}
|
|
32
|
+
const current = useCurrent ? program.context.currentCoordinate : undefined;
|
|
33
|
+
if (current !== undefined && findCoordinate(program, current)?.type === "parallel") {
|
|
34
|
+
return { id: current, create: false };
|
|
35
|
+
}
|
|
36
|
+
const compatible = program.semanticSpec.coordinates.filter(
|
|
37
|
+
coordinate => coordinate.type === "parallel"
|
|
38
|
+
);
|
|
39
|
+
if (compatible.length > 1) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`${operation} requires coordinate when multiple Parallel coordinates are available.`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (compatible.length === 1) return { id: compatible[0].id, create: false };
|
|
45
|
+
const conflict = findCoordinate(program, "parallel");
|
|
46
|
+
if (conflict !== undefined) {
|
|
47
|
+
throw new Error('Coordinate "parallel" already exists with a different type.');
|
|
48
|
+
}
|
|
49
|
+
return { id: "parallel", create: true };
|
|
50
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
3
|
+
import { validateKeys } from "../../core/validation.js";
|
|
4
|
+
import {
|
|
5
|
+
deriveBin2DRows,
|
|
6
|
+
normalizeBin2DTransform
|
|
7
|
+
} from "../../grammar/bin2d.js";
|
|
8
|
+
import { applyLayerDataRematerialization } from
|
|
9
|
+
"../../materialization/dependencies.js";
|
|
10
|
+
import { planDerivedDataRevision } from
|
|
11
|
+
"../../materialization/dataProvenance.js";
|
|
12
|
+
import {
|
|
13
|
+
findDataset,
|
|
14
|
+
findDatasetConsumer
|
|
15
|
+
} from "../../selectors/datasets.js";
|
|
16
|
+
import { MATERIALIZE_OPTIONS, requireDerivedDataset } from "./shared.js";
|
|
17
|
+
|
|
18
|
+
const OPTIONS = Object.freeze([
|
|
19
|
+
"id", "source", "x", "y", "bins", "extent", "includeEmpty", "members", "as"
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
function ownerConfig(program, id) {
|
|
23
|
+
return program.materializationConfigs.data?.bin2d?.[id];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function requireCurrentBin2D(program, id, config) {
|
|
27
|
+
const current = findDataset(program, config.current);
|
|
28
|
+
if (
|
|
29
|
+
current?.source === undefined ||
|
|
30
|
+
current.transform?.length !== 1 ||
|
|
31
|
+
current.transform[0].type !== "bin2d"
|
|
32
|
+
) {
|
|
33
|
+
throw new Error(`2D bin owner "${id}" has no current derived dataset.`);
|
|
34
|
+
}
|
|
35
|
+
return current;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function directLayerConsumers(program, data) {
|
|
39
|
+
return program.semanticSpec.layers
|
|
40
|
+
.filter(layer => layer.data === data)
|
|
41
|
+
.map(layer => layer.id);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function rejectDerivedConsumers(program, data) {
|
|
45
|
+
const dependent = findDatasetConsumer(program, data);
|
|
46
|
+
if (dependent !== undefined) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Cannot replace 2D bin dataset "${data}" while derived dataset ` +
|
|
49
|
+
`"${dependent.id}" depends on it.`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function preflight(program, sourceId, transform) {
|
|
55
|
+
const source = findDataset(program, sourceId);
|
|
56
|
+
if (source?.values === undefined) {
|
|
57
|
+
throw new Error(`Source dataset "${sourceId}" has no values.`);
|
|
58
|
+
}
|
|
59
|
+
deriveBin2DRows(source.values, transform);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const materializeBin2DData = action(
|
|
63
|
+
{
|
|
64
|
+
op: "materializeBin2DData",
|
|
65
|
+
description: "Materialize one immutable rectangular 2D-bin dataset."
|
|
66
|
+
},
|
|
67
|
+
function (args = {}) {
|
|
68
|
+
validateKeys(args, MATERIALIZE_OPTIONS, "materializeBin2DData");
|
|
69
|
+
const { id, source, transform } = requireDerivedDataset(
|
|
70
|
+
this,
|
|
71
|
+
args.id,
|
|
72
|
+
"bin2d"
|
|
73
|
+
);
|
|
74
|
+
const result = deriveBin2DRows(source.values, transform);
|
|
75
|
+
return this
|
|
76
|
+
.editSemantic({
|
|
77
|
+
property: `dataset[${id}].transform`,
|
|
78
|
+
value: [{ ...transform, resolved: result.resolved }]
|
|
79
|
+
})
|
|
80
|
+
.editSemantic({
|
|
81
|
+
property: `dataset[${id}].values`,
|
|
82
|
+
value: result.values
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
export const createBin2DData = action(
|
|
88
|
+
{
|
|
89
|
+
op: "createBin2DData",
|
|
90
|
+
description: "Create or revise immutable rectangular 2D-bin values."
|
|
91
|
+
},
|
|
92
|
+
function (args = {}) {
|
|
93
|
+
validateKeys(args, OPTIONS, "createBin2DData");
|
|
94
|
+
const owner = validateUserId(args.id, "2D bin dataset id");
|
|
95
|
+
const config = ownerConfig(this, owner);
|
|
96
|
+
const previous = config === undefined
|
|
97
|
+
? undefined
|
|
98
|
+
: requireCurrentBin2D(this, owner, config);
|
|
99
|
+
const source = validateUserId(
|
|
100
|
+
args.source ?? previous?.source ?? this.context.currentData,
|
|
101
|
+
"Source dataset id"
|
|
102
|
+
);
|
|
103
|
+
const transform = normalizeBin2DTransform({ ...args, id: owner });
|
|
104
|
+
preflight(this, source, transform);
|
|
105
|
+
|
|
106
|
+
if (previous === undefined) {
|
|
107
|
+
return this
|
|
108
|
+
.createDerivedData({ id: owner, source, transform: [transform] })
|
|
109
|
+
.materializeBin2DData({ id: owner })
|
|
110
|
+
._withMaterializationConfig(
|
|
111
|
+
["data", "bin2d", owner],
|
|
112
|
+
{ current: owner }
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
rejectDerivedConsumers(this, previous.id);
|
|
117
|
+
const consumers = directLayerConsumers(this, previous.id);
|
|
118
|
+
const revision = planDerivedDataRevision(this, {
|
|
119
|
+
owner,
|
|
120
|
+
role: "Bin2DData",
|
|
121
|
+
previous: previous.id,
|
|
122
|
+
consumers
|
|
123
|
+
});
|
|
124
|
+
let next = this
|
|
125
|
+
.createDerivedData({ id: revision.id, source, transform: [transform] })
|
|
126
|
+
.materializeBin2DData({ id: revision.id });
|
|
127
|
+
for (const rebind of revision.rebinds) {
|
|
128
|
+
next = next.rebindLayerData(rebind);
|
|
129
|
+
next = applyLayerDataRematerialization(next, rebind.id);
|
|
130
|
+
}
|
|
131
|
+
next = next.releaseDerivedData(revision.release);
|
|
132
|
+
return next._withMaterializationConfig(
|
|
133
|
+
["data", "bin2d", owner],
|
|
134
|
+
{ current: revision.id }
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
);
|
|
@@ -8,6 +8,23 @@ const OPTIONS = Object.freeze([
|
|
|
8
8
|
"id", "source", "field", "groupBy", "bandwidth", "extent", "steps",
|
|
9
9
|
"kernel", "normalization", "as"
|
|
10
10
|
]);
|
|
11
|
+
const CATEGORICAL_OPTIONS = Object.freeze([...OPTIONS, "placement"]);
|
|
12
|
+
|
|
13
|
+
function densityTransform(args, placement) {
|
|
14
|
+
return {
|
|
15
|
+
type: "density",
|
|
16
|
+
field: args.field,
|
|
17
|
+
...(args.groupBy === undefined ? {} : { groupBy: args.groupBy }),
|
|
18
|
+
bandwidth: args.bandwidth ?? "auto",
|
|
19
|
+
extent: args.extent ?? "auto",
|
|
20
|
+
steps: args.steps ?? 100,
|
|
21
|
+
kernel: args.kernel ?? "gaussian",
|
|
22
|
+
normalization: args.normalization ?? "unit",
|
|
23
|
+
as: args.as ?? [`${args.field}_value`, `${args.field}_density`],
|
|
24
|
+
resolve: "shared",
|
|
25
|
+
...(placement === undefined ? {} : { placement })
|
|
26
|
+
};
|
|
27
|
+
}
|
|
11
28
|
|
|
12
29
|
export const materializeDensityData = action(
|
|
13
30
|
{ op: "materializeDensityData", description: "Materialize one grouped kernel-density dataset." },
|
|
@@ -26,7 +43,10 @@ export const materializeDensityData = action(
|
|
|
26
43
|
...transform,
|
|
27
44
|
resolved: {
|
|
28
45
|
bandwidth: result.bandwidth,
|
|
29
|
-
extent: result.extent
|
|
46
|
+
extent: result.extent,
|
|
47
|
+
...(result.splitDomain === undefined
|
|
48
|
+
? {}
|
|
49
|
+
: { splitDomain: result.splitDomain })
|
|
30
50
|
}
|
|
31
51
|
}]
|
|
32
52
|
})
|
|
@@ -49,20 +69,41 @@ export const createDensityData = action(
|
|
|
49
69
|
if (typeof args.field !== "string" || args.field.length === 0) {
|
|
50
70
|
throw new TypeError("createDensityData requires a non-empty field string.");
|
|
51
71
|
}
|
|
52
|
-
const transform =
|
|
53
|
-
type: "density",
|
|
54
|
-
field: args.field,
|
|
55
|
-
...(args.groupBy === undefined ? {} : { groupBy: args.groupBy }),
|
|
56
|
-
bandwidth: args.bandwidth ?? "auto",
|
|
57
|
-
extent: args.extent ?? "auto",
|
|
58
|
-
steps: args.steps ?? 100,
|
|
59
|
-
kernel: args.kernel ?? "gaussian",
|
|
60
|
-
normalization: args.normalization ?? "unit",
|
|
61
|
-
as: args.as ?? [`${args.field}_value`, `${args.field}_density`],
|
|
62
|
-
resolve: "shared"
|
|
63
|
-
};
|
|
72
|
+
const transform = densityTransform(args);
|
|
64
73
|
return this
|
|
65
74
|
.createDerivedData({ id, source, transform: [transform] })
|
|
66
75
|
.materializeDensityData({ id });
|
|
67
76
|
}
|
|
68
77
|
);
|
|
78
|
+
|
|
79
|
+
export const createCategoricalDensityData = action(
|
|
80
|
+
{
|
|
81
|
+
op: "createCategoricalDensityData",
|
|
82
|
+
description: "Create category-placed kernel-density values."
|
|
83
|
+
},
|
|
84
|
+
function (args = {}) {
|
|
85
|
+
validateKeys(args, CATEGORICAL_OPTIONS, "createCategoricalDensityData");
|
|
86
|
+
const id = validateUserId(args.id, "Density dataset id");
|
|
87
|
+
const source = validateUserId(
|
|
88
|
+
args.source ?? this.context.currentData,
|
|
89
|
+
"Source dataset id"
|
|
90
|
+
);
|
|
91
|
+
if (typeof args.field !== "string" || args.field.length === 0) {
|
|
92
|
+
throw new TypeError(
|
|
93
|
+
"createCategoricalDensityData requires a non-empty field string."
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (args.placement?.type !== "category") {
|
|
97
|
+
throw new Error(
|
|
98
|
+
"createCategoricalDensityData requires normalized category placement."
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return this
|
|
102
|
+
.createDerivedData({
|
|
103
|
+
id,
|
|
104
|
+
source,
|
|
105
|
+
transform: [densityTransform(args, args.placement)]
|
|
106
|
+
})
|
|
107
|
+
.materializeDensityData({ id });
|
|
108
|
+
}
|
|
109
|
+
);
|