function renderSVGGl() {
const div = DOM.element("div");
div.style.position = "relative";
const svg = d3.select(DOM.svg(width, height));
const tooltip = d3
.select(div)
.append("div")
.attr("class", "tooltip")
.style("display", "none");
svg.style("position", "relative");
svg.style("z-index", "1000");
svg.style("display", "block");
svg.style("width", "100%");
pixiApp.view.style.position = "absolute";
pixiApp.view.style.width = "100%";
pixiApp.view.style.height = "auto";
div.appendChild(pixiApp.view);
div.appendChild(svg.node());
document.body.appendChild(div);
const xScaleZoom = xScale.copy();
const yScaleZoom = yScale.copy();
const delaunay = d3.Delaunay.from(
filterData.map((e) => [xScale(e.x) * maxZoom, yScale(e.y) * maxZoom])
);
const voronoi = delaunay.voronoi(extent.flat().map((e) => e * maxZoom));
let currScale = 1;
// 2. CONTROL HOW CIRCLES GET REDRAWN AFTER UPDATES (eg zoom)
const redrawCtx = () => {
pixiPoints.forEach((e, i) => {
e.x = xScaleZoom(filterData[i].x);
e.y = yScaleZoom(filterData[i].y);
if (e.selected) {
e.scale.x = 1.5 * dotScale(currScale);
e.scale.y = 1.5 * dotScale(currScale);
e.tint = getCircleColor(filterData[i]);
e.alpha = 0.4;
} else {
e.scale.x = dotScale(currScale);
e.scale.y = dotScale(currScale);
e.tint = getCircleColor(filterData[i]);
e.alpha = getCircleAlpha(filterData[i]);
}
});
};
// 3. IMPLEMENT ZOOM HANDLERS
function zoomed(event, d) {
currScale = event.transform.k;
xScaleZoom.range(
[margin.left, width - margin.right].map((d) => event.transform.applyX(d))
);
yScaleZoom.range(
[margin.top, height - margin.bottom].map((d) => event.transform.applyY(d))
);
updateAnnotations(xScaleZoom, yScaleZoom, currScale);
redrawCtx();
}
const zoom = d3
.zoom()
.scaleExtent([1, maxZoom])
.translateExtent([
[0, 0],
[width, height]
])
.extent(extent)
.on("zoom", zoomed);
let tick = false;
svg.on("mousemove", (event, d) => {
if (!tick) {
window.requestAnimationFrame(() => {
mouseMoved(event);
tick = false;
});
tick = true;
}
});
// 4. HANDLE MOUSE INTERACTION
function mouseMoved(event) {
const [rmx, rmy] = [event.layerX, event.layerY];
const [mx, my] = [
xScale(xScaleZoom.invert(rmx)) * maxZoom,
yScale(yScaleZoom.invert(rmy)) * maxZoom
];
const tooltip_width = parseInt(tooltip.style("width"));
pointsContainer.children.forEach((d) => {
d.selected = false;
});
const voronoi_pt_idx = delaunay.find(mx, my);
const points_container_last_idx = pointsContainer.children.length - 1;
if (pointsContainer.children[points_container_last_idx].selected) {
pointsContainer.children[points_container_last_idx].selected = false; // deselect previous element
tooltip.style("display", "none");
}
if (voronoi_pt_idx !== null) {
const d = filterData[voronoi_pt_idx];
tooltip
.style("display", "block")
.attr("data-px", `${d.x}`)
.attr("data-py", d.y)
.style("left", `${xScaleZoom(d.x)}px`)
.style("top", `${yScaleZoom(d.y)}px`)
.html(generateTooltipHTMLContent(d));
const ind = pointsContainer.children.findIndex(
(e) => e.orig_idx === voronoi_pt_idx
);
pointsContainer.children[ind].selected = true; //select over element
const temp = pointsContainer.children[points_container_last_idx];
pointsContainer.children[points_container_last_idx] =
pointsContainer.children[ind];
pointsContainer.children[ind] = temp;
redrawCtx();
} else {
tooltip.style("display", "none");
}
}
// FINALLY, BIND IT ALL TOGETHER: ---------------------------------------
// 5. HOOK UP ZOOM HANDLERS TO svg CANVAS
svg
.on("click", (e) => {
const [rmx, rmy] = [d3.event.layerX, d3.event.layerY];
const [mx, my] = [xScale.invert(rmx), yScale.invert(rmy)];
})
.call(zoom)
.call(zoom.scaleTo, 1)
.on("dblclick.zoom", (e) => {
svg.transition().duration(750).call(zoom.transform, d3.zoomIdentity);
});
// 6. ADD ANNOTATIONS
svg
.append("g")
.attr("class", "annotation-group")
.style("font-size", "1.2em")
.call(makeAnnotations);
return { div, redrawCtx };
}