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.
382 lines
9.0 KiB
382 lines
9.0 KiB
4 years ago
|
'use strict'
|
||
|
|
||
|
module.exports = cleanPSLG
|
||
|
|
||
|
var UnionFind = require('union-find')
|
||
|
var boxIntersect = require('box-intersect')
|
||
|
var segseg = require('robust-segment-intersect')
|
||
|
var rat = require('big-rat')
|
||
|
var ratCmp = require('big-rat/cmp')
|
||
|
var ratToFloat = require('big-rat/to-float')
|
||
|
var ratVec = require('rat-vec')
|
||
|
var nextafter = require('nextafter')
|
||
|
|
||
|
var solveIntersection = require('./lib/rat-seg-intersect')
|
||
|
|
||
|
// Bounds on a rational number when rounded to a float
|
||
|
function boundRat (r) {
|
||
|
var f = ratToFloat(r)
|
||
|
return [
|
||
|
nextafter(f, -Infinity),
|
||
|
nextafter(f, Infinity)
|
||
|
]
|
||
|
}
|
||
|
|
||
|
// Convert a list of edges in a pslg to bounding boxes
|
||
|
function boundEdges (points, edges) {
|
||
|
var bounds = new Array(edges.length)
|
||
|
for (var i = 0; i < edges.length; ++i) {
|
||
|
var e = edges[i]
|
||
|
var a = points[e[0]]
|
||
|
var b = points[e[1]]
|
||
|
bounds[i] = [
|
||
|
nextafter(Math.min(a[0], b[0]), -Infinity),
|
||
|
nextafter(Math.min(a[1], b[1]), -Infinity),
|
||
|
nextafter(Math.max(a[0], b[0]), Infinity),
|
||
|
nextafter(Math.max(a[1], b[1]), Infinity)
|
||
|
]
|
||
|
}
|
||
|
return bounds
|
||
|
}
|
||
|
|
||
|
// Convert a list of points into bounding boxes by duplicating coords
|
||
|
function boundPoints (points) {
|
||
|
var bounds = new Array(points.length)
|
||
|
for (var i = 0; i < points.length; ++i) {
|
||
|
var p = points[i]
|
||
|
bounds[i] = [
|
||
|
nextafter(p[0], -Infinity),
|
||
|
nextafter(p[1], -Infinity),
|
||
|
nextafter(p[0], Infinity),
|
||
|
nextafter(p[1], Infinity)
|
||
|
]
|
||
|
}
|
||
|
return bounds
|
||
|
}
|
||
|
|
||
|
// Find all pairs of crossing edges in a pslg (given edge bounds)
|
||
|
function getCrossings (points, edges, edgeBounds) {
|
||
|
var result = []
|
||
|
boxIntersect(edgeBounds, function (i, j) {
|
||
|
var e = edges[i]
|
||
|
var f = edges[j]
|
||
|
if (e[0] === f[0] || e[0] === f[1] ||
|
||
|
e[1] === f[0] || e[1] === f[1]) {
|
||
|
return
|
||
|
}
|
||
|
var a = points[e[0]]
|
||
|
var b = points[e[1]]
|
||
|
var c = points[f[0]]
|
||
|
var d = points[f[1]]
|
||
|
if (segseg(a, b, c, d)) {
|
||
|
result.push([i, j])
|
||
|
}
|
||
|
})
|
||
|
return result
|
||
|
}
|
||
|
|
||
|
// Find all pairs of crossing vertices in a pslg (given edge/vert bounds)
|
||
|
function getTJunctions (points, edges, edgeBounds, vertBounds) {
|
||
|
var result = []
|
||
|
boxIntersect(edgeBounds, vertBounds, function (i, v) {
|
||
|
var e = edges[i]
|
||
|
if (e[0] === v || e[1] === v) {
|
||
|
return
|
||
|
}
|
||
|
var p = points[v]
|
||
|
var a = points[e[0]]
|
||
|
var b = points[e[1]]
|
||
|
if (segseg(a, b, p, p)) {
|
||
|
result.push([i, v])
|
||
|
}
|
||
|
})
|
||
|
return result
|
||
|
}
|
||
|
|
||
|
// Cut edges along crossings/tjunctions
|
||
|
function cutEdges (floatPoints, edges, crossings, junctions, useColor) {
|
||
|
var i, e
|
||
|
|
||
|
// Convert crossings into tjunctions by constructing rational points
|
||
|
var ratPoints = floatPoints.map(function(p) {
|
||
|
return [
|
||
|
rat(p[0]),
|
||
|
rat(p[1])
|
||
|
]
|
||
|
})
|
||
|
for (i = 0; i < crossings.length; ++i) {
|
||
|
var crossing = crossings[i]
|
||
|
e = crossing[0]
|
||
|
var f = crossing[1]
|
||
|
var ee = edges[e]
|
||
|
var ef = edges[f]
|
||
|
var x = solveIntersection(
|
||
|
ratVec(floatPoints[ee[0]]),
|
||
|
ratVec(floatPoints[ee[1]]),
|
||
|
ratVec(floatPoints[ef[0]]),
|
||
|
ratVec(floatPoints[ef[1]]))
|
||
|
if (!x) {
|
||
|
// Segments are parallel, should already be handled by t-junctions
|
||
|
continue
|
||
|
}
|
||
|
var idx = floatPoints.length
|
||
|
floatPoints.push([ratToFloat(x[0]), ratToFloat(x[1])])
|
||
|
ratPoints.push(x)
|
||
|
junctions.push([e, idx], [f, idx])
|
||
|
}
|
||
|
|
||
|
// Sort tjunctions
|
||
|
junctions.sort(function (a, b) {
|
||
|
if (a[0] !== b[0]) {
|
||
|
return a[0] - b[0]
|
||
|
}
|
||
|
var u = ratPoints[a[1]]
|
||
|
var v = ratPoints[b[1]]
|
||
|
return ratCmp(u[0], v[0]) || ratCmp(u[1], v[1])
|
||
|
})
|
||
|
|
||
|
// Split edges along junctions
|
||
|
for (i = junctions.length - 1; i >= 0; --i) {
|
||
|
var junction = junctions[i]
|
||
|
e = junction[0]
|
||
|
|
||
|
var edge = edges[e]
|
||
|
var s = edge[0]
|
||
|
var t = edge[1]
|
||
|
|
||
|
// Check if edge is not lexicographically sorted
|
||
|
var a = floatPoints[s]
|
||
|
var b = floatPoints[t]
|
||
|
if (((a[0] - b[0]) || (a[1] - b[1])) < 0) {
|
||
|
var tmp = s
|
||
|
s = t
|
||
|
t = tmp
|
||
|
}
|
||
|
|
||
|
// Split leading edge
|
||
|
edge[0] = s
|
||
|
var last = edge[1] = junction[1]
|
||
|
|
||
|
// If we are grouping edges by color, remember to track data
|
||
|
var color
|
||
|
if (useColor) {
|
||
|
color = edge[2]
|
||
|
}
|
||
|
|
||
|
// Split other edges
|
||
|
while (i > 0 && junctions[i - 1][0] === e) {
|
||
|
var junction = junctions[--i]
|
||
|
var next = junction[1]
|
||
|
if (useColor) {
|
||
|
edges.push([last, next, color])
|
||
|
} else {
|
||
|
edges.push([last, next])
|
||
|
}
|
||
|
last = next
|
||
|
}
|
||
|
|
||
|
// Add final edge
|
||
|
if (useColor) {
|
||
|
edges.push([last, t, color])
|
||
|
} else {
|
||
|
edges.push([last, t])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Return constructed rational points
|
||
|
return ratPoints
|
||
|
}
|
||
|
|
||
|
// Merge overlapping points
|
||
|
function dedupPoints (floatPoints, ratPoints, floatBounds) {
|
||
|
var numPoints = ratPoints.length
|
||
|
var uf = new UnionFind(numPoints)
|
||
|
|
||
|
// Compute rational bounds
|
||
|
var bounds = []
|
||
|
for (var i = 0; i < ratPoints.length; ++i) {
|
||
|
var p = ratPoints[i]
|
||
|
var xb = boundRat(p[0])
|
||
|
var yb = boundRat(p[1])
|
||
|
bounds.push([
|
||
|
nextafter(xb[0], -Infinity),
|
||
|
nextafter(yb[0], -Infinity),
|
||
|
nextafter(xb[1], Infinity),
|
||
|
nextafter(yb[1], Infinity)
|
||
|
])
|
||
|
}
|
||
|
|
||
|
// Link all points with over lapping boxes
|
||
|
boxIntersect(bounds, function (i, j) {
|
||
|
uf.link(i, j)
|
||
|
})
|
||
|
|
||
|
// Do 1 pass over points to combine points in label sets
|
||
|
var noDupes = true
|
||
|
var labels = new Array(numPoints)
|
||
|
for (var i = 0; i < numPoints; ++i) {
|
||
|
var j = uf.find(i)
|
||
|
if (j !== i) {
|
||
|
// Clear no-dupes flag, zero out label
|
||
|
noDupes = false
|
||
|
// Make each point the top-left point from its cell
|
||
|
floatPoints[j] = [
|
||
|
Math.min(floatPoints[i][0], floatPoints[j][0]),
|
||
|
Math.min(floatPoints[i][1], floatPoints[j][1])
|
||
|
]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If no duplicates, return null to signal termination
|
||
|
if (noDupes) {
|
||
|
return null
|
||
|
}
|
||
|
|
||
|
var ptr = 0
|
||
|
for (var i = 0; i < numPoints; ++i) {
|
||
|
var j = uf.find(i)
|
||
|
if (j === i) {
|
||
|
labels[i] = ptr
|
||
|
floatPoints[ptr++] = floatPoints[i]
|
||
|
} else {
|
||
|
labels[i] = -1
|
||
|
}
|
||
|
}
|
||
|
|
||
|
floatPoints.length = ptr
|
||
|
|
||
|
// Do a second pass to fix up missing labels
|
||
|
for (var i = 0; i < numPoints; ++i) {
|
||
|
if (labels[i] < 0) {
|
||
|
labels[i] = labels[uf.find(i)]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Return resulting union-find data structure
|
||
|
return labels
|
||
|
}
|
||
|
|
||
|
function compareLex2 (a, b) { return (a[0] - b[0]) || (a[1] - b[1]) }
|
||
|
function compareLex3 (a, b) {
|
||
|
var d = (a[0] - b[0]) || (a[1] - b[1])
|
||
|
if (d) {
|
||
|
return d
|
||
|
}
|
||
|
if (a[2] < b[2]) {
|
||
|
return -1
|
||
|
} else if (a[2] > b[2]) {
|
||
|
return 1
|
||
|
}
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
// Remove duplicate edge labels
|
||
|
function dedupEdges (edges, labels, useColor) {
|
||
|
if (edges.length === 0) {
|
||
|
return
|
||
|
}
|
||
|
if (labels) {
|
||
|
for (var i = 0; i < edges.length; ++i) {
|
||
|
var e = edges[i]
|
||
|
var a = labels[e[0]]
|
||
|
var b = labels[e[1]]
|
||
|
e[0] = Math.min(a, b)
|
||
|
e[1] = Math.max(a, b)
|
||
|
}
|
||
|
} else {
|
||
|
for (var i = 0; i < edges.length; ++i) {
|
||
|
var e = edges[i]
|
||
|
var a = e[0]
|
||
|
var b = e[1]
|
||
|
e[0] = Math.min(a, b)
|
||
|
e[1] = Math.max(a, b)
|
||
|
}
|
||
|
}
|
||
|
if (useColor) {
|
||
|
edges.sort(compareLex3)
|
||
|
} else {
|
||
|
edges.sort(compareLex2)
|
||
|
}
|
||
|
var ptr = 1
|
||
|
for (var i = 1; i < edges.length; ++i) {
|
||
|
var prev = edges[i - 1]
|
||
|
var next = edges[i]
|
||
|
if (next[0] === prev[0] && next[1] === prev[1] &&
|
||
|
(!useColor || next[2] === prev[2])) {
|
||
|
continue
|
||
|
}
|
||
|
edges[ptr++] = next
|
||
|
}
|
||
|
edges.length = ptr
|
||
|
}
|
||
|
|
||
|
function preRound (points, edges, useColor) {
|
||
|
var labels = dedupPoints(points, [], boundPoints(points))
|
||
|
dedupEdges(edges, labels, useColor)
|
||
|
return !!labels
|
||
|
}
|
||
|
|
||
|
// Repeat until convergence
|
||
|
function snapRound (points, edges, useColor) {
|
||
|
// 1. find edge crossings
|
||
|
var edgeBounds = boundEdges(points, edges)
|
||
|
var crossings = getCrossings(points, edges, edgeBounds)
|
||
|
|
||
|
// 2. find t-junctions
|
||
|
var vertBounds = boundPoints(points)
|
||
|
var tjunctions = getTJunctions(points, edges, edgeBounds, vertBounds)
|
||
|
|
||
|
// 3. cut edges, construct rational points
|
||
|
var ratPoints = cutEdges(points, edges, crossings, tjunctions, useColor)
|
||
|
|
||
|
// 4. dedupe verts
|
||
|
var labels = dedupPoints(points, ratPoints, vertBounds)
|
||
|
|
||
|
// 5. dedupe edges
|
||
|
dedupEdges(edges, labels, useColor)
|
||
|
|
||
|
// 6. check termination
|
||
|
if (!labels) {
|
||
|
return (crossings.length > 0 || tjunctions.length > 0)
|
||
|
}
|
||
|
|
||
|
// More iterations necessary
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// Main loop, runs PSLG clean up until completion
|
||
|
function cleanPSLG (points, edges, colors) {
|
||
|
// If using colors, augment edges with color data
|
||
|
var prevEdges
|
||
|
if (colors) {
|
||
|
prevEdges = edges
|
||
|
var augEdges = new Array(edges.length)
|
||
|
for (var i = 0; i < edges.length; ++i) {
|
||
|
var e = edges[i]
|
||
|
augEdges[i] = [e[0], e[1], colors[i]]
|
||
|
}
|
||
|
edges = augEdges
|
||
|
}
|
||
|
|
||
|
// First round: remove duplicate edges and points
|
||
|
var modified = preRound(points, edges, !!colors)
|
||
|
|
||
|
// Run snap rounding until convergence
|
||
|
while (snapRound(points, edges, !!colors)) {
|
||
|
modified = true
|
||
|
}
|
||
|
|
||
|
// Strip color tags
|
||
|
if (!!colors && modified) {
|
||
|
prevEdges.length = 0
|
||
|
colors.length = 0
|
||
|
for (var i = 0; i < edges.length; ++i) {
|
||
|
var e = edges[i]
|
||
|
prevEdges.push([e[0], e[1]])
|
||
|
colors.push(e[2])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return modified
|
||
|
}
|