ggaction 0.0.9 → 0.0.11
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 +84 -0
- package/README.md +5 -4
- package/knowledge/action-card.schema.json +1 -0
- package/knowledge/action-cards.json +1 -10941
- package/knowledge/action-cards.schema.json +1 -0
- package/knowledge/extension-authoring.md +248 -0
- package/knowledge/intent-taxonomy.json +1 -183
- package/knowledge/intent-taxonomy.schema.json +1 -0
- package/knowledge/mcp-resources.json +1 -95
- package/knowledge/mcp-resources.schema.json +1 -0
- package/knowledge/task-packet.schema.json +1 -159
- package/knowledge/task-resolver.js +1 -1230
- package/package.json +19 -2
- package/src/actions/boxPlots/edit.js +45 -298
- package/src/actions/boxPlots/options.js +46 -77
- package/src/actions/boxPlots/resolve.js +1 -1
- package/src/actions/charts/bar.js +3 -0
- package/src/actions/charts/heatmap.js +73 -83
- package/src/actions/charts/index.js +1 -1
- package/src/actions/charts/parallel.js +35 -1
- package/src/actions/charts/shared.js +20 -0
- package/src/actions/composition/actions.js +46 -49
- package/src/actions/data/bin2d.js +105 -2
- package/src/actions/data/box.js +17 -17
- package/src/actions/data/density.js +42 -78
- package/src/actions/data/gradientProfile.js +22 -52
- package/src/actions/data/horizon.js +16 -41
- package/src/actions/data/index.js +10 -10
- package/src/actions/data/interval.js +16 -38
- package/src/actions/data/intervalEdit.js +55 -0
- package/src/actions/data/regression.js +7 -17
- package/src/actions/data/shared.js +47 -0
- package/src/actions/data/timeUnit.js +14 -38
- package/src/actions/data/window.js +14 -38
- package/src/actions/distributions/revision.js +154 -34
- package/src/actions/encodings/angle.js +9 -10
- package/src/actions/encodings/appearance.js +17 -51
- package/src/actions/encodings/density/resolve.js +3 -1
- package/src/actions/encodings/density.js +1 -15
- package/src/actions/encodings/horizon/resolve.js +13 -3
- package/src/actions/encodings/horizon.js +8 -7
- package/src/actions/encodings/index.js +9 -9
- package/src/actions/encodings/offset.js +8 -24
- package/src/actions/encodings/parallel.js +9 -10
- package/src/actions/encodings/pathOrder.js +6 -13
- package/src/actions/encodings/position/policies/index.js +4 -4
- package/src/actions/encodings/position/policies/line.js +47 -17
- package/src/actions/encodings/position/resolve.js +24 -5
- package/src/actions/encodings/ranged.js +18 -23
- package/src/actions/encodings/remove.js +15 -25
- package/src/actions/encodings/ruleAppearance.js +9 -24
- package/src/actions/encodings/shared.js +37 -0
- package/src/actions/encodings/strokeDash.js +8 -23
- package/src/actions/encodings/text.js +9 -10
- package/src/actions/errorBands/create.js +105 -39
- package/src/actions/errorBands/edit.js +89 -93
- package/src/actions/errorBands/index.js +1 -2
- package/src/actions/errorBars/create.js +78 -27
- package/src/actions/errorBars/edit.js +81 -48
- package/src/actions/errorBars/index.js +1 -2
- package/src/actions/facets/actions.js +45 -69
- package/src/actions/facets/derive.js +38 -53
- package/src/actions/facets/replay.js +0 -1
- package/src/actions/gradientPlots/components.js +59 -48
- package/src/actions/gradientPlots/edit.js +35 -216
- package/src/actions/gradientPlots/index.js +11 -10
- package/src/actions/gradientPlots/materialize.js +21 -35
- package/src/actions/gradientPlots/options.js +5 -7
- package/src/actions/gradientPlots/paint.js +9 -6
- package/src/actions/gradientPlots/resolve.js +1 -1
- package/src/actions/guides/axes/index.js +3 -3
- package/src/actions/guides/axes/labels.js +68 -26
- package/src/actions/guides/axes/lines.js +22 -2
- package/src/actions/guides/axes/policy.js +28 -8
- package/src/actions/guides/axes/ticks.js +27 -9
- package/src/actions/guides/axes/titles.js +113 -65
- package/src/actions/guides/grids/resolve.js +14 -0
- package/src/actions/guides/guides.js +2 -4
- package/src/actions/guides/index.js +2 -2
- package/src/actions/guides/legends/categorical/actions.js +44 -68
- package/src/actions/guides/legends/categorical/components.js +6 -1
- package/src/actions/guides/legends/categorical/index.js +2 -2
- package/src/actions/guides/legends/categorical/layout.js +148 -175
- package/src/actions/guides/legends/categorical/recipes.js +5 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -1
- package/src/actions/guides/legends/continuous/common.js +117 -66
- package/src/actions/guides/legends/continuous/gradient.js +38 -37
- package/src/actions/guides/legends/continuous/interval.js +97 -76
- package/src/actions/guides/legends/continuous/opacity.js +28 -38
- package/src/actions/guides/legends/edit.js +14 -1
- package/src/actions/guides/legends/index.js +1 -1
- package/src/actions/guides/legends/lane.js +2 -1
- package/src/actions/guides/legends/remove.js +8 -0
- package/src/actions/guides/legends/size.js +36 -43
- package/src/actions/guides/legends/strokeWidth.js +20 -26
- package/src/actions/guides/polar/axes/labels.js +17 -1
- package/src/actions/guides/polar/axes/shared.js +7 -1
- package/src/actions/guides/polar/axes/titles.js +19 -2
- package/src/actions/guides/polar/grids.js +4 -0
- package/src/actions/guides/polar/resolve.js +6 -0
- package/src/actions/index.js +13 -13
- package/src/actions/intervals/resolve.js +87 -117
- package/src/actions/marks/arc/actions.js +31 -36
- package/src/actions/marks/area/actions.js +23 -31
- package/src/actions/marks/index.js +6 -6
- package/src/actions/marks/line/actions.js +33 -52
- package/src/actions/marks/line/materialize.js +7 -5
- package/src/actions/marks/point/materialize.js +5 -3
- package/src/actions/marks/remove.js +21 -0
- package/src/actions/marks/rule/actions.js +53 -53
- package/src/actions/marks/shared.js +8 -4
- package/src/actions/marks/text/actions.js +14 -6
- package/src/actions/marks/text/layout.js +21 -45
- package/src/actions/marks/tick/actions.js +22 -21
- package/src/actions/primitives/createGraphics.js +4 -0
- package/src/actions/primitives/editGraphics.js +4 -0
- package/src/actions/primitives/index.js +3 -3
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +2 -2
- package/src/actions/primitives/semanticValidation/layer.js +1 -1
- package/src/actions/regression/edit.js +3 -43
- package/src/actions/scales/consumers/families.js +24 -4
- package/src/actions/scales/consumers/index.js +1 -1
- package/src/actions/scales/consumers/seriesLayout.js +17 -0
- package/src/actions/scales/definitions.js +100 -103
- package/src/actions/scales/materialize.js +5 -0
- package/src/actions/selection/actions.js +79 -121
- package/src/actions/titles/actions.js +48 -94
- package/src/actions/titles/resolve.js +14 -27
- package/src/actions/violinPlots/create.js +37 -44
- package/src/core/action.js +20 -5
- package/src/core/extensionRegistry.js +104 -0
- package/src/core/textMetrics.js +74 -37
- package/src/core/validation.js +18 -0
- package/src/extension.js +8 -1
- package/src/grammar/aggregate.js +48 -28
- package/src/grammar/arcs.js +58 -11
- package/src/grammar/areaSeries.js +14 -16
- package/src/grammar/bin2d.js +37 -23
- package/src/grammar/boxPlot.js +14 -13
- package/src/grammar/categoricalDensity.js +14 -14
- package/src/grammar/categoryOrder.js +8 -14
- package/src/grammar/curveCommands.js +87 -20
- package/src/grammar/density.js +162 -141
- package/src/grammar/facets/index.js +14 -0
- package/src/grammar/facets/scales.js +7 -4
- package/src/grammar/gradientProfile.js +12 -15
- package/src/grammar/histogram.js +145 -83
- package/src/grammar/horizon.js +60 -23
- package/src/grammar/interval.js +14 -13
- package/src/grammar/lineSeries.js +91 -61
- package/src/grammar/numeric.js +290 -0
- package/src/grammar/paint.js +3 -4
- package/src/grammar/palettes.js +21 -32
- package/src/grammar/pathCommands.js +6 -0
- package/src/grammar/polarPaths.js +5 -5
- package/src/grammar/regression/derive.js +48 -9
- package/src/grammar/regression/models.js +248 -69
- package/src/grammar/regression/parameters.js +8 -0
- package/src/grammar/scales/color.js +35 -26
- package/src/grammar/scales/continuous.js +71 -24
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/discretized.js +42 -12
- package/src/grammar/scales/fields.js +21 -4
- package/src/grammar/scales/ordinal.js +117 -61
- package/src/grammar/scales/temporal.js +40 -11
- package/src/grammar/scales/transformed.js +199 -52
- package/src/grammar/schemas/graphicBounds.js +270 -79
- package/src/grammar/seriesLayout.js +50 -18
- package/src/grammar/ticks.js +123 -53
- package/src/grammar/timeUnit.js +18 -8
- package/src/grammar/transforms.js +4 -1
- package/src/grammar/window.js +70 -57
- package/src/index.js +1 -1
- package/src/layout/canvas.js +4 -8
- package/src/layout/composition.js +39 -40
- package/src/layout/facets.js +10 -14
- package/src/layout/labels.js +91 -15
- package/src/layout/legend.js +22 -20
- package/src/layout/legendLane.js +54 -40
- package/src/layout/title.js +7 -7
- package/src/materialization/bars/aggregate.js +14 -40
- package/src/materialization/bars/grouped.js +7 -18
- package/src/materialization/bars/histogram.js +2 -19
- package/src/materialization/bars/ranged.js +20 -15
- package/src/materialization/bars/resolve.js +21 -0
- package/src/materialization/facetGuides/legacyCategorical.js +2 -1
- package/src/materialization/facetGuides/placement.js +34 -19
- package/src/materialization/facetGuides/preparation.js +9 -5
- package/src/materialization/facets.js +39 -27
- package/src/materialization/guides/resources.js +2 -1
- package/src/materialization/marks/capabilities.js +5 -2
- package/src/materialization/marks/index.js +24 -4
- package/src/materialization/scales/policies/bar.js +61 -59
- package/src/materialization/scales/policies/binnedPosition.js +34 -0
- package/src/materialization/scales/policies/series.js +13 -7
- package/src/materialization/scales/resolve.js +4 -5
- package/src/materialization/selection/items/arc.js +16 -2
- package/src/materialization/selection/items/bar.js +4 -2
- package/src/materialization/selection/items/common.js +21 -1
- package/src/materialization/selection/items/path.js +19 -2
- package/src/materialization/selection/items/point.js +4 -16
- package/src/materialization/selection/items/tick.js +13 -21
- package/src/materialization/selection/point.js +11 -4
- package/src/materialization/selection/styles.js +19 -27
- package/src/materialization/text.js +4 -1
- package/src/mcp/adapter.js +8 -3
- package/src/renderers/canvas/fill.js +2 -2
- package/src/renderers/canvas/index.js +35 -2
- package/src/renderers/canvas/native.js +160 -0
- package/src/renderers/pdf.js +14 -0
- package/src/renderers/svg.js +37 -4
- package/types/extension.d.ts +19 -0
- package/types/index.d.ts +3 -0
- package/types/program.d.ts +509 -131
- package/types/svg.d.ts +1 -0
- package/src/actions/errorBands/components.js +0 -81
- package/src/actions/errorBands/options.js +0 -39
- package/src/actions/errorBands/resolve.js +0 -28
- package/src/actions/errorBars/components.js +0 -62
- package/src/actions/errorBars/options.js +0 -58
- package/src/actions/errorBars/resolve.js +0 -25
|
@@ -1,34 +1,99 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
requireFiniteBounds,
|
|
3
|
+
resolveTextBounds
|
|
4
|
+
} from "../../core/textMetrics.js";
|
|
5
|
+
import { interpolateNumber } from "../numeric.js";
|
|
6
|
+
import {
|
|
7
|
+
findGraphic,
|
|
8
|
+
findGraphicParent,
|
|
9
|
+
requireGraphic
|
|
10
|
+
} from "./graphicTree.js";
|
|
3
11
|
|
|
4
|
-
|
|
5
|
-
|
|
12
|
+
const EPSILON = Number.EPSILON * 64;
|
|
13
|
+
|
|
14
|
+
function box(left, right, top, bottom, extent = 0) {
|
|
6
15
|
return {
|
|
7
|
-
left:
|
|
8
|
-
right:
|
|
9
|
-
top:
|
|
10
|
-
bottom:
|
|
16
|
+
left: left - extent,
|
|
17
|
+
right: right + extent,
|
|
18
|
+
top: top - extent,
|
|
19
|
+
bottom: bottom + extent
|
|
11
20
|
};
|
|
12
21
|
}
|
|
13
22
|
|
|
23
|
+
function includeExtent(result, left, right, top, bottom) {
|
|
24
|
+
if (result === undefined) return { left, right, top, bottom };
|
|
25
|
+
result.left = Math.min(result.left, left);
|
|
26
|
+
result.right = Math.max(result.right, right);
|
|
27
|
+
result.top = Math.min(result.top, top);
|
|
28
|
+
result.bottom = Math.max(result.bottom, bottom);
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function unionBounds(bounds) {
|
|
33
|
+
let result;
|
|
34
|
+
for (const item of bounds) {
|
|
35
|
+
if (item !== undefined) {
|
|
36
|
+
result = includeExtent(
|
|
37
|
+
result, item.left, item.right, item.top, item.bottom
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
14
44
|
function cubicValue(start, control1, control2, end, t) {
|
|
15
45
|
const remaining = 1 - t;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
46
|
+
if (Math.max(
|
|
47
|
+
Math.abs(start),
|
|
48
|
+
Math.abs(control1),
|
|
49
|
+
Math.abs(control2),
|
|
50
|
+
Math.abs(end)
|
|
51
|
+
) <= Number.MAX_VALUE / 4) {
|
|
52
|
+
return remaining ** 3 * start +
|
|
53
|
+
3 * remaining ** 2 * t * control1 +
|
|
54
|
+
3 * remaining * t ** 2 * control2 +
|
|
55
|
+
t ** 3 * end;
|
|
56
|
+
}
|
|
57
|
+
const left = interpolateNumber(start, control1, t);
|
|
58
|
+
const middle = interpolateNumber(control1, control2, t);
|
|
59
|
+
const right = interpolateNumber(control2, end, t);
|
|
60
|
+
return interpolateNumber(
|
|
61
|
+
interpolateNumber(left, middle, t),
|
|
62
|
+
interpolateNumber(middle, right, t),
|
|
63
|
+
t
|
|
64
|
+
);
|
|
20
65
|
}
|
|
21
66
|
|
|
22
67
|
function cubicExtrema(start, control1, control2, end) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
68
|
+
let scale = Math.max(
|
|
69
|
+
Math.abs(start),
|
|
70
|
+
Math.abs(control1),
|
|
71
|
+
Math.abs(control2),
|
|
72
|
+
Math.abs(end)
|
|
73
|
+
);
|
|
74
|
+
const normalizeCoordinates = scale > Number.MAX_VALUE / 24 ||
|
|
75
|
+
(scale > 0 && scale < EPSILON);
|
|
76
|
+
if (normalizeCoordinates) {
|
|
77
|
+
start /= scale;
|
|
78
|
+
control1 /= scale;
|
|
79
|
+
control2 /= scale;
|
|
80
|
+
end /= scale;
|
|
81
|
+
}
|
|
82
|
+
let quadratic = 3 * (-start + 3 * control1 - 3 * control2 + end);
|
|
83
|
+
let linear = 2 * (3 * start - 6 * control1 + 3 * control2);
|
|
84
|
+
let constant = 3 * (control1 - start);
|
|
85
|
+
scale = Math.max(
|
|
28
86
|
Math.abs(quadratic),
|
|
29
87
|
Math.abs(linear),
|
|
30
88
|
Math.abs(constant)
|
|
31
89
|
);
|
|
90
|
+
let epsilon = EPSILON * Math.max(normalizeCoordinates ? 0 : 1, scale);
|
|
91
|
+
if (scale > Math.sqrt(Number.MAX_VALUE) / 2) {
|
|
92
|
+
quadratic /= scale;
|
|
93
|
+
linear /= scale;
|
|
94
|
+
constant /= scale;
|
|
95
|
+
epsilon = EPSILON;
|
|
96
|
+
}
|
|
32
97
|
if (Math.abs(quadratic) <= epsilon) {
|
|
33
98
|
if (Math.abs(linear) <= epsilon) return [];
|
|
34
99
|
const root = -constant / linear;
|
|
@@ -43,14 +108,16 @@ function cubicExtrema(start, control1, control2, end) {
|
|
|
43
108
|
].filter(value => value > 0 && value < 1);
|
|
44
109
|
}
|
|
45
110
|
|
|
46
|
-
function
|
|
47
|
-
|
|
111
|
+
export function resolvePathCommandBounds(commands) {
|
|
112
|
+
let bounds;
|
|
48
113
|
let current;
|
|
49
114
|
for (const command of commands) {
|
|
50
115
|
if (command.op === "M" || command.op === "L") {
|
|
51
116
|
if ([command.x, command.y].every(Number.isFinite)) {
|
|
52
117
|
current = { x: command.x, y: command.y };
|
|
53
|
-
|
|
118
|
+
bounds = includeExtent(
|
|
119
|
+
bounds, current.x, current.x, current.y, current.y
|
|
120
|
+
);
|
|
54
121
|
}
|
|
55
122
|
continue;
|
|
56
123
|
}
|
|
@@ -64,25 +131,106 @@ function pathPoints(commands) {
|
|
|
64
131
|
...cubicExtrema(current.y, command.y1, command.y2, command.y)
|
|
65
132
|
]);
|
|
66
133
|
for (const t of roots) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
134
|
+
const x = cubicValue(current.x, command.x1, command.x2, command.x, t);
|
|
135
|
+
const y = cubicValue(current.y, command.y1, command.y2, command.y, t);
|
|
136
|
+
bounds = includeExtent(
|
|
137
|
+
bounds,
|
|
138
|
+
x,
|
|
139
|
+
x,
|
|
140
|
+
y,
|
|
141
|
+
y
|
|
142
|
+
);
|
|
71
143
|
}
|
|
72
144
|
current = { x: command.x, y: command.y };
|
|
73
145
|
}
|
|
74
|
-
return
|
|
146
|
+
return bounds;
|
|
75
147
|
}
|
|
76
148
|
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
149
|
+
function endpointDirection(start, command, end, reverse = false) {
|
|
150
|
+
const endpoint = reverse ? end : start;
|
|
151
|
+
const candidates = command.op === "C"
|
|
152
|
+
? reverse
|
|
153
|
+
? [{ x: command.x2, y: command.y2 },
|
|
154
|
+
{ x: command.x1, y: command.y1 }, start]
|
|
155
|
+
: [{ x: command.x1, y: command.y1 },
|
|
156
|
+
{ x: command.x2, y: command.y2 }, end]
|
|
157
|
+
: [reverse ? start : end];
|
|
158
|
+
const sign = reverse ? -1 : 1;
|
|
159
|
+
for (const point of candidates) {
|
|
160
|
+
let x = (point.x - endpoint.x) * sign;
|
|
161
|
+
let y = (point.y - endpoint.y) * sign;
|
|
162
|
+
let length = Math.hypot(x, y);
|
|
163
|
+
if (length === Infinity) {
|
|
164
|
+
const scale = Math.max(
|
|
165
|
+
Math.abs(point.x), Math.abs(point.y),
|
|
166
|
+
Math.abs(endpoint.x), Math.abs(endpoint.y)
|
|
167
|
+
);
|
|
168
|
+
x = (point.x / scale - endpoint.x / scale) * sign;
|
|
169
|
+
y = (point.y / scale - endpoint.y / scale) * sign;
|
|
170
|
+
length = Math.hypot(x, y);
|
|
171
|
+
}
|
|
172
|
+
if (length !== 0) return { x: x / length, y: y / length };
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function includeMiter(bounds, point, incoming, outgoing, strokeExtent) {
|
|
178
|
+
if (incoming === undefined || outgoing === undefined) return;
|
|
179
|
+
const cross = incoming.x * outgoing.y - incoming.y * outgoing.x;
|
|
180
|
+
if (Math.abs(cross) <= EPSILON) return;
|
|
181
|
+
const normalX = -incoming.y - outgoing.y;
|
|
182
|
+
const normalY = incoming.x + outgoing.x;
|
|
183
|
+
const normalLength = Math.hypot(normalX, normalY);
|
|
184
|
+
const ratio = 2 / normalLength;
|
|
185
|
+
if (ratio > 10 * (1 + EPSILON)) return;
|
|
186
|
+
const distance = strokeExtent * ratio * (cross > 0 ? -1 : 1) / normalLength;
|
|
187
|
+
includeExtent(
|
|
188
|
+
bounds,
|
|
189
|
+
point.x + normalX * distance,
|
|
190
|
+
point.x + normalX * distance,
|
|
191
|
+
point.y + normalY * distance,
|
|
192
|
+
point.y + normalY * distance
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function pathStrokeBounds(commands, bounds, strokeExtent) {
|
|
197
|
+
const expanded = box(
|
|
198
|
+
bounds.left, bounds.right, bounds.top, bounds.bottom, strokeExtent
|
|
199
|
+
);
|
|
200
|
+
if (!(strokeExtent > 0)) return expanded;
|
|
201
|
+
const start = { x: commands[0].x, y: commands[0].y };
|
|
202
|
+
let current = start;
|
|
203
|
+
let firstDirection;
|
|
204
|
+
let previousDirection;
|
|
205
|
+
let segmentCount = 0;
|
|
206
|
+
for (const command of commands.slice(1)) {
|
|
207
|
+
const end = command.op === "Z"
|
|
208
|
+
? start
|
|
209
|
+
: { x: command.x, y: command.y };
|
|
210
|
+
const outgoing = endpointDirection(current, command, end);
|
|
211
|
+
const incoming = endpointDirection(current, command, end, true);
|
|
212
|
+
if (outgoing !== undefined || incoming !== undefined) {
|
|
213
|
+
if (segmentCount > 0) {
|
|
214
|
+
includeMiter(expanded, current, previousDirection, outgoing, strokeExtent);
|
|
215
|
+
} else {
|
|
216
|
+
firstDirection = outgoing;
|
|
217
|
+
}
|
|
218
|
+
previousDirection = incoming;
|
|
219
|
+
segmentCount += 1;
|
|
220
|
+
}
|
|
221
|
+
current = end;
|
|
222
|
+
if (command.op === "Z") break;
|
|
223
|
+
}
|
|
224
|
+
if (commands.at(-1)?.op === "Z" && segmentCount > 1) {
|
|
225
|
+
includeMiter(
|
|
226
|
+
expanded,
|
|
227
|
+
start,
|
|
228
|
+
previousDirection,
|
|
229
|
+
firstDirection,
|
|
230
|
+
strokeExtent
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
return expanded;
|
|
86
234
|
}
|
|
87
235
|
|
|
88
236
|
function primitiveBounds(type, properties = {}) {
|
|
@@ -92,32 +240,34 @@ function primitiveBounds(type, properties = {}) {
|
|
|
92
240
|
return undefined;
|
|
93
241
|
}
|
|
94
242
|
const radius = properties.radius + strokeExtent;
|
|
95
|
-
return
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
243
|
+
return box(
|
|
244
|
+
properties.x - radius,
|
|
245
|
+
properties.x + radius,
|
|
246
|
+
properties.y - radius,
|
|
247
|
+
properties.y + radius
|
|
248
|
+
);
|
|
101
249
|
}
|
|
102
250
|
if (type === "rect") {
|
|
103
251
|
if (![properties.x, properties.y, properties.width, properties.height]
|
|
104
252
|
.every(Number.isFinite)) return undefined;
|
|
105
|
-
return
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
253
|
+
return box(
|
|
254
|
+
properties.x,
|
|
255
|
+
properties.x + properties.width,
|
|
256
|
+
properties.y,
|
|
257
|
+
properties.y + properties.height,
|
|
258
|
+
strokeExtent
|
|
259
|
+
);
|
|
111
260
|
}
|
|
112
261
|
if (type === "line") {
|
|
113
262
|
if (![properties.x1, properties.y1, properties.x2, properties.y2]
|
|
114
263
|
.every(Number.isFinite)) return undefined;
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
264
|
+
return box(
|
|
265
|
+
Math.min(properties.x1, properties.x2),
|
|
266
|
+
Math.max(properties.x1, properties.x2),
|
|
267
|
+
Math.min(properties.y1, properties.y2),
|
|
268
|
+
Math.max(properties.y1, properties.y2),
|
|
269
|
+
strokeExtent
|
|
270
|
+
);
|
|
121
271
|
}
|
|
122
272
|
if (type === "text") {
|
|
123
273
|
if (
|
|
@@ -133,51 +283,92 @@ function primitiveBounds(type, properties = {}) {
|
|
|
133
283
|
if (!Array.isArray(commands)) return undefined;
|
|
134
284
|
const bounds = resolvePathCommandBounds(commands);
|
|
135
285
|
if (bounds === undefined) return undefined;
|
|
136
|
-
return
|
|
137
|
-
left: bounds.left - strokeExtent,
|
|
138
|
-
right: bounds.right + strokeExtent,
|
|
139
|
-
top: bounds.top - strokeExtent,
|
|
140
|
-
bottom: bounds.bottom + strokeExtent
|
|
141
|
-
};
|
|
286
|
+
return pathStrokeBounds(commands, bounds, strokeExtent);
|
|
142
287
|
}
|
|
143
288
|
return undefined;
|
|
144
289
|
}
|
|
145
290
|
|
|
146
|
-
function
|
|
147
|
-
if (
|
|
291
|
+
function translateBounds(bounds, x, y) {
|
|
292
|
+
if (bounds === undefined) return undefined;
|
|
293
|
+
return box(
|
|
294
|
+
bounds.left + x,
|
|
295
|
+
bounds.right + x,
|
|
296
|
+
bounds.top + y,
|
|
297
|
+
bounds.bottom + y
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function canvasOffset(object, property) {
|
|
302
|
+
const value = object.type === "canvas" ? object.properties?.[property] : 0;
|
|
303
|
+
return Number.isFinite(value) ? value : 0;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function ancestorOffset(graphicSpec, target) {
|
|
307
|
+
const offset = [0, 0];
|
|
308
|
+
const visited = new Set();
|
|
309
|
+
let current = target;
|
|
310
|
+
while (true) {
|
|
311
|
+
if (visited.has(current)) {
|
|
312
|
+
throw new Error(`Graphic attachment cycle includes "${current}".`);
|
|
313
|
+
}
|
|
314
|
+
visited.add(current);
|
|
315
|
+
const parent = findGraphicParent(graphicSpec, current);
|
|
316
|
+
if (parent === undefined) return offset;
|
|
317
|
+
offset[0] += canvasOffset(parent.object, "x");
|
|
318
|
+
offset[1] += canvasOffset(parent.object, "y");
|
|
319
|
+
current = parent.id;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function objectBounds(graphicSpec, id, object, ancestors, x, y) {
|
|
324
|
+
if (ancestors.has(id)) {
|
|
148
325
|
throw new Error(`Graphic attachment cycle includes "${id}".`);
|
|
149
326
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
327
|
+
ancestors.add(id);
|
|
328
|
+
let result = object.items === undefined
|
|
329
|
+
? translateBounds(primitiveBounds(object.type, object.properties), x, y)
|
|
330
|
+
: unionBounds(object.items.map(item => translateBounds(
|
|
331
|
+
primitiveBounds(item.type ?? object.type, item.properties),
|
|
332
|
+
x,
|
|
333
|
+
y
|
|
334
|
+
)));
|
|
335
|
+
const childX = x + canvasOffset(object, "x");
|
|
336
|
+
const childY = y + canvasOffset(object, "y");
|
|
337
|
+
for (const childId of object.children ?? []) {
|
|
157
338
|
const child = findGraphic(graphicSpec, childId);
|
|
158
339
|
if (child?.kind !== "object") {
|
|
159
340
|
throw new Error(`Unknown attached graphic "${childId}".`);
|
|
160
341
|
}
|
|
161
|
-
const bounds = objectBounds(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
342
|
+
const bounds = objectBounds(
|
|
343
|
+
graphicSpec, childId, child.object, ancestors, childX, childY
|
|
344
|
+
);
|
|
345
|
+
if (bounds !== undefined) {
|
|
346
|
+
result = includeExtent(
|
|
347
|
+
result, bounds.left, bounds.right, bounds.top, bounds.bottom
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
ancestors.delete(id);
|
|
352
|
+
return result;
|
|
165
353
|
}
|
|
166
354
|
|
|
167
355
|
export function resolveConcreteGraphicBounds(graphicSpec, target) {
|
|
168
356
|
const found = requireGraphic(graphicSpec, target);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
357
|
+
const [x, y] = ancestorOffset(graphicSpec, target);
|
|
358
|
+
return requireFiniteBounds(found.kind === "item"
|
|
359
|
+
? translateBounds(
|
|
360
|
+
primitiveBounds(found.object.type ?? found.owner.type, found.object.properties),
|
|
361
|
+
x,
|
|
362
|
+
y
|
|
363
|
+
)
|
|
364
|
+
: objectBounds(graphicSpec, found.id, found.object, new Set(), x, y));
|
|
173
365
|
}
|
|
174
366
|
|
|
175
367
|
export function unionConcreteGraphicBounds(graphicSpec, targets) {
|
|
176
368
|
if (!Array.isArray(targets)) {
|
|
177
369
|
throw new TypeError("Graphic bounds targets must be an array.");
|
|
178
370
|
}
|
|
179
|
-
return unionBounds(targets.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}));
|
|
371
|
+
return unionBounds(targets.map(target =>
|
|
372
|
+
resolveConcreteGraphicBounds(graphicSpec, target)
|
|
373
|
+
));
|
|
183
374
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { cloneAndFreeze } from "../core/immutable.js";
|
|
2
2
|
import { COLOR_LAYOUTS } from "../core/vocabulary.js";
|
|
3
|
+
import {
|
|
4
|
+
normalizedFiniteSum,
|
|
5
|
+
restoreFiniteScale,
|
|
6
|
+
requireFiniteResult
|
|
7
|
+
} from "./numeric.js";
|
|
3
8
|
|
|
4
9
|
export const DEFAULT_SERIES_BASELINE = 0;
|
|
5
10
|
|
|
@@ -16,6 +21,15 @@ function validateValues(values) {
|
|
|
16
21
|
}
|
|
17
22
|
}
|
|
18
23
|
|
|
24
|
+
function requireResolvedSegment(start, end, value, layout) {
|
|
25
|
+
requireFiniteResult(end, `Series ${layout} end`);
|
|
26
|
+
if (value > 0 ? end <= start : end >= start) {
|
|
27
|
+
throw new RangeError(
|
|
28
|
+
`Series ${layout} value cannot resolve a distinct finite segment.`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
export function layoutSeriesPartition(
|
|
20
34
|
values,
|
|
21
35
|
layout,
|
|
@@ -35,18 +49,43 @@ export function layoutSeriesPartition(
|
|
|
35
49
|
));
|
|
36
50
|
}
|
|
37
51
|
|
|
38
|
-
if (
|
|
52
|
+
if (layout === "stack" || layout === "fill" || layout === "center") {
|
|
39
53
|
if (values.some(value => value < 0)) {
|
|
40
54
|
throw new RangeError(`${layout} layout requires non-negative values.`);
|
|
41
55
|
}
|
|
42
56
|
const total = values.reduce((sum, value) => sum + value, 0);
|
|
43
57
|
if (layout === "fill" && total === 0) return cloneAndFreeze([]);
|
|
44
|
-
|
|
58
|
+
const normalized = Number.isFinite(total)
|
|
59
|
+
? undefined
|
|
60
|
+
: normalizedFiniteSum(values, `Series ${layout} values`);
|
|
61
|
+
if (layout === "stack" && normalized) {
|
|
62
|
+
throw new RangeError("Series stack total is outside the finite numeric range.");
|
|
63
|
+
}
|
|
64
|
+
let offset = 0;
|
|
65
|
+
if (layout === "center") {
|
|
66
|
+
offset = -(normalized
|
|
67
|
+
? restoreFiniteScale(
|
|
68
|
+
normalized.total / 2,
|
|
69
|
+
normalized.scale,
|
|
70
|
+
"Series center half-total"
|
|
71
|
+
)
|
|
72
|
+
: total / 2);
|
|
73
|
+
}
|
|
74
|
+
let positiveIndex = 0;
|
|
75
|
+
const positiveCount = values.filter(value => value > 0).length;
|
|
45
76
|
return cloneAndFreeze(values.flatMap((value, index) => {
|
|
46
77
|
if (value === 0) return [];
|
|
47
|
-
|
|
78
|
+
positiveIndex += 1;
|
|
79
|
+
const resolvedValue = layout === "fill"
|
|
80
|
+
? normalized
|
|
81
|
+
? (value / normalized.scale) / normalized.total
|
|
82
|
+
: value / total
|
|
83
|
+
: value;
|
|
48
84
|
const start = offset;
|
|
49
|
-
const end =
|
|
85
|
+
const end = layout === "fill" && positiveIndex === positiveCount
|
|
86
|
+
? 1
|
|
87
|
+
: start + resolvedValue;
|
|
88
|
+
requireResolvedSegment(start, end, value, layout);
|
|
50
89
|
offset = end;
|
|
51
90
|
return [{ index, value, start, end }];
|
|
52
91
|
}));
|
|
@@ -58,6 +97,7 @@ export function layoutSeriesPartition(
|
|
|
58
97
|
if (value === 0) return [];
|
|
59
98
|
const start = value > 0 ? positive : negative;
|
|
60
99
|
const end = start + value;
|
|
100
|
+
requireResolvedSegment(start, end, value, layout);
|
|
61
101
|
if (value > 0) positive = end;
|
|
62
102
|
else negative = end;
|
|
63
103
|
return [{ index, value, start, end }];
|
|
@@ -78,18 +118,10 @@ export function resolveSeriesLayoutDomainValues(partitions, layout) {
|
|
|
78
118
|
...partition
|
|
79
119
|
]);
|
|
80
120
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
return partitions.flatMap(partition =>
|
|
90
|
-
layoutSeriesPartition(partition, layout).flatMap(segment => [
|
|
91
|
-
segment.start,
|
|
92
|
-
segment.end
|
|
93
|
-
])
|
|
94
|
-
);
|
|
121
|
+
return partitions.flatMap(partition => {
|
|
122
|
+
const segments = layoutSeriesPartition(partition, layout);
|
|
123
|
+
return layout === "center" && segments.length === 0
|
|
124
|
+
? [DEFAULT_SERIES_BASELINE]
|
|
125
|
+
: segments.flatMap(segment => [segment.start, segment.end]);
|
|
126
|
+
});
|
|
95
127
|
}
|