@windborne/grapher 1.0.26 → 1.0.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windborne/grapher",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "description": "Graphing library",
5
5
  "main": "src/index.js",
6
6
  "module": "dist/bundle.esm.js",
@@ -335,6 +335,8 @@ export default class RangeGraph extends React.PureComponent {
335
335
  width={pixelMaxX - pixelMinX}
336
336
  height={totalHeight}
337
337
  className="target-selection-outline"
338
+ onMouseDown={this.startScroll}
339
+ onTouchStart={this.startScroll}
338
340
  />
339
341
  </g>
340
342
 
package/src/grapher.scss CHANGED
@@ -675,7 +675,8 @@
675
675
 
676
676
  .target-selection-outline {
677
677
  stroke: $range-graph-selection-outline-color;
678
- fill: none;
678
+ cursor: ew-resize;
679
+ fill: transparent;
679
680
  }
680
681
 
681
682
  .selection-bar-track {
@@ -8,8 +8,7 @@ import drawBars from './draw_bars';
8
8
  import drawLine from './draw_line';
9
9
  import LineProgram from './line_program';
10
10
  import ShadowProgram from './shadow_program';
11
- import sizeCanvas from './size_canvas';
12
- import { applyReducedOpacity } from "../helpers/colors";
11
+ import sizeCanvas, { DPI_INCREASE } from './size_canvas';
13
12
 
14
13
  export default class GraphBodyRenderer extends Eventable {
15
14
 
@@ -109,7 +108,23 @@ export default class GraphBodyRenderer extends Eventable {
109
108
  }
110
109
 
111
110
  let cutoffIndex = -1;
112
- // cutoff time calculations for visible bounds-based approach
111
+ let cutoffTime = null;
112
+ let cutoffData = singleSeries.data;
113
+
114
+ const isObjectFormat = singleSeries.data && singleSeries.data.length > 0 &&
115
+ typeof singleSeries.data[0] === 'object' &&
116
+ !Array.isArray(singleSeries.data[0]);
117
+
118
+ if (isObjectFormat && singleSeries.cutoffTime) {
119
+ cutoffData = singleSeries.data.map(point => {
120
+ const xValue = point[singleSeries.xKey || 'x'];
121
+ const yValue = point[singleSeries.yKey || 'y'];
122
+
123
+ const convertedX = typeof xValue === 'string' ? new Date(xValue) : xValue;
124
+
125
+ return [convertedX, yValue];
126
+ });
127
+ }
113
128
 
114
129
  if (singleSeries.cutoffTime && singleSeries.data && singleSeries.data.length > 0) {
115
130
  let cutoffDate;
@@ -121,40 +136,61 @@ export default class GraphBodyRenderer extends Eventable {
121
136
  cutoffDate = singleSeries.cutoffTime;
122
137
  }
123
138
 
124
- // getting the ghost point
125
- const cutoffTime = cutoffDate instanceof Date ? cutoffDate.getTime() : cutoffDate;
139
+ cutoffTime = cutoffDate instanceof Date ? cutoffDate.getTime() : cutoffDate;
126
140
 
127
- for (let i = 0; i < singleSeries.data.length - 1; i++) {
128
- const currentPoint = singleSeries.data[i];
129
- const nextPoint = singleSeries.data[i + 1];
141
+ for (let i = 0; i < cutoffData.length - 1; i++) {
142
+ const currentPoint = cutoffData[i];
143
+ const nextPoint = cutoffData[i + 1];
130
144
 
131
- const currentTime = Array.isArray(currentPoint) ?
132
- (currentPoint[0] instanceof Date ? currentPoint[0].getTime() : currentPoint[0]) : i;
133
- const nextTime = Array.isArray(nextPoint) ?
134
- (nextPoint[0] instanceof Date ? nextPoint[0].getTime() : nextPoint[0]) : (i + 1);
145
+ const currentTime = currentPoint[0] instanceof Date ? currentPoint[0].getTime() : currentPoint[0];
146
+ const nextTime = nextPoint[0] instanceof Date ? nextPoint[0].getTime() : nextPoint[0];
135
147
 
136
148
  if (currentTime <= cutoffTime && cutoffTime <= nextTime) {
137
- // interpolate exact position between these two points
138
149
  const timeRatio = (cutoffTime - currentTime) / (nextTime - currentTime);
139
150
  cutoffIndex = i + timeRatio;
140
151
  break;
141
152
  } else if (currentTime > cutoffTime) {
142
- // cutoff is before the first data point
143
153
  cutoffIndex = i;
144
154
  break;
145
155
  }
146
156
  }
147
157
 
148
- // cutoff is after all data points
149
158
  if (cutoffIndex === -1) {
150
- cutoffIndex = singleSeries.data.length - 1;
159
+ cutoffIndex = cutoffData.length - 1;
151
160
  }
152
-
153
-
154
- // Note: cutoffIndex is used for cutoff calculations but we no longer split data
155
161
  }
156
162
 
157
- const getIndividualPoints = (useDataSpace) => {
163
+ const getIndividualPoints = (useDataSpace, includeBeyondBounds = false) => {
164
+ if (!useDataSpace && inRenderSpace && inRenderSpace.yValues) {
165
+ if (!bounds) {
166
+ bounds = singleSeries.axis.currentBounds;
167
+ }
168
+
169
+ const individualPoints = [];
170
+ const { yValues, nullMask } = inRenderSpace;
171
+ const threshold = yValues.length / 2;
172
+ let pastThreshold = 0;
173
+ const samples = [];
174
+
175
+ for (let pixelX = 0; pixelX < yValues.length; pixelX++) {
176
+ if (nullMask[pixelX] === 0) {
177
+ const xCoord = pixelX * DPI_INCREASE;
178
+ individualPoints.push([xCoord, yValues[pixelX]]);
179
+
180
+ if (pixelX > threshold) {
181
+ pastThreshold++;
182
+ if (samples.length < 3) samples.push({pixelX, xCoord, nullMask: nullMask[pixelX]});
183
+ }
184
+ }
185
+ }
186
+
187
+ if (individualPoints.length < 50) {
188
+ return getIndividualPoints(true, includeBeyondBounds);
189
+ }
190
+
191
+ return individualPoints;
192
+ }
193
+
158
194
  if (!bounds) {
159
195
  bounds = singleSeries.axis.currentBounds;
160
196
  }
@@ -165,15 +201,58 @@ export default class GraphBodyRenderer extends Eventable {
165
201
  data = singleSeries.inDataSpace;
166
202
  }
167
203
 
168
- for (let [x, y] of data) {
169
- if (y === null) {
204
+ let boundsMinX = bounds.minX instanceof Date ? bounds.minX.getTime() : bounds.minX;
205
+ let boundsMaxX = bounds.maxX instanceof Date ? bounds.maxX.getTime() : bounds.maxX;
206
+
207
+ let foundBeyondBounds = false;
208
+ let lastPointBeforeBounds = null;
209
+
210
+ for (let i = 0; i < data.length; i++) {
211
+ let x, y;
212
+
213
+ if (Array.isArray(data[i])) {
214
+ [x, y] = data[i];
215
+ } else if (typeof data[i] === 'object' && data[i] !== null) {
216
+ x = data[i][singleSeries.xKey];
217
+ y = data[i][singleSeries.yKey];
218
+ } else {
219
+ continue;
220
+ }
221
+
222
+ if (y === null || y === undefined) {
223
+ continue;
224
+ }
225
+
226
+ let xValue = x instanceof Date ? x.getTime() : x;
227
+
228
+ if (xValue < boundsMinX) {
229
+ if (includeBeyondBounds) {
230
+ lastPointBeforeBounds = [xValue, y];
231
+ }
170
232
  continue;
171
233
  }
234
+
235
+ if (xValue > boundsMaxX) {
236
+ if (includeBeyondBounds && !foundBeyondBounds) {
237
+ foundBeyondBounds = true;
238
+ } else {
239
+ break;
240
+ }
241
+ }
172
242
 
173
- individualPoints.push([
174
- (x - bounds.minX) / (bounds.maxX - bounds.minX) * this._sizing.renderWidth,
175
- (1.0 - (y - bounds.minY) / (bounds.maxY - bounds.minY)) * this._sizing.renderHeight
176
- ]);
243
+ const renderWidth = this._sizing.renderWidth / DPI_INCREASE;
244
+ const xCoord = (xValue - boundsMinX) / (boundsMaxX - boundsMinX) * (renderWidth - 1) * DPI_INCREASE;
245
+ const yCoord = (1.0 - (y - bounds.minY) / (bounds.maxY - bounds.minY)) * this._sizing.renderHeight;
246
+
247
+ individualPoints.push([xCoord, yCoord]);
248
+ }
249
+
250
+ if (lastPointBeforeBounds && includeBeyondBounds) {
251
+ const [beforeXValue, beforeY] = lastPointBeforeBounds;
252
+ const renderWidth = this._sizing.renderWidth / DPI_INCREASE;
253
+ const beforeXCoord = (beforeXValue - boundsMinX) / (boundsMaxX - boundsMinX) * (renderWidth - 1) * DPI_INCREASE;
254
+ const beforeYCoord = (1.0 - (beforeY - bounds.minY) / (bounds.maxY - bounds.minY)) * this._sizing.renderHeight;
255
+ individualPoints.unshift([beforeXCoord, beforeYCoord]);
177
256
  }
178
257
 
179
258
  return individualPoints;
@@ -224,7 +303,6 @@ export default class GraphBodyRenderer extends Eventable {
224
303
  return;
225
304
  }
226
305
 
227
- //we still need a canvas context for cpu stuff
228
306
  if (!this._context2d) {
229
307
  this._context2d = this._canvas.getContext('2d', { willReadFrequently: false });
230
308
  }
@@ -235,7 +313,6 @@ export default class GraphBodyRenderer extends Eventable {
235
313
  }
236
314
 
237
315
  if (this._webgl) {
238
- // make sure we don't have any webgl stuff in the way before we get back to CPU rendering
239
316
  this._context.flush();
240
317
  }
241
318
 
@@ -275,7 +352,7 @@ export default class GraphBodyRenderer extends Eventable {
275
352
  if (singleSeries.cutoffTime) {
276
353
  barParams.cutoffIndex = cutoffIndex;
277
354
  barParams.cutoffOpacity = 0.35;
278
- barParams.originalData = singleSeries.data;
355
+ barParams.originalData = cutoffData;
279
356
  barParams.renderCutoffGradient = cutoffIndex >= 0;
280
357
 
281
358
  const selection = this === this._stateController.rangeGraphRenderer
@@ -301,11 +378,10 @@ export default class GraphBodyRenderer extends Eventable {
301
378
  inRenderSpaceAreaBottom
302
379
  };
303
380
 
304
- // add cutoff information for gradient area rendering
305
381
  if (singleSeries.cutoffTime) {
306
382
  areaParams.cutoffIndex = cutoffIndex;
307
383
  areaParams.cutoffOpacity = 0.35;
308
- areaParams.originalData = singleSeries.data;
384
+ areaParams.originalData = cutoffData;
309
385
  areaParams.renderCutoffGradient = cutoffIndex >= 0;
310
386
  areaParams.isPreview = this === this._stateController.rangeGraphRenderer;
311
387
 
@@ -344,7 +420,9 @@ export default class GraphBodyRenderer extends Eventable {
344
420
 
345
421
  let zero = singleSeries.zeroLineY === 'bottom' ?
346
422
  this._sizing.renderHeight :
347
- (1.0 - ((singleSeries.zeroLineY || 0) - bounds.minY) / (bounds.maxY - bounds.minY)) * this._sizing.renderHeight;
423
+ singleSeries.zeroLineY !== undefined ?
424
+ (1.0 - ((singleSeries.zeroLineY) - bounds.minY) / (bounds.maxY - bounds.minY)) * this._sizing.renderHeight :
425
+ this._sizing.renderHeight;
348
426
 
349
427
  const boundsChanged = !this._lastBounds ||
350
428
  bounds.minY !== this._lastBounds.minY ||
@@ -372,21 +450,20 @@ export default class GraphBodyRenderer extends Eventable {
372
450
  inRenderSpaceAreaBottom
373
451
  };
374
452
 
375
- // add cutoff information for gradient shadow rendering
376
453
  if (singleSeries.cutoffTime) {
377
454
  shadowParams.cutoffIndex = cutoffIndex;
378
455
  shadowParams.cutoffOpacity = 0.35;
379
- shadowParams.originalData = singleSeries.data;
456
+ shadowParams.originalData = cutoffData;
380
457
  shadowParams.renderCutoffGradient = cutoffIndex >= 0;
381
458
  shadowParams.isPreview = this === this._stateController.rangeGraphRenderer;
382
459
 
383
460
  const selection = this === this._stateController.rangeGraphRenderer
384
461
  ? this._stateController._bounds
385
462
  : (this._stateController._selection || this._stateController._bounds);
386
- shadowParams.selectionBounds = selection;
463
+ shadowParams.selectionBounds = selection || bounds;
387
464
  }
388
465
 
389
- this._shadowProgram.draw(getIndividualPoints(true), shadowParams);
466
+ this._shadowProgram.draw(getIndividualPoints(false, true), shadowParams);
390
467
 
391
468
  if (this._webgl) {
392
469
  const gl = this._context;
@@ -395,10 +472,8 @@ export default class GraphBodyRenderer extends Eventable {
395
472
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
396
473
  }
397
474
 
398
-
399
475
  if (singleSeries.zeroLineWidth && singleSeries.zeroLineWidth > 0) {
400
476
  if (this._context2d) {
401
- // in non-webgl mode, use the existing 2d context
402
477
  this._context2d.save();
403
478
  this._context2d.strokeStyle = singleSeries.zeroLineColor || getColor(singleSeries.color, singleSeries.index, singleSeries.multigrapherSeriesIndex);
404
479
  this._context2d.lineWidth = singleSeries.zeroLineWidth;
@@ -410,7 +485,6 @@ export default class GraphBodyRenderer extends Eventable {
410
485
  this._context2d.stroke();
411
486
  this._context2d.restore();
412
487
  } else {
413
- // in webgl mode, we instead create an overlay 2d canvas for the zero line
414
488
  if (!this._zeroLineCanvas) {
415
489
  this._zeroLineCanvas = document.createElement('canvas');
416
490
  this._zeroLineCanvas.style.position = 'absolute';
@@ -460,20 +534,18 @@ export default class GraphBodyRenderer extends Eventable {
460
534
  return;
461
535
  }
462
536
 
463
- // Add cutoff information to drawParams for gradient line rendering
464
537
  if (singleSeries.cutoffTime) {
465
538
  drawParams.cutoffIndex = cutoffIndex;
466
539
  drawParams.cutoffOpacity = 0.35;
467
- drawParams.originalData = singleSeries.data;
468
- drawParams.renderCutoffGradient = cutoffIndex >= 0; // Only render cutoff if valid cutoff
540
+ drawParams.originalData = cutoffData;
541
+ drawParams.renderCutoffGradient = cutoffIndex >= 0;
469
542
  drawParams.currentBounds = bounds;
470
- drawParams.isPreview = this === this._stateController.rangeGraphRenderer; // Flag for preview rendering
543
+ drawParams.isPreview = this === this._stateController.rangeGraphRenderer;
471
544
 
472
- // Always set selectionBounds with fallback
473
545
  const selection = this === this._stateController.rangeGraphRenderer
474
546
  ? this._stateController._bounds
475
547
  : (this._stateController._selection || this._stateController._bounds);
476
- drawParams.selectionBounds = selection;
548
+ drawParams.selectionBounds = selection || bounds;
477
549
  }
478
550
 
479
551
  if (this._webgl) {
@@ -108,7 +108,7 @@ export default class LineProgram {
108
108
  gl.uniform4f(gl.getUniformLocation(this._program, 'shadowColor'), ...colorToVector(shadowColor));
109
109
 
110
110
  const cutoffX = parameters.cutoffX !== undefined ? parameters.cutoffX : -1.0; // Use parameter or disable
111
- const cutoffOpacity = parameters.cutoffOpacity !== undefined ? parameters.cutoffOpacity : 1.0;
111
+ const cutoffOpacity = parameters.cutoffOpacity !== undefined ? parameters.cutoffOpacity : 0.35;
112
112
 
113
113
  gl.uniform1f(gl.getUniformLocation(this._program, 'cutoffX'), cutoffX);
114
114
  gl.uniform1f(gl.getUniformLocation(this._program, 'cutoffOpacity'), cutoffOpacity);
@@ -260,7 +260,7 @@ export default class LineProgram {
260
260
  if (timeRatio < 0) {
261
261
  this.draw(dataInRenderSpace, { ...parameters, renderCutoffGradient: false });
262
262
  } else if (timeRatio > 1) {
263
- const reducedOpacityColor = this.applyReducedOpacity(parameters.color, cutoffOpacity);
263
+ const reducedOpacityColor = applyReducedOpacity(parameters.color, cutoffOpacity);
264
264
  this.draw(dataInRenderSpace, {
265
265
  ...parameters,
266
266
  color: reducedOpacityColor,
@@ -290,7 +290,7 @@ export default class LineProgram {
290
290
  if (cutoffTime < visibleMinTime) {
291
291
  this.draw(dataInRenderSpace, { ...parameters, renderCutoffGradient: false });
292
292
  } else if (cutoffTime > visibleMaxTime) {
293
- const reducedOpacityColor = this.applyReducedOpacity(parameters.color, cutoffOpacity);
293
+ const reducedOpacityColor = applyReducedOpacity(parameters.color, cutoffOpacity);
294
294
  this.draw(dataInRenderSpace, {
295
295
  ...parameters,
296
296
  color: reducedOpacityColor,
@@ -189,6 +189,7 @@ export default class ShadowProgram {
189
189
  * @param {Object} params - Rendering parameters
190
190
  */
191
191
  draw(individualPoints, params) {
192
+
192
193
  if (!individualPoints || individualPoints.length < 2) {
193
194
  return;
194
195
  }
@@ -211,6 +212,7 @@ export default class ShadowProgram {
211
212
  const trapezoids = [];
212
213
  const { zero, inRenderSpaceAreaBottom } = params;
213
214
 
215
+
214
216
  for (let i = 0; i < individualPoints.length - 1; i++) {
215
217
  const [x1, y1] = individualPoints[i];
216
218
  const [x2, y2] = individualPoints[i + 1];
@@ -251,17 +253,35 @@ export default class ShadowProgram {
251
253
  });
252
254
  }
253
255
  } else {
254
- const trapezoid = { x1, y1, x2, y2, bottomY1, bottomY2 };
256
+ // Skip trapezoids completely outside canvas
257
+ if (x1 > width || x2 < 0) {
258
+ continue;
259
+ }
260
+
261
+ // Clip trapezoid to canvas bounds if it extends beyond
262
+ let finalX2 = x2;
263
+ let finalY2 = y2;
264
+ let finalBottomY2 = bottomY2;
265
+
266
+ if (x2 > width) {
267
+ const ratio = (width - x1) / (x2 - x1);
268
+ finalX2 = width;
269
+ finalY2 = y1 + (y2 - y1) * ratio;
270
+ finalBottomY2 = bottomY1 + (bottomY2 - bottomY1) * ratio;
271
+ }
272
+
273
+ const trapezoid = { x1, y1, x2: finalX2, y2: finalY2, bottomY1, bottomY2: finalBottomY2 };
255
274
  trapezoids.push(trapezoid);
256
275
  }
257
276
  }
277
+
278
+
258
279
 
259
280
  if (trapezoids.length === 0) {
260
281
  return;
261
282
  }
262
283
 
263
284
  const geometry = this.generateTrapezoidGeometry(trapezoids);
264
-
265
285
  const positionLoc = gl.getAttribLocation(this._program, "position");
266
286
  const trapezoidBoundsLoc = gl.getAttribLocation(
267
287
  this._program,
@@ -329,6 +349,13 @@ export default class ShadowProgram {
329
349
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, geometry.indices, gl.STATIC_DRAW);
330
350
 
331
351
  gl.drawElements(gl.TRIANGLES, geometry.indices.length, gl.UNSIGNED_INT, 0);
352
+
353
+ const error = gl.getError();
354
+ if (error !== gl.NO_ERROR) {
355
+ console.error('WebGL error in shadow rendering:', error);
356
+ } else {
357
+ //he he he haw
358
+ }
332
359
  }
333
360
 
334
361
  /**
@@ -337,45 +364,56 @@ export default class ShadowProgram {
337
364
  * @param {Object} params - Rendering parameters with cutoff info
338
365
  */
339
366
  drawShadowWithCutoff(individualPoints, params) {
367
+
340
368
  const { cutoffIndex, cutoffOpacity, originalData, selectionBounds, zero } =
341
369
  params;
342
370
 
343
371
  this._lastIndividualPoints = null;
344
372
  this._lastParams = null;
345
373
 
374
+ // All cutoff data is now in tuple format [x, y] from graph_body_renderer
346
375
  let cutoffTime;
347
- if (typeof originalData[0] === "object" && originalData[0].length === 2) {
376
+
377
+ if (Array.isArray(originalData[0]) && originalData[0].length === 2) {
348
378
  const baseIndex = Math.floor(cutoffIndex);
349
379
  const fraction = cutoffIndex - baseIndex;
350
380
 
351
381
  if (fraction === 0 || baseIndex >= originalData.length - 1) {
352
- const cutoffDate =
353
- originalData[Math.min(baseIndex, originalData.length - 1)][0];
354
- cutoffTime =
355
- cutoffDate instanceof Date ? cutoffDate.getTime() : cutoffDate;
382
+ const cutoffItem = originalData[Math.min(baseIndex, originalData.length - 1)];
383
+ const cutoffDate = cutoffItem[0];
384
+ cutoffTime = cutoffDate instanceof Date ? cutoffDate.getTime() : cutoffDate;
356
385
  } else {
357
- const currentDate = originalData[baseIndex][0];
358
- const nextDate = originalData[baseIndex + 1][0];
359
- const currentTime =
360
- currentDate instanceof Date ? currentDate.getTime() : currentDate;
361
- const nextTime =
362
- nextDate instanceof Date ? nextDate.getTime() : nextDate;
386
+ const currentItem = originalData[baseIndex];
387
+ const nextItem = originalData[baseIndex + 1];
388
+ const currentDate = currentItem[0];
389
+ const nextDate = nextItem[0];
390
+ const currentTime = currentDate instanceof Date ? currentDate.getTime() : currentDate;
391
+ const nextTime = nextDate instanceof Date ? nextDate.getTime() : nextDate;
363
392
  cutoffTime = currentTime + fraction * (nextTime - currentTime);
364
393
  }
365
394
  } else {
366
395
  cutoffTime = cutoffIndex;
367
396
  }
368
397
 
369
- const firstTime =
370
- originalData[0][0] instanceof Date
371
- ? originalData[0][0].getTime()
372
- : originalData[0][0];
373
- const lastTime =
374
- originalData[originalData.length - 1][0] instanceof Date
375
- ? originalData[originalData.length - 1][0].getTime()
376
- : originalData[originalData.length - 1][0];
377
-
378
- const timeRatio = (cutoffTime - firstTime) / (lastTime - firstTime);
398
+ const visibleBounds = params.selectionBounds;
399
+ let firstTime, lastTime;
400
+
401
+ if (visibleBounds && visibleBounds.minX !== undefined && visibleBounds.maxX !== undefined) {
402
+ firstTime = visibleBounds.minX instanceof Date ? visibleBounds.minX.getTime() : visibleBounds.minX;
403
+ lastTime = visibleBounds.maxX instanceof Date ? visibleBounds.maxX.getTime() : visibleBounds.maxX;
404
+ } else {
405
+ const firstItem = originalData[0];
406
+ const lastItem = originalData[originalData.length - 1];
407
+ const firstX = firstItem[0];
408
+ const lastX = lastItem[0];
409
+
410
+ firstTime = firstX instanceof Date ? firstX.getTime() : firstX;
411
+ lastTime = lastX instanceof Date ? lastX.getTime() : lastX;
412
+ }
413
+
414
+ const timeDiff = cutoffTime - firstTime;
415
+ const totalTime = lastTime - firstTime;
416
+ const timeRatio = timeDiff / totalTime;
379
417
 
380
418
  if (timeRatio < 0) {
381
419
  this.draw(individualPoints, { ...params, renderCutoffGradient: false });
@@ -392,7 +430,7 @@ export default class ShadowProgram {
392
430
  } else {
393
431
  this.drawSplitShadowTrapezoids(
394
432
  individualPoints,
395
- params,
433
+ { ...params, selectionBounds: params.selectionBounds },
396
434
  timeRatio,
397
435
  cutoffTime
398
436
  );
@@ -407,41 +445,41 @@ export default class ShadowProgram {
407
445
  * @param {number} cutoffTime - Cutoff timestamp
408
446
  */
409
447
  drawSplitShadowTrapezoids(individualPoints, params, timeRatio, cutoffTime) {
410
- const { zero, cutoffOpacity } = params;
448
+ const { zero, cutoffOpacity, selectionBounds } = params;
411
449
  const gl = this._gl;
412
450
 
413
- const renderCutoffIndex = timeRatio * (individualPoints.length - 1);
414
- const splitIndex = Math.floor(renderCutoffIndex);
415
- const fraction = renderCutoffIndex - splitIndex;
416
-
417
- let ghostPoint = null;
418
- if (
419
- splitIndex >= 0 &&
420
- splitIndex < individualPoints.length - 1 &&
421
- fraction > 0
422
- ) {
423
- const beforePoint = individualPoints[splitIndex];
424
- const afterPoint = individualPoints[splitIndex + 1];
425
- ghostPoint = [
426
- beforePoint[0] + fraction * (afterPoint[0] - beforePoint[0]),
427
- beforePoint[1] + fraction * (afterPoint[1] - beforePoint[1]),
428
- ];
429
- }
451
+ const renderWidth = gl.canvas.width;
452
+ const cutoffPixelX = timeRatio * renderWidth;
430
453
 
431
454
  const preCutoffPoints = [];
432
- for (let i = 0; i <= splitIndex && i < individualPoints.length; i++) {
433
- preCutoffPoints.push(individualPoints[i]);
434
- }
435
- if (ghostPoint && splitIndex < individualPoints.length - 1) {
436
- preCutoffPoints.push(ghostPoint);
437
- }
438
-
439
455
  const postCutoffPoints = [];
440
- if (ghostPoint && splitIndex < individualPoints.length - 1) {
441
- postCutoffPoints.push(ghostPoint);
456
+
457
+ for (let i = 0; i < individualPoints.length; i++) {
458
+ const [pixelX, pixelY] = individualPoints[i];
459
+
460
+ if (pixelX < cutoffPixelX) {
461
+ preCutoffPoints.push(individualPoints[i]);
462
+ } else {
463
+ postCutoffPoints.push(individualPoints[i]);
464
+ }
442
465
  }
443
- for (let i = splitIndex + 1; i < individualPoints.length; i++) {
444
- postCutoffPoints.push(individualPoints[i]);
466
+
467
+ let ghostPoint = null;
468
+ if (preCutoffPoints.length > 0 && postCutoffPoints.length > 0) {
469
+ const lastPrePoint = preCutoffPoints[preCutoffPoints.length - 1];
470
+ const firstPostPoint = postCutoffPoints[0];
471
+
472
+ const [x1, y1] = lastPrePoint;
473
+ const [x2, y2] = firstPostPoint;
474
+
475
+ if (x2 !== x1) {
476
+ const interpolationRatio = (cutoffPixelX - x1) / (x2 - x1);
477
+ const ghostY = y1 + interpolationRatio * (y2 - y1);
478
+ ghostPoint = [cutoffPixelX, ghostY];
479
+
480
+ preCutoffPoints.push(ghostPoint);
481
+ postCutoffPoints.unshift(ghostPoint);
482
+ }
445
483
  }
446
484
 
447
485
  if (preCutoffPoints.length >= 2) {
@@ -467,6 +505,7 @@ export default class ShadowProgram {
467
505
  gradient: translucentGradient,
468
506
  renderCutoffGradient: false,
469
507
  });
508
+ } else {
470
509
  }
471
510
 
472
511
  if (postCutoffPoints.length >= 2) {
@@ -474,6 +513,7 @@ export default class ShadowProgram {
474
513
  ...params,
475
514
  renderCutoffGradient: false,
476
515
  });
516
+ } else {
477
517
  }
478
518
  }
479
519
  }