UNPKG

webglimpse

Version:

Webglimpse is a data visualization library for the web.

607 lines 124 kB
/* * 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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