Skip to content

Instantly share code, notes, and snippets.

@alexmacy
Last active September 18, 2017 02:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexmacy/23e3e4238884e8bba826246a815817cc to your computer and use it in GitHub Desktop.
Save alexmacy/23e3e4238884e8bba826246a815817cc to your computer and use it in GitHub Desktop.
voronoi.find() and resizing charts
license: mit

The buttons at the top let you add more charts and snap them to the screen tiled to all be visible. The charts are also interactive in that they can be resized and moved. They are also responsive to mouseover events, using voronoi.find() to highlight the cell in each chart for the mouse's relative location.

Also see Philippe Rivière's block Voronoi.find(x,y) .

<!DOCTYPE html>
<html>
<script src="//d3js.org/d3.v4.min.js"></script>
<style>
.edges,.corners rect {
fill: white;
fill-opacity: 0;
width: 20px;
height: 20px;
}
.controls {
position: absolute;
top: 10px;
right: 10px;
z-index: 9999;
}
svg {
border: 1px solid black;
position: absolute;
background: rgba(255, 255, 255, .9);
}
.e, .w {
cursor: ew-resize;
}
.n, .s {
cursor: ns-resize;
}
.n-w, .s-e {
cursor: nwse-resize;
}
.s-w, .n-e {
cursor: nesw-resize;
}
path {
fill: none;
stroke: black;
}
</style>
<body>
<div class="controls">
<button onclick="charts.push(new Chart())">New Chart</button>
<button onclick="snap()">Snap</button>
</div>
<div class="chartDiv" style="position: absolute;top: 0px;left: 0px;">
</div>
</body>
<script>
const edgeWidth = 20;
const dragVals = { x: 0, y: 0, w: 0, top: 0, n: 0, width: 0, height: 0 };
const charts = [];
let maxZ = 0;
const Chart = function() {
let width = innerWidth / 2;
let height = innerHeight / 2;
let svgX = 0;
let svgY = 0;
const chartNum = charts.length;
const data = d3.range(200).map(function(d) {return [Math.random(), Math.random()]})
const voronoi = d3.voronoi().size([1, 1]);
const diagram = voronoi(data);
const path = d3.line()
.x(function(d) {return d[0] * (width - edgeWidth * 2)})
.y(function(d) {return d[1] * (height - edgeWidth * 2)});
const svg = d3.select(".chartDiv").append("svg")
.style("z-index", ++maxZ);
const edges = svg.append("g")
.attr("class", "edges")
.selectAll("rect")
.data(["n", "e", "s", "w"])
.enter().append("rect")
.attr("class", function(d) {return d})
.call(d3.drag().on("start", dragStarted).on("drag", resized))
const corners = svg.append("g")
.attr("class", "corners")
.selectAll("rect")
.data(["n-w", "n-e", "s-w", "s-e"])
.enter().append("rect")
.attr("class", function(d) {return d})
.call(d3.drag().on("start", dragStarted).on("drag", resized))
const chartBody = svg.append("g")
.style("stroke", "black")
.attr("transform", `translate(${edgeWidth}, ${edgeWidth})`)
const circle = chartBody.append("circle")
.style("fill", "#119911")
.style("opacity", .7)
const polygons = chartBody.selectAll("path")
.data(diagram.polygons())
.enter().append("path")
const mouseSensor = chartBody.append("rect")
.attr("class", "sensor")
.style("fill-opacity", 0)
.style("cursor", "move")
.call(d3.drag().on("start", dragStarted).on("drag", resized))
.on("mousemove", function() {
const mouseX = (d3.event.layerX - edgeWidth) / (width - edgeWidth * 2);
const mouseY = (d3.event.layerY - edgeWidth) / (height - edgeWidth * 2);
for (let chart of charts) chart.highlight(mouseX, mouseY);
})
.on("mouseout", function() {
return d3.selectAll("path").style("fill", "none")
})
reDraw()
this.highlight = function(x, y) {
const cellNum = diagram.find(x, y).index
polygons.style("fill", function(d, i) {
return i === cellNum ? "red" : "none"
});
}
this.snap = function() {
const split = charts.length % 2 === 0;
width = split ? innerWidth / 2 : innerWidth;
height = split ? innerHeight / (charts.length / 2) : innerHeight / charts.length;
svgX = split ? chartNum % 2 * width : 0;
svgY = split ? Math.floor(chartNum / 2) * height : chartNum * height;
reDraw(500);
}
function dragStarted() {
svg.style("z-index", ++maxZ);
dragVals.x = d3.event.x;
dragVals.y = d3.event.y;
dragVals.w = svgX;
dragVals.n = svgY;
dragVals.width = width;
dragVals.height = height;
}
function resized(d) {
const dx = d3.event.x - dragVals.x;
const dy = d3.event.y - dragVals.y;
if (!d) {
svgX += d3.event.x - dragVals.x;
svgY += d3.event.y - dragVals.y;
return reDraw();
}
if (d.includes("e")) {
width = Math.max(50, Math.min(innerWidth - svgX, dragVals.width + dx));
}
if (d.includes("s")) {
height = Math.max(50, Math.min(innerHeight - svgY, dragVals.height + dy));
}
if (d.includes("w")) {
if (width - dx < 50) return;
svgX = Math.max(0, svgX + dx);
width = svgX === 0 ? dragVals.width + dragVals.w : width - dx;
}
if (d.includes("n")) {
if (height - dy < 50) return;
svgY = Math.max(0, svgY + dy);
height = svgY === 0 ? dragVals.height + dragVals.n : height - dy;
}
reDraw()
}
function reDraw(t = 0) {
svg.transition().duration(t)
.attr("width", width + "px")
.attr("height", height + "px")
.style("left", svgX + "px")
.style("top", svgY + "px")
edges.transition().duration(t)
.attr("width", function(d) {return d === "e" || d === "w" ? edgeWidth : width})
.attr("height", function(d) {return d === "n" || d === "s" ? edgeWidth : height})
.attr("x", function(d) {return d === "e" ? width - edgeWidth : 0})
.attr("y", function(d) {return d === "s" ? height - edgeWidth : 0});
corners.transition().duration(t)
.attr("x", function(d) {return d.includes("e") ? width - edgeWidth : 0})
.attr("y", function(d) {return d.includes("s") ? height - edgeWidth : 0});
chartBody.transition().duration(t)
.attr("width", width - edgeWidth * 2)
.attr("height", height - edgeWidth * 2)
circle.transition().duration(t)
.attr("cx", width / 2 - edgeWidth)
.attr("cy", height / 2 - edgeWidth)
.attr("r", Math.min(width, height) / 4)
mouseSensor.transition().duration(t)
.attr("width", width - edgeWidth * 2)
.attr("height", height - edgeWidth * 2)
polygons.transition().duration(t)
.attr("d", path)
}
}
function snap() {
charts.forEach(function(d) {return d.snap()})
}
charts.push(new Chart());
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment