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