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.
 
 
 
 
 
t-viSNE/modules/d3-annotations/d3-annotator.js

299 lines
8.3 KiB

d3v3.ringNote = function() {
var draggable = false,
controlRadius = 15;
var dragCenter = d3v3.behavior.drag()
.origin(function(d) { return { x: 0, y: 0}; })
.on("drag", dragmoveCenter);
var dragRadius = d3v3.behavior.drag()
.origin(function(d) { return { x: 0, y: 0 }; })
.on("drag", dragmoveRadius);
var dragText = d3v3.behavior.drag()
.origin(function(d) { return { x: 0, y: 0 }; })
.on("drag", dragmoveText);
var path = d3v3.svg.line();
function draw(selection, annotation) {
selection.selectAll(".ring-note").remove();
var gRingNote = selection.selectAll(".ring-note")
.data(annotation)
.enter().append("g")
.attr("class", "ring-note")
.attr("transform", function(d) {
return "translate(" + d.cx + "," + d.cy + ")";
});
var gAnnotation = gRingNote.append("g")
.attr("class", "annotation");
var circle = gAnnotation.append("circle")
.attr("r", function(d) { return d.r; });
var line = gAnnotation.append("path")
.call(updateLine);
var text = gAnnotation.append("text")
.call(updateText);
if (draggable) {
var gControls = gRingNote.append("g")
.attr("class", "controls");
// Draggable circle that moves the circle's location
var center = gControls.append("circle")
.attr("class", "center")
.call(styleControl)
.call(dragCenter);
// Draggable circle that changes the circle's radius
var radius = gControls.append("circle")
.attr("class", "radius")
.attr("cx", function(d) { return d.r; })
.call(styleControl)
.call(dragRadius);
// Make text draggble
text
.style("cursor", "move")
.call(dragText);
}
return selection;
}
draw.draggable = function(_) {
if (!arguments.length) return draggable;
draggable = _;
return draw;
};
// Region in relation to circle, e.g., N, NW, W, SW, etc.
function getRegion(x, y, r) {
var px = r * Math.cos(Math.PI/4),
py = r * Math.sin(Math.PI/4);
var distance = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
if (distance < r) {
return null;
}
else {
if (x > px) {
// East
if (y > py) return "SE";
if (y < -py) return "NE";
if (x > r) return "E";
return null;
}
else if (x < -px) {
// West
if (y > py) return "SW";
if (y < -py) return "NW";
if (x < -r) return "W";
return null;
}
else {
// Center
if (y > r) return "S";
if (y < -r) return "N";
}
}
}
function dragmoveCenter(d) {
var gRingNote = d3v3.select(this.parentNode.parentNode);
d.cx += d3v3.event.x;
d.cy += d3v3.event.y;
gRingNote
.attr("transform", function(d) {
return "translate(" + d.cx + "," + d.cy + ")";
});
}
function dragmoveRadius(d) {
var gRingNote = d3v3.select(this.parentNode.parentNode),
gAnnotation = gRingNote.select(".annotation"),
circle = gAnnotation.select("circle"),
line = gAnnotation.select("path"),
text = gAnnotation.select("text"),
radius = d3v3.select(this);
d.r += d3v3.event.dx;
circle.attr("r", function(d) { return d.r; });
radius.attr("cx", function(d) { return d.r; });
line.call(updateLine);
text.call(updateText);
}
function dragmoveText(d) {
var gAnnotation = d3v3.select(this.parentNode),
line = gAnnotation.select("path"),
text = d3v3.select(this);
d.textOffset[0] += d3v3.event.dx;
d.textOffset[1] += d3v3.event.dy;
text.call(updateText);
line.call(updateLine);
}
function updateLine(selection) {
return selection.attr("d", function(d) {
var x = d.textOffset[0],
y = d.textOffset[1],
lineData = getLineData(x, y, d.r);
return path(lineData);
});
}
function getLineData(x, y, r) {
var region = getRegion(x, y, r);
if (region == null) {
// No line if text is inside the circle
return [];
}
else {
// Cardinal directions
if (region == "N") return [[0, -r], [0, y]];
if (region == "E") return [[r, 0], [x, 0]];
if (region == "S") return [[0, r], [0, y]];
if (region == "W") return [[-r, 0],[x, 0]];
var d0 = r * Math.cos(Math.PI/4),
d1 = Math.min(Math.abs(x), Math.abs(y)) - d0;
// Intermediate directions
if (region == "NE") return [[ d0, -d0], [ d0 + d1, -d0 - d1], [x, y]];
if (region == "SE") return [[ d0, d0], [ d0 + d1, d0 + d1], [x, y]];
if (region == "SW") return [[-d0, d0], [-d0 - d1, d0 + d1], [x, y]];
if (region == "NW") return [[-d0, -d0], [-d0 - d1, -d0 - d1], [x, y]];
}
}
function updateText(selection) {
return selection.each(function(d) {
var x = d.textOffset[0],
y = d.textOffset[1],
region = getRegion(x, y, d.r),
textCoords = getTextCoords(x, y, region);
d3v3.select(this)
.attr("x", textCoords.x)
.attr("y", textCoords.y)
.text(d.text)
.each(function(d) {
var x = d.textOffset[0],
y = d.textOffset[1],
textAnchor = getTextAnchor(x, y, region);
var dx = textAnchor == "start" ? "0.33em" :
textAnchor == "end" ? "-0.33em" : "0";
var dy = textAnchor !== "middle" ? ".33em" :
["NW", "N", "NE"].indexOf(region) !== -1 ? "-.33em" : "1em";
var orientation = textAnchor !== "middle" ? undefined :
["NW", "N", "NE"].indexOf(region) !== -1 ? "bottom" : "top";
d3v3.select(this)
.style("text-anchor", textAnchor)
.attr("dx", dx)
.attr("dy", dy)
.call(wrapText, d.textWidth || 960, orientation);
});
});
}
function getTextCoords(x, y, region) {
if (region == "N") return { x: 0, y: y };
if (region == "E") return { x: x, y: 0 };
if (region == "S") return { x: 0, y: y };
if (region == "W") return { x: x, y: 0 };
return { x: x, y: y };
}
function getTextAnchor(x, y, region) {
if (region == null) {
return "middle";
}
else {
// Cardinal directions
if (region == "N") return "middle";
if (region == "E") return "start";
if (region == "S") return "middle";
if (region == "W") return "end";
var xLonger = Math.abs(x) > Math.abs(y);
// Intermediate directions`
if (region == "NE") return xLonger ? "start" : "middle";
if (region == "SE") return xLonger ? "start" : "middle";
if (region == "SW") return xLonger ? "end" : "middle";
if (region == "NW") return xLonger ? "end" : "middle";
}
}
// Adapted from: https://bl.ocks.org/mbostock/7555321
function wrapText(text, width, orientation) {
text.each(function(d) {
var text = d3v3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 1,
lineHeight = 1.1, // ems
x = text.attr("x"),
dx = text.attr("dx"),
tspan = text.text(null).append("tspan").attr("x", x).attr("dx", dx);
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan")
.attr("x", x)
.attr("dx", dx)
.attr("dy", lineHeight + "em")
.text(word);
lineNumber++;
}
}
var dy;
if (orientation == "bottom") {
dy = -lineHeight * (lineNumber-1) - .33;
}
else if (orientation == "top") {
dy = 1;
}
else {
dy = -lineHeight * ((lineNumber-1) / 2) + .33;
}
text.attr("dy", dy + "em");
});
}
function styleControl(selection) {
selection
.attr("r", controlRadius)
.style("fill-opacity", "0")
.style("stroke", "black")
.style("stroke-dasharray", "3, 3")
.style("cursor", "move");
}
return draw;
};