ggaction 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/README.md +83 -90
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/package.json +11 -4
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +1 -0
- package/src/actions/boxPlots/components.js +1 -96
- package/src/actions/boxPlots/create.js +6 -1
- package/src/actions/boxPlots/edit.js +8 -0
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +1 -1
- package/src/actions/canvas/actions.js +2 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +2 -1
- package/src/actions/canvas/plotOrder.js +14 -0
- package/src/actions/charts/area.js +1 -1
- package/src/actions/charts/bar.js +4 -4
- package/src/actions/charts/density.js +1 -1
- package/src/actions/charts/ecdf.js +1 -1
- package/src/actions/charts/endpoints.js +54 -5
- package/src/actions/charts/heatmap.js +1 -1
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +1 -1
- package/src/actions/charts/index.js +2 -1
- package/src/actions/charts/interval-regression.js +3 -3
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/radar.js +63 -3
- package/src/actions/charts/raincloud.js +37 -6
- package/src/actions/charts/rug-strip.js +2 -2
- package/src/actions/charts/scatter.js +3 -3
- package/src/actions/charts/text.js +1 -1
- package/src/actions/composition/actions.js +10 -3
- package/src/actions/coordinates/actions.js +2 -0
- package/src/actions/data/basic.js +6 -0
- package/src/actions/data/derived.js +6 -2
- package/src/actions/data/edit.js +22 -36
- package/src/actions/data/filter.js +1 -376
- package/src/actions/data/flatten.js +19 -0
- package/src/actions/data/index.js +6 -0
- package/src/actions/data/lookup.js +50 -0
- package/src/actions/data/raincloudFilter.js +56 -0
- package/src/actions/data/shared.js +10 -0
- package/src/actions/data/timeUnit.js +1 -1
- package/src/actions/distributions/revision.js +3 -1
- package/src/actions/encodings/angle.js +2 -1
- package/src/actions/encodings/appearance.js +38 -13
- package/src/actions/encodings/channels.js +14 -3
- package/src/actions/encodings/color/continuous.js +26 -10
- package/src/actions/encodings/color/index.js +50 -20
- package/src/actions/encodings/color/policy.js +57 -4
- package/src/actions/encodings/parallel.js +4 -0
- package/src/actions/encodings/position/policies/bar.js +10 -6
- package/src/actions/encodings/position/policies/line.js +1 -152
- package/src/actions/encodings/position/resolve.js +4 -1
- package/src/actions/encodings/ranged.js +46 -0
- package/src/actions/encodings/remove.js +1 -1
- package/src/actions/encodings/ruleAppearance.js +7 -6
- package/src/actions/encodings/shared.js +14 -2
- package/src/actions/encodings/stroke.js +20 -14
- package/src/actions/encodings/strokeDash.js +9 -11
- package/src/actions/encodings/text.js +30 -2
- package/src/actions/errorBars/edit.js +24 -4
- package/src/actions/facets/actions.js +1 -793
- package/src/actions/facets/derive.js +34 -5
- package/src/actions/facets/replay.js +1 -38
- package/src/actions/gradientPlots/components.js +21 -9
- package/src/actions/gradientPlots/create.js +4 -2
- package/src/actions/gradientPlots/edit.js +18 -1
- package/src/actions/gradientPlots/materialize.js +35 -36
- package/src/actions/gradientPlots/options.js +1 -1
- package/src/actions/gradientPlots/paint.js +13 -7
- package/src/actions/guides/applicability.js +2 -0
- package/src/actions/guides/axes/axes.js +39 -0
- package/src/actions/guides/axes/axis.js +9 -5
- package/src/actions/guides/axes/edit.js +11 -8
- package/src/actions/guides/axes/labels.js +24 -8
- package/src/actions/guides/axes/parallel/policy.js +3 -5
- package/src/actions/guides/axes/parallel/resolve.js +4 -3
- package/src/actions/guides/axes/policy.js +22 -0
- package/src/actions/guides/axes/tickGroups.js +4 -2
- package/src/actions/guides/axes/ticks.js +22 -4
- package/src/actions/guides/axes/titles.js +19 -6
- package/src/actions/guides/facadeAxes.js +3 -0
- package/src/actions/guides/grids/grid.js +3 -1
- package/src/actions/guides/grids/resolve.js +7 -11
- package/src/actions/guides/guides.js +21 -1
- package/src/actions/guides/legends/categorical/actions.js +1 -515
- package/src/actions/guides/legends/categorical/components.js +1 -4
- package/src/actions/guides/legends/categorical/layout.js +5 -2
- package/src/actions/guides/legends/categorical/options.js +4 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -3
- package/src/actions/guides/legends/categorical/symbols.js +3 -3
- package/src/actions/guides/legends/colorSize.js +2 -0
- package/src/actions/guides/legends/continuous/common.js +1 -1
- package/src/actions/guides/legends/continuous/gradient.js +1 -360
- package/src/actions/guides/legends/continuous/interval.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +15 -1
- package/src/actions/guides/legends/edit.js +1 -1
- package/src/actions/guides/legends/focused.js +1 -1
- package/src/actions/guides/legends/lane.js +1 -1
- package/src/actions/guides/legends/sampling.js +1 -140
- package/src/actions/guides/legends/strokeWidth.js +3 -2
- package/src/actions/guides/legends/transition.js +19 -2
- package/src/actions/guides/polar/axes/facade.js +5 -4
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/resolve.js +2 -1
- package/src/actions/guides/tickValues.js +7 -7
- package/src/actions/marks/arc/actions.js +5 -7
- package/src/actions/marks/area/actions.js +9 -3
- package/src/actions/marks/bar/create.js +4 -2
- package/src/actions/marks/bar/edit.js +9 -6
- package/src/actions/marks/bar/materialize.js +5 -3
- package/src/actions/marks/dependentReferences.js +87 -0
- package/src/actions/marks/index.js +2 -0
- package/src/actions/marks/lifecycle.js +2 -1
- package/src/actions/marks/line/actions.js +24 -6
- package/src/actions/marks/line/materialize.js +7 -3
- package/src/actions/marks/point/create.js +5 -4
- package/src/actions/marks/point/edit.js +6 -3
- package/src/actions/marks/point/jitter.js +6 -6
- package/src/actions/marks/point/materialize.js +36 -39
- package/src/actions/marks/rect/actions.js +16 -4
- package/src/actions/marks/references.js +183 -35
- package/src/actions/marks/rule/actions.js +20 -9
- package/src/actions/marks/shared.js +2 -1
- package/src/actions/marks/text/actions.js +10 -3
- package/src/actions/marks/tick/actions.js +11 -2
- package/src/actions/primitives/editGraphics.js +28 -3
- package/src/actions/primitives/index.js +9 -0
- package/src/actions/primitives/semanticAction.js +13 -7
- package/src/actions/primitives/semanticValidation/layer.js +20 -0
- package/src/actions/primitives/semanticValidation/scale.js +8 -1
- package/src/actions/regression/create.js +6 -1
- package/src/actions/regression/edit.js +10 -1
- package/src/actions/scales/channels.js +1 -1
- package/src/actions/scales/consumers/common.js +19 -1
- package/src/actions/scales/consumers/families.js +2 -2
- package/src/actions/scales/consumers/seriesLayout.js +4 -3
- package/src/actions/scales/edit.js +11 -0
- package/src/actions/scales/editPolicy.js +5 -3
- package/src/actions/scales/preview.js +2 -1
- package/src/actions/scales/quantitativeColor.js +2 -4
- package/src/actions/selection/actions.js +10 -1
- package/src/actions/theme/reconcile.js +37 -9
- package/src/core/compositionState.js +26 -7
- package/src/core/datasetReferences.js +17 -0
- package/src/core/facetAxisPolicy.js +30 -0
- package/src/core/immutable.js +1 -0
- package/src/core/resourceReferences.js +5 -3
- package/src/core/validation.js +4 -0
- package/src/core/vocabulary.js +24 -2
- package/src/grammar/aggregate.js +1 -0
- package/src/grammar/arcs.js +1 -1
- package/src/grammar/areaSeries.js +1 -1
- package/src/grammar/axisTextPlacement.js +1 -1
- package/src/grammar/bars/policy.js +2 -2
- package/src/grammar/curveCommands.js +35 -2
- package/src/grammar/datasetDependencies.js +97 -0
- package/src/grammar/datasetSchema.js +32 -2
- package/src/grammar/facetHeaders.js +1 -0
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +7 -5
- package/src/grammar/facets/legendIntent.js +6 -2
- package/src/grammar/facets/scales.js +27 -7
- package/src/grammar/flatten.js +85 -0
- package/src/grammar/histogram.js +13 -1
- package/src/grammar/horizon.js +1 -494
- package/src/grammar/jitter.js +2 -2
- package/src/grammar/legendSpacing.js +45 -0
- package/src/grammar/lineMissing.js +90 -0
- package/src/grammar/lineSeries.js +1 -376
- package/src/grammar/lookup.js +97 -0
- package/src/grammar/markFilter.js +4 -2
- package/src/grammar/markLabels.js +8 -3
- package/src/grammar/numeric.js +1 -1
- package/src/grammar/pathCommands.js +1 -1
- package/src/grammar/pathSeries.js +1 -109
- package/src/grammar/pointPolygon.js +81 -0
- package/src/grammar/pointShapes.js +15 -15
- package/src/grammar/rects.js +1 -1
- package/src/grammar/regression/derive.js +1 -195
- package/src/grammar/regression/parameters.js +7 -0
- package/src/grammar/scales/appearance.js +1 -1
- package/src/grammar/scales/color.js +1 -1
- package/src/grammar/scales/colorConsumers.js +1 -63
- package/src/grammar/scales/continuous.js +58 -1
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/ordinal.js +5 -2
- package/src/grammar/scales/policies.js +3 -3
- package/src/grammar/schemas/graphicBounds.js +40 -12
- package/src/grammar/schemas/graphicClip.js +23 -0
- package/src/grammar/schemas/graphicTree.js +16 -0
- package/src/grammar/statisticalReference.js +22 -0
- package/src/grammar/text.js +65 -1
- package/src/grammar/ticks.js +92 -1
- package/src/grammar/timeUnit.js +9 -3
- package/src/grammar/transformTopology.js +3 -1
- package/src/grammar/transforms.js +24 -0
- package/src/inspection.js +1 -495
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -1
- package/src/layout/facetContentFlow.js +101 -0
- package/src/layout/facetHeaderTitles.js +1 -0
- package/src/layout/facetTopLane.js +29 -0
- package/src/layout/facets.js +1 -1
- package/src/layout/guideCollisions.js +9 -3
- package/src/layout/legendGroup.js +1 -1
- package/src/layout/legendItems.js +26 -10
- package/src/layout/legendSpacing.js +66 -0
- package/src/layout/plotDrawOrder.js +17 -0
- package/src/materialization/bars/histogram.js +8 -2
- package/src/materialization/bars/resolve.js +1 -1
- package/src/materialization/composition.js +1 -0
- package/src/materialization/compositionSnapshot.js +22 -2
- package/src/materialization/dataProvenance.js +1 -1
- package/src/materialization/facetContentBounds.js +96 -0
- package/src/materialization/facetGuides/layout.js +1 -1
- package/src/materialization/facetGuides/placement.js +39 -11
- package/src/materialization/facetGuides/preparation.js +43 -24
- package/src/materialization/facetHeaders.js +1 -1
- package/src/materialization/facetTopLane.js +25 -0
- package/src/materialization/facets.js +1 -1
- package/src/materialization/graphicHierarchy.js +30 -1
- package/src/materialization/graphicTranslation.js +2 -0
- package/src/materialization/guides/layout.js +1 -1
- package/src/materialization/guides/resources.js +1 -1
- package/src/materialization/legends.js +1 -1
- package/src/materialization/markClip.js +26 -0
- package/src/materialization/marks/capabilities.js +3 -2
- package/src/materialization/marks/grouping.js +7 -5
- package/src/materialization/marks/index.js +4 -2
- package/src/materialization/marks/policies.js +4 -0
- package/src/materialization/plotDrawOrder.js +44 -0
- package/src/materialization/rect.js +11 -3
- package/src/materialization/scaleGuideDependencies.js +13 -5
- package/src/materialization/selection/items/common.js +1 -1
- package/src/materialization/selection/items/path.js +1 -0
- package/src/materialization/selection/policies/index.js +2 -1
- package/src/materialization/text.js +10 -5
- package/src/mcp/cli.js +0 -0
- package/src/persistence/validation.js +114 -18
- package/src/persistence.js +4 -2
- package/src/renderers/canvas/index.js +16 -0
- package/src/renderers/canvas/isolated.js +11 -1
- package/src/renderers/canvas/native.js +7 -0
- package/src/renderers/canvas/scene.js +66 -0
- package/src/renderers/canvas/text.js +3 -2
- package/src/renderers/svg.js +1 -617
- package/src/renderers/svgSerialization.js +643 -0
- package/src/renderers/transitionScene.js +67 -0
- package/src/scene.js +89 -0
- package/src/selectors/axes.js +1 -1
- package/src/selectors/datasets.js +1 -0
- package/src/selectors/grids.js +9 -0
- package/src/selectors/markClip.js +28 -0
- package/src/selectors/raincloud.js +7 -0
- package/src/version.js +1 -1
- package/types/basic.d.ts +7 -0
- package/types/index.d.ts +8 -0
- package/types/program.d.ts +286 -70
- package/types/scene.d.ts +35 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{cartesianAxisOwners as O,axisGraphicIds as S}from"../../selectors/axes.js";import{resolveAuthoredFacetLegendLayout as B}from"./ownerLayout.js";import{PLOT_LEGEND_POSITIONS as F}from"../../layout/legend.js";import{resolveConcreteGraphicBounds as P}from"../../grammar/schemas/graphicBounds.js";import{resolveTextBounds as y,textBoundsIntersect as C}from"../../core/textMetrics.js";import{resolveFacetHeaderItems as E,retainedFacetGuideIds as T}from"../facetHeaders.js";function w(n,t,e){return{left:n.left+t,right:n.right+t,top:n.top+e,bottom:n.bottom+e}}function M(n,t,e,m){const f=n.materializationConfigs.textMetrics,h=[];for(const o of t.children){const c=n.children[o.id],g=new Set(o.footerShift===void 0?[]:O(c).filter(d=>d.channel==="x").flatMap(d=>["line","ticks","labels","title"].flatMap((a,l)=>d.config?.[a]?.position==="bottom"?[S(d.channel,d.id)[l]]:[])));for(const d of T(n,o.id)){const a=P(c.graphicSpec,d,f);a!==void 0&&h.push(w(a,o.x,o.y+(g.has(d)?o.footerShift:0)))}}const{items:i}=E(t,e,m);for(const o of i)o.properties.text!==""&&h.push(y(o.properties,f));return h}function b(n,t,e,m,f){const h=n.right-n.left,i=n.bottom-n.top,o=e==="right"?t.x+t.width+f:e==="left"?t.x-f-h:m==="left"?t.x:m==="right"?t.x+t.width-h:t.x+(t.width-h)/2,c=["left","right"].includes(e)?t.y:e==="top"?t.y-f-i:t.y+t.height+f;return{left:o,right:o+h,top:c,bottom:c+i}}function j(n,{layout:t,plot:e,plots:m,title:f,preparedHeaders:h,prepared:i}){if(i?.mode==="authored")return B(n,{layout:t,plot:e,title:f,prepared:i,guides:M(n,t,m,h)});if(i?.mode!=="promoted")return{layout:t,plot:e};const o=i.occupiedGroup;if(o?.placement.at!==void 0||F.includes(o?.placement.position))return{layout:t,plot:e};const c=i.reservation.position,g=i.reservation.align,d=i.kinds.map(r=>i.source.guideConfigs.legend[r]),a=i.reservation.gap,l=o===void 0?d.every(r=>r.inferredOffset===!0):o.inferredOffset===!0;let s=b(i.bounds,e,c,g,a);const v=M(n,t,m,h),I=v.map(r=>c==="right"?r.right-(e.x+e.width):c==="left"?e.x-r.left:c==="top"?e.y-r.top:r.bottom-(e.y+e.height));if(l)s=b(i.bounds,e,c,g,Math.max(a,...I.map(r=>r+a)));else if(v.some(r=>C(s,r)))throw new Error("Facet shared legend overlaps retained axes or headers.");const x=Math.max(0,t.padding.left-s.left),u=Math.max(0,f.height+t.padding.top-s.top),G=t.children.map(r=>({...r,x:r.x+x,y:r.y+u})),L={...e,x:e.x+x,y:e.y+u};return s=w(s,x,u),{plot:L,layout:{...t,children:G,width:Math.ceil(Math.max(t.width+x,s.right+t.padding.right)),height:Math.ceil(Math.max(t.height+u,s.bottom+t.padding.bottom)),legend:{position:c,x:s.left,y:s.top,width:s.right-s.left,height:s.bottom-s.top,translation:{x:s.left-i.bounds.left,y:s.top-i.bounds.top}}}}}export{j as resolvePromotedFacetLegendLayout};
|
|
@@ -145,15 +145,8 @@ function attachAuthoredParentLegend(program, prepared, layout, synchronizeColorS
|
|
|
145
145
|
compositionSpec: { ...spec, facet: { ...spec.facet, guides: { ...spec.facet.guides, legend: prepared.intent } } } });
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
export function
|
|
149
|
-
|
|
150
|
-
throw new TypeError("Facet guide composition requires a resolved layout.");
|
|
151
|
-
}
|
|
152
|
-
if (!isPlainObject(plot)) {
|
|
153
|
-
throw new TypeError("Facet guide composition requires resolved plot bounds.");
|
|
154
|
-
}
|
|
155
|
-
const prepared = prepareSharedFacetLegend(program);
|
|
156
|
-
const children = layout.children.map(cell => {
|
|
148
|
+
export function prepareFacetGuideOwnership(program, placements, prepared) {
|
|
149
|
+
const children = placements.map(cell => {
|
|
157
150
|
const child = program.children[cell.id];
|
|
158
151
|
return {
|
|
159
152
|
id: cell.id,
|
|
@@ -164,17 +157,36 @@ export function applyFacetGuideComposition(program, { layout, plot, synchronizeC
|
|
|
164
157
|
legendKinds: legendKinds(child)
|
|
165
158
|
};
|
|
166
159
|
});
|
|
167
|
-
|
|
168
|
-
placements
|
|
160
|
+
return planFacetGuideOwnership({
|
|
161
|
+
placements,
|
|
169
162
|
children,
|
|
170
163
|
axes: program.compositionSpec.facet.guides.axes,
|
|
171
164
|
legend: program.compositionSpec.facet.guides.legend,
|
|
172
165
|
...(prepared === undefined ? {} : { sharedLegends: prepared.compatibility })
|
|
173
166
|
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function applyFacetGuideComposition(program, { layout, plot, synchronizeColorSizeMeaning } = {}) {
|
|
170
|
+
if (!isPlainObject(layout) || !Array.isArray(layout.children)) {
|
|
171
|
+
throw new TypeError("Facet guide composition requires a resolved layout.");
|
|
172
|
+
}
|
|
173
|
+
if (!isPlainObject(plot)) {
|
|
174
|
+
throw new TypeError("Facet guide composition requires resolved plot bounds.");
|
|
175
|
+
}
|
|
176
|
+
const prepared = prepareSharedFacetLegend(program);
|
|
177
|
+
const ownership = prepareFacetGuideOwnership(program, layout.children, prepared);
|
|
174
178
|
let next = program;
|
|
175
179
|
for (const cell of layout.children) {
|
|
176
180
|
const namespace = `${program.compositionSpec.id}-${cell.id}`;
|
|
177
181
|
const childPlan = ownership.children[cell.id];
|
|
182
|
+
// Density guides belong to the gradient owner rather than a color channel.
|
|
183
|
+
// Facets currently support no shared density guide; the parent's legend
|
|
184
|
+
// policy still removes each local copy while retaining hue legends.
|
|
185
|
+
for (const [owner, config] of Object.entries(program.children[cell.id].markConfigs)) {
|
|
186
|
+
if (!config.gradientPlot) continue;
|
|
187
|
+
next = removeNamespacedGraphics(next, namespace,
|
|
188
|
+
["DensityLegend", "DensityLegendLabels", "DensityLegendTitle"].map(suffix => `${owner}${suffix}`));
|
|
189
|
+
}
|
|
178
190
|
const axisOwners = cartesianAxisOwners(program.children[cell.id]);
|
|
179
191
|
for (const axisId of childPlan.removeAxes) {
|
|
180
192
|
const owner = axisOwners.find(owner => owner.id === axisId);
|
|
@@ -185,6 +197,22 @@ export function applyFacetGuideComposition(program, { layout, plot, synchronizeC
|
|
|
185
197
|
const ids = axisGraphicIds(owner.channel, axisId);
|
|
186
198
|
next = removeNamespacedGraphics(next, namespace, components.map(component => ids[["line", "ticks", "labels", "title"].indexOf(component)]));
|
|
187
199
|
}
|
|
200
|
+
if (cell.footerShift !== undefined) {
|
|
201
|
+
for (const owner of axisOwners.filter(owner => owner.channel === "x")) {
|
|
202
|
+
for (const [index, component] of ["line", "ticks", "labels", "title"].entries()) {
|
|
203
|
+
if (owner.config?.[component]?.position !== "bottom") continue;
|
|
204
|
+
const id = namespaceGraphicId(namespace, axisGraphicIds(owner.channel, owner.id)[index]);
|
|
205
|
+
if (next.graphicSpec.objects[id] !== undefined) {
|
|
206
|
+
// Footer guides belong to the parent viewport: moving them within
|
|
207
|
+
// the child Canvas would clip ragged-row owners at its old edge.
|
|
208
|
+
const snapshot = next.graphicSpec;
|
|
209
|
+
next = next.editGraphics({ target: id, remove: true });
|
|
210
|
+
next = attachSnapshotObject(next, snapshot, id, "canvas");
|
|
211
|
+
next = translateGraphic(next, id, cell.x, cell.y + cell.footerShift);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
188
216
|
if (childPlan.removeLegends.length > 0) {
|
|
189
217
|
next = removeNamespacedGraphics(
|
|
190
218
|
next,
|
|
@@ -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{measureFacetContentChildren as V}from"./facetContentBounds.js";import{facetAxisMode as P,hasOuterFacetAxes as W}from"../core/facetAxisPolicy.js";import{isPlainObject as v}from"../core/immutable.js";import{formatVisibleText as _,measureTextWidth as U,resolveTextBounds as z,textBoundsIntersect as S}from"../core/textMetrics.js";import{validateFacetHeaderRotations as K}from"../grammar/facetHeaders.js";import{resolveDisplayLabel as N}from"../grammar/displayLabels.js";import{resolveGraphicBounds as F}from"../layout/canvas.js";import{unionConcreteGraphicBounds as Y}from"../grammar/schemas/graphicBounds.js";import{intersectClipBounds as Z}from"../grammar/schemas/graphicClip.js";import{DEFAULT_COLORS as J,DEFAULT_FONT_FAMILY as Q}from"../theme/defaults.js";import{prepareFacetHeaderTitles as X}from"../layout/facetHeaderTitles.js";import{cartesianAxisOwners as tt,axisGraphicIds as E}from"../selectors/axes.js";import{resolveFacetOuterSides as et}from"../grammar/facets/guides.js";import{resolveConcreteGraphicBounds as ot}from"../grammar/schemas/graphicBounds.js";import{allLegendGraphicIds as nt}from"./guides/resources.js";const it=Object.freeze(["top","right","bottom","left"]),B=Object.freeze({top:0,right:0,bottom:0,left:0});function ct(t){return`${t}-headers`}function T(){return{mode:"legacy",common:{fontSize:12,fontFamily:Q,fontWeight:600,color:J.strongText,offset:10,align:"center"},row:{side:"left"},column:{side:"top"}}}function j(t){if(!v(t))throw new TypeError("Facet headers config must be a plain object.");if(K(t),t.common===void 0&&t.mode===void 0)return{...T(),common:{...T().common,...t}};if(!["legacy","roles"].includes(t.mode)||!v(t.common)||!v(t.row)||!v(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 N(t,e.labelMap,_)}function st(t){return new Map((t.facet.grid?.cells??[]).map(e=>[e.id,e]))}function rt(t,e){const o=t.compositionSpec,c=y(e,"all"),s=st(o);return o.children.map((i,n)=>{const r=s.get(i),f=r===void 0?b(o.facet.values[n],c):`${b(r.rowValue,c)} \xB7 ${b(r.columnValue,c)}`;return{topology:"legacy",role:"column",id:i,text:f,config:c}})}function G(t,e){const o=e==="column"?"column":"row",c=e==="column"?"columnValue":"rowValue",s=new Map;for(const i of t.facet.grid.cells){const n=s.get(i[o]);if(n===void 0)s.set(i[o],{coordinate:i[o],value:i[c],ids:[i.id]});else{if(n.value!==i[c])throw new Error(`Facet ${e} header values conflict at coordinate ${i[o]}.`);n.ids.push(i.id)}}return[...s.values()].sort((i,n)=>i.coordinate-n.coordinate)}function ft(t,e){const o=t.compositionSpec,c=y(e,"column");if(o.facet.grid===void 0)return o.children.map((r,f)=>({topology:"cell-column",role:"column",id:r,ids:[r],row:Math.floor(f/o.columns),column:f%o.columns,text:b(o.facet.values[f],c),config:c}));const s=y(e,"row"),i=G(o,"column").map(r=>({topology:"grid-column",role:"column",ids:r.ids,coordinate:r.coordinate,text:b(r.value,c),config:c})),n=G(o,"row").map(r=>({topology:"grid-row",role:"row",ids:r.ids,coordinate:r.coordinate,text:b(r.value,s),config:s}));return[...i,...n]}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 at(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)?U(t.text,t.config,o):t.config.fontSize}function lt(t){const e={...B};if(!["plot","content"].includes(t.compositionSpec.spacing))return e;for(const o of t.compositionSpec.children){const c=t.children[o],s=F(c),i=c.graphicSpec.objects.canvas.properties;e.left=Math.max(e.left,s.x),e.top=Math.max(e.top,s.y),e.right=Math.max(e.right,i.width-s.x-s.width),e.bottom=Math.max(e.bottom,i.height-s.y-s.height)}return e}function O(t,e,o,c,s){const i=at(t,e,o)+t.config.offset+(t.clearance?.[e]??0);return x(t.config)&&s?Math.max(0,i-c[e]):i}function dt(t,e,o){if(!e.some(n=>["content","view"].includes(n.config.anchor)))return;if(!["plot","content"].includes(t.compositionSpec.spacing))throw new Error("Facet header content anchor requires plot spacing or content spacing.");const c=new Map,s=e.some(n=>n.config.anchor==="view")?new Map(V(t).map(n=>[n.id,n])):void 0;for(const n of t.compositionSpec.children){const r=t.children[n],f=F(r),d={left:f.x,right:f.x+f.width,top:f.y,bottom:f.y+f.height},l=r.semanticSpec.layers.map(u=>u.id).filter(u=>r.graphicSpec.objects[u]!==void 0),h=Y(r.graphicSpec,l,o),a=r.graphicSpec.objects.canvas.properties,m=h===void 0?void 0:Z(h,{left:0,right:a.width,top:0,bottom:a.height});c.set(n,m===void 0||m.left>m.right||m.top>m.bottom?d:C([d,m]))}const i=s===void 0?void 0:new Map([...s].map(([n,r])=>[n,{left:r.bounds.left+r.plot.x,right:r.bounds.right+r.plot.x,top:r.bounds.top+r.plot.y,bottom:r.bounds.bottom+r.plot.y}]));for(const n of e)if(["content","view"].includes(n.config.anchor)){n.config.anchor==="view"&&(n.contentById=i),n.clearance={...B};for(const r of n.ids??[n.id]){const f=F(t.children[r]),d=(n.contentById??c).get(r);for(const[l,h]of Object.entries({left:f.x-d.left,right:d.right-f.x-f.width,top:f.y-d.top,bottom:d.bottom-f.y-f.height}))n.clearance[l]=Math.max(n.clearance[l],h)}}return c}function R(t,e,o,c){const s=t.filter(i=>i.role===e&&i.text!=="");return s.length===0?0:Math.max(...s.map(i=>O(i,i.config.side,o,c,!0)))}function ut(t,e){const o=t.compositionSpec,c=t.children[e],s=W(o.facet.guides.axes)?et({placements:o.facet.grid?.cells??o.children.map((n,r)=>({id:n,row:Math.floor(r/o.columns),column:r%o.columns}))}):void 0,i=tt(c).flatMap(n=>E(n.channel,n.id).filter((r,f)=>f>=2&&(s===void 0||P(o.facet.guides.axes,n.channel)==="each"||s[n.config?.[["line","ticks","labels","title"][f]]?.position]?.includes(e))));for(const n of["theta","radius","parallel"])c.guideConfigs.axis?.[n]!==void 0&&i.push(...E(n).slice(2));return o.facet.guides.legend===!1&&i.push(...nt(Object.keys(c.guideConfigs.legend??{}))),i.filter(n=>c.graphicSpec.objects[n]!==void 0)}function mt(t,e){const o=t.children[e],c=F(o),s=ut(t,e).map(i=>ot(o.graphicSpec,i,o.materializationConfigs.textMetrics)).filter(Boolean);return{top:Math.max(0,...s.map(i=>c.y-i.top)),bottom:Math.max(0,...s.map(i=>i.bottom-c.y-c.height)),left:Math.max(0,...s.map(i=>c.x-i.left)),right:Math.max(0,...s.map(i=>i.right-c.x-c.width))}}function Rt(t,e){const o=j(e),c=o.mode==="legacy"?rt(t,o):ft(t,o),s=t.compositionSpec.facet.grid===void 0?Math.ceil(t.compositionSpec.children.length/t.compositionSpec.columns):Math.max(...t.compositionSpec.facet.grid.cells.map(a=>a.row))+1,i=t.materializationConfigs.textMetrics,n=["column",...t.compositionSpec.facet.grid===void 0?[]:["row"]].map(a=>[a,y(o,a)]),r=n.some(([,a])=>a.title!==void 0);if(["plot","content"].includes(t.compositionSpec.spacing)){const a=new Map(t.compositionSpec.children.map(m=>[m,mt(t,m)]));for(const m of c)m.clearance=Object.fromEntries(it.map(u=>{const p=Math.max(0,...(m.ids??[m.id]).map(g=>a.get(g)[u]));return[u,p>m.config.offset?p:0]}))}const f=dt(t,c,i),d=r||c.some(a=>x(a.config))?lt(t):B,l={outer:{...B},cellRows:{top:Array(s).fill(0),bottom:Array(s).fill(0)}};if(o.mode==="legacy"&&["plot","content"].includes(t.compositionSpec.spacing))for(const a of c){if(a.text==="")continue;const m=t.compositionSpec.children.indexOf(a.id),u=t.compositionSpec.facet.grid?.cells[m]?.row??Math.floor(m/t.compositionSpec.columns);l.cellRows.top[u]=Math.max(l.cellRows.top[u],O(a,"top",i,d,u===0))}if(o.mode==="roles"){const a=y(o,"column"),m=R(c,"column",i,d);if(t.compositionSpec.facet.grid===void 0){const u=["left","right"].includes(a.side);u&&(l.cellColumns={left:Array(t.compositionSpec.columns).fill(0),right:Array(t.compositionSpec.columns).fill(0)});for(const p of c)if(p.text!==""){const g=u?l.cellColumns:l.cellRows,M=u?p.column:p.row,$=u?t.compositionSpec.columns-1:s-1,q=["top","left"].includes(a.side)?M===0:M===$;g[a.side][M]=Math.max(g[a.side][M],O(p,a.side,i,d,q))}}else{l.outer[a.side]=m;const u=y(o,"row");l.outer[u.side]=R(c,"row",i,d)}}const h=X({roles:n,descriptors:c,space:d,headerLayout:l,profile:i,ids:t.compositionSpec.children});return{headers:o,descriptors:c,titles:h,headerLayout:l,...f===void 0?{}:{contentById:f},...(r||c.some(a=>x(a.config)))&&i!==void 0?{profile:i}:{}}}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 pt(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,s){return s==="start"?t-o:s==="end"?e-c:(t+e-o-c)/2}function k(t,e,o,c){const{config:s}=t,i=H(t,c),n={textAlign:"center",textBaseline:"middle",rotation:s.rotation};return["top","bottom"].includes(s.side)?{...n,x:L(e.left,e.right,i.left,i.right,s.align),y:s.side==="top"?o.top-s.offset-i.bottom:o.bottom+s.offset-i.top}:{...n,x:s.side==="left"?o.left-s.offset-i.right:o.right+s.offset-i.left,y:L(e.top,e.bottom,i.top,i.bottom,s.align)}}function ht(t,e,o,c,s,i){i=t.contentById??i;const n=t.ids.map(p=>e.get(p)),r=t.ids.find((p,g)=>n[g]===void 0),f=t.ids.find(p=>o.get(p)===void 0);if(r!==void 0||f!==void 0)throw new Error(`Facet header requires cell and plot bounds for "${r??f}".`);const d=t.ids.map((p,g)=>w(n[g],o.get(p))),l=C(d),h=["content","view"].includes(t.config.anchor)?C(t.ids.map((p,g)=>I(n[g],i.get(p)))):["plot","content"].includes(c)?l:C(n.map(A)),a=t.config;if(x(a)||t.topology==="role-title"){const p=k(t,l,h,s),g=["left","right"].includes(a.side)?"y":"x";return a.alongOffset!==void 0&&(p[g]+=a.alongOffset),{descriptor:t,snapshots:h,properties:p}}const m=["content","view"].includes(a.anchor)?0:t.clearance?.[a.side]??0;let u;if(["top","bottom"].includes(a.side)){u=D(l,a.align);const p=a.side==="top";u.y=p?h.top-m-a.offset-a.fontSize/2:h.bottom+m+a.offset+a.fontSize/2,u.textBaseline="middle"}else{u=pt(l,a.align);const p=a.side==="left";u.x=p?h.left-m-a.offset:h.right+m+a.offset,u.textAlign=p?"right":"left"}return{descriptor:t,snapshots:h,properties:u}}function gt(t,e,o,c,s,i){if(i=t.contentById??i,e===void 0||o===void 0)throw new Error(`Facet header requires cell and plot bounds for "${t.id}".`);const n=["content","view"].includes(t.config.anchor)?I(e,i.get(t.id)):w(e,o);if(x(t.config)&&["plot","content"].includes(c)){const r=w(e,o);return{descriptor:t,snapshots:A(e),properties:k({...t,config:{...t.config,side:"top"}},r,n,s)}}return{descriptor:t,snapshots:A(e),properties:{...D(w(e,o),t.config.align),y:["plot","content"].includes(c)?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 xt(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 wt(t,e,o,c,s,i){const n=e.map((r,f)=>t[f].descriptor.text===""?void 0:z(r.properties,c));for(const[r,f]of n.entries())if(f!==void 0&&(f.left<-1e-9||f.right>o.width+1e-9||f.top<-1e-9||f.bottom>o.height+1e-9||n.some((d,l)=>l<r&&d!==void 0&&S(d,f))||o.children.some(d=>S(f,i===void 0?["plot","content"].includes(o.spacing)?w(d,s.get(d.id)):A(d):I(d,i.get(d.id))))))throw new Error("Facet role headers require sufficient non-overlapping reserved space.")}function yt(t,e,o,c,s){const i=[];for(let n=0;n<t.length;n+=1){if(t[n].properties.text==="")continue;const r=z(t[n].properties,c),f=e.children[n],d=o.get(f.id);if(r.left<0||r.right>e.width||r.top<0||r.bottom>e.height||i.some(l=>S(l,r))||(["plot","content"].includes(e.spacing)?e.children.some(l=>S(r,s===void 0?w(l,o.get(l.id)):I(l,s.get(l.id)))):S(r,w(f,d))))throw new Error("Facet headers require sufficient non-overlapping space above every child plot.");i.push(r)}}function bt(t,e,o){const c=new Map(t.children.map(n=>[n.id,n])),s=new Map(e.map(n=>[n.id,n])),i=[...o.descriptors,...o.titles??[]].map(n=>n.topology==="legacy"?gt(n,c.get(n.id),s.get(n.id),t.spacing,o.profile,o.contentById):ht(n,c,s,t.spacing,o.profile,o.contentById));return{records:i,items:i.map(xt),plotById:s}}function Dt(t,e,o,c){const s=t.materializationConfigs.textMetrics,{records:i,items:n,plotById:r}=bt(e,o,c);c.headers.mode==="legacy"?yt(n,e,r,s,c.contentById):wt(i,n,e,s,r,c.contentById);const f=ct(t.compositionSpec.id);return t.createGraphics({id:f,type:"collection",parent:"canvas"}).editGraphics({target:f,property:"items",value:n})}export{T as createDefaultFacetHeaders,ct as facetHeaderGraphicId,Dt as materializeFacetHeaders,j as normalizeFacetHeadersConfig,Rt as prepareFacetHeaders,y as resolveFacetHeaderConfig,bt as resolveFacetHeaderItems,ut 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{
|
|
1
|
+
import{walkGraphicTreeEvents as P}from"../grammar/schemas/graphicTree.js";import{measureFacetContentChildren as E}from"./facetContentBounds.js";import{measureFacetContentChildren as It}from"./facetContentBounds.js";import{resolveFacetContentFlow as O,resolveFacetContentFooters as H}from"../layout/facetContentFlow.js";import{resolveConcreteGraphicBounds as G}from"../grammar/schemas/graphicBounds.js";import{textBoundsFitCanvas as $,textBoundsIntersect as S,resolveTextBounds as I}from"../core/textMetrics.js";import{resolveGraphicBounds as C}from"../layout/canvas.js";import{resolvePlacedPlotBounds as R}from"../layout/composition.js";import{alignedTextAnchor as A,alignedTitleAnchor as _,buildTitleReadingBlock as q,paintedTitleComponent as D,resolveTitleComponentBounds as W,titleTextProperties as N}from"../layout/title.js";import{resolveFacetLayout as k}from"../layout/facets.js";import{assertFacetTopLane as Z}from"./facetTopLane.js";import{namespaceGraphicId as j,namespaceGraphicSnapshot as J}from"./compositionSnapshot.js";import{prepareSharedFacetLegend as T}from"./facetGuides/index.js";import{resolvePromotedFacetLegendLayout as K}from"./facetGuides/layout.js";import{attachSnapshotObject as Q,clearCompositionChildren as U,compositionChildDescriptor as V}from"./composition.js";import{materializeFacetHeaders as X,resolveFacetHeaderItems as Y,prepareFacetHeaders as tt,facetHeaderGraphicId as F,retainedFacetGuideIds as et}from"./facetHeaders.js";const ot={top:0,right:0,bottom:0,left:0},L=Object.freeze(["chartTitle","chartSubtitle"]);function it(t){const c=t.materializationConfigs.facets?.[t.compositionSpec.id];if(c===void 0)throw new Error(`Facet "${t.compositionSpec.id}" requires materialization config.`);return c}function nt(t){if(t.semanticSpec.title.text===void 0)return{height:0};const c=t.titleConfig;if(c===void 0)throw new Error("Facet title requires chart title configuration.");const e=q({text:t.semanticSpec.title.text,subtitle:t.semanticSpec.title.subtitle},c,t.materializationConfigs.textMetrics),i=8+c.offset;if(i<0)throw new Error("Facet parent title offset places text outside the Canvas.");const h=Math.ceil(i+e.height);return{config:c,block:e,top:i,reservation:h,height:c.position==="top"?h:0}}function z(t,c,e,i,h,f,n){if(e.length===0)return t;const{config:s,block:o,top:a}=n,d=t.graphicSpec.objects.canvas.properties,p=["top","bottom"].includes(s.position),u=D(p?{x:A(f.x,f.width,s.align)+(s.alongOffset??0),y:i.map(m=>(s.position==="top"?a:d.height-a-o.height)+m),lines:e,textAlign:s.align,...s.position==="bottom"?{rotation:0,explicitRotation:!0}:{}}:{x:i.map(m=>s.position==="left"?a+m:d.width-a-m),y:_(f.y,f.height,o.width,s.align)+(s.alongOffset??0),lines:e,textAlign:"center",rotation:s.position==="left"?-Math.PI/2:Math.PI/2,explicitRotation:!0}),w=W(u,h,t.materializationConfigs.textMetrics);if(!$(w,t.graphicSpec.objects.canvas.properties))throw new Error("Facet parent title places text outside the Canvas.");const v=u.lines.length;let l=t.createGraphics({id:c,type:"text",...v>1?{length:v}:{},parent:"canvas"});for(const[m,x]of Object.entries({...N(u,h),...u.explicitRotation?{rotation:u.rotation}:{}}))l=l.editGraphics({target:c,property:m,value:x});return l}function ct(t,c,e){let i=z(t,L[0],e.block.titleLines,e.block.titleCenters,e.config.titleStyle,c,e);return e.block.subtitleLines.length>0&&(i=z(i,L[1],e.block.subtitleLines,e.block.subtitleCenters,e.config.subtitleStyle,c,e)),i}function st(t,c,e){const i=t.compositionSpec,h=c.plots,f={spacing:"content",children:c.children.map((r,g)=>({...r,x:0,y:0,row:Math.floor(g/i.columns),column:g%i.columns}))},{records:n,items:s}=Y(f,h,e),o=n.flatMap((r,g)=>{const b=r.descriptor;return b.topology==="role-title"||b.ids?.length>1?[]:[{...s[g],ownerId:b.id??b.ids[0]}]}),a=E(t,{headerItems:o}),d=O({children:a,columns:i.columns,gap:i.gap}),p=new Map(a.map(r=>[r.id,r])),u=new Map(d.children.map(r=>[r.column,r])),w=H({flow:d,footers:[...u.values()].filter(r=>p.get(r.id).footerBounds!==void 0).map(r=>({id:r.id,column:r.column}))}),v=new Map(w.map(r=>[r.ownerId,r])),l={...d.bounds};for(const r of w){const g=p.get(r.ownerId),b=g.footerBounds,y=r.y-g.plot.height;l.left=Math.min(l.left,r.x+b.left),l.right=Math.max(l.right,r.x+b.right),l.top=Math.min(l.top,y+b.top),l.bottom=Math.max(l.bottom,y+b.bottom)}const m=k({...c,spacing:"canvas",topLane:void 0,children:[{id:"packed",width:l.right-l.left,height:l.bottom-l.top}],columns:1,gap:0,headerLayout:{outer:e.headerLayout.outer},plots:void 0}),x=m.children[0].x-l.left,B=m.children[0].y-l.top;return{...m,spacing:"content",columns:d.columns,rows:Math.ceil(d.children.length/d.columns),gap:i.gap,children:d.children.map(r=>{const g=p.get(r.id),y=t.children[r.id].graphicSpec.objects.canvas.properties,M=v.get(r.id);return{id:r.id,row:r.row,column:r.column,x:x+r.x-g.plot.x,y:B+r.y-g.plot.y,width:y.width,height:y.height,...M===void 0?{}:{footerShift:M.y-r.y-g.plot.height}}})}}function rt(t,c){if(t._assertCompositionProgram("resolveFacetProgramLayout"),t.compositionSpec.type!=="facet")throw new Error("resolveFacetProgramLayout requires a facet composition.");c??=T(t);const e=nt(t),i=t.compositionSpec,h=tt(t,it(t).headers),f=new Map((i.facet.grid?.cells??[]).map(p=>[p.id,p])),n={...i.padding};e.config&&e.config.position!=="top"&&(n[e.config.position]+=e.reservation);const s={children:i.children.map((p,u)=>({...V(p,t.children[p]),value:i.facet.values[u],...f.has(p)?{row:f.get(p).row,column:f.get(p).column}:{}})),spacing:i.spacing,topLane:i.topLane,plots:i.children.map(p=>({id:p,...C(t.children[p])})),columns:i.columns,gap:i.gap,align:i.align,padding:n,titleHeight:e.height,headerLayout:h.headerLayout,sharedLegend:i.facet.guides.legend==="shared"&&c?.mode!=="promoted",...c===void 0?{}:{sharedLegendGap:c.reservation.gap,sharedLegendWidth:c.reservation.width,sharedLegendHeight:c.reservation.height,sharedLegendPosition:c.reservation.position}};let o=i.spacing==="content"?st(t,s,h):k(s);const a=i.children.map(p=>({id:p,...C(t.children[p])}));let d=R({placements:o.children,plots:a});return{layout:o,plot:d}=K(t,{layout:o,plot:d,plots:a,title:e,preparedHeaders:h,prepared:c}),{layout:o,title:e,plot:d,plots:a,preparedHeaders:h}}function at(t,c,e){const i=[],h=c.children.map((n,s)=>({id:n.id,left:n.x+e[s].x,right:n.x+e[s].x+e[s].width,top:n.y+e[s].y,bottom:n.y+e[s].y+e[s].height}));for(const n of c.children){const s=et(t,n.id);for(const o of s){const a=j(`${t.compositionSpec.id}-${n.id}`,o);if(t.graphicSpec.objects[a]===void 0)continue;const d=G(t.graphicSpec,a,t.materializationConfigs.textMetrics);d!==void 0&&i.push({owner:n.id,bounds:d})}}const f=t.graphicSpec.objects[F(t.compositionSpec.id)];for(const n of f?.items??[])n.properties.text!==""&&i.push({owner:"headers",bounds:I(n.properties,t.materializationConfigs.textMetrics)});for(let n=0;n<i.length;n+=1){const s=i[n];if(h.some(o=>o.id!==s.owner&&S(s.bounds,o))||i.slice(0,n).some(o=>o.owner!==s.owner&&S(s.bounds,o.bounds)))throw new Error("Plot spacing gap is too small for the retained facet guides or headers.")}}function dt(t,c){if(c.titles.length===0)return;const e=F(t.compositionSpec.id),i=t.materializationConfigs.textMetrics,h=t.graphicSpec.objects[e].items.slice(c.descriptors.length).map(n=>I(n.properties,i)),f=n=>{if((n.ownerId??n.id)===e)return;const s=n.object.type??n.owner?.type,o=n.object.properties;if(s!=="text"||o?.text===""||o?.opacity===0)return;const a=G(t.graphicSpec,n.id,i);if(a!==void 0&&h.some(d=>S(d,a)))throw new Error("Facet field title overlaps retained chart text.")};P(t.graphicSpec,{enter:f,item:f})}function Lt(t){const c=T(t),{layout:e,title:i,plot:h,plots:f,preparedHeaders:n}=rt(t,c),s=t.graphicSpec.objects.canvas?.properties?.background??"white";let o=U(t);o.graphicSpec.objects.canvas===void 0&&(o=o.createGraphics({id:"canvas",type:"canvas"}));for(const[a,d]of Object.entries({width:e.width,height:e.height,background:s}))o=o.editGraphics({target:"canvas",property:a,value:d});for(const a of e.children){const d=t.children[a.id],p=J(d.graphicSpec,{namespace:`${t.compositionSpec.id}-${a.id}`,x:a.x,y:a.y});if(o=Q(o,p,p.order[0],"canvas"),["plot","content"].includes(e.spacing)){const u=p.order[0],w=j(`${t.compositionSpec.id}-${a.id}`,"plot-background"),v=C(d),l=o.graphicSpec.objects[u].children?.[0];o=o.editGraphics({target:u,property:"background",value:"transparent"}).createGraphics({id:w,type:"rect",parent:u,...l===void 0?{}:{before:l}});for(const[m,x]of Object.entries({...v,stroke:"none",strokeWidth:0,fill:d.graphicSpec.objects.canvas.properties.background??"transparent"}))o=o.editGraphics({target:w,property:m,value:x})}}return o=X(o,e,f,n),o=o.composeFacetGuides({layout:e,plot:h}),e.spacing==="plot"&&at(o,e,f),i.config&&(o=ct(o,h,i)),dt(o,n),Z(o,{layout:e,plots:f,headerId:F(t.compositionSpec.id),titleIds:L}),o._withCanvasConfig({margin:ot,size:{width:"auto",height:"auto"}})}export{Lt as materializeFacetGraphics,It as measureFacetContentChildren,rt as resolveFacetProgramLayout};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateRegressionPlacement } from "../grammar/regression/parameters.js";
|
|
1
2
|
import { cartesianAxisOwners } from "../selectors/axes.js";
|
|
2
3
|
import { axisGraphicIds } from "./guides/resources.js";
|
|
3
4
|
import {
|
|
@@ -5,6 +6,7 @@ import {
|
|
|
5
6
|
graphicSiblings
|
|
6
7
|
} from "../grammar/schemas/graphicTree.js";
|
|
7
8
|
import { findDataset } from "../selectors/datasets.js";
|
|
9
|
+
import { hasPlotUnderlays, firstOverlayGuide } from "./plotDrawOrder.js";
|
|
8
10
|
|
|
9
11
|
export const CANVAS_GRAPHIC_ID = "canvas";
|
|
10
12
|
export const PLOT_GRAPHIC_ID = "plot-main";
|
|
@@ -57,6 +59,10 @@ export function resolveMarkGraphicPlacement(program, { data, markType }) {
|
|
|
57
59
|
? dataset.transform[0]
|
|
58
60
|
: undefined;
|
|
59
61
|
if (transform?.type !== "regression") {
|
|
62
|
+
if (hasPlotUnderlays(program)) {
|
|
63
|
+
const before = firstOverlayGuide(program);
|
|
64
|
+
return resolvePlotGraphicPlacement(program, before === undefined ? {} : { before });
|
|
65
|
+
}
|
|
60
66
|
const named = new Set(cartesianAxisOwners(program).filter(owner => owner.named)
|
|
61
67
|
.flatMap(owner => axisGraphicIds(owner.channel, owner.id)));
|
|
62
68
|
const axis = program.graphicSpec.objects[PLOT_GRAPHIC_ID]?.children?.find(
|
|
@@ -74,7 +80,9 @@ export function resolveMarkGraphicPlacement(program, { data, markType }) {
|
|
|
74
80
|
const namespaced = eligible.filter(layer =>
|
|
75
81
|
`${layer.id}RegressionData` === dataset.id
|
|
76
82
|
);
|
|
77
|
-
const
|
|
83
|
+
const declared = program.semanticSpec.layers.filter(layer =>
|
|
84
|
+
program.markConfigs[layer.id]?.regression?.dataId === dataset.id);
|
|
85
|
+
const sources = declared.length === 1 ? declared : namespaced.length === 1 ? namespaced : eligible;
|
|
78
86
|
if (sources.length !== 1) {
|
|
79
87
|
throw new Error(
|
|
80
88
|
"Regression graphic placement requires one source mark layer."
|
|
@@ -116,3 +124,24 @@ export function preserveGraphicPlacement(program, id) {
|
|
|
116
124
|
...(next === undefined ? {} : { before: next })
|
|
117
125
|
};
|
|
118
126
|
}
|
|
127
|
+
|
|
128
|
+
export function reconcileRegressionPlacement(program, ownerId) {
|
|
129
|
+
const config = program.markConfigs[ownerId].regression;
|
|
130
|
+
const placement = validateRegressionPlacement(config.placement);
|
|
131
|
+
const children = program.graphicSpec.objects[PLOT_GRAPHIC_ID]?.children;
|
|
132
|
+
if (children === undefined) throw new Error("Regression placement requires the plot container.");
|
|
133
|
+
const owned = [config.bandId, config.lineId].filter(id => id !== undefined && children.includes(id));
|
|
134
|
+
const order = children.filter(id => !owned.includes(id));
|
|
135
|
+
if (placement === "plot-last") {
|
|
136
|
+
const overlay = firstOverlayGuide(program);
|
|
137
|
+
const index = overlay === undefined ? order.length : order.indexOf(overlay);
|
|
138
|
+
order.splice(index < 0 ? order.length : index, 0, ...owned);
|
|
139
|
+
} else {
|
|
140
|
+
const index = order.indexOf(ownerId);
|
|
141
|
+
if (index < 0) throw new Error("Regression placement requires its source mark in the plot.");
|
|
142
|
+
order.splice(index, 1, ...(owned.includes(config.bandId) ? [config.bandId] : []),
|
|
143
|
+
ownerId, ...(owned.includes(config.lineId) ? [config.lineId] : []));
|
|
144
|
+
}
|
|
145
|
+
if (order.every((id, i) => id === children[i])) return program;
|
|
146
|
+
return program.editGraphics({ target: PLOT_GRAPHIC_ID, property: "children", value: order });
|
|
147
|
+
}
|
|
@@ -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({
|