可视化D3专题系列(二)折线图

4,565 阅读4分钟

Author: arcsin1

time: 2020.04.25

开篇认识折线图

  • 折线图是最基本最常见的可视化图形,我们用D3一步步绘制
  • 折线图是由一个笛卡尔坐标系(直角坐标系),一些点和线组成的统计图表,常用来表示数值随连续时间间隔或有序类别的变化。
  • 从数据上来说,折线图需要一个连续时间字段或一个分类字段和至少一个连续数据字段。

适用场景

  • (单条)同一变量随时间或有序类别的变化。(连续性质)
  • (多条)多个随时间或有序类别变化的变量对比。

不适用场景

  • (单条)x轴节点过多,即时间或者连续点太多了。

  • (多条)数据样本过多,导致折线堆积,难以聚焦到重点。(可以用交互动画解决) 典型列子

  • 变量数值大多情况下在0

那么如何用D3绘制折线图?

先看看折线图的样子(你心急,完整demo链接在最下面)

  我分为5个部分:(目前只介绍svg篇,后续再介绍canvas篇)
  
  1. 画出x轴
  2. 画出y轴
  3. 画出数据圆点和值label
  4. 画出线条连线
  5. 动画(后面补上)

开始

准备好数据

 const data = [
        { year: "1991", value: 3 },
        { year: "1992", value: 4 },
        { year: "1993", value: 3.5 },
        { year: "1994", value: 5 },
        { year: "1995", value: 4.9 },
        { year: "1996", value: 6 },
        { year: "1997", value: 7 },
        { year: "1998", value: 9 },
        { year: "1999", value: 13 }
      ];

我们开始创建一个svg

注意点: svg的元素基本在g里面绘制,当做group

const width = 600;
const height = 400;

 const svg = d3
   .select("body")
   .append("svg")
   .attr("width", width)
   .attr("height", height);
   
 // 创建一个g 当后面元素的group容器,移到(30,30)的位置
 // 定义上下左右边距给坐标轴文字距离
 const m = { top: 30, right: 30, bottom: 30, left: 30 };
 const g = svg.append("g").attr("transform", "translate(30, 30)");
 
 // 实际我们图的高度宽度
 const gW = width - m.left;
 const gH = height - m.top - m.bottom;

开始画出x轴

关于比例尺参考 可视化D3专题系列(一)初见

再简单介绍scaleBand的东西:

创建scaleBand一般用在条形图时(也可以给折线图),考虑到每个条之间的填充,有助于确定条的几何形状。域被指定为值的数组(每个带一个值),范围被指定为带的最小和最大范围(例如条形图的总宽度)。

实际上,scaleBand会将范围划分为n个波段(其中n是域数组中值的数量),并考虑任何指定的填充来计算波段的位置和宽度。

 var bandScale = d3.scaleBand()
  .domain(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
  .range([0, 200]);

bandScale('Mon'); // returns 0
bandScale('Tue'); // returns 40
bandScale('Fri'); // returns 160

// 可以使用.bandwidth()以下命令访问每个波段的宽度:

bandScale.bandwidth();  // returns 40

正式画x轴啦

// 定义x坐标轴的比例尺,gW为x轴的宽度,关于scaleBand参考上一篇文章以及相关文章
// 这里我们会得到一个xScale.bandwidth()的距离,bandwidth()访问每个波段的宽度:
 const xScale = d3.scaleBand().range([0, gW]);
     
// 定义好x轴定义域,画出x轴axisBottom,底部位置,year是值
 xScale.domain(data.map(item => item.year));

 g.append("g")
   .attr("transform", `translate(0, ${gH})`)
   .call(d3.axisBottom(xScale))
   .attr("stroke", "red");

更详细比列尺介绍

开始画出y轴

// 定义y坐标轴的比例尺,gH为y轴的宽度
 const yScale = d3.scaleLinear().range([gH, 0]);
 
 // 定义好y轴d定义域,画出y轴,y轴画在左边axisLeft,value是值
 
 yScale.domain([0, d3.max(data, item => item.value)]);
 
 g.append("g")
   .call(d3.axisLeft(yScale))
   .attr("stroke", "red");
   

画出数据圆点和值label

要注意我们要把点和文字放在一个g里面,才能顺利画出来

// 先给点画上小圆圈和文字,创建一个文字和圆圈的group
// join那句可以改为以前v4写法.enter().append('circle')

const group1 = g
.selectAll(".gruop-circle-text")
.data(data)
.join("g")
.attr("class", "gruop-circle-text");

// 画出圆点即是圆圈,xScale.bandwidth()就用到了,bandwidth()访问每个波段的宽度,
// xScale.bandwidth() / 2 我们把元素位置居中

 group1
   .selectAll("circle")
   .data(data)
   .join("circle")
   .attr("cx", d => {
     return xScale(d.year) + xScale.bandwidth() / 2;
   })
   .attr("cy", d => {
     return yScale(d.value);
   })
   .attr("r", 3)
   .attr("fill", "red");
   
   
  // 绘制出文字
	
 group1
   .selectAll("text")
   .data(data)
   .join("text")
   .attr("x", d => {
     return xScale(d.year) + xScale.bandwidth() / 2;
   })
   .attr("y", d => yScale(d.value) - 2)
   .text(d => d.value);

我们来连个线吧

d3 为我们提供更优质的line生成器 d3.line

更多关于线条生成参考

// 创建一个line的生成器 用d3.line,把所有点连起来
	const line = d3
        .line()
        .x(d => {
          // 这里是d3.scaleBand自带比例尺
          return xScale(d.year) + xScale.bandwidth() / 2;
        })
        .y(d => {
          return yScale(d.value);
        })
        .curve(d3.curveCatmullRom);  //这里有多种形态可以选择
       

该绘制线条了

   g.append("path")
        .attr("d", line(data))
        .attr("fill", "none")
        .attr("stroke", "purple");

完整折线图

结尾

看到这里,其实发现绘制一个基本折线图就是 一个个积木搭建起来的,是不是很有趣。

下一章我们来讲讲多条折线图和各种动画效果。

周末愉快!