webglimpse
Version:
Webglimpse is a data visualization library for the web.
607 lines • 124 kB
JavaScript
/*
* Copyright (c) 2014, Metron, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import { white, darker } from '../color';
import { Pane, yFrac, isLeftMouseDown } from '../core';
import { attachAxisMouseListeners1D, Axis2D } from '../plot/axis';
import { newEdgeAxisPainter } from '../plot/edge_axis_painter';
import { fixedSize, Side, solid_FRAGSHADER, dash_FRAGSHADER, putQuadXys } from '../misc';
import { newColumnLayout } from '../layout/column_layout';
import { newOverlayLayout } from '../layout/overlay_layout';
import { indexAtOrBefore, indexNearest } from '../util/sorted_arrays';
import { concatLines, GL, ensureCapacityFloat32, hasval } from '../util/util';
import { Program, UniformColor, UniformMatrix4f, Attribute, Uniform1f } from '../shader';
import { newDynamicBuffer } from '../buffer';
import { glOrthoAxis } from '../matrix';
export function newTimeseriesRowPaneFactory(rowOptions) {
return function (drawable, timeAxis, dataAxis, model, row, ui, options) {
const rowTopPadding = (hasval(rowOptions) && hasval(rowOptions.rowTopPadding) ? rowOptions.rowTopPadding : 6);
const rowBottomPadding = (hasval(rowOptions) && hasval(rowOptions.rowBottomPadding) ? rowOptions.rowBottomPadding : 6);
const axisWidth = (hasval(rowOptions) && hasval(rowOptions.axisWidth) ? rowOptions.axisWidth : 60);
const painterFactories = (hasval(rowOptions) && hasval(rowOptions.painterFactories) ? rowOptions.painterFactories : []);
const axisOptions = (hasval(rowOptions) && hasval(rowOptions.axisOptions) ? rowOptions.axisOptions : {});
const keyPrefix = options.isMaximized ? 'maximized-' : '';
const getRowHeight = function () {
// maximized rows do not specifiy a height (they should fill available space)
if (options.isMaximized) {
return null;
}
// if the row has a custom row specified, use it
else if (hasval(row.rowHeight)) {
return row.rowHeight;
}
// otherwise use the default for this RowPaneFactory
else if (hasval(rowOptions) && hasval(rowOptions.rowHeight)) {
return rowOptions.rowHeight;
}
// as a last resort use a hard coded default
else {
return 135;
}
};
const rowHeight = getRowHeight();
const timelineFont = options.timelineFont;
const timelineFgColor = options.timelineFgColor;
const draggableEdgeWidth = options.draggableEdgeWidth;
const snapToDistance = options.snapToDistance;
const rowUi = ui.rowUi(row.rowGuid);
const input = ui.input;
const selection = ui.selection;
if (!hasval(axisOptions.font)) {
axisOptions.font = timelineFont;
}
if (!hasval(axisOptions.tickColor)) {
axisOptions.tickColor = timelineFgColor;
}
if (!hasval(axisOptions.textColor)) {
axisOptions.textColor = timelineFgColor;
}
if (!hasval(axisOptions.showLabel)) {
axisOptions.showLabel = true;
}
if (!hasval(axisOptions.shortenLabels)) {
axisOptions.shortenLabels = false;
}
const redraw = function () {
drawable.redraw();
};
// setup pane for data (y) axis painter and mouse listener
const yAxisPane = new Pane({ updatePrefSize: fixedSize(axisWidth, rowHeight) });
dataAxis.limitsChanged.on(redraw);
attachAxisMouseListeners1D(yAxisPane, dataAxis, true);
// add listener to update the height of the row if the rowHeight attribute changes
const updateRowHeight = function () {
yAxisPane.layout = { updatePrefSize: fixedSize(axisWidth, getRowHeight()) };
};
row.attrsChanged.on(updateRowHeight);
const isDragMode = function (viewport, i, j) {
const fragment = getNearestFragment(viewport, i, j).fragment;
return hasval(fragment);
};
const rowContentPane = new Pane(newColumnLayout(), true, isDragMode);
const underlayPane = new Pane(newOverlayLayout(), false);
const overlayPane = new Pane(null, false);
const painterOptions = { timelineFont: timelineFont, timelineFgColor: timelineFgColor, timelineThickness: 1, rowTopPadding: rowTopPadding, rowBottomPadding: rowBottomPadding };
for (let n = 0; n < painterFactories.length; n++) {
const createPainter = painterFactories[n];
rowContentPane.addPainter(createPainter(drawable, timeAxis, dataAxis, model, row, ui, painterOptions));
}
yAxisPane.addPainter(newEdgeAxisPainter(dataAxis, Side.RIGHT, axisOptions));
rowContentPane.addPane(yAxisPane, 0);
underlayPane.addPane(rowContentPane, true);
underlayPane.addPane(overlayPane, false);
rowUi.addPane(keyPrefix + 'content', rowContentPane);
rowUi.addPane(keyPrefix + 'overlay', overlayPane);
rowUi.addPane(keyPrefix + 'underlay', underlayPane);
rowUi.addPane(keyPrefix + 'y-axis', yAxisPane);
row.timeseriesGuids.valueAdded.on(redraw);
row.timeseriesGuids.valueMoved.on(redraw);
row.timeseriesGuids.valueRemoved.on(redraw);
const addFragmentRedraw = function (fragmentGuid) {
const fragment = model.timeseriesFragment(fragmentGuid);
fragment.dataChanged.on(redraw);
};
const removeFragmentRedraw = function (fragmentGuid) {
const fragment = model.timeseriesFragment(fragmentGuid);
fragment.dataChanged.off(redraw);
};
const addRedraw = function (timeseriesGuid) {
const timeseries = model.timeseries(timeseriesGuid);
timeseries.attrsChanged.on(redraw);
timeseries.fragmentGuids.valueAdded.on(redraw);
timeseries.fragmentGuids.valueRemoved.on(redraw);
timeseries.fragmentGuids.forEach(addFragmentRedraw);
timeseries.fragmentGuids.valueAdded.on(addFragmentRedraw);
timeseries.fragmentGuids.valueRemoved.on(removeFragmentRedraw);
};
row.timeseriesGuids.forEach(addRedraw);
row.timeseriesGuids.valueAdded.on(addRedraw);
const removeRedraw = function (timeseriesGuid) {
const timeseries = model.timeseries(timeseriesGuid);
timeseries.attrsChanged.off(redraw);
timeseries.fragmentGuids.valueAdded.off(redraw);
timeseries.fragmentGuids.valueRemoved.off(redraw);
timeseries.fragmentGuids.forEach(removeFragmentRedraw);
};
row.timeseriesGuids.valueRemoved.on(removeRedraw);
const timeAtCoords_PMILLIS = function (viewport, i) {
return timeAxis.tAtFrac_PMILLIS(viewport.xFrac(i));
};
const timeAtPointer_PMILLIS = function (ev) {
return timeAtCoords_PMILLIS(ev.paneViewport, ev.i);
};
// Used by both sets of listeners to know whether a timeseries-drag is in progress
let timeseriesDragMode = null;
// Hook up input notifications
//
let recentMouseMove = null;
rowContentPane.mouseMove.on(function (ev) {
input.mouseMove.fire(ev);
if (!hasval(timeseriesDragMode)) {
input.timeHover.fire(timeAtPointer_PMILLIS(ev), ev);
input.rowHover.fire(row, ev);
}
recentMouseMove = ev;
});
rowContentPane.mouseExit.on(function (ev) {
input.mouseExit.fire(ev);
if (!hasval(timeseriesDragMode)) {
input.timeHover.fire(null, ev);
input.rowHover.fire(null, ev);
input.eventHover.fire(null, ev);
}
recentMouseMove = null;
});
const uiMillisPerPxChanged = function () {
if (!hasval(timeseriesDragMode) && recentMouseMove != null) {
const ev = recentMouseMove;
input.timeHover.fire(timeAtPointer_PMILLIS(ev), ev);
}
};
ui.millisPerPx.changed.on(uiMillisPerPxChanged);
rowContentPane.mouseUp.on(function (ev) {
input.mouseUp.fire(ev);
});
rowContentPane.mouseDown.on(function (ev) {
input.mouseDown.fire(ev);
});
rowContentPane.mouseWheel.on(options.mouseWheelListener);
rowContentPane.contextMenu.on(function (ev) {
input.contextMenu.fire(ev);
});
// Begin annotation selection
//
const getNearestAnnotation = function (viewport, i, j) {
// maximum number of pixels away from a point the mouse can be to select it
const pickBuffer_PIXEL = 10;
// value per pixel in x and y directions
const vppx = ui.millisPerPx.value;
const vppy = dataAxis.vSize / rowContentPane.viewport.h;
const pickBuffer_PMILLIS = pickBuffer_PIXEL * vppx;
const ev_time = timeAtCoords_PMILLIS(viewport, i);
const ev_value = dataAxis.vAtFrac(viewport.yFrac(j));
let bestAnnotation = null;
let best_PIXEL = null;
if (ev_time) {
for (let annotationIdx = 0; annotationIdx < row.annotationGuids.length; annotationIdx++) {
const annotationGuid = row.annotationGuids.valueAt(annotationIdx);
const annotation = model.annotation(annotationGuid);
if (!annotation.pickable) {
continue;
}
const styleGuid = annotation.styleGuid;
const style = ui.annotationStyle(styleGuid);
const dy_PIXEL = Math.abs(annotation.y - ev_value) / vppy;
const dx_PIXEL = Math.abs(annotation.time_PMILLIS - ev_time) / vppx;
let d_PIXEL = 0;
if (style.uiHint === 'point') {
d_PIXEL = Math.sqrt(dx_PIXEL * dx_PIXEL + dy_PIXEL * dy_PIXEL);
}
else if (style.uiHint === 'horizontal-line') {
d_PIXEL = dy_PIXEL;
}
else if (style.uiHint === 'vertical-line') {
d_PIXEL = dx_PIXEL;
}
if (d_PIXEL < pickBuffer_PIXEL) {
if (!hasval(best_PIXEL) || d_PIXEL < best_PIXEL) {
bestAnnotation = annotation;
best_PIXEL = d_PIXEL;
}
}
}
}
return bestAnnotation;
};
const getNearestAnnotationEvent = function (ev) {
return getNearestAnnotation(ev.paneViewport, ev.i, ev.j);
};
overlayPane.mouseMove.on(function (ev) {
// update selection.hoveredYValue
const y = dataAxis.vAtFrac(yFrac(ev));
selection.hoveredY.value = y;
// update selection.hoveredAnnotation
const result = getNearestAnnotationEvent(ev);
selection.hoveredAnnotation.value = result;
});
selection.hoveredAnnotation.changed.on(redraw);
overlayPane.mouseExit.on(function () {
selection.hoveredY.value = undefined;
selection.hoveredAnnotation.value = null;
});
// Begin timeseries-drag
//
function chooseTimeseriesDragMode(timelineUi, hoveredTimeseriesFragment) {
if (!hasval(hoveredTimeseriesFragment)) {
return null;
}
// return the edit mode of the selected fragment
else {
return hoveredTimeseriesFragment.userEditMode;
}
}
const updateCursor = function () {
if (!timeseriesDragMode) {
const mouseCursors = { 'xy': 'move', 'y': 'ns-resize' };
rowContentPane.mouseCursor = mouseCursors[chooseTimeseriesDragMode(ui, selection.hoveredTimeseries.fragment)];
}
};
ui.millisPerPx.changed.on(updateCursor);
selection.hoveredTimeseries.changed.on(updateCursor);
const getNearestFragment = function (viewport, i, j) {
// maximum number of pixels away from a point the mouse can be to select it
const pickBuffer_PIXEL = 10;
// value per pixel in x and y directions
const vppx = ui.millisPerPx.value;
const vppy = dataAxis.vSize / rowContentPane.viewport.h;
const pickBuffer_PMILLIS = pickBuffer_PIXEL * vppx;
let bestFragment;
let bestIndex;
let best_PIXEL;
const ev_time = timeAtCoords_PMILLIS(viewport, i);
const ev_value = dataAxis.vAtFrac(viewport.yFrac(j));
if (ev_time) {
for (let timeseriesIdx = 0; timeseriesIdx < row.timeseriesGuids.length; timeseriesIdx++) {
const timeseriesGuid = row.timeseriesGuids.valueAt(timeseriesIdx);
const timeseries = model.timeseries(timeseriesGuid);
for (let fragmentIdx = 0; fragmentIdx < timeseries.fragmentGuids.length; fragmentIdx++) {
const fragmentGuid = timeseries.fragmentGuids.valueAt(fragmentIdx);
const fragment = model.timeseriesFragment(fragmentGuid);
// fragments should not overlap
if (fragment.start_PMILLIS - pickBuffer_PMILLIS < ev_time && fragment.end_PMILLIS + pickBuffer_PMILLIS > ev_time) {
// bars are drawn starting at the point and continuing to the next point, so we need to choose the closest index differently
const index = timeseries.uiHint === 'bars' ? indexAtOrBefore(fragment.times_PMILLIS, ev_time) : indexNearest(fragment.times_PMILLIS, ev_time);
const value = fragment.data[index];
const time = fragment.times_PMILLIS[index];
const dy_PIXEL = (value - ev_value) / vppy;
const dx_PIXEL = (time - ev_time) / vppx;
const d_PIXEL = Math.sqrt(dx_PIXEL * dx_PIXEL + dy_PIXEL * dy_PIXEL);
const filter = function () {
if (timeseries.uiHint === 'bars') {
return (timeseries.baseline < ev_value && ev_value < value) ||
(timeseries.baseline > ev_value && ev_value > value);
}
else {
return d_PIXEL < pickBuffer_PIXEL;
}
};
if ((!best_PIXEL || d_PIXEL < best_PIXEL) && filter()) {
best_PIXEL = d_PIXEL;
bestFragment = fragment;
bestIndex = index;
}
}
}
}
}
return { fragment: bestFragment, index: bestIndex };
};
const getNearestFragmentEvent = function (ev) {
return getNearestFragment(ev.paneViewport, ev.i, ev.j);
};
// choose the closest data point to the mouse cursor position and fire an event when it changes
rowContentPane.mouseMove.on(function (ev) {
if (!hasval(timeseriesDragMode)) {
const result = getNearestFragmentEvent(ev);
selection.hoveredTimeseries.setValue(result.fragment, result.index);
}
});
selection.hoveredTimeseries.changed.on(redraw);
rowContentPane.mouseExit.on(function () {
selection.hoveredTimeseries.clearValue();
});
rowContentPane.mouseDown.on(function (ev) {
if (isLeftMouseDown(ev.mouseEvent)) {
timeseriesDragMode = chooseTimeseriesDragMode(ui, selection.hoveredTimeseries.fragment);
}
});
rowContentPane.mouseMove.on(function (ev) {
if (hasval(timeseriesDragMode)) {
const x = timeAtPointer_PMILLIS(ev);
const y = dataAxis.vAtFrac(yFrac(ev));
const fragment = selection.hoveredTimeseries.fragment;
const fragment_time = fragment.times_PMILLIS;
if (timeseriesDragMode === 'y') {
fragment.setData(selection.hoveredTimeseries.index, y);
}
else if (timeseriesDragMode === 'xy') {
const index = fragment.setData(selection.hoveredTimeseries.index, y, x);
if (index !== selection.hoveredTimeseries.index) {
selection.hoveredTimeseries.setValue(fragment, index);
}
}
}
});
// Finish event-drag
//
rowContentPane.mouseUp.on(function (ev) {
timeseriesDragMode = null;
});
rowContentPane.dispose.on(function () {
rowUi.removePane(keyPrefix + 'content');
rowUi.removePane(keyPrefix + 'overlay');
rowUi.removePane(keyPrefix + 'underlay');
rowUi.removePane(keyPrefix + 'y-axis');
dataAxis.limitsChanged.off(redraw);
row.timeseriesGuids.valueAdded.off(redraw);
row.timeseriesGuids.valueMoved.off(redraw);
row.timeseriesGuids.valueRemoved.off(redraw);
row.timeseriesGuids.valueAdded.off(addRedraw);
row.timeseriesGuids.valueRemoved.off(removeRedraw);
selection.hoveredTimeseries.changed.off(redraw);
row.attrsChanged.off(updateRowHeight);
row.timeseriesGuids.forEach(function (timeseriesGuid) {
const timeseries = model.timeseries(timeseriesGuid);
timeseries.attrsChanged.off(redraw);
timeseries.fragmentGuids.valueAdded.off(redraw);
timeseries.fragmentGuids.valueRemoved.off(redraw);
});
});
return underlayPane;
};
}
export function newTimeseriesPainterFactory(options) {
// Painter Factory
return function (drawable, timeAxis, dataAxis, model, rowModel, ui) {
const selection = ui.selection;
const defaultColor = hasval(options) && hasval(options.timelineFgColor) ? options.timelineFgColor : white;
const defaultThickness = hasval(options) && hasval(options.timelineThickness) ? options.timelineThickness : 1;
const modelview_line_VERTSHADER = concatLines(' uniform mat4 u_modelViewMatrix; ', ' attribute vec4 a_Position; ', ' attribute float a_Distance; ', ' varying float v_Distance; ', ' uniform float u_PointSize; ', ' ', ' void main( ) { ', ' gl_PointSize = u_PointSize ; ', ' gl_Position = u_modelViewMatrix * a_Position ; ', ' v_Distance = a_Distance; ', ' } ', ' ');
const modelview_pointsize_VERTSHADER = concatLines(' uniform mat4 u_modelViewMatrix; ', ' attribute vec4 a_Position; ', ' uniform float u_PointSize; ', ' ', ' void main( ) { ', ' gl_PointSize = u_PointSize ; ', ' gl_Position = u_modelViewMatrix * a_Position ; ', ' } ', ' ');
const program = new Program(modelview_pointsize_VERTSHADER, solid_FRAGSHADER);
const u_Color = new UniformColor(program, 'u_Color');
const u_modelViewMatrix = new UniformMatrix4f(program, 'u_modelViewMatrix');
const a_Position = new Attribute(program, 'a_Position');
const u_PointSize = new Uniform1f(program, 'u_PointSize');
const lineProgram = new Program(modelview_line_VERTSHADER, dash_FRAGSHADER);
const u_lpColor = new UniformColor(lineProgram, 'u_Color');
const u_lpmodelViewMatrix = new UniformMatrix4f(lineProgram, 'u_modelViewMatrix');
const a_lpPosition = new Attribute(lineProgram, 'a_Position');
const u_lpPointSize = new Uniform1f(lineProgram, 'u_PointSize');
const a_lpDistance = new Attribute(lineProgram, 'a_Distance');
const u_lpDash = new Uniform1f(lineProgram, 'u_Dash');
const axis = new Axis2D(timeAxis, dataAxis);
let xys = new Float32Array(0);
let dists = new Float32Array(0);
const xysBuffer = newDynamicBuffer();
const distBuffer = newDynamicBuffer();
// Painter
return function (gl, viewport) {
gl.blendFuncSeparate(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA, GL.ONE, GL.ONE_MINUS_SRC_ALPHA);
gl.enable(GL.BLEND);
let baseline = 0;
for (let i = 0; i < rowModel.timeseriesGuids.length; i++) {
// collect fragments and sort them by time
let totalSize = 0;
const sortedFragments = new Array();
const timeseriesGuid = rowModel.timeseriesGuids.valueAt(i);
const timeseries = model.timeseries(timeseriesGuid);
for (let j = 0; j < timeseries.fragmentGuids.length; j++) {
const timeseriesFragmentGuid = timeseries.fragmentGuids.valueAt(j);
const fragment = model.timeseriesFragment(timeseriesFragmentGuid);
sortedFragments.push(fragment);
totalSize += fragment.times_PMILLIS.length;
}
sortedFragments.sort((a, b) => a.start_PMILLIS - b.start_PMILLIS);
if (timeseries.uiHint === 'lines' || timeseries.uiHint === 'points' || timeseries.uiHint === 'lines-and-points' || timeseries.uiHint === undefined) {
// enable the shader
lineProgram.use(gl);
u_lpmodelViewMatrix.setData(gl, glOrthoAxis(axis));
const size = totalSize * 2;
xys = ensureCapacityFloat32(xys, size);
dists = ensureCapacityFloat32(dists, totalSize);
let index = 0;
for (let j = 0; j < sortedFragments.length; j++) {
const fragment = sortedFragments[j];
const data = fragment.data;
const times_PMILLIS = fragment.times_PMILLIS;
for (let k = 0; k < data.length; k++, index += 2) {
xys[index] = timeAxis.vAtTime(times_PMILLIS[k]);
xys[index + 1] = data[k];
if (k !== 0) {
const x = (timeAxis.vAtTime(times_PMILLIS[k]) - timeAxis.vAtTime(times_PMILLIS[k - 1]));
const y = data[k] - data[k - 1];
dists[k] = dists[k - 1] + Math.sqrt(x * x + y * y);
}
else {
dists[k] = 0;
}
}
}
const lineColor = hasval(timeseries.lineColor) ? timeseries.lineColor : defaultColor;
u_lpColor.setData(gl, lineColor);
const dash = hasval(timeseries.dash) ? timeseries.dash : 0;
u_lpDash.setData(gl, dash);
const lineThickness = hasval(timeseries.lineThickness) ? timeseries.lineThickness : defaultThickness;
gl.lineWidth(lineThickness);
xysBuffer.setData(xys.subarray(0, index));
a_lpPosition.setDataAndEnable(gl, xysBuffer, 2, GL.FLOAT);
distBuffer.setData(dists.subarray(0, totalSize));
a_lpDistance.setDataAndEnable(gl, distBuffer, 1, GL.FLOAT);
if (timeseries.uiHint === 'lines' || timeseries.uiHint === 'lines-and-points' || timeseries.uiHint === undefined) {
// draw the lines
gl.drawArrays(GL.LINE_STRIP, 0, size / 2);
}
// point size works in WebKit and actually works in Minefield as well even though
// VERTEX_PROGRAM_POINT_SIZE and POINT_SMOOTH aren't defined
if (timeseries.uiHint === 'points' || timeseries.uiHint === 'lines-and-points') {
const pointColor = hasval(timeseries.pointColor) ? timeseries.pointColor : defaultColor;
u_Color.setData(gl, pointColor);
u_PointSize.setData(gl, timeseries.pointSize);
gl.drawArrays(GL.POINTS, 0, size / 2);
}
lineProgram.endUse(gl);
}
else if (timeseries.uiHint === 'bars') {
// enable the shader
program.use(gl);
u_modelViewMatrix.setData(gl, glOrthoAxis(axis));
// The last data point defines the right edge of the bar
// but it does not have its own bar drawn, so we need at
// least 2 data points to draw any bars
if (totalSize >= 2) {
baseline = timeseries.baseline;
const size = (totalSize - 1) * 12;
xys = ensureCapacityFloat32(xys, size);
let index = 0;
for (let j = 0; j < sortedFragments.length; j++) {
const fragment = sortedFragments[j];
const data = fragment.data;
const times_PMILLIS = fragment.times_PMILLIS;
for (let k = 0; k < data.length - 1; k++) {
const x1 = timeAxis.vAtTime(times_PMILLIS[k]);
const y1 = data[k];
const x2 = timeAxis.vAtTime(times_PMILLIS[k + 1]);
const y2 = data[k + 1];
index = putQuadXys(xys, index, x1, x2, y1, baseline);
}
}
const lineColor = hasval(timeseries.lineColor) ? timeseries.lineColor : defaultColor;
u_Color.setData(gl, lineColor);
xysBuffer.setData(xys.subarray(0, index));
a_Position.setDataAndEnable(gl, xysBuffer, 2, GL.FLOAT);
gl.drawArrays(GL.TRIANGLES, 0, size / 2);
}
program.endUse(gl);
}
else if (timeseries.uiHint === 'area') {
// enable the shader
program.use(gl);
u_modelViewMatrix.setData(gl, glOrthoAxis(axis));
baseline = timeseries.baseline;
const size = totalSize * 4;
// the last data point defines the right edge of the bar
// but it does not have its own bar drawn
xys = ensureCapacityFloat32(xys, size);
let index = 0;
for (let j = 0; j < sortedFragments.length; j++) {
const fragment = sortedFragments[j];
const data = fragment.data;
const times_PMILLIS = fragment.times_PMILLIS;
for (let k = 0; k < data.length; k++, index += 4) {
const x1 = timeAxis.vAtTime(times_PMILLIS[k]);
const y1 = data[k];
xys[index] = x1;
xys[index + 1] = baseline;
xys[index + 2] = x1;
xys[index + 3] = y1;
}
}
const lineColor = hasval(timeseries.lineColor) ? timeseries.lineColor : defaultColor;
u_Color.setData(gl, lineColor);
xysBuffer.setData(xys.subarray(0, index));
a_Position.setDataAndEnable(gl, xysBuffer, 2, GL.FLOAT);
gl.drawArrays(GL.TRIANGLE_STRIP, 0, size / 2);
program.endUse(gl);
}
// highlight hovered point
if (selection.hoveredTimeseries.fragment && timeseries.fragmentGuids.hasValue(selection.hoveredTimeseries.fragment.fragmentGuid)) {
// enable the shader
program.use(gl);
u_modelViewMatrix.setData(gl, glOrthoAxis(axis));
if (timeseries.uiHint === 'area' || timeseries.uiHint === 'lines' || timeseries.uiHint === 'points' || timeseries.uiHint === 'lines-and-points' || timeseries.uiHint === undefined) {
const size = 8;
xys = ensureCapacityFloat32(xys, size);
const vppx = timeAxis.vSize / viewport.w;
const vppy = dataAxis.vSize / viewport.h;
const highlightSize = hasval(timeseries.pointSize) ? timeseries.pointSize : 5;
const bufferx = (highlightSize / 2) * vppx;
const buffery = (highlightSize / 2) * vppy;
const fragment = selection.hoveredTimeseries.fragment;
const y = selection.hoveredTimeseries.data;
const x = timeAxis.vAtTime(selection.hoveredTimeseries.times_PMILLIS);
xys[0] = x - bufferx;
xys[1] = y - buffery;
xys[2] = x + bufferx;
xys[3] = y - buffery;
xys[4] = x + bufferx;
xys[5] = y + buffery;
xys[6] = x - bufferx;
xys[7] = y + buffery;
const color = hasval(timeseries.pointColor) ? timeseries.pointColor : defaultColor;
u_Color.setData(gl, darker(color, 0.8));
xysBuffer.setData(xys.subarray(0, size));
a_Position.setDataAndEnable(gl, xysBuffer, 2, GL.FLOAT);
gl.drawArrays(GL.LINE_LOOP, 0, size / 2);
}
else if (timeseries.uiHint === 'bars') {
const size = 8;
xys = ensureCapacityFloat32(xys, size);
const fragment = selection.hoveredTimeseries.fragment;
const index = selection.hoveredTimeseries.index;
if (index < fragment.data.length) {
const x1 = timeAxis.vAtTime(fragment.times_PMILLIS[index]);
const y1 = fragment.data[index];
const x2 = timeAxis.vAtTime(fragment.times_PMILLIS[index + 1]);
const y2 = fragment.data[index + 1];
xys[0] = x1;
xys[1] = y1;
xys[2] = x2;
xys[3] = y1;
xys[4] = x2;
xys[5] = baseline;
xys[6] = x1;
xys[7] = baseline;
const color = hasval(timeseries.lineColor) ? timeseries.lineColor : defaultColor;
u_Color.setData(gl, darker(color, 0.8));
xysBuffer.setData(xys.subarray(0, size));
a_Position.setDataAndEnable(gl, xysBuffer, 2, GL.FLOAT);
gl.drawArrays(GL.LINE_LOOP, 0, size / 2);
}
}
program.endUse(gl);
}
}
// disable shader and attribute buffers
a_Position.disable(gl);
a_lpDistance.disable(gl);
};
};
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGltZWxpbmVfdGltZXNlcmllc19yb3cuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvd2ViZ2xpbXBzZS90aW1lbGluZS90aW1lbGluZV90aW1lc2VyaWVzX3Jvdy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTRCRztBQUNILE9BQU8sRUFBUyxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0sVUFBVSxDQUFDO0FBQ2hELE9BQU8sRUFBcUIsSUFBSSxFQUF3QixLQUFLLEVBQUUsZUFBZSxFQUFVLE1BQU0sU0FBUyxDQUFDO0FBRXhHLE9BQU8sRUFBVSwwQkFBMEIsRUFBRSxNQUFNLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFHMUUsT0FBTyxFQUEwQixrQkFBa0IsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBRXZGLE9BQU8sRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLGdCQUFnQixFQUFFLGVBQWUsRUFBRSxVQUFVLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFFekYsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzFELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBRTVELE9BQU8sRUFBRSxlQUFlLEVBQUUsWUFBWSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDdEUsT0FBTyxFQUFFLFdBQVcsRUFBRSxFQUFFLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQzlFLE9BQU8sRUFBRSxPQUFPLEVBQUUsWUFBWSxFQUFFLGVBQWUsRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQ3pGLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUM3QyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBcUJ4QyxNQUFNLFVBQVUsMkJBQTJC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