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.
187 lines
4.2 KiB
187 lines
4.2 KiB
'use strict'
|
|
|
|
var bsearch = require('binary-search-bounds')
|
|
var orient = require('robust-orientation')[3]
|
|
|
|
var EVENT_POINT = 0
|
|
var EVENT_END = 1
|
|
var EVENT_START = 2
|
|
|
|
module.exports = monotoneTriangulate
|
|
|
|
//A partial convex hull fragment, made of two unimonotone polygons
|
|
function PartialHull(a, b, idx, lowerIds, upperIds) {
|
|
this.a = a
|
|
this.b = b
|
|
this.idx = idx
|
|
this.lowerIds = lowerIds
|
|
this.upperIds = upperIds
|
|
}
|
|
|
|
//An event in the sweep line procedure
|
|
function Event(a, b, type, idx) {
|
|
this.a = a
|
|
this.b = b
|
|
this.type = type
|
|
this.idx = idx
|
|
}
|
|
|
|
//This is used to compare events for the sweep line procedure
|
|
// Points are:
|
|
// 1. sorted lexicographically
|
|
// 2. sorted by type (point < end < start)
|
|
// 3. segments sorted by winding order
|
|
// 4. sorted by index
|
|
function compareEvent(a, b) {
|
|
var d =
|
|
(a.a[0] - b.a[0]) ||
|
|
(a.a[1] - b.a[1]) ||
|
|
(a.type - b.type)
|
|
if(d) { return d }
|
|
if(a.type !== EVENT_POINT) {
|
|
d = orient(a.a, a.b, b.b)
|
|
if(d) { return d }
|
|
}
|
|
return a.idx - b.idx
|
|
}
|
|
|
|
function testPoint(hull, p) {
|
|
return orient(hull.a, hull.b, p)
|
|
}
|
|
|
|
function addPoint(cells, hulls, points, p, idx) {
|
|
var lo = bsearch.lt(hulls, p, testPoint)
|
|
var hi = bsearch.gt(hulls, p, testPoint)
|
|
for(var i=lo; i<hi; ++i) {
|
|
var hull = hulls[i]
|
|
|
|
//Insert p into lower hull
|
|
var lowerIds = hull.lowerIds
|
|
var m = lowerIds.length
|
|
while(m > 1 && orient(
|
|
points[lowerIds[m-2]],
|
|
points[lowerIds[m-1]],
|
|
p) > 0) {
|
|
cells.push(
|
|
[lowerIds[m-1],
|
|
lowerIds[m-2],
|
|
idx])
|
|
m -= 1
|
|
}
|
|
lowerIds.length = m
|
|
lowerIds.push(idx)
|
|
|
|
//Insert p into upper hull
|
|
var upperIds = hull.upperIds
|
|
var m = upperIds.length
|
|
while(m > 1 && orient(
|
|
points[upperIds[m-2]],
|
|
points[upperIds[m-1]],
|
|
p) < 0) {
|
|
cells.push(
|
|
[upperIds[m-2],
|
|
upperIds[m-1],
|
|
idx])
|
|
m -= 1
|
|
}
|
|
upperIds.length = m
|
|
upperIds.push(idx)
|
|
}
|
|
}
|
|
|
|
function findSplit(hull, edge) {
|
|
var d
|
|
if(hull.a[0] < edge.a[0]) {
|
|
d = orient(hull.a, hull.b, edge.a)
|
|
} else {
|
|
d = orient(edge.b, edge.a, hull.a)
|
|
}
|
|
if(d) { return d }
|
|
if(edge.b[0] < hull.b[0]) {
|
|
d = orient(hull.a, hull.b, edge.b)
|
|
} else {
|
|
d = orient(edge.b, edge.a, hull.b)
|
|
}
|
|
return d || hull.idx - edge.idx
|
|
}
|
|
|
|
function splitHulls(hulls, points, event) {
|
|
var splitIdx = bsearch.le(hulls, event, findSplit)
|
|
var hull = hulls[splitIdx]
|
|
var upperIds = hull.upperIds
|
|
var x = upperIds[upperIds.length-1]
|
|
hull.upperIds = [x]
|
|
hulls.splice(splitIdx+1, 0,
|
|
new PartialHull(event.a, event.b, event.idx, [x], upperIds))
|
|
}
|
|
|
|
|
|
function mergeHulls(hulls, points, event) {
|
|
//Swap pointers for merge search
|
|
var tmp = event.a
|
|
event.a = event.b
|
|
event.b = tmp
|
|
var mergeIdx = bsearch.eq(hulls, event, findSplit)
|
|
var upper = hulls[mergeIdx]
|
|
var lower = hulls[mergeIdx-1]
|
|
lower.upperIds = upper.upperIds
|
|
hulls.splice(mergeIdx, 1)
|
|
}
|
|
|
|
|
|
function monotoneTriangulate(points, edges) {
|
|
|
|
var numPoints = points.length
|
|
var numEdges = edges.length
|
|
|
|
var events = []
|
|
|
|
//Create point events
|
|
for(var i=0; i<numPoints; ++i) {
|
|
events.push(new Event(
|
|
points[i],
|
|
null,
|
|
EVENT_POINT,
|
|
i))
|
|
}
|
|
|
|
//Create edge events
|
|
for(var i=0; i<numEdges; ++i) {
|
|
var e = edges[i]
|
|
var a = points[e[0]]
|
|
var b = points[e[1]]
|
|
if(a[0] < b[0]) {
|
|
events.push(
|
|
new Event(a, b, EVENT_START, i),
|
|
new Event(b, a, EVENT_END, i))
|
|
} else if(a[0] > b[0]) {
|
|
events.push(
|
|
new Event(b, a, EVENT_START, i),
|
|
new Event(a, b, EVENT_END, i))
|
|
}
|
|
}
|
|
|
|
//Sort events
|
|
events.sort(compareEvent)
|
|
|
|
//Initialize hull
|
|
var minX = events[0].a[0] - (1 + Math.abs(events[0].a[0])) * Math.pow(2, -52)
|
|
var hull = [ new PartialHull([minX, 1], [minX, 0], -1, [], [], [], []) ]
|
|
|
|
//Process events in order
|
|
var cells = []
|
|
for(var i=0, numEvents=events.length; i<numEvents; ++i) {
|
|
var event = events[i]
|
|
var type = event.type
|
|
if(type === EVENT_POINT) {
|
|
addPoint(cells, hull, points, event.a, event.idx)
|
|
} else if(type === EVENT_START) {
|
|
splitHulls(hull, points, event)
|
|
} else {
|
|
mergeHulls(hull, points, event)
|
|
}
|
|
}
|
|
|
|
//Return triangulation
|
|
return cells
|
|
}
|
|
|