StackGenVis: Alignment of Data, Algorithms, and Models for Stacking Ensemble Learning Using Performance Metrics https://doi.org/10.1109/TVCG.2020.3030352
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
StackGenVis/frontend/node_modules/regl-line2d/index.js

712 lines
16 KiB

'use strict'
const rgba = require('color-normalize')
const getBounds = require('array-bounds')
const extend = require('object-assign')
const glslify = require('glslify')
const pick = require('pick-by-alias')
const flatten = require('flatten-vertex-data')
const triangulate = require('earcut')
const normalize = require('array-normalize')
const { float32, fract32 } = require('to-float32')
const WeakMap = require('es6-weak-map')
const parseRect = require('parse-rect')
module.exports = Line2D
/** @constructor */
function Line2D (regl, options) {
if (!(this instanceof Line2D)) return new Line2D(regl, options)
if (typeof regl === 'function') {
if (!options) options = {}
options.regl = regl
}
else {
options = regl
}
if (options.length) options.positions = options
regl = options.regl
if (!regl.hasExtension('ANGLE_instanced_arrays')) {
throw Error('regl-error2d: `ANGLE_instanced_arrays` extension should be enabled');
}
// persistent variables
this.gl = regl._gl
this.regl = regl
// list of options for lines
this.passes = []
// cached shaders instance
this.shaders = Line2D.shaders.has(regl) ? Line2D.shaders.get(regl) : Line2D.shaders.set(regl, Line2D.createShaders(regl)).get(regl)
// init defaults
this.update(options)
}
Line2D.dashMult = 2
Line2D.maxPatternLength = 256
Line2D.precisionThreshold = 3e6
Line2D.maxPoints = 1e4
Line2D.maxLines = 2048
// cache of created draw calls per-regl instance
Line2D.shaders = new WeakMap()
// create static shaders once
Line2D.createShaders = function (regl) {
let offsetBuffer = regl.buffer({
usage: 'static',
type: 'float',
data: [0,1, 0,0, 1,1, 1,0]
})
let shaderOptions = {
primitive: 'triangle strip',
instances: regl.prop('count'),
count: 4,
offset: 0,
uniforms: {
miterMode: (ctx, prop) => prop.join === 'round' ? 2 : 1,
miterLimit: regl.prop('miterLimit'),
scale: regl.prop('scale'),
scaleFract: regl.prop('scaleFract'),
translateFract: regl.prop('translateFract'),
translate: regl.prop('translate'),
thickness: regl.prop('thickness'),
dashPattern: regl.prop('dashTexture'),
opacity: regl.prop('opacity'),
pixelRatio: regl.context('pixelRatio'),
id: regl.prop('id'),
dashSize: regl.prop('dashLength'),
viewport: (c, p) => [p.viewport.x, p.viewport.y, c.viewportWidth, c.viewportHeight],
depth: regl.prop('depth')
},
blend: {
enable: true,
color: [0,0,0,0],
equation: {
rgb: 'add',
alpha: 'add'
},
func: {
srcRGB: 'src alpha',
dstRGB: 'one minus src alpha',
srcAlpha: 'one minus dst alpha',
dstAlpha: 'one'
}
},
depth: {
enable: (c, p) => {
return !p.overlay
}
},
stencil: {enable: false},
scissor: {
enable: true,
box: regl.prop('viewport')
},
viewport: regl.prop('viewport')
}
// simplified rectangular line shader
let drawRectLine = regl(extend({
vert: glslify('./rect-vert.glsl'),
frag: glslify('./rect-frag.glsl'),
attributes: {
// if point is at the end of segment
lineEnd: {
buffer: offsetBuffer,
divisor: 0,
stride: 8,
offset: 0
},
// if point is at the top of segment
lineTop: {
buffer: offsetBuffer,
divisor: 0,
stride: 8,
offset: 4
},
// beginning of line coordinate
aCoord: {
buffer: regl.prop('positionBuffer'),
stride: 8,
offset: 8,
divisor: 1
},
// end of line coordinate
bCoord: {
buffer: regl.prop('positionBuffer'),
stride: 8,
offset: 16,
divisor: 1
},
aCoordFract: {
buffer: regl.prop('positionFractBuffer'),
stride: 8,
offset: 8,
divisor: 1
},
bCoordFract: {
buffer: regl.prop('positionFractBuffer'),
stride: 8,
offset: 16,
divisor: 1
},
color: {
buffer: regl.prop('colorBuffer'),
stride: 4,
offset: 0,
divisor: 1
}
}
}, shaderOptions))
// create regl draw
let drawMiterLine
try {
drawMiterLine = regl(extend({
// culling removes polygon creasing
cull: {
enable: true,
face: 'back'
},
vert: glslify('./miter-vert.glsl'),
frag: glslify('./miter-frag.glsl'),
attributes: {
// is line end
lineEnd: {
buffer: offsetBuffer,
divisor: 0,
stride: 8,
offset: 0
},
// is line top
lineTop: {
buffer: offsetBuffer,
divisor: 0,
stride: 8,
offset: 4
},
// left color
aColor: {
buffer: regl.prop('colorBuffer'),
stride: 4,
offset: 0,
divisor: 1
},
// right color
bColor: {
buffer: regl.prop('colorBuffer'),
stride: 4,
offset: 4,
divisor: 1
},
prevCoord: {
buffer: regl.prop('positionBuffer'),
stride: 8,
offset: 0,
divisor: 1
},
aCoord: {
buffer: regl.prop('positionBuffer'),
stride: 8,
offset: 8,
divisor: 1
},
bCoord: {
buffer: regl.prop('positionBuffer'),
stride: 8,
offset: 16,
divisor: 1
},
nextCoord: {
buffer: regl.prop('positionBuffer'),
stride: 8,
offset: 24,
divisor: 1
}
}
}, shaderOptions))
} catch (e) {
// IE/bad Webkit fallback
drawMiterLine = drawRectLine
}
// fill shader
let drawFill = regl({
primitive: 'triangle',
elements: (ctx, prop) => prop.triangles,
offset: 0,
vert: glslify('./fill-vert.glsl'),
frag: glslify('./fill-frag.glsl'),
uniforms: {
scale: regl.prop('scale'),
color: regl.prop('fill'),
scaleFract: regl.prop('scaleFract'),
translateFract: regl.prop('translateFract'),
translate: regl.prop('translate'),
opacity: regl.prop('opacity'),
pixelRatio: regl.context('pixelRatio'),
id: regl.prop('id'),
viewport: (ctx, prop) => [prop.viewport.x, prop.viewport.y, ctx.viewportWidth, ctx.viewportHeight]
},
attributes: {
position: {
buffer: regl.prop('positionBuffer'),
stride: 8,
offset: 8
},
positionFract: {
buffer: regl.prop('positionFractBuffer'),
stride: 8,
offset: 8
}
},
blend: shaderOptions.blend,
depth: { enable: false },
scissor: shaderOptions.scissor,
stencil: shaderOptions.stencil,
viewport: shaderOptions.viewport
})
return {
fill: drawFill, rect: drawRectLine, miter: drawMiterLine
}
}
// used to for new lines instances
Line2D.defaults = {
dashes: null,
join: 'miter',
miterLimit: 1,
thickness: 10,
cap: 'square',
color: 'black',
opacity: 1,
overlay: false,
viewport: null,
range: null,
close: false,
fill: null
}
Line2D.prototype.render = function (...args) {
if (args.length) {
this.update(...args)
}
this.draw()
}
Line2D.prototype.draw = function (...args) {
// render multiple polylines via regl batch
(args.length ? args : this.passes).forEach((s, i) => {
// render array pass as a list of passes
if (s && Array.isArray(s)) return this.draw(...s)
if (typeof s === 'number') s = this.passes[s]
if (!(s && s.count > 1 && s.opacity)) return
this.regl._refresh()
if (s.fill && s.triangles && s.triangles.length > 2) {
this.shaders.fill(s)
}
if (!s.thickness) return
// high scale is only available for rect mode with precision
if (s.scale[0] * s.viewport.width > Line2D.precisionThreshold || s.scale[1] * s.viewport.height > Line2D.precisionThreshold) {
this.shaders.rect(s)
}
// thin this.passes or too many points are rendered as simplified rect shader
else if (s.join === 'rect' || (!s.join && (s.thickness <= 2 || s.count >= Line2D.maxPoints))) {
this.shaders.rect(s)
}
else {
this.shaders.miter(s)
}
})
return this
}
Line2D.prototype.update = function (options) {
if (!options) return
if (options.length != null) {
if (typeof options[0] === 'number') options = [{positions: options}]
}
// make options a batch
else if (!Array.isArray(options)) options = [options]
let { regl, gl } = this
// process per-line settings
options.forEach((o, i) => {
let state = this.passes[i]
if (o === undefined) return
// null-argument removes pass
if (o === null) {
this.passes[i] = null
return
}
if (typeof o[0] === 'number') o = {positions: o}
// handle aliases
o = pick(o, {
positions: 'positions points data coords',
thickness: 'thickness lineWidth lineWidths line-width linewidth width stroke-width strokewidth strokeWidth',
join: 'lineJoin linejoin join type mode',
miterLimit: 'miterlimit miterLimit',
dashes: 'dash dashes dasharray dash-array dashArray',
color: 'color colour stroke colors colours stroke-color strokeColor',
fill: 'fill fill-color fillColor',
opacity: 'alpha opacity',
overlay: 'overlay crease overlap intersect',
close: 'closed close closed-path closePath',
range: 'range dataBox',
viewport: 'viewport viewBox',
hole: 'holes hole hollow'
})
// init state
if (!state) {
this.passes[i] = state = {
id: i,
scale: null,
scaleFract: null,
translate: null,
translateFract: null,
count: 0,
hole: [],
depth: 0,
dashLength: 1,
dashTexture: regl.texture({
channels: 1,
data: new Uint8Array([255]),
width: 1,
height: 1,
mag: 'linear',
min: 'linear'
}),
colorBuffer: regl.buffer({
usage: 'dynamic',
type: 'uint8',
data: new Uint8Array()
}),
positionBuffer: regl.buffer({
usage: 'dynamic',
type: 'float',
data: new Uint8Array()
}),
positionFractBuffer: regl.buffer({
usage: 'dynamic',
type: 'float',
data: new Uint8Array()
})
}
o = extend({}, Line2D.defaults, o)
}
if (o.thickness != null) state.thickness = parseFloat(o.thickness)
if (o.opacity != null) state.opacity = parseFloat(o.opacity)
if (o.miterLimit != null) state.miterLimit = parseFloat(o.miterLimit)
if (o.overlay != null) {
state.overlay = !!o.overlay
if (i < Line2D.maxLines) {
state.depth = 2 * (Line2D.maxLines - 1 - i % Line2D.maxLines) / Line2D.maxLines - 1.;
}
}
if (o.join != null) state.join = o.join
if (o.hole != null) state.hole = o.hole
if (o.fill != null) state.fill = !o.fill ? null : rgba(o.fill, 'uint8')
if (o.viewport != null) state.viewport = parseRect(o.viewport)
if (!state.viewport) {
state.viewport = parseRect([
gl.drawingBufferWidth,
gl.drawingBufferHeight
])
}
if (o.close != null) state.close = o.close
// reset positions
if (o.positions === null) o.positions = []
if (o.positions) {
let positions, count
// if positions are an object with x/y
if (o.positions.x && o.positions.y) {
let xPos = o.positions.x
let yPos = o.positions.y
count = state.count = Math.max(
xPos.length,
yPos.length
)
positions = new Float64Array(count * 2)
for (let i = 0; i < count; i++) {
positions[i * 2] = xPos[i]
positions[i * 2 + 1] = yPos[i]
}
}
else {
positions = flatten(o.positions, 'float64')
count = state.count = Math.floor(positions.length / 2)
}
let bounds = state.bounds = getBounds(positions, 2)
// create fill positions
// FIXME: fill positions can be set only along with positions
if (state.fill) {
let pos = []
// filter bad vertices and remap triangles to ensure shape
let ids = {}
let lastId = 0
for (let i = 0, ptr = 0, l = state.count; i < l; i++) {
let x = positions[i*2]
let y = positions[i*2 + 1]
if (isNaN(x) || isNaN(y) || x == null || y == null) {
x = positions[lastId*2]
y = positions[lastId*2 + 1]
ids[i] = lastId
}
else {
lastId = i
}
pos[ptr++] = x
pos[ptr++] = y
}
let triangles = triangulate(pos, state.hole || [])
for (let i = 0, l = triangles.length; i < l; i++) {
if (ids[triangles[i]] != null) triangles[i] = ids[triangles[i]]
}
state.triangles = triangles
}
// update position buffers
let npos = new Float64Array(positions)
normalize(npos, 2, bounds)
let positionData = new Float64Array(count * 2 + 6)
// rotate first segment join
if (state.close) {
if (positions[0] === positions[count*2 - 2] &&
positions[1] === positions[count*2 - 1]) {
positionData[0] = npos[count*2 - 4]
positionData[1] = npos[count*2 - 3]
}
else {
positionData[0] = npos[count*2 - 2]
positionData[1] = npos[count*2 - 1]
}
}
else {
positionData[0] = npos[0]
positionData[1] = npos[1]
}
positionData.set(npos, 2)
// add last segment
if (state.close) {
// ignore coinciding start/end
if (positions[0] === positions[count*2 - 2] &&
positions[1] === positions[count*2 - 1]) {
positionData[count*2 + 2] = npos[2]
positionData[count*2 + 3] = npos[3]
state.count -= 1
}
else {
positionData[count*2 + 2] = npos[0]
positionData[count*2 + 3] = npos[1]
positionData[count*2 + 4] = npos[2]
positionData[count*2 + 5] = npos[3]
}
}
// add stub
else {
positionData[count*2 + 2] = npos[count*2 - 2]
positionData[count*2 + 3] = npos[count*2 - 1]
positionData[count*2 + 4] = npos[count*2 - 2]
positionData[count*2 + 5] = npos[count*2 - 1]
}
state.positionBuffer(float32(positionData))
state.positionFractBuffer(fract32(positionData))
}
if (o.range) {
state.range = o.range
} else if (!state.range) {
state.range = state.bounds
}
if ((o.range || o.positions) && state.count) {
let bounds = state.bounds
let boundsW = bounds[2] - bounds[0],
boundsH = bounds[3] - bounds[1]
let rangeW = state.range[2] - state.range[0],
rangeH = state.range[3] - state.range[1]
state.scale = [
boundsW / rangeW,
boundsH / rangeH
]
state.translate = [
-state.range[0] / rangeW + bounds[0] / rangeW || 0,
-state.range[1] / rangeH + bounds[1] / rangeH || 0
]
state.scaleFract = fract32(state.scale)
state.translateFract = fract32(state.translate)
}
if (o.dashes) {
let dashLength = 0., dashData
if (!o.dashes || o.dashes.length < 2) {
dashLength = 1.
dashData = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255])
}
else {
dashLength = 0.;
for(let i = 0; i < o.dashes.length; ++i) {
dashLength += o.dashes[i]
}
dashData = new Uint8Array(dashLength * Line2D.dashMult)
let ptr = 0
let fillColor = 255
// repeat texture two times to provide smooth 0-step
for (let k = 0; k < 2; k++) {
for(let i = 0; i < o.dashes.length; ++i) {
for(let j = 0, l = o.dashes[i] * Line2D.dashMult * .5; j < l; ++j) {
dashData[ptr++] = fillColor
}
fillColor ^= 255
}
}
}
state.dashLength = dashLength
state.dashTexture({
channels: 1,
data: dashData,
width: dashData.length,
height: 1,
mag: 'linear',
min: 'linear'
}, 0, 0)
}
if (o.color) {
let count = state.count
let colors = o.color
if (!colors) colors = 'transparent'
let colorData = new Uint8Array(count * 4 + 4)
// convert colors to typed arrays
if (!Array.isArray(colors) || typeof colors[0] === 'number') {
let c = rgba(colors, 'uint8')
for (let i = 0; i < count + 1; i++) {
colorData.set(c, i * 4)
}
} else {
for (let i = 0; i < count; i++) {
let c = rgba(colors[i], 'uint8')
colorData.set(c, i * 4)
}
colorData.set(rgba(colors[0], 'uint8'), count * 4)
}
state.colorBuffer({
usage: 'dynamic',
type: 'uint8',
data: colorData
})
}
})
// remove unmentioned passes
if (options.length < this.passes.length) {
for (let i = options.length; i < this.passes.length; i++) {
let pass = this.passes[i]
if (!pass) continue
pass.colorBuffer.destroy()
pass.positionBuffer.destroy()
pass.dashTexture.destroy()
}
this.passes.length = options.length
}
// remove null items
let passes = []
for (let i = 0; i < this.passes.length; i++) {
if (this.passes[i] !== null) passes.push(this.passes[i])
}
this.passes = passes
return this
}
Line2D.prototype.destroy = function () {
this.passes.forEach(pass => {
pass.colorBuffer.destroy()
pass.positionBuffer.destroy()
pass.dashTexture.destroy()
})
this.passes.length = 0
return this
}