td-plots 1.5.1 → 1.5.3
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/dist/components/Histogram.d.ts +1 -1
- package/dist/components/Utils.d.ts +2 -0
- package/dist/index.esm.js +128 -54
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +128 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -57,6 +57,7 @@ var css_248z = ".plot-container{height:100%;max-width:100%;min-height:300px;over
|
|
|
57
57
|
styleInject(css_248z);
|
|
58
58
|
|
|
59
59
|
// Utility functions for our components
|
|
60
|
+
var ONEAVGMONTH = 2629800000; // Average month length in ms, copied from plotly constants in plotly.js/src/constants/numerical.js
|
|
60
61
|
// Type guard to check if array contains only numbers
|
|
61
62
|
var isNumberArray = function (arr) {
|
|
62
63
|
return arr.every(function (item) { return typeof item === 'number' && !isNaN(item); });
|
|
@@ -109,6 +110,21 @@ var roundToPrevDay = function (timestamp) {
|
|
|
109
110
|
date.setHours(0, 0, 0, 0); // Start of day
|
|
110
111
|
return date.toISOString();
|
|
111
112
|
};
|
|
113
|
+
// Convert plotly M# string to number of milliseconds
|
|
114
|
+
// According to the docs, plotly will use M# whenever the bin size is
|
|
115
|
+
// larger than one average month. Even if the bin size is 2 years, plotly
|
|
116
|
+
// will still use M24 instead of Y2 (plotly.js/src/plots/cartesian/axes.js).
|
|
117
|
+
var plotlyMToMilliseconds = function (mString) {
|
|
118
|
+
var match = mString.match(/^M(\d+)$/);
|
|
119
|
+
if (match) {
|
|
120
|
+
var months = parseInt(match[1], 10);
|
|
121
|
+
return months * ONEAVGMONTH;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
console.error("plotlyMToMilliseconds: Invalid M# string: ".concat(mString));
|
|
125
|
+
}
|
|
126
|
+
return 0;
|
|
127
|
+
};
|
|
112
128
|
|
|
113
129
|
function getDefaultExportFromCjs (x) {
|
|
114
130
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
@@ -6061,6 +6077,8 @@ var HistogramPlot = function (props) {
|
|
|
6061
6077
|
var containerRef = React.useRef(null);
|
|
6062
6078
|
// Track any selections made in this plot so we can style the selection box.
|
|
6063
6079
|
var _o = React.useState(null), selectedRange = _o[0], setSelectedRange = _o[1];
|
|
6080
|
+
// Combine all data into one for later calculations
|
|
6081
|
+
var allData = tslib.__spreadArray(tslib.__spreadArray([], data, true), unselectedData, true);
|
|
6064
6082
|
// If all the data becomes selected, we should forget any old selections.
|
|
6065
6083
|
React.useEffect(function () {
|
|
6066
6084
|
if (unselectedData.length === 0) {
|
|
@@ -6073,6 +6091,8 @@ var HistogramPlot = function (props) {
|
|
|
6073
6091
|
// to access that information once the plot has been initialized so that we can prevent the
|
|
6074
6092
|
// axis range from changing during interaction. Dates use strings.
|
|
6075
6093
|
var _p = React.useState(undefined), fixedXAxisRange = _p[0], setFixedXAxisRange = _p[1];
|
|
6094
|
+
// track xbins too
|
|
6095
|
+
var _q = React.useState(undefined), binSize = _q[0], setBinSize = _q[1];
|
|
6076
6096
|
// Once the plot is drawn, record the initial axis range so we can keep it fixed.
|
|
6077
6097
|
// figure should be Readonly<Plotly.Figure> but react-plotly.js doesn't expose that type, so we use any.
|
|
6078
6098
|
var handlePlotUpdate = function (figure, graphDiv) {
|
|
@@ -6088,17 +6108,42 @@ var HistogramPlot = function (props) {
|
|
|
6088
6108
|
setFixedXAxisRange(range);
|
|
6089
6109
|
}
|
|
6090
6110
|
}
|
|
6111
|
+
if (!binSize) {
|
|
6112
|
+
// Get the bin size from the first trace. Both traces should have the same bin size.
|
|
6113
|
+
if (figure && figure.data && figure.data.length > 0 && figure.data[0].xbins && figure.data[0].xbins.size) {
|
|
6114
|
+
setBinSize(figure.data[0].xbins.size);
|
|
6115
|
+
}
|
|
6116
|
+
}
|
|
6091
6117
|
};
|
|
6118
|
+
// Track shift key state
|
|
6119
|
+
var isShiftPressed = React.useRef(false);
|
|
6120
|
+
// Add keyboard event listeners to track shift key
|
|
6121
|
+
React.useEffect(function () {
|
|
6122
|
+
var handleKeyDown = function (e) {
|
|
6123
|
+
if (e.key === 'Shift') {
|
|
6124
|
+
isShiftPressed.current = true;
|
|
6125
|
+
}
|
|
6126
|
+
};
|
|
6127
|
+
var handleKeyUp = function (e) {
|
|
6128
|
+
if (e.key === 'Shift') {
|
|
6129
|
+
isShiftPressed.current = false;
|
|
6130
|
+
}
|
|
6131
|
+
};
|
|
6132
|
+
// Add event listeners to document to catch shift key globally
|
|
6133
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
6134
|
+
document.addEventListener('keyup', handleKeyUp);
|
|
6135
|
+
// Cleanup
|
|
6136
|
+
return function () {
|
|
6137
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
6138
|
+
document.removeEventListener('keyup', handleKeyUp);
|
|
6139
|
+
};
|
|
6140
|
+
}, []);
|
|
6092
6141
|
// Create handler for click event that can use event data to update the plot if desired.
|
|
6093
6142
|
var handleClick = function (event) {
|
|
6143
|
+
var _a, _b, _c, _d;
|
|
6094
6144
|
if (!event || !event.points || event.points.length === 0) {
|
|
6095
6145
|
return;
|
|
6096
6146
|
}
|
|
6097
|
-
// The main trace is always curve number 0.
|
|
6098
|
-
// If we clicked the unselected data trace, nothing should happen.
|
|
6099
|
-
if (event.points[0].curveNumber !== 0) {
|
|
6100
|
-
return;
|
|
6101
|
-
}
|
|
6102
6147
|
// Use the bin number to determine which bar was clicked and determine the range of the clicked bar.
|
|
6103
6148
|
if ("binNumber" in event.points[0] && typeof event.points[0].binNumber === 'number') {
|
|
6104
6149
|
// Get the index of the clicked bar with respect to the trace. So if
|
|
@@ -6113,37 +6158,49 @@ var HistogramPlot = function (props) {
|
|
|
6113
6158
|
// This is the start of the first bin out of all the traces. The global first bin.
|
|
6114
6159
|
var globalFirstBinStart = void 0;
|
|
6115
6160
|
if (isDateArray(data)) {
|
|
6116
|
-
// Date bins are represented as strings. We'll need to convert
|
|
6117
|
-
globalFirstBinStart = new Date(event.points[0].data.xbins.start).getTime();
|
|
6118
|
-
minTraceValue = Math.min.apply(Math, data.map(function (d) { return d.getTime(); }));
|
|
6161
|
+
// Date bins are represented as strings (sometimes). We'll need to convert whatever plotly gives us to timestamps.
|
|
6162
|
+
globalFirstBinStart = fixedXAxisRange ? new Date(fixedXAxisRange[0]).getTime() : new Date(event.points[0].data.xbins.start).getTime();
|
|
6163
|
+
minTraceValue = (event.points[0].curveNumber === 0) ? Math.min.apply(Math, data.map(function (d) { return d.getTime(); })) : Math.min.apply(Math, unselectedData.map(function (d) { return d.getTime(); }));
|
|
6119
6164
|
}
|
|
6120
6165
|
else {
|
|
6121
|
-
// All of the event info should be numbers
|
|
6122
|
-
if (typeof event.points[0].data.xbins.start !== 'number') {
|
|
6123
|
-
console.error("Unexpected type for xbins.start. Expected number.");
|
|
6124
|
-
return;
|
|
6125
|
-
}
|
|
6126
6166
|
// Get the min value of the trace and the beginning of the first bin (globally)
|
|
6127
|
-
minTraceValue = Math.min.apply(Math, data);
|
|
6128
|
-
globalFirstBinStart = event.points[0].data.xbins.start;
|
|
6167
|
+
minTraceValue = (event.points[0].curveNumber === 0) ? Math.min.apply(Math, data) : Math.min.apply(Math, unselectedData);
|
|
6168
|
+
globalFirstBinStart = fixedXAxisRange ? fixedXAxisRange[0] : event.points[0].data.xbins.start;
|
|
6129
6169
|
}
|
|
6170
|
+
// Finally, we need to calculate the min and max values of the clicked bin.
|
|
6171
|
+
// If the bin size is a month or more, plotly records it in their "mstring" format like "M3" for 3 months.
|
|
6172
|
+
// We then must convert it back to milliseconds. Otherwise, it's always ms.
|
|
6173
|
+
var size = binSize !== null && binSize !== void 0 ? binSize : (_d = (_c = (_b = (_a = event.points) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.xbins) === null || _d === void 0 ? void 0 : _d.size;
|
|
6174
|
+
var convertedBinSize = typeof size === 'string'
|
|
6175
|
+
? plotlyMToMilliseconds(size)
|
|
6176
|
+
: size;
|
|
6130
6177
|
// This minTraceValue is in the 0th bin of this trace. Find the index of this bin in the whole plot.
|
|
6131
|
-
var clickedBinGlobalIndex = clickedBinIndex + Math.floor((minTraceValue - globalFirstBinStart) /
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
globalFirstBinStart + (clickedBinGlobalIndex + 1) * binSize
|
|
6137
|
-
], minBinValue = _a[0], maxBinValue = _a[1];
|
|
6178
|
+
var clickedBinGlobalIndex = clickedBinIndex + Math.floor((minTraceValue - globalFirstBinStart) / convertedBinSize);
|
|
6179
|
+
var _e = [
|
|
6180
|
+
globalFirstBinStart + clickedBinGlobalIndex * convertedBinSize,
|
|
6181
|
+
globalFirstBinStart + (clickedBinGlobalIndex + 1) * convertedBinSize
|
|
6182
|
+
], minBinValue = _e[0], maxBinValue = _e[1];
|
|
6138
6183
|
if (isDateArray(data)) {
|
|
6139
6184
|
var minDate = new Date(minBinValue);
|
|
6140
6185
|
var maxDate = new Date(maxBinValue);
|
|
6141
|
-
handleClickOrSelection(minDate, maxDate);
|
|
6142
|
-
|
|
6186
|
+
handleClickOrSelection(minDate, maxDate, isShiftPressed.current);
|
|
6187
|
+
var newMinMax = [minDate, maxDate];
|
|
6188
|
+
if (isShiftPressed.current && selectedRange) {
|
|
6189
|
+
setSelectedRange(tslib.__spreadArray(tslib.__spreadArray([], selectedRange, true), [newMinMax], false));
|
|
6190
|
+
}
|
|
6191
|
+
else {
|
|
6192
|
+
setSelectedRange([newMinMax]);
|
|
6193
|
+
}
|
|
6143
6194
|
}
|
|
6144
6195
|
else {
|
|
6145
|
-
handleClickOrSelection(minBinValue, maxBinValue);
|
|
6146
|
-
|
|
6196
|
+
handleClickOrSelection(minBinValue, maxBinValue, isShiftPressed.current);
|
|
6197
|
+
var newMinMax = [minBinValue, maxBinValue];
|
|
6198
|
+
if (isShiftPressed.current && selectedRange) {
|
|
6199
|
+
setSelectedRange(tslib.__spreadArray(tslib.__spreadArray([], selectedRange, true), [newMinMax], false));
|
|
6200
|
+
}
|
|
6201
|
+
else {
|
|
6202
|
+
setSelectedRange([newMinMax]);
|
|
6203
|
+
}
|
|
6147
6204
|
}
|
|
6148
6205
|
}
|
|
6149
6206
|
};
|
|
@@ -6163,9 +6220,13 @@ var HistogramPlot = function (props) {
|
|
|
6163
6220
|
// Means at least one bin has been selected
|
|
6164
6221
|
var firstBinMidPoint = new Date(event.points[0].x).getTime();
|
|
6165
6222
|
var lastBinMidPoint = new Date(event.points[event.points.length - 1].x).getTime();
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6223
|
+
// If the bin size is a month or more, plotly records it in their "mstring" format like "M3" for 3 months.
|
|
6224
|
+
// We then must convert it back to milliseconds. Otherwise, it's always ms.
|
|
6225
|
+
var convertedBinSize = typeof binSize === 'string'
|
|
6226
|
+
? plotlyMToMilliseconds(binSize)
|
|
6227
|
+
: binSize;
|
|
6228
|
+
minValue = new Date(firstBinMidPoint - convertedBinSize / 2);
|
|
6229
|
+
maxValue = new Date(lastBinMidPoint + convertedBinSize / 2);
|
|
6169
6230
|
}
|
|
6170
6231
|
else {
|
|
6171
6232
|
// No bins selected, so the range should be empty.
|
|
@@ -6185,11 +6246,12 @@ var HistogramPlot = function (props) {
|
|
|
6185
6246
|
// Set the range based on the bins selected. Plotly will include a bin in event.points if
|
|
6186
6247
|
// at least half of it is selected.
|
|
6187
6248
|
if (event.points && event.points.length > 0) {
|
|
6188
|
-
|
|
6189
|
-
var
|
|
6190
|
-
var
|
|
6191
|
-
var
|
|
6192
|
-
var
|
|
6249
|
+
// Find the max and min midpoints. They may be from curve 0 or 1.
|
|
6250
|
+
var firstBinMidPoint = Math.min.apply(Math, event.points.map(function (p) { return p.x; }));
|
|
6251
|
+
var lastBinMidPoint = Math.max.apply(Math, event.points.map(function (p) { return p.x; }));
|
|
6252
|
+
var convertedBinSize = binSize;
|
|
6253
|
+
var roundedMinValue = firstBinMidPoint - convertedBinSize / 2;
|
|
6254
|
+
var roundedMaxValue = lastBinMidPoint + convertedBinSize / 2;
|
|
6193
6255
|
minValue = roundedMinValue;
|
|
6194
6256
|
maxValue = roundedMaxValue;
|
|
6195
6257
|
}
|
|
@@ -6208,12 +6270,24 @@ var HistogramPlot = function (props) {
|
|
|
6208
6270
|
if (minValue !== undefined && maxValue !== undefined) {
|
|
6209
6271
|
// Update selected range. Have to be strict about types.
|
|
6210
6272
|
if (typeof minValue === 'number' && typeof maxValue === 'number') {
|
|
6211
|
-
|
|
6273
|
+
var newMinMax = [minValue, maxValue];
|
|
6274
|
+
if (isShiftPressed.current && selectedRange) {
|
|
6275
|
+
setSelectedRange(tslib.__spreadArray(tslib.__spreadArray([], selectedRange, true), [newMinMax], false));
|
|
6276
|
+
}
|
|
6277
|
+
else {
|
|
6278
|
+
setSelectedRange([newMinMax]);
|
|
6279
|
+
}
|
|
6212
6280
|
}
|
|
6213
6281
|
else if (minValue instanceof Date && maxValue instanceof Date) {
|
|
6214
|
-
|
|
6282
|
+
var newMinMax = [minValue, maxValue];
|
|
6283
|
+
if (isShiftPressed.current && selectedRange) {
|
|
6284
|
+
setSelectedRange(tslib.__spreadArray(tslib.__spreadArray([], selectedRange, true), [newMinMax], false));
|
|
6285
|
+
}
|
|
6286
|
+
else {
|
|
6287
|
+
setSelectedRange([newMinMax]);
|
|
6288
|
+
}
|
|
6215
6289
|
}
|
|
6216
|
-
handleClickOrSelection(minValue, maxValue);
|
|
6290
|
+
handleClickOrSelection(minValue, maxValue, isShiftPressed.current);
|
|
6217
6291
|
}
|
|
6218
6292
|
};
|
|
6219
6293
|
// Create the selected range box
|
|
@@ -6224,20 +6298,20 @@ var HistogramPlot = function (props) {
|
|
|
6224
6298
|
return []; // Don't show the box if the entire dataset is selected.
|
|
6225
6299
|
// Create a multiply-like effect by using a semi-transparent dark overlay
|
|
6226
6300
|
var multiplyColor = 'rgba(29, 104, 185, 0.1)';
|
|
6227
|
-
return
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6301
|
+
return selectedRange.map(function (maxMin) { return ({
|
|
6302
|
+
type: 'rect',
|
|
6303
|
+
x0: isDateArray(maxMin) ? maxMin[0].getTime() : maxMin[0],
|
|
6304
|
+
x1: isDateArray(maxMin) ? maxMin[1].getTime() : maxMin[1],
|
|
6305
|
+
y0: 0,
|
|
6306
|
+
y1: 1,
|
|
6307
|
+
yref: 'paper',
|
|
6308
|
+
fillcolor: multiplyColor,
|
|
6309
|
+
line: {
|
|
6310
|
+
width: 1,
|
|
6311
|
+
color: multiplyColor
|
|
6312
|
+
},
|
|
6313
|
+
layer: 'above' // Ensure the selection box is above the bars
|
|
6314
|
+
}); });
|
|
6241
6315
|
}, [selectedRange, unselectedData]);
|
|
6242
6316
|
// Calculate the mean of the selected data using normalized data
|
|
6243
6317
|
var meanValue = (_a = calculateMean(data)) !== null && _a !== void 0 ? _a : 0; // Default to 0 if no data
|
|
@@ -6256,7 +6330,6 @@ var HistogramPlot = function (props) {
|
|
|
6256
6330
|
}
|
|
6257
6331
|
}] : [];
|
|
6258
6332
|
// Draw mean line for all data
|
|
6259
|
-
var allData = tslib.__spreadArray(tslib.__spreadArray([], data, true), unselectedData, true);
|
|
6260
6333
|
var allDataMeanValue = (_c = calculateMean(allData)) !== null && _c !== void 0 ? _c : 0;
|
|
6261
6334
|
var allDataMeanLine = (statsAnnotations.includes('mean') && unselectedData.length > 0 && data.length > 0) ? [{
|
|
6262
6335
|
type: 'line',
|
|
@@ -6328,7 +6401,7 @@ var HistogramPlot = function (props) {
|
|
|
6328
6401
|
// If binSizeOverride is provided, use it to set the bin size and range explicitly.
|
|
6329
6402
|
// Plotly does a better job of setting bins and ending them at nice numbers, so only use
|
|
6330
6403
|
// this prop when necessary.
|
|
6331
|
-
var xBins = binSizeOverride
|
|
6404
|
+
var xBins = (binSizeOverride && allData.length > 0)
|
|
6332
6405
|
? (isDateArray(allData)
|
|
6333
6406
|
? {
|
|
6334
6407
|
start: roundToPrevDay(Math.min.apply(Math, allData.map(function (d) { return d.getTime(); }))), // Find a nice round number as a starting point.
|
|
@@ -6358,7 +6431,7 @@ var HistogramPlot = function (props) {
|
|
|
6358
6431
|
width: 0.5,
|
|
6359
6432
|
},
|
|
6360
6433
|
},
|
|
6361
|
-
hovertemplate: '[%{x})<br>Count: %{y}<extra></extra>',
|
|
6434
|
+
hovertemplate: '[%{x})<br>Count: %{y}<extra></extra>',
|
|
6362
6435
|
};
|
|
6363
6436
|
var meanAnnotation = (statsAnnotations.includes('mean') && meanLine && data.length > 0) ? [{
|
|
6364
6437
|
x: meanValue,
|
|
@@ -6449,6 +6522,7 @@ var HistogramPlot = function (props) {
|
|
|
6449
6522
|
ticklabelposition: 'outside',
|
|
6450
6523
|
tickformat: isDateArray(data) ? dateTickFormat : undefined, // Format ticks for dates
|
|
6451
6524
|
automargin: true, // Adjust margin if tick labels rotate
|
|
6525
|
+
hoverformat: (isNumberArray(allData) && Math.max.apply(Math, allData) - Math.min.apply(Math, allData) > 3) ? '.1~f' : undefined,
|
|
6452
6526
|
},
|
|
6453
6527
|
yaxis: {
|
|
6454
6528
|
title: {
|