D3入门系列(2)--简单的条形图、折线图、散点图和文本换行

时间:2021-06-11 23:24:09

SVG画布

HTML 5 提供两种强有力的“画布”:SVG 和 Canvas。

SVG的特点:

  • SVG 绘制的是矢量图,因此对图像进行放大不会失真
  • 基于 XML,可以为每个元素添加 JavaScript 事件处理器
  • 每个图形均视为对象,更改对象的属性,图形也会改变
  • 不适合游戏应用

Canvas特点:

  • 绘制的是位图,图像放大后会失真
  • 不支持事件处理器
  • 能够以 .png 或 .jpg 格式保存图像
  • 适合游戏应用

那么,对于数据可视化,SVG的优势就显而易见了,而且D3中很多图形生成器也是只支持SVG的

注意,在 SVG 中,x 轴的正方向是水平向右,y 轴的正方向是垂直向下的

Step1  添加SVG画布

 1  var width = 400;  //画布的宽度
 2  var height = 400;   //画布的高度
 3  
 4  var svg = d3.select("body")     //选择文档中的body元素
 5      .append("svg")          //添加一个svg元素
 6      .attr("width", width)       //设定宽度
 7      .attr("height", height);    //设定高度
 8 
 9 //画布周边的空白
10  var padding = {left:30, right:30, top:20, bottom:20};

如此,便可以在画布上作图了

比例尺

利用比例尺的目的主要是将某一区域的值映射到另一区域,其大小关系不变,也就是说,让图形自适应画布的大小。

在数学中,x 的范围被称为定义域,y 的范围被称为值域。D3 中的比例尺,也有定义域和值域,分别被称为 domain 和 range。开发者需要指定 domain 和 range 的范围,如此即可得到一个计算关系。

D3中的比例尺最常用的有两种:

线性比例尺

d3.scale.linear() 返回一个线性比例尺。domain() 和 range() 分别设定比例尺的定义域和值域

 1 var dataset = [1.2, 2.3, 0.9, 1.5, 3.3];
 2 
 3 var min = d3.min(dataset);
 4 var max = d3.max(dataset);
 5 
 6 var linear = d3.scale.linear()
 7         .domain([min, max])
 8         .range([0, 300]);
 9 
10 linear(0.9);    //返回 0
11 linear(2.3);    //返回 175
12 linear(3.3);    //返回 300

注意:d3.scale.linear() 的返回值,是可以当做函数来使用的。因此,才有这样的用法:linear(0.9)。

序数比例尺

d3.scale.ordinal() 返回一个线性比例尺。domain() 和 range() 分别设定比例尺的定义域和值域

 1 var index = [0, 1, 2, 3, 4];
 2 var color = ["red", "blue", "green", "yellow", "black"];
 3 
 4 var ordinal = d3.scale.ordinal()
 5         .domain(index)
 6         .range(color);
 7 
 8 ordinal(0); //返回 red
 9 ordinal(2); //返回 green
10 ordinal(4); //返回 black

 0 对应颜色 red,1 对应 blue,依次类推

Step2  定义数据和比例尺

 1  var dataset=[10, 20, 30, 40, 33, 24, 12, 5];
 2  
 3  //x轴
 4  var xScale=d3.scale.ordinal()
 5               .domain(d3.range(dataset.length))
 6               .rangeRoundBands([0,width-padding.left-padding.right]);
 7  
 8  //y轴
 9  var yScale=d3.scale.linear()
10               .domain([0,d3.max(dataset)])
11               .range([height-padding.top-padding.bottom,0]);//y轴正方向向下

ordinal.rangeRoundBands - 用几个离散区间来分割一个连续的区间,区间边界和宽度会取整

 坐标轴

D3中用于定义坐标轴的组件:d3.svg.axis()

 1 //数据
 2 var dataset = [ 2.5 , 2.1 , 1.7 , 1.3 , 0.9 ];
 3 //定义比例尺,其中使用了数组dataset
 4 var linear = d3.scale.linear()
 5       .domain([0, d3.max(dataset)])
 6       .range([0, 250]);
 7 //定义坐标轴,其中使用了线性比例尺linear
 8 var axis = d3.svg.axis()
 9      .scale(linear)      //指定比例尺
10      .orient("bottom")   //指定刻度的方向
11      .ticks(7);          //指定刻度的数量

 追加到画布上:

1 svg.append("g")
2   .attr("class","axis")
3   .attr("transform","translate(20,130)")
4   .call(axis);  

注意: svg.append("g").call(axis); 与 axis(svg.append(g)); 是相等的。

为axis设定样式(y也是常用的样式了)

<style>
.axis path,
.axis line{
    fill: none;
    stroke: black;
    shape-rendering: crispEdges;
}

.axis text {
    font-family: sans-serif;
    font-size: 11px;
}
</style>

Step3  定义坐标轴

1 //定义x轴
2 var xAxis = d3.svg.axis()
3     .scale(xScale)
4     .orient("bottom");
5         
6 //定义y轴
7 var yAxis = d3.svg.axis()
8     .scale(yScale)
9     .orient("left");

Step4  添加矩形和文字元素

//矩形之间的空白
var rectPadding = 4;

//添加矩形元素
var rects = svg.selectAll(".MyRect")
        .data(dataset)
        .enter()
        .append("rect")
        .attr("class","MyRect")
        .attr("transform","translate(" + padding.left + "," + padding.top + ")")
        .attr("x", function(d,i){
            return xScale(i) + rectPadding/2;
        } )
        .attr("y",function(d){
            return yScale(d);
        })
        .attr("width", xScale.rangeBand() - rectPadding )
        .attr("height", function(d){
            return height - padding.top - padding.bottom - yScale(d);
        })
.attr("fill","steelblue");
//添加文字元素 var texts = svg.selectAll(".MyText") .data(dataset) .enter() .append("text") .attr("class","MyText") .attr("transform","translate(" + padding.left + "," + padding.top + ")") .attr("x", function(d,i){ return xScale(i) + rectPadding/2; } ) .attr("y",function(d){ return yScale(d); }) .attr("dx",function(){ return (xScale.rangeBand() - rectPadding)/2; }) .attr("dy",function(d){ return 20; }) .text(function(d){ return d; });

Step5  添加坐标轴元素

 1 //添加x轴
 2 svg.append("g")
 3   .attr("class","axis")
 4   .attr("transform","translate(" + padding.left + "," + (height - padding.bottom) + ")")
 5   .call(xAxis); 
 6         
 7 //添加y轴
 8 svg.append("g")
 9   .attr("class","axis")
10   .attr("transform","translate(" + padding.left + "," + padding.top + ")")
11   .call(yAxis);

 

折线图

 1     //定义画布
 2     var width=400;
 3     var height=400;
 4 
 5     var svg=d3.select("body")
 6               .append("svg")
 7               .attr("width",width)
 8               .attr("height",height);
 9     //定义内边距
10     var padding={left:20,right:20,top:10,bottom:10};
11     
12     //数据
13     var dataset=[11,35,23,78,55,18,98,100,22,65]
14     //定义比例尺
15     var xscale=d3.scale.linear()
16                  .domain([0,dataset.length-1])
17                  .range([0,width-padding.left-padding.right])
18     var yscale=d3.scale.linear()
19                  .domain([0,d3.max(dataset)])
20                  .range([height-padding.top-padding.bottom,0])
21     //绘制坐标轴
22     var xAxis=d3.svg.axis()
23                 .scale(xscale)
24                 .orient("bottom")
25     var yAxis=d3.svg.axis()
26                 .scale(yscale)
27                 .orient("left")
28     d3.select("svg")
29      .append("g")
30      .attr("transform","translate(" + padding.left + "," + (height - padding.bottom) + ")")
31      .call(xAxis)
32      .attr("class","axis")
33      
34     d3.select("svg")
35      .append("g")
36      .attr("transform","translate("+padding.left+","+padding.top+")")
37      .call(yAxis)
38      .attr("class","axis")
39 
40     
41     //绘制图形
42     var line_generator=d3.svg.line()
43                          .x(function(d,i){
44                              return xscale(i)//x轴的点用数据下标表示
45                          })
46                          .y(function(d){
47                              return yscale(d)
48                          });
49                          //.interpolate("linear")
50     var g=svg.append("g")
51             .attr("transform","translate("+padding.left+","+padding.top+")")
52 
53     
54     g.append("path")
55       .attr("d",line_generator(dataset))
56       .attr('stroke', 'black')
57       .attr('stroke-width', 1)
58       .attr("fill","none")

 散点图

关键代码如下:

var circle=svg.selectAll("circle")
                  .data(dataset)
                  .enter()
                  .append("circle")
                  .attr("fill","black")
                  .attr("r",3)
                  .attr("cx",function(d){
                      return padding.left+xscale(d[0])
                  })
                  .attr("cy",function(d){
                      return padding.top+yscale(d[1])  //重要!!
                  })

上面标红的地方说明以下,本来想着y轴向下,便写成了 height-padding.bottom-yscale(d[1]) 

但是发现画出的点的位置并不正确,原来原因是上面定义比例尺时,将值域已经设置成了 [height-2*padding.top,0] ,可以说,此时的坐标轴方向已经反转,所以此时的计算只需加上 padding.top 即可

其实,更不易出错的方法是,将点放在一个group内,那么cx, cy只需按比例计算,然后在将group做一个transform变换即可。如上面画折线图的方法。

文本的换行

最后讲一下文本的换行

方法1:利用tspan标签

var str = "云中谁寄锦书来,雁字回时,月满西楼";            
            
var text = svg.append("text")
            .attr("x",30)
            .attr("y",100)
            .attr("font-size",30)
            .attr("font-family","simsun");
//将字符串分段
var strs = str.split(",") ;

text.selectAll("tspan")
    .data(strs)
    .enter()
    .append("tspan")
    .attr("x",text.attr("x"))  //文本从x=?处开始
    .attr("dy","1em")  //文本较y轴的相对位移,此处也就意味着换行
    .text(function(d){
        return d;
         });

 

方法2:引用库 http://www.ourd3js.com/library/multext.js,其实质仍旧是tspan,只是进行了封装罢了

文件里只实现了一个函数 appendMultiText(),其各参数的意义为:

appendMultiText(
    container,            //文本的容器,可以是<svg>或<g> 
    str,                 //字符串
    posX,                 //文本的x坐标
    posY,                 //文本的y坐标
    width,                 //每一行的宽度,单位为像素
    fontsize,             //文字的大小(可省略),默认为 14
    fontfamily            //文字的字体(可省略),默认为 simsun, arial
)

小实例:

var str = "青青子衿,悠悠我心,但为君故,沉吟至今。";            
                        
var multext = appendMultiText(svg,str,30,100,120,20,"simsun");
                
multext.attr("transform","rotate(-20)");