Make a Bar Chart
- Representing a data table in JavaScript
- Creating rectangles for each row
- Using linear and band scales
- The margin convention
- Adding axes
以下學習內容參考博客:傳送門
select()
選擇所有指定元素的第一個
selectAll()
選擇指定元素的全部
上面兩個函數返回的結果為選擇集
關于 select 和 selectAll 的參數,其實是符合 CSS 選擇器的條件的,即用“井號(#)”表示 id,用“點(.)”表示 class。
datum()
綁定一個數據到選擇集上
data()
綁定一個數組到選擇集上,數組的各項值分別與選擇集的各項元素綁定
在選擇集A和給定數據集B進行綁定的時候,會返回一個值,這個值有三種狀態。默認然會的是update
狀態。通過update
狀態我們還能得到exit
狀態和enter
狀態。
update
:A∩BA\cap BA∩Bexit
:A?BA-BA?Benter
:B?AB-AB?A
比較常用的是enter
,當我們綁定新數據以后一般要對新數據進行處理。對enter()
函數的處理會應用到每個數據上。例如:
svg.selectAll("rect")//選擇svg內的所有矩形.data(dataset)//綁定數組.enter()//指定選擇集的enter部分.append("rect")//添加足夠數量的矩形元素.attr("x",20).attr("y",function(d,i){return i * rectHeight;}).attr("width",function(d){return d;}).attr("height",rectHeight-2).attr("fill","steelblue");
以上面的代碼為例。我們對所有的矩形綁定了data
以后獲得entry
,然后對每個數據進行處理。需要注意的是對屬性的設置要么是常數要么應該傳入一個函數,并且函數可以有兩個參數,第一個是數據,第二個是數據的下標。
學習實現代碼:https://vizhub.com/Edward-Elric233/b9b751bfae674d0aa65deae87899b710
index.html
<!DOCTYPE html>
<html><head><title>Makign a Bar Chart</title><link rel="stylesheet" href="styles.css"><script src="https://unpkg.com/d3@5.7.0/dist/d3.min.js"></script><!-- find D3 file on UNPKG d3.min.js-->
</head><body><svg width="960" height="500"></svg><script src="./index.js">// console.log(d3); test whether you have imported d3.js or not</script></body></html>
index.js
const svg = d3.select('svg');
// svg.style('background-color', 'red'); test
const width = +svg.attr('width');
const height = +svg.attr('height');const render = data => {const xValue = d => d.population;const yValue = d => d.country;const margin = { top: 20, right: 20, bottom: 20, left: 100 };const innerWidth = width - margin.left - margin.right;const innerHeight = height - margin.top - margin.bottom;const xScale = d3.scaleLinear().domain([0, d3.max(data, xValue)]).range([0, innerWidth]);const yScale = d3.scaleBand().domain(data.map(yValue)).range([0, innerHeight]).padding(0.1);const yAxis = d3.axisLeft(yScale);const xAxis = d3.axisBottom(xScale);const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);//yAxis(g.append('g'));g.append('g').call(yAxis);g.append('g').call(xAxis).attr('transform', `translate(0,${innerHeight})`);let colorSet = ['#eb2617', '#ffaa00', '#4dff00', '#00fbff', '#bb00ff', '#eeff00'];const createGetColor = (idx) => {var i = idx || -1;return {get: () => { i = (i + 1) % colorSet.length; return colorSet[i]; }};};const getColor = createGetColor();g.selectAll('rect').data(data).enter().append('rect').attr('y', d => yScale(yValue(d))).attr('width', d => xScale(xValue(d))).attr('height', yScale.bandwidth()).attr('fill', getColor.get);
};d3.csv("https://gist.githubusercontent.com/Edward-Elric233/23f3024c472ffd7e34e6a5ac04bad26c/raw/6ced2249ea6f5d12f72c1eb00b8c1278d2c86e95/every%2520countries'%2520population").then(data => {data.forEach(d => {d.population = +d.population * 1000;});render(data);// console.log(data);
});
實現效果:
需要注意的是使用d3綁定csv文件的時候要求csv文件不能是本地,必須上傳到服務器然后使用http
協議訪問。我這里使用的是gist.github
上傳的數據。
這里其他地方都比較簡單,主要詳細講解一下js文件。
d3.csv('data.csv').then(data => {data.forEach(d => {d.population = +d.population * 1000;});render(data);
上面的代碼主要是讀取數據然后再對數據進行處理,處理使用的是我們自己編寫的函數render
。
然后就是render
函數。
const xScale = d3.scaleLinear().domain([0, d3.max(data, xValue)]).range([0, innerWidth]);const yScale = d3.scaleBand().domain(data.map(yValue)).range([0, innerHeight]).padding(0.1);
scaleLinear
函數用來標注柱狀圖的刻度。定義域domain
是一個范圍,值域range
也是一個范圍,d3會自動進行一個比例映射。這個函數返回一個函數,我們使用xScale(input)
輸入一個數字d3就會返回一個值,這個值是按照比例進行映射的。
scaleBand
函數的定義域是一個數組,值域是一個范圍。然后我們可以獲取yScale.bandwidth()
來確定每個柱狀圖的寬度(因為是水平的所以其實是高度)。每個條形的y坐標我們可以通過這個函數輸入參數獲取。例如這里的yScale('China')
就會返回一個值,這個值是計算好的,我們不用處理。
其他的就是最前面的做法,使用selectAll
獲取到所有矩形(此時為空),然后和data
進行綁定,然后再對enter
進行處理。每個數據都會得到一個矩形并且有相應的坐標。
這里使用xValue
和yValue
函數的原因是因為多個地方都要取數據的屬性,我們將這種操作抽象出來以后就只用修改一個地方。
這個代碼中和老師講解不同的是我的柱狀圖的顏色是五顏六色的。我覺得老師講都弄成一種顏色太過單調了,就想辦法改了一下。主要就是設置了每個矩形的fill
屬性,傳入一個每次會返回不同顏色的函數,如下:
let colorSet = ['#eb2617', '#ffaa00', '#4dff00', '#00fbff', '#bb00ff', '#eeff00'];const createGetColor = (idx) => {var i = idx || -1;return {get: () => { i = (i + 1) % colorSet.length; return colorSet[i]; }};};
const getColor = createGetColor();
這里使用的是通過閉包的方式在函數里面保存了一個累加器,從而實現不斷返回新顏色。
Customizing Axes
Formatting numbers
為了使得坐標上的數字變得規格化,我們可以使用d3中的format
函數。我們可以在http://bl.ocks.org/zanarmstrong/05c1e95bf7aa16c4768e查看如何進行格式化。
const xAxisTickFormat = number => format('.3s')(number).replace('G','B');
const yAxis = axisLeft(yScale);
const xAxis = axisBottom(xScale).tickFormat(xAxisTickFormat);
Removing unnecessary lines
如果一些東西是多出來的想要進行刪除可以在開發者工具中的選擇多余的部分查看屬性然后用select
和remove
進行刪除。
g.append('g').call(yAxis).selectAll('.domain, .tick line').remove();
Adding a visualization title
g.append('text').attr('y', -10).text('Top 10 Most Population Countries').style('font-size', 40);
Adding axis lables
const yAxisG = g.append('g').call(yAxis).selectAll('.domain, .tick line').remove();const xAxisG = g.append('g').call(xAxis) .attr('transform', `translate(0,${innerHeight})`);xAxisG.append('text').attr('y', 50).attr('x', innerWidth-60).attr('fill', 'black').text('populiation').style('font-size', 20);
Making tick grid lines
可以將下面的刻度的線弄的很長,比如:
const xAxis = axisBottom(xScale).tickFormat(xAxisTickFormat).tickSize(-innerHeight);
可是我覺得這樣很丑,所以就算了。
課程還介紹了一個關于Data Visualization的一個文檔,感興趣的可以下載:https://github.com/amycesal/dataviz-style-guide/blob/master/Sunlight-StyleGuide-DataViz.pdf
經過上面代碼的修改以及顏色的修改,最后的效果圖:
看起來已經比較標準了。vizhub代碼:https://vizhub.com/Edward-Elric233/dc1509720f104350a589b46eda59157a
本地代碼:
index.html
<!DOCTYPE html>
<html><head><title>Makign a Bar Chart</title><link rel="stylesheet" href="styles.css"><script src="https://unpkg.com/d3@5.7.0/dist/d3.min.js"></script><!-- find D3 file on UNPKG d3.min.js-->
</head><body><svg width="960" height="500"></svg><script src="./index.js">// console.log(d3); test whether you have imported d3.js or not</script></body></html>
index.js
const svg = d3.select('svg');
// svg.style('background-color', 'red'); test
const width = +svg.attr('width');
const height = +svg.attr('height');const render = data => {const xValue = d => d.population;const yValue = d => d.country;const margin = { top: 60, right: 20, bottom: 80, left: 150 };const innerWidth = width - margin.left - margin.right;const innerHeight = height - margin.top - margin.bottom;const xScale = d3.scaleLinear().domain([0, d3.max(data, xValue)]).range([0, innerWidth]);const yScale = d3.scaleBand().domain(data.map(yValue)).range([0, innerHeight]).padding(0.1);const xAxisTickFormat = number => d3.format('.3s')(number).replace('G', 'B');const yAxis = d3.axisLeft(yScale);const xAxis = d3.axisBottom(xScale).tickFormat(xAxisTickFormat);const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`);//yAxis(g.append('g'));const yAxisG = g.append('g').call(yAxis).selectAll('.domain, .tick line').remove();const xAxisG = g.append('g').call(xAxis).attr('transform', `translate(0,${innerHeight})`);xAxisG.append('text').attr('class', 'axis-label').attr('y', 60).attr('x', innerWidth / 2).attr('fill', 'black').text('populiation');let colorSet = ['#eb2617', '#ffaa00', '#4dff00', '#00fbff', '#bb00ff', '#eeff00'];const createGetColor = (idx) => {var i = idx || -1;return {get: () => { i = (i + 1) % colorSet.length; return colorSet[i]; }};};const getColor = createGetColor();g.selectAll('rect').data(data).enter().append('rect').attr('y', d => yScale(yValue(d))).attr('width', d => xScale(xValue(d))).attr('height', yScale.bandwidth()).attr('fill', getColor.get);g.append('text').attr('class', 'title').attr('y', -20).text('Top 10 Most Population Countries');
};d3.csv("https://gist.githubusercontent.com/Edward-Elric233/23f3024c472ffd7e34e6a5ac04bad26c/raw/6ced2249ea6f5d12f72c1eb00b8c1278d2c86e95/every%2520countries'%2520population").then(data => {data.forEach(d => {d.population = +d.population * 1000;});render(data);// console.log(data);
});
styles.css
body {margin: 0px;overflow: hidden;font-family: manosapce;
}text {font-family: sans-serif;
}.tick text {font-size: 2em;fill: #8E8883
}.axis-label {fill: #8E8883;font-size: 2.5em
}.title {font-size: 3em;fill: #8E8883
}