ngx-hana-nameserver-history-viewer 21.0.0 → 21.1.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.
@@ -2,11 +2,11 @@ import * as i0 from '@angular/core';
2
2
  import { isDevMode, Injectable, EventEmitter, Output, Input, Component, ChangeDetectionStrategy, ElementRef, ViewChild } from '@angular/core';
3
3
  import { SelectionModel } from '@angular/cdk/collections';
4
4
  import moment from 'moment-timezone';
5
- import * as chartjs from 'chart.js';
6
- import { Chart as Chart$2 } from 'chart.js';
5
+ import { Chart, registerables, TimeScale, LinearScale, CategoryScale, PointElement, LineElement, Title, Tooltip, Legend, Filler, Decimation } from 'chart.js';
6
+ import 'chartjs-adapter-moment';
7
+ import zoomPlugin from 'chartjs-plugin-zoom';
7
8
  import { parse } from 'papaparse';
8
- import * as hammerjs from 'hammerjs';
9
- import moment$1 from 'moment';
9
+ import 'hammerjs';
10
10
  import * as i2 from 'nshviewer-angular-datetime-picker';
11
11
  import { OwlDateTimeModule, OwlNativeDateTimeModule } from 'nshviewer-angular-datetime-picker';
12
12
  import { SelectionTableComponent } from 'ngx-selection-table';
@@ -1094,8 +1094,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
1094
1094
  type: Injectable
1095
1095
  }] });
1096
1096
 
1097
- // Using dynamic flag to fix the issue of ng-packagr #696: Lambda not supported
1098
- // @dynamic
1097
+ // Register Chart.js components and plugins
1098
+ Chart.register(...registerables, TimeScale, LinearScale, CategoryScale, PointElement, LineElement, Title, Tooltip, Legend, Filler, Decimation, zoomPlugin);
1099
+ const MAX_POINTS_TO_RENDER = 100;
1099
1100
  class ChartService {
1100
1101
  /**
1101
1102
  * toggle the dataset status of the chart, synchronously
@@ -1131,21 +1132,13 @@ class ChartService {
1131
1132
  */
1132
1133
  static _initConfig(chart) {
1133
1134
  // init data sets
1134
- if (chart.data && chart.data.datasets) {
1135
+ if (chart.data?.datasets) {
1135
1136
  chart.data.datasets.length = 0;
1136
1137
  chart.data.datasets = void 0;
1137
1138
  chart.data = void 0;
1138
1139
  }
1139
1140
  // init options
1140
- if (chart.options && chart.options.scales && (chart.options.scales.xAxes || chart.options.scales.yAxis)) {
1141
- if (chart.options.scales.xAxes) {
1142
- chart.options.scales.xAxes.length = 0;
1143
- chart.options.scales.xAxes = void 0;
1144
- }
1145
- if (chart.options.scales.yAxes) {
1146
- chart.options.scales.yAxes.length = 0;
1147
- chart.options.scales.yAxes = void 0;
1148
- }
1141
+ if (chart.options?.scales) {
1149
1142
  chart.options.scales = void 0;
1150
1143
  chart.options = void 0;
1151
1144
  }
@@ -1161,62 +1154,70 @@ class ChartService {
1161
1154
  return {
1162
1155
  borderColor: color,
1163
1156
  backgroundColor: color,
1164
- borderWidth: 1.25,
1157
+ borderWidth: 1,
1165
1158
  spanGaps: false,
1166
1159
  label: header[i],
1167
1160
  data: data[i],
1168
1161
  fill: false,
1169
1162
  yAxisID: `y-axis-${i}`,
1170
- hidden: !defaultItems.includes(header[i])
1163
+ hidden: !defaultItems.includes(header[i]),
1164
+ pointRadius: 0,
1165
+ pointHitRadius: 5,
1166
+ pointHoverRadius: 5,
1167
+ // Enable smooth curves for more natural line appearance
1168
+ tension: 0.2,
1169
+ // Enable decimation for each dataset
1170
+ parsing: false,
1171
+ normalized: true
1171
1172
  };
1172
1173
  });
1173
1174
  }
1174
- static _generateXAxes(time) {
1175
- const format = 'MMMDD HH:mm:ss';
1176
- return [
1177
- {
1175
+ static _generateScales(timeArray, data, yScaleArray) {
1176
+ const scales = {
1177
+ x: {
1178
1178
  type: 'time',
1179
1179
  time: {
1180
- // unit: 'second',
1181
- // stepSize: 1,
1182
1180
  displayFormats: {
1183
- 'millisecond': format,
1184
- 'second': format,
1185
- 'minute': format,
1181
+ 'millisecond': 'MMMDD HH:mm',
1182
+ 'second': 'MMMDD HH:mm',
1183
+ 'minute': 'MMMDD HH:mm',
1186
1184
  'hour': 'MMMDD HH',
1187
- 'day': 'MMMDD YYYY',
1188
- 'week': format,
1189
- 'month': format,
1190
- 'quarter': format,
1191
- 'year': format,
1185
+ 'day': 'MMMDD',
1186
+ 'week': 'MMMDD',
1187
+ 'month': 'MMM YYYY',
1188
+ 'quarter': 'MMM YYYY',
1189
+ 'year': 'MMM YYYY',
1192
1190
  },
1193
- tooltipFormat: 'll HH:mm:ss',
1194
- min: time[0],
1195
- max: time[time.length - 1]
1191
+ tooltipFormat: 'll HH:mm:ss'
1196
1192
  },
1197
- // scaleLabel: {
1198
- // display: false,
1199
- // labelString: 'Date'
1200
- // },
1193
+ min: timeArray[0],
1194
+ max: timeArray[timeArray.length - 1],
1201
1195
  ticks: {
1196
+ source: 'auto',
1202
1197
  maxRotation: 0,
1203
- reverse: true
1204
- }
1198
+ autoSkip: true,
1199
+ // callback: function(val, index) {
1200
+ // // Hide every 2nd tick label
1201
+ // return index % 2 === 0? this.getLabelForValue(val) : '';
1202
+ // },
1203
+ },
1204
+ reverse: false
1205
1205
  }
1206
- ];
1207
- }
1208
- static _generateYAxes(data, yScale) {
1209
- return data.map((item, i) => ({
1210
- type: 'linear',
1211
- display: false,
1212
- position: 'left',
1213
- id: `y-axis-${i}`,
1214
- gridLines: {},
1215
- ticks: {
1206
+ };
1207
+ // Add Y axes
1208
+ data.forEach((item, i) => {
1209
+ scales[`y-axis-${i}`] = {
1210
+ type: 'linear',
1211
+ display: false,
1212
+ position: 'left',
1213
+ grid: {
1214
+ drawOnChartArea: i === 0, // only show grid for first axis
1215
+ },
1216
1216
  min: 0,
1217
- max: yScale[i]
1218
- }
1219
- }));
1217
+ max: yScaleArray[i]
1218
+ };
1219
+ });
1220
+ return scales;
1220
1221
  }
1221
1222
  /**
1222
1223
  * generate configuration for chart
@@ -1228,70 +1229,117 @@ class ChartService {
1228
1229
  datasets: ChartService._generateDataSets(data, header, headerKey, defaultItems)
1229
1230
  },
1230
1231
  options: {
1231
- elements: { point: { radius: 0, hitRadius: 5, hoverRadius: 5 } }, // set radius to 0 not to display point
1232
- responsive: true,
1233
- animation: {
1234
- duration: 0, // general animation time
1235
- },
1236
- hover: {
1237
- animationDuration: 0, // duration of animations when hovering an item
1238
- onHover: function (e) {
1239
- const point = this.getElementAtEvent(e);
1240
- if (point.length) {
1241
- e.target.style.cursor = 'pointer';
1242
- }
1243
- else {
1244
- e.target.style.cursor = 'default';
1245
- }
1232
+ layout: {
1233
+ padding: {
1234
+ right: 25 // Add padding to the right side of the chart
1246
1235
  }
1247
1236
  },
1248
- responsiveAnimationDuration: 0, // animation duration after a resize
1249
- legend: {
1250
- position: 'bottom',
1251
- onHover: (e) => {
1252
- e.target.style.cursor = 'pointer';
1253
- },
1254
- labels: {
1255
- filter: (legendItem) => selection.isSelected(tableSource.find(row => legendItem.text === row.KPI))
1256
- }
1237
+ interaction: {
1238
+ intersect: true, // true means not a range hit
1239
+ axis: 'x',
1240
+ mode: 'nearest'
1257
1241
  },
1258
- title: {
1259
- display: true,
1260
- text: title
1261
- },
1262
- responsiveDownsample: {
1263
- enabled: true,
1264
- desiredDataPointDistance: 2,
1265
- minNumPoints: 200,
1266
- maxNumPointsToDraw: 100
1242
+ responsive: true,
1243
+ animation: false, // must disable it otherwise zoomin will hang
1244
+ onHover: (event, elements) => {
1245
+ const target = event.native?.target || event.target;
1246
+ if (target) {
1247
+ target.style.cursor = elements?.length > 0 ? 'pointer' : 'default';
1248
+ }
1267
1249
  },
1268
- tooltips: {
1269
- callbacks: {
1270
- label: function (tooltipItem, chartData) {
1271
- // format numbers with commas and add unit information
1272
- const label = chartData.datasets[tooltipItem.datasetIndex].label || '';
1273
- if (label) {
1274
- // get unit (eg: MB, GB, MB/s, % and so on)
1275
- const rowItem = tableSource.find(controlTableRow => label === controlTableRow.KPI) || '';
1276
- const unit = rowItem[Item.unit] && rowItem[Item.unit] !== Unit.PCT ? ` ${rowItem[Item.unit]}` : rowItem[Item.unit] || '';
1277
- return `${label}: ${getNumberWithCommas(tooltipItem.yLabel)}${unit}`;
1250
+ elements: { point: { radius: 0, hitRadius: 5, hoverRadius: 5 } }, // set radius to 0 not to display point
1251
+ parsing: false,
1252
+ normalized: true,
1253
+ datasets: { line: { pointRadius: 0, pointHitRadius: 5, pointHoverRadius: 5 } },
1254
+ plugins: {
1255
+ legend: {
1256
+ position: 'bottom',
1257
+ onHover: (event, legendItem) => {
1258
+ const target = event.native?.target || event.target;
1259
+ if (target) {
1260
+ target.style.cursor = 'pointer';
1278
1261
  }
1279
- else {
1280
- return getNumberWithCommas(tooltipItem.yLabel);
1262
+ },
1263
+ labels: {
1264
+ filter: (legendItem) => selection.isSelected(tableSource.find(row => legendItem.text === row.KPI))
1265
+ }
1266
+ },
1267
+ title: {
1268
+ display: true,
1269
+ text: title
1270
+ },
1271
+ tooltip: {
1272
+ callbacks: {
1273
+ label: function (context) {
1274
+ // format numbers with commas and add unit information
1275
+ const label = context.dataset.label || '';
1276
+ if (label) {
1277
+ // get unit (eg: MB, GB, MB/s, % and so on)
1278
+ const rowItem = tableSource.find(controlTableRow => label === controlTableRow.KPI) || '';
1279
+ const unit = rowItem[Item.unit] && rowItem[Item.unit] !== Unit.PCT ? ` ${rowItem[Item.unit]}` : rowItem[Item.unit] || '';
1280
+ return `${label}: ${getNumberWithCommas(context.parsed.y)}${unit}`;
1281
+ }
1282
+ else {
1283
+ return getNumberWithCommas(context.parsed.y);
1284
+ }
1281
1285
  }
1282
1286
  }
1287
+ },
1288
+ // downsampling
1289
+ decimation: {
1290
+ enabled: true,
1291
+ algorithm: 'lttb', // Largest-Triangle-Three-Buckets algorithm
1292
+ samples: 500, // Increased samples for better quality
1293
+ threshold: 1 // increase it may hang the ui due to swith of using/not using downsampling, disable it (set it to 0), will slowdown average zoom in.
1294
+ },
1295
+ zoom: {
1296
+ limits: {
1297
+ x: {
1298
+ min: 'original',
1299
+ max: 'original',
1300
+ //minRange: 180000 // 3 min the min range that can be zoomed
1301
+ }
1302
+ },
1303
+ pan: {
1304
+ enabled: true,
1305
+ mode: 'x',
1306
+ modifierKey: 'ctrl'
1307
+ },
1308
+ zoom: {
1309
+ // wheel: {
1310
+ // enabled: true,
1311
+ // speed: 0.02
1312
+ // },
1313
+ // pinch: {
1314
+ // enabled: true
1315
+ // },
1316
+ drag: {
1317
+ enabled: true,
1318
+ threshold: 10,
1319
+ drawTime: 'beforeDraw'
1320
+ },
1321
+ mode: 'x',
1322
+ onZoom: zoomCallback,
1323
+ onZoomComplete: (context => {
1324
+ const chart = context?.chart;
1325
+ if (!chart?.data?.datasets) {
1326
+ return;
1327
+ }
1328
+ // make point visible when current points are less than MAX_POINTS_TO_RENDER
1329
+ const pointRadiusChangeFlag = chart.data.datasets.some(dataset => dataset.data.length <= MAX_POINTS_TO_RENDER);
1330
+ const pointRadius = pointRadiusChangeFlag ? 2 : 0;
1331
+ chart.data.datasets.forEach((dataset) => {
1332
+ dataset.pointRadius = pointRadius;
1333
+ });
1334
+ if (chart.options?.elements?.point) {
1335
+ chart.options.elements.point.radius = pointRadius;
1336
+ }
1337
+ chart.update();
1338
+ })
1339
+ }
1283
1340
  }
1284
1341
  },
1285
- scales: {
1286
- xAxes: ChartService._generateXAxes(time),
1287
- yAxes: ChartService._generateYAxes(data, yScale)
1288
- },
1289
- zoom: {
1290
- enabled: true,
1291
- drag: true,
1292
- mode: 'x',
1293
- onZoom: zoomCallback
1294
- }
1342
+ scales: ChartService._generateScales(time, data, yScale)
1295
1343
  }
1296
1344
  };
1297
1345
  }
@@ -1301,12 +1349,65 @@ class ChartService {
1301
1349
  }
1302
1350
  return -1;
1303
1351
  }
1352
+ /**
1353
+ * WORKAROUND: Setup mousemove event listener to fix Chart.js tooltip issue
1354
+ * Issue: https://github.com/chartjs/Chart.js/issues/11972
1355
+ * TODO: Remove this entire method when the Chart.js bug is fixed
1356
+ */
1357
+ _setupTooltipWorkaround(chart) {
1358
+ const canvas = chart.canvas;
1359
+ const mouseMoveHandler = (event) => {
1360
+ const rect = canvas.getBoundingClientRect();
1361
+ const x = event.clientX - rect.left;
1362
+ const y = event.clientY - rect.top;
1363
+ // Check if mouse is within chart area
1364
+ const chartArea = chart.chartArea;
1365
+ if (chartArea) {
1366
+ const isInChartArea = x >= chartArea.left && x <= chartArea.right &&
1367
+ y <= chartArea.bottom && y >= chartArea.top;
1368
+ // Get legend area (legend is positioned at bottom)
1369
+ const legendArea = chart.legend;
1370
+ let isInLegendArea = false;
1371
+ if (legendArea && legendArea.legendHitBoxes) {
1372
+ isInLegendArea = legendArea.legendHitBoxes.some((hitBox) => x >= hitBox.left && x <= hitBox.left + hitBox.width &&
1373
+ y >= hitBox.top && y <= hitBox.top + hitBox.height);
1374
+ }
1375
+ if (!isInChartArea && !isInLegendArea) {
1376
+ // Mouse is in axis area (but not legend), force hide tooltip and active points
1377
+ if (chart.tooltip) {
1378
+ chart.tooltip.setActiveElements([], { x: 0, y: 0 });
1379
+ }
1380
+ // Clear active elements to hide hover points
1381
+ chart.setActiveElements([]);
1382
+ chart.update('none');
1383
+ canvas.style.cursor = 'default';
1384
+ }
1385
+ }
1386
+ };
1387
+ canvas.addEventListener('mousemove', mouseMoveHandler);
1388
+ // Store the handler for cleanup
1389
+ chart._mouseMoveHandler = mouseMoveHandler;
1390
+ }
1391
+ /**
1392
+ * WORKAROUND: Clean up mousemove event listener for Chart.js tooltip issue
1393
+ * Issue: https://github.com/chartjs/Chart.js/issues/11972
1394
+ * TODO: Remove this entire method when the Chart.js bug is fixed
1395
+ */
1396
+ _cleanupTooltipWorkaround(chart) {
1397
+ const canvas = chart.canvas;
1398
+ const mouseMoveHandler = chart._mouseMoveHandler;
1399
+ if (canvas && mouseMoveHandler) {
1400
+ canvas.removeEventListener('mousemove', mouseMoveHandler);
1401
+ }
1402
+ }
1304
1403
  /**
1305
1404
  * destroy the chart, release the resource of the chart
1306
1405
  */
1307
1406
  _destroyChart(chart) {
1308
1407
  return new Promise((resolve) => {
1309
1408
  if (chart) {
1409
+ // WORKAROUND: Clean up tooltip workaround - TODO: Remove when Chart.js bug is fixed
1410
+ this._cleanupTooltipWorkaround(chart);
1310
1411
  chart.destroy();
1311
1412
  }
1312
1413
  resolve();
@@ -1344,7 +1445,10 @@ class ChartService {
1344
1445
  }
1345
1446
  });
1346
1447
  }
1347
- chart.resetZoom();
1448
+ // Use the zoom plugin's resetZoom method
1449
+ if (chart.resetZoom) {
1450
+ chart.resetZoom();
1451
+ }
1348
1452
  resolve(datasetStatusBeforeReset);
1349
1453
  }
1350
1454
  else {
@@ -1391,8 +1495,9 @@ class ChartService {
1391
1495
  return new Promise((resolve, reject) => {
1392
1496
  const ctx = document.getElementById('chartNameServerHistory')?.getContext('2d');
1393
1497
  if (ctx) {
1394
- const cfg = ChartService._generateChartConfig(time, data, yScale, header, headerKey, selection, tableSource, title, defaultItems, zoomCB);
1395
- this._chart = new Chart$2(ctx, cfg);
1498
+ this._chart = new Chart(ctx, ChartService._generateChartConfig(time, data, yScale, header, headerKey, selection, tableSource, title, defaultItems, zoomCB));
1499
+ // WORKAROUND: Setup tooltip workaround - TODO: Remove when Chart.js bug is fixed
1500
+ this._setupTooltipWorkaround(this._chart);
1396
1501
  resolve();
1397
1502
  }
1398
1503
  else {
@@ -1801,1141 +1906,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
1801
1906
  type: Injectable
1802
1907
  }] });
1803
1908
 
1804
- /**
1805
- * This plugin is adapted from: https://github.com/3dcl/chartjs-plugin-responsive-downsample
1806
- * for controlling the radius of point.
1807
- *
1808
- * MIT License
1809
- * Copyright (c) 2018 3D Content Logistics
1810
- *
1811
- * Permission is hereby granted, free of charge, to any person obtaining a copy
1812
- * of this software and associated documentation files (the "Software"), to deal
1813
- * in the Software without restriction, including without limitation the rights
1814
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1815
- * copies of the Software, and to permit persons to whom the Software is
1816
- * furnished to do so, subject to the following conditions:
1817
- * The above copyright notice and this permission notice shall be included in all
1818
- * copies or substantial portions of the Software.
1819
- *
1820
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1821
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1822
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1823
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1824
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1825
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1826
- * SOFTWARE.
1827
- */
1828
- /**
1829
- * Check if a value is null or undefined
1830
- */
1831
- function isNil(value) {
1832
- return (typeof value === 'undefined') || value === null;
1833
- }
1834
- /**
1835
- * Clamp a number to a range
1836
- * @param value
1837
- * @param min
1838
- * @param max
1839
- */
1840
- function clamp(value, min, max) {
1841
- return Math.min(Math.max(value, min), max);
1842
- }
1843
- /**
1844
- * Recursively assign default values to an object if object is missing the keys.
1845
- * @param object The destination object to assign default values to
1846
- * @param defaults The default values for the object
1847
- * @return The destination object
1848
- */
1849
- function defaultsDeep(object, defaults) {
1850
- for (const key in defaults) {
1851
- if (defaults.hasOwnProperty(key)) {
1852
- const value = object[key];
1853
- if (typeof value === 'undefined') {
1854
- object[key] = defaults[key];
1855
- }
1856
- else if (value !== null && typeof value === 'object') {
1857
- object[key] = defaultsDeep(value, defaults[key]);
1858
- }
1859
- }
1860
- }
1861
- return object;
1862
- }
1863
- /**
1864
- * Finds the first element in an array for that the comaperator functions returns true
1865
- *
1866
- * @export
1867
- * @param array An array
1868
- * @param compareFunction Comperator function returning true for the element seeked
1869
- * @returns The found element or undefined
1870
- */
1871
- function findInArray(array, compareFunction) {
1872
- if (isNil(array)) {
1873
- return undefined;
1874
- }
1875
- for (let i = 0; i < array.length; i++) {
1876
- if (compareFunction(array[i]) === true) {
1877
- return array[i];
1878
- }
1879
- }
1880
- return undefined;
1881
- }
1882
- /**
1883
- * Finds the first index in an array for that the comaperator function for an element returns true
1884
- *
1885
- * @export
1886
- * @param array An array of elements
1887
- * @param compareFunction Comperator function returning true for the element seeked
1888
- * @returns Index of the matched element or -1 if no element was found
1889
- */
1890
- function findIndexInArray(array, compareFunction) {
1891
- if (isNil(array)) {
1892
- return undefined;
1893
- }
1894
- for (let i = 0; i < array.length; i++) {
1895
- if (compareFunction(array[i]) === true) {
1896
- return i;
1897
- }
1898
- }
1899
- return -1;
1900
- }
1901
- /**
1902
- * If points number is less than minNumPoints, show radius for all points.
1903
- */
1904
- function changePointRadius(chart, options) {
1905
- if (chart && chart.data && chart.data.datasets && options) {
1906
- let pointRadiusChangeFlag = false;
1907
- const maxNumPointsToDraw = options.maxNumPointsToDraw == null ? 100 : options.maxNumPointsToDraw;
1908
- for (const dataset of chart.data.datasets) {
1909
- if (dataset.data.length < maxNumPointsToDraw) {
1910
- pointRadiusChangeFlag = true;
1911
- break;
1912
- }
1913
- }
1914
- chart.options.elements.point.radius = pointRadiusChangeFlag ? 2 : 0;
1915
- }
1916
- }
1917
-
1918
- /**
1919
- * A mipmap data structure for line chart data. Uses averages to downsample data.
1920
- */
1921
- class DataMipmap {
1922
- /**
1923
- * Create a data mipmap
1924
- * @param data The orignal line chart data
1925
- * @param minNumPoints Minimal number of points on lowest mipmap level(limits number of levels)
1926
- */
1927
- constructor(data, minNumPoints = 2) {
1928
- this.minNumPoints = minNumPoints;
1929
- this.setData(data);
1930
- }
1931
- /**
1932
- * Set the line chart data and update mipmap level.
1933
- * @param data The orignal line chart data
1934
- */
1935
- setData(data) {
1936
- this.originalData = data || [];
1937
- this.mipMaps = [];
1938
- this.resolution = this.computeResolution(this.originalData);
1939
- this.createMipMap();
1940
- }
1941
- /**
1942
- * Set the minimal number of points
1943
- * @param minNumPoints Minimal number of points on lowest mipmap level(limits number of levels)
1944
- */
1945
- setMinNumPoints(minNumPoints) {
1946
- this.minNumPoints = minNumPoints;
1947
- this.mipMaps = [];
1948
- this.createMipMap();
1949
- }
1950
- /**
1951
- * Get the best fitting mipmap level for a certain scale resolution
1952
- * @param resolution Desired resolution in ms per pixel
1953
- */
1954
- getMipMapForResolution(resolution) {
1955
- return this.getMipMapLevel(this.getMipMapIndexForResolution(resolution));
1956
- }
1957
- /**
1958
- * Computes the index of the best fitting mipmap level for a certain scale resolution
1959
- * @param resolution Desired resolution in ms per pixel
1960
- */
1961
- getMipMapIndexForResolution(resolution) {
1962
- if (isNil(resolution)) {
1963
- return 0;
1964
- }
1965
- const factor = resolution / this.resolution;
1966
- return clamp(Math.floor(Math.log(factor) / Math.log(2.0)), 0, this.mipMaps.length - 1);
1967
- }
1968
- /**
1969
- * Get a mipmap level by index
1970
- * @param level The index of the mipmap level
1971
- */
1972
- getMipMapLevel(level) {
1973
- return this.mipMaps[level];
1974
- }
1975
- /**
1976
- * Get all mipmap level
1977
- */
1978
- getMipMaps() {
1979
- return this.mipMaps;
1980
- }
1981
- /**
1982
- * Get the number of available mipmap level
1983
- */
1984
- getNumLevel() {
1985
- return this.mipMaps.length;
1986
- }
1987
- computeResolution(data) {
1988
- let minTimeDistance = Infinity;
1989
- for (let i = 0, end = this.originalData.length - 1; i < end; ++i) {
1990
- const current = this.originalData[i];
1991
- const next = this.originalData[i + 1];
1992
- minTimeDistance = Math.min(Math.abs(this.getTime(current) - this.getTime(next)), minTimeDistance);
1993
- }
1994
- return minTimeDistance;
1995
- }
1996
- createMipMap() {
1997
- let targetResolution = this.resolution;
1998
- let targetLength = this.originalData.length;
1999
- this.mipMaps.push(this.originalData);
2000
- let lastMipMap = this.originalData;
2001
- while (lastMipMap.length > this.minNumPoints) {
2002
- targetResolution = targetResolution * 2;
2003
- targetLength = Math.floor(targetLength * 0.5);
2004
- lastMipMap = this.downsampleToResolution(lastMipMap, targetResolution, targetLength);
2005
- this.mipMaps.push(lastMipMap);
2006
- }
2007
- }
2008
- downsampleToResolution(data, targetResolution, targetLength) {
2009
- const output = [];
2010
- let aggregationValues = [];
2011
- let firstPoint = data[0];
2012
- aggregationValues.push(firstPoint);
2013
- for (let i = 1, end = data.length; i < end; ++i) {
2014
- const currentPoint = data[i];
2015
- const timeDistance = Math.abs(this.getTime(firstPoint) - this.getTime(currentPoint));
2016
- if (timeDistance < targetResolution) {
2017
- aggregationValues.push(currentPoint);
2018
- }
2019
- else {
2020
- // create new sensor value in output
2021
- const newPoint = this.getAverage(aggregationValues);
2022
- output.push(newPoint);
2023
- // reset aggregation data structure
2024
- firstPoint = currentPoint;
2025
- aggregationValues = [currentPoint];
2026
- }
2027
- }
2028
- // insert last point
2029
- output.push(this.getAverage(aggregationValues));
2030
- return output;
2031
- }
2032
- getAverage(aggregationValues) {
2033
- const value = aggregationValues
2034
- .map(point => point.y)
2035
- .reduce((previous, current) => previous + current)
2036
- / aggregationValues.length;
2037
- return {
2038
- x: aggregationValues[0].x || aggregationValues[0].t,
2039
- y: value
2040
- };
2041
- }
2042
- getTime(point) {
2043
- const x = point.x || point.t;
2044
- if (typeof x === 'number') {
2045
- return x;
2046
- }
2047
- else if (typeof x === 'string') {
2048
- return new Date(x).getTime();
2049
- }
2050
- else {
2051
- return x.getTime();
2052
- }
2053
- }
2054
- }
2055
-
2056
- /**
2057
- * A mipmap data structure that uses Largest-Triangle-Three-Buckets algorithm to downsample data
2058
- */
2059
- class LTTBDataMipmap extends DataMipmap {
2060
- getMipMapIndexForResolution(resolution) {
2061
- if (isNil(resolution)) {
2062
- return 0;
2063
- }
2064
- let index = findIndexInArray(this.resolutions, (levelResolution) => levelResolution >= resolution);
2065
- if (index === -1) {
2066
- // use smallest mipmap as fallback
2067
- index = this.resolutions.length - 1;
2068
- }
2069
- return index;
2070
- }
2071
- createMipMap() {
2072
- super.createMipMap();
2073
- this.resolutions = this.mipMaps.map((level) => this.computeAverageResolution(level));
2074
- }
2075
- /**
2076
- * This method is adapted from: https://github.com/sveinn-steinarsson/flot-downsample
2077
- *
2078
- * The MIT License
2079
- * Copyright (c) 2013 by Sveinn Steinarsson
2080
- * Permission is hereby granted, free of charge, to any person obtaining a copy
2081
- * of this software and associated documentation files (the "Software"), to deal
2082
- * in the Software without restriction, including without limitation the rights
2083
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2084
- * copies of the Software, and to permit persons to whom the Software is
2085
- * furnished to do so, subject to the following conditions:
2086
- * The above copyright notice and this permission notice shall be included in
2087
- * all copies or substantial portions of the Software.
2088
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2089
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2090
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2091
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2092
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2093
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2094
- * THE SOFTWARE.
2095
- */
2096
- downsampleToResolution(data, targetResolution, targetLength) {
2097
- const dataLength = data.length;
2098
- if (targetLength >= dataLength || targetLength === 0) {
2099
- return data; // data has target size
2100
- }
2101
- const output = [];
2102
- // bucket size, leave room for start and end data points
2103
- const bucksetSize = (dataLength - 2) / (targetLength - 2);
2104
- let a = 0; // initially a is the first point in the triangle
2105
- let maxAreaPoint;
2106
- let maxArea;
2107
- let area;
2108
- let nextA;
2109
- // always add the first point
2110
- output.push(data[a]);
2111
- for (let i = 0; i < targetLength - 2; ++i) {
2112
- // Calculate point average for next bucket (containing c)
2113
- let avgX = 0;
2114
- let avgY = 0;
2115
- let avgRangeStart = Math.floor((i + 1) * bucksetSize) + 1;
2116
- let avgRangeEnd = Math.floor((i + 2) * bucksetSize) + 1;
2117
- avgRangeEnd = avgRangeEnd < dataLength ? avgRangeEnd : dataLength;
2118
- const avgRangeLength = avgRangeEnd - avgRangeStart;
2119
- for (; avgRangeStart < avgRangeEnd; avgRangeStart++) {
2120
- avgX += this.getTime(data[avgRangeStart]);
2121
- avgY += data[avgRangeStart].y * 1;
2122
- }
2123
- avgX /= avgRangeLength;
2124
- avgY /= avgRangeLength;
2125
- // Get the range for this bucket
2126
- let rangeOffs = Math.floor((i + 0) * bucksetSize) + 1;
2127
- const rangeTo = Math.floor((i + 1) * bucksetSize) + 1;
2128
- // Point a
2129
- const pointA = data[a];
2130
- const pointAX = this.getTime(pointA);
2131
- const pointAY = pointA.y * 1;
2132
- maxArea = area = -1;
2133
- for (; rangeOffs < rangeTo; rangeOffs++) {
2134
- // Calculate triangle area over three buckets
2135
- area = Math.abs((pointAX - avgX) * (data[rangeOffs].y - pointAY) -
2136
- (pointAX - this.getTime(data[rangeOffs])) * (avgY - pointAY)) * 0.5;
2137
- if (area > maxArea) {
2138
- maxArea = area;
2139
- maxAreaPoint = data[rangeOffs];
2140
- nextA = rangeOffs; // Next a is this b
2141
- }
2142
- }
2143
- output.push(maxAreaPoint); // Pick this point from the bucket
2144
- a = nextA; // This a is the next a (chosen b)
2145
- }
2146
- output.push(data[dataLength - 1]); // Always add last
2147
- return output;
2148
- }
2149
- computeAverageResolution(data) {
2150
- let timeDistances = 0;
2151
- for (let i = 0, end = this.originalData.length - 1; i < end; ++i) {
2152
- const current = this.originalData[i];
2153
- const next = this.originalData[i + 1];
2154
- timeDistances += Math.abs(this.getTime(current) - this.getTime(next));
2155
- }
2156
- return timeDistances / (data.length - 1);
2157
- }
2158
- }
2159
-
2160
- function getCompareValue(value) {
2161
- if (typeof value === 'number') {
2162
- return value;
2163
- }
2164
- else if (typeof value === 'string') {
2165
- return (new Date(value)).getTime();
2166
- }
2167
- else if (value instanceof Date) {
2168
- return value.getTime();
2169
- }
2170
- else {
2171
- return moment$1(value).toDate().getTime();
2172
- }
2173
- }
2174
- function rangeIsEqual(previousValue, currentValue) {
2175
- if (isNil(previousValue) ||
2176
- isNil(currentValue) ||
2177
- isNil(previousValue[0]) ||
2178
- isNil(previousValue[1]) ||
2179
- isNil(currentValue[0]) ||
2180
- isNil(currentValue[1])) {
2181
- return false;
2182
- }
2183
- previousValue = [getCompareValue(previousValue[0]), getCompareValue(previousValue[1])];
2184
- currentValue = [getCompareValue(currentValue[0]), getCompareValue(currentValue[1])];
2185
- return previousValue[0] === currentValue[0] && previousValue[1] === currentValue[1];
2186
- }
2187
- function getScaleRange(scale) {
2188
- if (isNil(scale)) {
2189
- return [null, null];
2190
- }
2191
- const start = scale.getValueForPixel(scale.left);
2192
- const end = scale.getValueForPixel(scale.right);
2193
- return [start, end];
2194
- }
2195
- function cullData(data, range) {
2196
- const startValue = getCompareValue(range[0]);
2197
- const endValue = getCompareValue(range[1]);
2198
- let startIndex = 0;
2199
- let endIndex = data.length;
2200
- for (let i = 1; i < data.length; ++i) {
2201
- const point = data[i];
2202
- const compareValue = getCompareValue(point.x || point.t);
2203
- if (compareValue <= startValue) {
2204
- startIndex = i;
2205
- }
2206
- if (compareValue >= endValue) {
2207
- endIndex = i + 1;
2208
- break;
2209
- }
2210
- }
2211
- return data.slice(startIndex, endIndex);
2212
- }
2213
-
2214
- /**
2215
- * This plugin is adapted from: https://github.com/3dcl/chartjs-plugin-responsive-downsample
2216
- * for controlling the radius of point.
2217
- *
2218
- * MIT License
2219
- * Copyright (c) 2018 3D Content Logistics
2220
- *
2221
- * Permission is hereby granted, free of charge, to any person obtaining a copy
2222
- * of this software and associated documentation files (the "Software"), to deal
2223
- * in the Software without restriction, including without limitation the rights
2224
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2225
- * copies of the Software, and to permit persons to whom the Software is
2226
- * furnished to do so, subject to the following conditions:
2227
- * The above copyright notice and this permission notice shall be included in all
2228
- * copies or substantial portions of the Software.
2229
- *
2230
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2231
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2232
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2233
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2234
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2235
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2236
- * SOFTWARE.
2237
- */
2238
- // Using dynamic flag to fix the issue of ng-packagr #696: Lambda not supported
2239
- // @dynamic
2240
- /**
2241
- * Chart js Plugin for downsampling data
2242
- */
2243
- class ResponsiveDownsamplePlugin {
2244
- static getPluginOptions(chart) {
2245
- const options = chart.options.responsiveDownsample || {};
2246
- defaultsDeep(options, {
2247
- enabled: false,
2248
- aggregationAlgorithm: 'LTTB',
2249
- desiredDataPointDistance: 1,
2250
- minNumPoints: 100,
2251
- maxNumPointsToDraw: 100,
2252
- cullData: true
2253
- });
2254
- if (options.enabled) {
2255
- chart.options.responsiveDownsample = options;
2256
- }
2257
- return options;
2258
- }
2259
- static hasDataChanged(chart) {
2260
- return !isNil(findInArray(chart.data.datasets, (dataset) => {
2261
- return isNil(dataset.mipMap);
2262
- }));
2263
- }
2264
- static createDataMipMap(chart, options) {
2265
- chart.data.datasets.forEach((dataset) => {
2266
- // @ts-ignore
2267
- const data = !isNil(dataset.originalData) ? dataset.originalData : dataset.data;
2268
- const mipMap = (options.aggregationAlgorithm === 'LTTB')
2269
- ? new LTTBDataMipmap(data, options.minNumPoints)
2270
- : new DataMipmap(data, options.minNumPoints);
2271
- dataset.originalData = data;
2272
- dataset.mipMap = mipMap;
2273
- // @ts-ignore
2274
- dataset.data = mipMap.getMipMapLevel(mipMap.getNumLevel() - 1); // set last level for first render pass
2275
- });
2276
- }
2277
- static restoreOriginalData(chart) {
2278
- let updated = false;
2279
- chart.data.datasets.forEach((dataset) => {
2280
- // @ts-ignore
2281
- if (!isNil(dataset.originalData) && dataset.data !== dataset.originalData) {
2282
- // @ts-ignore
2283
- dataset.data = dataset.originalData;
2284
- updated = true;
2285
- }
2286
- });
2287
- return updated;
2288
- }
2289
- static getTargetResolution(chart, options) {
2290
- const xScale = chart.scales['x-axis-0'];
2291
- if (isNil(xScale)) {
2292
- return null;
2293
- }
2294
- const start = moment$1(xScale.getValueForPixel(xScale.left));
2295
- const end = moment$1(xScale.getValueForPixel(xScale.left + 1));
2296
- const targetResolution = end.diff(start);
2297
- return targetResolution * options.desiredDataPointDistance;
2298
- }
2299
- static updateMipMap(chart, options, rangeChanged) {
2300
- let updated = false;
2301
- chart.data.datasets.forEach((dataset) => {
2302
- const mipMap = dataset.mipMap;
2303
- if (isNil(mipMap)) {
2304
- return;
2305
- }
2306
- const mipMalLevel = mipMap.getMipMapIndexForResolution(options.targetResolution);
2307
- if (mipMalLevel === dataset.currentMipMapLevel && !rangeChanged) {
2308
- // skip update if mip map level and data range did not change
2309
- return;
2310
- }
2311
- updated = true;
2312
- dataset.currentMipMapLevel = mipMalLevel;
2313
- let newData = mipMap.getMipMapLevel(mipMalLevel);
2314
- if (options.cullData) {
2315
- newData = cullData(newData, options.scaleRange);
2316
- }
2317
- // @ts-ignore
2318
- dataset.data = newData;
2319
- });
2320
- return updated;
2321
- }
2322
- beforeInit(chart) {
2323
- const options = ResponsiveDownsamplePlugin.getPluginOptions(chart);
2324
- if (!options.enabled) {
2325
- return;
2326
- }
2327
- ResponsiveDownsamplePlugin.createDataMipMap(chart, options);
2328
- options.needsUpdate = true;
2329
- }
2330
- beforeDatasetsUpdate(chart) {
2331
- const options = ResponsiveDownsamplePlugin.getPluginOptions(chart);
2332
- if (!options.enabled) {
2333
- // restore original data and remove state from options
2334
- options.needsUpdate = ResponsiveDownsamplePlugin.restoreOriginalData(chart);
2335
- delete options.targetResolution;
2336
- delete options.scaleRange;
2337
- return;
2338
- }
2339
- // only update mip map if data set was reloaded externally
2340
- if (ResponsiveDownsamplePlugin.hasDataChanged(chart)) {
2341
- ResponsiveDownsamplePlugin.createDataMipMap(chart, options);
2342
- options.needsUpdate = true;
2343
- }
2344
- }
2345
- beforeRender(chart) {
2346
- const options = ResponsiveDownsamplePlugin.getPluginOptions(chart);
2347
- if (!options.enabled) {
2348
- // update chart if data was restored from original data
2349
- if (options.needsUpdate) {
2350
- options.needsUpdate = false;
2351
- chart.update(0);
2352
- return false;
2353
- }
2354
- return true;
2355
- }
2356
- const targetResolution = ResponsiveDownsamplePlugin.getTargetResolution(chart, options);
2357
- const xScale = chart.scales['x-axis-0'];
2358
- const scaleRange = getScaleRange(xScale);
2359
- const rangeChanged = !rangeIsEqual(options.scaleRange, scaleRange);
2360
- if (options.needsUpdate ||
2361
- options.targetResolution !== targetResolution ||
2362
- rangeChanged) {
2363
- options.targetResolution = targetResolution;
2364
- options.scaleRange = scaleRange;
2365
- options.needsUpdate = false;
2366
- if (ResponsiveDownsamplePlugin.updateMipMap(chart, options, rangeChanged)) {
2367
- changePointRadius(chart, options);
2368
- // update chart and cancel current render
2369
- chart.update(0);
2370
- return false;
2371
- }
2372
- }
2373
- return true;
2374
- }
2375
- }
2376
-
2377
- /**
2378
- * This plugin is adapted from: https://github.com/3dcl/chartjs-plugin-responsive-downsample
2379
- * for controlling the radius of point.
2380
- *
2381
- * MIT License
2382
- * Copyright (c) 2018 3D Content Logistics
2383
- *
2384
- * Permission is hereby granted, free of charge, to any person obtaining a copy
2385
- * of this software and associated documentation files (the "Software"), to deal
2386
- * in the Software without restriction, including without limitation the rights
2387
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2388
- * copies of the Software, and to permit persons to whom the Software is
2389
- * furnished to do so, subject to the following conditions:
2390
- * The above copyright notice and this permission notice shall be included in all
2391
- * copies or substantial portions of the Software.
2392
- *
2393
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2394
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2395
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2396
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2397
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2398
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2399
- * SOFTWARE.
2400
- */
2401
- const Chart$1 = window && window.Chart ? window.Chart : chartjs.Chart;
2402
- // comment this because this plugin is only used internally, merge ChartOptions interface is unnecessary
2403
- // and causes build error: "error TS2665: Invalid module name in augmentation.".
2404
- // // Extend chart.js options interface
2405
- // declare module 'chart.js' {
2406
- // interface ChartOptions {
2407
- // /**
2408
- // * Options for responsive downsample plugin
2409
- // */
2410
- // responsiveDownsample?: ResponsiveDownsamplePluginOptions;
2411
- // }
2412
- // }
2413
- Chart$1.pluginService.register(new ResponsiveDownsamplePlugin());
2414
-
2415
- /**
2416
- * this plugin is adapted from: https://github.com/chartjs/chartjs-plugin-zoom
2417
- * for following two open issues:
2418
- * https://github.com/chartjs/chartjs-plugin-zoom/pull/155
2419
- * https://github.com/chartjs/chartjs-plugin-zoom/pull/150
2420
- *
2421
- * The MIT License (MIT) Copyright (c) 2013-2016 Nick Downie
2422
- * Permission is hereby granted, free of charge, to any person obtaining a copy of this
2423
- * software and associated documentation files (the "Software"), to deal in the Software
2424
- * without restriction, including without limitation the rights to use, copy, modify, merge,
2425
- * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
2426
- * to whom the Software is furnished to do so, subject to the following conditions:
2427
- *
2428
- * The above copyright notice and this permission notice shall be included in all copies or
2429
- * substantial portions of the Software.
2430
- *
2431
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
2432
- * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
2433
- * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
2434
- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
2435
- * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2436
- * THE SOFTWARE.
2437
- */
2438
- /*jslint browser:true, devel:true, white:true, vars:true */
2439
- /*global require*/
2440
- // hammer JS for touch support
2441
- const Hammer = window && window.Hammer ? window.Hammer : hammerjs.Hammer;
2442
- const Chart = window && window.Chart ? window.Chart : chartjs.Chart;
2443
- const helpers = Chart.helpers;
2444
- // Take the zoom namespace of Chart
2445
- const zoomNS = Chart.Zoom = Chart.Zoom || {};
2446
- // Where we store functions to handle different scale types
2447
- const zoomFunctions = zoomNS.zoomFunctions = zoomNS.zoomFunctions || {};
2448
- const panFunctions = zoomNS.panFunctions = zoomNS.panFunctions || {};
2449
- // Default options if none are provided
2450
- const defaultOptions = zoomNS.defaults = {
2451
- pan: {
2452
- enabled: true,
2453
- mode: 'xy',
2454
- speed: 20,
2455
- threshold: 10
2456
- },
2457
- zoom: {
2458
- enabled: true,
2459
- mode: 'xy',
2460
- sensitivity: 3
2461
- }
2462
- };
2463
- function directionEnabled(mode, dir) {
2464
- if (mode === undefined) {
2465
- return true;
2466
- }
2467
- else if (typeof mode === 'string') {
2468
- return mode.indexOf(dir) !== -1;
2469
- }
2470
- return false;
2471
- }
2472
- function rangeMaxLimiter(zoomPanOptions, newMax) {
2473
- if (zoomPanOptions.scaleAxes && zoomPanOptions.rangeMax &&
2474
- !helpers.isNullOrUndef(zoomPanOptions.rangeMax[zoomPanOptions.scaleAxes])) {
2475
- const rangeMax = zoomPanOptions.rangeMax[zoomPanOptions.scaleAxes];
2476
- if (newMax > rangeMax) {
2477
- newMax = rangeMax;
2478
- }
2479
- }
2480
- return newMax;
2481
- }
2482
- function rangeMinLimiter(zoomPanOptions, newMin) {
2483
- if (zoomPanOptions.scaleAxes && zoomPanOptions.rangeMin &&
2484
- !helpers.isNullOrUndef(zoomPanOptions.rangeMin[zoomPanOptions.scaleAxes])) {
2485
- const rangeMin = zoomPanOptions.rangeMin[zoomPanOptions.scaleAxes];
2486
- if (newMin < rangeMin) {
2487
- newMin = rangeMin;
2488
- }
2489
- }
2490
- return newMin;
2491
- }
2492
- function zoomIndexScale(scale, zoom, center, zoomOptions) {
2493
- const labels = scale.chart.data.labels;
2494
- let minIndex = scale.minIndex;
2495
- const lastLabelIndex = labels.length - 1;
2496
- let maxIndex = scale.maxIndex;
2497
- const sensitivity = zoomOptions.sensitivity;
2498
- const chartCenter = scale.isHorizontal() ? scale.left + (scale.width / 2) : scale.top + (scale.height / 2);
2499
- const centerPointer = scale.isHorizontal() ? center.x : center.y;
2500
- zoomNS.zoomCumulativeDelta = zoom > 1 ? zoomNS.zoomCumulativeDelta + 1 : zoomNS.zoomCumulativeDelta - 1;
2501
- if (Math.abs(zoomNS.zoomCumulativeDelta) > sensitivity) {
2502
- if (zoomNS.zoomCumulativeDelta < 0) {
2503
- if (centerPointer >= chartCenter) {
2504
- if (minIndex <= 0) {
2505
- maxIndex = Math.min(lastLabelIndex, maxIndex + 1);
2506
- }
2507
- else {
2508
- minIndex = Math.max(0, minIndex - 1);
2509
- }
2510
- }
2511
- else if (centerPointer < chartCenter) {
2512
- if (maxIndex >= lastLabelIndex) {
2513
- minIndex = Math.max(0, minIndex - 1);
2514
- }
2515
- else {
2516
- maxIndex = Math.min(lastLabelIndex, maxIndex + 1);
2517
- }
2518
- }
2519
- zoomNS.zoomCumulativeDelta = 0;
2520
- }
2521
- else if (zoomNS.zoomCumulativeDelta > 0) {
2522
- if (centerPointer >= chartCenter) {
2523
- minIndex = minIndex < maxIndex ? minIndex = Math.min(maxIndex, minIndex + 1) : minIndex;
2524
- }
2525
- else if (centerPointer < chartCenter) {
2526
- maxIndex = maxIndex > minIndex ? maxIndex = Math.max(minIndex, maxIndex - 1) : maxIndex;
2527
- }
2528
- zoomNS.zoomCumulativeDelta = 0;
2529
- }
2530
- scale.options.ticks.min = rangeMinLimiter(zoomOptions, labels[minIndex]);
2531
- scale.options.ticks.max = rangeMaxLimiter(zoomOptions, labels[maxIndex]);
2532
- }
2533
- }
2534
- function zoomTimeScale(scale, zoom, center, zoomOptions) {
2535
- const options = scale.options;
2536
- // var range;
2537
- // var min_percent;
2538
- // if (scale.isHorizontal()) {
2539
- // range = scale.right - scale.left;
2540
- // min_percent = (center.x - scale.left) / range;
2541
- // } else {
2542
- // range = scale.bottom - scale.top;
2543
- // min_percent = (center.y - scale.top) / range;
2544
- // }
2545
- //
2546
- // var max_percent = 1 - min_percent;
2547
- const range = scale.max - scale.min;
2548
- const newDiff = range * (zoom - 1);
2549
- const cursorPixel = scale.isHorizontal() ? center.x : center.y;
2550
- const min_percent = (scale.getValueForPixel(cursorPixel) - scale.min) / range;
2551
- const max_percent = 1 - min_percent;
2552
- const minDelta = newDiff * min_percent;
2553
- const maxDelta = newDiff * max_percent;
2554
- const newMin = scale.min + minDelta;
2555
- const newMax = scale.max - maxDelta;
2556
- const diffMinMax = newMax - newMin;
2557
- const minLimitExceeded = rangeMinLimiter(zoomOptions, diffMinMax) !== diffMinMax;
2558
- const maxLimitExceeded = rangeMaxLimiter(zoomOptions, diffMinMax) !== diffMinMax;
2559
- if (!minLimitExceeded && !maxLimitExceeded) {
2560
- options.time.min = newMin;
2561
- options.time.max = newMax;
2562
- }
2563
- }
2564
- function zoomNumericalScale(scale, zoom, center, zoomOptions) {
2565
- const range = scale.max - scale.min;
2566
- const newDiff = range * (zoom - 1);
2567
- const cursorPixel = scale.isHorizontal() ? center.x : center.y;
2568
- const min_percent = (scale.getValueForPixel(cursorPixel) - scale.min) / range;
2569
- const max_percent = 1 - min_percent;
2570
- const minDelta = newDiff * min_percent;
2571
- const maxDelta = newDiff * max_percent;
2572
- scale.options.ticks.min = rangeMinLimiter(zoomOptions, scale.min + minDelta);
2573
- scale.options.ticks.max = rangeMaxLimiter(zoomOptions, scale.max - maxDelta);
2574
- }
2575
- function zoomScale(scale, zoom, center, zoomOptions) {
2576
- const fn = zoomFunctions[scale.options.type];
2577
- if (fn) {
2578
- fn(scale, zoom, center, zoomOptions);
2579
- }
2580
- }
2581
- function doZoom(chartInstance, zoom, center, whichAxes) {
2582
- const ca = chartInstance.chartArea;
2583
- if (!center) {
2584
- center = {
2585
- x: (ca.left + ca.right) / 2,
2586
- y: (ca.top + ca.bottom) / 2,
2587
- };
2588
- }
2589
- const zoomOptions = chartInstance.options.zoom;
2590
- if (zoomOptions && helpers.getValueOrDefault(zoomOptions.enabled, defaultOptions.zoom.enabled)) {
2591
- // Do the zoom here
2592
- const zoomMode = helpers.getValueOrDefault(chartInstance.options.zoom.mode, defaultOptions.zoom.mode);
2593
- zoomOptions.sensitivity = helpers.getValueOrDefault(chartInstance.options.zoom.sensitivity, defaultOptions.zoom.sensitivity);
2594
- // Which axe should be modified when figers were used.
2595
- let _whichAxes;
2596
- if (zoomMode === 'xy' && whichAxes !== undefined) {
2597
- // based on fingers positions
2598
- _whichAxes = whichAxes;
2599
- }
2600
- else {
2601
- // no effect
2602
- _whichAxes = 'xy';
2603
- }
2604
- helpers.each(chartInstance.scales, function (scale, id) {
2605
- if (scale.isHorizontal() && directionEnabled(zoomMode, 'x') && directionEnabled(_whichAxes, 'x')) {
2606
- zoomOptions.scaleAxes = 'x';
2607
- zoomScale(scale, zoom, center, zoomOptions);
2608
- }
2609
- else if (!scale.isHorizontal() && directionEnabled(zoomMode, 'y') && directionEnabled(_whichAxes, 'y')) {
2610
- // Do Y zoom
2611
- zoomOptions.scaleAxes = 'y';
2612
- zoomScale(scale, zoom, center, zoomOptions);
2613
- }
2614
- });
2615
- chartInstance.update(0);
2616
- if (typeof zoomOptions.onZoom === 'function') {
2617
- zoomOptions.onZoom();
2618
- }
2619
- }
2620
- }
2621
- function panIndexScale(scale, delta, panOptions) {
2622
- const labels = scale.chart.data.labels;
2623
- const lastLabelIndex = labels.length - 1;
2624
- const offsetAmt = Math.max((scale.ticks.length - ((scale.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
2625
- const panSpeed = panOptions.speed;
2626
- let minIndex = scale.minIndex;
2627
- const step = Math.round(scale.width / (offsetAmt * panSpeed));
2628
- let maxIndex;
2629
- zoomNS.panCumulativeDelta += delta;
2630
- minIndex = zoomNS.panCumulativeDelta > step ? Math.max(0, minIndex - 1) : zoomNS.panCumulativeDelta < -step ? Math.min(lastLabelIndex - offsetAmt + 1, minIndex + 1) : minIndex;
2631
- zoomNS.panCumulativeDelta = minIndex !== scale.minIndex ? 0 : zoomNS.panCumulativeDelta;
2632
- maxIndex = Math.min(lastLabelIndex, minIndex + offsetAmt - 1);
2633
- scale.options.ticks.min = rangeMinLimiter(panOptions, labels[minIndex]);
2634
- scale.options.ticks.max = rangeMaxLimiter(panOptions, labels[maxIndex]);
2635
- }
2636
- function panTimeScale(scale, delta, panOptions) {
2637
- const options = scale.options;
2638
- const limitedMax = rangeMaxLimiter(panOptions, scale.getValueForPixel(scale.getPixelForValue(scale.max) - delta));
2639
- const limitedMin = rangeMinLimiter(panOptions, scale.getValueForPixel(scale.getPixelForValue(scale.min) - delta));
2640
- const limitedTimeDelta = delta < 0 ? limitedMax - scale.max : limitedMin - scale.min;
2641
- options.time.max = scale.max + limitedTimeDelta;
2642
- options.time.min = scale.min + limitedTimeDelta;
2643
- }
2644
- function panNumericalScale(scale, delta, panOptions) {
2645
- const tickOpts = scale.options.ticks;
2646
- const start = scale.start, end = scale.end;
2647
- if (tickOpts.reverse) {
2648
- tickOpts.max = scale.getValueForPixel(scale.getPixelForValue(start) - delta);
2649
- tickOpts.min = scale.getValueForPixel(scale.getPixelForValue(end) - delta);
2650
- }
2651
- else {
2652
- tickOpts.min = scale.getValueForPixel(scale.getPixelForValue(start) - delta);
2653
- tickOpts.max = scale.getValueForPixel(scale.getPixelForValue(end) - delta);
2654
- }
2655
- tickOpts.min = rangeMinLimiter(panOptions, tickOpts.min);
2656
- tickOpts.max = rangeMaxLimiter(panOptions, tickOpts.max);
2657
- }
2658
- function panScale(scale, delta, panOptions) {
2659
- const fn = panFunctions[scale.options.type];
2660
- if (fn) {
2661
- fn(scale, delta, panOptions);
2662
- }
2663
- }
2664
- function doPan(chartInstance, deltaX, deltaY) {
2665
- const panOptions = chartInstance.options.pan;
2666
- if (panOptions && helpers.getValueOrDefault(panOptions.enabled, defaultOptions.pan.enabled)) {
2667
- const panMode = helpers.getValueOrDefault(chartInstance.options.pan.mode, defaultOptions.pan.mode);
2668
- panOptions.speed = helpers.getValueOrDefault(chartInstance.options.pan.speed, defaultOptions.pan.speed);
2669
- helpers.each(chartInstance.scales, function (scale, id) {
2670
- if (scale.isHorizontal() && directionEnabled(panMode, 'x') && deltaX !== 0) {
2671
- panOptions.scaleAxes = 'x';
2672
- panScale(scale, deltaX, panOptions);
2673
- }
2674
- else if (!scale.isHorizontal() && directionEnabled(panMode, 'y') && deltaY !== 0) {
2675
- panOptions.scaleAxes = 'y';
2676
- panScale(scale, deltaY, panOptions);
2677
- }
2678
- });
2679
- chartInstance.update(0);
2680
- if (typeof panOptions.onPan === 'function') {
2681
- panOptions.onPan();
2682
- }
2683
- }
2684
- }
2685
- function positionInChartArea(chartInstance, position) {
2686
- return (position.x >= chartInstance.chartArea.left && position.x <= chartInstance.chartArea.right) &&
2687
- (position.y >= chartInstance.chartArea.top && position.y <= chartInstance.chartArea.bottom);
2688
- }
2689
- function getYAxis(chartInstance) {
2690
- const scales = chartInstance.scales;
2691
- for (const scaleId in scales) {
2692
- if (scales.hasOwnProperty(scaleId)) {
2693
- const scale = scales[scaleId];
2694
- if (!scale.isHorizontal()) {
2695
- return scale;
2696
- }
2697
- }
2698
- }
2699
- }
2700
- // Store these for later
2701
- zoomNS.zoomFunctions.category = zoomIndexScale;
2702
- zoomNS.zoomFunctions.time = zoomTimeScale;
2703
- zoomNS.zoomFunctions.linear = zoomNumericalScale;
2704
- zoomNS.zoomFunctions.logarithmic = zoomNumericalScale;
2705
- zoomNS.panFunctions.category = panIndexScale;
2706
- zoomNS.panFunctions.time = panTimeScale;
2707
- zoomNS.panFunctions.linear = panNumericalScale;
2708
- zoomNS.panFunctions.logarithmic = panNumericalScale;
2709
- // Globals for catergory pan and zoom
2710
- zoomNS.panCumulativeDelta = 0;
2711
- zoomNS.zoomCumulativeDelta = 0;
2712
- // Chartjs Zoom Plugin
2713
- const zoomPlugin = {
2714
- afterInit: function (chartInstance) {
2715
- helpers.each(chartInstance.scales, function (scale) {
2716
- scale.originalOptions = helpers.clone(scale.options);
2717
- });
2718
- chartInstance.resetZoom = function () {
2719
- helpers.each(chartInstance.scales, function (scale, id) {
2720
- const timeOptions = scale.options.time;
2721
- const tickOptions = scale.options.ticks;
2722
- if (timeOptions) {
2723
- timeOptions.min = scale.originalOptions.time.min;
2724
- timeOptions.max = scale.originalOptions.time.max;
2725
- }
2726
- if (tickOptions) {
2727
- tickOptions.min = scale.originalOptions.ticks.min;
2728
- tickOptions.max = scale.originalOptions.ticks.max;
2729
- }
2730
- });
2731
- helpers.each(chartInstance.data.datasets, function (dataset, id) {
2732
- dataset._meta = null;
2733
- });
2734
- chartInstance.update();
2735
- };
2736
- },
2737
- beforeInit: function (chartInstance) {
2738
- chartInstance.zoom = {};
2739
- const node = chartInstance.zoom.node = chartInstance.chart.ctx.canvas;
2740
- const options = chartInstance.options;
2741
- const panThreshold = helpers.getValueOrDefault(options.pan ? options.pan.threshold : undefined, zoomNS.defaults.pan.threshold);
2742
- if (!options.zoom || !options.zoom.enabled) {
2743
- return;
2744
- }
2745
- if (options.zoom.drag) {
2746
- // Only want to zoom horizontal axis
2747
- options.zoom.mode = 'x';
2748
- chartInstance.zoom._mouseDownHandler = function (event) {
2749
- chartInstance.zoom._dragZoomStart = event;
2750
- };
2751
- node.addEventListener('mousedown', chartInstance.zoom._mouseDownHandler);
2752
- chartInstance.zoom._mouseMoveHandler = function (event) {
2753
- if (chartInstance.zoom._dragZoomStart) {
2754
- chartInstance.zoom._dragZoomEnd = event;
2755
- chartInstance.update(0);
2756
- }
2757
- };
2758
- node.addEventListener('mousemove', chartInstance.zoom._mouseMoveHandler);
2759
- chartInstance.zoom._mouseUpHandler = function (event) {
2760
- if (chartInstance.zoom._dragZoomStart) {
2761
- const chartArea = chartInstance.chartArea;
2762
- const yAxis = getYAxis(chartInstance);
2763
- const beginPoint = chartInstance.zoom._dragZoomStart;
2764
- const offsetX = beginPoint.target.getBoundingClientRect().left;
2765
- const startX = Math.max(Math.min(beginPoint.clientX, event.clientX) - offsetX, chartArea.left);
2766
- const endX = Math.min(Math.max(beginPoint.clientX, event.clientX) - offsetX, chartArea.right);
2767
- const dragDistance = endX - startX;
2768
- const chartDistance = chartArea.right - chartArea.left;
2769
- const zoom = 1 + (chartDistance - dragDistance) / chartDistance;
2770
- const centerX = chartArea.left + (startX - chartArea.left) / (zoom - 1);
2771
- // Remove drag start and end before chart update to stop drawing selected area
2772
- chartInstance.zoom._dragZoomStart = null;
2773
- chartInstance.zoom._dragZoomEnd = null;
2774
- if (dragDistance > 0) {
2775
- doZoom(chartInstance, zoom, {
2776
- x: centerX,
2777
- y: (yAxis.bottom - yAxis.top) / 2,
2778
- });
2779
- }
2780
- }
2781
- };
2782
- // node.addEventListener('mouseup', chartInstance.zoom._mouseUpHandler);
2783
- node.ownerDocument.addEventListener('mouseup', chartInstance.zoom._mouseUpHandler);
2784
- }
2785
- else {
2786
- chartInstance.zoom._wheelHandler = function (event) {
2787
- const rect = event.target.getBoundingClientRect();
2788
- const offsetX = event.clientX - rect.left;
2789
- const offsetY = event.clientY - rect.top;
2790
- const center = {
2791
- x: offsetX,
2792
- y: offsetY
2793
- };
2794
- if (event.deltaY < 0) {
2795
- doZoom(chartInstance, 1.1, center);
2796
- }
2797
- else {
2798
- doZoom(chartInstance, 0.909, center);
2799
- }
2800
- // Prevent the event from triggering the default behavior (eg. Content scrolling).
2801
- event.preventDefault();
2802
- };
2803
- node.addEventListener('wheel', chartInstance.zoom._wheelHandler);
2804
- }
2805
- if (Hammer) {
2806
- const mc = new Hammer.Manager(node);
2807
- mc.add(new Hammer.Pinch());
2808
- mc.add(new Hammer.Pan({
2809
- threshold: panThreshold
2810
- }));
2811
- // Hammer reports the total scaling. We need the incremental amount
2812
- let currentPinchScaling;
2813
- const handlePinch = function (e) {
2814
- const diff = 1 / (currentPinchScaling) * e.scale;
2815
- const rect = e.target.getBoundingClientRect();
2816
- const offsetX = e.center.x - rect.left;
2817
- const offsetY = e.center.y - rect.top;
2818
- const center = {
2819
- x: offsetX,
2820
- y: offsetY
2821
- };
2822
- // fingers position difference
2823
- const x = Math.abs(e.pointers[0].clientX - e.pointers[1].clientX);
2824
- const y = Math.abs(e.pointers[0].clientY - e.pointers[1].clientY);
2825
- // diagonal fingers will change both (xy) axes
2826
- const p = x / y;
2827
- let xy;
2828
- if (p > 0.3 && p < 1.7) {
2829
- xy = 'xy';
2830
- }
2831
- else if (x > y) {
2832
- // x axis
2833
- xy = 'x';
2834
- }
2835
- else {
2836
- // y axis
2837
- xy = 'y';
2838
- }
2839
- doZoom(chartInstance, diff, center, xy);
2840
- // Keep track of overall scale
2841
- currentPinchScaling = e.scale;
2842
- };
2843
- mc.on('pinchstart', function (e) {
2844
- currentPinchScaling = 1; // reset tracker
2845
- });
2846
- mc.on('pinch', handlePinch);
2847
- mc.on('pinchend', function (e) {
2848
- handlePinch(e);
2849
- currentPinchScaling = null; // reset
2850
- zoomNS.zoomCumulativeDelta = 0;
2851
- });
2852
- let currentDeltaX = null, currentDeltaY = null, panning = false;
2853
- const handlePan = function (e) {
2854
- if (currentDeltaX !== null && currentDeltaY !== null) {
2855
- panning = true;
2856
- const deltaX = e.deltaX - currentDeltaX;
2857
- const deltaY = e.deltaY - currentDeltaY;
2858
- currentDeltaX = e.deltaX;
2859
- currentDeltaY = e.deltaY;
2860
- doPan(chartInstance, deltaX, deltaY);
2861
- }
2862
- };
2863
- mc.on('panstart', function (e) {
2864
- currentDeltaX = 0;
2865
- currentDeltaY = 0;
2866
- handlePan(e);
2867
- });
2868
- mc.on('panmove', handlePan);
2869
- mc.on('panend', function (e) {
2870
- currentDeltaX = null;
2871
- currentDeltaY = null;
2872
- zoomNS.panCumulativeDelta = 0;
2873
- setTimeout(function () { panning = false; }, 500);
2874
- });
2875
- chartInstance.zoom._ghostClickHandler = function (e) {
2876
- if (panning) {
2877
- e.stopImmediatePropagation();
2878
- e.preventDefault();
2879
- }
2880
- };
2881
- node.addEventListener('click', chartInstance.zoom._ghostClickHandler);
2882
- chartInstance._mc = mc;
2883
- }
2884
- },
2885
- beforeDatasetsDraw: function (chartInstance) {
2886
- const ctx = chartInstance.chart.ctx;
2887
- const chartArea = chartInstance.chartArea;
2888
- ctx.save();
2889
- ctx.beginPath();
2890
- if (chartInstance.zoom._dragZoomEnd) {
2891
- const yAxis = getYAxis(chartInstance);
2892
- const beginPoint = chartInstance.zoom._dragZoomStart;
2893
- const endPoint = chartInstance.zoom._dragZoomEnd;
2894
- const offsetX = beginPoint.target.getBoundingClientRect().left;
2895
- const startX = Math.min(beginPoint.clientX, endPoint.clientX) - offsetX;
2896
- const endX = Math.max(beginPoint.clientX, endPoint.clientX) - offsetX;
2897
- const rectWidth = endX - startX;
2898
- ctx.fillStyle = 'rgba(225,225,225,0.5)';
2899
- ctx.lineWidth = 5;
2900
- ctx.fillRect(startX, yAxis.top, rectWidth, yAxis.bottom - yAxis.top);
2901
- }
2902
- ctx.rect(chartArea.left, chartArea.top, chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
2903
- ctx.clip();
2904
- },
2905
- afterDatasetsDraw: function (chartInstance) {
2906
- chartInstance.chart.ctx.restore();
2907
- },
2908
- destroy: function (chartInstance) {
2909
- if (chartInstance.zoom) {
2910
- const options = chartInstance.options;
2911
- const node = chartInstance.zoom.node;
2912
- if (options.zoom && options.zoom.drag) {
2913
- node.removeEventListener('mousedown', chartInstance.zoom._mouseDownHandler);
2914
- node.removeEventListener('mousemove', chartInstance.zoom._mouseMoveHandler);
2915
- // node.removeEventListener('mouseup', chartInstance.zoom._mouseUpHandler);
2916
- node.ownerDocument.removeEventListener('mouseup', chartInstance.zoom._mouseUpHandler);
2917
- }
2918
- else {
2919
- node.removeEventListener('wheel', chartInstance.zoom._wheelHandler);
2920
- }
2921
- if (Hammer) {
2922
- node.removeEventListener('click', chartInstance.zoom._ghostClickHandler);
2923
- }
2924
- delete chartInstance.zoom;
2925
- const mc = chartInstance._mc;
2926
- if (mc) {
2927
- mc.remove('pinchstart');
2928
- mc.remove('pinch');
2929
- mc.remove('pinchend');
2930
- mc.remove('panstart');
2931
- mc.remove('pan');
2932
- mc.remove('panend');
2933
- }
2934
- }
2935
- }
2936
- };
2937
- Chart.pluginService.register(zoomPlugin);
2938
-
2939
1909
  class FileDropInputComponent {
2940
1910
  constructor() {
2941
1911
  this.dropAreaText = '';