ggaction 0.0.16 → 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 +66 -0
- package/README.md +3 -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 +10 -4
- 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/bin.js +1 -1
- package/src/actions/data/box.js +2 -1
- package/src/actions/data/create.js +18 -5
- package/src/actions/data/density.js +1 -1
- package/src/actions/data/derived.js +15 -1
- package/src/actions/data/edit.js +20 -7
- package/src/actions/data/filter.js +15 -4
- package/src/actions/data/index.js +3 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/regression.js +6 -2
- package/src/actions/data/revise.js +37 -5
- package/src/actions/data/shared.js +33 -4
- package/src/actions/data/sort.js +21 -0
- package/src/actions/data/stack.js +3 -1
- package/src/actions/data/summary.js +1 -1
- package/src/actions/encodings/angle.js +4 -1
- package/src/actions/encodings/appearance.js +43 -31
- package/src/actions/encodings/barWidth.js +36 -11
- package/src/actions/encodings/channels.js +0 -1
- package/src/actions/encodings/color/continuous.js +5 -2
- package/src/actions/encodings/color/index.js +5 -2
- package/src/actions/encodings/color/policy.js +10 -3
- package/src/actions/encodings/density/resolve.js +1 -6
- package/src/actions/encodings/offset.js +4 -1
- 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 +9 -3
- package/src/actions/encodings/ranged.js +23 -1
- package/src/actions/encodings/remove.js +3 -2
- package/src/actions/encodings/ruleAppearance.js +4 -1
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/stroke.js +4 -1
- package/src/actions/encodings/strokeDash.js +6 -1
- package/src/actions/encodings/text.js +12 -4
- 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 +17 -4
- package/src/actions/marks/bar/edit.js +18 -7
- 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/create.js +8 -1
- package/src/actions/marks/point/edit.js +13 -6
- package/src/actions/marks/point/materialize.js +4 -2
- package/src/actions/marks/rect/actions.js +26 -11
- package/src/actions/marks/remove.js +2 -0
- package/src/actions/marks/rule/actions.js +31 -12
- package/src/actions/marks/text/actions.js +42 -11
- package/src/actions/marks/text/layout.js +3 -0
- package/src/actions/marks/tick/actions.js +23 -8
- package/src/actions/primitives/semanticAction.js +37 -3
- package/src/actions/primitives/semanticValidation/dataset.js +13 -1
- package/src/actions/primitives/semanticValidation/layer.js +26 -2
- package/src/actions/primitives/semanticValidation/scale.js +6 -0
- package/src/actions/regression/create.js +26 -5
- package/src/actions/regression/edit.js +50 -15
- package/src/actions/regression/follow.js +22 -0
- package/src/actions/regression/resolve.js +2 -6
- package/src/actions/scales/channels.js +20 -15
- package/src/actions/scales/consumers/common.js +2 -1
- package/src/actions/scales/consumers/families.js +16 -5
- package/src/actions/scales/create.js +10 -4
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +3 -3
- package/src/actions/scales/editPolicy.js +9 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/parallel.js +1 -1
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +49 -2
- package/src/actions/scales/quantitativeColor.js +3 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +83 -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/action.js +3 -2
- package/src/core/compositionState.js +4 -1
- package/src/core/font.js +9 -2
- package/src/core/optionRequirements.js +1 -0
- package/src/core/programState.js +5 -1
- 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 +41 -22
- 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 +382 -0
- package/src/grammar/density.js +53 -5
- 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/filter.js +133 -39
- 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 +34 -10
- package/src/grammar/itemMissing.js +59 -0
- 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 +63 -16
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/models.js +116 -59
- package/src/grammar/regression/parameters.js +69 -6
- 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 -190
- 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 +5 -3
- package/src/grammar/sort.js +88 -0
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +120 -45
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +16 -7
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transformTopology.js +1 -0
- package/src/grammar/transforms.js +25 -12
- package/src/grammar/valueFormat.js +17 -5
- package/src/grammar/weightedStatistics.js +1 -6
- package/src/grammar/window.js +4 -58
- package/src/inspection.js +495 -0
- package/src/inspectionDescriptors.js +3 -0
- 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 +26 -3
- 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/revisionIdentity.js +19 -0
- 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 +72 -7
- 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 +17 -6
- 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 +94 -8
- 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/inspection.d.ts +17 -0
- package/types/program.d.ts +494 -112
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { axisThemeTypography } from "../../theme/state.js";
|
|
2
|
+
import { canvasOverflowError } from "../../../layout/canvas.js";
|
|
1
3
|
import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
|
|
2
4
|
import { action } from "../../../core/action.js";
|
|
3
5
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
4
6
|
import {
|
|
5
7
|
sameOrderedValues,
|
|
6
8
|
validateGeneratedItemLimit,
|
|
9
|
+
validateTextStyle,
|
|
7
10
|
validateOptionObject,
|
|
8
|
-
validateNonEmptyString,
|
|
9
11
|
validateNonNegativeFinite,
|
|
10
12
|
validatePositiveFinite
|
|
11
13
|
} from "../../../core/validation.js";
|
|
@@ -19,7 +21,7 @@ import {
|
|
|
19
21
|
import { formatTimeTick, formatTimeTicks } from "../../../grammar/ticks.js";
|
|
20
22
|
import { resolveRotation } from "../../../grammar/rotation.js";
|
|
21
23
|
import { valuesFromTickConfig } from "../tickValues.js";
|
|
22
|
-
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
24
|
+
import { AXIS_LABEL_FONT_SIZES, DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
23
25
|
"../../../theme/defaults.js";
|
|
24
26
|
import {
|
|
25
27
|
defaultAxisPosition,
|
|
@@ -40,19 +42,21 @@ import { resolveConcreteGraphicBounds } from
|
|
|
40
42
|
import { wrapText } from "../../../layout/text.js";
|
|
41
43
|
import {
|
|
42
44
|
normalizeDisplayLabelMap,
|
|
45
|
+
normalizeDisplayColorMap,
|
|
46
|
+
resolveDisplayLabelColor,
|
|
43
47
|
resolveDisplayLabel
|
|
44
48
|
} from "../../../grammar/displayLabels.js";
|
|
45
49
|
|
|
46
50
|
const OPTIONS = [
|
|
47
51
|
"scale", "position", "count", "values", "offset", "format", "color",
|
|
48
52
|
"fontSize", "fontFamily", "fontWeight", "rotation", "maxWidth", "wrap",
|
|
49
|
-
"lineHeight", "overlap", "labelMap"
|
|
53
|
+
"lineHeight", "overlap", "labelMap", "alongOffset", "flush", "colorMap"
|
|
50
54
|
];
|
|
51
55
|
|
|
52
56
|
const DEFAULTS = {
|
|
53
57
|
count: 5,
|
|
54
58
|
color: DEFAULT_COLORS.text,
|
|
55
|
-
fontSize:
|
|
59
|
+
fontSize: AXIS_LABEL_FONT_SIZES.cartesian,
|
|
56
60
|
fontFamily: DEFAULT_FONT_FAMILY,
|
|
57
61
|
fontWeight: "normal",
|
|
58
62
|
rotation: 0,
|
|
@@ -78,15 +82,7 @@ function normalizeLabelLayout(config, args, operation) {
|
|
|
78
82
|
return next;
|
|
79
83
|
}
|
|
80
84
|
|
|
81
|
-
export
|
|
82
|
-
validateNonEmptyString(config.color, `${label} color`);
|
|
83
|
-
validatePositiveFinite(config.fontSize, `${label} fontSize`);
|
|
84
|
-
validateNonEmptyString(config.fontFamily, `${label} fontFamily`);
|
|
85
|
-
if (typeof config.fontWeight !== "string" &&
|
|
86
|
-
!Number.isFinite(config.fontWeight)) {
|
|
87
|
-
throw new TypeError(`${label} fontWeight must be a string or number.`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
85
|
+
export { validateTextStyle as validateAxisTextStyle };
|
|
90
86
|
|
|
91
87
|
function validateOptions(args, operation, create) {
|
|
92
88
|
validateOptionObject(
|
|
@@ -94,6 +90,8 @@ function validateOptions(args, operation, create) {
|
|
|
94
90
|
create ? OPTIONS : OPTIONS.filter(key => key !== "scale"),
|
|
95
91
|
operation
|
|
96
92
|
);
|
|
93
|
+
if (Object.hasOwn(args, "flush") && typeof args.flush !== "boolean") throw new TypeError("Label flush must be a boolean.");
|
|
94
|
+
if (Object.hasOwn(args, "alongOffset") && !Number.isFinite(args.alongOffset)) throw new TypeError("Label alongOffset must be a finite number.");
|
|
97
95
|
if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) {
|
|
98
96
|
throw new Error(`${operation} cannot use count and values together.`);
|
|
99
97
|
}
|
|
@@ -116,9 +114,11 @@ function validateConfig(channel, config) {
|
|
|
116
114
|
if (config.mode === "values") {
|
|
117
115
|
validateGeneratedItemLimit(config.values.length, "Label value count");
|
|
118
116
|
}
|
|
117
|
+
if (config.alongOffset !== undefined && !Number.isFinite(config.alongOffset)) throw new TypeError("Label alongOffset must be a finite number.");
|
|
119
118
|
validateNonNegativeFinite(config.offset, "Label offset");
|
|
120
|
-
|
|
119
|
+
validateTextStyle(config, "Label");
|
|
121
120
|
validateAxisFormat(config.format);
|
|
121
|
+
if (config.flush === true && config.rotation !== 0) throw new Error("Label flush requires unrotated Cartesian labels.");
|
|
122
122
|
if (!Number.isFinite(config.rotation)) {
|
|
123
123
|
throw new TypeError("Label rotation must resolve to finite radians.");
|
|
124
124
|
}
|
|
@@ -140,24 +140,30 @@ function validateConfig(channel, config) {
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
function
|
|
144
|
-
if (!Object.hasOwn(args, "labelMap")) return config;
|
|
145
|
-
const scale = program.resolvedScales[config.scale];
|
|
146
|
-
if (!["ordinal", "band", "point"].includes(scale?.type)) {
|
|
147
|
-
throw new Error(`${operation} labelMap requires a categorical scale.`);
|
|
148
|
-
}
|
|
143
|
+
function normalizeAxisLabelMaps(program, config, args, operation) {
|
|
149
144
|
const next = { ...config };
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
);
|
|
145
|
+
for (const [property, normalize] of [
|
|
146
|
+
["labelMap", normalizeDisplayLabelMap],
|
|
147
|
+
["colorMap", normalizeDisplayColorMap]
|
|
148
|
+
]) {
|
|
149
|
+
if (!Object.hasOwn(args, property)) continue;
|
|
150
|
+
if (args[property] === "auto") delete next[property];
|
|
151
|
+
else next[property] = normalize(args[property], `${operation} ${property}`);
|
|
157
152
|
}
|
|
153
|
+
validateAxisLabelMapScale(next, program.resolvedScales[config.scale], operation);
|
|
158
154
|
return next;
|
|
159
155
|
}
|
|
160
156
|
|
|
157
|
+
function validateAxisLabelMapScale(config, scale, operation = "Axis") {
|
|
158
|
+
if (["ordinal", "band", "point"].includes(scale?.type)) return;
|
|
159
|
+
for (const property of ["labelMap", "colorMap"]) {
|
|
160
|
+
if (config[property] !== undefined &&
|
|
161
|
+
!config[property].every(entry => Number.isFinite(entry.value))) {
|
|
162
|
+
throw new TypeError(`${operation} continuous ${property} values must be finite numbers or timestamps.`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
161
167
|
function assertTickCompatibility(ticks, config, operation) {
|
|
162
168
|
if (!ticks) return;
|
|
163
169
|
if (ticks.scale !== config.scale || ticks.mode !== config.mode) throw new Error(`${operation} conflicts with axis ticks.`);
|
|
@@ -226,15 +232,13 @@ function resolve(program, channel, config) {
|
|
|
226
232
|
channel
|
|
227
233
|
});
|
|
228
234
|
const discrete = ["ordinal", "band", "point"].includes(scale?.type);
|
|
229
|
-
|
|
230
|
-
throw new Error("Axis labelMap requires a categorical scale.");
|
|
231
|
-
}
|
|
235
|
+
validateAxisLabelMapScale(config, scale);
|
|
232
236
|
if ((
|
|
233
237
|
!["linear", "time", "ordinal", "band", "point"].includes(scale?.type) &&
|
|
234
238
|
!isTransformedScaleType(scale?.type)
|
|
235
239
|
) || !bounds) throw new Error("Axis labels require a supported resolved scale and Canvas bounds.");
|
|
236
240
|
if (discrete && config.mode !== "values") throw new Error("Discrete axis labels require explicit or inferred values, not count.");
|
|
237
|
-
|
|
241
|
+
let values = valuesFromTickConfig(program, config);
|
|
238
242
|
validateGeneratedItemLimit(values.length, "Label value count");
|
|
239
243
|
if (discrete) {
|
|
240
244
|
const domainValues = new Set(scale.domain);
|
|
@@ -243,7 +247,7 @@ function resolve(program, channel, config) {
|
|
|
243
247
|
const low = Math.min(...scale.domain), high = Math.max(...scale.domain);
|
|
244
248
|
if (!values.every(value => value >= low && value <= high)) throw new RangeError("Label values must be inside the scale domain.");
|
|
245
249
|
}
|
|
246
|
-
|
|
250
|
+
let positions = discrete
|
|
247
251
|
? mapOrdinalPositionValues(values, scale)
|
|
248
252
|
: mapContinuousScaleValues(values, scale);
|
|
249
253
|
const fallback = value => formatAxisValue(
|
|
@@ -256,24 +260,39 @@ function resolve(program, channel, config) {
|
|
|
256
260
|
? formatTransformedTick(scale.type, item)
|
|
257
261
|
: String(item)
|
|
258
262
|
);
|
|
259
|
-
const
|
|
260
|
-
? values
|
|
261
|
-
:
|
|
262
|
-
|
|
263
|
-
|
|
263
|
+
const automaticTimeLabels = scale.type === "time" && config.format === "auto"
|
|
264
|
+
? formatTimeTicks(values, scale.domain)
|
|
265
|
+
: undefined;
|
|
266
|
+
let text = values.map((value, index) =>
|
|
267
|
+
resolveDisplayLabel(value, config.labelMap, raw =>
|
|
268
|
+
automaticTimeLabels?.[index] ?? fallback(raw)));
|
|
269
|
+
// Empty mapped text suppresses the label, never its raw tick or scale value.
|
|
270
|
+
// Filter all aligned arrays before wrapping so group/color indices stay dense.
|
|
271
|
+
if (text.includes("")) {
|
|
272
|
+
const visible = text.map((value, index) => value === "" ? -1 : index)
|
|
273
|
+
.filter(index => index !== -1);
|
|
274
|
+
values = visible.map(index => values[index]);
|
|
275
|
+
positions = visible.map(index => positions[index]);
|
|
276
|
+
text = visible.map(index => text[index]);
|
|
277
|
+
}
|
|
264
278
|
const geometry = {
|
|
265
279
|
values,
|
|
266
280
|
...resolveAxisLabelGeometry({
|
|
267
281
|
bounds,
|
|
268
282
|
channel,
|
|
269
283
|
position: config.position,
|
|
270
|
-
positions,
|
|
284
|
+
positions: positions.map(value => value + (config.alongOffset ?? 0)),
|
|
271
285
|
offset: config.offset
|
|
272
286
|
})
|
|
273
287
|
};
|
|
274
288
|
const resolved = expandWrappedLabels(geometry, text, config, channel, program.materializationConfigs.textMetrics);
|
|
289
|
+
if (channel === "x" && Math.abs(Math.sin(config.rotation)) > 1e-9) {
|
|
290
|
+
const outward = config.position === "bottom" ? 1 : -1;
|
|
291
|
+
resolved.textAlign = Math.sin(config.rotation) * outward < 0 ? "right" : "left";
|
|
292
|
+
resolved.textBaseline = "middle";
|
|
293
|
+
}
|
|
275
294
|
const canvas = findCanvasGraphic(program)?.properties;
|
|
276
|
-
|
|
295
|
+
let labelBounds = resolved.text.map((value, index) => resolveTextBounds({
|
|
277
296
|
x: Array.isArray(resolved.x) ? resolved.x[index] : resolved.x,
|
|
278
297
|
y: Array.isArray(resolved.y) ? resolved.y[index] : resolved.y,
|
|
279
298
|
text: value,
|
|
@@ -284,7 +303,58 @@ function resolve(program, channel, config) {
|
|
|
284
303
|
textBaseline: resolved.textBaseline,
|
|
285
304
|
rotation: config.rotation
|
|
286
305
|
}, program.materializationConfigs.textMetrics));
|
|
287
|
-
|
|
306
|
+
if (program.materializationConfigs.canvas?.plot !== undefined || config.rotation !== 0) {
|
|
307
|
+
const horizontal = channel === "x";
|
|
308
|
+
const near = ["bottom", "right"].includes(config.position);
|
|
309
|
+
const edge = horizontal ? bounds.y + (near ? bounds.height : 0)
|
|
310
|
+
: bounds.x + (near ? bounds.width : 0);
|
|
311
|
+
const boundKey = horizontal ? (near ? "top" : "bottom") : (near ? "left" : "right");
|
|
312
|
+
const grouped = unionLabelBounds(labelBounds.map((bounds, index) => ({ bounds, group: resolved.groups[index] })));
|
|
313
|
+
const shifts = grouped.map(item => edge + (near ? config.offset : -config.offset) - item[boundKey]);
|
|
314
|
+
const axis = horizontal ? "y" : "x";
|
|
315
|
+
const positions = resolved[axis];
|
|
316
|
+
resolved[axis] = resolved.text.map((_, index) =>
|
|
317
|
+
(Array.isArray(positions) ? positions[index] : positions) + shifts[resolved.groups[index]]);
|
|
318
|
+
labelBounds = labelBounds.map((item, index) => {
|
|
319
|
+
const delta = shifts[resolved.groups[index]];
|
|
320
|
+
return horizontal ? { ...item, top: item.top + delta, bottom: item.bottom + delta }
|
|
321
|
+
: { ...item, left: item.left + delta, right: item.right + delta };
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
if (config.flush === true) {
|
|
325
|
+
const along = channel === "x" ? "x" : "y";
|
|
326
|
+
const lowKey = channel === "x" ? "left" : "top";
|
|
327
|
+
const highKey = channel === "x" ? "right" : "bottom";
|
|
328
|
+
const low = Math.min(...scale.range) + (config.alongOffset ?? 0);
|
|
329
|
+
const high = Math.max(...scale.range) + (config.alongOffset ?? 0);
|
|
330
|
+
const grouped = unionLabelBounds(labelBounds.map((bounds, index) => ({ bounds, group: resolved.groups[index] })));
|
|
331
|
+
const shifts = grouped.map(item => {
|
|
332
|
+
if (item[highKey] - item[lowKey] > high - low + 1e-9) {
|
|
333
|
+
throw new Error(`The ${channel}-axis label cannot flush inside the scale span.`);
|
|
334
|
+
}
|
|
335
|
+
return item[lowKey] < low ? low - item[lowKey]
|
|
336
|
+
: item[highKey] > high ? high - item[highKey] : 0;
|
|
337
|
+
});
|
|
338
|
+
const positions = resolved[along];
|
|
339
|
+
resolved[along] = resolved.text.map((_, index) =>
|
|
340
|
+
(Array.isArray(positions) ? positions[index] : positions) + shifts[resolved.groups[index]]);
|
|
341
|
+
labelBounds = labelBounds.map((item, index) => {
|
|
342
|
+
const delta = shifts[resolved.groups[index]];
|
|
343
|
+
return { ...item, [lowKey]: item[lowKey] + delta, [highKey]: item[highKey] + delta };
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
const rotatedOverlap = config.rotation !== 0;
|
|
347
|
+
const collisionBounds = rotatedOverlap ? resolved.text.map((text, index) => {
|
|
348
|
+
const x = Array.isArray(resolved.x) ? resolved.x[index] : resolved.x;
|
|
349
|
+
const y = Array.isArray(resolved.y) ? resolved.y[index] : resolved.y;
|
|
350
|
+
const cosine = Math.cos(config.rotation), sine = Math.sin(config.rotation);
|
|
351
|
+
return resolveTextBounds({
|
|
352
|
+
x: x * cosine + y * sine, y: -x * sine + y * cosine, text,
|
|
353
|
+
fontSize: config.fontSize, fontFamily: config.fontFamily, fontWeight: config.fontWeight,
|
|
354
|
+
textAlign: resolved.textAlign, textBaseline: resolved.textBaseline
|
|
355
|
+
}, program.materializationConfigs.textMetrics);
|
|
356
|
+
}) : labelBounds;
|
|
357
|
+
const groupedBounds = unionLabelBounds(collisionBounds.map((bounds, index) => ({
|
|
288
358
|
bounds,
|
|
289
359
|
group: resolved.groups[index]
|
|
290
360
|
})));
|
|
@@ -292,10 +362,11 @@ function resolve(program, channel, config) {
|
|
|
292
362
|
? left.left - right.left
|
|
293
363
|
: left.top - right.top);
|
|
294
364
|
if (!canvas || !labelBounds.every(item => textBoundsFitCanvas(item, canvas))) {
|
|
295
|
-
throw
|
|
365
|
+
throw canvasOverflowError(`The ${channel}-axis labels do not fit the Canvas margin.`, labelBounds, canvas);
|
|
296
366
|
}
|
|
297
|
-
if (config.overlap === "error" && orderedBounds.some((item, index) =>
|
|
298
|
-
|
|
367
|
+
if (config.overlap === "error" && orderedBounds.some((item, index) => rotatedOverlap
|
|
368
|
+
? orderedBounds.slice(0, index).some(previous => textBoundsIntersect(previous, item))
|
|
369
|
+
: index > 0 && textBoundsIntersect(orderedBounds[index - 1], item))) {
|
|
299
370
|
throw new Error(`The ${channel}-axis labels overlap each other.`);
|
|
300
371
|
}
|
|
301
372
|
const title = program.graphicSpec.objects[`${channel}AxisTitle`]
|
|
@@ -333,7 +404,7 @@ function makeEdit(channel) {
|
|
|
333
404
|
...(explicitMode ? { inferredValues: false } : {}),
|
|
334
405
|
mode
|
|
335
406
|
}, args, op);
|
|
336
|
-
config =
|
|
407
|
+
config = normalizeAxisLabelMaps(this, config, args, op);
|
|
337
408
|
if (mode === "values") delete config.count; else delete config.values;
|
|
338
409
|
validateConfig(channel, config);
|
|
339
410
|
assertTickCompatibility(this.guideConfigs.axis?.[channel]?.ticks, config, op);
|
|
@@ -344,7 +415,9 @@ function makeEdit(channel) {
|
|
|
344
415
|
x: resolved.x,
|
|
345
416
|
y: resolved.y,
|
|
346
417
|
text: resolved.text,
|
|
347
|
-
fill: config.color
|
|
418
|
+
fill: config.colorMap === undefined ? config.color
|
|
419
|
+
: resolved.text.map((_, index) => resolveDisplayLabelColor(
|
|
420
|
+
resolved.values[resolved.groups[index]], config.colorMap, config.color)),
|
|
348
421
|
fontSize: config.fontSize,
|
|
349
422
|
fontFamily: config.fontFamily,
|
|
350
423
|
fontWeight: config.fontWeight,
|
|
@@ -399,6 +472,7 @@ function makeCreate(channel) {
|
|
|
399
472
|
fontSize: DEFAULTS.fontSize,
|
|
400
473
|
fontFamily: DEFAULTS.fontFamily,
|
|
401
474
|
fontWeight: DEFAULTS.fontWeight,
|
|
475
|
+
...axisThemeTypography(this.materializationConfigs.theme, "labels"),
|
|
402
476
|
rotation: DEFAULTS.rotation,
|
|
403
477
|
overlap: DEFAULTS.overlap,
|
|
404
478
|
...args,
|
|
@@ -406,7 +480,7 @@ function makeCreate(channel) {
|
|
|
406
480
|
(ticks?.inferredValues === true || inferredDiscreteValues !== undefined),
|
|
407
481
|
mode
|
|
408
482
|
}, args, op);
|
|
409
|
-
config =
|
|
483
|
+
config = normalizeAxisLabelMaps(this, config, args, op);
|
|
410
484
|
if (mode === "values") {
|
|
411
485
|
config.values ??= ticks?.values ?? inferredDiscreteValues;
|
|
412
486
|
} else {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { requireSemanticScale } from "../../../selectors/scales.js";
|
|
1
2
|
import { editGraphicProperties } from "../../primitives/graphicProperties.js";
|
|
2
3
|
import { isSourceOwnedText } from "../../../grammar/text.js";
|
|
3
4
|
import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
|
|
@@ -8,7 +9,7 @@ import {
|
|
|
8
9
|
validateNonEmptyString,
|
|
9
10
|
validateNonNegativeFinite
|
|
10
11
|
} from "../../../core/validation.js";
|
|
11
|
-
import { resolveGuideCoordinateBounds } from "../../../materialization/coordinateBounds.js";
|
|
12
|
+
import { axisScaleBindings, resolveGuideCoordinateBounds } from "../../../materialization/coordinateBounds.js";
|
|
12
13
|
import { DEFAULT_COLORS } from "../../../theme/defaults.js";
|
|
13
14
|
import {
|
|
14
15
|
defaultAxisPosition,
|
|
@@ -27,10 +28,10 @@ function validateStyle({ color, lineWidth }) {
|
|
|
27
28
|
validateNonNegativeFinite(lineWidth, "Axis lineWidth");
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
function resolveLineRange(resolvedScale, bounds, channel) {
|
|
31
|
+
function resolveLineRange(resolvedScale, bounds, channel, explicitRange) {
|
|
31
32
|
const range = resolvedScale?.range;
|
|
32
33
|
if (
|
|
33
|
-
resolvedScale?.type !== "time" ||
|
|
34
|
+
explicitRange || resolvedScale?.type !== "time" ||
|
|
34
35
|
!Number.isFinite(resolvedScale.bandwidth)
|
|
35
36
|
) return range;
|
|
36
37
|
const plotRange = channel === "x"
|
|
@@ -46,7 +47,7 @@ function resolveGeometry(program, channel, scaleId, position) {
|
|
|
46
47
|
layer => !isSourceOwnedText(layer) && layer.encoding?.[channel]?.scale === scaleId
|
|
47
48
|
);
|
|
48
49
|
|
|
49
|
-
if (!hasConsumer) {
|
|
50
|
+
if (!hasConsumer && !axisScaleBindings(program, scaleId).some(binding => binding.channel === channel)) {
|
|
50
51
|
throw new Error(
|
|
51
52
|
`Axis line requires scale "${scaleId}" on the ${channel} channel.`
|
|
52
53
|
);
|
|
@@ -75,7 +76,7 @@ function resolveGeometry(program, channel, scaleId, position) {
|
|
|
75
76
|
bounds,
|
|
76
77
|
channel,
|
|
77
78
|
position,
|
|
78
|
-
resolveLineRange(resolvedScale, bounds, channel)
|
|
79
|
+
resolveLineRange(resolvedScale, bounds, channel, Array.isArray(requireSemanticScale(program, scaleId).range))
|
|
79
80
|
);
|
|
80
81
|
}
|
|
81
82
|
|
|
@@ -68,7 +68,7 @@ function makeAxisAction(create) {
|
|
|
68
68
|
? { mode: "selected", dimensions: resolved.dimensions.map(dimension => ({ field: dimension.field })) }
|
|
69
69
|
: resolveParallelAxisConfigs(this, resolved.dimensions);
|
|
70
70
|
const previous = config.dimensions.find(dimension => dimension.field === args.field);
|
|
71
|
-
const updated = patchParallelAxis(previous, args, create, operation);
|
|
71
|
+
const updated = patchParallelAxis(previous, args, create, operation, this.materializationConfigs.theme);
|
|
72
72
|
const next = withTitle(withOwner(this, resolved, {
|
|
73
73
|
...config, dimensions: config.dimensions.map(dimension => dimension.field === args.field ? updated : dimension)
|
|
74
74
|
}), args, create);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { axisThemeTypography } from "../../../theme/state.js";
|
|
1
2
|
import {
|
|
2
3
|
validateGeneratedItemLimit,
|
|
3
4
|
validateNonEmptyString,
|
|
4
5
|
validateNonNegativeFinite,
|
|
5
6
|
validateOptionObject
|
|
6
7
|
} from "../../../../core/validation.js";
|
|
7
|
-
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from "../../../../theme/defaults.js";
|
|
8
|
+
import { AXIS_LABEL_FONT_SIZES, AXIS_TITLE_FONT_SIZE, DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from "../../../../theme/defaults.js";
|
|
8
9
|
import { validateAxisTextStyle } from "../labels.js";
|
|
9
10
|
import { validateAxisFormat } from "../policy.js";
|
|
10
11
|
|
|
@@ -20,15 +21,15 @@ const STYLE_OPTIONS = Object.freeze({
|
|
|
20
21
|
title: ["text", "offset", "color", "fontSize", "fontFamily", "fontWeight"]
|
|
21
22
|
});
|
|
22
23
|
|
|
23
|
-
export function defaultParallelAxis(field) {
|
|
24
|
+
export function defaultParallelAxis(field, theme) {
|
|
24
25
|
return {
|
|
25
26
|
field,
|
|
26
27
|
line: { color: DEFAULT_COLORS.axis, lineWidth: 1.25 },
|
|
27
28
|
ticks: { mode: "auto", length: 8, color: DEFAULT_COLORS.mutedText, lineWidth: 1 },
|
|
28
29
|
labels: { mode: "auto", offset: 9, format: "auto", color: DEFAULT_COLORS.axis,
|
|
29
|
-
fontSize:
|
|
30
|
+
fontSize: AXIS_LABEL_FONT_SIZES.parallel, fontFamily: DEFAULT_FONT_FAMILY, fontWeight: "normal", ...axisThemeTypography(theme, "labels") },
|
|
30
31
|
title: { offset: 20, color: DEFAULT_COLORS.axisTitle,
|
|
31
|
-
fontSize:
|
|
32
|
+
fontSize: AXIS_TITLE_FONT_SIZE, fontFamily: DEFAULT_FONT_FAMILY, fontWeight: 600, ...axisThemeTypography(theme, "title") }
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -98,8 +99,8 @@ function patchComponent(previous, defaults, options, part, create, operation) {
|
|
|
98
99
|
return next;
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
export function patchParallelAxis(previous, args, create, operation) {
|
|
102
|
-
const defaults = defaultParallelAxis(args.field);
|
|
102
|
+
export function patchParallelAxis(previous, args, create, operation, theme) {
|
|
103
|
+
const defaults = defaultParallelAxis(args.field, theme);
|
|
103
104
|
const next = { ...previous, field: args.field };
|
|
104
105
|
const updates = {};
|
|
105
106
|
for (const part of ["line", "title"]) {
|
|
@@ -132,6 +133,6 @@ export function resolveParallelAxisConfigs(program, dimensions) {
|
|
|
132
133
|
const previous = program.guideConfigs.axis?.parallel?.axes;
|
|
133
134
|
const mode = previous?.mode ?? "all";
|
|
134
135
|
const configs = dimensions.map(dimension => previous?.dimensions?.find(config => config.field === dimension.field) ??
|
|
135
|
-
(mode === "all" ? defaultParallelAxis(dimension.field) : { field: dimension.field }));
|
|
136
|
+
(mode === "all" ? defaultParallelAxis(dimension.field, program.materializationConfigs.theme) : { field: dimension.field }));
|
|
136
137
|
return { mode, dimensions: configs };
|
|
137
138
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
|
|
1
2
|
import { action } from "../../../core/action.js";
|
|
2
3
|
import { validateKeys } from "../../../core/validation.js";
|
|
3
4
|
import { axisGraphicIds } from
|
|
@@ -20,7 +21,7 @@ function makeRemoveAxis(channel) {
|
|
|
20
21
|
op: operation,
|
|
21
22
|
description: `Remove the complete ${channel}-axis resource.`
|
|
22
23
|
},
|
|
23
|
-
function (args = {}) {
|
|
24
|
+
withGuideLayoutValidation(function (args = {}) {
|
|
24
25
|
validateKeys(args, OPTIONS, operation);
|
|
25
26
|
const semantic = this.semanticSpec.guides.axis?.[channel];
|
|
26
27
|
const config = this.guideConfigs.axis?.[channel];
|
|
@@ -50,7 +51,7 @@ function makeRemoveAxis(channel) {
|
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
return next._withoutMaterializationConfig(["guides", "axis", channel]);
|
|
53
|
-
}
|
|
54
|
+
})
|
|
54
55
|
);
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -18,7 +18,7 @@ const EDIT_OPTIONS = Object.freeze([
|
|
|
18
18
|
"ticks",
|
|
19
19
|
"labels"
|
|
20
20
|
]);
|
|
21
|
-
const TICK_OPTIONS = Object.freeze(["length", "color", "lineWidth"]);
|
|
21
|
+
const TICK_OPTIONS = Object.freeze(["length", "color", "lineWidth", "alongOffset"]);
|
|
22
22
|
const LABEL_OPTIONS = Object.freeze([
|
|
23
23
|
"offset",
|
|
24
24
|
"format",
|
|
@@ -31,7 +31,10 @@ const LABEL_OPTIONS = Object.freeze([
|
|
|
31
31
|
"wrap",
|
|
32
32
|
"lineHeight",
|
|
33
33
|
"overlap",
|
|
34
|
-
"labelMap"
|
|
34
|
+
"labelMap",
|
|
35
|
+
"alongOffset",
|
|
36
|
+
"flush",
|
|
37
|
+
"colorMap"
|
|
35
38
|
]);
|
|
36
39
|
const SHARED_CREATE = Object.freeze(["scale", "position", "count", "values"]);
|
|
37
40
|
const SHARED_EDIT = Object.freeze(["position", "count", "values"]);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canvasOverflowError } from "../../../layout/canvas.js";
|
|
1
2
|
import { editGraphicProperties } from "../../primitives/graphicProperties.js";
|
|
2
3
|
import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
|
|
3
4
|
import { applyMaterializationPlan } from "../../../materialization/planner.js";
|
|
@@ -31,7 +32,7 @@ import {
|
|
|
31
32
|
import { findCanvasGraphic, resolvePlotGraphicPlacement } from
|
|
32
33
|
"../../../materialization/graphicHierarchy.js";
|
|
33
34
|
|
|
34
|
-
const OPTIONS = Object.freeze(["scale", "position", "count", "values", "length", "color", "lineWidth"]);
|
|
35
|
+
const OPTIONS = Object.freeze(["scale", "position", "count", "values", "length", "color", "lineWidth", "alongOffset"]);
|
|
35
36
|
const DEFAULTS = Object.freeze({
|
|
36
37
|
count: DEFAULT_TICK_COUNT,
|
|
37
38
|
length: 6,
|
|
@@ -45,6 +46,7 @@ function validateOptions(args, operation, create) {
|
|
|
45
46
|
create ? OPTIONS : OPTIONS.filter(key => key !== "scale"),
|
|
46
47
|
operation
|
|
47
48
|
);
|
|
49
|
+
if (Object.hasOwn(args, "alongOffset") && !Number.isFinite(args.alongOffset)) throw new TypeError("Tick alongOffset must be a finite number.");
|
|
48
50
|
if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) throw new Error(`${operation} cannot use count and values together.`);
|
|
49
51
|
if (Array.isArray(args.values)) {
|
|
50
52
|
validateGeneratedItemLimit(args.values.length, "Tick value count");
|
|
@@ -65,6 +67,7 @@ function validateConfig(channel, config) {
|
|
|
65
67
|
(typeof value === "number" && Number.isFinite(value))
|
|
66
68
|
))
|
|
67
69
|
) throw new TypeError("Tick values must be nominal values or finite numbers.");
|
|
70
|
+
if (config.alongOffset !== undefined && !Number.isFinite(config.alongOffset)) throw new TypeError("Tick alongOffset must be a finite number.");
|
|
68
71
|
validateNonNegativeFinite(config.length, "Tick length");
|
|
69
72
|
validateNonNegativeFinite(config.lineWidth, "Tick lineWidth");
|
|
70
73
|
validateNonEmptyString(config.color, "Tick color");
|
|
@@ -102,7 +105,7 @@ function geometry(program, channel, config) {
|
|
|
102
105
|
bounds,
|
|
103
106
|
channel,
|
|
104
107
|
position: config.position,
|
|
105
|
-
positions,
|
|
108
|
+
positions: positions.map(value => value + (config.alongOffset ?? 0)),
|
|
106
109
|
length: config.length
|
|
107
110
|
})
|
|
108
111
|
};
|
|
@@ -127,7 +130,7 @@ function geometry(program, channel, config) {
|
|
|
127
130
|
coordinates.some(value => !Number.isFinite(value)) ||
|
|
128
131
|
!fits
|
|
129
132
|
) {
|
|
130
|
-
throw
|
|
133
|
+
throw canvasOverflowError(`The ${channel}-axis ticks do not fit the Canvas margin.`, [{ left: left - strokeExtent, right: right + strokeExtent, top: top - strokeExtent, bottom: bottom + strokeExtent }], canvas);
|
|
131
134
|
}
|
|
132
135
|
return resolved;
|
|
133
136
|
}
|