D3.js学习笔记:绘制简单的时间轴

目录

最近研究按时间排序的事件序列,说的直接点就是日志,想用时间线来表示状态发生变化的各个时间点。考虑用D3实现,下面是我尝试绘制时间线的记录。

时间序列

确定起止时间点。因为要使用d3的比例尺,所以不需要生成时间序列,只需要给出时间范围即可。我只关注某一天的事件,时间范围就是从当前的0时到第二天的0时。

利用d3的时间间隔函数可以生成0时的date对象。d3.time.day返回当天0时的对象,d3.time.day.offset返回隔几天而时间不变的对象。

var start_date = d3.time.day(scope.date);
var end_date = d3.time.day.offset(start_date, 1);

例如:

scope.date = Mon Dec 15 2014 08:00:00 GMT+0800  
start_date = Mon Dec 15 2014 00:00:00 GMT+0800  
end_date = Tue Dec 16 2014 00:00:00 GMT+0800

时间比例尺

显示时间点确定在时间线上的位置,所以需要将时间范围映射到时间线的长度中。利用d3为时间准备的比例尺d3.time.scale可以实现:

var time_line_scale = d3.time.scale()
    .domain([start_date, end_date])
    .range([0, time_line_axis_width]);

例:

> time_line_axis_width  
700
> time_line_scale(new Date("Mon Dec 15 2014 12:00:00 GMT+0800")  
350

12点映射为长度的一半。

时间轴坐标

d3提供封装好的坐标函数d3.svg.axis

var time_line_axis = d3.svg.axis().scale(time_line_scale).orient("bottom");

绘制坐标轴

将目前的坐标绘制到屏幕中,这里多写点儿代码

var svg = d3.select(element[0]).append('svg')
    .attr('class','YlGn')
    .attr('width', svg_attr.width + 'px')
    .attr('height', svg_attr.height + 'px');
var axis_group = svg.append("g")
    .attr('class', 'time-line axis')
    .call(time_line_axis);

得到的效果:

默认的样式不太好看,需要进一步修改:

加入新的样式表(来自《数据可视化实战:使用D3设计交互式图表》):

.axis path,
.axis line {
    fill: none;
    stroke: black;
    shape-rendering: crispEdges;
}
.axis text {
    font-family: sans-serif;
    font-size: 11px;
}

新的效果如下:

最左边的标签依旧看不清,因为默认从(0,0)开始绘图,需要将坐标轴往左下平移,才能显示所有的标签,并为后续添加点留出空间。使用svgtransform属性实现平移。我的坐标轴都在g元素中,所以只需要平移g即可实现整体平移。

var axis_group = svg.append("g")
    .attr('class', 'time-line axis')
    .attr('transform', 'translate(' + time_line_attr.left_margin + ',' + time_line_attr.top_margin + ')')
    .call(time_line_axis);

新的效果:

时间点

下面在坐标轴上画出时间点。时间点数据:

var point_data=[
    {name: 'active', time:Date('Mon Dec 15 2014 11:58:53 GMT+0800')},
    {name: 'complete', time:Date('Mon Dec 15 2014 12:54:06 GMT+0800')},
    {name: 'submitted', time:Date('Mon Dec 15 2014 11:58:51 GMT+0800')}
]

将点画在坐标轴上,并使用上面的平移设置:

var point_g = svg.append("g")
    .attr('class','time-point')
    .attr('transform', 'translate(' + time_line_attr.left_margin + ',' + time_line_attr.top_margin + ')');

point_g.selectAll('circle')
    .data(point_data);
    .enter()
    .append('circle')
    .attr('cx', function(d,i){
        return time_line_scale(d.time);
    })
    .attr('cy', -2)
    .attr('r', 5);

效果如下:

但数据中有三个点,为什么坐标轴上只有两个?

仔细看下数据可以发现,有两个点时间只差2秒钟,坐标轴范围太大,两个点分辨不出来。还好,d3提供缩放功能,当缩放到一定的比率就可以看到这确实是两个点,下面就介绍如何实现缩放。

缩放

参照d3官网上的一篇文章《Pan+Zoom》,使用d3.behavior.zoom实现缩放。

定义缩放

单方向x轴缩放,为了看到很近的两个时间,设置无限放大。

var zoom = d3.behavior.zoom()
    .x(time_line_scale)
    .scaleExtent([1, Infinity])
    .on("zoom", zoomed);

定义缩放时间处理函数

上文中zoomed是缩放事件发生时调用的函数,重新设置坐标轴和点的位置。

function zoomed() {
    svg.select('.axis').call(time_line_axis);
    point_g.selectAll('circle')
        .data(point_data)
        .attr('cx', function(d,i){
            return time_line_scale(d.time);
        })
}

应用缩放

在整个svg上应用缩放即可。

var svg = d3.select(element[0]).append('svg')
    .attr('class','YlGn')
    .attr('width', svg_attr.width + 'px')
    .attr('height', svg_attr.height + 'px')
    .call(zoom);

效果图

缩放到足够尺度时,就能分辨出之前重叠到一起的两个点。

修饰

添加说明

目前只使用svg的title标签添加简单的浏览器鼠标指向说明。

var point_g = svg.append("g")
    .attr('class','time-point')
    .attr('transform', 'translate(' + time_line_attr.left_margin + ',' + time_line_attr.top_margin + ')');

point_g.selectAll('circle')
    .data(point_data);
    .enter()
    .append('circle')
    .attr('cx', function(d,i){
        return time_line_scale(d.time);
    })
    .attr('cy', -2)
    .attr('r', 5)
    .append('title')
    .text(function(d,i){
        return d.name + " at " + time_format(d.time);
    });;

效果

后续需要进一步修改、美化。

项目

本文的代码已整合成单独的项目nuwe-timeline