@deephaven/chart 0.85.10 → 0.85.11

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.
@@ -293,6 +293,17 @@ class ChartUtils {
293
293
  return Number(values[0]) + Number(values[1]) / 60;
294
294
  }
295
295
 
296
+ /**
297
+ * Converts a decimal to a period. e.g 9.5 to '09:30'
298
+ *
299
+ * @param decimal the decimal value to
300
+ */
301
+ static decimalToPeriod(decimal) {
302
+ var hours = Math.floor(decimal);
303
+ var minutes = Math.round((decimal - hours) * 60);
304
+ return "".concat(hours.toString().padStart(2, '0'), ":").concat(minutes.toString().padStart(2, '0'));
305
+ }
306
+
296
307
  /**
297
308
  * Groups an array and returns a map
298
309
  * @param array The object to group
@@ -337,6 +348,48 @@ class ChartUtils {
337
348
  } = settings;
338
349
  return title;
339
350
  }
351
+ static getTimeZoneDiff(calendarTimeZone, formatterTimeZone) {
352
+ return formatterTimeZone ? (calendarTimeZone.standardOffset - formatterTimeZone.standardOffset) / 60 : 0;
353
+ }
354
+
355
+ /**
356
+ * Creates closed periods for a partial holiday.
357
+ *
358
+ * @param holidayPeriods the business periods for the holiday
359
+ * @param calendarPeriods the business periods for the calendar
360
+ * @returns an array of closed ranges for the partial holiday. Should be the ranges during the regular business hours that are _not_ specified by the holiday periods.
361
+ */
362
+ static createClosedRangesForPartialHoliday(holidayPeriods, calendarPeriods) {
363
+ // First restrict the periods to only those that are actual business periods.
364
+ var calendarRanges = calendarPeriods.map(period => [ChartUtils.periodToDecimal(period.open), ChartUtils.periodToDecimal(period.close)]);
365
+ calendarRanges.sort((a, b) => a[0] - b[0]);
366
+ if (calendarRanges.length === 0) {
367
+ calendarRanges.push([0, 24]);
368
+ }
369
+ var holidayRanges = holidayPeriods.map(period => [ChartUtils.periodToDecimal(period.open), ChartUtils.periodToDecimal(period.close)]);
370
+ holidayRanges.sort((a, b) => a[0] - b[0]);
371
+ var closedRanges = [];
372
+
373
+ // Separate index cursor for the holiday ranges
374
+ for (var c = 0; c < calendarRanges.length; c += 1) {
375
+ var calendarRange = calendarRanges[c];
376
+ var lastClose = calendarRange[0];
377
+ for (var h = 0; h < holidayRanges.length; h += 1) {
378
+ var holidayRange = holidayRanges[h];
379
+ if (holidayRange[1] > lastClose && holidayRange[0] < calendarRange[1]) {
380
+ if (holidayRange[0] > lastClose) {
381
+ closedRanges.push([lastClose, holidayRange[0]]);
382
+ }
383
+ // eslint-disable-next-line prefer-destructuring
384
+ lastClose = holidayRange[1];
385
+ }
386
+ }
387
+ if (lastClose < calendarRange[1]) {
388
+ closedRanges.push([lastClose, calendarRange[1]]);
389
+ }
390
+ }
391
+ return closedRanges;
392
+ }
340
393
  constructor(dh) {
341
394
  _defineProperty(this, "dh", void 0);
342
395
  _defineProperty(this, "daysOfWeek", void 0);
@@ -353,7 +406,6 @@ class ChartUtils {
353
406
  * @returns A map of axis layout property names to axis formats
354
407
  */
355
408
  getAxisFormats(figure, formatter) {
356
- var _this = this;
357
409
  var axisFormats = new Map();
358
410
  var nullFormat = {
359
411
  tickformat: null,
@@ -372,74 +424,41 @@ class ChartUtils {
372
424
  sources
373
425
  } = series;
374
426
  var axisSources = sources.filter(source => source.axis);
375
- var _loop = function _loop() {
427
+ for (var k = 0; k < axisSources.length; k += 1) {
376
428
  var source = axisSources[k];
377
429
  var {
378
- axis
430
+ axis: _axis
379
431
  } = source;
380
432
  var {
381
433
  type: axisType
382
- } = axis;
434
+ } = _axis;
383
435
  var typeAxes = axisTypeMap.get(axisType);
384
436
  assertNotNull(typeAxes);
385
- var axisIndex = typeAxes.indexOf(axis);
386
- var axisProperty = _this.getAxisPropertyName(axisType);
437
+ var axisIndex = typeAxes.indexOf(_axis);
438
+ var axisProperty = this.getAxisPropertyName(axisType);
387
439
  if (axisProperty != null) {
388
440
  var axisLayoutProperty = ChartUtils.getAxisLayoutProperty(axisProperty, axisIndex);
389
441
  if (axisFormats.has(axisLayoutProperty)) {
390
442
  log.debug("".concat(axisLayoutProperty, " already added."));
391
443
  } else {
392
444
  log.debug("Adding ".concat(axisLayoutProperty, " to axisFormats."));
393
- var axisFormat = _this.getPlotlyAxisFormat(source, formatter);
445
+ var axisFormat = this.getPlotlyAxisFormat(source, formatter);
394
446
  if (axisFormat === null) {
395
447
  axisFormats.set(axisLayoutProperty, nullFormat);
396
448
  } else {
397
449
  axisFormats.set(axisLayoutProperty, axisFormat);
398
450
  var {
399
451
  businessCalendar
400
- } = axis;
452
+ } = _axis;
401
453
  if (businessCalendar != null) {
402
- var rangebreaks = [];
403
- var {
404
- businessPeriods,
405
- businessDays,
406
- holidays,
407
- timeZone: calendarTimeZone
408
- } = businessCalendar;
409
- var typeFormatter = formatter === null || formatter === void 0 ? void 0 : formatter.getColumnTypeFormatter(BUSINESS_COLUMN_TYPE);
410
- var formatterTimeZone;
411
- if (isDateTimeColumnFormatter(typeFormatter)) {
412
- formatterTimeZone = typeFormatter.dhTimeZone;
413
- }
414
- var timeZoneDiff = formatterTimeZone ? (calendarTimeZone.standardOffset - formatterTimeZone.standardOffset) / 60 : 0;
415
- if (holidays.length > 0) {
416
- rangebreaks.push(..._this.createRangeBreakValuesFromHolidays(holidays, calendarTimeZone, formatterTimeZone));
417
- }
418
- businessPeriods.forEach(period => rangebreaks.push({
419
- pattern: 'hour',
420
- bounds: [ChartUtils.periodToDecimal(period.close) + timeZoneDiff, ChartUtils.periodToDecimal(period.open) + timeZoneDiff]
421
- }));
422
- // If there are seven business days, then there is no weekend
423
- if (businessDays.length < _this.daysOfWeek.length) {
424
- _this.createBoundsFromDays(businessDays).forEach(weekendBounds => rangebreaks.push({
425
- pattern: 'day of week',
426
- bounds: weekendBounds
427
- }));
428
- }
429
- axisFormat.rangebreaks = rangebreaks;
454
+ axisFormat.rangebreaks = this.createRangeBreaksFromBusinessCalendar(businessCalendar, formatter);
430
455
  }
431
456
  if (axisFormats.size === _chart2.axes.length) {
432
- return {
433
- v: axisFormats
434
- };
457
+ return axisFormats;
435
458
  }
436
459
  }
437
460
  }
438
461
  }
439
- };
440
- for (var k = 0; k < axisSources.length; k += 1) {
441
- var _ret = _loop();
442
- if (typeof _ret === "object") return _ret.v;
443
462
  }
444
463
  }
445
464
  }
@@ -657,16 +676,16 @@ class ChartUtils {
657
676
  for (var k = 0; k < sources.length; k += 1) {
658
677
  var source = sources[k];
659
678
  var {
660
- axis: _axis,
679
+ axis: _axis2,
661
680
  type: sourceType
662
681
  } = source;
663
682
  var dataAttributeName = this.getPlotlyProperty(plotStyle, sourceType);
664
683
  set(seriesData, dataAttributeName, []);
665
- var axisProperty = _axis != null ? this.getAxisPropertyName(_axis.type) : null;
684
+ var axisProperty = _axis2 != null ? this.getAxisPropertyName(_axis2.type) : null;
666
685
  if (axisProperty != null) {
667
- var axes = axisTypeMap.get(_axis.type);
686
+ var axes = axisTypeMap.get(_axis2.type);
668
687
  if (axes) {
669
- var axisIndex = axes.indexOf(_axis);
688
+ var axisIndex = axes.indexOf(_axis2);
670
689
  var axisIndexString = axisIndex > 0 ? "".concat(axisIndex + 1) : '';
671
690
  seriesData["".concat(axisProperty, "axis")] = "".concat(axisProperty).concat(axisIndexString);
672
691
  }
@@ -958,26 +977,26 @@ class ChartUtils {
958
977
  assertNotNull(typeAxes);
959
978
  assertNotNull(figureTypeAxes);
960
979
  for (var chartAxisIndex = 0; chartAxisIndex < typeAxes.length; chartAxisIndex += 1) {
961
- var _axis2 = typeAxes[chartAxisIndex];
962
- var figureAxisIndex = figureTypeAxes.indexOf(_axis2);
980
+ var _axis3 = typeAxes[chartAxisIndex];
981
+ var figureAxisIndex = figureTypeAxes.indexOf(_axis3);
963
982
  var axisLayoutProperty = ChartUtils.getAxisLayoutProperty(axisProperty, figureAxisIndex);
964
983
  if (layout[axisLayoutProperty] == null) {
965
984
  layout[axisLayoutProperty] = {};
966
985
  }
967
986
  var layoutAxis = layout[axisLayoutProperty];
968
987
  if (layoutAxis != null) {
969
- this.updateLayoutAxis(layoutAxis, _axis2, chartAxisIndex, axisPositionMap, xAxisSize, yAxisSize, bounds);
988
+ this.updateLayoutAxis(layoutAxis, _axis3, chartAxisIndex, axisPositionMap, xAxisSize, yAxisSize, bounds);
970
989
  var {
971
990
  range: _range,
972
991
  autorange
973
992
  } = layoutAxis;
974
993
  if (axisRangeParser != null && _range !== undefined && (autorange === undefined || autorange === false)) {
975
- var rangeParser = axisRangeParser(_axis2);
994
+ var rangeParser = axisRangeParser(_axis3);
976
995
  var [rangeStart, rangeEnd] = rangeParser(_range);
977
996
  log.debug('Setting downsample range', plotSize, rangeStart, rangeEnd);
978
- _axis2.range(plotSize, rangeStart, rangeEnd);
997
+ _axis3.range(plotSize, rangeStart, rangeEnd);
979
998
  } else {
980
- _axis2.range(plotSize);
999
+ _axis3.range(plotSize);
981
1000
  }
982
1001
  }
983
1002
  }
@@ -1075,6 +1094,45 @@ class ChartUtils {
1075
1094
  }
1076
1095
  }
1077
1096
 
1097
+ /**
1098
+ * Creates the bounds for the periods specified.
1099
+ * For example, if you pass in [['09:00', '17:00']], it will return [17, 9] (closing at 5pm, opening at 9am the next day)
1100
+ * If you pass [['09:00', '12:00'], ['13:00', '17:00']], it will return [12, 13] (closing at noon, opening at 1pm) and [17, 9] (closing at 5pm, opening at 9am the next day)
1101
+ * @param periods Periods to map
1102
+ * @param timeZoneDiff Time zone difference in hours
1103
+ * @returns Bounds for the periods in plotly format
1104
+ */
1105
+ // eslint-disable-next-line class-methods-use-this
1106
+ createBoundsFromPeriods(periods) {
1107
+ var timeZoneDiff = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
1108
+ if (periods.length === 0) {
1109
+ return [];
1110
+ }
1111
+ var numberPeriods = periods.map(period => [(ChartUtils.periodToDecimal(period.open) + timeZoneDiff) % 24, (ChartUtils.periodToDecimal(period.close) + timeZoneDiff) % 24]).sort((a, b) => a[0] - b[0]);
1112
+ var bounds = [];
1113
+ for (var i = 0; i < numberPeriods.length; i += 1) {
1114
+ var period = numberPeriods[i];
1115
+ var nextPeriod = numberPeriods[(i + 1) % numberPeriods.length];
1116
+ bounds.push([period[1], nextPeriod[0]]);
1117
+ }
1118
+ return bounds;
1119
+ }
1120
+
1121
+ /**
1122
+ * Creates range breaks for plotly from business periods.
1123
+ * @param periods Business periods to create the breaks for
1124
+ * @param timeZoneDiff Time zone difference in hours
1125
+ * @returns Plotly range breaks for the business periods
1126
+ */
1127
+ createBreaksFromPeriods(periods) {
1128
+ var timeZoneDiff = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
1129
+ var bounds = this.createBoundsFromPeriods(periods, timeZoneDiff);
1130
+ return bounds.map(bound => ({
1131
+ pattern: 'hour',
1132
+ bounds: bound
1133
+ }));
1134
+ }
1135
+
1078
1136
  /**
1079
1137
  * Creates range break bounds for plotly from business days.
1080
1138
  * For example a standard business week of ['MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY']
@@ -1084,47 +1142,91 @@ class ChartUtils {
1084
1142
  * @param businessDays the days to display on the x-axis
1085
1143
  */
1086
1144
  createBoundsFromDays(businessDays) {
1145
+ var weekLength = this.daysOfWeek.length;
1146
+ // No breaks if all days are business days
1147
+ if (businessDays.length === weekLength) {
1148
+ return [];
1149
+ }
1087
1150
  var businessDaysInt = businessDays.map(day => this.daysOfWeek.indexOf(day));
1088
- var nonBusinessDaysInt = this.daysOfWeek.filter(day => !businessDays.includes(day)).map(day => this.daysOfWeek.indexOf(day));
1089
- // These are the days when business reopens (e.g. Monday after a weekend)
1090
- var reopenDays = new Set();
1091
- nonBusinessDaysInt.forEach(closed => {
1092
- for (var i = closed + 1; i < closed + this.daysOfWeek.length; i += 1) {
1093
- var adjustedDay = i % this.daysOfWeek.length;
1094
- if (businessDaysInt.includes(adjustedDay)) {
1095
- reopenDays.add(adjustedDay);
1096
- break;
1097
- }
1151
+ var businessDaysSet = new Set(businessDaysInt);
1152
+
1153
+ // These are the days when business is closed (e.g. Saturday to start the weekend)
1154
+ var closedDays = new Set();
1155
+ for (var i = 0; i < weekLength; i += 1) {
1156
+ if (!businessDaysSet.has(i) && businessDaysSet.has((i - 1 + weekLength) % weekLength)) {
1157
+ closedDays.add(i);
1098
1158
  }
1099
- });
1159
+ }
1100
1160
  var boundsArray = [];
1101
- // For each reopen day, find the furthest previous closed day
1102
- reopenDays.forEach(open => {
1103
- for (var i = open - 1; i > open - this.daysOfWeek.length; i -= 1) {
1104
- var adjustedDay = i < 0 ? i + this.daysOfWeek.length : i;
1105
- if (businessDaysInt.includes(adjustedDay)) {
1106
- var closedDay = (adjustedDay + 1) % 7;
1107
- boundsArray.push([closedDay, open]);
1108
- break;
1161
+ // For each close day, find the next open day
1162
+ closedDays.forEach(closedDay => {
1163
+ for (var _i = 0; _i < weekLength; _i += 1) {
1164
+ var adjustedDay = (closedDay + _i) % weekLength;
1165
+ if (businessDaysSet.has(adjustedDay)) {
1166
+ boundsArray.push([closedDay, adjustedDay]);
1167
+ return;
1109
1168
  }
1110
1169
  }
1170
+ throw new Error("Unable to find open day for closed day ".concat(closedDay, ", businessDays: ").concat(businessDays));
1111
1171
  });
1112
1172
  return boundsArray;
1113
1173
  }
1114
1174
 
1175
+ /**
1176
+ * Breaks in plotly for business days
1177
+ * @param businessDays Business days to create the breaks for
1178
+ * @returns Plotly range breaks for the business days
1179
+ */
1180
+ createBreaksFromDays(businessDays) {
1181
+ var bounds = this.createBoundsFromDays(businessDays);
1182
+ return bounds.map(bound => ({
1183
+ pattern: 'day of week',
1184
+ bounds: bound
1185
+ }));
1186
+ }
1187
+
1188
+ /**
1189
+ * Creates range breaks for plotly from a business calendar.
1190
+ * @param businessCalendar Calendar to create the breaks from
1191
+ * @param formatter Formatter to use for time zones
1192
+ * @returns Plotly Rangebreaks for the business calendar
1193
+ */
1194
+ createRangeBreaksFromBusinessCalendar(businessCalendar, formatter) {
1195
+ var rangebreaks = [];
1196
+ var {
1197
+ businessPeriods,
1198
+ businessDays,
1199
+ holidays,
1200
+ timeZone: calendarTimeZone
1201
+ } = businessCalendar;
1202
+ var typeFormatter = formatter === null || formatter === void 0 ? void 0 : formatter.getColumnTypeFormatter(BUSINESS_COLUMN_TYPE);
1203
+ var formatterTimeZone;
1204
+ if (isDateTimeColumnFormatter(typeFormatter)) {
1205
+ formatterTimeZone = typeFormatter.dhTimeZone;
1206
+ }
1207
+ var timeZoneDiff = ChartUtils.getTimeZoneDiff(calendarTimeZone, formatterTimeZone);
1208
+ if (holidays.length > 0) {
1209
+ rangebreaks.push(...this.createRangeBreakValuesFromHolidays(holidays, calendarTimeZone, formatterTimeZone, businessCalendar));
1210
+ }
1211
+ rangebreaks.push(...this.createBreaksFromPeriods(businessPeriods, timeZoneDiff));
1212
+ rangebreaks.push(...this.createBreaksFromDays(businessDays));
1213
+ return rangebreaks;
1214
+ }
1215
+
1115
1216
  /**
1116
1217
  * Creates an array of range breaks for all holidays.
1117
1218
  *
1118
1219
  * @param holidays an array of holidays
1119
1220
  * @param calendarTimeZone the time zone for the business calendar
1120
1221
  * @param formatterTimeZone the time zone for the formatter
1222
+ * @param calendar the calendar the holidays are from
1121
1223
  */
1122
- createRangeBreakValuesFromHolidays(holidays, calendarTimeZone, formatterTimeZone) {
1224
+ createRangeBreakValuesFromHolidays(holidays, calendarTimeZone, formatterTimeZone, calendar) {
1123
1225
  var fullHolidays = [];
1124
1226
  var partialHolidays = [];
1125
1227
  holidays.forEach(holiday => {
1126
1228
  if (holiday.businessPeriods.length > 0) {
1127
- partialHolidays.push(...this.createPartialHoliday(holiday, calendarTimeZone, formatterTimeZone));
1229
+ partialHolidays.push(...this.createPartialHoliday(holiday, calendarTimeZone, formatterTimeZone, calendar));
1128
1230
  } else {
1129
1231
  fullHolidays.push(this.createFullHoliday(holiday, calendarTimeZone, formatterTimeZone));
1130
1232
  }
@@ -1152,29 +1254,33 @@ class ChartUtils {
1152
1254
  * @param holiday the partial holiday
1153
1255
  * @param calendarTimeZone the time zone for the business calendar
1154
1256
  * @param formatterTimeZone the time zone for the formatter
1257
+ * @param calendar the calendar the holiday is from. Used to check against the default business periods to ensure this holiday needs to be specified
1258
+ *
1259
+ * @returns an array of range breaks for the partial holiday
1155
1260
  */
1156
- createPartialHoliday(holiday, calendarTimeZone, formatterTimeZone) {
1157
- // If a holiday has business periods {open1, close1} and {open2, close2}
1158
- // This will generate range breaks for:
1159
- // closed from 00:00 to open1
1160
- // closed from close1 to open2
1161
- // closed from close2 to 23:59:59.999999
1261
+ createPartialHoliday(holiday, calendarTimeZone, formatterTimeZone, calendar) {
1262
+ var _calendar$businessPer;
1263
+ if (holiday.businessPeriods.length === 0) {
1264
+ return [];
1265
+ }
1162
1266
  var dateString = holiday.date.toString();
1163
- var closedPeriods = ['00:00'];
1164
- holiday.businessPeriods.forEach(period => {
1165
- closedPeriods.push(period.open);
1166
- closedPeriods.push(period.close);
1167
- });
1168
- // To go up to 23:59:59.999999, we calculate the dvalue using 24 - close
1169
- closedPeriods.push('24:00');
1267
+
1268
+ // First check that the holiday is on a business day. If it's not, we can ignore it
1269
+ if (calendar) {
1270
+ var dayOfWeek = new Date(dateString).getDay();
1271
+ var isBusinessDay = calendar.businessDays.includes(this.daysOfWeek[dayOfWeek]);
1272
+ if (!isBusinessDay) {
1273
+ return [];
1274
+ }
1275
+ }
1276
+ var closedPeriods = ChartUtils.createClosedRangesForPartialHoliday(holiday.businessPeriods, (_calendar$businessPer = calendar === null || calendar === void 0 ? void 0 : calendar.businessPeriods) !== null && _calendar$businessPer !== void 0 ? _calendar$businessPer : []);
1170
1277
  var rangeBreaks = [];
1171
- for (var i = 0; i < closedPeriods.length; i += 2) {
1172
- var startClose = closedPeriods[i];
1173
- var endClose = closedPeriods[i + 1];
1278
+ for (var i = 0; i < closedPeriods.length; i += 1) {
1279
+ var [closeStart, closeEnd] = closedPeriods[i];
1174
1280
  // Skip over any periods where start and close are the same (zero hours)
1175
- if (startClose !== endClose) {
1176
- var values = [this.adjustDateForTimeZone("".concat(dateString, " ").concat(startClose, ":00.000000"), calendarTimeZone, formatterTimeZone)];
1177
- var dvalue = MILLIS_PER_HOUR * (ChartUtils.periodToDecimal(endClose) - ChartUtils.periodToDecimal(startClose));
1281
+ if (closeStart !== closeEnd) {
1282
+ var values = [this.adjustDateForTimeZone("".concat(dateString, " ").concat(ChartUtils.decimalToPeriod(closeStart), ":00.000000"), calendarTimeZone, formatterTimeZone)];
1283
+ var dvalue = MILLIS_PER_HOUR * (closeEnd - closeStart);
1178
1284
  rangeBreaks.push({
1179
1285
  values,
1180
1286
  dvalue