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/gl-axes3d/axes.js

607 lines
15 KiB

4 years ago
'use strict'
module.exports = createAxes
var createText = require('./lib/text.js')
var createLines = require('./lib/lines.js')
var createBackground = require('./lib/background.js')
var getCubeProperties = require('./lib/cube.js')
var Ticks = require('./lib/ticks.js')
var identity = new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1])
function copyVec3(a, b) {
a[0] = b[0]
a[1] = b[1]
a[2] = b[2]
return a
}
function Axes(gl) {
this.gl = gl
this.pixelRatio = 1
this.bounds = [ [-10, -10, -10],
[ 10, 10, 10] ]
this.ticks = [ [], [], [] ]
this.autoTicks = true
this.tickSpacing = [ 1, 1, 1 ]
this.tickEnable = [ true, true, true ]
this.tickFont = [ 'sans-serif', 'sans-serif', 'sans-serif' ]
this.tickSize = [ 12, 12, 12 ]
this.tickAngle = [ 0, 0, 0 ]
this.tickAlign = [ 'auto', 'auto', 'auto' ]
this.tickColor = [ [0,0,0,1], [0,0,0,1], [0,0,0,1] ]
this.tickPad = [ 10, 10, 10 ]
this.lastCubeProps = {
cubeEdges: [0,0,0],
axis: [0,0,0]
}
this.labels = [ 'x', 'y', 'z' ]
this.labelEnable = [ true, true, true ]
this.labelFont = 'sans-serif'
this.labelSize = [ 20, 20, 20 ]
this.labelAngle = [ 0, 0, 0 ]
this.labelAlign = [ 'auto', 'auto', 'auto' ]
this.labelColor = [ [0,0,0,1], [0,0,0,1], [0,0,0,1] ]
this.labelPad = [ 10, 10, 10 ]
this.lineEnable = [ true, true, true ]
this.lineMirror = [ false, false, false ]
this.lineWidth = [ 1, 1, 1 ]
this.lineColor = [ [0,0,0,1], [0,0,0,1], [0,0,0,1] ]
this.lineTickEnable = [ true, true, true ]
this.lineTickMirror = [ false, false, false ]
this.lineTickLength = [ 0, 0, 0 ]
this.lineTickWidth = [ 1, 1, 1 ]
this.lineTickColor = [ [0,0,0,1], [0,0,0,1], [0,0,0,1] ]
this.gridEnable = [ true, true, true ]
this.gridWidth = [ 1, 1, 1 ]
this.gridColor = [ [0,0,0,1], [0,0,0,1], [0,0,0,1] ]
this.zeroEnable = [ true, true, true ]
this.zeroLineColor = [ [0,0,0,1], [0,0,0,1], [0,0,0,1] ]
this.zeroLineWidth = [ 2, 2, 2 ]
this.backgroundEnable = [ false, false, false ]
this.backgroundColor = [ [0.8, 0.8, 0.8, 0.5],
[0.8, 0.8, 0.8, 0.5],
[0.8, 0.8, 0.8, 0.5] ]
this._firstInit = true
this._text = null
this._lines = null
this._background = createBackground(gl)
}
var proto = Axes.prototype
proto.update = function(options) {
options = options || {}
//Option parsing helper functions
function parseOption(nest, cons, name) {
if(name in options) {
var opt = options[name]
var prev = this[name]
var next
if(nest ? (Array.isArray(opt) && Array.isArray(opt[0])) :
Array.isArray(opt) ) {
this[name] = next = [ cons(opt[0]), cons(opt[1]), cons(opt[2]) ]
} else {
this[name] = next = [ cons(opt), cons(opt), cons(opt) ]
}
for(var i=0; i<3; ++i) {
if(next[i] !== prev[i]) {
return true
}
}
}
return false
}
var NUMBER = parseOption.bind(this, false, Number)
var BOOLEAN = parseOption.bind(this, false, Boolean)
var STRING = parseOption.bind(this, false, String)
var COLOR = parseOption.bind(this, true, function(v) {
if(Array.isArray(v)) {
if(v.length === 3) {
return [ +v[0], +v[1], +v[2], 1.0 ]
} else if(v.length === 4) {
return [ +v[0], +v[1], +v[2], +v[3] ]
}
}
return [ 0, 0, 0, 1 ]
})
//Tick marks and bounds
var nextTicks
var ticksUpdate = false
var boundsChanged = false
if('bounds' in options) {
var bounds = options.bounds
i_loop:
for(var i=0; i<2; ++i) {
for(var j=0; j<3; ++j) {
if(bounds[i][j] !== this.bounds[i][j]) {
boundsChanged = true
}
this.bounds[i][j] = bounds[i][j]
}
}
}
if('ticks' in options) {
nextTicks = options.ticks
ticksUpdate = true
this.autoTicks = false
for(var i=0; i<3; ++i) {
this.tickSpacing[i] = 0.0
}
} else if(NUMBER('tickSpacing')) {
this.autoTicks = true
boundsChanged = true
}
if(this._firstInit) {
if(!('ticks' in options || 'tickSpacing' in options)) {
this.autoTicks = true
}
//Force tick recomputation on first update
boundsChanged = true
ticksUpdate = true
this._firstInit = false
}
if(boundsChanged && this.autoTicks) {
nextTicks = Ticks.create(this.bounds, this.tickSpacing)
ticksUpdate = true
}
//Compare next ticks to previous ticks, only update if needed
if(ticksUpdate) {
for(var i=0; i<3; ++i) {
nextTicks[i].sort(function(a,b) {
return a.x-b.x
})
}
if(Ticks.equal(nextTicks, this.ticks)) {
ticksUpdate = false
} else {
this.ticks = nextTicks
}
}
//Parse tick properties
BOOLEAN('tickEnable')
if(STRING('tickFont')) {
ticksUpdate = true //If font changes, must rebuild vbo
}
NUMBER('tickSize')
NUMBER('tickAngle')
NUMBER('tickPad')
COLOR('tickColor')
//Axis labels
var labelUpdate = STRING('labels')
if(STRING('labelFont')) {
labelUpdate = true
}
BOOLEAN('labelEnable')
NUMBER('labelSize')
NUMBER('labelPad')
COLOR('labelColor')
//Axis lines
BOOLEAN('lineEnable')
BOOLEAN('lineMirror')
NUMBER('lineWidth')
COLOR('lineColor')
//Axis line ticks
BOOLEAN('lineTickEnable')
BOOLEAN('lineTickMirror')
NUMBER('lineTickLength')
NUMBER('lineTickWidth')
COLOR('lineTickColor')
//Grid lines
BOOLEAN('gridEnable')
NUMBER('gridWidth')
COLOR('gridColor')
//Zero line
BOOLEAN('zeroEnable')
COLOR('zeroLineColor')
NUMBER('zeroLineWidth')
//Background
BOOLEAN('backgroundEnable')
COLOR('backgroundColor')
//Update text if necessary
if(!this._text) {
this._text = createText(
this.gl,
this.bounds,
this.labels,
this.labelFont,
this.ticks,
this.tickFont)
} else if(this._text && (labelUpdate || ticksUpdate)) {
this._text.update(
this.bounds,
this.labels,
this.labelFont,
this.ticks,
this.tickFont)
}
//Update lines if necessary
if(this._lines && ticksUpdate) {
this._lines.dispose()
this._lines = null
}
if(!this._lines) {
this._lines = createLines(this.gl, this.bounds, this.ticks)
}
}
function OffsetInfo() {
this.primalOffset = [0,0,0]
this.primalMinor = [0,0,0]
this.mirrorOffset = [0,0,0]
this.mirrorMinor = [0,0,0]
}
var LINE_OFFSET = [ new OffsetInfo(), new OffsetInfo(), new OffsetInfo() ]
function computeLineOffset(result, i, bounds, cubeEdges, cubeAxis) {
var primalOffset = result.primalOffset
var primalMinor = result.primalMinor
var dualOffset = result.mirrorOffset
var dualMinor = result.mirrorMinor
var e = cubeEdges[i]
//Calculate offsets
for(var j=0; j<3; ++j) {
if(i === j) {
continue
}
var a = primalOffset,
b = dualOffset,
c = primalMinor,
d = dualMinor
if(e & (1<<j)) {
a = dualOffset
b = primalOffset
c = dualMinor
d = primalMinor
}
a[j] = bounds[0][j]
b[j] = bounds[1][j]
if(cubeAxis[j] > 0) {
c[j] = -1
d[j] = 0
} else {
c[j] = 0
d[j] = +1
}
}
}
var CUBE_ENABLE = [0,0,0]
var DEFAULT_PARAMS = {
model: identity,
view: identity,
projection: identity,
_ortho: false
}
proto.isOpaque = function() {
return true
}
proto.isTransparent = function() {
return false
}
proto.drawTransparent = function(params) {}
var ALIGN_OPTION_AUTO = 0 // i.e. as defined in the shader the text would rotate to stay upwards range: [-90,90]
var PRIMAL_MINOR = [0,0,0]
var MIRROR_MINOR = [0,0,0]
var PRIMAL_OFFSET = [0,0,0]
proto.draw = function(params) {
params = params || DEFAULT_PARAMS
var gl = this.gl
//Geometry for camera and axes
var model = params.model || identity
var view = params.view || identity
var projection = params.projection || identity
var bounds = this.bounds
var isOrtho = params._ortho || false
//Unpack axis info
var cubeParams = getCubeProperties(model, view, projection, bounds, isOrtho)
var cubeEdges = cubeParams.cubeEdges
var cubeAxis = cubeParams.axis
var cx = view[12]
var cy = view[13]
var cz = view[14]
var cw = view[15]
var orthoFix = (isOrtho) ? 2 : 1 // double up padding for orthographic ticks & labels
var pixelScaleF = orthoFix * this.pixelRatio * (projection[3]*cx + projection[7]*cy + projection[11]*cz + projection[15]*cw) / gl.drawingBufferHeight
for(var i=0; i<3; ++i) {
this.lastCubeProps.cubeEdges[i] = cubeEdges[i]
this.lastCubeProps.axis[i] = cubeAxis[i]
}
//Compute axis info
var lineOffset = LINE_OFFSET
for(var i=0; i<3; ++i) {
computeLineOffset(
LINE_OFFSET[i],
i,
this.bounds,
cubeEdges,
cubeAxis)
}
//Set up state parameters
var gl = this.gl
//Draw background first
var cubeEnable = CUBE_ENABLE
for(var i=0; i<3; ++i) {
if(this.backgroundEnable[i]) {
cubeEnable[i] = cubeAxis[i]
} else {
cubeEnable[i] = 0
}
}
this._background.draw(
model,
view,
projection,
bounds,
cubeEnable,
this.backgroundColor)
//Draw lines
this._lines.bind(
model,
view,
projection,
this)
//First draw grid lines and zero lines
for(var i=0; i<3; ++i) {
var x = [0,0,0]
if(cubeAxis[i] > 0) {
x[i] = bounds[1][i]
} else {
x[i] = bounds[0][i]
}
//Draw grid lines
for(var j=0; j<2; ++j) {
var u = (i + 1 + j) % 3
var v = (i + 1 + (j^1)) % 3
if(this.gridEnable[u]) {
this._lines.drawGrid(u, v, this.bounds, x, this.gridColor[u], this.gridWidth[u]*this.pixelRatio)
}
}
//Draw zero lines (need to do this AFTER all grid lines are drawn)
for(var j=0; j<2; ++j) {
var u = (i + 1 + j) % 3
var v = (i + 1 + (j^1)) % 3
if(this.zeroEnable[v]) {
//Check if zero line in bounds
if(Math.min(bounds[0][v], bounds[1][v]) <= 0 && Math.max(bounds[0][v], bounds[1][v]) >= 0) {
this._lines.drawZero(u, v, this.bounds, x, this.zeroLineColor[v], this.zeroLineWidth[v]*this.pixelRatio)
}
}
}
}
//Then draw axis lines and tick marks
for(var i=0; i<3; ++i) {
//Draw axis lines
if(this.lineEnable[i]) {
this._lines.drawAxisLine(i, this.bounds, lineOffset[i].primalOffset, this.lineColor[i], this.lineWidth[i]*this.pixelRatio)
}
if(this.lineMirror[i]) {
this._lines.drawAxisLine(i, this.bounds, lineOffset[i].mirrorOffset, this.lineColor[i], this.lineWidth[i]*this.pixelRatio)
}
//Compute minor axes
var primalMinor = copyVec3(PRIMAL_MINOR, lineOffset[i].primalMinor)
var mirrorMinor = copyVec3(MIRROR_MINOR, lineOffset[i].mirrorMinor)
var tickLength = this.lineTickLength
for(var j=0; j<3; ++j) {
var scaleFactor = pixelScaleF / model[5*j]
primalMinor[j] *= tickLength[j] * scaleFactor
mirrorMinor[j] *= tickLength[j] * scaleFactor
}
//Draw axis line ticks
if(this.lineTickEnable[i]) {
this._lines.drawAxisTicks(i, lineOffset[i].primalOffset, primalMinor, this.lineTickColor[i], this.lineTickWidth[i]*this.pixelRatio)
}
if(this.lineTickMirror[i]) {
this._lines.drawAxisTicks(i, lineOffset[i].mirrorOffset, mirrorMinor, this.lineTickColor[i], this.lineTickWidth[i]*this.pixelRatio)
}
}
this._lines.unbind()
//Draw text sprites
this._text.bind(
model,
view,
projection,
this.pixelRatio)
var alignOpt // options in shader are from this list {-1, 0, 1, 2, 3, ..., n}
// -1: backward compatible
// 0: raw data
// 1: auto align, free angles
// 2: auto align, horizontal or vertical
//3-n: auto align, round to n directions e.g. 12 -> round to angles with 30-degree steps
var hv_ratio = 0.5 // can have an effect on the ratio between horizontals and verticals when using option 2
var enableAlign
var alignDir
function alignTo(i) {
alignDir = [0,0,0]
alignDir[i] = 1
}
function solveTickAlignments(i, minor, major) {
var i1 = (i + 1) % 3
var i2 = (i + 2) % 3
var A = minor[i1]
var B = minor[i2]
var C = major[i1]
var D = major[i2]
if ((A > 0) && (D > 0)) { alignTo(i1); return; }
else if ((A > 0) && (D < 0)) { alignTo(i1); return; }
else if ((A < 0) && (D > 0)) { alignTo(i1); return; }
else if ((A < 0) && (D < 0)) { alignTo(i1); return; }
else if ((B > 0) && (C > 0)) { alignTo(i2); return; }
else if ((B > 0) && (C < 0)) { alignTo(i2); return; }
else if ((B < 0) && (C > 0)) { alignTo(i2); return; }
else if ((B < 0) && (C < 0)) { alignTo(i2); return; }
}
for(var i=0; i<3; ++i) {
var minor = lineOffset[i].primalMinor
var major = lineOffset[i].mirrorMinor
var offset = copyVec3(PRIMAL_OFFSET, lineOffset[i].primalOffset)
for(var j=0; j<3; ++j) {
if(this.lineTickEnable[i]) {
offset[j] += pixelScaleF * minor[j] * Math.max(this.lineTickLength[j], 0) / model[5*j]
}
}
var axis = [0,0,0]
axis[i] = 1
//Draw tick text
if(this.tickEnable[i]) {
if(this.tickAngle[i] === -3600) {
this.tickAngle[i] = 0
this.tickAlign[i] = 'auto'
} else {
this.tickAlign[i] = -1
}
enableAlign = 1;
alignOpt = [this.tickAlign[i], hv_ratio, enableAlign]
if(alignOpt[0] === 'auto') alignOpt[0] = ALIGN_OPTION_AUTO
else alignOpt[0] = parseInt('' + alignOpt[0])
alignDir = [0,0,0]
solveTickAlignments(i, minor, major)
//Add tick padding
for(var j=0; j<3; ++j) {
offset[j] += pixelScaleF * minor[j] * this.tickPad[j] / model[5*j]
}
//Draw axis
this._text.drawTicks(
i,
this.tickSize[i],
this.tickAngle[i],
offset,
this.tickColor[i],
axis,
alignDir,
alignOpt)
}
//Draw labels
if(this.labelEnable[i]) {
enableAlign = 0
alignDir = [0,0,0]
if(this.labels[i].length > 4) { // for large label axis enable alignDir to axis
alignTo(i)
enableAlign = 1
}
alignOpt = [this.labelAlign[i], hv_ratio, enableAlign]
if(alignOpt[0] === 'auto') alignOpt[0] = ALIGN_OPTION_AUTO
else alignOpt[0] = parseInt('' + alignOpt[0])
//Add label padding
for(var j=0; j<3; ++j) {
offset[j] += pixelScaleF * minor[j] * this.labelPad[j] / model[5*j]
}
offset[i] += 0.5 * (bounds[0][i] + bounds[1][i])
//Draw axis
this._text.drawLabel(
i,
this.labelSize[i],
this.labelAngle[i],
offset,
this.labelColor[i],
[0,0,0],
alignDir,
alignOpt)
}
}
this._text.unbind()
}
proto.dispose = function() {
this._text.dispose()
this._lines.dispose()
this._background.dispose()
this._lines = null
this._text = null
this._background = null
this.gl = null
}
function createAxes(gl, options) {
var axes = new Axes(gl)
axes.update(options)
return axes
}