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