ggaction 0.0.21 → 0.0.23
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 +90 -0
- package/README.md +83 -90
- 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/package.json +11 -4
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +1 -0
- package/src/actions/boxPlots/components.js +1 -96
- package/src/actions/boxPlots/create.js +6 -1
- package/src/actions/boxPlots/edit.js +8 -0
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +1 -1
- package/src/actions/canvas/actions.js +2 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +2 -1
- package/src/actions/canvas/plotOrder.js +14 -0
- package/src/actions/charts/area.js +1 -1
- package/src/actions/charts/bar.js +4 -4
- package/src/actions/charts/density.js +1 -1
- package/src/actions/charts/ecdf.js +1 -1
- package/src/actions/charts/endpoints.js +54 -5
- package/src/actions/charts/heatmap.js +1 -1
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +1 -1
- package/src/actions/charts/index.js +2 -1
- package/src/actions/charts/interval-regression.js +3 -3
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/radar.js +63 -3
- package/src/actions/charts/raincloud.js +37 -6
- package/src/actions/charts/rug-strip.js +2 -2
- package/src/actions/charts/scatter.js +3 -3
- package/src/actions/charts/text.js +1 -1
- package/src/actions/composition/actions.js +10 -3
- package/src/actions/coordinates/actions.js +2 -0
- package/src/actions/data/basic.js +6 -0
- package/src/actions/data/derived.js +6 -2
- package/src/actions/data/edit.js +22 -36
- package/src/actions/data/filter.js +1 -376
- package/src/actions/data/flatten.js +19 -0
- package/src/actions/data/index.js +6 -0
- package/src/actions/data/lookup.js +50 -0
- package/src/actions/data/raincloudFilter.js +56 -0
- package/src/actions/data/shared.js +10 -0
- package/src/actions/data/timeUnit.js +1 -1
- package/src/actions/distributions/revision.js +3 -1
- package/src/actions/encodings/angle.js +2 -1
- package/src/actions/encodings/appearance.js +38 -13
- package/src/actions/encodings/channels.js +14 -3
- package/src/actions/encodings/color/continuous.js +26 -10
- package/src/actions/encodings/color/index.js +50 -20
- package/src/actions/encodings/color/policy.js +57 -4
- package/src/actions/encodings/parallel.js +4 -0
- package/src/actions/encodings/position/policies/bar.js +10 -6
- package/src/actions/encodings/position/policies/line.js +1 -152
- package/src/actions/encodings/position/resolve.js +4 -1
- package/src/actions/encodings/ranged.js +46 -0
- package/src/actions/encodings/remove.js +1 -1
- package/src/actions/encodings/ruleAppearance.js +7 -6
- package/src/actions/encodings/shared.js +14 -2
- package/src/actions/encodings/stroke.js +20 -14
- package/src/actions/encodings/strokeDash.js +9 -11
- package/src/actions/encodings/text.js +30 -2
- package/src/actions/errorBars/edit.js +24 -4
- package/src/actions/facets/actions.js +1 -793
- package/src/actions/facets/derive.js +34 -5
- package/src/actions/facets/replay.js +1 -38
- package/src/actions/gradientPlots/components.js +21 -9
- package/src/actions/gradientPlots/create.js +4 -2
- package/src/actions/gradientPlots/edit.js +18 -1
- package/src/actions/gradientPlots/materialize.js +35 -36
- package/src/actions/gradientPlots/options.js +1 -1
- package/src/actions/gradientPlots/paint.js +13 -7
- package/src/actions/guides/applicability.js +2 -0
- package/src/actions/guides/axes/axes.js +39 -0
- package/src/actions/guides/axes/axis.js +9 -5
- package/src/actions/guides/axes/edit.js +11 -8
- package/src/actions/guides/axes/labels.js +24 -8
- package/src/actions/guides/axes/parallel/policy.js +3 -5
- package/src/actions/guides/axes/parallel/resolve.js +4 -3
- package/src/actions/guides/axes/policy.js +22 -0
- package/src/actions/guides/axes/tickGroups.js +4 -2
- package/src/actions/guides/axes/ticks.js +22 -4
- package/src/actions/guides/axes/titles.js +19 -6
- package/src/actions/guides/facadeAxes.js +3 -0
- package/src/actions/guides/grids/grid.js +3 -1
- package/src/actions/guides/grids/resolve.js +7 -11
- package/src/actions/guides/guides.js +21 -1
- package/src/actions/guides/legends/categorical/actions.js +1 -515
- package/src/actions/guides/legends/categorical/components.js +1 -4
- package/src/actions/guides/legends/categorical/layout.js +5 -2
- package/src/actions/guides/legends/categorical/options.js +4 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -3
- package/src/actions/guides/legends/categorical/symbols.js +3 -3
- package/src/actions/guides/legends/colorSize.js +2 -0
- package/src/actions/guides/legends/continuous/common.js +1 -1
- package/src/actions/guides/legends/continuous/gradient.js +1 -360
- package/src/actions/guides/legends/continuous/interval.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +15 -1
- package/src/actions/guides/legends/edit.js +1 -1
- package/src/actions/guides/legends/focused.js +1 -1
- package/src/actions/guides/legends/lane.js +1 -1
- package/src/actions/guides/legends/sampling.js +1 -140
- package/src/actions/guides/legends/strokeWidth.js +3 -2
- package/src/actions/guides/legends/transition.js +19 -2
- package/src/actions/guides/polar/axes/facade.js +5 -4
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/resolve.js +2 -1
- package/src/actions/guides/tickValues.js +7 -7
- package/src/actions/marks/arc/actions.js +5 -7
- package/src/actions/marks/area/actions.js +9 -3
- package/src/actions/marks/bar/create.js +4 -2
- package/src/actions/marks/bar/edit.js +9 -6
- package/src/actions/marks/bar/materialize.js +5 -3
- package/src/actions/marks/dependentReferences.js +87 -0
- package/src/actions/marks/index.js +2 -0
- package/src/actions/marks/lifecycle.js +2 -1
- package/src/actions/marks/line/actions.js +24 -6
- package/src/actions/marks/line/materialize.js +7 -3
- package/src/actions/marks/point/create.js +5 -4
- package/src/actions/marks/point/edit.js +6 -3
- package/src/actions/marks/point/jitter.js +6 -6
- package/src/actions/marks/point/materialize.js +36 -39
- package/src/actions/marks/rect/actions.js +16 -4
- package/src/actions/marks/references.js +183 -35
- package/src/actions/marks/rule/actions.js +20 -9
- package/src/actions/marks/shared.js +2 -1
- package/src/actions/marks/text/actions.js +10 -3
- package/src/actions/marks/tick/actions.js +11 -2
- package/src/actions/primitives/editGraphics.js +28 -3
- package/src/actions/primitives/index.js +9 -0
- package/src/actions/primitives/semanticAction.js +13 -7
- package/src/actions/primitives/semanticValidation/layer.js +20 -0
- package/src/actions/primitives/semanticValidation/scale.js +8 -1
- package/src/actions/regression/create.js +6 -1
- package/src/actions/regression/edit.js +10 -1
- package/src/actions/scales/channels.js +1 -1
- package/src/actions/scales/consumers/common.js +19 -1
- package/src/actions/scales/consumers/families.js +2 -2
- package/src/actions/scales/consumers/seriesLayout.js +4 -3
- package/src/actions/scales/edit.js +11 -0
- package/src/actions/scales/editPolicy.js +5 -3
- package/src/actions/scales/preview.js +2 -1
- package/src/actions/scales/quantitativeColor.js +2 -4
- package/src/actions/selection/actions.js +10 -1
- package/src/actions/theme/reconcile.js +37 -9
- package/src/core/compositionState.js +26 -7
- package/src/core/datasetReferences.js +17 -0
- package/src/core/facetAxisPolicy.js +30 -0
- package/src/core/immutable.js +1 -0
- package/src/core/resourceReferences.js +5 -3
- package/src/core/validation.js +4 -0
- package/src/core/vocabulary.js +24 -2
- package/src/grammar/aggregate.js +1 -0
- package/src/grammar/arcs.js +1 -1
- package/src/grammar/areaSeries.js +1 -1
- package/src/grammar/axisTextPlacement.js +1 -1
- package/src/grammar/bars/policy.js +2 -2
- package/src/grammar/curveCommands.js +35 -2
- package/src/grammar/datasetDependencies.js +97 -0
- package/src/grammar/datasetSchema.js +32 -2
- package/src/grammar/facetHeaders.js +1 -0
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +7 -5
- package/src/grammar/facets/legendIntent.js +6 -2
- package/src/grammar/facets/scales.js +27 -7
- package/src/grammar/flatten.js +85 -0
- package/src/grammar/histogram.js +13 -1
- package/src/grammar/horizon.js +1 -494
- package/src/grammar/jitter.js +2 -2
- package/src/grammar/legendSpacing.js +45 -0
- package/src/grammar/lineMissing.js +90 -0
- package/src/grammar/lineSeries.js +1 -376
- package/src/grammar/lookup.js +97 -0
- package/src/grammar/markFilter.js +4 -2
- package/src/grammar/markLabels.js +8 -3
- package/src/grammar/numeric.js +1 -1
- package/src/grammar/pathCommands.js +1 -1
- package/src/grammar/pathSeries.js +1 -109
- package/src/grammar/pointPolygon.js +81 -0
- package/src/grammar/pointShapes.js +15 -15
- package/src/grammar/rects.js +1 -1
- package/src/grammar/regression/derive.js +1 -195
- package/src/grammar/regression/parameters.js +7 -0
- package/src/grammar/scales/appearance.js +1 -1
- package/src/grammar/scales/color.js +1 -1
- package/src/grammar/scales/colorConsumers.js +1 -63
- package/src/grammar/scales/continuous.js +58 -1
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/ordinal.js +5 -2
- package/src/grammar/scales/policies.js +3 -3
- package/src/grammar/schemas/graphicBounds.js +40 -12
- package/src/grammar/schemas/graphicClip.js +23 -0
- package/src/grammar/schemas/graphicTree.js +16 -0
- package/src/grammar/statisticalReference.js +22 -0
- package/src/grammar/text.js +65 -1
- package/src/grammar/ticks.js +92 -1
- package/src/grammar/timeUnit.js +9 -3
- package/src/grammar/transformTopology.js +3 -1
- package/src/grammar/transforms.js +24 -0
- package/src/inspection.js +1 -495
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -1
- package/src/layout/facetContentFlow.js +101 -0
- package/src/layout/facetHeaderTitles.js +1 -0
- package/src/layout/facetTopLane.js +29 -0
- package/src/layout/facets.js +1 -1
- package/src/layout/guideCollisions.js +9 -3
- package/src/layout/legendGroup.js +1 -1
- package/src/layout/legendItems.js +26 -10
- package/src/layout/legendSpacing.js +66 -0
- package/src/layout/plotDrawOrder.js +17 -0
- package/src/materialization/bars/histogram.js +8 -2
- package/src/materialization/bars/resolve.js +1 -1
- package/src/materialization/composition.js +1 -0
- package/src/materialization/compositionSnapshot.js +22 -2
- package/src/materialization/dataProvenance.js +1 -1
- package/src/materialization/facetContentBounds.js +96 -0
- package/src/materialization/facetGuides/layout.js +1 -1
- package/src/materialization/facetGuides/placement.js +39 -11
- package/src/materialization/facetGuides/preparation.js +43 -24
- package/src/materialization/facetHeaders.js +1 -1
- package/src/materialization/facetTopLane.js +25 -0
- package/src/materialization/facets.js +1 -1
- package/src/materialization/graphicHierarchy.js +30 -1
- package/src/materialization/graphicTranslation.js +2 -0
- package/src/materialization/guides/layout.js +1 -1
- package/src/materialization/guides/resources.js +1 -1
- package/src/materialization/legends.js +1 -1
- package/src/materialization/markClip.js +26 -0
- package/src/materialization/marks/capabilities.js +3 -2
- package/src/materialization/marks/grouping.js +7 -5
- package/src/materialization/marks/index.js +4 -2
- package/src/materialization/marks/policies.js +4 -0
- package/src/materialization/plotDrawOrder.js +44 -0
- package/src/materialization/rect.js +11 -3
- package/src/materialization/scaleGuideDependencies.js +13 -5
- package/src/materialization/selection/items/common.js +1 -1
- package/src/materialization/selection/items/path.js +1 -0
- package/src/materialization/selection/policies/index.js +2 -1
- package/src/materialization/text.js +10 -5
- package/src/mcp/cli.js +0 -0
- package/src/persistence/validation.js +114 -18
- package/src/persistence.js +4 -2
- package/src/renderers/canvas/index.js +16 -0
- package/src/renderers/canvas/isolated.js +11 -1
- package/src/renderers/canvas/native.js +7 -0
- package/src/renderers/canvas/scene.js +66 -0
- package/src/renderers/canvas/text.js +3 -2
- package/src/renderers/svg.js +1 -617
- package/src/renderers/svgSerialization.js +643 -0
- package/src/renderers/transitionScene.js +67 -0
- package/src/scene.js +89 -0
- package/src/selectors/axes.js +1 -1
- package/src/selectors/datasets.js +1 -0
- package/src/selectors/grids.js +9 -0
- package/src/selectors/markClip.js +28 -0
- package/src/selectors/raincloud.js +7 -0
- package/src/version.js +1 -1
- package/types/basic.d.ts +7 -0
- package/types/index.d.ts +8 -0
- package/types/program.d.ts +286 -70
- package/types/scene.d.ts +35 -0
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
} from "../../materialization/dependencies.js";
|
|
11
11
|
import {
|
|
12
12
|
getLayerScaleIds,
|
|
13
|
-
getMarkMaterializationStep
|
|
13
|
+
getMarkMaterializationStep,
|
|
14
|
+
reusePlannedMarkScales
|
|
14
15
|
} from "../../materialization/marks/index.js";
|
|
15
16
|
import { buildMaterializationPlan } from "../../materialization/planner.js";
|
|
16
17
|
import { requireDataset } from "../../selectors/datasets.js";
|
|
@@ -163,6 +164,32 @@ function buildFacetCellCandidate(
|
|
|
163
164
|
return { program, populated: true };
|
|
164
165
|
}
|
|
165
166
|
|
|
167
|
+
function partitionGuideValues(program) {
|
|
168
|
+
let next = program;
|
|
169
|
+
const restrict = config => {
|
|
170
|
+
if (config?.mode !== "values" || config.inferredValues === true) return config;
|
|
171
|
+
const scale = next.resolvedScales[config.scale];
|
|
172
|
+
if (!Array.isArray(scale?.domain)) return config;
|
|
173
|
+
const discrete = ["ordinal", "band", "point"].includes(scale.type);
|
|
174
|
+
const domain = new Set(scale.domain);
|
|
175
|
+
const low = Math.min(...scale.domain), high = Math.max(...scale.domain);
|
|
176
|
+
return { ...config, values: config.values.filter(value => discrete
|
|
177
|
+
? domain.has(value) : value >= low && value <= high) };
|
|
178
|
+
};
|
|
179
|
+
for (const [id, axis] of Object.entries(program.guideConfigs.axis ?? {})) {
|
|
180
|
+
for (const component of ["ticks", "labels"]) {
|
|
181
|
+
const config = axis[component];
|
|
182
|
+
const restricted = restrict(config);
|
|
183
|
+
if (restricted !== config) next = next._withGuideConfig(id, component, restricted);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for (const [id, config] of Object.entries(program.guideConfigs.grid ?? {})) {
|
|
187
|
+
const restricted = restrict(config);
|
|
188
|
+
if (restricted !== config) next = next._withGridConfig(id, restricted);
|
|
189
|
+
}
|
|
190
|
+
return next;
|
|
191
|
+
}
|
|
192
|
+
|
|
166
193
|
function applyResolvedDomains(
|
|
167
194
|
program,
|
|
168
195
|
childId,
|
|
@@ -188,19 +215,21 @@ function applyResolvedDomains(
|
|
|
188
215
|
domain
|
|
189
216
|
});
|
|
190
217
|
}
|
|
218
|
+
next = partitionGuideValues(next);
|
|
191
219
|
const marks = materializeMarks ? next.semanticSpec.layers.flatMap(layer => {
|
|
192
220
|
const step = getMarkMaterializationStep(next, layer);
|
|
193
221
|
if (step === undefined) return [];
|
|
194
|
-
return [
|
|
195
|
-
? [{ ...step, args: { ...step.args, scales: false } }]
|
|
196
|
-
: [step];
|
|
222
|
+
return [step];
|
|
197
223
|
}) : [];
|
|
198
224
|
const guides = Object.keys(resolution.scales).flatMap(id =>
|
|
199
225
|
planScaleGuideRematerialization(next, id)
|
|
200
226
|
);
|
|
201
227
|
return applyMaterializationPlan(
|
|
202
228
|
next,
|
|
203
|
-
buildMaterializationPlan({
|
|
229
|
+
buildMaterializationPlan({
|
|
230
|
+
marks: reusePlannedMarkScales(next, marks, Object.keys(resolution.scales)),
|
|
231
|
+
guides
|
|
232
|
+
})
|
|
204
233
|
);
|
|
205
234
|
}
|
|
206
235
|
|
|
@@ -1,38 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isPlainObject } from "../../core/immutable.js";
|
|
3
|
-
import { validateUserId } from "../../core/identifiers.js";
|
|
4
|
-
|
|
5
|
-
import { findTransformPolicy } from "../../grammar/transforms.js";
|
|
6
|
-
|
|
7
|
-
const REPLAY_OPTIONS = Object.freeze(["id", "source", "transform"]);
|
|
8
|
-
|
|
9
|
-
function requestedTransform(transform) {
|
|
10
|
-
if (!isPlainObject(transform)) {
|
|
11
|
-
throw new TypeError("replayDerivedData transform must be a plain object.");
|
|
12
|
-
}
|
|
13
|
-
const policy = findTransformPolicy(transform.type);
|
|
14
|
-
if (policy?.facetTopology === undefined) {
|
|
15
|
-
throw new Error(
|
|
16
|
-
`replayDerivedData does not support transform "${transform.type ?? "unknown"}".`
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
return {
|
|
20
|
-
transform: policy.replayTransform?.(transform) ?? transform,
|
|
21
|
-
materialize: policy.materializeOp
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const replayDerivedData = /* @__PURE__ */ closedAction(
|
|
26
|
-
{
|
|
27
|
-
op: "replayDerivedData",
|
|
28
|
-
description: "Replay one stored derived-data transform for a facet cell."
|
|
29
|
-
}, REPLAY_OPTIONS,
|
|
30
|
-
function (args = {}) {
|
|
31
|
-
const id = validateUserId(args.id, "Facet replay dataset id");
|
|
32
|
-
const source = validateUserId(args.source, "Facet replay source id");
|
|
33
|
-
const resolved = requestedTransform(args.transform);
|
|
34
|
-
return this
|
|
35
|
-
.createDerivedData({ id, source, transform: [resolved.transform] })
|
|
36
|
-
[resolved.materialize]({ id });
|
|
37
|
-
}
|
|
38
|
-
);
|
|
1
|
+
import{findRaincloudParent as d}from"../../selectors/raincloud.js";import{closedAction as m}from"../../core/action.js";import{isPlainObject as p}from"../../core/immutable.js";import{validateUserId as s}from"../../core/identifiers.js";import{findTransformPolicy as c}from"../../grammar/transforms.js";import{applyLayerDataRematerialization as l}from"../../materialization/dependencies.js";const y=Object.freeze(["id","source","transform"]);function u(r){if(!p(r))throw new TypeError("replayDerivedData transform must be a plain object.");const e=c(r.type);if(e?.facetTopology===void 0)throw new Error(`replayDerivedData does not support transform "${r.type??"unknown"}".`);return{transform:e.replayTransform?.(r)??r,materialize:e.materializeOp}}const b=m({op:"replayDerivedData",description:"Replay one stored derived-data transform for a facet cell."},y,function(r={}){const e=s(r.id,"Facet replay dataset id"),i=s(r.source,"Facet replay source id"),a=u(r.transform);let t=this;if(a.transform.type==="markFilter"&&d(t,a.transform.target)===void 0){const o=a.transform.target,{markFilter:f,...n}=t.markConfigs[o]??{};t=t.rebindLayerData({id:o,data:i})._withMarkConfig(o,n),t=l(t,o)._withMarkConfig(o,{...n,markFilter:{...f,source:i,dataset:e,empty:!1}})}return t.createDerivedData({id:e,source:i,transform:[a.transform]})[a.materialize]({id:e})});export{b as replayDerivedData};
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
import { resolveGraphicBounds } from "../../layout/canvas.js";
|
|
10
10
|
import { findLayer } from "../../selectors/layers.js";
|
|
11
11
|
import { createDensityLegendPaint } from "./paint.js";
|
|
12
|
+
import { legendResourcePolicy } from "../../materialization/guides/resources.js";
|
|
13
|
+
import { resolveConcreteGraphicBounds } from "../../grammar/schemas/graphicBounds.js";
|
|
12
14
|
|
|
13
15
|
const CENTER_OPTIONS = Object.freeze([
|
|
14
16
|
"id", "owner", "data", "category", "categoryType", "coordinate",
|
|
@@ -94,11 +96,18 @@ export const rematerializeGradientPlotLegend = /* @__PURE__ */ action(
|
|
|
94
96
|
if (position !== "right") {
|
|
95
97
|
throw new Error('Gradient plot density legend currently requires position "right".');
|
|
96
98
|
}
|
|
97
|
-
const
|
|
99
|
+
const right = bounds.x + bounds.width;
|
|
100
|
+
const legendBounds = Object.keys(this.guideConfigs.legend ?? {}).flatMap(kind =>
|
|
101
|
+
legendResourcePolicy(kind).graphicIds.filter(id => this.graphicSpec.objects[id])
|
|
102
|
+
.map(id => resolveConcreteGraphicBounds(this.graphicSpec, id, this.materializationConfigs.textMetrics)))
|
|
103
|
+
.filter(box => box && box.left >= right);
|
|
104
|
+
const x = Math.max(right + 50, ...legendBounds.map(box => box.right + 24));
|
|
98
105
|
const y = bounds.y + 50;
|
|
99
106
|
const width = 22;
|
|
100
107
|
const height = Math.min(170, Math.max(80, bounds.height - 110));
|
|
101
|
-
|
|
108
|
+
const baseline = this.semanticSpec.guides.legend?.color?.scale === `${owner}Density`
|
|
109
|
+
? this.editSemantic({ property: "guide.legend.color", remove: true }) : this;
|
|
110
|
+
let next = editProperties(baseline, ids.strip, {
|
|
102
111
|
x, y, width, height,
|
|
103
112
|
fill: createDensityLegendPaint(config.gradient.opacity),
|
|
104
113
|
stroke: "#cbd5e1",
|
|
@@ -144,14 +153,14 @@ function resolveDensityLegend(program, args) {
|
|
|
144
153
|
}
|
|
145
154
|
|
|
146
155
|
export function fulfillGradientPlotLegend(program, args) {
|
|
147
|
-
const { ids
|
|
148
|
-
|
|
149
|
-
|
|
156
|
+
const { ids } = resolveDensityLegend(program, args);
|
|
157
|
+
if (Object.keys(program.markConfigs).some(owner => owner !== args.owner &&
|
|
158
|
+
program.graphicSpec.objects[`${owner}DensityLegend`])) {
|
|
150
159
|
guideConflict("Gradient plot density legend uses a different scale or family");
|
|
151
160
|
}
|
|
152
161
|
const existing = Object.values(ids).filter(id => program.graphicSpec.objects[id] !== undefined);
|
|
153
162
|
if (existing.length === 0) return program.createGradientPlotLegend(args);
|
|
154
|
-
if (existing.length !== Object.keys(ids).length
|
|
163
|
+
if (existing.length !== Object.keys(ids).length) {
|
|
155
164
|
guideConflict("Gradient plot density legend has incomplete ownership");
|
|
156
165
|
}
|
|
157
166
|
const { owner, ...explicit } = args;
|
|
@@ -170,12 +179,15 @@ export const createGradientPlotLegend = /* @__PURE__ */ action(
|
|
|
170
179
|
for (const [property, value] of [
|
|
171
180
|
[`scale[${densityScale}].type`, "sequential"],
|
|
172
181
|
[`scale[${densityScale}].domain`, this.markConfigs[args.owner].gradientPlot.intensityDomain],
|
|
173
|
-
[`scale[${densityScale}].range`, { palette: { name: "greys" } }]
|
|
174
|
-
["guide.legend.color.scale", densityScale],
|
|
175
|
-
["guide.legend.color.title", title]
|
|
182
|
+
[`scale[${densityScale}].range`, { palette: { name: "greys" } }]
|
|
176
183
|
]) {
|
|
177
184
|
next = next.editSemantic({ property, value });
|
|
178
185
|
}
|
|
186
|
+
// Older snapshots used the color-channel slot for this independent guide.
|
|
187
|
+
// Its existing owner config is sufficient; free that slot for actual hue.
|
|
188
|
+
if (next.semanticSpec.guides.legend?.color?.scale === densityScale) {
|
|
189
|
+
next = next.editSemantic({ property: "guide.legend.color", remove: true });
|
|
190
|
+
}
|
|
179
191
|
for (const [id, type, length] of [
|
|
180
192
|
[ids.strip, "rect"],
|
|
181
193
|
[ids.labels, "text", 2],
|
|
@@ -2,6 +2,7 @@ import { closedAction } from "../../core/action.js";
|
|
|
2
2
|
|
|
3
3
|
import { findDataset } from "../../selectors/datasets.js";
|
|
4
4
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
5
|
+
import { normalizeEncoding } from "../charts/shared.js";
|
|
5
6
|
import {
|
|
6
7
|
GRADIENT_PLOT_OPTIONS,
|
|
7
8
|
gradientEncodingArgs,
|
|
@@ -49,6 +50,7 @@ export const createGradientPlot = /* @__PURE__ */ closedAction(
|
|
|
49
50
|
const width = resolveGradientWidth(args.width);
|
|
50
51
|
const gradient = resolveGradientAppearance(args.gradient);
|
|
51
52
|
const center = resolveGradientCenter(args.center);
|
|
53
|
+
const color = normalizeEncoding(args.color, "createGradientPlot color");
|
|
52
54
|
const guides = resolveGradientGuides(args.guides);
|
|
53
55
|
const category = positions.orientation === "vertical"
|
|
54
56
|
? positions.x
|
|
@@ -70,11 +72,11 @@ export const createGradientPlot = /* @__PURE__ */ closedAction(
|
|
|
70
72
|
width,
|
|
71
73
|
gradient,
|
|
72
74
|
center,
|
|
75
|
+
...(color === undefined ? {} : { color }),
|
|
73
76
|
guides
|
|
74
77
|
},
|
|
75
78
|
itemFilterable: false,
|
|
76
|
-
stroke: false
|
|
77
|
-
strokeWidth: 0
|
|
79
|
+
stroke: false
|
|
78
80
|
});
|
|
79
81
|
if (positions.x !== undefined) {
|
|
80
82
|
next = next.encodeX({
|
|
@@ -9,6 +9,7 @@ import { removeOwnedMark } from "../marks/remove.js";
|
|
|
9
9
|
import { GRADIENT_PROFILE_FIELDS } from "../../grammar/gradientProfile.js";
|
|
10
10
|
import { requestedStrokeDetails } from "../../grammar/strokeStyle.js";
|
|
11
11
|
import { removeGradientPlotLegend } from "./components.js";
|
|
12
|
+
import { rematerializeExistingLegend } from "../encodings/shared.js";
|
|
12
13
|
import {
|
|
13
14
|
resolveDistributionRoles,
|
|
14
15
|
setCartesianPosition,
|
|
@@ -191,6 +192,22 @@ export const editGradientPlot = /* @__PURE__ */ closedAction(
|
|
|
191
192
|
intensityDomain: profile.transform[0].resolved.intensityDomain
|
|
192
193
|
}
|
|
193
194
|
});
|
|
195
|
+
// Hue assigned to the old category follows the category role. Internal
|
|
196
|
+
// profile fields keep their stable names across statistical revisions.
|
|
197
|
+
if (candidate.category !== current.category) {
|
|
198
|
+
for (const channel of ["color", "stroke"]) {
|
|
199
|
+
const binding = owner.encoding?.[channel];
|
|
200
|
+
if (binding?.field !== current.category ||
|
|
201
|
+
!["nominal", "ordinal"].includes(binding.fieldType)) continue;
|
|
202
|
+
next = next.editSemantic({
|
|
203
|
+
property: `layer[${owner.id}].encoding.${channel}.field`,
|
|
204
|
+
value: candidate.category
|
|
205
|
+
}).editSemantic({
|
|
206
|
+
property: `layer[${owner.id}].encoding.${channel}.fieldType`,
|
|
207
|
+
value: candidate.categoryType
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
194
211
|
if (center === false && hadCenter) {
|
|
195
212
|
next = removeCenter(next, current.centerId);
|
|
196
213
|
}
|
|
@@ -252,7 +269,7 @@ export const editGradientPlot = /* @__PURE__ */ closedAction(
|
|
|
252
269
|
});
|
|
253
270
|
}
|
|
254
271
|
next = next.releaseDerivedData(revision.release);
|
|
255
|
-
return next._withContext({
|
|
272
|
+
return rematerializeExistingLegend(next)._withContext({
|
|
256
273
|
currentMark: owner.id,
|
|
257
274
|
currentData: candidate.source
|
|
258
275
|
});
|
|
@@ -5,6 +5,8 @@ import { validateUserId } from "../../core/identifiers.js";
|
|
|
5
5
|
import { isPlainObject } from "../../core/immutable.js";
|
|
6
6
|
import { GRADIENT_PROFILE_FIELDS } from "../../grammar/gradientProfile.js";
|
|
7
7
|
import { requestedStrokeDetails } from "../../grammar/strokeStyle.js";
|
|
8
|
+
import { materializeRectItem } from "../../grammar/roundedRect.js";
|
|
9
|
+
import { replaceGraphicItems } from "../primitives/editGraphics.js";
|
|
8
10
|
import {
|
|
9
11
|
mapContinuousScaleValues,
|
|
10
12
|
mapOrdinalPositionValues
|
|
@@ -16,7 +18,7 @@ import { findDataset } from "../../selectors/datasets.js";
|
|
|
16
18
|
import { findLayer } from "../../selectors/layers.js";
|
|
17
19
|
import {
|
|
18
20
|
createGradientPlotPaint,
|
|
19
|
-
|
|
21
|
+
resolveGradientPlotAppearanceValues
|
|
20
22
|
} from "./paint.js";
|
|
21
23
|
import { resolveGradientOrientation } from "./resolve.js";
|
|
22
24
|
|
|
@@ -73,11 +75,9 @@ function bodyItems(program, layer, dataset, config) {
|
|
|
73
75
|
rows.map(row => row[GRADIENT_PROFILE_FIELDS.upper]),
|
|
74
76
|
measureScale
|
|
75
77
|
);
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
layer,
|
|
79
|
-
categories
|
|
80
|
-
);
|
|
78
|
+
const appearance = program.markConfigs[layer.id];
|
|
79
|
+
const mapped = Object.fromEntries(["color", "opacity", "stroke", "strokeWidth"]
|
|
80
|
+
.map(channel => [channel, resolveGradientPlotAppearanceValues(program, layer, rows, channel)]));
|
|
81
81
|
const thickness = categoryScale.bandwidth * config.width.band;
|
|
82
82
|
if (!Number.isFinite(thickness) || thickness <= 0) {
|
|
83
83
|
throw new Error(`Gradient plot "${layer.id}" requires a positive band width.`);
|
|
@@ -98,28 +98,27 @@ function bodyItems(program, layer, dataset, config) {
|
|
|
98
98
|
width: Math.abs(a - b),
|
|
99
99
|
height: thickness
|
|
100
100
|
};
|
|
101
|
-
return {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
stroke:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
};
|
|
101
|
+
return materializeRectItem({
|
|
102
|
+
...geometry,
|
|
103
|
+
fill: createGradientPlotPaint({
|
|
104
|
+
values: row[GRADIENT_PROFILE_FIELDS.values],
|
|
105
|
+
intensities: row[GRADIENT_PROFILE_FIELDS.intensities]
|
|
106
|
+
}, {
|
|
107
|
+
orientation: config.orientation,
|
|
108
|
+
valueScale: measureScale,
|
|
109
|
+
intensityDomain: config.intensityDomain,
|
|
110
|
+
palette: config.gradient.palette,
|
|
111
|
+
opacity: config.gradient.opacity,
|
|
112
|
+
...(mapped.color === undefined
|
|
113
|
+
? appearance.fillExplicit ? { baseColor: appearance.fill } : {}
|
|
114
|
+
: { baseColor: mapped.color[index] })
|
|
115
|
+
}),
|
|
116
|
+
opacity: mapped.opacity?.[index] ?? appearance.opacity ?? 1,
|
|
117
|
+
stroke: mapped.stroke?.[index] ?? (appearance.stroke === false ? "transparent" : appearance.stroke ?? "transparent"),
|
|
118
|
+
strokeWidth: mapped.strokeWidth?.[index] ??
|
|
119
|
+
(mapped.stroke === undefined && appearance.stroke === false ? 0 : appearance.strokeWidth ?? 1),
|
|
120
|
+
...requestedStrokeDetails(appearance, "Gradient plot strip")
|
|
121
|
+
}, appearance);
|
|
123
122
|
});
|
|
124
123
|
}
|
|
125
124
|
|
|
@@ -128,7 +127,7 @@ export const materializeGradientPlotFill = /* @__PURE__ */ action(
|
|
|
128
127
|
op: "materializeGradientPlotFill",
|
|
129
128
|
description: "Materialize categorical density profiles as gradient strips."
|
|
130
129
|
},
|
|
131
|
-
function ({ id } = {}) {
|
|
130
|
+
function ({ id, scales = true } = {}) {
|
|
132
131
|
const owner = validateUserId(id, "Gradient-plot id");
|
|
133
132
|
const layer = findLayer(this, owner);
|
|
134
133
|
const config = this.markConfigs[owner]?.gradientPlot;
|
|
@@ -142,17 +141,14 @@ export const materializeGradientPlotFill = /* @__PURE__ */ action(
|
|
|
142
141
|
const scaleIds = [...new Set([
|
|
143
142
|
layer.encoding?.x?.scale,
|
|
144
143
|
layer.encoding?.y?.scale,
|
|
145
|
-
layer.encoding?.
|
|
144
|
+
...["color", "opacity", "stroke", "strokeWidth"].map(channel => layer.encoding?.[channel]?.scale)
|
|
146
145
|
].filter(Boolean))];
|
|
147
146
|
let next = this;
|
|
148
|
-
for (const scale of scaleIds) {
|
|
147
|
+
for (const scale of scales ? scaleIds : []) {
|
|
149
148
|
next = next.rematerializeScale({ id: scale, marks: false, guides: false });
|
|
150
149
|
}
|
|
151
|
-
next = next
|
|
152
|
-
|
|
153
|
-
property: "items",
|
|
154
|
-
value: bodyItems(next, findLayer(next, owner), dataset, config)
|
|
155
|
-
});
|
|
150
|
+
next = replaceGraphicItems(next, owner, "rect",
|
|
151
|
+
bodyItems(next, findLayer(next, owner), dataset, config));
|
|
156
152
|
next = applyMaterializationPlan(
|
|
157
153
|
next,
|
|
158
154
|
buildMaterializationPlan({
|
|
@@ -253,6 +249,9 @@ export const materializeGradientPlot = /* @__PURE__ */ action(
|
|
|
253
249
|
...requestedStrokeDetails(config.center, "Gradient plot center")
|
|
254
250
|
});
|
|
255
251
|
}
|
|
252
|
+
if (config.color !== undefined) {
|
|
253
|
+
next = next.encodeColor({ ...config.color, target: owner });
|
|
254
|
+
}
|
|
256
255
|
const guides = config.guides;
|
|
257
256
|
if (guides !== false) {
|
|
258
257
|
const wantsStandard = guides.axes !== false || guides.grid !== false;
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
|
|
13
13
|
export const GRADIENT_PLOT_OPTIONS = Object.freeze([
|
|
14
14
|
"id", "target", "data", "x", "y", "coordinate", "density", "width",
|
|
15
|
-
"gradient", "center", "guides"
|
|
15
|
+
"gradient", "center", "color", "guides"
|
|
16
16
|
]);
|
|
17
17
|
|
|
18
18
|
function plain(value, keys, label, operation) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { resolveContinuousPalette } from "../../grammar/palettes.js";
|
|
2
2
|
import { inverseLerp } from "../../grammar/numeric.js";
|
|
3
3
|
import { mapScaleConsumerValues } from "../../materialization/scales/map.js";
|
|
4
|
+
import { readScaleField } from "../../grammar/scales/index.js";
|
|
5
|
+
import { resolveSequentialColorStops } from "../../grammar/scales/color.js";
|
|
4
6
|
|
|
5
7
|
function parseHex(value) {
|
|
6
8
|
const match = value.match(/^#([0-9a-f]{6})$/i);
|
|
@@ -31,21 +33,25 @@ function paletteColor(colors, amount) {
|
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
function categoricalRamp(base) {
|
|
34
|
-
const
|
|
36
|
+
const normalized = resolveSequentialColorStops([base, base])[0];
|
|
37
|
+
const rgb = parseHex(normalized);
|
|
35
38
|
const pale = interpolate([255, 255, 255], rgb, 0.2);
|
|
36
39
|
return [
|
|
37
40
|
`#${pale.map(value => value.toString(16).padStart(2, "0")).join("")}`,
|
|
38
|
-
|
|
41
|
+
normalized
|
|
39
42
|
];
|
|
40
43
|
}
|
|
41
44
|
|
|
42
|
-
export function
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
export function resolveGradientPlotAppearanceValues(program, layer, rows, channel) {
|
|
46
|
+
const encoding = layer.encoding?.[channel];
|
|
47
|
+
if (encoding === undefined) return undefined;
|
|
48
|
+
const scale = program.resolvedScales[encoding.scale];
|
|
45
49
|
if (scale === undefined) {
|
|
46
|
-
throw new Error(`Gradient plot "${layer.id}" requires a resolved
|
|
50
|
+
throw new Error(`Gradient plot "${layer.id}" requires a resolved ${channel} scale.`);
|
|
47
51
|
}
|
|
48
|
-
return mapScaleConsumerValues(
|
|
52
|
+
return mapScaleConsumerValues(readScaleField(rows, encoding.field, encoding.fieldType, {
|
|
53
|
+
temporalUnit: encoding.temporalUnit
|
|
54
|
+
}), scale, channel);
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
export function createGradientPlotPaint(profile, {
|
|
@@ -25,6 +25,8 @@ function hasEncoding(program, layers, channel, { scaled = false, grid = false }
|
|
|
25
25
|
return layers.some(layer => {
|
|
26
26
|
if (isHorizonLayer(program, layer) && channel !== "x") return false;
|
|
27
27
|
const encoding = layer.encoding?.[channel];
|
|
28
|
+
if (encoding?.datum !== undefined && encoding.field === undefined &&
|
|
29
|
+
layer.encoding?.[`${channel}2`]?.field === undefined) return false;
|
|
28
30
|
if (!scaled && !grid) return encoding !== undefined;
|
|
29
31
|
if (encoding?.scale === undefined) return false;
|
|
30
32
|
if (!grid || POLAR_POSITION_CHANNELS.includes(channel)) return true;
|
|
@@ -5,6 +5,7 @@ import { isPlainObject } from "../../../core/immutable.js";
|
|
|
5
5
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
6
6
|
import { validateKeys, validateOptionObject } from "../../../core/validation.js";
|
|
7
7
|
import { findCoordinate } from "../../../selectors/coordinates.js";
|
|
8
|
+
import { resolveParallelAxisTarget } from "./parallel/resolve.js";
|
|
8
9
|
import { resolvePolarGuideResources } from "../polar/resolve.js";
|
|
9
10
|
|
|
10
11
|
const TOP_OPTIONS = Object.freeze([
|
|
@@ -326,6 +327,44 @@ const createAxes = /* @__PURE__ */ action(
|
|
|
326
327
|
}
|
|
327
328
|
);
|
|
328
329
|
|
|
330
|
+
// Aggregate existing component editors; omission/false preserves that axis.
|
|
331
|
+
const editAxes = /* @__PURE__ */ action(
|
|
332
|
+
{ op: "editAxes", description: "Edit selected existing axes in one atomic layout transaction." },
|
|
333
|
+
function (args = {}) {
|
|
334
|
+
const methods = { x: "editXAxis", y: "editYAxis", theta: "editThetaAxis",
|
|
335
|
+
radius: "editRadialAxis", parallel: "editParallelAxis" };
|
|
336
|
+
validateOptionObject(args, Object.keys(methods), "editAxes", { allowEmpty: false });
|
|
337
|
+
const plan = [];
|
|
338
|
+
for (const [channel, method] of Object.entries(methods)) {
|
|
339
|
+
const option = args[channel];
|
|
340
|
+
if (option === undefined || option === false) continue;
|
|
341
|
+
const entries = Array.isArray(option) ? option : [option];
|
|
342
|
+
if (entries.length === 0 || entries.some(entry => !isPlainObject(entry))) {
|
|
343
|
+
throw new TypeError(`editAxes ${channel} must be a plain object or a nonempty array of edit options.`);
|
|
344
|
+
}
|
|
345
|
+
if (Array.isArray(option) && ["theta", "radius"].includes(channel)) {
|
|
346
|
+
throw new TypeError(`editAxes ${channel} does not accept an array.`);
|
|
347
|
+
}
|
|
348
|
+
const targets = new Set();
|
|
349
|
+
for (const entry of entries) {
|
|
350
|
+
if (Array.isArray(option) && ["x", "y"].includes(channel) && entry.target === undefined) {
|
|
351
|
+
throw new Error(`editAxes ${channel} arrays require an explicit axis target for every entry.`);
|
|
352
|
+
}
|
|
353
|
+
const key = channel === "parallel" ? `${resolveParallelAxisTarget(this, entry.target ?? this.guideConfigs.axis?.parallel?.axes?.target)}:${entry.field}` : entry.target ?? channel;
|
|
354
|
+
if (targets.has(key)) throw new Error(`editAxes ${channel} contains a duplicate axis target.`);
|
|
355
|
+
targets.add(key);
|
|
356
|
+
plan.push({ method, args: entry });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (plan.length === 0) throw new Error("editAxes requires at least one axis change.");
|
|
360
|
+
const apply = source => plan.reduce((program, step) => program[step.method](step.args), source);
|
|
361
|
+
// Preflight the full immutable proposal before beginning the returned branch.
|
|
362
|
+
withGuideLayoutTransaction(this, apply);
|
|
363
|
+
return withGuideLayoutTransaction(this, apply);
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
|
|
329
367
|
export function registerAxisCollectionActions(ProgramClass) {
|
|
330
368
|
ProgramClass.prototype.createAxes = createAxes;
|
|
369
|
+
ProgramClass.prototype.editAxes = editAxes;
|
|
331
370
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AXIS_RESOURCE_CHANNELS } from "../../../core/vocabulary.js";
|
|
2
2
|
import { cartesianAxisOwners, axisTargetArgs, assertCartesianAxisSideAvailable } from "../../../selectors/axes.js";
|
|
3
3
|
import { axisGraphicIds } from "../../../materialization/guides/resources.js";
|
|
4
|
-
import { defaultAxisPosition, validateAxisPosition } from "./policy.js";
|
|
4
|
+
import { defaultAxisPosition, validateAxisPosition, resolveAxisFrameConfig } from "./policy.js";
|
|
5
5
|
import { isSourceOwnedText } from "../../../grammar/text.js";
|
|
6
6
|
import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
|
|
7
7
|
import { action } from "../../../core/action.js";
|
|
@@ -21,12 +21,13 @@ const TOP_OPTIONS = Object.freeze([
|
|
|
21
21
|
"line",
|
|
22
22
|
"ticksAndLabels",
|
|
23
23
|
"title",
|
|
24
|
-
"offset"
|
|
24
|
+
"offset", "underlay"
|
|
25
25
|
]);
|
|
26
26
|
const LINE_OPTIONS = Object.freeze(["color", "lineWidth"]);
|
|
27
27
|
const TICK_GROUP_OPTIONS = Object.freeze([
|
|
28
28
|
"count",
|
|
29
29
|
"values",
|
|
30
|
+
"rounding",
|
|
30
31
|
"ticks",
|
|
31
32
|
"labels"
|
|
32
33
|
]);
|
|
@@ -39,7 +40,7 @@ const TITLE_OPTIONS = Object.freeze([
|
|
|
39
40
|
"fontSize",
|
|
40
41
|
"fontFamily",
|
|
41
42
|
"fontWeight",
|
|
42
|
-
"alongOffset", "align", "baseline"
|
|
43
|
+
"alongOffset", "align", "baseline", "overlap"
|
|
43
44
|
]);
|
|
44
45
|
|
|
45
46
|
function validateNested(value, supported, label) {
|
|
@@ -53,6 +54,7 @@ function validateNested(value, supported, label) {
|
|
|
53
54
|
export function validateAxisArgs(args, operation) {
|
|
54
55
|
validateKeys(args, TOP_OPTIONS, operation);
|
|
55
56
|
if (Object.hasOwn(args, "offset")) validateNonNegativeFinite(args.offset, "Axis frame offset");
|
|
57
|
+
resolveAxisFrameConfig(undefined, args);
|
|
56
58
|
|
|
57
59
|
validateEnabledAxisComponents(args, operation);
|
|
58
60
|
if (Object.hasOwn(args, "line") && args.line !== false) {
|
|
@@ -136,8 +138,10 @@ function makeCreateAxis(channel) {
|
|
|
136
138
|
const shared = axisTargetArgs(axisId, channel);
|
|
137
139
|
if (Object.hasOwn(args, "scale")) shared.scale = args.scale;
|
|
138
140
|
if (Object.hasOwn(args, "position")) shared.position = args.position;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
+
const frame = resolveAxisFrameConfig(source.guideConfigs.axis?.[axisId]?.frame, args);
|
|
142
|
+
let next = frame === undefined
|
|
143
|
+
? source._withoutMaterializationConfig(["guides", "axis", axisId, "frame"])
|
|
144
|
+
: source._withGuideConfig(axisId, "frame", frame);
|
|
141
145
|
|
|
142
146
|
if (Object.hasOwn(args, "coordinate")) {
|
|
143
147
|
const coordinate = validateUserId(args.coordinate, "Coordinate id");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { selectCartesianAxis, axisTargetArgs, assertCartesianAxisSideAvailable } from "../../../selectors/axes.js";
|
|
2
|
-
import { validateAxisPosition } from "./policy.js";
|
|
2
|
+
import { validateAxisPosition, resolveAxisFrameConfig } from "./policy.js";
|
|
3
3
|
import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
|
|
4
4
|
import { action } from "../../../core/action.js";
|
|
5
5
|
import { isPlainObject } from "../../../core/immutable.js";
|
|
@@ -14,21 +14,21 @@ import {
|
|
|
14
14
|
} from "./components.js";
|
|
15
15
|
|
|
16
16
|
const OPTIONS = Object.freeze([
|
|
17
|
-
"target", "position", "line", "ticks", "labels", "ticksAndLabels", "title", "offset"
|
|
17
|
+
"target", "position", "line", "ticks", "labels", "ticksAndLabels", "title", "offset", "underlay"
|
|
18
18
|
]);
|
|
19
19
|
const LINE_OPTIONS = Object.freeze(["color", "lineWidth"]);
|
|
20
20
|
const TICK_OPTIONS = Object.freeze([
|
|
21
|
-
"count", "values", "length", "color", "lineWidth", "alongOffset", "strokeDash", "strokeDashMap"
|
|
21
|
+
"count", "values", "rounding", "length", "color", "lineWidth", "alongOffset", "strokeDash", "strokeDashMap"
|
|
22
22
|
]);
|
|
23
23
|
const LABEL_OPTIONS = Object.freeze([
|
|
24
|
-
"count", "values", "offset", "format", "color", "fontSize",
|
|
24
|
+
"count", "values", "rounding", "offset", "format", "color", "fontSize",
|
|
25
25
|
"fontFamily", "fontWeight", "rotation", "maxWidth", "wrap",
|
|
26
26
|
"lineHeight", "overlap", "alongOffset", "labelMap", "flush", "colorMap", "align"
|
|
27
27
|
]);
|
|
28
|
-
const GROUP_OPTIONS = Object.freeze(["count", "values", "ticks", "labels"]);
|
|
28
|
+
const GROUP_OPTIONS = Object.freeze(["count", "values", "rounding", "ticks", "labels"]);
|
|
29
29
|
const TITLE_OPTIONS = Object.freeze([
|
|
30
30
|
"text", "at", "offset", "rotation", "color", "fontSize",
|
|
31
|
-
"fontFamily", "fontWeight", "alongOffset", "align", "baseline"
|
|
31
|
+
"fontFamily", "fontWeight", "alongOffset", "align", "baseline", "overlap"
|
|
32
32
|
]);
|
|
33
33
|
|
|
34
34
|
function validateNested(args, key, options, operation, { allowFalse = false } = {}) {
|
|
@@ -43,6 +43,7 @@ function validateNested(args, key, options, operation, { allowFalse = false } =
|
|
|
43
43
|
|
|
44
44
|
function validateArgs(args, operation) {
|
|
45
45
|
if (Object.hasOwn(args, "offset")) validateNonNegativeFinite(args.offset, "Axis frame offset");
|
|
46
|
+
resolveAxisFrameConfig(undefined, args);
|
|
46
47
|
validateOptionObject(args, OPTIONS, operation, {
|
|
47
48
|
allowEmpty: false,
|
|
48
49
|
emptyMessage: `${operation} requires at least one axis change.`,
|
|
@@ -96,7 +97,7 @@ function buildPlan(program, channel, args, operation, axisId = channel) {
|
|
|
96
97
|
plan.push({ kind: "remove", component });
|
|
97
98
|
} else if (value !== undefined) {
|
|
98
99
|
plan.push({ kind: "edit", operation: edit, args: { ...shared, ...value } });
|
|
99
|
-
} else if ((args.position !== undefined || Object.hasOwn(args, "offset")) && hasComponent(program, channel, component, axisId)) {
|
|
100
|
+
} else if ((args.position !== undefined || Object.hasOwn(args, "offset") || Object.hasOwn(args, "underlay")) && hasComponent(program, channel, component, axisId)) {
|
|
100
101
|
plan.push({ kind: "edit", operation: edit, args: shared });
|
|
101
102
|
}
|
|
102
103
|
};
|
|
@@ -165,7 +166,9 @@ function makeEditAxis(channel) {
|
|
|
165
166
|
coordinate: selected.owner.coordinate, position });
|
|
166
167
|
source = source.editSemantic({ property: `guide.axis.${axisId}.position`, value: position });
|
|
167
168
|
}
|
|
168
|
-
|
|
169
|
+
const frame = resolveAxisFrameConfig(source.guideConfigs.axis?.[axisId]?.frame, args);
|
|
170
|
+
source = frame === undefined ? source._withoutMaterializationConfig(["guides", "axis", axisId, "frame"])
|
|
171
|
+
: source._withGuideConfig(axisId, "frame", frame);
|
|
169
172
|
const plan = buildPlan(source, channel, args, operation, axisId);
|
|
170
173
|
|
|
171
174
|
// Run the complete proposal against an immutable speculative branch so a
|