mintwaterfall 0.8.6
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 +223 -0
- package/CONTRIBUTING.md +199 -0
- package/README.md +363 -0
- package/dist/index.d.ts +149 -0
- package/dist/mintwaterfall.cjs.js +7978 -0
- package/dist/mintwaterfall.esm.js +7907 -0
- package/dist/mintwaterfall.min.js +7 -0
- package/dist/mintwaterfall.umd.js +7978 -0
- package/index.d.ts +149 -0
- package/package.json +126 -0
- package/src/enterprise/enterprise-core.js +0 -0
- package/src/enterprise/enterprise-feature-template.js +0 -0
- package/src/enterprise/feature-registry.js +0 -0
- package/src/enterprise/features/breakdown.js +0 -0
- package/src/features/breakdown.js +0 -0
- package/src/features/conditional-formatting.js +0 -0
- package/src/index.js +111 -0
- package/src/mintwaterfall-accessibility.ts +680 -0
- package/src/mintwaterfall-advanced-data.ts +1034 -0
- package/src/mintwaterfall-advanced-interactions.ts +649 -0
- package/src/mintwaterfall-advanced-performance.ts +582 -0
- package/src/mintwaterfall-animations.ts +595 -0
- package/src/mintwaterfall-brush.ts +471 -0
- package/src/mintwaterfall-chart-core.ts +296 -0
- package/src/mintwaterfall-chart.ts +1915 -0
- package/src/mintwaterfall-data.ts +1100 -0
- package/src/mintwaterfall-export.ts +475 -0
- package/src/mintwaterfall-hierarchical-layouts.ts +724 -0
- package/src/mintwaterfall-layouts.ts +647 -0
- package/src/mintwaterfall-performance.ts +573 -0
- package/src/mintwaterfall-scales.ts +437 -0
- package/src/mintwaterfall-shapes.ts +385 -0
- package/src/mintwaterfall-statistics.ts +821 -0
- package/src/mintwaterfall-themes.ts +391 -0
- package/src/mintwaterfall-tooltip.ts +450 -0
- package/src/mintwaterfall-zoom.ts +399 -0
- package/src/types/js-modules.d.ts +25 -0
- package/src/utils/compatibility-layer.js +0 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
// MintWaterfall Enhanced Scales System - TypeScript Version
|
|
2
|
+
// Provides advanced D3.js scale support including time and ordinal scales with full type safety
|
|
3
|
+
|
|
4
|
+
import * as d3 from 'd3';
|
|
5
|
+
|
|
6
|
+
// Type definitions for scale systems
|
|
7
|
+
export interface ScaleSystemOptions {
|
|
8
|
+
range?: [number, number];
|
|
9
|
+
nice?: boolean;
|
|
10
|
+
padding?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface TimeScaleOptions {
|
|
14
|
+
range?: [number, number];
|
|
15
|
+
nice?: boolean;
|
|
16
|
+
tickFormat?: string | 'auto';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface OrdinalScaleOptions {
|
|
20
|
+
range?: string[];
|
|
21
|
+
unknown?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface BandScaleOptions {
|
|
25
|
+
padding?: number;
|
|
26
|
+
paddingInner?: number | null;
|
|
27
|
+
paddingOuter?: number | null;
|
|
28
|
+
align?: number;
|
|
29
|
+
range?: [number, number];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface LinearScaleOptions {
|
|
33
|
+
range?: [number, number];
|
|
34
|
+
nice?: boolean;
|
|
35
|
+
zero?: boolean;
|
|
36
|
+
clamp?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type ScaleType = 'linear' | 'band' | 'time' | 'ordinal' | 'adaptive';
|
|
40
|
+
export type DimensionType = 'x' | 'y';
|
|
41
|
+
|
|
42
|
+
// Scale factory interface
|
|
43
|
+
export interface ScaleFactory {
|
|
44
|
+
createAdaptiveScale(data: any[], dimension?: DimensionType): d3.ScaleLinear<number, number> | d3.ScaleBand<string> | d3.ScaleTime<number, number>;
|
|
45
|
+
createTimeScale(values: Date[], options?: TimeScaleOptions): d3.ScaleTime<number, number>;
|
|
46
|
+
createOrdinalScale(values: any[], options?: OrdinalScaleOptions): d3.ScaleOrdinal<any, string, string>;
|
|
47
|
+
createBandScale(values: any[], options?: BandScaleOptions): d3.ScaleBand<string>;
|
|
48
|
+
createLinearScale(values: number[], options?: LinearScaleOptions): d3.ScaleLinear<number, number>;
|
|
49
|
+
createLogScale(values: number[], options?: LinearScaleOptions): d3.ScaleLogarithmic<number, number> | d3.ScaleLinear<number, number>;
|
|
50
|
+
setDefaultRange(range: [number, number]): void;
|
|
51
|
+
getScaleInfo(scale: any): ScaleInfo;
|
|
52
|
+
scaleUtils: ScaleUtilities;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ScaleInfo {
|
|
56
|
+
type: string;
|
|
57
|
+
domain: any[];
|
|
58
|
+
range: any[];
|
|
59
|
+
bandwidth?: number;
|
|
60
|
+
step?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createScaleSystem(): ScaleFactory {
|
|
64
|
+
let defaultRange: [number, number] = [0, 800];
|
|
65
|
+
|
|
66
|
+
// Enhanced scale factory with auto-detection
|
|
67
|
+
function createAdaptiveScale(data: any[], dimension: DimensionType = "x"): d3.ScaleLinear<number, number> | d3.ScaleBand<string> | d3.ScaleTime<number, number> {
|
|
68
|
+
const values = data.map(d => dimension === "x" ? d.label : d.cumulativeTotal);
|
|
69
|
+
|
|
70
|
+
// Detect data type and return appropriate scale
|
|
71
|
+
if (values.every(v => v instanceof Date)) {
|
|
72
|
+
return createTimeScale(values);
|
|
73
|
+
} else if (values.every(v => typeof v === "string" || isNaN(v))) {
|
|
74
|
+
// For categorical/string data, use band scale for positioning
|
|
75
|
+
return createBandScale(values);
|
|
76
|
+
} else if (values.every(v => typeof v === "number")) {
|
|
77
|
+
return createLinearScale(values);
|
|
78
|
+
} else {
|
|
79
|
+
// Mixed types - fallback to band scale
|
|
80
|
+
return d3.scaleBand<string>().domain(values.map(String)).range(defaultRange);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Time scale with intelligent formatting
|
|
85
|
+
function createTimeScale(values: Date[], options: TimeScaleOptions = {}): d3.ScaleTime<number, number> {
|
|
86
|
+
const {
|
|
87
|
+
range = defaultRange,
|
|
88
|
+
nice = true,
|
|
89
|
+
tickFormat = "auto"
|
|
90
|
+
} = options;
|
|
91
|
+
|
|
92
|
+
const extent = d3.extent(values) as [Date, Date];
|
|
93
|
+
const scale = d3.scaleTime()
|
|
94
|
+
.domain(extent)
|
|
95
|
+
.range(range);
|
|
96
|
+
|
|
97
|
+
if (nice) {
|
|
98
|
+
scale.nice();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Auto-detect appropriate time format
|
|
102
|
+
if (tickFormat === "auto" && extent[0] && extent[1] && extent[0] instanceof Date && extent[1] instanceof Date) {
|
|
103
|
+
const timeSpan = extent[1].getTime() - extent[0].getTime();
|
|
104
|
+
const days = timeSpan / (1000 * 60 * 60 * 24);
|
|
105
|
+
|
|
106
|
+
if (days < 1) {
|
|
107
|
+
(scale as any).tickFormat = d3.timeFormat("%H:%M");
|
|
108
|
+
} else if (days < 30) {
|
|
109
|
+
(scale as any).tickFormat = d3.timeFormat("%m/%d");
|
|
110
|
+
} else if (days < 365) {
|
|
111
|
+
(scale as any).tickFormat = d3.timeFormat("%b %Y");
|
|
112
|
+
} else {
|
|
113
|
+
(scale as any).tickFormat = d3.timeFormat("%Y");
|
|
114
|
+
}
|
|
115
|
+
} else if (typeof tickFormat === "string") {
|
|
116
|
+
(scale as any).tickFormat = d3.timeFormat(tickFormat);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return scale;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Enhanced ordinal scale with color mapping
|
|
123
|
+
function createOrdinalScale(values: any[], options: OrdinalScaleOptions = {}): d3.ScaleOrdinal<any, string, string> {
|
|
124
|
+
const {
|
|
125
|
+
range = d3.schemeCategory10,
|
|
126
|
+
unknown = "#ccc"
|
|
127
|
+
} = options;
|
|
128
|
+
|
|
129
|
+
const uniqueValues = [...new Set(values)];
|
|
130
|
+
|
|
131
|
+
return d3.scaleOrdinal<any, string>()
|
|
132
|
+
.domain(uniqueValues)
|
|
133
|
+
.range(range)
|
|
134
|
+
.unknown(unknown as string) as d3.ScaleOrdinal<any, string, string>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Band scale for categorical data positioning
|
|
138
|
+
function createBandScale(values: any[], options: BandScaleOptions = {}): d3.ScaleBand<string> {
|
|
139
|
+
const {
|
|
140
|
+
padding = 0.1,
|
|
141
|
+
paddingInner = null,
|
|
142
|
+
paddingOuter = null,
|
|
143
|
+
align = 0.5,
|
|
144
|
+
range = defaultRange
|
|
145
|
+
} = options;
|
|
146
|
+
|
|
147
|
+
const uniqueValues = [...new Set(values.map(String))];
|
|
148
|
+
const scale = d3.scaleBand<string>()
|
|
149
|
+
.domain(uniqueValues)
|
|
150
|
+
.range(range)
|
|
151
|
+
.align(align);
|
|
152
|
+
|
|
153
|
+
if (paddingInner !== null) {
|
|
154
|
+
scale.paddingInner(paddingInner);
|
|
155
|
+
}
|
|
156
|
+
if (paddingOuter !== null) {
|
|
157
|
+
scale.paddingOuter(paddingOuter);
|
|
158
|
+
}
|
|
159
|
+
if (paddingInner === null && paddingOuter === null) {
|
|
160
|
+
scale.padding(padding);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return scale;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Linear scale with enhanced options
|
|
167
|
+
function createLinearScale(values: number[], options: LinearScaleOptions = {}): d3.ScaleLinear<number, number> {
|
|
168
|
+
const {
|
|
169
|
+
range = defaultRange,
|
|
170
|
+
nice = true,
|
|
171
|
+
zero = false,
|
|
172
|
+
clamp = false
|
|
173
|
+
} = options;
|
|
174
|
+
|
|
175
|
+
let domain = d3.extent(values) as [number, number];
|
|
176
|
+
|
|
177
|
+
// Include zero in domain if requested
|
|
178
|
+
if (zero) {
|
|
179
|
+
domain = [Math.min(0, domain[0]), Math.max(0, domain[1])];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const scale = d3.scaleLinear()
|
|
183
|
+
.domain(domain)
|
|
184
|
+
.range(range);
|
|
185
|
+
|
|
186
|
+
if (nice) {
|
|
187
|
+
scale.nice();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (clamp) {
|
|
191
|
+
scale.clamp(true);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return scale;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function setDefaultRange(range: [number, number]): void {
|
|
198
|
+
defaultRange = range;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function getScaleInfo(scale: any): ScaleInfo {
|
|
202
|
+
const info: ScaleInfo = {
|
|
203
|
+
type: 'unknown',
|
|
204
|
+
domain: [],
|
|
205
|
+
range: []
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
info.domain = scale.domain();
|
|
210
|
+
info.range = scale.range();
|
|
211
|
+
|
|
212
|
+
// Detect scale type
|
|
213
|
+
if (typeof scale.bandwidth === 'function') {
|
|
214
|
+
info.type = 'band';
|
|
215
|
+
info.bandwidth = scale.bandwidth();
|
|
216
|
+
if (typeof scale.step === 'function') {
|
|
217
|
+
info.step = scale.step();
|
|
218
|
+
}
|
|
219
|
+
} else if (typeof scale.nice === 'function') {
|
|
220
|
+
// Check if it's a time scale by testing if domain contains dates
|
|
221
|
+
if (info.domain.length > 0 && info.domain[0] instanceof Date) {
|
|
222
|
+
info.type = 'time';
|
|
223
|
+
} else {
|
|
224
|
+
info.type = 'linear';
|
|
225
|
+
}
|
|
226
|
+
} else if (typeof scale.unknown === 'function') {
|
|
227
|
+
info.type = 'ordinal';
|
|
228
|
+
}
|
|
229
|
+
} catch (e) {
|
|
230
|
+
// Fallback for scales that don't support these methods
|
|
231
|
+
console.warn('Could not extract complete scale info:', e);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return info;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Log scale with fallback to linear for non-positive values
|
|
238
|
+
function createLogScale(values: number[], options: LinearScaleOptions = {}): d3.ScaleLogarithmic<number, number> | d3.ScaleLinear<number, number> {
|
|
239
|
+
// Check if all values are positive for log scale
|
|
240
|
+
const hasNonPositive = values.some(v => v <= 0);
|
|
241
|
+
|
|
242
|
+
if (hasNonPositive) {
|
|
243
|
+
// Fallback to linear scale
|
|
244
|
+
return createLinearScale(values, options);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const {
|
|
248
|
+
range = defaultRange,
|
|
249
|
+
nice = true,
|
|
250
|
+
clamp = false
|
|
251
|
+
} = options;
|
|
252
|
+
|
|
253
|
+
const domain = d3.extent(values) as [number, number];
|
|
254
|
+
const scale = d3.scaleLog()
|
|
255
|
+
.domain(domain)
|
|
256
|
+
.range(range);
|
|
257
|
+
|
|
258
|
+
if (nice) {
|
|
259
|
+
scale.nice();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (clamp) {
|
|
263
|
+
scale.clamp(true);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return scale;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
createAdaptiveScale,
|
|
271
|
+
createTimeScale,
|
|
272
|
+
createOrdinalScale,
|
|
273
|
+
createBandScale,
|
|
274
|
+
createLinearScale,
|
|
275
|
+
createLogScale,
|
|
276
|
+
setDefaultRange,
|
|
277
|
+
getScaleInfo,
|
|
278
|
+
scaleUtils: createScaleUtilities()
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Standalone scale creation functions for backward compatibility
|
|
283
|
+
export function createTimeScale(values: Date[], options: TimeScaleOptions = {}): d3.ScaleTime<number, number> {
|
|
284
|
+
const factory = createScaleSystem();
|
|
285
|
+
if (options.range) {
|
|
286
|
+
factory.setDefaultRange(options.range);
|
|
287
|
+
}
|
|
288
|
+
return factory.createTimeScale(values, options);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function createOrdinalScale(values: any[], options: OrdinalScaleOptions = {}): d3.ScaleOrdinal<any, string, string> {
|
|
292
|
+
const factory = createScaleSystem();
|
|
293
|
+
const scale = factory.createOrdinalScale(values, options);
|
|
294
|
+
// Ensure the scale is properly configured and callable
|
|
295
|
+
return scale as d3.ScaleOrdinal<any, string, string>;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function createBandScale(values: any[], options: BandScaleOptions = {}): d3.ScaleBand<string> {
|
|
299
|
+
const factory = createScaleSystem();
|
|
300
|
+
// Set a default range for band scales if not provided
|
|
301
|
+
const range = options.range || [0, 400];
|
|
302
|
+
factory.setDefaultRange(range as [number, number]);
|
|
303
|
+
return factory.createBandScale(values, options);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function createLinearScale(values: number[], options: LinearScaleOptions = {}): d3.ScaleLinear<number, number> {
|
|
307
|
+
const factory = createScaleSystem();
|
|
308
|
+
if (options.range) {
|
|
309
|
+
factory.setDefaultRange(options.range);
|
|
310
|
+
}
|
|
311
|
+
return factory.createLinearScale(values, options);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Enhanced scale utilities
|
|
315
|
+
export interface ScaleUtilities {
|
|
316
|
+
formatTickValue(scale: any, value: any): string;
|
|
317
|
+
getTickCount(scale: any, targetSize: number): number;
|
|
318
|
+
createColorScale(domain: any[], scheme?: readonly string[]): d3.ScaleOrdinal<any, string, string>;
|
|
319
|
+
invertScale(scale: any, pixel: number): any;
|
|
320
|
+
detectScaleType(values: any[]): ScaleType;
|
|
321
|
+
createAxis(scale: any, orientation?: 'top' | 'bottom' | 'left' | 'right'): any;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function createScaleUtilities(): ScaleUtilities {
|
|
325
|
+
|
|
326
|
+
function formatTickValue(scale: any, value: any): string {
|
|
327
|
+
if (typeof scale.tickFormat === 'function') {
|
|
328
|
+
// Time scales
|
|
329
|
+
return scale.tickFormat()(value);
|
|
330
|
+
} else if (scale.tickFormat) {
|
|
331
|
+
// Scales with custom formatters
|
|
332
|
+
return scale.tickFormat(value);
|
|
333
|
+
} else if (typeof value === 'number') {
|
|
334
|
+
// Default number formatting
|
|
335
|
+
if (Math.abs(value) >= 1000000) {
|
|
336
|
+
return `${(value / 1000000).toFixed(1)}M`;
|
|
337
|
+
} else if (Math.abs(value) >= 1000) {
|
|
338
|
+
return `${(value / 1000).toFixed(1)}K`;
|
|
339
|
+
} else {
|
|
340
|
+
return value.toFixed(0);
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
return String(value);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function getTickCount(scale: any, targetSize: number): number {
|
|
348
|
+
const range = scale.range();
|
|
349
|
+
const rangeSize = Math.abs(range[1] - range[0]);
|
|
350
|
+
|
|
351
|
+
// Aim for ticks every 50-100 pixels
|
|
352
|
+
const idealTickCount = Math.max(2, Math.floor(rangeSize / 75));
|
|
353
|
+
|
|
354
|
+
// Cap at reasonable limits
|
|
355
|
+
return Math.min(10, Math.max(2, idealTickCount));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function createColorScale(domain: any[], scheme: readonly string[] = d3.schemeCategory10): d3.ScaleOrdinal<any, string, string> {
|
|
359
|
+
return d3.scaleOrdinal<any, string, string>()
|
|
360
|
+
.domain(domain)
|
|
361
|
+
.range([...scheme]);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function invertScale(scale: any, pixel: number): any {
|
|
365
|
+
if (typeof scale.invert === 'function') {
|
|
366
|
+
// Linear and time scales
|
|
367
|
+
return scale.invert(pixel);
|
|
368
|
+
} else if (typeof scale.bandwidth === 'function') {
|
|
369
|
+
// Band scales - find the band that contains the pixel
|
|
370
|
+
const domain = scale.domain();
|
|
371
|
+
const bandwidth = scale.bandwidth();
|
|
372
|
+
const step = scale.step();
|
|
373
|
+
|
|
374
|
+
for (let i = 0; i < domain.length; i++) {
|
|
375
|
+
const bandStart = scale(domain[i]);
|
|
376
|
+
if (pixel >= bandStart && pixel <= bandStart + bandwidth) {
|
|
377
|
+
return domain[i];
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// If not in any band, return closest
|
|
382
|
+
const distances = domain.map((d: any) => Math.abs(scale(d) + bandwidth / 2 - pixel));
|
|
383
|
+
const minIndex = distances.indexOf(Math.min(...distances));
|
|
384
|
+
return domain[minIndex];
|
|
385
|
+
} else {
|
|
386
|
+
// Ordinal scales - return undefined for pixel-based inversion
|
|
387
|
+
return undefined;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function detectScaleType(values: any[]): ScaleType {
|
|
392
|
+
if (values.every(v => v instanceof Date)) {
|
|
393
|
+
return 'time';
|
|
394
|
+
} else if (values.every(v => typeof v === "string" || isNaN(v))) {
|
|
395
|
+
return 'band';
|
|
396
|
+
} else if (values.every(v => typeof v === "number")) {
|
|
397
|
+
return 'linear';
|
|
398
|
+
} else {
|
|
399
|
+
return 'adaptive';
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function createAxis(scale: any, orientation: 'top' | 'bottom' | 'left' | 'right' = 'bottom'): any {
|
|
404
|
+
let axis;
|
|
405
|
+
|
|
406
|
+
switch (orientation) {
|
|
407
|
+
case 'top':
|
|
408
|
+
axis = d3.axisTop(scale);
|
|
409
|
+
break;
|
|
410
|
+
case 'bottom':
|
|
411
|
+
axis = d3.axisBottom(scale);
|
|
412
|
+
break;
|
|
413
|
+
case 'left':
|
|
414
|
+
axis = d3.axisLeft(scale);
|
|
415
|
+
break;
|
|
416
|
+
case 'right':
|
|
417
|
+
axis = d3.axisRight(scale);
|
|
418
|
+
break;
|
|
419
|
+
default:
|
|
420
|
+
axis = d3.axisBottom(scale);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return axis;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
formatTickValue,
|
|
428
|
+
getTickCount,
|
|
429
|
+
createColorScale,
|
|
430
|
+
invertScale,
|
|
431
|
+
detectScaleType,
|
|
432
|
+
createAxis
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Export default instance for convenience
|
|
437
|
+
export const scaleUtilities = createScaleUtilities();
|