ggaction 0.0.11 → 0.0.13
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 +80 -0
- package/README.md +3 -2
- 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 +4 -2
- package/src/actions/basic.js +2 -0
- package/src/actions/boxPlots/materialize.js +2 -1
- 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 +3 -2
- package/src/actions/charts/area.js +47 -0
- package/src/actions/charts/bar.js +11 -5
- package/src/actions/charts/beeswarm.js +70 -0
- package/src/actions/charts/density.js +44 -0
- package/src/actions/charts/ecdf.js +183 -0
- package/src/actions/charts/endpoints.js +451 -0
- package/src/actions/charts/heatmap.js +2 -2
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +71 -0
- package/src/actions/charts/index.js +37 -0
- package/src/actions/charts/interval-regression.js +169 -0
- package/src/actions/charts/line.js +5 -5
- package/src/actions/charts/parallel.js +1 -1
- package/src/actions/charts/pie.js +44 -0
- package/src/actions/charts/polar.js +123 -0
- package/src/actions/charts/radar.js +299 -0
- package/src/actions/charts/radial.js +42 -0
- package/src/actions/charts/raincloud.js +422 -0
- package/src/actions/charts/rug-strip.js +279 -0
- package/src/actions/charts/scatter.js +9 -3
- package/src/actions/charts/shared.js +69 -10
- package/src/actions/composition/actions.js +118 -1
- package/src/actions/data/bin.js +29 -0
- package/src/actions/data/computed.js +24 -0
- package/src/actions/data/derived.js +66 -0
- package/src/actions/data/ecdf.js +38 -0
- package/src/actions/data/filter.js +212 -28
- package/src/actions/data/fold.js +24 -0
- package/src/actions/data/index.js +24 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/intervalEdit.js +86 -4
- package/src/actions/data/regression.js +5 -2
- package/src/actions/data/stack.js +26 -0
- package/src/actions/data/summary.js +26 -0
- package/src/actions/data/timeUnit.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/color/continuous.js +19 -59
- package/src/actions/encodings/color/index.js +28 -22
- 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 +120 -33
- package/src/actions/encodings/horizon/resolve.js +8 -4
- package/src/actions/encodings/horizon.js +18 -8
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/offset.js +33 -27
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +40 -4
- package/src/actions/encodings/position/index.js +43 -5
- package/src/actions/encodings/position/policies/arc.js +37 -2
- package/src/actions/encodings/position/policies/area.js +4 -42
- package/src/actions/encodings/position/policies/bar.js +48 -13
- package/src/actions/encodings/position/resolve.js +79 -24
- package/src/actions/encodings/ranged.js +123 -97
- package/src/actions/encodings/remove.js +37 -32
- package/src/actions/encodings/ruleAppearance.js +23 -9
- package/src/actions/encodings/seriesLayout.js +104 -0
- package/src/actions/encodings/shared.js +25 -23
- 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 +18 -5
- package/src/actions/errorBands/edit.js +341 -21
- package/src/actions/errorBars/create.js +153 -11
- package/src/actions/errorBars/edit.js +365 -19
- package/src/actions/facets/actions.js +410 -12
- package/src/actions/facets/derive.js +57 -8
- package/src/actions/gradientPlots/components.js +33 -11
- package/src/actions/gradientPlots/materialize.js +5 -7
- package/src/actions/guides/applicability.js +20 -17
- 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 +129 -19
- package/src/actions/guides/axes/lines.js +7 -5
- 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 +44 -23
- 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 +14 -8
- package/src/actions/guides/axes/ticks.js +5 -4
- package/src/actions/guides/axes/titles.js +21 -8
- 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 +10 -2
- package/src/actions/guides/guides.js +35 -28
- package/src/actions/guides/legends/categorical/actions.js +193 -249
- package/src/actions/guides/legends/categorical/components.js +4 -5
- package/src/actions/guides/legends/categorical/layout.js +36 -349
- package/src/actions/guides/legends/categorical/options.js +30 -14
- package/src/actions/guides/legends/categorical/recipes.js +14 -0
- package/src/actions/guides/legends/categorical/resolve.js +21 -10
- package/src/actions/guides/legends/categorical/symbols.js +2 -3
- package/src/actions/guides/legends/continuous/common.js +69 -14
- package/src/actions/guides/legends/continuous/gradient.js +111 -83
- package/src/actions/guides/legends/continuous/index.js +6 -2
- package/src/actions/guides/legends/continuous/interval.js +71 -92
- package/src/actions/guides/legends/continuous/opacity.js +105 -84
- package/src/actions/guides/legends/creation.js +27 -0
- package/src/actions/guides/legends/edit.js +198 -66
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/lane.js +98 -86
- package/src/actions/guides/legends/lifecycle.js +132 -0
- package/src/actions/guides/legends/remove.js +31 -76
- package/src/actions/guides/legends/size.js +149 -105
- package/src/actions/guides/legends/strokeWidth.js +106 -64
- package/src/actions/guides/legends/target.js +12 -0
- package/src/actions/guides/legends/transition.js +43 -0
- package/src/actions/guides/polar/axes/facade.js +77 -62
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/lines.js +2 -1
- package/src/actions/guides/polar/axes/shared.js +6 -0
- package/src/actions/guides/polar/axes/ticks.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/grids.js +7 -7
- package/src/actions/guides/reuse.js +44 -0
- package/src/actions/guides/tickValues.js +19 -21
- package/src/actions/index.js +2 -0
- package/src/actions/intervals/resolve.js +35 -15
- package/src/actions/marks/arc/actions.js +1 -1
- package/src/actions/marks/area/actions.js +18 -4
- package/src/actions/marks/area/materialize.js +5 -2
- package/src/actions/marks/bar/materialize.js +11 -7
- package/src/actions/marks/index.js +5 -0
- package/src/actions/marks/line/actions.js +43 -108
- package/src/actions/marks/line/materialize.js +34 -52
- package/src/actions/marks/point/edit.js +11 -15
- 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 +138 -14
- package/src/actions/marks/point/packing.js +68 -0
- package/src/actions/marks/references.js +87 -0
- package/src/actions/marks/remove.js +17 -4
- package/src/actions/marks/rule/actions.js +60 -4
- package/src/actions/marks/rule/appearance.js +26 -0
- package/src/actions/marks/shared.js +12 -12
- package/src/actions/marks/text/actions.js +224 -6
- package/src/actions/marks/text/layout.js +2 -2
- package/src/actions/primitives/createGraphics.js +3 -1
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticAction.js +39 -2
- package/src/actions/primitives/semanticValidation/guide.js +10 -0
- package/src/actions/primitives/semanticValidation/layer.js +18 -1
- package/src/actions/primitives/semanticValidation/scale.js +10 -1
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +19 -6
- package/src/actions/regression/resolve.js +1 -1
- package/src/actions/scales/consumers/common.js +20 -53
- package/src/actions/scales/consumers/families.js +27 -14
- package/src/actions/scales/consumers/index.js +23 -2
- package/src/actions/scales/consumers/seriesLayout.js +33 -2
- package/src/actions/scales/create.js +4 -4
- package/src/actions/scales/definitions.js +19 -3
- package/src/actions/scales/edit.js +46 -34
- package/src/actions/scales/editPolicy.js +21 -31
- package/src/actions/scales/materialize.js +10 -46
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +39 -0
- package/src/actions/scales/quantitativeColor.js +9 -5
- package/src/actions/theme/actions.js +43 -0
- package/src/actions/theme/index.js +9 -0
- package/src/actions/theme/reconcile.js +882 -0
- package/src/actions/titles/actions.js +11 -10
- package/src/actions/titles/resolve.js +1 -40
- package/src/actions/violinPlots/create.js +86 -42
- package/src/actions/violinPlots/edit.js +191 -0
- package/src/actions/violinPlots/index.js +2 -0
- package/src/core/ChartProgram.js +7 -0
- package/src/core/action.js +22 -2
- package/src/core/compositionState.js +136 -8
- package/src/core/vocabulary.js +2 -2
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +213 -65
- package/src/grammar/areaEndpoints.js +46 -0
- package/src/grammar/areaSeries.js +87 -41
- package/src/grammar/bars/aggregate.js +33 -12
- package/src/grammar/bars/policy.js +9 -7
- package/src/grammar/bin.js +181 -0
- package/src/grammar/categoryOrder.js +34 -0
- package/src/grammar/computed.js +150 -0
- package/src/grammar/ecdf.js +198 -0
- package/src/grammar/facets/index.js +117 -6
- package/src/grammar/fold.js +123 -0
- package/src/grammar/horizon.js +6 -4
- package/src/grammar/interval.js +37 -20
- package/src/grammar/lineSeries.js +20 -63
- package/src/grammar/markFilter.js +22 -4
- package/src/grammar/markLabels.js +93 -0
- package/src/grammar/parallelAxisTitles.js +16 -0
- package/src/grammar/pathSeries.js +91 -0
- package/src/grammar/pointPacking.js +183 -0
- package/src/grammar/positionCompatibility.js +1 -1
- 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 +7 -2
- package/src/grammar/regression/models.js +22 -7
- package/src/grammar/regression/parameters.js +40 -13
- package/src/grammar/rotation.js +29 -0
- package/src/grammar/rules.js +9 -21
- package/src/grammar/scales/color.js +23 -1
- package/src/grammar/scales/colorConsumers.js +38 -0
- package/src/grammar/scales/definition.js +15 -1
- package/src/grammar/scales/discretized.js +11 -5
- package/src/grammar/scales/fields.js +38 -8
- package/src/grammar/scales/mapping.js +2 -0
- package/src/grammar/scales/ordinal.js +23 -0
- package/src/grammar/scales/radial.js +55 -0
- package/src/grammar/scales/types.js +1 -0
- package/src/grammar/schemas/semanticPath.js +17 -3
- package/src/grammar/stack.js +170 -0
- package/src/grammar/statistics/confidenceInterval.js +101 -0
- package/src/grammar/summary.js +183 -0
- package/src/grammar/text.js +22 -16
- package/src/grammar/timeUnit.js +21 -11
- package/src/grammar/transformTopology.js +6 -0
- package/src/grammar/transforms.js +36 -0
- package/src/grammar/valueFormat.js +112 -0
- package/src/grammar/window.js +13 -7
- package/src/layout/facets.js +86 -23
- package/src/layout/guideCollisions.js +12 -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 +38 -152
- package/src/materialization/bars/histogram.js +24 -0
- package/src/materialization/categorySlotOffset.js +40 -0
- package/src/materialization/compositionSnapshot.js +1 -1
- package/src/materialization/dependencies.js +57 -16
- package/src/materialization/encodings.js +1 -1
- package/src/materialization/facetGuides/legacyCategorical.js +7 -1
- package/src/materialization/facetGuides/placement.js +16 -1
- package/src/materialization/facetGuides/preparation.js +26 -1
- package/src/materialization/facets.js +11 -2
- package/src/materialization/guides/layout.js +57 -0
- package/src/materialization/guides/resources.js +2 -2
- package/src/materialization/legends.js +10 -1
- package/src/materialization/marks/capabilities.js +30 -7
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +63 -25
- package/src/materialization/marks/policies.js +2 -0
- package/src/materialization/rect.js +33 -33
- package/src/materialization/rowEncoding.js +70 -14
- package/src/materialization/scaleGuideDependencies.js +8 -5
- package/src/materialization/scales/map.js +1 -0
- package/src/materialization/scales/policies/arc.js +42 -0
- package/src/materialization/scales/policies/bar.js +0 -43
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/offset.js +54 -0
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +48 -18
- package/src/materialization/selection/compatibility.js +11 -0
- package/src/materialization/selection/items/bar.js +12 -68
- package/src/materialization/selection/items/common.js +8 -2
- package/src/materialization/selection/items/path.js +3 -13
- package/src/materialization/selection/items/rect.js +7 -4
- package/src/materialization/text.js +64 -5
- package/src/mcp/adapter.js +2 -2
- package/src/selectors/datasets.js +13 -0
- package/src/selectors/layers.js +3 -2
- package/src/selectors/scales.js +6 -0
- package/src/theme/defaults.js +39 -0
- package/types/basic.d.ts +11 -2
- package/types/index.d.ts +126 -0
- package/types/program.d.ts +1327 -124
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { isPlainObject } from "../core/immutable.js";
|
|
2
2
|
import { validateBoxTransform } from "./boxPlot.js";
|
|
3
|
+
import { validateBinTransform } from "./bin.js";
|
|
3
4
|
import {
|
|
4
5
|
requestedBin2DTransform,
|
|
5
6
|
validateBin2DTransform
|
|
6
7
|
} from "./bin2d.js";
|
|
7
8
|
import { validateDensityTransform } from "./density.js";
|
|
9
|
+
import { validateECDFTransform } from "./ecdf.js";
|
|
10
|
+
import { validateComputedTransform } from "./computed.js";
|
|
8
11
|
import { validateFilterTransform } from "./filter.js";
|
|
12
|
+
import { validateFoldTransform } from "./fold.js";
|
|
9
13
|
import {
|
|
10
14
|
requestedGradientProfileTransform,
|
|
11
15
|
validateGradientProfileTransform
|
|
@@ -19,6 +23,8 @@ import { validateMarkFilterTransform } from "./markFilter.js";
|
|
|
19
23
|
import { validateRegressionTransform } from "./regression/index.js";
|
|
20
24
|
import { validateWindowTransform } from "./window.js";
|
|
21
25
|
import { validateTimeUnitTransform } from "./timeUnit.js";
|
|
26
|
+
import { validateSummaryTransform } from "./summary.js";
|
|
27
|
+
import { validateStackTransform } from "./stack.js";
|
|
22
28
|
import { findTransformTopology } from "./transformTopology.js";
|
|
23
29
|
|
|
24
30
|
function requestedDensityTransform(transform) {
|
|
@@ -44,12 +50,22 @@ function facetHorizonTransform(transform, { scales = {} } = {}) {
|
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
const TRANSFORM_POLICIES = Object.freeze({
|
|
53
|
+
bin: Object.freeze({
|
|
54
|
+
...findTransformTopology("bin"),
|
|
55
|
+
validate: validateBinTransform,
|
|
56
|
+
materializeOp: "materializeBinData"
|
|
57
|
+
}),
|
|
47
58
|
bin2d: Object.freeze({
|
|
48
59
|
...findTransformTopology("bin2d"),
|
|
49
60
|
validate: validateBin2DTransform,
|
|
50
61
|
materializeOp: "materializeBin2DData",
|
|
51
62
|
replayTransform: requestedBin2DTransform
|
|
52
63
|
}),
|
|
64
|
+
computed: Object.freeze({
|
|
65
|
+
...findTransformTopology("computed"),
|
|
66
|
+
validate: validateComputedTransform,
|
|
67
|
+
materializeOp: "materializeComputedData"
|
|
68
|
+
}),
|
|
53
69
|
boxOutlier: Object.freeze({
|
|
54
70
|
...findTransformTopology("boxOutlier"),
|
|
55
71
|
validate: validateBoxTransform,
|
|
@@ -66,11 +82,21 @@ const TRANSFORM_POLICIES = Object.freeze({
|
|
|
66
82
|
materializeOp: "materializeDensityData",
|
|
67
83
|
replayTransform: requestedDensityTransform
|
|
68
84
|
}),
|
|
85
|
+
ecdf: Object.freeze({
|
|
86
|
+
...findTransformTopology("ecdf"),
|
|
87
|
+
validate: validateECDFTransform,
|
|
88
|
+
materializeOp: "materializeECDFData"
|
|
89
|
+
}),
|
|
69
90
|
filter: Object.freeze({
|
|
70
91
|
...findTransformTopology("filter"),
|
|
71
92
|
validate: validateFilterTransform,
|
|
72
93
|
materializeOp: "materializeFilteredData"
|
|
73
94
|
}),
|
|
95
|
+
fold: Object.freeze({
|
|
96
|
+
...findTransformTopology("fold"),
|
|
97
|
+
validate: validateFoldTransform,
|
|
98
|
+
materializeOp: "materializeFoldData"
|
|
99
|
+
}),
|
|
74
100
|
gradientProfile: Object.freeze({
|
|
75
101
|
...findTransformTopology("gradientProfile"),
|
|
76
102
|
validate: validateGradientProfileTransform,
|
|
@@ -99,6 +125,16 @@ const TRANSFORM_POLICIES = Object.freeze({
|
|
|
99
125
|
validate: validateRegressionTransform,
|
|
100
126
|
materializeOp: "materializeRegressionData"
|
|
101
127
|
}),
|
|
128
|
+
summary: Object.freeze({
|
|
129
|
+
...findTransformTopology("summary"),
|
|
130
|
+
validate: validateSummaryTransform,
|
|
131
|
+
materializeOp: "materializeSummaryData"
|
|
132
|
+
}),
|
|
133
|
+
stack: Object.freeze({
|
|
134
|
+
...findTransformTopology("stack"),
|
|
135
|
+
validate: validateStackTransform,
|
|
136
|
+
materializeOp: "materializeStackData"
|
|
137
|
+
}),
|
|
102
138
|
timeUnit: Object.freeze({
|
|
103
139
|
...findTransformTopology("timeUnit"),
|
|
104
140
|
validate: validateTimeUnitTransform,
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const NUMERIC_FORMAT = /^\.(0?\d|1[0-2])(f|%|e)$/;
|
|
2
|
+
const TIME_DIRECTIVES = new Set(["Y", "m", "d", "b", "%"]);
|
|
3
|
+
const MONTH_NAMES = Object.freeze([
|
|
4
|
+
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
5
|
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
|
6
|
+
]);
|
|
7
|
+
|
|
8
|
+
export function isUtcValueFormat(format) {
|
|
9
|
+
if (typeof format !== "string") return false;
|
|
10
|
+
let directives = 0;
|
|
11
|
+
for (let index = 0; index < format.length; index += 1) {
|
|
12
|
+
if (format[index] !== "%") continue;
|
|
13
|
+
const directive = format[index + 1];
|
|
14
|
+
if (!TIME_DIRECTIVES.has(directive)) return false;
|
|
15
|
+
if (directive !== "%") directives += 1;
|
|
16
|
+
index += 1;
|
|
17
|
+
}
|
|
18
|
+
return directives > 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function validateValueFormat(format, label = "Label format", {
|
|
22
|
+
allowDecimalsObject = false
|
|
23
|
+
} = {}) {
|
|
24
|
+
if (format === undefined || format === "auto") return format ?? "auto";
|
|
25
|
+
if (
|
|
26
|
+
format !== null &&
|
|
27
|
+
typeof format === "object" &&
|
|
28
|
+
Number.isInteger(format.decimals) &&
|
|
29
|
+
format.decimals >= 0 &&
|
|
30
|
+
format.decimals <= 100 &&
|
|
31
|
+
Object.keys(format).length === 1
|
|
32
|
+
) {
|
|
33
|
+
if (allowDecimalsObject) return format;
|
|
34
|
+
}
|
|
35
|
+
if (typeof format === "string" && (
|
|
36
|
+
NUMERIC_FORMAT.test(format) || isUtcValueFormat(format)
|
|
37
|
+
)) {
|
|
38
|
+
return format;
|
|
39
|
+
}
|
|
40
|
+
const numericCandidate = typeof format === "string"
|
|
41
|
+
? format.match(/^\.(\d+)(f|%|e)$/)
|
|
42
|
+
: undefined;
|
|
43
|
+
if (numericCandidate && Number(numericCandidate[1]) > 12) {
|
|
44
|
+
throw new RangeError(`${label} numeric format supports at most 12 decimals.`);
|
|
45
|
+
}
|
|
46
|
+
throw new TypeError(
|
|
47
|
+
`${label} must be auto${allowDecimalsObject ? ", { decimals }," : ","} or a supported format string (numeric or UTC).`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function pad(value, length = 2) {
|
|
52
|
+
return String(value).padStart(length, "0");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function formatUtcValue(value, format, label) {
|
|
56
|
+
const date = new Date(value);
|
|
57
|
+
if (!Number.isFinite(date.getTime())) {
|
|
58
|
+
throw new TypeError(`${label} requires a valid date or timestamp.`);
|
|
59
|
+
}
|
|
60
|
+
const values = {
|
|
61
|
+
Y: pad(date.getUTCFullYear(), 4),
|
|
62
|
+
m: pad(date.getUTCMonth() + 1),
|
|
63
|
+
d: pad(date.getUTCDate()),
|
|
64
|
+
b: MONTH_NAMES[date.getUTCMonth()]
|
|
65
|
+
};
|
|
66
|
+
return format.replace(/%([Ymdb%])/g, (_, directive) =>
|
|
67
|
+
directive === "%" ? "%" : values[directive]
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function formatValue(value, {
|
|
72
|
+
format = "auto",
|
|
73
|
+
valueType,
|
|
74
|
+
autoFormatter = current => String(current),
|
|
75
|
+
label = "Label format",
|
|
76
|
+
allowDecimalsObject = false
|
|
77
|
+
} = {}) {
|
|
78
|
+
const resolved = validateValueFormat(format, label, {
|
|
79
|
+
allowDecimalsObject
|
|
80
|
+
});
|
|
81
|
+
if (value === undefined || value === null) return undefined;
|
|
82
|
+
if (resolved === "auto") return autoFormatter(value);
|
|
83
|
+
if (["nominal", "ordinal", "discrete"].includes(valueType)) {
|
|
84
|
+
throw new Error('Discrete labels require format "auto".');
|
|
85
|
+
}
|
|
86
|
+
const utc = isUtcValueFormat(resolved);
|
|
87
|
+
if (valueType === "temporal") {
|
|
88
|
+
if (!utc) throw new Error("Temporal labels require a supported UTC format string.");
|
|
89
|
+
return formatUtcValue(value, resolved, label);
|
|
90
|
+
}
|
|
91
|
+
if (utc) {
|
|
92
|
+
if (valueType === "quantitative") {
|
|
93
|
+
throw new Error("Quantitative labels cannot use a UTC format string.");
|
|
94
|
+
}
|
|
95
|
+
return formatUtcValue(value, resolved, label);
|
|
96
|
+
}
|
|
97
|
+
if (!Number.isFinite(value)) {
|
|
98
|
+
throw new TypeError(`${label} "${typeof resolved === "string" ? resolved : "decimals"}" requires a finite number.`);
|
|
99
|
+
}
|
|
100
|
+
if (typeof resolved === "object") return value.toFixed(resolved.decimals);
|
|
101
|
+
const match = resolved.match(NUMERIC_FORMAT);
|
|
102
|
+
const precision = Number(match[1]);
|
|
103
|
+
if (match[2] === "f") return value.toFixed(precision);
|
|
104
|
+
if (match[2] === "%") {
|
|
105
|
+
const percentage = value * 100;
|
|
106
|
+
if (!Number.isFinite(percentage)) {
|
|
107
|
+
throw new RangeError(`${label} percent format overflow.`);
|
|
108
|
+
}
|
|
109
|
+
return `${percentage.toFixed(precision)}%`;
|
|
110
|
+
}
|
|
111
|
+
return value.toExponential(precision);
|
|
112
|
+
}
|
package/src/grammar/window.js
CHANGED
|
@@ -336,10 +336,16 @@ function stableWindowOutput(operation, calculate) {
|
|
|
336
336
|
}
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
function setWindowOutput(entry, field, value) {
|
|
340
|
+
Object.defineProperty(entry.row, field, {
|
|
341
|
+
value, enumerable: true, writable: true, configurable: true
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
339
345
|
function applyOperation(partition, operation, sortBy) {
|
|
340
346
|
if (operation.op === "rowNumber") {
|
|
341
347
|
partition.forEach((entry, index) => {
|
|
342
|
-
entry
|
|
348
|
+
setWindowOutput(entry, operation.as, index + 1);
|
|
343
349
|
});
|
|
344
350
|
return;
|
|
345
351
|
}
|
|
@@ -354,7 +360,7 @@ function applyOperation(partition, operation, sortBy) {
|
|
|
354
360
|
rank = index + 1;
|
|
355
361
|
denseRank += 1;
|
|
356
362
|
}
|
|
357
|
-
entry
|
|
363
|
+
setWindowOutput(entry, operation.as, operation.op === "rank" ? rank : denseRank);
|
|
358
364
|
});
|
|
359
365
|
return;
|
|
360
366
|
}
|
|
@@ -364,7 +370,7 @@ function applyOperation(partition, operation, sortBy) {
|
|
|
364
370
|
stableFinitePrefixSums(values, label)
|
|
365
371
|
);
|
|
366
372
|
partition.forEach((entry, index) => {
|
|
367
|
-
entry
|
|
373
|
+
setWindowOutput(entry, operation.as, totals[index]);
|
|
368
374
|
});
|
|
369
375
|
return;
|
|
370
376
|
}
|
|
@@ -379,18 +385,18 @@ function applyOperation(partition, operation, sortBy) {
|
|
|
379
385
|
index + operation.frame.following
|
|
380
386
|
);
|
|
381
387
|
const values = operationValues(partition, operation, first, last);
|
|
382
|
-
entry
|
|
388
|
+
setWindowOutput(entry, operation.as, stableWindowOutput(operation, label =>
|
|
383
389
|
calculate(values, label)
|
|
384
|
-
);
|
|
390
|
+
));
|
|
385
391
|
});
|
|
386
392
|
return;
|
|
387
393
|
}
|
|
388
394
|
const direction = operation.op === "lag" ? -1 : 1;
|
|
389
395
|
partition.forEach((entry, index) => {
|
|
390
396
|
const peer = partition[index + direction * operation.offset];
|
|
391
|
-
entry
|
|
397
|
+
setWindowOutput(entry, operation.as, peer === undefined
|
|
392
398
|
? operation.default
|
|
393
|
-
: peer.row[operation.field];
|
|
399
|
+
: peer.row[operation.field]);
|
|
394
400
|
});
|
|
395
401
|
}
|
|
396
402
|
|
package/src/layout/facets.js
CHANGED
|
@@ -9,6 +9,11 @@ import {
|
|
|
9
9
|
|
|
10
10
|
export const DEFAULT_FACET_LEGEND_GAP = 18;
|
|
11
11
|
export const DEFAULT_FACET_LEGEND_WIDTH = 132;
|
|
12
|
+
export const DEFAULT_FACET_LEGEND_HEIGHT = 0;
|
|
13
|
+
|
|
14
|
+
const FACET_LEGEND_POSITIONS = Object.freeze([
|
|
15
|
+
"right", "left", "top", "bottom"
|
|
16
|
+
]);
|
|
12
17
|
|
|
13
18
|
function resolveColumns(columns, count) {
|
|
14
19
|
const value = columns ?? count;
|
|
@@ -18,6 +23,29 @@ function resolveColumns(columns, count) {
|
|
|
18
23
|
return Math.min(value, count);
|
|
19
24
|
}
|
|
20
25
|
|
|
26
|
+
function resolveGridCoordinates(children, columns) {
|
|
27
|
+
const requested = children.some(child => child.row !== undefined || child.column !== undefined);
|
|
28
|
+
if (!requested) return undefined;
|
|
29
|
+
if (children.some(child => !(
|
|
30
|
+
Number.isInteger(child.row) && child.row >= 0 &&
|
|
31
|
+
Number.isInteger(child.column) && child.column >= 0
|
|
32
|
+
))) {
|
|
33
|
+
throw new RangeError("Facet grid children require non-negative integer row and column coordinates.");
|
|
34
|
+
}
|
|
35
|
+
const keys = children.map(child => `${child.row}:${child.column}`);
|
|
36
|
+
if (new Set(keys).size !== keys.length) {
|
|
37
|
+
throw new Error("Facet grid children must occupy unique row and column coordinates.");
|
|
38
|
+
}
|
|
39
|
+
const requiredColumns = Math.max(...children.map(child => child.column)) + 1;
|
|
40
|
+
if (columns !== undefined && columns !== requiredColumns) {
|
|
41
|
+
throw new RangeError("Facet grid columns must match the declared column domain.");
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
columns: requiredColumns,
|
|
45
|
+
rows: Math.max(...children.map(child => child.row)) + 1
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
21
49
|
function alignedOffset(remaining, align) {
|
|
22
50
|
if (align === "start") return 0;
|
|
23
51
|
if (align === "end") return remaining;
|
|
@@ -33,13 +61,17 @@ export function resolveFacetLayout({
|
|
|
33
61
|
titleHeight = 0,
|
|
34
62
|
sharedLegend = false,
|
|
35
63
|
sharedLegendGap = DEFAULT_FACET_LEGEND_GAP,
|
|
36
|
-
sharedLegendWidth = DEFAULT_FACET_LEGEND_WIDTH
|
|
64
|
+
sharedLegendWidth = DEFAULT_FACET_LEGEND_WIDTH,
|
|
65
|
+
sharedLegendHeight = DEFAULT_FACET_LEGEND_HEIGHT,
|
|
66
|
+
sharedLegendPosition = "right"
|
|
37
67
|
} = {}) {
|
|
38
68
|
const values = children?.map(child => child?.value);
|
|
69
|
+
const coordinates = resolveGridCoordinates(children ?? [], columns);
|
|
39
70
|
const resolvedChildren = normalizeCompositionChildren(children?.map(
|
|
40
|
-
({ value: _value, ...child }) => child
|
|
71
|
+
({ value: _value, row: _row, column: _column, ...child }) => child
|
|
41
72
|
));
|
|
42
|
-
const resolvedColumns =
|
|
73
|
+
const resolvedColumns = coordinates?.columns ??
|
|
74
|
+
resolveColumns(columns, resolvedChildren.length);
|
|
43
75
|
const resolvedGap = validateCompositionSpacing(gap, "Facet gap");
|
|
44
76
|
const resolvedAlign = normalizeCompositionAlign(align);
|
|
45
77
|
const resolvedPadding = normalizeCompositionPadding(padding);
|
|
@@ -58,12 +90,31 @@ export function resolveFacetLayout({
|
|
|
58
90
|
sharedLegendWidth,
|
|
59
91
|
"Facet shared legend width"
|
|
60
92
|
);
|
|
61
|
-
const
|
|
93
|
+
const legendHeight = validateCompositionSpacing(
|
|
94
|
+
sharedLegendHeight,
|
|
95
|
+
"Facet shared legend height"
|
|
96
|
+
);
|
|
97
|
+
if (!FACET_LEGEND_POSITIONS.includes(sharedLegendPosition)) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`Unknown facet shared legend position "${sharedLegendPosition}".`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const sideLegend = ["right", "left"].includes(sharedLegendPosition);
|
|
103
|
+
const beforeGrid = ["left", "top"].includes(sharedLegendPosition);
|
|
104
|
+
const legendLane = sharedLegend
|
|
105
|
+
? legendGap + (sideLegend ? legendWidth : legendHeight)
|
|
106
|
+
: 0;
|
|
107
|
+
const rowCount = coordinates?.rows ??
|
|
108
|
+
Math.ceil(resolvedChildren.length / resolvedColumns);
|
|
62
109
|
const columnWidths = Array(resolvedColumns).fill(-Infinity);
|
|
63
110
|
const rowHeights = Array(rowCount).fill(-Infinity);
|
|
64
111
|
resolvedChildren.forEach((child, index) => {
|
|
65
|
-
const column =
|
|
66
|
-
|
|
112
|
+
const column = coordinates === undefined
|
|
113
|
+
? index % resolvedColumns
|
|
114
|
+
: children[index].column;
|
|
115
|
+
const row = coordinates === undefined
|
|
116
|
+
? Math.floor(index / resolvedColumns)
|
|
117
|
+
: children[index].row;
|
|
67
118
|
columnWidths[column] = Math.max(columnWidths[column], child.width);
|
|
68
119
|
rowHeights[row] = Math.max(rowHeights[row], child.height);
|
|
69
120
|
});
|
|
@@ -76,21 +127,27 @@ export function resolveFacetLayout({
|
|
|
76
127
|
.reduce((sum, height) => sum + height, 0) + resolvedGap * row
|
|
77
128
|
);
|
|
78
129
|
const placements = resolvedChildren.map((child, index) => {
|
|
79
|
-
const column =
|
|
80
|
-
|
|
130
|
+
const column = coordinates === undefined
|
|
131
|
+
? index % resolvedColumns
|
|
132
|
+
: children[index].column;
|
|
133
|
+
const row = coordinates === undefined
|
|
134
|
+
? Math.floor(index / resolvedColumns)
|
|
135
|
+
: children[index].row;
|
|
81
136
|
return {
|
|
82
137
|
id: child.id,
|
|
83
138
|
value: values[index],
|
|
84
139
|
column,
|
|
85
140
|
row,
|
|
86
|
-
x: columnStarts[column] +
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
141
|
+
x: columnStarts[column] + (beforeGrid && sideLegend ? legendLane : 0) +
|
|
142
|
+
alignedOffset(
|
|
143
|
+
columnWidths[column] - child.width,
|
|
144
|
+
resolvedAlign
|
|
145
|
+
),
|
|
146
|
+
y: rowStarts[row] + (beforeGrid && !sideLegend ? legendLane : 0) +
|
|
147
|
+
alignedOffset(
|
|
148
|
+
rowHeights[row] - child.height,
|
|
149
|
+
resolvedAlign
|
|
150
|
+
),
|
|
94
151
|
width: child.width,
|
|
95
152
|
height: child.height
|
|
96
153
|
};
|
|
@@ -111,16 +168,22 @@ export function resolveFacetLayout({
|
|
|
111
168
|
padding: resolvedPadding,
|
|
112
169
|
titleHeight: resolvedTitleHeight,
|
|
113
170
|
gridWidth,
|
|
114
|
-
width: gridWidth + (
|
|
115
|
-
|
|
116
|
-
: 0),
|
|
117
|
-
height: gridHeight,
|
|
171
|
+
width: gridWidth + (sideLegend ? legendLane : 0),
|
|
172
|
+
height: gridHeight + (sideLegend ? 0 : legendLane),
|
|
118
173
|
children: placements,
|
|
119
174
|
...(sharedLegend ? {
|
|
120
175
|
legend: {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
176
|
+
position: sharedLegendPosition,
|
|
177
|
+
x: sharedLegendPosition === "right"
|
|
178
|
+
? gridWidth + legendGap
|
|
179
|
+
: 0,
|
|
180
|
+
y: sharedLegendPosition === "bottom"
|
|
181
|
+
? gridHeight + legendGap
|
|
182
|
+
: sharedLegendPosition === "top"
|
|
183
|
+
? resolvedTitleHeight
|
|
184
|
+
: resolvedTitleHeight + resolvedPadding.top + 30,
|
|
185
|
+
width: legendWidth,
|
|
186
|
+
height: legendHeight
|
|
124
187
|
}
|
|
125
188
|
} : {})
|
|
126
189
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function assertGuideCollisionBlocks(blocks) {
|
|
2
|
+
for (let index = 0; index < blocks.length; index += 1) {
|
|
3
|
+
const a = blocks[index];
|
|
4
|
+
for (const b of blocks.slice(index + 1)) {
|
|
5
|
+
if (a.position !== b.position || a.kind === "axis" && b.kind === "axis") continue;
|
|
6
|
+
if (a.bounds.left < b.bounds.right && a.bounds.right > b.bounds.left &&
|
|
7
|
+
a.bounds.top < b.bounds.bottom && a.bounds.bottom > b.bounds.top) {
|
|
8
|
+
throw new Error(`The ${a.position} ${a.id} and ${b.id} overlap; guides require more margin space or different offsets.`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
package/src/layout/legend.js
CHANGED
|
@@ -3,19 +3,11 @@ import {
|
|
|
3
3
|
measureTextWidth
|
|
4
4
|
} from "../core/textMetrics.js";
|
|
5
5
|
|
|
6
|
-
export function measureLegendSymbolHeight(config) {
|
|
7
|
-
return Math.max(...config.symbol.layers.map(layer => {
|
|
8
|
-
if (layer.type === "swatch") return layer.height;
|
|
9
|
-
if (layer.type === "point") return layer.size * 2;
|
|
10
|
-
return layer.lineWidth;
|
|
11
|
-
}));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
6
|
export function measureLegendTextWidth(value, style = { fontSize: 12 }) {
|
|
15
7
|
return measureTextWidth(formatVisibleText(value), style);
|
|
16
8
|
}
|
|
17
9
|
|
|
18
|
-
export function resolveLegendGrid(config, width, count) {
|
|
10
|
+
export function resolveLegendGrid(config, width, count, symbolHeight) {
|
|
19
11
|
const labels = config.domain.map(formatVisibleText);
|
|
20
12
|
const itemWidths = labels.map(
|
|
21
13
|
label => width + config.labels.offset +
|
|
@@ -39,7 +31,7 @@ export function resolveLegendGrid(config, width, count) {
|
|
|
39
31
|
config.itemGap * Math.max(0, actualColumns - 1);
|
|
40
32
|
const rowHeight = Math.max(
|
|
41
33
|
config.labels.fontSize,
|
|
42
|
-
|
|
34
|
+
symbolHeight
|
|
43
35
|
);
|
|
44
36
|
const gridHeight = rowHeight * rowCount +
|
|
45
37
|
config.itemGap * Math.max(0, rowCount - 1);
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { measureTextWidth, resolveTextBounds } from "../core/textMetrics.js";
|
|
2
|
+
import { alignLegendStart, resolveLegendGrid } from "./legend.js";
|
|
3
|
+
|
|
4
|
+
// Measure item content before choosing its edge. Family owners supply the
|
|
5
|
+
// sample dimensions and formatted labels; this module does not inspect scales.
|
|
6
|
+
export function resolveLegendItemLayout(plot, config, labels, symbol, canvas) {
|
|
7
|
+
const strokes = labels.map((_, index) => Array.isArray(symbol.strokeWidth)
|
|
8
|
+
? symbol.strokeWidth[index] : symbol.strokeWidth ?? 0);
|
|
9
|
+
const sampleBounds = labels.map((_, index) => symbol.itemBounds?.[index] ?? {
|
|
10
|
+
left: -strokes[index] / 2, right: symbol.width + strokes[index] / 2,
|
|
11
|
+
top: -symbol.height / 2 - strokes[index] / 2,
|
|
12
|
+
bottom: symbol.height / 2 + strokes[index] / 2
|
|
13
|
+
});
|
|
14
|
+
// Retain a family's minimum slot while reserving its complete painted extent.
|
|
15
|
+
const sampleLeft = Math.min(0, ...sampleBounds.map(bounds => bounds.left));
|
|
16
|
+
const sampleRight = Math.max(symbol.width, ...sampleBounds.map(bounds => bounds.right));
|
|
17
|
+
const sampleWidth = sampleRight - sampleLeft;
|
|
18
|
+
const sampleHeight = Math.max(symbol.height,
|
|
19
|
+
...sampleBounds.map(bounds => 2 * Math.max(-bounds.top, bounds.bottom)));
|
|
20
|
+
const side = ["left", "right"].includes(config.position);
|
|
21
|
+
const titleVisible = config.titleVisible !== false;
|
|
22
|
+
const titleWidth = titleVisible ? measureTextWidth(config.title, config.titleStyle) : 0;
|
|
23
|
+
const titleHeight = titleVisible ? config.titleStyle.fontSize : 0;
|
|
24
|
+
const itemWidth = sampleWidth + config.labels.offset + Math.max(...labels.map(
|
|
25
|
+
text => measureTextWidth(text, config.labels)
|
|
26
|
+
));
|
|
27
|
+
let symbolX;
|
|
28
|
+
let labelX;
|
|
29
|
+
let itemY;
|
|
30
|
+
let title;
|
|
31
|
+
if (config.layout === "legacy-bottom") {
|
|
32
|
+
const widths = labels.map(text => sampleWidth + config.labels.offset + measureTextWidth(text, config.labels));
|
|
33
|
+
const width = widths.reduce((sum, value) => sum + value, 0) + config.itemGap * (labels.length - 1);
|
|
34
|
+
const x = config.align === "center" ? (canvas.width - width) / 2 : alignLegendStart(plot, width, config.align);
|
|
35
|
+
let cursor = x;
|
|
36
|
+
symbolX = widths.map(width => {
|
|
37
|
+
const start = cursor - sampleLeft;
|
|
38
|
+
cursor += width + config.itemGap;
|
|
39
|
+
return start;
|
|
40
|
+
});
|
|
41
|
+
labelX = symbolX.map(value => value + sampleRight + config.labels.offset);
|
|
42
|
+
itemY = labels.map(() => canvas.height - 28);
|
|
43
|
+
title = { x: x + width / 2, y: canvas.height - 52, align: "center" };
|
|
44
|
+
} else if (side) {
|
|
45
|
+
const width = Math.max(itemWidth, titleWidth);
|
|
46
|
+
const x = config.position === "right" ? plot.x + plot.width + config.offset
|
|
47
|
+
: plot.x - config.offset - width;
|
|
48
|
+
const itemHeight = Math.max(sampleHeight, config.labels.fontSize);
|
|
49
|
+
const pitch = Math.max(config.itemGap, itemHeight);
|
|
50
|
+
symbolX = labels.map(() => x - sampleLeft);
|
|
51
|
+
labelX = labels.map(() => x + sampleWidth + config.labels.offset);
|
|
52
|
+
const firstY = Math.max(plot.y + 52, titleVisible
|
|
53
|
+
? plot.y + 20 + titleHeight / 2 + 12 + itemHeight / 2 : plot.y + 52);
|
|
54
|
+
itemY = labels.map((_, index) => firstY + index * pitch);
|
|
55
|
+
title = { x, y: plot.y + 20, align: "left" };
|
|
56
|
+
} else {
|
|
57
|
+
const grid = resolveLegendGrid({ ...config, domain: labels }, sampleWidth, labels.length, sampleHeight);
|
|
58
|
+
const inline = config.titlePosition === "left" && titleVisible;
|
|
59
|
+
const prefix = inline ? titleWidth + 20 : 0;
|
|
60
|
+
const width = inline ? prefix + grid.gridWidth : Math.max(titleWidth, grid.gridWidth);
|
|
61
|
+
const top = !inline && titleVisible ? titleHeight + 12 : 0;
|
|
62
|
+
const height = inline ? Math.max(titleHeight, grid.gridHeight) : top + grid.gridHeight;
|
|
63
|
+
const x = alignLegendStart(plot, width, config.align);
|
|
64
|
+
const y = config.position === "top" ? plot.y - config.offset - height
|
|
65
|
+
: plot.y + plot.height + config.offset;
|
|
66
|
+
const gridX = inline ? x + prefix : x + (width - grid.gridWidth) / 2;
|
|
67
|
+
const gridY = inline ? y + (height - grid.gridHeight) / 2 : y + top;
|
|
68
|
+
let cursor = 0;
|
|
69
|
+
const columns = grid.columnWidths.map(width => {
|
|
70
|
+
const start = cursor;
|
|
71
|
+
cursor += width + config.itemGap;
|
|
72
|
+
return start;
|
|
73
|
+
});
|
|
74
|
+
symbolX = grid.cells.map(cell => gridX + columns[cell.column] - sampleLeft);
|
|
75
|
+
labelX = symbolX.map(value => value + sampleRight + config.labels.offset);
|
|
76
|
+
itemY = grid.cells.map(cell => gridY + grid.rowHeight / 2 + cell.row * (grid.rowHeight + config.itemGap));
|
|
77
|
+
title = inline ? { x, y: y + height / 2, align: "left" }
|
|
78
|
+
: { x: x + width / 2, y: y + titleHeight / 2, align: "center" };
|
|
79
|
+
}
|
|
80
|
+
const textBounds = (x, y, text, style, align = "left") => resolveTextBounds({
|
|
81
|
+
x, y, text, ...style, textAlign: align, textBaseline: "middle"
|
|
82
|
+
});
|
|
83
|
+
const bounds = [
|
|
84
|
+
...(titleVisible ? [textBounds(title.x, title.y, config.title, config.titleStyle, title.align)] : []),
|
|
85
|
+
...labels.map((label, index) => textBounds(labelX[index], itemY[index], label, config.labels)),
|
|
86
|
+
...symbolX.map((x, index) => {
|
|
87
|
+
const local = sampleBounds[index];
|
|
88
|
+
return { left: x + local.left, right: x + local.right,
|
|
89
|
+
top: itemY[index] + local.top, bottom: itemY[index] + local.bottom };
|
|
90
|
+
})
|
|
91
|
+
];
|
|
92
|
+
if (config.layout === "legacy-bottom") {
|
|
93
|
+
const itemBounds = titleVisible ? bounds.slice(1) : bounds;
|
|
94
|
+
if (titleVisible && itemBounds.some(bound => bound.top < bounds[0].bottom)) {
|
|
95
|
+
throw new Error("Legacy bottom legend requires more space between its fixed title and item rows.");
|
|
96
|
+
}
|
|
97
|
+
if (bounds.some(bound => bound.top <= plot.y + plot.height)) {
|
|
98
|
+
throw new Error("Legacy bottom legend requires more bottom-margin space.");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { symbolX, labelX, itemY, title, bounds };
|
|
102
|
+
}
|