|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
|
|
.axis path, |
|
.axis line { |
|
fill: none; |
|
stroke: #000; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
.axis text { |
|
font: 10px sans-serif; |
|
cursor: default; |
|
} |
|
|
|
.cell .voronoi { |
|
fill: transparent; |
|
//stroke: grey; |
|
} |
|
|
|
.path-to-axis { |
|
shape-rendering: crispEdges; |
|
opacity: 0.25; |
|
} |
|
|
|
.info { |
|
fill: grey; |
|
} |
|
|
|
</style> |
|
<svg width="960" height="500"></svg> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="https://raw.githack.com/Kcnarf/d3-beeswarm/master/build/d3-beeswarm.js"></script> |
|
<script> |
|
|
|
var svg = d3.select("svg"), |
|
margin = {top: 40, right: 40, bottom: 40, left: 40}, |
|
width = svg.attr("width") - margin.left - margin.right, |
|
height = svg.attr("height") - margin.top - margin.bottom; |
|
beeswarmHeight = 100; |
|
axisHeight = 25; |
|
|
|
var formatValue = d3.format(",d"); |
|
|
|
var x = d3.scaleLog() |
|
.rangeRound([0, width]); |
|
|
|
var deviation = d3.scaleLinear() //represente position deviation with color |
|
.range(['black','red']); |
|
|
|
var g = svg.append("g") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|
|
|
d3.csv("flare.csv", type, function(error, data) { |
|
if (error) throw error; |
|
|
|
x.domain(d3.extent(data, function(d) { return d.value; })); |
|
|
|
g.append("g") |
|
.classed("axis axis--x", true) |
|
.attr("transform", "translate(0," + (beeswarmHeight) + ")") |
|
.call(d3.axisBottom(x).ticks(20, ".0s")); |
|
|
|
//begin: arrange data with d3-force layout |
|
var simulation = d3.forceSimulation(data) |
|
.force("x", d3.forceX(function(d) { return x(d.value); }).strength(1)) |
|
.force("y", d3.forceY(beeswarmHeight/2-axisHeight/2)) |
|
.force("collide", d3.forceCollide(4)) |
|
.stop(); |
|
|
|
for (var i = 0; i < 120; ++i) simulation.tick(); |
|
//end: arrange data with d3-force layout |
|
|
|
//begin: arrange data with d3-beeswarm |
|
var beeswarm = d3.beeswarm() |
|
.data(data.sort(function(a,b) { return b.value-a.value; })) |
|
.radius(4) |
|
.distributeOn(function(d) { return x(d.value);}) |
|
.arrange(); |
|
//end: arrange data with d3-beeswarm |
|
|
|
//represente position deviation with color |
|
deviation.domain(d3.extent(data, function(d) { return Math.abs(d.x-x(d.value)); })); |
|
|
|
//reorder data so that highiest position deviation are drawn last/on top |
|
data.sort(function(a,b) { return Math.abs(b.x-x(b.value))-Math.abs(a.x-x(a.value)); }); |
|
|
|
|
|
//begin: draw force-based arrangement |
|
var forceVoronoi = d3.voronoi() |
|
.extent([[-margin.left,0], [width+margin.right, beeswarmHeight-axisHeight]]) |
|
.x(function(d) { return d.x; }) |
|
.y(function(d) { return d.y; }) |
|
.polygons(data); |
|
|
|
var forceCellContainer = g.append("g") |
|
.classed("cell-container force", true); |
|
|
|
forceCellContainer.append("text") |
|
.classed("info", true) |
|
.attr("transform", "translate("+width+",0)") |
|
.attr("text-anchor", "end") |
|
.text("force-based arrangement"); |
|
|
|
var forcePathToAxisContainer = forceCellContainer.append("g"); |
|
|
|
forcePathToAxisContainer.selectAll(".path-to-axis") |
|
.data(forceVoronoi) |
|
.enter() |
|
.append("path") |
|
.classed("path-to-axis", true) |
|
.attr("data-flare-id", function(d) { return d.data.id; }) |
|
.attr("d", function(d) { return "M"+[d.data.x, beeswarmHeight-axisHeight]+"V"+(beeswarmHeight); }) |
|
.style("stroke", function(d){ return deviation(Math.abs(d.data.x-x(d.data.value))); }) |
|
.on("mouseenter", function(d) { highlight(d.data); }) |
|
.on("mouseout", function(d) { trivialize(d.data); }); |
|
|
|
var forceCell = forceCellContainer.selectAll(".cell.force").data(forceVoronoi) |
|
.enter() |
|
.append("g") |
|
.classed("cell force", true) |
|
.attr("data-flare-id", function(d) { return d.data.id; }); |
|
forceCell.append("circle") |
|
.attr("r", 3) |
|
.attr("cx", function(d) { return d.data.x; }) |
|
.attr("cy", function(d) { return d.data.y; }) |
|
.style("fill", function(d){ return deviation(Math.abs(d.data.x-x(d.data.value))); }); |
|
|
|
forceCell.append("path") |
|
.classed("voronoi", true) |
|
.attr("d", function(d) { return "M" + d.join("L") + "Z"; }) |
|
.on("mouseenter", function(d) { highlight(d.data); }) |
|
.on("mouseout", function(d) { trivialize(d.data); }); |
|
|
|
forceCell.append("title") |
|
.text(function(d) { return d.data.id + "\n" + formatValue(d.data.value); }); |
|
//end: draw force-based arrangement |
|
|
|
//begin: draw d3-beeswarm arrangement |
|
var beeswarmVoronoi = d3.voronoi() |
|
.extent([[-margin.left, -beeswarmHeight/2], [width + margin.right, beeswarmHeight/2]]) |
|
.x(function(d) { return d.x; }) |
|
.y(function(d) { return d.y; }) |
|
.polygons(beeswarm); |
|
|
|
var beeswarmCellContainer = g.append("g") |
|
.classed("cell-container beeswarm", true) |
|
.attr("transform", "translate(0,"+(beeswarmHeight)+")"); |
|
|
|
beeswarmCellContainer.append("text") |
|
.classed("info", true) |
|
.attr("transform", "translate("+[width, beeswarmHeight+axisHeight]+")") |
|
.attr("text-anchor", "end") |
|
.text("d3-beeswarm arrangement"); |
|
|
|
var beeswarmPathToAxisContainer = beeswarmCellContainer.append("g"); |
|
|
|
beeswarmPathToAxisContainer.selectAll(".path-to-axis") |
|
.data(beeswarmVoronoi) |
|
.enter() |
|
.append("path") |
|
.classed("path-to-axis", true) |
|
.attr("data-flare-id", function(d) { return d.data.datum.id;}) |
|
.attr("d", function(d) { return "M"+[d.data.x, axisHeight]+"V0"; }) |
|
.style("stroke", "black") |
|
.on("mouseenter", function(d) { highlight(d.data.datum); }) |
|
.on("mouseout", function(d) { trivialize(d.data.datum); }); |
|
|
|
var beeCell = beeswarmCellContainer.selectAll(".cell.beeswarm").data(beeswarmVoronoi) |
|
.enter() |
|
.append("g") |
|
.classed("cell beeswarm", true) |
|
.attr("data-flare-id", function(d) { return d.data.datum.id; }); |
|
|
|
beeCell.append("circle") |
|
.attr("r", 3) |
|
.attr("cx", function(d) { return d.data.x; }) |
|
.attr("cy", function(d) { return d.data.y+beeswarmHeight/2+axisHeight; }) |
|
.style("fill", function(d){ return deviation(Math.abs(d.data.x-x(d.data.value))); }); |
|
|
|
beeCell.append("path") |
|
.classed("voronoi", true) |
|
.attr("d", function(d) { return "M" + d.join("L") + "Z"; }) |
|
.attr("transform", "translate(0,"+(beeswarmHeight/2+axisHeight)+")") |
|
.on("mouseenter", function(d) { highlight(d.data.datum); }) |
|
.on("mouseout", function(d) { trivialize(d.data.datum); }); |
|
|
|
beeCell.append("title") |
|
.text(function(d) { return d.data.datum.id + "\n" + formatValue(d.data.datum.value); }); |
|
//end: draw d3-beeswarm arrangement |
|
}); |
|
|
|
function highlight (datum) { |
|
d3.selectAll("[data-flare-id='"+datum.id+"'].path-to-axis").style("opacity", 1); |
|
d3.selectAll("[data-flare-id='"+datum.id+"'] circle").attr("r", 4); |
|
} |
|
|
|
function trivialize (datum) { |
|
d3.selectAll("[data-flare-id='"+datum.id+"'].path-to-axis").style("opacity", 0.25); |
|
d3.selectAll("[data-flare-id='"+datum.id+"'] circle").attr("r", 3); |
|
} |
|
|
|
function type(d) { |
|
if (!d.value) return; |
|
d.value = +d.value; |
|
return d; |
|
} |
|
|
|
</script> |