ggaction 0.0.23 → 0.0.26
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 +65 -0
- package/README.md +2 -2
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +1 -1
- package/src/accessibility.js +13 -0
- package/src/actions/canvas/actions.js +2 -1
- package/src/actions/canvas/fitting.js +68 -13
- package/src/actions/charts/beeswarm.js +2 -1
- package/src/actions/charts/ecdf.js +2 -1
- package/src/actions/charts/endpoints.js +25 -16
- package/src/actions/charts/heatmap.js +6 -4
- package/src/actions/charts/missing.js +17 -0
- package/src/actions/charts/radar.js +70 -33
- package/src/actions/charts/raincloud.js +18 -4
- package/src/actions/charts/rug-strip.js +2 -2
- package/src/actions/charts/shared.js +17 -0
- package/src/actions/composition/actions.js +24 -7
- package/src/actions/composition/adjacent.js +90 -0
- package/src/actions/composition/index.js +11 -1
- package/src/actions/composition/layer.js +42 -0
- package/src/actions/composition/layerPlan.js +176 -0
- package/src/actions/composition/targets.js +105 -0
- package/src/actions/data/basicBin2d.js +1 -1
- package/src/actions/data/bin2d.js +2 -2
- package/src/actions/data/derived.js +3 -0
- package/src/actions/data/filter.js +1 -1
- package/src/actions/data/raincloudFilter.js +3 -1
- package/src/actions/encodings/appearance.js +26 -7
- package/src/actions/encodings/channels.js +1 -0
- package/src/actions/encodings/color/continuous.js +37 -34
- package/src/actions/encodings/color/index.js +57 -43
- package/src/actions/encodings/color/policy.js +15 -0
- package/src/actions/encodings/density/resolve.js +3 -2
- package/src/actions/encodings/density.js +2 -2
- package/src/actions/encodings/offset.js +2 -0
- package/src/actions/encodings/pathOrder.js +2 -0
- package/src/actions/encodings/position/areaAlignment.js +42 -0
- package/src/actions/encodings/position/index.js +37 -3
- package/src/actions/encodings/position/policies/line.js +1 -1
- package/src/actions/encodings/ranged.js +4 -2
- package/src/actions/encodings/remove.js +13 -10
- package/src/actions/encodings/ruleAppearance.js +14 -2
- package/src/actions/facets/actions.js +1 -1
- package/src/actions/guides/axes/axes.js +2 -1
- package/src/actions/guides/axes/components.js +5 -0
- package/src/actions/guides/axes/edit.js +1 -188
- package/src/actions/guides/axes/labels.js +2 -537
- package/src/actions/guides/axes/parallel/lifecycle.js +11 -4
- package/src/actions/guides/axes/parallel/policy.js +5 -2
- package/src/actions/guides/axes/parallel/resolve.js +3 -1
- package/src/actions/guides/axes/parallel.js +9 -4
- package/src/actions/guides/axes/tickGroups.js +16 -1
- package/src/actions/guides/axes/ticks.js +33 -24
- package/src/actions/guides/axes/titles.js +1 -389
- package/src/actions/guides/facadeLegend.js +3 -2
- package/src/actions/guides/grids/grid.js +3 -2
- package/src/actions/guides/grids/resolve.js +2 -1
- package/src/actions/guides/guides.js +3 -2
- package/src/actions/guides/legends/automatic.js +28 -0
- package/src/actions/guides/legends/blocks.js +15 -21
- package/src/actions/guides/legends/categorical/actions.js +1 -1
- package/src/actions/guides/legends/categorical/components.js +4 -3
- package/src/actions/guides/legends/categorical/layout.js +5 -2
- package/src/actions/guides/legends/categorical/options.js +9 -14
- package/src/actions/guides/legends/categorical/recipes.js +3 -0
- package/src/actions/guides/legends/categorical/resolve.js +7 -5
- package/src/actions/guides/legends/colorSize.js +51 -14
- package/src/actions/guides/legends/continuous/common.js +1 -1
- package/src/actions/guides/legends/continuous/gradient.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +2 -0
- package/src/actions/guides/legends/edit.js +1 -1
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/group.js +1 -1
- package/src/actions/guides/legends/lane.js +1 -1
- package/src/actions/guides/legends/lifecycle.js +2 -2
- package/src/actions/guides/legends/remove.js +18 -4
- package/src/actions/guides/legends/size.js +1 -1
- package/src/actions/guides/legends/transition.js +15 -6
- package/src/actions/guides/polar/axes/facade.js +4 -2
- package/src/actions/guides/polar/axes/labels.js +6 -1
- package/src/actions/guides/polar/axes/shared.js +4 -4
- package/src/actions/guides/polar/axes/titles.js +13 -2
- package/src/actions/guides/polar/grids.js +6 -3
- package/src/actions/guides/tickValues.js +7 -1
- package/src/actions/index.js +2 -0
- package/src/actions/marks/area/actions.js +9 -2
- package/src/actions/marks/area/materialize.js +1 -1
- package/src/actions/marks/bar/materialize.js +4 -4
- package/src/actions/marks/lifecycle.js +14 -0
- package/src/actions/marks/line/actions.js +3 -0
- package/src/actions/marks/line/materialize.js +4 -2
- package/src/actions/marks/point/edit.js +2 -0
- package/src/actions/marks/point/jitter.js +2 -0
- package/src/actions/marks/point/materialize.js +5 -4
- package/src/actions/marks/point/packing.js +2 -0
- package/src/actions/marks/references.js +5 -3
- package/src/actions/marks/remove.js +16 -0
- package/src/actions/marks/shared.js +2 -1
- package/src/actions/marks/text/actions.js +5 -2
- package/src/actions/overlays/index.js +168 -0
- package/src/actions/overlays/snapshot.js +45 -0
- package/src/actions/primitives/editGraphics.js +21 -27
- package/src/actions/primitives/semanticAction.js +4 -5
- package/src/actions/primitives/semanticValidation/guide.js +1 -0
- package/src/actions/primitives/semanticValidation/layer.js +7 -0
- package/src/actions/scales/consumers/common.js +10 -0
- package/src/actions/scales/consumers/families.js +1 -1
- package/src/actions/scales/preview.js +1 -1
- package/src/actions/selection/actions.js +63 -11
- package/src/actions/titles/actions.js +3 -14
- package/src/actions/titles/resolve.js +23 -10
- package/src/core/action.js +41 -25
- package/src/core/compositionScales.js +12 -0
- package/src/core/compositionState.js +20 -3
- package/src/core/facetGap.js +44 -0
- package/src/core/immutable.js +11 -6
- package/src/core/programState.js +16 -0
- package/src/core/resourceReferences.js +19 -3
- package/src/core/validation.js +7 -0
- package/src/core/vocabulary.js +8 -2
- package/src/grammar/actionTargets.js +24 -0
- package/src/grammar/axisTextPlacement.js +1 -1
- package/src/grammar/bin2d.js +41 -4
- package/src/grammar/colorSizeLegend.js +56 -5
- package/src/grammar/displayLabels.js +10 -0
- package/src/grammar/facetHeaders.js +1 -1
- package/src/grammar/facets/dependencies.js +10 -3
- package/src/grammar/facets/guides.js +2 -1
- package/src/grammar/facets/legendIntent.js +7 -5
- package/src/grammar/legendBlocks.js +1 -1
- package/src/grammar/legendCombinations.js +1 -1
- package/src/grammar/legendGroups.js +1 -1
- package/src/grammar/legendItemsOffset.js +12 -0
- package/src/grammar/legendOverflow.js +25 -0
- package/src/grammar/lineSeries.js +1 -1
- package/src/grammar/markSelection.js +23 -0
- package/src/grammar/overlay.js +51 -0
- package/src/grammar/parallelCoordinates.js +1 -208
- package/src/grammar/pathCommands.js +8 -2
- package/src/grammar/pathSeries.js +1 -1
- package/src/grammar/plotComposition.js +152 -0
- package/src/grammar/pointShapes.js +18 -0
- package/src/grammar/scales/transformed.js +1 -1
- package/src/grammar/schemas/concreteGraphic.js +8 -1
- package/src/grammar/schemas/graphicBounds.js +8 -1
- package/src/grammar/schemas/graphicTree.js +18 -2
- package/src/grammar/schemas/semanticPath.js +3 -0
- package/src/grammar/strokeStyle.js +14 -8
- package/src/grammar/text.js +72 -0
- package/src/inspection.js +1 -1
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/composition.js +32 -10
- package/src/layout/compositionLegend.js +91 -0
- package/src/layout/facetContentFlow.js +15 -7
- package/src/layout/facetHeaderTitles.js +1 -1
- package/src/layout/facets.js +1 -1
- package/src/layout/labels.js +55 -27
- package/src/layout/legend.js +3 -2
- package/src/layout/legendGroup.js +1 -1
- package/src/layout/legendItems.js +7 -6
- package/src/layout/legendLane.js +1 -1
- package/src/layout/legendText.js +1 -1
- package/src/layout/title.js +1 -1
- package/src/materialization/adjacentSource.js +27 -0
- package/src/materialization/composition.js +165 -52
- package/src/materialization/compositionLegends.js +120 -0
- package/src/materialization/compositionScales.js +125 -0
- package/src/materialization/compositionTitle.js +96 -0
- package/src/materialization/facetGuides/edit.js +22 -2
- package/src/materialization/facetGuides/layout.js +1 -1
- package/src/materialization/facetGuides/owner.js +4 -3
- package/src/materialization/facetGuides/ownerLayout.js +12 -6
- package/src/materialization/facetGuides/placement.js +19 -22
- package/src/materialization/facetGuides/preparation.js +17 -5
- package/src/materialization/facetHeaders.js +5 -1
- package/src/materialization/facets.js +1 -1
- package/src/materialization/guides/layout.js +1 -1
- package/src/materialization/guides/resources.js +1 -1
- package/src/materialization/guides/visibility.js +105 -0
- package/src/materialization/legendClip.js +1 -1
- package/src/materialization/legendGroups.js +1 -1
- package/src/materialization/legendItemsOffset.js +68 -0
- package/src/materialization/legendSuppression.js +22 -0
- package/src/materialization/legends.js +3 -2
- package/src/materialization/marks/capabilities.js +4 -0
- package/src/materialization/marks/index.js +5 -1
- package/src/materialization/marks/pathOrder.js +1 -1
- package/src/materialization/marks/policies.js +2 -1
- package/src/materialization/overlayItems.js +154 -0
- package/src/materialization/planner.js +19 -4
- package/src/materialization/scales/policies/binnedPosition.js +43 -5
- package/src/materialization/scales/policies/series.js +12 -4
- package/src/materialization/scales/resolve.js +2 -0
- package/src/materialization/selection/items/bar.js +21 -3
- package/src/materialization/selection/items/common.js +18 -5
- package/src/materialization/selection/items/overlay.js +15 -0
- package/src/materialization/selection/items/path.js +1 -0
- package/src/materialization/selection/items/point.js +2 -3
- package/src/materialization/selection/items/tick.js +3 -12
- package/src/materialization/selection/path.js +2 -2
- package/src/materialization/selection/policies/bar.js +2 -0
- package/src/materialization/selection/policies/index.js +35 -1
- package/src/materialization/selection/policies/point.js +2 -0
- package/src/materialization/selection/policies/text.js +17 -0
- package/src/materialization/selection/rows.js +103 -0
- package/src/materialization/selection/state.js +19 -3
- package/src/materialization/selection/styles.js +8 -0
- package/src/materialization/text.js +6 -489
- package/src/materialization/textGeometry.js +503 -0
- package/src/materialization/textLines.js +20 -0
- package/src/persistence/validation.js +116 -25
- package/src/renderers/canvas/index.js +12 -7
- package/src/renderers/canvas/text.js +5 -5
- package/src/renderers/nativeBackend.js +25 -0
- package/src/renderers/pdf.js +3 -3
- package/src/renderers/png.js +4 -3
- package/src/renderers/svgSerialization.js +1 -643
- package/src/scene.js +5 -3
- package/src/selectors/plotComposition.js +115 -0
- package/src/selectors/selectionTargets.js +19 -0
- package/src/version.js +1 -1
- package/types/basic.d.ts +4 -2
- package/types/index.d.ts +14 -0
- package/types/inspection.d.ts +34 -0
- package/types/program.d.ts +572 -372
|
@@ -11,6 +11,11 @@ export const STROKE_STYLE_PROPERTIES = Object.freeze([
|
|
|
11
11
|
"lineJoin",
|
|
12
12
|
"miterLimit"
|
|
13
13
|
]);
|
|
14
|
+
// Bounds and collision checks resolve this small vocabulary for every mark.
|
|
15
|
+
// Share the nine common immutable results rather than allocating per item;
|
|
16
|
+
// nondefault miter limits remain uncached so arbitrary user values stay bounded.
|
|
17
|
+
const commonStrokeDetails = new Map(LINE_CAPS.flatMap(lineCap => LINE_JOINS.map(lineJoin =>
|
|
18
|
+
[`${lineCap}:${lineJoin}`, freezeOwned({ lineCap, lineJoin, miterLimit: DEFAULT_MITER_LIMIT })])));
|
|
14
19
|
|
|
15
20
|
function validateEnum(value, values, property, label) {
|
|
16
21
|
if (typeof value !== "string") {
|
|
@@ -46,17 +51,18 @@ export function resolveStrokeDetails(request = {}) {
|
|
|
46
51
|
if (!isPlainObject(request)) {
|
|
47
52
|
throw new TypeError("Stroke style details must be a plain object.");
|
|
48
53
|
}
|
|
49
|
-
|
|
50
|
-
lineCap: request.lineCap === undefined
|
|
54
|
+
const lineCap = request.lineCap === undefined
|
|
51
55
|
? DEFAULT_LINE_CAP
|
|
52
|
-
: validateLineCap(request.lineCap)
|
|
53
|
-
|
|
56
|
+
: validateLineCap(request.lineCap);
|
|
57
|
+
const lineJoin = request.lineJoin === undefined
|
|
54
58
|
? DEFAULT_LINE_JOIN
|
|
55
|
-
: validateLineJoin(request.lineJoin)
|
|
56
|
-
|
|
59
|
+
: validateLineJoin(request.lineJoin);
|
|
60
|
+
const miterLimit = request.miterLimit === undefined
|
|
57
61
|
? DEFAULT_MITER_LIMIT
|
|
58
|
-
: validateMiterLimit(request.miterLimit)
|
|
59
|
-
|
|
62
|
+
: validateMiterLimit(request.miterLimit);
|
|
63
|
+
return miterLimit === DEFAULT_MITER_LIMIT
|
|
64
|
+
? commonStrokeDetails.get(`${lineCap}:${lineJoin}`)
|
|
65
|
+
: freezeOwned({ lineCap, lineJoin, miterLimit });
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
export function requestedStrokeDetails(request = {}, label = "Stroke style") {
|
package/src/grammar/text.js
CHANGED
|
@@ -118,6 +118,8 @@ export function formatTextValue(value, format = "auto") {
|
|
|
118
118
|
|
|
119
119
|
export function normalizeTextMarkConfig(options, base = DEFAULT_TEXT_MARK) {
|
|
120
120
|
const config = { ...base };
|
|
121
|
+
if (options.textOverrides === "auto") delete config.textOverrides;
|
|
122
|
+
else if (Object.hasOwn(options, "textOverrides")) config.textOverrides = normalizeTextOverrides(options.textOverrides);
|
|
121
123
|
if (Object.hasOwn(options, "lineHeight")) {
|
|
122
124
|
if (!Number.isFinite(options.lineHeight) || options.lineHeight <= 0) throw new RangeError("Text lineHeight must be positive and finite.");
|
|
123
125
|
config.lineHeight = options.lineHeight;
|
|
@@ -158,3 +160,73 @@ export function normalizeTextMarkConfig(options, base = DEFAULT_TEXT_MARK) {
|
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
export { resolveTextLines } from "../core/textMetrics.js";
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
export const isTextIdentityValue = value => value === null || typeof value === "string" ||
|
|
166
|
+
typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
|
|
167
|
+
export const textIdentity = (fields = {}, channels = {}) => Object.fromEntries(
|
|
168
|
+
Object.entries({ fields, channels }).map(([part, values]) => [part,
|
|
169
|
+
Object.fromEntries(Object.entries(values).filter(([, value]) => isTextIdentityValue(value)))]));
|
|
170
|
+
export const textIdentityKey = match => JSON.stringify(["fields", "channels"].map(part =>
|
|
171
|
+
Object.entries(match[part] ?? {}).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)));
|
|
172
|
+
|
|
173
|
+
export function normalizeTextOverrides(value) {
|
|
174
|
+
if (!Array.isArray(value)) throw new TypeError("textOverrides must be an array or auto.");
|
|
175
|
+
const seen = new Set();
|
|
176
|
+
for (const entry of value) {
|
|
177
|
+
if (!isPlainObject(entry) || Object.keys(entry).length !== 3 ||
|
|
178
|
+
!isPlainObject(entry.match) || typeof entry.text !== "string" ||
|
|
179
|
+
!Number.isInteger(entry.occurrence) || entry.occurrence < 0) {
|
|
180
|
+
throw new TypeError("Each text override requires match, occurrence and text.");
|
|
181
|
+
}
|
|
182
|
+
if (!Object.keys(entry.match).length || Object.keys(entry.match).some(part =>
|
|
183
|
+
!["fields", "channels"].includes(part) || !isPlainObject(entry.match[part]) ||
|
|
184
|
+
!Object.keys(entry.match[part]).length || !Object.values(entry.match[part]).every(isTextIdentityValue))) {
|
|
185
|
+
throw new TypeError("Text match requires nonempty fields or channels containing scalar values.");
|
|
186
|
+
}
|
|
187
|
+
const key = `${textIdentityKey(entry.match)}/${entry.occurrence}`;
|
|
188
|
+
if (seen.has(key)) throw new Error("Duplicate text override selector.");
|
|
189
|
+
seen.add(key);
|
|
190
|
+
}
|
|
191
|
+
return cloneAndFreeze(value);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Count semantic matches before visibility/placement filtering. Never address a graphic index.
|
|
195
|
+
export function resolveTextOverrides(items, overrides) {
|
|
196
|
+
const result = new Map();
|
|
197
|
+
if (!overrides?.length) return result;
|
|
198
|
+
const groups = new Map(), priorities = new Map();
|
|
199
|
+
for (const [priority, { match, occurrence, text }] of overrides.entries()) {
|
|
200
|
+
const keys = ["fields", "channels"].flatMap(part => Object.keys(match[part] ?? {}).sort().map(key => [part, key]));
|
|
201
|
+
const shape = JSON.stringify(keys);
|
|
202
|
+
if (!groups.has(shape)) groups.set(shape, { keys, values: new Map() });
|
|
203
|
+
const values = groups.get(shape).values, key = JSON.stringify(keys.map(([part, field]) => match[part][field]));
|
|
204
|
+
if (!values.has(key)) values.set(key, new Map());
|
|
205
|
+
values.get(key).set(occurrence, { text, priority });
|
|
206
|
+
}
|
|
207
|
+
// Complete identities from the editor share one shape: O(items + overrides),
|
|
208
|
+
// rather than scanning all items once for every previously edited label.
|
|
209
|
+
for (const { keys, values } of groups.values()) {
|
|
210
|
+
const counts = new Map();
|
|
211
|
+
for (const item of items) {
|
|
212
|
+
if (!keys.every(([part, field]) => Object.hasOwn(item[part] ?? {}, field) && isTextIdentityValue(item[part][field]))) continue;
|
|
213
|
+
const key = JSON.stringify(keys.map(([part, field]) => item[part][field]));
|
|
214
|
+
const candidates = values.get(key); if (!candidates) continue;
|
|
215
|
+
const occurrence = counts.get(key) ?? 0; counts.set(key, occurrence + 1);
|
|
216
|
+
const chosen = candidates.get(occurrence);
|
|
217
|
+
if (chosen && chosen.priority > (priorities.get(item) ?? -1)) {
|
|
218
|
+
result.set(item, chosen.text); priorities.set(item, chosen.priority);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function textIdentityOccurrences(items) {
|
|
226
|
+
const counts = new Map();
|
|
227
|
+
return items.map(item => {
|
|
228
|
+
const identity = textIdentity(item.fields, item.channels), key = textIdentityKey(identity);
|
|
229
|
+
const occurrence = counts.get(key) ?? 0; counts.set(key, occurrence + 1);
|
|
230
|
+
return { identity, occurrence };
|
|
231
|
+
});
|
|
232
|
+
}
|
package/src/inspection.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveGraphicClipBounds as B,resolveGraphicParentOffset as W}from"./grammar/schemas/graphicBounds.js";import{resolveFacetDefinition as K,resolveFacetGridDefinition as L}from"./grammar/facets/index.js";import{APPEARANCE_MARK_TYPES as V}from"./core/vocabulary.js";import{ChartProgram as E}from"./ChartProgram.js";import{BasicChartProgram as C}from"./BasicChartProgram.js";import{getWrappedActionMetadata as J}from"./core/action.js";import{cloneAndFreeze as b,isOwned as _,isPlainObject as S}from"./core/immutable.js";import{encodeValue as Y}from"./persistence/codec.js";import{findDataset as z,hasDatasetOwner as Z,resolveDatasetReference as F}from"./selectors/datasets.js";import{findLayer as G}from"./selectors/layers.js";import{findSemanticScale as H}from"./selectors/scales.js";import{findCoordinate as Q}from"./selectors/coordinates.js";import{inferDatasetSchema as X}from"./grammar/datasetSchema.js";import{BUILTIN_ACTION_DESCRIPTOR_RECORDS as ee}from"./inspectionDescriptors.js";const ne=new Set(["data","mark","scale","coordinate","guide"]),ie=new Map(ee.map(([e,n,i,t,c])=>[e,{options:n.map(([m,l,k,d,a])=>({path:m,required:l,kind:k,...d===null?{}:{choices:d},...a===null?{}:{unit:a}})),idOptions:i,prerequisites:t,entryPoints:c}]));function P(e,n="Program"){if(!(e instanceof E||e instanceof C))throw new TypeError(`${n} must be a ggaction program.`);if(e.actionStack.length!==0)throw new Error(`${n} must have a closed action stack.`);return e}function $(e,n=[]){if(!Array.isArray(n))throw new TypeError("childPath must be an array.");let i=e;for(const t of n){if(typeof t!="string"||!Object.hasOwn(i.children,t))throw new Error(`Unknown program child "${String(t)}".`);i=i.children[t]}return i}function R(e){if(!S(e)||!ne.has(e.kind)||typeof e.id!="string"||e.id.length===0)throw new TypeError("Inspection target requires a supported kind and non-empty id.");return e}function I(e,n){const i=$(e,n.childPath);return n.kind==="data"?Z(i,n.id)?F(i,n.id,"Inspection target"):z(i,n.id):n.kind==="mark"?G(i,n.id):n.kind==="scale"?H(i,n.id):n.kind==="coordinate"?Q(i,n.id):i.semanticSpec.guides[n.id]}function g(e,n,i){return{kind:e,id:n,...i?.length?{childPath:[...i]}:{}}}function te(e,{data:n,childPath:i}={}){P(e);const t=$(e,i);if(typeof n!="string"||n.length===0)throw new TypeError("getDatasetSchema requires data.");const c=F(t,n,"Dataset schema data"),m=c.schema??X(c.values??[]);return b({data:c.id,schema:m})}function be(e,{action:n,target:i,options:t={}}={}){if(P(e),typeof n!="string"||n.length===0)throw new TypeError("describeAction requires action.");if(!S(t))throw new TypeError("describeAction options must be a plain object.");let c;if(i!==void 0){if(R(i),c=I(e,i),c===void 0)return b({version:1,action:n,applicability:"incompatible",parameterDefinitions:[],requirements:[],checks:[{name:"target",status:"failed",evidencePaths:[],findings:[]}],findings:[{severity:"error",code:"missing-resource",reason:"target-unavailable",message:`Unknown ${i.kind} "${i.id}".`,resource:g(i.kind,i.id,i.childPath)}]});if(t.target!==void 0&&t.target!==i.id)return b({version:1,action:n,applicability:"incompatible",parameterDefinitions:[],requirements:[],checks:[{name:"target",status:"failed",evidencePaths:["target"],findings:[]}],findings:[{severity:"error",code:"incompatible-resource",reason:"target-mismatch",message:`Inspection target "${i.id}" conflicts with options.target "${t.target}".`,resource:g(i.kind,i.id,i.childPath),optionPath:"target"}]});const f=V[n];if(f&&(i.kind!=="mark"||!f.includes(c.mark?.type)))return b({version:1,action:n,applicability:"unsupported",parameterDefinitions:[],requirements:[],checks:[{name:"target",status:"not_applicable",evidencePaths:["target"],findings:[]}],findings:[{severity:"info",code:"unsupported-combination",reason:"mark-type",message:`${n} supports ${f.join(", ")} marks.`,resource:g(i.kind,i.id,i.childPath)}]})}const m=e[n],l=typeof m=="function"?J(m):void 0;if(l===void 0)return b({version:1,action:n,applicability:typeof m=="function"?"unverified":"unsupported",parameterDefinitions:[],requirements:[],checks:[],findings:[{severity:"info",code:"unsupported-format",reason:typeof m=="function"?"extension-unverified":"action-unavailable",message:`Action "${n}" has no built-in inspection descriptor.`}]});const k=ie.get(n),d=l.options??k?.options.map(f=>f.path);if(d===void 0)return b({version:1,action:n,applicability:"unverified",parameterDefinitions:[],requirements:[],checks:[{name:"execution",status:"not_run",evidencePaths:[],findings:[]}],findings:[{severity:"info",code:"unsupported-format",reason:"descriptor-unavailable",message:`Action "${n}" is registered but its static option descriptor is unavailable.`}]});const a=Object.keys(t).find(f=>!d.includes(f)),o=(k?.options.filter(f=>f.required).map(f=>f.path)??[]).filter(f=>t[f]===void 0);let p=[];if(c!==void 0&&i.kind==="mark"){const f=$(e,i.childPath),O=n.startsWith("edit")&&n.endsWith("Plot")?Object.entries(f.markConfigs[i.id]??{}).find(([r])=>r.toLowerCase()===n.slice(4).toLowerCase())?.[1]:void 0,x=O?.source===void 0?c.data:t.data??O.source,y=te(e,{data:x,childPath:i.childPath}).schema;if(y.completeness==="known"){const r=new Set(y.fields.map(v=>v.name));p=(k?.options??[]).filter(v=>v.kind==="field"&&typeof t[v.path]=="string"&&!r.has(t[v.path])).map(v=>({severity:"error",code:"missing-resource",reason:"field-unavailable",message:`Dataset does not contain field "${t[v.path]}".`,resource:g("data",x,i.childPath),optionPath:v.path}))}}if(a===void 0&&o.length===0&&(n==="facet"&&t.field||n==="facetGrid"&&t.rows&&t.columns))try{(n==="facet"?K:L)(e.semanticSpec,t)}catch(f){p.push({severity:"error",code:"unsupported-combination",reason:"facet-partition",message:f.message,optionPath:"data"})}const u=a!==void 0?[{severity:"error",code:"invalid-option",reason:"unknown-option",message:`Unknown ${n} option "${a}".`,optionPath:a}]:[...o.map(f=>({severity:"info",code:"missing-input",reason:"required-option",message:`${n} requires option "${f}".`,optionPath:f})),...p],h=a!==void 0||p.length?"incompatible":o.length?"needs-input":"supported";return b({version:1,action:n,applicability:h,parameterDefinitions:k?.options.map(f=>({...f,active:!0}))??d.map(f=>({path:f,kind:"object",required:!1,active:!0})),requirements:o.map(f=>({reason:"required-option",optionPath:f})),checks:[{name:"option-shape",status:a===void 0?"passed":"failed",evidencePaths:Object.keys(t),findings:a===void 0?[]:u},{name:"execution",status:"not_run",evidencePaths:[],findings:[]}],findings:u})}function T(e){return Array.isArray(e)?e.map(T):S(e)?Object.fromEntries(Object.keys(e).sort().map(n=>[n,T(e[n])])):e}function j(e){return JSON.stringify(Y(T(e)))}const D=new WeakMap;function M(e,n){if(Object.is(e,n))return!0;if(e===null||n===null||typeof e!="object"||typeof n!="object"||Array.isArray(e)!==Array.isArray(n))return!1;if(!Array.isArray(e)&&(!S(e)||!S(n)))return j(e)===j(n);const i=_(e)&&_(n),t=i?D.get(e)?.get(n):void 0;if(t!==void 0)return t;const c=Object.keys(e),m=(!Array.isArray(e)||e.length===n.length)&&c.length===Object.keys(n).length&&c.every(l=>Object.hasOwn(n,l)&&M(e[l],n[l]));return i&&(D.has(e)||D.set(e,new WeakMap),D.get(e).set(n,m)),m}function N(e,n=[],i=[]){for(const t of e.semanticSpec.datasets)i.push({ref:g("data",t.id,n),value:t});for(const[t,c]of e.semanticSpec.layers.entries())i.push({ref:g("mark",c.id,n),value:{semantic:c,graphic:e.graphicSpec.objects[c.id],config:e.markConfigs[c.id],orderIndex:t}});for(const t of e.semanticSpec.scales)i.push({ref:g("scale",t.id,n),value:{semantic:t,resolved:e.resolvedScales[t.id]}});for(const t of e.semanticSpec.coordinates)i.push({ref:g("coordinate",t.id,n),value:t});for(const[t,c]of Object.entries(e.semanticSpec.guides))i.push({ref:g("guide",t,n),value:{semantic:c,graphic:e.graphicSpec.objects[t],config:e.guideConfigs[t]}});Object.keys(e.semanticSpec.title).length&&i.push({ref:g("guide","title",n),value:{semantic:e.semanticSpec.title,config:e.titleConfig}}),e.graphicSpec.objects.canvas!==void 0&&i.push({ref:g("canvas","canvas",n),value:e.graphicSpec.objects.canvas}),Object.keys(e.compositionSpec??{}).length>0&&i.push({ref:g("composition","layout",n),value:e.compositionSpec});for(const[t,c]of Object.entries(e.children))N(c,[...n,t],i);return i}function A(e){return`${e.childPath?.join("/")??""}|${e.kind}|${e.id}`}function se(e){return e==="data"?"data":e==="scale"?"scale":e==="guide"?"guide":["coordinate","canvas","composition"].includes(e)?"structure":"style"}function re(e,n,i){return i!=="mark"||e===void 0||n===void 0?se(i):e.value.orderIndex!==n.value.orderIndex?"structure":!M(e.value.semantic?.encoding,n.value.semantic?.encoding)||e.value.semantic?.data!==n.value.semantic?.data?"binding":M(e.value.semantic,n.value.semantic)?"style":"structure"}function Se(e,n,{target:i}={}){P(e,"Before program"),P(n,"After program"),i!==void 0&&R(i);const t=new Map(N(e).map(a=>[A(a.ref),a])),c=new Map(N(n).map(a=>[A(a.ref),a]));let l=[...new Set([...t.keys(),...c.keys()])].sort().flatMap(a=>{const s=t.get(a),o=c.get(a),p=s??o;if(s!==void 0&&o!==void 0&&M(s.value,o.value))return[];const u=p.ref.kind==="mark"&&s!==void 0&&o!==void 0?[...new Set([...Object.keys(s.value.semantic?.encoding??{}),...Object.keys(o.value.semantic?.encoding??{})])].filter(h=>!M(s.value.semantic?.encoding?.[h],o.value.semantic?.encoding?.[h])):[];return[{resource:p.ref,operation:s===void 0?"add":o===void 0?"remove":"modify",effect:re(s,o,p.ref.kind),roles:u}]});if(i!==void 0){const a=[e,n].flatMap(o=>{const p=I(o,i);return p===void 0?[]:[A(g(i.kind,i.kind==="data"?p.id:i.id,i.childPath))]});if(a.length===0)throw new Error(`Unknown ${i.kind} "${i.id}" in both programs.`);const s=new Set(a);for(const o of[t,c]){let p=!0;for(;p;){p=!1;for(const[u,h]of o){if(s.has(u))continue;const f=j(h.ref.childPath??[]);[...s].some(x=>{const y=o.get(x);return y===void 0||j(y.ref.childPath??[])!==f?!1:y.ref.kind==="data"?h.ref.kind==="data"?h.value.source===y.ref.id:h.ref.kind==="mark"&&h.value.semantic?.data===y.ref.id:y.ref.kind==="mark"?h.ref.kind==="mark"&&h.value.semantic?.source===y.ref.id||h.ref.kind==="guide"&&(h.value.semantic?.target===y.ref.id||h.value.config?.target===y.ref.id):y.ref.kind==="scale"?h.ref.kind==="mark"&&Object.values(h.value.semantic?.encoding??{}).some(r=>r?.scale===y.ref.id||r?.dimensions?.some(v=>v.scale===y.ref.id))||h.ref.kind==="guide"&&(h.value.semantic?.scale===y.ref.id||h.value.config?.scale===y.ref.id):y.ref.kind==="coordinate"&&h.ref.kind==="mark"&&h.value.semantic?.coordinate===y.ref.id})&&(s.add(u),p=!0)}}}l=l.filter(o=>s.has(A(o.resource)))}const k=![E,C].includes(e.constructor)||![E,C].includes(n.constructor),d=k?[{severity:"info",code:"unsupported-format",reason:"extension-unverified",message:"Extension-owned semantic equivalence could not be fully verified."}]:[];return b({version:1,equivalence:l.length?"different":k?"unknown":"equal",completeProgramComparison:i===void 0,changes:l,affected:l.map(a=>a.resource),findings:d})}function q(e,n){if(!S(e))return null;if(n==="circle")return!Number.isFinite(e.x)||!Number.isFinite(e.y)||!Number.isFinite(e.radius)||e.radius<0?null:{x:e.x-e.radius,y:e.y-e.radius,width:e.radius*2,height:e.radius*2};if(n==="rect")return[e.x,e.y,e.width,e.height].every(Number.isFinite)?{x:Math.min(e.x,e.x+e.width),y:Math.min(e.y,e.y+e.height),width:Math.abs(e.width),height:Math.abs(e.height)}:null;if(n==="path"&&Array.isArray(e.commands)){const i=e.commands.flatMap(m=>Number.isFinite(m.x)&&Number.isFinite(m.y)?[[m.x,m.y]]:[]);if(i.length===0)return null;const t=i.map(m=>m[0]),c=i.map(m=>m[1]);return{x:Math.min(...t),y:Math.min(...c),width:Math.max(...t)-Math.min(...t),height:Math.max(...c)-Math.min(...c)}}return[e.x1,e.y1,e.x2,e.y2].every(Number.isFinite)?{x:Math.min(e.x1,e.x2),y:Math.min(e.y1,e.y2),width:Math.abs(e.x2-e.x1),height:Math.abs(e.y2-e.y1)}:null}function ce(e,n,i){const t=e.graphicSpec.objects[n.id],c=z(e,n.data),m=new Set(["circle","rect","line","path"]),l=t?.items??(t?.properties?[{properties:t.properties}]:[]),k=l.map(r=>r.type??t?.type),d=t!==void 0&&k.every(r=>m.has(r)||r==="text"),a=d&&k.every(r=>m.has(r)),s=l.filter(r=>r.properties.opacity===0).length,o=l.filter(r=>{const v=q(r.properties,r.type??t?.type);return v!==null&&v.width===0&&v.height===0}).length,p=l.map(r=>q(r.properties,r.type??t?.type)).filter(Boolean),u=t===void 0?void 0:B(e.graphicSpec,n.id,{rootViewport:!0}),h=t===void 0?[0,0]:W(e.graphicSpec,n.id),f=u===void 0?null:l.flatMap((r,v)=>{const w=q(r.properties,r.type??t.type);return w!==null&&(u.right<=u.left||u.bottom<=u.top||w.x+h[0]+w.width<u.left||w.y+h[1]+w.height<u.top||w.x+h[0]>u.right||w.y+h[1]>u.bottom)?[v]:[]}),O=f?.length??null,x=p.length===0?null:{x:Math.min(...p.map(r=>r.x)),y:Math.min(...p.map(r=>r.y)),width:Math.max(...p.map(r=>r.x+r.width))-Math.min(...p.map(r=>r.x)),height:Math.max(...p.map(r=>r.y+r.height))-Math.min(...p.map(r=>r.y))},y=["line","area"].includes(n.mark?.type)?"series":"item";return{owner:g("mark",n.id,i),grain:y,rawRows:c?.values?.length??null,logicalDataItems:d?l.length:null,drawablePrimitives:d?l.length:null,visibleCandidates:a?Math.max(0,l.length-new Set([...l.map((r,v)=>r.properties.opacity===0?v:-1),...l.map((r,v)=>{const w=q(r.properties,r.type??t?.type);return w!==null&&w.width===0&&w.height===0?v:-1}),...f??[]].filter(r=>r>=0)).size):null,flags:[{reason:"opacity-zero",count:s},{reason:"zero-size",count:o},{reason:"outside-clip",count:O},{reason:"missing-skipped",count:n.mark?.missing==="skip"&&c?.values?Math.max(0,c.values.length-l.length):null}],bounds:x,sourceRefs:c===void 0?[]:[g("data",c.id,i)],checks:[{name:"concrete-structure",status:t===void 0&&!e.compositionSpec?.facet?"failed":d?"passed":"not_applicable",evidencePaths:[n.id],findings:[]},{name:"pixel-visibility",status:"not_run",evidencePaths:[],findings:[]}]}}function U(e,n,i){const t=new Set(e.semanticSpec.layers.map(s=>s.id)),c=n==="axis"?/axis/iu:n==="grid"?/grid/iu:n==="legend"?/legend/iu:n==="title"?/title|subtitle/iu:new RegExp(n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"iu"),m=Object.keys(e.graphicSpec.objects).filter(s=>s!=="canvas"&&!s.startsWith("plot-")&&!t.has(s)&&c.test(s)),l=m.flatMap(s=>{const o=e.graphicSpec.objects[s];return Array.isArray(o?.items)?o.items:o?.properties===void 0?[]:[{properties:o.properties}]}),k=n==="title"?e.semanticSpec.title:e.semanticSpec.guides[n],d=[];function a(s){if(S(s)){typeof s.scale=="string"&&d.push(g("scale",s.scale,i)),typeof s.coordinate=="string"&&d.push(g("coordinate",s.coordinate,i));for(const o of Object.values(s))a(o)}}return a(k),{owner:g("guide",n,i),grain:"unknown",rawRows:null,logicalDataItems:l.length,drawablePrimitives:l.length,visibleCandidates:null,flags:[{reason:"opacity-zero",count:null},{reason:"zero-size",count:null},{reason:"outside-clip",count:null},{reason:"missing-skipped",count:null}],bounds:null,sourceRefs:[...new Map(d.map(s=>[A(s),s])).values()],checks:[{name:"concrete-structure",status:m.length>0?"passed":"not_applicable",evidencePaths:m,findings:[]},{name:"pixel-visibility",status:"not_run",evidencePaths:[],findings:[]}]}}function xe(e,{target:n}={}){P(e),n!==void 0&&R(n);const i=[];function t(d,a=[]){const s=new Set,o=new Set;function p(u){if(o.has(u))throw new Error(`Graphic hierarchy contains a cycle at "${u}".`);if(s.has(u))throw new Error(`Graphic "${u}" is referenced more than once.`);const h=d.graphicSpec.objects[u];if(h===void 0)throw new Error(`Graphic hierarchy references missing object "${u}".`);s.add(u),o.add(u);for(const f of h.children??[])p(f);o.delete(u)}for(const u of d.graphicSpec.order)p(u);for(const u of d.semanticSpec.layers)i.push(ce(d,u,a));for(const u of Object.keys(d.semanticSpec.guides))i.push(U(d,u,a));Object.keys(d.semanticSpec.title).length>0&&i.push(U(d,"title",a));for(const[u,h]of Object.entries(d.children))t(h,[...a,u])}t(e);let c=i;if(n!==void 0){const d=$(e,n.childPath),a=I(e,n);if(a===void 0)throw new Error(`Unknown ${n.kind} "${n.id}".`);c=i.filter(s=>{if(j(s.owner.childPath??[])!==j(n.childPath??[]))return!1;if(n.kind==="mark")return s.owner.id===n.id;const o=G(d,s.owner.id);return n.kind==="data"?o?.data===a.id:n.kind==="coordinate"?o?.coordinate===n.id:n.kind==="scale"?Object.values(o?.encoding??{}).some(p=>p?.scale===n.id||p?.dimensions?.some(u=>u.scale===n.id)):n.kind==="guide"?s.owner.kind==="guide"&&s.owner.id===n.id:!1})}const m=[];function l(d,a=[]){for(const s of Object.values(d.materializationConfigs.calculations??{}))if(S(s))for(const o of Object.values(s))m.push(a.length===0?o:{...o,childPath:a});for(const[s,o]of Object.entries(d.children))l(o,[...a,s])}l(e);const k=c.filter(d=>d.logicalDataItems===null).map(d=>d.owner);return b({version:1,views:c,calculations:m,checks:[{name:"pixel-visibility",status:"not_run",evidencePaths:[],findings:[]}],findings:[],coverage:{partial:c.some(d=>d.checks.some(a=>a.status!=="passed")),unsupported:k}})}export{Se as comparePrograms,be as describeAction,te as getDatasetSchema,xe as inspectProgram};
|
|
1
|
+
import{resolveMarkItems as Q}from"./materialization/selection/policies/index.js";import{highlightTargets as ee}from"./selectors/selectionTargets.js";import{resolvePathCommandBounds as ne,unionConcreteGraphicBounds as ie}from"./grammar/schemas/graphicBounds.js";import{facetUnitTemplate as U}from"./actions/facets/actions.js";import{plotComposition as te}from"./selectors/plotComposition.js";import{overlaySourceReason as L}from"./grammar/overlay.js";import{findUpstreamTransform as W}from"./materialization/dataProvenance.js";import{planLayerPlot as se}from"./actions/composition/layerPlan.js";import{resolveAdjacentSource as re}from"./materialization/adjacentSource.js";import{normalizeActionTargets as oe}from"./grammar/actionTargets.js";import{resolveGraphicClipBounds as ce,resolveGraphicParentOffset as ae}from"./grammar/schemas/graphicBounds.js";import{resolveFacetDefinition as de,resolveFacetGridDefinition as ue}from"./grammar/facets/index.js";import{APPEARANCE_MARK_TYPES as D,FIXED_APPEARANCE_MARK_TYPES as K}from"./core/vocabulary.js";import{ChartProgram as I}from"./ChartProgram.js";import{BasicChartProgram as R}from"./BasicChartProgram.js";import{getWrappedActionMetadata as fe,supportsScopedActionTargets as le}from"./core/action.js";import{cloneAndFreeze as S,isOwned as V,isPlainObject as M}from"./core/immutable.js";import{encodeValue as pe}from"./persistence/codec.js";import{findDataset as C,hasDatasetOwner as me,resolveDatasetReference as Y}from"./selectors/datasets.js";import{findLayer as _,resolveEligibleLayer as he}from"./selectors/layers.js";import"./selectors/markOwners.js";import{normalizeCompositionShares as ye}from"./core/compositionScales.js";import{findSemanticScale as ve}from"./selectors/scales.js";import{findCoordinate as F}from"./selectors/coordinates.js";import{inferDatasetSchema as ge}from"./grammar/datasetSchema.js";import{BUILTIN_ACTION_DESCRIPTOR_RECORDS as ke}from"./inspectionDescriptors.js";const we=new Set(["data","mark","scale","coordinate","guide"]),be=new Map(ke.map(([e,n,i,t,a])=>[e,{options:n.map(([h,m,g,l,d])=>({path:h,required:m,kind:g,...l===null?{}:{choices:l},...d===null?{}:{unit:d}})),idOptions:i,prerequisites:t,entryPoints:a}]));function O(e,n="Program"){if(!(e instanceof I||e instanceof R))throw new TypeError(`${n} must be a ggaction program.`);if(e.actionStack.length!==0)throw new Error(`${n} must have a closed action stack.`);return e}function P(e,n=[]){if(!Array.isArray(n))throw new TypeError("childPath must be an array.");let i=e;for(const t of n){if(typeof t!="string"||!Object.hasOwn(i.children,t))throw new Error(`Unknown program child "${String(t)}".`);i=i.children[t]}return i}function B(e){if(!M(e)||!we.has(e.kind)||typeof e.id!="string"||e.id.length===0)throw new TypeError("Inspection target requires a supported kind and non-empty id.");return e}function N(e,n){const i=P(e,n.childPath);return n.kind==="data"?me(i,n.id)?Y(i,n.id,"Inspection target"):C(i,n.id):n.kind==="mark"?_(i,n.id):n.kind==="scale"?ve(i,n.id):n.kind==="coordinate"?F(i,n.id):i.semanticSpec.guides[n.id]}function w(e,n,i){return{kind:e,id:n,...i?.length?{childPath:[...i]}:{}}}function en(e,{target:n,childPath:i}={}){return O(e),M(n)&&(i=n.childPath,n=n.id),S(te(P(e,i),n))}function Se(e,{data:n,childPath:i}={}){O(e);const t=P(e,i);if(typeof n!="string"||n.length===0)throw new TypeError("getDatasetSchema requires data.");const a=Y(t,n,"Dataset schema data"),h=a.schema??ge(a.values??[]);return S({data:a.id,schema:h})}function xe(e,{action:n,target:i,options:t={}}={}){if(O(e),!M(t))throw new TypeError("describeAction options must be a plain object.");if(M(t.target)||Array.isArray(t.target)){if(O(e),i!==void 0)throw new Error("Use one inspection target or explicit action target selectors, not both.");if(!le(e.constructor,n))return S({version:1,action:n,applicability:"unsupported",parameterDefinitions:[],requirements:[],checks:[],findings:[{severity:"info",code:"unsupported-combination",reason:"entry-point",message:"Scoped action targets require a Full domain action."}]});const{target:s,...x}=t,y=oe(s).map(v=>{let j=e;for(const H of v.childPath){if(j.compositionSpec?.type==="facet")throw new Error("Facet cells follow their source; use editFacetSource for persistent edits.");j=P(j,[H])}if(v.facetSource){if(j.compositionSpec?.type!=="facet")throw new Error("facetSource requires a facet view.");j=U(j,!0)}if(j.compositionSpec?.type==="facet"&&v.id!==void 0)throw new Error("Target a facet source through editFacetSource.");const Z=v.id===void 0||n.includes("CompositionChild")?void 0:{kind:"mark",id:v.id};return xe(j,{action:n,target:Z,options:{...x,...v.id===void 0?{}:{target:v.id}}})}),k=["incompatible","unsupported","needs-input","unverified","supported"].find(v=>y.some(j=>j.applicability===v));return S({...y[0],applicability:k,requirements:y.flatMap(v=>v.requirements),findings:y.flatMap(v=>v.findings),checks:y.flatMap(v=>v.checks)})}if(O(e),typeof n!="string"||n.length===0)throw new TypeError("describeAction requires action.");if(!M(t))throw new TypeError("describeAction options must be a plain object.");let a;if(i!==void 0){if(B(i),a=N(e,i),a===void 0)return S({version:1,action:n,applicability:"incompatible",parameterDefinitions:[],requirements:[],checks:[{name:"target",status:"failed",evidencePaths:[],findings:[]}],findings:[{severity:"error",code:"missing-resource",reason:"target-unavailable",message:`Unknown ${i.kind} "${i.id}".`,resource:w(i.kind,i.id,i.childPath)}]});if(t.target!==void 0&&t.target!==i.id)return S({version:1,action:n,applicability:"incompatible",parameterDefinitions:[],requirements:[],checks:[{name:"target",status:"failed",evidencePaths:["target"],findings:[]}],findings:[{severity:"error",code:"incompatible-resource",reason:"target-mismatch",message:`Inspection target "${i.id}" conflicts with options.target "${t.target}".`,resource:w(i.kind,i.id,i.childPath),optionPath:"target"}]});const s=t.field!==void 0?D[n]:K[n]??D[n];if(s&&(i.kind!=="mark"||!s.includes(a.mark?.type)))return S({version:1,action:n,applicability:"unsupported",parameterDefinitions:[],requirements:[],checks:[{name:"target",status:"not_applicable",evidencePaths:["target"],findings:[]}],findings:[{severity:"info",code:"unsupported-combination",reason:"mark-type",message:`${n} supports ${s.join(", ")} marks.`,resource:w(i.kind,i.id,i.childPath)}]})}const h=e[n],m=typeof h=="function"?fe(h):void 0;if(m===void 0)return S({version:1,action:n,applicability:typeof h=="function"?"unverified":"unsupported",parameterDefinitions:[],requirements:[],checks:[],findings:[{severity:"info",code:"unsupported-format",reason:typeof h=="function"?"extension-unverified":"action-unavailable",message:`Action "${n}" has no built-in inspection descriptor.`}]});let g=be.get(n);a?.mark&&K[n]?.includes(a.mark.type)&&!D[n].includes(a.mark.type)&&(g={...g,options:g.options.filter(s=>!["field","fieldType","scale"].includes(s.path))});const l=m.options??g?.options.map(s=>s.path);if(l===void 0)return S({version:1,action:n,applicability:"unverified",parameterDefinitions:[],requirements:[],checks:[{name:"execution",status:"not_run",evidencePaths:[],findings:[]}],findings:[{severity:"info",code:"unsupported-format",reason:"descriptor-unavailable",message:`Action "${n}" is registered but its static option descriptor is unavailable.`}]});const d=Object.keys(t).find(s=>!l.includes(s)),r=(g?.options.filter(s=>s.required).map(s=>s.path)??[]).filter(s=>t[s]===void 0);let p=[];if(n==="createLayerPlot")try{const s=P(e,i?.childPath);se(s.compositionSpec?.type==="facet"?U(s,!0):s,{...t,source:t.source??i?.id})}catch(s){p.push({severity:"error",code:"unsupported-combination",reason:"layer-compatibility",message:s.message})}if(n==="createAdjacentPlot"){const s=P(e,i?.childPath);try{re(s,t.source??i?.id)}catch(x){t.source||i?.id||s.compositionSpec&&s.compositionSpec.type!=="facet"?p.push({severity:"error",code:"missing-resource",reason:"adjacent-source",message:x.message}):r.push("source")}}if(n==="editCompositionScales"&&((!e.compositionSpec||e.compositionSpec.type==="facet")&&p.push({severity:"error",code:"unsupported-combination",reason:"composition-scope",message:"editCompositionScales requires a concat parent."}),t.share!==void 0))try{if(!Object.keys(ye(t.share)).length)throw new Error("At least one shared-channel policy is required.")}catch(s){p.push({severity:"error",code:"invalid-option",reason:"share-policy",message:s.message,optionPath:"share"})}if(["createOverlay","editOverlay"].includes(n)){const s=P(e,i?.childPath),x=n==="createOverlay"?t.source??i?.id??s.context.currentMark:t.target??i?.id??s.context.currentMark,b=_(s,x);b===void 0?r.push(n==="createOverlay"?"source":"target"):(n==="editOverlay"?!b.derivedBindings?.overlay:L(b,F(s,b.coordinate)?.type,W(s,C(s,b.data),"density")))?p.push({severity:"error",code:"unsupported-combination",reason:"overlay-source",message:n==="createOverlay"?L(b,F(s,b.coordinate)?.type,W(s,C(s,b.data),"density")):"editOverlay requires a linked overlay target."}):n==="createOverlay"&&b.mark.type==="point"&&t.type!=="point"&&!t.orderBy&&r.push("orderBy")}if(a!==void 0&&i.kind==="mark"){const s=P(e,i.childPath),x=n.startsWith("edit")&&n.endsWith("Plot")?Object.entries(s.markConfigs[i.id]??{}).find(([u])=>u.toLowerCase()===n.slice(4).toLowerCase())?.[1]:void 0,b=x?.source===void 0?a.data:t.data??x.source,y=Se(e,{data:b,childPath:i.childPath}).schema;if(y.completeness==="known"){const u=new Set(y.fields.map(k=>k.name));p.push(...(g?.options??[]).filter(k=>k.kind==="field"&&typeof t[k.path]=="string"&&!u.has(t[k.path])).map(k=>({severity:"error",code:"missing-resource",reason:"field-unavailable",message:`Dataset does not contain field "${t[k.path]}".`,resource:w("data",b,i.childPath),optionPath:k.path})))}}if(d===void 0&&r.length===0&&(n==="facet"&&t.field||n==="facetGrid"&&t.rows&&t.columns))try{(n==="facet"?de:ue)(e.semanticSpec,t)}catch(s){p.push({severity:"error",code:"unsupported-combination",reason:"facet-partition",message:s.message,optionPath:"data"})}const f=d!==void 0?[{severity:"error",code:"invalid-option",reason:"unknown-option",message:`Unknown ${n} option "${d}".`,optionPath:d}]:[...r.map(s=>({severity:"info",code:"missing-input",reason:"required-option",message:`${n} requires option "${s}".`,optionPath:s})),...p],c=d!==void 0||p.length?"incompatible":r.length?"needs-input":"supported";return S({version:1,action:n,applicability:c,parameterDefinitions:g?.options.map(s=>({...s,active:!0}))??l.map(s=>({path:s,kind:"object",required:!1,active:!0})),requirements:r.map(s=>({reason:"required-option",optionPath:s})),checks:[{name:"option-shape",status:d===void 0?"passed":"failed",evidencePaths:Object.keys(t),findings:d===void 0?[]:f},{name:"execution",status:"not_run",evidencePaths:[],findings:[]}],findings:f})}function z(e){return Array.isArray(e)?e.map(z):M(e)?Object.fromEntries(Object.keys(e).sort().map(n=>[n,z(e[n])])):e}function A(e){return JSON.stringify(pe(z(e)))}const $=new WeakMap;function E(e,n){if(Object.is(e,n))return!0;if(e===null||n===null||typeof e!="object"||typeof n!="object"||Array.isArray(e)!==Array.isArray(n))return!1;if(!Array.isArray(e)&&(!M(e)||!M(n)))return A(e)===A(n);const i=V(e)&&V(n),t=i?$.get(e)?.get(n):void 0;if(t!==void 0)return t;const a=Object.keys(e),h=(!Array.isArray(e)||e.length===n.length)&&a.length===Object.keys(n).length&&a.every(m=>Object.hasOwn(n,m)&&E(e[m],n[m]));return i&&($.has(e)||$.set(e,new WeakMap),$.get(e).set(n,h)),h}function G(e,n=[],i=[]){for(const t of e.semanticSpec.datasets)i.push({ref:w("data",t.id,n),value:t});for(const[t,a]of e.semanticSpec.layers.entries())i.push({ref:w("mark",a.id,n),value:{semantic:a,graphic:e.graphicSpec.objects[a.id],config:e.markConfigs[a.id],orderIndex:t}});for(const t of e.semanticSpec.scales)i.push({ref:w("scale",t.id,n),value:{semantic:t,resolved:e.resolvedScales[t.id]}});for(const t of e.semanticSpec.coordinates)i.push({ref:w("coordinate",t.id,n),value:t});for(const[t,a]of Object.entries(e.semanticSpec.guides))i.push({ref:w("guide",t,n),value:{semantic:a,graphic:e.graphicSpec.objects[t],config:e.guideConfigs[t]}});Object.keys(e.semanticSpec.title).length&&i.push({ref:w("guide","title",n),value:{semantic:e.semanticSpec.title,config:e.titleConfig}}),e.graphicSpec.objects.canvas!==void 0&&i.push({ref:w("canvas","canvas",n),value:e.graphicSpec.objects.canvas}),Object.keys(e.compositionSpec??{}).length>0&&i.push({ref:w("composition","layout",n),value:e.compositionSpec});for(const[t,a]of Object.entries(e.children))G(a,[...n,t],i);return i}function q(e){return`${e.childPath?.join("/")??""}|${e.kind}|${e.id}`}function Me(e){return e==="data"?"data":e==="scale"?"scale":e==="guide"?"guide":["coordinate","canvas","composition"].includes(e)?"structure":"style"}function je(e,n,i){return i!=="mark"||e===void 0||n===void 0?Me(i):e.value.orderIndex!==n.value.orderIndex?"structure":!E(e.value.semantic?.encoding,n.value.semantic?.encoding)||e.value.semantic?.data!==n.value.semantic?.data?"binding":E(e.value.semantic,n.value.semantic)?"style":"structure"}function nn(e,n,{target:i}={}){O(e,"Before program"),O(n,"After program"),i!==void 0&&B(i);const t=new Map(G(e).map(d=>[q(d.ref),d])),a=new Map(G(n).map(d=>[q(d.ref),d]));let m=[...new Set([...t.keys(),...a.keys()])].sort().flatMap(d=>{const o=t.get(d),r=a.get(d),p=o??r;if(o!==void 0&&r!==void 0&&E(o.value,r.value))return[];const f=p.ref.kind==="mark"&&o!==void 0&&r!==void 0?[...new Set([...Object.keys(o.value.semantic?.encoding??{}),...Object.keys(r.value.semantic?.encoding??{})])].filter(c=>!E(o.value.semantic?.encoding?.[c],r.value.semantic?.encoding?.[c])):[];return[{resource:p.ref,operation:o===void 0?"add":r===void 0?"remove":"modify",effect:je(o,r,p.ref.kind),roles:f}]});if(i!==void 0){const d=[e,n].flatMap(r=>{const p=N(r,i);return p===void 0?[]:[q(w(i.kind,i.kind==="data"?p.id:i.id,i.childPath))]});if(d.length===0)throw new Error(`Unknown ${i.kind} "${i.id}" in both programs.`);const o=new Set(d);for(const r of[t,a]){let p=!0;for(;p;){p=!1;for(const[f,c]of r){if(o.has(f))continue;const s=A(c.ref.childPath??[]);[...o].some(b=>{const y=r.get(b);return y===void 0||A(y.ref.childPath??[])!==s?!1:y.ref.kind==="data"?c.ref.kind==="data"?c.value.source===y.ref.id:c.ref.kind==="mark"&&c.value.semantic?.data===y.ref.id:y.ref.kind==="mark"?c.ref.kind==="mark"&&c.value.semantic?.source===y.ref.id||c.ref.kind==="guide"&&(c.value.semantic?.target===y.ref.id||c.value.config?.target===y.ref.id):y.ref.kind==="scale"?c.ref.kind==="mark"&&Object.values(c.value.semantic?.encoding??{}).some(u=>u?.scale===y.ref.id||u?.dimensions?.some(k=>k.scale===y.ref.id))||c.ref.kind==="guide"&&(c.value.semantic?.scale===y.ref.id||c.value.config?.scale===y.ref.id):y.ref.kind==="coordinate"&&c.ref.kind==="mark"&&c.value.semantic?.coordinate===y.ref.id})&&(o.add(f),p=!0)}}}m=m.filter(r=>o.has(q(r.resource)))}const g=![I,R].includes(e.constructor)||![I,R].includes(n.constructor),l=g?[{severity:"info",code:"unsupported-format",reason:"extension-unverified",message:"Extension-owned semantic equivalence could not be fully verified."}]:[];return S({version:1,equivalence:m.length?"different":g?"unknown":"equal",completeProgramComparison:i===void 0,changes:m,affected:m.map(d=>d.resource),findings:l})}function T(e,n){if(!M(e))return null;if(n==="circle")return!Number.isFinite(e.x)||!Number.isFinite(e.y)||!Number.isFinite(e.radius)||e.radius<0?null:{x:e.x-e.radius,y:e.y-e.radius,width:e.radius*2,height:e.radius*2};if(n==="rect")return[e.x,e.y,e.width,e.height].every(Number.isFinite)?{x:Math.min(e.x,e.x+e.width),y:Math.min(e.y,e.y+e.height),width:Math.abs(e.width),height:Math.abs(e.height)}:null;if(n==="path"&&Array.isArray(e.commands)){const i=e.commands.flatMap(h=>Number.isFinite(h.x)&&Number.isFinite(h.y)?[[h.x,h.y]]:[]);if(i.length===0)return null;const t=i.map(h=>h[0]),a=i.map(h=>h[1]);return{x:Math.min(...t),y:Math.min(...a),width:Math.max(...t)-Math.min(...t),height:Math.max(...a)-Math.min(...a)}}return[e.x1,e.y1,e.x2,e.y2].every(Number.isFinite)?{x:Math.min(e.x1,e.x2),y:Math.min(e.y1,e.y2),width:Math.abs(e.x2-e.x1),height:Math.abs(e.y2-e.y1)}:null}function Pe(e,n,i){const t=e.graphicSpec.objects[n.id],a=C(e,n.data),h=new Set(["circle","rect","line","path"]),m=t?.items??(t?.properties?[{properties:t.properties}]:[]),g=m.map(u=>u.type??t?.type),l=t!==void 0&&g.every(u=>h.has(u)||u==="text"),d=l&&g.every(u=>h.has(u)),o=m.filter(u=>u.properties.opacity===0).length,r=m.filter(u=>{const k=T(u.properties,u.type??t?.type);return k!==null&&k.width===0&&k.height===0}).length,p=m.map(u=>T(u.properties,u.type??t?.type)).filter(Boolean),f=t===void 0?void 0:ce(e.graphicSpec,n.id,{rootViewport:!0}),c=t===void 0?[0,0]:ae(e.graphicSpec,n.id),s=f===void 0?null:m.flatMap((u,k)=>{const v=T(u.properties,u.type??t.type);return v!==null&&(f.right<=f.left||f.bottom<=f.top||v.x+c[0]+v.width<f.left||v.y+c[1]+v.height<f.top||v.x+c[0]>f.right||v.y+c[1]>f.bottom)?[k]:[]}),x=s?.length??null,b=p.length===0?null:{x:Math.min(...p.map(u=>u.x)),y:Math.min(...p.map(u=>u.y)),width:Math.max(...p.map(u=>u.x+u.width))-Math.min(...p.map(u=>u.x)),height:Math.max(...p.map(u=>u.y+u.height))-Math.min(...p.map(u=>u.y))},y=["line","area"].includes(n.mark?.type)?"series":"item";return{owner:w("mark",n.id,i),grain:y,rawRows:a?.values?.length??null,logicalDataItems:l?m.length:null,drawablePrimitives:l?m.length:null,visibleCandidates:d?Math.max(0,m.length-new Set([...m.map((u,k)=>u.properties.opacity===0?k:-1),...m.map((u,k)=>{const v=T(u.properties,u.type??t?.type);return v!==null&&v.width===0&&v.height===0?k:-1}),...s??[]].filter(u=>u>=0)).size):null,flags:[{reason:"opacity-zero",count:o},{reason:"zero-size",count:r},{reason:"outside-clip",count:x},{reason:"missing-skipped",count:n.mark?.missing==="skip"&&a?.values?Math.max(0,a.values.length-m.length):null}],bounds:b,sourceRefs:[...a===void 0?[]:[w("data",a.id,i)],...n.derivedBindings?.overlay?[w("mark",n.derivedBindings.overlay.source,i)]:[]],checks:[{name:"concrete-structure",status:t===void 0&&!e.compositionSpec?.facet?"failed":l?"passed":"not_applicable",evidencePaths:[n.id],findings:[]},{name:"pixel-visibility",status:"not_run",evidencePaths:[],findings:[]}]}}function J(e,n,i){const t=new Set(e.semanticSpec.layers.map(o=>o.id)),a=n==="axis"?/axis/iu:n==="grid"?/grid/iu:n==="legend"?/legend/iu:n==="title"?/title|subtitle/iu:new RegExp(n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"iu"),h=Object.keys(e.graphicSpec.objects).filter(o=>o!=="canvas"&&!o.startsWith("plot-")&&!t.has(o)&&a.test(o)),m=h.flatMap(o=>{const r=e.graphicSpec.objects[o];return Array.isArray(r?.items)?r.items:r?.properties===void 0?[]:[{properties:r.properties}]}),g=n==="title"?e.semanticSpec.title:e.semanticSpec.guides[n],l=[];function d(o){if(M(o)){typeof o.scale=="string"&&l.push(w("scale",o.scale,i)),typeof o.coordinate=="string"&&l.push(w("coordinate",o.coordinate,i));for(const r of Object.values(o))d(r)}}return d(g),{owner:w("guide",n,i),grain:"unknown",rawRows:null,logicalDataItems:m.length,drawablePrimitives:m.length,visibleCandidates:null,flags:[{reason:"opacity-zero",count:null},{reason:"zero-size",count:null},{reason:"outside-clip",count:null},{reason:"missing-skipped",count:null}],bounds:null,sourceRefs:[...new Map(l.map(o=>[q(o),o])).values()],checks:[{name:"concrete-structure",status:h.length>0?"passed":"not_applicable",evidencePaths:h,findings:[]},{name:"pixel-visibility",status:"not_run",evidencePaths:[],findings:[]}]}}function tn(e,{target:n}={}){O(e),n!==void 0&&B(n);const i=[];function t(l,d=[]){const o=new Set,r=new Set;function p(f){if(r.has(f))throw new Error(`Graphic hierarchy contains a cycle at "${f}".`);if(o.has(f))throw new Error(`Graphic "${f}" is referenced more than once.`);const c=l.graphicSpec.objects[f];if(c===void 0)throw new Error(`Graphic hierarchy references missing object "${f}".`);o.add(f),r.add(f);for(const s of c.children??[])p(s);r.delete(f)}for(const f of l.graphicSpec.order)p(f);for(const f of l.semanticSpec.layers)i.push(Pe(l,f,d));for(const f of Object.keys(l.semanticSpec.guides))i.push(J(l,f,d));Object.keys(l.semanticSpec.title).length>0&&i.push(J(l,"title",d));for(const[f,c]of Object.entries(l.children))t(c,[...d,f])}t(e);let a=i;if(n!==void 0){const l=P(e,n.childPath),d=N(e,n);if(d===void 0)throw new Error(`Unknown ${n.kind} "${n.id}".`);a=i.filter(o=>{if(A(o.owner.childPath??[])!==A(n.childPath??[]))return!1;if(n.kind==="mark")return o.owner.id===n.id;const r=_(l,o.owner.id);return n.kind==="data"?r?.data===d.id:n.kind==="coordinate"?r?.coordinate===n.id:n.kind==="scale"?Object.values(r?.encoding??{}).some(p=>p?.scale===n.id||p?.dimensions?.some(f=>f.scale===n.id)):n.kind==="guide"?o.owner.kind==="guide"&&o.owner.id===n.id:!1})}const h=[];function m(l,d=[]){for(const o of Object.values(l.materializationConfigs.calculations??{}))if(M(o))for(const r of Object.values(o))h.push(d.length===0?r:{...r,childPath:d});for(const[o,r]of Object.entries(l.children))m(r,[...d,o])}m(e);const g=a.filter(l=>l.logicalDataItems===null).map(l=>l.owner);return S({version:1,views:a,calculations:h,checks:[{name:"pixel-visibility",status:"not_run",evidencePaths:[],findings:[]}],findings:[],coverage:{partial:a.some(l=>l.checks.some(d=>d.status!=="passed")),unsupported:g}})}const X=new WeakMap;function sn(e,{target:n,grain:i="item"}={}){O(e);const t=typeof n=="object"?P(e,n.childPath):e,h=(typeof n=="object"?n.id:n)??he(t,{label:"mark items",predicate:o=>o.data!==void 0}).id;let m=X.get(t);m||X.set(t,m=new Map);const g=`${h}:${i}`;if(m.has(g))return m.get(g);const l=new Map;for(const[o,r]of Object.entries(t.graphicSpec.objects)){l.set(o,r);for(const p of r.items??[])l.set(p.id,{type:p.type??r.type,properties:p.properties})}const d=S(ee(t,h).flatMap(o=>Q(t,o,i).map(r=>{const p=r.graphicIds.map(c=>l.get(c)).filter(Boolean).map(c=>({type:c.type,properties:c.properties})),f=p.map(c=>{const s=c.properties;return s.commands?ne(s.commands):c.type==="circle"?{left:s.x-s.radius,top:s.y-s.radius,right:s.x+s.radius,bottom:s.y+s.radius}:c.type==="line"?{left:Math.min(s.x1,s.x2),top:Math.min(s.y1,s.y2),right:Math.max(s.x1,s.x2),bottom:Math.max(s.y1,s.y2)}:c.type==="rect"?{left:s.x,top:s.y,right:s.x+s.width,bottom:s.y+s.height}:ie({objects:{item:c},order:["item"]},["item"],t.materializationConfigs.textMetrics)}).filter(Boolean);return{key:r.key,target:r.layer,markType:r.markType,grain:i,fields:r.fields,channels:r.channels,properties:r.properties,rows:r.rows,geometry:p,bounds:f.length?{left:Math.min(...f.map(c=>c.left)),top:Math.min(...f.map(c=>c.top)),right:Math.max(...f.map(c=>c.right)),bottom:Math.max(...f.map(c=>c.bottom))}:null}})));return m.set(g,d),d}export{nn as comparePrograms,xe as describeAction,Se as getDatasetSchema,sn as getMarkItems,en as getPlotComposition,tn as inspectProgram};
|