There appear to be two key issues:
Enter/Update/Exit
The exit selection only contains elements when there are selected elements in the DOM which do not have corresponding items in the data array. In your case we have an empty selection with bound data:
// an empty selection (no elements with tag box exist):
var box = graph.selectAll('box')
.data(rdata) // bind data to the selection
Then we use the enter selection box.enter() to create elements so that every item in the data array as a corresponding element in the DOM. Since there are no matching elements in the DOM, we create a new element for every item in the DOM:
var boxenter = box.enter()
.append('rect')
...
The box exit selection, box.exit() contains any elements for which a corresponding item in the data array does not exist. selection.exit() does nothing other than return a selection. Since there was no pre-existing elements, no elements can be in the exit selection. So this:
box.exit().transition().duration(500).remove()
Does nothing as the exit selection is empty.
For reference, any existing elements that are not included in the exit selection are in the update selection (box).
If we want to do something with the newly entered boxes, we can use the boxenter selection.
Without using a key function with .data(), only one of the enter/exit selections can contain elements: either we have too many elements (exit selection contains elements) or not enough (enter selection contains elements).
Transitions
Your use of selection.transition().duration(500).remove() will remove a selection, but it will not transition anything. Transitions are used to change an attribute/style/property over time. You haven't specified what property you want to transition. selection.transition().duration(500).remove() will simply remove the selection from the DOM after 500 ms.
Instead, you need to transition an attribute/style/property, eg:
selection.transition() // return a transition (not a selection).
.attr('y',d => y(0))
.attr('height',0)
.duration(10000)
.remove();
Note, transitions have similar methods as selections - but there are methods for each which do not apply to the other. Here .attr(), .duration(), and .remove() are common to both transitions and selections, but .duration(), for example is only for transitions.
We can just chain the transition to the enter selection, since we want to remove it right after we add it.
Here's the above in action:
barchart_hist()
function barchart_hist() {
var m = [10, 80, 25, 80]
var w = 600 - m[1] - m[3]
var h = 400 - m[0] - m[2]
var y_data = [2,1,5,6,2,3,0]
var graph = d3.select('body').append("svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")")
var x = d3.scaleBand()
.domain(d3.range(y_data.length))
.range([0, w])
.padding(0.05);
var y = d3.scaleLinear()
.domain([0, d3.max(y_data)])
.range([h,0])
var xAxis = d3.axisBottom(x)
.ticks(y_data.length)
var yAxisLeft = d3.axisLeft(y)
.ticks(d3.max(y_data))
graph.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h) + ")")
.call(xAxis);
graph.append("g")
.attr("class", "y axis")
.attr("transform", "translate(-25,0)")
.call(yAxisLeft);
graph.selectAll("bar")
.data(y_data)
.enter().append("rect")
.style("fill", "none")
.attr('stroke','black')
.attr("x", (d,i) => x(i))
.attr("y", (d,i) => y(d))
.attr("width", x.bandwidth())
.attr("height", (d,i) => h - y(d));
var rdata = [[0,1],[3,4],[2,4],[5,6],[4,6],[1,6]]
var box = graph.selectAll('box')
.data(rdata)
var boxenter = box.enter()
.append('rect')
.attr('stroke','red')
.attr('stroke-width',2)
.attr('fill','none')
.attr('x',d => {
return x(d[0])})
.attr('y',d => y(y_data[d[0]]))
.attr('width',d => {
return x(d[1])-x(d[0])})
.attr('height',d => {
return y(0) - y(y_data[d[0]])
})
.transition()
.attr('y',d => y(0))
.attr('height',0)
.duration(10000)
.remove();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
Consecutive Transitions
Ok, now we can look at putting the above together for consecutive transitions. This would be easiest if you had the same number of elements in each series: we could just use one data array that has properties representing the values in both arrays and transition from one to the other while also entering items only once. If desired, I can also expand on that approach.
But, as you have different sized arrays (or you might have dynamic data), we'll need a different approach. The below uses a standardized data structure for both arrays so that we can use the same update function for each.
I modified the last element in the first data array so it is visible in the chart for demonstration of the code (so you know it isn't errantly missing)
// The data:
var data1 = [[0,2],[1,1],[2,5],[3,6],[4,2],[5,3],[6,1]];
var data2 = [[0,1],[3,4],[2,4],[5,6],[4,6],[1,6]]
// The set up:
var m = [10, 80, 25, 80]
var w = 600 - m[1] - m[3]
var h = 300 - m[0] - m[2]
var graph = d3.select('body').append("svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")")
var xAxis = graph.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h) + ")")
var yAxis = graph.append("g")
.attr("class", "y axis")
.attr("transform", "translate(-25,0)")
// (For demonstration):
var i = 0;
var datasets = [data1, data2];
// Update function:
function update(data) {
// set up scales:
var x = d3.scaleBand()
.domain(d3.range(data.length))
.range([0, w])
.padding(0.05);
var y = d3.scaleLinear()
.domain([0, d3.max(data, d=>d[1])])
.range([h,0])
// set up axis:
xAxis.transition().call(d3.axisBottom(x));
yAxis.transition().call(d3.axisLeft(y));
// Enter then remove:
graph.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.style("fill", "none")
.attr('stroke','black')
.attr("x", d=>x(d[0]))
.attr("width", x.bandwidth())
// Start new rects with zero height:
.attr("y", y(0))
.attr("height", 0)
// Transition up:
.transition()
.attr("y", d=>y(d[1]))
.attr("height", d=>y(0)-y(d[1]))
// Transition down:
.transition()
.delay(1000) // wait a bit first
.attr("y", y(0))
.attr("height", 0)
.remove()
.end()
// get next dataset, and repeat:
.then(()=>update(datasets[++i%2]));
// example to get one more data set and stop:
//.then(function() {
// if (data === data1) update(data2)
// else console.log("end");
//});
}
update(data1);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
Note the use of transition.end() returns a promise once all transitions end whereas the use of transition.on("end", fn) will trigger on each element's transition end.
You've stated you want to remove the existing elements before updating with the new data. There are many questions on SO that ask how to remove all existing elements and then create new elements, this pattern omits update and exit selections and is frequently non-canonical. It may be that your second data array items don't represent any of the entities represented in the first data array - in which case this approach is fine. But, often this approach limits your functionality. Should you want to update the existing items with new data, we can alter the approach to make full use of updating existing data points (which may tell an interesting story, depending on your data and what it represents):
// The data:
var data1 = [[0,2],[1,1],[2,5],[3,6],[4,2],[5,3],[6,1]];
var data2 = [[0,1],[3,4],[2,4],[5,6],[4,6],[1,6]]
// The set up:
var m = [10, 80, 25, 80]
var w = 600 - m[1] - m[3]
var h = 300 - m[0] - m[2]
var graph = d3.select('body').append("svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")")
var xAxis = graph.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h) + ")")
var yAxis = graph.append("g")
.attr("class", "y axis")
.attr("transform", "translate(-25,0)")
// (For demonstration):
var i = 0;
var datasets = [data1, data2];
// Update function:
function update(data) {
// set up scales:
var x = d3.scaleBand()
.domain(d3.range(data.length))
.range([0, w])
.padding(0.05);
var y = d3.scaleLinear()
.domain([0, d3.max(data, d=>d[1])])
.range([h,0])
// set up axis:
xAxis.transition().delay(500).call(d3.axisBottom(x));
yAxis.transition().delay(500).call(d3.axisLeft(y));
// Enter then remove:
var selection = graph.selectAll(".bar")
.data(data);
var enterTransition = selection.enter()
.append("rect")
.attr("class","bar")
.style("fill", "steelblue")
.attr("opacity",0)
.attr('stroke','black')
.attr('stroke-width',1)
.attr("x", d=>x(d[0]))
.attr("width", x.bandwidth())
// Start new rects with zero height:
.attr("y", y(0))
.attr("height", 0)
// Transition up and remove color:
.transition()
.delay(500)
.duration(1000)
.style("fill","white")
.attr("opacity",1)
.attr("y", d=>y(d[1]))
.attr("height", d=>y(0)-y(d[1]))
.end()
var updateTransition = selection.transition()
.delay(500)
.duration(1000)
.attr("y", d=>y(d[1]))
.attr("height", d=>y(0)-y(d[1]))
.attr("x", d=>x(d[0]))
.attr("width", x.bandwidth())
.end()
var exitTransition = selection.exit()
.transition()
.duration(500)
.attr("opacity",0)
.style("fill","crimson")
.remove()
.end()
Promise.allSettled([enterTransition,updateTransition,exitTransition])
.then(()=>update(datasets[++i%2]));
}
update(data1);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
I've added red for exit selection, blue for enter, with some opacity changes to highlight the changes better