|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
|
|
|
|
<body> |
|
<button id="clickMe" type="button" |
|
style='font-size:30px; position: absolute;'>Add Nodes</button> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script> |
|
|
|
//SVG setup |
|
const margin = {top: 10, right: 30, bottom: 30, left: 30}, |
|
width = 550 - margin.left - margin.right, |
|
height = 480 - margin.top - margin.bottom; |
|
|
|
|
|
|
|
let allData = d3.range(1000).map((d,i) => ({r: 40 - i * 0.5, |
|
Value: width/2 + d3.randomNormal(0, 1.5)() * 50, |
|
nodeGroup: i <= 23 ? 'llama' : i <= 39 ? 'resp' : 'dsn', |
|
dotValue: i % 2 === 0 ? |
|
d3.randomNormal(8, 2.5)().toFixed(1): |
|
d3.randomNormal(4.5, .75)().toFixed(1)})); |
|
|
|
|
|
//x scales |
|
//x scales |
|
const x = d3.scaleLinear() |
|
.domain(d3.extent(allData, d => +d.Value)) |
|
.rangeRound([0, width]); |
|
|
|
//set up svg |
|
const svg = d3.select("body") |
|
.append("svg") |
|
.attr("width", width + margin.left + margin.right) |
|
.attr("height", height + margin.top + margin.bottom) |
|
.append("g") |
|
.attr("transform", |
|
`translate(${margin.left}, ${margin.top})`); |
|
|
|
const t = d3.transition() |
|
.duration(1000); |
|
|
|
const dataFile = "roster.csv" |
|
|
|
//number of bins for histogram |
|
const nbins = 20; |
|
|
|
//Note: data fetching is done each time the function is ran |
|
//as d3.csv is replaced by tabletop.js request to get data each time |
|
//from google spreadsheet |
|
function update(num){ |
|
// Get the data |
|
allData.forEach(function(d) { |
|
d.Value = +d.Value; |
|
}); |
|
//simulate new data by randomizing/slicing |
|
let data = allData.slice(0, 0+num) |
|
|
|
//histogram binning |
|
const histogram = d3.histogram() |
|
.domain(x.domain()) |
|
.thresholds(x.ticks(nbins)) |
|
.value(function(d) { return d.Value;} ) |
|
|
|
//binning data and filtering out empty bins |
|
const bins = histogram(data).filter(d => d.length>0) |
|
|
|
//g container for each bin |
|
let binContainer = svg.selectAll(".gBin") |
|
.data(bins); |
|
|
|
binContainer.exit().remove() |
|
|
|
let binContainerEnter = binContainer.enter() |
|
.append("g") |
|
.attr("class", "gBin") |
|
.attr("transform", d => `translate(${x(d.x0)}, ${height})`) |
|
|
|
//need to populate the bin containers with data the first time |
|
binContainerEnter.selectAll("circle") |
|
.data(d => d.map((p, i) => { |
|
return {idx: i, |
|
name: p.Name, |
|
value: p.Value, |
|
radius: (x(d.x1)-x(d.x0))/2 |
|
} |
|
})) |
|
.enter() |
|
.append("circle") |
|
.attr("cx", 0) //g element already at correct x pos |
|
.attr("cy", function(d) { |
|
return - d.idx * 2 * d.radius - d.radius; }) |
|
.attr("r", 0) |
|
.transition() |
|
.duration(500) |
|
.attr("r", function(d) { |
|
return (d.length==0) ? 0 : d.radius; }) |
|
|
|
binContainerEnter.merge(binContainer) |
|
.attr("transform", d => `translate(${x(d.x0)}, ${height})`) |
|
|
|
//enter/update/exit for circles, inside each container |
|
let dots = binContainer.selectAll("circle") |
|
.data(d => d.map((p, i) => { |
|
return {idx: i, |
|
name: p.Name, |
|
value: p.Value, |
|
radius: (x(d.x1)-x(d.x0))/2 |
|
} |
|
})) |
|
|
|
|
|
//UPDATE old elements present in new data. |
|
dots.attr("class", "update"); |
|
|
|
//ENTER new elements present in new data. |
|
dots.enter() |
|
.append("circle") |
|
.attr("class", "enter") |
|
.attr("cx", 0) //g element already at correct x pos |
|
.attr("cy", function(d) { |
|
return - d.idx * 2 * d.radius - d.radius; }) |
|
.attr("r", 0) |
|
.merge(dots) |
|
.transition() |
|
.duration(500) |
|
.attr("r", function(d) { |
|
return (d.length==0) ? 0 : d.radius; }) |
|
};//update |
|
|
|
|
|
|
|
|
|
// add x axis |
|
svg.append("g") |
|
.attr("class", "axis axis--x") |
|
.attr("transform", "translate(0," + height + ")") |
|
.call(d3.axisBottom(x)); |
|
|
|
//draw everything |
|
update(0); |
|
let clicked = 1; |
|
d3.select('#clickMe') |
|
.on('click', function() { |
|
update(clicked); |
|
clicked == 0 || clicked == 1 ? clicked = clicked + 1 : clicked = 200; |
|
}) |
|
|
|
</script> |
|
</body> |
|
</html> |