@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/dist/bundle.cjs +1 -1
- package/dist/bundle.cjs.map +1 -1
- package/dist/bundle.esm.js +1 -1
- package/dist/bundle.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/range_graph.jsx +2 -0
- package/src/grapher.scss +2 -1
- package/src/renderer/graph_body_renderer.js +117 -45
- package/src/renderer/line_program.js +3 -3
- package/src/renderer/shadow_program.js +93 -53
package/package.json
CHANGED
|
@@ -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
|
@@ -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
|
-
|
|
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
|
-
|
|
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 <
|
|
128
|
-
const currentPoint =
|
|
129
|
-
const nextPoint =
|
|
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 =
|
|
132
|
-
|
|
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 =
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
468
|
-
drawParams.renderCutoffGradient = cutoffIndex >= 0;
|
|
540
|
+
drawParams.originalData = cutoffData;
|
|
541
|
+
drawParams.renderCutoffGradient = cutoffIndex >= 0;
|
|
469
542
|
drawParams.currentBounds = bounds;
|
|
470
|
-
drawParams.isPreview = this === this._stateController.rangeGraphRenderer;
|
|
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 :
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
353
|
-
|
|
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
|
|
358
|
-
const
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
const
|
|
362
|
-
|
|
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
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
|
414
|
-
const
|
|
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
|
-
|
|
441
|
-
|
|
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
|
-
|
|
444
|
-
|
|
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
|
}
|