ggaction 0.0.20 → 0.0.22
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 +77 -0
- package/README.md +3 -2
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/package.json +6 -2
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +1 -0
- package/src/actions/boxPlots/components.js +1 -96
- package/src/actions/boxPlots/create.js +6 -1
- package/src/actions/boxPlots/edit.js +8 -0
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +1 -1
- package/src/actions/canvas/actions.js +2 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +2 -1
- package/src/actions/canvas/plotOrder.js +14 -0
- package/src/actions/charts/area.js +1 -1
- package/src/actions/charts/bar.js +4 -4
- package/src/actions/charts/density.js +1 -1
- package/src/actions/charts/ecdf.js +1 -1
- package/src/actions/charts/endpoints.js +54 -5
- package/src/actions/charts/heatmap.js +1 -1
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +1 -1
- package/src/actions/charts/index.js +2 -1
- package/src/actions/charts/interval-regression.js +1 -1
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/radar.js +63 -3
- package/src/actions/charts/raincloud.js +37 -6
- package/src/actions/charts/rug-strip.js +2 -2
- package/src/actions/charts/scatter.js +3 -3
- package/src/actions/charts/text.js +1 -1
- package/src/actions/composition/actions.js +10 -3
- package/src/actions/coordinates/actions.js +2 -0
- package/src/actions/data/basic.js +6 -0
- package/src/actions/data/derived.js +6 -2
- package/src/actions/data/edit.js +21 -35
- package/src/actions/data/filter.js +1 -376
- package/src/actions/data/flatten.js +19 -0
- package/src/actions/data/index.js +6 -0
- package/src/actions/data/lookup.js +50 -0
- package/src/actions/data/raincloudFilter.js +56 -0
- package/src/actions/data/shared.js +10 -0
- package/src/actions/distributions/revision.js +3 -1
- package/src/actions/encodings/angle.js +2 -1
- package/src/actions/encodings/appearance.js +38 -13
- package/src/actions/encodings/channels.js +14 -3
- package/src/actions/encodings/color/continuous.js +26 -10
- package/src/actions/encodings/color/index.js +50 -20
- package/src/actions/encodings/color/policy.js +57 -4
- package/src/actions/encodings/parallel.js +4 -0
- package/src/actions/encodings/position/policies/bar.js +10 -6
- package/src/actions/encodings/position/policies/line.js +1 -152
- package/src/actions/encodings/position/resolve.js +4 -1
- package/src/actions/encodings/ranged.js +46 -0
- package/src/actions/encodings/remove.js +1 -1
- package/src/actions/encodings/ruleAppearance.js +7 -6
- package/src/actions/encodings/shared.js +14 -2
- package/src/actions/encodings/stroke.js +20 -14
- package/src/actions/encodings/strokeDash.js +9 -11
- package/src/actions/encodings/text.js +30 -2
- package/src/actions/errorBars/edit.js +24 -4
- package/src/actions/facets/actions.js +1 -793
- package/src/actions/facets/derive.js +34 -5
- package/src/actions/facets/replay.js +1 -38
- package/src/actions/gradientPlots/components.js +21 -9
- package/src/actions/gradientPlots/create.js +4 -2
- package/src/actions/gradientPlots/edit.js +18 -1
- package/src/actions/gradientPlots/materialize.js +35 -36
- package/src/actions/gradientPlots/options.js +1 -1
- package/src/actions/gradientPlots/paint.js +13 -7
- package/src/actions/guides/applicability.js +2 -0
- package/src/actions/guides/axes/axes.js +39 -0
- package/src/actions/guides/axes/axis.js +9 -5
- package/src/actions/guides/axes/edit.js +11 -8
- package/src/actions/guides/axes/labels.js +24 -8
- package/src/actions/guides/axes/parallel/policy.js +3 -5
- package/src/actions/guides/axes/parallel/resolve.js +4 -3
- package/src/actions/guides/axes/policy.js +22 -0
- package/src/actions/guides/axes/tickGroups.js +4 -2
- package/src/actions/guides/axes/ticks.js +22 -4
- package/src/actions/guides/axes/titles.js +19 -6
- package/src/actions/guides/facadeAxes.js +3 -0
- package/src/actions/guides/grids/grid.js +3 -1
- package/src/actions/guides/grids/resolve.js +7 -11
- package/src/actions/guides/guides.js +21 -1
- package/src/actions/guides/legends/categorical/actions.js +1 -515
- package/src/actions/guides/legends/categorical/layout.js +5 -2
- package/src/actions/guides/legends/categorical/options.js +4 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -3
- package/src/actions/guides/legends/categorical/symbols.js +3 -3
- package/src/actions/guides/legends/colorSize.js +2 -0
- package/src/actions/guides/legends/continuous/common.js +1 -1
- package/src/actions/guides/legends/continuous/gradient.js +1 -360
- package/src/actions/guides/legends/continuous/interval.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +15 -1
- package/src/actions/guides/legends/edit.js +1 -1
- package/src/actions/guides/legends/focused.js +1 -1
- package/src/actions/guides/legends/lane.js +1 -1
- package/src/actions/guides/legends/sampling.js +1 -140
- package/src/actions/guides/legends/strokeWidth.js +3 -2
- package/src/actions/guides/legends/transition.js +19 -2
- package/src/actions/guides/polar/axes/facade.js +5 -4
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/resolve.js +2 -1
- package/src/actions/guides/tickValues.js +7 -7
- package/src/actions/marks/arc/actions.js +5 -7
- package/src/actions/marks/area/actions.js +9 -3
- package/src/actions/marks/bar/create.js +4 -2
- package/src/actions/marks/bar/edit.js +9 -6
- package/src/actions/marks/bar/materialize.js +5 -3
- package/src/actions/marks/dependentReferences.js +87 -0
- package/src/actions/marks/index.js +2 -0
- package/src/actions/marks/lifecycle.js +2 -1
- package/src/actions/marks/line/actions.js +24 -6
- package/src/actions/marks/line/materialize.js +7 -3
- package/src/actions/marks/point/create.js +5 -4
- package/src/actions/marks/point/edit.js +6 -3
- package/src/actions/marks/point/jitter.js +6 -6
- package/src/actions/marks/point/materialize.js +36 -39
- package/src/actions/marks/rect/actions.js +16 -4
- package/src/actions/marks/references.js +183 -35
- package/src/actions/marks/rule/actions.js +20 -9
- package/src/actions/marks/shared.js +2 -1
- package/src/actions/marks/text/actions.js +10 -3
- package/src/actions/marks/tick/actions.js +11 -2
- package/src/actions/primitives/editGraphics.js +28 -3
- package/src/actions/primitives/index.js +9 -0
- package/src/actions/primitives/semanticAction.js +13 -7
- package/src/actions/primitives/semanticValidation/layer.js +20 -0
- package/src/actions/primitives/semanticValidation/scale.js +8 -1
- package/src/actions/scales/channels.js +1 -1
- package/src/actions/scales/consumers/common.js +19 -1
- package/src/actions/scales/consumers/families.js +2 -2
- package/src/actions/scales/consumers/seriesLayout.js +4 -3
- package/src/actions/scales/edit.js +11 -0
- package/src/actions/scales/editPolicy.js +5 -3
- package/src/actions/scales/preview.js +2 -1
- package/src/actions/scales/quantitativeColor.js +2 -4
- package/src/actions/selection/actions.js +10 -1
- package/src/actions/theme/reconcile.js +37 -9
- package/src/core/compositionState.js +21 -4
- package/src/core/datasetReferences.js +17 -0
- package/src/core/immutable.js +1 -0
- package/src/core/resourceReferences.js +5 -3
- package/src/core/validation.js +4 -0
- package/src/core/vocabulary.js +24 -2
- package/src/grammar/aggregate.js +1 -0
- package/src/grammar/arcs.js +1 -1
- package/src/grammar/areaSeries.js +1 -1
- package/src/grammar/axisTextPlacement.js +1 -1
- package/src/grammar/bars/policy.js +2 -2
- package/src/grammar/datasetDependencies.js +97 -0
- package/src/grammar/datasetSchema.js +31 -1
- package/src/grammar/facetHeaders.js +1 -0
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/legendIntent.js +6 -2
- package/src/grammar/facets/scales.js +27 -7
- package/src/grammar/flatten.js +85 -0
- package/src/grammar/histogram.js +13 -1
- package/src/grammar/horizon.js +1 -494
- package/src/grammar/interval.js +4 -3
- package/src/grammar/jitter.js +2 -2
- package/src/grammar/legendSpacing.js +45 -0
- package/src/grammar/lineMissing.js +90 -0
- package/src/grammar/lineSeries.js +1 -376
- package/src/grammar/lookup.js +97 -0
- package/src/grammar/markFilter.js +4 -2
- package/src/grammar/markLabels.js +8 -3
- package/src/grammar/numeric.js +1 -1
- package/src/grammar/pathCommands.js +1 -1
- package/src/grammar/pathSeries.js +1 -109
- package/src/grammar/pointPolygon.js +81 -0
- package/src/grammar/pointShapes.js +15 -15
- package/src/grammar/rects.js +1 -1
- package/src/grammar/regression/derive.js +1 -195
- package/src/grammar/scales/appearance.js +1 -1
- package/src/grammar/scales/color.js +1 -1
- package/src/grammar/scales/colorConsumers.js +1 -63
- package/src/grammar/scales/continuous.js +58 -1
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/ordinal.js +5 -2
- package/src/grammar/scales/policies.js +3 -3
- package/src/grammar/schemas/graphicBounds.js +40 -12
- package/src/grammar/schemas/graphicClip.js +23 -0
- package/src/grammar/schemas/graphicTree.js +16 -0
- package/src/grammar/statisticalReference.js +22 -0
- package/src/grammar/text.js +65 -1
- package/src/grammar/ticks.js +92 -1
- package/src/grammar/transformTopology.js +3 -1
- package/src/grammar/transforms.js +24 -0
- package/src/inspection.js +1 -495
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -1
- package/src/layout/facetContentFlow.js +101 -0
- package/src/layout/facetHeaderTitles.js +1 -0
- package/src/layout/facetTopLane.js +29 -0
- package/src/layout/facets.js +1 -1
- package/src/layout/legendGroup.js +1 -1
- package/src/layout/legendItems.js +26 -10
- package/src/layout/legendSpacing.js +66 -0
- package/src/layout/plotDrawOrder.js +17 -0
- package/src/materialization/bars/histogram.js +8 -2
- package/src/materialization/bars/resolve.js +1 -1
- package/src/materialization/composition.js +1 -0
- package/src/materialization/compositionSnapshot.js +22 -2
- package/src/materialization/dataProvenance.js +1 -1
- package/src/materialization/facetGuides/layout.js +1 -1
- package/src/materialization/facetGuides/placement.js +23 -11
- package/src/materialization/facetGuides/preparation.js +43 -24
- package/src/materialization/facetHeaders.js +1 -1
- package/src/materialization/facetTopLane.js +25 -0
- package/src/materialization/facets.js +1 -1
- package/src/materialization/graphicHierarchy.js +5 -0
- package/src/materialization/graphicTranslation.js +2 -0
- package/src/materialization/guides/layout.js +1 -1
- package/src/materialization/guides/resources.js +1 -1
- package/src/materialization/legends.js +1 -1
- package/src/materialization/markClip.js +26 -0
- package/src/materialization/marks/capabilities.js +3 -2
- package/src/materialization/marks/grouping.js +7 -5
- package/src/materialization/marks/index.js +4 -2
- package/src/materialization/marks/policies.js +4 -0
- package/src/materialization/plotDrawOrder.js +44 -0
- package/src/materialization/rect.js +11 -3
- package/src/materialization/scaleGuideDependencies.js +13 -5
- package/src/materialization/selection/items/common.js +1 -1
- package/src/materialization/selection/items/path.js +1 -0
- package/src/materialization/selection/policies/index.js +2 -1
- package/src/materialization/text.js +10 -5
- package/src/mcp/cli.js +0 -0
- package/src/persistence/validation.js +108 -18
- package/src/persistence.js +4 -2
- package/src/renderers/canvas/index.js +16 -0
- package/src/renderers/canvas/isolated.js +11 -1
- package/src/renderers/canvas/native.js +7 -0
- package/src/renderers/canvas/scene.js +66 -0
- package/src/renderers/canvas/text.js +3 -2
- package/src/renderers/svg.js +1 -617
- package/src/renderers/svgSerialization.js +643 -0
- package/src/renderers/transitionScene.js +67 -0
- package/src/scene.js +89 -0
- package/src/selectors/axes.js +1 -1
- package/src/selectors/datasets.js +1 -0
- package/src/selectors/grids.js +9 -0
- package/src/selectors/markClip.js +28 -0
- package/src/selectors/raincloud.js +7 -0
- package/src/version.js +1 -1
- package/types/basic.d.ts +7 -0
- package/types/index.d.ts +8 -0
- package/types/program.d.ts +270 -60
- package/types/scene.d.ts +35 -0
|
@@ -2,7 +2,7 @@ import { localLegendPosition } from "../../grammar/legendGroups.js";
|
|
|
2
2
|
import { prepareAuthoredFacetLegend, facetLegendIntent } from "./owner.js";
|
|
3
3
|
import { isPlainObject } from "../../core/immutable.js";
|
|
4
4
|
import { PLOT_LEGEND_POSITIONS } from "../../layout/legend.js";
|
|
5
|
-
import { isContinuousColorScaleType } from "../../grammar/scales/types.js";
|
|
5
|
+
import { isContinuousColorScaleType, isDiscretizedColorScaleType } from "../../grammar/scales/types.js";
|
|
6
6
|
import {
|
|
7
7
|
unionConcreteGraphicBounds
|
|
8
8
|
} from "../../grammar/schemas/graphicBounds.js";
|
|
@@ -83,8 +83,24 @@ function continuousValues(domain, count) {
|
|
|
83
83
|
return sampleNumericRange(domain[0], domain[1], count, "Facet legend domain");
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
function legendRequests(program) {
|
|
87
|
+
const request = legendCandidates(program);
|
|
88
|
+
const layer = findLayer(program, request.target);
|
|
89
|
+
const separate = request.channels.filter(channel => {
|
|
90
|
+
if (!["color", "stroke"].includes(channel)) return false;
|
|
91
|
+
const type = findSemanticScale(program, layer.encoding[channel].scale)?.type;
|
|
92
|
+
return isContinuousColorScaleType(type) || isDiscretizedColorScaleType(type);
|
|
93
|
+
});
|
|
94
|
+
if (separate.length === 0) return [request];
|
|
95
|
+
const categorical = request.channels.filter(channel => !separate.includes(channel));
|
|
96
|
+
return [
|
|
97
|
+
...(categorical.length === 0 ? [] : [{ ...request, channels: categorical }]),
|
|
98
|
+
...separate.map(channel => ({ ...request, channels: [channel] }))
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
|
|
86
102
|
function prepareAutoLegendSource(child) {
|
|
87
|
-
const
|
|
103
|
+
const requests = legendRequests(child);
|
|
88
104
|
const canvas = child.graphicSpec.objects.canvas;
|
|
89
105
|
let source = child
|
|
90
106
|
._withCanvasConfig({
|
|
@@ -99,7 +115,7 @@ function prepareAutoLegendSource(child) {
|
|
|
99
115
|
.editGraphics({
|
|
100
116
|
target: "canvas",
|
|
101
117
|
property: "height",
|
|
102
|
-
value: LEGEND_SOURCE_CANVAS.height
|
|
118
|
+
value: LEGEND_SOURCE_CANVAS.height * requests.length
|
|
103
119
|
});
|
|
104
120
|
if (canvas?.properties.background !== undefined) {
|
|
105
121
|
source = source.editGraphics({
|
|
@@ -108,27 +124,30 @@ function prepareAutoLegendSource(child) {
|
|
|
108
124
|
value: canvas.properties.background
|
|
109
125
|
});
|
|
110
126
|
}
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
const gradients = [];
|
|
128
|
+
for (const request of requests) {
|
|
129
|
+
const colorChannel = request.channels.find(channel => ["color", "stroke"].includes(channel));
|
|
130
|
+
const scale = findSemanticScale(
|
|
131
|
+
source,
|
|
132
|
+
findLayer(source, request.target)?.encoding?.[colorChannel]?.scale
|
|
133
|
+
);
|
|
134
|
+
const gradient = isContinuousColorScaleType(scale?.type);
|
|
135
|
+
const gradientPrefix = colorChannel === "color"
|
|
136
|
+
? "colorGradient"
|
|
137
|
+
: "strokeGradient";
|
|
138
|
+
source = source.createLegend({
|
|
139
|
+
target: request.target,
|
|
140
|
+
channels: request.channels,
|
|
141
|
+
...(gradient ? {
|
|
142
|
+
count: 5,
|
|
143
|
+
gradient: { length: 180, thickness: 12 },
|
|
144
|
+
labels: { offset: 9, fontSize: 10 },
|
|
145
|
+
titleStyle: { color: DEFAULT_COLORS.strongText, fontSize: 11 }
|
|
146
|
+
} : {})
|
|
147
|
+
});
|
|
148
|
+
if (gradient && scale.type === "sequential") gradients.push({ scale, gradientPrefix });
|
|
149
|
+
}
|
|
150
|
+
for (const { scale, gradientPrefix } of gradients) {
|
|
132
151
|
const resolved = source.resolvedScales[scale.id];
|
|
133
152
|
source = source
|
|
134
153
|
.editGraphics({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isPlainObject as x}from"../core/immutable.js";import{formatVisibleText as E,measureTextWidth as S,resolveTextBounds as b,textBoundsIntersect as p}from"../core/textMetrics.js";import{resolveDisplayLabel as T}from"../grammar/displayLabels.js";import{DEFAULT_COLORS as I,DEFAULT_FONT_FAMILY as B}from"../theme/defaults.js";const K=Object.freeze(["top","right","bottom","left"]),D=Object.freeze({top:0,right:0,bottom:0,left:0});function F(){return{mode:"legacy",common:{fontSize:12,fontFamily:B,fontWeight:600,color:I.strongText,offset:10,align:"center"},row:{side:"left"},column:{side:"top"}}}function M(e){if(!x(e))throw new TypeError("Facet headers config must be a plain object.");if(e.common===void 0&&e.mode===void 0)return{...F(),common:{...F().common,...e}};if(!["legacy","roles"].includes(e.mode)||!x(e.common)||!x(e.row)||!x(e.column))throw new Error("Facet headers config must contain canonical mode and role objects.");return{mode:e.mode,common:{...e.common},row:{...e.row},column:{...e.column}}}function g(e,t){const n=M(e);return t==="all"?{...n.common}:{...n.common,...n[t]}}function u(e,t){return T(e,t.labelMap,E)}function H(e){return new Map((e.facet.grid?.cells??[]).map(t=>[t.id,t]))}function O(e,t){const n=e.compositionSpec,s=g(t,"all"),c=H(n);return n.children.map((i,o)=>{const r=c.get(i),f=r===void 0?u(n.facet.values[o],s):`${u(r.rowValue,s)} \xB7 ${u(r.columnValue,s)}`;return{topology:"legacy",role:"column",id:i,text:f,config:s}})}function z(e,t){const n=t==="column"?"column":"row",s=t==="column"?"columnValue":"rowValue",c=new Map;for(const i of e.facet.grid.cells){const o=c.get(i[n]);if(o===void 0)c.set(i[n],{coordinate:i[n],value:i[s],ids:[i.id]});else{if(o.value!==i[s])throw new Error(`Facet ${t} header values conflict at coordinate ${i[n]}.`);o.ids.push(i.id)}}return[...c.values()].sort((i,o)=>i.coordinate-o.coordinate)}function L(e,t){const n=e.compositionSpec,s=g(t,"column");if(n.facet.grid===void 0)return n.children.map((r,f)=>({topology:"cell-column",role:"column",id:r,ids:[r],row:Math.floor(f/n.columns),column:f%n.columns,text:u(n.facet.values[f],s),config:s}));const c=g(t,"row"),i=z(n,"column").map(r=>({topology:"grid-column",role:"column",ids:r.ids,coordinate:r.coordinate,text:u(r.value,s),config:s})),o=z(n,"row").map(r=>({topology:"grid-row",role:"row",ids:r.ids,coordinate:r.coordinate,text:u(r.value,c),config:c}));return[...i,...o]}function v(e,t,n){const s=e.filter(c=>c.role===t&&c.text!=="");return s.length===0?0:Math.max(...t==="column"?s.map(c=>c.config.fontSize+c.config.offset):s.map(c=>S(c.text,c.config,n)+c.config.offset))}function N(e,t){const n=M(t),s=n.mode==="legacy"?O(e,n):L(e,n),c=e.compositionSpec.facet.grid===void 0?Math.ceil(e.compositionSpec.children.length/e.compositionSpec.columns):Math.max(...e.compositionSpec.facet.grid.cells.map(o=>o.row))+1,i={outer:{...D},cellRows:{top:Array(c).fill(0),bottom:Array(c).fill(0)}};if(n.mode==="legacy"&&e.compositionSpec.spacing==="plot")for(const o of s){if(o.text==="")continue;const r=e.compositionSpec.children.indexOf(o.id),f=e.compositionSpec.facet.grid?.cells[r]?.row??Math.floor(r/e.compositionSpec.columns);i.cellRows.top[f]=Math.max(i.cellRows.top[f],o.config.offset+o.config.fontSize)}if(n.mode==="roles"){const o=g(n,"column"),r=v(s,"column",e.materializationConfigs.textMetrics);if(e.compositionSpec.facet.grid===void 0){const f=["left","right"].includes(o.side);f&&(i.cellColumns={left:Array(e.compositionSpec.columns).fill(0),right:Array(e.compositionSpec.columns).fill(0)});for(const l of s)if(l.text!==""){const a=f?i.cellColumns:i.cellRows,m=f?l.column:l.row,d=f?S(l.text,l.config,e.materializationConfigs.textMetrics):l.config.fontSize;a[o.side][m]=Math.max(a[o.side][m],d+l.config.offset)}}else{i.outer[o.side]=r;const f=g(n,"row");i.outer[f.side]=v(s,"row",e.materializationConfigs.textMetrics)}}return{headers:n,descriptors:s,headerLayout:i}}function A(e,t){return t==="start"?{x:e.left,textAlign:"left"}:t==="end"?{x:e.right,textAlign:"right"}:{x:(e.left+e.right)/2,textAlign:"center"}}function R(e,t){return t==="start"?{y:e.top,textBaseline:"top"}:t==="end"?{y:e.bottom,textBaseline:"bottom"}:{y:(e.top+e.bottom)/2,textBaseline:"middle"}}function C(e){return{left:Math.min(...e.map(t=>t.left)),right:Math.max(...e.map(t=>t.right)),top:Math.min(...e.map(t=>t.top)),bottom:Math.max(...e.map(t=>t.bottom))}}function h(e,t){return{left:e.x+t.x,right:e.x+t.x+t.width,top:e.y+t.y,bottom:e.y+t.y+t.height}}function y(e){return{left:e.x,right:e.x+e.width,top:e.y,bottom:e.y+e.height}}function $(e,t,n,s){const c=e.ids.map(d=>t.get(d)),i=e.ids.find((d,w)=>c[w]===void 0),o=e.ids.find(d=>n.get(d)===void 0);if(i!==void 0||o!==void 0)throw new Error(`Facet header requires cell and plot bounds for "${i??o}".`);const r=e.ids.map((d,w)=>h(c[w],n.get(d))),f=C(r),l=s==="plot"?f:C(c.map(y)),a=e.config;let m;if(["top","bottom"].includes(a.side)){m=A(f,a.align);const d=a.side==="top";m.y=d?l.top-a.offset-a.fontSize/2:l.bottom+a.offset+a.fontSize/2,m.textBaseline="middle"}else{m=R(f,a.align);const d=a.side==="left";m.x=d?l.left-a.offset:l.right+a.offset,m.textAlign=d?"right":"left"}return{descriptor:e,snapshots:l,properties:m}}function j(e,t,n,s){if(t===void 0||n===void 0)throw new Error(`Facet header requires cell and plot bounds for "${e.id}".`);return{descriptor:e,snapshots:y(t),properties:{...A(h(t,n),e.config.align),y:s==="plot"?t.y+n.y-e.config.offset-e.config.fontSize/2:t.y+e.config.offset,textBaseline:"middle"}}}function q(e){const{config:t}=e.descriptor;return{type:"text",properties:{...e.properties,text:e.descriptor.text,fill:t.color,fontSize:t.fontSize,fontFamily:t.fontFamily,fontWeight:t.fontWeight}}}function V(e,t,n,s,c){const i=t.map((o,r)=>e[r].descriptor.text===""?void 0:b(o.properties,s));for(const[o,r]of i.entries())if(r!==void 0&&(r.left<-1e-9||r.right>n.width+1e-9||r.top<-1e-9||r.bottom>n.height+1e-9||i.some((f,l)=>l<o&&f!==void 0&&p(f,r))||n.children.some(f=>p(r,n.spacing==="plot"?h(f,c.get(f.id)):y(f)))))throw new Error("Facet role headers require sufficient non-overlapping reserved space.")}function G(e,t,n,s){const c=[];for(let i=0;i<e.length;i+=1){if(e[i].properties.text==="")continue;const o=b(e[i].properties,s),r=t.children[i],f=n.get(r.id);if(o.left<0||o.right>t.width||o.top<0||o.bottom>t.height||c.some(l=>p(l,o))||(t.spacing==="plot"?t.children.some(l=>p(o,h(l,n.get(l.id)))):p(o,h(r,f))))throw new Error("Facet headers require sufficient non-overlapping space above every child plot.");c.push(o)}}function P(e,t,n){const s=new Map(e.children.map(o=>[o.id,o])),c=new Map(t.map(o=>[o.id,o])),i=n.descriptors.map(o=>o.topology==="legacy"?j(o,s.get(o.id),c.get(o.id),e.spacing):$(o,s,c,e.spacing));return{records:i,items:i.map(q),plotById:c}}function Y(e,t,n,s){const c=e.materializationConfigs.textMetrics,{records:i,items:o,plotById:r}=P(t,n,s);s.headers.mode==="legacy"?G(o,t,r,c):V(i,o,t,c,r);const f=`${e.compositionSpec.id}-headers`;return e.createGraphics({id:f,type:"collection",parent:"canvas"}).editGraphics({target:f,property:"items",value:o})}export{F as createDefaultFacetHeaders,Y as materializeFacetHeaders,M as normalizeFacetHeadersConfig,N as prepareFacetHeaders,g as resolveFacetHeaderConfig,P as resolveFacetHeaderItems};
|
|
1
|
+
import{isPlainObject as F}from"../core/immutable.js";import{formatVisibleText as V,measureTextWidth as P,resolveTextBounds as z,textBoundsIntersect as S}from"../core/textMetrics.js";import{validateFacetHeaderRotations as W}from"../grammar/facetHeaders.js";import{resolveDisplayLabel as _}from"../grammar/displayLabels.js";import{resolveGraphicBounds as B}from"../layout/canvas.js";import{unionConcreteGraphicBounds as U}from"../grammar/schemas/graphicBounds.js";import{intersectClipBounds as K}from"../grammar/schemas/graphicClip.js";import{DEFAULT_COLORS as N,DEFAULT_FONT_FAMILY as Y}from"../theme/defaults.js";import{prepareFacetHeaderTitles as Z}from"../layout/facetHeaderTitles.js";import{cartesianAxisOwners as J,axisGraphicIds as E}from"../selectors/axes.js";import{resolveFacetOuterSides as Q}from"../grammar/facets/guides.js";import{resolveConcreteGraphicBounds as X}from"../grammar/schemas/graphicBounds.js";import{allLegendGraphicIds as tt}from"./guides/resources.js";const et=Object.freeze(["top","right","bottom","left"]),v=Object.freeze({top:0,right:0,bottom:0,left:0});function ot(t){return`${t}-headers`}function T(){return{mode:"legacy",common:{fontSize:12,fontFamily:Y,fontWeight:600,color:N.strongText,offset:10,align:"center"},row:{side:"left"},column:{side:"top"}}}function j(t){if(!F(t))throw new TypeError("Facet headers config must be a plain object.");if(W(t),t.common===void 0&&t.mode===void 0)return{...T(),common:{...T().common,...t}};if(!["legacy","roles"].includes(t.mode)||!F(t.common)||!F(t.row)||!F(t.column))throw new Error("Facet headers config must contain canonical mode and role objects.");return{mode:t.mode,common:{...t.common},row:{...t.row},column:{...t.column}}}function y(t,e){const o=j(t);return e==="all"?{...o.common}:{...o.common,...o[e]}}function b(t,e){return _(t,e.labelMap,V)}function nt(t){return new Map((t.facet.grid?.cells??[]).map(e=>[e.id,e]))}function it(t,e){const o=t.compositionSpec,c=y(e,"all"),i=nt(o);return o.children.map((n,s)=>{const r=i.get(n),a=r===void 0?b(o.facet.values[s],c):`${b(r.rowValue,c)} \xB7 ${b(r.columnValue,c)}`;return{topology:"legacy",role:"column",id:n,text:a,config:c}})}function G(t,e){const o=e==="column"?"column":"row",c=e==="column"?"columnValue":"rowValue",i=new Map;for(const n of t.facet.grid.cells){const s=i.get(n[o]);if(s===void 0)i.set(n[o],{coordinate:n[o],value:n[c],ids:[n.id]});else{if(s.value!==n[c])throw new Error(`Facet ${e} header values conflict at coordinate ${n[o]}.`);s.ids.push(n.id)}}return[...i.values()].sort((n,s)=>n.coordinate-s.coordinate)}function ct(t,e){const o=t.compositionSpec,c=y(e,"column");if(o.facet.grid===void 0)return o.children.map((r,a)=>({topology:"cell-column",role:"column",id:r,ids:[r],row:Math.floor(a/o.columns),column:a%o.columns,text:b(o.facet.values[a],c),config:c}));const i=y(e,"row"),n=G(o,"column").map(r=>({topology:"grid-column",role:"column",ids:r.ids,coordinate:r.coordinate,text:b(r.value,c),config:c})),s=G(o,"row").map(r=>({topology:"grid-row",role:"row",ids:r.ids,coordinate:r.coordinate,text:b(r.value,i),config:i}));return[...n,...s]}function x(t){return t.rotation!==void 0&&t.rotation!==0}function H(t,e){return z({x:0,y:0,text:t.text,...t.config,textAlign:"center",textBaseline:"middle"},e)}function st(t,e,o){if(x(t.config)){const c=H(t,o);return["left","right"].includes(e)?c.right-c.left:c.bottom-c.top}return["left","right"].includes(e)?P(t.text,t.config,o):t.config.fontSize}function rt(t){const e={...v};if(t.compositionSpec.spacing!=="plot")return e;for(const o of t.compositionSpec.children){const c=t.children[o],i=B(c),n=c.graphicSpec.objects.canvas.properties;e.left=Math.max(e.left,i.x),e.top=Math.max(e.top,i.y),e.right=Math.max(e.right,n.width-i.x-i.width),e.bottom=Math.max(e.bottom,n.height-i.y-i.height)}return e}function O(t,e,o,c,i){const n=st(t,e,o)+t.config.offset+(t.clearance?.[e]??0);return x(t.config)&&i?Math.max(0,n-c[e]):n}function ft(t,e,o){if(!e.some(i=>i.config.anchor==="content"))return;if(t.compositionSpec.spacing!=="plot")throw new Error("Facet header content anchor requires plot spacing.");const c=new Map;for(const i of t.compositionSpec.children){const n=t.children[i],s=B(n),r={left:s.x,right:s.x+s.width,top:s.y,bottom:s.y+s.height},a=n.semanticSpec.layers.map(f=>f.id).filter(f=>n.graphicSpec.objects[f]!==void 0),u=U(n.graphicSpec,a,o),l=n.graphicSpec.objects.canvas.properties,h=u===void 0?void 0:K(u,{left:0,right:l.width,top:0,bottom:l.height});c.set(i,h===void 0||h.left>h.right||h.top>h.bottom?r:C([r,h]))}for(const i of e)if(i.config.anchor==="content"){i.clearance={...v};for(const n of i.ids??[i.id]){const s=B(t.children[n]),r=c.get(n);for(const[a,u]of Object.entries({left:s.x-r.left,right:r.right-s.x-s.width,top:s.y-r.top,bottom:r.bottom-s.y-s.height}))i.clearance[a]=Math.max(i.clearance[a],u)}}return c}function R(t,e,o,c){const i=t.filter(n=>n.role===e&&n.text!=="");return i.length===0?0:Math.max(...i.map(n=>O(n,n.config.side,o,c,!0)))}function at(t,e){const o=t.compositionSpec,c=t.children[e],i=o.facet.guides.axes==="outer"?Q({placements:o.facet.grid?.cells??o.children.map((s,r)=>({id:s,row:Math.floor(r/o.columns),column:r%o.columns}))}):void 0,n=J(c).flatMap(s=>E(s.channel,s.id).filter((r,a)=>a>=2&&(i===void 0||i[s.config?.[["line","ticks","labels","title"][a]]?.position]?.includes(e))));for(const s of["theta","radius","parallel"])c.guideConfigs.axis?.[s]!==void 0&&n.push(...E(s).slice(2));return o.facet.guides.legend===!1&&n.push(...tt(Object.keys(c.guideConfigs.legend??{}))),n.filter(s=>c.graphicSpec.objects[s]!==void 0)}function lt(t,e){const o=t.children[e],c=B(o),i=at(t,e).map(n=>X(o.graphicSpec,n,o.materializationConfigs.textMetrics)).filter(Boolean);return{top:Math.max(0,...i.map(n=>c.y-n.top)),bottom:Math.max(0,...i.map(n=>n.bottom-c.y-c.height)),left:Math.max(0,...i.map(n=>c.x-n.left)),right:Math.max(0,...i.map(n=>n.right-c.x-c.width))}}function Et(t,e){const o=j(e),c=o.mode==="legacy"?it(t,o):ct(t,o),i=t.compositionSpec.facet.grid===void 0?Math.ceil(t.compositionSpec.children.length/t.compositionSpec.columns):Math.max(...t.compositionSpec.facet.grid.cells.map(f=>f.row))+1,n=t.materializationConfigs.textMetrics,s=["column",...t.compositionSpec.facet.grid===void 0?[]:["row"]].map(f=>[f,y(o,f)]),r=s.some(([,f])=>f.title!==void 0);if(t.compositionSpec.spacing==="plot"){const f=new Map(t.compositionSpec.children.map(p=>[p,lt(t,p)]));for(const p of c)p.clearance=Object.fromEntries(et.map(m=>{const d=Math.max(0,...(p.ids??[p.id]).map(g=>f.get(g)[m]));return[m,d>p.config.offset?d:0]}))}const a=ft(t,c,n),u=r||c.some(f=>x(f.config))?rt(t):v,l={outer:{...v},cellRows:{top:Array(i).fill(0),bottom:Array(i).fill(0)}};if(o.mode==="legacy"&&t.compositionSpec.spacing==="plot")for(const f of c){if(f.text==="")continue;const p=t.compositionSpec.children.indexOf(f.id),m=t.compositionSpec.facet.grid?.cells[p]?.row??Math.floor(p/t.compositionSpec.columns);l.cellRows.top[m]=Math.max(l.cellRows.top[m],O(f,"top",n,u,m===0))}if(o.mode==="roles"){const f=y(o,"column"),p=R(c,"column",n,u);if(t.compositionSpec.facet.grid===void 0){const m=["left","right"].includes(f.side);m&&(l.cellColumns={left:Array(t.compositionSpec.columns).fill(0),right:Array(t.compositionSpec.columns).fill(0)});for(const d of c)if(d.text!==""){const g=m?l.cellColumns:l.cellRows,M=m?d.column:d.row,$=m?t.compositionSpec.columns-1:i-1,q=["top","left"].includes(f.side)?M===0:M===$;g[f.side][M]=Math.max(g[f.side][M],O(d,f.side,n,u,q))}}else{l.outer[f.side]=p;const m=y(o,"row");l.outer[m.side]=R(c,"row",n,u)}}const h=Z({roles:s,descriptors:c,space:u,headerLayout:l,profile:n,ids:t.compositionSpec.children});return{headers:o,descriptors:c,titles:h,headerLayout:l,...a===void 0?{}:{contentById:a},...(r||c.some(f=>x(f.config)))&&n!==void 0?{profile:n}:{}}}function D(t,e){return e==="start"?{x:t.left,textAlign:"left"}:e==="end"?{x:t.right,textAlign:"right"}:{x:(t.left+t.right)/2,textAlign:"center"}}function dt(t,e){return e==="start"?{y:t.top,textBaseline:"top"}:e==="end"?{y:t.bottom,textBaseline:"bottom"}:{y:(t.top+t.bottom)/2,textBaseline:"middle"}}function C(t){return{left:Math.min(...t.map(e=>e.left)),right:Math.max(...t.map(e=>e.right)),top:Math.min(...t.map(e=>e.top)),bottom:Math.max(...t.map(e=>e.bottom))}}function w(t,e){return{left:t.x+e.x,right:t.x+e.x+e.width,top:t.y+e.y,bottom:t.y+e.y+e.height}}function A(t){return{left:t.x,right:t.x+t.width,top:t.y,bottom:t.y+t.height}}function I(t,e){return{left:t.x+e.left,right:t.x+e.right,top:t.y+e.top,bottom:t.y+e.bottom}}function L(t,e,o,c,i){return i==="start"?t-o:i==="end"?e-c:(t+e-o-c)/2}function k(t,e,o,c){const{config:i}=t,n=H(t,c),s={textAlign:"center",textBaseline:"middle",rotation:i.rotation};return["top","bottom"].includes(i.side)?{...s,x:L(e.left,e.right,n.left,n.right,i.align),y:i.side==="top"?o.top-i.offset-n.bottom:o.bottom+i.offset-n.top}:{...s,x:i.side==="left"?o.left-i.offset-n.right:o.right+i.offset-n.left,y:L(e.top,e.bottom,n.top,n.bottom,i.align)}}function ut(t,e,o,c,i,n){const s=t.ids.map(d=>e.get(d)),r=t.ids.find((d,g)=>s[g]===void 0),a=t.ids.find(d=>o.get(d)===void 0);if(r!==void 0||a!==void 0)throw new Error(`Facet header requires cell and plot bounds for "${r??a}".`);const u=t.ids.map((d,g)=>w(s[g],o.get(d))),l=C(u),h=t.config.anchor==="content"?C(t.ids.map((d,g)=>I(s[g],n.get(d)))):c==="plot"?l:C(s.map(A)),f=t.config;if(x(f)||t.topology==="role-title"){const d=k(t,l,h,i),g=["left","right"].includes(f.side)?"y":"x";return f.alongOffset!==void 0&&(d[g]+=f.alongOffset),{descriptor:t,snapshots:h,properties:d}}const p=f.anchor==="content"?0:t.clearance?.[f.side]??0;let m;if(["top","bottom"].includes(f.side)){m=D(l,f.align);const d=f.side==="top";m.y=d?h.top-p-f.offset-f.fontSize/2:h.bottom+p+f.offset+f.fontSize/2,m.textBaseline="middle"}else{m=dt(l,f.align);const d=f.side==="left";m.x=d?h.left-p-f.offset:h.right+p+f.offset,m.textAlign=d?"right":"left"}return{descriptor:t,snapshots:h,properties:m}}function mt(t,e,o,c,i,n){if(e===void 0||o===void 0)throw new Error(`Facet header requires cell and plot bounds for "${t.id}".`);const s=t.config.anchor==="content"?I(e,n.get(t.id)):w(e,o);if(x(t.config)&&c==="plot"){const r=w(e,o);return{descriptor:t,snapshots:A(e),properties:k({...t,config:{...t.config,side:"top"}},r,s,i)}}return{descriptor:t,snapshots:A(e),properties:{...D(w(e,o),t.config.align),y:c==="plot"?e.y+o.y-(t.clearance?.top??0)-t.config.offset-t.config.fontSize/2:e.y+t.config.offset,textBaseline:"middle",...x(t.config)?{rotation:t.config.rotation}:{}}}}function pt(t){const{config:e}=t.descriptor;return{type:"text",properties:{...t.properties,text:t.descriptor.text,fill:e.color,fontSize:e.fontSize,fontFamily:e.fontFamily,fontWeight:e.fontWeight}}}function ht(t,e,o,c,i,n){const s=e.map((r,a)=>t[a].descriptor.text===""?void 0:z(r.properties,c));for(const[r,a]of s.entries())if(a!==void 0&&(a.left<-1e-9||a.right>o.width+1e-9||a.top<-1e-9||a.bottom>o.height+1e-9||s.some((u,l)=>l<r&&u!==void 0&&S(u,a))||o.children.some(u=>S(a,n===void 0?o.spacing==="plot"?w(u,i.get(u.id)):A(u):I(u,n.get(u.id))))))throw new Error("Facet role headers require sufficient non-overlapping reserved space.")}function gt(t,e,o,c,i){const n=[];for(let s=0;s<t.length;s+=1){if(t[s].properties.text==="")continue;const r=z(t[s].properties,c),a=e.children[s],u=o.get(a.id);if(r.left<0||r.right>e.width||r.top<0||r.bottom>e.height||n.some(l=>S(l,r))||(e.spacing==="plot"?e.children.some(l=>S(r,i===void 0?w(l,o.get(l.id)):I(l,i.get(l.id)))):S(r,w(a,u))))throw new Error("Facet headers require sufficient non-overlapping space above every child plot.");n.push(r)}}function xt(t,e,o){const c=new Map(t.children.map(s=>[s.id,s])),i=new Map(e.map(s=>[s.id,s])),n=[...o.descriptors,...o.titles??[]].map(s=>s.topology==="legacy"?mt(s,c.get(s.id),i.get(s.id),t.spacing,o.profile,o.contentById):ut(s,c,i,t.spacing,o.profile,o.contentById));return{records:n,items:n.map(pt),plotById:i}}function Tt(t,e,o,c){const i=t.materializationConfigs.textMetrics,{records:n,items:s,plotById:r}=xt(e,o,c);c.headers.mode==="legacy"?gt(s,e,r,i,c.contentById):ht(n,s,e,i,r,c.contentById);const a=ot(t.compositionSpec.id);return t.createGraphics({id:a,type:"collection",parent:"canvas"}).editGraphics({target:a,property:"items",value:s})}export{T as createDefaultFacetHeaders,ot as facetHeaderGraphicId,Tt as materializeFacetHeaders,j as normalizeFacetHeadersConfig,Et as prepareFacetHeaders,y as resolveFacetHeaderConfig,xt as resolveFacetHeaderItems,at as retainedFacetGuideIds};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { walkGraphicTreeEvents } from "../grammar/schemas/graphicTree.js";
|
|
2
|
+
import { resolveConcreteGraphicBounds } from "../grammar/schemas/graphicBounds.js";
|
|
3
|
+
import { assertFacetTopLaneBounds } from "../layout/facetTopLane.js";
|
|
4
|
+
|
|
5
|
+
export function assertFacetTopLane(program, { layout, plots, headerId, titleIds }) {
|
|
6
|
+
if (program.compositionSpec.topLane === undefined) return;
|
|
7
|
+
const titles = [], headers = [], retained = [];
|
|
8
|
+
const graphic = program.graphicSpec, profile = program.materializationConfigs.textMetrics;
|
|
9
|
+
const collect = entry => {
|
|
10
|
+
const type = entry.object.type ?? entry.owner?.type;
|
|
11
|
+
const properties = entry.object.properties;
|
|
12
|
+
if (type !== "text" || properties === undefined || properties.text === "" || properties.opacity === 0) return;
|
|
13
|
+
const bounds = resolveConcreteGraphicBounds(graphic, entry.id, profile);
|
|
14
|
+
if (bounds === undefined) return;
|
|
15
|
+
const owner = entry.ownerId ?? entry.id;
|
|
16
|
+
if (titleIds.includes(owner)) titles.push(bounds);
|
|
17
|
+
else if (owner === headerId) headers.push(bounds);
|
|
18
|
+
else retained.push(bounds);
|
|
19
|
+
};
|
|
20
|
+
walkGraphicTreeEvents(graphic, { enter: collect, item: collect });
|
|
21
|
+
assertFacetTopLaneBounds({ canvas: graphic.objects.canvas.properties, titles, headers, retained,
|
|
22
|
+
plots: layout.children.map((cell, index) => ({ left: cell.x + plots[index].x,
|
|
23
|
+
right: cell.x + plots[index].x + plots[index].width, top: cell.y + plots[index].y,
|
|
24
|
+
bottom: cell.y + plots[index].y + plots[index].height })) });
|
|
25
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cartesianAxisOwners as
|
|
1
|
+
import{cartesianAxisOwners as W}from"../selectors/axes.js";import{resolveConcreteGraphicBounds as q,resolvePrimitiveGraphicBounds as N,resolveGraphicParentOffset as V,resolveGraphicClipBounds as Z}from"../grammar/schemas/graphicBounds.js";import{intersectClipBounds as J}from"../grammar/schemas/graphicClip.js";import{cloneAndFreeze as K,isPlainObject as Q}from"../core/immutable.js";import{walkGraphicTreeEvents as _}from"../grammar/schemas/graphicTree.js";import{textBoundsFitCanvas as U,textBoundsIntersect as L,resolveTextBounds as M,requireFiniteBounds as X}from"../core/textMetrics.js";import{axisGraphicIds as Y,allLegendGraphicIds as ee}from"./guides/resources.js";import{resolveGraphicBounds as G}from"../layout/canvas.js";import{resolvePlacedPlotBounds as te}from"../layout/composition.js";import{alignedTextAnchor as oe,buildTitleReadingBlock as ie,paintedTitleComponent as ne,resolveTitleComponentBounds as re,titleTextProperties as ce}from"../layout/title.js";import{resolveFacetLayout as se}from"../layout/facets.js";import{assertFacetTopLane as ae}from"./facetTopLane.js";import{namespaceGraphicId as R,namespaceGraphicSnapshot as pe}from"./compositionSnapshot.js";import{prepareSharedFacetLegend as I}from"./facetGuides/index.js";import{prepareFacetGuideOwnership as de}from"./facetGuides/placement.js";import{resolvePromotedFacetLegendLayout as le}from"./facetGuides/layout.js";import{attachSnapshotObject as fe,clearCompositionChildren as he,compositionChildDescriptor as ue}from"./composition.js";import{materializeFacetHeaders as me,prepareFacetHeaders as ge,facetHeaderGraphicId as B,retainedFacetGuideIds as we}from"./facetHeaders.js";const be={top:0,right:0,bottom:0,left:0},T=Object.freeze(["chartTitle","chartSubtitle"]);function z(e,i){return i===void 0?e:e===void 0?{...i}:{left:Math.min(e.left,i.left),right:Math.max(e.right,i.right),top:Math.min(e.top,i.top),bottom:Math.max(e.bottom,i.bottom)}}function De(e,{headerItems:i=[]}={}){if(e?.compositionSpec?.type!=="facet")throw new Error("Facet content measurement requires a facet composition.");const t=e.compositionSpec,r=new Map((t.facet.grid?.cells??[]).map(c=>[c.id,c])),l=t.children.map((c,f)=>({id:c,row:r.get(c)?.row??Math.floor(f/t.columns),column:r.get(c)?.column??f%t.columns})),d=I(e),n=de(e,l,d),p=new Set(t.children),o=new Map;if(!Array.isArray(i)||i.some(c=>!p.has(c?.ownerId)||c.type!=="text"||!Q(c.properties)))throw new TypeError("Facet content headers require concrete text with one observed child owner.");for(const c of i)o.has(c.ownerId)||o.set(c.ownerId,[]),o.get(c.ownerId).push(c);const s=["line","ticks","labels","title"];return K(l.map(c=>{const f=e.children[c.id],m=f.graphicSpec,b=f.materializationConfigs.textMetrics,h=G(f),x=n.children[c.id],y=new Map;for(const a of W(f)){const g=Y(a.channel,a.id);for(const[S,w]of s.entries())x.removeAxes.includes(a.id)||x.removeAxisComponents?.[a.id]?.includes(w)?y.set(g[S],"removed"):t.facet.guides.axes==="outer"&&a.channel==="x"&&a.config?.[w]?.position==="bottom"&&y.set(g[S],"footer")}for(const a of ee(x.removeLegends))y.set(a,"removed");const v=new Map,F=[],j=new Map,P=a=>{const g=F.at(-1);if(g==="removed")return;const S=a.object.type??a.owner?.type,w=a.object.properties;if(!w||S==="canvas"||w.opacity===0||S==="text"&&(w.text===""||w.fill==="none"||w.fill==="transparent"))return;const C=a.ownerId??a.id;j.has(C)||j.set(C,{offset:V(m,C),clip:Z(m,C,{rootViewport:!0})});const{offset:[O,A],clip:H}=j.get(C);let u=N(S,w,b);if(u===void 0||(u=X({left:u.left+O,right:u.right+O,top:u.top+A,bottom:u.bottom+A}),H!==void 0&&(u=J(u,H)),u.left>u.right||u.top>u.bottom))return;const $=g??"body";v.set($,z(v.get($),u))};_(m,{enter(a){F.push(F.at(-1)==="removed"?"removed":y.get(a.id)??F.at(-1)),a.object.items===void 0&&!a.object.children?.length&&P(a)},item:P,exit(){F.pop()}});let k=z({left:h.x,right:h.x+h.width,top:h.y,bottom:h.y+h.height},v.get("body"));for(const a of o.get(c.id)??[]){const g=a.properties;g.text!==""&&g.opacity!==0&&g.fill!=="none"&&g.fill!=="transparent"&&(k=z(k,M(g,b)))}const E=a=>a===void 0?void 0:{left:a.left-h.x,right:a.right-h.x,top:a.top-h.y,bottom:a.bottom-h.y};return{...c,plot:h,bounds:E(k),...v.has("footer")?{footerBounds:E(v.get("footer"))}:{}}}))}function ve(e){const i=e.materializationConfigs.facets?.[e.compositionSpec.id];if(i===void 0)throw new Error(`Facet "${e.compositionSpec.id}" requires materialization config.`);return i}function xe(e){if(e.semanticSpec.title.text===void 0)return{height:0};const i=e.titleConfig;if(i===void 0)throw new Error("Facet title requires chart title configuration.");if(i.position!=="top")throw new Error("Facet parent title currently supports only top position.");const t=ie({text:e.semanticSpec.title.text,subtitle:e.semanticSpec.title.subtitle},i),r=8+i.offset;if(r<0)throw new Error("Facet parent title offset places text outside the Canvas.");return{config:i,block:t,top:r,height:Math.ceil(r+t.height)}}function D(e,i,t,r,l,d,n){if(t.length===0)return e;const p=oe(d.x,d.width,e.titleConfig.align)+(e.titleConfig.alongOffset??0),o=ne({x:p,y:r.map(m=>n+m),lines:t,textAlign:e.titleConfig.align}),s=re(o,l,e.materializationConfigs.textMetrics);if(!U(s,e.graphicSpec.objects.canvas.properties))throw new Error("Facet parent title places text outside the Canvas.");const c=o.lines.length;let f=e.createGraphics({id:i,type:"text",...c>1?{length:c}:{},parent:"canvas"});for(const[m,b]of Object.entries(ce(o,l)))f=f.editGraphics({target:i,property:m,value:b});return f}function ye(e,i,t){let r=D(e,T[0],t.block.titleLines,t.block.titleCenters,t.config.titleStyle,i,t.top);return t.block.subtitleLines.length>0&&(r=D(r,T[1],t.block.subtitleLines,t.block.subtitleCenters,t.config.subtitleStyle,i,t.top)),r}function Se(e,i){if(e._assertCompositionProgram("resolveFacetProgramLayout"),e.compositionSpec.type!=="facet")throw new Error("resolveFacetProgramLayout requires a facet composition.");i??=I(e);const t=xe(e),r=e.compositionSpec,l=ge(e,ve(e).headers),d=new Map((r.facet.grid?.cells??[]).map(s=>[s.id,s]));let n=se({children:r.children.map((s,c)=>({...ue(s,e.children[s]),value:r.facet.values[c],...d.has(s)?{row:d.get(s).row,column:d.get(s).column}:{}})),spacing:r.spacing,topLane:r.topLane,plots:r.children.map(s=>({id:s,...G(e.children[s])})),columns:r.columns,gap:r.gap,align:r.align,padding:r.padding,titleHeight:t.height,headerLayout:l.headerLayout,sharedLegend:r.facet.guides.legend==="shared"&&i?.mode!=="promoted",...i===void 0?{}:{sharedLegendGap:i.reservation.gap,sharedLegendWidth:i.reservation.width,sharedLegendHeight:i.reservation.height,sharedLegendPosition:i.reservation.position}});const p=r.children.map(s=>({id:s,...G(e.children[s])}));let o=te({placements:n.children,plots:p});return{layout:n,plot:o}=le(e,{layout:n,plot:o,plots:p,title:t,preparedHeaders:l,prepared:i}),{layout:n,title:t,plot:o,plots:p,preparedHeaders:l}}function Ce(e,i,t){const r=[],l=i.children.map((n,p)=>({id:n.id,left:n.x+t[p].x,right:n.x+t[p].x+t[p].width,top:n.y+t[p].y,bottom:n.y+t[p].y+t[p].height}));for(const n of i.children){const p=we(e,n.id);for(const o of p){const s=R(`${e.compositionSpec.id}-${n.id}`,o);if(e.graphicSpec.objects[s]===void 0)continue;const c=q(e.graphicSpec,s,e.materializationConfigs.textMetrics);c!==void 0&&r.push({owner:n.id,bounds:c})}}const d=e.graphicSpec.objects[B(e.compositionSpec.id)];for(const n of d?.items??[])n.properties.text!==""&&r.push({owner:"headers",bounds:M(n.properties,e.materializationConfigs.textMetrics)});for(let n=0;n<r.length;n+=1){const p=r[n];if(l.some(o=>o.id!==p.owner&&L(p.bounds,o))||r.slice(0,n).some(o=>o.owner!==p.owner&&L(p.bounds,o.bounds)))throw new Error("Plot spacing gap is too small for the retained facet guides or headers.")}}function Fe(e,i){if(i.titles.length===0)return;const t=B(e.compositionSpec.id),r=e.materializationConfigs.textMetrics,l=e.graphicSpec.objects[t].items.slice(i.descriptors.length).map(n=>M(n.properties,r)),d=n=>{if((n.ownerId??n.id)===t)return;const p=n.object.type??n.owner?.type,o=n.object.properties;if(p!=="text"||o?.text===""||o?.opacity===0)return;const s=q(e.graphicSpec,n.id,r);if(s!==void 0&&l.some(c=>L(c,s)))throw new Error("Facet field title overlaps retained chart text.")};_(e.graphicSpec,{enter:d,item:d})}function We(e){const i=I(e),{layout:t,title:r,plot:l,plots:d,preparedHeaders:n}=Se(e,i),p=e.graphicSpec.objects.canvas?.properties?.background??"white";let o=he(e);o.graphicSpec.objects.canvas===void 0&&(o=o.createGraphics({id:"canvas",type:"canvas"}));for(const[s,c]of Object.entries({width:t.width,height:t.height,background:p}))o=o.editGraphics({target:"canvas",property:s,value:c});for(const s of t.children){const c=e.children[s.id],f=pe(c.graphicSpec,{namespace:`${e.compositionSpec.id}-${s.id}`,x:s.x,y:s.y});if(o=fe(o,f,f.order[0],"canvas"),t.spacing==="plot"){const m=f.order[0],b=R(`${e.compositionSpec.id}-${s.id}`,"plot-background"),h=G(c),x=o.graphicSpec.objects[m].children?.[0];o=o.editGraphics({target:m,property:"background",value:"transparent"}).createGraphics({id:b,type:"rect",parent:m,...x===void 0?{}:{before:x}});for(const[y,v]of Object.entries({...h,stroke:"none",strokeWidth:0,fill:c.graphicSpec.objects.canvas.properties.background??"transparent"}))o=o.editGraphics({target:b,property:y,value:v})}}return o=me(o,t,d,n),o=o.composeFacetGuides({layout:t,plot:l}),t.spacing==="plot"&&Ce(o,t,d),r.height>0&&(o=ye(o,l,r)),Fe(o,n),ae(o,{layout:t,plots:d,headerId:B(e.compositionSpec.id),titleIds:T}),o._withCanvasConfig({margin:be,size:{width:"auto",height:"auto"}})}export{We as materializeFacetGraphics,De as measureFacetContentChildren,Se as resolveFacetProgramLayout};
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
graphicSiblings
|
|
6
6
|
} from "../grammar/schemas/graphicTree.js";
|
|
7
7
|
import { findDataset } from "../selectors/datasets.js";
|
|
8
|
+
import { hasPlotUnderlays, firstOverlayGuide } from "./plotDrawOrder.js";
|
|
8
9
|
|
|
9
10
|
export const CANVAS_GRAPHIC_ID = "canvas";
|
|
10
11
|
export const PLOT_GRAPHIC_ID = "plot-main";
|
|
@@ -57,6 +58,10 @@ export function resolveMarkGraphicPlacement(program, { data, markType }) {
|
|
|
57
58
|
? dataset.transform[0]
|
|
58
59
|
: undefined;
|
|
59
60
|
if (transform?.type !== "regression") {
|
|
61
|
+
if (hasPlotUnderlays(program)) {
|
|
62
|
+
const before = firstOverlayGuide(program);
|
|
63
|
+
return resolvePlotGraphicPlacement(program, before === undefined ? {} : { before });
|
|
64
|
+
}
|
|
60
65
|
const named = new Set(cartesianAxisOwners(program).filter(owner => owner.named)
|
|
61
66
|
.flatMap(owner => axisGraphicIds(owner.channel, owner.id)));
|
|
62
67
|
const axis = program.graphicSpec.objects[PLOT_GRAPHIC_ID]?.children?.find(
|
|
@@ -35,6 +35,8 @@ export function translatedProperties(type, properties, dx, dy) {
|
|
|
35
35
|
export function translateGraphic(program, id, dx, dy) {
|
|
36
36
|
const graphic = program.graphicSpec.objects[id];
|
|
37
37
|
if (dx === 0 && dy === 0) return program;
|
|
38
|
+
if (graphic.clip !== undefined) program = program.editGraphics({target: id, property: "clip",
|
|
39
|
+
value: {...graphic.clip, x: graphic.clip.x + dx, y: graphic.clip.y + dy}});
|
|
38
40
|
if (graphic.items !== undefined) {
|
|
39
41
|
let next = program.editGraphics({
|
|
40
42
|
target: id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resetLegendClipGraphics as
|
|
1
|
+
import{resetLegendClipGraphics as m,hasLegendClipGraphics as c}from"../legendClip.js";import{localLegendPosition as g}from"../legendGroups.js";import{needsLegendAxisReflow as p,needsSideLegendAxisReflow as h}from"./resources.js";import{getErrorDetails as C}from"../../core/diagnostics.js";import{planCanvasRematerialization as L}from"../layout.js";import{applyMaterializationPlan as v}from"../planner.js";import{assertGuideCollisionBlocks as G}from"../../layout/guideCollisions.js";import{resolveGuideCollisionBlocks as y}from"./resources.js";import{hasPlotUnderlays as u}from"../plotDrawOrder.js";import{resolveGuideCollisionBlocks as A}from"./resources.js";function z(i,l){if(i.context.deferGuideLayoutValidation===!0)return l(i);const a=i.materializationConfigs.canvas?.plot;let n=i.materializationConfigs.canvas?.margin,e;for(let s=0;;s+=1)try{let t=m(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=v(t,L(t))),e=l(t),c(i)&&!c(e)&&Object.values(e.guideConfigs.legend??{}).some(o=>o.clipHeight!==void 0)&&(e=e.rematerializeLegend()),e.compositionSpec===void 0&&h(i,e)&&(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&&p(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()),(u(i)||u(e))&&(e=e.rematerializePlotDrawOrder());break}catch(t){const o=C(t)?.canvasOverflow;if(a===void 0||o===void 0||s>=16)throw t;n=Object.fromEntries(Object.keys(n).map(d=>[d,o[d]>0?Math.ceil((n[d]+o[d])*4)/4:n[d]]))}const{deferGuideLayoutValidation:w,...f}=e.context;Object.hasOwn(i.context,"deferGuideLayoutValidation")&&(f.deferGuideLayoutValidation=i.context.deferGuideLayoutValidation);const r=e._clone({context:f});return G(y(r.graphicSpec,r.guideConfigs,r.titleConfig,r.materializationConfigs.textMetrics)),r}function B(i){return function(l){return z(this,a=>i.call(a,l))}}export{A as resolveGuideCollisionBlocks,z as withGuideLayoutTransaction,B as withGuideLayoutValidation};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{axisGraphicIds as
|
|
1
|
+
import{axisGraphicIds as k}from"../../selectors/axes.js";import{localLegendPosition as z,localLegendPlacement as C}from"../legendGroups.js";import{resolveConcreteGraphicBounds as B,unionConcreteGraphicBounds as x}from"../../grammar/schemas/graphicBounds.js";function T(e,i,n,s){const t=[],r=(o,a,u,l)=>{if(u===void 0)return;const b=l.filter(O=>e.objects[O]!==void 0),m=x(e,b,s);m!==void 0&&t.push({id:o,kind:a,position:u,bounds:m})};r("chart title","title",n?.position,["chartTitle","chartSubtitle"]);for(const[o,a]of Object.entries(i.axis??{})){const u=["line","ticks","labels","title"].map(b=>a[b]?.position).find(Boolean),l=["x","y"].includes(o)?o:["top","bottom"].includes(u)?"x":["left","right"].includes(u)?"y":void 0;if(l!==void 0)for(const[b,m]of["line","ticks","labels","title"].entries()){const O=k(l,o)[b],I=t.length;if(r(`${o}-axis ${m}`,"axis",a[m]?.position,[O]),t.length===I)continue;const h=t.at(-1);if(h.axisId=o,h.namedAxis=o!==l,["labels","title"].includes(m)){const G=e.objects[O];h.textBounds=G.items?.map(v=>B(e,v.id,s))??[h.bounds]}}}const c=$(),d=i.legend??{},f=c.find(o=>o.family==="categorical"&&d[o.kind]!==void 0),g=f!==void 0&&d.size!==void 0&&d[f.kind].target===d.size.target&&z(d[f.kind])===void 0&&z(d.size)===void 0&&(d.size.inheritPosition!==!1||d[f.kind].position===d.size.position),p=i.legendGroups??{};for(const[o,a]of Object.entries(p)){const u=c.filter(l=>d[l.kind]?.target===o&&z(d[l.kind])===void 0).flatMap(l=>l.graphicIds);r(`${o} legend group`,"legend",a.placement.position??"free",u)}for(const o of c){const a=d[o.kind];if(p[a?.target]!==void 0&&z(a)===void 0||a===void 0||g&&o.kind==="size")continue;const u=g&&o===f?[...o.graphicIds,...c.find(l=>l.kind==="size").graphicIds]:o.graphicIds;r(`${o.kind}${g&&o===f?"+size":""} legend`,"legend",z(a)??a.position,u)}return t}const L=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(L.map(e=>`seriesLegend${e}`))}),color:Object.freeze({semanticKind:"color",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(L.map(e=>`colorLegend${e}`))}),stroke:Object.freeze({semanticKind:"stroke",family:"categorical",rematerializeOp:void 0,graphicIds:Object.freeze(L.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 K(e){return w(e).graphicIds}function w(e){const i=j[e];if(i===void 0)throw new Error(`Unknown legend graphic kind "${e}".`);return i}function $(){return Object.entries(j).map(([e,i])=>({kind:e,...i}))}function R(e){return[...new Set(e.flatMap(K))]}function y(e){const i=T(e.graphicSpec,e.guideConfigs,void 0,e.materializationConfigs.textMetrics).filter(n=>n.kind==="axis");for(const n of["theta","radius"]){if(e.guideConfigs.axis?.[n]===void 0)continue;const s=k(n).slice(2).filter(r=>e.graphicSpec.objects[r]!==void 0),t=x(e.graphicSpec,s,e.materializationConfigs.textMetrics);if(t!==void 0)for(const r of["left","right","top","bottom"])i.push({id:`${n}-axis text`,kind:"axis",position:r,bounds:t})}return i}function A(e,i,n){if(i.inferredOffset!==!0||!["top","bottom","left","right"].includes(i.position)||i.layout==="legacy-bottom")return i.offset;const s=y(e).filter(r=>r.position===i.position),t=Math.max(0,...s.map(r=>({bottom:r.bounds.bottom-n.y-n.height,top:n.y-r.bounds.top,left:n.x-r.bounds.left,right:r.bounds.right-n.x-n.width})[i.position]));return i.offset+t}function S(e,i,n){if(i!=="size"||n.inheritPosition===!1)return n;const s=["series","color","stroke"].map(t=>e.guideConfigs.legend?.[t]).find(t=>t?.target===n.target);return s===void 0?n:{...n,position:s.position,offset:s.offset,inferredOffset:s.inferredOffset}}function M(e,i){return[e,i].some(s=>Object.entries(s.guideConfigs.legend??{}).some(([t,r])=>{const c=S(s,t,r);return c.inferredOffset===!0&&["top","bottom"].includes(c.position)&&c.layout!=="legacy-bottom"})||Object.values(s.guideConfigs.legendGroups??{}).some(t=>t.inferredOffset===!0&&["top","bottom"].includes(t.placement.position)))&&[e,i].some(s=>y(s).some(t=>["top","bottom"].includes(t.position)))}function N(e,i){const n=new Set;for(const t of[e,i])for(const[r,c]of Object.entries(t.guideConfigs.legend??{})){const d=C(c),f=d===void 0?t.guideConfigs.legendGroups?.[c.target]:void 0,g=S(t,r,c),p=d??f?.placement??g;(d?.inferredOffset??f?.inferredOffset??g.inferredOffset)===!0&&p.at===void 0&&["left","right"].includes(p.position)&&g.layout!=="legacy-bottom"&&n.add(p.position)}const s=t=>JSON.stringify(y(t).filter(r=>n.has(r.position)));return n.size>0&&s(e)!==s(i)}export{R as allLegendGraphicIds,k as axisGraphicIds,S as effectiveLegendOffsetConfig,K as legendGraphicIds,$ as legendResourcePolicies,w as legendResourcePolicy,M as needsLegendAxisReflow,N as needsSideLegendAxisReflow,T as resolveGuideCollisionBlocks,A as resolveLegendAxisOffset};
|
|
@@ -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", "bar"].includes(layer.mark?.type) &&
|
|
5
|
+
return ["point", "line", "bar", "rect"].includes(layer.mark?.type) &&
|
|
6
6
|
layer.encoding?.opacity?.scale !== undefined;
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { findLayer } from "../selectors/layers.js";
|
|
2
|
+
import { resolvePrimitiveGraphicBounds } from "../grammar/schemas/graphicBounds.js";
|
|
3
|
+
import { resolveCoordinateBounds } from "./coordinateBounds.js";
|
|
4
|
+
import { markClipPositionOwner, validateMarkClipCoordinate } from "../selectors/markClip.js";
|
|
5
|
+
|
|
6
|
+
// Called explicitly by the owning mark materializer, using wrapped primitive edits.
|
|
7
|
+
export function materializeMarkClip(program, id) {
|
|
8
|
+
const intent = program.markConfigs[id]?.clip;
|
|
9
|
+
if (intent === undefined) return program;
|
|
10
|
+
const graphic = program.graphicSpec.objects[id];
|
|
11
|
+
if (intent === false) return graphic?.clip === undefined ? program
|
|
12
|
+
: program.editGraphics({target: id, property: "clip", value: false});
|
|
13
|
+
const layer = markClipPositionOwner(program, findLayer(program, id));
|
|
14
|
+
validateMarkClipCoordinate(program, layer, intent);
|
|
15
|
+
// Incomplete marks keep authoring intent until a coordinate and Canvas exist.
|
|
16
|
+
const bounds = layer?.coordinate === undefined ? undefined : resolveCoordinateBounds(program, layer.coordinate);
|
|
17
|
+
if (bounds === undefined) {
|
|
18
|
+
const drawable = (graphic?.items ?? (graphic ? [graphic] : [])).some(item =>
|
|
19
|
+
resolvePrimitiveGraphicBounds(item.type ?? graphic.type, item.properties) !== undefined);
|
|
20
|
+
if (drawable) throw new Error("Drawable mark clip requires a resolved Cartesian plot frame.");
|
|
21
|
+
return program;
|
|
22
|
+
}
|
|
23
|
+
const rect = {x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height};
|
|
24
|
+
if (graphic?.clip !== undefined && Object.keys(rect).every(key => rect[key] === graphic.clip[key])) return program;
|
|
25
|
+
return program.editGraphics({target: id, property: "clip", value: rect});
|
|
26
|
+
}
|
|
@@ -190,6 +190,7 @@ export function canMaterializeText(program, layer) {
|
|
|
190
190
|
return hasCartesianPositionScales(layer);
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
export function canMaterializeRect(
|
|
194
|
-
return
|
|
193
|
+
export function canMaterializeRect(program, layer) {
|
|
194
|
+
return program.markConfigs[layer.id]?.gradientPlot?.materialized === true ||
|
|
195
|
+
resolveRectMode(layer) !== undefined;
|
|
195
196
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { findUpstreamTransform } from "../dataProvenance.js";
|
|
2
|
-
import {
|
|
2
|
+
import { validatePathSeriesAppearance } from "../../grammar/pathSeries.js";
|
|
3
3
|
|
|
4
4
|
export function validatePathGroupAppearance(program, layer, dataset) {
|
|
5
|
-
|
|
5
|
+
const density = layer.mark.type === "area" &&
|
|
6
|
+
findUpstreamTransform(program, dataset, "density");
|
|
7
|
+
if (density) {
|
|
6
8
|
// Density placement owns a category/split grain beyond the visible group field.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
return validatePathSeriesAppearance(dataset.values, layer, program.semanticSpec.scales,
|
|
10
|
+
[density.groupBy, density.placement?.split?.field].filter(Boolean));
|
|
9
11
|
}
|
|
10
|
-
validatePathSeriesAppearance(dataset.values, layer);
|
|
12
|
+
validatePathSeriesAppearance(dataset.values, layer, program.semanticSpec.scales);
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export function assertPathGroupCompatible(program, layer, dataset, group) {
|
|
@@ -62,7 +62,8 @@ export function getSourceDependentMarkSteps(program, sourceId) {
|
|
|
62
62
|
getMarkMaterializationPolicy(layer)?.sourceDependent === true;
|
|
63
63
|
const statistical = program.markConfigs?.[layer.id]
|
|
64
64
|
?.statisticalReference?.source === sourceId;
|
|
65
|
-
|
|
65
|
+
const filteredCompanion = program.markConfigs?.[layer.id]?.markFilter?.owner === sourceId;
|
|
66
|
+
return sourceOwned || statistical || filteredCompanion
|
|
66
67
|
? [getMarkMaterializationStep(program, layer) ??
|
|
67
68
|
getExistingMarkRematerializationStep(program, layer)].filter(
|
|
68
69
|
step => step !== undefined
|
|
@@ -166,7 +167,8 @@ export function reusePlannedMarkScales(program, steps, scaleIds) {
|
|
|
166
167
|
const resolved = new Set(scaleIds);
|
|
167
168
|
return steps.map(step => {
|
|
168
169
|
const layer = findLayer(program, step.args?.id);
|
|
169
|
-
|
|
170
|
+
const policy = layer === undefined ? undefined : getMarkMaterializationPolicy(layer);
|
|
171
|
+
if (policy?.acceptsResolvedScales !== true || step.op !== policy.op ||
|
|
170
172
|
!getLayerScaleIds(layer).every(id => resolved.has(id))) return step;
|
|
171
173
|
return { ...step, args: { ...step.args, scales: false } };
|
|
172
174
|
});
|
|
@@ -48,6 +48,7 @@ const MARK_MATERIALIZATION_POLICIES = Object.freeze({
|
|
|
48
48
|
area: Object.freeze({
|
|
49
49
|
canMaterialize: canMaterializeArea,
|
|
50
50
|
op: "rematerializeAreaMark",
|
|
51
|
+
acceptsResolvedScales: true,
|
|
51
52
|
positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: true }),
|
|
52
53
|
encoding: Object.freeze({
|
|
53
54
|
sharedChannels: Object.freeze(["color", "stroke"]),
|
|
@@ -66,12 +67,14 @@ const MARK_MATERIALIZATION_POLICIES = Object.freeze({
|
|
|
66
67
|
bar: Object.freeze({
|
|
67
68
|
canMaterialize: canMaterializeBar,
|
|
68
69
|
op: "rematerializeBarMark",
|
|
70
|
+
acceptsResolvedScales: true,
|
|
69
71
|
positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: false }),
|
|
70
72
|
scaleApplication: Object.freeze({ default: "defer" })
|
|
71
73
|
}),
|
|
72
74
|
rule: Object.freeze({
|
|
73
75
|
canMaterialize: canMaterializeRule,
|
|
74
76
|
op: "rematerializeRuleMark",
|
|
77
|
+
acceptsResolvedScales: true,
|
|
75
78
|
positionEncoding: Object.freeze({ incomplete: "mark", scaleFirst: false }),
|
|
76
79
|
scaleApplication: Object.freeze({ default: "defer" })
|
|
77
80
|
}),
|
|
@@ -91,6 +94,7 @@ const MARK_MATERIALIZATION_POLICIES = Object.freeze({
|
|
|
91
94
|
rect: Object.freeze({
|
|
92
95
|
canMaterialize: canMaterializeRect,
|
|
93
96
|
op: "rematerializeRectMark",
|
|
97
|
+
acceptsResolvedScales: true,
|
|
94
98
|
positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: true }),
|
|
95
99
|
encoding: Object.freeze({ scaleFirst: true }),
|
|
96
100
|
scaleApplication: Object.freeze({
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { cartesianAxisOwners, axisGraphicIds, PARALLEL_AXIS_GRAPHICS } from "../selectors/axes.js";
|
|
2
|
+
import { gridNames } from "../selectors/grids.js";
|
|
3
|
+
import { resolvePlotDrawOrder } from "../layout/plotDrawOrder.js";
|
|
4
|
+
|
|
5
|
+
export function hasPlotUnderlays(program) {
|
|
6
|
+
return program.compositionSpec === undefined && (
|
|
7
|
+
program.materializationConfigs.canvas?.plotBorder?.underlay === true ||
|
|
8
|
+
cartesianAxisOwners(program).some(owner => owner.config?.frame?.underlay === true)
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function plotDrawOrderResources(program) {
|
|
13
|
+
const axes = cartesianAxisOwners(program).map(owner => ({
|
|
14
|
+
ids: axisGraphicIds(owner.channel, owner.id), underlay: owner.config?.frame?.underlay === true
|
|
15
|
+
}));
|
|
16
|
+
for (const channel of ["theta", "radius"]) if (program.semanticSpec.guides.axis?.[channel] !== undefined) {
|
|
17
|
+
axes.push({ ids: axisGraphicIds(channel), underlay: false });
|
|
18
|
+
}
|
|
19
|
+
if (program.semanticSpec.guides.axis?.parallel !== undefined) axes.push({ ids: Object.values(PARALLEL_AXIS_GRAPHICS), underlay: false });
|
|
20
|
+
return {
|
|
21
|
+
children: program.graphicSpec.objects["plot-main"]?.children ?? [],
|
|
22
|
+
grids: ["horizontal", "vertical"].flatMap((direction, index) => {
|
|
23
|
+
const config = program.guideConfigs.grid?.[direction];
|
|
24
|
+
return config === undefined ? [] : [{ id: gridNames(direction).graphic, zIndex: config.zIndex, index }];
|
|
25
|
+
}),
|
|
26
|
+
border: program.materializationConfigs.canvas?.plotBorder === undefined ? undefined : {
|
|
27
|
+
id: "plotBorder", underlay: program.materializationConfigs.canvas.plotBorder.underlay === true
|
|
28
|
+
}, axes
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function resolveProgramPlotDrawOrder(program) {
|
|
33
|
+
return resolvePlotDrawOrder(plotDrawOrderResources(program));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function firstOverlayGuide(program) {
|
|
37
|
+
const { children, grids, border, axes } = plotDrawOrderResources(program);
|
|
38
|
+
const overlay = new Set([
|
|
39
|
+
...grids.filter(grid => grid.zIndex > 0).map(grid => grid.id),
|
|
40
|
+
...(border?.underlay === false ? [border.id] : []),
|
|
41
|
+
...axes.filter(axis => !axis.underlay).flatMap(axis => axis.ids)
|
|
42
|
+
]);
|
|
43
|
+
return children.find(id => overlay.has(id));
|
|
44
|
+
}
|
|
@@ -27,7 +27,7 @@ function requiredChannels(layer, mode) {
|
|
|
27
27
|
return [
|
|
28
28
|
...position,
|
|
29
29
|
...(layer.encoding?.color ? ["color"] : []),
|
|
30
|
-
...
|
|
30
|
+
...["stroke", "opacity", "strokeWidth"].filter(channel => layer.encoding?.[channel])
|
|
31
31
|
];
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -96,6 +96,10 @@ export function resolveRectRows(program, layer, dataset) {
|
|
|
96
96
|
const stroke = layer.encoding?.stroke === undefined
|
|
97
97
|
? undefined
|
|
98
98
|
: mappedEncoding(program, layer, dataset, "stroke").values;
|
|
99
|
+
const opacity = layer.encoding?.opacity === undefined ? undefined
|
|
100
|
+
: mappedEncoding(program, layer, dataset, "opacity").values;
|
|
101
|
+
const strokeWidth = layer.encoding?.strokeWidth === undefined ? undefined
|
|
102
|
+
: mappedEncoding(program, layer, dataset, "strokeWidth").values;
|
|
99
103
|
const config = program.markConfigs[layer.id] ?? DEFAULT_RECT_MARK;
|
|
100
104
|
|
|
101
105
|
const rows = rectUsesFields(layer) ? dataset.values : [{}];
|
|
@@ -105,7 +109,9 @@ export function resolveRectRows(program, layer, dataset) {
|
|
|
105
109
|
(x !== undefined && !Number.isFinite(x.values[index])) ||
|
|
106
110
|
(y !== undefined && !Number.isFinite(y.values[index])) ||
|
|
107
111
|
typeof fill !== "string" ||
|
|
108
|
-
(stroke !== undefined && typeof stroke[index] !== "string")
|
|
112
|
+
(stroke !== undefined && typeof stroke[index] !== "string") ||
|
|
113
|
+
(opacity !== undefined && !Number.isFinite(opacity[index])) ||
|
|
114
|
+
(strokeWidth !== undefined && !Number.isFinite(strokeWidth[index]))
|
|
109
115
|
) return [];
|
|
110
116
|
|
|
111
117
|
let geometry;
|
|
@@ -138,7 +144,9 @@ export function resolveRectRows(program, layer, dataset) {
|
|
|
138
144
|
return [{
|
|
139
145
|
row,
|
|
140
146
|
sourceIndex: index,
|
|
141
|
-
properties: { ...geometry, ...appearance(config, fill, stroke?.[index])
|
|
147
|
+
properties: { ...geometry, ...appearance(config, fill, stroke?.[index]),
|
|
148
|
+
...(opacity === undefined ? {} : { opacity: opacity[index] }),
|
|
149
|
+
...(strokeWidth === undefined ? {} : { strokeWidth: strokeWidth[index] }) }
|
|
142
150
|
}];
|
|
143
151
|
});
|
|
144
152
|
}
|
|
@@ -16,14 +16,22 @@ const GRIDS = Object.freeze([
|
|
|
16
16
|
Object.freeze({ role: "radial", op: "rematerializeRadialGrid" })
|
|
17
17
|
]);
|
|
18
18
|
|
|
19
|
-
export function planAxisTickGridRematerialization(program, channel, axisId = channel) {
|
|
19
|
+
export function planAxisTickGridRematerialization(program, channel, axisId = channel, { syncLabels = false } = {}) {
|
|
20
20
|
const ticks = program.guideConfigs.axis?.[axisId]?.ticks;
|
|
21
21
|
const role = channel === "x" ? "vertical" : "horizontal";
|
|
22
22
|
const config = program.guideConfigs.grid?.[role];
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const labels = syncLabels && program.guideConfigs.axis?.[axisId]?.labels
|
|
24
|
+
? [{ op: `edit${channel.toUpperCase()}AxisLabels`, args: {
|
|
25
|
+
...axisTargetArgs(axisId, channel),
|
|
26
|
+
...(ticks.mode === "values" ? { values: ticks.values } : {
|
|
27
|
+
count: ticks.count,
|
|
28
|
+
...(ticks.rounding === undefined ? {} : { rounding: ticks.rounding })
|
|
29
|
+
})
|
|
30
|
+
} }] : [];
|
|
31
|
+
return buildMaterializationPlan({ guides: [...labels,
|
|
32
|
+
...(config?.inferredValues === true && config.scale === ticks?.scale
|
|
33
|
+
? GRIDS.filter(grid => grid.role === role).map(grid => ({ op: grid.op })) : [])
|
|
34
|
+
] });
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
function usesPositionalScale(program, id) {
|
|
@@ -102,7 +102,7 @@ function collectionBounds(program, items) {
|
|
|
102
102
|
const bounds = unionConcreteGraphicBounds(
|
|
103
103
|
program.graphicSpec,
|
|
104
104
|
items.map(item => item.id)
|
|
105
|
-
, program.materializationConfigs.textMetrics);
|
|
105
|
+
, program.materializationConfigs.textMetrics, {includeOwnerClips: false});
|
|
106
106
|
if (bounds === undefined) return {};
|
|
107
107
|
const { left, top, right, bottom } = bounds;
|
|
108
108
|
return { x: left, y: top, width: right - left, height: bottom - top };
|
|
@@ -8,6 +8,7 @@ import { pointSelectionPolicy } from "./point.js";
|
|
|
8
8
|
import { ruleSelectionPolicy } from "./rule.js";
|
|
9
9
|
import { tickSelectionPolicy } from "./tick.js";
|
|
10
10
|
import { rectSelectionPolicy } from "./rect.js";
|
|
11
|
+
import { applyItemMissingPolicy } from "../../../grammar/itemMissing.js";
|
|
11
12
|
|
|
12
13
|
const POLICIES = Object.freeze({
|
|
13
14
|
arc: arcSelectionPolicy,
|
|
@@ -42,5 +43,5 @@ export function resolveMarkItems(program, target, grain = "item") {
|
|
|
42
43
|
if (!policy.supportedGrains.includes(grain)) {
|
|
43
44
|
throw new Error(`Mark "${target}" does not support ${grain} selection grain.`);
|
|
44
45
|
}
|
|
45
|
-
return policy.resolveItems(program, layer, dataset, grain);
|
|
46
|
+
return policy.resolveItems(program, layer, applyItemMissingPolicy(layer, dataset), grain);
|
|
46
47
|
}
|