td-plots 1.5.2 → 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/index.js CHANGED
@@ -125,29 +125,6 @@ var plotlyMToMilliseconds = function (mString) {
125
125
  }
126
126
  return 0;
127
127
  };
128
- // Convert a string specifying a day (with no time) to a timestamp
129
- var convertDateDescriptionToTimestamp = function (startValue) {
130
- if (typeof startValue === 'number') {
131
- // Then we're assuming it's already a timestamp
132
- return startValue;
133
- }
134
- if (typeof startValue === 'string') {
135
- // Try to parse the date string.
136
- var date = new Date(startValue);
137
- // Sometimes the date will fail to be a proper Date if it does not
138
- // have a time component. Add a time stamp if necessary.
139
- if (isNaN(date.getTime())) {
140
- // Then date did not have a time and is just a day. Try adding a time.
141
- var dateWithTime = new Date(startValue + 'T00:00:00');
142
- if (isNaN(dateWithTime.getTime())) {
143
- throw new Error("Cannot parse date: ".concat(startValue));
144
- }
145
- return dateWithTime.getTime();
146
- }
147
- return date.getTime();
148
- }
149
- throw new Error("Unexpected type for start value: ".concat(typeof startValue));
150
- };
151
128
 
152
129
  function getDefaultExportFromCjs (x) {
153
130
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
@@ -6100,6 +6077,8 @@ var HistogramPlot = function (props) {
6100
6077
  var containerRef = React.useRef(null);
6101
6078
  // Track any selections made in this plot so we can style the selection box.
6102
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);
6103
6082
  // If all the data becomes selected, we should forget any old selections.
6104
6083
  React.useEffect(function () {
6105
6084
  if (unselectedData.length === 0) {
@@ -6112,6 +6091,8 @@ var HistogramPlot = function (props) {
6112
6091
  // to access that information once the plot has been initialized so that we can prevent the
6113
6092
  // axis range from changing during interaction. Dates use strings.
6114
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];
6115
6096
  // Once the plot is drawn, record the initial axis range so we can keep it fixed.
6116
6097
  // figure should be Readonly<Plotly.Figure> but react-plotly.js doesn't expose that type, so we use any.
6117
6098
  var handlePlotUpdate = function (figure, graphDiv) {
@@ -6127,17 +6108,42 @@ var HistogramPlot = function (props) {
6127
6108
  setFixedXAxisRange(range);
6128
6109
  }
6129
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
+ }
6130
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
+ }, []);
6131
6141
  // Create handler for click event that can use event data to update the plot if desired.
6132
6142
  var handleClick = function (event) {
6143
+ var _a, _b, _c, _d;
6133
6144
  if (!event || !event.points || event.points.length === 0) {
6134
6145
  return;
6135
6146
  }
6136
- // The main trace is always curve number 0.
6137
- // If we clicked the unselected data trace, nothing should happen.
6138
- if (event.points[0].curveNumber !== 0) {
6139
- return;
6140
- }
6141
6147
  // Use the bin number to determine which bar was clicked and determine the range of the clicked bar.
6142
6148
  if ("binNumber" in event.points[0] && typeof event.points[0].binNumber === 'number') {
6143
6149
  // Get the index of the clicked bar with respect to the trace. So if
@@ -6153,40 +6159,48 @@ var HistogramPlot = function (props) {
6153
6159
  var globalFirstBinStart = void 0;
6154
6160
  if (isDateArray(data)) {
6155
6161
  // Date bins are represented as strings (sometimes). We'll need to convert whatever plotly gives us to timestamps.
6156
- globalFirstBinStart = convertDateDescriptionToTimestamp(event.points[0].data.xbins.start);
6157
- minTraceValue = Math.min.apply(Math, data.map(function (d) { return d.getTime(); }));
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(); }));
6158
6164
  }
6159
6165
  else {
6160
- // All of the event info should be numbers
6161
- if (typeof event.points[0].data.xbins.start !== 'number') {
6162
- console.error("Unexpected type for xbins.start. Expected number.");
6163
- return;
6164
- }
6165
6166
  // Get the min value of the trace and the beginning of the first bin (globally)
6166
- minTraceValue = Math.min.apply(Math, data);
6167
- 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;
6168
6169
  }
6169
6170
  // Finally, we need to calculate the min and max values of the clicked bin.
6170
6171
  // If the bin size is a month or more, plotly records it in their "mstring" format like "M3" for 3 months.
6171
6172
  // We then must convert it back to milliseconds. Otherwise, it's always ms.
6172
- var binSize = typeof event.points[0].data.xbins.size === 'string'
6173
- ? plotlyMToMilliseconds(event.points[0].data.xbins.size)
6174
- : event.points[0].data.xbins.size;
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;
6175
6177
  // This minTraceValue is in the 0th bin of this trace. Find the index of this bin in the whole plot.
6176
- var clickedBinGlobalIndex = clickedBinIndex + Math.floor((minTraceValue - globalFirstBinStart) / binSize);
6177
- var _a = [
6178
- globalFirstBinStart + clickedBinGlobalIndex * binSize,
6179
- globalFirstBinStart + (clickedBinGlobalIndex + 1) * binSize
6180
- ], 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];
6181
6183
  if (isDateArray(data)) {
6182
6184
  var minDate = new Date(minBinValue);
6183
6185
  var maxDate = new Date(maxBinValue);
6184
- handleClickOrSelection(minDate, maxDate);
6185
- setSelectedRange([minDate, maxDate]);
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
+ }
6186
6194
  }
6187
6195
  else {
6188
- handleClickOrSelection(minBinValue, maxBinValue);
6189
- setSelectedRange([minBinValue, maxBinValue]);
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
+ }
6190
6204
  }
6191
6205
  }
6192
6206
  };
@@ -6208,11 +6222,11 @@ var HistogramPlot = function (props) {
6208
6222
  var lastBinMidPoint = new Date(event.points[event.points.length - 1].x).getTime();
6209
6223
  // If the bin size is a month or more, plotly records it in their "mstring" format like "M3" for 3 months.
6210
6224
  // We then must convert it back to milliseconds. Otherwise, it's always ms.
6211
- var binSize = typeof event.points[0].data.xbins.size === 'string'
6212
- ? plotlyMToMilliseconds(event.points[0].data.xbins.size)
6213
- : event.points[0].data.xbins.size;
6214
- minValue = new Date(firstBinMidPoint - binSize / 2);
6215
- maxValue = new Date(lastBinMidPoint + binSize / 2);
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);
6216
6230
  }
6217
6231
  else {
6218
6232
  // No bins selected, so the range should be empty.
@@ -6232,11 +6246,12 @@ var HistogramPlot = function (props) {
6232
6246
  // Set the range based on the bins selected. Plotly will include a bin in event.points if
6233
6247
  // at least half of it is selected.
6234
6248
  if (event.points && event.points.length > 0) {
6235
- var firstBinMidPoint = event.points[0].x;
6236
- var lastBinMidPoint = event.points[event.points.length - 1].x;
6237
- var binSize = event.points[0].data.xbins.size;
6238
- var roundedMinValue = firstBinMidPoint - binSize / 2;
6239
- var roundedMaxValue = lastBinMidPoint + binSize / 2;
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;
6240
6255
  minValue = roundedMinValue;
6241
6256
  maxValue = roundedMaxValue;
6242
6257
  }
@@ -6255,12 +6270,24 @@ var HistogramPlot = function (props) {
6255
6270
  if (minValue !== undefined && maxValue !== undefined) {
6256
6271
  // Update selected range. Have to be strict about types.
6257
6272
  if (typeof minValue === 'number' && typeof maxValue === 'number') {
6258
- setSelectedRange([minValue, maxValue]);
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
+ }
6259
6280
  }
6260
6281
  else if (minValue instanceof Date && maxValue instanceof Date) {
6261
- setSelectedRange([minValue, maxValue]);
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
+ }
6262
6289
  }
6263
- handleClickOrSelection(minValue, maxValue);
6290
+ handleClickOrSelection(minValue, maxValue, isShiftPressed.current);
6264
6291
  }
6265
6292
  };
6266
6293
  // Create the selected range box
@@ -6271,20 +6298,20 @@ var HistogramPlot = function (props) {
6271
6298
  return []; // Don't show the box if the entire dataset is selected.
6272
6299
  // Create a multiply-like effect by using a semi-transparent dark overlay
6273
6300
  var multiplyColor = 'rgba(29, 104, 185, 0.1)';
6274
- return [{
6275
- type: 'rect',
6276
- x0: isDateArray(selectedRange) ? selectedRange[0].getTime() : selectedRange[0],
6277
- x1: isDateArray(selectedRange) ? selectedRange[1].getTime() : selectedRange[1],
6278
- y0: 0,
6279
- y1: 1,
6280
- yref: 'paper',
6281
- fillcolor: multiplyColor,
6282
- line: {
6283
- width: 1,
6284
- color: multiplyColor
6285
- },
6286
- layer: 'above' // Ensure the selection box is above the bars
6287
- }];
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
+ }); });
6288
6315
  }, [selectedRange, unselectedData]);
6289
6316
  // Calculate the mean of the selected data using normalized data
6290
6317
  var meanValue = (_a = calculateMean(data)) !== null && _a !== void 0 ? _a : 0; // Default to 0 if no data
@@ -6303,7 +6330,6 @@ var HistogramPlot = function (props) {
6303
6330
  }
6304
6331
  }] : [];
6305
6332
  // Draw mean line for all data
6306
- var allData = tslib.__spreadArray(tslib.__spreadArray([], data, true), unselectedData, true);
6307
6333
  var allDataMeanValue = (_c = calculateMean(allData)) !== null && _c !== void 0 ? _c : 0;
6308
6334
  var allDataMeanLine = (statsAnnotations.includes('mean') && unselectedData.length > 0 && data.length > 0) ? [{
6309
6335
  type: 'line',