ggaction 0.0.17 → 0.0.18
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 +46 -0
- package/README.md +2 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -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 +3 -3
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +3 -2
- package/src/actions/boxPlots/create.js +14 -4
- package/src/actions/boxPlots/edit.js +39 -33
- package/src/actions/boxPlots/materialize.js +7 -3
- package/src/actions/boxPlots/options.js +37 -11
- package/src/actions/canvas/actions.js +70 -95
- package/src/actions/canvas/fitting.js +16 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +28 -0
- package/src/actions/charts/area.js +2 -2
- package/src/actions/charts/bar.js +34 -7
- package/src/actions/charts/ecdf.js +4 -18
- package/src/actions/charts/endpoints.js +4 -18
- package/src/actions/charts/heatmap.js +4 -4
- package/src/actions/charts/index.js +2 -0
- package/src/actions/charts/interval-regression.js +2 -2
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/pie.js +3 -47
- package/src/actions/charts/radial.js +34 -12
- package/src/actions/charts/scatter.js +16 -2
- package/src/actions/charts/shared.js +2 -2
- package/src/actions/charts/text.js +35 -0
- package/src/actions/composition/actions.js +6 -2
- package/src/actions/data/box.js +2 -1
- package/src/actions/data/edit.js +1 -1
- package/src/actions/data/regression.js +2 -1
- package/src/actions/data/shared.js +3 -2
- package/src/actions/data/stack.js +3 -1
- package/src/actions/encodings/appearance.js +37 -29
- package/src/actions/encodings/barWidth.js +36 -11
- package/src/actions/encodings/channels.js +0 -1
- package/src/actions/encodings/color/continuous.js +1 -1
- package/src/actions/encodings/color/index.js +1 -1
- package/src/actions/encodings/color/policy.js +10 -3
- package/src/actions/encodings/density/resolve.js +1 -6
- package/src/actions/encodings/position/policies/arc.js +12 -10
- package/src/actions/encodings/position/policies/area.js +3 -3
- package/src/actions/encodings/position/policies/bar.js +15 -6
- package/src/actions/encodings/position/policies/line.js +17 -6
- package/src/actions/encodings/position/resolve.js +3 -2
- package/src/actions/encodings/ranged.js +17 -0
- package/src/actions/encodings/remove.js +3 -2
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/text.js +6 -3
- package/src/actions/errorBands/create.js +2 -2
- package/src/actions/errorBands/edit.js +2 -1
- package/src/actions/facets/actions.js +158 -240
- package/src/actions/gradientPlots/resolve.js +2 -17
- package/src/actions/guides/applicability.js +1 -1
- package/src/actions/guides/axes/axis.js +16 -4
- package/src/actions/guides/axes/edit.js +4 -4
- package/src/actions/guides/axes/labels.js +120 -46
- package/src/actions/guides/axes/lines.js +6 -5
- package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
- package/src/actions/guides/axes/parallel/policy.js +8 -7
- package/src/actions/guides/axes/remove.js +3 -2
- package/src/actions/guides/axes/tickGroups.js +5 -2
- package/src/actions/guides/axes/ticks.js +6 -3
- package/src/actions/guides/axes/titles.js +71 -26
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facadeLegend.js +10 -2
- package/src/actions/guides/grids/grid.js +7 -1
- package/src/actions/guides/grids/resolve.js +59 -12
- package/src/actions/guides/legends/blocks.js +3 -1
- package/src/actions/guides/legends/categorical/actions.js +70 -17
- package/src/actions/guides/legends/categorical/components.js +12 -8
- package/src/actions/guides/legends/categorical/index.js +6 -0
- package/src/actions/guides/legends/categorical/layout.js +37 -8
- package/src/actions/guides/legends/categorical/options.js +31 -20
- package/src/actions/guides/legends/categorical/resolve.js +2 -2
- package/src/actions/guides/legends/categorical/symbols.js +18 -12
- package/src/actions/guides/legends/categorical/validation.js +1 -6
- package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
- package/src/actions/guides/legends/continuous/common.js +1 -447
- package/src/actions/guides/legends/continuous/gradient.js +47 -34
- package/src/actions/guides/legends/continuous/interval.js +4 -4
- package/src/actions/guides/legends/continuous/opacity.js +19 -12
- package/src/actions/guides/legends/edit.js +1 -604
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/group.js +1 -0
- package/src/actions/guides/legends/lane.js +1 -516
- package/src/actions/guides/legends/remove.js +3 -0
- package/src/actions/guides/legends/sampling.js +1 -1
- package/src/actions/guides/legends/size.js +51 -43
- package/src/actions/guides/legends/strokeWidth.js +11 -29
- package/src/actions/guides/legends/target.js +3 -3
- package/src/actions/guides/legends/transition.js +21 -7
- package/src/actions/guides/polar/axes/labels.js +4 -2
- package/src/actions/guides/polar/axes/titles.js +7 -5
- package/src/actions/guides/polar/resolve.js +6 -14
- package/src/actions/index.js +16 -16
- package/src/actions/marks/arc/actions.js +3 -10
- package/src/actions/marks/area/actions.js +8 -5
- package/src/actions/marks/area/materialize.js +8 -9
- package/src/actions/marks/bar/create.js +10 -4
- package/src/actions/marks/bar/edit.js +8 -4
- package/src/actions/marks/bar/materialize.js +9 -6
- package/src/actions/marks/line/actions.js +11 -4
- package/src/actions/marks/line/materialize.js +9 -5
- package/src/actions/marks/point/edit.js +1 -1
- package/src/actions/marks/rect/actions.js +3 -3
- package/src/actions/marks/remove.js +2 -0
- package/src/actions/marks/rule/actions.js +7 -4
- package/src/actions/marks/text/actions.js +17 -5
- package/src/actions/marks/text/layout.js +3 -0
- package/src/actions/primitives/semanticValidation/layer.js +4 -0
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +3 -3
- package/src/actions/regression/resolve.js +2 -6
- package/src/actions/scales/channels.js +9 -4
- package/src/actions/scales/consumers/families.js +16 -5
- package/src/actions/scales/create.js +8 -3
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +2 -2
- package/src/actions/scales/editPolicy.js +9 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/preview.js +21 -1
- package/src/actions/scales/quantitativeColor.js +3 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +81 -27
- package/src/actions/theme/state.js +12 -0
- package/src/actions/titles/actions.js +5 -18
- package/src/actions/titles/resolve.js +41 -19
- package/src/actions/violinPlots/edit.js +3 -20
- package/src/core/compositionState.js +4 -1
- package/src/core/font.js +9 -2
- package/src/core/optionRequirements.js +1 -0
- package/src/core/resourceReferences.js +3 -0
- package/src/core/textMetricProfile.js +3 -2
- package/src/core/textMetrics.js +41 -4
- package/src/core/validation.js +26 -0
- package/src/core/vocabulary.js +4 -0
- package/src/grammar/aggregate.js +4 -18
- package/src/grammar/arcs.js +1 -386
- package/src/grammar/areaPaint.js +10 -0
- package/src/grammar/areaSeries.js +18 -10
- package/src/grammar/bars/geometry.js +30 -3
- package/src/grammar/bars/policy.js +12 -1
- package/src/grammar/bin.js +2 -12
- package/src/grammar/bin2d.js +1 -6
- package/src/grammar/boxPlot.js +56 -9
- package/src/grammar/categoryOrder.js +1 -6
- package/src/grammar/complete.js +15 -19
- package/src/grammar/computed.js +92 -25
- package/src/grammar/curveCommands.js +29 -16
- package/src/grammar/datasetSchema.js +3 -7
- package/src/grammar/displayLabels.js +28 -0
- package/src/grammar/ecdf.js +2 -9
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +5 -0
- package/src/grammar/facets/index.js +2 -5
- package/src/grammar/fold.js +2 -12
- package/src/grammar/gradientProfile.js +1 -6
- package/src/grammar/impute.js +21 -48
- package/src/grammar/interval.js +2 -9
- package/src/grammar/legendGradient.js +18 -0
- package/src/grammar/legendGroups.js +1 -0
- package/src/grammar/legendTitleStyle.js +22 -0
- package/src/grammar/lineSeries.js +32 -17
- package/src/grammar/markLabels.js +5 -2
- package/src/grammar/normalize.js +4 -54
- package/src/grammar/numeric.js +1 -290
- package/src/grammar/parallelCoordinates.js +1 -6
- package/src/grammar/parseTime.js +41 -0
- package/src/grammar/pathCommands.js +11 -4
- package/src/grammar/polar.js +1 -184
- package/src/grammar/positionCompatibility.js +3 -3
- package/src/grammar/regression/derive.js +7 -1
- package/src/grammar/regression/models.js +83 -55
- package/src/grammar/regression/parameters.js +14 -3
- package/src/grammar/roundedRect.js +42 -54
- package/src/grammar/scales/appearance.js +1 -206
- package/src/grammar/scales/color.js +1 -382
- package/src/grammar/scales/colorConsumers.js +5 -2
- package/src/grammar/scales/definition.js +1 -197
- package/src/grammar/scales/discretized.js +1 -195
- package/src/grammar/scales/size.js +1 -456
- package/src/grammar/scales/temporal.js +2 -13
- package/src/grammar/scales/transformed.js +1 -383
- package/src/grammar/scales/types.js +1 -1
- package/src/grammar/scales/validation.js +1 -66
- package/src/grammar/schemas/concreteGraphic.js +11 -0
- package/src/grammar/schemas/graphic.js +3 -0
- package/src/grammar/schemas/semanticPath.js +2 -1
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +3 -26
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +16 -7
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transforms.js +4 -1
- package/src/grammar/valueFormat.js +17 -5
- package/src/grammar/weightedStatistics.js +1 -6
- package/src/grammar/window.js +4 -58
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -110
- package/src/layout/facets.js +1 -244
- package/src/layout/labels.js +1 -0
- package/src/layout/legend.js +13 -0
- package/src/layout/legendGroup.js +1 -0
- package/src/layout/legendItems.js +59 -15
- package/src/layout/legendLane.js +1 -441
- package/src/layout/legendText.js +1 -0
- package/src/layout/title.js +35 -13
- package/src/materialization/bars/aggregate.js +11 -5
- package/src/materialization/bars/histogram.js +4 -1
- package/src/materialization/bars/ranged.js +19 -6
- package/src/materialization/bars/resolve.js +22 -1
- package/src/materialization/coordinateBounds.js +8 -0
- package/src/materialization/dependencies.js +4 -44
- package/src/materialization/facetGuides/layout.js +1 -0
- package/src/materialization/facetGuides/placement.js +19 -8
- package/src/materialization/facetGuides/preparation.js +15 -7
- package/src/materialization/facetHeaders.js +1 -347
- package/src/materialization/facets.js +1 -185
- package/src/materialization/graphicHierarchy.js +1 -1
- package/src/materialization/guides/layout.js +1 -57
- package/src/materialization/guides/resources.js +1 -123
- package/src/materialization/layout.js +53 -0
- package/src/materialization/legendGroups.js +1 -0
- package/src/materialization/legendText.js +1 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/capabilities.js +5 -6
- package/src/materialization/rect.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +1 -1
- package/src/materialization/scales/map.js +3 -0
- package/src/materialization/scales/policies/arc.js +8 -7
- package/src/materialization/scales/policies/bar.js +19 -7
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +5 -3
- package/src/materialization/selection/items/arc.js +1 -1
- package/src/materialization/selection/items/bar.js +7 -6
- package/src/materialization/selection/items/path.js +7 -6
- package/src/materialization/selection/items/rect.js +2 -2
- package/src/materialization/text.js +13 -4
- package/src/materialization/textLines.js +15 -0
- package/src/persistence/codec.js +37 -6
- package/src/persistence/validation.js +47 -0
- package/src/persistence.js +18 -5
- package/src/renderers/canvas/circle.js +6 -3
- package/src/renderers/canvas/fill.js +7 -1
- package/src/renderers/canvas/index.js +2 -1
- package/src/renderers/canvas/line.js +1 -0
- package/src/renderers/canvas/path.js +1 -1
- package/src/renderers/canvas/rect.js +11 -8
- package/src/renderers/canvas/stroke.js +2 -1
- package/src/renderers/canvas/text.js +5 -3
- package/src/renderers/svg.js +4 -1
- package/src/selectors/layers.js +19 -0
- package/src/theme/defaults.js +8 -2
- package/src/version.js +1 -1
- package/types/basic.d.ts +1 -0
- package/types/index.d.ts +7 -0
- package/types/program.d.ts +347 -85
package/src/grammar/polar.js
CHANGED
|
@@ -1,184 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { validateOptionObject } from "../core/validation.js";
|
|
3
|
-
import { validatePair } from "./scales/validation.js";
|
|
4
|
-
|
|
5
|
-
const POLAR_FRAME_OPTIONS = Object.freeze(["center", "radius"]);
|
|
6
|
-
const POLAR_CENTER_OPTIONS = Object.freeze(["x", "y"]);
|
|
7
|
-
const POLAR_RADIUS_OPTIONS = Object.freeze(["unit", "value"]);
|
|
8
|
-
const POLAR_RADIUS_UNITS = new Set(["fraction", "px"]);
|
|
9
|
-
|
|
10
|
-
function validateBounds(bounds) {
|
|
11
|
-
if (
|
|
12
|
-
!isPlainObject(bounds) ||
|
|
13
|
-
![bounds.x, bounds.y, bounds.width, bounds.height].every(Number.isFinite) ||
|
|
14
|
-
bounds.width < 0 ||
|
|
15
|
-
bounds.height < 0
|
|
16
|
-
) {
|
|
17
|
-
throw new TypeError(
|
|
18
|
-
"Polar geometry requires finite, non-negative graphical bounds."
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
return bounds;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function finiteUnitInterval(value, label) {
|
|
25
|
-
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
26
|
-
throw new RangeError(`${label} must be a finite number from 0 through 1.`);
|
|
27
|
-
}
|
|
28
|
-
return value;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function normalizePolarFrameOptions(value) {
|
|
32
|
-
if (value === "auto") return value;
|
|
33
|
-
if (!isPlainObject(value)) {
|
|
34
|
-
throw new TypeError('Polar frame must be "auto" or a plain object.');
|
|
35
|
-
}
|
|
36
|
-
validateOptionObject(value, POLAR_FRAME_OPTIONS, "polar frame");
|
|
37
|
-
const center = value.center ?? {};
|
|
38
|
-
if (!isPlainObject(center)) {
|
|
39
|
-
throw new TypeError("Polar frame center must be a plain object.");
|
|
40
|
-
}
|
|
41
|
-
validateOptionObject(center, POLAR_CENTER_OPTIONS, "polar frame center");
|
|
42
|
-
const radius = value.radius ?? { unit: "fraction", value: 1 };
|
|
43
|
-
if (!isPlainObject(radius)) {
|
|
44
|
-
throw new TypeError("Polar frame radius must be a plain object.");
|
|
45
|
-
}
|
|
46
|
-
validateOptionObject(radius, POLAR_RADIUS_OPTIONS, "polar frame radius", {
|
|
47
|
-
allowEmpty: false,
|
|
48
|
-
emptyError: TypeError
|
|
49
|
-
});
|
|
50
|
-
if (!POLAR_RADIUS_UNITS.has(radius.unit)) {
|
|
51
|
-
throw new Error(`Unknown Polar frame radius unit "${radius.unit}".`);
|
|
52
|
-
}
|
|
53
|
-
if (!Number.isFinite(radius.value) || radius.value <= 0) {
|
|
54
|
-
throw new RangeError("Polar frame radius must be a positive finite number.");
|
|
55
|
-
}
|
|
56
|
-
if (radius.unit === "fraction" && radius.value > 1) {
|
|
57
|
-
throw new RangeError("Polar frame radius fraction must not exceed 1.");
|
|
58
|
-
}
|
|
59
|
-
return cloneAndFreeze({
|
|
60
|
-
center: {
|
|
61
|
-
x: finiteUnitInterval(center.x ?? 0.5, "Polar frame center x"),
|
|
62
|
-
y: finiteUnitInterval(center.y ?? 0.5, "Polar frame center y")
|
|
63
|
-
},
|
|
64
|
-
radius: {
|
|
65
|
-
unit: radius.unit,
|
|
66
|
-
value: radius.value
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function resolvePolarFrame(bounds, requestedFrame = "auto") {
|
|
72
|
-
validateBounds(bounds);
|
|
73
|
-
if (requestedFrame === undefined || requestedFrame === "auto") {
|
|
74
|
-
return cloneAndFreeze({
|
|
75
|
-
centerX: bounds.x + bounds.width / 2,
|
|
76
|
-
centerY: bounds.y + bounds.height / 2,
|
|
77
|
-
availableRadius: Math.min(bounds.width, bounds.height) / 2
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
const requested = normalizePolarFrameOptions(requestedFrame);
|
|
81
|
-
const centerX = bounds.x + bounds.width * requested.center.x;
|
|
82
|
-
const centerY = bounds.y + bounds.height * requested.center.y;
|
|
83
|
-
const maximumRadius = Math.min(
|
|
84
|
-
centerX - bounds.x,
|
|
85
|
-
bounds.x + bounds.width - centerX,
|
|
86
|
-
centerY - bounds.y,
|
|
87
|
-
bounds.y + bounds.height - centerY
|
|
88
|
-
);
|
|
89
|
-
if (!Number.isFinite(maximumRadius) || maximumRadius <= 0) {
|
|
90
|
-
throw new RangeError(
|
|
91
|
-
"Polar frame center must leave a positive radius inside its bounds."
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
const availableRadius = requested.radius.unit === "fraction"
|
|
95
|
-
? maximumRadius * requested.radius.value
|
|
96
|
-
: requested.radius.value;
|
|
97
|
-
if (availableRadius > maximumRadius) {
|
|
98
|
-
throw new RangeError(
|
|
99
|
-
`Polar frame radius ${availableRadius} exceeds the maximum radius ${maximumRadius}.`
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
return cloneAndFreeze({
|
|
103
|
-
centerX,
|
|
104
|
-
centerY,
|
|
105
|
-
availableRadius
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function validateThetaRange(range) {
|
|
110
|
-
if (range === "auto") return range;
|
|
111
|
-
const validated = validatePair(range, "Theta scale range");
|
|
112
|
-
if (Math.abs(validated[1] - validated[0]) > 360) {
|
|
113
|
-
throw new RangeError("Theta scale range span must not exceed 360 degrees.");
|
|
114
|
-
}
|
|
115
|
-
return validated;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function validateRadialRange(range, availableRadius) {
|
|
119
|
-
if (range === "auto") return range;
|
|
120
|
-
const validated = validatePair(range, "Radius scale range");
|
|
121
|
-
if (validated.some(value => value < 0)) {
|
|
122
|
-
throw new RangeError("Radius scale range values must be non-negative.");
|
|
123
|
-
}
|
|
124
|
-
if (
|
|
125
|
-
availableRadius !== undefined &&
|
|
126
|
-
validated.some(value => value > availableRadius)
|
|
127
|
-
) {
|
|
128
|
-
throw new RangeError(
|
|
129
|
-
`Radius scale range must fit within the available radius ${availableRadius}.`
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
return validated;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function resolvePolarScaleRange(range, channel, bounds, frame) {
|
|
136
|
-
if (channel === "theta") {
|
|
137
|
-
return range === "auto"
|
|
138
|
-
? cloneAndFreeze([0, 360])
|
|
139
|
-
: validateThetaRange(range);
|
|
140
|
-
}
|
|
141
|
-
if (channel !== "radius") {
|
|
142
|
-
throw new Error(`Unknown Polar position channel "${channel}".`);
|
|
143
|
-
}
|
|
144
|
-
const resolvedFrame = frame ?? resolvePolarFrame(bounds);
|
|
145
|
-
return range === "auto"
|
|
146
|
-
? cloneAndFreeze([0, resolvedFrame.availableRadius])
|
|
147
|
-
: validateRadialRange(range, resolvedFrame.availableRadius);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export function polarDirection(theta) {
|
|
151
|
-
if (!Number.isFinite(theta)) {
|
|
152
|
-
throw new TypeError("Polar theta must be a finite number of degrees.");
|
|
153
|
-
}
|
|
154
|
-
const angle = theta * Math.PI / 180;
|
|
155
|
-
return cloneAndFreeze({
|
|
156
|
-
x: Math.sin(angle),
|
|
157
|
-
y: -Math.cos(angle)
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function polarToCartesian({ theta, radius, frame }) {
|
|
162
|
-
const direction = polarDirection(theta);
|
|
163
|
-
if (!Number.isFinite(radius) || radius < 0) {
|
|
164
|
-
throw new TypeError("Polar radius must be a non-negative finite number.");
|
|
165
|
-
}
|
|
166
|
-
const resolvedFrame = frame?.availableRadius === undefined
|
|
167
|
-
? resolvePolarFrame(frame)
|
|
168
|
-
: frame;
|
|
169
|
-
if (
|
|
170
|
-
![resolvedFrame.centerX, resolvedFrame.centerY,
|
|
171
|
-
resolvedFrame.availableRadius].every(Number.isFinite)
|
|
172
|
-
) {
|
|
173
|
-
throw new TypeError("Polar frame must contain finite geometry.");
|
|
174
|
-
}
|
|
175
|
-
if (radius > resolvedFrame.availableRadius) {
|
|
176
|
-
throw new RangeError(
|
|
177
|
-
`Polar radius ${radius} exceeds the available radius ${resolvedFrame.availableRadius}.`
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
return cloneAndFreeze({
|
|
181
|
-
x: resolvedFrame.centerX + radius * direction.x,
|
|
182
|
-
y: resolvedFrame.centerY + radius * direction.y
|
|
183
|
-
});
|
|
184
|
-
}
|
|
1
|
+
import{cloneAndFreeze as n,isPlainObject as l}from"../core/immutable.js";import{validateOptionObject as u}from"../core/validation.js";import{validatePair as c}from"./scales/validation.js";const w=Object.freeze(["center","radius","overflow"]),h=Object.freeze(["x","y"]),d=Object.freeze(["unit","value"]),p=new Set(["fraction","px"]);function v(e){if(!l(e)||![e.x,e.y,e.width,e.height].every(Number.isFinite)||e.width<0||e.height<0)throw new TypeError("Polar geometry requires finite, non-negative graphical bounds.");return e}function s(e,r){if(!Number.isFinite(e)||e<0||e>1)throw new RangeError(`${r} must be a finite number from 0 through 1.`);return e}function R(e){if(e==="auto")return e;if(!l(e))throw new TypeError('Polar frame must be "auto" or a plain object.');if(u(e,w,"polar frame"),e.overflow!==void 0&&!["error","allow"].includes(e.overflow))throw new Error("Polar frame overflow must be error or allow.");const r=e.center??{};if(!l(r))throw new TypeError("Polar frame center must be a plain object.");u(r,h,"polar frame center");const t=e.radius??{unit:"fraction",value:1};if(!l(t))throw new TypeError("Polar frame radius must be a plain object.");if(u(t,d,"polar frame radius",{allowEmpty:!1,emptyError:TypeError}),!p.has(t.unit))throw new Error(`Unknown Polar frame radius unit "${t.unit}".`);if(!Number.isFinite(t.value)||t.value<=0)throw new RangeError("Polar frame radius must be a positive finite number.");if(t.unit==="fraction"&&t.value>1)throw new RangeError("Polar frame radius fraction must not exceed 1.");return n({center:{x:s(r.x??.5,"Polar frame center x"),y:s(r.y??.5,"Polar frame center y")},radius:{unit:t.unit,value:t.value},...e.overflow==="allow"?{overflow:"allow"}:{}})}function m(e,r="auto"){if(v(e),r===void 0||r==="auto")return n({centerX:e.x+e.width/2,centerY:e.y+e.height/2,availableRadius:Math.min(e.width,e.height)/2});const t=R(r),a=e.x+e.width*t.center.x,i=e.y+e.height*t.center.y,o=Math.min(a-e.x,e.x+e.width-a,i-e.y,e.y+e.height-i);if(!Number.isFinite(o)||o<=0)throw new RangeError("Polar frame center must leave a positive radius inside its bounds.");const f=t.radius.unit==="fraction"?o*t.radius.value:t.radius.value;if(f>o&&t.overflow!=="allow")throw new RangeError(`Polar frame radius ${f} exceeds the maximum radius ${o}.`);return n({centerX:a,centerY:i,availableRadius:f})}function P(e){if(e==="auto")return e;const r=c(e,"Theta scale range");if(Math.abs(r[1]-r[0])>360)throw new RangeError("Theta scale range span must not exceed 360 degrees.");return r}function x(e,r){if(e==="auto")return e;const t=c(e,"Radius scale range");if(t.some(a=>a<0))throw new RangeError("Radius scale range values must be non-negative.");if(r!==void 0&&t.some(a=>a>r))throw new RangeError(`Radius scale range must fit within the available radius ${r}.`);return t}function T(e,r,t,a){if(r==="theta")return e==="auto"?n([0,360]):P(e);if(r!=="radius")throw new Error(`Unknown Polar position channel "${r}".`);const i=a??m(t);return e==="auto"?n([0,i.availableRadius]):x(e,i.availableRadius)}function y(e){if(!Number.isFinite(e))throw new TypeError("Polar theta must be a finite number of degrees.");const r=e*Math.PI/180;return n({x:Math.sin(r),y:-Math.cos(r)})}function b({theta:e,radius:r,frame:t}){const a=y(e);if(!Number.isFinite(r)||r<0)throw new TypeError("Polar radius must be a non-negative finite number.");const i=t?.availableRadius===void 0?m(t):t;if(![i.centerX,i.centerY,i.availableRadius].every(Number.isFinite))throw new TypeError("Polar frame must contain finite geometry.");if(r>i.availableRadius)throw new RangeError(`Polar radius ${r} exceeds the available radius ${i.availableRadius}.`);return n({x:i.centerX+r*a.x,y:i.centerY+r*a.y})}export{R as normalizePolarFrameOptions,y as polarDirection,b as polarToCartesian,m as resolvePolarFrame,T as resolvePolarScaleRange,x as validateRadialRange,P as validateThetaRange};
|
|
@@ -6,14 +6,14 @@ export const POSITION_FIELD_COMPATIBILITY = Object.freeze({
|
|
|
6
6
|
radius: Object.freeze(["quantitative"])
|
|
7
7
|
}),
|
|
8
8
|
line: Object.freeze({
|
|
9
|
-
x: Object.freeze(["quantitative", "temporal"]),
|
|
9
|
+
x: Object.freeze(["quantitative", "temporal", "ordinal", "nominal"]),
|
|
10
10
|
y: Object.freeze(["quantitative", "temporal", "ordinal", "nominal"]),
|
|
11
11
|
theta: Object.freeze(["quantitative", "temporal", "ordinal", "nominal"]),
|
|
12
12
|
radius: Object.freeze(["quantitative"])
|
|
13
13
|
}),
|
|
14
14
|
area: Object.freeze({
|
|
15
|
-
x: Object.freeze(["quantitative", "temporal"]),
|
|
16
|
-
y: Object.freeze(["quantitative", "temporal"])
|
|
15
|
+
x: Object.freeze(["quantitative", "temporal", "ordinal", "nominal"]),
|
|
16
|
+
y: Object.freeze(["quantitative", "temporal", "ordinal", "nominal"])
|
|
17
17
|
}),
|
|
18
18
|
arc: Object.freeze({
|
|
19
19
|
theta: Object.freeze(["quantitative", "ordinal", "nominal"]),
|
|
@@ -24,6 +24,7 @@ export function deriveRegression(values, {
|
|
|
24
24
|
method,
|
|
25
25
|
degree,
|
|
26
26
|
span,
|
|
27
|
+
robustIterations,
|
|
27
28
|
confidenceMethod,
|
|
28
29
|
level,
|
|
29
30
|
confidence,
|
|
@@ -43,6 +44,7 @@ export function deriveRegression(values, {
|
|
|
43
44
|
method,
|
|
44
45
|
degree,
|
|
45
46
|
span,
|
|
47
|
+
robustIterations,
|
|
46
48
|
confidenceMethod,
|
|
47
49
|
level,
|
|
48
50
|
confidence,
|
|
@@ -102,7 +104,11 @@ export function deriveRegression(values, {
|
|
|
102
104
|
const size = parameters.degree + 1;
|
|
103
105
|
work += groupRows.length * size ** 2 + size ** 3;
|
|
104
106
|
} else if (parameters.method === "loess") {
|
|
105
|
-
|
|
107
|
+
const observedSet = new Set(observedX);
|
|
108
|
+
const newPredictions = predict === undefined ? 0 :
|
|
109
|
+
xValues.filter(value => !observedSet.has(value)).length;
|
|
110
|
+
work += groupRows.length *
|
|
111
|
+
(observedX.length * (1 + (parameters.robustIterations ?? 0)) + newPredictions) *
|
|
106
112
|
Math.ceil(Math.log2(groupRows.length + 1));
|
|
107
113
|
}
|
|
108
114
|
validateWorkLimit(work, "Regression computation");
|
|
@@ -376,7 +376,56 @@ function weightedPrediction(items, x, y, xValue, xScale = 1, yScale = 1) {
|
|
|
376
376
|
return meanY - (variance === 0 ? 0 : covariance / variance) * meanDifference;
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
-
function
|
|
379
|
+
function applyResidualWeights(neighbors, residualWeights) {
|
|
380
|
+
if (!neighbors.some(item => item.weight > 0)) {
|
|
381
|
+
neighbors = neighbors.map(item => ({ ...item, weight: 1 }));
|
|
382
|
+
}
|
|
383
|
+
if (residualWeights === undefined) return neighbors;
|
|
384
|
+
const weighted = neighbors.map(item => ({
|
|
385
|
+
...item, weight: item.weight * residualWeights[item.index]
|
|
386
|
+
}));
|
|
387
|
+
return weighted.some(item => item.weight > 0) ? weighted : neighbors;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function loessPrediction(rows, {
|
|
391
|
+
x, y, xValue, xScale, yScale, neighborCount, residualWeights, scaled = false
|
|
392
|
+
}, label) {
|
|
393
|
+
const rawDistances = rows.map(row => Math.abs(row[x] - xValue));
|
|
394
|
+
const scaledDistances = scaled || rawDistances.some(value => !Number.isFinite(value));
|
|
395
|
+
const neighbors = rows
|
|
396
|
+
.map((row, index) => ({
|
|
397
|
+
row,
|
|
398
|
+
index,
|
|
399
|
+
distance: scaledDistances
|
|
400
|
+
? Math.abs(row[x] / xScale - xValue / xScale)
|
|
401
|
+
: rawDistances[index]
|
|
402
|
+
}))
|
|
403
|
+
.sort((left, right) =>
|
|
404
|
+
left.distance - right.distance || left.index - right.index
|
|
405
|
+
)
|
|
406
|
+
.slice(0, neighborCount);
|
|
407
|
+
const radius = neighbors.at(-1).distance;
|
|
408
|
+
let weighted = neighbors.map(neighbor => ({
|
|
409
|
+
...neighbor,
|
|
410
|
+
weight: radius === 0
|
|
411
|
+
? 1
|
|
412
|
+
: (1 - (neighbor.distance / radius) ** 3) ** 3
|
|
413
|
+
}));
|
|
414
|
+
weighted = applyResidualWeights(weighted, residualWeights);
|
|
415
|
+
const ordinary = weightedPrediction(weighted, x, y, xValue);
|
|
416
|
+
return {
|
|
417
|
+
prediction: Number.isFinite(ordinary)
|
|
418
|
+
? ordinary
|
|
419
|
+
: restoreFiniteScale(
|
|
420
|
+
weightedPrediction(weighted, x, y, xValue, xScale, yScale),
|
|
421
|
+
yScale,
|
|
422
|
+
label
|
|
423
|
+
),
|
|
424
|
+
neighborIndices: weighted.map(item => item.index)
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function fitLoessGroup(rows, { x, y, group, span, robustIterations = 0 }) {
|
|
380
429
|
const groupLabel = group === undefined ? "all" : String(group);
|
|
381
430
|
const label = `LOESS regression group "${groupLabel}"`;
|
|
382
431
|
if (rows.length < 2 || new Set(rows.map(row => row[x])).size < 2) {
|
|
@@ -390,64 +439,42 @@ function fitLoessGroup(rows, { x, y, group, span }) {
|
|
|
390
439
|
.sort((left, right) => left - right);
|
|
391
440
|
const xScale = maximumMagnitude(rows.map(row => row[x])) || 1;
|
|
392
441
|
const yScale = maximumMagnitude(rows.map(row => row[y])) || 1;
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
})
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
weightedPrediction(weighted, x, y, xValue, xScale, yScale),
|
|
422
|
-
yScale,
|
|
423
|
-
`${label} prediction`
|
|
424
|
-
),
|
|
425
|
-
neighborIndices: weighted.map(item => item.index)
|
|
426
|
-
};
|
|
427
|
-
});
|
|
428
|
-
return { count: rows.length, span, neighborCount, fits, rows, x, y, xScale, yScale };
|
|
442
|
+
const fitAtObserved = residualWeights => xValues.map(xValue => ({
|
|
443
|
+
x: xValue,
|
|
444
|
+
...loessPrediction(rows, {
|
|
445
|
+
x, y, xValue, xScale, yScale, neighborCount, residualWeights
|
|
446
|
+
}, `${label} prediction`)
|
|
447
|
+
}));
|
|
448
|
+
let fits = fitAtObserved();
|
|
449
|
+
let residualWeights;
|
|
450
|
+
for (let iteration = 0; iteration < robustIterations; iteration += 1) {
|
|
451
|
+
const predictions = new Map(fits.map(fit => [fit.x, fit.prediction]));
|
|
452
|
+
const residuals = rows.map(row => Math.abs(row[y] / yScale - predictions.get(row[x]) / yScale));
|
|
453
|
+
const sorted = [...residuals].sort((a, b) => a - b);
|
|
454
|
+
const middle = Math.floor(sorted.length / 2);
|
|
455
|
+
const median = sorted.length % 2 === 0
|
|
456
|
+
? (sorted[middle - 1] + sorted[middle]) / 2 : sorted[middle];
|
|
457
|
+
const tolerance = 64 * Number.EPSILON;
|
|
458
|
+
const cutoff = 6 * median;
|
|
459
|
+
const nextWeights = residuals.map(residual => {
|
|
460
|
+
if (cutoff <= tolerance) return residual <= tolerance ? 1 : 0;
|
|
461
|
+
const ratio = residual / cutoff;
|
|
462
|
+
return ratio >= 1 ? 0 : (1 - ratio * ratio) ** 2;
|
|
463
|
+
});
|
|
464
|
+
if (nextWeights.every((weight, index) => weight === (residualWeights?.[index] ?? 1))) break;
|
|
465
|
+
residualWeights = nextWeights;
|
|
466
|
+
fits = fitAtObserved(residualWeights);
|
|
467
|
+
}
|
|
468
|
+
return { count: rows.length, span, neighborCount, fits, rows, x, y, xScale, yScale,
|
|
469
|
+
...(robustIterations === 0 ? {} : { robustIterations, residualWeights: residualWeights ?? rows.map(() => 1) }) };
|
|
429
470
|
}
|
|
430
471
|
|
|
431
472
|
function predictLoessAt(model, xValue) {
|
|
432
473
|
const found = model.fits.find(fit => fit.x === xValue);
|
|
433
474
|
if (found !== undefined) return found.prediction;
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
distance: Math.abs(row[model.x] / model.xScale - xValue / model.xScale)
|
|
438
|
-
})).sort((left, right) => left.distance - right.distance || left.index - right.index)
|
|
439
|
-
.slice(0, model.neighborCount);
|
|
440
|
-
const radius = neighbors.at(-1).distance;
|
|
441
|
-
const weighted = neighbors.map(neighbor => ({
|
|
442
|
-
...neighbor,
|
|
443
|
-
weight: radius === 0 ? 1 : (1 - (neighbor.distance / radius) ** 3) ** 3
|
|
444
|
-
}));
|
|
445
|
-
const ordinary = weightedPrediction(weighted, model.x, model.y, xValue);
|
|
446
|
-
return Number.isFinite(ordinary) ? ordinary : restoreFiniteScale(
|
|
447
|
-
weightedPrediction(weighted, model.x, model.y, xValue, model.xScale, model.yScale),
|
|
448
|
-
model.yScale,
|
|
449
|
-
"LOESS regression prediction"
|
|
450
|
-
);
|
|
475
|
+
return loessPrediction(model.rows, {
|
|
476
|
+
...model, xValue, scaled: true
|
|
477
|
+
}, "LOESS regression prediction").prediction;
|
|
451
478
|
}
|
|
452
479
|
|
|
453
480
|
export function fitRegressionGroup(rows, { x, y, group, parameters }) {
|
|
@@ -472,7 +499,8 @@ export function fitRegressionGroup(rows, { x, y, group, parameters }) {
|
|
|
472
499
|
interval: parameters.interval
|
|
473
500
|
});
|
|
474
501
|
}
|
|
475
|
-
return fitLoessGroup(rows, { x, y, group, span: parameters.span
|
|
502
|
+
return fitLoessGroup(rows, { x, y, group, span: parameters.span,
|
|
503
|
+
robustIterations: parameters.robustIterations });
|
|
476
504
|
}
|
|
477
505
|
|
|
478
506
|
export function predictRegressionAt(model, xValue, parameters) {
|
|
@@ -45,6 +45,7 @@ export function normalizeRegressionParameters({
|
|
|
45
45
|
method = "linear",
|
|
46
46
|
degree,
|
|
47
47
|
span,
|
|
48
|
+
robustIterations,
|
|
48
49
|
confidenceMethod,
|
|
49
50
|
level,
|
|
50
51
|
confidence,
|
|
@@ -69,7 +70,15 @@ export function normalizeRegressionParameters({
|
|
|
69
70
|
"Regression LOESS span must be greater than zero and at most one."
|
|
70
71
|
);
|
|
71
72
|
}
|
|
72
|
-
|
|
73
|
+
const iterations = robustIterations === undefined ? 0 : robustIterations;
|
|
74
|
+
if (!Number.isInteger(iterations) || iterations < 0 || iterations > 32) {
|
|
75
|
+
throw new RangeError("LOESS robustIterations must be an integer in [0, 32].");
|
|
76
|
+
}
|
|
77
|
+
return cloneAndFreeze({ method, span: resolvedSpan,
|
|
78
|
+
...(iterations === 0 ? {} : { robustIterations: iterations }) });
|
|
79
|
+
}
|
|
80
|
+
if (robustIterations !== undefined) {
|
|
81
|
+
throw new Error("Regression robustIterations requires the loess method.");
|
|
73
82
|
}
|
|
74
83
|
if (span !== undefined) {
|
|
75
84
|
throw new Error("Regression span requires the loess method.");
|
|
@@ -140,7 +149,7 @@ export function validateRegressionTransform(transform) {
|
|
|
140
149
|
const supported = [
|
|
141
150
|
"type", "method", "x", "y", "groupBy", "confidenceMethod", "level",
|
|
142
151
|
"confidence", "interval",
|
|
143
|
-
"degree", "span", "predict", "missing"
|
|
152
|
+
"degree", "span", "robustIterations", "predict", "missing"
|
|
144
153
|
];
|
|
145
154
|
const unknown = Object.keys(transform).find(key => !supported.includes(key));
|
|
146
155
|
if (unknown !== undefined) {
|
|
@@ -166,6 +175,7 @@ export function validateRegressionTransform(transform) {
|
|
|
166
175
|
method: transform.method,
|
|
167
176
|
degree: transform.degree,
|
|
168
177
|
span: transform.span,
|
|
178
|
+
robustIterations: transform.robustIterations,
|
|
169
179
|
confidenceMethod: transform.confidenceMethod,
|
|
170
180
|
level: transform.level,
|
|
171
181
|
confidence: transform.confidence,
|
|
@@ -202,7 +212,8 @@ export function normalizeRegressionTransform(args = {}) {
|
|
|
202
212
|
? { degree: parameters.degree }
|
|
203
213
|
: {}),
|
|
204
214
|
...(parameters.method === "loess"
|
|
205
|
-
? { span: parameters.span
|
|
215
|
+
? { span: parameters.span, ...(parameters.robustIterations === undefined
|
|
216
|
+
? {} : { robustIterations: parameters.robustIterations }) }
|
|
206
217
|
: parameters.interval === false ? { interval: false } : {
|
|
207
218
|
confidenceMethod: parameters.confidenceMethod,
|
|
208
219
|
level: parameters.level,
|
|
@@ -5,8 +5,12 @@ import {
|
|
|
5
5
|
} from "./strokeStyle.js";
|
|
6
6
|
|
|
7
7
|
export const ROUNDED_RECT_K = 4 * (Math.sqrt(2) - 1) / 3;
|
|
8
|
+
export const RECT_RADIUS_PROPERTIES = Object.freeze([
|
|
9
|
+
"cornerRadius", "cornerRadiusTopLeft", "cornerRadiusTopRight",
|
|
10
|
+
"cornerRadiusBottomRight", "cornerRadiusBottomLeft"
|
|
11
|
+
]);
|
|
8
12
|
export const RECT_STYLE_PROPERTIES = Object.freeze([
|
|
9
|
-
|
|
13
|
+
...RECT_RADIUS_PROPERTIES,
|
|
10
14
|
...STROKE_STYLE_PROPERTIES
|
|
11
15
|
]);
|
|
12
16
|
|
|
@@ -17,12 +21,12 @@ function requireFinite(value, property) {
|
|
|
17
21
|
return value;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
export function validateCornerRadius(value, label = "Rect style") {
|
|
24
|
+
export function validateCornerRadius(value, label = "Rect style", property = "cornerRadius") {
|
|
21
25
|
if (!Number.isFinite(value)) {
|
|
22
|
-
throw new TypeError(`${label}
|
|
26
|
+
throw new TypeError(`${label} ${property} must be a finite number.`);
|
|
23
27
|
}
|
|
24
28
|
if (value < 0) {
|
|
25
|
-
throw new RangeError(`${label}
|
|
29
|
+
throw new RangeError(`${label} ${property} must not be negative.`);
|
|
26
30
|
}
|
|
27
31
|
return value;
|
|
28
32
|
}
|
|
@@ -33,9 +37,9 @@ export function requestedRectStyleDetails(request = {}, label = "Rect style") {
|
|
|
33
37
|
}
|
|
34
38
|
return freezeOwned({
|
|
35
39
|
...requestedStrokeDetails(request, label),
|
|
36
|
-
...
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
...Object.fromEntries(RECT_RADIUS_PROPERTIES
|
|
41
|
+
.filter(property => Object.hasOwn(request, property))
|
|
42
|
+
.map(property => [property, validateCornerRadius(request[property], label, property)]))
|
|
39
43
|
});
|
|
40
44
|
}
|
|
41
45
|
|
|
@@ -65,60 +69,48 @@ export function resolveRoundedRectRadius(width, height, requestedRadius) {
|
|
|
65
69
|
return Math.min(requestedRadius, width / 2, height / 2);
|
|
66
70
|
}
|
|
67
71
|
|
|
72
|
+
export function hasRectRadius(style = {}) {
|
|
73
|
+
return RECT_RADIUS_PROPERTIES.some(property => Object.hasOwn(style, property));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolveCornerRadii(width, height, requested) {
|
|
77
|
+
const style = typeof requested === "number" ? { cornerRadius: requested } : requested;
|
|
78
|
+
const details = requestedRectStyleDetails(style, "Rounded rect");
|
|
79
|
+
return RECT_RADIUS_PROPERTIES.slice(1).map(property => resolveRoundedRectRadius(
|
|
80
|
+
width, height, details[property] ?? details.cornerRadius ?? 0
|
|
81
|
+
));
|
|
82
|
+
}
|
|
83
|
+
|
|
68
84
|
export function roundedRectCommands({ x, y, width, height, radius }) {
|
|
69
85
|
const geometry = normalizeRectGeometry({ x, y, width, height });
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
geometry.height,
|
|
73
|
-
radius
|
|
74
|
-
);
|
|
75
|
-
if (!(r > 0)) {
|
|
86
|
+
const [tl, tr, br, bl] = resolveCornerRadii(geometry.width, geometry.height, radius);
|
|
87
|
+
if (![tl, tr, br, bl].some(value => value > 0)) {
|
|
76
88
|
throw new RangeError("Rounded rect commands require a positive resolved radius.");
|
|
77
89
|
}
|
|
78
90
|
const right = geometry.x + geometry.width;
|
|
79
91
|
const bottom = geometry.y + geometry.height;
|
|
80
|
-
const
|
|
92
|
+
const k = ROUNDED_RECT_K;
|
|
81
93
|
return freezeOwned([
|
|
82
|
-
{ op: "M", x: geometry.x +
|
|
83
|
-
{ op: "L", x: right -
|
|
94
|
+
{ op: "M", x: geometry.x + tl, y: geometry.y },
|
|
95
|
+
{ op: "L", x: right - tr, y: geometry.y },
|
|
84
96
|
{
|
|
85
|
-
op: "C",
|
|
86
|
-
|
|
87
|
-
y1: geometry.y,
|
|
88
|
-
x2: right,
|
|
89
|
-
y2: geometry.y + r - c,
|
|
90
|
-
x: right,
|
|
91
|
-
y: geometry.y + r
|
|
97
|
+
op: "C", x1: right - tr + k * tr, y1: geometry.y,
|
|
98
|
+
x2: right, y2: geometry.y + tr - k * tr, x: right, y: geometry.y + tr
|
|
92
99
|
},
|
|
93
|
-
{ op: "L", x: right, y: bottom -
|
|
100
|
+
{ op: "L", x: right, y: bottom - br },
|
|
94
101
|
{
|
|
95
|
-
op: "C",
|
|
96
|
-
|
|
97
|
-
y1: bottom - r + c,
|
|
98
|
-
x2: right - r + c,
|
|
99
|
-
y2: bottom,
|
|
100
|
-
x: right - r,
|
|
101
|
-
y: bottom
|
|
102
|
+
op: "C", x1: right, y1: bottom - br + k * br,
|
|
103
|
+
x2: right - br + k * br, y2: bottom, x: right - br, y: bottom
|
|
102
104
|
},
|
|
103
|
-
{ op: "L", x: geometry.x +
|
|
105
|
+
{ op: "L", x: geometry.x + bl, y: bottom },
|
|
104
106
|
{
|
|
105
|
-
op: "C",
|
|
106
|
-
|
|
107
|
-
y1: bottom,
|
|
108
|
-
x2: geometry.x,
|
|
109
|
-
y2: bottom - r + c,
|
|
110
|
-
x: geometry.x,
|
|
111
|
-
y: bottom - r
|
|
107
|
+
op: "C", x1: geometry.x + bl - k * bl, y1: bottom,
|
|
108
|
+
x2: geometry.x, y2: bottom - bl + k * bl, x: geometry.x, y: bottom - bl
|
|
112
109
|
},
|
|
113
|
-
{ op: "L", x: geometry.x, y: geometry.y +
|
|
110
|
+
{ op: "L", x: geometry.x, y: geometry.y + tl },
|
|
114
111
|
{
|
|
115
|
-
op: "C",
|
|
116
|
-
|
|
117
|
-
y1: geometry.y + r - c,
|
|
118
|
-
x2: geometry.x + r - c,
|
|
119
|
-
y2: geometry.y,
|
|
120
|
-
x: geometry.x + r,
|
|
121
|
-
y: geometry.y
|
|
112
|
+
op: "C", x1: geometry.x, y1: geometry.y + tl - k * tl,
|
|
113
|
+
x2: geometry.x + tl - k * tl, y2: geometry.y, x: geometry.x + tl, y: geometry.y
|
|
122
114
|
},
|
|
123
115
|
{ op: "Z" }
|
|
124
116
|
].map(command => freezeOwned(command)));
|
|
@@ -129,15 +121,11 @@ export function materializeRectItem(properties, requestedRadius = 0) {
|
|
|
129
121
|
throw new TypeError("Rounded rect item properties must be a plain object.");
|
|
130
122
|
}
|
|
131
123
|
const geometry = normalizeRectGeometry(properties);
|
|
132
|
-
const
|
|
133
|
-
geometry.width,
|
|
134
|
-
geometry.height,
|
|
135
|
-
requestedRadius
|
|
136
|
-
);
|
|
124
|
+
const radii = resolveCornerRadii(geometry.width, geometry.height, requestedRadius);
|
|
137
125
|
const appearance = Object.fromEntries(Object.entries(properties).filter(
|
|
138
126
|
([key]) => !["x", "y", "width", "height"].includes(key)
|
|
139
127
|
));
|
|
140
|
-
return radius === 0
|
|
128
|
+
return radii.every(radius => radius === 0)
|
|
141
129
|
? freezeOwned({
|
|
142
130
|
type: "rect",
|
|
143
131
|
properties: freezeOwned({ ...geometry, ...appearance })
|
|
@@ -145,7 +133,7 @@ export function materializeRectItem(properties, requestedRadius = 0) {
|
|
|
145
133
|
: freezeOwned({
|
|
146
134
|
type: "path",
|
|
147
135
|
properties: freezeOwned({
|
|
148
|
-
commands: roundedRectCommands({ ...geometry, radius }),
|
|
136
|
+
commands: roundedRectCommands({ ...geometry, radius: requestedRadius }),
|
|
149
137
|
...appearance
|
|
150
138
|
})
|
|
151
139
|
});
|