ggaction 0.0.17 → 0.0.19
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 +96 -0
- package/README.md +6 -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 +20 -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 +8 -7
- 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/derived.js +11 -1
- package/src/actions/data/edit.js +3 -1
- package/src/actions/data/index.js +4 -0
- package/src/actions/data/regression.js +25 -16
- package/src/actions/data/regressionStatistics.js +73 -0
- package/src/actions/data/shared.js +8 -4
- package/src/actions/data/stack.js +3 -1
- package/src/actions/distributions/revision.js +24 -18
- 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 +14 -8
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/shared.js +26 -24
- 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 +176 -247
- package/src/actions/facets/guides.js +2 -1
- package/src/actions/gradientPlots/resolve.js +2 -17
- package/src/actions/guides/applicability.js +1 -1
- package/src/actions/guides/axes/axes.js +30 -11
- package/src/actions/guides/axes/axis.js +72 -10
- package/src/actions/guides/axes/components.js +13 -13
- package/src/actions/guides/axes/edit.js +36 -24
- package/src/actions/guides/axes/labels.js +155 -71
- package/src/actions/guides/axes/lines.js +27 -24
- 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 +13 -8
- package/src/actions/guides/axes/tickGroups.js +11 -7
- package/src/actions/guides/axes/ticks.js +36 -24
- package/src/actions/guides/axes/titles.js +107 -69
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facadeAxes.js +33 -24
- package/src/actions/guides/facadeLegend.js +12 -4
- package/src/actions/guides/grids/grid.js +8 -2
- package/src/actions/guides/grids/resolve.js +102 -58
- package/src/actions/guides/legends/blocks.js +46 -20
- package/src/actions/guides/legends/categorical/actions.js +95 -19
- package/src/actions/guides/legends/categorical/components.js +15 -18
- package/src/actions/guides/legends/categorical/index.js +8 -0
- package/src/actions/guides/legends/categorical/layout.js +45 -20
- package/src/actions/guides/legends/categorical/options.js +39 -22
- package/src/actions/guides/legends/categorical/recipes.js +7 -3
- package/src/actions/guides/legends/categorical/resolve.js +11 -4
- package/src/actions/guides/legends/categorical/symbols.js +38 -18
- package/src/actions/guides/legends/categorical/validation.js +1 -6
- package/src/actions/guides/legends/colorSize.js +110 -0
- package/src/actions/guides/legends/combine.js +37 -0
- 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 +73 -46
- package/src/actions/guides/legends/continuous/interval.js +4 -4
- package/src/actions/guides/legends/continuous/opacity.js +33 -21
- package/src/actions/guides/legends/creation.js +2 -0
- package/src/actions/guides/legends/edit.js +1 -604
- package/src/actions/guides/legends/focused.js +8 -7
- package/src/actions/guides/legends/group.js +1 -0
- package/src/actions/guides/legends/lane.js +1 -516
- package/src/actions/guides/legends/lifecycle.js +2 -1
- package/src/actions/guides/legends/remove.js +9 -3
- package/src/actions/guides/legends/sampling.js +1 -1
- package/src/actions/guides/legends/size.js +65 -47
- package/src/actions/guides/legends/strokeWidth.js +13 -29
- package/src/actions/guides/legends/target.js +2 -79
- package/src/actions/guides/legends/transition.js +44 -11
- 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/guides/tickValues.js +8 -5
- 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 +29 -14
- package/src/actions/marks/bar/materialize.js +15 -7
- 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/point/materialize.js +5 -1
- package/src/actions/marks/rect/actions.js +3 -3
- package/src/actions/marks/remove.js +12 -10
- 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/createGraphics.js +3 -2
- package/src/actions/primitives/editGraphics.js +15 -11
- package/src/actions/primitives/graphicProperties.js +1 -6
- package/src/actions/primitives/semanticAction.js +27 -1
- package/src/actions/primitives/semanticValidation/guide.js +11 -1
- package/src/actions/primitives/semanticValidation/layer.js +8 -0
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +18 -6
- 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 +10 -5
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +3 -3
- package/src/actions/scales/editPolicy.js +12 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +21 -1
- package/src/actions/scales/quantitativeColor.js +8 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +126 -45
- 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 +6 -3
- package/src/core/font.js +9 -2
- package/src/core/graphicProperties.js +1 -0
- package/src/core/optionRequirements.js +1 -0
- package/src/core/programState.js +4 -0
- package/src/core/resourceReferences.js +5 -2
- package/src/core/textMetricProfile.js +3 -2
- package/src/core/textMetrics.js +41 -4
- package/src/core/validation.js +35 -0
- package/src/core/vocabulary.js +7 -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/axisTextPlacement.js +1 -0
- 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/colorSizeLegend.js +39 -0
- package/src/grammar/complete.js +15 -19
- package/src/grammar/computed.js +115 -26
- package/src/grammar/curveCommands.js +29 -16
- package/src/grammar/datasetSchema.js +13 -9
- package/src/grammar/displayLabels.js +36 -0
- package/src/grammar/ecdf.js +2 -9
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +58 -30
- package/src/grammar/facets/index.js +2 -5
- package/src/grammar/facets/legendIntent.js +65 -0
- package/src/grammar/fold.js +2 -12
- package/src/grammar/gradientProfile.js +1 -6
- package/src/grammar/guideDash.js +1 -0
- package/src/grammar/impute.js +21 -48
- package/src/grammar/interval.js +2 -9
- package/src/grammar/legendBlocks.js +1 -0
- package/src/grammar/legendBorder.js +9 -0
- package/src/grammar/legendClip.js +19 -0
- package/src/grammar/legendCombinations.js +45 -0
- package/src/grammar/legendGradient.js +27 -0
- package/src/grammar/legendGroups.js +1 -0
- package/src/grammar/legendOpacity.js +6 -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/pointShapes.js +16 -2
- package/src/grammar/polar.js +1 -184
- package/src/grammar/positionCompatibility.js +3 -3
- package/src/grammar/regression/derive.js +16 -2
- package/src/grammar/regression/models.js +142 -54
- package/src/grammar/regression/parameters.js +23 -4
- package/src/grammar/regression/statistics.js +64 -0
- 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 +14 -0
- package/src/grammar/schemas/graphic.js +6 -0
- package/src/grammar/schemas/graphicBounds.js +31 -7
- package/src/grammar/schemas/semanticPath.js +19 -3
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +4 -27
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +22 -11
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transformTopology.js +1 -0
- package/src/grammar/transforms.js +15 -2
- 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/guideCollisions.js +9 -1
- 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/compositionSnapshot.js +2 -7
- package/src/materialization/coordinateBounds.js +21 -0
- package/src/materialization/dependencies.js +4 -44
- package/src/materialization/facetGuides/edit.js +30 -0
- package/src/materialization/facetGuides/layout.js +1 -0
- package/src/materialization/facetGuides/owner.js +125 -0
- package/src/materialization/facetGuides/ownerLayout.js +71 -0
- package/src/materialization/facetGuides/placement.js +71 -14
- package/src/materialization/facetGuides/preparation.js +22 -7
- package/src/materialization/facetHeaders.js +1 -347
- package/src/materialization/facets.js +1 -185
- package/src/materialization/graphicHierarchy.js +5 -1
- package/src/materialization/graphicTranslation.js +73 -0
- 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/legendBorder.js +28 -0
- package/src/materialization/legendClip.js +76 -0
- package/src/materialization/legendGroups.js +1 -0
- package/src/materialization/legendText.js +1 -0
- package/src/materialization/legends.js +2 -2
- package/src/materialization/marks/capabilities.js +5 -6
- package/src/materialization/rect.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +14 -15
- package/src/materialization/scales/map.js +6 -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 +7 -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/materialization/typography.js +10 -2
- package/src/persistence/codec.js +37 -6
- package/src/persistence/validation.js +220 -0
- package/src/persistence.js +19 -6
- package/src/renderers/canvas/circle.js +6 -3
- package/src/renderers/canvas/fill.js +7 -1
- package/src/renderers/canvas/index.js +22 -11
- package/src/renderers/canvas/isolated.js +166 -0
- package/src/renderers/canvas/limits.js +3 -0
- 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/pdf.js +2 -2
- package/src/renderers/png.js +2 -2
- package/src/renderers/svg.js +14 -4
- package/src/selectors/axes.js +1 -0
- package/src/selectors/graphicCapacity.js +38 -0
- 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 +3 -0
- package/types/index.d.ts +12 -0
- package/types/program.d.ts +536 -129
|
@@ -1,57 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { unionConcreteGraphicBounds } from "../../grammar/schemas/graphicBounds.js";
|
|
3
|
-
import { axisGraphicIds, legendResourcePolicies } from "./resources.js";
|
|
4
|
-
|
|
5
|
-
// Only domain-owned guides participate. Extension primitives may deliberately
|
|
6
|
-
// overlap, and a combined categorical/size legend is one occupied group.
|
|
7
|
-
export function resolveGuideCollisionBlocks(graphicSpec, guideConfigs, titleConfig, profile) {
|
|
8
|
-
const blocks = [];
|
|
9
|
-
const append = (id, kind, position, ids) => {
|
|
10
|
-
if (position === undefined) return;
|
|
11
|
-
const existing = ids.filter(id => graphicSpec.objects[id] !== undefined);
|
|
12
|
-
const bounds = unionConcreteGraphicBounds(graphicSpec, existing, profile);
|
|
13
|
-
if (bounds !== undefined) blocks.push({ id, kind, position, bounds });
|
|
14
|
-
};
|
|
15
|
-
append("chart title", "title", titleConfig?.position, ["chartTitle", "chartSubtitle"]);
|
|
16
|
-
for (const channel of ["x", "y"]) {
|
|
17
|
-
for (const id of axisGraphicIds(channel)) {
|
|
18
|
-
const component = id.slice(`${channel}Axis`.length).toLowerCase();
|
|
19
|
-
append(`${channel}-axis ${component}`, "axis", guideConfigs.axis?.[channel]?.[component]?.position, [id]);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
const policies = legendResourcePolicies();
|
|
23
|
-
const configs = guideConfigs.legend ?? {};
|
|
24
|
-
const categorical = policies.find(policy => policy.family === "categorical" && configs[policy.kind] !== undefined);
|
|
25
|
-
const combined = categorical !== undefined && configs.size !== undefined &&
|
|
26
|
-
configs[categorical.kind].target === configs.size.target;
|
|
27
|
-
for (const policy of policies) {
|
|
28
|
-
const config = configs[policy.kind];
|
|
29
|
-
if (config === undefined || combined && policy.kind === "size") continue;
|
|
30
|
-
const ids = combined && policy === categorical
|
|
31
|
-
? [...policy.graphicIds, ...policies.find(item => item.kind === "size").graphicIds]
|
|
32
|
-
: policy.graphicIds;
|
|
33
|
-
append(`${policy.kind}${combined && policy === categorical ? "+size" : ""} legend`, "legend", config.position, ids);
|
|
34
|
-
}
|
|
35
|
-
return blocks;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// Defer cross-guide checks while sibling materializers still carry old
|
|
40
|
-
// coordinates. The scope is transient and never survives a returned program.
|
|
41
|
-
export function withGuideLayoutTransaction(program, apply) {
|
|
42
|
-
if (program.context.deferGuideLayoutValidation === true) return apply(program);
|
|
43
|
-
const result = apply(program._withContext({ deferGuideLayoutValidation: true }));
|
|
44
|
-
const { deferGuideLayoutValidation: _deferred, ...context } = result.context;
|
|
45
|
-
if (Object.hasOwn(program.context, "deferGuideLayoutValidation")) {
|
|
46
|
-
context.deferGuideLayoutValidation = program.context.deferGuideLayoutValidation;
|
|
47
|
-
}
|
|
48
|
-
const next = result._clone({ context });
|
|
49
|
-
assertGuideCollisionBlocks(resolveGuideCollisionBlocks(next.graphicSpec, next.guideConfigs, next.titleConfig, next.materializationConfigs.textMetrics));
|
|
50
|
-
return next;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function withGuideLayoutValidation(implementation) {
|
|
54
|
-
return function (args) {
|
|
55
|
-
return withGuideLayoutTransaction(this, program => implementation.call(program, args));
|
|
56
|
-
};
|
|
57
|
-
}
|
|
1
|
+
import{resetLegendClipGraphics as u,hasLegendClipGraphics as c}from"../legendClip.js";import{localLegendPosition as g}from"../legendGroups.js";import{needsLegendAxisReflow as m}from"./resources.js";import{getErrorDetails as p}from"../../core/diagnostics.js";import{planCanvasRematerialization as h}from"../layout.js";import{applyMaterializationPlan as C}from"../planner.js";import{assertGuideCollisionBlocks as L}from"../../layout/guideCollisions.js";import{resolveGuideCollisionBlocks as v}from"./resources.js";import{resolveGuideCollisionBlocks as M}from"./resources.js";function G(i,d){if(i.context.deferGuideLayoutValidation===!0)return d(i);const a=i.materializationConfigs.canvas?.plot;let n=i.materializationConfigs.canvas?.margin,e;for(let s=0;;s+=1)try{let t=u(i)._withContext({deferGuideLayoutValidation:!0});s>0&&(t=t.editGraphics({target:"canvas",property:"width",value:a.width+n.left+n.right}).editGraphics({target:"canvas",property:"height",value:a.height+n.top+n.bottom})._withCanvasConfig({...t.materializationConfigs.canvas,margin:n}),t=C(t,h(t))),e=d(t),c(i)&&!c(e)&&Object.values(e.guideConfigs.legend??{}).some(o=>o.clipHeight!==void 0)&&(e=e.rematerializeLegend()),e.compositionSpec===void 0&&Object.values(e.guideConfigs.legend??{}).some(o=>["left","right"].includes(o.position))&&(e=e.rematerializeSideLegendLane()),e.compositionSpec===void 0&&m(i,e)&&(e=e.rematerializeHorizontalLegendLane()),e.compositionSpec===void 0&&(Object.keys(e.guideConfigs.legendGroups??{}).length>0||Object.values(e.guideConfigs.legend??{}).some(o=>g(o)!==void 0))&&(e=e.rematerializeLegendGroups()),a!==void 0&&e.titleConfig!==void 0&&(e=e.rematerializeTitle());break}catch(t){const o=p(t)?.canvasOverflow;if(a===void 0||o===void 0||s>=16)throw t;n=Object.fromEntries(Object.keys(n).map(l=>[l,o[l]>0?Math.ceil((n[l]+o[l])*4)/4:n[l]]))}const{deferGuideLayoutValidation:y,...f}=e.context;Object.hasOwn(i.context,"deferGuideLayoutValidation")&&(f.deferGuideLayoutValidation=i.context.deferGuideLayoutValidation);const r=e._clone({context:f});return L(v(r.graphicSpec,r.guideConfigs,r.titleConfig,r.materializationConfigs.textMetrics)),r}function S(i){return function(d){return G(this,a=>i.call(a,d))}}export{M as resolveGuideCollisionBlocks,G as withGuideLayoutTransaction,S as withGuideLayoutValidation};
|
|
@@ -1,123 +1 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const CATEGORICAL_COMPONENTS = Object.freeze([
|
|
4
|
-
"Symbols", "SymbolLines", "SymbolPoints", "SymbolSwatches",
|
|
5
|
-
"Labels", "Title", "Background"
|
|
6
|
-
]);
|
|
7
|
-
|
|
8
|
-
const LEGEND_RESOURCE_POLICIES = Object.freeze({
|
|
9
|
-
series: Object.freeze({
|
|
10
|
-
semanticKind: "series",
|
|
11
|
-
family: "categorical",
|
|
12
|
-
rematerializeOp: undefined,
|
|
13
|
-
graphicIds: Object.freeze(CATEGORICAL_COMPONENTS.map(
|
|
14
|
-
component => `seriesLegend${component}`
|
|
15
|
-
))
|
|
16
|
-
}),
|
|
17
|
-
color: Object.freeze({
|
|
18
|
-
semanticKind: "color",
|
|
19
|
-
family: "categorical",
|
|
20
|
-
rematerializeOp: undefined,
|
|
21
|
-
graphicIds: Object.freeze(CATEGORICAL_COMPONENTS.map(
|
|
22
|
-
component => `colorLegend${component}`
|
|
23
|
-
))
|
|
24
|
-
}),
|
|
25
|
-
stroke: Object.freeze({
|
|
26
|
-
semanticKind: "stroke",
|
|
27
|
-
family: "categorical",
|
|
28
|
-
rematerializeOp: undefined,
|
|
29
|
-
graphicIds: Object.freeze(CATEGORICAL_COMPONENTS.map(
|
|
30
|
-
component => `strokeLegend${component}`
|
|
31
|
-
))
|
|
32
|
-
}),
|
|
33
|
-
size: Object.freeze({
|
|
34
|
-
semanticKind: "size",
|
|
35
|
-
family: "size",
|
|
36
|
-
rematerializeOp: "rematerializeSizeLegend",
|
|
37
|
-
graphicIds: Object.freeze([
|
|
38
|
-
"sizeLegendBackground", "sizeLegendSymbols", "sizeLegendLabels", "sizeLegendTitle"
|
|
39
|
-
])
|
|
40
|
-
}),
|
|
41
|
-
gradient: Object.freeze({
|
|
42
|
-
semanticKind: "color",
|
|
43
|
-
family: "continuous",
|
|
44
|
-
rematerializeOp: "rematerializeGradientLegend",
|
|
45
|
-
graphicIds: Object.freeze([
|
|
46
|
-
"colorGradientBackground", "colorGradientStrips", "colorGradientTicks",
|
|
47
|
-
"colorGradientLabels", "colorGradientTitle"
|
|
48
|
-
])
|
|
49
|
-
}),
|
|
50
|
-
interval: Object.freeze({
|
|
51
|
-
semanticKind: "color",
|
|
52
|
-
family: "interval",
|
|
53
|
-
rematerializeOp: "rematerializeIntervalLegend",
|
|
54
|
-
graphicIds: Object.freeze([
|
|
55
|
-
"colorLegendBackground", "colorLegendSymbols", "colorLegendLabels",
|
|
56
|
-
"colorLegendTitle"
|
|
57
|
-
])
|
|
58
|
-
}),
|
|
59
|
-
strokeGradient: Object.freeze({
|
|
60
|
-
semanticKind: "stroke",
|
|
61
|
-
family: "continuous",
|
|
62
|
-
rematerializeOp: "rematerializeStrokeGradientLegend",
|
|
63
|
-
graphicIds: Object.freeze([
|
|
64
|
-
"strokeGradientBackground", "strokeGradientStrips", "strokeGradientTicks",
|
|
65
|
-
"strokeGradientLabels", "strokeGradientTitle"
|
|
66
|
-
])
|
|
67
|
-
}),
|
|
68
|
-
strokeInterval: Object.freeze({
|
|
69
|
-
semanticKind: "stroke",
|
|
70
|
-
family: "interval",
|
|
71
|
-
rematerializeOp: "rematerializeStrokeIntervalLegend",
|
|
72
|
-
graphicIds: Object.freeze([
|
|
73
|
-
"strokeIntervalBackground", "strokeIntervalSymbols",
|
|
74
|
-
"strokeIntervalLabels", "strokeIntervalTitle"
|
|
75
|
-
])
|
|
76
|
-
}),
|
|
77
|
-
opacity: Object.freeze({
|
|
78
|
-
semanticKind: "opacity",
|
|
79
|
-
family: "continuous",
|
|
80
|
-
rematerializeOp: "rematerializeOpacityLegend",
|
|
81
|
-
graphicIds: Object.freeze([
|
|
82
|
-
"opacityLegendBackground", "opacityLegendSymbols", "opacityLegendLabels",
|
|
83
|
-
"opacityLegendTitle"
|
|
84
|
-
])
|
|
85
|
-
}),
|
|
86
|
-
strokeWidth: Object.freeze({
|
|
87
|
-
semanticKind: "strokeWidth",
|
|
88
|
-
family: "strokeWidth",
|
|
89
|
-
rematerializeOp: "rematerializeStrokeWidthLegend",
|
|
90
|
-
graphicIds: Object.freeze([
|
|
91
|
-
"strokeWidthLegendBackground", "strokeWidthLegendSymbols", "strokeWidthLegendLabels",
|
|
92
|
-
"strokeWidthLegendTitle"
|
|
93
|
-
])
|
|
94
|
-
})
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
export function axisGraphicIds(channel) {
|
|
98
|
-
const prefix = channel === "radius" ? "radial" : channel;
|
|
99
|
-
return AXIS_COMPONENTS.map(component => `${prefix}Axis${component}`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function legendGraphicIds(kind) {
|
|
103
|
-
return legendResourcePolicy(kind).graphicIds;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function legendResourcePolicy(kind) {
|
|
107
|
-
const policy = LEGEND_RESOURCE_POLICIES[kind];
|
|
108
|
-
if (policy === undefined) {
|
|
109
|
-
throw new Error(`Unknown legend graphic kind "${kind}".`);
|
|
110
|
-
}
|
|
111
|
-
return policy;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function legendResourcePolicies() {
|
|
115
|
-
return Object.entries(LEGEND_RESOURCE_POLICIES).map(([kind, policy]) => ({
|
|
116
|
-
kind,
|
|
117
|
-
...policy
|
|
118
|
-
}));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function allLegendGraphicIds(kinds) {
|
|
122
|
-
return [...new Set(kinds.flatMap(legendGraphicIds))];
|
|
123
|
-
}
|
|
1
|
+
import{axisGraphicIds as h}from"../../selectors/axes.js";import{localLegendPosition as m}from"../legendGroups.js";import{resolveConcreteGraphicBounds as G,unionConcreteGraphicBounds as v}from"../../grammar/schemas/graphicBounds.js";function y(e,t,s,o){const n=[],r=(i,a,l,c)=>{if(l===void 0)return;const p=c.filter(b=>e.objects[b]!==void 0),g=v(e,p,o);g!==void 0&&n.push({id:i,kind:a,position:l,bounds:g})};r("chart title","title",s?.position,["chartTitle","chartSubtitle"]);for(const[i,a]of Object.entries(t.axis??{})){const l=["line","ticks","labels","title"].map(p=>a[p]?.position).find(Boolean),c=["x","y"].includes(i)?i:["top","bottom"].includes(l)?"x":["left","right"].includes(l)?"y":void 0;if(c!==void 0)for(const[p,g]of["line","ticks","labels","title"].entries()){const b=h(c,i)[p],I=n.length;if(r(`${i}-axis ${g}`,"axis",a[g]?.position,[b]),n.length===I)continue;const z=n.at(-1);if(z.axisId=i,z.namedAxis=i!==c,["labels","title"].includes(g)){const x=e.objects[b];z.textBounds=x.items?.map(S=>G(e,S.id,o))??[z.bounds]}}}const f=T(),d=t.legend??{},u=f.find(i=>i.family==="categorical"&&d[i.kind]!==void 0),k=u!==void 0&&d.size!==void 0&&d[u.kind].target===d.size.target&&m(d[u.kind])===void 0&&m(d.size)===void 0&&(d.size.inheritPosition!==!1||d[u.kind].position===d.size.position),L=t.legendGroups??{};for(const[i,a]of Object.entries(L)){const l=f.filter(c=>d[c.kind]?.target===i&&m(d[c.kind])===void 0).flatMap(c=>c.graphicIds);r(`${i} legend group`,"legend",a.placement.position??"free",l)}for(const i of f){const a=d[i.kind];if(L[a?.target]!==void 0&&m(a)===void 0||a===void 0||k&&i.kind==="size")continue;const l=k&&i===u?[...i.graphicIds,...f.find(c=>c.kind==="size").graphicIds]:i.graphicIds;r(`${i.kind}${k&&i===u?"+size":""} legend`,"legend",m(a)??a.position,l)}return n}const O=Object.freeze(["Symbols","SymbolLines","SymbolPoints","SymbolSwatches","Labels","Title","Background"]),j=Object.freeze({colorSize:Object.freeze({semanticKind:"colorSize",family:"sampled",rematerializeOp:void 0,graphicIds:Object.freeze(["colorSizeLegendBackground","colorSizeLegendSymbols","colorSizeLegendLabels","colorSizeLegendTitle"])}),series:Object.freeze({semanticKind:"series",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(O.map(e=>`seriesLegend${e}`))}),color:Object.freeze({semanticKind:"color",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(O.map(e=>`colorLegend${e}`))}),stroke:Object.freeze({semanticKind:"stroke",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(O.map(e=>`strokeLegend${e}`))}),size:Object.freeze({semanticKind:"size",family:"size",rematerializeOp:"rematerializeSizeLegend",graphicIds:Object.freeze(["sizeLegendBackground","sizeLegendSymbols","sizeLegendLabels","sizeLegendTitle"])}),gradient:Object.freeze({semanticKind:"color",family:"continuous",rematerializeOp:"rematerializeGradientLegend",graphicIds:Object.freeze(["colorGradientBackground","colorGradientStrips","colorGradientTicks","colorGradientSymbols","colorGradientLabels","colorGradientTitle"])}),interval:Object.freeze({semanticKind:"color",family:"interval",rematerializeOp:"rematerializeIntervalLegend",graphicIds:Object.freeze(["colorLegendBackground","colorLegendSymbols","colorLegendLabels","colorLegendTitle"])}),strokeGradient:Object.freeze({semanticKind:"stroke",family:"continuous",rematerializeOp:"rematerializeStrokeGradientLegend",graphicIds:Object.freeze(["strokeGradientBackground","strokeGradientStrips","strokeGradientTicks","strokeGradientLabels","strokeGradientTitle"])}),strokeInterval:Object.freeze({semanticKind:"stroke",family:"interval",rematerializeOp:"rematerializeStrokeIntervalLegend",graphicIds:Object.freeze(["strokeIntervalBackground","strokeIntervalSymbols","strokeIntervalLabels","strokeIntervalTitle"])}),opacity:Object.freeze({semanticKind:"opacity",family:"continuous",rematerializeOp:"rematerializeOpacityLegend",graphicIds:Object.freeze(["opacityLegendBackground","opacityLegendSymbols","opacityLegendLabels","opacityLegendTitle"])}),strokeWidth:Object.freeze({semanticKind:"strokeWidth",family:"strokeWidth",rematerializeOp:"rematerializeStrokeWidthLegend",graphicIds:Object.freeze(["strokeWidthLegendBackground","strokeWidthLegendSymbols","strokeWidthLegendLabels","strokeWidthLegendTitle"])})});function B(e){return C(e).graphicIds}function C(e){const t=j[e];if(t===void 0)throw new Error(`Unknown legend graphic kind "${e}".`);return t}function T(){return Object.entries(j).map(([e,t])=>({kind:e,...t}))}function W(e){return[...new Set(e.flatMap(B))]}function w(e,t,s){if(t.inferredOffset!==!0||!["top","bottom"].includes(t.position)||t.layout==="legacy-bottom")return t.offset;const o=y(e.graphicSpec,e.guideConfigs,void 0,e.materializationConfigs.textMetrics).filter(r=>r.kind==="axis"&&r.position===t.position),n=t.position==="bottom"?Math.max(0,...o.map(r=>r.bounds.bottom-s.y-s.height)):Math.max(0,...o.map(r=>s.y-r.bounds.top));return t.offset+n}function K(e,t,s){if(t!=="size"||s.inheritPosition===!1)return s;const o=["series","color","stroke"].map(n=>e.guideConfigs.legend?.[n]).find(n=>n?.target===s.target);return o===void 0?s:{...s,position:o.position,offset:o.offset,inferredOffset:o.inferredOffset}}function M(e,t){return[e,t].some(o=>Object.entries(o.guideConfigs.legend??{}).some(([n,r])=>{const f=K(o,n,r);return f.inferredOffset===!0&&["top","bottom"].includes(f.position)&&f.layout!=="legacy-bottom"})||Object.values(o.guideConfigs.legendGroups??{}).some(n=>n.inferredOffset===!0&&["top","bottom"].includes(n.placement.position)))&&[e,t].some(o=>y(o.graphicSpec,o.guideConfigs,void 0,o.materializationConfigs.textMetrics).some(n=>n.kind==="axis"&&["top","bottom"].includes(n.position)))}export{W as allLegendGraphicIds,h as axisGraphicIds,K as effectiveLegendOffsetConfig,B as legendGraphicIds,T as legendResourcePolicies,C as legendResourcePolicy,M as needsLegendAxisReflow,y as resolveGuideCollisionBlocks,w as resolveLegendAxisOffset};
|
|
@@ -1,4 +1,57 @@
|
|
|
1
|
+
import { canDeferScaleConsumerApplication, getLayerScaleIds, getMarkMaterializationStep } from "./marks/index.js";
|
|
2
|
+
import { buildMaterializationPlan } from "./planner.js";
|
|
3
|
+
import { hasMaterializedLegend } from "./legends.js";
|
|
4
|
+
import { needsCanvasScaleRematerialization, planScaleGuideRematerialization } from "./scaleGuideDependencies.js";
|
|
5
|
+
|
|
6
|
+
export function planCanvasRematerialization(program) {
|
|
7
|
+
const marks = [];
|
|
8
|
+
for (const layer of program.semanticSpec.layers) {
|
|
9
|
+
const step = getMarkMaterializationStep(program, layer);
|
|
10
|
+
if (step !== undefined) marks.push(step);
|
|
11
|
+
}
|
|
12
|
+
const deferredMarkIds = new Set(
|
|
13
|
+
marks
|
|
14
|
+
.map(step => step.args.id)
|
|
15
|
+
);
|
|
16
|
+
const scales = [];
|
|
17
|
+
for (const scale of program.semanticSpec.scales) {
|
|
18
|
+
if (needsCanvasScaleRematerialization(program, scale)) {
|
|
19
|
+
const deferredConsumers = program.semanticSpec.layers.filter(layer =>
|
|
20
|
+
canDeferScaleConsumerApplication(layer) &&
|
|
21
|
+
getLayerScaleIds(layer).includes(scale.id)
|
|
22
|
+
);
|
|
23
|
+
const canDeferMarks = deferredConsumers.length === 0 ||
|
|
24
|
+
deferredConsumers.every(layer => deferredMarkIds.has(layer.id));
|
|
25
|
+
scales.push({
|
|
26
|
+
op: "rematerializeScale",
|
|
27
|
+
args: {
|
|
28
|
+
id: scale.id,
|
|
29
|
+
guides: false,
|
|
30
|
+
...(canDeferMarks ? { marks: false } : {})
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const guides = program.semanticSpec.scales.flatMap(scale =>
|
|
36
|
+
needsCanvasScaleRematerialization(program, scale)
|
|
37
|
+
? planScaleGuideRematerialization(program, scale.id)
|
|
38
|
+
: []
|
|
39
|
+
);
|
|
40
|
+
if (hasMaterializedLegend(program)) {
|
|
41
|
+
guides.push({ op: "rematerializeLegend" });
|
|
42
|
+
}
|
|
43
|
+
const layout = planLayoutRematerialization(program);
|
|
44
|
+
return buildMaterializationPlan({ scales, marks, guides, layout });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
1
48
|
const LAYOUT_CONSUMER_POLICIES = Object.freeze([
|
|
49
|
+
Object.freeze({
|
|
50
|
+
applies(program) {
|
|
51
|
+
return program.compositionSpec === undefined && program.materializationConfigs?.canvas?.plotBorder !== undefined;
|
|
52
|
+
},
|
|
53
|
+
step: Object.freeze({ op: "rematerializePlotBorder" })
|
|
54
|
+
}),
|
|
2
55
|
Object.freeze({
|
|
3
56
|
applies(program) {
|
|
4
57
|
return program.semanticSpec.title.text !== undefined &&
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { preserveGraphicPlacement } from './graphicHierarchy.js';
|
|
2
|
+
import { editGraphicProperties } from '../core/graphicProperties.js';
|
|
3
|
+
|
|
4
|
+
function borderCommands({x,y,width,height}) {
|
|
5
|
+
return [{op:'M',x,y},{op:'L',x:x+width,y},{op:'L',x:x+width,y:y+height},
|
|
6
|
+
{op:'L',x,y:y+height},{op:'Z'}];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function materializeLegendBorder(program,id,bounds,border) {
|
|
10
|
+
const dashed=border.strokeDash?.length>0,type=dashed?'path':'rect';
|
|
11
|
+
let next=program;
|
|
12
|
+
if(program.graphicSpec.objects[id]?.type!==type) {
|
|
13
|
+
const placement=preserveGraphicPlacement(program,id);
|
|
14
|
+
next=next.editGraphics({target:id,remove:true}).createGraphics({id,type,...placement});
|
|
15
|
+
}
|
|
16
|
+
return editGraphicProperties(next,id,{
|
|
17
|
+
...(dashed?{commands:borderCommands(bounds)}:bounds),
|
|
18
|
+
fill:border.background,stroke:border.color,strokeWidth:border.lineWidth,
|
|
19
|
+
...(dashed?{strokeDash:border.strokeDash}:{})
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function editLegendBorderBounds(program,id,bounds) {
|
|
24
|
+
if(program.graphicSpec.objects[id]?.type==='path') {
|
|
25
|
+
return program.editGraphics({target:id,property:'commands',value:borderCommands(bounds)});
|
|
26
|
+
}
|
|
27
|
+
return editGraphicProperties(program,id,{x:bounds.x,y:bounds.y,width:bounds.width,height:bounds.height});
|
|
28
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import{cloneAndFreeze}from'../core/immutable.js';
|
|
2
|
+
import{namespaceGraphicId}from'../selectors/graphicCapacity.js';
|
|
3
|
+
|
|
4
|
+
const clipPrefix=namespaceGraphicId('legendSampleClip','');
|
|
5
|
+
import{categoricalLegendSymbolId}from'../grammar/legendBlocks.js';
|
|
6
|
+
import{validateLegendClip}from'../grammar/legendClip.js';
|
|
7
|
+
import{hasRectRadius}from'../grammar/roundedRect.js';
|
|
8
|
+
import{resolvePrimitiveGraphicBounds}from'../grammar/schemas/graphicBounds.js';
|
|
9
|
+
import{translatedProperties}from'./graphicTranslation.js';
|
|
10
|
+
|
|
11
|
+
function roles(kind,config){
|
|
12
|
+
if(['series','color','stroke'].includes(kind))return config.symbol.layers.map(layer=>({
|
|
13
|
+
id:categoricalLegendSymbolId(config,layer.type),point:layer.type==='point',
|
|
14
|
+
type:layer.type==='line'?'line':layer.type==='point'?config.channels.includes('shape')?'collection':'circle':hasRectRadius(layer)?'collection':'rect'
|
|
15
|
+
}));
|
|
16
|
+
const prefix=kind==='gradient'?'colorGradient':`${kind}Legend`;
|
|
17
|
+
return [{id:`${prefix}Symbols`,type:'circle',point:true}];
|
|
18
|
+
}
|
|
19
|
+
function wrapped(owner){return owner?.children?.some(id=>id.startsWith(clipPrefix));}
|
|
20
|
+
export function hasLegendClipGraphics(program){
|
|
21
|
+
return Object.values(program.graphicSpec.objects).some(wrapped);
|
|
22
|
+
}
|
|
23
|
+
function copyGraphic(program,objects){return program._clone({graphicSpec:cloneAndFreeze({...program.graphicSpec,objects})});}
|
|
24
|
+
export function resetLegendClipGraphics(program){
|
|
25
|
+
if(!hasLegendClipGraphics(program))return program;
|
|
26
|
+
const objects={...program.graphicSpec.objects};
|
|
27
|
+
function remove(id){const object=objects[id];for(const child of object?.children??[])remove(child);delete objects[id];}
|
|
28
|
+
for(const[kind,config]of Object.entries(program.guideConfigs.legend??{})){
|
|
29
|
+
const descriptors=roles(kind,config);
|
|
30
|
+
if(!descriptors.some(role=>wrapped(objects[role.id])))continue;
|
|
31
|
+
for(const role of descriptors){
|
|
32
|
+
const owner=objects[role.id];if(!owner)continue;
|
|
33
|
+
for(const child of owner.children??[])remove(child);
|
|
34
|
+
objects[role.id]={type:role.type,items:[]};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return copyGraphic(program,objects);
|
|
38
|
+
}
|
|
39
|
+
function union(bounds){return bounds.reduce((a,b)=>a===undefined?b:{left:Math.min(a.left,b.left),right:Math.max(a.right,b.right),top:Math.min(a.top,b.top),bottom:Math.max(a.bottom,b.bottom)},undefined);}
|
|
40
|
+
function anchorY(type,p){
|
|
41
|
+
if(type==='circle')return p.y;
|
|
42
|
+
if(type==='rect')return p.y+p.height/2;
|
|
43
|
+
throw new Error('Path samples require their authored glyph anchor.');
|
|
44
|
+
}
|
|
45
|
+
export function applyLegendClipGraphics(program,{force=false,anchors={}}={}){
|
|
46
|
+
if(program.context.deferLegendClip===true&&!force)return program;
|
|
47
|
+
let next=program;
|
|
48
|
+
for(const[kind,config]of Object.entries(program.guideConfigs.legend??{})){
|
|
49
|
+
validateLegendClip(kind,config);if(config.clipHeight===undefined)continue;
|
|
50
|
+
if (["series","color","stroke"].includes(kind) && anchors[kind] === undefined && !force) continue;
|
|
51
|
+
const descriptors=roles(kind,config),owners=descriptors.map(role=>next.graphicSpec.objects[role.id]);
|
|
52
|
+
if(owners.some(wrapped)||owners.some(owner=>!owner?.items))continue;
|
|
53
|
+
const count=owners[0].items.length;if(count===0||owners.some(owner=>owner.items.length!==count))continue;
|
|
54
|
+
const objects={...next.graphicSpec.objects},primary=descriptors[0].id,children=[];
|
|
55
|
+
for(let index=0;index<count;index++){
|
|
56
|
+
const samples=owners.map((owner,j)=>({item:owner.items[index],type:owner.items[index].type??owner.type,role:descriptors[j]}));
|
|
57
|
+
const point=samples.find(sample=>sample.role.point),y=anchors[kind]?.[index]??anchorY(point.type,point.item.properties);
|
|
58
|
+
const b=union(samples.map(sample=>resolvePrimitiveGraphicBounds(sample.type,sample.item.properties)));
|
|
59
|
+
const x=b.left,top=y-config.clipHeight/2,id=namespaceGraphicId('legendSampleClip',namespaceGraphicId(`${primary}:${index}`,'window')),glyphs=[];
|
|
60
|
+
for(const sample of samples){
|
|
61
|
+
const glyph=namespaceGraphicId(sample.item.id,'glyph');glyphs.push(glyph);
|
|
62
|
+
objects[glyph]={type:sample.type,properties:translatedProperties(sample.type,sample.item.properties,-x,-top)};
|
|
63
|
+
}
|
|
64
|
+
objects[id]={type:'canvas',properties:{x,y:top,width:b.right-b.left,height:config.clipHeight},children:glyphs};children.push(id);
|
|
65
|
+
}
|
|
66
|
+
for(const descriptor of descriptors)objects[descriptor.id]={type:'collection',items:[],...(descriptor.id===primary?{children}:{})};
|
|
67
|
+
next=copyGraphic(next,objects);
|
|
68
|
+
}
|
|
69
|
+
return next;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function restoreLegendClipContext(program, previous) {
|
|
73
|
+
const {deferLegendClip: _deferred, ...context} = program.context;
|
|
74
|
+
if (previous !== undefined) context.deferLegendClip = previous;
|
|
75
|
+
return program._clone({context});
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{localLegendPosition as c,localLegendPlacement as s}from"../grammar/legendGroups.js";import"../core/vocabulary.js";import{PLOT_LEGEND_POSITIONS as l}from"../layout/legend.js";function u(n,t){return n.guideConfigs.legendGroups?.[t]}function f(n,t){const e=s(t),o=e===void 0?u(n,t.target)??(n.context.facetLegendIntrinsic===!0?{placement:{position:t.position}}:void 0):{placement:e};if(o===void 0)return t;const i=o.placement.position,r=l.includes(i),p=r?t.direction==="horizontal"?"top":"right":i??(l.includes(t.position)?t.direction==="horizontal"?"top":"right":t.position??"right");return{...t,...e===void 0?{}:{blockLocalPosition:e.position},position:p,offset:0,align:"left",groupIntrinsic:!0,...r?{groupCorner:!0}:{},...p==="right"?{align:"center"}:{}}}export{f as intrinsicLegendConfig,u as legendGroup,s as localLegendPlacement,c as localLegendPosition};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{materializeTextLines as o}from"./textLines.js";import{resolveLegendTitleText as l}from"../layout/legendText.js";function m(e,i,t,n){const{lines:r}=l(t,n,e.materializationConfigs.textMetrics);return o(e,i,r)}export{m as materializeLegendTitleLines};
|
|
@@ -2,7 +2,7 @@ import { findLayer } from "../selectors/layers.js";
|
|
|
2
2
|
import { LEGEND_CONFIG_KINDS } from "../core/vocabulary.js";
|
|
3
3
|
|
|
4
4
|
export function isOpacityLegendLayer(layer) {
|
|
5
|
-
return ["point", "line"].includes(layer.mark?.type) &&
|
|
5
|
+
return ["point", "line", "bar"].includes(layer.mark?.type) &&
|
|
6
6
|
layer.encoding?.opacity?.scale !== undefined;
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -24,6 +24,6 @@ export function materializedLegendUsesScale(program, id) {
|
|
|
24
24
|
const order = program.semanticSpec.guides.legend?.[kind]?.order;
|
|
25
25
|
const linkedScale = order?.channel === undefined ? undefined
|
|
26
26
|
: findLayer(program, config.target)?.encoding?.[order.channel]?.scale;
|
|
27
|
-
return config.scale === id || config.scales?.includes(id) || linkedScale === id;
|
|
27
|
+
return config.colorScale === id || config.sizeScale === id || config.scale === id || config.scales?.includes(id) || linkedScale === id;
|
|
28
28
|
});
|
|
29
29
|
}
|
|
@@ -60,10 +60,10 @@ export function canMaterializeLine(program, layer) {
|
|
|
60
60
|
layer.mark?.type === "line" &&
|
|
61
61
|
hasCartesianPositionScales(layer) &&
|
|
62
62
|
((isAggregate(layer.encoding.y.aggregate) &&
|
|
63
|
-
(layer.encoding.x.fieldType
|
|
63
|
+
(["temporal", "nominal", "ordinal"].includes(layer.encoding.x.fieldType) ||
|
|
64
64
|
(layer.encoding.x.fieldType === "quantitative" &&
|
|
65
65
|
layer.encoding.x.bin !== undefined))) ||
|
|
66
|
-
(layer.encoding.x.fieldType
|
|
66
|
+
(["quantitative", "nominal", "ordinal"].includes(layer.encoding.x.fieldType) &&
|
|
67
67
|
layer.encoding.y.fieldType === "quantitative") ||
|
|
68
68
|
((interval || layer.encoding.y.aggregate === undefined) &&
|
|
69
69
|
["quantitative", "temporal"].includes(layer.encoding.x.fieldType) &&
|
|
@@ -114,15 +114,14 @@ export function canMaterializeArc(_program, layer) {
|
|
|
114
114
|
}
|
|
115
115
|
if (layer.encoding.theta.fieldType === "quantitative") {
|
|
116
116
|
return (
|
|
117
|
-
layer.encoding.theta.aggregate === undefined
|
|
118
|
-
layer.encoding?.radius === undefined
|
|
117
|
+
layer.encoding.theta.aggregate === undefined
|
|
119
118
|
);
|
|
120
119
|
}
|
|
121
120
|
if (!["nominal", "ordinal"].includes(layer.encoding.theta.fieldType)) {
|
|
122
121
|
return false;
|
|
123
122
|
}
|
|
124
123
|
if (["count", "sum"].includes(layer.encoding.theta.aggregate)) {
|
|
125
|
-
return
|
|
124
|
+
return true;
|
|
126
125
|
}
|
|
127
126
|
return (
|
|
128
127
|
layer.encoding.theta.aggregate === undefined &&
|
|
@@ -136,7 +135,7 @@ export function canMaterializeBar(program, layer) {
|
|
|
136
135
|
return false;
|
|
137
136
|
}
|
|
138
137
|
const grain = resolveBarGrain(layer);
|
|
139
|
-
if (![BAR_GRAINS.histogram, BAR_GRAINS.aggregate, BAR_GRAINS.ranged].includes(grain)) {
|
|
138
|
+
if (![BAR_GRAINS.histogram, BAR_GRAINS.aggregate, BAR_GRAINS.ranged, BAR_GRAINS.centered].includes(grain)) {
|
|
140
139
|
return false;
|
|
141
140
|
}
|
|
142
141
|
if (
|
|
@@ -144,7 +144,7 @@ export function resolveRectRows(program, layer, dataset) {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
export function resolveRectGraphicItems(program, layer, dataset) {
|
|
147
|
-
const radius = program.markConfigs[layer.id]
|
|
147
|
+
const radius = program.markConfigs[layer.id] ?? {};
|
|
148
148
|
return resolveRectRows(program, layer, dataset).map(item =>
|
|
149
149
|
materializeRectItem(item.properties, radius)
|
|
150
150
|
);
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
+
import { cartesianAxisOwners, axisTargetArgs } from "../selectors/axes.js";
|
|
2
|
+
import { axisGraphicIds } from "./guides/resources.js";
|
|
1
3
|
import { isSourceOwnedText } from "../grammar/text.js";
|
|
2
4
|
import { POSITION_ENCODING_CHANNELS } from "../core/vocabulary.js";
|
|
3
5
|
import { materializedLegendUsesScale } from "./legends.js";
|
|
4
6
|
import { buildMaterializationPlan } from "./planner.js";
|
|
5
7
|
|
|
6
|
-
const CARTESIAN_AXES = Object.freeze([
|
|
7
|
-
Object.freeze({ channel: "x", prefix: "X", lineObject: "xAxisLine" }),
|
|
8
|
-
Object.freeze({ channel: "y", prefix: "Y", lineObject: "yAxisLine" })
|
|
9
|
-
]);
|
|
10
8
|
const POLAR_AXES = Object.freeze([
|
|
11
9
|
Object.freeze({ channel: "theta", prefix: "Theta" }),
|
|
12
10
|
Object.freeze({ channel: "radius", prefix: "Radial" })
|
|
@@ -18,8 +16,8 @@ const GRIDS = Object.freeze([
|
|
|
18
16
|
Object.freeze({ role: "radial", op: "rematerializeRadialGrid" })
|
|
19
17
|
]);
|
|
20
18
|
|
|
21
|
-
export function planAxisTickGridRematerialization(program, channel) {
|
|
22
|
-
const ticks = program.guideConfigs.axis?.[
|
|
19
|
+
export function planAxisTickGridRematerialization(program, channel, axisId = channel) {
|
|
20
|
+
const ticks = program.guideConfigs.axis?.[axisId]?.ticks;
|
|
23
21
|
const role = channel === "x" ? "vertical" : "horizontal";
|
|
24
22
|
const config = program.guideConfigs.grid?.[role];
|
|
25
23
|
return buildMaterializationPlan({ guides:
|
|
@@ -48,9 +46,7 @@ function usesRadialScale(program, id) {
|
|
|
48
46
|
|
|
49
47
|
function semanticGuideUsesScale(program, id) {
|
|
50
48
|
return [
|
|
51
|
-
...
|
|
52
|
-
program.semanticSpec.guides.axis?.[channel]
|
|
53
|
-
),
|
|
49
|
+
...cartesianAxisOwners(program).map(owner => owner.semantic),
|
|
54
50
|
...POLAR_AXES.map(({ channel }) =>
|
|
55
51
|
program.semanticSpec.guides.axis?.[channel]
|
|
56
52
|
),
|
|
@@ -64,7 +60,7 @@ export function needsCanvasScaleRematerialization(program, scale) {
|
|
|
64
60
|
usesRadialScale(program, scale.id) ||
|
|
65
61
|
semanticGuideUsesScale(program, scale.id)) &&
|
|
66
62
|
(program.resolvedScales[scale.id] !== undefined || scale.radialMapping !== undefined) &&
|
|
67
|
-
usesPositionalScale(program, scale.id)
|
|
63
|
+
(usesPositionalScale(program, scale.id) || semanticGuideUsesScale(program, scale.id))
|
|
68
64
|
);
|
|
69
65
|
}
|
|
70
66
|
|
|
@@ -83,16 +79,19 @@ export function planScaleGuideRematerialization(program, id) {
|
|
|
83
79
|
args: { target: guideConfigs.axis.parallel.axes.target }
|
|
84
80
|
});
|
|
85
81
|
}
|
|
86
|
-
for (const
|
|
82
|
+
for (const owner of cartesianAxisOwners(program)) {
|
|
83
|
+
const axis = { channel: owner.channel, prefix: owner.channel.toUpperCase(),
|
|
84
|
+
lineObject: axisGraphicIds(owner.channel, owner.id)[0] };
|
|
85
|
+
const target = owner.named ? { args: axisTargetArgs(owner.id, owner.channel) } : {};
|
|
87
86
|
if (
|
|
88
87
|
objects[axis.lineObject] &&
|
|
89
|
-
|
|
88
|
+
owner.scale === id
|
|
90
89
|
) {
|
|
91
|
-
guides.push({ op: componentEditOperation(axis.prefix, "line") });
|
|
90
|
+
guides.push({ op: componentEditOperation(axis.prefix, "line"), ...target });
|
|
92
91
|
}
|
|
93
92
|
for (const component of ["ticks", "labels", "title"]) {
|
|
94
|
-
if (guideConfigs.axis?.[
|
|
95
|
-
guides.push({ op: componentEditOperation(axis.prefix, component) });
|
|
93
|
+
if (guideConfigs.axis?.[owner.id]?.[component]?.scale === id) {
|
|
94
|
+
guides.push({ op: componentEditOperation(axis.prefix, component), ...target });
|
|
96
95
|
}
|
|
97
96
|
}
|
|
98
97
|
}
|
|
@@ -20,7 +20,13 @@ export function mapScaleConsumerValues(values, resolvedScale, channel) {
|
|
|
20
20
|
resolvedScale.domain,
|
|
21
21
|
resolvedScale.range,
|
|
22
22
|
{
|
|
23
|
+
type: resolvedScale.type,
|
|
24
|
+
base: resolvedScale.base,
|
|
25
|
+
exponent: resolvedScale.exponent,
|
|
26
|
+
constant: resolvedScale.constant,
|
|
23
27
|
interpolation: resolvedScale.interpolate,
|
|
28
|
+
whitePoint: resolvedScale.whitePoint,
|
|
29
|
+
gamma: resolvedScale.gamma,
|
|
24
30
|
midpoint: resolvedScale.midpoint,
|
|
25
31
|
clamp: resolvedScale.clamp ?? false,
|
|
26
32
|
...(Object.hasOwn(resolvedScale, "unknown")
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveArcInnerRadius } from "../../../grammar/arcs.js";
|
|
1
2
|
import { validateMeasuredRadiusScale } from "../../../grammar/scales/radial.js";
|
|
2
3
|
export function resolveArcAutoPositionRange({
|
|
3
4
|
consumers,
|
|
@@ -15,16 +16,16 @@ export function resolveArcAutoPositionRange({
|
|
|
15
16
|
return range;
|
|
16
17
|
}
|
|
17
18
|
if (channel === "radius") {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const outer = Math.max(...range);
|
|
20
|
+
const inner = consumers.map(consumer => resolveArcInnerRadius(
|
|
21
|
+
markConfigs[consumer.layer.id]?.innerRadius ?? 0, outer
|
|
22
|
+
));
|
|
23
|
+
if (new Set(inner).size !== 1) {
|
|
22
24
|
throw new Error(
|
|
23
25
|
`Shared arc radius scale "${scale.id}" requires one innerRadius policy.`
|
|
24
26
|
);
|
|
25
27
|
}
|
|
26
|
-
|
|
27
|
-
return [outer * ratios[0], outer];
|
|
28
|
+
return [inner[0], outer];
|
|
28
29
|
}
|
|
29
30
|
if (
|
|
30
31
|
channel === "theta" &&
|
|
@@ -67,7 +68,7 @@ export function validateMeasuredRadiusConsumers({ scale, domain, range, consumer
|
|
|
67
68
|
const config = markConfigs[layer.id] ?? {};
|
|
68
69
|
if ((config.padAngle ?? 0) !== 0) throw new Error("Measured Arc requires padAngle 0.");
|
|
69
70
|
if (scale.range !== "auto" && config.innerRadiusExplicit === true &&
|
|
70
|
-
Math.abs(config.innerRadius - range[0] / range[1]) > Number.EPSILON * 8) {
|
|
71
|
+
Math.abs(resolveArcInnerRadius(config.innerRadius, range[1]) / range[1] - range[0] / range[1]) > Number.EPSILON * 8) {
|
|
71
72
|
throw new Error("Measured Arc innerRadius must agree with its explicit radius range.");
|
|
72
73
|
}
|
|
73
74
|
}
|
|
@@ -7,14 +7,19 @@ import {
|
|
|
7
7
|
interpolateNumber,
|
|
8
8
|
inverseLerp
|
|
9
9
|
} from "../../../grammar/numeric.js";
|
|
10
|
-
export function resolveTemporalBarBand(valuesByConsumer, domain, range) {
|
|
10
|
+
export function resolveTemporalBarBand(valuesByConsumer, domain, range, markConfigs = {}, preserveRange = false) {
|
|
11
11
|
const temporalBars = valuesByConsumer.filter(({ consumer }) => {
|
|
12
12
|
const channels = resolveBarChannels(consumer.layer);
|
|
13
|
-
return resolveBarGrain(consumer.layer)
|
|
13
|
+
return [BAR_GRAINS.aggregate, BAR_GRAINS.ranged].includes(resolveBarGrain(consumer.layer)) &&
|
|
14
14
|
channels?.category === consumer.channel &&
|
|
15
15
|
consumer.encoding.fieldType === "temporal";
|
|
16
16
|
});
|
|
17
17
|
if (temporalBars.length === 0) return undefined;
|
|
18
|
+
const intervalBars = temporalBars.filter(({consumer}) => markConfigs[consumer.layer.id]?.barWidth?.interval !== undefined);
|
|
19
|
+
if (intervalBars.length > 0) {
|
|
20
|
+
if (intervalBars.length !== temporalBars.length) throw new Error("Temporal interval and centered bar widths cannot share a position scale.");
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
18
23
|
const temporalFields = new Set(
|
|
19
24
|
temporalBars.map(({ consumer }) => consumer.encoding.field)
|
|
20
25
|
);
|
|
@@ -31,6 +36,11 @@ export function resolveTemporalBarBand(valuesByConsumer, domain, range) {
|
|
|
31
36
|
"A temporal bar position scale requires compatible bar, line, rule, or text consumers of one field."
|
|
32
37
|
);
|
|
33
38
|
}
|
|
39
|
+
// Explicit pixel widths do not need an inferred half-slot inset. Keep one
|
|
40
|
+
// common range when a shared scale also has band-width bar consumers.
|
|
41
|
+
preserveRange ||= temporalBars.every(({ consumer }) =>
|
|
42
|
+
markConfigs[consumer.layer.id]?.barWidth?.pixels !== undefined
|
|
43
|
+
);
|
|
34
44
|
const values = temporalBars.flatMap(item => item.values);
|
|
35
45
|
const ordered = [...new Set(values)].sort((left, right) => left - right);
|
|
36
46
|
if (ordered.length < 2) {
|
|
@@ -59,7 +69,7 @@ export function resolveTemporalBarBand(valuesByConsumer, domain, range) {
|
|
|
59
69
|
interpolateNumber(range[0], range[1], bandwidthFraction) - range[0]
|
|
60
70
|
)
|
|
61
71
|
: directBandwidth;
|
|
62
|
-
const resolvedRange = stable
|
|
72
|
+
const resolvedRange = preserveRange ? [...range] : stable
|
|
63
73
|
? [
|
|
64
74
|
interpolateNumber(range[0], range[1], bandwidthFraction / 2),
|
|
65
75
|
interpolateNumber(range[0], range[1], 1 - bandwidthFraction / 2)
|
|
@@ -79,10 +89,12 @@ export function resolveTemporalBarBand(valuesByConsumer, domain, range) {
|
|
|
79
89
|
});
|
|
80
90
|
let bandwidth = Infinity;
|
|
81
91
|
for (let index = 1; index < positions.length; index += 1) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
const gap = Math.abs(positions[index] - positions[index - 1]);
|
|
93
|
+
// Keep a finite conservative slot when an explicitly requested span itself
|
|
94
|
+
// exceeds the representable difference of two finite endpoints.
|
|
95
|
+
bandwidth = Math.min(bandwidth,
|
|
96
|
+
preserveRange && !Number.isFinite(gap) ? estimatedBandwidth : gap);
|
|
97
|
+
|
|
86
98
|
}
|
|
87
99
|
if (![estimatedBandwidth, bandwidth, ...resolvedRange].every(Number.isFinite)) {
|
|
88
100
|
throw new RangeError("Temporal bar position exceeds the finite numeric range.");
|
|
@@ -27,11 +27,13 @@ export function resolveSeriesLayoutDomain({
|
|
|
27
27
|
seriesLayouts[index] === undefined &&
|
|
28
28
|
!isSourceOwnedText(consumer.layer)
|
|
29
29
|
);
|
|
30
|
+
const layout = activeLayouts[0].layout;
|
|
30
31
|
const compatibleDirect = directConsumers.every(({ consumer }) =>
|
|
31
32
|
layoutConsumers.every(({ consumer: layoutConsumer }) => {
|
|
32
33
|
const aggregate = layoutConsumer.encoding.aggregate;
|
|
33
34
|
const directEncoding = consumer.encoding;
|
|
34
35
|
return directEncoding.fieldType === "quantitative" && (
|
|
36
|
+
layout === "overlay" ||
|
|
35
37
|
Object.hasOwn(directEncoding, "datum") ||
|
|
36
38
|
(
|
|
37
39
|
aggregate !== "count" &&
|
|
@@ -45,7 +47,6 @@ export function resolveSeriesLayoutDomain({
|
|
|
45
47
|
`Series layout scale "${id}" cannot be shared with another policy.`
|
|
46
48
|
);
|
|
47
49
|
}
|
|
48
|
-
const layout = activeLayouts[0].layout;
|
|
49
50
|
const values = [
|
|
50
51
|
...activeLayouts.flatMap(item => item.values),
|
|
51
52
|
...directConsumers.flatMap(item => item.values)
|