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.
277 lines
8.2 KiB
277 lines
8.2 KiB
4 years ago
|
'use strict'
|
||
|
|
||
|
module.exports = createTextElements
|
||
|
|
||
|
var createBuffer = require('gl-buffer')
|
||
|
var createShader = require('gl-shader')
|
||
|
var getText = require('text-cache')
|
||
|
var bsearch = require('binary-search-bounds')
|
||
|
var shaders = require('./shaders')
|
||
|
|
||
|
function TextElements(plot, vbo, shader) {
|
||
|
this.plot = plot
|
||
|
this.vbo = vbo
|
||
|
this.shader = shader
|
||
|
this.tickOffset = [[],[]]
|
||
|
this.tickX = [[],[]]
|
||
|
this.labelOffset = [0,0]
|
||
|
this.labelCount = [0,0]
|
||
|
}
|
||
|
|
||
|
var proto = TextElements.prototype
|
||
|
|
||
|
proto.drawTicks = (function() {
|
||
|
var DATA_AXIS = [0,0]
|
||
|
var SCREEN_OFFSET = [0,0]
|
||
|
var ZERO_2 = [0,0]
|
||
|
|
||
|
return function(axis) {
|
||
|
var plot = this.plot
|
||
|
var shader = this.shader
|
||
|
var tickX = this.tickX[axis]
|
||
|
var tickOffset = this.tickOffset[axis]
|
||
|
var gl = plot.gl
|
||
|
var viewBox = plot.viewBox
|
||
|
var dataBox = plot.dataBox
|
||
|
var screenBox = plot.screenBox
|
||
|
var pixelRatio = plot.pixelRatio
|
||
|
var tickEnable = plot.tickEnable
|
||
|
var tickPad = plot.tickPad
|
||
|
var textColor = plot.tickColor
|
||
|
var textAngle = plot.tickAngle
|
||
|
// todo check if this should be used (now unused)
|
||
|
// var tickLength = plot.tickMarkLength
|
||
|
|
||
|
var labelEnable = plot.labelEnable
|
||
|
var labelPad = plot.labelPad
|
||
|
var labelColor = plot.labelColor
|
||
|
var labelAngle = plot.labelAngle
|
||
|
var labelOffset = this.labelOffset[axis]
|
||
|
var labelCount = this.labelCount[axis]
|
||
|
|
||
|
var start = bsearch.lt(tickX, dataBox[axis])
|
||
|
var end = bsearch.le(tickX, dataBox[axis+2])
|
||
|
|
||
|
DATA_AXIS[0] = DATA_AXIS[1] = 0
|
||
|
DATA_AXIS[axis] = 1
|
||
|
|
||
|
SCREEN_OFFSET[axis] = (viewBox[2+axis] + viewBox[axis]) / (screenBox[2+axis] - screenBox[axis]) - 1.0
|
||
|
|
||
|
var screenScale = 2.0 / screenBox[2+(axis^1)] - screenBox[axis^1]
|
||
|
|
||
|
SCREEN_OFFSET[axis^1] = screenScale * viewBox[axis^1] - 1.0
|
||
|
if(tickEnable[axis]) {
|
||
|
SCREEN_OFFSET[axis^1] -= screenScale * pixelRatio * tickPad[axis]
|
||
|
if(start < end && tickOffset[end] > tickOffset[start]) {
|
||
|
shader.uniforms.dataAxis = DATA_AXIS
|
||
|
shader.uniforms.screenOffset = SCREEN_OFFSET
|
||
|
shader.uniforms.color = textColor[axis]
|
||
|
shader.uniforms.angle = textAngle[axis]
|
||
|
gl.drawArrays(
|
||
|
gl.TRIANGLES,
|
||
|
tickOffset[start],
|
||
|
tickOffset[end] - tickOffset[start])
|
||
|
}
|
||
|
}
|
||
|
if(labelEnable[axis] && labelCount) {
|
||
|
SCREEN_OFFSET[axis^1] -= screenScale * pixelRatio * labelPad[axis]
|
||
|
shader.uniforms.dataAxis = ZERO_2
|
||
|
shader.uniforms.screenOffset = SCREEN_OFFSET
|
||
|
shader.uniforms.color = labelColor[axis]
|
||
|
shader.uniforms.angle = labelAngle[axis]
|
||
|
gl.drawArrays(
|
||
|
gl.TRIANGLES,
|
||
|
labelOffset,
|
||
|
labelCount)
|
||
|
}
|
||
|
|
||
|
SCREEN_OFFSET[axis^1] = screenScale * viewBox[2+(axis^1)] - 1.0
|
||
|
if(tickEnable[axis+2]) {
|
||
|
SCREEN_OFFSET[axis^1] += screenScale * pixelRatio * tickPad[axis+2]
|
||
|
if(start < end && tickOffset[end] > tickOffset[start]) {
|
||
|
shader.uniforms.dataAxis = DATA_AXIS
|
||
|
shader.uniforms.screenOffset = SCREEN_OFFSET
|
||
|
shader.uniforms.color = textColor[axis+2]
|
||
|
shader.uniforms.angle = textAngle[axis+2]
|
||
|
gl.drawArrays(
|
||
|
gl.TRIANGLES,
|
||
|
tickOffset[start],
|
||
|
tickOffset[end] - tickOffset[start])
|
||
|
}
|
||
|
}
|
||
|
if(labelEnable[axis+2] && labelCount) {
|
||
|
SCREEN_OFFSET[axis^1] += screenScale * pixelRatio * labelPad[axis+2]
|
||
|
shader.uniforms.dataAxis = ZERO_2
|
||
|
shader.uniforms.screenOffset = SCREEN_OFFSET
|
||
|
shader.uniforms.color = labelColor[axis+2]
|
||
|
shader.uniforms.angle = labelAngle[axis+2]
|
||
|
gl.drawArrays(
|
||
|
gl.TRIANGLES,
|
||
|
labelOffset,
|
||
|
labelCount)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
})()
|
||
|
|
||
|
proto.drawTitle = (function() {
|
||
|
var DATA_AXIS = [0,0]
|
||
|
var SCREEN_OFFSET = [0,0]
|
||
|
|
||
|
return function() {
|
||
|
var plot = this.plot
|
||
|
var shader = this.shader
|
||
|
var gl = plot.gl
|
||
|
var screenBox = plot.screenBox
|
||
|
var titleCenter = plot.titleCenter
|
||
|
var titleAngle = plot.titleAngle
|
||
|
var titleColor = plot.titleColor
|
||
|
var pixelRatio = plot.pixelRatio
|
||
|
|
||
|
if(!this.titleCount) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
for(var i=0; i<2; ++i) {
|
||
|
SCREEN_OFFSET[i] = 2.0 * (titleCenter[i]*pixelRatio - screenBox[i]) /
|
||
|
(screenBox[2+i] - screenBox[i]) - 1
|
||
|
}
|
||
|
|
||
|
shader.bind()
|
||
|
shader.uniforms.dataAxis = DATA_AXIS
|
||
|
shader.uniforms.screenOffset = SCREEN_OFFSET
|
||
|
shader.uniforms.angle = titleAngle
|
||
|
shader.uniforms.color = titleColor
|
||
|
|
||
|
gl.drawArrays(gl.TRIANGLES, this.titleOffset, this.titleCount)
|
||
|
}
|
||
|
})()
|
||
|
|
||
|
proto.bind = (function() {
|
||
|
var DATA_SHIFT = [0,0]
|
||
|
var DATA_SCALE = [0,0]
|
||
|
var TEXT_SCALE = [0,0]
|
||
|
|
||
|
return function() {
|
||
|
var plot = this.plot
|
||
|
var shader = this.shader
|
||
|
var bounds = plot._tickBounds
|
||
|
var dataBox = plot.dataBox
|
||
|
var screenBox = plot.screenBox
|
||
|
var viewBox = plot.viewBox
|
||
|
|
||
|
shader.bind()
|
||
|
|
||
|
//Set up coordinate scaling uniforms
|
||
|
for(var i=0; i<2; ++i) {
|
||
|
|
||
|
var lo = bounds[i]
|
||
|
var hi = bounds[i+2]
|
||
|
var boundScale = hi - lo
|
||
|
var dataCenter = 0.5 * (dataBox[i+2] + dataBox[i])
|
||
|
var dataWidth = (dataBox[i+2] - dataBox[i])
|
||
|
|
||
|
var viewLo = viewBox[i]
|
||
|
var viewHi = viewBox[i+2]
|
||
|
var viewScale = viewHi - viewLo
|
||
|
var screenLo = screenBox[i]
|
||
|
var screenHi = screenBox[i+2]
|
||
|
var screenScale = screenHi - screenLo
|
||
|
|
||
|
DATA_SCALE[i] = 2.0 * boundScale / dataWidth * viewScale / screenScale
|
||
|
DATA_SHIFT[i] = 2.0 * (lo - dataCenter) / dataWidth * viewScale / screenScale
|
||
|
}
|
||
|
|
||
|
TEXT_SCALE[1] = 2.0 * plot.pixelRatio / (screenBox[3] - screenBox[1])
|
||
|
TEXT_SCALE[0] = TEXT_SCALE[1] * (screenBox[3] - screenBox[1]) / (screenBox[2] - screenBox[0])
|
||
|
|
||
|
shader.uniforms.dataScale = DATA_SCALE
|
||
|
shader.uniforms.dataShift = DATA_SHIFT
|
||
|
shader.uniforms.textScale = TEXT_SCALE
|
||
|
|
||
|
//Set attributes
|
||
|
this.vbo.bind()
|
||
|
shader.attributes.textCoordinate.pointer()
|
||
|
}
|
||
|
})()
|
||
|
|
||
|
proto.update = function(options) {
|
||
|
var vertices = []
|
||
|
var axesTicks = options.ticks
|
||
|
var bounds = options.bounds
|
||
|
var i, j, k, data, scale, dimension
|
||
|
|
||
|
for(dimension=0; dimension<2; ++dimension) {
|
||
|
var offsets = [Math.floor(vertices.length/3)], tickX = [-Infinity]
|
||
|
|
||
|
//Copy vertices over to buffer
|
||
|
var ticks = axesTicks[dimension]
|
||
|
for(i=0; i<ticks.length; ++i) {
|
||
|
var tick = ticks[i]
|
||
|
var x = tick.x
|
||
|
var text = tick.text
|
||
|
var font = tick.font || 'sans-serif'
|
||
|
scale = (tick.fontSize || 12)
|
||
|
|
||
|
var coordScale = 1.0 / (bounds[dimension+2] - bounds[dimension])
|
||
|
var coordShift = bounds[dimension]
|
||
|
|
||
|
var rows = text.split('\n')
|
||
|
for(var r = 0; r < rows.length; r++) {
|
||
|
data = getText(font, rows[r]).data
|
||
|
for (j = 0; j < data.length; j += 2) {
|
||
|
vertices.push(
|
||
|
data[j] * scale,
|
||
|
-data[j + 1] * scale - r * scale * 1.2,
|
||
|
(x - coordShift) * coordScale)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
offsets.push(Math.floor(vertices.length/3))
|
||
|
tickX.push(x)
|
||
|
}
|
||
|
|
||
|
this.tickOffset[dimension] = offsets
|
||
|
this.tickX[dimension] = tickX
|
||
|
}
|
||
|
|
||
|
//Add labels
|
||
|
for(dimension=0; dimension<2; ++dimension) {
|
||
|
this.labelOffset[dimension] = Math.floor(vertices.length/3)
|
||
|
|
||
|
data = getText(options.labelFont[dimension], options.labels[dimension], { textAlign: 'center' }).data
|
||
|
scale = options.labelSize[dimension]
|
||
|
for(i=0; i<data.length; i+=2) {
|
||
|
vertices.push(data[i]*scale, -data[i+1]*scale, 0)
|
||
|
}
|
||
|
|
||
|
this.labelCount[dimension] =
|
||
|
Math.floor(vertices.length/3) - this.labelOffset[dimension]
|
||
|
}
|
||
|
|
||
|
//Add title
|
||
|
this.titleOffset = Math.floor(vertices.length/3)
|
||
|
data = getText(options.titleFont, options.title).data
|
||
|
scale = options.titleSize
|
||
|
for(i=0; i<data.length; i+=2) {
|
||
|
vertices.push(data[i]*scale, -data[i+1]*scale, 0)
|
||
|
}
|
||
|
this.titleCount = Math.floor(vertices.length/3) - this.titleOffset
|
||
|
|
||
|
//Upload new vertices
|
||
|
this.vbo.update(vertices)
|
||
|
}
|
||
|
|
||
|
proto.dispose = function() {
|
||
|
this.vbo.dispose()
|
||
|
this.shader.dispose()
|
||
|
}
|
||
|
|
||
|
function createTextElements(plot) {
|
||
|
var gl = plot.gl
|
||
|
var vbo = createBuffer(gl)
|
||
|
var shader = createShader(gl, shaders.textVert, shaders.textFrag)
|
||
|
var text = new TextElements(plot, vbo, shader)
|
||
|
return text
|
||
|
}
|