UNPKG

webglimpse

Version:

Webglimpse is a data visualization library for the web.

658 lines 332 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 { Color, darker } from '../color'; import { Pane, isLeftMouseDown } from '../core'; import { TimelineLaneArray, effectiveEdges_PMILLIS } from './timeline_lanes'; import { concatLines, GL, ensureCapacityFloat32, hasval, isNumber } from '../util/util'; import { indexNearest } from '../util/sorted_arrays'; import { Program, Attribute, Uniform1f } from '../shader'; import { newDynamicBuffer } from '../buffer'; import { putQuadXys, putRgbas, putQuadRgbas, putUpperRightTriangleXys, putLowerRightTriangleXys, putLowerLeftTriangleXys, putUpperLeftTriangleXys, varyingColor_FRAGSHADER } from '../misc'; import { TextureRenderer, Texture2D } from '../texture'; import { newTextTextureCache2 } from '../text'; export function newEventsRowPaneFactory(eventsRowOpts) { // Pane Factory return function (drawable, timeAxis, dataAxis, model, row, ui, options) { const rowTopPadding = (hasval(eventsRowOpts) && hasval(eventsRowOpts.rowTopPadding) ? eventsRowOpts.rowTopPadding : 6); const rowBottomPadding = (hasval(eventsRowOpts) && hasval(eventsRowOpts.rowBottomPadding) ? eventsRowOpts.rowBottomPadding : 6); const laneHeight = (hasval(eventsRowOpts) && hasval(eventsRowOpts.laneHeight) ? eventsRowOpts.laneHeight : 33); const painterFactories = (hasval(eventsRowOpts) && hasval(eventsRowOpts.painterFactories) ? eventsRowOpts.painterFactories : []); const allowMultipleLanes = (hasval(eventsRowOpts) && hasval(eventsRowOpts.allowMultipleLanes) ? eventsRowOpts.allowMultipleLanes : true); 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; const lanes = new TimelineLaneArray(model, row, ui, allowMultipleLanes); 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); }; const eventAtCoords = function (viewport, i, j) { const laneNum = Math.floor((viewport.jEnd - j - rowTopPadding) / laneHeight); const time_PMILLIS = timeAtCoords_PMILLIS(viewport, i); return lanes.eventAt(laneNum, time_PMILLIS); }; const eventAtPointer = function (ev) { return eventAtCoords(ev.paneViewport, ev.i, ev.j); }; const isInsideAnEvent = function (viewport, i, j) { return hasval(eventAtCoords(viewport, i, j)); }; // Create pane // const layout = { updatePrefSize: function (parentPrefSize) { parentPrefSize.h = rowTopPadding + rowBottomPadding + Math.max(1, lanes.length) * laneHeight; parentPrefSize.w = null; } }; const rowContentPane = new Pane(layout, true, isInsideAnEvent); rowUi.addPane('content', rowContentPane); const painterOptions = { timelineFont: timelineFont, timelineFgColor: timelineFgColor, rowTopPadding: rowTopPadding, rowBottomPadding: rowBottomPadding, laneHeight: laneHeight }; for (let n = 0; n < painterFactories.length; n++) { const createPainter = painterFactories[n]; rowContentPane.addPainter(createPainter(drawable, timeAxis, lanes, ui, painterOptions)); } const redraw = function () { drawable.redraw(); }; row.eventGuids.valueAdded.on(redraw); row.eventGuids.valueMoved.on(redraw); row.eventGuids.valueRemoved.on(redraw); const watchEventAttrs = function (eventGuid) { model.event(eventGuid).attrsChanged.on(redraw); }; row.eventGuids.forEach(watchEventAttrs); row.eventGuids.valueAdded.on(watchEventAttrs); const removeRedraw = function (eventGuid) { model.event(eventGuid).attrsChanged.off(redraw); }; row.eventGuids.valueRemoved.on(removeRedraw); // Used by both sets of listeners to know whether an event-drag is in progress let eventDragMode = null; // Hook up input notifications // let recentMouseMove = null; rowContentPane.mouseMove.on(function (ev) { input.mouseMove.fire(ev); if (!eventDragMode) { input.timeHover.fire(timeAtPointer_PMILLIS(ev), ev); input.rowHover.fire(row, ev); input.eventHover.fire(eventAtPointer(ev), ev); } recentMouseMove = ev; }); rowContentPane.mouseExit.on(function (ev) { input.mouseExit.fire(ev); if (!eventDragMode) { input.timeHover.fire(null, ev); input.rowHover.fire(null, ev); input.eventHover.fire(null, ev); } recentMouseMove = null; }); const uiMillisPerPxChanged = function () { if (!eventDragMode && recentMouseMove != null) { const ev = recentMouseMove; input.timeHover.fire(timeAtPointer_PMILLIS(ev), ev); input.eventHover.fire(eventAtPointer(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 event-drag // let eventDragEvents = []; let eventDragOffsets_MILLIS = {}; let eventDragSnapTimes_PMILLIS = []; // Event-edges are draggable for events at least this wide const minEventWidthForEdgeDraggability = 3 * draggableEdgeWidth; // When dragging an event-edge, the event cannot be made narrower than this // // Needs to be greater than minEventWidthForEdgeDraggability -- by enough to // cover floating-point precision loss -- so a user can't accidentally make // an event so narrow that it can't easily be widened again. // const minEventWidthWhenDraggingEdge = minEventWidthForEdgeDraggability + 1; function allUserEditable(events) { for (let n = 0; n < events.length; n++) { if (!events[n].userEditable) { return false; } } return true; } function chooseEventDragMode(timelineUi, mouseTime_PMILLIS, timelineEventDragEvents) { if (timelineEventDragEvents.length === 0) { // If no events are selected, then we don't have any to drag return null; } else if (!allUserEditable(timelineEventDragEvents)) { // If any selected event is not user-editable, don't allow editing return 'undraggable'; } else if (timelineEventDragEvents.length > 1) { // If more than one event is selected, don't allow edge dragging return 'center'; } else if (timelineEventDragEvents.length === 1) { const event = timelineEventDragEvents[0]; const pxPerMilli = 1 / timelineUi.millisPerPx.value; const eventWidth = (event.end_PMILLIS - event.start_PMILLIS) * pxPerMilli; if (eventWidth < minEventWidthForEdgeDraggability) { // If event isn't very wide, don't try to allow edge dragging return 'center'; } else { const mouseOffset = (mouseTime_PMILLIS - event.start_PMILLIS) * pxPerMilli; if (mouseOffset < draggableEdgeWidth) { // If mouse is near the left edge, drag the event's start-time return 'start'; } else if (mouseOffset < eventWidth - draggableEdgeWidth) { // If mouse is in the center, drag the whole event return 'center'; } else { // If mouse is near the right edge, drag the event's end-time return 'end'; } } } else { // Should never get here, because we have clauses above for length === 0, length === 1, and length > 1 return null; } } const updateCursor = function () { if (!eventDragMode) { const mouseCursors = { 'center': 'default', 'start': 'w-resize', 'end': 'e-resize', 'undraggable': 'default' }; const hoveredTime_PMILLIS = selection.hoveredTime_PMILLIS.value; // if a multi-selection has been made, update the cursor based on all the events in the multi-selection if (selection.selectedEvents.length > 1) { rowContentPane.mouseCursor = mouseCursors[chooseEventDragMode(ui, hoveredTime_PMILLIS, selection.selectedEvents.toArray())]; } else { const hoveredEvent = selection.hoveredEvent.value; const hoveredEvents = (hasval(hoveredEvent) ? [hoveredEvent] : []); rowContentPane.mouseCursor = mouseCursors[chooseEventDragMode(ui, hoveredTime_PMILLIS, hoveredEvents)]; } } }; ui.millisPerPx.changed.on(updateCursor); selection.hoveredTime_PMILLIS.changed.on(updateCursor); selection.hoveredEvent.changed.on(updateCursor); rowContentPane.mouseDown.on(function (ev) { if (isLeftMouseDown(ev.mouseEvent)) { const eventDragEventsSet = selection.selectedEvents; eventDragEvents = eventDragEventsSet.toArray(); eventDragMode = chooseEventDragMode(ui, timeAtPointer_PMILLIS(ev), eventDragEvents); eventDragSnapTimes_PMILLIS = new Array(); const numSnapTimes = 0; const allEventGuids = row.eventGuids; for (let n = 0; n < allEventGuids.length; n++) { const eventGuid = allEventGuids.valueAt(n); if (!eventDragEventsSet.hasId(eventGuid)) { const event = model.event(eventGuid); eventDragSnapTimes_PMILLIS.push(event.start_PMILLIS); eventDragSnapTimes_PMILLIS.push(event.end_PMILLIS); } } eventDragSnapTimes_PMILLIS.sort(); } }); function findSnapShift_MILLIS(t_PMILLIS, maxShift_MILLIS) { const i = indexNearest(eventDragSnapTimes_PMILLIS, t_PMILLIS); if (i >= 0) { const shift_MILLIS = eventDragSnapTimes_PMILLIS[i] - t_PMILLIS; if (Math.abs(shift_MILLIS) <= maxShift_MILLIS) { return shift_MILLIS; } } return null; } // Compute (and remember) the pointer time, for use by the event-drag listeners below // let eventDragPointer_PMILLIS = null; const updateEventDragPointer = function (ev) { if (isLeftMouseDown(ev.mouseEvent) && eventDragMode) { eventDragPointer_PMILLIS = timeAtPointer_PMILLIS(ev); } }; rowContentPane.mouseDown.on(updateEventDragPointer); rowContentPane.mouseMove.on(updateEventDragPointer); // Dragging event-center // const grabEventCenter = function () { if (eventDragMode === 'center') { for (let n = 0; n < eventDragEvents.length; n++) { const event = eventDragEvents[n]; eventDragOffsets_MILLIS[event.eventGuid] = eventDragPointer_PMILLIS - event.start_PMILLIS; } // If this is a simple click-and-release, leave the mouse-cursor alone -- // but once we can tell that it's actually a drag, change to a drag cursor // const beginDrag = function () { rowContentPane.mouseCursor = 'move'; }; rowContentPane.mouseMove.on(beginDrag); const pendingBeginDrag = setTimeout(beginDrag, 300); const endDrag = function () { clearTimeout(pendingBeginDrag); rowContentPane.mouseMove.off(beginDrag); rowContentPane.mouseUp.off(endDrag); }; rowContentPane.mouseUp.on(endDrag); } }; rowContentPane.mouseDown.on(grabEventCenter); const dragEventCenter = function () { if (eventDragMode === 'center') { const maxSnapShift_MILLIS = snapToDistance * (timeAxis.tSize_MILLIS / rowContentPane.viewport.w); let snapShift_MILLIS = null; for (let n = 0; n < eventDragEvents.length; n++) { const event = eventDragEvents[n]; const newStart_PMILLIS = (eventDragPointer_PMILLIS - eventDragOffsets_MILLIS[event.eventGuid]); const newEnd_PMILLIS = event.end_PMILLIS + (newStart_PMILLIS - event.start_PMILLIS); const eventStartSnapShift_MILLIS = findSnapShift_MILLIS(newStart_PMILLIS, maxSnapShift_MILLIS); if (hasval(eventStartSnapShift_MILLIS)) { if (!hasval(snapShift_MILLIS) || Math.abs(eventStartSnapShift_MILLIS) < Math.abs(snapShift_MILLIS)) { snapShift_MILLIS = eventStartSnapShift_MILLIS; } } const eventEndSnapShift_MILLIS = findSnapShift_MILLIS(newEnd_PMILLIS, maxSnapShift_MILLIS); if (hasval(eventEndSnapShift_MILLIS)) { if (!hasval(snapShift_MILLIS) || Math.abs(eventEndSnapShift_MILLIS) < Math.abs(snapShift_MILLIS)) { snapShift_MILLIS = eventEndSnapShift_MILLIS; } } } if (!hasval(snapShift_MILLIS)) { snapShift_MILLIS = 0; } for (let n = 0; n < eventDragEvents.length; n++) { const event = eventDragEvents[n]; const newStart_PMILLIS = eventDragPointer_PMILLIS - eventDragOffsets_MILLIS[event.eventGuid] + snapShift_MILLIS; const newEnd_PMILLIS = event.end_PMILLIS + (newStart_PMILLIS - event.start_PMILLIS); event.setInterval(newStart_PMILLIS, newEnd_PMILLIS); } } }; rowContentPane.mouseMove.on(dragEventCenter); // Dragging event-start // const grabEventStart = function () { if (eventDragMode === 'start') { for (let n = 0; n < eventDragEvents.length; n++) { const event = eventDragEvents[n]; eventDragOffsets_MILLIS[event.eventGuid] = eventDragPointer_PMILLIS - event.start_PMILLIS; } } }; rowContentPane.mouseDown.on(grabEventStart); const dragEventStart = function () { if (eventDragMode === 'start') { const wMin_MILLIS = minEventWidthWhenDraggingEdge * timeAxis.vSize / rowContentPane.viewport.w; const maxSnapShift_MILLIS = snapToDistance * (timeAxis.tSize_MILLIS / rowContentPane.viewport.w); let snapShift_MILLIS = null; for (let n = 0; n < eventDragEvents.length; n++) { const event = eventDragEvents[n]; const newStart_PMILLIS = eventDragPointer_PMILLIS - eventDragOffsets_MILLIS[event.eventGuid]; const eventSnapShift_MILLIS = findSnapShift_MILLIS(newStart_PMILLIS, maxSnapShift_MILLIS); if (hasval(eventSnapShift_MILLIS)) { if (!hasval(snapShift_MILLIS) || Math.abs(eventSnapShift_MILLIS) < Math.abs(snapShift_MILLIS)) { snapShift_MILLIS = eventSnapShift_MILLIS; } } } if (!hasval(snapShift_MILLIS)) { snapShift_MILLIS = 0; } for (let n = 0; n < eventDragEvents.length; n++) { const event = eventDragEvents[n]; const newStart_PMILLIS = eventDragPointer_PMILLIS - eventDragOffsets_MILLIS[event.eventGuid] + snapShift_MILLIS; event.start_PMILLIS = Math.trunc(Math.min(event.end_PMILLIS - wMin_MILLIS, newStart_PMILLIS)); } } }; rowContentPane.mouseMove.on(dragEventStart); timeAxis.limitsChanged.on(dragEventStart); // Dragging event-end // const grabEventEnd = function () { if (eventDragMode === 'end') { for (let n = 0; n < eventDragEvents.length; n++) { const event = eventDragEvents[n]; eventDragOffsets_MILLIS[event.eventGuid] = eventDragPointer_PMILLIS - event.end_PMILLIS; } } }; rowContentPane.mouseDown.on(grabEventEnd); const dragEventEnd = function () { if (eventDragMode === 'end') { const wMin_MILLIS = minEventWidthWhenDraggingEdge * timeAxis.vSize / rowContentPane.viewport.w; const maxSnapShift_MILLIS = snapToDistance * (timeAxis.tSize_MILLIS / rowContentPane.viewport.w); let snapShift_MILLIS = null; for (let n = 0; n < eventDragEvents.length; n++) { const event = eventDragEvents[n]; const newEnd_PMILLIS = eventDragPointer_PMILLIS - eventDragOffsets_MILLIS[event.eventGuid]; const eventSnapShift_MILLIS = findSnapShift_MILLIS(newEnd_PMILLIS, maxSnapShift_MILLIS); if (hasval(eventSnapShift_MILLIS)) { if (!hasval(snapShift_MILLIS) || Math.abs(eventSnapShift_MILLIS) < Math.abs(snapShift_MILLIS)) { snapShift_MILLIS = eventSnapShift_MILLIS; } } } if (!hasval(snapShift_MILLIS)) { snapShift_MILLIS = 0; } for (let n = 0; n < eventDragEvents.length; n++) { const event = eventDragEvents[n]; const newEnd_PMILLIS = eventDragPointer_PMILLIS - eventDragOffsets_MILLIS[event.eventGuid] + snapShift_MILLIS; event.end_PMILLIS = Math.trunc(Math.max(event.start_PMILLIS + wMin_MILLIS, newEnd_PMILLIS)); } } }; rowContentPane.mouseMove.on(dragEventEnd); timeAxis.limitsChanged.on(dragEventEnd); // Finish event-drag // rowContentPane.mouseUp.on(function (ev) { eventDragEvents = []; eventDragOffsets_MILLIS = {}; eventDragSnapTimes_PMILLIS = []; eventDragPointer_PMILLIS = null; eventDragMode = null; }); rowContentPane.dispose.on(function () { lanes.dispose(); timeAxis.limitsChanged.off(dragEventEnd); timeAxis.limitsChanged.off(dragEventStart); ui.millisPerPx.changed.off(uiMillisPerPxChanged); ui.millisPerPx.changed.off(updateCursor); selection.hoveredTime_PMILLIS.changed.off(updateCursor); selection.hoveredEvent.changed.off(updateCursor); row.eventGuids.valueAdded.off(redraw); row.eventGuids.valueMoved.off(redraw); row.eventGuids.valueRemoved.off(redraw); row.eventGuids.valueRemoved.off(removeRedraw); row.eventGuids.valueAdded.off(watchEventAttrs); row.eventGuids.forEach(function (eventGuid) { model.event(eventGuid).attrsChanged.off(redraw); }); }); return rowContentPane; }; } function eventLimitsPainterHelper(limitsOpts, drawable, timeAxis, lanes, ui, options) { const rowTopPadding = options.rowTopPadding; const rowBottomPadding = options.rowBottomPadding; const laneHeight = options.laneHeight; const lineColor = (hasval(limitsOpts) && hasval(limitsOpts.lineColor) ? limitsOpts.lineColor : new Color(1, 0, 0, 1)); const lineThickness = (hasval(limitsOpts) && hasval(limitsOpts.lineThickness) ? limitsOpts.lineThickness : 2.5); const xyFrac_vColor_VERTSHADER = concatLines(' ', ' attribute vec2 a_XyFrac; ', ' attribute vec4 a_Color; ', ' ', ' varying vec4 v_Color; ', ' ', ' void main( ) { ', ' gl_Position = vec4( ( -1.0 + 2.0*a_XyFrac ), 0.0, 1.0 ); ', ' v_Color = a_Color; ', ' } ', ' '); const program = new Program(xyFrac_vColor_VERTSHADER, varyingColor_FRAGSHADER); const a_XyFrac = new Attribute(program, 'a_XyFrac'); const a_Color = new Attribute(program, 'a_Color'); let xys = new Float32Array(0); const xysBuffer = newDynamicBuffer(); let rgbas = new Float32Array(0); const rgbasBuffer = newDynamicBuffer(); return { paint(indexXys, indexRgbas, gl, viewport) { if (indexXys > 0) { gl.blendFuncSeparate(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA, GL.ONE, GL.ONE_MINUS_SRC_ALPHA); gl.enable(GL.BLEND); program.use(gl); xysBuffer.setData(xys.subarray(0, indexXys)); a_XyFrac.setDataAndEnable(gl, xysBuffer, 2, GL.FLOAT); rgbasBuffer.setData(rgbas.subarray(0, indexRgbas)); a_Color.setDataAndEnable(gl, rgbasBuffer, 4, GL.FLOAT); gl.drawArrays(GL.TRIANGLES, 0, Math.floor(indexXys / 2)); a_Color.disable(gl); a_XyFrac.disable(gl); program.endUse(gl); } }, ensureCapacity: function (eventCount) { const numVertices = (6 * 3 /* triangles */ * eventCount); xys = ensureCapacityFloat32(xys, 2 * numVertices); rgbas = ensureCapacityFloat32(rgbas, 4 * numVertices); }, fillEvent: function (laneIndex, eventIndex, indexXys, indexRgbas, viewport) { const lane = lanes.lane(laneIndex); const event = lane.event(eventIndex); const wLine = lineThickness / viewport.w; const hLine = lineThickness / viewport.h; const jTop = rowTopPadding + (laneIndex) * laneHeight; const yTop = (viewport.h - jTop) / viewport.h; const jBottom = rowTopPadding + (laneIndex + 1) * laneHeight; const yBottom = (viewport.h - jBottom) / viewport.h; const yMid = (yTop + yBottom) / 2; const xLeft = hasval(event.startLimit_PMILLIS) ? timeAxis.tFrac(event.startLimit_PMILLIS) : 0; const xRight = hasval(event.endLimit_PMILLIS) ? timeAxis.tFrac(event.endLimit_PMILLIS) : 1; indexXys = putQuadXys(xys, indexXys, xLeft, xRight, yMid - hLine / 2, yMid + hLine / 2); indexXys = putQuadXys(xys, indexXys, xLeft, xLeft - wLine, yTop, yBottom); indexXys = putQuadXys(xys, indexXys, xRight, xRight + wLine, yTop, yBottom); indexRgbas = putRgbas(rgbas, indexRgbas, lineColor, 18); return { indexXys: indexXys, indexRgbas: indexRgbas }; } }; } export function newEventLimitsPainterFactory(limitOpts) { // Painter Factory return function (drawable, timeAxis, lanes, ui, options) { const helper = eventLimitsPainterHelper(limitOpts, drawable, timeAxis, lanes, ui, options); // Painter return function (gl, viewport) { const selectedEvents = ui.selection.selectedEvents; // XXX Instead of estimating the number of events we will need to draw ahead of time // XXX (difficult because selected events may be present in multiple lanes, so // XXX selectedEvents.length might not be sufficient) just make enough space for all events. // XXX Potentially quite inefficient with lots of events (and few selected events). helper.ensureCapacity(lanes.numEvents); let indexXys = 0; let indexRgbas = 0; for (let l = 0; l < lanes.length; l++) { const lane = lanes.lane(l); for (let e = 0; e < lane.length; e++) { const event = lane.event(e); // check whether the event is selected and has limits defined if (selectedEvents.hasId(event.eventGuid) && (hasval(event.startLimit_PMILLIS) || hasval(event.endLimit_PMILLIS))) { const indexes = helper.fillEvent(l, e, indexXys, indexRgbas, viewport); indexXys = indexes.indexXys; indexRgbas = indexes.indexRgbas; } } } helper.paint(indexXys, indexRgbas, gl, viewport); }; }; } export var JointType; (function (JointType) { JointType[JointType["BEVEL"] = 0] = "BEVEL"; JointType[JointType["MITER"] = 1] = "MITER"; })(JointType || (JointType = {})); export var FillPattern; (function (FillPattern) { FillPattern[FillPattern["solid"] = 0] = "solid"; FillPattern[FillPattern["stripe"] = 1] = "stripe"; FillPattern[FillPattern["gradient"] = 2] = "gradient"; })(FillPattern || (FillPattern = {})); function eventStripedBarPainterHelper(barOpts, drawable, timeAxis, lanes, ui, options) { const rowTopPadding = options.rowTopPadding; const rowBottomPadding = options.rowBottomPadding; const laneHeight = options.laneHeight; const topMargin = (hasval(barOpts) && hasval(barOpts.topMargin) ? barOpts.topMargin : 1.2); const bottomMargin = (hasval(barOpts) && hasval(barOpts.bottomMargin) ? barOpts.bottomMargin : 1.2); const borderThickness = (hasval(barOpts) && hasval(barOpts.borderThickness) ? barOpts.borderThickness : 2); const cornerType = (hasval(barOpts) && hasval(barOpts.cornerType) ? barOpts.cornerType : JointType.BEVEL); const defaultColor = (hasval(barOpts) && hasval(barOpts.defaultColor) ? barOpts.defaultColor : options.timelineFgColor.withAlphaTimes(0.4)); const defaultColorSecondary = new Color(1, 1, 1, 1); const minimumVisibleWidth = (hasval(barOpts) && hasval(barOpts.minimumVisibleWidth) ? barOpts.minimumVisibleWidth : 0); const stripeWidth = (hasval(barOpts) && hasval(barOpts.stripeWidth) ? barOpts.stripeWidth : 5); const stripeSecondaryWidth = (hasval(barOpts) && hasval(barOpts.stripeSecondaryWidth) ? barOpts.stripeSecondaryWidth : 5); const stripeSlant = (hasval(barOpts) && hasval(barOpts.stripeSlant) ? barOpts.stripeSlant : 1); const featherWidth = (hasval(barOpts) && hasval(barOpts.featherWidth) ? barOpts.featherWidth : 2); const selection = ui.selection; const xyFrac_vColor_VERTSHADER = concatLines(' ', ' attribute vec2 a_XyFrac; ', ' attribute vec4 a_Color; ', ' attribute vec4 a_ColorSecondary; ', ' attribute vec2 a_relativeXy; ', ' attribute float a_fillPattern; ', ' ', ' varying vec4 v_Color; ', ' varying vec4 v_ColorSecondary; ', ' varying vec2 v_relativeXy; ', ' varying float v_fillPattern; ', ' ', ' void main( ) { ', ' gl_Position = vec4( ( -1.0 + 2.0*a_XyFrac ), 0.0, 1.0 ); ', ' v_Color = a_Color; ', ' v_ColorSecondary = a_ColorSecondary; ', ' v_relativeXy = a_relativeXy; ', ' v_fillPattern = a_fillPattern; ', ' } ', ' '); const fillPattern_FRAGSHADER = concatLines(' #define PI 3.1415926535897932384626433832795 ', ' ', ' precision lowp float; ', ' // the width in pixels of the first color stripe ', ' uniform float u_stripeWidth; ', ' // the width in pixels of the second color stripe ', ' uniform float u_stripeSecondaryWidth; ', ' // the slant of the stipes: 0 = horizontal, 1 = 45 degrees ', ' uniform float u_slant; ', ' // width in pixels of the antialiasing of the slant ', ' uniform float u_featherWidth; ', ' ', ' varying vec4 v_Color; ', ' varying vec4 v_ColorSecondary; ', ' varying vec2 v_relativeXy; ', ' varying float v_fillPattern; ', ' ', ' void pattern_stripe( ) { ', ' float stripeWidthTotal = u_stripeWidth + u_stripeSecondaryWidth; ', ' ', ' // calculate the value indicating where we are in the stripe pattern ', ' float stripeCoord = mod( v_relativeXy.x + u_slant * v_relativeXy.y , stripeWidthTotal ); ', ' ', ' // we are in the feather region beween the two stripes ', ' if ( stripeCoord < u_featherWidth ) { ', ' float diff = stripeCoord / u_featherWidth; ', ' gl_FragColor = vec4 ( v_Color.xyz * diff + (1.0-diff) * v_ColorSecondary.xyz, 1.0 ); ', ' } ', ' // we are in the color 1 stripe ', ' else if ( stripeCoord < u_stripeWidth ) { ', ' gl_FragColor = v_Color; ', ' } ', ' // we are the feather region between the two stripes ', ' else if ( stripeCoord < u_stripeWidth + u_featherWidth ) { ', ' float diff = ( stripeCoord - u_stripeWidth ) / u_featherWidth; ', ' gl_FragColor = vec4 ( v_Color.xyz * (1.0-diff) + diff * v_ColorSecondary.xyz, 1.0 ); ', ' } ', ' // we are in the color 2 stripe ', ' else { ', ' gl_FragColor = v_ColorSecondary; ', ' } ', ' } ', ' ', ' void pattern_gradient( ) { ', ' float stripeWidthTotal = u_stripeWidth + u_stripeSecondaryWidth; ', ' ', ' // calculate the value indicating where we are in the stripe pattern ', ' float stripeCoord = mod( v_relativeXy.x + u_slant * v_relativeXy.y , stripeWidthTotal ); ', ' ', ' float weightedCoord; ', ' if ( stripeCoord < u_stripeWidth ) { ', ' float slope = PI / u_stripeWidth; ', ' weightedCoord = slope * stripeCoord; ', ' } ', ' else { ', ' float slope = PI / u_stripeSecondaryWidth; ', ' weightedCoord = PI + slope * ( stripeCoord - u_stripeWidth ); ', ' } ', ' ', ' // sin wave domain: [0, stripeWidthTotal ] range: [0, 1] ', ' float frac = sin( weightedCoord ) * 2.0 - 1.0; ', ' ', ' // mix primary and secondary colors based on gradient fraction ', ' gl_FragColor = mix( v_Color, v_ColorSecondary, frac ); ', ' } ', ' ', ' void pattern_solid( ) { ', ' gl_FragColor = v_Color; ', ' } ', ' ', ' void main( ) { ', ' if ( v_fillPattern == 1.0 ) { ', ' pattern_stripe( ); ', ' } ', ' else if ( v_fillPattern == 2.0 ) { ', ' pattern_gradient( ); ', ' } ', ' else { ', ' pattern_solid( ); ', ' } ', ' } ', ' ', ' ', ' '); const program = new Program(xyFrac_vColor_VERTSHADER, fillPattern_FRAGSHADER); const a_XyFrac = new Attribute(program, 'a_XyFrac'); const a_Color = new Attribute(program, 'a_Color'); const a_ColorSecondary = new Attribute(program, 'a_ColorSecondary'); const a_relativeXy = new Attribute(program, 'a_relativeXy'); const a_fillPattern = new Attribute(program, 'a_fillPattern'); const u_stripeWidth = new Uniform1f(program, 'u_stripeWidth'); const u_stripeSecondaryWidth = new Uniform1f(program, 'u_stripeSecondaryWidth'); const u_slant = new Uniform1f(program, 'u_slant'); const u_featherWidth = new Uniform1f(program, 'u_featherWidth'); let xys = new Float32Array(0); const xysBuffer = newDynamicBuffer(); let rgbas = new Float32Array(0); const rgbasBuffer = newDynamicBuffer(); let rgbasSecondary = new Float32Array(0); const rgbasSecondaryBuffer = newDynamicBuffer(); let relativeXys = new Float32Array(0); const relativeXysBuffer = newDynamicBuffer(); let fillPattern = new Float32Array(0); const fillPatternBuffer = newDynamicBuffer(); return { paint(indexXys, indexRgbas, gl, viewport, indexRelativeXys, indexFillPattern) { if (indexXys === 0 || indexRgbas === 0) { return; } gl.blendFuncSeparate(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA, GL.ONE, GL.ONE_MINUS_SRC_ALPHA); gl.enable(GL.BLEND); program.use(gl); u_slant.setData(gl, stripeSlant); u_stripeWidth.setData(gl, stripeWidth); u_stripeSecondaryWidth.setData(gl, stripeSecondaryWidth); u_featherWidth.setData(gl, featherWidth); xysBuffer.setData(xys.subarray(0, indexXys)); a_XyFrac.setDataAndEnable(gl, xysBuffer, 2, GL.FLOAT); rgbasBuffer.setData(rgbas.subarray(0, indexRgbas)); a_Color.setDataAndEnable(gl, rgbasBuffer, 4, GL.FLOAT); rgbasSecondaryBuffer.setData(rgbasSecondary.subarray(0, indexRgbas)); a_ColorSecondary.setDataAndEnable(gl, rgbasSecondaryBuffer, 4, GL.FLOAT); relativeXysBuffer.setData(relativeXys.subarray(0, indexRelativeXys)); a_relativeXy.setDataAndEnable(gl, relativeXysBuffer, 2, GL.FLOAT); fillPatternBuffer.setData(fillPattern.subarray(0, indexFillPattern)); a_fillPattern.setDataAndEnable(gl, fillPatternBuffer, 1, GL.FLOAT); gl.drawArrays(GL.TRIANGLES, 0, Math.floor(indexXys / 2)); a_Color.disable(gl); a_XyFrac.disable(gl); a_ColorSecondary.disable(gl); a_fillPattern.disable(gl); a_relativeXy.disable(gl); program.endUse(gl); }, ensureCapacity: function (eventCount) { const numVertices = (6 * (1 /*quads*/)) * eventCount; xys = ensureCapacityFloat32(xys, 2 * numVertices); rgbas = ensureCapacityFloat32(rgbas, 4 * numVertices); rgbasSecondary = ensureCapacityFloat32(rgbasSecondary, 4 * numVertices); relativeXys = ensureCapacityFloat32(relativeXys, 2 * numVertices); fillPattern = ensureCapacityFloat32(fillPattern, numVertices); }, fillEvent: function (laneIndex, eventIndex, indexXys, indexRgbas, viewport, indexRelativeXys, indexFillPattern) { const lane = lanes.lane(laneIndex); const event = lane.event(eventIndex); const wBorder = borderThickness / viewport.w; const hBorder = borderThickness / viewport.h; const _topMargin = hasval(event.topMargin) ? event.topMargin : topMargin; const _bottomMargin = hasval(event.bottomMargin) ? event.bottomMargin : bottomMargin; const jTop = rowTopPadding + (laneIndex) * laneHeight + _topMargin; const yTop = (viewport.h - jTop) / viewport.h; const jBottom = rowTopPadding + (laneIndex + 1) * laneHeight - _bottomMargin; const yBottom = (viewport.h - jBottom) / viewport.h; const xLeft = timeAxis.tFrac(event.start_PMILLIS); const xRight = timeAxis.tFrac(event.end_PMILLIS); const xWidthPixels = viewport.w * (xRight - xLeft); const yHeightPixels = jTop - jBottom; if (!(xRight < 0 || xLeft > 1) && xWidthPixels > minimumVisibleWidth) { // Fill let fillColor = (event.bgColor || defaultColor); let fillColorSecondary = (event.bgSecondaryColor || defaultColorSecondary); if (event === selection.hoveredEvent.value) { fillColor = darker(fillColor, 0.8); fillColorSecondary = darker(fillColorSecondary, 0.8); } indexXys = putQuadXys(xys, indexXys, xLeft + wBorder, xRight - wBorder, yTop - hBorder, yBottom + hBorder); const startIndex = indexRgbas; putQuadRgbas(rgbas, startIndex, fillColor); indexRgbas = putQuadRgbas(rgbasSecondary, startIndex, fillColorSecondary); // create a quad with relative coordinates indexRelativeXys = putQuadXys(relativeXys, indexRelativeXys, 0.0, xWidthPixels, 0.0, yHeightPixels); // Set the fillPatternValue per vertex of the quad const fillPatternValue = event.fillPattern; fillPattern[indexFillPattern++] = fillPatternValue; fillPattern[indexFillPattern++] = fillPatternValue; fillPattern[indexFillPattern++] = fillPatternValue; fillPattern[indexFillPattern++] = fillPatternValue; fillPattern[indexFillPattern++] = fillPatternValue; fillPattern[indexFillPattern++] = fillPatternValue; } return { indexXys: indexXys, indexRgbas: indexRgbas, indexRelativeXys: indexRelativeXys, indexFillPattern: indexFillPattern }; } }; } function eventDashedBorderPainterHelper(barOpts, drawable, timeAxis, lanes, ui, options) { const rowTopPadding = options.rowTopPadding; const rowBottomPadding = options.rowBottomPadding; const laneHeight = options.laneHeight; const topMargin = (hasval(barOpts) && hasval(barOpts.topMargin) ? barOpts.topMargin : 1.2); const bottomMargin = (hasval(barOpts) && hasval(barOpts.bottomMargin) ? barOpts.bottomMargin : 1.2); const borderThickness = (hasval(barOpts) && hasval(barOpts.borderThickness) ? barOpts.borderThickness : 2); const cornerType = (hasval(barOpts) && hasval(barOpts.cornerType) ? barOpts.cornerType : JointType.BEVEL); const defaultColor = (hasval(barOpts) && hasval(barOpts.defaultColor) ? barOpts.defaultColor : options.timelineFgColor.withAlphaTimes(0.4)); const defaultBorderColor = (hasval(barOpts) && hasval(barOpts.defaultBorderColor) ? barOpts.defaultBorderColor : null); const selectedBorderColor = (hasval(barOpts) && hasval(barOpts.selectedBorderColor) ? barOpts.selectedBorderColor : null); const minimumVisibleWidth = (hasval(barOpts) && hasval(barOpts.minimumVisibleWidth) ? barOpts.minimumVisibleWidth : 0); const dashLength = (hasval(barOpts) && hasval(barOpts.dashLength) ? barOpts.dashLength : 5); const defaultSecondaryColor = new Color(0, 0, 0, 0); const selection = ui.selection; const dashedBorder_VERTSHADER = concatLines(' ', ' attribute vec2 a_XyFrac; ', ' attribute vec4 a_Color; ', ' attribute vec4 a_SecondaryColor; ', ' attribute float a_LengthSoFar; ', ' ', ' varying vec4 v_Color; ', ' varying vec4 v_SecondaryColor; ', ' varying float f_LengthSoFar; ', ' ', ' void main( ) { ', ' gl_Position = vec4( ( -1.0 + 2.0*a_XyFrac ), 0.0, 1.0 ); ', ' v_Color = a_Color; ', ' v_SecondaryColor = a_SecondaryColor; ', ' f_LengthSoFar = a_LengthSoFar; ', ' } ', ' '); const varyingBorder_FRAGSHADER = concatLines(' ', ' precision lowp float; ', ' varying vec4 v_Color; ', ' varying vec4 v_SecondaryColor; ', ' varying float f_LengthSoFar; ', ' //dashes are u_DashLength_PX pixels long ', ' uniform float u_DashLength_PX; ', ' ', ' void main( ) { ', ' gl_FragColor = v_Color; ', ' ', ' if (f_LengthSoFar > 0.0) { ', ' float mod = mod(f_LengthSoFar, u_DashLength_PX * 2.0); ', ' float alpha = 1.0; ', ' if ( mod < u_DashLength_PX ) { ', ' gl_FragColor = v_SecondaryColor; ', ' }