ggaction 0.0.15 → 0.0.16
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 +68 -0
- package/README.md +22 -2
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-packet.schema.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +33 -6
- package/src/BasicChartProgram.js +3 -1
- package/src/ChartProgram.js +2 -0
- package/src/accessibility.js +157 -0
- package/src/actions/boxPlots/components.js +6 -8
- package/src/actions/boxPlots/create.js +4 -5
- package/src/actions/boxPlots/edit.js +3 -4
- package/src/actions/boxPlots/materialize.js +1 -1
- package/src/actions/canvas/actions.js +3 -3
- package/src/actions/canvas/fitting.js +1 -1
- package/src/actions/categoryOrder/index.js +6 -8
- package/src/actions/charts/area.js +1 -1
- package/src/actions/charts/bar.js +1 -1
- package/src/actions/charts/beeswarm.js +1 -1
- package/src/actions/charts/density.js +1 -1
- package/src/actions/charts/ecdf.js +2 -2
- package/src/actions/charts/endpoints.js +4 -4
- 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/interval-regression.js +2 -2
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/parallel.js +1 -1
- package/src/actions/charts/pie.js +1 -1
- package/src/actions/charts/polar.js +2 -2
- package/src/actions/charts/radar.js +1 -1
- package/src/actions/charts/radial.js +2 -2
- package/src/actions/charts/raincloud.js +2 -2
- package/src/actions/charts/rug-strip.js +2 -2
- package/src/actions/charts/scatter.js +1 -1
- package/src/actions/composition/actions.js +20 -24
- package/src/actions/coordinates/actions.js +1 -1
- package/src/actions/coordinates/edit.js +1 -1
- package/src/actions/data/basicBin2d.js +4 -5
- package/src/actions/data/bin.js +2 -2
- package/src/actions/data/bin2d.js +6 -8
- package/src/actions/data/bin2dMaterialize.js +4 -5
- package/src/actions/data/box.js +4 -4
- package/src/actions/data/complete.js +2 -2
- package/src/actions/data/computed.js +2 -2
- package/src/actions/data/create.js +5 -12
- package/src/actions/data/density.js +3 -3
- package/src/actions/data/derived.js +10 -14
- package/src/actions/data/ecdf.js +2 -2
- package/src/actions/data/edit.js +35 -23
- package/src/actions/data/filter.js +14 -20
- package/src/actions/data/fold.js +2 -2
- package/src/actions/data/gradientProfile.js +2 -2
- package/src/actions/data/horizon.js +2 -2
- package/src/actions/data/impute.js +2 -2
- package/src/actions/data/index.js +55 -54
- package/src/actions/data/interval.js +2 -2
- package/src/actions/data/intervalEdit.js +26 -18
- package/src/actions/data/normalize.js +2 -2
- package/src/actions/data/regression.js +5 -6
- package/src/actions/data/revise.js +48 -0
- package/src/actions/data/revisionBindings.js +13 -33
- package/src/actions/data/stack.js +2 -2
- package/src/actions/data/summary.js +2 -2
- package/src/actions/data/timeUnit.js +2 -2
- package/src/actions/data/window.js +2 -2
- package/src/actions/encodings/angle.js +1 -1
- package/src/actions/encodings/appearance.js +4 -4
- package/src/actions/encodings/barWidth.js +1 -1
- package/src/actions/encodings/channels.js +2 -2
- package/src/actions/encodings/color/index.js +1 -1
- package/src/actions/encodings/density.js +2 -2
- package/src/actions/encodings/histogram.js +1 -1
- package/src/actions/encodings/horizon.js +6 -8
- package/src/actions/encodings/offset.js +2 -2
- package/src/actions/encodings/parallel.js +4 -5
- package/src/actions/encodings/pathOrder.js +6 -11
- package/src/actions/encodings/position/index.js +4 -4
- package/src/actions/encodings/ranged.js +5 -5
- package/src/actions/encodings/remove.js +4 -5
- package/src/actions/encodings/ruleAppearance.js +1 -1
- package/src/actions/encodings/seriesLayout.js +1 -1
- package/src/actions/encodings/stroke.js +1 -1
- package/src/actions/encodings/strokeDash.js +2 -2
- package/src/actions/encodings/text.js +1 -1
- package/src/actions/errorBands/create.js +11 -17
- package/src/actions/errorBands/edit.js +13 -131
- package/src/actions/errorBars/create.js +8 -13
- package/src/actions/errorBars/edit.js +10 -121
- package/src/actions/facets/actions.js +30 -16
- package/src/actions/facets/derive.js +8 -2
- package/src/actions/facets/guides.js +4 -5
- package/src/actions/facets/replay.js +4 -5
- package/src/actions/gradientPlots/components.js +5 -6
- package/src/actions/gradientPlots/create.js +4 -5
- package/src/actions/gradientPlots/edit.js +4 -5
- package/src/actions/gradientPlots/materialize.js +2 -2
- package/src/actions/gradientPlots/rebind.js +4 -5
- package/src/actions/guides/axes/axes.js +23 -4
- package/src/actions/guides/axes/axis.js +2 -2
- package/src/actions/guides/axes/edit.js +2 -2
- package/src/actions/guides/axes/labels.js +9 -9
- package/src/actions/guides/axes/lines.js +9 -7
- package/src/actions/guides/axes/parallel/lifecycle.js +13 -13
- package/src/actions/guides/axes/parallel.js +5 -5
- package/src/actions/guides/axes/remove.js +4 -4
- package/src/actions/guides/axes/tickGroups.js +4 -4
- package/src/actions/guides/axes/ticks.js +13 -4
- package/src/actions/guides/axes/titles.js +10 -10
- package/src/actions/guides/grids/grid.js +18 -29
- package/src/actions/guides/guides.js +1 -1
- package/src/actions/guides/legends/blocks.js +1 -1
- package/src/actions/guides/legends/categorical/actions.js +4 -4
- package/src/actions/guides/legends/categorical/components.js +38 -33
- package/src/actions/guides/legends/categorical/layout.js +1 -1
- package/src/actions/guides/legends/categorical/symbols.js +27 -30
- package/src/actions/guides/legends/continuous/common.js +46 -8
- package/src/actions/guides/legends/continuous/gradient.js +89 -155
- package/src/actions/guides/legends/continuous/interval.js +13 -13
- package/src/actions/guides/legends/continuous/opacity.js +30 -79
- package/src/actions/guides/legends/continuous/stroke.js +22 -322
- package/src/actions/guides/legends/edit.js +1 -1
- package/src/actions/guides/legends/focused.js +5 -5
- package/src/actions/guides/legends/lane.js +6 -6
- package/src/actions/guides/legends/remove.js +4 -5
- package/src/actions/guides/legends/size.js +21 -82
- package/src/actions/guides/legends/strokeWidth.js +17 -77
- package/src/actions/guides/polar/axes/facade.js +4 -4
- package/src/actions/guides/polar/axes/labels.js +5 -5
- package/src/actions/guides/polar/axes/lines.js +9 -15
- package/src/actions/guides/polar/axes/ticks.js +9 -15
- package/src/actions/guides/polar/axes/titles.js +5 -5
- package/src/actions/guides/polar/grids.js +11 -13
- package/src/actions/index.js +2 -0
- package/src/actions/intervals/revision.js +126 -0
- package/src/actions/marks/arc/actions.js +3 -3
- package/src/actions/marks/area/actions.js +4 -4
- package/src/actions/marks/bar/create.js +1 -1
- package/src/actions/marks/bar/edit.js +1 -1
- package/src/actions/marks/bar/materialize.js +12 -38
- package/src/actions/marks/line/actions.js +3 -3
- package/src/actions/marks/point/create.js +1 -1
- package/src/actions/marks/point/edit.js +1 -1
- package/src/actions/marks/point/jitter.js +6 -8
- package/src/actions/marks/point/materialize.js +16 -24
- package/src/actions/marks/point/packing.js +6 -8
- package/src/actions/marks/rect/actions.js +3 -3
- package/src/actions/marks/references.js +8 -10
- package/src/actions/marks/remove.js +5 -49
- package/src/actions/marks/rule/actions.js +4 -4
- package/src/actions/marks/text/actions.js +5 -5
- package/src/actions/marks/text/layout.js +12 -20
- package/src/actions/marks/text/removal.js +1 -1
- package/src/actions/marks/text/selection.js +4 -5
- package/src/actions/marks/tick/actions.js +3 -3
- package/src/actions/primitives/createGraphics.js +5 -4
- package/src/actions/primitives/editGraphics.js +5 -4
- package/src/actions/primitives/graphicProperties.js +6 -0
- package/src/actions/primitives/semantic.js +10 -3
- package/src/actions/primitives/semanticAction.js +7 -4
- package/src/actions/primitives/semanticValidation/dataset.js +2 -4
- package/src/actions/regression/components.js +10 -14
- package/src/actions/regression/create.js +3 -4
- package/src/actions/regression/edit.js +3 -4
- package/src/actions/resources/remove.js +12 -11
- package/src/actions/scales/create.js +1 -1
- package/src/actions/scales/edit.js +1 -1
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/offset.js +2 -2
- package/src/actions/scales/parallel.js +1 -1
- package/src/actions/scales/quantitativeColor.js +4 -5
- package/src/actions/selection/actions.js +25 -36
- package/src/actions/textMetrics/index.js +56 -0
- package/src/actions/theme/actions.js +2 -2
- package/src/actions/theme/reconcile.js +65 -121
- package/src/actions/titles/actions.js +8 -8
- package/src/actions/titles/resolve.js +3 -3
- package/src/actions/violinPlots/create.js +1 -1
- package/src/actions/violinPlots/edit.js +1 -1
- package/src/core/action.js +107 -35
- package/src/core/diagnostics.js +16 -0
- package/src/core/extensionRegistry.js +27 -0
- package/src/core/font.js +18 -0
- package/src/core/identifiers.js +5 -4
- package/src/core/immutable.js +10 -3
- package/src/core/optionRequirements.js +8 -0
- package/src/core/programState.js +1 -1
- package/src/core/resourceReferences.js +15 -6
- package/src/core/textMetricProfile.js +28 -0
- package/src/core/textMetrics.js +24 -3
- package/src/core/validation.js +22 -10
- package/src/diagnostics.js +1 -0
- package/src/grammar/impute.js +3 -2
- package/src/grammar/scales/types.js +8 -0
- package/src/grammar/schemas/graphicBounds.js +11 -11
- package/src/layout/labels.js +11 -10
- package/src/layout/legend.js +4 -4
- package/src/layout/legendItems.js +6 -6
- package/src/layout/text.js +10 -9
- package/src/layout/title.js +9 -9
- package/src/materialization/facetGuides/placement.js +1 -1
- package/src/materialization/facetGuides/preparation.js +1 -1
- package/src/materialization/facetHeaders.js +11 -10
- package/src/materialization/guides/layout.js +3 -3
- package/src/materialization/scaleGuideDependencies.js +10 -0
- package/src/materialization/selection/items/common.js +1 -1
- package/src/materialization/text.js +2 -2
- package/src/materialization/typography.js +31 -0
- package/src/mcp/cli.js +5 -3
- package/src/persistence/codec.js +65 -0
- package/src/persistence/validation.js +185 -0
- package/src/persistence.js +74 -0
- package/src/renderers/canvas/index.js +5 -4
- package/src/renderers/nativeBackend.js +10 -0
- package/src/renderers/options.js +13 -0
- package/src/renderers/pdf.js +25 -54
- package/src/renderers/png.js +22 -17
- package/src/renderers/svg.js +10 -14
- package/src/renderers/text.js +1 -11
- package/src/selectors/coordinates.js +3 -1
- package/src/selectors/datasets.js +8 -6
- package/src/selectors/layers.js +6 -4
- package/src/selectors/markOwners.js +46 -0
- package/src/selectors/scales.js +6 -4
- package/src/theme/defaults.js +2 -1
- package/src/version.js +2 -0
- package/types/accessibility.d.ts +37 -0
- package/types/basic.d.ts +4 -2
- package/types/diagnostics.d.ts +15 -0
- package/types/index.d.ts +5 -0
- package/types/pdf.d.ts +9 -0
- package/types/persistence.d.ts +7 -0
- package/types/png.d.ts +9 -0
- package/types/program.d.ts +34 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { action } from "../../../../core/action.js";
|
|
1
|
+
import { action, closedAction } from "../../../../core/action.js";
|
|
2
2
|
import { isPlainObject } from "../../../../core/immutable.js";
|
|
3
3
|
import { validateKeys } from "../../../../core/validation.js";
|
|
4
4
|
import { mapScaleConsumerValues } from "../../../../materialization/scales/map.js";
|
|
@@ -7,6 +7,7 @@ import { DEFAULT_COLORS } from "../../../../theme/defaults.js";
|
|
|
7
7
|
import {
|
|
8
8
|
assertLegendBoundsInsideCanvas,
|
|
9
9
|
editLegendBackground,
|
|
10
|
+
editGraphicProperties,
|
|
10
11
|
formatContinuousValues,
|
|
11
12
|
normalizeContinuousLegend,
|
|
12
13
|
requireResolvedLegendScale,
|
|
@@ -26,7 +27,7 @@ export const DEFAULT_GRADIENT_SIZE = Object.freeze({ length: 120, thickness: 12
|
|
|
26
27
|
|
|
27
28
|
const GRADIENT_OPTIONS = Object.freeze(["length", "thickness"]);
|
|
28
29
|
|
|
29
|
-
export function resolveGradientLayout(program, config, scale) {
|
|
30
|
+
export function resolveGradientLayout(program, config, scale, label = "Gradient legend") {
|
|
30
31
|
const { plot, canvas } = resolveContinuousBounds(program);
|
|
31
32
|
const vertical = ["right", "left"].includes(config.position);
|
|
32
33
|
const length = config.gradient.length;
|
|
@@ -111,25 +112,25 @@ export function resolveGradientLayout(program, config, scale) {
|
|
|
111
112
|
bottom: Math.max(tick.y1, tick.y2) + 0.5
|
|
112
113
|
}));
|
|
113
114
|
const labelBounds = labels.map((label, index) =>
|
|
114
|
-
resolveLegendTextBounds(label, texts[index], config.labels)
|
|
115
|
+
resolveLegendTextBounds(label, texts[index], config.labels, program.materializationConfigs.textMetrics)
|
|
115
116
|
);
|
|
116
117
|
const titleBounds = config.titleVisible === false ? undefined : resolveLegendTextBounds(
|
|
117
118
|
title,
|
|
118
119
|
config.title,
|
|
119
120
|
config.titleStyle
|
|
120
|
-
);
|
|
121
|
+
, program.materializationConfigs.textMetrics);
|
|
121
122
|
const occupiedBounds = [stripBounds, ...tickBounds, ...labelBounds,
|
|
122
123
|
...(config.titleVisible === false ? [] : [titleBounds])];
|
|
123
124
|
assertLegendBoundsInsideCanvas(
|
|
124
125
|
occupiedBounds,
|
|
125
126
|
canvas,
|
|
126
|
-
|
|
127
|
+
`${label} layout`, config
|
|
127
128
|
);
|
|
128
129
|
const background = resolveLegendBackgroundFromBounds(
|
|
129
130
|
occupiedBounds,
|
|
130
131
|
config.border,
|
|
131
132
|
canvas,
|
|
132
|
-
|
|
133
|
+
label, config
|
|
133
134
|
);
|
|
134
135
|
return {
|
|
135
136
|
vertical, x, y, length, thickness, values, texts, labels, ticks, title,
|
|
@@ -137,11 +138,11 @@ export function resolveGradientLayout(program, config, scale) {
|
|
|
137
138
|
};
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
function resolveGradientConfig(program, config) {
|
|
141
|
-
const layer = resolveContinuousColorLayer(program, config.target);
|
|
142
|
-
const encoding = layer.encoding
|
|
141
|
+
export function resolveGradientConfig(program, config, channel = "color") {
|
|
142
|
+
const layer = resolveContinuousColorLayer(program, config.target, channel);
|
|
143
|
+
const encoding = layer.encoding[channel];
|
|
143
144
|
if (!["quantitative", "temporal"].includes(encoding.fieldType)) {
|
|
144
|
-
throw new Error("Gradient legend requires quantitative or temporal
|
|
145
|
+
throw new Error(`${channel === "color" ? "Gradient" : "Stroke gradient"} legend requires quantitative or temporal ${channel}.`);
|
|
145
146
|
}
|
|
146
147
|
const scale = requireResolvedLegendScale(
|
|
147
148
|
program,
|
|
@@ -163,153 +164,84 @@ function resolveGradientConfig(program, config) {
|
|
|
163
164
|
};
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
|
|
167
|
+
// Both channels use the same positions and width policy. Their paint and
|
|
168
|
+
// semantic/graphic namespaces remain explicit channel-owned choices.
|
|
169
|
+
export function materializeGradientLegend(program, resolved, appearance) {
|
|
170
|
+
const stroke = appearance !== undefined;
|
|
171
|
+
const channel = stroke ? "stroke" : "color";
|
|
172
|
+
const kind = stroke ? "strokeGradient" : "gradient";
|
|
173
|
+
const prefix = `${channel}Gradient`;
|
|
174
|
+
const { scale, encoding, config: currentConfig } = resolved;
|
|
175
|
+
const config = resolveEffectiveLegendBlockConfig(program, kind, currentConfig);
|
|
176
|
+
const layout = resolveGradientLayout(program, config, scale,
|
|
177
|
+
stroke ? "Stroke gradient legend" : "Gradient legend");
|
|
178
|
+
const stripCount = 60;
|
|
179
|
+
const stripSize = layout.length / stripCount;
|
|
180
|
+
const strips = Array.from({ length: stripCount }, (_, index) => {
|
|
181
|
+
const fraction = (index + 0.5) / stripCount;
|
|
182
|
+
const position = layout.vertical ? 1 - fraction : fraction;
|
|
183
|
+
const samplingScale = scale.midpoint === undefined ? { ...scale, domain: [0, 1] } : scale;
|
|
184
|
+
const value = scale.midpoint === undefined ? position : interpolateNumber(...scale.domain, position);
|
|
185
|
+
const [color] = mapScaleConsumerValues([value], samplingScale, channel);
|
|
186
|
+
return {
|
|
187
|
+
x: layout.x + (layout.vertical ? 0 : index * stripSize),
|
|
188
|
+
y: layout.y + (layout.vertical ? index * stripSize : 0),
|
|
189
|
+
width: layout.vertical ? layout.thickness : stripSize,
|
|
190
|
+
height: layout.vertical ? stripSize : layout.thickness,
|
|
191
|
+
fill: stroke ? appearance.fill : color,
|
|
192
|
+
stroke: color,
|
|
193
|
+
strokeWidth: stroke ? appearance.strokeWidth : 0
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
let next = program
|
|
197
|
+
.editSemantic({ property: `guide.legend.${channel}.scale`, value: encoding.scale })
|
|
198
|
+
.editSemantic({ property: `guide.legend.${channel}.title`, value: config.title })
|
|
199
|
+
._withLegendConfig(kind, currentConfig);
|
|
200
|
+
// Preserve each channel's established primitive trace order.
|
|
201
|
+
if (!stroke) next = next.editGraphics({ target: `${prefix}Strips`, property: "length", value: strips.length });
|
|
202
|
+
next = editLegendBackground(next, `${prefix}Background`, layout.background, config.border);
|
|
203
|
+
if (stroke) next = next.editGraphics({ target: `${prefix}Strips`, property: "length", value: strips.length });
|
|
204
|
+
for (const property of ["x", "y", "width", "height", "fill", "stroke", "strokeWidth"]) {
|
|
205
|
+
next = next.editGraphics({ target: `${prefix}Strips`, property, value: strips.map(strip => strip[property]) });
|
|
206
|
+
}
|
|
207
|
+
next = next.editGraphics({ target: `${prefix}Ticks`, property: "length", value: layout.ticks.length });
|
|
208
|
+
for (const property of ["x1", "y1", "x2", "y2"]) {
|
|
209
|
+
next = next.editGraphics({ target: `${prefix}Ticks`, property, value: layout.ticks.map(tick => tick[property]) });
|
|
210
|
+
}
|
|
211
|
+
next = editGraphicProperties(next, `${prefix}Ticks`, { stroke: DEFAULT_COLORS.mutedText, strokeWidth: 1 });
|
|
212
|
+
next = editGraphicProperties(next, `${prefix}Labels`, {
|
|
213
|
+
length: layout.labels.length, x: layout.labels.map(label => label.x),
|
|
214
|
+
y: layout.labels.map(label => label.y), text: layout.texts
|
|
215
|
+
});
|
|
216
|
+
next = styleContinuousText(next, `${prefix}Labels`, config.labels, { align: layout.labels[0].align });
|
|
217
|
+
if (config.titleVisible === false) return next;
|
|
218
|
+
next = editGraphicProperties(next, `${prefix}Title`, { x: layout.title.x, y: layout.title.y, text: config.title });
|
|
219
|
+
return styleContinuousText(next, `${prefix}Title`, config.titleStyle, { align: layout.title.align });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export const rematerializeGradientLegend = /* @__PURE__ */ closedAction(
|
|
167
223
|
{
|
|
168
224
|
op: "rematerializeGradientLegend",
|
|
169
225
|
description: "Rematerialize a continuous color gradient legend."
|
|
170
|
-
},
|
|
226
|
+
}, [],
|
|
171
227
|
function (args = {}) {
|
|
172
|
-
validateKeys(args, [], "rematerializeGradientLegend");
|
|
173
228
|
const stored = this.guideConfigs.legend?.gradient;
|
|
174
229
|
if (stored === undefined) {
|
|
175
230
|
throw new Error("Gradient legend requires stored configuration.");
|
|
176
231
|
}
|
|
177
|
-
|
|
178
|
-
const config = resolveEffectiveLegendBlockConfig(this, "gradient", currentConfig);
|
|
179
|
-
const layout = resolveGradientLayout(this, config, scale);
|
|
180
|
-
const stripCount = 60;
|
|
181
|
-
const stripSize = layout.length / stripCount;
|
|
182
|
-
const strips = Array.from({ length: stripCount }, (_, index) => {
|
|
183
|
-
const fraction = (index + 0.5) / stripCount;
|
|
184
|
-
const position = layout.vertical ? 1 - fraction : fraction;
|
|
185
|
-
// Keep legacy uniform samples exact without a value/domain round trip.
|
|
186
|
-
const samplingScale = scale.midpoint === undefined ? { ...scale, domain: [0, 1] } : scale;
|
|
187
|
-
const value = scale.midpoint === undefined ? position : interpolateNumber(...scale.domain, position);
|
|
188
|
-
const [color] = mapScaleConsumerValues([value], samplingScale, "color");
|
|
189
|
-
return {
|
|
190
|
-
x: layout.x + (layout.vertical ? 0 : index * stripSize),
|
|
191
|
-
y: layout.y + (layout.vertical ? index * stripSize : 0),
|
|
192
|
-
width: layout.vertical ? layout.thickness : stripSize,
|
|
193
|
-
height: layout.vertical ? stripSize : layout.thickness,
|
|
194
|
-
fill: color,
|
|
195
|
-
stroke: color,
|
|
196
|
-
strokeWidth: 0
|
|
197
|
-
};
|
|
198
|
-
});
|
|
199
|
-
let next = this
|
|
200
|
-
.editSemantic({
|
|
201
|
-
property: "guide.legend.color.scale",
|
|
202
|
-
value: encoding.scale
|
|
203
|
-
})
|
|
204
|
-
.editSemantic({
|
|
205
|
-
property: "guide.legend.color.title",
|
|
206
|
-
value: config.title
|
|
207
|
-
})
|
|
208
|
-
._withLegendConfig("gradient", currentConfig)
|
|
209
|
-
.editGraphics({
|
|
210
|
-
target: "colorGradientStrips",
|
|
211
|
-
property: "length",
|
|
212
|
-
value: strips.length
|
|
213
|
-
});
|
|
214
|
-
next = editLegendBackground(
|
|
215
|
-
next,
|
|
216
|
-
"colorGradientBackground",
|
|
217
|
-
layout.background,
|
|
218
|
-
config.border
|
|
219
|
-
);
|
|
220
|
-
for (const property of [
|
|
221
|
-
"x", "y", "width", "height", "fill", "stroke", "strokeWidth"
|
|
222
|
-
]) {
|
|
223
|
-
next = next.editGraphics({
|
|
224
|
-
target: "colorGradientStrips",
|
|
225
|
-
property,
|
|
226
|
-
value: strips.map(strip => strip[property])
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
next = next.editGraphics({
|
|
230
|
-
target: "colorGradientTicks",
|
|
231
|
-
property: "length",
|
|
232
|
-
value: layout.ticks.length
|
|
233
|
-
});
|
|
234
|
-
for (const property of ["x1", "y1", "x2", "y2"]) {
|
|
235
|
-
next = next.editGraphics({
|
|
236
|
-
target: "colorGradientTicks",
|
|
237
|
-
property,
|
|
238
|
-
value: layout.ticks.map(tick => tick[property])
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
next = next
|
|
242
|
-
.editGraphics({
|
|
243
|
-
target: "colorGradientTicks",
|
|
244
|
-
property: "stroke",
|
|
245
|
-
value: DEFAULT_COLORS.mutedText
|
|
246
|
-
})
|
|
247
|
-
.editGraphics({
|
|
248
|
-
target: "colorGradientTicks",
|
|
249
|
-
property: "strokeWidth",
|
|
250
|
-
value: 1
|
|
251
|
-
})
|
|
252
|
-
.editGraphics({
|
|
253
|
-
target: "colorGradientLabels",
|
|
254
|
-
property: "length",
|
|
255
|
-
value: layout.labels.length
|
|
256
|
-
})
|
|
257
|
-
.editGraphics({
|
|
258
|
-
target: "colorGradientLabels",
|
|
259
|
-
property: "x",
|
|
260
|
-
value: layout.labels.map(label => label.x)
|
|
261
|
-
})
|
|
262
|
-
.editGraphics({
|
|
263
|
-
target: "colorGradientLabels",
|
|
264
|
-
property: "y",
|
|
265
|
-
value: layout.labels.map(label => label.y)
|
|
266
|
-
})
|
|
267
|
-
.editGraphics({
|
|
268
|
-
target: "colorGradientLabels",
|
|
269
|
-
property: "text",
|
|
270
|
-
value: layout.texts
|
|
271
|
-
});
|
|
272
|
-
next = styleContinuousText(
|
|
273
|
-
next,
|
|
274
|
-
"colorGradientLabels",
|
|
275
|
-
config.labels,
|
|
276
|
-
{ align: layout.labels[0].align }
|
|
277
|
-
);
|
|
278
|
-
if (config.titleVisible === false) return next;
|
|
279
|
-
next = next
|
|
280
|
-
.editGraphics({
|
|
281
|
-
target: "colorGradientTitle",
|
|
282
|
-
property: "x",
|
|
283
|
-
value: layout.title.x
|
|
284
|
-
})
|
|
285
|
-
.editGraphics({
|
|
286
|
-
target: "colorGradientTitle",
|
|
287
|
-
property: "y",
|
|
288
|
-
value: layout.title.y
|
|
289
|
-
})
|
|
290
|
-
.editGraphics({
|
|
291
|
-
target: "colorGradientTitle",
|
|
292
|
-
property: "text",
|
|
293
|
-
value: config.title
|
|
294
|
-
});
|
|
295
|
-
return styleContinuousText(
|
|
296
|
-
next,
|
|
297
|
-
"colorGradientTitle",
|
|
298
|
-
config.titleStyle,
|
|
299
|
-
{ align: layout.title.align }
|
|
300
|
-
);
|
|
232
|
+
return materializeGradientLegend(this, resolveGradientConfig(this, stored));
|
|
301
233
|
}
|
|
302
234
|
);
|
|
303
235
|
|
|
304
236
|
|
|
305
|
-
export function resolveGradientLegendCreation(program, args = {}) {
|
|
237
|
+
export function resolveGradientLegendCreation(program, args = {}, channel = "color") {
|
|
306
238
|
const config = normalizeContinuousLegend(args, "gradient");
|
|
307
239
|
if (args.channels !== undefined && (
|
|
308
240
|
!Array.isArray(args.channels) ||
|
|
309
241
|
args.channels.length !== 1 ||
|
|
310
|
-
args.channels[0] !==
|
|
242
|
+
args.channels[0] !== channel
|
|
311
243
|
)) {
|
|
312
|
-
throw new Error(
|
|
244
|
+
throw new Error(`Gradient legend requires channels: ["${channel}"].`);
|
|
313
245
|
}
|
|
314
246
|
if (args.gradient !== undefined && !isPlainObject(args.gradient)) {
|
|
315
247
|
throw new TypeError("createLegend.gradient must be a plain object.");
|
|
@@ -326,62 +258,64 @@ export function resolveGradientLegendCreation(program, args = {}) {
|
|
|
326
258
|
config.titleVisible = true;
|
|
327
259
|
validatePositive(config.gradient.length, "Gradient length");
|
|
328
260
|
validatePositive(config.gradient.thickness, "Gradient thickness");
|
|
329
|
-
const resolved = resolveGradientConfig(program, config);
|
|
261
|
+
const resolved = resolveGradientConfig(program, config, channel);
|
|
330
262
|
return resolved;
|
|
331
263
|
}
|
|
332
264
|
|
|
333
|
-
export function createGradientLegendFromConfig(program, config) {
|
|
334
|
-
const
|
|
335
|
-
|
|
265
|
+
export function createGradientLegendFromConfig(program, config, channel = "color") {
|
|
266
|
+
const prefix = `${channel}Gradient`;
|
|
267
|
+
const kind = channel === "color" ? "gradient" : "strokeGradient";
|
|
268
|
+
const resolved = resolveGradientConfig(program, config, channel);
|
|
269
|
+
resolveGradientLayout(program, resolved.config, resolved.scale, channel === "color" ? "Gradient legend" : "Stroke gradient legend");
|
|
336
270
|
let next = program
|
|
337
271
|
.editSemantic({
|
|
338
|
-
property:
|
|
272
|
+
property: `guide.legend.${channel}.scale`,
|
|
339
273
|
value: resolved.encoding.scale
|
|
340
274
|
})
|
|
341
275
|
.editSemantic({
|
|
342
|
-
property:
|
|
276
|
+
property: `guide.legend.${channel}.title`,
|
|
343
277
|
value: resolved.config.title
|
|
344
278
|
})
|
|
345
|
-
._withLegendConfig(
|
|
279
|
+
._withLegendConfig(kind, resolved.config);
|
|
346
280
|
if (resolved.config.border !== false) {
|
|
347
281
|
next = next.createGraphics({
|
|
348
|
-
id:
|
|
282
|
+
id: `${prefix}Background`,
|
|
349
283
|
type: "rect",
|
|
350
284
|
...resolveLegendGraphicPlacement(next)
|
|
351
285
|
});
|
|
352
286
|
}
|
|
353
287
|
next = next
|
|
354
288
|
.createGraphics({
|
|
355
|
-
id:
|
|
289
|
+
id: `${prefix}Strips`,
|
|
356
290
|
type: "rect",
|
|
357
291
|
length: 0,
|
|
358
292
|
...resolveLegendGraphicPlacement(next, resolved.config.border === false
|
|
359
293
|
? {}
|
|
360
|
-
: { after:
|
|
294
|
+
: { after: `${prefix}Background` })
|
|
361
295
|
})
|
|
362
296
|
.createGraphics({
|
|
363
|
-
id:
|
|
297
|
+
id: `${prefix}Ticks`,
|
|
364
298
|
type: "line",
|
|
365
299
|
length: 0,
|
|
366
300
|
...resolveLegendGraphicPlacement(next)
|
|
367
301
|
})
|
|
368
302
|
.createGraphics({
|
|
369
|
-
id:
|
|
303
|
+
id: `${prefix}Labels`,
|
|
370
304
|
type: "text",
|
|
371
305
|
length: 0,
|
|
372
306
|
...resolveLegendGraphicPlacement(next)
|
|
373
307
|
});
|
|
374
308
|
if (resolved.config.titleVisible !== false) {
|
|
375
309
|
next = next.createGraphics({
|
|
376
|
-
id:
|
|
310
|
+
id: `${prefix}Title`,
|
|
377
311
|
type: "text",
|
|
378
312
|
...resolveLegendGraphicPlacement(next)
|
|
379
313
|
});
|
|
380
314
|
}
|
|
381
|
-
return next
|
|
315
|
+
return next[channel === "color" ? "rematerializeGradientLegend" : "rematerializeStrokeGradientLegend"]();
|
|
382
316
|
}
|
|
383
317
|
|
|
384
|
-
export const createGradientLegend = action(
|
|
318
|
+
export const createGradientLegend = /* @__PURE__ */ action(
|
|
385
319
|
{
|
|
386
320
|
op: "createGradientLegend",
|
|
387
321
|
description: "Create a continuous color gradient legend."
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolveLegendItemLayout } from "../../../../layout/legendItems.js";
|
|
2
|
-
import { action } from "../../../../core/action.js";
|
|
2
|
+
import { action, closedAction } from "../../../../core/action.js";
|
|
3
3
|
import { isPlainObject } from "../../../../core/immutable.js";
|
|
4
4
|
import {
|
|
5
5
|
validateKeys,
|
|
@@ -86,15 +86,16 @@ export function normalizeIntervalLegend(args) {
|
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
export function resolveIntervalConfig(program, stored) {
|
|
90
|
-
const
|
|
91
|
-
const
|
|
89
|
+
export function resolveIntervalConfig(program, stored, channel = "color") {
|
|
90
|
+
const label = channel === "color" ? "Interval legend" : "Stroke interval legend";
|
|
91
|
+
const layer = resolveContinuousColorLayer(program, stored.target, channel);
|
|
92
|
+
const encoding = layer.encoding[channel];
|
|
92
93
|
if (encoding.fieldType !== "quantitative") {
|
|
93
|
-
throw new Error(
|
|
94
|
+
throw new Error(`${label} requires quantitative ${channel}.`);
|
|
94
95
|
}
|
|
95
96
|
const scale = program.resolvedScales[encoding.scale];
|
|
96
97
|
if (!["quantize", "quantile", "threshold"].includes(scale?.type)) {
|
|
97
|
-
throw new Error(
|
|
98
|
+
throw new Error(`${label} requires a resolved discretized scale "${encoding.scale}".`);
|
|
98
99
|
}
|
|
99
100
|
return { encoding, scale, config: {
|
|
100
101
|
...stored,
|
|
@@ -104,32 +105,31 @@ export function resolveIntervalConfig(program, stored) {
|
|
|
104
105
|
} };
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
export function resolveIntervalLayout(program, config, scale) {
|
|
108
|
+
export function resolveIntervalLayout(program, config, scale, label = "Interval legend") {
|
|
108
109
|
const { plot, canvas } = resolveContinuousBounds(program);
|
|
109
110
|
const labels = formatDiscretizedIntervals(scale.thresholds, config.labels.format);
|
|
110
|
-
const layout = resolveLegendItemLayout(plot, config, labels, config.symbol);
|
|
111
|
+
const layout = resolveLegendItemLayout(plot, config, labels, config.symbol, undefined, program.materializationConfigs.textMetrics);
|
|
111
112
|
const occupiedBounds = layout.bounds;
|
|
112
113
|
assertLegendBoundsInsideCanvas(
|
|
113
114
|
occupiedBounds,
|
|
114
115
|
canvas,
|
|
115
|
-
|
|
116
|
+
`${label} layout`, config
|
|
116
117
|
);
|
|
117
118
|
const background = resolveLegendBackgroundFromBounds(
|
|
118
119
|
occupiedBounds,
|
|
119
120
|
config.border,
|
|
120
121
|
canvas,
|
|
121
|
-
|
|
122
|
+
label, config
|
|
122
123
|
);
|
|
123
124
|
return { labels, ...layout, background };
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
export const rematerializeIntervalLegend = /* @__PURE__ */
|
|
127
|
+
export const rematerializeIntervalLegend = /* @__PURE__ */ closedAction(
|
|
127
128
|
{
|
|
128
129
|
op: "rematerializeIntervalLegend",
|
|
129
130
|
description: "Rematerialize a discretized color interval legend."
|
|
130
|
-
},
|
|
131
|
+
}, [],
|
|
131
132
|
function (args = {}) {
|
|
132
|
-
validateKeys(args, [], "rematerializeIntervalLegend");
|
|
133
133
|
const stored = this.guideConfigs.legend?.interval;
|
|
134
134
|
if (stored === undefined) {
|
|
135
135
|
throw new Error("Interval legend requires stored configuration.");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { editGraphicProperties } from "../../../primitives/graphicProperties.js";
|
|
2
|
+
import { action, closedAction } from "../../../../core/action.js";
|
|
2
3
|
import { isPlainObject } from "../../../../core/immutable.js";
|
|
3
4
|
import { validateKeys } from "../../../../core/validation.js";
|
|
4
5
|
import { mapLinearValues } from "../../../../grammar/scales/index.js";
|
|
@@ -94,7 +95,7 @@ function resolveOpacityLayout(program, config, scale) {
|
|
|
94
95
|
config.labels.format
|
|
95
96
|
);
|
|
96
97
|
const symbolExtent = config.symbol.radius + (config.symbol.strokeWidth ?? 0) / 2;
|
|
97
|
-
const labelWidths = texts.map(text => measureTextWidth(text, config.labels));
|
|
98
|
+
const labelWidths = texts.map(text => measureTextWidth(text, config.labels, program.materializationConfigs.textMetrics));
|
|
98
99
|
let symbols;
|
|
99
100
|
let labels;
|
|
100
101
|
let title;
|
|
@@ -124,7 +125,7 @@ function resolveOpacityLayout(program, config, scale) {
|
|
|
124
125
|
align: config.position === "right" ? "left" : "right"
|
|
125
126
|
};
|
|
126
127
|
} else if (config.titlePosition === "left") {
|
|
127
|
-
const titleWidth = config.titleVisible === false ? 0 : measureTextWidth(config.title, config.titleStyle);
|
|
128
|
+
const titleWidth = config.titleVisible === false ? 0 : measureTextWidth(config.title, config.titleStyle, program.materializationConfigs.textMetrics);
|
|
128
129
|
const titlePrefix = config.titleVisible === false ? 0 : titleWidth + 20;
|
|
129
130
|
const samplesWidth = labelWidths.reduce(
|
|
130
131
|
(sum, width) => sum + symbolExtent * 2 + config.labels.offset + width,
|
|
@@ -187,9 +188,9 @@ function resolveOpacityLayout(program, config, scale) {
|
|
|
187
188
|
title,
|
|
188
189
|
config.title,
|
|
189
190
|
config.titleStyle
|
|
190
|
-
);
|
|
191
|
+
, program.materializationConfigs.textMetrics);
|
|
191
192
|
const labelBounds = labels.map((label, index) =>
|
|
192
|
-
resolveLegendTextBounds(label, texts[index], config.labels)
|
|
193
|
+
resolveLegendTextBounds(label, texts[index], config.labels, program.materializationConfigs.textMetrics)
|
|
193
194
|
);
|
|
194
195
|
const symbolBounds = symbols.map(symbol => ({
|
|
195
196
|
left: symbol.x - symbolExtent,
|
|
@@ -212,13 +213,12 @@ function resolveOpacityLayout(program, config, scale) {
|
|
|
212
213
|
return { values, texts, symbols, labels, title, background };
|
|
213
214
|
}
|
|
214
215
|
|
|
215
|
-
export const rematerializeOpacityLegend = /* @__PURE__ */
|
|
216
|
+
export const rematerializeOpacityLegend = /* @__PURE__ */ closedAction(
|
|
216
217
|
{
|
|
217
218
|
op: "rematerializeOpacityLegend",
|
|
218
219
|
description: "Rematerialize a field-opacity sample legend."
|
|
219
|
-
},
|
|
220
|
+
}, [],
|
|
220
221
|
function (args = {}) {
|
|
221
|
-
validateKeys(args, [], "rematerializeOpacityLegend");
|
|
222
222
|
const stored = this.guideConfigs.legend?.opacity;
|
|
223
223
|
if (stored === undefined) {
|
|
224
224
|
throw new Error("Opacity legend requires stored configuration.");
|
|
@@ -229,7 +229,7 @@ export const rematerializeOpacityLegend = /* @__PURE__ */ action(
|
|
|
229
229
|
const opacities = mapLinearValues(layout.values, scale.domain, scale.range, {
|
|
230
230
|
clamp: scale.clamp ?? false
|
|
231
231
|
});
|
|
232
|
-
let next = this
|
|
232
|
+
let next = editGraphicProperties(editGraphicProperties(editGraphicProperties(this
|
|
233
233
|
.editSemantic({
|
|
234
234
|
property: "guide.legend.opacity.scale",
|
|
235
235
|
value: encoding.scale
|
|
@@ -238,57 +238,20 @@ export const rematerializeOpacityLegend = /* @__PURE__ */ action(
|
|
|
238
238
|
property: "guide.legend.opacity.title",
|
|
239
239
|
value: config.title
|
|
240
240
|
})
|
|
241
|
-
._withLegendConfig("opacity", currentConfig)
|
|
242
|
-
.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
value: layout.symbols.map(symbol => symbol.y)
|
|
256
|
-
})
|
|
257
|
-
.editGraphics({
|
|
258
|
-
target: "opacityLegendSymbols",
|
|
259
|
-
property: "radius",
|
|
260
|
-
value: config.symbol.radius
|
|
261
|
-
})
|
|
262
|
-
.editGraphics({
|
|
263
|
-
target: "opacityLegendSymbols",
|
|
264
|
-
property: "fill",
|
|
265
|
-
value: config.symbol.fill
|
|
266
|
-
})
|
|
267
|
-
.editGraphics({
|
|
268
|
-
target: "opacityLegendSymbols",
|
|
269
|
-
property: "opacity",
|
|
270
|
-
value: opacities
|
|
271
|
-
})
|
|
272
|
-
.editGraphics({
|
|
273
|
-
target: "opacityLegendLabels",
|
|
274
|
-
property: "length",
|
|
275
|
-
value: layout.values.length
|
|
276
|
-
})
|
|
277
|
-
.editGraphics({
|
|
278
|
-
target: "opacityLegendLabels",
|
|
279
|
-
property: "x",
|
|
280
|
-
value: layout.labels.map(label => label.x)
|
|
281
|
-
})
|
|
282
|
-
.editGraphics({
|
|
283
|
-
target: "opacityLegendLabels",
|
|
284
|
-
property: "y",
|
|
285
|
-
value: layout.labels.map(label => label.y)
|
|
286
|
-
})
|
|
287
|
-
.editGraphics({
|
|
288
|
-
target: "opacityLegendLabels",
|
|
289
|
-
property: "text",
|
|
290
|
-
value: layout.texts
|
|
291
|
-
});
|
|
241
|
+
._withLegendConfig("opacity", currentConfig), "opacityLegendSymbols", {
|
|
242
|
+
length: layout.values.length,
|
|
243
|
+
x: layout.symbols.map(symbol => symbol.x),
|
|
244
|
+
y: layout.symbols.map(symbol => symbol.y)
|
|
245
|
+
}), "opacityLegendSymbols", {
|
|
246
|
+
radius: config.symbol.radius,
|
|
247
|
+
fill: config.symbol.fill,
|
|
248
|
+
opacity: opacities
|
|
249
|
+
}), "opacityLegendLabels", {
|
|
250
|
+
length: layout.values.length,
|
|
251
|
+
x: layout.labels.map(label => label.x),
|
|
252
|
+
y: layout.labels.map(label => label.y),
|
|
253
|
+
text: layout.texts
|
|
254
|
+
});
|
|
292
255
|
next = editLegendBackground(
|
|
293
256
|
next,
|
|
294
257
|
"opacityLegendBackground",
|
|
@@ -316,22 +279,11 @@ export const rematerializeOpacityLegend = /* @__PURE__ */ action(
|
|
|
316
279
|
{ align: layout.labels[0].align }
|
|
317
280
|
);
|
|
318
281
|
if (config.titleVisible === false) return next;
|
|
319
|
-
next = next
|
|
320
|
-
.
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
})
|
|
325
|
-
.editGraphics({
|
|
326
|
-
target: "opacityLegendTitle",
|
|
327
|
-
property: "y",
|
|
328
|
-
value: layout.title.y
|
|
329
|
-
})
|
|
330
|
-
.editGraphics({
|
|
331
|
-
target: "opacityLegendTitle",
|
|
332
|
-
property: "text",
|
|
333
|
-
value: config.title
|
|
334
|
-
});
|
|
282
|
+
next = editGraphicProperties(next, "opacityLegendTitle", {
|
|
283
|
+
x: layout.title.x,
|
|
284
|
+
y: layout.title.y,
|
|
285
|
+
text: config.title
|
|
286
|
+
});
|
|
335
287
|
return styleContinuousText(
|
|
336
288
|
next,
|
|
337
289
|
"opacityLegendTitle",
|
|
@@ -419,13 +371,12 @@ export const createOpacityLegend = /* @__PURE__ */ action(
|
|
|
419
371
|
}
|
|
420
372
|
);
|
|
421
373
|
|
|
422
|
-
export const removeOpacityLegend = /* @__PURE__ */
|
|
374
|
+
export const removeOpacityLegend = /* @__PURE__ */ closedAction(
|
|
423
375
|
{
|
|
424
376
|
op: "removeOpacityLegend",
|
|
425
377
|
description: "Remove a field-opacity legend after switching to constant opacity."
|
|
426
|
-
},
|
|
378
|
+
}, [],
|
|
427
379
|
function (args = {}) {
|
|
428
|
-
validateKeys(args, [], "removeOpacityLegend");
|
|
429
380
|
if (this.guideConfigs.legend?.opacity === undefined) return this;
|
|
430
381
|
const targets = [
|
|
431
382
|
"opacityLegendBackground",
|