d3 - 简单的坐标轴例子

source link: http://m.blog.csdn.net/blog/zxlvxj/44065561

varcontainer = d3.select(‘body‘)

  .append(‘svg‘)

  .attr(‘width‘, width + margin.left + margin.right)

  .attr(‘height‘, height + margin.top + margin.bottom);

我在 Chrome Stable 里测试的:

Array.apply(null, newArray(124995))

报错是:

Maximum call stack size exceeded

小一个数字就不报错了.
然后在 Node 环境里尝试了一下, 这个数字要小不少, 但没往下去确定.

很奇怪, 这个表达式不是递归调用啊, 为什么报超出最大栈呢?

http://segmentfault.com/q/1010000000643173

首先你要知道new Array(124995)创建一个有124995个元素的数组,其中每个元素都是undefined
然后,X.apply(Y, [arg1, arg2, ...])的含义其实是Y.X(arg1, arg2, ...),在你的程序里,你在用一个有124995个元素的数组调用Array函数,相当于null.Array(undefined, undefined, ...),参数共有124995个。
虽说没有递归,但是函数调用时会把每个参数都放在stack里,你一下放了十几万个,stack就爆掉了。

step2:创建SVG容器

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

varmargin = {top: 20, right: 20, bottom: 30, left: 50},

  width = document.body.clientWidth - margin.left - margin.right,

  height = 500 - margin.top - margin.bottom;

varcontainer = d3.select(‘body‘)

  .append(‘svg‘)

  .attr(‘width‘, width + margin.left + margin.right)

  .attr(‘height‘, height + margin.top + margin.bottom);

varsvg = container.append(‘g‘)

  .attr(‘class‘,‘content‘)

  .attr(‘transform‘,‘translate(‘+ margin.left + ‘,‘+ margin.top + ‘)‘);

margin、width、height定义了svg节点的位置和尺寸,后面会用到。d3.select类似jquery的选择器,并且d3的语法也支持串联调用,append(‘svg’)将svg追加到body的尾部,同时为svg节点设置了宽度和高度值,attr也有get和set两种用法。

svg的g元素类似于div,在这里作为一组元素的容器,后面加入的元素都放在g里面,g可以设置统一的css,里面的子元素会继承可继承css属性。margin和position对g的定位不起作用,只能使用translate通过位移来定位。

step3:定位坐标轴

既然d3是数据驱动的,那必须要有数据啊,没有数据肿么能搞呢。好吧,首先模拟一份数据,就模拟本月的pv数据吧,即12月每天的pv数据,日期采用yy-mm-dd的格式,pv随机一个100以内的整数。

JavaScript

1

2

3

4

5

vardata = Array.apply(0, Array(31)).map(function(item, i) {

  // 产生31条数据

  i++;

  return{date: ‘2013-12-‘+ (i < 10 ? ‘0‘+ i : i), pv: parseInt(Math.random() * 100)}

});

然后定义坐标轴的一些参数

JavaScript

1

2

3

4

5

6

7

varx = d3.time.scale()

  .domain(d3.extent(data,function(d) { returnd.day; }))

  .range([0, width]);

vary = d3.scale.linear()

  .domain([0, d3.max(data, function(d) { returnd.value; })])

  .range([height, 0]);

横坐标是日期,这里使用d3.time自动帮我们在时间和字符串之间做转换。y轴使用普通的线性缩放坐标轴。其实这里的x和y也是一个function,后续会用到。
domain规定了坐标轴上值的范围,d3.extent从数组里选出最小值和最大值,d3.max选数组里面最大值。range规定了坐标轴端点的位置,svg的坐标原点是左上角,向右为正,向下为正,而y轴正方向为由下向上,所以(0, height)才是图表的坐标原点。
然后使用d3的axis定制坐标轴

JavaScript

1

2

3

4

5

6

7

8

9

varxAxis = d3.svg.axis()

  .scale(x)

  .orient(‘bottom‘)

  .ticks(30);

varyAxis = d3.svg.axis()

  .scale(y)

  .orient(‘left‘)

  .ticks(10);

orient有四个参数(left、right、top、bottom)定义了坐标轴的位置,这里很好理解。
ticks定义了坐标轴上除最小值和最大值以外最多有多少个刻度,因为一个月最多有31天,ticks(30)就足以展示每天的刻度了。

然后就可以把坐标轴加进svg容器了

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

// 横坐标

svg.append(‘g‘)

  .attr(‘class‘,‘x axis‘)

  .attr(‘transform‘,‘translate(0,‘+ height + ‘)‘)

  .call(xAxis)

  // 增加坐标值说明

  .append(‘text‘)

  .text(‘日期‘)

  .attr(‘transform‘,‘translate(‘+ width + ‘, 0)‘);

// 纵坐标

svg.append(‘g‘)

  .attr(‘class‘,‘y axis‘)

  .call(yAxis)

  .append(‘text‘)

  .text(‘次/天‘);

加上坐标轴之后的效果图应该是这样

  

step4:画线

有了坐标轴之后我们可以加上图表的主体部分了,pv图应该是一条折线图。怎么加折线呢,d3提供了丰富的图表元素,需要折线只需要append(‘path’)即可,了解svg的都知道,path的d属性是最重要的,决定了折线的“路径”,这里就不详细讲解path了。

我们只有一个数组的数据,怎么转化成需要的d呢,别担心,d3帮我们做了这部分工作。首先需要用d3.svg.line生成一个“线条函数”,然后将数据传给该函数即可生成我们想要的d,我们需要做的就是定制这个“线条函数”的两条坐标轴分别由数据的哪部分决定。

下面看代码

JavaScript

1

2

3

4

varline = d3.svg.line()

  .x(function(d) { returnx(d.date); })

  .y(function(d) { returny(d.pv); })

  .interpolate(‘monotone‘);

上面的代码很好理解,设置了x坐标轴由date属性决定,y坐标轴由pv属性决定,最后还调用了interpolate,该方法会改变线条相邻两点之间的链接方式以及是否闭合,接受的参数有linear,step-before,step-after,basis,basis-open,basis-closed,bundle,cardinal,cardinal-open,cardinal-closed,monotone,读者可以一一尝试,看看线条有什么不一样。

“线条函数”生成好了,可以应用到path上了

JavaScript

1

2

3

varpath = svg.append(‘path‘)

  .attr(‘class‘,‘line‘)

  .attr(‘d‘, line(data));

此时的图应该是这样了

step5:打点

到这里其实基本的图形已经实现了,只用了应该不到20行代码,不过这也太丑了点吧,而且完全木有交互啊。

别急,ga的pv图在每个数据点都会有一个小点来占位,其实本来我们的数据就是离散的,图上也应该是离散的一些点,不过为了图表好看,也为了方便查看数据的走势,折线图显然更形象一些。

下面就在折线上增加相应的点,点我们可以用circle,要增加元素用append即可

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

varg = svg.selectAll(‘circle‘)

  .data(data)

  .enter()

  .append(‘g‘)

  .append(‘circle‘)

  .attr(‘class‘,‘linecircle‘)

  .attr(‘cx‘, line.x())

  .attr(‘cy‘, line.y())

  .attr(‘r‘, 3.5)

  .on(‘mouseover‘,function() {

    d3.select(this).transition().duration(500).attr(‘r‘, 5);

  })

  .on(‘mouseout‘,function() {

    d3.select(this).transition().duration(500).attr(‘r‘, 3.5);

  });

这里的代码可能复杂一点,因为circle不止一个,需要使用selectAll,而circle现在是还不存在的。selectAll(‘circle’)的作用可以理解成先预定若干个circle的位置,等有数据了再插入svg容器里。

enter就表明有数据来了,将每个circle放到单独的g里面,这里没有特殊的用意,就像html里面习惯用div来装其他元素一样。

为circle设置一些属性,cx、cy代表圆心x、y坐标,line.x()和line.y()会返回折线上相应点的x、y坐标,这样添加的circle就依附在折线上了。r表示圆半径,同时为circle添加了两个鼠标事件,这样鼠标在circle上移动和移出的时候增加了圆半径变化的一个动画。

效果图

step6:增加tips

现在看整体数据倒是可以了,不过看某天的具体数据还是太不方便了,如果在circle上直接标注出具体的数据又太挫了。

咋办?嘿嘿,参考ga呗。ga在鼠标经过某点的纵坐标所在的直线的时候就会在改点附近出现具体的数据tips,赞,既能清晰地看到整体的走势又能看到每天的具体数据。

先上效果图

图中用一个圆角矩形和两行文字组成了一个简单的tips

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

vartips = svg.append(‘g‘).attr(‘class‘,‘tips‘);

tips.append(‘rect‘)

  .attr(‘class‘,‘tips-border‘)

  .attr(‘width‘, 200)

  .attr(‘height‘, 50)

  .attr(‘rx‘, 10)

  .attr(‘ry‘, 10);

varwording1 = tips.append(‘text‘)

  .attr(‘class‘,‘tips-text‘)

  .attr(‘x‘, 10)

  .attr(‘y‘, 20)

  .text(‘‘);

varwording2 = tips.append(‘text‘)

  .attr(‘class‘,‘tips-text‘)

  .attr(‘x‘, 10)

  .attr(‘y‘, 40)

  .text(‘‘);

为啥要用矩形呢,为啥不直接在g上设置圆角效果呢?实践证明对g设置的width、height、border-radius均无效,无赖只能使用svg的rect元素了。

rx、ry是圆角两个方向的半径,原理同border-radius。展示文字用text元素即可,这里的x和y还是坐标,不过是相对于父元素g的坐标。

最后的关键是怎么让tips出现在该出现的位置和展示对的数据,即鼠标经过某个点的纵坐标所在的直线是tips出现在改点附近,且展示改点的数据。

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

container

  .on(‘mousemove‘,function() {

    varm = d3.mouse(this),

      cx = m[0] - margin.left;

    varx0 = x.invert(cx);

    vari = (d3.bisector(function(d) {

      returnd.date;

    }).left)(data, x0, 1);

    vard0 = data[i - 1],

      d1 = data[i] || {},

      d = x0 - d0.date > d1.date - x0 ? d1 : d0;

    functionformatWording(d) {

      return‘日期:‘ + d3.time.format(‘%Y-%m-%d‘)(d.date);

    }

    wording1.text(formatWording(d));

    wording2.text(‘PV:‘+ d.pv);

    varx1 = x(d.date),

      y1 = y(d.pv);

    // 处理超出边界的情况

    vardx = x1 > width ? x1 - width + 200 : x1 + 200 > width ? 200 : 0;

    vardy = y1 > height ? y1 - height + 50 : y1 + 50 > height ? 50 : 0;

    x1 -= dx;

    y1 -= dy;

    d3.select(‘.tips‘)

      .attr(‘transform‘,‘translate(‘+ x1 + ‘,‘+ y1 + ‘)‘);

    d3.select(‘.tips‘).style(‘display‘,‘block‘);

  })

  .on(‘mouseout‘,function() {

    d3.select(‘.tips‘).style(‘display‘,‘none‘);

  });

这段长长的代码需要重点解释一下,首先是d3.mouse(this),这个方法会返回当前鼠标的坐标,是一个数组,分别是x和y坐标。

下面这一步最重要的一点来了,x.invert(cx)跟据传入的横坐标数值返回该横坐标的实际数据上的值,在本例中返回一个日期。

下面的i是根据返回的日期反向得到data数组中的元素位置。有了这个i一切都好办了,接下来的代码是为了判断鼠标在两个日期之间离哪个更近。

后面的代码都很简单了,拿到了tips应该出现的x、y坐标之后设置tips的transform即可,再控制tips的display属性就达到了最后的效果。

完整代码地址:http://pan.baidu.com/s/1jirPO,提取码:m0qb

时间: 2024-10-10 22:01:00

d3 - 简单的坐标轴例子的相关文章

D3.js:坐标轴

坐标轴: 是可视化图表中经常出现的一种图形,由一些列线段和刻度组成.坐标轴在 SVG 中是没有现成的图形元素的,需要用其他的元素组合构成.D3 提供了坐标轴的组件,如此在 SVG 画布中绘制坐标轴变得像添加一个普通元素一样简单. - 坐标轴的组成 在 SVG 画布的预定义元素里,有六种基本图形: 矩形 圆形 椭圆 线段 折线 多边形 另外,还有一种比较特殊,也是功能最强的元素: 路径 画布中的所有图形,都是由以上七种元素组成.显然,这里面没有坐标轴 这种元素.因此,我们需要用其他元素来组合成坐标

使用Multiplayer Networking做一个简单的多人游戏例子-2/3(Unity3D开发之二十六)

猴子原创,欢迎转载.转载请注明: 转载自Cocos2Der-CSDN,谢谢! 原文地址: http://blog.csdn.net/cocos2der/article/details/51007512 使用Multiplayer Networking做一个简单的多人游戏例子-1/3 使用Multiplayer Networking做一个简单的多人游戏例子-2/3 使用Multiplayer Networking做一个简单的多人游戏例子-3/3 7. 在网络中控制Player移动 上一篇中,玩家操

一个简单的KVO例子

一个简单的KVO例子. 两个界面,第一个界面显示名字和配偶(spouse)名字,第二个界面显示修改名字和配偶名字,返回时,将看到第一个界面的名字显示发生改变. 首先定义一个person类作为model. #import <Foundation/Foundation.h> @interface Person : NSObject @property (strong, nonatomic) NSString *name; @property (strong, nonatomic) NSString

Java一个简单的死锁例子

内容:一个简单的死锁例子,大概的思路:两个线程A和B,两把锁X和Y,现在A先拿到锁X,然后sleep()一段时间,我们知道sleep()是不会释放锁资源的.然后如果这段时间线程B拿到锁Y,也sleep()一段时间的话,那么等到两个线程都醒过来的话,那么将互相等待对方释放锁资源而僵持下去,陷入死锁.flag的作用就是让A和B获得不同的锁. public class TestDeadLock { public void run() { MyThread mt = new MyThread(); ne

Spring简单的小例子SpringDemo,用于初略理解什么是Spring以及JavaBean的一些概念

一.开发前的准备 两个开发包spring-framework-3.1.1.RELEASE-with-docs.zip和commons-logging-1.2-bin.zip,将它们解压,然后把Spring开发包下dist目录的所有包和commons-logging包下的commons-logging-1.1.1.jar复制到名为Spring3.1.1的文件夹下.那么Spring开发所需要的包就组织好了. 二.建立项目,导入包 在项目节点上右键,Build Path/ADD Libraries/U

编写一个简单的jdbc例子程序

1 package it.cast.jdbc; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.ResultSet; 6 import java.sql.SQLException; 7 import java.sql.Statement; 8 9 public class Base { 10 11 public static void main(String[] args) th

一个简单的小例子让你明白c#中的委托-终于懂了!

模拟主持人发布一个问题,由多个嘉宾来回答这个问题. 分析:从需求中抽出Host (主持人) 类和Guests (嘉宾) 类. 作为问题的发布者,Host不知道问题如何解答.因此它只能发布这个事件,将事件委托给多个嘉宾去处理.因此在Host 类定义事件,在Guests类中定义事件的响应方法.通过多番委托的"+="将响应方法添加到事件列表中,最终 Host 类将触发这个事件.实现过程如下: 代码其实很少下面贴出来所有代码: QuestionArgs.cs view plaincopy to

C#中使用UdpClient类进行简单通信的例子

UdpClient 类提供了一些简单的方法,用于在阻止同步模式下发送和接收无连接 UDP 数据报. 因为 UDP 是无连接传输协议,所以不需要在发送和接收数据前建立远程主机连接.但您可以选择使用下面两种方法之一来建立默认远程主机: 使用远程主机名和端口号作为参数创建 UdpClient 类的实例. 创建 UdpClient 类的实例,然后调用 Connect 方法. 可以使用在 UdpClient 中提供的任何一种发送方法将数据发送到远程设备. 使用 Receive 方法可以从远程主机接收数据.

Servlet学习教程(三)---- 一个简单的Servlet例子

我们用个最简单的Servlet例子来解说一下Servlet简单配置以及Servlet类实现类的写法. 第一,我们新建一个Dynamic Web Project,起名Servlet 点击NEXT,设置Default output folder 为Servlet/WebContent/WEB-INF/classes 第二,创建一个包,包名为Servlet,然后创建一个类名为WelcomeServlet类.(Servlet类当然缺少不了Servlet容器,请注意你的开发软件是否已经集成Servlet容