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 ∩ B A\cap B A∩Bexit
: A − B A-B A−Benter
: B − A B-A B−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
}