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)开始绘图,需要将坐标轴往左下平移,才能显示所有的标签,并为后续添加点留出空间。使用svg
的transform
属性实现平移。我的坐标轴都在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