ggaction 0.0.1
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 +27 -0
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/package.json +84 -0
- package/src/AGENTS.md +119 -0
- package/src/ChartProgram.js +10 -0
- package/src/actions/boxPlots/components.js +88 -0
- package/src/actions/boxPlots/create.js +84 -0
- package/src/actions/boxPlots/index.js +10 -0
- package/src/actions/boxPlots/materialize.js +183 -0
- package/src/actions/boxPlots/options.js +147 -0
- package/src/actions/boxPlots/resolve.js +38 -0
- package/src/actions/canvas/actions.js +144 -0
- package/src/actions/canvas/index.js +6 -0
- package/src/actions/coordinates/actions.js +85 -0
- package/src/actions/coordinates/index.js +5 -0
- package/src/actions/data/box.js +40 -0
- package/src/actions/data/create.js +33 -0
- package/src/actions/data/density.js +62 -0
- package/src/actions/data/derived.js +52 -0
- package/src/actions/data/filter.js +168 -0
- package/src/actions/data/index.js +32 -0
- package/src/actions/data/interval.js +69 -0
- package/src/actions/data/regression.js +71 -0
- package/src/actions/data/shared.js +25 -0
- package/src/actions/encodings/appearance.js +243 -0
- package/src/actions/encodings/barWidth.js +60 -0
- package/src/actions/encodings/color.js +329 -0
- package/src/actions/encodings/density.js +225 -0
- package/src/actions/encodings/histogram.js +50 -0
- package/src/actions/encodings/index.js +23 -0
- package/src/actions/encodings/offset.js +104 -0
- package/src/actions/encodings/position/policies/area.js +24 -0
- package/src/actions/encodings/position/policies/bar.js +110 -0
- package/src/actions/encodings/position/policies/common.js +17 -0
- package/src/actions/encodings/position/policies/index.js +25 -0
- package/src/actions/encodings/position/policies/line.js +74 -0
- package/src/actions/encodings/position/policies/point.js +19 -0
- package/src/actions/encodings/position/policies/rule.js +17 -0
- package/src/actions/encodings/position/resolve.js +170 -0
- package/src/actions/encodings/position.js +176 -0
- package/src/actions/encodings/ranged.js +276 -0
- package/src/actions/encodings/ruleAppearance.js +47 -0
- package/src/actions/encodings/shared.js +164 -0
- package/src/actions/encodings/strokeDash.js +145 -0
- package/src/actions/errorBands/components.js +81 -0
- package/src/actions/errorBands/create.js +159 -0
- package/src/actions/errorBands/index.js +7 -0
- package/src/actions/errorBands/resolve.js +28 -0
- package/src/actions/errorBars/components.js +62 -0
- package/src/actions/errorBars/create.js +144 -0
- package/src/actions/errorBars/index.js +7 -0
- package/src/actions/errorBars/resolve.js +25 -0
- package/src/actions/guides/axes/axes.js +204 -0
- package/src/actions/guides/axes/axis.js +131 -0
- package/src/actions/guides/axes/index.js +17 -0
- package/src/actions/guides/axes/labels.js +231 -0
- package/src/actions/guides/axes/lines.js +149 -0
- package/src/actions/guides/axes/policy.js +155 -0
- package/src/actions/guides/axes/tickGroups.js +149 -0
- package/src/actions/guides/axes/ticks.js +179 -0
- package/src/actions/guides/axes/titles.js +264 -0
- package/src/actions/guides/grids/grid.js +228 -0
- package/src/actions/guides/grids/index.js +1 -0
- package/src/actions/guides/grids/resolve.js +258 -0
- package/src/actions/guides/guides.js +138 -0
- package/src/actions/guides/index.js +11 -0
- package/src/actions/guides/legends/categorical/actions.js +329 -0
- package/src/actions/guides/legends/categorical/components.js +140 -0
- package/src/actions/guides/legends/categorical/index.js +49 -0
- package/src/actions/guides/legends/categorical/layout.js +445 -0
- package/src/actions/guides/legends/categorical/options.js +188 -0
- package/src/actions/guides/legends/categorical/recipes.js +120 -0
- package/src/actions/guides/legends/categorical/resolve.js +131 -0
- package/src/actions/guides/legends/categorical/symbols.js +280 -0
- package/src/actions/guides/legends/categorical/validation.js +27 -0
- package/src/actions/guides/legends/continuous/common.js +273 -0
- package/src/actions/guides/legends/continuous/gradient.js +363 -0
- package/src/actions/guides/legends/continuous/index.js +25 -0
- package/src/actions/guides/legends/continuous/interval.js +234 -0
- package/src/actions/guides/legends/continuous/opacity.js +382 -0
- package/src/actions/guides/legends/edit.js +314 -0
- package/src/actions/guides/legends/index.js +9 -0
- package/src/actions/guides/legends/size.js +220 -0
- package/src/actions/guides/tickValues.js +84 -0
- package/src/actions/index.js +31 -0
- package/src/actions/intervals/resolve.js +352 -0
- package/src/actions/marks/area.js +369 -0
- package/src/actions/marks/bar/create.js +46 -0
- package/src/actions/marks/bar/edit.js +90 -0
- package/src/actions/marks/bar/index.js +9 -0
- package/src/actions/marks/bar/materialize.js +130 -0
- package/src/actions/marks/index.js +13 -0
- package/src/actions/marks/line.js +248 -0
- package/src/actions/marks/point.js +420 -0
- package/src/actions/marks/rule.js +287 -0
- package/src/actions/marks/shared.js +112 -0
- package/src/actions/primitives/createGraphics.js +163 -0
- package/src/actions/primitives/editGraphics.js +208 -0
- package/src/actions/primitives/index.js +9 -0
- package/src/actions/primitives/semantic.js +168 -0
- package/src/actions/primitives/semanticValue.js +182 -0
- package/src/actions/regression/components.js +216 -0
- package/src/actions/regression/create.js +102 -0
- package/src/actions/regression/index.js +23 -0
- package/src/actions/regression/resolve.js +57 -0
- package/src/actions/scales/consumers.js +283 -0
- package/src/actions/scales/create.js +148 -0
- package/src/actions/scales/definitions.js +319 -0
- package/src/actions/scales/edit.js +290 -0
- package/src/actions/scales/index.js +11 -0
- package/src/actions/scales/materialize.js +466 -0
- package/src/actions/scales/quantitativeColor.js +45 -0
- package/src/actions/selection/actions.js +384 -0
- package/src/actions/selection/index.js +23 -0
- package/src/actions/titles/actions.js +235 -0
- package/src/actions/titles/index.js +19 -0
- package/src/actions/titles/resolve.js +321 -0
- package/src/core/ChartProgram.js +316 -0
- package/src/core/action.js +120 -0
- package/src/core/identifiers.js +28 -0
- package/src/core/immutable.js +94 -0
- package/src/core/specs.js +29 -0
- package/src/core/textMetrics.js +23 -0
- package/src/core/validation.js +54 -0
- package/src/core/vocabulary.js +58 -0
- package/src/extension.js +2 -0
- package/src/grammar/aggregate.js +259 -0
- package/src/grammar/areaSeries.js +217 -0
- package/src/grammar/bars/aggregate.js +98 -0
- package/src/grammar/bars/geometry.js +54 -0
- package/src/grammar/bars/policy.js +90 -0
- package/src/grammar/boxPlot.js +140 -0
- package/src/grammar/coordinates.js +25 -0
- package/src/grammar/curveCommands.js +356 -0
- package/src/grammar/density.js +296 -0
- package/src/grammar/filter.js +169 -0
- package/src/grammar/histogram.js +332 -0
- package/src/grammar/interval.js +343 -0
- package/src/grammar/lineSeries.js +197 -0
- package/src/grammar/markFilter.js +46 -0
- package/src/grammar/markSelection.js +326 -0
- package/src/grammar/palettes.js +208 -0
- package/src/grammar/pathCommands.js +86 -0
- package/src/grammar/pointShapes.js +164 -0
- package/src/grammar/positionCompatibility.js +32 -0
- package/src/grammar/regression.js +578 -0
- package/src/grammar/ruleAppearance.js +12 -0
- package/src/grammar/rules.js +90 -0
- package/src/grammar/scales/appearance.js +185 -0
- package/src/grammar/scales/color.js +349 -0
- package/src/grammar/scales/continuous.js +111 -0
- package/src/grammar/scales/definition.js +153 -0
- package/src/grammar/scales/discretized.js +159 -0
- package/src/grammar/scales/fields.js +133 -0
- package/src/grammar/scales/index.js +12 -0
- package/src/grammar/scales/mapping.js +27 -0
- package/src/grammar/scales/ordinal.js +221 -0
- package/src/grammar/scales/policies.js +50 -0
- package/src/grammar/scales/temporal.js +53 -0
- package/src/grammar/scales/transformed.js +236 -0
- package/src/grammar/scales/types.js +102 -0
- package/src/grammar/scales/validation.js +66 -0
- package/src/grammar/scales.js +1 -0
- package/src/grammar/schemas/concreteGraphic.js +73 -0
- package/src/grammar/schemas/graphic.js +131 -0
- package/src/grammar/schemas/graphicBounds.js +146 -0
- package/src/grammar/schemas/graphicTree.js +133 -0
- package/src/grammar/schemas/mark.js +4 -0
- package/src/grammar/schemas/semanticPath.js +174 -0
- package/src/grammar/seriesLayout.js +76 -0
- package/src/grammar/ticks.js +149 -0
- package/src/grammar/transforms.js +37 -0
- package/src/index.js +2 -0
- package/src/layout/canvas.js +114 -0
- package/src/layout/grid.js +17 -0
- package/src/layout/legend.js +51 -0
- package/src/layout/text.js +62 -0
- package/src/layout/title.js +107 -0
- package/src/materialization/bars/aggregate.js +184 -0
- package/src/materialization/bars/grouped.js +117 -0
- package/src/materialization/bars/histogram.js +163 -0
- package/src/materialization/bars/ranged.js +53 -0
- package/src/materialization/bars/resolve.js +56 -0
- package/src/materialization/dataProvenance.js +15 -0
- package/src/materialization/density.js +29 -0
- package/src/materialization/dependencies.js +134 -0
- package/src/materialization/encodings.js +50 -0
- package/src/materialization/graphicHierarchy.js +104 -0
- package/src/materialization/legends.js +20 -0
- package/src/materialization/marks.js +110 -0
- package/src/materialization/planner.js +38 -0
- package/src/materialization/selection/filter.js +40 -0
- package/src/materialization/selection/items.js +503 -0
- package/src/materialization/selection/path.js +37 -0
- package/src/materialization/selection/point.js +118 -0
- package/src/materialization/selection/policies/area.js +10 -0
- package/src/materialization/selection/policies/bar.js +10 -0
- package/src/materialization/selection/policies/index.js +40 -0
- package/src/materialization/selection/policies/line.js +10 -0
- package/src/materialization/selection/policies/point.js +10 -0
- package/src/materialization/selection/policies/rule.js +10 -0
- package/src/materialization/selection/state.js +67 -0
- package/src/materialization/selection/styles.js +183 -0
- package/src/renderers/canvas/circle.js +62 -0
- package/src/renderers/canvas/index.js +132 -0
- package/src/renderers/canvas/line.js +54 -0
- package/src/renderers/canvas/path.js +98 -0
- package/src/renderers/canvas/rect.js +57 -0
- package/src/renderers/canvas/text.js +96 -0
- package/src/renderers/canvas/validation.js +23 -0
- package/src/renderers/png.js +29 -0
- package/src/selectors/coordinates.js +13 -0
- package/src/selectors/datasets.js +13 -0
- package/src/selectors/index.js +4 -0
- package/src/selectors/layers.js +32 -0
- package/src/selectors/scales.js +21 -0
- package/src/theme/defaults.js +13 -0
- package/types/extension.d.ts +14 -0
- package/types/index.d.ts +81 -0
- package/types/png.d.ts +14 -0
- package/types/program.d.ts +1096 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
+
import {
|
|
4
|
+
sameOrderedValues,
|
|
5
|
+
validateNonEmptyString,
|
|
6
|
+
validateNonNegativeFinite,
|
|
7
|
+
validatePositiveFinite
|
|
8
|
+
} from "../../../core/validation.js";
|
|
9
|
+
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
10
|
+
import {
|
|
11
|
+
isTransformedScaleType,
|
|
12
|
+
mapContinuousScaleValues,
|
|
13
|
+
formatTransformedTick,
|
|
14
|
+
mapOrdinalPositionValues
|
|
15
|
+
} from "../../../grammar/scales.js";
|
|
16
|
+
import { formatTimeTick } from "../../../grammar/ticks.js";
|
|
17
|
+
import { valuesFromTickConfig } from "../tickValues.js";
|
|
18
|
+
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
19
|
+
"../../../theme/defaults.js";
|
|
20
|
+
import {
|
|
21
|
+
defaultAxisPosition,
|
|
22
|
+
formatAxisValue,
|
|
23
|
+
resolveAxisLabelGeometry,
|
|
24
|
+
validateAxisFormat,
|
|
25
|
+
validateAxisPosition
|
|
26
|
+
} from "./policy.js";
|
|
27
|
+
import { resolvePlotGraphicPlacement } from
|
|
28
|
+
"../../../materialization/graphicHierarchy.js";
|
|
29
|
+
|
|
30
|
+
const OPTIONS = Object.freeze([
|
|
31
|
+
"scale", "position", "count", "values", "offset", "format", "color",
|
|
32
|
+
"fontSize", "fontFamily", "fontWeight"
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const DEFAULTS = Object.freeze({
|
|
36
|
+
count: 5,
|
|
37
|
+
color: DEFAULT_COLORS.text,
|
|
38
|
+
fontSize: 12,
|
|
39
|
+
fontFamily: DEFAULT_FONT_FAMILY,
|
|
40
|
+
fontWeight: "normal"
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
function validateOptions(args, operation, create) {
|
|
44
|
+
for (const key of Object.keys(args)) {
|
|
45
|
+
if (!OPTIONS.includes(key) || (!create && key === "scale")) {
|
|
46
|
+
throw new Error(`Unknown ${operation} option "${key}".`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) {
|
|
50
|
+
throw new Error(`${operation} cannot use count and values together.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function validateConfig(channel, config) {
|
|
55
|
+
validateAxisPosition(channel, config.position);
|
|
56
|
+
if (config.mode === "count" && (!Number.isInteger(config.count) || config.count <= 0)) throw new RangeError("Label count must be a positive integer.");
|
|
57
|
+
if (
|
|
58
|
+
config.mode === "values" &&
|
|
59
|
+
(!Array.isArray(config.values) || !config.values.every(value =>
|
|
60
|
+
typeof value === "string" ||
|
|
61
|
+
typeof value === "boolean" ||
|
|
62
|
+
(typeof value === "number" && Number.isFinite(value))
|
|
63
|
+
))
|
|
64
|
+
) throw new TypeError("Label values must be nominal values or finite numbers.");
|
|
65
|
+
validateNonNegativeFinite(config.offset, "Label offset");
|
|
66
|
+
validatePositiveFinite(config.fontSize, "Label fontSize");
|
|
67
|
+
validateNonEmptyString(config.color, "Label color");
|
|
68
|
+
validateNonEmptyString(config.fontFamily, "Label fontFamily");
|
|
69
|
+
if ((typeof config.fontWeight !== "string" && !Number.isFinite(config.fontWeight))) throw new TypeError("Label fontWeight must be a string or number.");
|
|
70
|
+
validateAxisFormat(config.format);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function assertTickCompatibility(ticks, config, operation) {
|
|
74
|
+
if (!ticks) return;
|
|
75
|
+
if (ticks.scale !== config.scale || ticks.mode !== config.mode) throw new Error(`${operation} conflicts with axis ticks.`);
|
|
76
|
+
if (config.mode === "count" && ticks.count !== config.count) throw new Error(`${operation} conflicts with axis ticks.`);
|
|
77
|
+
if (config.mode === "values" && !sameOrderedValues(ticks.values, config.values)) throw new Error(`${operation} conflicts with axis ticks.`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolve(program, channel, config) {
|
|
81
|
+
const scale = program.resolvedScales[config.scale];
|
|
82
|
+
const bounds = resolveGraphicBounds(program);
|
|
83
|
+
const discrete = ["ordinal", "band", "point"].includes(scale?.type);
|
|
84
|
+
if ((
|
|
85
|
+
!["linear", "time", "ordinal", "band", "point"].includes(scale?.type) &&
|
|
86
|
+
!isTransformedScaleType(scale?.type)
|
|
87
|
+
) || !bounds) throw new Error("Axis labels require a supported resolved scale and Canvas bounds.");
|
|
88
|
+
if (discrete && config.mode !== "values") throw new Error("Discrete axis labels require explicit or inferred values, not count.");
|
|
89
|
+
const values = valuesFromTickConfig(program, config);
|
|
90
|
+
if (discrete) {
|
|
91
|
+
const domainValues = new Set(scale.domain);
|
|
92
|
+
if (!values.every(value => domainValues.has(value))) throw new RangeError("Label values must be inside the scale domain.");
|
|
93
|
+
} else {
|
|
94
|
+
const low = Math.min(...scale.domain), high = Math.max(...scale.domain);
|
|
95
|
+
if (!values.every(value => value >= low && value <= high)) throw new RangeError("Label values must be inside the scale domain.");
|
|
96
|
+
}
|
|
97
|
+
const positions = discrete
|
|
98
|
+
? mapOrdinalPositionValues(values, scale)
|
|
99
|
+
: mapContinuousScaleValues(values, scale);
|
|
100
|
+
const text = values.map(value => formatAxisValue(
|
|
101
|
+
value,
|
|
102
|
+
scale.type,
|
|
103
|
+
config.format,
|
|
104
|
+
item => scale.type === "time"
|
|
105
|
+
? formatTimeTick(item, scale.domain)
|
|
106
|
+
: isTransformedScaleType(scale.type)
|
|
107
|
+
? formatTransformedTick(scale.type, item)
|
|
108
|
+
: String(item)
|
|
109
|
+
));
|
|
110
|
+
const resolved = {
|
|
111
|
+
values,
|
|
112
|
+
text,
|
|
113
|
+
...resolveAxisLabelGeometry({
|
|
114
|
+
bounds,
|
|
115
|
+
channel,
|
|
116
|
+
position: config.position,
|
|
117
|
+
positions,
|
|
118
|
+
offset: config.offset
|
|
119
|
+
})
|
|
120
|
+
};
|
|
121
|
+
const canvas = program.graphicSpec.objects.canvas?.properties;
|
|
122
|
+
const maximumTextWidth = Math.max(
|
|
123
|
+
0,
|
|
124
|
+
...text.map(value => [...value].length * config.fontSize * 0.6)
|
|
125
|
+
);
|
|
126
|
+
const fits = config.position === "top"
|
|
127
|
+
? resolved.y - config.fontSize >= 0
|
|
128
|
+
: config.position === "right"
|
|
129
|
+
? resolved.x + maximumTextWidth <= canvas?.width
|
|
130
|
+
: true;
|
|
131
|
+
if (!canvas || !fits) {
|
|
132
|
+
throw new Error(`The ${channel}-axis labels do not fit the Canvas margin.`);
|
|
133
|
+
}
|
|
134
|
+
return resolved;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function makeEdit(channel) {
|
|
138
|
+
const op = channel === "x" ? "editXAxisLabels" : "editYAxisLabels";
|
|
139
|
+
return action({ op, description: `Edit concrete ${channel}-axis labels.` }, function (args = {}) {
|
|
140
|
+
validateOptions(args, op, false);
|
|
141
|
+
const id = `${channel}AxisLabels`;
|
|
142
|
+
if (this.graphicSpec.objects[id]?.type !== "text") throw new Error(`${op} requires existing axis labels.`);
|
|
143
|
+
const previous = this.guideConfigs.axis?.[channel]?.labels;
|
|
144
|
+
if (!previous) throw new Error(`${op} requires label configuration.`);
|
|
145
|
+
const explicitMode = Object.hasOwn(args, "values") || Object.hasOwn(args, "count");
|
|
146
|
+
const ticks = this.guideConfigs.axis?.[channel]?.ticks;
|
|
147
|
+
const inferredValues = !explicitMode && previous.inferredValues === true &&
|
|
148
|
+
ticks?.mode === "values"
|
|
149
|
+
? ticks.values
|
|
150
|
+
: undefined;
|
|
151
|
+
const mode = Object.hasOwn(args, "values") || inferredValues !== undefined
|
|
152
|
+
? "values"
|
|
153
|
+
: Object.hasOwn(args, "count") ? "count" : previous.mode;
|
|
154
|
+
const config = {
|
|
155
|
+
...previous,
|
|
156
|
+
...args,
|
|
157
|
+
...(inferredValues === undefined ? {} : { values: inferredValues }),
|
|
158
|
+
...(explicitMode ? { inferredValues: false } : {}),
|
|
159
|
+
mode
|
|
160
|
+
};
|
|
161
|
+
if (mode === "values") delete config.count; else delete config.values;
|
|
162
|
+
validateConfig(channel, config);
|
|
163
|
+
assertTickCompatibility(this.guideConfigs.axis?.[channel]?.ticks, config, op);
|
|
164
|
+
const resolved = resolve(this, channel, config);
|
|
165
|
+
let next = this._withGuideConfig(channel, "labels", config)
|
|
166
|
+
.editGraphics({ target: id, property: "length", value: resolved.values.length });
|
|
167
|
+
for (const property of ["x", "y", "text"]) next = next.editGraphics({ target: id, property, value: resolved[property] });
|
|
168
|
+
return next
|
|
169
|
+
.editGraphics({ target: id, property: "fill", value: config.color })
|
|
170
|
+
.editGraphics({ target: id, property: "fontSize", value: config.fontSize })
|
|
171
|
+
.editGraphics({ target: id, property: "fontFamily", value: config.fontFamily })
|
|
172
|
+
.editGraphics({ target: id, property: "fontWeight", value: config.fontWeight })
|
|
173
|
+
.editGraphics({ target: id, property: "textAlign", value: resolved.textAlign })
|
|
174
|
+
.editGraphics({ target: id, property: "textBaseline", value: resolved.textBaseline });
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const editXAxisLabels = makeEdit("x");
|
|
179
|
+
const editYAxisLabels = makeEdit("y");
|
|
180
|
+
|
|
181
|
+
function makeCreate(channel) {
|
|
182
|
+
const op = channel === "x" ? "createXAxisLabels" : "createYAxisLabels";
|
|
183
|
+
const edit = channel === "x" ? "editXAxisLabels" : "editYAxisLabels";
|
|
184
|
+
return action({ op, description: `Create concrete ${channel}-axis labels.` }, function (args = {}) {
|
|
185
|
+
validateOptions(args, op, true);
|
|
186
|
+
const scale = validateUserId(args.scale ?? channel, "Scale id");
|
|
187
|
+
const guideScale = this.semanticSpec.guides.axis?.[channel]?.scale;
|
|
188
|
+
if (guideScale && guideScale !== scale) throw new Error(`${op} conflicts with the existing axis scale.`);
|
|
189
|
+
const id = `${channel}AxisLabels`;
|
|
190
|
+
if (this.graphicSpec.objects[id]) throw new Error(`${op} requires missing axis labels.`);
|
|
191
|
+
const ticks = this.guideConfigs.axis?.[channel]?.ticks;
|
|
192
|
+
const hasValues = Object.hasOwn(args, "values");
|
|
193
|
+
const hasCount = Object.hasOwn(args, "count");
|
|
194
|
+
const mode = hasValues ? "values" : hasCount ? "count" : ticks?.mode ?? "count";
|
|
195
|
+
const config = {
|
|
196
|
+
scale,
|
|
197
|
+
position: defaultAxisPosition(channel),
|
|
198
|
+
offset: channel === "x" ? 18 : 12,
|
|
199
|
+
format: "auto",
|
|
200
|
+
color: DEFAULTS.color,
|
|
201
|
+
fontSize: DEFAULTS.fontSize,
|
|
202
|
+
fontFamily: DEFAULTS.fontFamily,
|
|
203
|
+
fontWeight: DEFAULTS.fontWeight,
|
|
204
|
+
...args,
|
|
205
|
+
inferredValues: !hasValues && !hasCount && ticks?.inferredValues === true,
|
|
206
|
+
mode
|
|
207
|
+
};
|
|
208
|
+
if (mode === "values") config.values ??= ticks?.values; else config.count ??= ticks?.count ?? DEFAULTS.count;
|
|
209
|
+
validateConfig(channel, config);
|
|
210
|
+
assertTickCompatibility(ticks, config, op);
|
|
211
|
+
resolve(this, channel, config);
|
|
212
|
+
return this.editSemantic({ property: `guide.axis.${channel}.scale`, value: scale })
|
|
213
|
+
.createGraphics({
|
|
214
|
+
id,
|
|
215
|
+
type: "text",
|
|
216
|
+
length: 0,
|
|
217
|
+
...resolvePlotGraphicPlacement(this)
|
|
218
|
+
})
|
|
219
|
+
._withGuideConfig(channel, "labels", config)[edit]();
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const createXAxisLabels = makeCreate("x");
|
|
224
|
+
const createYAxisLabels = makeCreate("y");
|
|
225
|
+
|
|
226
|
+
export function registerAxisLabelActions(Class) {
|
|
227
|
+
Class.prototype.editXAxisLabels = editXAxisLabels;
|
|
228
|
+
Class.prototype.editYAxisLabels = editYAxisLabels;
|
|
229
|
+
Class.prototype.createXAxisLabels = createXAxisLabels;
|
|
230
|
+
Class.prototype.createYAxisLabels = createYAxisLabels;
|
|
231
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
+
import {
|
|
4
|
+
validateKeys,
|
|
5
|
+
validateNonEmptyString,
|
|
6
|
+
validateNonNegativeFinite
|
|
7
|
+
} from "../../../core/validation.js";
|
|
8
|
+
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
9
|
+
import { DEFAULT_COLORS } from "../../../theme/defaults.js";
|
|
10
|
+
import {
|
|
11
|
+
defaultAxisPosition,
|
|
12
|
+
resolveAxisLineGeometry,
|
|
13
|
+
validateAxisPosition
|
|
14
|
+
} from "./policy.js";
|
|
15
|
+
import { resolvePlotGraphicPlacement } from
|
|
16
|
+
"../../../materialization/graphicHierarchy.js";
|
|
17
|
+
|
|
18
|
+
const DEFAULT_STYLE = Object.freeze({ color: DEFAULT_COLORS.text, lineWidth: 1 });
|
|
19
|
+
const CREATE_OPTIONS = Object.freeze(["scale", "position", "color", "lineWidth"]);
|
|
20
|
+
const EDIT_OPTIONS = Object.freeze(["position", "color", "lineWidth"]);
|
|
21
|
+
|
|
22
|
+
function validateStyle({ color, lineWidth }) {
|
|
23
|
+
validateNonEmptyString(color, "Axis line color");
|
|
24
|
+
validateNonNegativeFinite(lineWidth, "Axis lineWidth");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveGeometry(program, channel, scaleId, position) {
|
|
28
|
+
const hasConsumer = program.semanticSpec.layers.some(
|
|
29
|
+
layer => layer.encoding?.[channel]?.scale === scaleId
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (!hasConsumer) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Axis line requires scale "${scaleId}" on the ${channel} channel.`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const range = program.resolvedScales[scaleId]?.range;
|
|
39
|
+
const bounds = resolveGraphicBounds(program);
|
|
40
|
+
|
|
41
|
+
if (!Array.isArray(range) || range.length !== 2 || !range.every(Number.isFinite)) {
|
|
42
|
+
throw new Error(`Axis line requires a resolved numeric scale "${scaleId}".`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (
|
|
46
|
+
bounds === undefined ||
|
|
47
|
+
![bounds.x, bounds.y, bounds.width, bounds.height].every(Number.isFinite)
|
|
48
|
+
) {
|
|
49
|
+
throw new Error("Axis line requires graphical Canvas bounds.");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return resolveAxisLineGeometry(bounds, channel, position, range);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function axisIds(channel) {
|
|
56
|
+
return {
|
|
57
|
+
graphic: `${channel}AxisLine`,
|
|
58
|
+
guidePath: `guide.axis.${channel}.scale`
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createEditAxisLine(channel) {
|
|
63
|
+
const operation = channel === "x" ? "editXAxisLine" : "editYAxisLine";
|
|
64
|
+
|
|
65
|
+
return action(
|
|
66
|
+
{ op: operation, description: `Edit the concrete ${channel}-axis line.` },
|
|
67
|
+
function (args = {}) {
|
|
68
|
+
validateKeys(args, EDIT_OPTIONS, operation);
|
|
69
|
+
const { graphic } = axisIds(channel);
|
|
70
|
+
const line = this.graphicSpec.objects[graphic];
|
|
71
|
+
|
|
72
|
+
if (line?.type !== "line") {
|
|
73
|
+
throw new Error(`${operation} requires an existing ${channel}-axis line.`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const previous = this.guideConfigs.axis?.[channel]?.line;
|
|
77
|
+
const position = validateAxisPosition(
|
|
78
|
+
channel,
|
|
79
|
+
args.position ?? previous?.position ?? defaultAxisPosition(channel)
|
|
80
|
+
);
|
|
81
|
+
const scaleId = this.semanticSpec.guides.axis?.[channel]?.scale;
|
|
82
|
+
const geometry = resolveGeometry(this, channel, scaleId, position);
|
|
83
|
+
const color = args.color ?? line.properties.stroke;
|
|
84
|
+
const lineWidth = args.lineWidth ?? line.properties.strokeWidth;
|
|
85
|
+
validateStyle({ color, lineWidth });
|
|
86
|
+
let next = this._withGuideConfig(channel, "line", {
|
|
87
|
+
position,
|
|
88
|
+
color,
|
|
89
|
+
lineWidth
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
for (const property of ["x1", "y1", "x2", "y2"]) {
|
|
93
|
+
next = next.editGraphics({ target: graphic, property, value: geometry[property] });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return next
|
|
97
|
+
.editGraphics({ target: graphic, property: "stroke", value: color })
|
|
98
|
+
.editGraphics({ target: graphic, property: "strokeWidth", value: lineWidth });
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const editXAxisLine = createEditAxisLine("x");
|
|
104
|
+
const editYAxisLine = createEditAxisLine("y");
|
|
105
|
+
|
|
106
|
+
function createAxisLine(channel) {
|
|
107
|
+
const operation = channel === "x" ? "createXAxisLine" : "createYAxisLine";
|
|
108
|
+
const editOperation = channel === "x" ? "editXAxisLine" : "editYAxisLine";
|
|
109
|
+
|
|
110
|
+
return action(
|
|
111
|
+
{ op: operation, description: `Create the concrete ${channel}-axis line.` },
|
|
112
|
+
function (args = {}) {
|
|
113
|
+
validateKeys(args, CREATE_OPTIONS, operation);
|
|
114
|
+
const scale = validateUserId(args.scale ?? channel, "Scale id");
|
|
115
|
+
const position = validateAxisPosition(
|
|
116
|
+
channel,
|
|
117
|
+
args.position ?? defaultAxisPosition(channel)
|
|
118
|
+
);
|
|
119
|
+
const color = args.color ?? DEFAULT_STYLE.color;
|
|
120
|
+
const lineWidth = args.lineWidth ?? DEFAULT_STYLE.lineWidth;
|
|
121
|
+
validateStyle({ color, lineWidth });
|
|
122
|
+
resolveGeometry(this, channel, scale, position);
|
|
123
|
+
const { graphic, guidePath } = axisIds(channel);
|
|
124
|
+
|
|
125
|
+
if (this.graphicSpec.objects[graphic] !== undefined) {
|
|
126
|
+
throw new Error(`${operation} requires a missing ${channel}-axis line.`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return this
|
|
130
|
+
.editSemantic({ property: guidePath, value: scale })
|
|
131
|
+
.createGraphics({
|
|
132
|
+
id: graphic,
|
|
133
|
+
type: "line",
|
|
134
|
+
...resolvePlotGraphicPlacement(this)
|
|
135
|
+
})
|
|
136
|
+
[editOperation]({ position, color, lineWidth });
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const createXAxisLine = createAxisLine("x");
|
|
142
|
+
const createYAxisLine = createAxisLine("y");
|
|
143
|
+
|
|
144
|
+
export function registerAxisLineActions(ProgramClass) {
|
|
145
|
+
ProgramClass.prototype.editXAxisLine = editXAxisLine;
|
|
146
|
+
ProgramClass.prototype.editYAxisLine = editYAxisLine;
|
|
147
|
+
ProgramClass.prototype.createXAxisLine = createXAxisLine;
|
|
148
|
+
ProgramClass.prototype.createYAxisLine = createYAxisLine;
|
|
149
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const AXIS_POSITIONS = Object.freeze({
|
|
2
|
+
x: Object.freeze(["bottom", "top"]),
|
|
3
|
+
y: Object.freeze(["left", "right"])
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
const NUMERIC_FORMATS = new Set([
|
|
7
|
+
".0f", ".1f", ".2f", ".0%", ".1%", ".2e"
|
|
8
|
+
]);
|
|
9
|
+
const TIME_FORMATS = new Set(["%Y", "%Y-%m", "%Y-%m-%d"]);
|
|
10
|
+
|
|
11
|
+
export function defaultAxisPosition(channel) {
|
|
12
|
+
return channel === "x" ? "bottom" : "left";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function validateAxisPosition(channel, position) {
|
|
16
|
+
if (!AXIS_POSITIONS[channel]?.includes(position)) {
|
|
17
|
+
throw new Error(`Unsupported ${channel}-axis position "${position}".`);
|
|
18
|
+
}
|
|
19
|
+
return position;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function defaultAxisTitleRotation(channel, position) {
|
|
23
|
+
if (channel === "x") return 0;
|
|
24
|
+
return position === "right" ? Math.PI / 2 : -Math.PI / 2;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function axisBaseline(bounds, channel, position) {
|
|
28
|
+
if (channel === "x") {
|
|
29
|
+
return position === "top" ? bounds.y : bounds.y + bounds.height;
|
|
30
|
+
}
|
|
31
|
+
return position === "right" ? bounds.x + bounds.width : bounds.x;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveAxisLineGeometry(bounds, channel, position, range) {
|
|
35
|
+
const baseline = axisBaseline(bounds, channel, position);
|
|
36
|
+
return channel === "x"
|
|
37
|
+
? { x1: range[0], y1: baseline, x2: range[1], y2: baseline }
|
|
38
|
+
: { x1: baseline, y1: range[0], x2: baseline, y2: range[1] };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function resolveAxisTickGeometry({
|
|
42
|
+
bounds,
|
|
43
|
+
channel,
|
|
44
|
+
position,
|
|
45
|
+
positions,
|
|
46
|
+
length
|
|
47
|
+
}) {
|
|
48
|
+
const baseline = axisBaseline(bounds, channel, position);
|
|
49
|
+
if (channel === "x") {
|
|
50
|
+
const end = position === "top" ? baseline - length : baseline + length;
|
|
51
|
+
return { x1: positions, y1: baseline, x2: positions, y2: end };
|
|
52
|
+
}
|
|
53
|
+
const end = position === "right" ? baseline + length : baseline - length;
|
|
54
|
+
return position === "right"
|
|
55
|
+
? { x1: baseline, y1: positions, x2: end, y2: positions }
|
|
56
|
+
: { x1: end, y1: positions, x2: baseline, y2: positions };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function resolveAxisLabelGeometry({
|
|
60
|
+
bounds,
|
|
61
|
+
channel,
|
|
62
|
+
position,
|
|
63
|
+
positions,
|
|
64
|
+
offset
|
|
65
|
+
}) {
|
|
66
|
+
const baseline = axisBaseline(bounds, channel, position);
|
|
67
|
+
if (channel === "x") {
|
|
68
|
+
return {
|
|
69
|
+
x: positions,
|
|
70
|
+
y: position === "top" ? baseline - offset : baseline + offset,
|
|
71
|
+
textAlign: "center",
|
|
72
|
+
textBaseline: position === "top" ? "bottom" : "top"
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
x: position === "right" ? baseline + offset : baseline - offset,
|
|
77
|
+
y: positions,
|
|
78
|
+
textAlign: position === "right" ? "left" : "right",
|
|
79
|
+
textBaseline: "middle"
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function resolveAxisTitleGeometry({
|
|
84
|
+
bounds,
|
|
85
|
+
channel,
|
|
86
|
+
position,
|
|
87
|
+
along,
|
|
88
|
+
offset
|
|
89
|
+
}) {
|
|
90
|
+
const baseline = axisBaseline(bounds, channel, position);
|
|
91
|
+
return channel === "x"
|
|
92
|
+
? { x: along, y: position === "top" ? baseline - offset : baseline + offset }
|
|
93
|
+
: { x: position === "right" ? baseline + offset : baseline - offset, y: along };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function validateAxisFormat(format) {
|
|
97
|
+
if (format === "auto") return format;
|
|
98
|
+
if (
|
|
99
|
+
format !== null &&
|
|
100
|
+
typeof format === "object" &&
|
|
101
|
+
Number.isInteger(format.decimals) &&
|
|
102
|
+
format.decimals >= 0 &&
|
|
103
|
+
Object.keys(format).length === 1
|
|
104
|
+
) {
|
|
105
|
+
return format;
|
|
106
|
+
}
|
|
107
|
+
if (typeof format === "string" && (
|
|
108
|
+
NUMERIC_FORMATS.has(format) || TIME_FORMATS.has(format)
|
|
109
|
+
)) {
|
|
110
|
+
return format;
|
|
111
|
+
}
|
|
112
|
+
throw new TypeError(
|
|
113
|
+
"Label format must be auto, { decimals }, or a supported format string."
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function pad(value, length = 2) {
|
|
118
|
+
return String(value).padStart(length, "0");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function formatTime(value, format) {
|
|
122
|
+
const date = new Date(value);
|
|
123
|
+
if (!Number.isFinite(date.getTime())) {
|
|
124
|
+
throw new TypeError("Time axis format requires valid timestamps.");
|
|
125
|
+
}
|
|
126
|
+
const year = pad(date.getUTCFullYear(), 4);
|
|
127
|
+
const month = pad(date.getUTCMonth() + 1);
|
|
128
|
+
const day = pad(date.getUTCDate());
|
|
129
|
+
if (format === "%Y") return year;
|
|
130
|
+
if (format === "%Y-%m") return `${year}-${month}`;
|
|
131
|
+
return `${year}-${month}-${day}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function formatAxisValue(value, scaleType, format, autoFormatter) {
|
|
135
|
+
validateAxisFormat(format);
|
|
136
|
+
if (format === "auto") return autoFormatter(value);
|
|
137
|
+
if (["ordinal", "band", "point"].includes(scaleType)) {
|
|
138
|
+
throw new Error('Discrete axis labels require format "auto".');
|
|
139
|
+
}
|
|
140
|
+
if (scaleType === "time") {
|
|
141
|
+
if (!TIME_FORMATS.has(format)) {
|
|
142
|
+
throw new Error("Time axis labels require a supported time format string.");
|
|
143
|
+
}
|
|
144
|
+
return formatTime(value, format);
|
|
145
|
+
}
|
|
146
|
+
if (TIME_FORMATS.has(format)) {
|
|
147
|
+
throw new Error("Quantitative axis labels cannot use a time format string.");
|
|
148
|
+
}
|
|
149
|
+
if (typeof format === "object") return value.toFixed(format.decimals);
|
|
150
|
+
if (format.endsWith("f")) return value.toFixed(Number(format[1]));
|
|
151
|
+
if (format.endsWith("%")) {
|
|
152
|
+
return `${(value * 100).toFixed(Number(format[1]))}%`;
|
|
153
|
+
}
|
|
154
|
+
return value.toExponential(2);
|
|
155
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { isPlainObject } from "../../../core/immutable.js";
|
|
3
|
+
import { validateKeys } from "../../../core/validation.js";
|
|
4
|
+
|
|
5
|
+
const CREATE_OPTIONS = Object.freeze([
|
|
6
|
+
"scale",
|
|
7
|
+
"position",
|
|
8
|
+
"count",
|
|
9
|
+
"values",
|
|
10
|
+
"ticks",
|
|
11
|
+
"labels"
|
|
12
|
+
]);
|
|
13
|
+
const EDIT_OPTIONS = Object.freeze([
|
|
14
|
+
"position",
|
|
15
|
+
"count",
|
|
16
|
+
"values",
|
|
17
|
+
"ticks",
|
|
18
|
+
"labels"
|
|
19
|
+
]);
|
|
20
|
+
const TICK_OPTIONS = Object.freeze(["length", "color", "lineWidth"]);
|
|
21
|
+
const LABEL_OPTIONS = Object.freeze([
|
|
22
|
+
"offset",
|
|
23
|
+
"format",
|
|
24
|
+
"color",
|
|
25
|
+
"fontSize",
|
|
26
|
+
"fontFamily",
|
|
27
|
+
"fontWeight"
|
|
28
|
+
]);
|
|
29
|
+
const SHARED_CREATE = Object.freeze(["scale", "position", "count", "values"]);
|
|
30
|
+
const SHARED_EDIT = Object.freeze(["position", "count", "values"]);
|
|
31
|
+
|
|
32
|
+
function validateNested(value, supported, label) {
|
|
33
|
+
if (!isPlainObject(value)) {
|
|
34
|
+
throw new TypeError(`${label} must be a plain object.`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
validateKeys(value, supported, label);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function validateArgs(args, operation, create) {
|
|
41
|
+
validateKeys(args, create ? CREATE_OPTIONS : EDIT_OPTIONS, operation);
|
|
42
|
+
|
|
43
|
+
if (Object.hasOwn(args, "count") && Object.hasOwn(args, "values")) {
|
|
44
|
+
throw new Error(`${operation} cannot use count and values together.`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (Object.hasOwn(args, "ticks")) {
|
|
48
|
+
validateNested(args.ticks, TICK_OPTIONS, `${operation}.ticks`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (Object.hasOwn(args, "labels")) {
|
|
52
|
+
validateNested(args.labels, LABEL_OPTIONS, `${operation}.labels`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!create && Object.keys(args).length === 0) {
|
|
56
|
+
throw new TypeError(`${operation} requires at least one option.`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function select(source, keys) {
|
|
61
|
+
const result = {};
|
|
62
|
+
|
|
63
|
+
for (const key of keys) {
|
|
64
|
+
if (Object.hasOwn(source, key)) result[key] = source[key];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function names(channel) {
|
|
71
|
+
const prefix = channel === "x" ? "X" : "Y";
|
|
72
|
+
return {
|
|
73
|
+
create: `create${prefix}AxisTicksAndLabels`,
|
|
74
|
+
edit: `edit${prefix}AxisTicksAndLabels`,
|
|
75
|
+
createTicks: `create${prefix}AxisTicks`,
|
|
76
|
+
createLabels: `create${prefix}AxisLabels`,
|
|
77
|
+
editTicks: `edit${prefix}AxisTicks`,
|
|
78
|
+
editLabels: `edit${prefix}AxisLabels`
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function makeCreate(channel) {
|
|
83
|
+
const operation = names(channel);
|
|
84
|
+
|
|
85
|
+
return action(
|
|
86
|
+
{
|
|
87
|
+
op: operation.create,
|
|
88
|
+
description: `Create ${channel}-axis ticks and labels.`
|
|
89
|
+
},
|
|
90
|
+
function (args = {}) {
|
|
91
|
+
validateArgs(args, operation.create, true);
|
|
92
|
+
const shared = select(args, SHARED_CREATE);
|
|
93
|
+
const tickArgs = { ...shared, ...(args.ticks ?? {}) };
|
|
94
|
+
const labelArgs = {
|
|
95
|
+
...select(shared, ["scale", "position"]),
|
|
96
|
+
...(args.labels ?? {})
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return this[operation.createTicks](tickArgs)[operation.createLabels](
|
|
100
|
+
labelArgs
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function makeEdit(channel) {
|
|
107
|
+
const operation = names(channel);
|
|
108
|
+
|
|
109
|
+
return action(
|
|
110
|
+
{
|
|
111
|
+
op: operation.edit,
|
|
112
|
+
description: `Edit ${channel}-axis ticks and labels.`
|
|
113
|
+
},
|
|
114
|
+
function (args = {}) {
|
|
115
|
+
validateArgs(args, operation.edit, false);
|
|
116
|
+
const shared = select(args, SHARED_EDIT);
|
|
117
|
+
const hasShared = Object.keys(shared).length > 0;
|
|
118
|
+
let next = this;
|
|
119
|
+
|
|
120
|
+
if (hasShared || Object.hasOwn(args, "ticks")) {
|
|
121
|
+
next = next[operation.editTicks]({
|
|
122
|
+
...shared,
|
|
123
|
+
...(args.ticks ?? {})
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (hasShared || Object.hasOwn(args, "labels")) {
|
|
128
|
+
next = next[operation.editLabels]({
|
|
129
|
+
...shared,
|
|
130
|
+
...(args.labels ?? {})
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return next;
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const createXAxisTicksAndLabels = makeCreate("x");
|
|
140
|
+
const createYAxisTicksAndLabels = makeCreate("y");
|
|
141
|
+
const editXAxisTicksAndLabels = makeEdit("x");
|
|
142
|
+
const editYAxisTicksAndLabels = makeEdit("y");
|
|
143
|
+
|
|
144
|
+
export function registerAxisTickGroupActions(Class) {
|
|
145
|
+
Class.prototype.createXAxisTicksAndLabels = createXAxisTicksAndLabels;
|
|
146
|
+
Class.prototype.createYAxisTicksAndLabels = createYAxisTicksAndLabels;
|
|
147
|
+
Class.prototype.editXAxisTicksAndLabels = editXAxisTicksAndLabels;
|
|
148
|
+
Class.prototype.editYAxisTicksAndLabels = editYAxisTicksAndLabels;
|
|
149
|
+
}
|