ggaction 0.0.17 → 0.0.18
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 +46 -0
- package/README.md +2 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +3 -3
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +3 -2
- package/src/actions/boxPlots/create.js +14 -4
- package/src/actions/boxPlots/edit.js +39 -33
- package/src/actions/boxPlots/materialize.js +7 -3
- package/src/actions/boxPlots/options.js +37 -11
- package/src/actions/canvas/actions.js +70 -95
- package/src/actions/canvas/fitting.js +16 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +28 -0
- package/src/actions/charts/area.js +2 -2
- package/src/actions/charts/bar.js +34 -7
- package/src/actions/charts/ecdf.js +4 -18
- package/src/actions/charts/endpoints.js +4 -18
- package/src/actions/charts/heatmap.js +4 -4
- package/src/actions/charts/index.js +2 -0
- package/src/actions/charts/interval-regression.js +2 -2
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/pie.js +3 -47
- package/src/actions/charts/radial.js +34 -12
- package/src/actions/charts/scatter.js +16 -2
- package/src/actions/charts/shared.js +2 -2
- package/src/actions/charts/text.js +35 -0
- package/src/actions/composition/actions.js +6 -2
- package/src/actions/data/box.js +2 -1
- package/src/actions/data/edit.js +1 -1
- package/src/actions/data/regression.js +2 -1
- package/src/actions/data/shared.js +3 -2
- package/src/actions/data/stack.js +3 -1
- package/src/actions/encodings/appearance.js +37 -29
- package/src/actions/encodings/barWidth.js +36 -11
- package/src/actions/encodings/channels.js +0 -1
- package/src/actions/encodings/color/continuous.js +1 -1
- package/src/actions/encodings/color/index.js +1 -1
- package/src/actions/encodings/color/policy.js +10 -3
- package/src/actions/encodings/density/resolve.js +1 -6
- package/src/actions/encodings/position/policies/arc.js +12 -10
- package/src/actions/encodings/position/policies/area.js +3 -3
- package/src/actions/encodings/position/policies/bar.js +15 -6
- package/src/actions/encodings/position/policies/line.js +17 -6
- package/src/actions/encodings/position/resolve.js +3 -2
- package/src/actions/encodings/ranged.js +17 -0
- package/src/actions/encodings/remove.js +3 -2
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/text.js +6 -3
- package/src/actions/errorBands/create.js +2 -2
- package/src/actions/errorBands/edit.js +2 -1
- package/src/actions/facets/actions.js +158 -240
- package/src/actions/gradientPlots/resolve.js +2 -17
- package/src/actions/guides/applicability.js +1 -1
- package/src/actions/guides/axes/axis.js +16 -4
- package/src/actions/guides/axes/edit.js +4 -4
- package/src/actions/guides/axes/labels.js +120 -46
- package/src/actions/guides/axes/lines.js +6 -5
- package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
- package/src/actions/guides/axes/parallel/policy.js +8 -7
- package/src/actions/guides/axes/remove.js +3 -2
- package/src/actions/guides/axes/tickGroups.js +5 -2
- package/src/actions/guides/axes/ticks.js +6 -3
- package/src/actions/guides/axes/titles.js +71 -26
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facadeLegend.js +10 -2
- package/src/actions/guides/grids/grid.js +7 -1
- package/src/actions/guides/grids/resolve.js +59 -12
- package/src/actions/guides/legends/blocks.js +3 -1
- package/src/actions/guides/legends/categorical/actions.js +70 -17
- package/src/actions/guides/legends/categorical/components.js +12 -8
- package/src/actions/guides/legends/categorical/index.js +6 -0
- package/src/actions/guides/legends/categorical/layout.js +37 -8
- package/src/actions/guides/legends/categorical/options.js +31 -20
- package/src/actions/guides/legends/categorical/resolve.js +2 -2
- package/src/actions/guides/legends/categorical/symbols.js +18 -12
- package/src/actions/guides/legends/categorical/validation.js +1 -6
- package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
- package/src/actions/guides/legends/continuous/common.js +1 -447
- package/src/actions/guides/legends/continuous/gradient.js +47 -34
- package/src/actions/guides/legends/continuous/interval.js +4 -4
- package/src/actions/guides/legends/continuous/opacity.js +19 -12
- package/src/actions/guides/legends/edit.js +1 -604
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/group.js +1 -0
- package/src/actions/guides/legends/lane.js +1 -516
- package/src/actions/guides/legends/remove.js +3 -0
- package/src/actions/guides/legends/sampling.js +1 -1
- package/src/actions/guides/legends/size.js +51 -43
- package/src/actions/guides/legends/strokeWidth.js +11 -29
- package/src/actions/guides/legends/target.js +3 -3
- package/src/actions/guides/legends/transition.js +21 -7
- package/src/actions/guides/polar/axes/labels.js +4 -2
- package/src/actions/guides/polar/axes/titles.js +7 -5
- package/src/actions/guides/polar/resolve.js +6 -14
- package/src/actions/index.js +16 -16
- package/src/actions/marks/arc/actions.js +3 -10
- package/src/actions/marks/area/actions.js +8 -5
- package/src/actions/marks/area/materialize.js +8 -9
- package/src/actions/marks/bar/create.js +10 -4
- package/src/actions/marks/bar/edit.js +8 -4
- package/src/actions/marks/bar/materialize.js +9 -6
- package/src/actions/marks/line/actions.js +11 -4
- package/src/actions/marks/line/materialize.js +9 -5
- package/src/actions/marks/point/edit.js +1 -1
- package/src/actions/marks/rect/actions.js +3 -3
- package/src/actions/marks/remove.js +2 -0
- package/src/actions/marks/rule/actions.js +7 -4
- package/src/actions/marks/text/actions.js +17 -5
- package/src/actions/marks/text/layout.js +3 -0
- package/src/actions/primitives/semanticValidation/layer.js +4 -0
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +3 -3
- package/src/actions/regression/resolve.js +2 -6
- package/src/actions/scales/channels.js +9 -4
- package/src/actions/scales/consumers/families.js +16 -5
- package/src/actions/scales/create.js +8 -3
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +2 -2
- package/src/actions/scales/editPolicy.js +9 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/preview.js +21 -1
- package/src/actions/scales/quantitativeColor.js +3 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +81 -27
- package/src/actions/theme/state.js +12 -0
- package/src/actions/titles/actions.js +5 -18
- package/src/actions/titles/resolve.js +41 -19
- package/src/actions/violinPlots/edit.js +3 -20
- package/src/core/compositionState.js +4 -1
- package/src/core/font.js +9 -2
- package/src/core/optionRequirements.js +1 -0
- package/src/core/resourceReferences.js +3 -0
- package/src/core/textMetricProfile.js +3 -2
- package/src/core/textMetrics.js +41 -4
- package/src/core/validation.js +26 -0
- package/src/core/vocabulary.js +4 -0
- package/src/grammar/aggregate.js +4 -18
- package/src/grammar/arcs.js +1 -386
- package/src/grammar/areaPaint.js +10 -0
- package/src/grammar/areaSeries.js +18 -10
- package/src/grammar/bars/geometry.js +30 -3
- package/src/grammar/bars/policy.js +12 -1
- package/src/grammar/bin.js +2 -12
- package/src/grammar/bin2d.js +1 -6
- package/src/grammar/boxPlot.js +56 -9
- package/src/grammar/categoryOrder.js +1 -6
- package/src/grammar/complete.js +15 -19
- package/src/grammar/computed.js +92 -25
- package/src/grammar/curveCommands.js +29 -16
- package/src/grammar/datasetSchema.js +3 -7
- package/src/grammar/displayLabels.js +28 -0
- package/src/grammar/ecdf.js +2 -9
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +5 -0
- package/src/grammar/facets/index.js +2 -5
- package/src/grammar/fold.js +2 -12
- package/src/grammar/gradientProfile.js +1 -6
- package/src/grammar/impute.js +21 -48
- package/src/grammar/interval.js +2 -9
- package/src/grammar/legendGradient.js +18 -0
- package/src/grammar/legendGroups.js +1 -0
- package/src/grammar/legendTitleStyle.js +22 -0
- package/src/grammar/lineSeries.js +32 -17
- package/src/grammar/markLabels.js +5 -2
- package/src/grammar/normalize.js +4 -54
- package/src/grammar/numeric.js +1 -290
- package/src/grammar/parallelCoordinates.js +1 -6
- package/src/grammar/parseTime.js +41 -0
- package/src/grammar/pathCommands.js +11 -4
- package/src/grammar/polar.js +1 -184
- package/src/grammar/positionCompatibility.js +3 -3
- package/src/grammar/regression/derive.js +7 -1
- package/src/grammar/regression/models.js +83 -55
- package/src/grammar/regression/parameters.js +14 -3
- package/src/grammar/roundedRect.js +42 -54
- package/src/grammar/scales/appearance.js +1 -206
- package/src/grammar/scales/color.js +1 -382
- package/src/grammar/scales/colorConsumers.js +5 -2
- package/src/grammar/scales/definition.js +1 -197
- package/src/grammar/scales/discretized.js +1 -195
- package/src/grammar/scales/size.js +1 -456
- package/src/grammar/scales/temporal.js +2 -13
- package/src/grammar/scales/transformed.js +1 -383
- package/src/grammar/scales/types.js +1 -1
- package/src/grammar/scales/validation.js +1 -66
- package/src/grammar/schemas/concreteGraphic.js +11 -0
- package/src/grammar/schemas/graphic.js +3 -0
- package/src/grammar/schemas/semanticPath.js +2 -1
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +3 -26
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +16 -7
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transforms.js +4 -1
- package/src/grammar/valueFormat.js +17 -5
- package/src/grammar/weightedStatistics.js +1 -6
- package/src/grammar/window.js +4 -58
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -110
- package/src/layout/facets.js +1 -244
- package/src/layout/labels.js +1 -0
- package/src/layout/legend.js +13 -0
- package/src/layout/legendGroup.js +1 -0
- package/src/layout/legendItems.js +59 -15
- package/src/layout/legendLane.js +1 -441
- package/src/layout/legendText.js +1 -0
- package/src/layout/title.js +35 -13
- package/src/materialization/bars/aggregate.js +11 -5
- package/src/materialization/bars/histogram.js +4 -1
- package/src/materialization/bars/ranged.js +19 -6
- package/src/materialization/bars/resolve.js +22 -1
- package/src/materialization/coordinateBounds.js +8 -0
- package/src/materialization/dependencies.js +4 -44
- package/src/materialization/facetGuides/layout.js +1 -0
- package/src/materialization/facetGuides/placement.js +19 -8
- package/src/materialization/facetGuides/preparation.js +15 -7
- package/src/materialization/facetHeaders.js +1 -347
- package/src/materialization/facets.js +1 -185
- package/src/materialization/graphicHierarchy.js +1 -1
- package/src/materialization/guides/layout.js +1 -57
- package/src/materialization/guides/resources.js +1 -123
- package/src/materialization/layout.js +53 -0
- package/src/materialization/legendGroups.js +1 -0
- package/src/materialization/legendText.js +1 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/capabilities.js +5 -6
- package/src/materialization/rect.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +1 -1
- package/src/materialization/scales/map.js +3 -0
- package/src/materialization/scales/policies/arc.js +8 -7
- package/src/materialization/scales/policies/bar.js +19 -7
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +5 -3
- package/src/materialization/selection/items/arc.js +1 -1
- package/src/materialization/selection/items/bar.js +7 -6
- package/src/materialization/selection/items/path.js +7 -6
- package/src/materialization/selection/items/rect.js +2 -2
- package/src/materialization/text.js +13 -4
- package/src/materialization/textLines.js +15 -0
- package/src/persistence/codec.js +37 -6
- package/src/persistence/validation.js +47 -0
- package/src/persistence.js +18 -5
- package/src/renderers/canvas/circle.js +6 -3
- package/src/renderers/canvas/fill.js +7 -1
- package/src/renderers/canvas/index.js +2 -1
- package/src/renderers/canvas/line.js +1 -0
- package/src/renderers/canvas/path.js +1 -1
- package/src/renderers/canvas/rect.js +11 -8
- package/src/renderers/canvas/stroke.js +2 -1
- package/src/renderers/canvas/text.js +5 -3
- package/src/renderers/svg.js +4 -1
- package/src/selectors/layers.js +19 -0
- package/src/theme/defaults.js +8 -2
- package/src/version.js +1 -1
- package/types/basic.d.ts +1 -0
- package/types/index.d.ts +7 -0
- package/types/program.d.ts +347 -85
|
@@ -15,12 +15,13 @@ import {
|
|
|
15
15
|
requestedStrokeDetails,
|
|
16
16
|
STROKE_STYLE_PROPERTIES
|
|
17
17
|
} from "../../grammar/strokeStyle.js";
|
|
18
|
+
import { ERROR_BAR_APPEARANCE_OPTIONS, resolveErrorBarAppearance } from "../errorBars/edit.js";
|
|
18
19
|
import { DEFAULT_COLORS } from "../../theme/defaults.js";
|
|
19
20
|
import { normalizeGuides } from "../charts/shared.js";
|
|
20
21
|
|
|
21
22
|
export const BOX_PLOT_OPTIONS = Object.freeze([
|
|
22
23
|
"id", "target", "data", "x", "y", "coordinate", "whisker",
|
|
23
|
-
"width", "outliers", "box", "median", "outlier", "guides"
|
|
24
|
+
"width", "summary", "outliers", "box", "median", "outlier", "guides"
|
|
24
25
|
]);
|
|
25
26
|
|
|
26
27
|
const DEFAULT_BOX = {
|
|
@@ -97,15 +98,20 @@ export function resolveBoxGuides(value) {
|
|
|
97
98
|
: normalizeGuides(value, "createBoxPlot");
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
export function resolveBoxWhisker(value, operation = "createBoxPlot") {
|
|
101
|
-
if (value === undefined) return Object.freeze({ type: "tukey", factor: 1.5 });
|
|
101
|
+
export function resolveBoxWhisker(value, operation = "createBoxPlot", defaultType = "tukey") {
|
|
102
|
+
if (value === undefined) return Object.freeze(defaultType === "tukey" ? { type: "tukey", factor: 1.5 } : { type: defaultType });
|
|
102
103
|
const options = plainOptions(
|
|
103
104
|
value,
|
|
104
|
-
["type", "factor"],
|
|
105
|
+
["type", "factor", ...ERROR_BAR_APPEARANCE_OPTIONS],
|
|
105
106
|
"whisker",
|
|
106
107
|
operation
|
|
107
108
|
);
|
|
108
|
-
const
|
|
109
|
+
const requestedAppearance = boxWhiskerAppearance(options);
|
|
110
|
+
const appearance = Object.keys(requestedAppearance).length === 0 ? {} : resolveErrorBarAppearance(requestedAppearance, {
|
|
111
|
+
defaults: { caps: true, capSize: 8, stroke: "#111111", strokeWidth: 1.5, strokeDash: "solid", opacity: 1 }, operation
|
|
112
|
+
});
|
|
113
|
+
const style = Object.fromEntries(Object.keys(requestedAppearance).map(key => [key, appearance[key]]));
|
|
114
|
+
const type = options.type ?? defaultType;
|
|
109
115
|
if (!["tukey", "minmax"].includes(type)) {
|
|
110
116
|
throw new Error(`Unsupported createBoxPlot whisker type "${type}".`);
|
|
111
117
|
}
|
|
@@ -113,7 +119,7 @@ export function resolveBoxWhisker(value, operation = "createBoxPlot") {
|
|
|
113
119
|
if (options.factor !== undefined) {
|
|
114
120
|
throw new Error("createBoxPlot minmax whiskers do not accept factor.");
|
|
115
121
|
}
|
|
116
|
-
return Object.freeze({ type });
|
|
122
|
+
return Object.freeze({ type, ...style });
|
|
117
123
|
}
|
|
118
124
|
const factor = options.factor ?? 1.5;
|
|
119
125
|
if (!Number.isFinite(factor) || factor <= 0) {
|
|
@@ -121,11 +127,15 @@ export function resolveBoxWhisker(value, operation = "createBoxPlot") {
|
|
|
121
127
|
"createBoxPlot whisker factor must be positive and finite."
|
|
122
128
|
);
|
|
123
129
|
}
|
|
124
|
-
return Object.freeze({ type, factor });
|
|
130
|
+
return Object.freeze({ type, factor, ...style });
|
|
125
131
|
}
|
|
126
132
|
|
|
127
133
|
export function resolveBoxWidth(value, operation = "createBoxPlot") {
|
|
128
|
-
const options = plainOptions(value, ["band"], "width", operation);
|
|
134
|
+
const options = plainOptions(value, ["band", "pixels"], "width", operation);
|
|
135
|
+
if (options.pixels !== undefined) {
|
|
136
|
+
if (options.band !== undefined) throw new Error(`${operation} width accepts band or pixels, not both.`);
|
|
137
|
+
return Object.freeze({ pixels: validatePositiveFinite(options.pixels, `${operation} width.pixels`) });
|
|
138
|
+
}
|
|
129
139
|
const band = options.band ?? 0.7;
|
|
130
140
|
if (!Number.isFinite(band) || band <= 0 || band >= 1) {
|
|
131
141
|
throw new RangeError(
|
|
@@ -135,6 +145,15 @@ export function resolveBoxWidth(value, operation = "createBoxPlot") {
|
|
|
135
145
|
return band;
|
|
136
146
|
}
|
|
137
147
|
|
|
148
|
+
export function boxBarWidth(width) {
|
|
149
|
+
return typeof width === "number" ? { band: width } : width;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function boxWhiskerAppearance(whisker) {
|
|
153
|
+
return Object.fromEntries(ERROR_BAR_APPEARANCE_OPTIONS.filter(key => Object.hasOwn(whisker, key))
|
|
154
|
+
.map(key => [key, whisker[key]]));
|
|
155
|
+
}
|
|
156
|
+
|
|
138
157
|
export function resolveBoxAppearance(value, operation = "createBoxPlot") {
|
|
139
158
|
return resolveAppearance(
|
|
140
159
|
value,
|
|
@@ -146,13 +165,20 @@ export function resolveBoxAppearance(value, operation = "createBoxPlot") {
|
|
|
146
165
|
}
|
|
147
166
|
|
|
148
167
|
export function resolveBoxMedianAppearance(value, operation = "createBoxPlot") {
|
|
149
|
-
|
|
150
|
-
|
|
168
|
+
const options = value === undefined ? {} : value;
|
|
169
|
+
if (!isPlainObject(options)) throw new TypeError(`${operation} median must be a plain object.`);
|
|
170
|
+
const { width, ...appearance } = options;
|
|
171
|
+
if (width !== undefined && width !== "auto") {
|
|
172
|
+
plainOptions(width, ["pixels"], "median.width", operation);
|
|
173
|
+
validatePositiveFinite(width.pixels, `${operation} median.width.pixels`);
|
|
174
|
+
}
|
|
175
|
+
return Object.freeze({ ...resolveAppearance(
|
|
176
|
+
appearance,
|
|
151
177
|
DEFAULT_MEDIAN,
|
|
152
178
|
"median",
|
|
153
179
|
operation,
|
|
154
180
|
requestedStrokeDetails
|
|
155
|
-
);
|
|
181
|
+
), ...(width === undefined || width === "auto" ? {} : { width: Object.freeze({ pixels: width.pixels }) }) });
|
|
156
182
|
}
|
|
157
183
|
|
|
158
184
|
export function resolveBoxOutlierAppearance(value, operation = "createBoxPlot") {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { action } from "../../core/action.js";
|
|
2
|
+
import { findLayer } from "../../selectors/layers.js";
|
|
2
3
|
import {
|
|
3
4
|
DEFAULT_CANVAS,
|
|
4
5
|
DEFAULT_MARGIN,
|
|
5
6
|
normalizeMargin,
|
|
7
|
+
normalizePlotBorder,
|
|
8
|
+
resolveCanvasPlot,
|
|
6
9
|
validateCanvasState
|
|
7
10
|
} from "../../layout/canvas.js";
|
|
8
11
|
import { cloneAndFreeze } from "../../core/immutable.js";
|
|
@@ -22,7 +25,9 @@ const CANVAS_OPTIONS = Object.freeze([
|
|
|
22
25
|
"width",
|
|
23
26
|
"height",
|
|
24
27
|
"background",
|
|
25
|
-
"margin"
|
|
28
|
+
"margin",
|
|
29
|
+
"plot",
|
|
30
|
+
"plotBorder"
|
|
26
31
|
]);
|
|
27
32
|
|
|
28
33
|
function validateOptions(args, operation, { allowEmpty = false } = {}) {
|
|
@@ -39,34 +44,60 @@ function requireCanvas(program) {
|
|
|
39
44
|
return canvas;
|
|
40
45
|
}
|
|
41
46
|
|
|
42
|
-
function resolveCanvasState(
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const baseMargin = canvasConfig?.margin ?? DEFAULT_MARGIN;
|
|
46
|
-
const baseSize = canvasConfig?.size ?? { width: "explicit", height: "explicit" };
|
|
47
|
+
function resolveCanvasState(properties, args, config = {}) {
|
|
48
|
+
const baseMargin = config.margin ?? DEFAULT_MARGIN;
|
|
49
|
+
const baseSize = config.size ?? { width: "explicit", height: "explicit" };
|
|
47
50
|
const state = {
|
|
48
|
-
width:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
? args.height
|
|
53
|
-
: canvas.properties.height,
|
|
54
|
-
background: Object.hasOwn(args, "background")
|
|
55
|
-
? args.background
|
|
56
|
-
: canvas.properties.background,
|
|
57
|
-
margin: Object.hasOwn(args, "margin")
|
|
58
|
-
? normalizeMargin(args.margin, baseMargin)
|
|
59
|
-
: baseMargin,
|
|
51
|
+
width: properties.width, height: properties.height, background: properties.background,
|
|
52
|
+
...args,
|
|
53
|
+
margin: Object.hasOwn(args, "margin") ? normalizeMargin(args.margin, baseMargin) : baseMargin,
|
|
54
|
+
plotBorder: normalizePlotBorder(Object.hasOwn(args, "plotBorder") ? args.plotBorder : (config.plotBorder ?? false)),
|
|
60
55
|
size: {
|
|
61
56
|
width: Object.hasOwn(args, "width") ? "explicit" : baseSize.width,
|
|
62
57
|
height: Object.hasOwn(args, "height") ? "explicit" : baseSize.height
|
|
63
58
|
}
|
|
64
59
|
};
|
|
65
|
-
|
|
60
|
+
delete state.plot;
|
|
61
|
+
const plot = resolveCanvasPlot(args, config.plot);
|
|
62
|
+
if (plot !== undefined) {
|
|
63
|
+
state.plot = plot;
|
|
64
|
+
state.width = plot.width + state.margin.left + state.margin.right;
|
|
65
|
+
state.height = plot.height + state.margin.top + state.margin.bottom;
|
|
66
|
+
state.size = { width: "explicit", height: "explicit" };
|
|
67
|
+
}
|
|
66
68
|
validateCanvasState(state);
|
|
67
69
|
return cloneAndFreeze(state);
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
function applyCanvasState(program, args, state) {
|
|
73
|
+
let next = program;
|
|
74
|
+
for (const property of ["width", "height", "background"]) {
|
|
75
|
+
if (Object.hasOwn(args, property) || state.plot !== undefined && property !== "background") {
|
|
76
|
+
next = next.editGraphics({ target: CANVAS_GRAPHIC_ID, property, value: state[property] });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return next._withCanvasConfig({ margin: state.margin, size: state.size,
|
|
80
|
+
...(state.plot === undefined ? {} : { plot: state.plot }),
|
|
81
|
+
...(state.plotBorder === false ? {} : { plotBorder: state.plotBorder }) });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function assertBorderAvailable(program, state) {
|
|
85
|
+
if (state.plotBorder !== false && program.materializationConfigs.canvas?.plotBorder === undefined &&
|
|
86
|
+
(program.graphicSpec.objects.plotBorder !== undefined || findLayer(program, "plotBorder") !== undefined)) {
|
|
87
|
+
throw new Error('Canvas plotBorder requires the reserved graphic id "plotBorder".');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function createHierarchy(program) {
|
|
92
|
+
return program.createGraphics({ id: CANVAS_GRAPHIC_ID, type: "canvas" })
|
|
93
|
+
.createGraphics({ id: PLOT_GRAPHIC_ID, type: "collection", parent: CANVAS_GRAPHIC_ID });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function initialSize(args) {
|
|
97
|
+
return Object.fromEntries(["width", "height"].map(key =>
|
|
98
|
+
[key, args.plot || Object.hasOwn(args, key) ? "explicit" : "auto"]));
|
|
99
|
+
}
|
|
100
|
+
|
|
70
101
|
export const editCanvas = /* @__PURE__ */ action(
|
|
71
102
|
{
|
|
72
103
|
op: "editCanvas",
|
|
@@ -74,30 +105,25 @@ export const editCanvas = /* @__PURE__ */ action(
|
|
|
74
105
|
},
|
|
75
106
|
function (args = {}) {
|
|
76
107
|
validateOptions(args, "editCanvas");
|
|
77
|
-
const state = resolveCanvasState(this, args);
|
|
108
|
+
const state = resolveCanvasState(requireCanvas(this).properties, args, this.materializationConfigs.canvas);
|
|
109
|
+
assertBorderAvailable(this, state);
|
|
78
110
|
let next = this;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (Object.hasOwn(args, property)) {
|
|
82
|
-
next = next.editGraphics({
|
|
83
|
-
target: "canvas",
|
|
84
|
-
property,
|
|
85
|
-
value: state[property]
|
|
86
|
-
});
|
|
87
|
-
}
|
|
111
|
+
if (state.plotBorder === false && this.materializationConfigs.canvas?.plotBorder !== undefined) {
|
|
112
|
+
next = next.editGraphics({ target: "plotBorder", remove: true });
|
|
88
113
|
}
|
|
89
|
-
|
|
90
|
-
next = next._withCanvasConfig({ margin: state.margin, size: state.size });
|
|
114
|
+
next = applyCanvasState(next, args, state);
|
|
91
115
|
|
|
92
116
|
if (
|
|
93
117
|
Object.hasOwn(args, "width") ||
|
|
94
118
|
Object.hasOwn(args, "height") ||
|
|
95
|
-
Object.hasOwn(args, "margin")
|
|
119
|
+
Object.hasOwn(args, "margin") || Object.hasOwn(args, "plot")
|
|
96
120
|
) {
|
|
97
121
|
next = applyMaterializationPlan(
|
|
98
122
|
next,
|
|
99
123
|
planCanvasRematerialization(next)
|
|
100
124
|
);
|
|
125
|
+
} else if (state.plotBorder !== false && Object.hasOwn(args, "plotBorder")) {
|
|
126
|
+
next = next.rematerializePlotBorder();
|
|
101
127
|
}
|
|
102
128
|
|
|
103
129
|
return next;
|
|
@@ -114,36 +140,14 @@ export const createCanvas = /* @__PURE__ */ action(
|
|
|
114
140
|
|
|
115
141
|
assertCanvasHierarchyAvailable(this);
|
|
116
142
|
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
? args.background
|
|
126
|
-
: DEFAULT_CANVAS.background,
|
|
127
|
-
margin: Object.hasOwn(args, "margin")
|
|
128
|
-
? args.margin
|
|
129
|
-
: DEFAULT_CANVAS.margin
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const next = this
|
|
133
|
-
.createGraphics({ id: CANVAS_GRAPHIC_ID, type: "canvas" })
|
|
134
|
-
.createGraphics({
|
|
135
|
-
id: PLOT_GRAPHIC_ID,
|
|
136
|
-
type: "collection",
|
|
137
|
-
parent: CANVAS_GRAPHIC_ID
|
|
138
|
-
})
|
|
139
|
-
.editCanvas(options);
|
|
140
|
-
return next._withCanvasConfig({
|
|
141
|
-
...next.materializationConfigs.canvas,
|
|
142
|
-
size: {
|
|
143
|
-
width: Object.hasOwn(args, "width") ? "explicit" : "auto",
|
|
144
|
-
height: Object.hasOwn(args, "height") ? "explicit" : "auto"
|
|
145
|
-
}
|
|
146
|
-
});
|
|
143
|
+
const plot = resolveCanvasPlot(args);
|
|
144
|
+
const options = { ...DEFAULT_CANVAS, ...args };
|
|
145
|
+
if (plot !== undefined) {
|
|
146
|
+
delete options.width;
|
|
147
|
+
delete options.height;
|
|
148
|
+
}
|
|
149
|
+
const next = createHierarchy(this).editCanvas(options);
|
|
150
|
+
return next._withCanvasConfig({ ...next.materializationConfigs.canvas, size: initialSize(args) });
|
|
147
151
|
}
|
|
148
152
|
);
|
|
149
153
|
|
|
@@ -155,38 +159,9 @@ export const createBasicCanvas = /* @__PURE__ */ action(
|
|
|
155
159
|
function (args = {}) {
|
|
156
160
|
validateOptions(args, "createCanvas", { allowEmpty: true });
|
|
157
161
|
assertCanvasHierarchyAvailable(this);
|
|
158
|
-
const state = {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
? args.background
|
|
163
|
-
: DEFAULT_CANVAS.background,
|
|
164
|
-
margin: Object.hasOwn(args, "margin")
|
|
165
|
-
? normalizeMargin(args.margin, DEFAULT_MARGIN)
|
|
166
|
-
: DEFAULT_MARGIN,
|
|
167
|
-
size: {
|
|
168
|
-
width: Object.hasOwn(args, "width") ? "explicit" : "auto",
|
|
169
|
-
height: Object.hasOwn(args, "height") ? "explicit" : "auto"
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
validateCanvasState(state);
|
|
173
|
-
let next = this
|
|
174
|
-
.createGraphics({
|
|
175
|
-
id: CANVAS_GRAPHIC_ID,
|
|
176
|
-
type: "canvas"
|
|
177
|
-
})
|
|
178
|
-
.createGraphics({
|
|
179
|
-
id: PLOT_GRAPHIC_ID,
|
|
180
|
-
type: "collection",
|
|
181
|
-
parent: CANVAS_GRAPHIC_ID
|
|
182
|
-
});
|
|
183
|
-
for (const property of ["width", "height", "background"]) {
|
|
184
|
-
next = next.editGraphics({
|
|
185
|
-
target: CANVAS_GRAPHIC_ID,
|
|
186
|
-
property,
|
|
187
|
-
value: state[property]
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
return next._withCanvasConfig({ margin: state.margin, size: state.size });
|
|
162
|
+
const state = resolveCanvasState(DEFAULT_CANVAS, args, { size: initialSize(args) });
|
|
163
|
+
assertBorderAvailable(this, state);
|
|
164
|
+
const next = applyCanvasState(createHierarchy(this), state, state);
|
|
165
|
+
return state.plotBorder === false ? next : next.rematerializePlotBorder();
|
|
191
166
|
}
|
|
192
167
|
);
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
import { resolveGraphicBounds } from "../../layout/canvas.js";
|
|
8
8
|
import { findCanvasGraphic } from
|
|
9
9
|
"../../materialization/graphicHierarchy.js";
|
|
10
|
+
import { resolveConcreteGraphicBounds } from "../../grammar/schemas/graphicBounds.js";
|
|
11
|
+
import { textBoundsFitCanvas } from "../../core/textMetrics.js";
|
|
10
12
|
|
|
11
13
|
const OPTIONS = Object.freeze([
|
|
12
14
|
"padding",
|
|
@@ -45,7 +47,17 @@ function normalizeOptions(args) {
|
|
|
45
47
|
|
|
46
48
|
function probe(program, margin) {
|
|
47
49
|
try {
|
|
48
|
-
|
|
50
|
+
const next = program.editCanvas({ margin });
|
|
51
|
+
const title = next.guideConfigs.axis?.y?.title;
|
|
52
|
+
if (title?.inferredOffset && Math.abs(Math.cos(title.rotation)) < 1e-10) {
|
|
53
|
+
const bounds = resolveConcreteGraphicBounds(next.graphicSpec, "yAxisTitle", next.materializationConfigs.textMetrics);
|
|
54
|
+
const edge = title.position === "right" ? "right" : "left";
|
|
55
|
+
const reserve = title.fontSize / 4 * (edge === "right" ? 1 : -1);
|
|
56
|
+
if (!textBoundsFitCanvas({...bounds, [edge]: bounds[edge] + reserve}, findCanvasGraphic(next).properties)) {
|
|
57
|
+
return {ok:false, message:"Fitting retains inferred axis-title ink clearance."};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { ok: true, program: next };
|
|
49
61
|
} catch (error) {
|
|
50
62
|
return {
|
|
51
63
|
ok: false,
|
|
@@ -171,6 +183,9 @@ export const fitCanvas = /* @__PURE__ */ action(
|
|
|
171
183
|
this.materializationConfigs.canvas?.margin === undefined) {
|
|
172
184
|
throw new Error("fitCanvas requires an existing Canvas.");
|
|
173
185
|
}
|
|
186
|
+
if (this.materializationConfigs.canvas.plot !== undefined) {
|
|
187
|
+
throw new Error("fitCanvas requires fixed outer dimensions; disable Canvas plot sizing first.");
|
|
188
|
+
}
|
|
174
189
|
const previous = this.materializationConfigs.fitting;
|
|
175
190
|
if (samePolicy(previous?.policy, policy) &&
|
|
176
191
|
previous?.result?.signature === layoutSignature(this)) {
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { createBasicCanvas, createCanvas, editCanvas } from "./actions.js";
|
|
2
2
|
import { fitCanvas } from "./fitting.js";
|
|
3
|
+
import { rematerializePlotBorder } from "./plotBorder.js";
|
|
3
4
|
|
|
4
5
|
export function registerCanvasActions(ProgramClass) {
|
|
5
6
|
ProgramClass.prototype.editCanvas = editCanvas;
|
|
6
7
|
ProgramClass.prototype.createCanvas = createCanvas;
|
|
7
8
|
ProgramClass.prototype.fitCanvas = fitCanvas;
|
|
9
|
+
ProgramClass.prototype.rematerializePlotBorder = rematerializePlotBorder;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export function registerBasicCanvasActions(ProgramClass) {
|
|
11
13
|
ProgramClass.prototype.createCanvas = createBasicCanvas;
|
|
14
|
+
ProgramClass.prototype.rematerializePlotBorder = rematerializePlotBorder;
|
|
12
15
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { noOptions } from "../../core/validation.js";
|
|
3
|
+
import { resolveGraphicBounds } from "../../layout/canvas.js";
|
|
4
|
+
import { resolvePlotGraphicPlacement } from "../../materialization/graphicHierarchy.js";
|
|
5
|
+
|
|
6
|
+
export const rematerializePlotBorder = /* @__PURE__ */ action(
|
|
7
|
+
{ op: "rematerializePlotBorder", description: "Materialize the optional inset plot border." },
|
|
8
|
+
function (args = {}) {
|
|
9
|
+
noOptions(args, "rematerializePlotBorder");
|
|
10
|
+
const border = this.materializationConfigs.canvas?.plotBorder;
|
|
11
|
+
if (border === undefined) return this;
|
|
12
|
+
const plot = resolveGraphicBounds(this);
|
|
13
|
+
const half = border.lineWidth / 2;
|
|
14
|
+
const axis = this.graphicSpec.objects["plot-main"].children?.find(id =>
|
|
15
|
+
/^(?:(?:x|y|theta|radial)Axis(?:Line|Ticks|Labels|Title)|parallelAxis(?:Lines|Ticks|Labels|Titles))$/.test(id));
|
|
16
|
+
let next = this.graphicSpec.objects.plotBorder === undefined
|
|
17
|
+
? this.createGraphics({ id: "plotBorder", type: "rect",
|
|
18
|
+
...resolvePlotGraphicPlacement(this, axis === undefined ? {} : { before: axis }) })
|
|
19
|
+
: this;
|
|
20
|
+
const properties = { x: plot.x + half, y: plot.y + half,
|
|
21
|
+
width: plot.width - border.lineWidth, height: plot.height - border.lineWidth,
|
|
22
|
+
fill: "transparent", stroke: border.color, strokeWidth: border.lineWidth };
|
|
23
|
+
for (const [property, value] of Object.entries(properties)) {
|
|
24
|
+
next = next.editGraphics({ target: "plotBorder", property, value });
|
|
25
|
+
}
|
|
26
|
+
return next;
|
|
27
|
+
}
|
|
28
|
+
);
|
|
@@ -21,8 +21,8 @@ export const createAreaPlot = /* @__PURE__ */ action({ op: OPERATION, descriptio
|
|
|
21
21
|
const independentChannel = valueChannel === "y" ? "x" : "y";
|
|
22
22
|
const independent = omitUndefinedOptions(normalizeFieldEncoding(args[independentChannel], `${OPERATION} ${independentChannel}`));
|
|
23
23
|
validateFacadeOptions(independent, ["field", "fieldType", "temporalUnit", "scale"], `${OPERATION} independent position`);
|
|
24
|
-
if (independent.fieldType !== undefined && !["quantitative", "temporal"].includes(independent.fieldType)) {
|
|
25
|
-
throw new Error("createAreaPlot independent position must be quantitative or
|
|
24
|
+
if (independent.fieldType !== undefined && !["quantitative", "temporal", "nominal", "ordinal"].includes(independent.fieldType)) {
|
|
25
|
+
throw new Error("createAreaPlot independent position must be quantitative, temporal, or categorical.");
|
|
26
26
|
}
|
|
27
27
|
const measurement = omitUndefinedOptions(normalizeFieldEncoding(args[valueChannel], `${OPERATION} ${valueChannel}`));
|
|
28
28
|
const ranged = Object.hasOwn(measurement, "lower") || Object.hasOwn(measurement, "upper");
|
|
@@ -17,12 +17,26 @@ import {
|
|
|
17
17
|
} from "./shared.js";
|
|
18
18
|
|
|
19
19
|
const OPTIONS = Object.freeze([
|
|
20
|
-
"id", "data", "coordinate", "x", "y", "color", "width", "bar", "guides"
|
|
20
|
+
"id", "data", "coordinate", "orientation", "x", "y", "color", "width", "bar", "guides"
|
|
21
21
|
]);
|
|
22
22
|
const BAR_OPTIONS = Object.freeze([
|
|
23
23
|
"fill", "opacity", "stroke", "strokeWidth", ...RECT_STYLE_PROPERTIES
|
|
24
24
|
]);
|
|
25
25
|
|
|
26
|
+
function normalizeBarPosition(value, label) {
|
|
27
|
+
const encoding = normalizeFieldEncoding(value, label);
|
|
28
|
+
if (!Object.hasOwn(encoding, "lower") && !Object.hasOwn(encoding, "upper")) return encoding;
|
|
29
|
+
validateFacadeOptions(encoding, ["lower", "upper", "fieldType", "scale"], label);
|
|
30
|
+
if (typeof encoding.lower !== "string" || !encoding.lower.length ||
|
|
31
|
+
typeof encoding.upper !== "string" || !encoding.upper.length) {
|
|
32
|
+
throw new TypeError(`${label} range requires lower and upper field names.`);
|
|
33
|
+
}
|
|
34
|
+
if (encoding.fieldType !== undefined && encoding.fieldType !== "quantitative") {
|
|
35
|
+
throw new Error(`${label} range must be quantitative.`);
|
|
36
|
+
}
|
|
37
|
+
return { ...encoding, fieldType: "quantitative" };
|
|
38
|
+
}
|
|
39
|
+
|
|
26
40
|
export const createBarPlot = /* @__PURE__ */ action(
|
|
27
41
|
{
|
|
28
42
|
op: "createBarPlot",
|
|
@@ -36,18 +50,31 @@ export const createBarPlot = /* @__PURE__ */ action(
|
|
|
36
50
|
});
|
|
37
51
|
const data = resolveFacadeData(this, args.data, "createBarPlot");
|
|
38
52
|
const bar = normalizeAppearance(args.bar, BAR_OPTIONS, "createBarPlot bar");
|
|
39
|
-
const x =
|
|
40
|
-
const y =
|
|
53
|
+
const x = normalizeBarPosition(args.x, "createBarPlot x");
|
|
54
|
+
const y = normalizeBarPosition(args.y, "createBarPlot y");
|
|
55
|
+
const xRange = Object.hasOwn(x, "lower");
|
|
56
|
+
const yRange = Object.hasOwn(y, "lower");
|
|
57
|
+
if (xRange && yRange) throw new Error("createBarPlot accepts only one range channel.");
|
|
41
58
|
x.fieldType = inferFacadeFieldType(this, data, x, "createBarPlot x");
|
|
42
59
|
y.fieldType = inferFacadeFieldType(this, data, y, "createBarPlot y");
|
|
43
60
|
const color = normalizeEncoding(args.color, "createBarPlot color");
|
|
44
61
|
const width = normalizeTargetOptions(args.width, "createBarPlot width");
|
|
45
62
|
const guides = normalizeGuides(args.guides, "createBarPlot");
|
|
63
|
+
const numeric = x.fieldType === "quantitative" && y.fieldType === "quantitative" &&
|
|
64
|
+
x.bin === undefined && y.bin === undefined && x.aggregate === undefined && y.aggregate === undefined;
|
|
65
|
+
if (args.orientation !== undefined && !numeric) throw new Error("createBarPlot orientation requires raw numeric positions.");
|
|
66
|
+
const orientation = numeric ? args.orientation ?? (xRange ? "horizontal" : "vertical") : undefined;
|
|
67
|
+
if (numeric && ((xRange && orientation !== "horizontal") || (yRange && orientation !== "vertical"))) {
|
|
68
|
+
throw new Error("createBarPlot range must match its numeric measure orientation.");
|
|
69
|
+
}
|
|
46
70
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
71
|
+
if (!numeric && ((xRange && !isBarCategoryEncoding(y)) || (yRange && !isBarCategoryEncoding(x)))) {
|
|
72
|
+
throw new Error("createBarPlot range requires a categorical or temporal opposite position.");
|
|
73
|
+
}
|
|
74
|
+
const positions = isBarCategoryEncoding(y) || orientation === "horizontal"
|
|
75
|
+
? [["encodeY", y], [xRange ? "encodeXRange" : "encodeX", x]]
|
|
76
|
+
: [["encodeX", x], [yRange ? "encodeYRange" : "encodeY", y]];
|
|
77
|
+
let next = this.createBarMark({ id, data, ...bar, ...(orientation === undefined ? {} : { orientation }) });
|
|
51
78
|
for (const [operation, encoding] of positions) {
|
|
52
79
|
next = next[operation](positionArgs(encoding, {
|
|
53
80
|
target: id, coordinate: args.coordinate
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { action } from "../../core/action.js";
|
|
2
1
|
import { validateUserId } from "../../core/identifiers.js";
|
|
2
|
+
import { action } from "../../core/action.js";
|
|
3
|
+
import { resolveConfiguredOwner } from "../../selectors/layers.js";
|
|
3
4
|
import { validateNonEmptyString, validateOptionObject } from "../../core/validation.js";
|
|
4
5
|
import { normalizeGroupFields } from "../../grammar/pathSeries.js";
|
|
5
6
|
import { STROKE_STYLE_PROPERTIES } from "../../grammar/strokeStyle.js";
|
|
@@ -55,22 +56,7 @@ function normalizeOutputs(value, id, operation) {
|
|
|
55
56
|
return { ...value };
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
const eligible = program.semanticSpec.layers.filter(
|
|
60
|
-
layer => program.markConfigs[layer.id]?.ecdfPlot !== undefined
|
|
61
|
-
);
|
|
62
|
-
if (requested !== undefined) {
|
|
63
|
-
const id = validateUserId(requested, "ECDF-plot owner id");
|
|
64
|
-
const owner = eligible.find(layer => layer.id === id);
|
|
65
|
-
if (owner === undefined) throw new Error(`Unknown ECDF-plot owner "${id}".`);
|
|
66
|
-
return owner;
|
|
67
|
-
}
|
|
68
|
-
const current = eligible.find(layer => layer.id === program.context.currentMark);
|
|
69
|
-
if (current !== undefined) return current;
|
|
70
|
-
if (eligible.length === 1) return eligible[0];
|
|
71
|
-
if (eligible.length === 0) throw new Error("editECDFPlot requires an ECDF plot.");
|
|
72
|
-
throw new Error("editECDFPlot target is ambiguous; provide target.");
|
|
73
|
-
}
|
|
59
|
+
|
|
74
60
|
|
|
75
61
|
export const createECDFPlot = /* @__PURE__ */ action(
|
|
76
62
|
{
|
|
@@ -169,7 +155,7 @@ export const editECDFPlot = /* @__PURE__ */ action(
|
|
|
169
155
|
if (changes.length === 0) {
|
|
170
156
|
throw new Error("editECDFPlot requires at least one ECDF option.");
|
|
171
157
|
}
|
|
172
|
-
const owner =
|
|
158
|
+
const owner = resolveConfiguredOwner(this, args.target, { config: "ecdfPlot", operation: "editECDFPlot", kind: "ECDF", article: "an" }, validateUserId);
|
|
173
159
|
const current = this.markConfigs[owner.id].ecdfPlot;
|
|
174
160
|
const revised = { ...current.options };
|
|
175
161
|
for (const key of changes) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
1
2
|
import { action } from "../../core/action.js";
|
|
2
3
|
import { STROKE_STYLE_PROPERTIES } from "../../grammar/strokeStyle.js";
|
|
3
4
|
import { isPlainObject } from "../../core/immutable.js";
|
|
4
|
-
import {
|
|
5
|
+
import { resolveConfiguredOwner } from "../../selectors/layers.js";
|
|
5
6
|
import { isNominalValue } from "../../grammar/scales/fields.js";
|
|
6
7
|
import {
|
|
7
8
|
validateNonEmptyString,
|
|
@@ -400,22 +401,7 @@ export const createDumbbellPlot = /* @__PURE__ */ action(
|
|
|
400
401
|
}
|
|
401
402
|
);
|
|
402
403
|
|
|
403
|
-
|
|
404
|
-
const eligible = program.semanticSpec.layers.filter(
|
|
405
|
-
layer => program.markConfigs[layer.id]?.endpointPlot !== undefined
|
|
406
|
-
);
|
|
407
|
-
if (requested !== undefined) {
|
|
408
|
-
const id = validateUserId(requested, "Endpoint-plot owner id");
|
|
409
|
-
const owner = eligible.find(layer => layer.id === id);
|
|
410
|
-
if (owner === undefined) throw new Error(`Unknown endpoint-plot owner "${id}".`);
|
|
411
|
-
return owner;
|
|
412
|
-
}
|
|
413
|
-
const current = eligible.find(layer => layer.id === program.context.currentMark);
|
|
414
|
-
if (current !== undefined) return current;
|
|
415
|
-
if (eligible.length === 1) return eligible[0];
|
|
416
|
-
if (eligible.length === 0) throw new Error("editEndpointPlot requires an endpoint plot.");
|
|
417
|
-
throw new Error("editEndpointPlot target is ambiguous; provide target.");
|
|
418
|
-
}
|
|
404
|
+
|
|
419
405
|
|
|
420
406
|
const KIND_METHOD = Object.freeze({
|
|
421
407
|
dot: "createDotPlot",
|
|
@@ -457,7 +443,7 @@ export const editEndpointPlot = /* @__PURE__ */ action(
|
|
|
457
443
|
if (changes.length === 0) {
|
|
458
444
|
throw new Error("editEndpointPlot requires at least one endpoint-plot option.");
|
|
459
445
|
}
|
|
460
|
-
const owner =
|
|
446
|
+
const owner = resolveConfiguredOwner(this, args.target, { config: "endpointPlot", operation: "editEndpointPlot", kind: "endpoint", article: "an" }, validateUserId);
|
|
461
447
|
const current = this.markConfigs[owner.id].endpointPlot;
|
|
462
448
|
const allowed = KIND_ROLES[current.kind];
|
|
463
449
|
const invalid = changes.find(key => !allowed.has(key));
|
|
@@ -153,12 +153,12 @@ export const createHeatmap = /* @__PURE__ */ action(
|
|
|
153
153
|
if (args.bin === undefined) {
|
|
154
154
|
const x = normalizePosition(args.x, "x", true);
|
|
155
155
|
const y = normalizePosition(args.y, "y", true);
|
|
156
|
-
const color = normalizeFieldEncoding(args.color, `${OPERATION} color`);
|
|
157
|
-
|
|
156
|
+
const color = args.color === undefined ? undefined : normalizeFieldEncoding(args.color, `${OPERATION} color`);
|
|
157
|
+
let next = this
|
|
158
158
|
.createRectMark({ id, data, ...rect })
|
|
159
159
|
.encodeX(positionArgs(x, { target: id, coordinate: args.coordinate }))
|
|
160
|
-
.encodeY(positionArgs(y, { target: id, coordinate: args.coordinate }))
|
|
161
|
-
|
|
160
|
+
.encodeY(positionArgs(y, { target: id, coordinate: args.coordinate }));
|
|
161
|
+
if (color !== undefined) next = next.encodeColor(targetArgs(color, id));
|
|
162
162
|
return applyFacadeGuides(next, guides, id);
|
|
163
163
|
}
|
|
164
164
|
|
|
@@ -5,6 +5,7 @@ import { createBarPlot } from "./bar.js";
|
|
|
5
5
|
import { createHistogram } from "./histogram.js";
|
|
6
6
|
import { createLinePlot } from "./line.js";
|
|
7
7
|
import { createScatterPlot } from "./scatter.js";
|
|
8
|
+
import { createTextPlot } from "./text.js";
|
|
8
9
|
import { createParallelCoordinates } from "./parallel.js";
|
|
9
10
|
import { createPiePlot } from "./pie.js";
|
|
10
11
|
import { createDensityPlot } from "./density.js";
|
|
@@ -28,6 +29,7 @@ export function registerChartActions(ProgramClass) {
|
|
|
28
29
|
ProgramClass.prototype.createHeatmap = createHeatmap;
|
|
29
30
|
ProgramClass.prototype.createHistogram = createHistogram;
|
|
30
31
|
ProgramClass.prototype.createScatterPlot = createScatterPlot;
|
|
32
|
+
ProgramClass.prototype.createTextPlot = createTextPlot;
|
|
31
33
|
ProgramClass.prototype.createLinePlot = createLinePlot;
|
|
32
34
|
ProgramClass.prototype.createParallelCoordinates = createParallelCoordinates;
|
|
33
35
|
ProgramClass.prototype.createPiePlot = createPiePlot;
|
|
@@ -135,7 +135,7 @@ export const createIntervalPlot = /* @__PURE__ */ action(
|
|
|
135
135
|
|
|
136
136
|
const REGRESSION_OPTIONS = Object.freeze([
|
|
137
137
|
"id", "data", "coordinate", "x", "y", "color", "size", "shape", "point",
|
|
138
|
-
"groupBy", "method", "degree", "span", "confidenceMethod", "level", "confidence",
|
|
138
|
+
"groupBy", "method", "degree", "span", "robustIterations", "confidenceMethod", "level", "confidence",
|
|
139
139
|
"interval", "band", "line", "guides"
|
|
140
140
|
]);
|
|
141
141
|
|
|
@@ -163,7 +163,7 @@ export const createRegressionPlot = /* @__PURE__ */ action(
|
|
|
163
163
|
guides: false
|
|
164
164
|
});
|
|
165
165
|
const regression = Object.fromEntries([
|
|
166
|
-
"groupBy", "method", "degree", "span", "confidenceMethod", "level",
|
|
166
|
+
"groupBy", "method", "degree", "span", "robustIterations", "confidenceMethod", "level",
|
|
167
167
|
"confidence", "interval", "band", "line"
|
|
168
168
|
].filter(key => Object.hasOwn(args, key)).map(key => [key, args[key]]));
|
|
169
169
|
next = next.createRegression({ target: id, ...regression });
|
|
@@ -20,7 +20,7 @@ const OPTIONS = Object.freeze([
|
|
|
20
20
|
"strokeDash", "line", "guides"
|
|
21
21
|
]);
|
|
22
22
|
const LINE_OPTIONS = Object.freeze([
|
|
23
|
-
"strokeWidth", "curve", "stroke", "opacity", "closed",
|
|
23
|
+
"strokeWidth", "curve", "tension", "stroke", "opacity", "closed",
|
|
24
24
|
...STROKE_STYLE_PROPERTIES
|
|
25
25
|
]);
|
|
26
26
|
|