function up(svg, d) {
if (!d.parent || !svg.selectAll(".exit").empty()) return;
svg.select(".background").datum(d.parent);
const transition1 = svg.transition().duration(duration);
const transition2 = transition1.transition();
const exit = svg.selectAll(".enter")
.attr("class", "exit");
x.domain([0, d3.max(d.parent.children, d => d.value)]);
// (采用新的定义域)重新绘制横坐标轴
// 使用过渡管理器 transition1 的配置,为该过程创建一个过渡,让横坐标轴更新有一个顺滑的视觉动效
svg.selectAll(".x-axis").transition(transition1)
.call(xAxis);
// 调整即将要被移除的柱子的 CSS 的 transform 属性的值,为这些柱子设置不同的横向偏移,让它们呈阶梯状
// 使用过渡管理器 transition1 的配置,为该过程创建一个过渡
exit.selectAll("g").transition(transition1)
.attr("transform", stagger());
// 采用新的横轴比例尺重新计算这些要移除的柱子的长度,而且将它们的颜色都变成蓝色
// 使用过渡管理器 transition1 的配置,为该过程创建一个过渡
// 所以该过渡和上一步的操作,让这些将要移除的柱子横向偏移呈阶梯状的过程同时进行
exit.selectAll("rect").transition(transition1)
.attr("width", d => x(d.value) - x(0))
.attr("fill", color(true));
// 再退一步,让这些柱子堆叠在一起成为一条长的矩形,且在纵向与原来下钻时点击的柱子的位置一样
// 调整即将要被移除的柱子的 CSS 的 transform 属性的值,让它们的纵向偏移都恢复为 0(在同一条水平线上)
// 使用过渡管理器 transition2 的配置,为该过程创建一个过渡
// 所以这个过渡动效会在 transition1 完成后再执行
exit.selectAll("g").transition(transition2)
.attr("transform", stack(d.index));
// 然后为即将移除的柱子的(大)容器添加一个淡出的过渡动效,将透明度从 100% 变成 0
// 使用过渡管理器 transition2 的配置,为该过程该过程创建一个过渡
exit.transition(transition2)
.attr("fill-opacity", 0)
.remove(); // 最后再移除元素
/**
* 在条形图上添加新的矩形柱子
*/
// 调用 bar 函数,在条形图中绘制出新的柱子
// 传入的依次的参数依次是:
// * svg 只包含一个 svg 元素的选择集
// * down 作为新增柱子被点击后时调用的函数
// * d.parent 作为 svg 的背景 <rect> 元素所绑定的数据
// 返回一个包含新增柱子的 <g> 容器
// * .exit 作为选择器,新增的元素会放置在需要移除的柱子的(大)容器之前,这样就可以避免遮挡住移除柱子过程中的过渡动效
const enter = bar(svg, down, d.parent, ".exit")
.attr("fill-opacity", 0); // 先将容器透明度设置为 0
// 通过调整各新增柱子(小容器)的 CSS 属性 transform
// 将新增的柱子沿纵轴分布好
enter.selectAll("g")
.attr("transform", (d, i) => `translate(0,${barStep * i})`); // barStep 是柱子的带宽
// 然后将显示新增柱子的(大)容器透明度设置回 100% 将它们显示出来
// 使用过渡管理器 transition2 的配置,为该过程创建一个淡入的过渡动效
enter.transition(transition2)
.attr("fill-opacity", 1);
// 设置新增的柱子的颜色
// 并单独处理与之前点击下钻时处于同一位置的新增的柱子
// Exiting nodes will obscure the parent bar, so hide it.
// Transition entering rects to the new x-scale.
// When the entering parent rect is done, make it visible!
enter.selectAll("rect")
// 新增的柱子会基于其所绑定的节点(数据)是否具有子节点来设置不同的颜色
.attr("fill", d => color(!!d.children))
// 因为在 transition2 过渡执行完成之前,即将移除的柱子还显示在页面
// 所以这里将对应的新增柱子的透明度设置为 0 先将其隐藏起来
// 等到 transition2 过渡执行结束后,再显示出来
.attr("fill-opacity", p => p === d ? 0 : null)
// 使用过渡管理器 transition2 的配置创建一个过渡
.transition(transition2)
// 基于新的横坐标轴比例尺,计算新添加的柱子矩形的长度
// 其实这一步是可以省略的,因为与 down() 函数不同(它是先构建出新增的柱子,再更新横坐标轴的定义域),因为在该函数中,是先更新横坐标轴的定义域再去构建新增的柱子
.attr("width", d => x(d.value) - x(0))
// 等到 transition2 过渡执行结束后,将所有的矩形元素的透明度都设置为 100%
// 所以前面隐藏起来的哪个柱子也会显示出来
// 在过渡事件 end 监听器的回调函数中 this 表示当前所遍历的 DOM 元素
// 通过 d3.select(this) 可以基于传入的 DOM 元素构建一个选择集,这样就可以使用 D3 的链式方法,例如 selection.attr() 为选择集中的元素设置属性
.on("end", function(p) { d3.select(this).attr("fill-opacity", 1); });
}