d3.js数据可视化从入门到放不下(四)—— 给图表加点“料”

472 阅读4分钟

上一篇,我们已经构建了一个基本的平面直角坐标系。本篇主要讲讲给如何给坐标系加上数据和过渡动画。

先整理一下svg的一些基础知识点:

示例代码:

<style>
  svg line {
    stroke: grey;
    stroke-width: 2;
  }

  svg circle {
    stroke: red;
    fill: none;
    stroke-width: 2;
  }

  svg rect {
    stroke: steelblue;
    fill: none;
    stroke-width: 2;
  }

  svg polygon {
    stroke: green;
    fill: none;
    stroke-width: 2;
  }
</style>

<script type="text/javascript">
  var width = 600,
    height = 500;

  var svg = d3.select("body").append("svg");

  svg.attr("height", height).attr("width", width);

  svg
    .append("line") // <-A
    .attr("x1", 0)
    .attr("y1", 200)
    .attr("x2", 100)
    .attr("y2", 100);

  svg
    .append("circle") // <-B
    .attr("cx", 200)
    .attr("cy", 150)
    .attr("r", 50);

  svg
    .append("rect")
    .attr("x", 300) // <-C
    .attr("y", 100)
    .attr("width", 100) // <-D
    .attr("height", 100)
    .attr("rx", 5); // <-E

  svg.append("polygon").attr("points", "450,200 500,100 550,200"); // <-F
</script>

上面的代码可以生成这些图形:

有关SVG坐标系的小知识

SVG画布的坐标始于整个画布的左上角(0,0),而止于右下角 (width, height),即画布的宽度和高度。

  • line:直线元素。创建一条起点位于(x1, y1),而终点在(x2, y2)的线段(见第A行);
  • circle:圆。append()函数可以在画布上绘制一个圆心位于(c2, cy),半径为r的源(见第B行);
  • rect:从第C行起,我们仍然使用append()函数绘制了一个左上角未预约(x, y),宽度和高度分别为widthheight的矩形。在第E行,定义了一个x方向长度和y方向长度分别为rx、``ry```的椭圆形,这个椭圆形将构成矩形的4个圆角;
  • polygon:该元素代表一个多边形。它使用points属性对多边形的定点进行了定义,points属性是一个坐标列表,不同坐标之间用空格进行分隔。

使用线条生成器

本例实际上是实现折线图效果,需要使用到d3.js的线条生成器。其实叫它路径生成器可能更准确一点,因为它和svg:line元素没啥关系,实际上是使用svg:path元素实现的。
d3的线条生成器非常灵活,你可以用它生成各种各样的图形。但是,为了简化图形的生成,d3还提供了其它的图形生成器,这个后面应该会介绍。在本例中,我们用d2.svg.line生成器绘制多条数据驱动的线条。

根据数据绘制折线图

mock方案

此处,使用一个二维数组作为mock数据进行线条的绘制。

let data = [
 [
   { x: 0, y: 5 },
   { x: 1, y: 9 },
   { x: 2, y: 7 },
   { x: 3, y: 5 },
   { x: 4, y: 3 },
   { x: 6, y: 4 },
   { x: 7, y: 2 },
   { x: 8, y: 3 },
   { x: 9, y: 2 },
 ],
 d3.range(10).map((i) => ({ c: i, y: Math.sin(i) + 5 }))
];

其中,第一个数据序列是写死的明确的定义的数据,而第二个数据序列是通过一个函数生成的。

定义尺度

数据定义完毕,为了展示数据,我们分别定义了x坐标轴和y坐标轴的尺度。

var width = 500,
   height = 500,
   margin = 30,
   duration = 500,    
   x = d3.scale.linear()
       .domain([0, 10])
       .range([margin, width - margin]),
   y = d3.scale.linear()
       .domain([0, 1])
       .range([height - margin, margin]);

需要注意的是,我们将这些尺度设置得尽量宽泛一些,以便包含两个数据序列中所有的点,在值域的声明中去掉了坐标轴与画布之间的边距,并且反转了y坐标轴的值域,以确保远点位于左下角,而不是svg标准中定义的左上角。

使用线条生成器

上面的操作,定义好了mock数据和尺度。接下来的重头戏就是使用d3.svgv.line函数生成这些线条。

let line = d3.svg.line()
				 .x(d => x(d.x))
                 .y(d => y(d.y))

d3.svg.line函数将返回一个d3线条生成器函数,我们可以对这个函数进行定制。在本例中,我们只是使用先前定义的xy坐标轴的尺度进行数据的映射。这样做既方便,也是一个公认的最佳实践。 现在我们要做的只剩下创建svg:path元素了。

svg.selectAll('path.line').data(data)
                           .enter()
                           .append('path')    //  < - E
                           .attr('class', 'line')
                           .attr('d', d => line(d))

绘制路径

创建路径的过程非常直接,见第E行。我们使用创建的二维数组的数据创建了两个svg:path元素,接着,又用line线条生成器处理数据中的每一个数据d(此处是作为函数的参数),并将生成的结果赋给每一个path元素的d属性。 检查元素发现,生成了svgv:path元素。

最终效果

到此,用d3实现了完整的折线统计图。

后续文章输出安排

  • 折线图-基本结构
  • 折线图-过渡动画
  • 折线图-线条曲度
  • 面积区域图
  • d3中的界面交互
    ......