parent
4c679fbffc
commit
a59dd0dcf6
2 changed files with 265 additions and 0 deletions
@ -0,0 +1,186 @@ |
||||
<!DOCTYPE html> |
||||
<meta charset="utf-8"> |
||||
<style> |
||||
|
||||
.links { |
||||
stroke: #000; |
||||
stroke-opacity: 0; |
||||
} |
||||
|
||||
.polygons { |
||||
stroke: #fff; |
||||
} |
||||
|
||||
.polygons :first-child { |
||||
fill: #ff3d5a; |
||||
} |
||||
|
||||
.sites { |
||||
fill: none; |
||||
stroke: none; |
||||
} |
||||
|
||||
.sites :first-child { |
||||
fill: #fff; |
||||
} |
||||
|
||||
.convex-hull { |
||||
fill: none; |
||||
stroke: #feccd5; |
||||
stroke-width: 5px; |
||||
} |
||||
|
||||
</style> |
||||
<svg width="960" height="800"></svg> |
||||
<script src="https://d3js.org/d3.v4.min.js"></script> |
||||
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script> |
||||
<script> |
||||
|
||||
var sourcedata = [ 100, 100, 200, 130, 400, 150, 160, 170, 130, 900, 100, 1000, 200, 130, 40, 150, 160, 170, 130 ]; |
||||
|
||||
var svg = d3.select("svg"), |
||||
width = +svg.attr("width"), |
||||
height = +svg.attr("height"), |
||||
margin = 0.4; // 0 ≤ m < 0.5 |
||||
|
||||
var color = d3.scaleOrdinal(d3.schemePastel1), |
||||
line = d3.line().curve(d3.curveCatmullRomClosed); |
||||
|
||||
function makeData (inputData) { |
||||
var outputData = []; |
||||
// First sum all the values. Then calculate a percentage of the whole. |
||||
var sum = inputData.reduce(function(acc, val) { |
||||
return acc + val; |
||||
}, 0); |
||||
|
||||
// Now turn the single data point into a pair of length, angle |
||||
for (var i = 0; i < inputData.length; i++) { |
||||
// calc the percentage |
||||
var value = 5 * inputData[i] / sum; |
||||
|
||||
// calculate length and angle from that percentage |
||||
var len = Math.min(width, height) / 2 * |
||||
(1 - margin) * Math.sqrt(value); |
||||
var angle = value * 2 * Math.PI; |
||||
|
||||
// stick the length and angle into the output data |
||||
outputData[i] = [ width / 2 + len * Math.cos(angle), |
||||
height / 2 + len * Math.sin(angle) ]; |
||||
} |
||||
return outputData; |
||||
} |
||||
|
||||
var sites = makeData( sourcedata ); |
||||
|
||||
var voronoi = d3.voronoi() |
||||
.extent([[-1, -1], [width + 1, height + 1]]); |
||||
|
||||
var convexhull = svg.append('path') |
||||
.attr('class', 'convex-hull'); |
||||
|
||||
var polygon = svg.append("g") |
||||
.attr("class", "polygons"); |
||||
|
||||
var link = svg.append("g") |
||||
.attr("class", "links"); |
||||
|
||||
var site = svg.append("g") |
||||
.attr("class", "sites"); |
||||
|
||||
redraw(); |
||||
|
||||
function moved() { |
||||
sites[0] = d3.mouse(this); |
||||
redraw(); |
||||
} |
||||
|
||||
function redraw() { |
||||
|
||||
var links = voronoi.links(sites), |
||||
ext = Math.sqrt(d3.median(links, function(l) { |
||||
var dx = l.source[0] - l.target[0], |
||||
dy = l.source[1] - l.target[1]; |
||||
return dx*dx + dy*dy; |
||||
})); |
||||
|
||||
var convex = d3.polygonHull(sites); |
||||
convex.centroid = d3.polygonCentroid(convex); |
||||
convex = convex.map(function(p){ |
||||
var dx = p[0] - convex.centroid[0], |
||||
dy = p[1] - convex.centroid[1], |
||||
angle = Math.atan2(dy, dx); |
||||
return [p[0] + Math.cos(angle) * ext, p[1] + Math.sin(angle) * ext]; |
||||
}); |
||||
|
||||
var sites2 = sites.slice(); // clone |
||||
for (var i = 0; i < convex.length; i++) { |
||||
var n = convex[i], m = convex[i+1]||convex[0]; |
||||
var dx = n[0] - m[0], |
||||
dy = n[1] - m[1], |
||||
dist = Math.sqrt(dx * dx + dy * dy); |
||||
var pts = 10 * Math.ceil(dist / 2 / ext); |
||||
for(var j=0; j <= pts; j++) { |
||||
var p = [m[0] + dx *j / pts, m[1] + dy * j / pts]; |
||||
p.artificial = 1; |
||||
sites2.push(p); |
||||
} |
||||
} |
||||
|
||||
var diagram = voronoi(sites2); |
||||
|
||||
var p = polygon.selectAll("path") |
||||
.data(diagram.polygons()); |
||||
p.enter().append("path").merge(p).call(redrawPolygon) |
||||
p.exit().remove(); |
||||
|
||||
var l = link |
||||
.selectAll("line") |
||||
.data(diagram.links().filter(function(l){ |
||||
return !l.source.artificial && !l.target.artificial; |
||||
})); |
||||
l.enter() |
||||
.append("line") |
||||
.merge(l) |
||||
.call(redrawLink) |
||||
.exit() |
||||
.remove(); |
||||
|
||||
var s = site |
||||
.selectAll("circle") |
||||
.data(sites); |
||||
s.enter() |
||||
.append("circle") |
||||
.attr("r", 2.5) |
||||
.merge(s) |
||||
.call(redrawSite); |
||||
|
||||
convexhull.attr('d', line(convex)); |
||||
|
||||
} |
||||
|
||||
function redrawPolygon(polygon) { |
||||
polygon |
||||
.attr("fill", function(d, i) { |
||||
return i < sites.length ? color(i) : 'none'; |
||||
}) |
||||
.attr("stroke-width", function(d, i) { |
||||
return i < sites.length ? 2 : 0; |
||||
}) |
||||
.attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; }); |
||||
} |
||||
|
||||
function redrawLink(link) { |
||||
link |
||||
.attr("x1", function(d) { return d.source[0]; }) |
||||
.attr("y1", function(d) { return d.source[1]; }) |
||||
.attr("x2", function(d) { return d.target[0]; }) |
||||
.attr("y2", function(d) { return d.target[1]; }); |
||||
} |
||||
|
||||
function redrawSite(site) { |
||||
site |
||||
.attr("cx", function(d) { return d[0]; }) |
||||
.attr("cy", function(d) { return d[1]; }); |
||||
} |
||||
|
||||
</script> |
@ -0,0 +1,79 @@ |
||||
<!DOCTYPE html> |
||||
<meta charset="utf-8"> |
||||
<canvas width="960" height="500"></canvas> |
||||
<script src="https://d3js.org/d3.v4.min.js"></script> |
||||
<script> |
||||
|
||||
var canvas = d3.select("canvas").on("touchmove mousemove", moved).node(), |
||||
context = canvas.getContext("2d"), |
||||
width = canvas.width, |
||||
height = canvas.height; |
||||
|
||||
var sites = d3.range(100) |
||||
.map(function(d) { return [Math.random() * width, Math.random() * height]; }); |
||||
|
||||
var voronoi = d3.voronoi() |
||||
.extent([[-1, -1], [width + 1, height + 1]]); |
||||
|
||||
redraw(); |
||||
|
||||
function moved() { |
||||
sites[0] = d3.mouse(this); |
||||
redraw(); |
||||
} |
||||
|
||||
function redraw() { |
||||
var diagram = voronoi(sites), |
||||
links = diagram.links(), |
||||
polygons = diagram.polygons(); |
||||
|
||||
context.clearRect(0, 0, width, height); |
||||
context.beginPath(); |
||||
drawCell(polygons[0]); |
||||
context.fillStyle = "#f00"; |
||||
context.fill(); |
||||
|
||||
context.beginPath(); |
||||
for (var i = 0, n = polygons.length; i < n; ++i) drawCell(polygons[i]); |
||||
context.strokeStyle = "#000"; |
||||
context.stroke(); |
||||
|
||||
context.beginPath(); |
||||
for (var i = 0, n = links.length; i < n; ++i) drawLink(links[i]); |
||||
context.strokeStyle = "rgba(0,0,0,0.2)"; |
||||
context.stroke(); |
||||
|
||||
context.beginPath(); |
||||
drawSite(sites[0]); |
||||
context.fillStyle = "#fff"; |
||||
context.fill(); |
||||
|
||||
context.beginPath(); |
||||
for (var i = 1, n = sites.length; i < n; ++i) drawSite(sites[i]); |
||||
context.fillStyle = "#000"; |
||||
context.fill(); |
||||
context.strokeStyle = "#fff"; |
||||
context.stroke(); |
||||
} |
||||
|
||||
function drawSite(site) { |
||||
context.moveTo(site[0] + 2.5, site[1]); |
||||
context.arc(site[0], site[1], 2.5, 0, 2 * Math.PI, false); |
||||
} |
||||
|
||||
function drawLink(link) { |
||||
context.moveTo(link.source[0], link.source[1]); |
||||
context.lineTo(link.target[0], link.target[1]); |
||||
} |
||||
|
||||
function drawCell(cell) { |
||||
if (!cell) return false; |
||||
context.moveTo(cell[0][0], cell[0][1]); |
||||
for (var j = 1, m = cell.length; j < m; ++j) { |
||||
context.lineTo(cell[j][0], cell[j][1]); |
||||
} |
||||
context.closePath(); |
||||
return true; |
||||
} |
||||
|
||||
</script> |
Loading…
Reference in new issue