准备

有了上一节的基础,我们需要再了解几个进阶的概念。

Scales

刻度,也就是把数据集重新调整刻度,比如创建一个线性刻度:

var normalize = d3.scale.linear().domain([0,50]).range([0,1])

domain(..) 用来设置输入数据的最大值和最小值,range(..)用来设置输出数据的最大和最小值。这里代表将创建了一个线性刻度,将[0,50] 映射到 [0,1]。
验证一下:

Axes

坐标轴,可以说是可视化中非常重要的部分了,直接给一个例子:

    // 创建一个svg元素
    var svgElement = d3.select("body")
      .append("svg")
      .attr({"width" : 500, "height" : 500});
    //创建一个线性刻度, domain(显示的实际刻度值) 是 [0,50]  range(真实的长度) 是 [10,400]
    var xScale = d3.scale.linear().domain([0,50]).range([10,400]);
    // 基于此刻度生成坐标轴
    var xAxis = d3.svg.axis().scale(xScale)
      .ticks(5) // 限定刻度个数
      .orient("bottom"); // 坐标轴label位置
    // 包坐标轴都包在一组里g
    var x = svgElement.append("g")
      .call(xAxis);

样式:

    path {
      stroke: steelblue;
      fill: none;
      stroke-width: 2;
    }

展示效果:

d3.svg.axis()方法建立坐标轴,ticks()设置坐标轴拥有的刻度数, 如果调用这个函数则默认值是10。

数据加载

d3支持 JSON, CSV, TSV, XML,HTML等各种格式化的数据格式,并提供相应的加载api,例如加载TSV数据:

    d3.tsv("xxxx/data.tsv", function (error, data) {
      if (error)
        console.log(error);
      else
        console.table(data);
    });

创建基本的图表与代码复用

之前的概念都是为了建立各种经典可视化视图,下面就用线形图和区域图做入门示例。

线形图

线形图是应用最广泛的图表类型,通常是用来显示基于时间序列的数据表明随时间变化的趋势。

  • 数据集

    这里示例数据集仍然采用tsv的格式,是一个股票在一个时间跨度内的收盘价数据集合。

  • 数据处理和格式转换

    由于数据中心date和close都是字符串格式,在绘制之前需要将他们转换为可用的格式,在加载数据之前先进行数据的处理:

    //Create a date parser
    var ParseDate = d3.time.format("%d-%b-%y").parse;
    
    //Read TSV file
    d3.tsv("data.tsv", function(error, data){
        //Parse Data into useable format
        data.forEach(function(d){
        d.date = ParseDate(d.date);
        d.close = +d.close;
        //the + sign converts numeric string to number
        });
        
        //Print the data as a table in the console
        console.table(data);
    });

    处理完后数据转换为:


    这里 d3.time.format.parse 函数可以将指定格式的时间数据转换为标准local时间格式;'+'号则强制类型转换将字符串转换为数字格式。

  • 画线

    我们在第一篇教程中讲过在svg中画线,需要给出每个点的坐标,这么多点是一个非常冗长的过程。在d3中我们只需要提供数据值和刻度d3就会自动帮我们完成这个繁重的工作,下面的代码就是一个可复用的线条生成器:

    //Create a line generator
    var valueline = d3.svg.line()
        .x(function(d){
            return xScale(d.date);
        })
        .y(function(d){
            return yScale(d.close);
        });
  • 组合代码

    根据前面两部分的步骤分解,我们在补充一下刻度的创建就可以完成整个绘图工作了,完整代码如下:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>line chart</title>
      <script src="https://d3js.org/d3.v3.js"></script>
      <style>
        path{
          stroke: steelblue;
          fill: none;
          stroke-width: 2;
        }
        .axis path,
        .axis line {
          fill: none;
          stroke: grey;
          stroke-width: 1;
          /* 指定SVG元素<path>的渲染模式 */
          shape-rendering: crispEdges;
        }
     
      </style>
    </head>
    
    <body>
      <script>
        //Set margins and sizes
        var margin = {
          top: 20,
          bottom: 50,
          right: 30,
          left: 50
        };
    
        var width = 700 - margin.left - margin.right;
        var height = 500 - margin.top - margin.bottom;
        //Create date parser
        var ParseDate = d3.time.format("%d-%b-%y").parse;
        //Create x and y scale to scale inputs
        var xScale = d3.time.scale().range([0, width]);
        var yScale = d3.scale.linear().range([height, 0]);
    
        //Create x and y axes
        var xAxis = d3.svg.axis().scale(xScale)
          .orient("bottom")
          .ticks(5);
        var yAxis = d3.svg.axis().scale(yScale)
          .orient("left")
          .ticks(5);
    
        //Create a line generator
        var valueline = d3.svg.line()
          .x(function (d) {
            return xScale(d.date);
          })
    
          .y(function (d) {
            return yScale(d.close);
          });
        //Create an SVG element and append it to the DOM
        var svgElement = d3.select("body").append("svg")
          .attr({ "width": width + margin.left + margin.right, "height": height + margin.top + margin.bottom })
          .append("g")
          .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
        //Read TSV file
        d3.tsv("./data.tsv", function (data) {
          //Parse Data into useable format
          data.forEach(function (d) {
            d.date = ParseDate(d.date);
            d.close = +d.close;
            //the + sign converts string automagically to number
          });
    
          //Set the domains of our scales
          xScale.domain(d3.extent(data, function (d) { return d.date; })); // d3.extent - find the minimum and maximum value in an array.
          yScale.domain([0, d3.max(data, function (d) { return d.close; })]);
    
          //append the svg path
          var path = svgElement.append("path")
            .attr("d", valueline(data));
    
          //Add X Axis
          var x = svgElement.append("g")
            .attr("class", "x axis")
            .attr("transform", "translate(0," + height + ")")
            .call(xAxis);
    
          //Add Y Axis
          var y = svgElement.append("g")
            .attr("class", "y axis")
            .call(yAxis);
    
          //Add label to y axis
          y.append("text")
            .attr("fill", "#000")
            .attr("transform", "rotate(-90)")
            .attr("y", 6)
            .attr("dy", "0.71em")
            .attr("text-anchor", "end")
            .text("Price ($)");
        });
    
      </script>
    </body>
    
    </html>

区域图

区域图和线形图唯一的区别就是在线下面标识出一片区域,因此只需要修改一点代码就可以变成区域图了。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>area chart</title>
  <script src="https://d3js.org/d3.v3.js"></script>
  <style>
    .axis path,
    .axis line {
      fill: none;
      stroke: black;
      stroke-width: 1;
      shape-rendering: crispEdges;
    }
  </style>
</head>

<body>
  <script>
    //Set margins and sizes
    var margin = {
      top: 20,
      bottom: 50,
      right: 30,
      left: 50
    };

    var width = 960 - margin.left - margin.right;
    var height = 500 - margin.top - margin.bottom;
    //Create date parser
    var ParseDate = d3.time.format("%d-%b-%y").parse;
    //Create x and y scale to scale inputs
    var xScale = d3.time.scale().range([0, width]);
    var yScale = d3.scale.linear().range([height, 0]);

    //Create x and y axes
    var xAxis = d3.svg.axis().scale(xScale)
      .orient("bottom")
      .ticks(5);

    var yAxis = d3.svg.axis().scale(yScale)
      .orient("left");

    //Create a area generator
    var area = d3.svg.area()
      .x(function (d) {
        return xScale(d.date);
      })
      .y1(function (d) {
        return yScale(d.close);
      }); // area.y1 - get or set the y1-coordinate (topline) accessor.

    //Create an SVG element and append it to the DOM
    var svgElement = d3.select("body")
      .append("svg").attr({ "width": width + margin.left + margin.right, "height": height + margin.top + margin.bottom })
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    //Read TSV file
    d3.tsv("./data.tsv", function (data) {
      //Parse Data into useable format
      data.forEach(function (d) {
        d.date = ParseDate(d.date);
        d.close = +d.close;
        //the + sign converts string automagically to number
      });

      //Set the domains of our scales
      xScale.domain(d3.extent(data, function (d) { return d.date; }));
      yScale.domain([0, d3.max(data, function (d) { return d.close; })]);
      area.y0(yScale(0));
      //append the svg path
      var path = svgElement.append("path")
        .attr("d", area(data))
        .attr("fill", "steelblue");
      //Add X Axis
      var x = svgElement.append("g")
        .attr("transform", "translate(0," + height + ")")
        .attr("class", "x axis")
        .call(xAxis);

      //Add Y Axis
      var y = svgElement.append("g")
        .call(yAxis)
        .attr("class", "y axis");

      //Add label to Y axis
      y.append("text")
        .attr("fill", "#000")
        .attr("transform", "rotate(-90)")
        .attr("y", 6)
        .attr("dy", "0.71em")
        .attr("text-anchor", "end")
        .text("Price ($)");
    });
  </script>
</body>

</html>

主要变化就是将线条生成器替换为区域生成器即可。

d3绘图基本步骤

根据上面两种图表的创建,我们可以提炼出一个比较标准的创建模块,按照如下步骤:

  1. 基本的html和css
  2. 预处理

    a. 提前设定好图表的偏移、尺寸和变量等。

    b. 创建刻度、坐标、色彩刻度等。

    c. 创建数据处理函数例如时间解析器、百分比格式处理等。

  3. 可视化具体设计

    这里根据不同的图表类型进行具体的代码,比如线条生成器和区域生成器。

  4. 创建svg
  5. 加载外部数据
  6. 组合代码
  7. 额外处理

    包括标签、特效等。

这是一个推荐的比较标准的绘制步骤,并不是严格要求的,只是参考形成一个固定的风格,以便于理解和后期维护。下一节我们利用这些经验来绘制一个新的图表类型。

尝试一下

这一节按照上面的步骤来做一个柱状图,数据集采用csv格式:

    1. 基本html和css
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>bar chart</title>
  <script src="https://d3js.org/d3.v3.js"></script>
  <style>
    .axis path,
    .axis line {
      fill: none;
      stroke: grey;
      stroke-width: 1;
      /* 指定SVG元素<path>的渲染模式 */
      shape-rendering: crispEdges;
    }
 
  </style>
</head>

<body>
  <script>
    1. 预处理
    //Set margins and sizes
    var margin = {
      top: 20,
      bottom: 70,
      right: 30,
      left: 50
    };

    var width = 700 - margin.left - margin.right;
    var height = 500 - margin.top - margin.bottom;
    //Create date parser
    var ParseDate = d3.time.format("%Y-%m").parse;
    //Create x and y scale to scale inputs
    var xScale = d3.scale.ordinal().rangeRoundBands([0, width], .05);
    var yScale = d3.scale.linear().range([height, 0]);

    //Create x and y axes
    var xAxis = d3.svg.axis().scale(xScale)
      .orient("bottom")
      .tickFormat(d3.time.format("%Y-%m"));
    var yAxis = d3.svg.axis().scale(yScale)
      .orient("left")
      .ticks(10);
    1. 可视化具体设计

柱状图不需要特别的设计。

    1. 创建svg
    //Create an SVG element and append it to the DOM
    var svgElement = d3.select("body").append("svg")
      .attr({ "width": width + margin.left + margin.right, "height": height + margin.top + margin.bottom })
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    1. 加载外部数据
    //Read CSV file
    d3.csv("./bar-data.csv", function (data) {
      //Parse Data into useable format
      data.forEach(function (d) {
        d.date = ParseDate(d.date);
        d.value = +d.value;
        //the + sign converts string automagically to number
      });

      //Set the domains of our scales
      xScale.domain(data.map(function(d) { return d.date; }));
      yScale.domain([0, d3.max(data, function (d) { return d.value; })]);
    1. 组合代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>bar chart</title>
  <script src="https://d3js.org/d3.v3.js"></script>
  <style>
    .axis {
      font: 10px sans-serif;
    }
    .axis path,
    .axis line {
      fill: none;
      stroke: grey;
      stroke-width: 1;
      /* 指定SVG元素<path>的渲染模式 */
      shape-rendering: crispEdges;
    }
 
  </style>
</head>

<body>
  <script>
    //Set margins and sizes
    var margin = {
      top: 20,
      bottom: 70,
      right: 30,
      left: 50
    };

    var width = 700 - margin.left - margin.right;
    var height = 500 - margin.top - margin.bottom;
    //Create date parser
    var ParseDate = d3.time.format("%Y-%m").parse;
    //Create x and y scale to scale inputs
    var xScale = d3.scale.ordinal().rangeRoundBands([0, width], .05);
    var yScale = d3.scale.linear().range([height, 0]);

    //Create x and y axes
    var xAxis = d3.svg.axis().scale(xScale)
      .orient("bottom")
      .tickFormat(d3.time.format("%Y-%m"));
    var yAxis = d3.svg.axis().scale(yScale)
      .orient("left")
      .ticks(10);

    //Create an SVG element and append it to the DOM
    var svgElement = d3.select("body").append("svg")
      .attr({ "width": width + margin.left + margin.right, "height": height + margin.top + margin.bottom })
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    //Read CSV file
    d3.csv("./bar-data.csv", function (data) {
      //Parse Data into useable format
      data.forEach(function (d) {
        d.date = ParseDate(d.date);
        d.value = +d.value;
        //the + sign converts string automagically to number
      });

      //Set the domains of our scales
      xScale.domain(data.map(function(d) { return d.date; }));
      yScale.domain([0, d3.max(data, function (d) { return d.value; })]);

      //Add X Axis
      var x = svgElement.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis)
        .selectAll("text")
        .style("text-anchor", "end")
        .attr("dx", "-.8em")
        .attr("dy", "-.55em")
        .attr("transform", "rotate(-90)" );

      //Add Y Axis
      var y = svgElement.append("g")
        .attr("class", "y axis")
        .call(yAxis);

      //Add label to y axis
      y.append("text")
        .attr("fill", "#000")
        .attr("transform", "rotate(-90)")
        .attr("y", 6)
        .attr("dy", "0.71em")
        .attr("text-anchor", "end")
        .text("Price ($)");
      
      svgElement.selectAll("rect")
        .data(data)
        .enter().append("rect")
        .style("fill", "steelblue")
        .attr("x", function(d) { return xScale(d.date); })
        .attr("width", xScale.rangeBand())
        .attr("y", function(d) { return yScale(d.value); })
        .attr("height", function(d) { return height - yScale(d.value); });
    });

  </script>
</body>

</html>

一个简单的柱状图就完成了:

其他常见图形

树状图、力导向图等常见的图形与新版本的d3写法也有不少变化,下面的教程再总结了。

参考资料

  1. 本文参考示例
  2. v3文档

Alee
291 声望8 粉丝

既然路走偏了,那就重新开始吧。


引用和评论

0 条评论