ggaction 0.0.3 → 0.0.5
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 +58 -3
- package/README.md +117 -83
- package/package.json +27 -11
- package/src/actions/boxPlots/create.js +13 -7
- package/src/actions/boxPlots/edit.js +31 -35
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +8 -1
- package/src/actions/boxPlots/resolve.js +10 -2
- package/src/actions/charts/bar.js +50 -0
- package/src/actions/charts/heatmap.js +205 -0
- package/src/actions/charts/histogram.js +58 -0
- package/src/actions/charts/index.js +15 -0
- package/src/actions/charts/line.js +69 -0
- package/src/actions/charts/parallel.js +76 -0
- package/src/actions/charts/scatter.js +56 -0
- package/src/actions/charts/shared.js +121 -0
- package/src/actions/coordinates/parallel.js +50 -0
- package/src/actions/data/bin2d.js +137 -0
- package/src/actions/data/density.js +54 -13
- package/src/actions/data/derived.js +22 -0
- package/src/actions/data/filter.js +5 -0
- package/src/actions/data/gradientProfile.js +65 -0
- package/src/actions/data/horizon.js +71 -0
- package/src/actions/data/index.js +29 -2
- package/src/actions/data/regression.js +1 -1
- package/src/actions/data/window.js +50 -0
- package/src/actions/encodings/appearance.js +1 -1
- package/src/actions/encodings/color/continuous.js +137 -0
- package/src/actions/encodings/color/index.js +167 -0
- package/src/actions/encodings/color/layout.js +99 -0
- package/src/actions/encodings/color/policy.js +88 -0
- package/src/actions/encodings/density/resolve.js +109 -0
- package/src/actions/encodings/density.js +193 -88
- package/src/actions/encodings/horizon/resolve.js +283 -0
- package/src/actions/encodings/horizon.js +364 -0
- package/src/actions/encodings/index.js +8 -2
- package/src/actions/encodings/offset.js +1 -1
- package/src/actions/encodings/parallel.js +115 -0
- package/src/actions/encodings/pathOrder.js +131 -0
- package/src/actions/encodings/position/apply.js +73 -4
- package/src/actions/encodings/{position.js → position/index.js} +21 -8
- package/src/actions/encodings/position/policies/arc.js +27 -4
- package/src/actions/encodings/position/policies/area.js +33 -2
- package/src/actions/encodings/position/policies/index.js +7 -2
- package/src/actions/encodings/position/policies/line.js +18 -2
- package/src/actions/encodings/position/resolve.js +25 -3
- package/src/actions/encodings/ranged.js +5 -3
- package/src/actions/encodings/ruleAppearance.js +88 -10
- package/src/actions/encodings/strokeDash.js +1 -1
- package/src/actions/encodings/text.js +1 -1
- package/src/actions/errorBands/options.js +1 -1
- package/src/actions/errorBars/options.js +1 -1
- package/src/actions/facets/actions.js +2 -3
- package/src/actions/facets/derive.js +35 -5
- package/src/actions/facets/guides.js +1 -1
- package/src/actions/facets/replay.js +7 -36
- package/src/actions/gradientPlots/components.js +177 -0
- package/src/actions/gradientPlots/create.js +96 -0
- package/src/actions/gradientPlots/edit.js +153 -0
- package/src/actions/gradientPlots/index.js +24 -0
- package/src/actions/gradientPlots/materialize.js +287 -0
- package/src/actions/gradientPlots/options.js +120 -0
- package/src/actions/gradientPlots/paint.js +107 -0
- package/src/actions/gradientPlots/rebind.js +51 -0
- package/src/actions/gradientPlots/resolve.js +81 -0
- package/src/actions/guides/applicability.js +44 -17
- package/src/actions/guides/axes/axes.js +37 -3
- package/src/actions/guides/axes/index.js +2 -0
- package/src/actions/guides/axes/labels.js +12 -7
- package/src/actions/guides/axes/parallel/resolve.js +86 -0
- package/src/actions/guides/axes/parallel.js +228 -0
- package/src/actions/guides/axes/ticks.js +1 -1
- package/src/actions/guides/axes/titles.js +12 -9
- package/src/actions/guides/grids/resolve.js +1 -1
- package/src/actions/guides/guides.js +60 -5
- package/src/actions/guides/legends/categorical/actions.js +48 -11
- package/src/actions/guides/legends/categorical/layout.js +4 -2
- package/src/actions/guides/legends/continuous/interval.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +1 -1
- package/src/actions/guides/legends/edit.js +7 -20
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/remove.js +6 -31
- package/src/actions/guides/legends/size.js +1 -1
- package/src/actions/guides/legends/strokeWidth.js +170 -0
- package/src/actions/guides/legends/target.js +25 -0
- package/src/actions/guides/polar/{axes.js → axes/index.js} +5 -5
- package/src/actions/guides/polar/index.js +1 -1
- package/src/actions/guides/polar/resolve.js +1 -1
- package/src/actions/guides/tickValues.js +1 -1
- package/src/actions/index.js +6 -0
- package/src/actions/intervals/resolve.js +1 -1
- package/src/actions/marks/arc/actions.js +2 -2
- package/src/actions/marks/area/actions.js +61 -137
- package/src/actions/marks/area/materialize.js +163 -0
- package/src/actions/marks/bar/edit.js +1 -1
- package/src/actions/marks/index.js +5 -1
- package/src/actions/marks/line/actions.js +100 -81
- package/src/actions/marks/line/materialize.js +146 -0
- package/src/actions/marks/point/create.js +76 -0
- package/src/actions/marks/point/edit.js +85 -0
- package/src/actions/marks/point/index.js +11 -1
- package/src/actions/marks/point/jitter.js +72 -0
- package/src/actions/marks/point/{actions.js → materialize.js} +175 -170
- package/src/actions/marks/rect/actions.js +4 -1
- package/src/actions/marks/remove.js +13 -2
- package/src/actions/marks/rule/actions.js +26 -4
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +20 -5
- package/src/actions/marks/text/index.js +7 -1
- package/src/actions/marks/text/layout.js +321 -0
- package/src/actions/primitives/semanticValidation/dataset.js +22 -0
- package/src/actions/primitives/semanticValidation/guide.js +43 -0
- package/src/actions/primitives/semanticValidation/index.js +23 -0
- package/src/actions/primitives/semanticValidation/layer.js +74 -0
- package/src/actions/primitives/semanticValidation/scale.js +74 -0
- package/src/actions/primitives/semanticValidation/shared.js +5 -0
- package/src/actions/primitives/semanticValue.js +1 -204
- package/src/actions/regression/components.js +1 -1
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +20 -22
- package/src/actions/scales/consumers/common.js +109 -0
- package/src/actions/scales/consumers/families.js +58 -0
- package/src/actions/scales/consumers/index.js +41 -0
- package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +10 -132
- package/src/actions/scales/create.js +1 -1
- package/src/actions/scales/definitions.js +32 -2
- package/src/actions/scales/edit.js +8 -206
- package/src/actions/scales/editPolicy.js +214 -0
- package/src/actions/scales/materialize.js +3 -3
- package/src/actions/scales/patch.js +26 -0
- package/src/actions/selection/actions.js +28 -3
- package/src/actions/violinPlots/create.js +198 -0
- package/src/actions/violinPlots/index.js +5 -0
- package/src/core/ChartProgram.js +22 -236
- package/src/core/compositionState.js +169 -0
- package/src/core/materializationState.js +65 -0
- package/src/core/programState.js +28 -0
- package/src/core/textMetrics.js +47 -0
- package/src/core/vocabulary.js +7 -2
- package/src/grammar/arcs.js +50 -13
- package/src/grammar/areaSeries.js +42 -6
- package/src/grammar/bars/aggregate.js +1 -1
- package/src/grammar/bin2d.js +333 -0
- package/src/grammar/categoricalDensity.js +192 -0
- package/src/grammar/coordinates.js +1 -1
- package/src/grammar/curveCommands.js +3 -1
- package/src/grammar/density.js +253 -27
- package/src/grammar/facets/dependencies.js +3 -11
- package/src/grammar/{facets.js → facets/index.js} +8 -6
- package/src/grammar/gradientProfile.js +213 -0
- package/src/grammar/horizon.js +455 -0
- package/src/grammar/interval.js +2 -96
- package/src/grammar/jitter.js +197 -0
- package/src/grammar/lineSeries.js +70 -1
- package/src/grammar/paint.js +88 -0
- package/src/grammar/palettes.js +5 -7
- package/src/grammar/parallelCoordinates.js +213 -0
- package/src/grammar/pathOrder.js +35 -0
- package/src/grammar/pointShapes.js +24 -0
- package/src/grammar/polarLineCommands.js +1 -1
- package/src/grammar/regression/derive.js +97 -0
- package/src/grammar/regression/index.js +22 -0
- package/src/grammar/regression/models.js +282 -0
- package/src/grammar/regression/parameters.js +106 -0
- package/src/grammar/rules.js +2 -1
- package/src/grammar/scales/appearance.js +21 -0
- package/src/grammar/scales/color.js +4 -2
- package/src/grammar/schemas/concreteGraphic.js +11 -1
- package/src/grammar/schemas/graphicBounds.js +19 -37
- package/src/grammar/schemas/semanticPath.js +17 -2
- package/src/grammar/statistics/studentT.js +98 -0
- package/src/grammar/transforms.js +101 -12
- package/src/grammar/window.js +339 -0
- package/src/layout/labels.js +270 -0
- package/src/layout/text.js +4 -1
- package/src/layout/title.js +10 -20
- package/src/materialization/bars/aggregate.js +1 -1
- package/src/materialization/bars/grouped.js +1 -1
- package/src/materialization/bars/histogram.js +1 -1
- package/src/materialization/bars/ranged.js +1 -1
- package/src/materialization/dataProvenance.js +56 -2
- package/src/materialization/density.js +1 -1
- package/src/materialization/dependencies.js +15 -101
- package/src/materialization/encodings.js +2 -2
- package/src/materialization/facetGuides/index.js +2 -0
- package/src/materialization/facetGuides/legacyCategorical.js +125 -0
- package/src/materialization/facetGuides/placement.js +126 -0
- package/src/materialization/facetGuides/preparation.js +188 -0
- package/src/materialization/facets.js +1 -1
- package/src/materialization/guides/resources.js +74 -24
- package/src/materialization/horizon.js +29 -0
- package/src/materialization/layout.js +15 -0
- package/src/materialization/marks/capabilities.js +161 -0
- package/src/materialization/marks/index.js +108 -0
- package/src/materialization/marks/pathOrder.js +71 -0
- package/src/materialization/marks/policies.js +91 -0
- package/src/materialization/rect.js +1 -1
- package/src/materialization/rowEncoding.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +102 -0
- package/src/materialization/scales/map.js +1 -1
- package/src/materialization/scales/policies/series.js +1 -1
- package/src/materialization/scales/resolve.js +3 -1
- package/src/materialization/selection/items/bar.js +1 -1
- package/src/materialization/selection/items/path.js +27 -1
- package/src/materialization/selection/items/point.js +1 -5
- package/src/materialization/selection/items/rect.js +21 -0
- package/src/materialization/selection/styles.js +21 -10
- package/src/materialization/text.js +12 -4
- package/src/renderers/canvas/fill.js +33 -0
- package/src/renderers/canvas/index.js +7 -0
- package/src/renderers/canvas/path.js +7 -4
- package/src/renderers/canvas/rect.js +9 -3
- package/src/renderers/canvas/text.js +14 -1
- package/src/selectors/datasets.js +4 -0
- package/src/theme/defaults.js +4 -0
- package/types/extension.d.ts +10 -1
- package/types/index.d.ts +76 -0
- package/types/program.d.ts +632 -10
- package/src/actions/encodings/color.js +0 -424
- package/src/grammar/regression.js +0 -578
- package/src/grammar/scales.js +0 -1
- package/src/materialization/facetGuides.js +0 -426
- package/src/materialization/marks.js +0 -316
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
|
+
|
|
3
|
+
const TRANSFORM_KEYS = Object.freeze([
|
|
4
|
+
"type", "partitionBy", "sortBy", "operations"
|
|
5
|
+
]);
|
|
6
|
+
const SORT_KEYS = Object.freeze(["field", "order"]);
|
|
7
|
+
const ORDER_VALUES = Object.freeze(["ascending", "descending"]);
|
|
8
|
+
const OPERATION_VALUES = Object.freeze([
|
|
9
|
+
"rowNumber", "rank", "denseRank", "cumulativeSum", "lag", "lead"
|
|
10
|
+
]);
|
|
11
|
+
const POSITION_OPERATIONS = new Set(["rowNumber", "rank", "denseRank"]);
|
|
12
|
+
const OFFSET_OPERATIONS = new Set(["lag", "lead"]);
|
|
13
|
+
|
|
14
|
+
function requireField(value, label) {
|
|
15
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
16
|
+
throw new TypeError(`${label} must be a non-empty string.`);
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function rejectUnknownKeys(value, supported, label) {
|
|
22
|
+
const unknown = Object.keys(value).find(key => !supported.includes(key));
|
|
23
|
+
if (unknown !== undefined) {
|
|
24
|
+
throw new Error(`Unknown ${label} property "${unknown}".`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validateFieldList(value, label) {
|
|
29
|
+
if (
|
|
30
|
+
!Array.isArray(value) ||
|
|
31
|
+
value.some(field => typeof field !== "string" || field.length === 0)
|
|
32
|
+
) {
|
|
33
|
+
throw new TypeError(`${label} must contain field names.`);
|
|
34
|
+
}
|
|
35
|
+
if (new Set(value).size !== value.length) {
|
|
36
|
+
throw new Error(`${label} fields must be unique.`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function validateSortBy(sortBy) {
|
|
41
|
+
if (!Array.isArray(sortBy)) {
|
|
42
|
+
throw new TypeError("Window sortBy must be an array.");
|
|
43
|
+
}
|
|
44
|
+
const fields = [];
|
|
45
|
+
sortBy.forEach((sort, index) => {
|
|
46
|
+
if (!isPlainObject(sort)) {
|
|
47
|
+
throw new TypeError(`Window sortBy[${index}] must be a plain object.`);
|
|
48
|
+
}
|
|
49
|
+
rejectUnknownKeys(sort, SORT_KEYS, `window sortBy[${index}]`);
|
|
50
|
+
fields.push(requireField(sort.field, `Window sortBy[${index}].field`));
|
|
51
|
+
if (!ORDER_VALUES.includes(sort.order)) {
|
|
52
|
+
throw new Error(`Unsupported window sort order "${sort.order}".`);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
if (new Set(fields).size !== fields.length) {
|
|
56
|
+
throw new Error("Window sortBy fields must be unique.");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function operationKeys(operation) {
|
|
61
|
+
if (POSITION_OPERATIONS.has(operation.op)) return ["op", "as"];
|
|
62
|
+
if (operation.op === "cumulativeSum") return ["op", "field", "as"];
|
|
63
|
+
if (OFFSET_OPERATIONS.has(operation.op)) {
|
|
64
|
+
return ["op", "field", "as", "offset", "default"];
|
|
65
|
+
}
|
|
66
|
+
return ["op"];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function validateOperations(operations, sortBy) {
|
|
70
|
+
if (!Array.isArray(operations) || operations.length === 0) {
|
|
71
|
+
throw new TypeError("Window operations must be a non-empty array.");
|
|
72
|
+
}
|
|
73
|
+
const outputs = new Set();
|
|
74
|
+
operations.forEach((operation, index) => {
|
|
75
|
+
if (!isPlainObject(operation)) {
|
|
76
|
+
throw new TypeError(`Window operation ${index} must be a plain object.`);
|
|
77
|
+
}
|
|
78
|
+
if (!OPERATION_VALUES.includes(operation.op)) {
|
|
79
|
+
throw new Error(`Unsupported window operation "${operation.op}".`);
|
|
80
|
+
}
|
|
81
|
+
rejectUnknownKeys(operation, operationKeys(operation), `window operation ${index}`);
|
|
82
|
+
const output = requireField(operation.as, `Window operation ${index} as`);
|
|
83
|
+
if (outputs.has(output)) {
|
|
84
|
+
throw new Error(`Window output field "${output}" must be unique.`);
|
|
85
|
+
}
|
|
86
|
+
outputs.add(output);
|
|
87
|
+
if (["rank", "denseRank"].includes(operation.op) && sortBy.length === 0) {
|
|
88
|
+
throw new Error(`${operation.op} requires a non-empty window sortBy.`);
|
|
89
|
+
}
|
|
90
|
+
if (!POSITION_OPERATIONS.has(operation.op)) {
|
|
91
|
+
requireField(operation.field, `Window ${operation.op} field`);
|
|
92
|
+
}
|
|
93
|
+
if (OFFSET_OPERATIONS.has(operation.op)) {
|
|
94
|
+
if (!Number.isInteger(operation.offset) || operation.offset <= 0) {
|
|
95
|
+
throw new RangeError(`Window ${operation.op} offset must be a positive integer.`);
|
|
96
|
+
}
|
|
97
|
+
if (!Object.hasOwn(operation, "default")) {
|
|
98
|
+
throw new TypeError(`Window ${operation.op} requires a default value.`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function validateWindowTransform(transform) {
|
|
105
|
+
if (!isPlainObject(transform)) {
|
|
106
|
+
throw new TypeError("Window transform must be a plain object.");
|
|
107
|
+
}
|
|
108
|
+
rejectUnknownKeys(transform, TRANSFORM_KEYS, "window transform");
|
|
109
|
+
if (transform.type !== "window") {
|
|
110
|
+
throw new Error(`Unsupported window transform "${transform.type}".`);
|
|
111
|
+
}
|
|
112
|
+
validateFieldList(transform.partitionBy, "Window partitionBy");
|
|
113
|
+
validateSortBy(transform.sortBy);
|
|
114
|
+
validateOperations(transform.operations, transform.sortBy);
|
|
115
|
+
return transform;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function normalizePartitionBy(value) {
|
|
119
|
+
if (value === undefined) return [];
|
|
120
|
+
return Array.isArray(value) ? [...value] : [value];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeSortBy(value) {
|
|
124
|
+
if (value === undefined) return [];
|
|
125
|
+
if (!Array.isArray(value)) return value;
|
|
126
|
+
return value.map(sort => isPlainObject(sort)
|
|
127
|
+
? { field: sort.field, order: sort.order ?? "ascending" }
|
|
128
|
+
: sort
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function normalizeOperations(value) {
|
|
133
|
+
if (!Array.isArray(value)) return value;
|
|
134
|
+
return value.map(operation => {
|
|
135
|
+
if (!isPlainObject(operation) || !OFFSET_OPERATIONS.has(operation.op)) {
|
|
136
|
+
return operation;
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
...operation,
|
|
140
|
+
offset: operation.offset ?? 1,
|
|
141
|
+
default: Object.hasOwn(operation, "default") ? operation.default : null
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function normalizeWindowTransform({
|
|
147
|
+
partitionBy,
|
|
148
|
+
sortBy,
|
|
149
|
+
operations
|
|
150
|
+
} = {}) {
|
|
151
|
+
const transform = {
|
|
152
|
+
type: "window",
|
|
153
|
+
partitionBy: normalizePartitionBy(partitionBy),
|
|
154
|
+
sortBy: normalizeSortBy(sortBy),
|
|
155
|
+
operations: normalizeOperations(operations)
|
|
156
|
+
};
|
|
157
|
+
validateWindowTransform(transform);
|
|
158
|
+
return cloneAndFreeze(transform);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function scalarKey(value, label) {
|
|
162
|
+
if (value === null) return "null";
|
|
163
|
+
if (typeof value === "string") return `string:${value.length}:${value}`;
|
|
164
|
+
if (typeof value === "boolean") return `boolean:${value}`;
|
|
165
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
166
|
+
return `number:${Object.is(value, -0) ? 0 : value}`;
|
|
167
|
+
}
|
|
168
|
+
throw new TypeError(
|
|
169
|
+
`${label} must contain null, strings, booleans, or finite numbers.`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function compareScalar(left, right, label) {
|
|
174
|
+
const leftMissing = left === null || left === undefined;
|
|
175
|
+
const rightMissing = right === null || right === undefined;
|
|
176
|
+
if (leftMissing || rightMissing) {
|
|
177
|
+
if (leftMissing && rightMissing) return 0;
|
|
178
|
+
return leftMissing ? 1 : -1;
|
|
179
|
+
}
|
|
180
|
+
if (typeof left !== typeof right || !["number", "string", "boolean"].includes(
|
|
181
|
+
typeof left
|
|
182
|
+
)) {
|
|
183
|
+
throw new TypeError(`${label} must contain one comparable primitive type.`);
|
|
184
|
+
}
|
|
185
|
+
if (typeof left === "number" && (!Number.isFinite(left) || !Number.isFinite(right))) {
|
|
186
|
+
throw new TypeError(`${label} must contain finite numbers.`);
|
|
187
|
+
}
|
|
188
|
+
if (left === right) return 0;
|
|
189
|
+
return left < right ? -1 : 1;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function compareEntries(left, right, sortBy, stable = true) {
|
|
193
|
+
for (const sort of sortBy) {
|
|
194
|
+
const comparison = compareScalar(
|
|
195
|
+
left.row[sort.field],
|
|
196
|
+
right.row[sort.field],
|
|
197
|
+
`Window sort field "${sort.field}"`
|
|
198
|
+
);
|
|
199
|
+
if (comparison !== 0) {
|
|
200
|
+
return sort.order === "ascending" ? comparison : -comparison;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return stable ? left.index - right.index : 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function validateSourceFields(rows, transform) {
|
|
207
|
+
const sourceFields = new Set(rows.flatMap(row => Object.keys(row)));
|
|
208
|
+
for (const field of [...transform.partitionBy, ...transform.sortBy.map(sort => sort.field)]) {
|
|
209
|
+
if (!sourceFields.has(field)) {
|
|
210
|
+
throw new Error(`Window source does not contain field "${field}".`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const available = new Set(sourceFields);
|
|
214
|
+
for (const operation of transform.operations) {
|
|
215
|
+
if (
|
|
216
|
+
!POSITION_OPERATIONS.has(operation.op) &&
|
|
217
|
+
!available.has(operation.field)
|
|
218
|
+
) {
|
|
219
|
+
throw new Error(`Window source does not contain field "${operation.field}".`);
|
|
220
|
+
}
|
|
221
|
+
if (available.has(operation.as)) {
|
|
222
|
+
throw new Error(`Window output field "${operation.as}" already exists.`);
|
|
223
|
+
}
|
|
224
|
+
available.add(operation.as);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function validatePartitionSortValues(partition, transform) {
|
|
229
|
+
for (const field of transform.partitionBy) {
|
|
230
|
+
partition.forEach(entry => scalarKey(
|
|
231
|
+
entry.row[field],
|
|
232
|
+
`Window partition field "${field}"`
|
|
233
|
+
));
|
|
234
|
+
}
|
|
235
|
+
for (const sort of transform.sortBy) {
|
|
236
|
+
const types = new Set(partition.flatMap(({ row }) => {
|
|
237
|
+
const value = row[sort.field];
|
|
238
|
+
if (value === null || value === undefined) return [];
|
|
239
|
+
if (
|
|
240
|
+
!["number", "string", "boolean"].includes(typeof value) ||
|
|
241
|
+
(typeof value === "number" && !Number.isFinite(value))
|
|
242
|
+
) {
|
|
243
|
+
throw new TypeError(
|
|
244
|
+
`Window sort field "${sort.field}" must contain comparable primitive values.`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
return [typeof value];
|
|
248
|
+
}));
|
|
249
|
+
if (types.size > 1) {
|
|
250
|
+
throw new TypeError(
|
|
251
|
+
`Window sort field "${sort.field}" must contain one comparable primitive type.`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function partitionRows(entries, partitionBy) {
|
|
258
|
+
const partitions = new Map();
|
|
259
|
+
for (const entry of entries) {
|
|
260
|
+
const key = partitionBy.length === 0
|
|
261
|
+
? "all"
|
|
262
|
+
: partitionBy.map(field => scalarKey(
|
|
263
|
+
entry.row[field],
|
|
264
|
+
`Window partition field "${field}"`
|
|
265
|
+
)).join("\0");
|
|
266
|
+
if (!partitions.has(key)) partitions.set(key, []);
|
|
267
|
+
partitions.get(key).push(entry);
|
|
268
|
+
}
|
|
269
|
+
return [...partitions.values()];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function applyOperation(partition, operation, sortBy) {
|
|
273
|
+
if (operation.op === "rowNumber") {
|
|
274
|
+
partition.forEach((entry, index) => {
|
|
275
|
+
entry.row[operation.as] = index + 1;
|
|
276
|
+
});
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (["rank", "denseRank"].includes(operation.op)) {
|
|
280
|
+
let rank = 1;
|
|
281
|
+
let denseRank = 1;
|
|
282
|
+
partition.forEach((entry, index) => {
|
|
283
|
+
if (
|
|
284
|
+
index > 0 &&
|
|
285
|
+
compareEntries(entry, partition[index - 1], sortBy, false) !== 0
|
|
286
|
+
) {
|
|
287
|
+
rank = index + 1;
|
|
288
|
+
denseRank += 1;
|
|
289
|
+
}
|
|
290
|
+
entry.row[operation.as] = operation.op === "rank" ? rank : denseRank;
|
|
291
|
+
});
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (operation.op === "cumulativeSum") {
|
|
295
|
+
let total = 0;
|
|
296
|
+
for (const entry of partition) {
|
|
297
|
+
const value = entry.row[operation.field];
|
|
298
|
+
if (!Number.isFinite(value)) {
|
|
299
|
+
throw new TypeError(
|
|
300
|
+
`Window cumulativeSum field "${operation.field}" must contain finite numbers.`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
total += value;
|
|
304
|
+
entry.row[operation.as] = total;
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const direction = operation.op === "lag" ? -1 : 1;
|
|
309
|
+
partition.forEach((entry, index) => {
|
|
310
|
+
const peer = partition[index + direction * operation.offset];
|
|
311
|
+
entry.row[operation.as] = peer === undefined
|
|
312
|
+
? operation.default
|
|
313
|
+
: peer.row[operation.field];
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function deriveWindowRows(rows, transform) {
|
|
318
|
+
if (!Array.isArray(rows)) {
|
|
319
|
+
throw new TypeError("Window source rows must be an array.");
|
|
320
|
+
}
|
|
321
|
+
if (!rows.every(isPlainObject)) {
|
|
322
|
+
throw new TypeError("Window source rows must be plain objects.");
|
|
323
|
+
}
|
|
324
|
+
validateWindowTransform(transform);
|
|
325
|
+
validateSourceFields(rows, transform);
|
|
326
|
+
const entries = rows.map((row, index) => ({ index, row: { ...row } }));
|
|
327
|
+
const partitions = partitionRows(entries, transform.partitionBy);
|
|
328
|
+
for (const partition of partitions) {
|
|
329
|
+
validatePartitionSortValues(partition, transform);
|
|
330
|
+
partition.sort((left, right) => compareEntries(left, right, transform.sortBy));
|
|
331
|
+
}
|
|
332
|
+
for (const operation of transform.operations) {
|
|
333
|
+
for (const partition of partitions) {
|
|
334
|
+
applyOperation(partition, operation, transform.sortBy);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
entries.sort((left, right) => left.index - right.index);
|
|
338
|
+
return cloneAndFreeze(entries.map(entry => entry.row));
|
|
339
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
|
+
import { resolveTextBounds } from "../core/textMetrics.js";
|
|
3
|
+
import {
|
|
4
|
+
validateNonNegativeFinite,
|
|
5
|
+
validateOptionObject
|
|
6
|
+
} from "../core/validation.js";
|
|
7
|
+
|
|
8
|
+
const AXES = new Set(["x", "y", "both"]);
|
|
9
|
+
const BOUNDS = new Set(["plot", "canvas"]);
|
|
10
|
+
const POLICY_OPTIONS = Object.freeze([
|
|
11
|
+
"axis", "padding", "maxDisplacement", "bounds"
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_LABEL_LAYOUT_GEOMETRY = cloneAndFreeze({
|
|
15
|
+
axis: "both",
|
|
16
|
+
padding: 3,
|
|
17
|
+
maxDisplacement: 48,
|
|
18
|
+
bounds: "plot"
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export function normalizeLabelLayoutGeometry(options = {}) {
|
|
22
|
+
validateOptionObject(options, POLICY_OPTIONS, "label layout");
|
|
23
|
+
const normalized = {
|
|
24
|
+
...DEFAULT_LABEL_LAYOUT_GEOMETRY,
|
|
25
|
+
...options
|
|
26
|
+
};
|
|
27
|
+
if (!AXES.has(normalized.axis)) {
|
|
28
|
+
throw new Error(`Unsupported label layout axis "${normalized.axis}".`);
|
|
29
|
+
}
|
|
30
|
+
if (!BOUNDS.has(normalized.bounds)) {
|
|
31
|
+
throw new Error(`Unsupported label layout bounds "${normalized.bounds}".`);
|
|
32
|
+
}
|
|
33
|
+
validateNonNegativeFinite(normalized.padding, "Label layout padding");
|
|
34
|
+
validateNonNegativeFinite(
|
|
35
|
+
normalized.maxDisplacement,
|
|
36
|
+
"Label layout maxDisplacement"
|
|
37
|
+
);
|
|
38
|
+
return cloneAndFreeze(normalized);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function validateBounds(bounds) {
|
|
42
|
+
if (
|
|
43
|
+
!isPlainObject(bounds) ||
|
|
44
|
+
![bounds.left, bounds.right, bounds.top, bounds.bottom].every(Number.isFinite) ||
|
|
45
|
+
bounds.right <= bounds.left ||
|
|
46
|
+
bounds.bottom <= bounds.top
|
|
47
|
+
) {
|
|
48
|
+
throw new RangeError("Label layout requires ordered finite bounds.");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function validateItem(item, ids) {
|
|
53
|
+
if (!isPlainObject(item)) {
|
|
54
|
+
throw new TypeError("Label layout items must be plain objects.");
|
|
55
|
+
}
|
|
56
|
+
if (typeof item.id !== "string" || item.id.length === 0 || ids.has(item.id)) {
|
|
57
|
+
throw new TypeError("Label layout items require unique non-empty ids.");
|
|
58
|
+
}
|
|
59
|
+
if (
|
|
60
|
+
![item.x, item.y, item.sourceX, item.sourceY, item.fontSize]
|
|
61
|
+
.every(Number.isFinite) ||
|
|
62
|
+
item.fontSize <= 0 ||
|
|
63
|
+
typeof item.text !== "string" ||
|
|
64
|
+
item.text.length === 0
|
|
65
|
+
) {
|
|
66
|
+
throw new TypeError(
|
|
67
|
+
"Label layout items require text, positive fontSize, and finite geometry."
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
ids.add(item.id);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function expanded(bounds, padding) {
|
|
74
|
+
const half = padding / 2;
|
|
75
|
+
return {
|
|
76
|
+
left: bounds.left - half,
|
|
77
|
+
right: bounds.right + half,
|
|
78
|
+
top: bounds.top - half,
|
|
79
|
+
bottom: bounds.bottom + half
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function intersectionArea(first, second) {
|
|
84
|
+
const width = Math.min(first.right, second.right) -
|
|
85
|
+
Math.max(first.left, second.left);
|
|
86
|
+
const height = Math.min(first.bottom, second.bottom) -
|
|
87
|
+
Math.max(first.top, second.top);
|
|
88
|
+
return width > 0 && height > 0 ? width * height : 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function overflow(bounds, boundary) {
|
|
92
|
+
return Math.max(0, boundary.left - bounds.left) +
|
|
93
|
+
Math.max(0, bounds.right - boundary.right) +
|
|
94
|
+
Math.max(0, boundary.top - bounds.top) +
|
|
95
|
+
Math.max(0, bounds.bottom - boundary.bottom);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function enumerateLabelOffsets({ axis, padding, maxDisplacement }) {
|
|
99
|
+
const policy = normalizeLabelLayoutGeometry({ axis, padding, maxDisplacement });
|
|
100
|
+
const step = Math.max(2, policy.padding);
|
|
101
|
+
const limit = Math.ceil(policy.maxDisplacement / step);
|
|
102
|
+
const candidates = [];
|
|
103
|
+
for (let x = -limit; x <= limit; x += 1) {
|
|
104
|
+
for (let y = -limit; y <= limit; y += 1) {
|
|
105
|
+
if (policy.axis === "x" && y !== 0) continue;
|
|
106
|
+
if (policy.axis === "y" && x !== 0) continue;
|
|
107
|
+
const offset = { x: x * step, y: y * step };
|
|
108
|
+
const distanceSquared = offset.x ** 2 + offset.y ** 2;
|
|
109
|
+
if (Math.sqrt(distanceSquared) > policy.maxDisplacement + 1e-9) continue;
|
|
110
|
+
candidates.push({ ...offset, distanceSquared });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
candidates.sort((first, second) =>
|
|
114
|
+
first.distanceSquared - second.distanceSquared ||
|
|
115
|
+
Math.abs(first.x) - Math.abs(second.x) ||
|
|
116
|
+
first.y - second.y ||
|
|
117
|
+
second.x - first.x
|
|
118
|
+
);
|
|
119
|
+
return cloneAndFreeze(candidates);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function textBounds(item, offset = { x: 0, y: 0 }) {
|
|
123
|
+
return resolveTextBounds({
|
|
124
|
+
x: item.x + offset.x,
|
|
125
|
+
y: item.y + offset.y,
|
|
126
|
+
text: item.text,
|
|
127
|
+
fontSize: item.fontSize,
|
|
128
|
+
textAlign: item.textAlign,
|
|
129
|
+
textBaseline: item.textBaseline,
|
|
130
|
+
rotation: item.rotation
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function candidateScore(bounds, boundary, placed, candidate, order) {
|
|
135
|
+
return {
|
|
136
|
+
outside: overflow(bounds, boundary),
|
|
137
|
+
overlap: placed.reduce(
|
|
138
|
+
(area, prior) => area + intersectionArea(bounds, prior),
|
|
139
|
+
0
|
|
140
|
+
),
|
|
141
|
+
distanceSquared: candidate.distanceSquared,
|
|
142
|
+
order
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function compareScore(first, second) {
|
|
147
|
+
return Number(first.outside > 0) - Number(second.outside > 0) ||
|
|
148
|
+
first.outside - second.outside ||
|
|
149
|
+
first.overlap - second.overlap ||
|
|
150
|
+
first.distanceSquared - second.distanceSquared ||
|
|
151
|
+
first.order - second.order;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function overlapPairs(items) {
|
|
155
|
+
const pairs = [];
|
|
156
|
+
for (let first = 0; first < items.length; first += 1) {
|
|
157
|
+
for (let second = first + 1; second < items.length; second += 1) {
|
|
158
|
+
if (
|
|
159
|
+
intersectionArea(
|
|
160
|
+
items[first].collisionBounds,
|
|
161
|
+
items[second].collisionBounds
|
|
162
|
+
) > 0
|
|
163
|
+
) {
|
|
164
|
+
pairs.push([items[first].id, items[second].id]);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return pairs;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function resolveLabelLayout({ items, bounds, ...options } = {}) {
|
|
172
|
+
if (!Array.isArray(items)) {
|
|
173
|
+
throw new TypeError("Label layout requires an item array.");
|
|
174
|
+
}
|
|
175
|
+
validateBounds(bounds);
|
|
176
|
+
const policy = normalizeLabelLayoutGeometry(options);
|
|
177
|
+
const ids = new Set();
|
|
178
|
+
for (const item of items) validateItem(item, ids);
|
|
179
|
+
const candidates = enumerateLabelOffsets(policy);
|
|
180
|
+
const base = items.map(item => ({
|
|
181
|
+
id: item.id,
|
|
182
|
+
collisionBounds: expanded(textBounds(item), policy.padding)
|
|
183
|
+
}));
|
|
184
|
+
const placed = [];
|
|
185
|
+
const resolved = [];
|
|
186
|
+
for (const item of items) {
|
|
187
|
+
let best;
|
|
188
|
+
for (const [order, candidate] of candidates.entries()) {
|
|
189
|
+
const boundsAtCandidate = textBounds(item, candidate);
|
|
190
|
+
const collisionBounds = expanded(boundsAtCandidate, policy.padding);
|
|
191
|
+
const score = candidateScore(
|
|
192
|
+
collisionBounds,
|
|
193
|
+
bounds,
|
|
194
|
+
placed,
|
|
195
|
+
candidate,
|
|
196
|
+
order
|
|
197
|
+
);
|
|
198
|
+
if (best === undefined || compareScore(score, best.score) < 0) {
|
|
199
|
+
best = { candidate, bounds: boundsAtCandidate, collisionBounds, score };
|
|
200
|
+
}
|
|
201
|
+
if (score.outside === 0 && score.overlap === 0) break;
|
|
202
|
+
}
|
|
203
|
+
const resolvedItem = {
|
|
204
|
+
...item,
|
|
205
|
+
baseX: item.x,
|
|
206
|
+
baseY: item.y,
|
|
207
|
+
x: item.x + best.candidate.x,
|
|
208
|
+
y: item.y + best.candidate.y,
|
|
209
|
+
dx: best.candidate.x,
|
|
210
|
+
dy: best.candidate.y,
|
|
211
|
+
distance: Math.sqrt(best.candidate.distanceSquared),
|
|
212
|
+
bounds: best.bounds,
|
|
213
|
+
collisionBounds: best.collisionBounds
|
|
214
|
+
};
|
|
215
|
+
placed.push(resolvedItem.collisionBounds);
|
|
216
|
+
resolved.push(resolvedItem);
|
|
217
|
+
}
|
|
218
|
+
const beforePairs = overlapPairs(base);
|
|
219
|
+
const afterPairs = overlapPairs(resolved);
|
|
220
|
+
const outside = resolved
|
|
221
|
+
.filter(item => overflow(item.collisionBounds, bounds) > 0)
|
|
222
|
+
.map(item => item.id);
|
|
223
|
+
const warnings = [];
|
|
224
|
+
if (afterPairs.length > 0) warnings.push({ code: "overlap", pairs: afterPairs });
|
|
225
|
+
if (outside.length > 0) warnings.push({ code: "bounds", items: outside });
|
|
226
|
+
return cloneAndFreeze({
|
|
227
|
+
items: resolved,
|
|
228
|
+
overlapBefore: beforePairs.length,
|
|
229
|
+
overlapAfter: afterPairs.length,
|
|
230
|
+
warnings
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function contains(bounds, point) {
|
|
235
|
+
return point.x >= bounds.left && point.x <= bounds.right &&
|
|
236
|
+
point.y >= bounds.top && point.y <= bounds.bottom;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function resolveLabelLeader(item) {
|
|
240
|
+
if (!isPlainObject(item) || ![item.dx, item.dy].every(Number.isFinite)) {
|
|
241
|
+
throw new TypeError("Label leader requires a resolved label item.");
|
|
242
|
+
}
|
|
243
|
+
if (item.dx === 0 && item.dy === 0) return undefined;
|
|
244
|
+
const source = { x: item.sourceX, y: item.sourceY };
|
|
245
|
+
if (contains(item.bounds, source)) return undefined;
|
|
246
|
+
const center = {
|
|
247
|
+
x: (item.bounds.left + item.bounds.right) / 2,
|
|
248
|
+
y: (item.bounds.top + item.bounds.bottom) / 2
|
|
249
|
+
};
|
|
250
|
+
const dx = center.x - source.x;
|
|
251
|
+
const dy = center.y - source.y;
|
|
252
|
+
const enterX = dx > 0
|
|
253
|
+
? (item.bounds.left - source.x) / dx
|
|
254
|
+
: dx < 0
|
|
255
|
+
? (item.bounds.right - source.x) / dx
|
|
256
|
+
: 0;
|
|
257
|
+
const enterY = dy > 0
|
|
258
|
+
? (item.bounds.top - source.y) / dy
|
|
259
|
+
: dy < 0
|
|
260
|
+
? (item.bounds.bottom - source.y) / dy
|
|
261
|
+
: 0;
|
|
262
|
+
const ratio = Math.max(0, Math.min(1, Math.max(enterX, enterY)));
|
|
263
|
+
return cloneAndFreeze({
|
|
264
|
+
id: item.id,
|
|
265
|
+
x1: source.x,
|
|
266
|
+
y1: source.y,
|
|
267
|
+
x2: source.x + dx * ratio,
|
|
268
|
+
y2: source.y + dy * ratio
|
|
269
|
+
});
|
|
270
|
+
}
|
package/src/layout/text.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { measureTextWidth } from "../core/textMetrics.js";
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export {
|
|
4
|
+
measureTextWidth,
|
|
5
|
+
resolveTextBounds
|
|
6
|
+
} from "../core/textMetrics.js";
|
|
4
7
|
|
|
5
8
|
function wrapCodePoints(text, maxWidth, style) {
|
|
6
9
|
const lines = [];
|
package/src/layout/title.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { measureTextWidth, wrapText } from "./text.js";
|
|
1
|
+
import { measureTextWidth, resolveTextBounds, wrapText } from "./text.js";
|
|
2
2
|
|
|
3
3
|
function lineCenters(lines, style, lineHeight, start) {
|
|
4
4
|
return lines.map((_, index) => start + style.fontSize / 2 + index * lineHeight);
|
|
@@ -62,25 +62,15 @@ export function alignedTextAnchor(start, length, align) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
function axisAlignedTextBounds({ x, y, text, style, align, rotation }) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
].map(([localX, localY]) => ({
|
|
75
|
-
x: x + localX * cosine - localY * sine,
|
|
76
|
-
y: y + localX * sine + localY * cosine
|
|
77
|
-
}));
|
|
78
|
-
return {
|
|
79
|
-
left: Math.min(...corners.map(point => point.x)),
|
|
80
|
-
right: Math.max(...corners.map(point => point.x)),
|
|
81
|
-
top: Math.min(...corners.map(point => point.y)),
|
|
82
|
-
bottom: Math.max(...corners.map(point => point.y))
|
|
83
|
-
};
|
|
65
|
+
return resolveTextBounds({
|
|
66
|
+
x,
|
|
67
|
+
y,
|
|
68
|
+
text,
|
|
69
|
+
...style,
|
|
70
|
+
textAlign: align,
|
|
71
|
+
textBaseline: "middle",
|
|
72
|
+
rotation
|
|
73
|
+
});
|
|
84
74
|
}
|
|
85
75
|
|
|
86
76
|
function unionBounds(bounds) {
|
|
@@ -2,7 +2,7 @@ import { deriveBarAggregates } from "../../grammar/bars/aggregate.js";
|
|
|
2
2
|
import {
|
|
3
3
|
mapContinuousScaleValues,
|
|
4
4
|
mapOrdinalPositionValues
|
|
5
|
-
} from "../../grammar/scales.js";
|
|
5
|
+
} from "../../grammar/scales/index.js";
|
|
6
6
|
import { sameOrderedValues } from "../../core/validation.js";
|
|
7
7
|
import {
|
|
8
8
|
DEFAULT_BAR_STROKE,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
mapOrdinalValues,
|
|
9
9
|
readNominalField,
|
|
10
10
|
readQuantitativeField
|
|
11
|
-
} from "../../grammar/scales.js";
|
|
11
|
+
} from "../../grammar/scales/index.js";
|
|
12
12
|
import { resolveBarColorLayout } from "../../grammar/bars/policy.js";
|
|
13
13
|
import { layoutSeriesPartition } from "../../grammar/seriesLayout.js";
|
|
14
14
|
import {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mapContinuousScaleValues, mapOrdinalPositionValues, mapOrdinalValues } from "../../grammar/scales.js";
|
|
1
|
+
import { mapContinuousScaleValues, mapOrdinalPositionValues, mapOrdinalValues } from "../../grammar/scales/index.js";
|
|
2
2
|
import { resolveBarWidth } from "../../grammar/bars/geometry.js";
|
|
3
3
|
import { DEFAULT_BAR_FILL, DEFAULT_BAR_STROKE, DEFAULT_BAR_STROKE_WIDTH } from "./resolve.js";
|
|
4
4
|
|