试想一下,如果要我们用尺规在纸上绘制圆内接正n边形,这并不是一件很容易做到的事情。但如果要我们在浏览器上完成这个任务,只要掌握了技巧,这会是一件很容易的事。
先看一下已经绘制出来的效果
从图形上我们可以看出来,先是绘制了一个圆,然后在圆内依次画出了圆的内接正6边形,正5边形,正4边形,正3角形。形成了覆盖效果。
再看一下对应的javascript代码
<!DOCTYPE html>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<head>
<title>绘制圆内接正多边形</title>
<script src="d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<h2>绘制圆内接正多边形</h2>
<div id="stage" width="800" height="800"></div>
<script type="text/javascript">
// 设置svg
var svg = d3.select("#stage").append("svg")
.attr("width", 800)
.attr("height", 800);
// 画圆
var myCircle = svg.selectAll(".cycle")
.data([1])
.enter()
.append("circle")
.attr("cx", 400)
.attr("cy", 400)
.attr("r", 200)
.style("fill", "#219E3E")
.style("stroke", "#17301D")
.style("stroke-width", "2");
//画多边形
var myPolygon = svg.selectAll(".polygon")
//.data([16,15,14,13,12,11,10,9,8,7,6,5,4,3])
.data([6,5,4,3])
.enter()
.append("polygon")
.attr("points", function (d, i) { return polygonPoints(400,400,200,d); } )
.style("fill", "lime")
.style("stroke", "purple")
.style("stroke-width", "2");
/*
*求圆内接正多边形的顶点串,形如x1,y1,x2,y2,...xn,yn
*cx,cy为圆心,r为半径,n为边数,正多边形底边与X轴平行
*/
function polygonPoints(cx,cy,r,n){
var alpha=2*Math.PI/n;
var a=Math.PI/2+alpha/2;
var points="";
for(var i=0;i<n;i++){
x=cx+r*Math.cos(a);
y=cy+r*Math.sin(a);
points+=x+","+y+",";
a+=alpha;
}
return points.substring(0,points.length-1);
}
</script>
</body>
</html>
从源码中可以看出,引入了一个js组件:d3.v3.min.js。
我们将代码稍做一点改动,就可以依次画出圆的内接正8边形,正7边形,正6边形,正5边形,正4边形,正3角形.
效果如下:
如果我不说明,恐怕你很难猜出来这到底画的是什么。
能够实现复杂的绘图效果,但是源代码却非常简单。这是为什么?
因为d3.js+svg的结合,极大地简化我们的绘图工作量。就拿本文的例子来说。svg本就是一种描述性的绘图语言(或者说标准)。只要我们给出了图形的基本要素,具体的绘制工作就不用我们操心了。
而d3.js的作用是什么呢?它可以非常方便地将数据转换为dom中的元素。所以称之为数据驱动文档的工具。
下面简单地说明一下绘图的原理。
要绘制一个圆
svg的基本语法是:
<svg width="800" height="800"><circle cx="400" cy="400" r="200" style="fill: rgb(33, 158, 62); stroke: rgb(23, 48, 29); stroke-width: 2px;"></circle></svg>
也就是说在svg标签中加入一个circel标签就可以画出一个圆,而一个圆的基本要素是圆心与半径。svg中用cx,xy属性指明圆心的x坐标与y坐标。r指明圆的半径。
再对照一下d3.js代码
// 画圆
var myCircle = svg.selectAll(".cycle")
.data([1])
.enter()
.append("circle")
.attr("cx", 400)
.attr("cy", 400)
.attr("r", 200)
.style("fill", "#219E3E")
.style("stroke", "#17301D")
.style("stroke-width", "2");
就会发现d3.js将用svg绘图的工作进一步简化,并且可以用对象化操作的方式来设置对象的各种属性,除了前面提到的圆心,半径,颜色,线型都可以方便地设置。
要绘制一个多边型,以最简单的三边形为例,svg的语法是:
<svg width="800" height="800"><polygon points="226.7949192431123,500.00000000000006,399.99999999999994,200,573.2050807568878,499.9999999999999" style="fill: lime; stroke: purple; stroke-width: 2px;"></polygon></svg>
也就是说在svg标签中加入一个polygon标签就可以画出一个多边形,而polygon标签的points属性指明的多边形的各个顶点的坐标。
再看一下对应的d3.js的代码
//画多边形
var myPolygon = svg.selectAll(".polygon")
//.data([16,15,14,13,12,11,10,9,8,7,6,5,4,3])
.data([3])
.enter()
.append("polygon")
.attr("points", function (d, i) { return polygonPoints(400,400,200,d); } )
.style("fill", "lime")
.style("stroke", "purple")
.style("stroke-width", "2");
这里的代码稍稍有点费解,但正是d3.js强大性的体现。在画圆时,cx,cy属性是直接给出来的。而在这里画三角形的最关键的属性points的值是由一个匿名函数
function (d, i) { return polygonPoints(400,400,200,d); }决定的。
也就是说可以根据参数d来动态地返回points。而这里的d代表数据集中的当前数据,数据集只是一个简单的数组 [3],所以d就代表3.
如果将数据集换成[6,5,4,3],就可以一次指定要画6边形,5边形,4边形,3边形,points的值将会根的参数d的不同而调用函数计算出来。
这就是d3.js的强大之处,画一个多边形与画一百个多边形,一万个多边形,代码不用发生变化,只要把数据集从1个元素的数组替换成100个元素的数组,10000个元素的数组。确实是很方便。
再看一下画出4个正多边形的代码,就会明白我说的意思了。
//画多边形
var myPolygon = svg.selectAll(".polygon")
//.data([16,15,14,13,12,11,10,9,8,7,6,5,4,3])
.data([6,5,4,3])
.enter()
.append("polygon")
.attr("points", function (d, i) { return polygonPoints(400,400,200,d); } )
.style("fill", "lime")
.style("stroke", "purple")
.style("stroke-width", "2");
如果想进一步了解d3.js强大而又有趣的功能,可以简单搜索一下d3.js的教程,上面有更多的例子。
补充说明,要画出内接正多边形,需要一点小小的解析几何的知识,这就是下面的函数中所蕴含的几何知识
/*
*求圆内接正多边形的顶点串,形如x1,y1,x2,y2,...xn,yn
*cx,cy为圆心,r为半径,n为边数,正多边形底边与X轴平行
*/
function polygonPoints(cx,cy,r,n){
var alpha=2*Math.PI/n;
var a=Math.PI/2+alpha/2;
var points="";
for(var i=0;i<n;i++){
x=cx+r*Math.cos(a);
y=cy+r*Math.sin(a);
points+=x+","+y+",";
a+=alpha;
}
return points.substring(0,points.length-1);
}
如果对代码有疑惑,那么是时候复习一下中学的数学知识了。除了参加高考,原来中学的数学知识也是有那么一点用处的。