ggaction 0.0.12 → 0.0.15
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 +127 -0
- package/README.md +20 -11
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/action-cards.schema.json +1 -1
- package/knowledge/extension-authoring.md +2 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +10 -3
- package/src/actions/basic.js +4 -2
- package/src/actions/boxPlots/components.js +14 -4
- package/src/actions/boxPlots/edit.js +31 -5
- package/src/actions/boxPlots/materialize.js +13 -4
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/canvas/actions.js +3 -1
- package/src/actions/canvas/fitting.js +211 -0
- package/src/actions/canvas/index.js +2 -0
- package/src/actions/categoryOrder/index.js +18 -6
- package/src/actions/charts/area.js +51 -0
- package/src/actions/charts/bar.js +13 -6
- package/src/actions/charts/beeswarm.js +70 -0
- package/src/actions/charts/density.js +48 -0
- package/src/actions/charts/ecdf.js +186 -0
- package/src/actions/charts/endpoints.js +473 -0
- package/src/actions/charts/heatmap.js +4 -3
- package/src/actions/charts/histogram.js +5 -4
- package/src/actions/charts/horizon.js +73 -0
- package/src/actions/charts/index.js +37 -0
- package/src/actions/charts/interval-regression.js +172 -0
- package/src/actions/charts/line.js +8 -6
- package/src/actions/charts/parallel.js +8 -2
- package/src/actions/charts/pie.js +52 -0
- package/src/actions/charts/polar.js +134 -0
- package/src/actions/charts/radar.js +305 -0
- package/src/actions/charts/radial.js +50 -0
- package/src/actions/charts/raincloud.js +422 -0
- package/src/actions/charts/rug-strip.js +283 -0
- package/src/actions/charts/scatter.js +11 -3
- package/src/actions/charts/shared.js +69 -10
- package/src/actions/composition/actions.js +129 -3
- package/src/actions/coordinates/actions.js +1 -1
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/bin.js +29 -0
- package/src/actions/data/bin2d.js +57 -185
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +24 -0
- package/src/actions/data/create.js +2 -2
- package/src/actions/data/density.js +12 -20
- package/src/actions/data/derived.js +111 -15
- package/src/actions/data/ecdf.js +38 -0
- package/src/actions/data/edit.js +539 -0
- package/src/actions/data/filter.js +226 -30
- package/src/actions/data/fold.js +24 -0
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +38 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/intervalEdit.js +86 -4
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +19 -21
- package/src/actions/data/revisionBindings.js +145 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/stack.js +26 -0
- package/src/actions/data/summary.js +26 -0
- package/src/actions/data/timeUnit.js +4 -1
- package/src/actions/data/window.js +1 -1
- package/src/actions/encodings/appearance.js +32 -13
- package/src/actions/encodings/barWidth.js +8 -0
- package/src/actions/encodings/basic.js +2 -0
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/continuous.js +19 -59
- package/src/actions/encodings/color/index.js +30 -23
- package/src/actions/encodings/color/layout.js +3 -97
- package/src/actions/encodings/color/policy.js +5 -8
- package/src/actions/encodings/density/resolve.js +37 -5
- package/src/actions/encodings/density.js +124 -34
- package/src/actions/encodings/histogram.js +3 -2
- package/src/actions/encodings/horizon/resolve.js +8 -4
- package/src/actions/encodings/horizon.js +18 -8
- package/src/actions/encodings/index.js +6 -0
- package/src/actions/encodings/offset.js +30 -29
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +63 -11
- package/src/actions/encodings/position/index.js +52 -6
- package/src/actions/encodings/position/policies/arc.js +14 -0
- package/src/actions/encodings/position/policies/area.js +4 -42
- package/src/actions/encodings/position/policies/bar.js +57 -13
- package/src/actions/encodings/position/resolve.js +88 -26
- package/src/actions/encodings/ranged.js +123 -97
- package/src/actions/encodings/remove.js +44 -34
- package/src/actions/encodings/ruleAppearance.js +24 -32
- package/src/actions/encodings/seriesLayout.js +105 -0
- package/src/actions/encodings/shared.js +107 -37
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/encodings/strokeDash.js +9 -6
- package/src/actions/encodings/temporal.js +7 -0
- package/src/actions/encodings/text.js +30 -15
- package/src/actions/errorBands/create.js +41 -8
- package/src/actions/errorBands/edit.js +360 -25
- package/src/actions/errorBars/create.js +56 -17
- package/src/actions/errorBars/edit.js +372 -21
- package/src/actions/facets/actions.js +619 -41
- package/src/actions/facets/derive.js +139 -25
- package/src/actions/gradientPlots/components.js +43 -13
- package/src/actions/gradientPlots/edit.js +14 -4
- package/src/actions/gradientPlots/materialize.js +8 -8
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/guides/applicability.js +28 -19
- package/src/actions/guides/axes/axes.js +4 -3
- package/src/actions/guides/axes/axis.js +18 -18
- package/src/actions/guides/axes/components.js +43 -0
- package/src/actions/guides/axes/edit.js +16 -51
- package/src/actions/guides/axes/labels.js +184 -28
- package/src/actions/guides/axes/lines.js +13 -7
- package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
- package/src/actions/guides/axes/parallel/policy.js +137 -0
- package/src/actions/guides/axes/parallel/resolve.js +50 -25
- package/src/actions/guides/axes/parallel.js +93 -217
- package/src/actions/guides/axes/policy.js +21 -67
- package/src/actions/guides/axes/tickGroups.js +15 -8
- package/src/actions/guides/axes/ticks.js +11 -6
- package/src/actions/guides/axes/titles.js +32 -11
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facade.js +106 -0
- package/src/actions/guides/facadeAxes.js +200 -0
- package/src/actions/guides/facadeLegend.js +63 -0
- package/src/actions/guides/grids/grid.js +37 -33
- package/src/actions/guides/grids/resolve.js +17 -4
- package/src/actions/guides/guides.js +54 -31
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +309 -279
- package/src/actions/guides/legends/categorical/components.js +21 -17
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +123 -354
- package/src/actions/guides/legends/categorical/options.js +32 -16
- package/src/actions/guides/legends/categorical/recipes.js +114 -9
- package/src/actions/guides/legends/categorical/resolve.js +30 -15
- package/src/actions/guides/legends/categorical/symbols.js +87 -33
- package/src/actions/guides/legends/continuous/common.js +99 -26
- package/src/actions/guides/legends/continuous/gradient.js +120 -89
- package/src/actions/guides/legends/continuous/index.js +8 -2
- package/src/actions/guides/legends/continuous/interval.js +79 -95
- package/src/actions/guides/legends/continuous/opacity.js +113 -89
- package/src/actions/guides/legends/continuous/stroke.js +525 -0
- package/src/actions/guides/legends/creation.js +40 -0
- package/src/actions/guides/legends/edit.js +321 -90
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +149 -102
- package/src/actions/guides/legends/lifecycle.js +143 -0
- package/src/actions/guides/legends/remove.js +43 -78
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +213 -116
- package/src/actions/guides/legends/strokeWidth.js +121 -73
- package/src/actions/guides/legends/target.js +123 -0
- package/src/actions/guides/legends/transition.js +192 -0
- package/src/actions/guides/polar/axes/facade.js +81 -66
- package/src/actions/guides/polar/axes/labels.js +28 -8
- package/src/actions/guides/polar/axes/lines.js +12 -5
- package/src/actions/guides/polar/axes/shared.js +22 -0
- package/src/actions/guides/polar/axes/ticks.js +6 -5
- package/src/actions/guides/polar/axes/titles.js +6 -5
- package/src/actions/guides/polar/grids.js +8 -8
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/guides/reuse.js +44 -0
- package/src/actions/guides/tickValues.js +19 -21
- package/src/actions/index.js +15 -2
- package/src/actions/intervals/resolve.js +35 -15
- package/src/actions/marks/arc/actions.js +78 -20
- package/src/actions/marks/area/actions.js +51 -12
- package/src/actions/marks/area/materialize.js +22 -3
- package/src/actions/marks/bar/create.js +11 -2
- package/src/actions/marks/bar/edit.js +20 -3
- package/src/actions/marks/bar/materialize.js +33 -9
- package/src/actions/marks/index.js +14 -0
- package/src/actions/marks/line/actions.js +71 -113
- package/src/actions/marks/line/materialize.js +57 -56
- package/src/actions/marks/point/create.js +8 -2
- package/src/actions/marks/point/edit.js +29 -21
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/point/jitter.js +3 -0
- package/src/actions/marks/point/materialize.js +154 -16
- package/src/actions/marks/point/packing.js +68 -0
- package/src/actions/marks/rect/actions.js +33 -12
- package/src/actions/marks/references.js +658 -0
- package/src/actions/marks/remove.js +87 -4
- package/src/actions/marks/rule/actions.js +77 -9
- package/src/actions/marks/rule/appearance.js +26 -0
- package/src/actions/marks/shared.js +13 -13
- package/src/actions/marks/text/actions.js +296 -13
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +156 -8
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +73 -0
- package/src/actions/marks/tick/actions.js +22 -5
- package/src/actions/primitives/createGraphics.js +3 -1
- package/src/actions/primitives/editGraphics.js +26 -0
- package/src/actions/primitives/semantic.js +3 -2
- package/src/actions/primitives/semanticAction.js +120 -10
- package/src/actions/primitives/semanticValidation/guide.js +10 -0
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +23 -1
- package/src/actions/primitives/semanticValidation/scale.js +38 -8
- package/src/actions/regression/components.js +26 -11
- package/src/actions/regression/create.js +11 -3
- package/src/actions/regression/edit.js +40 -13
- package/src/actions/regression/resolve.js +1 -1
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +172 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +21 -54
- package/src/actions/scales/consumers/families.js +28 -14
- package/src/actions/scales/consumers/index.js +42 -4
- package/src/actions/scales/consumers/seriesLayout.js +57 -12
- package/src/actions/scales/create.js +58 -26
- package/src/actions/scales/definitions.js +109 -39
- package/src/actions/scales/edit.js +101 -34
- package/src/actions/scales/editPolicy.js +95 -35
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +14 -49
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +140 -0
- package/src/actions/scales/quantitativeColor.js +9 -5
- package/src/actions/selection/actions.js +154 -30
- package/src/actions/selection/index.js +3 -0
- package/src/actions/theme/actions.js +98 -0
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +16 -0
- package/src/actions/theme/reconcile.js +1130 -0
- package/src/actions/theme/state.js +160 -0
- package/src/actions/titles/actions.js +11 -10
- package/src/actions/titles/resolve.js +1 -40
- package/src/actions/violinPlots/create.js +90 -43
- package/src/actions/violinPlots/edit.js +192 -0
- package/src/actions/violinPlots/index.js +2 -0
- package/src/core/ChartProgram.js +7 -0
- package/src/core/action.js +32 -2
- package/src/core/compositionState.js +141 -8
- package/src/core/programState.js +20 -0
- package/src/core/resourceReferences.js +332 -0
- package/src/core/vocabulary.js +16 -3
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +58 -1
- package/src/grammar/areaEndpoints.js +46 -0
- package/src/grammar/areaSeries.js +87 -41
- package/src/grammar/bars/aggregate.js +37 -13
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bars/policy.js +9 -7
- package/src/grammar/bin.js +226 -0
- package/src/grammar/categoryOrder.js +34 -0
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +351 -0
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/ecdf.js +198 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +123 -52
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/fold.js +123 -0
- package/src/grammar/horizon.js +6 -4
- package/src/grammar/impute.js +340 -0
- package/src/grammar/interval.js +37 -20
- package/src/grammar/lineSeries.js +20 -63
- package/src/grammar/markFilter.js +22 -4
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +110 -0
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/parallelAxisTitles.js +16 -0
- package/src/grammar/pathSeries.js +109 -0
- package/src/grammar/pointPacking.js +183 -0
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/positionDatum.js +12 -0
- package/src/grammar/rects.js +9 -1
- package/src/grammar/regression/derive.js +4 -0
- package/src/grammar/regression/index.js +8 -2
- package/src/grammar/regression/models.js +22 -7
- package/src/grammar/regression/parameters.js +63 -13
- package/src/grammar/rotation.js +29 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +8 -21
- package/src/grammar/scales/color.js +23 -1
- package/src/grammar/scales/colorConsumers.js +57 -0
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +42 -5
- package/src/grammar/scales/discretized.js +11 -5
- package/src/grammar/scales/fields.js +38 -8
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/mapping.js +2 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/radial.js +55 -0
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/scales/types.js +1 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +179 -30
- package/src/grammar/schemas/semanticPath.js +23 -4
- package/src/grammar/stack.js +170 -0
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/statistics/confidenceInterval.js +101 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +245 -0
- package/src/grammar/text.js +22 -16
- package/src/grammar/timeUnit.js +234 -13
- package/src/grammar/transformTopology.js +10 -0
- package/src/grammar/transforms.js +418 -16
- package/src/grammar/valueFormat.js +112 -0
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +232 -21
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +100 -12
- package/src/layout/guideCollisions.js +12 -0
- package/src/layout/labels.js +343 -0
- package/src/layout/legend.js +2 -10
- package/src/layout/legendItems.js +102 -0
- package/src/layout/legendLane.js +100 -11
- package/src/materialization/bars/aggregate.js +56 -147
- package/src/materialization/bars/histogram.js +104 -20
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/categorySlotOffset.js +40 -0
- package/src/materialization/composition.js +3 -1
- package/src/materialization/compositionSnapshot.js +1 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +93 -16
- package/src/materialization/encodings.js +1 -1
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +8 -2
- package/src/materialization/facetGuides/preparation.js +12 -7
- package/src/materialization/facetHeaders.js +346 -0
- package/src/materialization/facets.js +23 -66
- package/src/materialization/guides/layout.js +57 -0
- package/src/materialization/guides/resources.js +28 -2
- package/src/materialization/legends.js +11 -2
- package/src/materialization/marks/capabilities.js +20 -5
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +68 -27
- package/src/materialization/marks/policies.js +4 -1
- package/src/materialization/rect.js +55 -42
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/rowEncoding.js +13 -13
- package/src/materialization/scaleGuideDependencies.js +8 -5
- package/src/materialization/scales/map.js +6 -3
- package/src/materialization/scales/policies/arc.js +42 -0
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +5 -1
- package/src/materialization/scales/resolve.js +65 -17
- package/src/materialization/selection/compatibility.js +11 -0
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +42 -71
- package/src/materialization/selection/items/common.js +27 -6
- package/src/materialization/selection/items/path.js +3 -13
- package/src/materialization/selection/items/rect.js +35 -8
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +324 -14
- package/src/mcp/adapter.js +2 -2
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/line.js +2 -3
- package/src/renderers/canvas/native.js +4 -4
- package/src/renderers/canvas/path.js +2 -3
- package/src/renderers/canvas/rect.js +2 -3
- package/src/renderers/canvas/stroke.js +11 -0
- package/src/renderers/svg.js +19 -0
- package/src/selectors/datasets.js +44 -0
- package/src/selectors/layers.js +7 -2
- package/src/selectors/scales.js +6 -0
- package/src/theme/defaults.js +140 -0
- package/types/basic.d.ts +17 -2
- package/types/index.d.ts +211 -0
- package/types/program.d.ts +2182 -204
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { freezeOwned, isPlainObject } from "../core/immutable.js";
|
|
2
|
+
import {
|
|
3
|
+
requestedStrokeDetails,
|
|
4
|
+
STROKE_STYLE_PROPERTIES
|
|
5
|
+
} from "./strokeStyle.js";
|
|
6
|
+
|
|
7
|
+
export const ROUNDED_RECT_K = 4 * (Math.sqrt(2) - 1) / 3;
|
|
8
|
+
export const RECT_STYLE_PROPERTIES = Object.freeze([
|
|
9
|
+
"cornerRadius",
|
|
10
|
+
...STROKE_STYLE_PROPERTIES
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
function requireFinite(value, property) {
|
|
14
|
+
if (!Number.isFinite(value)) {
|
|
15
|
+
throw new TypeError(`Rounded rect ${property} must be a finite number.`);
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function validateCornerRadius(value, label = "Rect style") {
|
|
21
|
+
if (!Number.isFinite(value)) {
|
|
22
|
+
throw new TypeError(`${label} cornerRadius must be a finite number.`);
|
|
23
|
+
}
|
|
24
|
+
if (value < 0) {
|
|
25
|
+
throw new RangeError(`${label} cornerRadius must not be negative.`);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function requestedRectStyleDetails(request = {}, label = "Rect style") {
|
|
31
|
+
if (!isPlainObject(request)) {
|
|
32
|
+
throw new TypeError(`${label} details must be a plain object.`);
|
|
33
|
+
}
|
|
34
|
+
return freezeOwned({
|
|
35
|
+
...requestedStrokeDetails(request, label),
|
|
36
|
+
...(Object.hasOwn(request, "cornerRadius")
|
|
37
|
+
? { cornerRadius: validateCornerRadius(request.cornerRadius, label) }
|
|
38
|
+
: {})
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function normalizeRectGeometry({ x, y, width, height }) {
|
|
43
|
+
x = requireFinite(x, "x");
|
|
44
|
+
y = requireFinite(y, "y");
|
|
45
|
+
width = requireFinite(width, "width");
|
|
46
|
+
height = requireFinite(height, "height");
|
|
47
|
+
if (width < 0) {
|
|
48
|
+
x += width;
|
|
49
|
+
width = -width;
|
|
50
|
+
}
|
|
51
|
+
if (height < 0) {
|
|
52
|
+
y += height;
|
|
53
|
+
height = -height;
|
|
54
|
+
}
|
|
55
|
+
return freezeOwned({ x, y, width, height });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function resolveRoundedRectRadius(width, height, requestedRadius) {
|
|
59
|
+
requireFinite(width, "width");
|
|
60
|
+
requireFinite(height, "height");
|
|
61
|
+
validateCornerRadius(requestedRadius, "Rounded rect");
|
|
62
|
+
if (width < 0 || height < 0) {
|
|
63
|
+
throw new RangeError("Rounded rect width and height must not be negative.");
|
|
64
|
+
}
|
|
65
|
+
return Math.min(requestedRadius, width / 2, height / 2);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function roundedRectCommands({ x, y, width, height, radius }) {
|
|
69
|
+
const geometry = normalizeRectGeometry({ x, y, width, height });
|
|
70
|
+
const r = resolveRoundedRectRadius(
|
|
71
|
+
geometry.width,
|
|
72
|
+
geometry.height,
|
|
73
|
+
radius
|
|
74
|
+
);
|
|
75
|
+
if (!(r > 0)) {
|
|
76
|
+
throw new RangeError("Rounded rect commands require a positive resolved radius.");
|
|
77
|
+
}
|
|
78
|
+
const right = geometry.x + geometry.width;
|
|
79
|
+
const bottom = geometry.y + geometry.height;
|
|
80
|
+
const c = ROUNDED_RECT_K * r;
|
|
81
|
+
return freezeOwned([
|
|
82
|
+
{ op: "M", x: geometry.x + r, y: geometry.y },
|
|
83
|
+
{ op: "L", x: right - r, y: geometry.y },
|
|
84
|
+
{
|
|
85
|
+
op: "C",
|
|
86
|
+
x1: right - r + c,
|
|
87
|
+
y1: geometry.y,
|
|
88
|
+
x2: right,
|
|
89
|
+
y2: geometry.y + r - c,
|
|
90
|
+
x: right,
|
|
91
|
+
y: geometry.y + r
|
|
92
|
+
},
|
|
93
|
+
{ op: "L", x: right, y: bottom - r },
|
|
94
|
+
{
|
|
95
|
+
op: "C",
|
|
96
|
+
x1: right,
|
|
97
|
+
y1: bottom - r + c,
|
|
98
|
+
x2: right - r + c,
|
|
99
|
+
y2: bottom,
|
|
100
|
+
x: right - r,
|
|
101
|
+
y: bottom
|
|
102
|
+
},
|
|
103
|
+
{ op: "L", x: geometry.x + r, y: bottom },
|
|
104
|
+
{
|
|
105
|
+
op: "C",
|
|
106
|
+
x1: geometry.x + r - c,
|
|
107
|
+
y1: bottom,
|
|
108
|
+
x2: geometry.x,
|
|
109
|
+
y2: bottom - r + c,
|
|
110
|
+
x: geometry.x,
|
|
111
|
+
y: bottom - r
|
|
112
|
+
},
|
|
113
|
+
{ op: "L", x: geometry.x, y: geometry.y + r },
|
|
114
|
+
{
|
|
115
|
+
op: "C",
|
|
116
|
+
x1: geometry.x,
|
|
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
|
|
122
|
+
},
|
|
123
|
+
{ op: "Z" }
|
|
124
|
+
].map(command => freezeOwned(command)));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function materializeRectItem(properties, requestedRadius = 0) {
|
|
128
|
+
if (!isPlainObject(properties)) {
|
|
129
|
+
throw new TypeError("Rounded rect item properties must be a plain object.");
|
|
130
|
+
}
|
|
131
|
+
const geometry = normalizeRectGeometry(properties);
|
|
132
|
+
const radius = resolveRoundedRectRadius(
|
|
133
|
+
geometry.width,
|
|
134
|
+
geometry.height,
|
|
135
|
+
requestedRadius
|
|
136
|
+
);
|
|
137
|
+
const appearance = Object.fromEntries(Object.entries(properties).filter(
|
|
138
|
+
([key]) => !["x", "y", "width", "height"].includes(key)
|
|
139
|
+
));
|
|
140
|
+
return radius === 0
|
|
141
|
+
? freezeOwned({
|
|
142
|
+
type: "rect",
|
|
143
|
+
properties: freezeOwned({ ...geometry, ...appearance })
|
|
144
|
+
})
|
|
145
|
+
: freezeOwned({
|
|
146
|
+
type: "path",
|
|
147
|
+
properties: freezeOwned({
|
|
148
|
+
commands: roundedRectCommands({ ...geometry, radius }),
|
|
149
|
+
...appearance
|
|
150
|
+
})
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function readRectItemGeometry(item) {
|
|
155
|
+
if (!isPlainObject(item) || !isPlainObject(item.properties)) return undefined;
|
|
156
|
+
if (item.type === "rect") {
|
|
157
|
+
try {
|
|
158
|
+
return normalizeRectGeometry(item.properties);
|
|
159
|
+
} catch {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const commands = item.type === "path" ? item.properties.commands : undefined;
|
|
164
|
+
if (
|
|
165
|
+
!Array.isArray(commands) ||
|
|
166
|
+
commands.length !== 10 ||
|
|
167
|
+
commands.map(command => command?.op).join("") !== "MLCLCLCLCZ"
|
|
168
|
+
) return undefined;
|
|
169
|
+
const geometry = {
|
|
170
|
+
x: commands[7].x,
|
|
171
|
+
y: commands[0].y,
|
|
172
|
+
width: commands[2].x - commands[7].x,
|
|
173
|
+
height: commands[4].y - commands[0].y
|
|
174
|
+
};
|
|
175
|
+
try {
|
|
176
|
+
return normalizeRectGeometry(geometry);
|
|
177
|
+
} catch {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
}
|
package/src/grammar/rules.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
+
import { normalizePositionDatum } from "./positionDatum.js";
|
|
1
2
|
import { cloneAndFreeze } from "../core/immutable.js";
|
|
2
3
|
import {
|
|
3
|
-
isNominalValue,
|
|
4
|
-
normalizeTemporalValue,
|
|
5
4
|
readNominalField,
|
|
6
5
|
readQuantitativeField,
|
|
7
|
-
readTemporalField
|
|
8
|
-
validateSemanticFieldType
|
|
6
|
+
readTemporalField
|
|
9
7
|
} from "./scales/index.js";
|
|
10
8
|
|
|
11
9
|
const ENDPOINT_CHANNELS = Object.freeze(["x", "y", "x2", "y2"]);
|
|
@@ -13,6 +11,7 @@ const VALUE_CHANNELS = Object.freeze([
|
|
|
13
11
|
...ENDPOINT_CHANNELS,
|
|
14
12
|
"xOffset",
|
|
15
13
|
"yOffset",
|
|
14
|
+
"stroke",
|
|
16
15
|
"strokeDash",
|
|
17
16
|
"strokeWidth",
|
|
18
17
|
"opacity"
|
|
@@ -39,26 +38,13 @@ function readField(rows, encoding) {
|
|
|
39
38
|
return readNominalField(rows, encoding.field);
|
|
40
39
|
}
|
|
41
40
|
if (encoding.fieldType === "temporal") {
|
|
42
|
-
return readTemporalField(rows, encoding.field);
|
|
41
|
+
return readTemporalField(rows, encoding.field, encoding.temporalUnit);
|
|
43
42
|
}
|
|
44
43
|
return readQuantitativeField(rows, encoding.field);
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
export function normalizeRuleDatum(value, fieldType, channel) {
|
|
48
|
-
|
|
49
|
-
if (fieldType === "quantitative") {
|
|
50
|
-
if (!Number.isFinite(value)) {
|
|
51
|
-
throw new TypeError(`Rule ${channel} datum must be a finite number.`);
|
|
52
|
-
}
|
|
53
|
-
return value;
|
|
54
|
-
}
|
|
55
|
-
if (fieldType === "temporal") {
|
|
56
|
-
return normalizeTemporalValue(value, `${channel} datum`, 0);
|
|
57
|
-
}
|
|
58
|
-
if (!isNominalValue(value)) {
|
|
59
|
-
throw new TypeError(`Rule ${channel} datum must be a nominal value.`);
|
|
60
|
-
}
|
|
61
|
-
return value;
|
|
46
|
+
export function normalizeRuleDatum(value, fieldType, channel, temporalUnit) {
|
|
47
|
+
return normalizePositionDatum(value, fieldType, channel, temporalUnit, "Rule");
|
|
62
48
|
}
|
|
63
49
|
|
|
64
50
|
export function deriveRuleValues(rows, layer) {
|
|
@@ -83,7 +69,8 @@ export function deriveRuleValues(rows, layer) {
|
|
|
83
69
|
const datum = normalizeRuleDatum(
|
|
84
70
|
encoding.datum,
|
|
85
71
|
encoding.fieldType,
|
|
86
|
-
channel
|
|
72
|
+
channel,
|
|
73
|
+
encoding.temporalUnit
|
|
87
74
|
);
|
|
88
75
|
values[channel] = Array.from({ length }, () => datum);
|
|
89
76
|
}
|
|
@@ -325,6 +325,23 @@ export function interpolateColorStops(stops, position, interpolation = "rgb") {
|
|
|
325
325
|
return interpolateColors(colors, position, method, true);
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
export function validateSequentialMidpoint(value, type, domain = "auto") {
|
|
329
|
+
if (value === undefined || value === "auto") return undefined;
|
|
330
|
+
if (type !== "sequential") {
|
|
331
|
+
throw new Error(`Scale type "${type}" does not support midpoint.`);
|
|
332
|
+
}
|
|
333
|
+
if (!Number.isFinite(value)) {
|
|
334
|
+
throw new TypeError("Scale midpoint must be finite or auto.");
|
|
335
|
+
}
|
|
336
|
+
if (domain !== "auto" && (
|
|
337
|
+
!Array.isArray(domain) || domain.length !== 2 || !domain.every(Number.isFinite) ||
|
|
338
|
+
!(value > Math.min(...domain) && value < Math.max(...domain))
|
|
339
|
+
)) {
|
|
340
|
+
throw new RangeError("Scale midpoint must lie strictly inside its resolved domain.");
|
|
341
|
+
}
|
|
342
|
+
return value;
|
|
343
|
+
}
|
|
344
|
+
|
|
328
345
|
export function mapSequentialColors(
|
|
329
346
|
values,
|
|
330
347
|
domain,
|
|
@@ -342,6 +359,7 @@ export function mapSequentialColors(
|
|
|
342
359
|
if (typeof shouldClamp !== "boolean") {
|
|
343
360
|
throw new TypeError("Sequential color clamp must be a boolean.");
|
|
344
361
|
}
|
|
362
|
+
const midpoint = validateSequentialMidpoint(options.midpoint, "sequential", domain);
|
|
345
363
|
const [start, end] = domain;
|
|
346
364
|
const constant = start === end;
|
|
347
365
|
const method = validateContinuousColorInterpolation(interpolation);
|
|
@@ -354,7 +372,11 @@ export function mapSequentialColors(
|
|
|
354
372
|
if (hasUnknown) return options.unknown;
|
|
355
373
|
throw new TypeError("Sequential color values must be finite numbers.");
|
|
356
374
|
}
|
|
357
|
-
const raw =
|
|
375
|
+
const raw = midpoint === undefined
|
|
376
|
+
? constant ? 0.5 : inverseLerp(value, start, end)
|
|
377
|
+
: (end > start ? value <= midpoint : value >= midpoint)
|
|
378
|
+
? inverseLerp(value, start, midpoint) / 2
|
|
379
|
+
: 0.5 + inverseLerp(value, midpoint, end) / 2;
|
|
358
380
|
return interpolateColors(colors, raw, method, shouldClamp);
|
|
359
381
|
}));
|
|
360
382
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { BAR_GRAINS, resolveBarChannels, resolveBarGrain } from "../bars/policy.js";
|
|
2
|
+
import { validateAggregate, validateAggregateFieldType } from "../aggregate.js";
|
|
3
|
+
import { isDiscretizedColorScaleType } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export function validateContinuousColorConsumer(layer, encoding, scale, {
|
|
6
|
+
inferAggregate = false,
|
|
7
|
+
channel = "color"
|
|
8
|
+
} = {}) {
|
|
9
|
+
const kind = layer.mark?.type;
|
|
10
|
+
if (channel === "stroke") {
|
|
11
|
+
if (!["point", "line", "area", "bar", "rect", "arc", "rule", "tick"].includes(kind)) {
|
|
12
|
+
throw new Error("Continuous stroke requires a supported graphical mark consumer.");
|
|
13
|
+
}
|
|
14
|
+
if (!['quantitative', 'temporal'].includes(encoding.fieldType) ||
|
|
15
|
+
(isDiscretizedColorScaleType(scale.type) && encoding.fieldType !== "quantitative")) {
|
|
16
|
+
throw new Error(`Scale type "${scale.type}" has an incompatible stroke field type.`);
|
|
17
|
+
}
|
|
18
|
+
if (encoding.aggregate !== undefined) {
|
|
19
|
+
throw new Error("Continuous stroke does not support aggregate.");
|
|
20
|
+
}
|
|
21
|
+
if (Object.hasOwn(scale, "unknown") && kind !== "point") {
|
|
22
|
+
throw new Error("Continuous stroke scale unknown currently requires a row-owned point mark.");
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
if (!["point", "bar", "rect"].includes(kind)) {
|
|
27
|
+
throw new Error("Continuous color requires a Point, aggregate Bar, or Rect consumer.");
|
|
28
|
+
}
|
|
29
|
+
if (!["quantitative", "temporal"].includes(encoding.fieldType) ||
|
|
30
|
+
(isDiscretizedColorScaleType(scale.type) && encoding.fieldType !== "quantitative")) {
|
|
31
|
+
throw new Error(`Scale type "${scale.type}" has an incompatible color field type.`);
|
|
32
|
+
}
|
|
33
|
+
if (Object.hasOwn(scale, "unknown") && kind !== "point") {
|
|
34
|
+
throw new Error("Continuous color scale unknown currently requires a row-owned point mark.");
|
|
35
|
+
}
|
|
36
|
+
if (kind !== "bar") {
|
|
37
|
+
if (encoding.aggregate !== undefined) throw new Error(`${kind} continuous color does not support aggregate.`);
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
if (["nominal", "ordinal"].includes(layer.encoding?.color?.fieldType)) {
|
|
41
|
+
throw new Error("Continuous bar color cannot replace an existing nominal color layout.");
|
|
42
|
+
}
|
|
43
|
+
if (encoding.fieldType !== "quantitative") {
|
|
44
|
+
throw new Error("Aggregate bar color currently requires a quantitative field.");
|
|
45
|
+
}
|
|
46
|
+
if (resolveBarGrain(layer) !== BAR_GRAINS.aggregate) {
|
|
47
|
+
throw new Error("Continuous bar color requires a complete categorical aggregate bar.");
|
|
48
|
+
}
|
|
49
|
+
const measure = layer.encoding?.[resolveBarChannels(layer).measure];
|
|
50
|
+
const aggregate = encoding.aggregate ?? (inferAggregate && measure?.field === encoding.field ? measure.aggregate : undefined);
|
|
51
|
+
if (aggregate === undefined) {
|
|
52
|
+
throw new Error("Continuous bar color requires aggregate when its field differs from the measure field.");
|
|
53
|
+
}
|
|
54
|
+
validateAggregate(aggregate);
|
|
55
|
+
validateAggregateFieldType(aggregate, encoding.fieldType);
|
|
56
|
+
return aggregate;
|
|
57
|
+
}
|
|
@@ -94,10 +94,10 @@ export function resolveContinuousDomain({ domain, values, type, nice, zero }) {
|
|
|
94
94
|
return resolved;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
export function resolveScaleRange(range, channel, bounds) {
|
|
97
|
+
export function resolveScaleRange(range, channel, bounds, polarFrame) {
|
|
98
98
|
validatePositionChannel(channel);
|
|
99
99
|
if (POLAR_POSITION_CHANNELS.includes(channel)) {
|
|
100
|
-
return resolvePolarScaleRange(range, channel, bounds);
|
|
100
|
+
return resolvePolarScaleRange(range, channel, bounds, polarFrame);
|
|
101
101
|
}
|
|
102
102
|
const validated = validateScaleRange(range);
|
|
103
103
|
if (validated !== "auto") return validated;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { validateMeasuredRadiusScale } from "./radial.js";
|
|
2
|
+
import { validateContinuousColorInterpolation, validateSequentialMidpoint } from "./color.js";
|
|
2
3
|
import { isTransformedScaleType } from "./mapping.js";
|
|
3
4
|
import {
|
|
4
5
|
normalizeTransformParameters,
|
|
@@ -23,13 +24,35 @@ function retainedValue(previous, patch, defaults, property, typeChanged) {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
function validateBandParameters(definition, previous, patch, defaults, typeChanged) {
|
|
27
|
+
const hasPadding = Object.hasOwn(patch, "padding");
|
|
28
|
+
if (
|
|
29
|
+
hasPadding &&
|
|
30
|
+
(Object.hasOwn(patch, "paddingInner") || Object.hasOwn(patch, "paddingOuter"))
|
|
31
|
+
) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
"Scale padding cannot be combined with paddingInner or paddingOuter."
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
if (
|
|
37
|
+
hasPadding &&
|
|
38
|
+
(!Number.isFinite(patch.padding) || patch.padding < 0 || patch.padding >= 1)
|
|
39
|
+
) {
|
|
40
|
+
throw new RangeError(
|
|
41
|
+
"Scale padding must be from 0 (inclusive) to 1 (exclusive)."
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const normalizedPatch = hasPadding
|
|
45
|
+
? { ...patch, paddingInner: patch.padding, paddingOuter: patch.padding }
|
|
46
|
+
: patch;
|
|
26
47
|
const paddingInner = retainedValue(
|
|
27
|
-
previous,
|
|
48
|
+
previous, normalizedPatch, defaults, "paddingInner", typeChanged
|
|
28
49
|
) ?? 0;
|
|
29
50
|
const paddingOuter = retainedValue(
|
|
30
|
-
previous,
|
|
51
|
+
previous, normalizedPatch, defaults, "paddingOuter", typeChanged
|
|
31
52
|
) ?? 0;
|
|
32
|
-
const align = retainedValue(
|
|
53
|
+
const align = retainedValue(
|
|
54
|
+
previous, normalizedPatch, defaults, "align", typeChanged
|
|
55
|
+
) ?? 0.5;
|
|
33
56
|
if (!Number.isFinite(paddingInner) || paddingInner < 0 || paddingInner >= 1) {
|
|
34
57
|
throw new RangeError(
|
|
35
58
|
"Scale paddingInner must be from 0 (inclusive) to 1 (exclusive)."
|
|
@@ -65,6 +88,7 @@ export function normalizeScaleDefinition({
|
|
|
65
88
|
defaults = {},
|
|
66
89
|
retainCoreOnTypeChange = false,
|
|
67
90
|
retainCompatibleOnTypeChange = false,
|
|
91
|
+
allowOrdinalBandParameters = false,
|
|
68
92
|
validateDomain,
|
|
69
93
|
validateRange
|
|
70
94
|
}) {
|
|
@@ -137,7 +161,12 @@ export function normalizeScaleDefinition({
|
|
|
137
161
|
throw new Error(`Scale type "${type}" does not support interpolate.`);
|
|
138
162
|
}
|
|
139
163
|
|
|
140
|
-
|
|
164
|
+
const midpoint = validateSequentialMidpoint(
|
|
165
|
+
retainedValue(previous, patch, defaults, "midpoint", typeChanged), type, definition.domain
|
|
166
|
+
);
|
|
167
|
+
if (midpoint !== undefined) definition.midpoint = midpoint;
|
|
168
|
+
|
|
169
|
+
if (type === "band" || (type === "ordinal" && allowOrdinalBandParameters)) {
|
|
141
170
|
validateBandParameters(definition, previous, patch, defaults, typeChanged);
|
|
142
171
|
} else if (type === "point") {
|
|
143
172
|
validatePointParameters(definition, previous, patch, defaults, typeChanged);
|
|
@@ -149,5 +178,13 @@ export function normalizeScaleDefinition({
|
|
|
149
178
|
}
|
|
150
179
|
}
|
|
151
180
|
|
|
181
|
+
const radialMapping = Object.hasOwn(patch, "radialMapping") ? patch.radialMapping : previous.radialMapping;
|
|
182
|
+
if (radialMapping !== undefined) {
|
|
183
|
+
definition.radialMapping = radialMapping;
|
|
184
|
+
validateMeasuredRadiusScale({ ...definition,
|
|
185
|
+
...(Object.hasOwn(patch, "unknown") || Object.hasOwn(previous, "unknown")
|
|
186
|
+
? { unknown: patch.unknown ?? previous.unknown } : {})
|
|
187
|
+
}, { allowAuto: true });
|
|
188
|
+
}
|
|
152
189
|
return definition;
|
|
153
190
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cloneAndFreeze } from "../../core/immutable.js";
|
|
2
2
|
import { validateGeneratedItemLimit } from "../../core/validation.js";
|
|
3
3
|
import { interpolateNumber } from "../numeric.js";
|
|
4
|
+
import { formatValue, validateValueFormat } from "../valueFormat.js";
|
|
4
5
|
import { resolveColorRange, validateColorRange } from "./appearance.js";
|
|
5
6
|
import { SCALE_ROLES, validateScaleTypeForRole } from "./types.js";
|
|
6
7
|
|
|
@@ -162,17 +163,22 @@ export function mapDiscretizedColors(values, scale) {
|
|
|
162
163
|
}));
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
export function formatDiscretizedIntervals(thresholds) {
|
|
166
|
+
export function formatDiscretizedIntervals(thresholds, format = "auto") {
|
|
166
167
|
nondecreasing(thresholds, "Discretized color thresholds");
|
|
168
|
+
const resolvedFormat = validateValueFormat(format, "Legend label format");
|
|
167
169
|
let resolution = Infinity;
|
|
168
170
|
for (let index = 1; index < thresholds.length; index += 1) {
|
|
169
171
|
const gap = thresholds[index] - thresholds[index - 1];
|
|
170
172
|
if (Number.isFinite(gap) && gap > 0) resolution = Math.min(resolution, gap);
|
|
171
173
|
}
|
|
172
|
-
let labels =
|
|
173
|
-
Number.isInteger(value) ? String(value) : value.toFixed(1)
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
let labels = resolvedFormat === "auto"
|
|
175
|
+
? thresholds.map(value => Number.isInteger(value) ? String(value) : value.toFixed(1))
|
|
176
|
+
: thresholds.map(value => formatValue(value, {
|
|
177
|
+
format: resolvedFormat,
|
|
178
|
+
valueType: "quantitative",
|
|
179
|
+
label: "Legend label format"
|
|
180
|
+
}));
|
|
181
|
+
if (resolvedFormat === "auto" && thresholds.some((value, index) => {
|
|
176
182
|
const rounded = Number(labels[index]);
|
|
177
183
|
return value !== 0 && rounded === 0 ||
|
|
178
184
|
index > 0 && value !== thresholds[index - 1] &&
|
|
@@ -81,9 +81,37 @@ function validTimestamp(value) {
|
|
|
81
81
|
return Number.isFinite(value) && Number.isFinite(new Date(value).getTime());
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
export function
|
|
84
|
+
export function validateTemporalUnit(unit) {
|
|
85
|
+
if (unit !== undefined && !["auto", "year", "timestamp"].includes(unit)) {
|
|
86
|
+
throw new Error(`Unsupported temporalUnit "${unit}"; use auto, year, or timestamp.`);
|
|
87
|
+
}
|
|
88
|
+
return unit;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function resolveTemporalUnit(options, fieldType, previous) {
|
|
92
|
+
if (fieldType !== "temporal") {
|
|
93
|
+
if (Object.hasOwn(options, "temporalUnit")) {
|
|
94
|
+
throw new Error("temporalUnit requires a temporal fieldType.");
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
validateTemporalUnit(options.temporalUnit);
|
|
99
|
+
const sameBinding = ["field", "datum"].some(key =>
|
|
100
|
+
Object.hasOwn(options, key) && Object.hasOwn(previous ?? {}, key) &&
|
|
101
|
+
options[key] === previous[key]);
|
|
102
|
+
return options.temporalUnit ?? (sameBinding && previous?.fieldType === "temporal"
|
|
103
|
+
? previous.temporalUnit : undefined);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function normalizeTemporalValue(value, field = "value", index = 0, temporalUnit = "auto") {
|
|
107
|
+
validateTemporalUnit(temporalUnit);
|
|
85
108
|
let timestamp;
|
|
86
|
-
if (
|
|
109
|
+
if (temporalUnit === "year") {
|
|
110
|
+
const year = typeof value === "string" && /^\d{4}$/.test(value) ? Number(value) : value;
|
|
111
|
+
if (Number.isInteger(year) && year >= 0 && year <= 9999) timestamp = utcDate(year);
|
|
112
|
+
} else if (temporalUnit === "timestamp") {
|
|
113
|
+
if (typeof value === "number") timestamp = value;
|
|
114
|
+
} else if (typeof value === "number") {
|
|
87
115
|
timestamp = Number.isInteger(value) && value >= 1000 && value <= 9999
|
|
88
116
|
? utcDate(value)
|
|
89
117
|
: value;
|
|
@@ -99,21 +127,22 @@ export function normalizeTemporalValue(value, field = "value", index = 0) {
|
|
|
99
127
|
if (!validTimestamp(timestamp)) {
|
|
100
128
|
throw new TypeError(
|
|
101
129
|
`Field "${field}" must contain a temporal string or finite timestamp ` +
|
|
102
|
-
`(including a valid date or four-digit year) at row ${index}.`
|
|
130
|
+
`(including a valid date or four-digit year) at row ${index}; temporalUnit is ${temporalUnit}.`
|
|
103
131
|
);
|
|
104
132
|
}
|
|
105
133
|
return timestamp;
|
|
106
134
|
}
|
|
107
135
|
|
|
108
|
-
export function readTemporalField(rows, field) {
|
|
136
|
+
export function readTemporalField(rows, field, temporalUnit) {
|
|
109
137
|
validateField(field);
|
|
138
|
+
validateTemporalUnit(temporalUnit);
|
|
110
139
|
return cloneAndFreeze(rows.map((row, index) => {
|
|
111
140
|
if (!Object.hasOwn(row, field)) {
|
|
112
141
|
throw new TypeError(
|
|
113
142
|
`Field "${field}" must contain a temporal string or finite timestamp at row ${index}.`
|
|
114
143
|
);
|
|
115
144
|
}
|
|
116
|
-
return normalizeTemporalValue(row[field], field, index);
|
|
145
|
+
return normalizeTemporalValue(row[field], field, index, temporalUnit);
|
|
117
146
|
}));
|
|
118
147
|
}
|
|
119
148
|
|
|
@@ -129,10 +158,11 @@ export function readNominalField(rows, field) {
|
|
|
129
158
|
}));
|
|
130
159
|
}
|
|
131
160
|
|
|
132
|
-
export function readScaleField(rows, field, fieldType, { allowUnknown = false } = {}) {
|
|
161
|
+
export function readScaleField(rows, field, fieldType, { allowUnknown = false, temporalUnit } = {}) {
|
|
133
162
|
validateSemanticFieldType(fieldType);
|
|
163
|
+
validateTemporalUnit(temporalUnit);
|
|
134
164
|
if (!allowUnknown) {
|
|
135
|
-
if (fieldType === "temporal") return readTemporalField(rows, field);
|
|
165
|
+
if (fieldType === "temporal") return readTemporalField(rows, field, temporalUnit);
|
|
136
166
|
if (["nominal", "ordinal"].includes(fieldType)) {
|
|
137
167
|
return readNominalField(rows, field);
|
|
138
168
|
}
|
|
@@ -149,7 +179,7 @@ export function readScaleField(rows, field, fieldType, { allowUnknown = false }
|
|
|
149
179
|
return isNominalValue(value) ? value : undefined;
|
|
150
180
|
}
|
|
151
181
|
try {
|
|
152
|
-
return normalizeTemporalValue(value, field, index);
|
|
182
|
+
return normalizeTemporalValue(value, field, index, temporalUnit);
|
|
153
183
|
} catch {
|
|
154
184
|
return undefined;
|
|
155
185
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mapMeasuredRadiusValues } from "./radial.js";
|
|
1
2
|
import { mapLinearValues } from "./continuous.js";
|
|
2
3
|
import { mapTransformedValues } from "./transformed.js";
|
|
3
4
|
import { SCALE_ROLES, validateScaleTypeForRole } from "./types.js";
|
|
@@ -9,6 +10,7 @@ export function isTransformedScaleType(type) {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export function mapContinuousScaleValues(values, scale) {
|
|
13
|
+
if (scale?.radialMapping !== undefined) return mapMeasuredRadiusValues(values, scale);
|
|
12
14
|
if (isTransformedScaleType(scale?.type)) {
|
|
13
15
|
validateScaleTypeForRole(scale.type, SCALE_ROLES.quantitativePosition);
|
|
14
16
|
return mapTransformedValues(values, scale.domain, scale.range, {
|
|
@@ -196,6 +196,7 @@ export function resolveOrdinalOffsetScale({
|
|
|
196
196
|
parentBandwidth,
|
|
197
197
|
paddingInner = 0,
|
|
198
198
|
paddingOuter = 0,
|
|
199
|
+
align = 0.5,
|
|
199
200
|
channel = "xOffset"
|
|
200
201
|
}) {
|
|
201
202
|
const resolvedDomain = resolveOrdinalDomain(domain, values);
|
|
@@ -217,6 +218,9 @@ export function resolveOrdinalOffsetScale({
|
|
|
217
218
|
"Offset scale paddingOuter must be a non-negative finite number."
|
|
218
219
|
);
|
|
219
220
|
}
|
|
221
|
+
if (!Number.isFinite(align) || align < 0 || align > 1) {
|
|
222
|
+
throw new RangeError("Offset scale align must be between 0 and 1.");
|
|
223
|
+
}
|
|
220
224
|
validateDomainValues(resolvedDomain, values, undefined, "ordinal");
|
|
221
225
|
const denominator = Math.max(
|
|
222
226
|
1,
|
|
@@ -231,15 +235,18 @@ export function resolveOrdinalOffsetScale({
|
|
|
231
235
|
if (!Number.isFinite(bandwidth) || bandwidth <= 0) {
|
|
232
236
|
throw new Error("Offset scale padding must leave a positive bandwidth.");
|
|
233
237
|
}
|
|
238
|
+
const occupied = step * (resolvedDomain.length - paddingInner);
|
|
239
|
+
const start = resolvedRange[0] + (resolvedRange[1] - resolvedRange[0] - occupied) * align;
|
|
234
240
|
return cloneAndFreeze({
|
|
235
241
|
type: "ordinal",
|
|
236
242
|
domain: resolvedDomain,
|
|
237
243
|
range: resolvedRange,
|
|
238
244
|
step,
|
|
239
|
-
start
|
|
245
|
+
start,
|
|
240
246
|
bandwidth,
|
|
241
247
|
paddingInner,
|
|
242
|
-
paddingOuter
|
|
248
|
+
paddingOuter,
|
|
249
|
+
align
|
|
243
250
|
});
|
|
244
251
|
}
|
|
245
252
|
|
|
@@ -296,7 +303,9 @@ export function mapOrdinalOffsetValues(values, scale) {
|
|
|
296
303
|
return finitePosition(
|
|
297
304
|
direct,
|
|
298
305
|
scale,
|
|
299
|
-
(scale.paddingOuter
|
|
306
|
+
(scale.paddingOuter * 2 * scale.align +
|
|
307
|
+
index +
|
|
308
|
+
(1 - scale.paddingInner) / 2) /
|
|
300
309
|
denominator
|
|
301
310
|
);
|
|
302
311
|
}));
|
|
@@ -9,9 +9,9 @@ export function validateScaleUnknown(channel, value) {
|
|
|
9
9
|
}
|
|
10
10
|
return value;
|
|
11
11
|
}
|
|
12
|
-
if (
|
|
12
|
+
if (["color", "stroke"].includes(channel)) {
|
|
13
13
|
if (typeof value !== "string" || value.length === 0) {
|
|
14
|
-
throw new TypeError(
|
|
14
|
+
throw new TypeError(`Scale unknown for ${channel} must be a non-empty color string.`);
|
|
15
15
|
}
|
|
16
16
|
return value;
|
|
17
17
|
}
|