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
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { datasetInputIds } from "../core/datasetReferences.js";
|
|
2
|
+
export { datasetInputReferences, datasetInputIds, rebindTransformDatasetInputs } from "../core/datasetReferences.js";
|
|
3
|
+
|
|
4
|
+
export function orderedDatasetAncestors(datasets, root) {
|
|
5
|
+
const byId = new Map(datasets.map(dataset => [dataset.id, dataset]));
|
|
6
|
+
const visiting = new Set(), visited = new Set(), result = [];
|
|
7
|
+
if (byId.size !== datasets.length) throw new Error("Dataset dependency graph contains duplicate ids.");
|
|
8
|
+
function visit(id) {
|
|
9
|
+
if (visiting.has(id)) throw new Error(`Dataset dependency graph contains a cycle at "${id}".`);
|
|
10
|
+
if (visited.has(id)) return;
|
|
11
|
+
const dataset = byId.get(id);
|
|
12
|
+
if (dataset === undefined) throw new Error(`Dataset dependency graph references missing input "${id}".`);
|
|
13
|
+
visiting.add(id);
|
|
14
|
+
for (const input of datasetInputIds(dataset)) visit(input);
|
|
15
|
+
visiting.delete(id);
|
|
16
|
+
visited.add(id);
|
|
17
|
+
result.push(dataset);
|
|
18
|
+
}
|
|
19
|
+
visit(root);
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// A stable topological extension of the historical primary-tree DFS order.
|
|
24
|
+
// A binary input waits for both changed parents; an ordinary tree keeps its old order.
|
|
25
|
+
export function orderedDatasetDependents(datasets, root, { skip = () => false } = {}) {
|
|
26
|
+
const byId = new Map(datasets.map(dataset => [dataset.id, dataset]));
|
|
27
|
+
if (byId.size !== datasets.length) throw new Error("Dataset dependency graph contains duplicate ids.");
|
|
28
|
+
if (!byId.has(root)) throw new Error(`Dataset dependency graph has no root "${root}".`);
|
|
29
|
+
const children = new Map();
|
|
30
|
+
for (const dataset of datasets) {
|
|
31
|
+
for (const input of datasetInputIds(dataset)) {
|
|
32
|
+
if (!byId.has(input)) throw new Error(`Dataset "${dataset.id}" references missing input "${input}".`);
|
|
33
|
+
const values = children.get(input) ?? [];
|
|
34
|
+
values.push(dataset);
|
|
35
|
+
children.set(input, values);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const rank = new Map(), visiting = new Set(), visited = new Set();
|
|
39
|
+
function visit(id) {
|
|
40
|
+
if (visiting.has(id)) throw new Error(`Dataset dependency graph contains a cycle at "${id}".`);
|
|
41
|
+
if (visited.has(id)) return;
|
|
42
|
+
visiting.add(id);
|
|
43
|
+
for (const dataset of children.get(id) ?? []) {
|
|
44
|
+
if (skip(dataset)) continue;
|
|
45
|
+
if (!rank.has(dataset.id)) rank.set(dataset.id, rank.size);
|
|
46
|
+
visit(dataset.id);
|
|
47
|
+
}
|
|
48
|
+
visiting.delete(id);
|
|
49
|
+
visited.add(id);
|
|
50
|
+
}
|
|
51
|
+
visit(root);
|
|
52
|
+
const remaining = new Map([...rank].map(([id]) => [id,
|
|
53
|
+
datasetInputIds(byId.get(id)).filter(parent => rank.has(parent)).length]));
|
|
54
|
+
const ready = [];
|
|
55
|
+
const less = (a, b) => rank.get(a) < rank.get(b);
|
|
56
|
+
function push(id) {
|
|
57
|
+
ready.push(id);
|
|
58
|
+
let index = ready.length - 1;
|
|
59
|
+
while (index > 0) {
|
|
60
|
+
const parent = Math.floor((index - 1) / 2);
|
|
61
|
+
if (!less(ready[index], ready[parent])) break;
|
|
62
|
+
[ready[index], ready[parent]] = [ready[parent], ready[index]];
|
|
63
|
+
index = parent;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function pop() {
|
|
67
|
+
const first = ready[0], last = ready.pop();
|
|
68
|
+
if (ready.length) {
|
|
69
|
+
ready[0] = last;
|
|
70
|
+
let index = 0;
|
|
71
|
+
for (;;) {
|
|
72
|
+
const left = index * 2 + 1, right = left + 1;
|
|
73
|
+
let next = index;
|
|
74
|
+
if (left < ready.length && less(ready[left], ready[next])) next = left;
|
|
75
|
+
if (right < ready.length && less(ready[right], ready[next])) next = right;
|
|
76
|
+
if (next === index) break;
|
|
77
|
+
[ready[index], ready[next]] = [ready[next], ready[index]];
|
|
78
|
+
index = next;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return first;
|
|
82
|
+
}
|
|
83
|
+
for (const [id, count] of remaining) if (count === 0) push(id);
|
|
84
|
+
const result = [];
|
|
85
|
+
while (ready.length) {
|
|
86
|
+
const id = pop();
|
|
87
|
+
result.push(byId.get(id));
|
|
88
|
+
for (const child of children.get(id) ?? []) {
|
|
89
|
+
if (!remaining.has(child.id)) continue;
|
|
90
|
+
const count = remaining.get(child.id) - 1;
|
|
91
|
+
remaining.set(child.id, count);
|
|
92
|
+
if (count === 0) push(child.id);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (result.length !== rank.size) throw new Error("Dataset dependency graph contains a cycle.");
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
@@ -268,8 +268,33 @@ function generatedField(name, storageType, transform, role, inputs = []) {
|
|
|
268
268
|
};
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
export function
|
|
271
|
+
export function datasetSchemaContext(source,transform,ownerId,resolveInput) {
|
|
272
|
+
const context={sourceId:source.id,ownerId,sourceTransform:source.transform?.[0]};
|
|
273
|
+
if(transform.type==="lookup"){
|
|
274
|
+
const input=resolveInput(transform.lookup.data);
|
|
275
|
+
if(input?.schema===undefined)throw new Error(`Lookup dataset "${transform.lookup.data}" requires schema.`);
|
|
276
|
+
assertFieldsAvailable(source.schema,[transform.field],{data:source.id,operation:"Lookup"});
|
|
277
|
+
assertFieldsAvailable(input.schema,[transform.lookup.key,...transform.lookup.fields],{data:input.id,operation:"Lookup"});
|
|
278
|
+
context.lookupSchema=input.schema;context.lookupSourceId=input.id;
|
|
279
|
+
}
|
|
280
|
+
return context;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function deriveTransformSchema(inputSchema, transform, values, { sourceId, ownerId, sourceTransform,lookupSchema,lookupSourceId } = {}) {
|
|
272
284
|
const input = validateDatasetSchema(inputSchema);
|
|
285
|
+
if(transform.type==="lookup"){
|
|
286
|
+
const right=validateDatasetSchema(lookupSchema),fields=input.fields.map(field=>({...field}));
|
|
287
|
+
for(let i=0;i<transform.lookup.fields.length;i++){
|
|
288
|
+
const field=transform.lookup.fields[i],name=transform.lookup.as[i];
|
|
289
|
+
if(fields.some(f=>f.name===name))throw new Error(`Lookup output field "${name}" already exists in primary data.`);
|
|
290
|
+
const inherited=right.fields.find(f=>f.name===field);
|
|
291
|
+
const references=[{data:sourceId,field:transform.field},{data:lookupSourceId,field:transform.lookup.key},{data:lookupSourceId,field}];
|
|
292
|
+
fields.push({name,storageType:inherited?.storageType??"unknown",nullable:true,optional:false,lineage:{
|
|
293
|
+
inputs:[...new Map(references.map(r=>[JSON.stringify(r),r])).values()],owner:ownerId,role:`lookup:${field}`
|
|
294
|
+
}});
|
|
295
|
+
}
|
|
296
|
+
return cloneAndFreeze({version:1,completeness:input.completeness==="known"||fields.length>0?"known":"unknown",origin:"derived",fields});
|
|
297
|
+
}
|
|
273
298
|
const definition = { ...transform, __sourceId: sourceId, __ownerId: ownerId };
|
|
274
299
|
const retain = ["filter", "markFilter", "sort", "computed", "timeUnit", "window", "normalize", "stack", "impute", "complete"];
|
|
275
300
|
let fields = retain.includes(transform.type) ? input.fields.map(field => ({ ...field })) : [];
|
|
@@ -354,6 +379,11 @@ export function deriveTransformSchema(inputSchema, transform, values, { sourceId
|
|
|
354
379
|
for (const [role, name] of Object.entries(transform.as ?? {})) {
|
|
355
380
|
push(generatedField(name, "number", definition, role, [transform.field]));
|
|
356
381
|
}
|
|
382
|
+
} else if (transform.type === "flatten") {
|
|
383
|
+
fields = input.fields.map(field => ({ ...field }));
|
|
384
|
+
transform.fields.forEach((field, index) => {
|
|
385
|
+
push(generatedField(transform.as[index], "unknown", definition, `value:${field}`, [field]));
|
|
386
|
+
});
|
|
357
387
|
} else if (transform.type === "fold") {
|
|
358
388
|
fields = input.fields.filter(field => !transform.fields.includes(field.name)).map(field => ({ ...field }));
|
|
359
389
|
push(generatedField(transform.as.key, "string", definition, "key", transform.fields));
|
|
@@ -374,7 +404,7 @@ export function deriveTransformSchema(inputSchema, transform, values, { sourceId
|
|
|
374
404
|
if (planned.lineage === undefined && !["complete", "impute"].includes(transform.type)) {
|
|
375
405
|
return planned;
|
|
376
406
|
}
|
|
377
|
-
return { ...planned, storageType: transform.type
|
|
407
|
+
return { ...planned, storageType: ["regressionStatistics", "timeUnit"].includes(transform.type) && planned.storageType === "number"
|
|
378
408
|
? "number" : field.storageType, nullable: field.nullable, optional: field.optional };
|
|
379
409
|
});
|
|
380
410
|
for (const field of inferred.fields) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{validateOptionObject as o,validateNonEmptyString as r,validatePositiveFinite as a,validateNonNegativeFinite as c}from"../core/validation.js";import{resolveRotation as s}from"./rotation.js";const f=Object.freeze(["text","fontSize","fontFamily","fontWeight","color","offset","align","rotation","alongOffset"]);function d(e){if(o(e,f,"Facet header title",{allowEmpty:!1}),r(e.text,"Facet header title text"),/[\r\n\u2028\u2029]/u.test(e.text))throw new TypeError("Facet header title text must be one line.");e.fontSize!==void 0&&a(e.fontSize,"Facet header title fontSize");for(const t of["fontFamily","color"])e[t]!==void 0&&r(e[t],`Facet header title ${t}`);if(e.fontWeight!==void 0&&!(typeof e.fontWeight=="string"&&e.fontWeight.length>0||Number.isFinite(e.fontWeight)))throw new TypeError("Facet header title fontWeight must be a non-empty string or finite number.");if(e.offset!==void 0&&c(e.offset,"Facet header title offset"),e.align!==void 0&&!["start","center","end"].includes(e.align))throw new TypeError("Facet header title align must be start, center, or end.");for(const t of["rotation","alongOffset"])if(Object.hasOwn(e,t)&&!Number.isFinite(e[t]))throw new TypeError(`Facet header title ${t} must contain finite normalized radians or coordinates.`)}function h(e,t){if(t===!1)return;o(t,f,"Facet header title",{allowEmpty:!1});for(const i of Object.keys(t))if(t[i]===void 0)throw new TypeError(`Facet header title ${i} must not be undefined.`);const n={...e,...t};return Object.hasOwn(t,"rotation")&&(n.rotation=s(t.rotation,"Facet header title rotation")),d(n),n}function w(e){for(const t of e.common===void 0?[e]:[e.common,e.row,e.column]){if(t!==void 0&&Object.hasOwn(t,"anchor")&&!["plot","content","view"].includes(t.anchor))throw new TypeError("Facet header anchor must be plot, content, or view.");if(t?.rotation!==void 0&&!Number.isFinite(t.rotation))throw new TypeError("Facet header rotation must contain finite normalized radians.");t?.title!==void 0&&d(t.title)}if(e.title!==void 0||e.common?.title!==void 0)throw new TypeError("Facet header title requires an explicit row or column role.");if(e.mode!=="roles"&&(e.row?.title!==void 0||e.column?.title!==void 0))throw new TypeError("Facet field titles require role header mode.")}export{h as normalizeFacetHeaderTitle,w as validateFacetHeaderRotations,d as validateFacetHeaderTitle};
|
|
@@ -227,9 +227,13 @@ function resolveAnchor(paths, datasets, classifications, requested) {
|
|
|
227
227
|
if (!datasets.has(id)) {
|
|
228
228
|
throw new Error(`Facet partition dataset "${id}" does not exist.`);
|
|
229
229
|
}
|
|
230
|
-
|
|
230
|
+
const explicitExpansion = ["fold", "flatten"].includes(
|
|
231
|
+
datasets.get(id).value.transform?.[0]?.type
|
|
232
|
+
) &&
|
|
233
|
+
paths.every(path => path.includes(id));
|
|
234
|
+
if (!common.includes(id) && !explicitExpansion) {
|
|
231
235
|
throw new Error(
|
|
232
|
-
`Facet partition dataset "${id}" is not a common row-preserving ancestor of every layer.`
|
|
236
|
+
`Facet partition dataset "${id}" is not a common row-preserving ancestor or materialized fold boundary or aligned-array boundary of every layer.`
|
|
233
237
|
);
|
|
234
238
|
}
|
|
235
239
|
return id;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { cloneAndFreeze, isPlainObject } from "../../core/immutable.js";
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
import { normalizeFacetAxes, facetAxisMode, hasOuterFacetAxes } from "../../core/facetAxisPolicy.js";
|
|
5
|
+
|
|
3
6
|
const LEGEND_FAMILIES = Object.freeze({
|
|
4
7
|
series: "categorical",
|
|
5
8
|
color: "categorical",
|
|
@@ -243,9 +246,7 @@ export function planFacetGuideOwnership({
|
|
|
243
246
|
if (!Array.isArray(children) || children.length !== cells.length) {
|
|
244
247
|
throw new Error("Facet guide ownership requires one child descriptor per placement.");
|
|
245
248
|
}
|
|
246
|
-
|
|
247
|
-
throw new Error("Facet axes must be each or outer.");
|
|
248
|
-
}
|
|
249
|
+
axes = normalizeFacetAxes(axes, "each", { stored: true });
|
|
249
250
|
if (![false, "shared"].includes(legend) && !isPlainObject(legend)) {
|
|
250
251
|
throw new Error("Facet legend must be false or shared.");
|
|
251
252
|
}
|
|
@@ -264,11 +265,12 @@ export function planFacetGuideOwnership({
|
|
|
264
265
|
const presentAxes = descriptor.axes ?? [];
|
|
265
266
|
const records = descriptor.axisOwners;
|
|
266
267
|
const removed = {};
|
|
267
|
-
const keepAxes = axes
|
|
268
|
-
? presentAxes.filter(channel => outer.children[cell.id][channel])
|
|
268
|
+
const keepAxes = !hasOuterFacetAxes(axes) ? presentAxes : records === undefined
|
|
269
|
+
? presentAxes.filter(channel => facetAxisMode(axes, channel) === "each" || outer.children[cell.id][channel])
|
|
269
270
|
: presentAxes.filter(id => {
|
|
270
271
|
const owner = records.find(owner => owner.id === id);
|
|
271
272
|
if (owner === undefined) throw new Error("Facet axis descriptors must identify every retained owner.");
|
|
273
|
+
if (facetAxisMode(axes, owner.channel) === "each") return true;
|
|
272
274
|
const components = Object.entries(owner.components);
|
|
273
275
|
const remove = components.filter(([, side]) => !sides[side]?.includes(cell.id)).map(([component]) => component);
|
|
274
276
|
if (remove.length > 0 && remove.length < components.length) removed[id] = remove;
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { isPlainObject } from "../../core/immutable.js";
|
|
2
|
+
import {validateLegendSpacing} from "../legendSpacing.js";
|
|
2
3
|
import { validateOptionObject, validateNonEmptyString } from "../../core/validation.js";
|
|
3
4
|
import { validateUserId } from "../../core/identifiers.js";
|
|
4
5
|
import { LEGEND_CONFIG_KINDS, LEGEND_POSITIONS } from "../../core/vocabulary.js";
|
|
5
6
|
import { describeLegendBlock } from "../legendBlocks.js";
|
|
6
7
|
import { validateLegendGroupState } from "../legendGroups.js";
|
|
7
8
|
import { resolveSharedFacetLegends, sameFacetGuideValue as same } from "./guides.js";
|
|
9
|
+
import { normalizeDisplayLabelMap } from "../displayLabels.js";
|
|
8
10
|
|
|
9
11
|
const CONFIG_KEYS = Object.freeze([
|
|
10
12
|
"kind", "target", "channels", "scales", "scale", "field", "fieldType", "domain", "title", "titleVisible",
|
|
11
13
|
"inferredTitle", "inferredSymbol", "inferredStrokeWidth", "inferredOffset", "position", "align", "offset", "direction", "columns",
|
|
12
|
-
"titlePosition", "symbol", "labels", "titleStyle", "itemGap", "layout", "overflow", "border", "gradient",
|
|
14
|
+
"titlePosition", "symbol", "labels", "titleStyle", "itemGap", "rowPadding", "titlePadding", "layout", "overflow", "border", "gradient",
|
|
13
15
|
"count", "sampling", "representation", "gradientDirection", "inheritAppearance", "inheritPosition", "blockOverrides",
|
|
14
|
-
"combine", "sizeAreas", "clipHeight"
|
|
16
|
+
"combine", "sizeAreas", "clipHeight", "defaultLabelMap"
|
|
15
17
|
]);
|
|
16
18
|
|
|
17
19
|
export function isAuthoredFacetLegend(value) {
|
|
@@ -31,6 +33,8 @@ export function validateAuthoredFacetLegend(program, intent) {
|
|
|
31
33
|
validateOptionObject(block, ["config", "guide"], "Stored parent legend block");
|
|
32
34
|
validateOptionObject(block.config, kind === "colorSize" ? [...CONFIG_KEYS, "colorScale", "sizeScale"] : CONFIG_KEYS, "Stored parent legend config");
|
|
33
35
|
const config = block.config;
|
|
36
|
+
validateLegendSpacing(kind,config);
|
|
37
|
+
if (config.defaultLabelMap !== undefined) normalizeDisplayLabelMap(config.defaultLabelMap, "Stored inferred legend labels");
|
|
34
38
|
validateUserId(config.target, "Parent legend target");
|
|
35
39
|
if (!LEGEND_POSITIONS.includes(config.position)) throw new Error("Stored parent legend requires a native position.");
|
|
36
40
|
for (const flag of ["inferredTitle", "inferredSymbol", "inferredStrokeWidth", "inferredOffset", "titleVisible", "inheritAppearance", "inheritPosition"]) {
|
|
@@ -89,17 +89,37 @@ export function normalizeFacetScalePolicies(semanticSpec, requested = {}) {
|
|
|
89
89
|
);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
+
const inferred = { ...requested };
|
|
93
|
+
let changed = true;
|
|
94
|
+
while (changed) {
|
|
95
|
+
changed = false;
|
|
96
|
+
for (const [id, channelSet] of used) {
|
|
97
|
+
const attached = [...channelSet];
|
|
98
|
+
const policies = [...new Set(attached
|
|
99
|
+
.filter(channel => inferred[channel] !== undefined)
|
|
100
|
+
.map(channel => inferred[channel]))];
|
|
101
|
+
if (policies.length > 1) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Facet scale "${id}" has conflicting channel policies for ${attached.join(", ")}.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
if (policies.length === 1) for (const channel of attached) {
|
|
107
|
+
if (inferred[channel] === undefined) {
|
|
108
|
+
inferred[channel] = policies[0];
|
|
109
|
+
changed = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
Object.assign(channels, inferred);
|
|
92
115
|
const scalePolicies = {};
|
|
93
116
|
for (const [id, channelSet] of used) {
|
|
94
117
|
const attached = [...channelSet];
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
`Facet scale "${id}" has conflicting channel policies for ${attached.join(", ")}.`
|
|
99
|
-
);
|
|
100
|
-
}
|
|
118
|
+
// One semantic scale cannot have different resolutions through its aliases.
|
|
119
|
+
// An explicitly chosen channel policy also owns omitted aliases; only
|
|
120
|
+
// contradictory explicit requests are ambiguous.
|
|
101
121
|
scalePolicies[id] = {
|
|
102
|
-
policy:
|
|
122
|
+
policy: channels[attached[0]],
|
|
103
123
|
channels: attached
|
|
104
124
|
};
|
|
105
125
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { rejectUnknownProperties, requireStringValue } from "../core/validation.js";
|
|
2
|
+
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
3
|
+
|
|
4
|
+
const KEYS = Object.freeze(["type", "fields", "as"]);
|
|
5
|
+
const MAX_FIELDS = 64;
|
|
6
|
+
const MAX_OUTPUT_ROWS = 10_000;
|
|
7
|
+
|
|
8
|
+
export function normalizeFlattenTransform({ fields, as } = {}) {
|
|
9
|
+
const aliases = as === undefined ? fields : as;
|
|
10
|
+
const transform = {
|
|
11
|
+
type: "flatten",
|
|
12
|
+
fields: Array.isArray(fields) ? [...fields] : fields,
|
|
13
|
+
as: Array.isArray(aliases) ? [...aliases] : aliases
|
|
14
|
+
};
|
|
15
|
+
validateFlattenTransform(transform);
|
|
16
|
+
return cloneAndFreeze(transform);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function validateFlattenTransform(transform) {
|
|
20
|
+
if (!isPlainObject(transform)) {
|
|
21
|
+
throw new TypeError("Flatten transform must be a plain object.");
|
|
22
|
+
}
|
|
23
|
+
rejectUnknownProperties(transform, KEYS, "flatten transform");
|
|
24
|
+
if (transform.type !== "flatten") {
|
|
25
|
+
throw new Error(`Unsupported flatten transform "${transform.type}".`);
|
|
26
|
+
}
|
|
27
|
+
if (!Array.isArray(transform.fields) || transform.fields.length === 0) {
|
|
28
|
+
throw new TypeError("Flatten fields must be a non-empty array.");
|
|
29
|
+
}
|
|
30
|
+
if (transform.fields.length > MAX_FIELDS) {
|
|
31
|
+
throw new RangeError(`Flatten fields cannot contain more than ${MAX_FIELDS} entries.`);
|
|
32
|
+
}
|
|
33
|
+
if (!Array.isArray(transform.as) || transform.as.length !== transform.fields.length) {
|
|
34
|
+
throw new TypeError("Flatten as must be an array with one alias per field.");
|
|
35
|
+
}
|
|
36
|
+
for (const key of ["fields", "as"]) {
|
|
37
|
+
for (const [index, field] of transform[key].entries()) {
|
|
38
|
+
requireStringValue(field, `Flatten ${key}[${index}]`);
|
|
39
|
+
}
|
|
40
|
+
if (new Set(transform[key]).size !== transform[key].length) {
|
|
41
|
+
throw new Error(`Flatten ${key} must be unique.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return transform;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function deriveFlattenRows(rows, transform) {
|
|
48
|
+
validateFlattenTransform(transform);
|
|
49
|
+
const selected = new Set(transform.fields);
|
|
50
|
+
const existing = new Set(rows.flatMap(row => Object.keys(row)));
|
|
51
|
+
for (const name of transform.as) {
|
|
52
|
+
if (existing.has(name) && !selected.has(name)) {
|
|
53
|
+
throw new Error(`Flatten output field "${name}" already exists outside the selected fields.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
let total = 0;
|
|
57
|
+
const lengths = rows.map((row, rowIndex) => {
|
|
58
|
+
let length = 0;
|
|
59
|
+
for (const field of transform.fields) {
|
|
60
|
+
const value = row[field];
|
|
61
|
+
if (value === undefined || value === null) continue;
|
|
62
|
+
if (!Array.isArray(value)) {
|
|
63
|
+
throw new TypeError(`Flatten field "${field}" must be an array at row ${rowIndex}.`);
|
|
64
|
+
}
|
|
65
|
+
length = Math.max(length, value.length);
|
|
66
|
+
}
|
|
67
|
+
total += length;
|
|
68
|
+
if (total > MAX_OUTPUT_ROWS) {
|
|
69
|
+
throw new RangeError(`Flatten output cannot exceed ${MAX_OUTPUT_ROWS} rows.`);
|
|
70
|
+
}
|
|
71
|
+
return length;
|
|
72
|
+
});
|
|
73
|
+
const output = [];
|
|
74
|
+
rows.forEach((row, rowIndex) => {
|
|
75
|
+
for (let index = 0; index < lengths[rowIndex]; index += 1) {
|
|
76
|
+
const next = { ...row };
|
|
77
|
+
transform.fields.forEach((field, fieldIndex) => {
|
|
78
|
+
const values = row[field];
|
|
79
|
+
next[transform.as[fieldIndex]] = values?.[index] ?? null;
|
|
80
|
+
});
|
|
81
|
+
output.push(next);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return output;
|
|
85
|
+
}
|
package/src/grammar/histogram.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
2
|
import { MAX_GENERATED_ITEMS } from "../core/validation.js";
|
|
3
|
+
import { niceLinearCountDomain, normalizeScaleNice } from "./scales/continuous.js";
|
|
3
4
|
import {
|
|
4
5
|
alignNumericStep,
|
|
5
6
|
cleanNumericValue,
|
|
@@ -299,7 +300,18 @@ function niceBins(extent, maxBins) {
|
|
|
299
300
|
return equalBins(extent, maxBins);
|
|
300
301
|
}
|
|
301
302
|
|
|
302
|
-
export function resolveHistogramBins({
|
|
303
|
+
export function resolveHistogramBins(options) {
|
|
304
|
+
if (isPlainObject(options.nice)) {
|
|
305
|
+
const { count } = normalizeScaleNice("linear", options.nice);
|
|
306
|
+
const bins = resolveBooleanHistogramBins({ ...options, nice: true });
|
|
307
|
+
return options.domain === undefined || options.domain === "auto"
|
|
308
|
+
? cloneAndFreeze({ ...bins, domain: niceLinearCountDomain(bins.domain, count) })
|
|
309
|
+
: bins;
|
|
310
|
+
}
|
|
311
|
+
return resolveBooleanHistogramBins(options);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function resolveBooleanHistogramBins({
|
|
303
315
|
values,
|
|
304
316
|
bin,
|
|
305
317
|
maxBins,
|