D3.js中Bullet Charts詳解
阿新 • • 發佈:2019-02-01
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>
子彈圖就解讀到這裡……