@windborne/grapher 1.0.29 → 1.0.30

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.29",
3
+ "version": "1.0.30",
4
4
  "description": "Graphing library",
5
5
  "main": "src/index.js",
6
6
  "module": "dist/bundle.esm.js",
package/readme.md CHANGED
@@ -214,7 +214,7 @@ Grapher supports multiple data formats within a series:
214
214
 
215
215
  | Prop | Type | Required | Description |
216
216
  |------|------|----------|-------------|
217
- | **x** | `number` | **✓** | X-coordinate position where the line should appear. |
217
+ | **x** | `number \| Date` | **✓** | X-coordinate position where the line should appear. Can be a numeric value or a Date object. |
218
218
  | color | `string` | ✗ | Optional line color. |
219
219
  | lineTop | `number` | ✗ | Optional value to specify the top position of the line. |
220
220
  | width | `number` | ✗ | Optional line width. |
@@ -91,3 +91,15 @@ export function applyReducedOpacityToGradient(gradient, opacityFactor) {
91
91
  return stop;
92
92
  });
93
93
  }
94
+
95
+ /**
96
+ * Creates a default gradient from a color at half opacity
97
+ * @param {string} color
98
+ * @returns {Array<[number, string]>}
99
+ */
100
+ export function createDefaultGradient(color) {
101
+ if (!color) return [[0, 'transparent'], [1, 'transparent']];
102
+
103
+ const halfOpacityColor = applyReducedOpacity(color, 0.5);
104
+ return [[0, halfOpacityColor], [1, halfOpacityColor]];
105
+ }
@@ -65,6 +65,7 @@ const SingleSeries = PropTypes.shape({
65
65
  background: PropTypes.object,
66
66
  hideFromKey: PropTypes.bool,
67
67
  showIndividualPoints: PropTypes.bool,
68
+ minPointSpacing: PropTypes.number,
68
69
  rendering: PropTypes.oneOf(['line', 'bar', 'area', 'shadow']), // defaults to line
69
70
  negativeColor: PropTypes.string, // only applies to bar
70
71
  gradient: PropTypes.array, // only applies to area
@@ -139,7 +140,7 @@ const DraggablePoint = PropTypes.shape({
139
140
  const DraggablePoints = PropTypes.arrayOf(DraggablePoint);
140
141
 
141
142
  const VerticalLine = PropTypes.shape({
142
- x: PropTypes.number.isRequired,
143
+ x: PropTypes.oneOfType([PropTypes.number, PropTypes.instanceOf(Date)]).isRequired,
143
144
  color: PropTypes.string,
144
145
  lineTop: PropTypes.number,
145
146
  width: PropTypes.number,
package/src/index.d.ts CHANGED
@@ -24,6 +24,7 @@ export interface SeriesData {
24
24
  background?: object;
25
25
  hideFromKey?: boolean;
26
26
  showIndividualPoints?: boolean;
27
+ minPointSpacing?: number;
27
28
  rendering?: 'line' | 'bar' | 'area' | 'shadow';
28
29
  negativeColor?: string;
29
30
  zeroLineWidth?: number;
@@ -80,7 +81,7 @@ export interface DraggablePoint {
80
81
  }
81
82
 
82
83
  export interface VerticalLine {
83
- x: number;
84
+ x: number | Date;
84
85
  color?: string;
85
86
  lineTop?: number;
86
87
  width?: number;
@@ -220,7 +220,7 @@ export default function drawArea(
220
220
  });
221
221
  }
222
222
 
223
- if (showIndividualPoints) {
223
+ if (showIndividualPoints && !renderCutoffGradient) {
224
224
  context.fillStyle = color;
225
225
 
226
226
  for (let [x, y] of individualPoints) {
@@ -312,14 +312,15 @@ function drawAreaWithCutoff(
312
312
  cutoffTime = cutoffIndex;
313
313
  }
314
314
 
315
- const firstTime =
316
- originalData[0][0] instanceof Date
317
- ? originalData[0][0].getTime()
318
- : originalData[0][0];
319
- const lastTime =
320
- originalData[originalData.length - 1][0] instanceof Date
321
- ? originalData[originalData.length - 1][0].getTime()
322
- : originalData[originalData.length - 1][0];
315
+ let firstTime, lastTime;
316
+ if (isPreview && selectionBounds) {
317
+ firstTime = selectionBounds.minX instanceof Date ? selectionBounds.minX.getTime() : selectionBounds.minX;
318
+ lastTime = selectionBounds.maxX instanceof Date ? selectionBounds.maxX.getTime() : selectionBounds.maxX;
319
+ } else {
320
+ firstTime = originalData[0][0] instanceof Date ? originalData[0][0].getTime() : originalData[0][0];
321
+ lastTime = originalData[originalData.length - 1][0] instanceof Date ?
322
+ originalData[originalData.length - 1][0].getTime() : originalData[originalData.length - 1][0];
323
+ }
323
324
 
324
325
  const timeRatio = (cutoffTime - firstTime) / (lastTime - firstTime);
325
326
 
@@ -657,7 +658,7 @@ function drawAreaWithCutoff(
657
658
  }
658
659
 
659
660
  // draw outline lines with cutoff
660
- if (timeRatio >= 0 && timeRatio <= 1) {
661
+ if (timeRatio >= 0 && timeRatio <= 1 && !isPreview) {
661
662
  if (selectionBounds) {
662
663
  const visibleMinTime =
663
664
  selectionBounds.minX instanceof Date
@@ -719,7 +720,7 @@ function drawAreaWithCutoff(
719
720
  highlighted,
720
721
  });
721
722
  }
722
- } else {
723
+ } else if (!isPreview) {
723
724
  // draw lines normally without cutoff
724
725
  drawLinesNormally(linePaths, {
725
726
  color,
@@ -744,7 +745,7 @@ function drawAreaWithCutoff(
744
745
  });
745
746
  }
746
747
 
747
- if (showIndividualPoints) {
748
+ if (showIndividualPoints && !isPreview) {
748
749
  if (timeRatio >= 0 && timeRatio <= 1) {
749
750
  if (selectionBounds) {
750
751
  const visibleMinTime =
@@ -1072,72 +1073,49 @@ function drawLinesWithCutoff(
1072
1073
  }
1073
1074
  width *= DPI_INCREASE;
1074
1075
 
1075
- let totalLength = 0;
1076
- const pathLengths = [];
1077
-
1078
- for (let path of linePaths) {
1079
- if (path.length > 0) {
1080
- pathLengths.push(path.length);
1081
- totalLength += path.length;
1082
- } else {
1083
- pathLengths.push(0);
1084
- }
1085
- }
1086
-
1087
- if (totalLength === 0) return;
1088
-
1089
- const globalCutoffIndex = timeRatio * totalLength;
1090
-
1091
- let currentIndex = 0;
1092
- let targetPathIndex = -1;
1093
- let cutoffIndexInPath = 0;
1094
-
1095
- for (let i = 0; i < pathLengths.length; i++) {
1096
- if (pathLengths[i] === 0) continue;
1097
-
1098
- if (globalCutoffIndex <= currentIndex + pathLengths[i]) {
1099
- targetPathIndex = i;
1100
- cutoffIndexInPath = globalCutoffIndex - currentIndex;
1101
- break;
1102
- }
1103
- currentIndex += pathLengths[i];
1104
- }
1105
-
1076
+ const cutoffPixelX = timeRatio * context.canvas.width;
1106
1077
  const preCutoffPaths = [];
1107
1078
  const postCutoffPaths = [];
1108
- let ghostPoint = null;
1109
1079
 
1110
- for (let i = 0; i < linePaths.length; i++) {
1111
- const path = linePaths[i];
1080
+ for (let path of linePaths) {
1112
1081
  if (!path.length) continue;
1113
1082
 
1114
- if (i < targetPathIndex) {
1115
- preCutoffPaths.push(path);
1116
- } else if (i > targetPathIndex) {
1117
- postCutoffPaths.push(path);
1118
- } else if (i === targetPathIndex) {
1119
- const splitIndex = Math.floor(cutoffIndexInPath);
1120
- const fraction = cutoffIndexInPath - splitIndex;
1121
-
1122
- if (splitIndex < path.length - 1 && fraction > 0) {
1123
- const [x1, y1] = path[splitIndex];
1124
- const [x2, y2] = path[splitIndex + 1];
1125
- ghostPoint = [x1 + fraction * (x2 - x1), y1 + fraction * (y2 - y1)];
1126
- }
1083
+ const prePath = [];
1084
+ const postPath = [];
1085
+ let ghostPoint = null;
1086
+ let splitIndex = -1;
1127
1087
 
1128
- if (splitIndex > 0) {
1129
- const prePath = path.slice(0, splitIndex + 1);
1130
- if (ghostPoint) prePath.push(ghostPoint);
1131
- preCutoffPaths.push(prePath);
1088
+ for (let i = 0; i < path.length; i++) {
1089
+ const [x, y] = path[i];
1090
+
1091
+ if (x < cutoffPixelX) {
1092
+ prePath.push([x, y]);
1093
+ splitIndex = i;
1094
+ } else {
1095
+ postPath.push([x, y]);
1132
1096
  }
1097
+ }
1133
1098
 
1134
- if (splitIndex < path.length - 1) {
1135
- const postPath = [];
1136
- if (ghostPoint) postPath.push(ghostPoint);
1137
- postPath.push(...path.slice(splitIndex + 1));
1138
- postCutoffPaths.push(postPath);
1099
+ if (prePath.length > 0 && postPath.length > 0) {
1100
+ const lastPrePoint = prePath[prePath.length - 1];
1101
+ const firstPostPoint = postPath[0];
1102
+ const [x1, y1] = lastPrePoint;
1103
+ const [x2, y2] = firstPostPoint;
1104
+
1105
+ if (x2 !== x1) {
1106
+ const fraction = (cutoffPixelX - x1) / (x2 - x1);
1107
+ ghostPoint = [cutoffPixelX, y1 + fraction * (y2 - y1)];
1108
+ prePath.push(ghostPoint);
1109
+ postPath.unshift(ghostPoint);
1139
1110
  }
1140
1111
  }
1112
+
1113
+ if (prePath.length > 0) {
1114
+ preCutoffPaths.push(prePath);
1115
+ }
1116
+ if (postPath.length > 0) {
1117
+ postCutoffPaths.push(postPath);
1118
+ }
1141
1119
  }
1142
1120
 
1143
1121
  if (preCutoffPaths.length > 0) {
@@ -1200,12 +1178,13 @@ function drawPointsWithCutoffByRatio(
1200
1178
  return;
1201
1179
  }
1202
1180
 
1203
- const cutoffPixelIndex = timeRatio * individualPoints.length;
1181
+ const canvasWidth = context.canvas.width;
1182
+ const cutoffPixelX = timeRatio * canvasWidth;
1204
1183
 
1205
1184
  for (let i = 0; i < individualPoints.length; i++) {
1206
1185
  const [x, y] = individualPoints[i];
1207
1186
 
1208
- const isBeforeCutoff = i < cutoffPixelIndex;
1187
+ const isBeforeCutoff = x < cutoffPixelX;
1209
1188
 
1210
1189
  let pointColor = color;
1211
1190
  if (isBeforeCutoff) {
@@ -3,7 +3,7 @@ import pathsFrom from './paths_from';
3
3
  import { applyReducedOpacity } from "../helpers/colors";
4
4
 
5
5
  export default function drawLine(dataInRenderSpace, {
6
- color, width=1, context, shadowColor='black', shadowBlur=5, dashed=false, dashPattern=null, highlighted=false, showIndividualPoints=false, getIndividualPoints, getRanges, cutoffIndex, cutoffOpacity, originalData, renderCutoffGradient, currentBounds, selectionBounds, rendering, isPreview
6
+ color, width=1, context, shadowColor='black', shadowBlur=5, dashed=false, dashPattern=null, highlighted=false, showIndividualPoints=false, pointRadius, getIndividualPoints, getRanges, cutoffIndex, cutoffOpacity, originalData, renderCutoffGradient, currentBounds, selectionBounds, rendering, isPreview
7
7
  }) {
8
8
  if (!context) {
9
9
  console.error("Canvas context is null in drawLine");
@@ -340,16 +340,22 @@ export default function drawLine(dataInRenderSpace, {
340
340
  }
341
341
 
342
342
  if (isPreview) {
343
- const firstTime = originalData[0][0] instanceof Date ? originalData[0][0].getTime() : originalData[0][0];
344
- const lastTime = originalData[originalData.length - 1][0] instanceof Date ?
345
- originalData[originalData.length - 1][0].getTime() : originalData[originalData.length - 1][0];
346
- const timeRatio = (cutoffTime - firstTime) / (lastTime - firstTime);
343
+ const visibleMinTime = selectionBounds.minX instanceof Date ? selectionBounds.minX.getTime() : selectionBounds.minX;
344
+ const visibleMaxTime = selectionBounds.maxX instanceof Date ? selectionBounds.maxX.getTime() : selectionBounds.maxX;
347
345
 
348
346
  for (let i = 0; i < individualPoints.length; i++) {
349
347
  const [x, y] = individualPoints[i];
350
348
 
351
- const pointRatio = i / (individualPoints.length - 1);
352
- const isBeforeCutoff = pointRatio < timeRatio;
349
+ let isBeforeCutoff = false;
350
+ if (cutoffTime < visibleMinTime) {
351
+ isBeforeCutoff = false;
352
+ } else if (cutoffTime > visibleMaxTime) {
353
+ isBeforeCutoff = (rendering !== 'shadow');
354
+ } else {
355
+ const visibleCutoffRatio = (cutoffTime - visibleMinTime) / (visibleMaxTime - visibleMinTime);
356
+ const cutoffPixelX = visibleCutoffRatio * context.canvas.width;
357
+ isBeforeCutoff = x < cutoffPixelX;
358
+ }
353
359
 
354
360
  if (isBeforeCutoff) {
355
361
  const reducedOpacityColor = applyReducedOpacity(color, cutoffOpacity);
@@ -359,7 +365,7 @@ export default function drawLine(dataInRenderSpace, {
359
365
  }
360
366
 
361
367
  context.beginPath();
362
- context.arc(x, y, width + 4, 0, 2 * Math.PI, false);
368
+ context.arc(x, y, pointRadius || 8, 0, 2 * Math.PI, false);
363
369
  context.fill();
364
370
  }
365
371
  } else if (!selectionBounds) {
@@ -367,7 +373,7 @@ export default function drawLine(dataInRenderSpace, {
367
373
  const [x, y] = individualPoints[i];
368
374
  context.fillStyle = color;
369
375
  context.beginPath();
370
- context.arc(x, y, width + 4, 0, 2 * Math.PI, false);
376
+ context.arc(x, y, pointRadius || 8, 0, 2 * Math.PI, false);
371
377
  context.fill();
372
378
  }
373
379
  } else {
@@ -396,7 +402,7 @@ export default function drawLine(dataInRenderSpace, {
396
402
  }
397
403
 
398
404
  context.beginPath();
399
- context.arc(x, y, width + 4, 0, 2 * Math.PI, false);
405
+ context.arc(x, y, pointRadius || 8, 0, 2 * Math.PI, false);
400
406
  context.fill();
401
407
  }
402
408
  }
@@ -405,7 +411,7 @@ export default function drawLine(dataInRenderSpace, {
405
411
  const [x, y] = individualPoints[i];
406
412
  context.fillStyle = color;
407
413
  context.beginPath();
408
- context.arc(x, y, width + 4, 0, 2 * Math.PI, false);
414
+ context.arc(x, y, pointRadius || 8, 0, 2 * Math.PI, false);
409
415
  context.fill();
410
416
  }
411
417
  }
@@ -1,5 +1,5 @@
1
1
  import Eventable from '../eventable';
2
- import getColor from '../helpers/colors';
2
+ import getColor, { createDefaultGradient } from '../helpers/colors';
3
3
  import inferType from '../state/infer_type';
4
4
  import BackgroundProgram from './background_program.js';
5
5
  import drawArea from './draw_area';
@@ -161,7 +161,7 @@ export default class GraphBodyRenderer extends Eventable {
161
161
  }
162
162
 
163
163
  const getIndividualPoints = (useDataSpace, includeBeyondBounds = false) => {
164
- if (!useDataSpace && inRenderSpace && inRenderSpace.yValues) {
164
+ if (!useDataSpace && inRenderSpace && inRenderSpace.yValues && !showIndividualPoints) {
165
165
  if (!bounds) {
166
166
  bounds = singleSeries.axis.currentBounds;
167
167
  }
@@ -188,6 +188,21 @@ export default class GraphBodyRenderer extends Eventable {
188
188
  return getIndividualPoints(true, includeBeyondBounds);
189
189
  }
190
190
 
191
+ // Apply minPointSpacing if specified
192
+ if (singleSeries.minPointSpacing && individualPoints.length > 1) {
193
+ const spacedPoints = [];
194
+ let lastX = -Infinity;
195
+
196
+ for (const [x, y] of individualPoints) {
197
+ if (x - lastX >= singleSeries.minPointSpacing) {
198
+ spacedPoints.push([x, y]);
199
+ lastX = x;
200
+ }
201
+ }
202
+
203
+ return spacedPoints;
204
+ }
205
+
191
206
  return individualPoints;
192
207
  }
193
208
 
@@ -255,6 +270,19 @@ export default class GraphBodyRenderer extends Eventable {
255
270
  individualPoints.unshift([beforeXCoord, beforeYCoord]);
256
271
  }
257
272
 
273
+ if (singleSeries.minPointSpacing && individualPoints.length > 1) {
274
+ const spacedPoints = [];
275
+ let lastX = -Infinity;
276
+
277
+ for (const [x, y] of individualPoints) {
278
+ if (x - lastX >= singleSeries.minPointSpacing) {
279
+ spacedPoints.push([x, y]);
280
+ lastX = x;
281
+ }
282
+ }
283
+
284
+ return spacedPoints;
285
+ }
258
286
  return individualPoints;
259
287
  };
260
288
 
@@ -351,7 +379,7 @@ export default class GraphBodyRenderer extends Eventable {
351
379
 
352
380
  if (singleSeries.cutoffTime) {
353
381
  barParams.cutoffIndex = cutoffIndex;
354
- barParams.cutoffOpacity = 0.35;
382
+ barParams.cutoffOpacity = singleSeries.cutoffOpacity !== undefined ? singleSeries.cutoffOpacity : 0.35;
355
383
  barParams.originalData = cutoffData;
356
384
  barParams.renderCutoffGradient = cutoffIndex >= 0;
357
385
 
@@ -380,7 +408,7 @@ export default class GraphBodyRenderer extends Eventable {
380
408
 
381
409
  if (singleSeries.cutoffTime) {
382
410
  areaParams.cutoffIndex = cutoffIndex;
383
- areaParams.cutoffOpacity = 0.35;
411
+ areaParams.cutoffOpacity = singleSeries.cutoffOpacity !== undefined ? singleSeries.cutoffOpacity : 0.35;
384
412
  areaParams.originalData = cutoffData;
385
413
  areaParams.renderCutoffGradient = cutoffIndex >= 0;
386
414
  areaParams.isPreview = this === this._stateController.rangeGraphRenderer;
@@ -418,11 +446,18 @@ export default class GraphBodyRenderer extends Eventable {
418
446
  bounds = singleSeries.axis.currentBounds;
419
447
  }
420
448
 
421
- let zero = singleSeries.zeroLineY === 'bottom' ?
422
- 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;
449
+ let zero;
450
+ if (singleSeries.zeroLineY === 'bottom') {
451
+ zero = this._sizing.renderHeight;
452
+ } else if (singleSeries.zeroLineY !== undefined) {
453
+ zero = (1.0 - ((singleSeries.zeroLineY) - bounds.minY) / (bounds.maxY - bounds.minY)) * this._sizing.renderHeight;
454
+ } else {
455
+ if (bounds.minY <= 0 && bounds.maxY >= 0) {
456
+ zero = (1.0 - (0 - bounds.minY) / (bounds.maxY - bounds.minY)) * this._sizing.renderHeight;
457
+ } else {
458
+ zero = this._sizing.renderHeight;
459
+ }
460
+ }
426
461
 
427
462
  const boundsChanged = !this._lastBounds ||
428
463
  bounds.minY !== this._lastBounds.minY ||
@@ -442,9 +477,10 @@ export default class GraphBodyRenderer extends Eventable {
442
477
  zero = 0;
443
478
  }
444
479
 
480
+ const shadowColor = getColor(singleSeries.color, singleSeries.index, singleSeries.multigrapherSeriesIndex);
445
481
  let shadowParams = {
446
- color: getColor(singleSeries.color, singleSeries.index, singleSeries.multigrapherSeriesIndex),
447
- gradient: singleSeries.gradient,
482
+ color: shadowColor,
483
+ gradient: singleSeries.gradient || createDefaultGradient(shadowColor),
448
484
  zero,
449
485
  sizing: this._sizing,
450
486
  inRenderSpaceAreaBottom
@@ -452,7 +488,7 @@ export default class GraphBodyRenderer extends Eventable {
452
488
 
453
489
  if (singleSeries.cutoffTime) {
454
490
  shadowParams.cutoffIndex = cutoffIndex;
455
- shadowParams.cutoffOpacity = 0.35;
491
+ shadowParams.cutoffOpacity = singleSeries.cutoffOpacity !== undefined ? singleSeries.cutoffOpacity : 0.35;
456
492
  shadowParams.originalData = cutoffData;
457
493
  shadowParams.renderCutoffGradient = cutoffIndex >= 0;
458
494
  shadowParams.isPreview = this === this._stateController.rangeGraphRenderer;
@@ -524,6 +560,7 @@ export default class GraphBodyRenderer extends Eventable {
524
560
  dashPattern: singleSeries.dashPattern,
525
561
  highlighted,
526
562
  showIndividualPoints: shouldShowIndividualPoints,
563
+ pointRadius: singleSeries.pointRadius,
527
564
  getIndividualPoints,
528
565
  getRanges: singleSeries.rangeKey ? getRanges : null,
529
566
  rendering: singleSeries.rendering // Pass rendering type for all charts
@@ -536,7 +573,7 @@ export default class GraphBodyRenderer extends Eventable {
536
573
 
537
574
  if (singleSeries.cutoffTime) {
538
575
  drawParams.cutoffIndex = cutoffIndex;
539
- drawParams.cutoffOpacity = 0.35;
576
+ drawParams.cutoffOpacity = singleSeries.cutoffOpacity !== undefined ? singleSeries.cutoffOpacity : 0.35;
540
577
  drawParams.originalData = cutoffData;
541
578
  drawParams.renderCutoffGradient = cutoffIndex >= 0;
542
579
  drawParams.currentBounds = bounds;
@@ -122,7 +122,8 @@ export default class LineProgram {
122
122
 
123
123
  gl.uniform1f(gl.getUniformLocation(this._circleProgram, 'width'), width);
124
124
  gl.uniform1f(gl.getUniformLocation(this._circleProgram, 'height'), height);
125
- gl.uniform1f(gl.getUniformLocation(this._circleProgram, 'pointSize'), 2*(thickness+6));
125
+ const pointSize = parameters.pointRadius ? parameters.pointRadius * 2 * DPI_INCREASE : 2*(thickness+6);
126
+ gl.uniform1f(gl.getUniformLocation(this._circleProgram, 'pointSize'), pointSize);
126
127
 
127
128
  const individualPoints = parameters.getIndividualPoints();
128
129
 
@@ -150,14 +151,14 @@ export default class LineProgram {
150
151
  const postCutoffPoints = [];
151
152
 
152
153
  if (parameters.isPreview) {
153
- const firstTime = originalData[0][0] instanceof Date ? originalData[0][0].getTime() : originalData[0][0];
154
- const lastTime = originalData[originalData.length - 1][0] instanceof Date ?
155
- originalData[originalData.length - 1][0].getTime() : originalData[originalData.length - 1][0];
156
- const timeRatio = (cutoffTime - firstTime) / (lastTime - firstTime);
154
+ const visibleMinTime = parameters.selectionBounds.minX instanceof Date ? parameters.selectionBounds.minX.getTime() : parameters.selectionBounds.minX;
155
+ const visibleMaxTime = parameters.selectionBounds.maxX instanceof Date ? parameters.selectionBounds.maxX.getTime() : parameters.selectionBounds.maxX;
156
+ const timeRatio = (cutoffTime - visibleMinTime) / (visibleMaxTime - visibleMinTime);
157
+ const cutoffPixelX = timeRatio * width;
157
158
 
158
159
  for (let i = 0; i < individualPoints.length; i++) {
159
- const pointRatio = i / (individualPoints.length - 1);
160
- if (pointRatio < timeRatio) {
160
+ const [pixelX, pixelY] = individualPoints[i];
161
+ if (pixelX < cutoffPixelX) {
161
162
  preCutoffPoints.push(individualPoints[i]);
162
163
  } else {
163
164
  postCutoffPoints.push(individualPoints[i]);
@@ -252,10 +253,10 @@ export default class LineProgram {
252
253
  }
253
254
 
254
255
  if (parameters.isPreview) {
255
- const firstTime = originalData[0][0] instanceof Date ? originalData[0][0].getTime() : originalData[0][0];
256
- const lastTime = originalData[originalData.length - 1][0] instanceof Date ?
257
- originalData[originalData.length - 1][0].getTime() : originalData[originalData.length - 1][0];
258
- const timeRatio = (cutoffTime - firstTime) / (lastTime - firstTime);
256
+ const gl = this._gl;
257
+ const visibleMinTime = selectionBounds.minX instanceof Date ? selectionBounds.minX.getTime() : selectionBounds.minX;
258
+ const visibleMaxTime = selectionBounds.maxX instanceof Date ? selectionBounds.maxX.getTime() : selectionBounds.maxX;
259
+ const timeRatio = (cutoffTime - visibleMinTime) / (visibleMaxTime - visibleMinTime);
259
260
 
260
261
  if (timeRatio < 0) {
261
262
  this.draw(dataInRenderSpace, { ...parameters, renderCutoffGradient: false });
@@ -267,7 +268,6 @@ export default class LineProgram {
267
268
  renderCutoffGradient: false
268
269
  });
269
270
  } else {
270
- const gl = this._gl;
271
271
  gl.enable(gl.BLEND);
272
272
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
273
273
 
@@ -422,9 +422,14 @@ export default class ShadowProgram {
422
422
  params.color,
423
423
  cutoffOpacity
424
424
  );
425
+ const translucentGradient = applyReducedOpacityToGradient(
426
+ params.gradient,
427
+ cutoffOpacity
428
+ );
425
429
  this.draw(individualPoints, {
426
430
  ...params,
427
431
  color: reducedOpacityColor,
432
+ gradient: translucentGradient,
428
433
  renderCutoffGradient: false,
429
434
  });
430
435
  } else {