ggaction 0.0.12 → 0.0.15
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 +127 -0
- package/README.md +20 -11
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/action-cards.schema.json +1 -1
- package/knowledge/extension-authoring.md +2 -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 +10 -3
- package/src/actions/basic.js +4 -2
- package/src/actions/boxPlots/components.js +14 -4
- package/src/actions/boxPlots/edit.js +31 -5
- package/src/actions/boxPlots/materialize.js +13 -4
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/canvas/actions.js +3 -1
- package/src/actions/canvas/fitting.js +211 -0
- package/src/actions/canvas/index.js +2 -0
- package/src/actions/categoryOrder/index.js +18 -6
- package/src/actions/charts/area.js +51 -0
- package/src/actions/charts/bar.js +13 -6
- package/src/actions/charts/beeswarm.js +70 -0
- package/src/actions/charts/density.js +48 -0
- package/src/actions/charts/ecdf.js +186 -0
- package/src/actions/charts/endpoints.js +473 -0
- package/src/actions/charts/heatmap.js +4 -3
- package/src/actions/charts/histogram.js +5 -4
- package/src/actions/charts/horizon.js +73 -0
- package/src/actions/charts/index.js +37 -0
- package/src/actions/charts/interval-regression.js +172 -0
- package/src/actions/charts/line.js +8 -6
- package/src/actions/charts/parallel.js +8 -2
- package/src/actions/charts/pie.js +52 -0
- package/src/actions/charts/polar.js +134 -0
- package/src/actions/charts/radar.js +305 -0
- package/src/actions/charts/radial.js +50 -0
- package/src/actions/charts/raincloud.js +422 -0
- package/src/actions/charts/rug-strip.js +283 -0
- package/src/actions/charts/scatter.js +11 -3
- package/src/actions/charts/shared.js +69 -10
- package/src/actions/composition/actions.js +129 -3
- package/src/actions/coordinates/actions.js +1 -1
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/bin.js +29 -0
- package/src/actions/data/bin2d.js +57 -185
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +24 -0
- package/src/actions/data/create.js +2 -2
- package/src/actions/data/density.js +12 -20
- package/src/actions/data/derived.js +111 -15
- package/src/actions/data/ecdf.js +38 -0
- package/src/actions/data/edit.js +539 -0
- package/src/actions/data/filter.js +226 -30
- package/src/actions/data/fold.js +24 -0
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +38 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/intervalEdit.js +86 -4
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +19 -21
- package/src/actions/data/revisionBindings.js +145 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/stack.js +26 -0
- package/src/actions/data/summary.js +26 -0
- package/src/actions/data/timeUnit.js +4 -1
- package/src/actions/data/window.js +1 -1
- package/src/actions/encodings/appearance.js +32 -13
- package/src/actions/encodings/barWidth.js +8 -0
- package/src/actions/encodings/basic.js +2 -0
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/continuous.js +19 -59
- package/src/actions/encodings/color/index.js +30 -23
- package/src/actions/encodings/color/layout.js +3 -97
- package/src/actions/encodings/color/policy.js +5 -8
- package/src/actions/encodings/density/resolve.js +37 -5
- package/src/actions/encodings/density.js +124 -34
- package/src/actions/encodings/histogram.js +3 -2
- package/src/actions/encodings/horizon/resolve.js +8 -4
- package/src/actions/encodings/horizon.js +18 -8
- package/src/actions/encodings/index.js +6 -0
- package/src/actions/encodings/offset.js +30 -29
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +63 -11
- package/src/actions/encodings/position/index.js +52 -6
- package/src/actions/encodings/position/policies/arc.js +14 -0
- package/src/actions/encodings/position/policies/area.js +4 -42
- package/src/actions/encodings/position/policies/bar.js +57 -13
- package/src/actions/encodings/position/resolve.js +88 -26
- package/src/actions/encodings/ranged.js +123 -97
- package/src/actions/encodings/remove.js +44 -34
- package/src/actions/encodings/ruleAppearance.js +24 -32
- package/src/actions/encodings/seriesLayout.js +105 -0
- package/src/actions/encodings/shared.js +107 -37
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/encodings/strokeDash.js +9 -6
- package/src/actions/encodings/temporal.js +7 -0
- package/src/actions/encodings/text.js +30 -15
- package/src/actions/errorBands/create.js +41 -8
- package/src/actions/errorBands/edit.js +360 -25
- package/src/actions/errorBars/create.js +56 -17
- package/src/actions/errorBars/edit.js +372 -21
- package/src/actions/facets/actions.js +619 -41
- package/src/actions/facets/derive.js +139 -25
- package/src/actions/gradientPlots/components.js +43 -13
- package/src/actions/gradientPlots/edit.js +14 -4
- package/src/actions/gradientPlots/materialize.js +8 -8
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/guides/applicability.js +28 -19
- package/src/actions/guides/axes/axes.js +4 -3
- package/src/actions/guides/axes/axis.js +18 -18
- package/src/actions/guides/axes/components.js +43 -0
- package/src/actions/guides/axes/edit.js +16 -51
- package/src/actions/guides/axes/labels.js +184 -28
- package/src/actions/guides/axes/lines.js +13 -7
- package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
- package/src/actions/guides/axes/parallel/policy.js +137 -0
- package/src/actions/guides/axes/parallel/resolve.js +50 -25
- package/src/actions/guides/axes/parallel.js +93 -217
- package/src/actions/guides/axes/policy.js +21 -67
- package/src/actions/guides/axes/tickGroups.js +15 -8
- package/src/actions/guides/axes/ticks.js +11 -6
- package/src/actions/guides/axes/titles.js +32 -11
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facade.js +106 -0
- package/src/actions/guides/facadeAxes.js +200 -0
- package/src/actions/guides/facadeLegend.js +63 -0
- package/src/actions/guides/grids/grid.js +37 -33
- package/src/actions/guides/grids/resolve.js +17 -4
- package/src/actions/guides/guides.js +54 -31
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +309 -279
- package/src/actions/guides/legends/categorical/components.js +21 -17
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +123 -354
- package/src/actions/guides/legends/categorical/options.js +32 -16
- package/src/actions/guides/legends/categorical/recipes.js +114 -9
- package/src/actions/guides/legends/categorical/resolve.js +30 -15
- package/src/actions/guides/legends/categorical/symbols.js +87 -33
- package/src/actions/guides/legends/continuous/common.js +99 -26
- package/src/actions/guides/legends/continuous/gradient.js +120 -89
- package/src/actions/guides/legends/continuous/index.js +8 -2
- package/src/actions/guides/legends/continuous/interval.js +79 -95
- package/src/actions/guides/legends/continuous/opacity.js +113 -89
- package/src/actions/guides/legends/continuous/stroke.js +525 -0
- package/src/actions/guides/legends/creation.js +40 -0
- package/src/actions/guides/legends/edit.js +321 -90
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +149 -102
- package/src/actions/guides/legends/lifecycle.js +143 -0
- package/src/actions/guides/legends/remove.js +43 -78
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +213 -116
- package/src/actions/guides/legends/strokeWidth.js +121 -73
- package/src/actions/guides/legends/target.js +123 -0
- package/src/actions/guides/legends/transition.js +192 -0
- package/src/actions/guides/polar/axes/facade.js +81 -66
- package/src/actions/guides/polar/axes/labels.js +28 -8
- package/src/actions/guides/polar/axes/lines.js +12 -5
- package/src/actions/guides/polar/axes/shared.js +22 -0
- package/src/actions/guides/polar/axes/ticks.js +6 -5
- package/src/actions/guides/polar/axes/titles.js +6 -5
- package/src/actions/guides/polar/grids.js +8 -8
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/guides/reuse.js +44 -0
- package/src/actions/guides/tickValues.js +19 -21
- package/src/actions/index.js +15 -2
- package/src/actions/intervals/resolve.js +35 -15
- package/src/actions/marks/arc/actions.js +78 -20
- package/src/actions/marks/area/actions.js +51 -12
- package/src/actions/marks/area/materialize.js +22 -3
- package/src/actions/marks/bar/create.js +11 -2
- package/src/actions/marks/bar/edit.js +20 -3
- package/src/actions/marks/bar/materialize.js +33 -9
- package/src/actions/marks/index.js +14 -0
- package/src/actions/marks/line/actions.js +71 -113
- package/src/actions/marks/line/materialize.js +57 -56
- package/src/actions/marks/point/create.js +8 -2
- package/src/actions/marks/point/edit.js +29 -21
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/point/jitter.js +3 -0
- package/src/actions/marks/point/materialize.js +154 -16
- package/src/actions/marks/point/packing.js +68 -0
- package/src/actions/marks/rect/actions.js +33 -12
- package/src/actions/marks/references.js +658 -0
- package/src/actions/marks/remove.js +87 -4
- package/src/actions/marks/rule/actions.js +77 -9
- package/src/actions/marks/rule/appearance.js +26 -0
- package/src/actions/marks/shared.js +13 -13
- package/src/actions/marks/text/actions.js +296 -13
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +156 -8
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +73 -0
- package/src/actions/marks/tick/actions.js +22 -5
- package/src/actions/primitives/createGraphics.js +3 -1
- package/src/actions/primitives/editGraphics.js +26 -0
- package/src/actions/primitives/semantic.js +3 -2
- package/src/actions/primitives/semanticAction.js +120 -10
- package/src/actions/primitives/semanticValidation/guide.js +10 -0
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +23 -1
- package/src/actions/primitives/semanticValidation/scale.js +38 -8
- package/src/actions/regression/components.js +26 -11
- package/src/actions/regression/create.js +11 -3
- package/src/actions/regression/edit.js +40 -13
- package/src/actions/regression/resolve.js +1 -1
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +172 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +21 -54
- package/src/actions/scales/consumers/families.js +28 -14
- package/src/actions/scales/consumers/index.js +42 -4
- package/src/actions/scales/consumers/seriesLayout.js +57 -12
- package/src/actions/scales/create.js +58 -26
- package/src/actions/scales/definitions.js +109 -39
- package/src/actions/scales/edit.js +101 -34
- package/src/actions/scales/editPolicy.js +95 -35
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +14 -49
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +140 -0
- package/src/actions/scales/quantitativeColor.js +9 -5
- package/src/actions/selection/actions.js +154 -30
- package/src/actions/selection/index.js +3 -0
- package/src/actions/theme/actions.js +98 -0
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +16 -0
- package/src/actions/theme/reconcile.js +1130 -0
- package/src/actions/theme/state.js +160 -0
- package/src/actions/titles/actions.js +11 -10
- package/src/actions/titles/resolve.js +1 -40
- package/src/actions/violinPlots/create.js +90 -43
- package/src/actions/violinPlots/edit.js +192 -0
- package/src/actions/violinPlots/index.js +2 -0
- package/src/core/ChartProgram.js +7 -0
- package/src/core/action.js +32 -2
- package/src/core/compositionState.js +141 -8
- package/src/core/programState.js +20 -0
- package/src/core/resourceReferences.js +332 -0
- package/src/core/vocabulary.js +16 -3
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +58 -1
- package/src/grammar/areaEndpoints.js +46 -0
- package/src/grammar/areaSeries.js +87 -41
- package/src/grammar/bars/aggregate.js +37 -13
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bars/policy.js +9 -7
- package/src/grammar/bin.js +226 -0
- package/src/grammar/categoryOrder.js +34 -0
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +351 -0
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/ecdf.js +198 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +123 -52
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/fold.js +123 -0
- package/src/grammar/horizon.js +6 -4
- package/src/grammar/impute.js +340 -0
- package/src/grammar/interval.js +37 -20
- package/src/grammar/lineSeries.js +20 -63
- package/src/grammar/markFilter.js +22 -4
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +110 -0
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/parallelAxisTitles.js +16 -0
- package/src/grammar/pathSeries.js +109 -0
- package/src/grammar/pointPacking.js +183 -0
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/positionDatum.js +12 -0
- package/src/grammar/rects.js +9 -1
- package/src/grammar/regression/derive.js +4 -0
- package/src/grammar/regression/index.js +8 -2
- package/src/grammar/regression/models.js +22 -7
- package/src/grammar/regression/parameters.js +63 -13
- package/src/grammar/rotation.js +29 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +8 -21
- package/src/grammar/scales/color.js +23 -1
- package/src/grammar/scales/colorConsumers.js +57 -0
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +42 -5
- package/src/grammar/scales/discretized.js +11 -5
- package/src/grammar/scales/fields.js +38 -8
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/mapping.js +2 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/radial.js +55 -0
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/scales/types.js +1 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +179 -30
- package/src/grammar/schemas/semanticPath.js +23 -4
- package/src/grammar/stack.js +170 -0
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/statistics/confidenceInterval.js +101 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +245 -0
- package/src/grammar/text.js +22 -16
- package/src/grammar/timeUnit.js +234 -13
- package/src/grammar/transformTopology.js +10 -0
- package/src/grammar/transforms.js +418 -16
- package/src/grammar/valueFormat.js +112 -0
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +232 -21
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +100 -12
- package/src/layout/guideCollisions.js +12 -0
- package/src/layout/labels.js +343 -0
- package/src/layout/legend.js +2 -10
- package/src/layout/legendItems.js +102 -0
- package/src/layout/legendLane.js +100 -11
- package/src/materialization/bars/aggregate.js +56 -147
- package/src/materialization/bars/histogram.js +104 -20
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/categorySlotOffset.js +40 -0
- package/src/materialization/composition.js +3 -1
- package/src/materialization/compositionSnapshot.js +1 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +93 -16
- package/src/materialization/encodings.js +1 -1
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +8 -2
- package/src/materialization/facetGuides/preparation.js +12 -7
- package/src/materialization/facetHeaders.js +346 -0
- package/src/materialization/facets.js +23 -66
- package/src/materialization/guides/layout.js +57 -0
- package/src/materialization/guides/resources.js +28 -2
- package/src/materialization/legends.js +11 -2
- package/src/materialization/marks/capabilities.js +20 -5
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +68 -27
- package/src/materialization/marks/policies.js +4 -1
- package/src/materialization/rect.js +55 -42
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/rowEncoding.js +13 -13
- package/src/materialization/scaleGuideDependencies.js +8 -5
- package/src/materialization/scales/map.js +6 -3
- package/src/materialization/scales/policies/arc.js +42 -0
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +5 -1
- package/src/materialization/scales/resolve.js +65 -17
- package/src/materialization/selection/compatibility.js +11 -0
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +42 -71
- package/src/materialization/selection/items/common.js +27 -6
- package/src/materialization/selection/items/path.js +3 -13
- package/src/materialization/selection/items/rect.js +35 -8
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +324 -14
- package/src/mcp/adapter.js +2 -2
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/line.js +2 -3
- package/src/renderers/canvas/native.js +4 -4
- package/src/renderers/canvas/path.js +2 -3
- package/src/renderers/canvas/rect.js +2 -3
- package/src/renderers/canvas/stroke.js +11 -0
- package/src/renderers/svg.js +19 -0
- package/src/selectors/datasets.js +44 -0
- package/src/selectors/layers.js +7 -2
- package/src/selectors/scales.js +6 -0
- package/src/theme/defaults.js +140 -0
- package/types/basic.d.ts +17 -2
- package/types/index.d.ts +211 -0
- package/types/program.d.ts +2182 -204
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { findUpstreamTransform } from "../dataProvenance.js";
|
|
2
|
+
import { readPathSeriesFields, validatePathSeriesAppearance } from "../../grammar/pathSeries.js";
|
|
3
|
+
|
|
4
|
+
export function validatePathGroupAppearance(program, layer, dataset) {
|
|
5
|
+
if (layer.mark.type === "area" && findUpstreamTransform(program, dataset, "density")) {
|
|
6
|
+
// Density placement owns a category/split grain beyond the visible group field.
|
|
7
|
+
readPathSeriesFields(dataset.values, layer);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
validatePathSeriesAppearance(dataset.values, layer);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function assertPathGroupCompatible(program, layer, dataset, group) {
|
|
14
|
+
if (layer.encoding?.parallel !== undefined) {
|
|
15
|
+
throw new Error("Parallel paths have row identity and do not support encodeGroup.");
|
|
16
|
+
}
|
|
17
|
+
const field = group?.field;
|
|
18
|
+
for (const [type, editor] of [
|
|
19
|
+
["density", "editDensity"],
|
|
20
|
+
["horizon", "editHorizon"],
|
|
21
|
+
["regression", "editRegression"],
|
|
22
|
+
["interval", "editErrorBand"]
|
|
23
|
+
]) {
|
|
24
|
+
const transform = findUpstreamTransform(program, dataset, type);
|
|
25
|
+
if (transform === undefined) continue;
|
|
26
|
+
const fields = type === "horizon" ? [transform.as.group]
|
|
27
|
+
: type === "interval"
|
|
28
|
+
? transform.groupBy.filter(value =>
|
|
29
|
+
value !== layer.encoding?.x?.field && value !== layer.encoding?.y?.field
|
|
30
|
+
)
|
|
31
|
+
: [transform.groupBy];
|
|
32
|
+
if (group?.fields !== undefined || group === undefined ||
|
|
33
|
+
field === undefined || !fields.includes(field)) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`${type === "density" ? "Density area" : type + " path"} mark "${layer.id}" must group by ${fields.length === 1 && fields[0] !== undefined ? JSON.stringify(fields[0]) : "its owned field"}; use ${editor}.`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (layer.layout?.mode !== undefined) {
|
|
40
|
+
if (group === undefined && !["overlay"].includes(layer.layout.mode)) throw new Error("Remove active series layout before removing its group.");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (layer.mark?.type === "area" && (
|
|
44
|
+
layer.encoding?.y?.stack === "center" ||
|
|
45
|
+
![undefined, "overlay"].includes(layer.encoding?.color?.layout)
|
|
46
|
+
)) {
|
|
47
|
+
if (group?.fields !== undefined ||
|
|
48
|
+
(layer.encoding?.color !== undefined && field !== layer.encoding.color.field)) {
|
|
49
|
+
throw new Error("Area layout requires one group field matching its color field.");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../../grammar/text.js";
|
|
2
|
+
import { findLayer } from "../../selectors/layers.js";
|
|
1
3
|
import {
|
|
2
4
|
POSITION_CHANNELS,
|
|
3
5
|
POSITION_ENCODING_CHANNELS
|
|
4
6
|
} from "../../core/vocabulary.js";
|
|
5
7
|
import { getMarkMaterializationPolicy } from "./policies.js";
|
|
8
|
+
import { buildMaterializationPlan } from "../planner.js";
|
|
6
9
|
|
|
7
10
|
export {
|
|
8
11
|
canMaterializeArc,
|
|
@@ -13,10 +16,32 @@ export {
|
|
|
13
16
|
canMaterializeRect,
|
|
14
17
|
canMaterializeRule,
|
|
15
18
|
canMaterializeText,
|
|
16
|
-
canMaterializeTick
|
|
19
|
+
canMaterializeTick,
|
|
20
|
+
isTextSource
|
|
17
21
|
} from "./capabilities.js";
|
|
18
22
|
|
|
19
|
-
export function
|
|
23
|
+
export function getLayerScaleIds(layer) {
|
|
24
|
+
if (isSourceOwnedText(layer)) return [];
|
|
25
|
+
return [
|
|
26
|
+
...Object.values(layer.encoding ?? {}).map(encoding => encoding?.scale),
|
|
27
|
+
...(layer.encoding?.parallel?.dimensions ?? []).map(dimension => dimension.scale)
|
|
28
|
+
].filter(scale => scale !== undefined);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getScaleConsumerMarkSteps(program, scaleIds) {
|
|
32
|
+
return program.semanticSpec.layers
|
|
33
|
+
.filter(layer => getLayerScaleIds(layer).some(id => scaleIds.includes(id)))
|
|
34
|
+
.map(layer => getMarkMaterializationStep(program, layer))
|
|
35
|
+
.filter(step => step !== undefined);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getMarkRematerializationStep(program, layer) {
|
|
39
|
+
if (program.markConfigs?.[layer.id]?.statisticalReference !== undefined) {
|
|
40
|
+
return { op: "rematerializeStatisticalReference", args: { id: layer.id } };
|
|
41
|
+
}
|
|
42
|
+
if (program.markConfigs?.[layer.id]?.markFilter?.empty === true) {
|
|
43
|
+
return { op: "materializeEmptyMark", args: { id: layer.id } };
|
|
44
|
+
}
|
|
20
45
|
const policy = getMarkMaterializationPolicy(layer);
|
|
21
46
|
return policy === undefined
|
|
22
47
|
? undefined
|
|
@@ -28,57 +53,61 @@ export function getMarkMaterializationStep(program, layer) {
|
|
|
28
53
|
if (policy === undefined || !policy.canMaterialize(program, layer)) {
|
|
29
54
|
return undefined;
|
|
30
55
|
}
|
|
31
|
-
return getMarkRematerializationStep(layer);
|
|
56
|
+
return getMarkRematerializationStep(program, layer);
|
|
32
57
|
}
|
|
33
58
|
|
|
34
59
|
export function getSourceDependentMarkSteps(program, sourceId) {
|
|
35
|
-
return program.semanticSpec.layers.flatMap(layer =>
|
|
36
|
-
layer.source === sourceId &&
|
|
37
|
-
|
|
38
|
-
|
|
60
|
+
return (program.semanticSpec.layers ?? []).flatMap(layer => {
|
|
61
|
+
const sourceOwned = layer.source === sourceId &&
|
|
62
|
+
getMarkMaterializationPolicy(layer)?.sourceDependent === true;
|
|
63
|
+
const statistical = program.markConfigs?.[layer.id]
|
|
64
|
+
?.statisticalReference?.source === sourceId;
|
|
65
|
+
return sourceOwned || statistical
|
|
66
|
+
? [getMarkMaterializationStep(program, layer) ??
|
|
67
|
+
getExistingMarkRematerializationStep(program, layer)].filter(
|
|
39
68
|
step => step !== undefined
|
|
40
69
|
)
|
|
41
|
-
: []
|
|
42
|
-
);
|
|
70
|
+
: [];
|
|
71
|
+
});
|
|
43
72
|
}
|
|
44
73
|
|
|
45
74
|
export function getPositionEncodingMaterializationSteps(program, layer, scaleId) {
|
|
46
75
|
const policy = getMarkMaterializationPolicy(layer);
|
|
47
76
|
if (policy === undefined) return [];
|
|
48
77
|
const complete = policy.canMaterialize(program, layer);
|
|
49
|
-
const mark = getMarkRematerializationStep(layer);
|
|
78
|
+
const mark = getMarkRematerializationStep(program, layer);
|
|
50
79
|
const sharedConsumerMarks = (program.semanticSpec.layers ?? [])
|
|
51
80
|
.filter(candidate =>
|
|
52
81
|
candidate.id !== layer.id &&
|
|
82
|
+
!isSourceOwnedText(candidate) &&
|
|
53
83
|
POSITION_CHANNELS.some(channel =>
|
|
54
84
|
candidate.encoding?.[channel]?.scale === scaleId
|
|
55
85
|
)
|
|
56
86
|
)
|
|
57
87
|
.map(candidate => getMarkMaterializationStep(program, candidate))
|
|
58
88
|
.filter(step => step !== undefined);
|
|
89
|
+
const dependentMarks = [mark, ...sharedConsumerMarks]
|
|
90
|
+
.flatMap(step => getSourceDependentMarkSteps(program, step.args.id));
|
|
59
91
|
const scale = {
|
|
60
92
|
op: "rematerializeScale",
|
|
61
93
|
args: sharedConsumerMarks.length === 0
|
|
62
94
|
? { id: scaleId }
|
|
63
95
|
: { id: scaleId, marks: false }
|
|
64
96
|
};
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
return policy.positionEncoding.scaleFirst
|
|
97
|
+
const direct = !complete && policy.positionEncoding.incomplete === "scale"
|
|
98
|
+
? [scale, ...sharedConsumerMarks]
|
|
99
|
+
: policy.positionEncoding.scaleFirst
|
|
70
100
|
? [scale, mark, ...sharedConsumerMarks]
|
|
71
101
|
: [mark, ...sharedConsumerMarks];
|
|
72
|
-
}
|
|
73
|
-
return policy.positionEncoding.scaleFirst
|
|
74
|
-
? [scale, mark, ...sharedConsumerMarks]
|
|
75
|
-
: [mark, ...sharedConsumerMarks];
|
|
102
|
+
return buildMaterializationPlan({ marks: [...direct, ...dependentMarks] });
|
|
76
103
|
}
|
|
77
104
|
|
|
78
105
|
export function getScaleConsumerMaterializationMode(layer, channel) {
|
|
79
106
|
const policy = getMarkMaterializationPolicy(layer);
|
|
80
107
|
if (policy === undefined) return "direct";
|
|
81
108
|
if (POSITION_ENCODING_CHANNELS.includes(channel)) {
|
|
109
|
+
// Attached labels require their source's completed geometry, after scales.
|
|
110
|
+
if (layer.source !== undefined && policy.sourceDependent === true) return "defer";
|
|
82
111
|
return policy.scaleApplication.position ?? policy.scaleApplication.default;
|
|
83
112
|
}
|
|
84
113
|
if (policy.scaleApplication.deferredChannels?.includes(channel)) {
|
|
@@ -99,7 +128,7 @@ export function getExistingMarkRematerializationStep(program, layer) {
|
|
|
99
128
|
) {
|
|
100
129
|
return undefined;
|
|
101
130
|
}
|
|
102
|
-
return getMarkRematerializationStep(layer);
|
|
131
|
+
return getMarkRematerializationStep(program, layer);
|
|
103
132
|
}
|
|
104
133
|
|
|
105
134
|
export function getEncodingMaterializationStages(program, layer, channel, scale) {
|
|
@@ -111,22 +140,34 @@ export function getEncodingMaterializationStages(program, layer, channel, scale)
|
|
|
111
140
|
args: { id: scale, guides: false, marks: false }
|
|
112
141
|
}]
|
|
113
142
|
: [];
|
|
114
|
-
const shared =
|
|
115
|
-
|
|
143
|
+
const shared = scale !== undefined && (
|
|
144
|
+
scales.length > 0 || policy.encoding?.sharedChannels?.includes(channel) === true
|
|
145
|
+
);
|
|
116
146
|
const candidates = shared
|
|
117
147
|
? program.semanticSpec.layers.filter(candidate =>
|
|
118
|
-
candidate.
|
|
119
|
-
candidate.encoding?.[channel]?.scale === scale
|
|
148
|
+
candidate.id === layer.id || candidate.encoding?.[channel]?.scale === scale
|
|
120
149
|
)
|
|
121
150
|
: [layer];
|
|
122
151
|
const marks = candidates.flatMap(candidate => {
|
|
123
|
-
|
|
152
|
+
const candidatePolicy = getMarkMaterializationPolicy(candidate);
|
|
153
|
+
if (candidatePolicy?.encoding?.skipRematerialization?.(program, candidate) === true) {
|
|
124
154
|
return [];
|
|
125
155
|
}
|
|
126
|
-
const step =
|
|
156
|
+
const step = candidate.id !== layer.id || candidatePolicy?.encoding?.completeOnly === true
|
|
127
157
|
? getMarkMaterializationStep(program, candidate)
|
|
128
|
-
: getMarkRematerializationStep(candidate);
|
|
158
|
+
: getMarkRematerializationStep(program, candidate);
|
|
129
159
|
return step === undefined ? [] : [step];
|
|
130
160
|
});
|
|
131
161
|
return { scales, marks };
|
|
132
162
|
}
|
|
163
|
+
|
|
164
|
+
// Reuse a plan's scale stage only when it covers every scale consumed by the mark.
|
|
165
|
+
export function reusePlannedMarkScales(program, steps, scaleIds) {
|
|
166
|
+
const resolved = new Set(scaleIds);
|
|
167
|
+
return steps.map(step => {
|
|
168
|
+
const layer = findLayer(program, step.args?.id);
|
|
169
|
+
if (layer === undefined || getMarkMaterializationPolicy(layer)?.acceptsResolvedScales !== true ||
|
|
170
|
+
!getLayerScaleIds(layer).every(id => resolved.has(id))) return step;
|
|
171
|
+
return { ...step, args: { ...step.args, scales: false } };
|
|
172
|
+
});
|
|
173
|
+
}
|
|
@@ -40,7 +40,9 @@ const MARK_MATERIALIZATION_POLICIES = Object.freeze({
|
|
|
40
40
|
line: Object.freeze({
|
|
41
41
|
canMaterialize: canMaterializeLine,
|
|
42
42
|
op: "rematerializeLineMark",
|
|
43
|
+
acceptsResolvedScales: true,
|
|
43
44
|
positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: false }),
|
|
45
|
+
encoding: Object.freeze({ scaleFirst: true, completeOnly: true }),
|
|
44
46
|
scaleApplication: Object.freeze({ default: "defer" })
|
|
45
47
|
}),
|
|
46
48
|
area: Object.freeze({
|
|
@@ -48,7 +50,7 @@ const MARK_MATERIALIZATION_POLICIES = Object.freeze({
|
|
|
48
50
|
op: "rematerializeAreaMark",
|
|
49
51
|
positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: true }),
|
|
50
52
|
encoding: Object.freeze({
|
|
51
|
-
sharedChannels: Object.freeze(["color"]),
|
|
53
|
+
sharedChannels: Object.freeze(["color", "stroke"]),
|
|
52
54
|
skipRematerialization: isIntentionallyEmptyArea
|
|
53
55
|
}),
|
|
54
56
|
scaleApplication: Object.freeze({ default: "defer" })
|
|
@@ -56,6 +58,7 @@ const MARK_MATERIALIZATION_POLICIES = Object.freeze({
|
|
|
56
58
|
arc: Object.freeze({
|
|
57
59
|
canMaterialize: canMaterializeArc,
|
|
58
60
|
op: "rematerializeArcMark",
|
|
61
|
+
acceptsResolvedScales: true,
|
|
59
62
|
positionEncoding: Object.freeze({ incomplete: "scale", scaleFirst: false }),
|
|
60
63
|
encoding: Object.freeze({ completeOnly: true }),
|
|
61
64
|
scaleApplication: Object.freeze({ default: "defer" })
|
|
@@ -2,28 +2,42 @@ import {
|
|
|
2
2
|
mapOrdinalPositionValues,
|
|
3
3
|
readScaleField
|
|
4
4
|
} from "../grammar/scales/index.js";
|
|
5
|
-
import {
|
|
5
|
+
import { normalizePositionDatum } from "../grammar/positionDatum.js";
|
|
6
|
+
import { resolveCoordinateBounds } from "./coordinateBounds.js";
|
|
7
|
+
import { RECT_MODES, resolveRectMode, rectUsesFields } from "../grammar/rects.js";
|
|
6
8
|
import { DEFAULT_RECT_MARK } from "./rectConfig.js";
|
|
7
9
|
import { mapScaleConsumerValues } from "./scales/map.js";
|
|
10
|
+
import { materializeRectItem } from "../grammar/roundedRect.js";
|
|
11
|
+
import { requestedStrokeDetails } from "../grammar/strokeStyle.js";
|
|
8
12
|
|
|
9
|
-
function optionalValues(rows, encoding) {
|
|
13
|
+
function optionalValues(rows, encoding, length, channel) {
|
|
14
|
+
if (Object.hasOwn(encoding, "datum")) {
|
|
15
|
+
const value = normalizePositionDatum(encoding.datum, encoding.fieldType, channel, encoding.temporalUnit, "Rect");
|
|
16
|
+
return Array.from({ length }, () => value);
|
|
17
|
+
}
|
|
10
18
|
return readScaleField(rows, encoding.field, encoding.fieldType, {
|
|
11
|
-
allowUnknown: true
|
|
19
|
+
allowUnknown: true, temporalUnit: encoding.temporalUnit
|
|
12
20
|
});
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
function requiredChannels(layer, mode) {
|
|
16
|
-
|
|
17
|
-
? ["
|
|
18
|
-
: ["x", "y",
|
|
24
|
+
const position = mode === RECT_MODES.xSpan ? ["x", "x2"]
|
|
25
|
+
: mode === RECT_MODES.ySpan ? ["y", "y2"]
|
|
26
|
+
: mode === RECT_MODES.ranged ? ["x", "x2", "y", "y2"] : ["x", "y"];
|
|
27
|
+
return [
|
|
28
|
+
...position,
|
|
29
|
+
...(layer.encoding?.color ? ["color"] : []),
|
|
30
|
+
...(layer.encoding?.stroke ? ["stroke"] : [])
|
|
31
|
+
];
|
|
19
32
|
}
|
|
20
33
|
|
|
21
34
|
export function resolveRectConsumerValues(layer, dataset, channel) {
|
|
22
|
-
const
|
|
35
|
+
const length = rectUsesFields(layer) ? dataset.values.length : 1;
|
|
36
|
+
const requested = optionalValues(dataset.values, layer.encoding[channel], length, channel);
|
|
23
37
|
const mode = resolveRectMode(layer);
|
|
24
38
|
if (mode === undefined) return requested;
|
|
25
39
|
const complete = requiredChannels(layer, mode).map(candidate =>
|
|
26
|
-
optionalValues(dataset.values, layer.encoding[candidate])
|
|
40
|
+
optionalValues(dataset.values, layer.encoding[candidate], length, candidate)
|
|
27
41
|
);
|
|
28
42
|
return requested.map((value, index) =>
|
|
29
43
|
complete.every(values => values[index] !== undefined) ? value : undefined
|
|
@@ -56,37 +70,42 @@ function mappedEncoding(program, layer, dataset, channel) {
|
|
|
56
70
|
};
|
|
57
71
|
}
|
|
58
72
|
|
|
59
|
-
function appearance(config, fill) {
|
|
73
|
+
function appearance(config, fill, stroke) {
|
|
60
74
|
return {
|
|
61
75
|
fill,
|
|
62
76
|
opacity: config.opacity,
|
|
63
|
-
stroke: config.stroke === false ? "transparent" : config.stroke,
|
|
64
|
-
strokeWidth:
|
|
77
|
+
stroke: stroke ?? (config.stroke === false ? "transparent" : config.stroke),
|
|
78
|
+
strokeWidth: stroke === undefined && config.stroke === false
|
|
79
|
+
? 0
|
|
80
|
+
: config.strokeWidth ?? 1,
|
|
81
|
+
...requestedStrokeDetails(config, "Rect mark")
|
|
65
82
|
};
|
|
66
83
|
}
|
|
67
84
|
|
|
68
85
|
export function resolveRectRows(program, layer, dataset) {
|
|
69
86
|
const mode = resolveRectMode(layer);
|
|
70
87
|
if (mode === undefined) return [];
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const y2 = mode === RECT_MODES.ranged
|
|
77
|
-
? mappedEncoding(program, layer, dataset, "y2")
|
|
78
|
-
: undefined;
|
|
88
|
+
const bounds = resolveCoordinateBounds(program, layer.coordinate);
|
|
89
|
+
const x = layer.encoding.x === undefined ? undefined : mappedEncoding(program, layer, dataset, "x");
|
|
90
|
+
const y = layer.encoding.y === undefined ? undefined : mappedEncoding(program, layer, dataset, "y");
|
|
91
|
+
const x2 = layer.encoding.x2 === undefined ? undefined : mappedEncoding(program, layer, dataset, "x2");
|
|
92
|
+
const y2 = layer.encoding.y2 === undefined ? undefined : mappedEncoding(program, layer, dataset, "y2");
|
|
79
93
|
const color = layer.encoding?.color === undefined
|
|
80
94
|
? undefined
|
|
81
95
|
: mappedEncoding(program, layer, dataset, "color").values;
|
|
96
|
+
const stroke = layer.encoding?.stroke === undefined
|
|
97
|
+
? undefined
|
|
98
|
+
: mappedEncoding(program, layer, dataset, "stroke").values;
|
|
82
99
|
const config = program.markConfigs[layer.id] ?? DEFAULT_RECT_MARK;
|
|
83
100
|
|
|
84
|
-
|
|
101
|
+
const rows = rectUsesFields(layer) ? dataset.values : [{}];
|
|
102
|
+
return rows.flatMap((row, index) => {
|
|
85
103
|
const fill = color === undefined ? config.fill : color[index];
|
|
86
104
|
if (
|
|
87
|
-
!Number.isFinite(x.values[index]) ||
|
|
88
|
-
!Number.isFinite(y.values[index]) ||
|
|
89
|
-
typeof fill !== "string"
|
|
105
|
+
(x !== undefined && !Number.isFinite(x.values[index])) ||
|
|
106
|
+
(y !== undefined && !Number.isFinite(y.values[index])) ||
|
|
107
|
+
typeof fill !== "string" ||
|
|
108
|
+
(stroke !== undefined && typeof stroke[index] !== "string")
|
|
90
109
|
) return [];
|
|
91
110
|
|
|
92
111
|
let geometry;
|
|
@@ -103,36 +122,30 @@ export function resolveRectRows(program, layer, dataset) {
|
|
|
103
122
|
height
|
|
104
123
|
};
|
|
105
124
|
} else {
|
|
106
|
-
const
|
|
107
|
-
const
|
|
125
|
+
const startX = x?.values[index] ?? bounds.x;
|
|
126
|
+
const startY = y?.values[index] ?? bounds.y;
|
|
127
|
+
const endX = x2?.values[index] ?? bounds.x + bounds.width;
|
|
128
|
+
const endY = y2?.values[index] ?? bounds.y + bounds.height;
|
|
108
129
|
if (!Number.isFinite(endX) || !Number.isFinite(endY)) return [];
|
|
109
130
|
geometry = {
|
|
110
|
-
x: Math.min(
|
|
111
|
-
y: Math.min(
|
|
112
|
-
width: Math.abs(endX -
|
|
113
|
-
height: Math.abs(endY -
|
|
131
|
+
x: Math.min(startX, endX),
|
|
132
|
+
y: Math.min(startY, endY),
|
|
133
|
+
width: Math.abs(endX - startX),
|
|
134
|
+
height: Math.abs(endY - startY)
|
|
114
135
|
};
|
|
115
136
|
if (geometry.width <= 0 || geometry.height <= 0) return [];
|
|
116
137
|
}
|
|
117
138
|
return [{
|
|
118
139
|
row,
|
|
119
140
|
sourceIndex: index,
|
|
120
|
-
|
|
121
|
-
["x", "y", "x2", "y2", "color"].flatMap(channel => {
|
|
122
|
-
const encoding = layer.encoding?.[channel];
|
|
123
|
-
return encoding?.field === undefined
|
|
124
|
-
? []
|
|
125
|
-
: [[channel, row[encoding.field]]];
|
|
126
|
-
})
|
|
127
|
-
),
|
|
128
|
-
properties: { ...geometry, ...appearance(config, fill) }
|
|
141
|
+
properties: { ...geometry, ...appearance(config, fill, stroke?.[index]) }
|
|
129
142
|
}];
|
|
130
143
|
});
|
|
131
144
|
}
|
|
132
145
|
|
|
133
146
|
export function resolveRectGraphicItems(program, layer, dataset) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
147
|
+
const radius = program.markConfigs[layer.id]?.cornerRadius ?? 0;
|
|
148
|
+
return resolveRectRows(program, layer, dataset).map(item =>
|
|
149
|
+
materializeRectItem(item.properties, radius)
|
|
150
|
+
);
|
|
138
151
|
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
validateUnitInterval
|
|
5
5
|
} from "../core/validation.js";
|
|
6
6
|
import { DEFAULT_COLORS } from "../theme/defaults.js";
|
|
7
|
+
import { requestedRectStyleDetails } from "../grammar/roundedRect.js";
|
|
7
8
|
|
|
8
9
|
export const DEFAULT_RECT_MARK = Object.freeze({
|
|
9
10
|
fill: DEFAULT_COLORS.mark,
|
|
@@ -13,7 +14,10 @@ export const DEFAULT_RECT_MARK = Object.freeze({
|
|
|
13
14
|
});
|
|
14
15
|
|
|
15
16
|
export function normalizeRectMarkConfig(patch, previous = DEFAULT_RECT_MARK) {
|
|
16
|
-
const result = {
|
|
17
|
+
const result = {
|
|
18
|
+
...previous,
|
|
19
|
+
...requestedRectStyleDetails(patch, "Rect")
|
|
20
|
+
};
|
|
17
21
|
if (Object.hasOwn(patch, "fill")) {
|
|
18
22
|
result.fill = validateNonEmptyString(patch.fill, "Rect fill");
|
|
19
23
|
}
|
|
@@ -5,15 +5,13 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
mapOrdinalOffsetValues,
|
|
7
7
|
mapOrdinalPositionValues,
|
|
8
|
-
|
|
9
|
-
readQuantitativeField,
|
|
10
|
-
readScaleField,
|
|
11
|
-
readTemporalField
|
|
8
|
+
readScaleField
|
|
12
9
|
} from "../grammar/scales/index.js";
|
|
13
10
|
import {
|
|
14
11
|
finiteMidpoint,
|
|
15
12
|
requireFiniteResult
|
|
16
13
|
} from "../grammar/numeric.js";
|
|
14
|
+
import { normalizePositionDatum } from "../grammar/positionDatum.js";
|
|
17
15
|
import { mapScaleConsumerValues } from "./scales/map.js";
|
|
18
16
|
|
|
19
17
|
export function resolveRowEncodingValues(program, layer, dataset, channel) {
|
|
@@ -26,15 +24,17 @@ export function resolveRowEncodingValues(program, layer, dataset, channel) {
|
|
|
26
24
|
);
|
|
27
25
|
}
|
|
28
26
|
const categorical = ["nominal", "ordinal"].includes(encoding.fieldType);
|
|
29
|
-
const values = Object.hasOwn(
|
|
30
|
-
?
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
const values = Object.hasOwn(encoding, "datum")
|
|
28
|
+
? Array(dataset.values.length).fill(normalizePositionDatum(
|
|
29
|
+
encoding.datum,
|
|
30
|
+
encoding.fieldType,
|
|
31
|
+
channel,
|
|
32
|
+
encoding.temporalUnit,
|
|
33
|
+
layer.mark.type[0].toUpperCase() + layer.mark.type.slice(1)
|
|
34
|
+
))
|
|
35
|
+
: readScaleField(dataset.values, encoding.field, encoding.fieldType, {
|
|
36
|
+
allowUnknown: Object.hasOwn(scale, "unknown"), temporalUnit: encoding.temporalUnit
|
|
37
|
+
});
|
|
38
38
|
if (categorical && OFFSET_POSITION_CHANNELS.includes(channel)) {
|
|
39
39
|
return mapOrdinalOffsetValues(values, scale);
|
|
40
40
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../grammar/text.js";
|
|
1
2
|
import { POSITION_ENCODING_CHANNELS } from "../core/vocabulary.js";
|
|
2
3
|
import { materializedLegendUsesScale } from "./legends.js";
|
|
3
4
|
import { buildMaterializationPlan } from "./planner.js";
|
|
@@ -19,10 +20,12 @@ const GRIDS = Object.freeze([
|
|
|
19
20
|
|
|
20
21
|
function usesPositionalScale(program, id) {
|
|
21
22
|
return program.semanticSpec.layers.some(layer =>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
!isSourceOwnedText(layer) && (
|
|
24
|
+
POSITION_ENCODING_CHANNELS.some(
|
|
25
|
+
channel => layer.encoding?.[channel]?.scale === id
|
|
26
|
+
) || (layer.encoding?.parallel?.dimensions ?? []).some(
|
|
27
|
+
dimension => dimension.scale === id
|
|
28
|
+
)
|
|
26
29
|
)
|
|
27
30
|
);
|
|
28
31
|
}
|
|
@@ -50,7 +53,7 @@ export function needsCanvasScaleRematerialization(program, scale) {
|
|
|
50
53
|
(scale.range === "auto" ||
|
|
51
54
|
usesRadialScale(program, scale.id) ||
|
|
52
55
|
semanticGuideUsesScale(program, scale.id)) &&
|
|
53
|
-
program.resolvedScales[scale.id] !== undefined &&
|
|
56
|
+
(program.resolvedScales[scale.id] !== undefined || scale.radialMapping !== undefined) &&
|
|
54
57
|
usesPositionalScale(program, scale.id)
|
|
55
58
|
);
|
|
56
59
|
}
|
|
@@ -4,21 +4,24 @@ import {
|
|
|
4
4
|
isOrdinalScaleType,
|
|
5
5
|
mapContinuousScaleValues,
|
|
6
6
|
mapDiscretizedColors,
|
|
7
|
+
mapSizeValues,
|
|
7
8
|
mapOrdinalValues,
|
|
8
9
|
mapSequentialColors
|
|
9
10
|
} from "../../grammar/scales/index.js";
|
|
10
11
|
|
|
11
12
|
export function mapScaleConsumerValues(values, resolvedScale, channel) {
|
|
12
|
-
if (channel === "
|
|
13
|
+
if (channel === "size") return mapSizeValues(values, resolvedScale);
|
|
14
|
+
if (["color", "stroke"].includes(channel) && isDiscretizedColorScaleType(resolvedScale.type)) {
|
|
13
15
|
return mapDiscretizedColors(values, resolvedScale);
|
|
14
16
|
}
|
|
15
|
-
if (
|
|
17
|
+
if (["color", "stroke"].includes(channel) && isContinuousColorScaleType(resolvedScale.type)) {
|
|
16
18
|
return mapSequentialColors(
|
|
17
19
|
values,
|
|
18
20
|
resolvedScale.domain,
|
|
19
21
|
resolvedScale.range,
|
|
20
22
|
{
|
|
21
23
|
interpolation: resolvedScale.interpolate,
|
|
24
|
+
midpoint: resolvedScale.midpoint,
|
|
22
25
|
clamp: resolvedScale.clamp ?? false,
|
|
23
26
|
...(Object.hasOwn(resolvedScale, "unknown")
|
|
24
27
|
? { unknown: resolvedScale.unknown }
|
|
@@ -27,7 +30,7 @@ export function mapScaleConsumerValues(values, resolvedScale, channel) {
|
|
|
27
30
|
);
|
|
28
31
|
}
|
|
29
32
|
if (
|
|
30
|
-
["color", "strokeDash", "shape"].includes(channel) &&
|
|
33
|
+
["color", "stroke", "strokeDash", "shape"].includes(channel) &&
|
|
31
34
|
isOrdinalScaleType(resolvedScale.type)
|
|
32
35
|
) {
|
|
33
36
|
return mapOrdinalValues(values, resolvedScale.domain, resolvedScale.range, {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateMeasuredRadiusScale } from "../../../grammar/scales/radial.js";
|
|
1
2
|
export function resolveArcAutoPositionRange({
|
|
2
3
|
consumers,
|
|
3
4
|
scale,
|
|
@@ -35,3 +36,44 @@ export function resolveArcAutoPositionRange({
|
|
|
35
36
|
}
|
|
36
37
|
return range;
|
|
37
38
|
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export function resolveMeasuredRadiusDomain({ scale, channel, allValues }) {
|
|
42
|
+
if (scale.radialMapping === undefined) return undefined;
|
|
43
|
+
if (channel !== "radius") throw new Error("Measured radius scales support only radius consumers.");
|
|
44
|
+
const maximum = allValues.reduce((maximum, value) => Math.max(maximum, value), -Infinity);
|
|
45
|
+
if (!Number.isFinite(maximum) || maximum <= 0 || allValues.some(value => value < 0)) {
|
|
46
|
+
throw new Error("Measured radius requires finite non-negative aggregates and a positive maximum.");
|
|
47
|
+
}
|
|
48
|
+
const domain = scale.domain === "auto" ? [0, maximum] : scale.domain;
|
|
49
|
+
if (Array.isArray(domain) && domain[1] < maximum) {
|
|
50
|
+
throw new RangeError("Measured radius domain maximum must cover every category aggregate.");
|
|
51
|
+
}
|
|
52
|
+
return domain;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function validateMeasuredRadiusConsumers({ scale, domain, range, consumers, markConfigs, thetaScales }) {
|
|
56
|
+
if (scale.radialMapping === undefined) return;
|
|
57
|
+
validateMeasuredRadiusScale({ ...scale, domain, range }, { allowAuto: true });
|
|
58
|
+
for (const consumer of consumers) {
|
|
59
|
+
const { layer, encoding } = consumer;
|
|
60
|
+
const theta = layer.encoding?.theta;
|
|
61
|
+
const thetaScale = thetaScales[layer.id];
|
|
62
|
+
if (!isPendingMeasuredRadiusConsumer(consumer) && (layer.mark?.type !== "arc" || !["count", "sum"].includes(encoding.aggregate) ||
|
|
63
|
+
!["nominal", "ordinal"].includes(theta?.fieldType) || theta.aggregate !== undefined ||
|
|
64
|
+
thetaScale?.type !== "band" || (thetaScale.paddingInner ?? 0) !== 0 || (thetaScale.paddingOuter ?? 0) !== 0)) {
|
|
65
|
+
throw new Error("Measured radius requires only equal-angle categorical Arc consumers.");
|
|
66
|
+
}
|
|
67
|
+
const config = markConfigs[layer.id] ?? {};
|
|
68
|
+
if ((config.padAngle ?? 0) !== 0) throw new Error("Measured Arc requires padAngle 0.");
|
|
69
|
+
if (scale.range !== "auto" && config.innerRadiusExplicit === true &&
|
|
70
|
+
Math.abs(config.innerRadius - range[0] / range[1]) > Number.EPSILON * 8) {
|
|
71
|
+
throw new Error("Measured Arc innerRadius must agree with its explicit radius range.");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function isPendingMeasuredRadiusConsumer({ layer, channel, encoding }) {
|
|
77
|
+
return layer.mark?.type === "arc" && channel === "radius" &&
|
|
78
|
+
["count", "sum"].includes(encoding.aggregate) && layer.encoding?.theta === undefined;
|
|
79
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../../../grammar/text.js";
|
|
1
2
|
import { resolveHistogramBins } from "../../../grammar/histogram.js";
|
|
2
3
|
|
|
3
4
|
export function resolveBinnedPositionDomain({
|
|
4
5
|
valuesByConsumer,
|
|
5
6
|
channel,
|
|
6
7
|
scale,
|
|
7
|
-
id
|
|
8
|
-
allValues
|
|
8
|
+
id
|
|
9
9
|
}) {
|
|
10
10
|
const binnedPositions = valuesByConsumer.filter(
|
|
11
11
|
({ consumer }) =>
|
|
@@ -13,7 +13,10 @@ export function resolveBinnedPositionDomain({
|
|
|
13
13
|
consumer.encoding.bin !== undefined
|
|
14
14
|
);
|
|
15
15
|
if (binnedPositions.length === 0) return undefined;
|
|
16
|
-
|
|
16
|
+
const independentConsumers = valuesByConsumer.filter(({ consumer, scalePolicy }) =>
|
|
17
|
+
scalePolicy !== false && !isSourceOwnedText(consumer.layer)
|
|
18
|
+
);
|
|
19
|
+
if (channel !== "x" || binnedPositions.length !== independentConsumers.length) {
|
|
17
20
|
throw new Error(
|
|
18
21
|
`Binned scale "${id}" cannot be shared with an unbinned consumer.`
|
|
19
22
|
);
|
|
@@ -25,7 +28,7 @@ export function resolveBinnedPositionDomain({
|
|
|
25
28
|
throw new Error(`Binned scale "${id}" requires one shared bin definition.`);
|
|
26
29
|
}
|
|
27
30
|
return resolveHistogramBins({
|
|
28
|
-
values:
|
|
31
|
+
values: binnedPositions.flatMap(item => item.values).filter(value => value !== undefined),
|
|
29
32
|
bin: binnedPositions[0].consumer.encoding.bin,
|
|
30
33
|
domain: scale.domain,
|
|
31
34
|
nice: scale.nice ?? true,
|