cx 25.4.1 → 25.5.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.
Files changed (140) hide show
  1. package/dist/charts.js +2 -2
  2. package/dist/data.js +17 -3
  3. package/dist/manifest.js +732 -732
  4. package/dist/ui.js +94 -61
  5. package/dist/widgets.js +25 -13
  6. package/package.json +1 -1
  7. package/src/charts/Legend.d.ts +45 -45
  8. package/src/charts/LegendEntry.js +128 -128
  9. package/src/charts/LegendEntry.scss +27 -27
  10. package/src/charts/PieChart.d.ts +92 -92
  11. package/src/charts/RangeMarker.js +6 -2
  12. package/src/charts/axis/Axis.d.ts +113 -113
  13. package/src/charts/axis/Axis.js +280 -280
  14. package/src/charts/axis/CategoryAxis.d.ts +30 -30
  15. package/src/charts/axis/CategoryAxis.js +241 -241
  16. package/src/charts/axis/NumericAxis.d.ts +46 -46
  17. package/src/charts/axis/NumericAxis.js +351 -351
  18. package/src/charts/axis/Stack.js +55 -55
  19. package/src/charts/axis/TimeAxis.d.ts +31 -28
  20. package/src/charts/axis/TimeAxis.js +611 -611
  21. package/src/charts/helpers/PointReducer.js +47 -47
  22. package/src/charts/helpers/SnapPointFinder.js +69 -69
  23. package/src/data/AugmentedViewBase.js +77 -75
  24. package/src/data/Binding.spec.js +69 -69
  25. package/src/data/ExposedRecordView.js +75 -70
  26. package/src/data/ExposedValueView.js +73 -72
  27. package/src/data/Expression.js +229 -229
  28. package/src/data/Expression.spec.js +229 -229
  29. package/src/data/Ref.d.ts +24 -24
  30. package/src/data/Ref.spec.js +79 -79
  31. package/src/data/StoreRef.spec.js +24 -24
  32. package/src/data/StringTemplate.js +92 -92
  33. package/src/data/StringTemplate.spec.js +132 -132
  34. package/src/data/StructuredDataAccessor.d.ts +7 -7
  35. package/src/data/StructuredSelector.js +132 -132
  36. package/src/data/SubscribableView.js +54 -54
  37. package/src/data/getAccessor.spec.js +11 -11
  38. package/src/data/getSelector.js +49 -49
  39. package/src/hooks/createLocalStorageRef.d.ts +3 -3
  40. package/src/hooks/createLocalStorageRef.js +20 -20
  41. package/src/index.scss +6 -6
  42. package/src/ui/Container.js +154 -183
  43. package/src/ui/Culture.d.ts +57 -57
  44. package/src/ui/Culture.js +139 -139
  45. package/src/ui/Cx.js +3 -3
  46. package/src/ui/DataProxy.js +45 -44
  47. package/src/ui/DetachedScope.js +98 -94
  48. package/src/ui/FocusManager.js +171 -171
  49. package/src/ui/Format.js +108 -108
  50. package/src/ui/HoverSync.js +147 -147
  51. package/src/ui/Instance.d.ts +1 -1
  52. package/src/ui/Instance.js +25 -16
  53. package/src/ui/IsolatedScope.js +30 -30
  54. package/src/ui/Repeater.d.ts +61 -61
  55. package/src/ui/Repeater.js +109 -102
  56. package/src/ui/Rescope.js +35 -31
  57. package/src/ui/Restate.js +167 -163
  58. package/src/ui/Widget.js +184 -200
  59. package/src/ui/adapter/ArrayAdapter.js +152 -142
  60. package/src/ui/adapter/TreeAdapter.js +101 -100
  61. package/src/ui/createFunctionalComponent.d.ts +1 -1
  62. package/src/ui/createFunctionalComponent.js +31 -36
  63. package/src/ui/layout/ContentPlaceholder.d.ts +19 -19
  64. package/src/ui/layout/ContentPlaceholder.js +105 -105
  65. package/src/ui/layout/ContentPlaceholder.spec.js +579 -579
  66. package/src/ui/layout/LabelsTopLayout.js +134 -134
  67. package/src/ui/layout/exploreChildren.d.ts +12 -15
  68. package/src/ui/layout/exploreChildren.js +27 -40
  69. package/src/util/Format.js +270 -270
  70. package/src/util/date/encodeDate.d.ts +1 -1
  71. package/src/util/date/encodeDate.js +8 -8
  72. package/src/util/date/encodeDateWithTimezoneOffset.d.ts +1 -1
  73. package/src/util/date/index.d.ts +11 -11
  74. package/src/util/date/index.js +11 -11
  75. package/src/util/date/parseDateInvariant.d.ts +3 -3
  76. package/src/util/date/parseDateInvariant.js +20 -20
  77. package/src/util/debounce.js +18 -18
  78. package/src/util/getSearchQueryPredicate.js +59 -59
  79. package/src/util/index.d.ts +51 -51
  80. package/src/util/index.js +54 -54
  81. package/src/util/isValidIdentifierName.d.ts +1 -1
  82. package/src/util/isValidIdentifierName.js +5 -5
  83. package/src/util/isValidIdentifierName.spec.js +33 -33
  84. package/src/util/scss/add-rules.scss +38 -38
  85. package/src/util/validatedDebounce.js +19 -19
  86. package/src/widgets/Button.js +118 -118
  87. package/src/widgets/CxCredit.scss +37 -37
  88. package/src/widgets/HighlightedSearchText.js +36 -36
  89. package/src/widgets/HighlightedSearchText.scss +18 -18
  90. package/src/widgets/List.js +594 -587
  91. package/src/widgets/List.scss +91 -91
  92. package/src/widgets/Sandbox.js +9 -8
  93. package/src/widgets/drag-drop/DropZone.js +214 -214
  94. package/src/widgets/form/Calendar.d.ts +86 -86
  95. package/src/widgets/form/Calendar.js +618 -618
  96. package/src/widgets/form/Calendar.scss +196 -196
  97. package/src/widgets/form/Checkbox.scss +127 -127
  98. package/src/widgets/form/ColorField.js +397 -397
  99. package/src/widgets/form/ColorField.scss +96 -96
  100. package/src/widgets/form/ColorPicker.scss +283 -283
  101. package/src/widgets/form/DateTimeField.js +576 -576
  102. package/src/widgets/form/DateTimePicker.js +392 -392
  103. package/src/widgets/form/LookupField.d.ts +179 -179
  104. package/src/widgets/form/LookupField.scss +219 -219
  105. package/src/widgets/form/MonthField.d.ts +99 -99
  106. package/src/widgets/form/MonthField.js +523 -523
  107. package/src/widgets/form/MonthPicker.d.ts +76 -76
  108. package/src/widgets/form/MonthPicker.js +641 -641
  109. package/src/widgets/form/MonthPicker.scss +118 -118
  110. package/src/widgets/form/NumberField.js +459 -459
  111. package/src/widgets/form/NumberField.scss +61 -61
  112. package/src/widgets/form/Radio.scss +121 -121
  113. package/src/widgets/form/Select.scss +99 -99
  114. package/src/widgets/form/Slider.scss +118 -118
  115. package/src/widgets/form/Switch.scss +140 -140
  116. package/src/widgets/form/TextArea.scss +43 -43
  117. package/src/widgets/form/TextField.js +290 -290
  118. package/src/widgets/form/TextField.scss +55 -55
  119. package/src/widgets/form/UploadButton.d.ts +34 -34
  120. package/src/widgets/form/variables.scss +353 -353
  121. package/src/widgets/grid/Grid.d.ts +442 -442
  122. package/src/widgets/grid/Grid.js +7 -0
  123. package/src/widgets/grid/GridRow.js +228 -228
  124. package/src/widgets/grid/TreeNode.d.ts +23 -23
  125. package/src/widgets/grid/TreeNode.scss +88 -88
  126. package/src/widgets/grid/variables.scss +133 -133
  127. package/src/widgets/nav/LinkButton.js +128 -128
  128. package/src/widgets/nav/Menu.scss +74 -74
  129. package/src/widgets/nav/Route.js +102 -106
  130. package/src/widgets/overlay/Dropdown.js +612 -612
  131. package/src/widgets/overlay/FlyweightTooltipTracker.js +39 -39
  132. package/src/widgets/overlay/Overlay.d.ts +73 -73
  133. package/src/widgets/overlay/Tooltip.js +1 -1
  134. package/src/widgets/overlay/Window.js +202 -202
  135. package/src/widgets/overlay/captureMouse.js +124 -124
  136. package/src/widgets/overlay/createHotPromiseWindowFactory.d.ts +18 -18
  137. package/src/widgets/overlay/createHotPromiseWindowFactory.js +56 -56
  138. package/src/widgets/overlay/index.d.ts +11 -11
  139. package/src/widgets/overlay/index.js +11 -11
  140. package/src/widgets/variables.scss +144 -144
@@ -1,611 +1,611 @@
1
- import { Axis } from "./Axis";
2
- import { VDOM } from "../../ui/Widget";
3
- import { Stack } from "./Stack";
4
- import { Format } from "../../ui/Format";
5
- import { isNumber } from "../../util/isNumber";
6
- import { zeroTime } from "../../util/date/zeroTime";
7
- import { Console } from "../../util/Console";
8
- import { parseDateInvariant } from "../../util";
9
-
10
- Format.registerFactory("yearOrMonth", (format) => {
11
- let year = Format.parse("datetime;yyyy");
12
- let month = Format.parse("datetime;MMM");
13
- return function (date) {
14
- let d = parseDateInvariant(date);
15
- if (d.getMonth() == 0) return year(d);
16
- else return month(d);
17
- };
18
- });
19
-
20
- Format.registerFactory("monthOrDay", (format) => {
21
- let month = Format.parse("datetime;MMM");
22
- let day = Format.parse("datetime;dd");
23
- return function (date) {
24
- let d = parseDateInvariant(date);
25
- if (d.getDate() == 1) return month(d);
26
- else return day(d);
27
- };
28
- });
29
-
30
- export class TimeAxis extends Axis {
31
- init() {
32
- if (this.labelAnchor == "auto") this.labelAnchor = this.vertical ? (this.secondary ? "start" : "end") : "start";
33
-
34
- if (this.labelDx == "auto") this.labelDx = this.vertical ? 0 : "5px";
35
-
36
- if (this.deadZone) {
37
- this.lowerDeadZone = this.deadZone;
38
- this.upperDeadZone = this.deadZone;
39
- }
40
-
41
- this.minLabelDistanceFormatOverride = {
42
- ...this.minLabelDistanceFormatOverrideDefaults,
43
- ...this.minLabelDistanceFormatOverride,
44
- };
45
-
46
- super.init();
47
- }
48
-
49
- declareData() {
50
- super.declareData(...arguments, {
51
- anchors: undefined,
52
- min: undefined,
53
- max: undefined,
54
- inverted: undefined,
55
- lowerDeadZone: undefined,
56
- upperDeadZone: undefined,
57
- });
58
- }
59
-
60
- initInstance(context, instance) {
61
- instance.calculator = new TimeScale();
62
- }
63
-
64
- explore(context, instance) {
65
- super.explore(context, instance);
66
- let { min, max, normalized, inverted, lowerDeadZone, upperDeadZone } = instance.data;
67
- instance.calculator.reset(
68
- min,
69
- max,
70
- this.snapToTicks,
71
- this.tickDivisions,
72
- Math.max(1, this.minTickDistance),
73
- Math.max(1, this.minLabelDistance),
74
- normalized,
75
- inverted,
76
- this.minTickUnit,
77
- lowerDeadZone,
78
- upperDeadZone,
79
- this.decode,
80
- this.useLabelDistanceFormatOverrides ? this.minLabelDistanceFormatOverride : {},
81
- this.format,
82
- );
83
- }
84
-
85
- render(context, instance, key) {
86
- let { data, cached, calculator } = instance;
87
-
88
- cached.axis = calculator.hash();
89
-
90
- if (!data.bounds.valid()) return null;
91
-
92
- let format = calculator.resolvedFormat;
93
- let minLabelDistance = calculator.resolvedMinLabelDistance;
94
- let formatter = Format.parse(format);
95
-
96
- return (
97
- <g key={key} className={data.classNames} style={data.style}>
98
- {this.renderTicksAndLabels(context, instance, formatter, minLabelDistance)}
99
- </g>
100
- );
101
- }
102
- }
103
-
104
- Axis.alias("time", TimeAxis);
105
-
106
- TimeAxis.prototype.baseClass = "timeaxis";
107
- TimeAxis.prototype.tickDivisions = {
108
- second: [[1, 5, 15, 30]],
109
- minute: [[1, 5, 15, 30]],
110
- hour: [
111
- [1, 2, 4, 8],
112
- [1, 3, 6, 12],
113
- ],
114
- day: [[1]],
115
- week: [[1]],
116
- month: [[1, 3, 6]],
117
- year: [
118
- [1, 2, 10],
119
- [1, 5, 10],
120
- [5, 10, 50],
121
- [10, 50, 100],
122
- ],
123
- };
124
-
125
- const TimeFormats = {
126
- fullDateAndTime: "datetime;yyyy MMM dd HH mm ss n",
127
- shortMonthDate: "datetime;yyyy MMM dd",
128
- };
129
-
130
- TimeAxis.prototype.snapToTicks = 0;
131
- TimeAxis.prototype.tickSize = 15;
132
- TimeAxis.prototype.minLabelDistance = 60;
133
- TimeAxis.prototype.minTickDistance = 60;
134
- TimeAxis.prototype.minTickUnit = "second";
135
- TimeAxis.prototype.useLabelDistanceFormatOverrides = false;
136
- TimeAxis.prototype.minLabelDistanceFormatOverrideDefaults = {
137
- [TimeFormats.fullDateAndTime]: 150,
138
- [TimeFormats.shortMonthDate]: 90,
139
- };
140
-
141
- function monthNumber(date) {
142
- return date.getFullYear() * 12 + date.getMonth() + (date.getDate() - 1) / 31;
143
- }
144
-
145
- function yearNumber(date) {
146
- return monthNumber(date) / 12;
147
- }
148
-
149
- const milliSeconds = {
150
- second: 1000,
151
- minute: 60 * 1000,
152
- hour: 3600 * 1000,
153
- day: 3600 * 24 * 1000,
154
- week: 3600 * 24 * 7 * 1000,
155
- month: 3600 * 24 * 30 * 1000,
156
- year: 3600 * 24 * 365 * 1000,
157
- };
158
-
159
- class TimeScale {
160
- reset(
161
- min,
162
- max,
163
- snapToTicks,
164
- tickDivisions,
165
- minTickDistance,
166
- minLabelDistance,
167
- normalized,
168
- inverted,
169
- minTickUnit,
170
- lowerDeadZone,
171
- upperDeadZone,
172
- decode,
173
- minLabelDistanceFormatOverride,
174
- format,
175
- ) {
176
- this.dateCache = {};
177
- this.min = min != null ? this.decodeValue(min) : null;
178
- this.max = max != null ? this.decodeValue(max) : null;
179
- this.snapToTicks = snapToTicks;
180
- this.tickDivisions = tickDivisions;
181
- this.minLabelDistance = minLabelDistance;
182
- this.minTickDistance = minTickDistance;
183
- this.tickSizes = [];
184
- this.normalized = normalized;
185
- this.minTickUnit = minTickUnit;
186
- this.inverted = inverted;
187
- this.lowerDeadZone = lowerDeadZone || 0;
188
- this.upperDeadZone = upperDeadZone || 0;
189
- delete this.minValue;
190
- delete this.maxValue;
191
- delete this.minValuePadded;
192
- delete this.maxValuePadded;
193
- this.stacks = {};
194
- this.decode = decode;
195
- this.minLabelDistanceFormatOverride = minLabelDistanceFormatOverride;
196
- this.format = format;
197
- }
198
-
199
- decodeValue(date) {
200
- if (date instanceof Date) return date.getTime();
201
-
202
- switch (typeof date) {
203
- case "string":
204
- let v = this.dateCache[date];
205
- if (!v) {
206
- if (this.decode) date = this.decode(date);
207
- v = this.dateCache[date] = parseDateInvariant(date).getTime();
208
- }
209
- return v;
210
-
211
- case "number":
212
- return parseDateInvariant(date).getTime();
213
- }
214
- }
215
-
216
- encodeValue(v) {
217
- return new Date(v).toISOString();
218
- }
219
-
220
- getFormat(unit, scale) {
221
- switch (unit) {
222
- case "year":
223
- return "datetime;yyyy";
224
-
225
- case "month":
226
- if (new Date(scale.min).getFullYear() != new Date(scale.max).getFullYear()) return "yearOrMonth";
227
- return "datetime;yyyy MMM";
228
-
229
- case "week":
230
- return "datetime;MMMdd";
231
-
232
- case "day":
233
- if (
234
- new Date(scale.min).getFullYear() != new Date(scale.max).getFullYear() ||
235
- new Date(scale.min).getMonth() != new Date(scale.max).getMonth()
236
- )
237
- return "monthOrDay";
238
-
239
- return TimeFormats.shortMonthDate;
240
-
241
- case "hour":
242
- return "datetime;HH mm n";
243
-
244
- case "minute":
245
- return "datetime;HH mm n";
246
-
247
- case "second":
248
- return "datetime;mm ss";
249
-
250
- default:
251
- return TimeFormats.fullDateAndTime;
252
- }
253
- }
254
-
255
- map(v, offset = 0) {
256
- return this.origin + (this.decodeValue(v) + offset - this.scale.min + this.scale.minPadding) * this.scale.factor;
257
- }
258
-
259
- constrainValue(v) {
260
- return Math.max(this.scale.min, Math.min(this.scale.max, v));
261
- }
262
-
263
- trackValue(v, offset = 0, constrain = false) {
264
- let value = (v - this.origin) / this.scale.factor - offset + this.scale.min - this.scale.minPadding;
265
- if (constrain) value = this.constrainValue(value);
266
- return value;
267
- }
268
-
269
- hash() {
270
- let r = {
271
- origin: this.origin,
272
- factor: this.scale.factor,
273
- min: this.scale.min,
274
- max: this.scale.max,
275
- minPadding: this.scale.minPadding,
276
- maxPadding: this.scale.maxPadding,
277
- };
278
- r.stacks = Object.keys(this.stacks)
279
- .map((s) => this.stacks[s].info.join(","))
280
- .join(":");
281
- return r;
282
- }
283
-
284
- isSame(x) {
285
- let hash = this.hash();
286
- let same = x && !Object.keys(hash).some((k) => x[k] !== hash[k]);
287
- this.shouldUpdate = !same;
288
- return same;
289
- }
290
-
291
- measure(a, b) {
292
- this.a = a;
293
- this.b = b;
294
-
295
- for (let s in this.stacks) {
296
- let info = this.stacks[s].measure(this.normalized);
297
- let [min, max, invalid] = info;
298
- if (this.minValue == null || min < this.minValue) this.minValue = min;
299
- if (this.max == null || max > this.maxValue) this.maxValue = max;
300
- this.stacks[s].info = info;
301
- }
302
-
303
- if (this.min == null) {
304
- if (this.minValue != null) this.min = this.minValue;
305
- else this.min = 0;
306
- }
307
-
308
- if (this.max == null) {
309
- if (this.maxValue != null) this.max = this.maxValue;
310
- else this.max = this.normalized ? 1 : 100;
311
- }
312
-
313
- this.origin = this.inverted ? this.b : this.a;
314
-
315
- this.calculateTicks();
316
- if (this.scale == null) {
317
- this.scale = this.getScale();
318
- }
319
- }
320
-
321
- getTimezoneOffset(date) {
322
- return date.getTimezoneOffset() * 60 * 1000;
323
- }
324
-
325
- getScale(tickSize, measure, minRange = 1000) {
326
- let { min, max, upperDeadZone, lowerDeadZone } = this;
327
-
328
- let smin = min;
329
- let smax = max;
330
-
331
- if (tickSize) {
332
- let minDate = new Date(min);
333
- let maxDate = new Date(max);
334
-
335
- switch (measure) {
336
- case "second":
337
- case "minute":
338
- case "hour":
339
- case "day":
340
- default:
341
- let minOffset = this.getTimezoneOffset(minDate);
342
- let maxOffset = this.getTimezoneOffset(maxDate);
343
- let mondayOffset = 4 * milliSeconds.day; //new Date(0).getDay() => 4
344
- smin = Math.floor((smin - minOffset - mondayOffset) / tickSize) * tickSize + minOffset + mondayOffset;
345
- smax = Math.ceil((smax - maxOffset - mondayOffset) / tickSize) * tickSize + maxOffset + mondayOffset;
346
- break;
347
-
348
- case "month":
349
- tickSize /= milliSeconds.month;
350
- let minMonth = monthNumber(minDate);
351
- let maxMonth = monthNumber(maxDate);
352
- minMonth = Math.floor(minMonth / tickSize) * tickSize;
353
- maxMonth = Math.ceil(maxMonth / tickSize) * tickSize;
354
- smin = new Date(Math.floor(minMonth / 12), minMonth % 12, 1).getTime();
355
- smax = new Date(Math.floor(maxMonth / 12), maxMonth % 12, 1).getTime();
356
- break;
357
-
358
- case "year":
359
- tickSize /= milliSeconds.year;
360
- let minYear = yearNumber(minDate);
361
- let maxYear = yearNumber(maxDate);
362
- minYear = Math.floor(minYear / tickSize) * tickSize;
363
- maxYear = Math.ceil(maxYear / tickSize) * tickSize;
364
- smin = new Date(minYear, 0, 1).getTime();
365
- smax = new Date(maxYear, 0, 1).getTime();
366
- break;
367
- }
368
- } else {
369
- if (this.minValue == min) smin = this.minValuePadded;
370
- if (this.maxValue == max) smax = this.maxValuePadded;
371
- }
372
-
373
- if (smax - smin < minRange) {
374
- let delta = (minRange - (smax - smin)) / 2;
375
- smin -= delta;
376
- smax += delta;
377
- }
378
-
379
- //padding should be activated only if using min/max obtained from the data
380
- let minPadding = this.minValue === min ? Math.max(0, smin - this.minValuePadded) : 0;
381
- let maxPadding = this.maxValue === max ? Math.max(0, this.maxValuePadded - smax) : 0;
382
-
383
- let factor = smin < smax ? Math.abs(this.b - this.a) / (smax - smin + minPadding + maxPadding) : 0;
384
- if (factor > 0 && (upperDeadZone > 0 || lowerDeadZone > 0)) {
385
- smin -= lowerDeadZone / factor;
386
- smax += upperDeadZone / factor;
387
- minPadding = this.minValuePadded != null ? Math.max(0, smin - this.minValuePadded) : 0;
388
- maxPadding = this.maxValuePadded != null ? Math.max(0, this.maxValuePadded - smax) : 0;
389
- factor = smin < smax ? Math.abs(this.b - this.a) / (smax - smin + minPadding + maxPadding) : 0;
390
- }
391
-
392
- let sign = this.b > this.a ? 1 : -1;
393
-
394
- return {
395
- factor: sign * (this.inverted ? -factor : factor),
396
- min: smin,
397
- max: smax,
398
- minPadding,
399
- maxPadding,
400
- };
401
- }
402
-
403
- acknowledge(value, width = 0, offset = 0) {
404
- value = this.decodeValue(value);
405
- if (this.minValue == null || value + offset - width / 2 < this.minValuePadded) {
406
- this.minValue = value;
407
- this.minValuePadded = value + offset - width / 2;
408
- }
409
- if (this.maxValue == null || value + offset + width / 2 > this.maxValuePadded) {
410
- this.maxValue = value;
411
- this.maxValuePadded = value + offset + width / 2;
412
- }
413
- }
414
-
415
- getStack(name) {
416
- let s = this.stacks[name];
417
- if (!s) s = this.stacks[name] = new Stack();
418
- return s;
419
- }
420
-
421
- stacknowledge(name, ordinal, value) {
422
- return this.getStack(name).acknowledge(ordinal, value);
423
- }
424
-
425
- stack(name, ordinal, value) {
426
- let v = this.getStack(name).stack(ordinal, value);
427
- return v != null ? this.map(v) : null;
428
- }
429
-
430
- findTickSize(minPxDist) {
431
- return this.tickSizes.find(({ size, noLabels }) => !noLabels && size * Math.abs(this.scale.factor) >= minPxDist);
432
- }
433
-
434
- getTickSizes() {
435
- return this.tickSizes;
436
- }
437
-
438
- calculateTicks() {
439
- let minReached = false;
440
-
441
- let minRange = 1000;
442
-
443
- for (let unit in milliSeconds) {
444
- if (!minReached) {
445
- if (unit == this.minTickUnit) minReached = true;
446
- else continue;
447
- }
448
-
449
- let unitSize = milliSeconds[unit];
450
- let divisions = this.tickDivisions[unit];
451
-
452
- if (this.tickSizes.length > 0) {
453
- //add ticks from higher levels
454
- this.tickSizes.push(...divisions[0].map((s) => ({ size: s * unitSize, measure: unit })));
455
- break;
456
- }
457
-
458
- let bestLabelDistance = Infinity;
459
- let bestMinLabelDistance = this.minLabelDistance;
460
- let bestTicks = [];
461
- let bestScale = null;
462
- let bestFormat = null;
463
-
464
- this.tickMeasure = unit;
465
-
466
- for (let i = 0; i < divisions.length; i++) {
467
- let divs = divisions[i];
468
- for (let d = 0; d < divs.length; d++) {
469
- //if (useSnapToTicks && d < Math.min(divs.length - 1, this.snapToTicks)) continue;
470
- let tickSize = divs[d] * unitSize;
471
- let scale = this.getScale(null, unit, tickSize);
472
- let format = this.format ?? this.getFormat(unit, scale);
473
- let minLabelDistance = this.minLabelDistanceFormatOverride[format] ?? this.minLabelDistance;
474
- let labelDistance = tickSize * Math.abs(scale.factor);
475
- if (labelDistance >= minLabelDistance && labelDistance < bestLabelDistance) {
476
- bestScale = scale;
477
- bestTicks = divs.map((s) => s * unitSize);
478
- bestLabelDistance = labelDistance;
479
- bestFormat = format;
480
- bestMinLabelDistance = minLabelDistance;
481
- minRange = tickSize;
482
- }
483
- }
484
- }
485
-
486
- this.scale = bestScale;
487
- this.tickSizes = bestTicks
488
- .filter((ts) => ts * Math.abs(bestScale.factor) >= this.minTickDistance)
489
- .map((size) => ({ size, measure: this.tickMeasure }));
490
- this.resolvedFormat = bestFormat;
491
- this.resolvedMinLabelDistance = bestMinLabelDistance;
492
- }
493
-
494
- let lowerTickUnit = null;
495
- switch (this.tickMeasure) {
496
- case "year":
497
- lowerTickUnit = "month";
498
- break;
499
- case "month":
500
- lowerTickUnit = "day";
501
- break;
502
- case "week":
503
- lowerTickUnit = "day";
504
- break;
505
- case "day":
506
- lowerTickUnit = "hour";
507
- break;
508
- case "hour":
509
- lowerTickUnit = "minute";
510
- break;
511
- case "minute":
512
- lowerTickUnit = "second";
513
- break;
514
- }
515
-
516
- if (lowerTickUnit && this.minTickUnit && milliSeconds[lowerTickUnit] < milliSeconds[this.minTickUnit])
517
- lowerTickUnit = this.minTickUnit == this.tickMeasure ? null : this.minTickUnit;
518
-
519
- if (lowerTickUnit != null && this.scale) {
520
- let bestMinorTickSize = Infinity;
521
- let divisions = this.tickDivisions[lowerTickUnit];
522
- let unitSize = milliSeconds[lowerTickUnit];
523
- for (let i = 0; i < divisions.length; i++) {
524
- let divs = divisions[i];
525
- for (let d = 0; d < divs.length; d++) {
526
- let tickSize = divs[d] * unitSize;
527
- if (tickSize * Math.abs(this.scale.factor) >= this.minTickDistance && tickSize < bestMinorTickSize) {
528
- bestMinorTickSize = tickSize;
529
- }
530
- }
531
- }
532
- if (bestMinorTickSize != Infinity) {
533
- this.tickSizes.unshift({ size: bestMinorTickSize, measure: lowerTickUnit, noLabels: true });
534
- if (this.tickSizes.length > 1) {
535
- let labelStep = this.tickSizes[1].size;
536
- let lowerScale = this.getScale(null, lowerTickUnit, minRange);
537
- if (lowerScale.max - lowerScale.min >= labelStep) this.scale = lowerScale;
538
- }
539
- }
540
- }
541
-
542
- if (isNumber(this.snapToTicks) && this.snapToTicks >= 0 && this.tickSizes.length > 0) {
543
- let tickSize = this.tickSizes[Math.min(this.tickSizes.length - 1, this.snapToTicks)];
544
- this.scale = this.getScale(tickSize.size, tickSize.measure, minRange);
545
- }
546
- }
547
-
548
- getTicks(tickSizes) {
549
- return tickSizes.map(({ size, measure }) => {
550
- let result = [],
551
- start,
552
- end,
553
- minDate,
554
- maxDate;
555
- if (measure == "year") {
556
- size /= milliSeconds.year;
557
- minDate = new Date(this.scale.min - this.scale.minPadding);
558
- maxDate = new Date(this.scale.max + this.scale.maxPadding);
559
- start = Math.ceil(yearNumber(minDate) / size) * size;
560
- end = Math.floor(yearNumber(maxDate) / size) * size;
561
- for (let i = start; i <= end; i += size) result.push(new Date(i, 0, 1).getTime());
562
- } else if (measure == "month") {
563
- size /= milliSeconds.month;
564
- minDate = new Date(this.scale.min - this.scale.minPadding);
565
- maxDate = new Date(this.scale.max + this.scale.maxPadding);
566
- start = Math.ceil(monthNumber(minDate) / size) * size;
567
- end = Math.floor(monthNumber(maxDate) / size) * size;
568
- for (let i = start; i <= end; i += size) result.push(new Date(Math.floor(i / 12), i % 12, 1).getTime());
569
- } else if (measure == "day" || measure == "week") {
570
- let multiplier = measure == "week" ? 7 : 1;
571
- size /= milliSeconds.day;
572
- minDate = new Date(this.scale.min - this.scale.minPadding);
573
- maxDate = new Date(this.scale.max + this.scale.maxPadding);
574
- let date = zeroTime(minDate);
575
- while (date.getTime() < minDate.getTime()) date.setDate(date.getDate() + 1);
576
- if (measure == "week") {
577
- //start on monday
578
- while (date.getDay() != 1) {
579
- date.setDate(date.getDate() + 1);
580
- }
581
- }
582
- while (date.getTime() <= maxDate.getTime()) {
583
- result.push(date);
584
- date = new Date(date);
585
- date.setDate(date.getDate() + multiplier);
586
- }
587
- } else {
588
- let minOffset = this.getTimezoneOffset(new Date(this.scale.min - this.scale.minPadding));
589
- let mondayOffset = 4 * milliSeconds.day;
590
- let date =
591
- Math.ceil((this.scale.min - this.scale.minPadding - minOffset - mondayOffset) / size) * size +
592
- minOffset +
593
- mondayOffset;
594
- while (date <= this.scale.max + this.scale.maxPadding) {
595
- result.push(date);
596
- date += size;
597
- }
598
- }
599
- return result;
600
- });
601
- }
602
-
603
- mapGridlines() {
604
- if (this.tickSizes.length == 0) return [];
605
- return this.getTicks([this.tickSizes[0]])[0].map((x) => this.map(x));
606
- }
607
-
608
- book() {
609
- Console.warn("TimeAxis does not support the autoSize flag for column and bar graphs.");
610
- }
611
- }
1
+ import { Axis } from "./Axis";
2
+ import { VDOM } from "../../ui/Widget";
3
+ import { Stack } from "./Stack";
4
+ import { Format } from "../../ui/Format";
5
+ import { isNumber } from "../../util/isNumber";
6
+ import { zeroTime } from "../../util/date/zeroTime";
7
+ import { Console } from "../../util/Console";
8
+ import { parseDateInvariant } from "../../util";
9
+
10
+ Format.registerFactory("yearOrMonth", (format) => {
11
+ let year = Format.parse("datetime;yyyy");
12
+ let month = Format.parse("datetime;MMM");
13
+ return function (date) {
14
+ let d = parseDateInvariant(date);
15
+ if (d.getMonth() == 0) return year(d);
16
+ else return month(d);
17
+ };
18
+ });
19
+
20
+ Format.registerFactory("monthOrDay", (format) => {
21
+ let month = Format.parse("datetime;MMM");
22
+ let day = Format.parse("datetime;dd");
23
+ return function (date) {
24
+ let d = parseDateInvariant(date);
25
+ if (d.getDate() == 1) return month(d);
26
+ else return day(d);
27
+ };
28
+ });
29
+
30
+ export class TimeAxis extends Axis {
31
+ init() {
32
+ if (this.labelAnchor == "auto") this.labelAnchor = this.vertical ? (this.secondary ? "start" : "end") : "start";
33
+
34
+ if (this.labelDx == "auto") this.labelDx = this.vertical ? 0 : "5px";
35
+
36
+ if (this.deadZone) {
37
+ this.lowerDeadZone = this.deadZone;
38
+ this.upperDeadZone = this.deadZone;
39
+ }
40
+
41
+ this.minLabelDistanceFormatOverride = {
42
+ ...this.minLabelDistanceFormatOverrideDefaults,
43
+ ...this.minLabelDistanceFormatOverride,
44
+ };
45
+
46
+ super.init();
47
+ }
48
+
49
+ declareData() {
50
+ super.declareData(...arguments, {
51
+ anchors: undefined,
52
+ min: undefined,
53
+ max: undefined,
54
+ inverted: undefined,
55
+ lowerDeadZone: undefined,
56
+ upperDeadZone: undefined,
57
+ });
58
+ }
59
+
60
+ initInstance(context, instance) {
61
+ instance.calculator = new TimeScale();
62
+ }
63
+
64
+ explore(context, instance) {
65
+ super.explore(context, instance);
66
+ let { min, max, normalized, inverted, lowerDeadZone, upperDeadZone } = instance.data;
67
+ instance.calculator.reset(
68
+ min,
69
+ max,
70
+ this.snapToTicks,
71
+ this.tickDivisions,
72
+ Math.max(1, this.minTickDistance),
73
+ Math.max(1, this.minLabelDistance),
74
+ normalized,
75
+ inverted,
76
+ this.minTickUnit,
77
+ lowerDeadZone,
78
+ upperDeadZone,
79
+ this.decode,
80
+ this.useLabelDistanceFormatOverrides ? this.minLabelDistanceFormatOverride : {},
81
+ this.format,
82
+ );
83
+ }
84
+
85
+ render(context, instance, key) {
86
+ let { data, cached, calculator } = instance;
87
+
88
+ cached.axis = calculator.hash();
89
+
90
+ if (!data.bounds.valid()) return null;
91
+
92
+ let format = calculator.resolvedFormat;
93
+ let minLabelDistance = calculator.resolvedMinLabelDistance;
94
+ let formatter = Format.parse(format);
95
+
96
+ return (
97
+ <g key={key} className={data.classNames} style={data.style}>
98
+ {this.renderTicksAndLabels(context, instance, formatter, minLabelDistance)}
99
+ </g>
100
+ );
101
+ }
102
+ }
103
+
104
+ Axis.alias("time", TimeAxis);
105
+
106
+ TimeAxis.prototype.baseClass = "timeaxis";
107
+ TimeAxis.prototype.tickDivisions = {
108
+ second: [[1, 5, 15, 30]],
109
+ minute: [[1, 5, 15, 30]],
110
+ hour: [
111
+ [1, 2, 4, 8],
112
+ [1, 3, 6, 12],
113
+ ],
114
+ day: [[1]],
115
+ week: [[1]],
116
+ month: [[1, 3, 6]],
117
+ year: [
118
+ [1, 2, 10],
119
+ [1, 5, 10],
120
+ [5, 10, 50],
121
+ [10, 50, 100],
122
+ ],
123
+ };
124
+
125
+ const TimeFormats = {
126
+ fullDateAndTime: "datetime;yyyy MMM dd HH mm ss n",
127
+ shortMonthDate: "datetime;yyyy MMM dd",
128
+ };
129
+
130
+ TimeAxis.prototype.snapToTicks = 0;
131
+ TimeAxis.prototype.tickSize = 15;
132
+ TimeAxis.prototype.minLabelDistance = 60;
133
+ TimeAxis.prototype.minTickDistance = 60;
134
+ TimeAxis.prototype.minTickUnit = "second";
135
+ TimeAxis.prototype.useLabelDistanceFormatOverrides = false;
136
+ TimeAxis.prototype.minLabelDistanceFormatOverrideDefaults = {
137
+ [TimeFormats.fullDateAndTime]: 150,
138
+ [TimeFormats.shortMonthDate]: 90,
139
+ };
140
+
141
+ function monthNumber(date) {
142
+ return date.getFullYear() * 12 + date.getMonth() + (date.getDate() - 1) / 31;
143
+ }
144
+
145
+ function yearNumber(date) {
146
+ return monthNumber(date) / 12;
147
+ }
148
+
149
+ const milliSeconds = {
150
+ second: 1000,
151
+ minute: 60 * 1000,
152
+ hour: 3600 * 1000,
153
+ day: 3600 * 24 * 1000,
154
+ week: 3600 * 24 * 7 * 1000,
155
+ month: 3600 * 24 * 30 * 1000,
156
+ year: 3600 * 24 * 365 * 1000,
157
+ };
158
+
159
+ class TimeScale {
160
+ reset(
161
+ min,
162
+ max,
163
+ snapToTicks,
164
+ tickDivisions,
165
+ minTickDistance,
166
+ minLabelDistance,
167
+ normalized,
168
+ inverted,
169
+ minTickUnit,
170
+ lowerDeadZone,
171
+ upperDeadZone,
172
+ decode,
173
+ minLabelDistanceFormatOverride,
174
+ format,
175
+ ) {
176
+ this.dateCache = {};
177
+ this.min = min != null ? this.decodeValue(min) : null;
178
+ this.max = max != null ? this.decodeValue(max) : null;
179
+ this.snapToTicks = snapToTicks;
180
+ this.tickDivisions = tickDivisions;
181
+ this.minLabelDistance = minLabelDistance;
182
+ this.minTickDistance = minTickDistance;
183
+ this.tickSizes = [];
184
+ this.normalized = normalized;
185
+ this.minTickUnit = minTickUnit;
186
+ this.inverted = inverted;
187
+ this.lowerDeadZone = lowerDeadZone || 0;
188
+ this.upperDeadZone = upperDeadZone || 0;
189
+ delete this.minValue;
190
+ delete this.maxValue;
191
+ delete this.minValuePadded;
192
+ delete this.maxValuePadded;
193
+ this.stacks = {};
194
+ this.decode = decode;
195
+ this.minLabelDistanceFormatOverride = minLabelDistanceFormatOverride;
196
+ this.format = format;
197
+ }
198
+
199
+ decodeValue(date) {
200
+ if (date instanceof Date) return date.getTime();
201
+
202
+ switch (typeof date) {
203
+ case "string":
204
+ let v = this.dateCache[date];
205
+ if (!v) {
206
+ if (this.decode) date = this.decode(date);
207
+ v = this.dateCache[date] = parseDateInvariant(date).getTime();
208
+ }
209
+ return v;
210
+
211
+ case "number":
212
+ return parseDateInvariant(date).getTime();
213
+ }
214
+ }
215
+
216
+ encodeValue(v) {
217
+ return new Date(v).toISOString();
218
+ }
219
+
220
+ getFormat(unit, scale) {
221
+ switch (unit) {
222
+ case "year":
223
+ return "datetime;yyyy";
224
+
225
+ case "month":
226
+ if (new Date(scale.min).getFullYear() != new Date(scale.max).getFullYear()) return "yearOrMonth";
227
+ return "datetime;yyyy MMM";
228
+
229
+ case "week":
230
+ return "datetime;MMMdd";
231
+
232
+ case "day":
233
+ if (
234
+ new Date(scale.min).getFullYear() != new Date(scale.max).getFullYear() ||
235
+ new Date(scale.min).getMonth() != new Date(scale.max).getMonth()
236
+ )
237
+ return "monthOrDay";
238
+
239
+ return TimeFormats.shortMonthDate;
240
+
241
+ case "hour":
242
+ return "datetime;HH mm n";
243
+
244
+ case "minute":
245
+ return "datetime;HH mm n";
246
+
247
+ case "second":
248
+ return "datetime;mm ss";
249
+
250
+ default:
251
+ return TimeFormats.fullDateAndTime;
252
+ }
253
+ }
254
+
255
+ map(v, offset = 0) {
256
+ return this.origin + (this.decodeValue(v) + offset - this.scale.min + this.scale.minPadding) * this.scale.factor;
257
+ }
258
+
259
+ constrainValue(v) {
260
+ return Math.max(this.scale.min, Math.min(this.scale.max, v));
261
+ }
262
+
263
+ trackValue(v, offset = 0, constrain = false) {
264
+ let value = (v - this.origin) / this.scale.factor - offset + this.scale.min - this.scale.minPadding;
265
+ if (constrain) value = this.constrainValue(value);
266
+ return value;
267
+ }
268
+
269
+ hash() {
270
+ let r = {
271
+ origin: this.origin,
272
+ factor: this.scale.factor,
273
+ min: this.scale.min,
274
+ max: this.scale.max,
275
+ minPadding: this.scale.minPadding,
276
+ maxPadding: this.scale.maxPadding,
277
+ };
278
+ r.stacks = Object.keys(this.stacks)
279
+ .map((s) => this.stacks[s].info.join(","))
280
+ .join(":");
281
+ return r;
282
+ }
283
+
284
+ isSame(x) {
285
+ let hash = this.hash();
286
+ let same = x && !Object.keys(hash).some((k) => x[k] !== hash[k]);
287
+ this.shouldUpdate = !same;
288
+ return same;
289
+ }
290
+
291
+ measure(a, b) {
292
+ this.a = a;
293
+ this.b = b;
294
+
295
+ for (let s in this.stacks) {
296
+ let info = this.stacks[s].measure(this.normalized);
297
+ let [min, max, invalid] = info;
298
+ if (this.minValue == null || min < this.minValue) this.minValue = min;
299
+ if (this.max == null || max > this.maxValue) this.maxValue = max;
300
+ this.stacks[s].info = info;
301
+ }
302
+
303
+ if (this.min == null) {
304
+ if (this.minValue != null) this.min = this.minValue;
305
+ else this.min = 0;
306
+ }
307
+
308
+ if (this.max == null) {
309
+ if (this.maxValue != null) this.max = this.maxValue;
310
+ else this.max = this.normalized ? 1 : 100;
311
+ }
312
+
313
+ this.origin = this.inverted ? this.b : this.a;
314
+
315
+ this.calculateTicks();
316
+ if (this.scale == null) {
317
+ this.scale = this.getScale();
318
+ }
319
+ }
320
+
321
+ getTimezoneOffset(date) {
322
+ return date.getTimezoneOffset() * 60 * 1000;
323
+ }
324
+
325
+ getScale(tickSize, measure, minRange = 1000) {
326
+ let { min, max, upperDeadZone, lowerDeadZone } = this;
327
+
328
+ let smin = min;
329
+ let smax = max;
330
+
331
+ if (tickSize) {
332
+ let minDate = new Date(min);
333
+ let maxDate = new Date(max);
334
+
335
+ switch (measure) {
336
+ case "second":
337
+ case "minute":
338
+ case "hour":
339
+ case "day":
340
+ default:
341
+ let minOffset = this.getTimezoneOffset(minDate);
342
+ let maxOffset = this.getTimezoneOffset(maxDate);
343
+ let mondayOffset = 4 * milliSeconds.day; //new Date(0).getDay() => 4
344
+ smin = Math.floor((smin - minOffset - mondayOffset) / tickSize) * tickSize + minOffset + mondayOffset;
345
+ smax = Math.ceil((smax - maxOffset - mondayOffset) / tickSize) * tickSize + maxOffset + mondayOffset;
346
+ break;
347
+
348
+ case "month":
349
+ tickSize /= milliSeconds.month;
350
+ let minMonth = monthNumber(minDate);
351
+ let maxMonth = monthNumber(maxDate);
352
+ minMonth = Math.floor(minMonth / tickSize) * tickSize;
353
+ maxMonth = Math.ceil(maxMonth / tickSize) * tickSize;
354
+ smin = new Date(Math.floor(minMonth / 12), minMonth % 12, 1).getTime();
355
+ smax = new Date(Math.floor(maxMonth / 12), maxMonth % 12, 1).getTime();
356
+ break;
357
+
358
+ case "year":
359
+ tickSize /= milliSeconds.year;
360
+ let minYear = yearNumber(minDate);
361
+ let maxYear = yearNumber(maxDate);
362
+ minYear = Math.floor(minYear / tickSize) * tickSize;
363
+ maxYear = Math.ceil(maxYear / tickSize) * tickSize;
364
+ smin = new Date(minYear, 0, 1).getTime();
365
+ smax = new Date(maxYear, 0, 1).getTime();
366
+ break;
367
+ }
368
+ } else {
369
+ if (this.minValue == min) smin = this.minValuePadded;
370
+ if (this.maxValue == max) smax = this.maxValuePadded;
371
+ }
372
+
373
+ if (smax - smin < minRange) {
374
+ let delta = (minRange - (smax - smin)) / 2;
375
+ smin -= delta;
376
+ smax += delta;
377
+ }
378
+
379
+ //padding should be activated only if using min/max obtained from the data
380
+ let minPadding = this.minValue === min ? Math.max(0, smin - this.minValuePadded) : 0;
381
+ let maxPadding = this.maxValue === max ? Math.max(0, this.maxValuePadded - smax) : 0;
382
+
383
+ let factor = smin < smax ? Math.abs(this.b - this.a) / (smax - smin + minPadding + maxPadding) : 0;
384
+ if (factor > 0 && (upperDeadZone > 0 || lowerDeadZone > 0)) {
385
+ smin -= lowerDeadZone / factor;
386
+ smax += upperDeadZone / factor;
387
+ minPadding = this.minValuePadded != null ? Math.max(0, smin - this.minValuePadded) : 0;
388
+ maxPadding = this.maxValuePadded != null ? Math.max(0, this.maxValuePadded - smax) : 0;
389
+ factor = smin < smax ? Math.abs(this.b - this.a) / (smax - smin + minPadding + maxPadding) : 0;
390
+ }
391
+
392
+ let sign = this.b > this.a ? 1 : -1;
393
+
394
+ return {
395
+ factor: sign * (this.inverted ? -factor : factor),
396
+ min: smin,
397
+ max: smax,
398
+ minPadding,
399
+ maxPadding,
400
+ };
401
+ }
402
+
403
+ acknowledge(value, width = 0, offset = 0) {
404
+ value = this.decodeValue(value);
405
+ if (this.minValue == null || value + offset - width / 2 < this.minValuePadded) {
406
+ this.minValue = value;
407
+ this.minValuePadded = value + offset - width / 2;
408
+ }
409
+ if (this.maxValue == null || value + offset + width / 2 > this.maxValuePadded) {
410
+ this.maxValue = value;
411
+ this.maxValuePadded = value + offset + width / 2;
412
+ }
413
+ }
414
+
415
+ getStack(name) {
416
+ let s = this.stacks[name];
417
+ if (!s) s = this.stacks[name] = new Stack();
418
+ return s;
419
+ }
420
+
421
+ stacknowledge(name, ordinal, value) {
422
+ return this.getStack(name).acknowledge(ordinal, value);
423
+ }
424
+
425
+ stack(name, ordinal, value) {
426
+ let v = this.getStack(name).stack(ordinal, value);
427
+ return v != null ? this.map(v) : null;
428
+ }
429
+
430
+ findTickSize(minPxDist) {
431
+ return this.tickSizes.find(({ size, noLabels }) => !noLabels && size * Math.abs(this.scale.factor) >= minPxDist);
432
+ }
433
+
434
+ getTickSizes() {
435
+ return this.tickSizes;
436
+ }
437
+
438
+ calculateTicks() {
439
+ let minReached = false;
440
+
441
+ let minRange = 1000;
442
+
443
+ for (let unit in milliSeconds) {
444
+ if (!minReached) {
445
+ if (unit == this.minTickUnit) minReached = true;
446
+ else continue;
447
+ }
448
+
449
+ let unitSize = milliSeconds[unit];
450
+ let divisions = this.tickDivisions[unit];
451
+
452
+ if (this.tickSizes.length > 0) {
453
+ //add ticks from higher levels
454
+ this.tickSizes.push(...divisions[0].map((s) => ({ size: s * unitSize, measure: unit })));
455
+ break;
456
+ }
457
+
458
+ let bestLabelDistance = Infinity;
459
+ let bestMinLabelDistance = this.minLabelDistance;
460
+ let bestTicks = [];
461
+ let bestScale = null;
462
+ let bestFormat = null;
463
+
464
+ this.tickMeasure = unit;
465
+
466
+ for (let i = 0; i < divisions.length; i++) {
467
+ let divs = divisions[i];
468
+ for (let d = 0; d < divs.length; d++) {
469
+ //if (useSnapToTicks && d < Math.min(divs.length - 1, this.snapToTicks)) continue;
470
+ let tickSize = divs[d] * unitSize;
471
+ let scale = this.getScale(null, unit, tickSize);
472
+ let format = this.format ?? this.getFormat(unit, scale);
473
+ let minLabelDistance = this.minLabelDistanceFormatOverride[format] ?? this.minLabelDistance;
474
+ let labelDistance = tickSize * Math.abs(scale.factor);
475
+ if (labelDistance >= minLabelDistance && labelDistance < bestLabelDistance) {
476
+ bestScale = scale;
477
+ bestTicks = divs.map((s) => s * unitSize);
478
+ bestLabelDistance = labelDistance;
479
+ bestFormat = format;
480
+ bestMinLabelDistance = minLabelDistance;
481
+ minRange = tickSize;
482
+ }
483
+ }
484
+ }
485
+
486
+ this.scale = bestScale;
487
+ this.tickSizes = bestTicks
488
+ .filter((ts) => ts * Math.abs(bestScale.factor) >= this.minTickDistance)
489
+ .map((size) => ({ size, measure: this.tickMeasure }));
490
+ this.resolvedFormat = bestFormat;
491
+ this.resolvedMinLabelDistance = bestMinLabelDistance;
492
+ }
493
+
494
+ let lowerTickUnit = null;
495
+ switch (this.tickMeasure) {
496
+ case "year":
497
+ lowerTickUnit = "month";
498
+ break;
499
+ case "month":
500
+ lowerTickUnit = "day";
501
+ break;
502
+ case "week":
503
+ lowerTickUnit = "day";
504
+ break;
505
+ case "day":
506
+ lowerTickUnit = "hour";
507
+ break;
508
+ case "hour":
509
+ lowerTickUnit = "minute";
510
+ break;
511
+ case "minute":
512
+ lowerTickUnit = "second";
513
+ break;
514
+ }
515
+
516
+ if (lowerTickUnit && this.minTickUnit && milliSeconds[lowerTickUnit] < milliSeconds[this.minTickUnit])
517
+ lowerTickUnit = this.minTickUnit == this.tickMeasure ? null : this.minTickUnit;
518
+
519
+ if (lowerTickUnit != null && this.scale) {
520
+ let bestMinorTickSize = Infinity;
521
+ let divisions = this.tickDivisions[lowerTickUnit];
522
+ let unitSize = milliSeconds[lowerTickUnit];
523
+ for (let i = 0; i < divisions.length; i++) {
524
+ let divs = divisions[i];
525
+ for (let d = 0; d < divs.length; d++) {
526
+ let tickSize = divs[d] * unitSize;
527
+ if (tickSize * Math.abs(this.scale.factor) >= this.minTickDistance && tickSize < bestMinorTickSize) {
528
+ bestMinorTickSize = tickSize;
529
+ }
530
+ }
531
+ }
532
+ if (bestMinorTickSize != Infinity) {
533
+ this.tickSizes.unshift({ size: bestMinorTickSize, measure: lowerTickUnit, noLabels: true });
534
+ if (this.tickSizes.length > 1) {
535
+ let labelStep = this.tickSizes[1].size;
536
+ let lowerScale = this.getScale(null, lowerTickUnit, minRange);
537
+ if (lowerScale.max - lowerScale.min >= labelStep) this.scale = lowerScale;
538
+ }
539
+ }
540
+ }
541
+
542
+ if (isNumber(this.snapToTicks) && this.snapToTicks >= 0 && this.tickSizes.length > 0) {
543
+ let tickSize = this.tickSizes[Math.min(this.tickSizes.length - 1, this.snapToTicks)];
544
+ this.scale = this.getScale(tickSize.size, tickSize.measure, minRange);
545
+ }
546
+ }
547
+
548
+ getTicks(tickSizes) {
549
+ return tickSizes.map(({ size, measure }) => {
550
+ let result = [],
551
+ start,
552
+ end,
553
+ minDate,
554
+ maxDate;
555
+ if (measure == "year") {
556
+ size /= milliSeconds.year;
557
+ minDate = new Date(this.scale.min - this.scale.minPadding);
558
+ maxDate = new Date(this.scale.max + this.scale.maxPadding);
559
+ start = Math.ceil(yearNumber(minDate) / size) * size;
560
+ end = Math.floor(yearNumber(maxDate) / size) * size;
561
+ for (let i = start; i <= end; i += size) result.push(new Date(i, 0, 1).getTime());
562
+ } else if (measure == "month") {
563
+ size /= milliSeconds.month;
564
+ minDate = new Date(this.scale.min - this.scale.minPadding);
565
+ maxDate = new Date(this.scale.max + this.scale.maxPadding);
566
+ start = Math.ceil(monthNumber(minDate) / size) * size;
567
+ end = Math.floor(monthNumber(maxDate) / size) * size;
568
+ for (let i = start; i <= end; i += size) result.push(new Date(Math.floor(i / 12), i % 12, 1).getTime());
569
+ } else if (measure == "day" || measure == "week") {
570
+ let multiplier = measure == "week" ? 7 : 1;
571
+ size /= milliSeconds.day;
572
+ minDate = new Date(this.scale.min - this.scale.minPadding);
573
+ maxDate = new Date(this.scale.max + this.scale.maxPadding);
574
+ let date = zeroTime(minDate);
575
+ while (date.getTime() < minDate.getTime()) date.setDate(date.getDate() + 1);
576
+ if (measure == "week") {
577
+ //start on monday
578
+ while (date.getDay() != 1) {
579
+ date.setDate(date.getDate() + 1);
580
+ }
581
+ }
582
+ while (date.getTime() <= maxDate.getTime()) {
583
+ result.push(date);
584
+ date = new Date(date);
585
+ date.setDate(date.getDate() + multiplier);
586
+ }
587
+ } else {
588
+ let minOffset = this.getTimezoneOffset(new Date(this.scale.min - this.scale.minPadding));
589
+ let mondayOffset = 4 * milliSeconds.day;
590
+ let date =
591
+ Math.ceil((this.scale.min - this.scale.minPadding - minOffset - mondayOffset) / size) * size +
592
+ minOffset +
593
+ mondayOffset;
594
+ while (date <= this.scale.max + this.scale.maxPadding) {
595
+ result.push(date);
596
+ date += size;
597
+ }
598
+ }
599
+ return result;
600
+ });
601
+ }
602
+
603
+ mapGridlines() {
604
+ if (this.tickSizes.length == 0) return [];
605
+ return this.getTicks([this.tickSizes[0]])[0].map((x) => this.map(x));
606
+ }
607
+
608
+ book() {
609
+ Console.warn("TimeAxis does not support the autoSize flag for column and bar graphs.");
610
+ }
611
+ }