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-error2d/index.js

523 lines
11 KiB

'use strict'
const getBounds = require('array-bounds')
const rgba = require('color-normalize')
const updateDiff = require('update-diff')
const pick = require('pick-by-alias')
const extend = require('object-assign')
const flatten = require('flatten-vertex-data')
const {float32, fract32} = require('to-float32')
module.exports = Error2D
const WEIGHTS = [
//direction, lineWidth shift, capSize shift
// x-error bar
[1, 0, 0, 1, 0, 0],
[1, 0, 0, -1, 0, 0],
[-1, 0, 0, -1, 0, 0],
[-1, 0, 0, -1, 0, 0],
[-1, 0, 0, 1, 0, 0],
[1, 0, 0, 1, 0, 0],
// x-error right cap
[1, 0, -1, 0, 0, 1],
[1, 0, -1, 0, 0, -1],
[1, 0, 1, 0, 0, -1],
[1, 0, 1, 0, 0, -1],
[1, 0, 1, 0, 0, 1],
[1, 0, -1, 0, 0, 1],
// x-error left cap
[-1, 0, -1, 0, 0, 1],
[-1, 0, -1, 0, 0, -1],
[-1, 0, 1, 0, 0, -1],
[-1, 0, 1, 0, 0, -1],
[-1, 0, 1, 0, 0, 1],
[-1, 0, -1, 0, 0, 1],
// y-error bar
[0, 1, 1, 0, 0, 0],
[0, 1, -1, 0, 0, 0],
[0, -1, -1, 0, 0, 0],
[0, -1, -1, 0, 0, 0],
[0, 1, 1, 0, 0, 0],
[0, -1, 1, 0, 0, 0],
// y-error top cap
[0, 1, 0, -1, 1, 0],
[0, 1, 0, -1, -1, 0],
[0, 1, 0, 1, -1, 0],
[0, 1, 0, 1, 1, 0],
[0, 1, 0, -1, 1, 0],
[0, 1, 0, 1, -1, 0],
// y-error bottom cap
[0, -1, 0, -1, 1, 0],
[0, -1, 0, -1, -1, 0],
[0, -1, 0, 1, -1, 0],
[0, -1, 0, 1, 1, 0],
[0, -1, 0, -1, 1, 0],
[0, -1, 0, 1, -1, 0]
]
function Error2D (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
let gl = regl._gl, drawErrors, positionBuffer, positionFractBuffer, colorBuffer, errorBuffer, meshBuffer,
defaults = {
color: 'black',
capSize: 5,
lineWidth: 1,
opacity: 1,
viewport: null,
range: null,
offset: 0,
count: 0,
bounds: null,
positions: [],
errors: []
}, groups = []
//color per-point
colorBuffer = regl.buffer({
usage: 'dynamic',
type: 'uint8',
data: new Uint8Array(0)
})
//xy-position per-point
positionBuffer = regl.buffer({
usage: 'dynamic',
type: 'float',
data: new Uint8Array(0)
})
//xy-position float32-fraction
positionFractBuffer = regl.buffer({
usage: 'dynamic',
type: 'float',
data: new Uint8Array(0)
})
//4 errors per-point
errorBuffer = regl.buffer({
usage: 'dynamic',
type: 'float',
data: new Uint8Array(0)
})
//error bar mesh
meshBuffer = regl.buffer({
usage: 'static',
type: 'float',
data: WEIGHTS
})
update(options)
//drawing method
drawErrors = regl({
vert: `
precision highp float;
attribute vec2 position, positionFract;
attribute vec4 error;
attribute vec4 color;
attribute vec2 direction, lineOffset, capOffset;
uniform vec4 viewport;
uniform float lineWidth, capSize;
uniform vec2 scale, scaleFract, translate, translateFract;
varying vec4 fragColor;
void main() {
fragColor = color / 255.;
vec2 pixelOffset = lineWidth * lineOffset + (capSize + lineWidth) * capOffset;
vec2 dxy = -step(.5, direction.xy) * error.xz + step(direction.xy, vec2(-.5)) * error.yw;
vec2 position = position + dxy;
vec2 pos = (position + translate) * scale
+ (positionFract + translateFract) * scale
+ (position + translate) * scaleFract
+ (positionFract + translateFract) * scaleFract;
pos += pixelOffset / viewport.zw;
gl_Position = vec4(pos * 2. - 1., 0, 1);
}
`,
frag: `
precision highp float;
varying vec4 fragColor;
uniform float opacity;
void main() {
gl_FragColor = fragColor;
gl_FragColor.a *= opacity;
}
`,
uniforms: {
range: regl.prop('range'),
lineWidth: regl.prop('lineWidth'),
capSize: regl.prop('capSize'),
opacity: regl.prop('opacity'),
scale: regl.prop('scale'),
translate: regl.prop('translate'),
scaleFract: regl.prop('scaleFract'),
translateFract: regl.prop('translateFract'),
viewport: (ctx, prop) => [prop.viewport.x, prop.viewport.y, ctx.viewportWidth, ctx.viewportHeight]
},
attributes: {
//dynamic attributes
color: {
buffer: colorBuffer,
offset: (ctx, prop) => prop.offset * 4,
divisor: 1,
},
position: {
buffer: positionBuffer,
offset: (ctx, prop) => prop.offset * 8,
divisor: 1
},
positionFract: {
buffer: positionFractBuffer,
offset: (ctx, prop) => prop.offset * 8,
divisor: 1
},
error: {
buffer: errorBuffer,
offset: (ctx, prop) => prop.offset * 16,
divisor: 1
},
//static attributes
direction: {
buffer: meshBuffer,
stride: 24,
offset: 0
},
lineOffset: {
buffer: meshBuffer,
stride: 24,
offset: 8
},
capOffset: {
buffer: meshBuffer,
stride: 24,
offset: 16
}
},
primitive: 'triangles',
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: false
},
scissor: {
enable: true,
box: regl.prop('viewport')
},
viewport: regl.prop('viewport'),
stencil: false,
instances: regl.prop('count'),
count: WEIGHTS.length
})
//expose API
extend(error2d, {
update: update,
draw: draw,
destroy: destroy,
regl: regl,
gl: gl,
canvas: gl.canvas,
groups: groups
})
return error2d
function error2d (opts) {
//update
if (opts) {
update(opts)
}
//destroy
else if (opts === null) {
destroy()
}
draw()
}
//main draw method
function draw (options) {
if (typeof options === 'number') return drawGroup(options)
//make options a batch
if (options && !Array.isArray(options)) options = [options]
regl._refresh()
//render multiple polylines via regl batch
groups.forEach((s, i) => {
if (!s) return
if (options) {
if (!options[i]) s.draw = false
else s.draw = true
}
//ignore draw flag for one pass
if (!s.draw) {
s.draw = true;
return
}
drawGroup(i)
})
}
//draw single error group by id
function drawGroup (s) {
if (typeof s === 'number') s = groups[s]
if (s == null) return
if (!(s && s.count && s.color && s.opacity && s.positions && s.positions.length > 1)) return
s.scaleRatio = [
s.scale[0] * s.viewport.width,
s.scale[1] * s.viewport.height
]
drawErrors(s)
if (s.after) s.after(s)
}
function update (options) {
if (!options) return
//direct points argument
if (options.length != null) {
if (typeof options[0] === 'number') options = [{positions: options}]
}
//make options a batch
else if (!Array.isArray(options)) options = [options]
//global count of points
let pointCount = 0, errorCount = 0
error2d.groups = groups = options.map((options, i) => {
let group = groups[i]
if (!options) return group
else if (typeof options === 'function') options = {after: options}
else if (typeof options[0] === 'number') options = {positions: options}
//copy options to avoid mutation & handle aliases
options = pick(options, {
color: 'color colors fill',
capSize: 'capSize cap capsize cap-size',
lineWidth: 'lineWidth line-width width line thickness',
opacity: 'opacity alpha',
range: 'range dataBox',
viewport: 'viewport viewBox',
errors: 'errors error',
positions: 'positions position data points'
})
if (!group) {
groups[i] = group = {
id: i,
scale: null,
translate: null,
scaleFract: null,
translateFract: null,
draw: true
}
options = extend({}, defaults, options)
}
updateDiff(group, options, [{
lineWidth: v => +v * .5,
capSize: v => +v * .5,
opacity: parseFloat,
errors: errors => {
errors = flatten(errors)
errorCount += errors.length
return errors
},
positions: (positions, state) => {
positions = flatten(positions, 'float64')
state.count = Math.floor(positions.length / 2)
state.bounds = getBounds(positions, 2)
state.offset = pointCount
pointCount += state.count
return positions
}
}, {
color: (colors, state) => {
let count = state.count
if (!colors) colors = 'transparent'
// 'black' or [0,0,0,0] case
if (!Array.isArray(colors) || typeof colors[0] === 'number') {
let color = colors
colors = Array(count)
for (let i = 0; i < count; i++) {
colors[i] = color
}
}
if (colors.length < count) throw Error('Not enough colors')
let colorData = new Uint8Array(count * 4)
//convert colors to float arrays
for (let i = 0; i < count; i++) {
let c = rgba(colors[i], 'uint8')
colorData.set(c, i * 4)
}
return colorData
},
range: (range, state, options) => {
let bounds = state.bounds
if (!range) range = bounds
state.scale = [1 / (range[2] - range[0]), 1 / (range[3] - range[1])]
state.translate = [-range[0], -range[1]]
state.scaleFract = fract32(state.scale)
state.translateFract = fract32(state.translate)
return range
},
viewport: vp => {
let viewport
if (Array.isArray(vp)) {
viewport = {
x: vp[0],
y: vp[1],
width: vp[2] - vp[0],
height: vp[3] - vp[1]
}
}
else if (vp) {
viewport = {
x: vp.x || vp.left || 0,
y: vp.y || vp.top || 0
}
if (vp.right) viewport.width = vp.right - viewport.x
else viewport.width = vp.w || vp.width || 0
if (vp.bottom) viewport.height = vp.bottom - viewport.y
else viewport.height = vp.h || vp.height || 0
}
else {
viewport = {
x: 0, y: 0,
width: gl.drawingBufferWidth,
height: gl.drawingBufferHeight
}
}
return viewport
}
}])
return group
})
if (pointCount || errorCount) {
let len = groups.reduce((acc, group, i) => {
return acc + (group ? group.count : 0)
}, 0)
let positionData = new Float64Array(len * 2)
let colorData = new Uint8Array(len * 4)
let errorData = new Float32Array(len * 4)
groups.forEach((group, i) => {
if (!group) return
let {positions, count, offset, color, errors} = group
if (!count) return
colorData.set(color, offset * 4)
errorData.set(errors, offset * 4)
positionData.set(positions, offset * 2)
})
positionBuffer(float32(positionData))
positionFractBuffer(fract32(positionData))
colorBuffer(colorData)
errorBuffer(errorData)
}
}
function destroy () {
positionBuffer.destroy()
positionFractBuffer.destroy()
colorBuffer.destroy()
errorBuffer.destroy()
meshBuffer.destroy()
}
}