ggaction 0.0.13 → 0.0.16
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 +146 -0
- package/README.md +41 -12
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-packet.schema.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +39 -7
- package/src/BasicChartProgram.js +3 -1
- package/src/ChartProgram.js +2 -0
- package/src/accessibility.js +157 -0
- package/src/actions/basic.js +2 -2
- package/src/actions/boxPlots/components.js +20 -12
- package/src/actions/boxPlots/create.js +4 -5
- package/src/actions/boxPlots/edit.js +34 -9
- package/src/actions/boxPlots/materialize.js +12 -4
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/canvas/actions.js +3 -3
- package/src/actions/canvas/fitting.js +1 -1
- package/src/actions/categoryOrder/index.js +21 -12
- package/src/actions/charts/area.js +6 -2
- package/src/actions/charts/bar.js +3 -2
- package/src/actions/charts/beeswarm.js +1 -1
- package/src/actions/charts/density.js +7 -3
- package/src/actions/charts/ecdf.js +6 -3
- package/src/actions/charts/endpoints.js +29 -7
- package/src/actions/charts/heatmap.js +3 -2
- package/src/actions/charts/histogram.js +5 -4
- package/src/actions/charts/horizon.js +4 -2
- package/src/actions/charts/interval-regression.js +7 -4
- package/src/actions/charts/line.js +4 -2
- package/src/actions/charts/parallel.js +9 -3
- package/src/actions/charts/pie.js +11 -3
- package/src/actions/charts/polar.js +17 -6
- package/src/actions/charts/radar.js +9 -3
- package/src/actions/charts/radial.js +12 -4
- package/src/actions/charts/raincloud.js +2 -2
- package/src/actions/charts/rug-strip.js +8 -4
- package/src/actions/charts/scatter.js +4 -2
- package/src/actions/composition/actions.js +29 -24
- package/src/actions/coordinates/actions.js +2 -2
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/basicBin2d.js +4 -5
- package/src/actions/data/bin.js +3 -3
- package/src/actions/data/bin2d.js +63 -193
- package/src/actions/data/bin2dMaterialize.js +4 -5
- package/src/actions/data/box.js +4 -4
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +4 -4
- package/src/actions/data/create.js +7 -14
- package/src/actions/data/density.js +15 -23
- package/src/actions/data/derived.js +57 -31
- package/src/actions/data/ecdf.js +2 -2
- package/src/actions/data/edit.js +551 -0
- package/src/actions/data/filter.js +28 -22
- package/src/actions/data/fold.js +2 -2
- package/src/actions/data/gradientProfile.js +2 -2
- package/src/actions/data/horizon.js +2 -2
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +62 -47
- package/src/actions/data/interval.js +2 -2
- package/src/actions/data/intervalEdit.js +26 -18
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +21 -27
- package/src/actions/data/revise.js +48 -0
- package/src/actions/data/revisionBindings.js +125 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/stack.js +2 -2
- package/src/actions/data/summary.js +3 -3
- package/src/actions/data/timeUnit.js +6 -3
- package/src/actions/data/window.js +3 -3
- package/src/actions/encodings/angle.js +1 -1
- package/src/actions/encodings/appearance.js +4 -4
- package/src/actions/encodings/barWidth.js +1 -1
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/index.js +3 -2
- package/src/actions/encodings/density.js +7 -4
- package/src/actions/encodings/histogram.js +4 -3
- package/src/actions/encodings/horizon.js +6 -8
- package/src/actions/encodings/index.js +4 -0
- package/src/actions/encodings/offset.js +22 -15
- package/src/actions/encodings/parallel.js +4 -5
- package/src/actions/encodings/pathOrder.js +6 -11
- package/src/actions/encodings/position/apply.js +53 -7
- package/src/actions/encodings/position/index.js +14 -6
- package/src/actions/encodings/position/policies/bar.js +9 -0
- package/src/actions/encodings/position/resolve.js +24 -7
- package/src/actions/encodings/ranged.js +5 -5
- package/src/actions/encodings/remove.js +13 -9
- package/src/actions/encodings/ruleAppearance.js +2 -24
- package/src/actions/encodings/seriesLayout.js +2 -1
- package/src/actions/encodings/shared.js +82 -14
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/encodings/strokeDash.js +2 -2
- package/src/actions/encodings/text.js +1 -1
- package/src/actions/errorBands/create.js +34 -20
- package/src/actions/errorBands/edit.js +34 -137
- package/src/actions/errorBars/create.js +43 -20
- package/src/actions/errorBars/edit.js +29 -125
- package/src/actions/facets/actions.js +267 -73
- package/src/actions/facets/derive.js +99 -28
- package/src/actions/facets/guides.js +4 -5
- package/src/actions/facets/replay.js +4 -5
- package/src/actions/gradientPlots/components.js +15 -8
- package/src/actions/gradientPlots/create.js +4 -5
- package/src/actions/gradientPlots/edit.js +18 -9
- package/src/actions/gradientPlots/materialize.js +5 -3
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/gradientPlots/rebind.js +4 -5
- package/src/actions/guides/applicability.js +8 -2
- package/src/actions/guides/axes/axes.js +23 -4
- package/src/actions/guides/axes/axis.js +2 -2
- package/src/actions/guides/axes/edit.js +2 -2
- package/src/actions/guides/axes/labels.js +67 -21
- package/src/actions/guides/axes/lines.js +15 -9
- package/src/actions/guides/axes/parallel/lifecycle.js +13 -13
- package/src/actions/guides/axes/parallel/resolve.js +6 -2
- package/src/actions/guides/axes/parallel.js +5 -5
- package/src/actions/guides/axes/remove.js +4 -4
- package/src/actions/guides/axes/tickGroups.js +6 -5
- package/src/actions/guides/axes/ticks.js +19 -6
- package/src/actions/guides/axes/titles.js +21 -13
- package/src/actions/guides/grids/grid.js +18 -29
- package/src/actions/guides/grids/resolve.js +7 -2
- package/src/actions/guides/guides.js +20 -4
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +138 -52
- package/src/actions/guides/legends/categorical/components.js +55 -45
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +98 -16
- package/src/actions/guides/legends/categorical/options.js +2 -2
- package/src/actions/guides/legends/categorical/recipes.js +111 -20
- package/src/actions/guides/legends/categorical/resolve.js +12 -8
- package/src/actions/guides/legends/categorical/symbols.js +104 -52
- package/src/actions/guides/legends/continuous/common.js +76 -20
- package/src/actions/guides/legends/continuous/gradient.js +95 -158
- package/src/actions/guides/legends/continuous/index.js +2 -0
- package/src/actions/guides/legends/continuous/interval.js +21 -16
- package/src/actions/guides/legends/continuous/opacity.js +37 -83
- package/src/actions/guides/legends/continuous/stroke.js +225 -0
- package/src/actions/guides/legends/creation.js +14 -1
- package/src/actions/guides/legends/edit.js +159 -60
- package/src/actions/guides/legends/focused.js +5 -5
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +59 -24
- package/src/actions/guides/legends/lifecycle.js +19 -8
- package/src/actions/guides/legends/remove.js +17 -8
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +100 -108
- package/src/actions/guides/legends/strokeWidth.js +38 -92
- package/src/actions/guides/legends/target.js +112 -1
- package/src/actions/guides/legends/transition.js +165 -16
- package/src/actions/guides/polar/axes/facade.js +8 -8
- package/src/actions/guides/polar/axes/labels.js +29 -10
- package/src/actions/guides/polar/axes/lines.js +19 -19
- package/src/actions/guides/polar/axes/shared.js +16 -0
- package/src/actions/guides/polar/axes/ticks.js +10 -16
- package/src/actions/guides/polar/axes/titles.js +6 -6
- package/src/actions/guides/polar/grids.js +12 -14
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/index.js +16 -3
- package/src/actions/intervals/revision.js +126 -0
- package/src/actions/marks/arc/actions.js +80 -22
- package/src/actions/marks/area/actions.js +38 -13
- package/src/actions/marks/area/materialize.js +17 -1
- package/src/actions/marks/bar/create.js +12 -3
- package/src/actions/marks/bar/edit.js +21 -4
- package/src/actions/marks/bar/materialize.js +34 -40
- package/src/actions/marks/index.js +10 -1
- package/src/actions/marks/line/actions.js +40 -17
- package/src/actions/marks/line/materialize.js +29 -10
- package/src/actions/marks/point/create.js +9 -3
- package/src/actions/marks/point/edit.js +19 -7
- package/src/actions/marks/point/jitter.js +6 -8
- package/src/actions/marks/point/materialize.js +48 -22
- package/src/actions/marks/point/packing.js +6 -8
- package/src/actions/marks/rect/actions.js +36 -15
- package/src/actions/marks/references.js +602 -33
- package/src/actions/marks/remove.js +67 -41
- package/src/actions/marks/rule/actions.js +51 -14
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +79 -14
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +157 -17
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +72 -0
- package/src/actions/marks/tick/actions.js +25 -8
- package/src/actions/primitives/createGraphics.js +5 -4
- package/src/actions/primitives/editGraphics.js +31 -4
- package/src/actions/primitives/graphicProperties.js +6 -0
- package/src/actions/primitives/semantic.js +11 -3
- package/src/actions/primitives/semanticAction.js +90 -14
- package/src/actions/primitives/semanticValidation/dataset.js +2 -4
- package/src/actions/primitives/semanticValidation/guide.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +5 -0
- package/src/actions/primitives/semanticValidation/scale.js +28 -7
- package/src/actions/regression/components.js +36 -25
- package/src/actions/regression/create.js +13 -6
- package/src/actions/regression/edit.js +24 -11
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +173 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +1 -1
- package/src/actions/scales/consumers/families.js +3 -2
- package/src/actions/scales/consumers/index.js +19 -2
- package/src/actions/scales/consumers/seriesLayout.js +28 -14
- package/src/actions/scales/create.js +55 -23
- package/src/actions/scales/definitions.js +94 -40
- package/src/actions/scales/edit.js +67 -12
- package/src/actions/scales/editPolicy.js +81 -11
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +5 -4
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/preview.js +111 -10
- package/src/actions/scales/quantitativeColor.js +4 -5
- package/src/actions/selection/actions.js +179 -66
- package/src/actions/selection/index.js +3 -0
- package/src/actions/textMetrics/index.js +56 -0
- package/src/actions/theme/actions.js +70 -15
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +9 -2
- package/src/actions/theme/reconcile.js +313 -121
- package/src/actions/theme/state.js +160 -0
- package/src/actions/titles/actions.js +8 -8
- package/src/actions/titles/resolve.js +3 -3
- package/src/actions/violinPlots/create.js +6 -3
- package/src/actions/violinPlots/edit.js +2 -1
- package/src/core/action.js +117 -35
- package/src/core/compositionState.js +6 -1
- package/src/core/diagnostics.js +16 -0
- package/src/core/extensionRegistry.js +27 -0
- package/src/core/font.js +18 -0
- package/src/core/identifiers.js +5 -4
- package/src/core/immutable.js +10 -3
- package/src/core/optionRequirements.js +8 -0
- package/src/core/programState.js +21 -1
- package/src/core/resourceReferences.js +341 -0
- package/src/core/textMetricProfile.js +28 -0
- package/src/core/textMetrics.js +24 -3
- package/src/core/validation.js +22 -10
- package/src/core/vocabulary.js +16 -3
- package/src/diagnostics.js +1 -0
- package/src/grammar/bars/aggregate.js +4 -1
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bin.js +54 -9
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +265 -64
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +7 -47
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/impute.js +341 -0
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +18 -1
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/pathSeries.js +26 -8
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/parameters.js +23 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/colorConsumers.js +20 -1
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +27 -4
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/scales/types.js +8 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +190 -41
- package/src/grammar/schemas/semanticPath.js +7 -2
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +78 -16
- package/src/grammar/timeUnit.js +216 -5
- package/src/grammar/transformTopology.js +4 -0
- package/src/grammar/transforms.js +394 -28
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +221 -16
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +59 -5
- package/src/layout/labels.js +351 -7
- package/src/layout/legend.js +4 -4
- package/src/layout/legendItems.js +6 -6
- package/src/layout/text.js +10 -9
- package/src/layout/title.js +9 -9
- package/src/materialization/bars/aggregate.js +25 -2
- package/src/materialization/bars/histogram.js +84 -24
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/composition.js +3 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +36 -0
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +9 -3
- package/src/materialization/facetGuides/preparation.js +13 -8
- package/src/materialization/facetHeaders.js +347 -0
- package/src/materialization/facets.js +15 -65
- package/src/materialization/guides/layout.js +3 -3
- package/src/materialization/guides/resources.js +26 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/index.js +11 -5
- package/src/materialization/marks/policies.js +2 -1
- package/src/materialization/rect.js +25 -12
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/scaleGuideDependencies.js +10 -0
- package/src/materialization/scales/map.js +5 -3
- package/src/materialization/scales/policies/binnedPosition.js +2 -2
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +4 -1
- package/src/materialization/scales/resolve.js +22 -6
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +30 -3
- package/src/materialization/selection/items/common.js +20 -5
- package/src/materialization/selection/items/rect.js +28 -4
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +265 -14
- package/src/materialization/typography.js +31 -0
- package/src/mcp/cli.js +5 -3
- package/src/persistence/codec.js +65 -0
- package/src/persistence/validation.js +185 -0
- package/src/persistence.js +74 -0
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/index.js +5 -4
- 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/nativeBackend.js +10 -0
- package/src/renderers/options.js +13 -0
- package/src/renderers/pdf.js +25 -54
- package/src/renderers/png.js +22 -17
- package/src/renderers/svg.js +29 -14
- package/src/renderers/text.js +1 -11
- package/src/selectors/coordinates.js +3 -1
- package/src/selectors/datasets.js +38 -5
- package/src/selectors/layers.js +10 -4
- package/src/selectors/markOwners.js +46 -0
- package/src/selectors/scales.js +6 -4
- package/src/theme/defaults.js +103 -1
- package/src/version.js +2 -0
- package/types/accessibility.d.ts +37 -0
- package/types/basic.d.ts +10 -2
- package/types/diagnostics.d.ts +15 -0
- package/types/index.d.ts +90 -0
- package/types/pdf.d.ts +9 -0
- package/types/persistence.d.ts +7 -0
- package/types/png.d.ts +9 -0
- package/types/program.d.ts +944 -118
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { readSeriesIdentity } from "../../grammar/pathSeries.js";
|
|
2
2
|
import {
|
|
3
|
-
countHistogramBins,
|
|
4
3
|
findHistogramBinIndex,
|
|
5
4
|
resolveHistogramBins
|
|
6
5
|
} from "../../grammar/histogram.js";
|
|
@@ -8,14 +7,55 @@ import {
|
|
|
8
7
|
mapContinuousScaleValues,
|
|
9
8
|
mapOrdinalValues,
|
|
10
9
|
readNominalField,
|
|
11
|
-
readQuantitativeField
|
|
10
|
+
readQuantitativeField,
|
|
11
|
+
readScaleField
|
|
12
12
|
} from "../../grammar/scales/index.js";
|
|
13
|
+
import { mapScaleConsumerValues } from "../scales/map.js";
|
|
13
14
|
import { resolveBarColorLayout } from "../../grammar/bars/policy.js";
|
|
14
15
|
import { layoutSeriesPartition } from "../../grammar/seriesLayout.js";
|
|
15
16
|
import {
|
|
16
17
|
DEFAULT_BAR_FILL,
|
|
17
18
|
resolveBarAppearance
|
|
18
19
|
} from "./resolve.js";
|
|
20
|
+
import {
|
|
21
|
+
readStatisticalWeights,
|
|
22
|
+
sumEntryWeights,
|
|
23
|
+
summarizeStatisticalWeights,
|
|
24
|
+
validateWeightedNumericFields
|
|
25
|
+
} from "../../grammar/weightedStatistics.js";
|
|
26
|
+
|
|
27
|
+
export function resolveHistogramInput(dataset, xEncoding) {
|
|
28
|
+
const allValues = readQuantitativeField(dataset.values, xEncoding.field);
|
|
29
|
+
if (xEncoding.weight === undefined) {
|
|
30
|
+
return {
|
|
31
|
+
rows: dataset.values,
|
|
32
|
+
values: allValues,
|
|
33
|
+
entries: dataset.values.map((row, index) => ({ row, index })),
|
|
34
|
+
mass(entries) {
|
|
35
|
+
return entries.length;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const weights = readStatisticalWeights(
|
|
40
|
+
dataset.values,
|
|
41
|
+
xEncoding.weight,
|
|
42
|
+
"Histogram"
|
|
43
|
+
);
|
|
44
|
+
validateWeightedNumericFields(weights.entries, [xEncoding.field], "Histogram");
|
|
45
|
+
const summary = summarizeStatisticalWeights(
|
|
46
|
+
weights.entries,
|
|
47
|
+
weights.definition.kind,
|
|
48
|
+
"Histogram data"
|
|
49
|
+
);
|
|
50
|
+
return {
|
|
51
|
+
rows: summary.positive.map(entry => entry.row),
|
|
52
|
+
values: summary.positive.map(entry => entry.row[xEncoding.field]),
|
|
53
|
+
entries: summary.positive,
|
|
54
|
+
mass(entries) {
|
|
55
|
+
return sumEntryWeights(entries, summary, "Histogram bin mass");
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
19
59
|
|
|
20
60
|
export function deriveHistogramSegments({
|
|
21
61
|
dataset,
|
|
@@ -24,7 +64,8 @@ export function deriveHistogramSegments({
|
|
|
24
64
|
xScale,
|
|
25
65
|
resolvedScales
|
|
26
66
|
}) {
|
|
27
|
-
const
|
|
67
|
+
const input = resolveHistogramInput(dataset, xEncoding);
|
|
68
|
+
const { rows, values: xValues, entries } = input;
|
|
28
69
|
const bins = resolveHistogramBins({
|
|
29
70
|
values: xValues,
|
|
30
71
|
bin: xEncoding.bin,
|
|
@@ -36,13 +77,13 @@ export function deriveHistogramSegments({
|
|
|
36
77
|
const layout = resolveBarColorLayout(layer);
|
|
37
78
|
|
|
38
79
|
if (layer.encoding?.group !== undefined) {
|
|
39
|
-
const identity = readSeriesIdentity(
|
|
80
|
+
const identity = readSeriesIdentity(rows, layer);
|
|
40
81
|
const cellRows = bins.boundaries.slice(0, -1).map(() => identity.domain.map(() => []));
|
|
41
82
|
const index = new Map(identity.domain.map((value, i) => [value, i]));
|
|
42
83
|
xValues.forEach((value, i) => { const bin = findHistogramBinIndex(value, bins.boundaries);
|
|
43
|
-
if (bin !== -1) cellRows[bin][index.get(identity.values[i])].push(
|
|
44
|
-
return cellRows.flatMap((cells, bin) => layoutSeriesPartition(cells.map(
|
|
45
|
-
const members = cells[segment.index];
|
|
84
|
+
if (bin !== -1) cellRows[bin][index.get(identity.values[i])].push(entries[i]); });
|
|
85
|
+
return cellRows.flatMap((cells, bin) => layoutSeriesPartition(cells.map(input.mass), layout).map(segment => {
|
|
86
|
+
const members = cells[segment.index].map(entry => entry.row);
|
|
46
87
|
let color, colorValue;
|
|
47
88
|
if (colorEncoding !== undefined) {
|
|
48
89
|
const values = readNominalField(members, colorEncoding.field);
|
|
@@ -59,8 +100,13 @@ export function deriveHistogramSegments({
|
|
|
59
100
|
}
|
|
60
101
|
|
|
61
102
|
if (colorEncoding?.scale === undefined) {
|
|
62
|
-
|
|
63
|
-
|
|
103
|
+
const cellRows = bins.boundaries.slice(0, -1).map(() => []);
|
|
104
|
+
xValues.forEach((value, index) => {
|
|
105
|
+
const bin = findHistogramBinIndex(value, bins.boundaries);
|
|
106
|
+
if (bin !== -1) cellRows[bin].push(entries[index]);
|
|
107
|
+
});
|
|
108
|
+
return cellRows.flatMap((cell, bin) =>
|
|
109
|
+
layoutSeriesPartition([input.mass(cell)], layout).map(segment => ({
|
|
64
110
|
bin,
|
|
65
111
|
start: bins.boundaries[bin],
|
|
66
112
|
end: bins.boundaries[bin + 1],
|
|
@@ -68,9 +114,7 @@ export function deriveHistogramSegments({
|
|
|
68
114
|
categoryCount: 1,
|
|
69
115
|
stackStart: segment.start,
|
|
70
116
|
stackEnd: segment.end,
|
|
71
|
-
members:
|
|
72
|
-
findHistogramBinIndex(xValues[index], bins.boundaries) === bin
|
|
73
|
-
)
|
|
117
|
+
members: cell.map(entry => entry.row)
|
|
74
118
|
}))
|
|
75
119
|
);
|
|
76
120
|
}
|
|
@@ -81,24 +125,24 @@ export function deriveHistogramSegments({
|
|
|
81
125
|
`Bar mark "${layer.id}" requires a resolved color scale.`
|
|
82
126
|
);
|
|
83
127
|
}
|
|
84
|
-
const colorValues = readNominalField(
|
|
128
|
+
const colorValues = readNominalField(rows, colorEncoding.field);
|
|
85
129
|
mapOrdinalValues(colorValues, colorScale.domain, colorScale.range);
|
|
86
130
|
const categoryIndex = new Map(
|
|
87
131
|
colorScale.domain.map((value, index) => [value, index])
|
|
88
132
|
);
|
|
89
|
-
const
|
|
90
|
-
colorScale.domain.map(() =>
|
|
133
|
+
const cells = bins.boundaries.slice(0, -1).map(() =>
|
|
134
|
+
colorScale.domain.map(() => [])
|
|
91
135
|
);
|
|
92
136
|
|
|
93
137
|
for (let index = 0; index < xValues.length; index += 1) {
|
|
94
138
|
const bin = findHistogramBinIndex(xValues[index], bins.boundaries);
|
|
95
139
|
const category = categoryIndex.get(colorValues[index]);
|
|
96
|
-
if (bin !== -1 && category !== undefined)
|
|
140
|
+
if (bin !== -1 && category !== undefined) cells[bin][category].push(entries[index]);
|
|
97
141
|
}
|
|
98
142
|
|
|
99
143
|
const segments = [];
|
|
100
|
-
for (let bin = 0; bin <
|
|
101
|
-
const partition = layoutSeriesPartition(
|
|
144
|
+
for (let bin = 0; bin < cells.length; bin += 1) {
|
|
145
|
+
const partition = layoutSeriesPartition(cells[bin].map(input.mass), layout);
|
|
102
146
|
for (const segment of partition) {
|
|
103
147
|
const category = segment.index;
|
|
104
148
|
segments.push({
|
|
@@ -106,13 +150,10 @@ export function deriveHistogramSegments({
|
|
|
106
150
|
start: bins.boundaries[bin],
|
|
107
151
|
end: bins.boundaries[bin + 1],
|
|
108
152
|
category,
|
|
109
|
-
categoryCount:
|
|
153
|
+
categoryCount: cells[bin].length,
|
|
110
154
|
stackStart: segment.start,
|
|
111
155
|
stackEnd: segment.end,
|
|
112
|
-
members:
|
|
113
|
-
findHistogramBinIndex(xValues[index], bins.boundaries) === bin &&
|
|
114
|
-
categoryIndex.get(colorValues[index]) === category
|
|
115
|
-
),
|
|
156
|
+
members: cells[bin][category].map(entry => entry.row),
|
|
116
157
|
color: mapOrdinalValues(
|
|
117
158
|
[colorScale.domain[category]],
|
|
118
159
|
colorScale.domain,
|
|
@@ -135,6 +176,24 @@ export function deriveHistogramRectangles(required, resolved) {
|
|
|
135
176
|
const layout = resolveBarColorLayout(required.layer);
|
|
136
177
|
const config = resolved.markConfigs[required.layer.id] ?? {};
|
|
137
178
|
const appearance = config.barAppearance ?? {};
|
|
179
|
+
const stroke = required.layer.encoding?.stroke;
|
|
180
|
+
const strokeValues = stroke === undefined ? undefined : segments.map(segment => {
|
|
181
|
+
const values = readScaleField(
|
|
182
|
+
segment.members,
|
|
183
|
+
stroke.field,
|
|
184
|
+
stroke.fieldType,
|
|
185
|
+
{ temporalUnit: stroke.temporalUnit }
|
|
186
|
+
);
|
|
187
|
+
if (new Set(values).size !== 1) {
|
|
188
|
+
throw new Error("Histogram stroke requires one value within each bin/series cell.");
|
|
189
|
+
}
|
|
190
|
+
return values[0];
|
|
191
|
+
});
|
|
192
|
+
const strokes = strokeValues === undefined ? undefined : mapScaleConsumerValues(
|
|
193
|
+
strokeValues,
|
|
194
|
+
resolved.resolvedScales[stroke.scale],
|
|
195
|
+
"stroke"
|
|
196
|
+
);
|
|
138
197
|
|
|
139
198
|
return segments.map((segment, index) => {
|
|
140
199
|
const [x1, x2] = mapContinuousScaleValues(
|
|
@@ -164,7 +223,8 @@ export function deriveHistogramRectangles(required, resolved) {
|
|
|
164
223
|
config.fill ??
|
|
165
224
|
existing[index]?.properties.fill ??
|
|
166
225
|
DEFAULT_BAR_FILL,
|
|
167
|
-
...resolveBarAppearance(config, existing[index]?.properties)
|
|
226
|
+
...resolveBarAppearance(config, existing[index]?.properties),
|
|
227
|
+
...(strokes === undefined ? {} : { stroke: strokes[index] })
|
|
168
228
|
};
|
|
169
229
|
});
|
|
170
230
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
mapContinuousScaleValues,
|
|
3
|
+
mapOrdinalPositionValues,
|
|
4
|
+
mapOrdinalValues,
|
|
5
|
+
readScaleField
|
|
6
|
+
} from "../../grammar/scales/index.js";
|
|
7
|
+
import { mapScaleConsumerValues } from "../scales/map.js";
|
|
2
8
|
import { resolveBarWidth } from "../../grammar/bars/geometry.js";
|
|
3
9
|
import {
|
|
4
10
|
DEFAULT_BAR_FILL,
|
|
@@ -36,6 +42,14 @@ export function deriveRangedRectangles(required, program, width) {
|
|
|
36
42
|
const appearanceConfig = color === undefined
|
|
37
43
|
? config
|
|
38
44
|
: { ...config, stroke: undefined };
|
|
45
|
+
const stroke = layer.encoding?.stroke;
|
|
46
|
+
const strokes = stroke === undefined ? undefined : mapScaleConsumerValues(
|
|
47
|
+
readScaleField(dataset.values, stroke.field, stroke.fieldType, {
|
|
48
|
+
temporalUnit: stroke.temporalUnit
|
|
49
|
+
}),
|
|
50
|
+
program.resolvedScales[stroke.scale],
|
|
51
|
+
"stroke"
|
|
52
|
+
);
|
|
39
53
|
return dataset.values.map((_, index) => vertical ? {
|
|
40
54
|
x: centers[index] - band / 2, y: Math.min(first[index], second[index]), width: band,
|
|
41
55
|
height: Math.abs(second[index] - first[index]), fill: fills[index],
|
|
@@ -44,7 +58,8 @@ export function deriveRangedRectangles(required, program, width) {
|
|
|
44
58
|
undefined,
|
|
45
59
|
color === undefined ? DEFAULT_BAR_STROKE : fills[index],
|
|
46
60
|
1
|
|
47
|
-
)
|
|
61
|
+
),
|
|
62
|
+
...(strokes === undefined ? {} : { stroke: strokes[index] })
|
|
48
63
|
} : {
|
|
49
64
|
x: Math.min(first[index], second[index]), y: centers[index] - band / 2,
|
|
50
65
|
width: Math.abs(second[index] - first[index]), height: band, fill: fills[index],
|
|
@@ -53,6 +68,7 @@ export function deriveRangedRectangles(required, program, width) {
|
|
|
53
68
|
undefined,
|
|
54
69
|
color === undefined ? DEFAULT_BAR_STROKE : fills[index],
|
|
55
70
|
1
|
|
56
|
-
)
|
|
71
|
+
),
|
|
72
|
+
...(strokes === undefined ? {} : { stroke: strokes[index] })
|
|
57
73
|
});
|
|
58
74
|
}
|
|
@@ -3,6 +3,7 @@ import { findDataset } from "../../selectors/datasets.js";
|
|
|
3
3
|
import { findLayer } from "../../selectors/layers.js";
|
|
4
4
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
5
5
|
import { BAR_GRAINS, resolveBarGrain } from "../../grammar/bars/policy.js";
|
|
6
|
+
import { requestedStrokeDetails } from "../../grammar/strokeStyle.js";
|
|
6
7
|
|
|
7
8
|
export const DEFAULT_BAR_FILL = DEFAULT_COLORS.mark;
|
|
8
9
|
export const DEFAULT_BAR_STROKE = "white";
|
|
@@ -25,7 +26,8 @@ export function resolveBarAppearance(
|
|
|
25
26
|
? 0
|
|
26
27
|
: appearance.strokeWidth ?? config.strokeWidth ??
|
|
27
28
|
existing?.strokeWidth ?? DEFAULT_BAR_STROKE_WIDTH,
|
|
28
|
-
...(opacity === undefined ? {} : { opacity })
|
|
29
|
+
...(opacity === undefined ? {} : { opacity }),
|
|
30
|
+
...requestedStrokeDetails(appearance, "Bar mark")
|
|
29
31
|
};
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -40,9 +42,10 @@ export function requireCompleteBar(program, id) {
|
|
|
40
42
|
throw new Error(`Bar mark "${id}" requires an existing dataset.`);
|
|
41
43
|
}
|
|
42
44
|
const graphic = program.graphicSpec.objects[id];
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
const roundedCollection = graphic?.type === "collection" &&
|
|
46
|
+
Object.hasOwn(program.markConfigs[id]?.barAppearance ?? {}, "cornerRadius") &&
|
|
47
|
+
graphic.items?.every(child => ["rect", "path"].includes(child.type));
|
|
48
|
+
if (graphic?.type !== "rect" && !roundedCollection) {
|
|
46
49
|
throw new Error(`Bar mark "${id}" requires rect graphics.`);
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -121,6 +121,8 @@ export function clearCompositionChildren(program) {
|
|
|
121
121
|
export function materializeCompositionGraphics(program) {
|
|
122
122
|
program._assertCompositionProgram("materializeCompositionGraphics");
|
|
123
123
|
const layout = resolveCompositionProgramLayout(program);
|
|
124
|
+
const background = program.graphicSpec.objects.canvas?.properties
|
|
125
|
+
?.background ?? "white";
|
|
124
126
|
let next = clearCompositionChildren(program);
|
|
125
127
|
if (next.graphicSpec.objects.canvas === undefined) {
|
|
126
128
|
next = next.createGraphics({ id: "canvas", type: "canvas" });
|
|
@@ -128,7 +130,7 @@ export function materializeCompositionGraphics(program) {
|
|
|
128
130
|
for (const [property, value] of Object.entries({
|
|
129
131
|
width: layout.width,
|
|
130
132
|
height: layout.height,
|
|
131
|
-
background
|
|
133
|
+
background
|
|
132
134
|
})) {
|
|
133
135
|
next = next.editGraphics({ target: "canvas", property, value });
|
|
134
136
|
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../grammar/text.js";
|
|
2
|
+
import { resolvePolarFrame } from "../grammar/polar.js";
|
|
3
|
+
import { resolveEffectiveBounds } from "../layout/aspect.js";
|
|
4
|
+
import { resolveGraphicBounds } from "../layout/canvas.js";
|
|
5
|
+
import { findCoordinate, requireCoordinate } from "../selectors/coordinates.js";
|
|
6
|
+
import { findSemanticScale } from "../selectors/scales.js";
|
|
7
|
+
|
|
8
|
+
const DATA_CHANNELS = Object.freeze(["x", "y"]);
|
|
9
|
+
const SECONDARY_CHANNELS = Object.freeze({ x2: "x", y2: "y" });
|
|
10
|
+
|
|
11
|
+
function samePair(left, right) {
|
|
12
|
+
return left.x === right.x && left.y === right.y;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function resolveDataAspectScalePair(program, target) {
|
|
16
|
+
const coordinate = typeof target === "string"
|
|
17
|
+
? requireCoordinate(program, target)
|
|
18
|
+
: target;
|
|
19
|
+
if (coordinate.type !== "cartesian") {
|
|
20
|
+
throw new Error("Coordinate data aspect requires a Cartesian coordinate.");
|
|
21
|
+
}
|
|
22
|
+
const layers = program.semanticSpec.layers.filter(layer =>
|
|
23
|
+
layer.coordinate === coordinate.id &&
|
|
24
|
+
!isSourceOwnedText(layer) &&
|
|
25
|
+
DATA_CHANNELS.some(channel => layer.encoding?.[channel] !== undefined)
|
|
26
|
+
);
|
|
27
|
+
if (layers.length === 0) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Coordinate "${coordinate.id}" data aspect requires active x and y consumers.`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const pairs = layers.map(layer => {
|
|
33
|
+
for (const channel of DATA_CHANNELS) {
|
|
34
|
+
const encoding = layer.encoding?.[channel];
|
|
35
|
+
if (
|
|
36
|
+
encoding?.scale === undefined ||
|
|
37
|
+
encoding.fieldType !== "quantitative"
|
|
38
|
+
) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Coordinate "${coordinate.id}" data aspect requires quantitative x and y scale consumers.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const [secondary, primary] of Object.entries(SECONDARY_CHANNELS)) {
|
|
45
|
+
const encoding = layer.encoding?.[secondary];
|
|
46
|
+
if (encoding !== undefined && (
|
|
47
|
+
encoding.fieldType !== "quantitative" ||
|
|
48
|
+
encoding.scale !== layer.encoding[primary].scale
|
|
49
|
+
)) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Coordinate "${coordinate.id}" data aspect requires ${secondary} to share its quantitative ${primary} scale.`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { x: layer.encoding.x.scale, y: layer.encoding.y.scale };
|
|
56
|
+
});
|
|
57
|
+
if (pairs.some(pair => !samePair(pair, pairs[0]))) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Coordinate "${coordinate.id}" data aspect requires one consistent x/y scale pair.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const scales = {
|
|
63
|
+
x: findSemanticScale(program, pairs[0].x),
|
|
64
|
+
y: findSemanticScale(program, pairs[0].y)
|
|
65
|
+
};
|
|
66
|
+
for (const channel of DATA_CHANNELS) {
|
|
67
|
+
if (scales[channel]?.type !== "linear") {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Coordinate data aspect requires a linear ${channel} scale.`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return Object.freeze({
|
|
74
|
+
x: pairs[0].x,
|
|
75
|
+
y: pairs[0].y
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function findDataAspectCoordinatesForScale(program, scaleId) {
|
|
80
|
+
return program.semanticSpec.coordinates.filter(coordinate => {
|
|
81
|
+
if (coordinate.aspect?.mode !== "data") return false;
|
|
82
|
+
const pair = resolveDataAspectScalePair(program, coordinate);
|
|
83
|
+
return pair.x === scaleId || pair.y === scaleId;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function sameNumericPair(left, right) {
|
|
88
|
+
return Array.isArray(left) && left.length === 2 &&
|
|
89
|
+
left.every((value, index) => Number.isFinite(value) &&
|
|
90
|
+
Math.abs(value - right[index]) <= 1e-9);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function validateExplicitDataRanges(program, pair, bounds) {
|
|
94
|
+
const expected = {
|
|
95
|
+
x: [bounds.x, bounds.x + bounds.width],
|
|
96
|
+
y: [bounds.y + bounds.height, bounds.y]
|
|
97
|
+
};
|
|
98
|
+
for (const channel of DATA_CHANNELS) {
|
|
99
|
+
const scale = findSemanticScale(program, pair[channel]);
|
|
100
|
+
if (scale.range === "auto") continue;
|
|
101
|
+
const directed = scale.reverse === true
|
|
102
|
+
? [...expected[channel]].reverse()
|
|
103
|
+
: expected[channel];
|
|
104
|
+
if (!sameNumericPair(scale.range, directed)) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Coordinate data aspect conflicts with explicit ${channel} scale range "${scale.id}".`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function resolveCoordinateBounds(program, target, {
|
|
113
|
+
resolvedScales = program.resolvedScales
|
|
114
|
+
} = {}) {
|
|
115
|
+
const allocated = resolveGraphicBounds(program);
|
|
116
|
+
if (target === undefined) return allocated;
|
|
117
|
+
const coordinate = requireCoordinate(program, target);
|
|
118
|
+
const aspect = coordinate.aspect;
|
|
119
|
+
if (aspect === undefined || aspect === "auto") return allocated;
|
|
120
|
+
if (aspect.mode !== "data") {
|
|
121
|
+
return resolveEffectiveBounds(allocated, aspect);
|
|
122
|
+
}
|
|
123
|
+
const pair = resolveDataAspectScalePair(program, coordinate);
|
|
124
|
+
const x = resolvedScales[pair.x];
|
|
125
|
+
const y = resolvedScales[pair.y];
|
|
126
|
+
if (x?.type !== "linear" || y?.type !== "linear") {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`Coordinate "${coordinate.id}" data aspect requires resolved linear x and y scales.`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const bounds = resolveEffectiveBounds(allocated, aspect, {
|
|
132
|
+
x: x.domain,
|
|
133
|
+
y: y.domain
|
|
134
|
+
});
|
|
135
|
+
validateExplicitDataRanges(program, pair, bounds);
|
|
136
|
+
return bounds;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function resolveCoordinatePolarFrame(program, target, options) {
|
|
140
|
+
const coordinate = requireCoordinate(program, target);
|
|
141
|
+
if (coordinate.type !== "polar") {
|
|
142
|
+
throw new Error(`Coordinate "${coordinate.id}" is not Polar.`);
|
|
143
|
+
}
|
|
144
|
+
return resolvePolarFrame(
|
|
145
|
+
resolveCoordinateBounds(program, coordinate.id, options),
|
|
146
|
+
coordinate.polarFrame
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function consumerCoordinateIds(consumers) {
|
|
151
|
+
return [...new Set(consumers.map(consumer => consumer.layer.coordinate))];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function sameBounds(left, right) {
|
|
155
|
+
return ["x", "y", "width", "height"].every(
|
|
156
|
+
property => Math.abs(left[property] - right[property]) <= 1e-9
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function resolveScaleConsumerBounds(program, consumers, options) {
|
|
161
|
+
const coordinateIds = consumerCoordinateIds(consumers);
|
|
162
|
+
if (coordinateIds.length === 0) return resolveGraphicBounds(program);
|
|
163
|
+
const allocated = resolveGraphicBounds(program);
|
|
164
|
+
const bounds = coordinateIds.map(id =>
|
|
165
|
+
id === undefined || findCoordinate(program, id) === undefined
|
|
166
|
+
? allocated
|
|
167
|
+
: resolveCoordinateBounds(program, id, options)
|
|
168
|
+
);
|
|
169
|
+
if (bounds.slice(1).some(candidate => !sameBounds(candidate, bounds[0]))) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
"A position scale cannot be shared across coordinates with different effective bounds."
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return bounds[0];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function resolveGuideCoordinateBounds(program, {
|
|
178
|
+
coordinate,
|
|
179
|
+
scale,
|
|
180
|
+
channel,
|
|
181
|
+
resolvedScales = program.resolvedScales
|
|
182
|
+
}) {
|
|
183
|
+
let target = coordinate;
|
|
184
|
+
if (target === undefined && scale !== undefined) {
|
|
185
|
+
const candidates = new Set();
|
|
186
|
+
for (const layer of program.semanticSpec.layers) {
|
|
187
|
+
if (isSourceOwnedText(layer)) continue;
|
|
188
|
+
if (layer.encoding?.[channel]?.scale === scale) {
|
|
189
|
+
candidates.add(layer.coordinate);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (candidates.has(undefined)) {
|
|
193
|
+
throw new Error("Guide scale consumers require stored coordinates.");
|
|
194
|
+
}
|
|
195
|
+
if (candidates.size > 1) {
|
|
196
|
+
throw new Error("Guide bounds found no unique compatible coordinate.");
|
|
197
|
+
}
|
|
198
|
+
target = candidates.values().next().value;
|
|
199
|
+
}
|
|
200
|
+
if (target !== undefined && findCoordinate(program, target) === undefined) {
|
|
201
|
+
throw new Error(`Unknown coordinate "${target}".`);
|
|
202
|
+
}
|
|
203
|
+
return resolveCoordinateBounds(program, target, { resolvedScales });
|
|
204
|
+
}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
planScaleGuideRematerialization
|
|
20
20
|
} from "./scaleGuideDependencies.js";
|
|
21
21
|
import { planLayoutRematerialization } from "./layout.js";
|
|
22
|
+
import { requireCoordinate } from "../selectors/coordinates.js";
|
|
22
23
|
|
|
23
24
|
export function planCanvasRematerialization(program) {
|
|
24
25
|
const marks = [];
|
|
@@ -61,6 +62,41 @@ export function planCanvasRematerialization(program) {
|
|
|
61
62
|
return buildMaterializationPlan({ scales, marks, guides, layout });
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
export function planCoordinateRematerialization(program, target) {
|
|
66
|
+
requireCoordinate(program, target);
|
|
67
|
+
const layers = program.semanticSpec.layers.filter(
|
|
68
|
+
layer => layer.coordinate === target
|
|
69
|
+
);
|
|
70
|
+
const scaleIds = [...new Set(layers.flatMap(layer => [
|
|
71
|
+
...["x", "y", "x2", "y2", "theta", "radius"].map(
|
|
72
|
+
channel => layer.encoding?.[channel]?.scale
|
|
73
|
+
),
|
|
74
|
+
...(layer.encoding?.parallel?.dimensions ?? []).map(
|
|
75
|
+
dimension => dimension.scale
|
|
76
|
+
)
|
|
77
|
+
]).filter(id => id !== undefined))];
|
|
78
|
+
const directMarks = layers
|
|
79
|
+
.map(layer => getMarkMaterializationStep(program, layer))
|
|
80
|
+
.filter(step => step !== undefined);
|
|
81
|
+
const marks = [
|
|
82
|
+
...directMarks,
|
|
83
|
+
...directMarks.flatMap(step =>
|
|
84
|
+
getSourceDependentMarkSteps(program, step.args.id)
|
|
85
|
+
)
|
|
86
|
+
];
|
|
87
|
+
return buildMaterializationPlan({
|
|
88
|
+
scales: scaleIds.map(id => ({
|
|
89
|
+
op: "rematerializeScale",
|
|
90
|
+
args: { id, guides: false, marks: false }
|
|
91
|
+
})),
|
|
92
|
+
marks: reusePlannedMarkScales(program, marks, scaleIds),
|
|
93
|
+
guides: scaleIds.flatMap(id =>
|
|
94
|
+
planScaleGuideRematerialization(program, id)
|
|
95
|
+
),
|
|
96
|
+
layout: planLayoutRematerialization(program)
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
64
100
|
export function planLayerDataRematerialization(program, id) {
|
|
65
101
|
const layer = requireLayer(program, id);
|
|
66
102
|
const scaleIds = [...new Set(getLayerScaleIds(layer))];
|
|
@@ -29,6 +29,9 @@ function collection(program, id, items) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export function resolveLegacyCategoricalLegend(program) {
|
|
32
|
+
if (program.semanticSpec.layers.some(layer =>
|
|
33
|
+
layer.encoding?.stroke?.scale !== undefined
|
|
34
|
+
)) return undefined;
|
|
32
35
|
const encodings = program.semanticSpec.layers.flatMap(layer =>
|
|
33
36
|
layer.encoding?.color === undefined
|
|
34
37
|
? []
|
|
@@ -46,10 +46,16 @@ function legendTranslation(prepared, plot, layout) {
|
|
|
46
46
|
y: layout.legend.y - prepared.bounds.top
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
-
const
|
|
49
|
+
const gradientKind = prepared.kinds?.find(kind =>
|
|
50
|
+
["gradient", "strokeGradient"].includes(kind)
|
|
51
|
+
);
|
|
52
|
+
const prefix = gradientKind === "strokeGradient"
|
|
53
|
+
? "strokeGradient"
|
|
54
|
+
: "colorGradient";
|
|
55
|
+
const strips = prepared.source.graphicSpec.objects[`${prefix}Strips`];
|
|
50
56
|
if (strips?.items?.[0] !== undefined) {
|
|
51
57
|
const properties = strips.items[0].properties;
|
|
52
|
-
const length = prepared.source.guideConfigs.legend.gradient.
|
|
58
|
+
const length = prepared.source.guideConfigs.legend[gradientKind].gradient.length;
|
|
53
59
|
return {
|
|
54
60
|
x: layout.legend.x - (position === "left"
|
|
55
61
|
? prepared.bounds.left
|
|
@@ -86,7 +92,7 @@ function attachParentLegend(program, prepared, plot, layout) {
|
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
function assertSharedLegendFits(program, id, layout, plot) {
|
|
89
|
-
const bounds = resolveConcreteGraphicBounds(program.graphicSpec, id);
|
|
95
|
+
const bounds = resolveConcreteGraphicBounds(program.graphicSpec, id, program.materializationConfigs.textMetrics);
|
|
90
96
|
if (bounds === undefined) {
|
|
91
97
|
throw new Error("Facet shared legend requires measurable concrete bounds.");
|
|
92
98
|
}
|
|
@@ -35,7 +35,7 @@ function legendCandidates(program) {
|
|
|
35
35
|
const layer = color[0];
|
|
36
36
|
return {
|
|
37
37
|
target: layer.id,
|
|
38
|
-
channels: ["color", "shape", "strokeDash"].filter(
|
|
38
|
+
channels: ["color", "stroke", "shape", "strokeDash"].filter(
|
|
39
39
|
channel => layer.encoding?.[channel]?.scale !== undefined
|
|
40
40
|
)
|
|
41
41
|
};
|
|
@@ -45,7 +45,7 @@ function legendCandidates(program) {
|
|
|
45
45
|
"Shared facet legend requires one unambiguous color legend target."
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
|
-
for (const channel of ["shape", "strokeDash", "size", "opacity"]) {
|
|
48
|
+
for (const channel of ["stroke", "shape", "strokeDash", "size", "opacity"]) {
|
|
49
49
|
const candidates = layers.filter(
|
|
50
50
|
layer => layer.encoding?.[channel]?.scale !== undefined
|
|
51
51
|
);
|
|
@@ -105,9 +105,14 @@ function prepareAutoLegendSource(child) {
|
|
|
105
105
|
}
|
|
106
106
|
const scale = findSemanticScale(
|
|
107
107
|
source,
|
|
108
|
-
findLayer(source, request.target)?.encoding?.
|
|
108
|
+
findLayer(source, request.target)?.encoding?.[
|
|
109
|
+
request.channels.includes("color") ? "color" : "stroke"
|
|
110
|
+
]?.scale
|
|
109
111
|
);
|
|
110
112
|
const gradient = scale?.type === "sequential";
|
|
113
|
+
const gradientPrefix = request.channels.includes("color")
|
|
114
|
+
? "colorGradient"
|
|
115
|
+
: "strokeGradient";
|
|
111
116
|
source = source.createLegend({
|
|
112
117
|
target: request.target,
|
|
113
118
|
channels: request.channels,
|
|
@@ -122,7 +127,7 @@ function prepareAutoLegendSource(child) {
|
|
|
122
127
|
const resolved = source.resolvedScales[scale.id];
|
|
123
128
|
source = source
|
|
124
129
|
.editGraphics({
|
|
125
|
-
target:
|
|
130
|
+
target: `${gradientPrefix}Labels`,
|
|
126
131
|
property: "text",
|
|
127
132
|
value: formatDistinctNumericSamples(
|
|
128
133
|
continuousValues(resolved.domain, 5),
|
|
@@ -130,9 +135,9 @@ function prepareAutoLegendSource(child) {
|
|
|
130
135
|
)
|
|
131
136
|
})
|
|
132
137
|
.editGraphics({
|
|
133
|
-
target:
|
|
138
|
+
target: `${gradientPrefix}Title`,
|
|
134
139
|
property: "y",
|
|
135
|
-
value: source.graphicSpec.objects.
|
|
140
|
+
value: source.graphicSpec.objects[`${gradientPrefix}Title`].properties.y + 8
|
|
136
141
|
});
|
|
137
142
|
}
|
|
138
143
|
return source;
|
|
@@ -192,11 +197,11 @@ export function prepareSharedFacetLegend(program) {
|
|
|
192
197
|
if (roots.length === 0) {
|
|
193
198
|
throw new Error("Shared facet legend has no concrete guide graphics.");
|
|
194
199
|
}
|
|
195
|
-
const bounds = unionConcreteGraphicBounds(source.graphicSpec, roots);
|
|
200
|
+
const bounds = unionConcreteGraphicBounds(source.graphicSpec, roots, source.materializationConfigs.textMetrics);
|
|
196
201
|
if (bounds === undefined) {
|
|
197
202
|
throw new Error("Shared facet legend has no measurable concrete bounds.");
|
|
198
203
|
}
|
|
199
|
-
const gradient = kinds.
|
|
204
|
+
const gradient = kinds.some(kind => ["gradient", "strokeGradient"].includes(kind));
|
|
200
205
|
const placement = promotedLegendPlacement(source, kinds);
|
|
201
206
|
const width = Math.ceil(bounds.right - bounds.left);
|
|
202
207
|
return {
|