@deephaven/chart 0.43.0 → 0.44.1-beta.0

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.
@@ -0,0 +1,1435 @@
1
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
2
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
3
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
4
+ function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
5
+ function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
6
+ import Log from '@deephaven/log';
7
+ import { TableUtils } from '@deephaven/jsapi-utils';
8
+ import set from 'lodash.set';
9
+ import { assertNotNull } from '@deephaven/utils';
10
+ import ChartTheme from "./ChartTheme.js";
11
+ var log = Log.module('ChartUtils');
12
+ var BUSINESS_COLUMN_TYPE = 'io.deephaven.time.DateTime';
13
+ var MILLIS_PER_HOUR = 3600000;
14
+ var NANOS_PER_MILLI = 1000000;
15
+ function isDateWrapper(value) {
16
+ return value.asDate !== undefined;
17
+ }
18
+ function isLongWrapper(value) {
19
+ return value.asNumber !== undefined;
20
+ }
21
+ function isDateTimeColumnFormatter(value) {
22
+ return value.dhTimeZone !== undefined;
23
+ }
24
+ function isRangedPlotlyAxis(value) {
25
+ return value != null && value.range != null && (value.autorange === false || value.autorange === undefined);
26
+ }
27
+ class ChartUtils {
28
+ /**
29
+ * Generate the plotly error bar data from the passed in data.
30
+ * Iris passes in the values as absolute, plotly needs them as relative.
31
+ * @param x The main data array
32
+ * @param xLow The absolute low values
33
+ * @param xHigh
34
+ *
35
+ * @returns The error_x object required by plotly, or null if none is required
36
+ */
37
+ static getPlotlyErrorBars(x, xLow, xHigh) {
38
+ var array = xHigh.map((value, i) => value - x[i]);
39
+ var arrayminus = xLow.map((value, i) => x[i] - value);
40
+ return {
41
+ type: 'data',
42
+ symmetric: false,
43
+ array,
44
+ arrayminus
45
+ };
46
+ }
47
+ static convertNumberPrefix(prefix) {
48
+ return prefix.replace(/\u00A4\u00A4/g, 'USD').replace(/\u00A4/g, '$');
49
+ }
50
+ static getPlotlyNumberFormat(formatter, columnType, formatPattern) {
51
+ if (!formatPattern) {
52
+ return null;
53
+ }
54
+
55
+ // We translate java formatting: https://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html
56
+ // Into d3 number formatting: https://github.com/d3/d3-format
57
+ // We can't translate number formatting exactly, but should be able to translate the most common cases
58
+ // First split it into the subpatterns; currently only handling the positive subpattern, ignoring the rest
59
+ var subpatterns = formatPattern.split(';');
60
+ var matchArray = subpatterns[0].match(/^([^#,0.]*)([#,]*)([0,]*)(\.?)(0*)(#*)(E?0*)(%?)(.*)/);
61
+ assertNotNull(matchArray);
62
+ var [, prefix, placeholderDigits, zeroDigits,, decimalDigits, optionalDecimalDigits, numberType, percentSign, suffix] = matchArray;
63
+ var paddingLength = zeroDigits.replace(',', '').length;
64
+ var isCommaSeparated = placeholderDigits.indexOf(',') >= 0 || zeroDigits.indexOf(',') >= 0;
65
+ var comma = isCommaSeparated ? ',' : '';
66
+ var plotlyNumberType = numberType != null && numberType !== '' ? 'e' : 'f';
67
+ var type = percentSign !== '' ? percentSign : plotlyNumberType;
68
+ var decimalLength = decimalDigits.length + optionalDecimalDigits.length;
69
+ // IDS-4565 Plotly uses an older version of d3 which doesn't support the trim option or negative brackets
70
+ // If plotly updates it's d3 version, this should be re-enabled
71
+ // const trimOption = optionalDecimalDigits.length > 0 ? '~' : '';
72
+ var trimOption = '';
73
+ var tickformat = "0".concat(paddingLength).concat(comma, ".").concat(decimalLength).concat(trimOption).concat(type);
74
+ var tickprefix = ChartUtils.convertNumberPrefix(prefix);
75
+ // prefix and suffix are processed the same
76
+ var ticksuffix = ChartUtils.convertNumberPrefix(suffix);
77
+ return {
78
+ tickformat,
79
+ tickprefix,
80
+ ticksuffix,
81
+ automargin: true
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Adds tick spacing for an axis that has gapBetweenMajorTicks defined.
87
+ *
88
+ * @param axisFormat the current axis format, may be null
89
+ * @param axis the current axis
90
+ * @param isDateType indicates if the columns is a date type
91
+ */
92
+ static addTickSpacing(axisFormat, axis, isDateType) {
93
+ var {
94
+ gapBetweenMajorTicks
95
+ } = axis;
96
+ if (gapBetweenMajorTicks > 0) {
97
+ var updatedFormat = axisFormat || {};
98
+ var tickSpacing = gapBetweenMajorTicks;
99
+ if (isDateType) {
100
+ // Need to convert from nanoseconds to milliseconds
101
+ tickSpacing = gapBetweenMajorTicks / NANOS_PER_MILLI;
102
+ }
103
+ if (axis.log) {
104
+ tickSpacing = Math.log(tickSpacing);
105
+ }
106
+ // Note that tickmode defaults to 'auto'
107
+ updatedFormat.tickmode = 'linear';
108
+ updatedFormat.dtick = tickSpacing;
109
+ return updatedFormat;
110
+ }
111
+ return axisFormat;
112
+ }
113
+
114
+ /**
115
+ * Retrieve the data source for a given axis in a chart
116
+ * @param chart The chart to get the source for
117
+ * @param axis The axis to find the source for
118
+ * @returns The first source matching this axis
119
+ */
120
+ static getSourceForAxis(chart, axis) {
121
+ for (var i = 0; i < chart.series.length; i += 1) {
122
+ var series = chart.series[i];
123
+ for (var j = 0; j < series.sources.length; j += 1) {
124
+ var source = series.sources[j];
125
+ if (source.axis === axis) {
126
+ return source;
127
+ }
128
+ }
129
+ }
130
+ return null;
131
+ }
132
+
133
+ /**
134
+ * Get visibility setting for the series object
135
+ * @param name The series name to get the visibility for
136
+ * @param settings Chart settings
137
+ * @returns True for visible series and 'legendonly' for hidden
138
+ */
139
+ static getSeriesVisibility(name, settings) {
140
+ if (settings != null && settings.hiddenSeries != null && settings.hiddenSeries.includes(name)) {
141
+ return 'legendonly';
142
+ }
143
+ return true;
144
+ }
145
+
146
+ /**
147
+ * Get hidden labels array from chart settings
148
+ * @param settings Chart settings
149
+ * @returns Array of hidden series names
150
+ */
151
+ static getHiddenLabels(settings) {
152
+ if (settings !== null && settings !== void 0 && settings.hiddenSeries) {
153
+ return [...settings.hiddenSeries];
154
+ }
155
+ return [];
156
+ }
157
+
158
+ /**
159
+ * Create a default series data object. Apply styling to the object afterward.
160
+ * @returns A simple series data object with no styling
161
+ */
162
+ static makeSeriesData(type, mode, name, showLegend) {
163
+ var orientation = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : ChartUtils.ORIENTATION.VERTICAL;
164
+ return {
165
+ type,
166
+ mode,
167
+ name,
168
+ orientation,
169
+ showlegend: showLegend !== null && showLegend !== void 0 ? showLegend : undefined
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Get the Plotly marker symbol for the provided Deephaven shape
175
+ * Deephaven shapes: https://deephaven.io/enterprise/docs/plotting/visual-formatting/#point-formatting
176
+ * Plotly shapes: https://plotly.com/javascript/reference/scattergl/#scattergl-marker-symbol
177
+ * Table of plotly shapes: https://plotly.com/python/marker-style/#custom-marker-symbols
178
+ * @param deephavenShape Deephaven shape to get the marker symbol for
179
+ */
180
+ static getMarkerSymbol(deephavenShape) {
181
+ switch (deephavenShape) {
182
+ case 'SQUARE':
183
+ return 'square';
184
+ case 'CIRCLE':
185
+ return 'circle';
186
+ case 'DIAMOND':
187
+ return 'diamond';
188
+ case 'UP_TRIANGLE':
189
+ return 'triangle-up';
190
+ case 'DOWN_TRIANGLE':
191
+ return 'triangle-down';
192
+ case 'RIGHT_TRIANGLE':
193
+ return 'triangle-right';
194
+ case 'LEFT_TRIANGLE':
195
+ return 'triangle-left';
196
+ // There don't seem to be any plotly equivalents for ellipse or rectangles
197
+ // Rectangles could be `line-ew`, `line-ns`, or `hourglass` and `bowtie` instead?
198
+ // Ellipse could be `asterisk` or `diamond-wide` instead?
199
+ // Just throw an error, we've already got a bunch of types.
200
+ case 'ELLIPSE':
201
+ case 'HORIZONTAL_RECTANGLE':
202
+ case 'VERTICAL_RECTANGLE':
203
+ default:
204
+ throw new Error("Unrecognized shape ".concat(deephavenShape));
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Get all axes for a given `Figure`. Iterates through all charts axes and concatenates them.
210
+ * @param figure Figure to get all axes for
211
+ */
212
+ static getAllAxes(figure) {
213
+ return figure.charts.reduce((axes, chart) => [...axes, ...chart.axes], []);
214
+ }
215
+
216
+ /**
217
+ * Retrieve the chart that contains the passed in series from the figure
218
+ * @param figure The figure to retrieve the chart from
219
+ * @param series The series to get the chart for
220
+ */
221
+ static getChartForSeries(figure, series) {
222
+ var {
223
+ charts
224
+ } = figure;
225
+ for (var i = 0; i < charts.length; i += 1) {
226
+ var _chart = charts[i];
227
+ for (var j = 0; j < _chart.series.length; j += 1) {
228
+ if (series === _chart.series[j]) {
229
+ return _chart;
230
+ }
231
+ }
232
+ }
233
+ return null;
234
+ }
235
+
236
+ /**
237
+ * Get an object mapping axis to their ranges
238
+ * @param layout The plotly layout object to get the ranges from
239
+ * @returns An object mapping the axis name to it's range
240
+ */
241
+ static getLayoutRanges(layout) {
242
+ var ranges = {};
243
+ var keys = Object.keys(layout).filter(key => key.indexOf('axis') >= 0);
244
+ for (var i = 0; i < keys.length; i += 1) {
245
+ var key = keys[i];
246
+ var value = layout[key];
247
+ if (isRangedPlotlyAxis(value)) {
248
+ // Only want to add the range if it's not autoranged
249
+ ranges[key] = [...value.range];
250
+ }
251
+ }
252
+ return ranges;
253
+ }
254
+ static getAxisLayoutProperty(axisProperty, axisIndex) {
255
+ var axisIndexString = axisIndex > 0 ? "".concat(axisIndex + 1) : '';
256
+ return "".concat(axisProperty !== null && axisProperty !== void 0 ? axisProperty : '', "axis").concat(axisIndexString);
257
+ }
258
+
259
+ /**
260
+ * Converts an open or close period to a declimal. e.g '09:30" to 9.5
261
+ *
262
+ * @param period the open or close value of the period
263
+ */
264
+ static periodToDecimal(period) {
265
+ var values = period.split(':');
266
+ return Number(values[0]) + Number(values[1]) / 60;
267
+ }
268
+
269
+ /**
270
+ * Groups an array and returns a map
271
+ * @param array The object to group
272
+ * @param property The property name to group by
273
+ * @returns A map containing the items grouped by their values for the property
274
+ */
275
+ static groupArray(array, property) {
276
+ return array.reduce((result, item) => {
277
+ var _result$get;
278
+ var key = item[property];
279
+ var group = (_result$get = result.get(key)) !== null && _result$get !== void 0 ? _result$get : [];
280
+ group.push(item);
281
+ result.set(key, group);
282
+ return result;
283
+ }, new Map());
284
+ }
285
+
286
+ /**
287
+ * Parses the colorway property of a theme and returns an array of colors
288
+ * Theme could have a single string with space separated colors or an array of strings representing the colorway
289
+ * @param theme The theme to get colorway from
290
+ * @returns Colorway array for the theme
291
+ */
292
+ static getColorwayFromTheme() {
293
+ var theme = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ChartTheme;
294
+ var colorway = [];
295
+ if (theme.colorway) {
296
+ if (Array.isArray(theme.colorway)) {
297
+ colorway = theme.colorway;
298
+ } else if (typeof theme.colorway === 'string') {
299
+ colorway = theme.colorway.split(' ');
300
+ } else {
301
+ log.warn("Unable to handle colorway property: ".concat(theme.colorway));
302
+ }
303
+ }
304
+ return colorway;
305
+ }
306
+ static titleFromSettings(settings) {
307
+ var {
308
+ series,
309
+ xAxis,
310
+ title = "".concat((series !== null && series !== void 0 ? series : []).join(', '), " by ").concat(xAxis)
311
+ } = settings;
312
+ return title;
313
+ }
314
+ constructor(dh) {
315
+ _defineProperty(this, "dh", void 0);
316
+ _defineProperty(this, "daysOfWeek", void 0);
317
+ this.dh = dh;
318
+ this.daysOfWeek = Object.freeze(dh.calendar.DayOfWeek.values());
319
+ }
320
+
321
+ /**
322
+ * Retrieve the axis formats from the provided figure.
323
+ * Currently defaults to just the x/y axes.
324
+ * @param figure The figure to get the axis formats for
325
+ * @param formatter The formatter to use when getting the axis format
326
+ * @returns A map of axis layout property names to axis formats
327
+ */
328
+ getAxisFormats(figure, formatter) {
329
+ var _this = this;
330
+ var axisFormats = new Map();
331
+ var nullFormat = {
332
+ tickformat: null,
333
+ ticksuffix: null
334
+ };
335
+ var allAxes = ChartUtils.getAllAxes(figure);
336
+ var axisTypeMap = ChartUtils.groupArray(allAxes, 'type');
337
+ var {
338
+ charts
339
+ } = figure;
340
+ for (var i = 0; i < charts.length; i += 1) {
341
+ var _chart2 = charts[i];
342
+ for (var j = 0; j < _chart2.series.length; j += 1) {
343
+ var series = _chart2.series[j];
344
+ var {
345
+ sources
346
+ } = series;
347
+ var axisSources = sources.filter(source => source.axis);
348
+ var _loop = function _loop() {
349
+ var source = axisSources[k];
350
+ var {
351
+ axis
352
+ } = source;
353
+ var {
354
+ type: axisType
355
+ } = axis;
356
+ var typeAxes = axisTypeMap.get(axisType);
357
+ assertNotNull(typeAxes);
358
+ var axisIndex = typeAxes.indexOf(axis);
359
+ var axisProperty = _this.getAxisPropertyName(axisType);
360
+ if (axisProperty != null) {
361
+ var axisLayoutProperty = ChartUtils.getAxisLayoutProperty(axisProperty, axisIndex);
362
+ if (axisFormats.has(axisLayoutProperty)) {
363
+ log.debug("".concat(axisLayoutProperty, " already added."));
364
+ } else {
365
+ log.debug("Adding ".concat(axisLayoutProperty, " to axisFormats."));
366
+ var axisFormat = _this.getPlotlyAxisFormat(source, formatter);
367
+ if (axisFormat === null) {
368
+ axisFormats.set(axisLayoutProperty, nullFormat);
369
+ } else {
370
+ axisFormats.set(axisLayoutProperty, axisFormat);
371
+ var {
372
+ businessCalendar
373
+ } = axis;
374
+ if (businessCalendar != null) {
375
+ var rangebreaks = [];
376
+ var {
377
+ businessPeriods,
378
+ businessDays,
379
+ holidays,
380
+ timeZone: calendarTimeZone
381
+ } = businessCalendar;
382
+ var typeFormatter = formatter === null || formatter === void 0 ? void 0 : formatter.getColumnTypeFormatter(BUSINESS_COLUMN_TYPE);
383
+ var formatterTimeZone;
384
+ if (isDateTimeColumnFormatter(typeFormatter)) {
385
+ formatterTimeZone = typeFormatter.dhTimeZone;
386
+ }
387
+ var timeZoneDiff = formatterTimeZone ? (calendarTimeZone.standardOffset - formatterTimeZone.standardOffset) / 60 : 0;
388
+ if (holidays.length > 0) {
389
+ rangebreaks.push(..._this.createRangeBreakValuesFromHolidays(holidays, calendarTimeZone, formatterTimeZone));
390
+ }
391
+ businessPeriods.forEach(period => rangebreaks.push({
392
+ pattern: 'hour',
393
+ bounds: [ChartUtils.periodToDecimal(period.close) + timeZoneDiff, ChartUtils.periodToDecimal(period.open) + timeZoneDiff]
394
+ }));
395
+ // If there are seven business days, then there is no weekend
396
+ if (businessDays.length < _this.daysOfWeek.length) {
397
+ _this.createBoundsFromDays(businessDays).forEach(weekendBounds => rangebreaks.push({
398
+ pattern: 'day of week',
399
+ bounds: weekendBounds
400
+ }));
401
+ }
402
+ axisFormat.rangebreaks = rangebreaks;
403
+ }
404
+ if (axisFormats.size === _chart2.axes.length) {
405
+ return {
406
+ v: axisFormats
407
+ };
408
+ }
409
+ }
410
+ }
411
+ }
412
+ };
413
+ for (var k = 0; k < axisSources.length; k += 1) {
414
+ var _ret = _loop();
415
+ if (typeof _ret === "object") return _ret.v;
416
+ }
417
+ }
418
+ }
419
+ return axisFormats;
420
+ }
421
+
422
+ /**
423
+ * Converts the Iris plot style into a plotly chart type
424
+ * @param plotStyle The plotStyle to use, see dh.plot.SeriesPlotStyle
425
+ * @param isBusinessTime If the plot is using business time for an axis
426
+ */
427
+ getPlotlyChartType(plotStyle, isBusinessTime) {
428
+ var {
429
+ dh
430
+ } = this;
431
+ switch (plotStyle) {
432
+ case dh.plot.SeriesPlotStyle.SCATTER:
433
+ // scattergl mode is more performant, but doesn't support the rangebreaks we need for businessTime calendars
434
+ return !isBusinessTime ? 'scattergl' : 'scatter';
435
+ case dh.plot.SeriesPlotStyle.LINE:
436
+ // There is also still some artifacting bugs with scattergl: https://github.com/plotly/plotly.js/issues/3522
437
+ // The artifacting only occurs on line plots, which we can draw with fairly decent performance using SVG paths
438
+ // Once the above plotly issue is fixed, scattergl should be used here (when !isBusinessTime)
439
+ return 'scatter';
440
+ case dh.plot.SeriesPlotStyle.BAR:
441
+ case dh.plot.SeriesPlotStyle.STACKED_BAR:
442
+ return 'bar';
443
+ case dh.plot.SeriesPlotStyle.PIE:
444
+ return 'pie';
445
+ case dh.plot.SeriesPlotStyle.TREEMAP:
446
+ return 'treemap';
447
+ case dh.plot.SeriesPlotStyle.HISTOGRAM:
448
+ return 'histogram';
449
+ case dh.plot.SeriesPlotStyle.OHLC:
450
+ return 'ohlc';
451
+ default:
452
+ return undefined;
453
+ }
454
+ }
455
+
456
+ /**
457
+ * Converts the Iris plot style into a plotly chart mode
458
+ * @param plotStyle The plotStyle to use, see dh.plot.SeriesPlotStyle.*
459
+ * @param areLinesVisible Whether lines are visible or not
460
+ * @param areShapesVisible Whether shapes are visible or not
461
+ */
462
+ getPlotlyChartMode(plotStyle, areLinesVisible, areShapesVisible) {
463
+ var {
464
+ dh
465
+ } = this;
466
+ var modes = new Set();
467
+ switch (plotStyle) {
468
+ case dh.plot.SeriesPlotStyle.SCATTER:
469
+ // Default to only showing shapes in scatter plots
470
+ if (areLinesVisible !== null && areLinesVisible !== void 0 ? areLinesVisible : false) {
471
+ modes.add(ChartUtils.MODE_LINES);
472
+ }
473
+ if (areShapesVisible !== null && areShapesVisible !== void 0 ? areShapesVisible : true) {
474
+ modes.add(ChartUtils.MODE_MARKERS);
475
+ }
476
+ break;
477
+ case dh.plot.SeriesPlotStyle.LINE:
478
+ // Default to only showing lines in line series
479
+ if (areLinesVisible !== null && areLinesVisible !== void 0 ? areLinesVisible : true) {
480
+ modes.add(ChartUtils.MODE_LINES);
481
+ }
482
+ if (areShapesVisible !== null && areShapesVisible !== void 0 ? areShapesVisible : false) {
483
+ modes.add(ChartUtils.MODE_MARKERS);
484
+ }
485
+ break;
486
+ default:
487
+ break;
488
+ }
489
+ return modes.size > 0 ? [...modes].join('+') : undefined;
490
+ }
491
+
492
+ /**
493
+ * Get the property to set on the series data for plotly
494
+ * @param plotStyle The plot style of the series
495
+ * @param sourceType The source type for the series
496
+ */
497
+ getPlotlyProperty(plotStyle, sourceType) {
498
+ var {
499
+ dh
500
+ } = this;
501
+ switch (plotStyle) {
502
+ case dh.plot.SeriesPlotStyle.PIE:
503
+ switch (sourceType) {
504
+ case dh.plot.SourceType.X:
505
+ return 'labels';
506
+ case dh.plot.SourceType.Y:
507
+ return 'values';
508
+ default:
509
+ break;
510
+ }
511
+ break;
512
+ case dh.plot.SeriesPlotStyle.OHLC:
513
+ switch (sourceType) {
514
+ case dh.plot.SourceType.TIME:
515
+ return 'x';
516
+ default:
517
+ break;
518
+ }
519
+ break;
520
+ case dh.plot.SeriesPlotStyle.TREEMAP:
521
+ switch (sourceType) {
522
+ case dh.plot.SourceType.X:
523
+ return 'ids';
524
+ case dh.plot.SourceType.Y:
525
+ return 'values';
526
+ case dh.plot.SourceType.LABEL:
527
+ return 'labels';
528
+ case dh.plot.SourceType.PARENT:
529
+ return 'parents';
530
+ case dh.plot.SourceType.COLOR:
531
+ return 'marker.colors';
532
+ default:
533
+ break;
534
+ }
535
+ break;
536
+ default:
537
+ break;
538
+ }
539
+ switch (sourceType) {
540
+ case dh.plot.SourceType.X:
541
+ return 'x';
542
+ case dh.plot.SourceType.Y:
543
+ return 'y';
544
+ case dh.plot.SourceType.Z:
545
+ return 'z';
546
+ case dh.plot.SourceType.X_LOW:
547
+ return 'xLow';
548
+ case dh.plot.SourceType.X_HIGH:
549
+ return 'xHigh';
550
+ case dh.plot.SourceType.Y_LOW:
551
+ return 'yLow';
552
+ case dh.plot.SourceType.Y_HIGH:
553
+ return 'yHigh';
554
+ case dh.plot.SourceType.TIME:
555
+ return 'time';
556
+ case dh.plot.SourceType.OPEN:
557
+ return 'open';
558
+ case dh.plot.SourceType.HIGH:
559
+ return 'high';
560
+ case dh.plot.SourceType.LOW:
561
+ return 'low';
562
+ case dh.plot.SourceType.CLOSE:
563
+ return 'close';
564
+ case dh.plot.SourceType.SHAPE:
565
+ return 'shape';
566
+ case dh.plot.SourceType.SIZE:
567
+ return 'size';
568
+ case dh.plot.SourceType.LABEL:
569
+ return 'label';
570
+ case dh.plot.SourceType.COLOR:
571
+ return 'color';
572
+ case dh.plot.SourceType.PARENT:
573
+ return 'parent';
574
+ case dh.plot.SourceType.HOVER_TEXT:
575
+ return 'hovertext';
576
+ case dh.plot.SourceType.TEXT:
577
+ return 'text';
578
+ default:
579
+ throw new Error("Unrecognized source type: ".concat(sourceType));
580
+ }
581
+ }
582
+ getPlotlySeriesOrientation(series) {
583
+ var _sources$, _sources$$axis;
584
+ var {
585
+ dh
586
+ } = this;
587
+ var {
588
+ sources
589
+ } = series;
590
+ if (sources.length === 2 && ((_sources$ = sources[0]) === null || _sources$ === void 0 ? void 0 : (_sources$$axis = _sources$.axis) === null || _sources$$axis === void 0 ? void 0 : _sources$$axis.type) === dh.plot.AxisType.Y) {
591
+ return ChartUtils.ORIENTATION.HORIZONTAL;
592
+ }
593
+ return ChartUtils.ORIENTATION.VERTICAL;
594
+ }
595
+
596
+ /**
597
+ * Create a data series (trace) for use with plotly
598
+ * @param series The series to create the series data with
599
+ * @param axisTypeMap The map of axes grouped by type
600
+ * @param seriesVisibility Visibility setting for the series
601
+ * @param theme The theme properties for the plot. See ChartTheme.js for an example
602
+ * @returns The series data (trace) object for use with plotly.
603
+ */
604
+ makeSeriesDataFromSeries(series, axisTypeMap, seriesVisibility) {
605
+ var showLegend = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
606
+ var theme = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : ChartTheme;
607
+ var {
608
+ name,
609
+ isLinesVisible,
610
+ isShapesVisible,
611
+ plotStyle,
612
+ lineColor,
613
+ shapeColor,
614
+ sources,
615
+ shape,
616
+ shapeSize
617
+ } = series;
618
+ var isBusinessTime = sources.some(source => {
619
+ var _source$axis;
620
+ return (_source$axis = source.axis) === null || _source$axis === void 0 ? void 0 : _source$axis.businessCalendar;
621
+ });
622
+ var type = this.getChartType(plotStyle, isBusinessTime);
623
+ var mode = this.getPlotlyChartMode(plotStyle, isLinesVisible !== null && isLinesVisible !== void 0 ? isLinesVisible : undefined, isShapesVisible !== null && isShapesVisible !== void 0 ? isShapesVisible : undefined);
624
+ var orientation = this.getPlotlySeriesOrientation(series);
625
+ var seriesData = ChartUtils.makeSeriesData(type, mode, name, showLegend, orientation);
626
+ this.addSourcesToSeriesData(seriesData, plotStyle, sources, axisTypeMap);
627
+ this.addStylingToSeriesData(seriesData, plotStyle, theme, lineColor, shapeColor, shape, shapeSize, seriesVisibility);
628
+ return seriesData;
629
+ }
630
+ addSourcesToSeriesData(seriesDataParam, plotStyle, sources, axisTypeMap) {
631
+ var seriesData = seriesDataParam;
632
+ for (var k = 0; k < sources.length; k += 1) {
633
+ var source = sources[k];
634
+ var {
635
+ axis: _axis,
636
+ type: sourceType
637
+ } = source;
638
+ var dataAttributeName = this.getPlotlyProperty(plotStyle, sourceType);
639
+ set(seriesData, dataAttributeName, []);
640
+ var axisProperty = _axis != null ? this.getAxisPropertyName(_axis.type) : null;
641
+ if (axisProperty != null) {
642
+ var axes = axisTypeMap.get(_axis.type);
643
+ if (axes) {
644
+ var axisIndex = axes.indexOf(_axis);
645
+ var axisIndexString = axisIndex > 0 ? "".concat(axisIndex + 1) : '';
646
+ seriesData["".concat(axisProperty, "axis")] = "".concat(axisProperty).concat(axisIndexString);
647
+ }
648
+ }
649
+ }
650
+ }
651
+ addStylingToSeriesData(seriesDataParam, plotStyle) {
652
+ var theme = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ChartTheme;
653
+ var lineColor = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
654
+ var shapeColor = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
655
+ var shape = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null;
656
+ var shapeSize = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : null;
657
+ var seriesVisibility = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : null;
658
+ var {
659
+ dh
660
+ } = this;
661
+ var seriesData = seriesDataParam;
662
+ // Add some empty objects so we can fill them in later with details without checking for existence
663
+ seriesData.marker = {
664
+ line: {}
665
+ }; // border line width on markers
666
+ seriesData.line = {
667
+ width: 1 // default line width for lines, should eventually be able to override
668
+ };
669
+
670
+ if (plotStyle === dh.plot.SeriesPlotStyle.AREA) {
671
+ seriesData.fill = 'tozeroy';
672
+ } else if (plotStyle === dh.plot.SeriesPlotStyle.STACKED_AREA) {
673
+ seriesData.stackgroup = 'stack';
674
+ } else if (plotStyle === dh.plot.SeriesPlotStyle.STEP) {
675
+ seriesData.line.shape = 'hv'; // plot.ly horizontal then vertical step styling
676
+ } else if (plotStyle === dh.plot.SeriesPlotStyle.HISTOGRAM) {
677
+ // The default histfunc in plotly is 'count', but the data passed up from the API provides explicit x/y values and bins
678
+ // Since it's converted to bar, just set the widths of each bar
679
+ seriesData.width = [];
680
+ if (seriesData.marker.line !== undefined) {
681
+ Object.assign(seriesData.marker.line, {
682
+ color: theme.paper_bgcolor,
683
+ width: 1
684
+ });
685
+ }
686
+ } else if (plotStyle === dh.plot.SeriesPlotStyle.OHLC) {
687
+ seriesData.increasing = {
688
+ line: {
689
+ color: theme.ohlc_increasing
690
+ }
691
+ };
692
+ seriesData.decreasing = {
693
+ line: {
694
+ color: theme.ohlc_decreasing
695
+ }
696
+ };
697
+ } else if (plotStyle === dh.plot.SeriesPlotStyle.PIE) {
698
+ seriesData.textinfo = 'label+percent';
699
+
700
+ // TODO Open DefinitelyTyped/Plotly PR to mark family and size as optional
701
+ // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/plotly.js/lib/traces/pie.d.ts#L6
702
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
703
+ seriesData.outsidetextfont = {
704
+ color: theme.title_color
705
+ };
706
+ } else if (plotStyle === dh.plot.SeriesPlotStyle.TREEMAP) {
707
+ seriesData.hoverinfo = 'text';
708
+ seriesData.textinfo = 'label+text';
709
+ seriesData.tiling = {
710
+ packing: 'squarify',
711
+ pad: 0
712
+ };
713
+ seriesData.textposition = 'middle center';
714
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
715
+ seriesData.outsidetextfont = {
716
+ color: theme.title_color
717
+ };
718
+ }
719
+ if (lineColor != null) {
720
+ if (plotStyle === dh.plot.SeriesPlotStyle.BAR) {
721
+ seriesData.marker.color = lineColor;
722
+ } else {
723
+ seriesData.line.color = lineColor;
724
+ }
725
+ }
726
+ if (shapeColor != null) {
727
+ seriesData.marker.color = shapeColor;
728
+ }
729
+ if (shape != null && shape.length > 0) {
730
+ try {
731
+ seriesData.marker.symbol = ChartUtils.getMarkerSymbol(shape);
732
+ } catch (e) {
733
+ log.warn('Unable to handle shape', shape, ':', e);
734
+ }
735
+ }
736
+ if (shapeSize != null) {
737
+ seriesData.marker.size = shapeSize * ChartUtils.DEFAULT_MARKER_SIZE;
738
+ }
739
+
740
+ // Skipping pie charts
741
+ // Pie slice visibility is configured in chart layout instead of series data
742
+ if (seriesVisibility != null && plotStyle !== dh.plot.SeriesPlotStyle.PIE) {
743
+ seriesData.visible = seriesVisibility;
744
+ }
745
+ }
746
+ getChartType(plotStyle, isBusinessTime) {
747
+ var {
748
+ dh
749
+ } = this;
750
+ switch (plotStyle) {
751
+ case dh.plot.SeriesPlotStyle.HISTOGRAM:
752
+ // When reading data from the `Figure`, it already provides bins and values, so rather than using
753
+ // plot.ly to calculate the bins and sum values, just convert it to a bar chart
754
+ return 'bar';
755
+ default:
756
+ return this.getPlotlyChartType(plotStyle, isBusinessTime);
757
+ }
758
+ }
759
+
760
+ /**
761
+ * Return the plotly axis property name
762
+ * @param axisType The axis type to get the property name for
763
+ */
764
+ getAxisPropertyName(axisType) {
765
+ var {
766
+ dh
767
+ } = this;
768
+ switch (axisType) {
769
+ case dh.plot.AxisType.X:
770
+ return 'x';
771
+ case dh.plot.AxisType.Y:
772
+ return 'y';
773
+ default:
774
+ return null;
775
+ }
776
+ }
777
+
778
+ /**
779
+ * Returns the plotly "side" value for the provided axis position
780
+ * @param axisPosition The Iris AxisPosition of the axis
781
+ */
782
+ getAxisSide(axisPosition) {
783
+ var {
784
+ dh
785
+ } = this;
786
+ switch (axisPosition) {
787
+ case dh.plot.AxisPosition.BOTTOM:
788
+ return 'bottom';
789
+ case dh.plot.AxisPosition.TOP:
790
+ return 'top';
791
+ case dh.plot.AxisPosition.LEFT:
792
+ return 'left';
793
+ case dh.plot.AxisPosition.RIGHT:
794
+ return 'right';
795
+ default:
796
+ return undefined;
797
+ }
798
+ }
799
+
800
+ /**
801
+ * Update the layout with all the axes information for the provided figure
802
+ * @param figure Figure to update the axes for
803
+ * @param layoutParam Layout object to update in place
804
+ * @param chartAxisRangeParser Function to retrieve the axis range parser
805
+ * @param plotWidth Width of the plot in pixels
806
+ * @param plotHeight Height of the plot in pixels
807
+ * @param theme Theme used for displaying the plot
808
+ */
809
+ updateFigureAxes(layoutParam, figure, chartAxisRangeParser) {
810
+ var plotWidth = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
811
+ var plotHeight = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
812
+ var theme = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : ChartTheme;
813
+ var layout = layoutParam;
814
+ var figureAxes = ChartUtils.getAllAxes(figure);
815
+ for (var i = 0; i < figure.charts.length; i += 1) {
816
+ var _chart3 = figure.charts[i];
817
+ var axisRangeParser = chartAxisRangeParser === null || chartAxisRangeParser === void 0 ? void 0 : chartAxisRangeParser(_chart3);
818
+ var bounds = this.getChartBounds(figure, _chart3, plotWidth, plotHeight);
819
+ this.updateLayoutAxes(layout, _chart3.axes, figureAxes, plotWidth, plotHeight, bounds, axisRangeParser, theme);
820
+ }
821
+ this.removeStaleAxes(layout, figureAxes);
822
+ }
823
+ getChartBounds(figure, chart, plotWidth, plotHeight) {
824
+ var _axisPositionMap$get;
825
+ var {
826
+ dh
827
+ } = this;
828
+ var {
829
+ cols,
830
+ rows
831
+ } = figure;
832
+ var {
833
+ column,
834
+ colspan,
835
+ row,
836
+ rowspan
837
+ } = chart;
838
+ var endColumn = column + colspan;
839
+ var endRow = row + rowspan;
840
+ var columnSize = 1 / cols;
841
+ var rowSize = 1 / rows;
842
+ var xMarginSize = ChartUtils.AXIS_SIZE_PX / plotWidth;
843
+ var yMarginSize = ChartUtils.AXIS_SIZE_PX / plotHeight;
844
+ var bounds = {
845
+ // Need to invert the row positioning so the first one defined shows up on top instead of the bottom, since coordinates start in bottom left
846
+ bottom: (rows - endRow) * rowSize + (endRow < rows ? yMarginSize / 2 : 0),
847
+ top: (rows - row) * rowSize - (row > 0 ? yMarginSize / 2 : 0),
848
+ left: column * columnSize + (column > 0 ? xMarginSize / 2 : 0),
849
+ right: endColumn * columnSize - (endColumn < cols ? xMarginSize / 2 : 0)
850
+ };
851
+
852
+ // Adjust the bounds based on where the legend is
853
+ // For now, always assume the legend is shown on the right
854
+ var axisPositionMap = ChartUtils.groupArray(chart.axes, 'position');
855
+ var rightAxes = (_axisPositionMap$get = axisPositionMap.get(dh.plot.AxisPosition.RIGHT)) !== null && _axisPositionMap$get !== void 0 ? _axisPositionMap$get : [];
856
+ if (rightAxes.length > 0) {
857
+ if (plotWidth > 0) {
858
+ bounds.right -= (bounds.right - bounds.left) * Math.max(0, Math.min(ChartUtils.LEGEND_WIDTH_PX / plotWidth, ChartUtils.MAX_LEGEND_SIZE));
859
+ } else {
860
+ bounds.right -= (bounds.right - bounds.left) * ChartUtils.DEFAULT_AXIS_SIZE;
861
+ }
862
+ }
863
+ return bounds;
864
+ }
865
+ getPlotlyDateFormat(formatter, columnType, formatPattern) {
866
+ var {
867
+ dh
868
+ } = this;
869
+ var tickformat = formatPattern == null ? undefined : formatPattern.replace('%', '%%').replace(/S{9}/g, '%9f').replace(/S{8}/g, '%8f').replace(/S{7}/g, '%7f').replace(/S{6}/g, '%6f').replace(/S{5}/g, '%5f').replace(/S{4}/g, '%4f').replace(/S{3}/g, '%3f').replace(/S{2}/g, '%2f').replace(/S{1}/g, '%1f').replace(/y{4}/g, '%Y').replace(/y{2}/g, '%y').replace(/M{4}/g, '%B').replace(/M{3}/g, '%b').replace(/M{2}/g, '%m').replace(/M{1}/g, '%-m').replace(/E{4,}/g, '%A').replace(/E{1,}/g, '%a').replace(/d{2}/g, '%d').replace(/([^%]|^)d{1}/g, '$1%-d').replace(/H{2}/g, '%H').replace(/h{2}/g, '%I').replace(/h{1}/g, '%-I').replace(/m{2}/g, '%M').replace(/s{2}/g, '%S').replace("'T'", 'T').replace(' z', ''); // timezone added as suffix if necessary
870
+
871
+ var ticksuffix;
872
+ var dataFormatter = formatter === null || formatter === void 0 ? void 0 : formatter.getColumnTypeFormatter(columnType);
873
+ if (dataFormatter != null && isDateTimeColumnFormatter(dataFormatter) && dataFormatter.dhTimeZone != null && dataFormatter.showTimeZone) {
874
+ ticksuffix = dh.i18n.DateTimeFormat.format(' z', new Date(), dataFormatter.dhTimeZone);
875
+ }
876
+ return {
877
+ tickformat,
878
+ ticksuffix,
879
+ automargin: true
880
+ };
881
+ }
882
+
883
+ /**
884
+ * Gets the plotly axis formatting information from the source passed in
885
+ * @param source The Source to get the formatter information from
886
+ * @param formatter The current formatter for formatting data
887
+ */
888
+ getPlotlyAxisFormat(source) {
889
+ var formatter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
890
+ var {
891
+ dh
892
+ } = this;
893
+ var {
894
+ axis,
895
+ columnType
896
+ } = source;
897
+ var {
898
+ formatPattern
899
+ } = axis;
900
+ var axisFormat = null;
901
+ if (TableUtils.isDateType(columnType)) {
902
+ axisFormat = this.getPlotlyDateFormat(formatter, columnType, formatPattern);
903
+ axisFormat = ChartUtils.addTickSpacing(axisFormat, axis, true);
904
+ } else if (TableUtils.isNumberType(columnType)) {
905
+ axisFormat = ChartUtils.getPlotlyNumberFormat(formatter, columnType, formatPattern);
906
+ axisFormat = ChartUtils.addTickSpacing(axisFormat, axis, false);
907
+ }
908
+ if (axis.formatType === dh.plot.AxisFormatType.CATEGORY) {
909
+ if (axisFormat) {
910
+ axisFormat.type = 'category';
911
+ } else {
912
+ axisFormat = {
913
+ type: 'category',
914
+ tickformat: undefined,
915
+ ticksuffix: undefined
916
+ };
917
+ }
918
+ }
919
+ return axisFormat;
920
+ }
921
+
922
+ /**
923
+ * Updates the axes positions and sizes in the layout object provided.
924
+ * If the axis did not exist in the layout previously, it is created and added.
925
+ * Any axis that no longer exists in axes is removed.
926
+ * With Downsampling enabled, will also update the range on the axis itself as appropriate
927
+ * @param layoutParam The layout object to update
928
+ * @param chartAxes The chart axes to update the layout with
929
+ * @param figureAxes All figure axes to update the layout with
930
+ * @param plotWidth The width of the plot to calculate the axis sizes for
931
+ * @param plotHeight The height of the plot to calculate the axis sizes for
932
+ * @param bounds The bounds for this set of axes
933
+ * @param axisRangeParser A function to retrieve the range parser for a given axis
934
+ */
935
+ updateLayoutAxes(layoutParam, chartAxes, figureAxes) {
936
+ var plotWidth = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
937
+ var plotHeight = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
938
+ var bounds = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {
939
+ left: 0,
940
+ top: 0,
941
+ right: 1,
942
+ bottom: 1
943
+ };
944
+ var axisRangeParser = arguments.length > 6 ? arguments[6] : undefined;
945
+ var theme = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : ChartTheme;
946
+ var {
947
+ dh
948
+ } = this;
949
+ var xAxisSize = plotWidth > 0 ? Math.max(ChartUtils.MIN_AXIS_SIZE, Math.min(ChartUtils.AXIS_SIZE_PX / plotHeight, ChartUtils.MAX_AXIS_SIZE)) : ChartUtils.DEFAULT_AXIS_SIZE;
950
+ var yAxisSize = plotHeight > 0 ? Math.max(ChartUtils.MIN_AXIS_SIZE, Math.min(ChartUtils.AXIS_SIZE_PX / plotWidth, ChartUtils.MAX_AXIS_SIZE)) : ChartUtils.DEFAULT_AXIS_SIZE;
951
+ var layout = layoutParam;
952
+ var axisPositionMap = ChartUtils.groupArray(chartAxes, 'position');
953
+ var axisTypeMap = ChartUtils.groupArray(chartAxes, 'type');
954
+ var axisTypes = [...axisTypeMap.keys()];
955
+ var figureAxisTypeMap = ChartUtils.groupArray(figureAxes, 'type');
956
+ for (var j = 0; j < axisTypes.length; j += 1) {
957
+ var axisType = axisTypes[j];
958
+ var axisProperty = this.getAxisPropertyName(axisType);
959
+ if (axisProperty != null) {
960
+ var typeAxes = axisTypeMap.get(axisType);
961
+ var figureTypeAxes = figureAxisTypeMap.get(axisType);
962
+ var isYAxis = axisType === dh.plot.AxisType.Y;
963
+ var plotSize = isYAxis ? plotHeight : plotWidth;
964
+ assertNotNull(typeAxes);
965
+ assertNotNull(figureTypeAxes);
966
+ for (var chartAxisIndex = 0; chartAxisIndex < typeAxes.length; chartAxisIndex += 1) {
967
+ var _axis2 = typeAxes[chartAxisIndex];
968
+ var figureAxisIndex = figureTypeAxes.indexOf(_axis2);
969
+ var axisLayoutProperty = ChartUtils.getAxisLayoutProperty(axisProperty, figureAxisIndex);
970
+ if (layout[axisLayoutProperty] == null) {
971
+ layout[axisLayoutProperty] = this.makeLayoutAxis(axisType, theme);
972
+ }
973
+ var layoutAxis = layout[axisLayoutProperty];
974
+ if (layoutAxis != null) {
975
+ this.updateLayoutAxis(layoutAxis, _axis2, chartAxisIndex, axisPositionMap, xAxisSize, yAxisSize, bounds);
976
+ var {
977
+ range: _range,
978
+ autorange
979
+ } = layoutAxis;
980
+ if (axisRangeParser != null && _range !== undefined && (autorange === undefined || autorange === false)) {
981
+ var rangeParser = axisRangeParser(_axis2);
982
+ var [rangeStart, rangeEnd] = rangeParser(_range);
983
+ log.debug('Setting downsample range', plotSize, rangeStart, rangeEnd);
984
+ _axis2.range(plotSize, rangeStart, rangeEnd);
985
+ } else {
986
+ _axis2.range(plotSize);
987
+ }
988
+ }
989
+ }
990
+ }
991
+ }
992
+ }
993
+
994
+ /**
995
+ * Remove any axes from the layout param that no longer belong to any series
996
+ * @param layoutParam Layout object to remove stale axes from
997
+ * @param axes All axes in the figure
998
+ */
999
+ removeStaleAxes(layoutParam, axes) {
1000
+ var layout = layoutParam;
1001
+ var figureAxisTypeMap = ChartUtils.groupArray(axes, 'type');
1002
+ var figureAxisTypes = [...figureAxisTypeMap.keys()];
1003
+ for (var i = 0; i < figureAxisTypes.length; i += 1) {
1004
+ var axisType = figureAxisTypes[i];
1005
+ var typeAxes = figureAxisTypeMap.get(axisType);
1006
+ assertNotNull(typeAxes);
1007
+ var axisIndex = typeAxes.length;
1008
+ // Delete any axes that may no longer exist
1009
+ var axisProperty = this.getAxisPropertyName(axisType);
1010
+ if (axisProperty != null) {
1011
+ var axisLayoutProperty = ChartUtils.getAxisLayoutProperty(axisProperty, axisIndex);
1012
+ while (layout[axisLayoutProperty] != null) {
1013
+ delete layout[axisLayoutProperty];
1014
+ axisIndex += 1;
1015
+ axisLayoutProperty = ChartUtils.getAxisLayoutProperty(axisProperty, axisIndex);
1016
+ }
1017
+ }
1018
+ }
1019
+ }
1020
+
1021
+ /**
1022
+ * Updates the layout axis object in place
1023
+ * @param layoutAxisParam The plotly layout axis param
1024
+ * @param axis The Iris Axis to update the plotly layout with
1025
+ * @param axisIndex The type index for this axis
1026
+ * @param axisPositionMap All the axes mapped by position
1027
+ * @param axisSize The size of each axis in percent
1028
+ * @param bounds The bounds of the axes domains
1029
+ */
1030
+ updateLayoutAxis(layoutAxisParam, axis, axisIndex, axisPositionMap, xAxisSize, yAxisSize, bounds) {
1031
+ var _axis$label;
1032
+ var {
1033
+ dh
1034
+ } = this;
1035
+ var isYAxis = axis.type === dh.plot.AxisType.Y;
1036
+ var axisSize = isYAxis ? yAxisSize : xAxisSize;
1037
+ var layoutAxis = layoutAxisParam;
1038
+ // Enterprise API returns null for empty axis labels
1039
+ // Passing null title text to Plotly results in incorrect axis position, DH-9164
1040
+ var label = (_axis$label = axis.label) !== null && _axis$label !== void 0 ? _axis$label : '';
1041
+ if (layoutAxis.title !== undefined && typeof layoutAxis.title !== 'string') {
1042
+ layoutAxis.title.text = label;
1043
+ } else {
1044
+ layoutAxis.title = {
1045
+ text: label
1046
+ };
1047
+ }
1048
+ if (axis.log) {
1049
+ layoutAxis.type = 'log';
1050
+ }
1051
+ layoutAxis.side = this.getAxisSide(axis.position);
1052
+ if (axisIndex > 0) {
1053
+ var _this$getAxisProperty, _axisPositionMap$get2;
1054
+ layoutAxis.overlaying = (_this$getAxisProperty = this.getAxisPropertyName(axis.type)) !== null && _this$getAxisProperty !== void 0 ? _this$getAxisProperty : undefined;
1055
+ var positionAxes = (_axisPositionMap$get2 = axisPositionMap.get(axis.position)) !== null && _axisPositionMap$get2 !== void 0 ? _axisPositionMap$get2 : [];
1056
+ var sideIndex = positionAxes.indexOf(axis);
1057
+ if (sideIndex > 0) {
1058
+ layoutAxis.anchor = 'free';
1059
+ if (axis.position === dh.plot.AxisPosition.RIGHT) {
1060
+ layoutAxis.position = bounds.right + (sideIndex - positionAxes.length + 1) * axisSize;
1061
+ } else if (axis.position === dh.plot.AxisPosition.TOP) {
1062
+ layoutAxis.position = bounds.top + (sideIndex - positionAxes.length + 1) * axisSize;
1063
+ } else if (axis.position === dh.plot.AxisPosition.BOTTOM) {
1064
+ layoutAxis.position = bounds.bottom + (positionAxes.length - sideIndex + 1) * axisSize;
1065
+ } else if (axis.position === dh.plot.AxisPosition.LEFT) {
1066
+ layoutAxis.position = bounds.left + (positionAxes.length - sideIndex + 1) * axisSize;
1067
+ }
1068
+ }
1069
+ } else if (axis.type === dh.plot.AxisType.X) {
1070
+ var leftAxes = axisPositionMap.get(dh.plot.AxisPosition.LEFT) || [];
1071
+ var rightAxes = axisPositionMap.get(dh.plot.AxisPosition.RIGHT) || [];
1072
+ var left = Math.max(bounds.left, bounds.left + (leftAxes.length - 1) * yAxisSize);
1073
+ var right = Math.min(bounds.right - (rightAxes.length - 1) * yAxisSize, bounds.right);
1074
+ layoutAxis.domain = [left, right];
1075
+ } else if (axis.type === dh.plot.AxisType.Y) {
1076
+ var bottomAxes = axisPositionMap.get(dh.plot.AxisPosition.BOTTOM) || [];
1077
+ var topAxes = axisPositionMap.get(dh.plot.AxisPosition.TOP) || [];
1078
+ var bottom = Math.max(bounds.bottom, bounds.bottom + (bottomAxes.length - 1) * xAxisSize);
1079
+ var top = Math.min(bounds.top - (topAxes.length - 1) * xAxisSize, bounds.top);
1080
+ layoutAxis.domain = [bottom, top];
1081
+ }
1082
+ }
1083
+
1084
+ /**
1085
+ * Creates range break bounds for plotly from business days.
1086
+ * For example a standard business week of ['MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY']
1087
+ * will result in [[6,1]] meaning close on Saturday and open on Monday.
1088
+ * If you remove Wednesday from the array, then you get two closures [[6, 1], [3, 4]]
1089
+ *
1090
+ * @param businessDays the days to display on the x-axis
1091
+ */
1092
+ createBoundsFromDays(businessDays) {
1093
+ var businessDaysInt = businessDays.map(day => this.daysOfWeek.indexOf(day));
1094
+ var nonBusinessDaysInt = this.daysOfWeek.filter(day => !businessDays.includes(day)).map(day => this.daysOfWeek.indexOf(day));
1095
+ // These are the days when business reopens (e.g. Monday after a weekend)
1096
+ var reopenDays = new Set();
1097
+ nonBusinessDaysInt.forEach(closed => {
1098
+ for (var i = closed + 1; i < closed + this.daysOfWeek.length; i += 1) {
1099
+ var adjustedDay = i % this.daysOfWeek.length;
1100
+ if (businessDaysInt.includes(adjustedDay)) {
1101
+ reopenDays.add(adjustedDay);
1102
+ break;
1103
+ }
1104
+ }
1105
+ });
1106
+ var boundsArray = [];
1107
+ // For each reopen day, find the furthest previous closed day
1108
+ reopenDays.forEach(open => {
1109
+ for (var i = open - 1; i > open - this.daysOfWeek.length; i -= 1) {
1110
+ var adjustedDay = i < 0 ? i + this.daysOfWeek.length : i;
1111
+ if (businessDaysInt.includes(adjustedDay)) {
1112
+ var closedDay = (adjustedDay + 1) % 7;
1113
+ boundsArray.push([closedDay, open]);
1114
+ break;
1115
+ }
1116
+ }
1117
+ });
1118
+ return boundsArray;
1119
+ }
1120
+
1121
+ /**
1122
+ * Creates an array of range breaks for all holidays.
1123
+ *
1124
+ * @param holidays an array of holidays
1125
+ * @param calendarTimeZone the time zone for the business calendar
1126
+ * @param formatterTimeZone the time zone for the formatter
1127
+ */
1128
+ createRangeBreakValuesFromHolidays(holidays, calendarTimeZone, formatterTimeZone) {
1129
+ var fullHolidays = [];
1130
+ var partialHolidays = [];
1131
+ holidays.forEach(holiday => {
1132
+ if (holiday.businessPeriods.length > 0) {
1133
+ partialHolidays.push(...this.createPartialHoliday(holiday, calendarTimeZone, formatterTimeZone));
1134
+ } else {
1135
+ fullHolidays.push(this.createFullHoliday(holiday, calendarTimeZone, formatterTimeZone));
1136
+ }
1137
+ });
1138
+ return [{
1139
+ values: fullHolidays
1140
+ }, ...partialHolidays];
1141
+ }
1142
+
1143
+ /**
1144
+ * Creates the range break value for a full holiday. A full holiday is day that has no business periods.
1145
+ *
1146
+ * @param holiday the full holiday
1147
+ * @param calendarTimeZone the time zone for the business calendar
1148
+ * @param formatterTimeZone the time zone for the formatter
1149
+ */
1150
+ createFullHoliday(holiday, calendarTimeZone, formatterTimeZone) {
1151
+ return this.adjustDateForTimeZone("".concat(holiday.date.toString(), " 00:00:00.000000"), calendarTimeZone, formatterTimeZone);
1152
+ }
1153
+
1154
+ /**
1155
+ * Creates the range break for a partial holiday. A partial holiday is holiday with business periods
1156
+ * that are different than the default business periods.
1157
+ *
1158
+ * @param holiday the partial holiday
1159
+ * @param calendarTimeZone the time zone for the business calendar
1160
+ * @param formatterTimeZone the time zone for the formatter
1161
+ */
1162
+ createPartialHoliday(holiday, calendarTimeZone, formatterTimeZone) {
1163
+ // If a holiday has business periods {open1, close1} and {open2, close2}
1164
+ // This will generate range breaks for:
1165
+ // closed from 00:00 to open1
1166
+ // closed from close1 to open2
1167
+ // closed from close2 to 23:59:59.999999
1168
+ var dateString = holiday.date.toString();
1169
+ var closedPeriods = ['00:00'];
1170
+ holiday.businessPeriods.forEach(period => {
1171
+ closedPeriods.push(period.open);
1172
+ closedPeriods.push(period.close);
1173
+ });
1174
+ // To go up to 23:59:59.999999, we calculate the dvalue using 24 - close
1175
+ closedPeriods.push('24:00');
1176
+ var rangeBreaks = [];
1177
+ for (var i = 0; i < closedPeriods.length; i += 2) {
1178
+ var startClose = closedPeriods[i];
1179
+ var endClose = closedPeriods[i + 1];
1180
+ // Skip over any periods where start and close are the same (zero hours)
1181
+ if (startClose !== endClose) {
1182
+ var values = [this.adjustDateForTimeZone("".concat(dateString, " ").concat(startClose, ":00.000000"), calendarTimeZone, formatterTimeZone)];
1183
+ var dvalue = MILLIS_PER_HOUR * (ChartUtils.periodToDecimal(endClose) - ChartUtils.periodToDecimal(startClose));
1184
+ rangeBreaks.push({
1185
+ values,
1186
+ dvalue
1187
+ });
1188
+ }
1189
+ }
1190
+ return rangeBreaks;
1191
+ }
1192
+
1193
+ /**
1194
+ * Adjusts a date string from the calendar time zone to the formatter time zone.
1195
+ *
1196
+ * @param dateString the date string
1197
+ * @param calendarTimeZone the time zone for the business calendar
1198
+ * @param formatterTimeZone the time zone for the formatter
1199
+ */
1200
+ adjustDateForTimeZone(dateString, calendarTimeZone, formatterTimeZone) {
1201
+ if (formatterTimeZone && formatterTimeZone.standardOffset !== calendarTimeZone.standardOffset) {
1202
+ return this.unwrapValue(this.wrapValue(dateString, BUSINESS_COLUMN_TYPE, calendarTimeZone), formatterTimeZone);
1203
+ }
1204
+ return dateString;
1205
+ }
1206
+
1207
+ /**
1208
+ * Creates the Figure settings from the Chart Builder settings
1209
+ * This should be deprecated at some point, and have Chart Builder create the figure settings directly.
1210
+ * This logic will still need to exist to translate existing charts, but could be part of a migration script
1211
+ * to translate the data.
1212
+ * Change when we decide to add more functionality to the Chart Builder.
1213
+ * @param settings The chart builder settings
1214
+ * @param settings.title The title for this figure
1215
+ * @param settings.xAxis The name of the column to use for the x-axis
1216
+ * @param settings.series The name of the columns to use for the series of this figure
1217
+ * @param settings.type The plot style for this figure
1218
+ */
1219
+ makeFigureSettings(settings, table) {
1220
+ var {
1221
+ dh
1222
+ } = this;
1223
+ var {
1224
+ series,
1225
+ xAxis: settingsAxis,
1226
+ type
1227
+ } = settings;
1228
+ var title = ChartUtils.titleFromSettings(settings);
1229
+ var xAxis = {
1230
+ formatType: "".concat(dh.plot.AxisFormatType.NUMBER),
1231
+ type: "".concat(dh.plot.AxisType.X),
1232
+ position: "".concat(dh.plot.AxisPosition.BOTTOM)
1233
+ };
1234
+ var yAxis = {
1235
+ formatType: "".concat(dh.plot.AxisFormatType.NUMBER),
1236
+ type: "".concat(dh.plot.AxisType.Y),
1237
+ position: "".concat(dh.plot.AxisPosition.LEFT)
1238
+ };
1239
+ return {
1240
+ charts: [{
1241
+ chartType: "".concat(dh.plot.ChartType.XY),
1242
+ axes: [xAxis, yAxis],
1243
+ series: (series !== null && series !== void 0 ? series : []).map(name => ({
1244
+ plotStyle: "".concat(type),
1245
+ name,
1246
+ dataSources: [{
1247
+ type: "".concat(dh.plot.SourceType.X),
1248
+ columnName: settingsAxis !== null && settingsAxis !== void 0 ? settingsAxis : '',
1249
+ axis: xAxis,
1250
+ table
1251
+ }, {
1252
+ type: "".concat(dh.plot.SourceType.Y),
1253
+ columnName: name,
1254
+ axis: yAxis,
1255
+ table
1256
+ }]
1257
+ }))
1258
+ }],
1259
+ title
1260
+ };
1261
+ }
1262
+
1263
+ /**
1264
+ * Unwraps a value provided from API to a value plotly can understand
1265
+ * Eg. Unwraps DateWrapper, LongWrapper objects.
1266
+ */
1267
+ unwrapValue(value, timeZone) {
1268
+ var {
1269
+ dh
1270
+ } = this;
1271
+ if (value != null) {
1272
+ if (isDateWrapper(value)) {
1273
+ return dh.i18n.DateTimeFormat.format(ChartUtils.DATE_FORMAT, value, timeZone);
1274
+ }
1275
+ if (isLongWrapper(value)) {
1276
+ return value.asNumber();
1277
+ }
1278
+ }
1279
+ return value;
1280
+ }
1281
+
1282
+ /**
1283
+ *
1284
+ * @param value The value to wrap up
1285
+ * @param columnType The type of column this value is from
1286
+ * @param timeZone The time zone if applicable
1287
+ */
1288
+ wrapValue(value, columnType) {
1289
+ var timeZone = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
1290
+ var {
1291
+ dh
1292
+ } = this;
1293
+ if (TableUtils.isDateType(columnType) && typeof value === 'string') {
1294
+ // Need to limit the format to the actual length of the string range set in plotly
1295
+ // Otherwise parse will fail
1296
+ var text = value;
1297
+ var format = ChartUtils.DATE_FORMAT.substr(0, value.length);
1298
+ var date = dh.i18n.DateTimeFormat.parse(format, text);
1299
+ if (!timeZone) {
1300
+ return date;
1301
+ }
1302
+
1303
+ // IDS-5994 Due to date parsing, time zone, and daylight savings shenanigans, we need
1304
+ // to pass the actual offset with the time to have it parse correctly.
1305
+ // However, the offset can change based on the date because of Daylight Savings
1306
+ // So we end up parsing the date multiple times. And curse daylight savings.
1307
+ var tzFormat = "".concat(format, " Z");
1308
+ var estimatedOffset = dh.i18n.DateTimeFormat.format('Z', date, timeZone);
1309
+ var estimatedDate = dh.i18n.DateTimeFormat.parse(tzFormat, "".concat(text, " ").concat(estimatedOffset));
1310
+ var offset = dh.i18n.DateTimeFormat.format('Z', estimatedDate, timeZone);
1311
+ return dh.i18n.DateTimeFormat.parse(tzFormat, "".concat(text, " ").concat(offset));
1312
+ }
1313
+ return value;
1314
+ }
1315
+ makeLayoutAxis(type) {
1316
+ var theme = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ChartTheme;
1317
+ var {
1318
+ dh
1319
+ } = this;
1320
+ var axis = {
1321
+ automargin: true,
1322
+ gridcolor: theme.gridcolor,
1323
+ linecolor: theme.linecolor,
1324
+ rangeslider: {
1325
+ visible: false
1326
+ },
1327
+ showline: true,
1328
+ ticklen: 5,
1329
+ // act as padding, can't find a tick padding
1330
+ tickcolor: theme.paper_bgcolor,
1331
+ // hide ticks as padding
1332
+ tickfont: {
1333
+ color: theme.zerolinecolor
1334
+ },
1335
+ title: {
1336
+ font: {
1337
+ color: theme.title_color
1338
+ }
1339
+ }
1340
+ };
1341
+ if (type === dh.plot.AxisType.X) {
1342
+ Object.assign(axis, {
1343
+ showgrid: true
1344
+ });
1345
+ } else if (type === dh.plot.AxisType.Y) {
1346
+ Object.assign(axis, {
1347
+ zerolinecolor: theme.zerolinecolor,
1348
+ zerolinewidth: 2
1349
+ });
1350
+ }
1351
+ return axis;
1352
+ }
1353
+ makeDefaultLayout() {
1354
+ var theme = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ChartTheme;
1355
+ var {
1356
+ dh
1357
+ } = this;
1358
+ var layout = _objectSpread(_objectSpread({}, theme), {}, {
1359
+ autosize: true,
1360
+ colorway: ChartUtils.getColorwayFromTheme(theme),
1361
+ font: {
1362
+ family: "'Fira Sans', sans-serif",
1363
+ color: theme.title_color
1364
+ },
1365
+ title: {
1366
+ font: {
1367
+ color: theme.title_color
1368
+ },
1369
+ yanchor: 'top',
1370
+ pad: _objectSpread({}, ChartUtils.DEFAULT_TITLE_PADDING),
1371
+ y: 1
1372
+ },
1373
+ legend: {
1374
+ font: {
1375
+ color: theme.title_color
1376
+ }
1377
+ },
1378
+ margin: _objectSpread({}, ChartUtils.DEFAULT_MARGIN),
1379
+ xaxis: this.makeLayoutAxis(dh.plot.AxisType.X, theme),
1380
+ yaxis: this.makeLayoutAxis(dh.plot.AxisType.Y, theme),
1381
+ polar: {
1382
+ angularaxis: this.makeLayoutAxis(dh.plot.AxisType.SHAPE, theme),
1383
+ radialaxis: this.makeLayoutAxis(dh.plot.AxisType.SHAPE, theme),
1384
+ bgcolor: theme.plot_bgcolor
1385
+ },
1386
+ scene: {
1387
+ xaxis: this.makeLayoutAxis(dh.plot.AxisType.X, theme),
1388
+ yaxis: this.makeLayoutAxis(dh.plot.AxisType.Y, theme),
1389
+ zaxis: this.makeLayoutAxis(dh.plot.AxisType.Z, theme)
1390
+ }
1391
+ });
1392
+ layout.datarevision = 0;
1393
+ return layout;
1394
+ }
1395
+
1396
+ /**
1397
+ * Hydrate settings from a JSONable object
1398
+ * @param settings Dehydrated settings
1399
+ */
1400
+ hydrateSettings(settings) {
1401
+ var {
1402
+ dh
1403
+ } = this;
1404
+ return _objectSpread(_objectSpread({}, settings), {}, {
1405
+ type: settings.type != null ? dh.plot.SeriesPlotStyle[settings.type] : undefined
1406
+ });
1407
+ }
1408
+ }
1409
+ _defineProperty(ChartUtils, "DEFAULT_AXIS_SIZE", 0.15);
1410
+ _defineProperty(ChartUtils, "MIN_AXIS_SIZE", 0.025);
1411
+ _defineProperty(ChartUtils, "MAX_AXIS_SIZE", 0.2);
1412
+ _defineProperty(ChartUtils, "AXIS_SIZE_PX", 75);
1413
+ _defineProperty(ChartUtils, "LEGEND_WIDTH_PX", 50);
1414
+ _defineProperty(ChartUtils, "MAX_LEGEND_SIZE", 0.25);
1415
+ _defineProperty(ChartUtils, "ORIENTATION", Object.freeze({
1416
+ HORIZONTAL: 'h',
1417
+ VERTICAL: 'v'
1418
+ }));
1419
+ _defineProperty(ChartUtils, "DATE_FORMAT", 'yyyy-MM-dd HH:mm:ss.SSSSSS');
1420
+ _defineProperty(ChartUtils, "DEFAULT_MARGIN", Object.freeze({
1421
+ l: 60,
1422
+ r: 50,
1423
+ t: 30,
1424
+ b: 60,
1425
+ pad: 0
1426
+ }));
1427
+ _defineProperty(ChartUtils, "DEFAULT_TITLE_PADDING", Object.freeze({
1428
+ t: 8
1429
+ }));
1430
+ _defineProperty(ChartUtils, "SUBTITLE_LINE_HEIGHT", 25);
1431
+ _defineProperty(ChartUtils, "DEFAULT_MARKER_SIZE", 6);
1432
+ _defineProperty(ChartUtils, "MODE_MARKERS", 'markers');
1433
+ _defineProperty(ChartUtils, "MODE_LINES", 'lines');
1434
+ export default ChartUtils;
1435
+ //# sourceMappingURL=ChartUtils.js.map