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.
230 lines
5.5 KiB
230 lines
5.5 KiB
"use strict"
|
|
|
|
module.exports = createSlabDecomposition
|
|
|
|
var bounds = require("binary-search-bounds")
|
|
var createRBTree = require("functional-red-black-tree")
|
|
var orient = require("robust-orientation")
|
|
var orderSegments = require("./lib/order-segments")
|
|
|
|
function SlabDecomposition(slabs, coordinates, horizontal) {
|
|
this.slabs = slabs
|
|
this.coordinates = coordinates
|
|
this.horizontal = horizontal
|
|
}
|
|
|
|
var proto = SlabDecomposition.prototype
|
|
|
|
function compareHorizontal(e, y) {
|
|
return e.y - y
|
|
}
|
|
|
|
function searchBucket(root, p) {
|
|
var lastNode = null
|
|
while(root) {
|
|
var seg = root.key
|
|
var l, r
|
|
if(seg[0][0] < seg[1][0]) {
|
|
l = seg[0]
|
|
r = seg[1]
|
|
} else {
|
|
l = seg[1]
|
|
r = seg[0]
|
|
}
|
|
var o = orient(l, r, p)
|
|
if(o < 0) {
|
|
root = root.left
|
|
} else if(o > 0) {
|
|
if(p[0] !== seg[1][0]) {
|
|
lastNode = root
|
|
root = root.right
|
|
} else {
|
|
var val = searchBucket(root.right, p)
|
|
if(val) {
|
|
return val
|
|
}
|
|
root = root.left
|
|
}
|
|
} else {
|
|
if(p[0] !== seg[1][0]) {
|
|
return root
|
|
} else {
|
|
var val = searchBucket(root.right, p)
|
|
if(val) {
|
|
return val
|
|
}
|
|
root = root.left
|
|
}
|
|
}
|
|
}
|
|
return lastNode
|
|
}
|
|
|
|
proto.castUp = function(p) {
|
|
var bucket = bounds.le(this.coordinates, p[0])
|
|
if(bucket < 0) {
|
|
return -1
|
|
}
|
|
var root = this.slabs[bucket]
|
|
var hitNode = searchBucket(this.slabs[bucket], p)
|
|
var lastHit = -1
|
|
if(hitNode) {
|
|
lastHit = hitNode.value
|
|
}
|
|
//Edge case: need to handle horizontal segments (sucks)
|
|
if(this.coordinates[bucket] === p[0]) {
|
|
var lastSegment = null
|
|
if(hitNode) {
|
|
lastSegment = hitNode.key
|
|
}
|
|
if(bucket > 0) {
|
|
var otherHitNode = searchBucket(this.slabs[bucket-1], p)
|
|
if(otherHitNode) {
|
|
if(lastSegment) {
|
|
if(orderSegments(otherHitNode.key, lastSegment) > 0) {
|
|
lastSegment = otherHitNode.key
|
|
lastHit = otherHitNode.value
|
|
}
|
|
} else {
|
|
lastHit = otherHitNode.value
|
|
lastSegment = otherHitNode.key
|
|
}
|
|
}
|
|
}
|
|
var horiz = this.horizontal[bucket]
|
|
if(horiz.length > 0) {
|
|
var hbucket = bounds.ge(horiz, p[1], compareHorizontal)
|
|
if(hbucket < horiz.length) {
|
|
var e = horiz[hbucket]
|
|
if(p[1] === e.y) {
|
|
if(e.closed) {
|
|
return e.index
|
|
} else {
|
|
while(hbucket < horiz.length-1 && horiz[hbucket+1].y === p[1]) {
|
|
hbucket = hbucket+1
|
|
e = horiz[hbucket]
|
|
if(e.closed) {
|
|
return e.index
|
|
}
|
|
}
|
|
if(e.y === p[1] && !e.start) {
|
|
hbucket = hbucket+1
|
|
if(hbucket >= horiz.length) {
|
|
return lastHit
|
|
}
|
|
e = horiz[hbucket]
|
|
}
|
|
}
|
|
}
|
|
//Check if e is above/below last segment
|
|
if(e.start) {
|
|
if(lastSegment) {
|
|
var o = orient(lastSegment[0], lastSegment[1], [p[0], e.y])
|
|
if(lastSegment[0][0] > lastSegment[1][0]) {
|
|
o = -o
|
|
}
|
|
if(o > 0) {
|
|
lastHit = e.index
|
|
}
|
|
} else {
|
|
lastHit = e.index
|
|
}
|
|
} else if(e.y !== p[1]) {
|
|
lastHit = e.index
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return lastHit
|
|
}
|
|
|
|
function IntervalSegment(y, index, start, closed) {
|
|
this.y = y
|
|
this.index = index
|
|
this.start = start
|
|
this.closed = closed
|
|
}
|
|
|
|
function Event(x, segment, create, index) {
|
|
this.x = x
|
|
this.segment = segment
|
|
this.create = create
|
|
this.index = index
|
|
}
|
|
|
|
|
|
function createSlabDecomposition(segments) {
|
|
var numSegments = segments.length
|
|
var numEvents = 2 * numSegments
|
|
var events = new Array(numEvents)
|
|
for(var i=0; i<numSegments; ++i) {
|
|
var s = segments[i]
|
|
var f = s[0][0] < s[1][0]
|
|
events[2*i] = new Event(s[0][0], s, f, i)
|
|
events[2*i+1] = new Event(s[1][0], s, !f, i)
|
|
}
|
|
events.sort(function(a,b) {
|
|
var d = a.x - b.x
|
|
if(d) {
|
|
return d
|
|
}
|
|
d = a.create - b.create
|
|
if(d) {
|
|
return d
|
|
}
|
|
return Math.min(a.segment[0][1], a.segment[1][1]) - Math.min(b.segment[0][1], b.segment[1][1])
|
|
})
|
|
var tree = createRBTree(orderSegments)
|
|
var slabs = []
|
|
var lines = []
|
|
var horizontal = []
|
|
var lastX = -Infinity
|
|
for(var i=0; i<numEvents; ) {
|
|
var x = events[i].x
|
|
var horiz = []
|
|
while(i < numEvents) {
|
|
var e = events[i]
|
|
if(e.x !== x) {
|
|
break
|
|
}
|
|
i += 1
|
|
if(e.segment[0][0] === e.x && e.segment[1][0] === e.x) {
|
|
if(e.create) {
|
|
if(e.segment[0][1] < e.segment[1][1]) {
|
|
horiz.push(new IntervalSegment(
|
|
e.segment[0][1],
|
|
e.index,
|
|
true,
|
|
true))
|
|
horiz.push(new IntervalSegment(
|
|
e.segment[1][1],
|
|
e.index,
|
|
false,
|
|
false))
|
|
} else {
|
|
horiz.push(new IntervalSegment(
|
|
e.segment[1][1],
|
|
e.index,
|
|
true,
|
|
false))
|
|
horiz.push(new IntervalSegment(
|
|
e.segment[0][1],
|
|
e.index,
|
|
false,
|
|
true))
|
|
}
|
|
}
|
|
} else {
|
|
if(e.create) {
|
|
tree = tree.insert(e.segment, e.index)
|
|
} else {
|
|
tree = tree.remove(e.segment)
|
|
}
|
|
}
|
|
}
|
|
slabs.push(tree.root)
|
|
lines.push(x)
|
|
horizontal.push(horiz)
|
|
}
|
|
return new SlabDecomposition(slabs, lines, horizontal)
|
|
} |