|
const data = [ |
|
{name:"orange", count:30}, |
|
{name:"apple", count:40}, |
|
{name:"banana", count:50}, |
|
{name:"strawberry", count:60}, |
|
{name:"pear", count:5} |
|
]; |
|
|
|
// create overlapping subsets |
|
const subsets = [ |
|
data.slice(0,3), |
|
data.slice(2,5) |
|
]; |
|
|
|
// set up chart area |
|
const margin = { |
|
left:30, |
|
right:30, |
|
top:10, |
|
bottom:30 |
|
}; |
|
|
|
const svg_height = 300; |
|
const svg_width = 500; |
|
|
|
const height = svg_height - margin.top - margin.bottom; |
|
const width = svg_width - margin.left - margin.right; |
|
|
|
const svg = d3.select("body").append("svg") |
|
.attr("width", svg_width) |
|
.attr("height", svg_height); |
|
|
|
const chart = svg.append("g") |
|
.attr("id", "chart") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top +")"); |
|
|
|
const xaxis = chart.append('g') |
|
.attr("id", "x-axis") |
|
.attr("transform", `translate(0,${height})`); |
|
|
|
const yaxis = chart.append('g') |
|
.attr("id", "y-axis"); |
|
|
|
function updatePlot(dataset) { |
|
|
|
// get old tick marks, if they exist |
|
const old_x = xaxis.node().__axis; |
|
let old_ticks = undefined; |
|
if (old_x) { |
|
old_ticks = chart.selectAll("#x-axis g.tick") |
|
.data(dataset.map((d) => d.name), old_x); |
|
} |
|
|
|
// select bars |
|
const bars = chart.selectAll(".bar").data(dataset, (d) => d.name); |
|
|
|
// make axis scales |
|
const x = d3.scaleBand().domain(dataset.map((d)=>d.name)).rangeRound([0,width]).padding(0.1); |
|
const y = d3.scaleLinear().domain([0,d3.max(dataset.map((d)=>d.count))]).rangeRound([height,0]); |
|
|
|
// helper function to wrap the next transition group in a semaphore |
|
const make_next_transition = function(num_transitions, next) { |
|
let n = num_transitions-1; |
|
return function () { |
|
n--; |
|
if (n === 0) { |
|
next(); |
|
} |
|
} |
|
}; |
|
|
|
const transition_groups = { |
|
|
|
// remove dead ticks and bars |
|
remove: function () { |
|
|
|
if (!old_x) { |
|
// first time drawing the plot, just skip to drawing the new data |
|
transition_groups['add_new'](); |
|
} else { |
|
const next_transition = make_next_transition(2, transition_groups["rescale_y"]); |
|
|
|
old_ticks |
|
.exit() |
|
.transition() |
|
.style("fill-opacity", 0) |
|
.remove() |
|
.on("end", next_transition); |
|
|
|
bars.exit().transition().style("fill-opacity", 0).remove() |
|
.on("end", next_transition); |
|
} |
|
}, |
|
|
|
// rescale y-axis |
|
rescale_y: function () { |
|
yaxis.transition().call(d3.axisLeft(y)).on("end", transition_groups["move"]); |
|
bars.transition() |
|
.attr("y", (d) => y(d.count)) |
|
.attr("height", (d) => height - y(d.count)); |
|
}, |
|
|
|
// move remaining ticks and bars |
|
move: function () { |
|
const next_transition = make_next_transition(2, transition_groups["add_new"]); |
|
|
|
old_ticks |
|
.transition() |
|
.attr("transform", (d) => "translate(" + (x(d) + x.bandwidth() / 2) + ", 0)") |
|
.on("end", next_transition); |
|
|
|
bars.transition() |
|
.attr("x", (d) => x(d.name)) |
|
.attr("width", x.bandwidth()) |
|
.on("end", next_transition); |
|
}, |
|
|
|
// and new bars and ticks |
|
add_new: function () { |
|
xaxis.transition().call(d3.axisBottom(x)); |
|
yaxis.transition().call(d3.axisLeft(y)); |
|
|
|
bars.enter().append("rect").attr("class", "bar") |
|
.attr("x", (d) => x(d.name)) |
|
.attr("y", (d) => y(d.count)) |
|
.attr("height", (d) => height - y(d.count)) |
|
.attr("width", x.bandwidth()) |
|
.style("fill-opacity", 0) |
|
.transition() |
|
.style("fill-opacity", 1) |
|
} |
|
}; |
|
|
|
transition_groups["remove"](); |
|
|
|
} |
|
|
|
// cycle among subsets on click |
|
let current_subset = 0; |
|
chart.on("click", function() { |
|
current_subset = (current_subset + 1) % subsets.length; |
|
updatePlot(subsets[current_subset]); |
|
}); |
|
|
|
// draw first subset on load |
|
updatePlot(subsets[0]); |