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.
316 lines
7.5 KiB
316 lines
7.5 KiB
'use strict'
|
|
|
|
module.exports = createHeatmap2D
|
|
|
|
var bsearch = require('binary-search-bounds')
|
|
var iota = require('iota-array')
|
|
var pool = require('typedarray-pool')
|
|
var createShader = require('gl-shader')
|
|
var createBuffer = require('gl-buffer')
|
|
|
|
var shaders = require('./lib/shaders')
|
|
|
|
function GLHeatmap2D (
|
|
plot,
|
|
shader,
|
|
pickShader,
|
|
positionBuffer,
|
|
weightBuffer,
|
|
colorBuffer,
|
|
idBuffer) {
|
|
this.plot = plot
|
|
this.shader = shader
|
|
this.pickShader = pickShader
|
|
this.positionBuffer = positionBuffer
|
|
this.weightBuffer = weightBuffer
|
|
this.colorBuffer = colorBuffer
|
|
this.idBuffer = idBuffer
|
|
this.xData = []
|
|
this.yData = []
|
|
this.shape = [0, 0]
|
|
this.bounds = [Infinity, Infinity, -Infinity, -Infinity]
|
|
this.pickOffset = 0
|
|
}
|
|
|
|
var proto = GLHeatmap2D.prototype
|
|
|
|
var WEIGHTS = [
|
|
0, 0,
|
|
1, 0,
|
|
0, 1,
|
|
1, 0,
|
|
1, 1,
|
|
0, 1
|
|
]
|
|
|
|
proto.draw = (function () {
|
|
var MATRIX = [
|
|
1, 0, 0,
|
|
0, 1, 0,
|
|
0, 0, 1
|
|
]
|
|
|
|
return function () {
|
|
var plot = this.plot
|
|
var shader = this.shader
|
|
var bounds = this.bounds
|
|
var numVertices = this.numVertices
|
|
|
|
if (numVertices <= 0) {
|
|
return
|
|
}
|
|
|
|
var gl = plot.gl
|
|
var dataBox = plot.dataBox
|
|
|
|
var boundX = bounds[2] - bounds[0]
|
|
var boundY = bounds[3] - bounds[1]
|
|
var dataX = dataBox[2] - dataBox[0]
|
|
var dataY = dataBox[3] - dataBox[1]
|
|
|
|
MATRIX[0] = 2.0 * boundX / dataX
|
|
MATRIX[4] = 2.0 * boundY / dataY
|
|
MATRIX[6] = 2.0 * (bounds[0] - dataBox[0]) / dataX - 1.0
|
|
MATRIX[7] = 2.0 * (bounds[1] - dataBox[1]) / dataY - 1.0
|
|
|
|
shader.bind()
|
|
|
|
var uniforms = shader.uniforms
|
|
uniforms.viewTransform = MATRIX
|
|
|
|
uniforms.shape = this.shape
|
|
|
|
var attributes = shader.attributes
|
|
this.positionBuffer.bind()
|
|
attributes.position.pointer()
|
|
|
|
this.weightBuffer.bind()
|
|
attributes.weight.pointer(gl.UNSIGNED_BYTE, false)
|
|
|
|
this.colorBuffer.bind()
|
|
attributes.color.pointer(gl.UNSIGNED_BYTE, true)
|
|
|
|
gl.drawArrays(gl.TRIANGLES, 0, numVertices)
|
|
}
|
|
})()
|
|
|
|
proto.drawPick = (function () {
|
|
var MATRIX = [
|
|
1, 0, 0,
|
|
0, 1, 0,
|
|
0, 0, 1
|
|
]
|
|
|
|
var PICK_VECTOR = [0, 0, 0, 0]
|
|
|
|
return function (pickOffset) {
|
|
var plot = this.plot
|
|
var shader = this.pickShader
|
|
var bounds = this.bounds
|
|
var numVertices = this.numVertices
|
|
|
|
if (numVertices <= 0) {
|
|
return
|
|
}
|
|
|
|
var gl = plot.gl
|
|
var dataBox = plot.dataBox
|
|
|
|
var boundX = bounds[2] - bounds[0]
|
|
var boundY = bounds[3] - bounds[1]
|
|
var dataX = dataBox[2] - dataBox[0]
|
|
var dataY = dataBox[3] - dataBox[1]
|
|
|
|
MATRIX[0] = 2.0 * boundX / dataX
|
|
MATRIX[4] = 2.0 * boundY / dataY
|
|
MATRIX[6] = 2.0 * (bounds[0] - dataBox[0]) / dataX - 1.0
|
|
MATRIX[7] = 2.0 * (bounds[1] - dataBox[1]) / dataY - 1.0
|
|
|
|
for (var i = 0; i < 4; ++i) {
|
|
PICK_VECTOR[i] = (pickOffset >> (i * 8)) & 0xff
|
|
}
|
|
|
|
this.pickOffset = pickOffset
|
|
|
|
shader.bind()
|
|
|
|
var uniforms = shader.uniforms
|
|
uniforms.viewTransform = MATRIX
|
|
uniforms.pickOffset = PICK_VECTOR
|
|
uniforms.shape = this.shape
|
|
|
|
var attributes = shader.attributes
|
|
this.positionBuffer.bind()
|
|
attributes.position.pointer()
|
|
|
|
this.weightBuffer.bind()
|
|
attributes.weight.pointer(gl.UNSIGNED_BYTE, false)
|
|
|
|
this.idBuffer.bind()
|
|
attributes.pickId.pointer(gl.UNSIGNED_BYTE, false)
|
|
|
|
gl.drawArrays(gl.TRIANGLES, 0, numVertices)
|
|
|
|
return pickOffset + this.shape[0] * this.shape[1]
|
|
}
|
|
})()
|
|
|
|
proto.pick = function (x, y, value) {
|
|
var pickOffset = this.pickOffset
|
|
var pointCount = this.shape[0] * this.shape[1]
|
|
if (value < pickOffset || value >= pickOffset + pointCount) {
|
|
return null
|
|
}
|
|
var pointId = value - pickOffset
|
|
var xData = this.xData
|
|
var yData = this.yData
|
|
return {
|
|
object: this,
|
|
pointId: pointId,
|
|
dataCoord: [
|
|
xData[pointId % this.shape[0]],
|
|
yData[(pointId / this.shape[0]) | 0]]
|
|
}
|
|
}
|
|
|
|
proto.update = function (options) {
|
|
options = options || {}
|
|
|
|
var shape = options.shape || [0, 0]
|
|
|
|
var x = options.x || iota(shape[0])
|
|
var y = options.y || iota(shape[1])
|
|
var z = options.z || new Float32Array(shape[0] * shape[1])
|
|
|
|
this.xData = x
|
|
this.yData = y
|
|
|
|
var colorLevels = options.colorLevels || [0]
|
|
var colorValues = options.colorValues || [0, 0, 0, 1]
|
|
var colorCount = colorLevels.length
|
|
|
|
var bounds = this.bounds
|
|
var lox = bounds[0] = x[0]
|
|
var loy = bounds[1] = y[0]
|
|
var hix = bounds[2] = x[x.length - 1]
|
|
var hiy = bounds[3] = y[y.length - 1]
|
|
|
|
var xs = 1.0 / (hix - lox)
|
|
var ys = 1.0 / (hiy - loy)
|
|
|
|
var numX = shape[0]
|
|
var numY = shape[1]
|
|
|
|
this.shape = [numX, numY]
|
|
|
|
var numVerts = (numX - 1) * (numY - 1) * (WEIGHTS.length >>> 1)
|
|
|
|
this.numVertices = numVerts
|
|
|
|
var colors = pool.mallocUint8(numVerts * 4)
|
|
var positions = pool.mallocFloat32(numVerts * 2)
|
|
var weights = pool.mallocUint8 (numVerts * 2)
|
|
var ids = pool.mallocUint32(numVerts)
|
|
|
|
var ptr = 0
|
|
|
|
for (var j = 0; j < numY - 1; ++j) {
|
|
var yc0 = ys * (y[j] - loy)
|
|
var yc1 = ys * (y[j + 1] - loy)
|
|
for (var i = 0; i < numX - 1; ++i) {
|
|
var xc0 = xs * (x[i] - lox)
|
|
var xc1 = xs * (x[i + 1] - lox)
|
|
|
|
for (var dd = 0; dd < WEIGHTS.length; dd += 2) {
|
|
var dx = WEIGHTS[dd]
|
|
var dy = WEIGHTS[dd + 1]
|
|
var offset = (j + dy) * numX + (i + dx)
|
|
var zc = z[offset]
|
|
var colorIdx = bsearch.le(colorLevels, zc)
|
|
var r, g, b, a
|
|
if (colorIdx < 0) {
|
|
r = colorValues[0]
|
|
g = colorValues[1]
|
|
b = colorValues[2]
|
|
a = colorValues[3]
|
|
} else if (colorIdx === colorCount - 1) {
|
|
r = colorValues[4 * colorCount - 4]
|
|
g = colorValues[4 * colorCount - 3]
|
|
b = colorValues[4 * colorCount - 2]
|
|
a = colorValues[4 * colorCount - 1]
|
|
} else {
|
|
var t = (zc - colorLevels[colorIdx]) /
|
|
(colorLevels[colorIdx + 1] - colorLevels[colorIdx])
|
|
var ti = 1.0 - t
|
|
var i0 = 4 * colorIdx
|
|
var i1 = 4 * (colorIdx + 1)
|
|
r = ti * colorValues[i0] + t * colorValues[i1]
|
|
g = ti * colorValues[i0 + 1] + t * colorValues[i1 + 1]
|
|
b = ti * colorValues[i0 + 2] + t * colorValues[i1 + 2]
|
|
a = ti * colorValues[i0 + 3] + t * colorValues[i1 + 3]
|
|
}
|
|
|
|
colors[4 * ptr] = 255 * r
|
|
colors[4 * ptr + 1] = 255 * g
|
|
colors[4 * ptr + 2] = 255 * b
|
|
colors[4 * ptr + 3] = 255 * a
|
|
|
|
positions[2*ptr] = xc0*.5 + xc1*.5;
|
|
positions[2*ptr+1] = yc0*.5 + yc1*.5;
|
|
|
|
weights[2*ptr] = dx;
|
|
weights[2*ptr+1] = dy;
|
|
|
|
ids[ptr] = j * numX + i
|
|
|
|
ptr += 1
|
|
}
|
|
}
|
|
}
|
|
|
|
this.positionBuffer.update(positions)
|
|
this.weightBuffer.update(weights)
|
|
this.colorBuffer.update(colors)
|
|
this.idBuffer.update(ids)
|
|
|
|
pool.free(positions)
|
|
pool.free(colors)
|
|
pool.free(weights)
|
|
pool.free(ids)
|
|
}
|
|
|
|
proto.dispose = function () {
|
|
this.shader.dispose()
|
|
this.pickShader.dispose()
|
|
this.positionBuffer.dispose()
|
|
this.weightBuffer.dispose()
|
|
this.colorBuffer.dispose()
|
|
this.idBuffer.dispose()
|
|
this.plot.removeObject(this)
|
|
}
|
|
|
|
function createHeatmap2D (plot, options) {
|
|
var gl = plot.gl
|
|
|
|
var shader = createShader(gl, shaders.vertex, shaders.fragment)
|
|
var pickShader = createShader(gl, shaders.pickVertex, shaders.pickFragment)
|
|
|
|
var positionBuffer = createBuffer(gl)
|
|
var weightBuffer = createBuffer(gl)
|
|
var colorBuffer = createBuffer(gl)
|
|
var idBuffer = createBuffer(gl)
|
|
|
|
var heatmap = new GLHeatmap2D(
|
|
plot,
|
|
shader,
|
|
pickShader,
|
|
positionBuffer,
|
|
weightBuffer,
|
|
colorBuffer,
|
|
idBuffer)
|
|
|
|
heatmap.update(options)
|
|
plot.addObject(heatmap)
|
|
|
|
return heatmap
|
|
}
|
|
|