D3.js中Bullet Charts详解

Bullet Charts

今天我们来聊一聊 占地儿小却能表达足够丰富的数据信息的子弹图。

子弹图,顾名思义是由于该类信息图的样子很像子弹射出后带出的轨道。子弹图无修饰的线性表达方式使我们能够在狭小的空间中表达丰富的数据信息,这种情况在寸尺寸金的报纸媒介上尤其明显。与通常所见的里程表或时速表类似,每一个单元的子弹图只能显示单一的数据信息源,并且通过添加合理的度量标尺可以显示更精确的阶段性数据信息。另外,子弹图通过优化设计还能够用于表达多项同类数据的对比,例如今年消费实际与去年实际消费的对比关系;再例如,还可以表达一项数据与不同目标的校对结果,例如非常好、令人满意、不好等目标。 —— [ 百度百科 ]

子弹图由五个部分组成,即文字标签、主体数据条柱、刻度量表、主要标记标识以及定性范围标识。如图1[引用地址] 所示:

1、文字标签

文字标签是用来说明子弹图一个单元所要表示的内容是什么,如图中的“2008年财政收入(单位:千元)”

2 、主体数据条柱

主体数据条柱是图中颜色最重的横向的那道黑色长条,是用来表示子弹图一个单元所表示内容的量有多大,如图中所示的,横条表示2008年财政收入大概到了275千元左右。

3、刻度量表

刻度量表如图1所示的刻度尺,即1-300,间距为50;

4、主要标记标识

主要标记标识用来标记显示某些关键的数值点,如图中竖型的短粗线,标识了大概265左右的数值为关键点;

5、定性范围标识

图中以三种不同的程度的灰色横条表示的范围,就是定性范围的标识方式,一般用不同的程度的颜色来标识不同的数值范围,如用深灰来标识从0-20的范围;用中灰色来标识从200-250的范围;用浅灰色来标识从250-300的范围;

d3.js官网的子弹图中包括了5个单元,展示了5个单元数据的对比关系示,如图2所示:

接下来详细解读这张紧凑而丰富的子弹图的实现源码

bullet.js文件解读—— [ 源码 ]

(function() {

// Chart design based on the recommendations of Stephen Few. Implementation
// based on the work of Clint Ivy, Jamie Love, and Jason Davies.
// http://projects.instantcognition.com/protovis/bulletchart/

// 子弹图是由Stephen Few设计发明的,Stephen Few现在是数据可视化方向的领先专家。
d3.bullet = function() {
  var orient = "left", // 定义子弹图默认的方向,为横向自左向右
      reverse = false, // 定义子弹图默认的方向是否与orient相反,如果为true,则子弹图横向自右向
                       // 左,即度量刻度方向反转 
      duration = 0,
      ranges = bulletRanges,     // 定义子弹图默认定性范围获取方法为bulletRanges()
      markers = bulletMarkers,   // 定义子弹图默认主要标记标识获取方法为bulletMarkers()
      measures = bulletMeasures, // 定义子弹图默认刻度量表获取方法为bulletMeasures()
      width = 380,               // 定义默认宽度
      height = 30,               // 定义默认高度 
      tickFormat = null;         // 定义默认坐标刻度格式

  // For each small multiple…
  // 生成子弹图的核心方法
  function bullet(g) {
    // 这里的参数g,是添加的group元素数组
    // 这里的参数d是g元素所绑定的data,i是循环index值
    g.each(function(d, i) {
      // 获取d中的ranges数组的值,并且对其进行降序排序,这里的this指向g
      var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
          // 获取d中的markers数组的值,并且对其进行降序排序,这里的this指向g
          markerz = markers.call(this, d, i).slice().sort(d3.descending),
          // 获取d中的measures数组的值,并且对其进行降序排序,这里的this指向g
          measurez = measures.call(this, d, i).slice().sort(d3.descending),
          // 用已经获得了ranges,markers和measures的this来更新g
          g = d3.select(this);

      // Compute the new x-scale.
      // 定义坐标轴为线性坐标,定义定义域,值域,值域这里根据reverse属性来确定方向
      var x1 = d3.scale.linear()
          .domain([0, Math.max(rangez[0], markerz[0], measurez[0])])
          .range(reverse ? [width, 0] : [0, width]);

      // Retrieve the old x-scale, if this is an update.
      var x0 = this.__chart__ || d3.scale.linear()
          .domain([0, Infinity])
          .range(x1.range());

      // Stash the new scale.
      this.__chart__ = x1;

      // Derive width-scales from the x-scales.
      // 计算子弹图定性范围的宽度 
      var w0 = bulletWidth(x0),
          w1 = bulletWidth(x1);

      // Update the range rects.
      // 为定性范围矩形条绑定定性范围数据
      var range = g.selectAll("rect.range")
          .data(rangez);
      // 生成定性范围矩形条,并且定义class属性,以便控制不同定性范围的颜色
      range.enter().append("rect")
          .attr("class", function(d, i) { return "range s" + i; })
          .attr("width", w0) // 定义定性范围矩形条的宽度
          .attr("height", height)
          .attr("x", reverse ? x0 : 0)
        .transition()
          .duration(duration)
          .attr("width", w1)
          .attr("x", reverse ? x1 : 0);
      // 启动转变函数transition()在duration时间间隔内绘制定性范围矩形
      range.transition()
          .duration(duration)
          .attr("x", reverse ? x1 : 0)
          .attr("width", w1)
          .attr("height", height);

      // Update the measure rects.
      // 为刻度量表绑定数据
      var measure = g.selectAll("rect.measure")
          .data(measurez);
      // 生成刻度量表,即主体数据条柱,并且定义class属性,以便控制不同主体数据的颜色
      measure.enter().append("rect")
          .attr("class", function(d, i) { return "measure s" + i; })
          .attr("width", w0)
          .attr("height", height / 3)
          .attr("x", reverse ? x0 : 0)
          .attr("y", height / 3)
        .transition()
          .duration(duration)
          .attr("width", w1)
          .attr("x", reverse ? x1 : 0);
      // 启动转变函数transition()在duration时间间隔内绘制主体数据条柱
      measure.transition()
          .duration(duration)
          .attr("width", w1)
          .attr("height", height / 3)
          .attr("x", reverse ? x1 : 0)
          .attr("y", height / 3);

      // Update the marker lines.
      // 为子弹图的标记标识绑定数据
      var marker = g.selectAll("line.marker")
          .data(markerz);
      // 生成标记标识线,
      marker.enter().append("line")
          .attr("class", "marker")
          .attr("x1", x0)
          .attr("x2", x0)
          .attr("y1", height / 6)
          .attr("y2", height * 5 / 6) .transition() .duration(duration) .attr("x1", x1) .attr("x2", x1); // 启动转变函数transition()在duration时间间隔内绘制标记标识线 marker.transition() .duration(duration) .attr("x1", x1) .attr("x2", x1) .attr("y1", height / 6) .attr("y2", height * 5 / 6);

      // Compute the tick format.
      // 计算坐标刻度的格式
      var format = tickFormat || x1.tickFormat(8);

      // Update the tick groups.
      // 为坐标刻度绑定数据
      var tick = g.selectAll("g.tick")
          .data(x1.ticks(8), function(d) { return this.textContent || format(d); });

      // Initialize the ticks with the old scale, x0.
      // 初始化刻度
      var tickEnter = tick.enter().append("g")
          .attr("class", "tick")
          .attr("transform", bulletTranslate(x0))
          .style("opacity", 1e-6);
      // 定义刻度线
      tickEnter.append("line")
          .attr("y1", height)
          .attr("y2", height * 7 / 6); // 定义刻度文本 tickEnter.append("text") .attr("text-anchor", "middle") .attr("dy", "1em") .attr("y", height * 7 / 6)
          .text(format);

      // Transition the entering ticks to the new scale, x1.
      tickEnter.transition()
          .duration(duration)
          .attr("transform", bulletTranslate(x1))
          .style("opacity", 1);

      // Transition the updating ticks to the new scale, x1.
      var tickUpdate = tick.transition()
          .duration(duration)
          .attr("transform", bulletTranslate(x1))
          .style("opacity", 1);

      tickUpdate.select("line")
          .attr("y1", height)
          .attr("y2", height * 7 / 6); tickUpdate.select("text") .attr("y", height * 7 / 6);

      // Transition the exiting ticks to the new scale, x1.
      tick.exit().transition()
          .duration(duration)
          .attr("transform", bulletTranslate(x1))
          .style("opacity", 1e-6)
          .remove();
    });
    d3.timer.flush();
  }

  // left, right, top, bottom
  //定义子弹图方向和是否反转的计算方法
  bullet.orient = function(x) {
    if (!arguments.length) return orient;
    orient = x;
    // 子弹图的reverse属性的计算规则为,如果orient为从右向左或者orient为自底向上,则反转坐标轴;否
    // 则,不进行反转
    reverse = orient == "right" || orient == "bottom";
    return bullet;
  };


  // ranges (bad, satisfactory, good)
  // 定义子弹图的定性范围方法,例如bad,satisfactroy,good三个等级
  bullet.ranges = function(x) {
    if (!arguments.length) return ranges;
    ranges = x;
    return bullet;
  };

  // markers (previous, goal)
  // 定义子弹图的重要标识关键点方法
  bullet.markers = function(x) {
    if (!arguments.length) return markers;
    markers = x;
    return bullet;
  };

  // measures (actual, forecast)
  // 定义子弹图的刻度量表方法
  bullet.measures = function(x) {
    if (!arguments.length) return measures;
    measures = x;
    return bullet;
  };

  //设置子弹图宽度的方法
  bullet.width = function(x) {
    if (!arguments.length) return width;
    width = x;
    return bullet;
  };
   //设置子弹图高度的方法
  bullet.height = function(x) {
    if (!arguments.length) return height;
    height = x;
    return bullet;
  };

  // 定义子弹图的坐标刻度格式方法
  bullet.tickFormat = function(x) {
    if (!arguments.length) return tickFormat;
    tickFormat = x;
    return bullet;
  };
  // 设置子弹图的duration属性值
  bullet.duration = function(x) {
    if (!arguments.length) return duration;
    duration = x;
    return bullet;
  };

  // 返回子弹图对象
  return bullet;
};
// 子弹图的定性范围获取方法
function bulletRanges(d) {
  return d.ranges;
}
// 子弹图的主要标识获取方法
function bulletMarkers(d) {
  return d.markers;
}
// 子弹图的刻度量表获取方法
function bulletMeasures(d) {
  return d.measures;
}
// 子弹图刻度的转换计算方法
function bulletTranslate(x) {
  return function(d) {
    return "translate(" + x(d) + ",0)";
  };
}
//由x坐标计算方式来得出子弹图宽度坐标的计算方式
function bulletWidth(x) {
  var x0 = x(0);
  return function(d) {
    return Math.abs(x(d) - x0);
  };
}

})();

index.html文件解读—— [ 源码 ]

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

body {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  margin: auto;
  padding-top: 40px;
  position: relative;
  width: 960px;
}

button {
  position: absolute;
  right: 10px;
  top: 10px;
}

.bullet { font: 10px sans-serif; }
.bullet .marker { stroke: #000; stroke-width: 2px; }
.bullet .tick line { stroke: #666; stroke-width: .5px; }
.bullet .range.s0 { fill: #eee; }
.bullet .range.s1 { fill: #ddd; }
.bullet .range.s2 { fill: #ccc; }
.bullet .measure.s0 { fill: lightsteelblue; }
.bullet .measure.s1 { fill: steelblue; }
.bullet .title { font-size: 14px; font-weight: bold; }
.bullet .subtitle { fill: #999; }

</style>
<button>Update</button>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="bullet.js"></script>
<script>

//定义margin属性以及子弹图的宽、高尺寸;
var margin = {top: 5, right: 40, bottom: 20, left: 120},
    width = 960 - margin.left - margin.right,
    height = 50 - margin.top - margin.bottom;

var chart = d3.bullet() //初始化一个子弹图对象
    .width(width)       //设置子弹图的宽度
    .height(height);    //设置子弹图的高度

d3.json("bullets.json", function(error, data) {
  if (error) throw error;

  var svg = d3.select("body").selectAll("svg") 
      .data(data)  // 为svg元素绑定bullets.json中的数据
    .enter().append("svg")
      .attr("class", "bullet") // 为svg元素定义class = "bulltet"
      .attr("width", width + margin.left + margin.right)
      .attr("height", height + margin.top + margin.bottom)
    .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
      .call(chart); // 将生成的svg作为chart函数对象的参数,调用bullet.js中的 
                    // bullet(g)函数进行一系列操作,生成子弹图

  var title = svg.append("g")
      .style("text-anchor", "end") //设置子弹图文字标签的位置在文字基点的左上方
      .attr("transform", "translate(-6," + height / 2 + ")");//定义文字的变换矩阵

  title.append("text")  // 定义文字的内容
      .attr("class", "title")
      .text(function(d) { return d.title; });

  title.append("text")  // 定义子标题的内容
      .attr("class", "subtitle")
      .attr("dy", "1em")
      .text(function(d) { return d.subtitle; });

  //随机变换数据,进行子弹图更新,并且定义过渡时间为1000毫秒
  d3.selectAll("button").on("click", function() {
    svg.datum(randomize).call(chart.duration(1000)); // TODO automatic transition
  });
});

// 更新子弹图的ranges、markers以及measures数据
function randomize(d) {
  if (!d.randomizer) d.randomizer = randomizer(d);
  d.ranges = d.ranges.map(d.randomizer);
  d.markers = d.markers.map(d.randomizer);
  d.measures = d.measures.map(d.randomizer);
  return d;
}

// 随机变换子弹图数据的方法
function randomizer(d) {
  var k = d3.max(d.ranges) * .2;
  return function(d) {
    return Math.max(0, d + k * (Math.random() - .5));
  };
}

</script>

子弹图就解读到这里……

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值