数据可视化【四】Bar Chart

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 AB
  • exit: A − B A-B AB
  • enter: B − A B-A BA

比较常用的是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进行处理。每个数据都会得到一个矩形并且有相应的坐标。

这里使用xValueyValue函数的原因是因为多个地方都要取数据的属性,我们将这种操作抽象出来以后就只用修改一个地方。

这个代码中和老师讲解不同的是我的柱状图的颜色是五颜六色的。我觉得老师讲都弄成一种颜色太过单调了,就想办法改了一下。主要就是设置了每个矩形的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

如果一些东西是多出来的想要进行删除可以在开发者工具中的选择多余的部分查看属性然后用selectremove进行删除。

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
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值