ngx-hana-nameserver-history-viewer 21.0.0 → 21.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/fesm2022/ngx-hana-nameserver-history-viewer.mjs +216 -1247
- package/fesm2022/ngx-hana-nameserver-history-viewer.mjs.map +1 -1
- package/ngx-hana-nameserver-history-viewer-21.1.1.tgz +0 -0
- package/package.json +5 -4
- package/types/ngx-hana-nameserver-history-viewer.d.ts +13 -2
- package/ngx-hana-nameserver-history-viewer-21.0.0.tgz +0 -0
|
@@ -2,11 +2,10 @@ 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
|
|
6
|
-
import
|
|
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';
|
|
10
9
|
import * as i2 from 'nshviewer-angular-datetime-picker';
|
|
11
10
|
import { OwlDateTimeModule, OwlNativeDateTimeModule } from 'nshviewer-angular-datetime-picker';
|
|
12
11
|
import { SelectionTableComponent } from 'ngx-selection-table';
|
|
@@ -1094,8 +1093,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
|
|
|
1094
1093
|
type: Injectable
|
|
1095
1094
|
}] });
|
|
1096
1095
|
|
|
1097
|
-
//
|
|
1098
|
-
|
|
1096
|
+
// Register Chart.js components and plugins
|
|
1097
|
+
Chart.register(...registerables, TimeScale, LinearScale, CategoryScale, PointElement, LineElement, Title, Tooltip, Legend, Filler, Decimation, zoomPlugin);
|
|
1098
|
+
const MAX_POINTS_TO_RENDER = 100;
|
|
1099
1099
|
class ChartService {
|
|
1100
1100
|
/**
|
|
1101
1101
|
* toggle the dataset status of the chart, synchronously
|
|
@@ -1131,21 +1131,13 @@ class ChartService {
|
|
|
1131
1131
|
*/
|
|
1132
1132
|
static _initConfig(chart) {
|
|
1133
1133
|
// init data sets
|
|
1134
|
-
if (chart.data
|
|
1134
|
+
if (chart.data?.datasets) {
|
|
1135
1135
|
chart.data.datasets.length = 0;
|
|
1136
1136
|
chart.data.datasets = void 0;
|
|
1137
1137
|
chart.data = void 0;
|
|
1138
1138
|
}
|
|
1139
1139
|
// init options
|
|
1140
|
-
if (chart.options
|
|
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
|
-
}
|
|
1140
|
+
if (chart.options?.scales) {
|
|
1149
1141
|
chart.options.scales = void 0;
|
|
1150
1142
|
chart.options = void 0;
|
|
1151
1143
|
}
|
|
@@ -1161,62 +1153,70 @@ class ChartService {
|
|
|
1161
1153
|
return {
|
|
1162
1154
|
borderColor: color,
|
|
1163
1155
|
backgroundColor: color,
|
|
1164
|
-
borderWidth: 1
|
|
1156
|
+
borderWidth: 1,
|
|
1165
1157
|
spanGaps: false,
|
|
1166
1158
|
label: header[i],
|
|
1167
1159
|
data: data[i],
|
|
1168
1160
|
fill: false,
|
|
1169
1161
|
yAxisID: `y-axis-${i}`,
|
|
1170
|
-
hidden: !defaultItems.includes(header[i])
|
|
1162
|
+
hidden: !defaultItems.includes(header[i]),
|
|
1163
|
+
pointRadius: 0,
|
|
1164
|
+
pointHitRadius: 5,
|
|
1165
|
+
pointHoverRadius: 5,
|
|
1166
|
+
// Enable smooth curves for more natural line appearance
|
|
1167
|
+
tension: 0.2,
|
|
1168
|
+
// Enable decimation for each dataset
|
|
1169
|
+
parsing: false,
|
|
1170
|
+
normalized: true
|
|
1171
1171
|
};
|
|
1172
1172
|
});
|
|
1173
1173
|
}
|
|
1174
|
-
static
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1177
|
-
{
|
|
1174
|
+
static _generateScales(timeArray, data, yScaleArray) {
|
|
1175
|
+
const scales = {
|
|
1176
|
+
x: {
|
|
1178
1177
|
type: 'time',
|
|
1179
1178
|
time: {
|
|
1180
|
-
// unit: 'second',
|
|
1181
|
-
// stepSize: 1,
|
|
1182
1179
|
displayFormats: {
|
|
1183
|
-
'millisecond':
|
|
1184
|
-
'second':
|
|
1185
|
-
'minute':
|
|
1180
|
+
'millisecond': 'MMMDD HH:mm:ss',
|
|
1181
|
+
'second': 'MMMDD HH:mm:ss',
|
|
1182
|
+
'minute': 'MMMDD HH:mm',
|
|
1186
1183
|
'hour': 'MMMDD HH',
|
|
1187
|
-
'day': 'MMMDD
|
|
1188
|
-
'week':
|
|
1189
|
-
'month':
|
|
1190
|
-
'quarter':
|
|
1191
|
-
'year':
|
|
1184
|
+
'day': 'MMMDD',
|
|
1185
|
+
'week': 'MMMDD',
|
|
1186
|
+
'month': 'MMM YYYY',
|
|
1187
|
+
'quarter': 'MMM YYYY',
|
|
1188
|
+
'year': 'MMM YYYY',
|
|
1192
1189
|
},
|
|
1193
|
-
tooltipFormat: 'll HH:mm:ss'
|
|
1194
|
-
min: time[0],
|
|
1195
|
-
max: time[time.length - 1]
|
|
1190
|
+
tooltipFormat: 'll HH:mm:ss'
|
|
1196
1191
|
},
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
// labelString: 'Date'
|
|
1200
|
-
// },
|
|
1192
|
+
min: timeArray[0],
|
|
1193
|
+
max: timeArray[timeArray.length - 1],
|
|
1201
1194
|
ticks: {
|
|
1195
|
+
source: 'auto',
|
|
1202
1196
|
maxRotation: 0,
|
|
1203
|
-
|
|
1204
|
-
|
|
1197
|
+
autoSkip: true,
|
|
1198
|
+
// callback: function(val, index) {
|
|
1199
|
+
// // Hide every 2nd tick label
|
|
1200
|
+
// return index % 2 === 0? this.getLabelForValue(val) : '';
|
|
1201
|
+
// },
|
|
1202
|
+
},
|
|
1203
|
+
reverse: false
|
|
1205
1204
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1205
|
+
};
|
|
1206
|
+
// Add Y axes
|
|
1207
|
+
data.forEach((item, i) => {
|
|
1208
|
+
scales[`y-axis-${i}`] = {
|
|
1209
|
+
type: 'linear',
|
|
1210
|
+
display: false,
|
|
1211
|
+
position: 'left',
|
|
1212
|
+
grid: {
|
|
1213
|
+
drawOnChartArea: i === 0, // only show grid for first axis
|
|
1214
|
+
},
|
|
1216
1215
|
min: 0,
|
|
1217
|
-
max:
|
|
1218
|
-
}
|
|
1219
|
-
})
|
|
1216
|
+
max: yScaleArray[i]
|
|
1217
|
+
};
|
|
1218
|
+
});
|
|
1219
|
+
return scales;
|
|
1220
1220
|
}
|
|
1221
1221
|
/**
|
|
1222
1222
|
* generate configuration for chart
|
|
@@ -1228,70 +1228,117 @@ class ChartService {
|
|
|
1228
1228
|
datasets: ChartService._generateDataSets(data, header, headerKey, defaultItems)
|
|
1229
1229
|
},
|
|
1230
1230
|
options: {
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
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
|
-
}
|
|
1231
|
+
layout: {
|
|
1232
|
+
padding: {
|
|
1233
|
+
right: 25 // Add padding to the right side of the chart
|
|
1246
1234
|
}
|
|
1247
1235
|
},
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
e.target.style.cursor = 'pointer';
|
|
1253
|
-
},
|
|
1254
|
-
labels: {
|
|
1255
|
-
filter: (legendItem) => selection.isSelected(tableSource.find(row => legendItem.text === row.KPI))
|
|
1256
|
-
}
|
|
1236
|
+
interaction: {
|
|
1237
|
+
intersect: true, // true means not a range hit
|
|
1238
|
+
axis: 'x',
|
|
1239
|
+
mode: 'nearest'
|
|
1257
1240
|
},
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
minNumPoints: 200,
|
|
1266
|
-
maxNumPointsToDraw: 100
|
|
1241
|
+
responsive: true,
|
|
1242
|
+
animation: false, // must disable it otherwise zoomin will hang
|
|
1243
|
+
onHover: (event, elements) => {
|
|
1244
|
+
const target = event.native?.target || event.target;
|
|
1245
|
+
if (target) {
|
|
1246
|
+
target.style.cursor = elements?.length > 0 ? 'pointer' : 'default';
|
|
1247
|
+
}
|
|
1267
1248
|
},
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1249
|
+
elements: { point: { radius: 0, hitRadius: 5, hoverRadius: 5 } }, // set radius to 0 not to display point
|
|
1250
|
+
parsing: false,
|
|
1251
|
+
normalized: true,
|
|
1252
|
+
datasets: { line: { pointRadius: 0, pointHitRadius: 5, pointHoverRadius: 5 } },
|
|
1253
|
+
plugins: {
|
|
1254
|
+
legend: {
|
|
1255
|
+
position: 'bottom',
|
|
1256
|
+
onHover: (event, legendItem) => {
|
|
1257
|
+
const target = event.native?.target || event.target;
|
|
1258
|
+
if (target) {
|
|
1259
|
+
target.style.cursor = 'pointer';
|
|
1278
1260
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1261
|
+
},
|
|
1262
|
+
labels: {
|
|
1263
|
+
filter: (legendItem) => selection.isSelected(tableSource.find(row => legendItem.text === row.KPI))
|
|
1264
|
+
}
|
|
1265
|
+
},
|
|
1266
|
+
title: {
|
|
1267
|
+
display: true,
|
|
1268
|
+
text: title
|
|
1269
|
+
},
|
|
1270
|
+
tooltip: {
|
|
1271
|
+
callbacks: {
|
|
1272
|
+
label: function (context) {
|
|
1273
|
+
// format numbers with commas and add unit information
|
|
1274
|
+
const label = context.dataset.label || '';
|
|
1275
|
+
if (label) {
|
|
1276
|
+
// get unit (eg: MB, GB, MB/s, % and so on)
|
|
1277
|
+
const rowItem = tableSource.find(controlTableRow => label === controlTableRow.KPI) || '';
|
|
1278
|
+
const unit = rowItem[Item.unit] && rowItem[Item.unit] !== Unit.PCT ? ` ${rowItem[Item.unit]}` : rowItem[Item.unit] || '';
|
|
1279
|
+
return `${label}: ${getNumberWithCommas(context.parsed.y)}${unit}`;
|
|
1280
|
+
}
|
|
1281
|
+
else {
|
|
1282
|
+
return getNumberWithCommas(context.parsed.y);
|
|
1283
|
+
}
|
|
1281
1284
|
}
|
|
1282
1285
|
}
|
|
1286
|
+
},
|
|
1287
|
+
// downsampling
|
|
1288
|
+
decimation: {
|
|
1289
|
+
enabled: true,
|
|
1290
|
+
algorithm: 'lttb', // Largest-Triangle-Three-Buckets algorithm
|
|
1291
|
+
samples: 500, // Increased samples for better quality
|
|
1292
|
+
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.
|
|
1293
|
+
},
|
|
1294
|
+
zoom: {
|
|
1295
|
+
limits: {
|
|
1296
|
+
x: {
|
|
1297
|
+
min: 'original',
|
|
1298
|
+
max: 'original',
|
|
1299
|
+
//minRange: 180000 // 3 min the min range that can be zoomed
|
|
1300
|
+
}
|
|
1301
|
+
},
|
|
1302
|
+
pan: {
|
|
1303
|
+
enabled: true,
|
|
1304
|
+
mode: 'x',
|
|
1305
|
+
modifierKey: 'ctrl'
|
|
1306
|
+
},
|
|
1307
|
+
zoom: {
|
|
1308
|
+
// wheel: {
|
|
1309
|
+
// enabled: true,
|
|
1310
|
+
// speed: 0.02
|
|
1311
|
+
// },
|
|
1312
|
+
// pinch: {
|
|
1313
|
+
// enabled: true
|
|
1314
|
+
// },
|
|
1315
|
+
drag: {
|
|
1316
|
+
enabled: true,
|
|
1317
|
+
threshold: 10,
|
|
1318
|
+
drawTime: 'beforeDraw'
|
|
1319
|
+
},
|
|
1320
|
+
mode: 'x',
|
|
1321
|
+
onZoom: zoomCallback,
|
|
1322
|
+
onZoomComplete: (context => {
|
|
1323
|
+
const chart = context?.chart;
|
|
1324
|
+
if (!chart?.data?.datasets) {
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
// make point visible when current points are less than MAX_POINTS_TO_RENDER
|
|
1328
|
+
const pointRadiusChangeFlag = chart.data.datasets.some(dataset => dataset.data.length <= MAX_POINTS_TO_RENDER);
|
|
1329
|
+
const pointRadius = pointRadiusChangeFlag ? 2 : 0;
|
|
1330
|
+
chart.data.datasets.forEach((dataset) => {
|
|
1331
|
+
dataset.pointRadius = pointRadius;
|
|
1332
|
+
});
|
|
1333
|
+
if (chart.options?.elements?.point) {
|
|
1334
|
+
chart.options.elements.point.radius = pointRadius;
|
|
1335
|
+
}
|
|
1336
|
+
chart.update();
|
|
1337
|
+
})
|
|
1338
|
+
}
|
|
1283
1339
|
}
|
|
1284
1340
|
},
|
|
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
|
-
}
|
|
1341
|
+
scales: ChartService._generateScales(time, data, yScale)
|
|
1295
1342
|
}
|
|
1296
1343
|
};
|
|
1297
1344
|
}
|
|
@@ -1301,12 +1348,65 @@ class ChartService {
|
|
|
1301
1348
|
}
|
|
1302
1349
|
return -1;
|
|
1303
1350
|
}
|
|
1351
|
+
/**
|
|
1352
|
+
* WORKAROUND: Setup mousemove event listener to fix Chart.js tooltip issue
|
|
1353
|
+
* Issue: https://github.com/chartjs/Chart.js/issues/11972
|
|
1354
|
+
* TODO: Remove this entire method when the Chart.js bug is fixed
|
|
1355
|
+
*/
|
|
1356
|
+
_setupTooltipWorkaround(chart) {
|
|
1357
|
+
const canvas = chart.canvas;
|
|
1358
|
+
const mouseMoveHandler = (event) => {
|
|
1359
|
+
const rect = canvas.getBoundingClientRect();
|
|
1360
|
+
const x = event.clientX - rect.left;
|
|
1361
|
+
const y = event.clientY - rect.top;
|
|
1362
|
+
// Check if mouse is within chart area
|
|
1363
|
+
const chartArea = chart.chartArea;
|
|
1364
|
+
if (chartArea) {
|
|
1365
|
+
const isInChartArea = x >= chartArea.left && x <= chartArea.right &&
|
|
1366
|
+
y <= chartArea.bottom && y >= chartArea.top;
|
|
1367
|
+
// Get legend area (legend is positioned at bottom)
|
|
1368
|
+
const legendArea = chart.legend;
|
|
1369
|
+
let isInLegendArea = false;
|
|
1370
|
+
if (legendArea && legendArea.legendHitBoxes) {
|
|
1371
|
+
isInLegendArea = legendArea.legendHitBoxes.some((hitBox) => x >= hitBox.left && x <= hitBox.left + hitBox.width &&
|
|
1372
|
+
y >= hitBox.top && y <= hitBox.top + hitBox.height);
|
|
1373
|
+
}
|
|
1374
|
+
if (!isInChartArea && !isInLegendArea) {
|
|
1375
|
+
// Mouse is in axis area (but not legend), force hide tooltip and active points
|
|
1376
|
+
if (chart.tooltip) {
|
|
1377
|
+
chart.tooltip.setActiveElements([], { x: 0, y: 0 });
|
|
1378
|
+
}
|
|
1379
|
+
// Clear active elements to hide hover points
|
|
1380
|
+
chart.setActiveElements([]);
|
|
1381
|
+
chart.update('none');
|
|
1382
|
+
canvas.style.cursor = 'default';
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
canvas.addEventListener('mousemove', mouseMoveHandler);
|
|
1387
|
+
// Store the handler for cleanup
|
|
1388
|
+
chart._mouseMoveHandler = mouseMoveHandler;
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* WORKAROUND: Clean up mousemove event listener for Chart.js tooltip issue
|
|
1392
|
+
* Issue: https://github.com/chartjs/Chart.js/issues/11972
|
|
1393
|
+
* TODO: Remove this entire method when the Chart.js bug is fixed
|
|
1394
|
+
*/
|
|
1395
|
+
_cleanupTooltipWorkaround(chart) {
|
|
1396
|
+
const canvas = chart.canvas;
|
|
1397
|
+
const mouseMoveHandler = chart._mouseMoveHandler;
|
|
1398
|
+
if (canvas && mouseMoveHandler) {
|
|
1399
|
+
canvas.removeEventListener('mousemove', mouseMoveHandler);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1304
1402
|
/**
|
|
1305
1403
|
* destroy the chart, release the resource of the chart
|
|
1306
1404
|
*/
|
|
1307
1405
|
_destroyChart(chart) {
|
|
1308
1406
|
return new Promise((resolve) => {
|
|
1309
1407
|
if (chart) {
|
|
1408
|
+
// WORKAROUND: Clean up tooltip workaround - TODO: Remove when Chart.js bug is fixed
|
|
1409
|
+
this._cleanupTooltipWorkaround(chart);
|
|
1310
1410
|
chart.destroy();
|
|
1311
1411
|
}
|
|
1312
1412
|
resolve();
|
|
@@ -1344,7 +1444,10 @@ class ChartService {
|
|
|
1344
1444
|
}
|
|
1345
1445
|
});
|
|
1346
1446
|
}
|
|
1347
|
-
|
|
1447
|
+
// Use the zoom plugin's resetZoom method
|
|
1448
|
+
if (chart.resetZoom) {
|
|
1449
|
+
chart.resetZoom();
|
|
1450
|
+
}
|
|
1348
1451
|
resolve(datasetStatusBeforeReset);
|
|
1349
1452
|
}
|
|
1350
1453
|
else {
|
|
@@ -1391,8 +1494,9 @@ class ChartService {
|
|
|
1391
1494
|
return new Promise((resolve, reject) => {
|
|
1392
1495
|
const ctx = document.getElementById('chartNameServerHistory')?.getContext('2d');
|
|
1393
1496
|
if (ctx) {
|
|
1394
|
-
|
|
1395
|
-
|
|
1497
|
+
this._chart = new Chart(ctx, ChartService._generateChartConfig(time, data, yScale, header, headerKey, selection, tableSource, title, defaultItems, zoomCB));
|
|
1498
|
+
// WORKAROUND: Setup tooltip workaround - TODO: Remove when Chart.js bug is fixed
|
|
1499
|
+
this._setupTooltipWorkaround(this._chart);
|
|
1396
1500
|
resolve();
|
|
1397
1501
|
}
|
|
1398
1502
|
else {
|
|
@@ -1801,1141 +1905,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImpor
|
|
|
1801
1905
|
type: Injectable
|
|
1802
1906
|
}] });
|
|
1803
1907
|
|
|
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
1908
|
class FileDropInputComponent {
|
|
2940
1909
|
constructor() {
|
|
2941
1910
|
this.dropAreaText = '';
|