D3入門系列(2)--簡單的條形圖、折線圖、散點圖和文本換行


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)");

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM