cx 24.10.5 → 24.10.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/dist/charts.js +152 -53
- package/dist/manifest.js +718 -718
- package/package.json +1 -1
- package/src/charts/axis/Axis.js +3 -3
- package/src/charts/axis/NumericAxis.js +1 -1
- package/src/charts/axis/TimeAxis.d.ts +28 -24
- package/src/charts/axis/TimeAxis.js +123 -41
package/package.json
CHANGED
package/src/charts/axis/Axis.js
CHANGED
|
@@ -56,13 +56,13 @@ export class Axis extends BoundedObject {
|
|
|
56
56
|
|
|
57
57
|
reportData(context, instance) {}
|
|
58
58
|
|
|
59
|
-
renderTicksAndLabels(context, instance, valueFormatter) {
|
|
59
|
+
renderTicksAndLabels(context, instance, valueFormatter, minLabelDistance) {
|
|
60
60
|
if (this.hidden) return false;
|
|
61
61
|
|
|
62
62
|
var { data, calculator, labelFormatter } = instance;
|
|
63
63
|
var { bounds } = data;
|
|
64
64
|
let { CSS, baseClass } = this;
|
|
65
|
-
var size = calculator.findTickSize(
|
|
65
|
+
var size = calculator.findTickSize(minLabelDistance);
|
|
66
66
|
|
|
67
67
|
var labelClass = CSS.expand(CSS.element(baseClass, "label"), data.labelClass);
|
|
68
68
|
var offsetClass = CSS.element(baseClass, "label-offset");
|
|
@@ -101,7 +101,7 @@ export class Axis extends BoundedObject {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
var t = [];
|
|
104
|
-
if (size
|
|
104
|
+
if (!!size && !data.hideLabels) {
|
|
105
105
|
var ticks = calculator.getTicks([size]);
|
|
106
106
|
ticks.forEach((serie, si) => {
|
|
107
107
|
serie.forEach((v, i) => {
|
|
@@ -59,7 +59,7 @@ export class NumericAxis extends Axis {
|
|
|
59
59
|
|
|
60
60
|
return (
|
|
61
61
|
<g key={key} className={data.classNames} style={data.style}>
|
|
62
|
-
{this.renderTicksAndLabels(context, instance, formatter)}
|
|
62
|
+
{this.renderTicksAndLabels(context, instance, formatter, this.minLabelDistance)}
|
|
63
63
|
</g>
|
|
64
64
|
);
|
|
65
65
|
}
|
|
@@ -1,24 +1,28 @@
|
|
|
1
|
-
import * as Cx from
|
|
2
|
-
import {AxisProps} from
|
|
3
|
-
|
|
4
|
-
interface TimeAxisProps extends AxisProps {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
1
|
+
import * as Cx from "../../core";
|
|
2
|
+
import { AxisProps } from "./Axis";
|
|
3
|
+
|
|
4
|
+
interface TimeAxisProps extends AxisProps {
|
|
5
|
+
/** Minimum value. */
|
|
6
|
+
min?: Cx.NumberProp;
|
|
7
|
+
|
|
8
|
+
/** Maximum value. */
|
|
9
|
+
max?: Cx.NumberProp;
|
|
10
|
+
|
|
11
|
+
/** Base CSS class to be applied to the element. Defaults to `timeaxis`. */
|
|
12
|
+
baseClass?: string;
|
|
13
|
+
|
|
14
|
+
/** A number ranged between `0-2`. `0` means that the range is aligned with the lowest ticks. Default value is `1`, which means that the range is aligned with medium ticks. Use value `2` to align with major ticks. */
|
|
15
|
+
snapToTicks?: 0 | 1 | 2 | false;
|
|
16
|
+
|
|
17
|
+
tickDivisions?: { [prop: string]: Array<number[]> };
|
|
18
|
+
minLabelDistance?: number;
|
|
19
|
+
minTickUnit?: string;
|
|
20
|
+
|
|
21
|
+
/** Set to true to apply precise label distances from minLabelDistanceFormatOverride based on the resolved label format. */
|
|
22
|
+
useLabelDistanceFormatOverrides?: boolean;
|
|
23
|
+
|
|
24
|
+
/** Mapping of formats to label distances, i.e. { "datetime;YYYYMM": 80 } */
|
|
25
|
+
minLabelDistanceFormatOverride?: Record<string, number>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class TimeAxis extends Cx.Widget<TimeAxisProps> {}
|
|
@@ -33,9 +33,14 @@ export class TimeAxis extends Axis {
|
|
|
33
33
|
|
|
34
34
|
if (this.deadZone) {
|
|
35
35
|
this.lowerDeadZone = this.deadZone;
|
|
36
|
-
|
|
36
|
+
pperDeadZone = this.deadZone;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
this.minLabelDistanceFormatOverride = {
|
|
40
|
+
...this.minLabelDistanceFormatOverrideDefaults,
|
|
41
|
+
...this.minLabelDistanceFormatOverride,
|
|
42
|
+
};
|
|
43
|
+
|
|
39
44
|
super.init();
|
|
40
45
|
}
|
|
41
46
|
|
|
@@ -62,14 +67,16 @@ export class TimeAxis extends Axis {
|
|
|
62
67
|
max,
|
|
63
68
|
this.snapToTicks,
|
|
64
69
|
this.tickDivisions,
|
|
65
|
-
this.minTickDistance,
|
|
66
|
-
this.minLabelDistance,
|
|
70
|
+
Math.max(1, this.minTickDistance),
|
|
71
|
+
Math.max(1, this.minLabelDistance),
|
|
67
72
|
normalized,
|
|
68
73
|
inverted,
|
|
69
74
|
this.minTickUnit,
|
|
70
75
|
lowerDeadZone,
|
|
71
76
|
upperDeadZone,
|
|
72
|
-
this.decode
|
|
77
|
+
this.decode,
|
|
78
|
+
this.useLabelDistanceFormatOverrides ? this.minLabelDistanceFormatOverride : {},
|
|
79
|
+
this.format,
|
|
73
80
|
);
|
|
74
81
|
}
|
|
75
82
|
|
|
@@ -80,12 +87,13 @@ export class TimeAxis extends Axis {
|
|
|
80
87
|
|
|
81
88
|
if (!data.bounds.valid()) return null;
|
|
82
89
|
|
|
83
|
-
let format =
|
|
90
|
+
let format = calculator.resolvedFormat;
|
|
91
|
+
let minLabelDistance = calculator.resolvedMinLabelDistance;
|
|
84
92
|
let formatter = Format.parse(format);
|
|
85
93
|
|
|
86
94
|
return (
|
|
87
95
|
<g key={key} className={data.classNames} style={data.style}>
|
|
88
|
-
{this.renderTicksAndLabels(context, instance, formatter)}
|
|
96
|
+
{this.renderTicksAndLabels(context, instance, formatter, minLabelDistance)}
|
|
89
97
|
</g>
|
|
90
98
|
);
|
|
91
99
|
}
|
|
@@ -112,11 +120,21 @@ TimeAxis.prototype.tickDivisions = {
|
|
|
112
120
|
],
|
|
113
121
|
};
|
|
114
122
|
|
|
123
|
+
const TimeFormats = {
|
|
124
|
+
fullDateAndTime: "datetime;yyyy MMM dd HH mm ss n",
|
|
125
|
+
shortMonthDate: "datetime;yyyy MMM dd",
|
|
126
|
+
};
|
|
127
|
+
|
|
115
128
|
TimeAxis.prototype.snapToTicks = 0;
|
|
116
129
|
TimeAxis.prototype.tickSize = 15;
|
|
117
130
|
TimeAxis.prototype.minLabelDistance = 60;
|
|
118
131
|
TimeAxis.prototype.minTickDistance = 60;
|
|
119
132
|
TimeAxis.prototype.minTickUnit = "second";
|
|
133
|
+
TimeAxis.prototype.useLabelDistanceFormatOverrides = false;
|
|
134
|
+
TimeAxis.prototype.minLabelDistanceFormatOverrideDefaults = {
|
|
135
|
+
[TimeFormats.fullDateAndTime]: 150,
|
|
136
|
+
[TimeFormats.shortMonthDate]: 90,
|
|
137
|
+
};
|
|
120
138
|
|
|
121
139
|
function monthNumber(date) {
|
|
122
140
|
return date.getFullYear() * 12 + date.getMonth() + (date.getDate() - 1) / 31;
|
|
@@ -149,7 +167,9 @@ class TimeScale {
|
|
|
149
167
|
minTickUnit,
|
|
150
168
|
lowerDeadZone,
|
|
151
169
|
upperDeadZone,
|
|
152
|
-
decode
|
|
170
|
+
decode,
|
|
171
|
+
minLabelDistanceFormatOverride,
|
|
172
|
+
format,
|
|
153
173
|
) {
|
|
154
174
|
this.dateCache = {};
|
|
155
175
|
this.min = min != null ? this.decodeValue(min) : null;
|
|
@@ -170,6 +190,8 @@ class TimeScale {
|
|
|
170
190
|
delete this.maxValuePadded;
|
|
171
191
|
this.stacks = {};
|
|
172
192
|
this.decode = decode;
|
|
193
|
+
this.minLabelDistanceFormatOverride = minLabelDistanceFormatOverride;
|
|
194
|
+
this.format = format;
|
|
173
195
|
}
|
|
174
196
|
|
|
175
197
|
decodeValue(date) {
|
|
@@ -193,13 +215,13 @@ class TimeScale {
|
|
|
193
215
|
return new Date(v).toISOString();
|
|
194
216
|
}
|
|
195
217
|
|
|
196
|
-
getFormat() {
|
|
197
|
-
switch (
|
|
218
|
+
getFormat(unit, scale) {
|
|
219
|
+
switch (unit) {
|
|
198
220
|
case "year":
|
|
199
221
|
return "datetime;yyyy";
|
|
200
222
|
|
|
201
223
|
case "month":
|
|
202
|
-
if (new Date(
|
|
224
|
+
if (new Date(scale.min).getFullYear() != new Date(scale.max).getFullYear()) return "yearOrMonth";
|
|
203
225
|
return "datetime;yyyy MMM";
|
|
204
226
|
|
|
205
227
|
case "week":
|
|
@@ -207,12 +229,12 @@ class TimeScale {
|
|
|
207
229
|
|
|
208
230
|
case "day":
|
|
209
231
|
if (
|
|
210
|
-
new Date(
|
|
211
|
-
new Date(
|
|
232
|
+
new Date(scale.min).getFullYear() != new Date(scale.max).getFullYear() ||
|
|
233
|
+
new Date(scale.min).getMonth() != new Date(scale.max).getMonth()
|
|
212
234
|
)
|
|
213
235
|
return "monthOrDay";
|
|
214
236
|
|
|
215
|
-
return
|
|
237
|
+
return TimeFormats.shortMonthDate;
|
|
216
238
|
|
|
217
239
|
case "hour":
|
|
218
240
|
return "datetime;HH mm n";
|
|
@@ -224,7 +246,7 @@ class TimeScale {
|
|
|
224
246
|
return "datetime;mm ss";
|
|
225
247
|
|
|
226
248
|
default:
|
|
227
|
-
return
|
|
249
|
+
return TimeFormats.fullDateAndTime;
|
|
228
250
|
}
|
|
229
251
|
}
|
|
230
252
|
|
|
@@ -288,31 +310,30 @@ class TimeScale {
|
|
|
288
310
|
|
|
289
311
|
this.origin = this.inverted ? this.b : this.a;
|
|
290
312
|
|
|
291
|
-
this.scale = this.getScale();
|
|
292
|
-
|
|
293
313
|
this.calculateTicks();
|
|
314
|
+
if (this.scale == null) {
|
|
315
|
+
this.scale = this.getScale();
|
|
316
|
+
}
|
|
294
317
|
}
|
|
295
318
|
|
|
296
319
|
getTimezoneOffset(date) {
|
|
297
320
|
return date.getTimezoneOffset() * 60 * 1000;
|
|
298
321
|
}
|
|
299
322
|
|
|
300
|
-
getScale(
|
|
323
|
+
getScale(tickSize, measure) {
|
|
301
324
|
let { min, max, upperDeadZone, lowerDeadZone } = this;
|
|
302
325
|
|
|
303
326
|
let smin = min;
|
|
304
327
|
let smax = max;
|
|
305
328
|
|
|
306
|
-
if (
|
|
307
|
-
let tickSize = tickSizes[Math.min(tickSizes.length - 1, this.snapToTicks)];
|
|
308
|
-
|
|
329
|
+
if (tickSize) {
|
|
309
330
|
let minDate = new Date(min);
|
|
310
331
|
let maxDate = new Date(max);
|
|
311
332
|
|
|
312
333
|
switch (measure) {
|
|
313
334
|
case "second":
|
|
314
335
|
case "minute":
|
|
315
|
-
case "
|
|
336
|
+
case "hour":
|
|
316
337
|
case "day":
|
|
317
338
|
default:
|
|
318
339
|
let minOffset = this.getTimezoneOffset(minDate);
|
|
@@ -399,7 +420,7 @@ class TimeScale {
|
|
|
399
420
|
}
|
|
400
421
|
|
|
401
422
|
findTickSize(minPxDist) {
|
|
402
|
-
return this.tickSizes.find((
|
|
423
|
+
return this.tickSizes.find(({ size }) => size * Math.abs(this.scale.factor) >= minPxDist);
|
|
403
424
|
}
|
|
404
425
|
|
|
405
426
|
getTickSizes() {
|
|
@@ -420,62 +441,124 @@ class TimeScale {
|
|
|
420
441
|
|
|
421
442
|
if (this.tickSizes.length > 0) {
|
|
422
443
|
//add ticks from higher levels
|
|
423
|
-
this.tickSizes.push(...divisions[0].map((s) => s * unitSize));
|
|
424
|
-
|
|
444
|
+
this.tickSizes.push(...divisions[0].map((s) => ({ size: s * unitSize, measure: unit })));
|
|
445
|
+
break;
|
|
425
446
|
}
|
|
426
447
|
|
|
427
448
|
let bestLabelDistance = Infinity;
|
|
449
|
+
let bestMinLabelDistance = this.minLabelDistance;
|
|
428
450
|
let bestTicks = [];
|
|
429
|
-
let bestScale =
|
|
451
|
+
let bestScale = null;
|
|
452
|
+
let bestFormat = null;
|
|
430
453
|
|
|
431
454
|
this.tickMeasure = unit;
|
|
432
455
|
|
|
433
456
|
for (let i = 0; i < divisions.length; i++) {
|
|
434
457
|
let divs = divisions[i];
|
|
435
|
-
let
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
let
|
|
439
|
-
|
|
458
|
+
for (let d = 0; d < divs.length; d++) {
|
|
459
|
+
//if (useSnapToTicks && d < Math.min(divs.length - 1, this.snapToTicks)) continue;
|
|
460
|
+
let tickSize = divs[d] * unitSize;
|
|
461
|
+
let scale = this.getScale(null, unit);
|
|
462
|
+
let format = this.format ?? this.getFormat(unit, scale);
|
|
463
|
+
let minLabelDistance = this.minLabelDistanceFormatOverride[format] ?? this.minLabelDistance;
|
|
464
|
+
let labelDistance = tickSize * Math.abs(scale.factor);
|
|
465
|
+
if (labelDistance >= minLabelDistance && labelDistance < bestLabelDistance) {
|
|
440
466
|
bestScale = scale;
|
|
441
|
-
bestTicks =
|
|
442
|
-
bestLabelDistance =
|
|
467
|
+
bestTicks = divs.map((s) => s * unitSize);
|
|
468
|
+
bestLabelDistance = labelDistance;
|
|
469
|
+
bestFormat = format;
|
|
470
|
+
bestMinLabelDistance = minLabelDistance;
|
|
443
471
|
}
|
|
444
|
-
}
|
|
472
|
+
}
|
|
445
473
|
}
|
|
474
|
+
|
|
446
475
|
this.scale = bestScale;
|
|
447
|
-
this.tickSizes = bestTicks
|
|
476
|
+
this.tickSizes = bestTicks
|
|
477
|
+
.filter((ts) => ts * Math.abs(bestScale.factor) >= this.minTickDistance)
|
|
478
|
+
.map((size) => ({ size, measure: this.tickMeasure }));
|
|
479
|
+
this.resolvedFormat = bestFormat;
|
|
480
|
+
this.resolvedMinLabelDistance = bestMinLabelDistance;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
let lowerTickUnit = null;
|
|
484
|
+
switch (this.tickMeasure) {
|
|
485
|
+
case "year":
|
|
486
|
+
lowerTickUnit = "month";
|
|
487
|
+
break;
|
|
488
|
+
case "month":
|
|
489
|
+
lowerTickUnit = "day";
|
|
490
|
+
break;
|
|
491
|
+
case "week":
|
|
492
|
+
lowerTickUnit = "day";
|
|
493
|
+
break;
|
|
494
|
+
case "day":
|
|
495
|
+
lowerTickUnit = "hour";
|
|
496
|
+
break;
|
|
497
|
+
case "hour":
|
|
498
|
+
lowerTickUnit = "minute";
|
|
499
|
+
break;
|
|
500
|
+
case "minute":
|
|
501
|
+
lowerTickUnit = "second";
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (lowerTickUnit != null && this.scale) {
|
|
506
|
+
let bestMinorTickSize = Infinity;
|
|
507
|
+
let divisions = this.tickDivisions[lowerTickUnit];
|
|
508
|
+
let unitSize = miliSeconds[lowerTickUnit];
|
|
509
|
+
for (let i = 0; i < divisions.length; i++) {
|
|
510
|
+
let divs = divisions[i];
|
|
511
|
+
for (let d = 0; d < divs.length; d++) {
|
|
512
|
+
let tickSize = divs[d] * unitSize;
|
|
513
|
+
if (tickSize * Math.abs(this.scale.factor) >= this.minTickDistance && tickSize < bestMinorTickSize) {
|
|
514
|
+
bestMinorTickSize = tickSize;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (bestMinorTickSize != Infinity) {
|
|
519
|
+
this.tickSizes.unshift({ size: bestMinorTickSize, measure: lowerTickUnit });
|
|
520
|
+
if (this.tickSizes.length > 1) {
|
|
521
|
+
let labelStep = this.tickSizes[1].size;
|
|
522
|
+
let lowerScale = this.getScale(null, lowerTickUnit);
|
|
523
|
+
if (lowerScale.max - lowerScale.min >= labelStep) this.scale = lowerScale;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (isNumber(this.snapToTicks) && this.snapToTicks >= 0) {
|
|
529
|
+
let tickSize = this.tickSizes[Math.min(this.tickSizes.length - 1, this.snapToTicks)];
|
|
530
|
+
this.scale = this.getScale(tickSize.size, tickSize.measure);
|
|
448
531
|
}
|
|
449
532
|
}
|
|
450
533
|
|
|
451
534
|
getTicks(tickSizes) {
|
|
452
|
-
return tickSizes.map((size) => {
|
|
535
|
+
return tickSizes.map(({ size, measure }) => {
|
|
453
536
|
let result = [],
|
|
454
537
|
start,
|
|
455
538
|
end,
|
|
456
539
|
minDate,
|
|
457
540
|
maxDate;
|
|
458
|
-
if (
|
|
541
|
+
if (measure == "year") {
|
|
459
542
|
size /= miliSeconds.year;
|
|
460
543
|
minDate = new Date(this.scale.min - this.scale.minPadding);
|
|
461
544
|
maxDate = new Date(this.scale.max + this.scale.maxPadding);
|
|
462
545
|
start = Math.ceil(yearNumber(minDate) / size) * size;
|
|
463
546
|
end = Math.floor(yearNumber(maxDate) / size) * size;
|
|
464
547
|
for (let i = start; i <= end; i += size) result.push(new Date(i, 0, 1).getTime());
|
|
465
|
-
} else if (
|
|
548
|
+
} else if (measure == "month") {
|
|
466
549
|
size /= miliSeconds.month;
|
|
467
550
|
minDate = new Date(this.scale.min - this.scale.minPadding);
|
|
468
551
|
maxDate = new Date(this.scale.max + this.scale.maxPadding);
|
|
469
552
|
start = Math.ceil(monthNumber(minDate) / size) * size;
|
|
470
553
|
end = Math.floor(monthNumber(maxDate) / size) * size;
|
|
471
554
|
for (let i = start; i <= end; i += size) result.push(new Date(Math.floor(i / 12), i % 12, 1).getTime());
|
|
472
|
-
} else if (
|
|
473
|
-
let multiplier =
|
|
555
|
+
} else if (measure == "day" || measure == "week") {
|
|
556
|
+
let multiplier = measure == "week" ? 7 : 1;
|
|
474
557
|
size /= miliSeconds.day;
|
|
475
558
|
minDate = new Date(this.scale.min - this.scale.minPadding);
|
|
476
559
|
maxDate = new Date(this.scale.max + this.scale.maxPadding);
|
|
477
560
|
let date = zeroTime(minDate);
|
|
478
|
-
if (
|
|
561
|
+
if (measure == "week") {
|
|
479
562
|
//start on monday
|
|
480
563
|
while (date.getDay() != 1) {
|
|
481
564
|
date.setDate(date.getDate() + 1);
|
|
@@ -504,7 +587,6 @@ class TimeScale {
|
|
|
504
587
|
|
|
505
588
|
mapGridlines() {
|
|
506
589
|
if (this.tickSizes.length == 0) return [];
|
|
507
|
-
|
|
508
590
|
return this.getTicks([this.tickSizes[0]])[0].map((x) => this.map(x));
|
|
509
591
|
}
|
|
510
592
|
}
|