用highcharts实现数据统计中时间对比功能
时间对比的错位问题
如果某个数据系列以某个时间为固定周期波动变化,我们通常会对其进行纵向的时间对比,比如某个生产服务器的带宽数据等等。
用echarts可以简单地实现多条数据系列同时显示,进行横向对比。但是在用其实现时间对比的时候遇到了麻烦,比如下图
两条数据虽然同时显示了,但是相邻位置的数据并不是一个相近的时间区间内的,显然这样的对比毫无意义。
先来分析一下数据内容。这两条数据是同一台服务器上相邻两天的带宽数据,这些数据每5分钟获取一次存入数据库。一般情况下,定时脚本执行插入的时间是固定的,插入的次数也是固定的,这样每天都数据量都相同,可以简单的进行对比。不过,现实的情况是,因为网络情况不稳定或者其他原因,每次插入数据库的时间并不相同,今天是00:01分插入数据,明天可能是00:03分插入数据。更糟糕的是,插入次数也是不稳定的,可能有些时间段内1小时插入12条数据,而另一个时间段只有3条/小时。在进行小范围的时间对比时这些影响不大,而如果需要以星期,月为区间进行对比,到后期同一位置的数据误差可能达到数个小时之久,我们需要一种更合理的方式处理这些数据的显示。
解决思路
在上面echarts图中,x轴的刻度是按照存储数据的时间定义的,类似这样
xAxis : [
{
type : 'category',
}
]
在进行时间对比时,我们需要两条x轴分别代表两个时间序列。因为数据存入时间不稳定,两条x轴在图表中不能精确的对应上。因此,要解决时间错位的问题,就必须采用绝对时间轴,用类似散点的方式来展示数据,让数据适应标准轴而不是数据生成时间轴。
在highcharts中,可以指定x轴的类型为时间,自动生成x轴。如下
xAxis: [
{
type: 'datetime',
minRange:3600000,
}
],
需要注意的是应提前限定对比区间长度的一致,才能让同等长度的时间轴单位长度代表的时间等长。
然而图表并不随人愿,总是让数据平铺到整个时间轴上,让数据量不相等的两个系列占用同样的宽度。因此我们需要指定开始时间和结束时间,并告诉highcharts,让那些没有数据对应的时间点空着吧。
series: [
{
name: '{{key}}',
visible: true,
xAxis: {{num}},
pointStart: Date.parse("{{start_point[key]}}"),
pointInterval:3600000 * 24 * {{start_point['days']}} - 3600000 * 8,
data: [null,{%for i in value %}[Date.parse("{{str(i['time'])[:-3]}}"),{{data}}],{%end%}null]
},
]
这个数据系列与echarts的不同在于,这里data的数据类似散点图,指定了该数据对应的时间点。注意这里x轴和数据的时间是用js函数生成的timestamp,并且在数据首尾都有null占位。
这个图表的效果如下:
即使该时间点无数据,图表也会让它空在那,以保证同一时间的数据一一对应
解决方案
总结一下,解决对比数据时间错位的问题 1. 采用绝对时间轴 2. 指定每个数据对应的时间 3. 双x轴时间等长 4. 声明无数据位置留空
遇到的问题
- highcharts时间显示错误 如果x轴和数据的时间是用date对象的UTC方法产生的,需要注意这个方法获取的月份参数应该比实际月份少一。。。因为date对象里一月份对应的是0,二月份对应的是1。因此如果数据原始时间格式是%Y-%m-%d的话,不推荐用UTC方法。 而用上面写到的parse方法的话,在中国会存在时间错位8小时的问题,因为parse是根据当前时区(东8)的时间生成的timestamp,而highcharts是用标准时来解析的。解决方法是给highcharts一个全局变量指定时区偏移8小时,或者关闭UTC设置。详见下面代码
附完整代码(tornado框架)
$(function () {
Highcharts.setOptions({
global: {
useUTC: false,
// timezoneOffset: -8 * 60,
}
});
$('#highcharts').highcharts({
chart: {
zoomType: 'x',
},
title: {
text: false
},
subtitle: {
text: '频道带宽(Mbps)'
},
xAxis: [
{
type: 'datetime',
minRange:3600000,
},
{% if comp_time %}
{
type: 'datetime',
minRange:3600000,
opposite: true
},
{% end %}
],
yAxis: {
title: {
text: '带宽(Mbps)'
}
},
legend: {
enabled: {% if compchannel or comp_time %}true{%else%}false{%end%}
},
plotOptions: {
area: {
fillColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1},
stops: [
[0, Highcharts.getOptions().colors[0]],
[1, Highcharts.Color(Highcharts.getOptions().colors[0]).setOpacity(0).get('rgba')]
]
},
marker: {
radius: 2
},
lineWidth: 1,
states: {
hover: {
lineWidth: 1
}
},
threshold: null,
},
series:{
events: {
mouseOver: function(event){
}
},
cursor: 'pointer',
}
},
{% if compchannel %}
tooltip:{
shared: true,
},
{% end %}
{% if comp_time %}
tooltip:{
// shared: true,
formatter:function(){
return '<strong>'+Highcharts.dateFormat('%Y-%m-%d %H:%M',this.x)+'</strong><br />'
+'带宽: '+this.y+' Mbps/s';
}
},
{% end %}
series: [
{% if comp_time %}
{% for num,(key,value) in enumerate(band.items()) %}
{
name: '{{key}}',
visible: true,
xAxis: {{num}},
pointStart: Date.parse("{{start_point[key]}}"),
pointInterval:3600000 * 24 * {{start_point['days']}} - 3600000 * 8,
data: [null,{%for i in value %}[Date.parse("{{str(i['time'])[:-3]}}"),{{float('%.2f'%(i['bandbit']/1000000))}}],{%end%}null]
},
{% end %}
{% else %}
{% if compchannel %}
{% for key,value in band.items() %}
{% if key != 'total'%}
{
name: '{{key}}',
visible: true,
data: [{%for i in value %}[Date.parse("{{str(i['time'])[:-3]}}"),{{float('%.2f'%(i['bandbit']/1000000))}}],{%end%}]
},
{% end %}
{% end %}
{% else %}
{% for key,value in band.items() %}
{% if key == 'total'%}
{
type: 'area',
name: '带宽总和',
pointStart: Date.parse("{{str(value[0]['time'])}}"),
data: [{%for i in value %}[Date.parse("{{str(i['time'])[:-3]}}"),{{float('%.2f'%(i['bandbit']/1000000))}}],{%end%}]
},
{% end %}
{% end %}
{% end %}
{% end %}
]
});
});