D3.js中Stacked-to-Grouped Bars详解

Posted wan353694124

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了D3.js中Stacked-to-Grouped Bars详解相关的知识,希望对你有一定的参考价值。

Stacked-to-Grouped Bars

聊聊堆积柱状图和簇状图
D3.js官网例子中,将这两种柱状图做在一起进行切换。见图如下:

堆积柱状图

簇状图

接下来对这两种柱状图的实现代码进行详细解释

index.html——源码

<!DOCTYPE html>
<meta charset="utf-8">
<style>

form 
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  position: absolute;
  left: 10px;
  top: 10px;


label 
  display: block;


</style>
<form>
  <label><input type="radio" name="mode" value="grouped"> Grouped</label>
  <label><input type="radio" name="mode" value="stacked" checked> Stacked</label>
</form>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>

var n = 4, // The number of series.这里n指的series的个数,即要展示几个系列或几个组的数据
    m = 58; // The number of values per series.这里的m指的是每个系列或组中有多少组数据

// The xz array has m elements, representing the x-values shared by all series.
// The yz array has n elements, representing the y-values of each of the n series.
// Each yz[i] is an array of m non-negative numbers representing a y-value for xz[i].
// The y01z array has the same structure as yz, but with stacked [y₀, y₁] instead of y.

// xz中存储d3.range(m)返回的数组,内容为从0-57,代表x轴展示的值
var xz = d3.range(m),
    // yz中存储4*58的矩阵,4表示4个序列,58表示每个序列的58个值
    yz = d3.range(n).map(function()  return bumps(m); ),

    // 1、d3.transpose(matrix)方法用于将矩阵matrix转置,此处调用d3.transpose(yz)之后,返回
    // 一个58*4的矩阵;
    // 2、d3.stack()方法返回一个默认的堆栈生成器
    // 3、d3.stack().keys()方法为堆栈生成器设置主键
    // 4、最后d3.stack().keys(d3.range(n))返回一个主键为0-3的堆栈生成器函数,
    // 暂且标记为stackGen()
    // 5、因此 y01z = stackGen(d3.transpose(yz)),即将yz的转置矩阵转换为堆栈的形式
    // 综上1,2,3,4,5可知,y01z中存储为绘制堆栈图形而准备的数据格式
    y01z = d3.stack().keys(d3.range(n))(d3.transpose(yz)),

    // 获取yz矩阵中的最大值
    yMax = d3.max(yz, function(y)  return d3.max(y); ),
    // 获取y01z矩阵中的最大值,其中后面取d[1]是因为堆栈结构[y₀, y₁]中,y1>y0
    y1Max = d3.max(y01z, function(y)  return d3.max(y, function(d)  return d[1]; ); );

// 获取svg元素
var svg = d3.select("svg"),
    // 定义外边距
    margin = top: 40, right: 10, bottom: 20, left: 10,
    width = +svg.attr("width") - margin.left - margin.right,
    height = +svg.attr("height") - margin.top - margin.bottom,
    // 定义g元素,并将其定位
    g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// 定义x轴比例尺函数
var x = d3.scaleBand()
    // 指定定义域
    .domain(xz)
    // 指定值域
    .rangeRound([0, width])
    // 设置填充
    .padding(0.08);

// 定义y轴比例尺函数
var y = d3.scaleLinear()
    // 指定定义域
    .domain([0, y1Max])
    // 指定值域
    .range([height, 0]);

// 定义颜色函数
var color = d3.scaleOrdinal()
     // 指定颜色函数的定义域,可以看出,通过系列的索引来计算不同的颜色
    .domain(d3.range(n))
     // 指定颜色函数的值域
    .range(d3.schemeCategory20c);

// 定义堆栈图的元素
var series = g.selectAll(".series")
  // 绑定堆栈数据
  .data(y01z)
  .enter().append("g")
    // 根据系列索引,定义填充色
    .attr("fill", function(d, i)  return color(i); );

// 定义柱状图的元素
var rect = series.selectAll("rect")
  // 为柱状图绑定数据
  .data(function(d)  return d; )
  .enter().append("rect")
    // 每个矩形柱子的横坐标通过上面定义的x()比例尺函数来计算
    .attr("x", function(d, i)  return x(i); )
    .attr("y", height)
    // 设置矩形条的宽度为上文中定义的x比例尺的刻度宽度
    .attr("width", x.bandwidth())
    // 设置矩形条的初始高度为0
    .attr("height", 0);

// 为矩形条柱添加过渡动画
rect.transition()
     // 动画的延迟时间
    .delay(function(d, i)  return i * 10; )
    // 通过比例尺函数y,计算动作终点时y属性的值
    .attr("y", function(d)  return y(d[1]); )
    // 通过比例尺函数y,计算动作终点时height属性的值
    .attr("height", function(d)  return y(d[0]) - y(d[1]); );

// 绘制x轴
// d3.axisBottom(x)使用上文定义的x比例尺返回一个面向底部的坐标生成器
// 为定义的g元素调用d3.axisBottom()函数,绘制x轴
g.append("g")
    .attr("class", "axis axis--x")
    .attr("transform", "translate(0," + height + ")")
    .call(d3.axisBottom(x)
         // 设置刻度的大小
        .tickSize(0)
         // 设置刻度的内边距
        .tickPadding(6));

// 定义一个input元素,为其绑定change事件监听,用来切换 堆栈图和簇状图
d3.selectAll("input")
    .on("change", changed);

//定义一个定时器,在第一次执行完时停止。类似js的setTimeout()
var timeout = d3.timeout(function() 
  d3.select("input[value=\\"grouped\\"]")
      // D3中通过property在设置一些特殊的html元素的属性
      // 因此这里的checked属性通过property来设置
      .property("checked", true)
      // 为input元素绑定一个自定义的事件change
      .dispatch("change");
, 2000);

// 切换堆栈图和簇状图
function changed() 
  // 停止当前定时器,防止后续的回调函数的执行
  timeout.stop();

  if (this.value === "grouped") transitionGrouped();
  else transitionStacked();


// 切换为簇状图
function transitionGrouped() 
  // 定义y的定义域
  y.domain([0, yMax]);

  // 为转换为簇状图添加过渡动画
  rect.transition()
      .duration(500)
      .delay(function(d, i)  return i * 10; )
      // 计算每个矩形条柱的x位置
      .attr("x", function(d, i)  return x(i) + x.bandwidth() / n * this.parentNode.__data__.key; )
      // 设置矩形条柱的宽度,由于簇状图一组n个系列,因此计算矩形的宽度要除以n
      .attr("width", x.bandwidth() / n)
    .transition()
      // 设置矩形条柱的y位置
      .attr("y", function(d)  return y(d[1] - d[0]); )
      // 设置矩形条柱的高度
      .attr("height", function(d)  return y(0) - y(d[1] - d[0]); );


// 切换为堆栈图
function transitionStacked() 
  // 切换y的定义域
  y.domain([0, y1Max]);

  // 为转换为堆栈图添加过渡动画
  rect.transition()
      // 设置动画的持续时长
      .duration(500)
      // 设置动画的延迟时间
      .delay(function(d, i)  return i * 10; )
      // 设置矩形条柱的y位置
      .attr("y", function(d)  return y(d[1]); )
      // 设置矩形条柱的高度
      .attr("height", function(d)  return y(d[0]) - y(d[1]); )
    .transition()
      // 设置动画结束时矩形条需要达到的x的位置
      .attr("x", function(d, i)  return x(i); )
      // 设置动画结束时矩形条需要达到的宽度
      .attr("width", x.bandwidth());


// Returns an array of m psuedorandom, smoothly-varying non-negative numbers.
// Inspired by Lee Byron’s test data generator.
// http://leebyron.com/streamgraph/
// bumps(m)方法用来返回m个伪随机数数组,而且数组中的数据是平滑变化的非负数
function bumps(m) 
  var values = [], i, j, w, x, y, z;

  // Initialize with uniform random values in [0.1, 0.2).
  for (i = 0; i < m; ++i) 
    values[i] = 0.1 + 0.1 * Math.random();
  

  // Add five random bumps.
  for (j = 0; j < 5; ++j) 
    x = 1 / (0.1 + Math.random());
    y = 2 * Math.random() - 0.5;
    z = 10 / (0.1 + Math.random());
    for (i = 0; i < m; i++) 
      w = (i / m - y) * z;
      values[i] += x * Math.exp(-w * w);
    
  

  // Ensure all values are positive.
  for (i = 0; i < m; ++i) 
    values[i] = Math.max(0, values[i]);
  

  return values;


</script>

至此,堆栈图和簇状图以及其相互换行解释完毕,这里比较难的是堆栈图所需要的数据矩阵的转换以及堆栈图与簇状图切换时,矩形条柱的x,y位置以及宽度的计算。

今天室外阴冷,淅淅沥沥的小雨,出门寒气逼人。

以上是关于D3.js中Stacked-to-Grouped Bars详解的主要内容,如果未能解决你的问题,请参考以下文章

D3.js的v5版本入门教程(第十二章)—— D3.js中各种精美的图形

Javascript / D3.js - 绘制大型数据集 - 提高 d3.js 绘制的 svg 图表中的缩放和平移速度

D3.js学习中总结的那些金句

D3.Js图表excel

在 d3.js 中制作弧线

D3.js 与 React.js