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
|
@@ -9,7 +9,7 @@ export type HistogramPlotProps = {
|
|
|
9
9
|
barColor?: string;
|
|
10
10
|
unselectedBarColor?: string;
|
|
11
11
|
selectorsColor?: string;
|
|
12
|
-
handleClickOrSelection?: (minValue: number | Date, maxValue: number | Date) => void;
|
|
12
|
+
handleClickOrSelection?: (minValue: number | Date, maxValue: number | Date, shiftKeyPressed?: boolean) => void;
|
|
13
13
|
containerStyleOverrides?: React.CSSProperties;
|
|
14
14
|
onDeselect?: () => void;
|
|
15
15
|
plotId?: string;
|
|
@@ -5,3 +5,5 @@ export declare function calculateStandardDeviation(arr: number[] | Date[]): numb
|
|
|
5
5
|
export declare const formatDateMDY: (timestamp: number) => string;
|
|
6
6
|
export declare const roundToNextDay: (timestamp: number) => string;
|
|
7
7
|
export declare const roundToPrevDay: (timestamp: number) => string;
|
|
8
|
+
export declare const plotlyMToMilliseconds: (mString: string) => number;
|
|
9
|
+
export declare const convertDateDescriptionToTimestamp: (startValue: string | number) => number;
|
package/dist/index.esm.js
CHANGED
|
@@ -37,6 +37,7 @@ var css_248z = ".plot-container{height:100%;max-width:100%;min-height:300px;over
|
|
|
37
37
|
styleInject(css_248z);
|
|
38
38
|
|
|
39
39
|
// Utility functions for our components
|
|
40
|
+
var ONEAVGMONTH = 2629800000; // Average month length in ms, copied from plotly constants in plotly.js/src/constants/numerical.js
|
|
40
41
|
// Type guard to check if array contains only numbers
|
|
41
42
|
var isNumberArray = function (arr) {
|
|
42
43
|
return arr.every(function (item) { return typeof item === 'number' && !isNaN(item); });
|
|
@@ -89,6 +90,21 @@ var roundToPrevDay = function (timestamp) {
|
|
|
89
90
|
date.setHours(0, 0, 0, 0); // Start of day
|
|
90
91
|
return date.toISOString();
|
|
91
92
|
};
|
|
93
|
+
// Convert plotly M# string to number of milliseconds
|
|
94
|
+
// According to the docs, plotly will use M# whenever the bin size is
|
|
95
|
+
// larger than one average month. Even if the bin size is 2 years, plotly
|
|
96
|
+
// will still use M24 instead of Y2 (plotly.js/src/plots/cartesian/axes.js).
|
|
97
|
+
var plotlyMToMilliseconds = function (mString) {
|
|
98
|
+
var match = mString.match(/^M(\d+)$/);
|
|
99
|
+
if (match) {
|
|
100
|
+
var months = parseInt(match[1], 10);
|
|
101
|
+
return months * ONEAVGMONTH;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
console.error("plotlyMToMilliseconds: Invalid M# string: ".concat(mString));
|
|
105
|
+
}
|
|
106
|
+
return 0;
|
|
107
|
+
};
|
|
92
108
|
|
|
93
109
|
function getDefaultExportFromCjs (x) {
|
|
94
110
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
@@ -6041,6 +6057,8 @@ var HistogramPlot = function (props) {
|
|
|
6041
6057
|
var containerRef = useRef(null);
|
|
6042
6058
|
// Track any selections made in this plot so we can style the selection box.
|
|
6043
6059
|
var _o = useState(null), selectedRange = _o[0], setSelectedRange = _o[1];
|
|
6060
|
+
// Combine all data into one for later calculations
|
|
6061
|
+
var allData = __spreadArray(__spreadArray([], data, true), unselectedData, true);
|
|
6044
6062
|
// If all the data becomes selected, we should forget any old selections.
|
|
6045
6063
|
useEffect(function () {
|
|
6046
6064
|
if (unselectedData.length === 0) {
|
|
@@ -6053,6 +6071,8 @@ var HistogramPlot = function (props) {
|
|
|
6053
6071
|
// to access that information once the plot has been initialized so that we can prevent the
|
|
6054
6072
|
// axis range from changing during interaction. Dates use strings.
|
|
6055
6073
|
var _p = useState(undefined), fixedXAxisRange = _p[0], setFixedXAxisRange = _p[1];
|
|
6074
|
+
// track xbins too
|
|
6075
|
+
var _q = useState(undefined), binSize = _q[0], setBinSize = _q[1];
|
|
6056
6076
|
// Once the plot is drawn, record the initial axis range so we can keep it fixed.
|
|
6057
6077
|
// figure should be Readonly<Plotly.Figure> but react-plotly.js doesn't expose that type, so we use any.
|
|
6058
6078
|
var handlePlotUpdate = function (figure, graphDiv) {
|
|
@@ -6068,17 +6088,42 @@ var HistogramPlot = function (props) {
|
|
|
6068
6088
|
setFixedXAxisRange(range);
|
|
6069
6089
|
}
|
|
6070
6090
|
}
|
|
6091
|
+
if (!binSize) {
|
|
6092
|
+
// Get the bin size from the first trace. Both traces should have the same bin size.
|
|
6093
|
+
if (figure && figure.data && figure.data.length > 0 && figure.data[0].xbins && figure.data[0].xbins.size) {
|
|
6094
|
+
setBinSize(figure.data[0].xbins.size);
|
|
6095
|
+
}
|
|
6096
|
+
}
|
|
6071
6097
|
};
|
|
6098
|
+
// Track shift key state
|
|
6099
|
+
var isShiftPressed = useRef(false);
|
|
6100
|
+
// Add keyboard event listeners to track shift key
|
|
6101
|
+
useEffect(function () {
|
|
6102
|
+
var handleKeyDown = function (e) {
|
|
6103
|
+
if (e.key === 'Shift') {
|
|
6104
|
+
isShiftPressed.current = true;
|
|
6105
|
+
}
|
|
6106
|
+
};
|
|
6107
|
+
var handleKeyUp = function (e) {
|
|
6108
|
+
if (e.key === 'Shift') {
|
|
6109
|
+
isShiftPressed.current = false;
|
|
6110
|
+
}
|
|
6111
|
+
};
|
|
6112
|
+
// Add event listeners to document to catch shift key globally
|
|
6113
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
6114
|
+
document.addEventListener('keyup', handleKeyUp);
|
|
6115
|
+
// Cleanup
|
|
6116
|
+
return function () {
|
|
6117
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
6118
|
+
document.removeEventListener('keyup', handleKeyUp);
|
|
6119
|
+
};
|
|
6120
|
+
}, []);
|
|
6072
6121
|
// Create handler for click event that can use event data to update the plot if desired.
|
|
6073
6122
|
var handleClick = function (event) {
|
|
6123
|
+
var _a, _b, _c, _d;
|
|
6074
6124
|
if (!event || !event.points || event.points.length === 0) {
|
|
6075
6125
|
return;
|
|
6076
6126
|
}
|
|
6077
|
-
// The main trace is always curve number 0.
|
|
6078
|
-
// If we clicked the unselected data trace, nothing should happen.
|
|
6079
|
-
if (event.points[0].curveNumber !== 0) {
|
|
6080
|
-
return;
|
|
6081
|
-
}
|
|
6082
6127
|
// Use the bin number to determine which bar was clicked and determine the range of the clicked bar.
|
|
6083
6128
|
if ("binNumber" in event.points[0] && typeof event.points[0].binNumber === 'number') {
|
|
6084
6129
|
// Get the index of the clicked bar with respect to the trace. So if
|
|
@@ -6093,37 +6138,49 @@ var HistogramPlot = function (props) {
|
|
|
6093
6138
|
// This is the start of the first bin out of all the traces. The global first bin.
|
|
6094
6139
|
var globalFirstBinStart = void 0;
|
|
6095
6140
|
if (isDateArray(data)) {
|
|
6096
|
-
// Date bins are represented as strings. We'll need to convert
|
|
6097
|
-
globalFirstBinStart = new Date(event.points[0].data.xbins.start).getTime();
|
|
6098
|
-
minTraceValue = Math.min.apply(Math, data.map(function (d) { return d.getTime(); }));
|
|
6141
|
+
// Date bins are represented as strings (sometimes). We'll need to convert whatever plotly gives us to timestamps.
|
|
6142
|
+
globalFirstBinStart = fixedXAxisRange ? new Date(fixedXAxisRange[0]).getTime() : new Date(event.points[0].data.xbins.start).getTime();
|
|
6143
|
+
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(); }));
|
|
6099
6144
|
}
|
|
6100
6145
|
else {
|
|
6101
|
-
// All of the event info should be numbers
|
|
6102
|
-
if (typeof event.points[0].data.xbins.start !== 'number') {
|
|
6103
|
-
console.error("Unexpected type for xbins.start. Expected number.");
|
|
6104
|
-
return;
|
|
6105
|
-
}
|
|
6106
6146
|
// Get the min value of the trace and the beginning of the first bin (globally)
|
|
6107
|
-
minTraceValue = Math.min.apply(Math, data);
|
|
6108
|
-
globalFirstBinStart = event.points[0].data.xbins.start;
|
|
6147
|
+
minTraceValue = (event.points[0].curveNumber === 0) ? Math.min.apply(Math, data) : Math.min.apply(Math, unselectedData);
|
|
6148
|
+
globalFirstBinStart = fixedXAxisRange ? fixedXAxisRange[0] : event.points[0].data.xbins.start;
|
|
6109
6149
|
}
|
|
6150
|
+
// Finally, we need to calculate the min and max values of the clicked bin.
|
|
6151
|
+
// If the bin size is a month or more, plotly records it in their "mstring" format like "M3" for 3 months.
|
|
6152
|
+
// We then must convert it back to milliseconds. Otherwise, it's always ms.
|
|
6153
|
+
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;
|
|
6154
|
+
var convertedBinSize = typeof size === 'string'
|
|
6155
|
+
? plotlyMToMilliseconds(size)
|
|
6156
|
+
: size;
|
|
6110
6157
|
// This minTraceValue is in the 0th bin of this trace. Find the index of this bin in the whole plot.
|
|
6111
|
-
var clickedBinGlobalIndex = clickedBinIndex + Math.floor((minTraceValue - globalFirstBinStart) /
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
globalFirstBinStart + (clickedBinGlobalIndex + 1) * binSize
|
|
6117
|
-
], minBinValue = _a[0], maxBinValue = _a[1];
|
|
6158
|
+
var clickedBinGlobalIndex = clickedBinIndex + Math.floor((minTraceValue - globalFirstBinStart) / convertedBinSize);
|
|
6159
|
+
var _e = [
|
|
6160
|
+
globalFirstBinStart + clickedBinGlobalIndex * convertedBinSize,
|
|
6161
|
+
globalFirstBinStart + (clickedBinGlobalIndex + 1) * convertedBinSize
|
|
6162
|
+
], minBinValue = _e[0], maxBinValue = _e[1];
|
|
6118
6163
|
if (isDateArray(data)) {
|
|
6119
6164
|
var minDate = new Date(minBinValue);
|
|
6120
6165
|
var maxDate = new Date(maxBinValue);
|
|
6121
|
-
handleClickOrSelection(minDate, maxDate);
|
|
6122
|
-
|
|
6166
|
+
handleClickOrSelection(minDate, maxDate, isShiftPressed.current);
|
|
6167
|
+
var newMinMax = [minDate, maxDate];
|
|
6168
|
+
if (isShiftPressed.current && selectedRange) {
|
|
6169
|
+
setSelectedRange(__spreadArray(__spreadArray([], selectedRange, true), [newMinMax], false));
|
|
6170
|
+
}
|
|
6171
|
+
else {
|
|
6172
|
+
setSelectedRange([newMinMax]);
|
|
6173
|
+
}
|
|
6123
6174
|
}
|
|
6124
6175
|
else {
|
|
6125
|
-
handleClickOrSelection(minBinValue, maxBinValue);
|
|
6126
|
-
|
|
6176
|
+
handleClickOrSelection(minBinValue, maxBinValue, isShiftPressed.current);
|
|
6177
|
+
var newMinMax = [minBinValue, maxBinValue];
|
|
6178
|
+
if (isShiftPressed.current && selectedRange) {
|
|
6179
|
+
setSelectedRange(__spreadArray(__spreadArray([], selectedRange, true), [newMinMax], false));
|
|
6180
|
+
}
|
|
6181
|
+
else {
|
|
6182
|
+
setSelectedRange([newMinMax]);
|
|
6183
|
+
}
|
|
6127
6184
|
}
|
|
6128
6185
|
}
|
|
6129
6186
|
};
|
|
@@ -6143,9 +6200,13 @@ var HistogramPlot = function (props) {
|
|
|
6143
6200
|
// Means at least one bin has been selected
|
|
6144
6201
|
var firstBinMidPoint = new Date(event.points[0].x).getTime();
|
|
6145
6202
|
var lastBinMidPoint = new Date(event.points[event.points.length - 1].x).getTime();
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6203
|
+
// If the bin size is a month or more, plotly records it in their "mstring" format like "M3" for 3 months.
|
|
6204
|
+
// We then must convert it back to milliseconds. Otherwise, it's always ms.
|
|
6205
|
+
var convertedBinSize = typeof binSize === 'string'
|
|
6206
|
+
? plotlyMToMilliseconds(binSize)
|
|
6207
|
+
: binSize;
|
|
6208
|
+
minValue = new Date(firstBinMidPoint - convertedBinSize / 2);
|
|
6209
|
+
maxValue = new Date(lastBinMidPoint + convertedBinSize / 2);
|
|
6149
6210
|
}
|
|
6150
6211
|
else {
|
|
6151
6212
|
// No bins selected, so the range should be empty.
|
|
@@ -6165,11 +6226,12 @@ var HistogramPlot = function (props) {
|
|
|
6165
6226
|
// Set the range based on the bins selected. Plotly will include a bin in event.points if
|
|
6166
6227
|
// at least half of it is selected.
|
|
6167
6228
|
if (event.points && event.points.length > 0) {
|
|
6168
|
-
|
|
6169
|
-
var
|
|
6170
|
-
var
|
|
6171
|
-
var
|
|
6172
|
-
var
|
|
6229
|
+
// Find the max and min midpoints. They may be from curve 0 or 1.
|
|
6230
|
+
var firstBinMidPoint = Math.min.apply(Math, event.points.map(function (p) { return p.x; }));
|
|
6231
|
+
var lastBinMidPoint = Math.max.apply(Math, event.points.map(function (p) { return p.x; }));
|
|
6232
|
+
var convertedBinSize = binSize;
|
|
6233
|
+
var roundedMinValue = firstBinMidPoint - convertedBinSize / 2;
|
|
6234
|
+
var roundedMaxValue = lastBinMidPoint + convertedBinSize / 2;
|
|
6173
6235
|
minValue = roundedMinValue;
|
|
6174
6236
|
maxValue = roundedMaxValue;
|
|
6175
6237
|
}
|
|
@@ -6188,12 +6250,24 @@ var HistogramPlot = function (props) {
|
|
|
6188
6250
|
if (minValue !== undefined && maxValue !== undefined) {
|
|
6189
6251
|
// Update selected range. Have to be strict about types.
|
|
6190
6252
|
if (typeof minValue === 'number' && typeof maxValue === 'number') {
|
|
6191
|
-
|
|
6253
|
+
var newMinMax = [minValue, maxValue];
|
|
6254
|
+
if (isShiftPressed.current && selectedRange) {
|
|
6255
|
+
setSelectedRange(__spreadArray(__spreadArray([], selectedRange, true), [newMinMax], false));
|
|
6256
|
+
}
|
|
6257
|
+
else {
|
|
6258
|
+
setSelectedRange([newMinMax]);
|
|
6259
|
+
}
|
|
6192
6260
|
}
|
|
6193
6261
|
else if (minValue instanceof Date && maxValue instanceof Date) {
|
|
6194
|
-
|
|
6262
|
+
var newMinMax = [minValue, maxValue];
|
|
6263
|
+
if (isShiftPressed.current && selectedRange) {
|
|
6264
|
+
setSelectedRange(__spreadArray(__spreadArray([], selectedRange, true), [newMinMax], false));
|
|
6265
|
+
}
|
|
6266
|
+
else {
|
|
6267
|
+
setSelectedRange([newMinMax]);
|
|
6268
|
+
}
|
|
6195
6269
|
}
|
|
6196
|
-
handleClickOrSelection(minValue, maxValue);
|
|
6270
|
+
handleClickOrSelection(minValue, maxValue, isShiftPressed.current);
|
|
6197
6271
|
}
|
|
6198
6272
|
};
|
|
6199
6273
|
// Create the selected range box
|
|
@@ -6204,20 +6278,20 @@ var HistogramPlot = function (props) {
|
|
|
6204
6278
|
return []; // Don't show the box if the entire dataset is selected.
|
|
6205
6279
|
// Create a multiply-like effect by using a semi-transparent dark overlay
|
|
6206
6280
|
var multiplyColor = 'rgba(29, 104, 185, 0.1)';
|
|
6207
|
-
return
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6281
|
+
return selectedRange.map(function (maxMin) { return ({
|
|
6282
|
+
type: 'rect',
|
|
6283
|
+
x0: isDateArray(maxMin) ? maxMin[0].getTime() : maxMin[0],
|
|
6284
|
+
x1: isDateArray(maxMin) ? maxMin[1].getTime() : maxMin[1],
|
|
6285
|
+
y0: 0,
|
|
6286
|
+
y1: 1,
|
|
6287
|
+
yref: 'paper',
|
|
6288
|
+
fillcolor: multiplyColor,
|
|
6289
|
+
line: {
|
|
6290
|
+
width: 1,
|
|
6291
|
+
color: multiplyColor
|
|
6292
|
+
},
|
|
6293
|
+
layer: 'above' // Ensure the selection box is above the bars
|
|
6294
|
+
}); });
|
|
6221
6295
|
}, [selectedRange, unselectedData]);
|
|
6222
6296
|
// Calculate the mean of the selected data using normalized data
|
|
6223
6297
|
var meanValue = (_a = calculateMean(data)) !== null && _a !== void 0 ? _a : 0; // Default to 0 if no data
|
|
@@ -6236,7 +6310,6 @@ var HistogramPlot = function (props) {
|
|
|
6236
6310
|
}
|
|
6237
6311
|
}] : [];
|
|
6238
6312
|
// Draw mean line for all data
|
|
6239
|
-
var allData = __spreadArray(__spreadArray([], data, true), unselectedData, true);
|
|
6240
6313
|
var allDataMeanValue = (_c = calculateMean(allData)) !== null && _c !== void 0 ? _c : 0;
|
|
6241
6314
|
var allDataMeanLine = (statsAnnotations.includes('mean') && unselectedData.length > 0 && data.length > 0) ? [{
|
|
6242
6315
|
type: 'line',
|
|
@@ -6308,7 +6381,7 @@ var HistogramPlot = function (props) {
|
|
|
6308
6381
|
// If binSizeOverride is provided, use it to set the bin size and range explicitly.
|
|
6309
6382
|
// Plotly does a better job of setting bins and ending them at nice numbers, so only use
|
|
6310
6383
|
// this prop when necessary.
|
|
6311
|
-
var xBins = binSizeOverride
|
|
6384
|
+
var xBins = (binSizeOverride && allData.length > 0)
|
|
6312
6385
|
? (isDateArray(allData)
|
|
6313
6386
|
? {
|
|
6314
6387
|
start: roundToPrevDay(Math.min.apply(Math, allData.map(function (d) { return d.getTime(); }))), // Find a nice round number as a starting point.
|
|
@@ -6338,7 +6411,7 @@ var HistogramPlot = function (props) {
|
|
|
6338
6411
|
width: 0.5,
|
|
6339
6412
|
},
|
|
6340
6413
|
},
|
|
6341
|
-
hovertemplate: '[%{x})<br>Count: %{y}<extra></extra>',
|
|
6414
|
+
hovertemplate: '[%{x})<br>Count: %{y}<extra></extra>',
|
|
6342
6415
|
};
|
|
6343
6416
|
var meanAnnotation = (statsAnnotations.includes('mean') && meanLine && data.length > 0) ? [{
|
|
6344
6417
|
x: meanValue,
|
|
@@ -6429,6 +6502,7 @@ var HistogramPlot = function (props) {
|
|
|
6429
6502
|
ticklabelposition: 'outside',
|
|
6430
6503
|
tickformat: isDateArray(data) ? dateTickFormat : undefined, // Format ticks for dates
|
|
6431
6504
|
automargin: true, // Adjust margin if tick labels rotate
|
|
6505
|
+
hoverformat: (isNumberArray(allData) && Math.max.apply(Math, allData) - Math.min.apply(Math, allData) > 3) ? '.1~f' : undefined,
|
|
6432
6506
|
},
|
|
6433
6507
|
yaxis: {
|
|
6434
6508
|
title: {
|