chart = {
let delaunay = [];
let voronoi;
let viewCoord = { scale: 1, offx: 0, offy: 0 };
let dragStartEventPos = [0, 0];
let dragStartCardPos = [0, 0];
function viewposTrans(event) {
return {
x:
dragStartCardPos[0] +
(event.x - dragStartEventPos[0]) / viewCoord.scale,
y:
dragStartCardPos[1] + (event.y - dragStartEventPos[1]) / viewCoord.scale
};
}
const simulation = d3
.forceSimulation(extSentences)
.force("x", d3.forceX(d => x(d.sx)).strength(0.5))
.force("y", d3.forceY(d => y(d.sy)).strength(0.5))
.force("collide", d3.forceCollide(cardHeight * 0.8).strength(0.5));
simulation.tick(100);
const rootEl = d3.select(
html`<div style="position: relative; width: ${width}px; height: ${height}px;"></div>`
);
const node = rootEl
.append("div")
.attr("id", "view")
.style("width", `${width}`)
.style("height", `${height}`)
.selectAll("div.card")
.data(extSentences)
.join("div")
.attr("class", d => `card ${d._cluster}`)
.style("position", "absolute")
.style("will-change", "transform")
.style(
"transform",
d => `translate(${d.x - cardWidth / 2}px, ${d.y - cardHeight / 2}px)`
)
.style("width", `${cardWidth}px`)
.style("height", `${cardHeight}px`)
.style("background-color", d => getClusterColor(d._cluster))
.style('box-shadow', '2px 2px 8px #0004')
.style('box-sizing', 'border-box')
.style('padding', '0.5em')
.style('overflow', 'hidden')
.style('font-size', '4px')
.style('cursor', 'grab')
.style("transition", "opacity 0.5s ease-out")
.text((d, i) => d._text);
const rootSvg = rootEl
.append("svg")
.attr("viewBox", `${[0, 0, width, height]}`)
.attr("class", "rootSvg");
const mesh = rootSvg
.append("path")
.attr("fill", "none")
.attr("class", "mesh")
.attr("stroke", "black");
const cell = rootEl
.select("svg")
.append("g")
.attr("fill", "none")
.attr("pointer-events", "all")
.selectAll("path")
.data(extSentences)
.join("path")
.attr("class", "cell");
function updatePartition() {
delaunay = d3.Delaunay.from(extSentences, d => d.x, d => d.y);
voronoi = delaunay.voronoi([0, 0, width, height]);
}
function findLabel(px, py) {
const index = delaunay.find(px, py);
const sentence = extSentences[index];
const distance =
((x.invert(px) - sentence.sx) ** 2 + (y.invert(py) - sentence.sy) ** 2) **
0.5;
return distance < 0.1
? sentence._cluster
: d3.max(extSentences, d => d._cluster) + 1;
}
const drag = simulation => {
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.1).restart();
node.filter(p => p === d).raise();
dragStartEventPos = [event.x, event.y];
dragStartCardPos = [d.x, d.y];
const viewpos = viewposTrans(event);
d.fx = viewpos.x;
d.fy = viewpos.y;
}
function dragged(event, d) {
const viewpos = viewposTrans(event);
d.fx = viewpos.x;
d.fy = viewpos.y;
console.log(viewpos.x)
const oldColor = getClusterColor(d._cluster);
const curColor = getClusterColor(findLabel(viewpos.x, viewpos.y));
d3.select(this).style(
"background",
`linear-gradient(135deg, ${oldColor} 30%, ${curColor} 70%
)`
);
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
const viewpos = viewposTrans(event);
d.sx = x.invert(viewpos.x);
d.sy = y.invert(viewpos.y);
d.fx = null;
d.fy = null;
d._cluster = findLabel(event.x, event.y);
d3.select(this).style("background", getClusterColor(d._cluster));
updatePartition();
simulation.nodes(extSentences);
}
return d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
};
let transform = d3.zoomIdentity.scale(0.5);
function zoomed(transform) {
d3.select("g").attr("transform", "translate(" + transform.x + "," + transform.y + ") scale(" + transform.k + ")");
d3.select("#view")
.style('transform-origin', `${transform.x}px ${transform.y}px`)
.style(
"transform",
`scale(${transform.k}) translate(${transform.x}px,${transform.y}px) `
);
viewCoord.scale = transform.k;
viewCoord.offx = transform.x;
viewCoord.offy = transform.y;
}
const zoom = d3
.zoom()
.scaleExtent([0.5, 3])
.on('zoom', ({ transform }) => zoomed(transform));
rootEl.call(zoom);
node.call(drag(simulation));
node.call(updatePartition);
simulation.on("tick", () => {
node.style("transform", d => {
let transform = `translate(${d.x - cardWidth / 2}px, ${d.y -
cardHeight / 2}px)`;
return transform;
});
});
invalidation.then(() => simulation.stop());
return rootEl.node();
}