t-viSNE: Interactive Assessment and Interpretation of t-SNE Projections
https://doi.org/10.1109/TVCG.2020.2986996
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.
125 lines
2.9 KiB
125 lines
2.9 KiB
//import * as d3 from 'd3';
|
|
|
|
function polygonToPath(polygon) {
|
|
return ("M" + (polygon.map(function (d) { return d.join(','); }).join('L')));
|
|
}
|
|
|
|
function distance(pt1, pt2) {
|
|
return Math.sqrt(Math.pow( (pt2[0] - pt1[0]), 2 ) + Math.pow( (pt2[1] - pt1[1]), 2 ));
|
|
}
|
|
|
|
function lasso() {
|
|
var dispatch = d3.dispatch('start', 'end');
|
|
|
|
// distance last point has to be to first point before it auto closes when mouse is released
|
|
var closeDistance = 75;
|
|
|
|
function lasso(root) {
|
|
// append a <g> with a rect
|
|
var g = root.append('g').attr('class', 'lasso-group');
|
|
var bbox = root.node().getBoundingClientRect();
|
|
var area = g
|
|
.append('rect')
|
|
.attr('width', bbox.width)
|
|
.attr('height', bbox.height)
|
|
.attr('fill', 'tomato')
|
|
.attr('opacity', 0);
|
|
|
|
var drag = d3
|
|
.drag()
|
|
.on('start', handleDragStart)
|
|
.on('drag', handleDrag)
|
|
.on('end', handleDragEnd);
|
|
|
|
area.call(drag);
|
|
|
|
var lassoPolygon;
|
|
var lassoPath;
|
|
var closePath;
|
|
|
|
function handleDragStart() {
|
|
lassoPolygon = [d3.mouse(this)];
|
|
if (lassoPath) {
|
|
lassoPath.remove();
|
|
}
|
|
|
|
lassoPath = g
|
|
.append('path')
|
|
.attr('fill', '#0bb')
|
|
.attr('fill-opacity', 0.1)
|
|
.attr('stroke', '#0bb')
|
|
.attr('stroke-dasharray', '3, 3');
|
|
|
|
closePath = g
|
|
.append('line')
|
|
.attr('x2', lassoPolygon[0][0])
|
|
.attr('y2', lassoPolygon[0][1])
|
|
.attr('stroke', '#0bb')
|
|
.attr('stroke-dasharray', '3, 3')
|
|
.attr('opacity', 0);
|
|
|
|
dispatch.call('start', lasso, lassoPolygon);
|
|
}
|
|
|
|
function handleDrag() {
|
|
var point = d3.mouse(this);
|
|
lassoPolygon.push(point);
|
|
lassoPath.attr('d', polygonToPath(lassoPolygon));
|
|
|
|
// indicate if we are within closing distance
|
|
if (
|
|
distance(lassoPolygon[0], lassoPolygon[lassoPolygon.length - 1]) <
|
|
closeDistance
|
|
) {
|
|
closePath
|
|
.attr('x1', point[0])
|
|
.attr('y1', point[1])
|
|
.attr('opacity', 1);
|
|
} else {
|
|
closePath.attr('opacity', 0);
|
|
}
|
|
}
|
|
|
|
function handleDragEnd() {
|
|
// remove the close path
|
|
closePath.remove();
|
|
closePath = null;
|
|
|
|
// succesfully closed
|
|
if (
|
|
distance(lassoPolygon[0], lassoPolygon[lassoPolygon.length - 1]) <
|
|
closeDistance
|
|
) {
|
|
lassoPath.attr('d', polygonToPath(lassoPolygon) + 'Z');
|
|
dispatch.call('end', lasso, lassoPolygon);
|
|
|
|
// otherwise cancel
|
|
} else {
|
|
lassoPath.remove();
|
|
lassoPath = null;
|
|
lassoPolygon = null;
|
|
}
|
|
}
|
|
|
|
lasso.reset = function () {
|
|
if (lassoPath) {
|
|
lassoPath.remove();
|
|
lassoPath = null;
|
|
}
|
|
|
|
lassoPolygon = null;
|
|
if (closePath) {
|
|
closePath.remove();
|
|
closePath = null;
|
|
}
|
|
};
|
|
}
|
|
|
|
lasso.on = function (type, callback) {
|
|
dispatch.on(type, callback);
|
|
return lasso;
|
|
};
|
|
|
|
return lasso;
|
|
}
|
|
|
|
|