【文章摘要】
最近用开源的AdminLTE做框架感觉效果特别好,其针对图表库Morris.js和flot都提供了不错的支持,也都提供了这两者的例子。不过Morris.js是基于Raphael.js来的,也就是其使用SVG和VML来绘制图形,而flot则是使用Canvas进行绘制,在绘制效率和浏览器兼容性等方面会有出入,同时两者需要的数据格式也不相同。本文中对两者的使用和性能进行了比较。
【文章索引】
【一、Morris.js的使用】
Morris.js最新版本是0.5.1,使用BSD协议,可以从官方网站 http://morrisjs.github.io/morris.js/ 或 GitHub https://github.com/morrisjs/morris.js 下载。Morris.js使用非常简单,其仅提供了折线图、面积图、柱形图和饼图四种类型的图表,不过也基本满足大部分需求。Morris.js基于Raphael,使用的是SVG或VML绘制图表,直接支持IE 6+、FF 3+、Chrome 5+、Safari 3+和Opera 9.5+,不过在图表内容比较多时效率比较低,这个之后再分析。除此之外Morris.js还需要依赖jQuery。
其数据格式要求如下:
bathroomData = [
{ time: "2014-06-17T10:54:01", r2: 5 },
{ time: "2014-06-17T11:09:01", r0: 4, r1: 79, r2: 7, r3: 79 },
{ time: "2014-06-17T11:24:01", r0: 11, r1: 88, r2: 13, r3: 100 },
{ time: "2014-06-17T11:39:01", r0: 11, r1: 69, r2: 12, r3: 73 },
...
]
即按X轴对数据进行分类,X轴数据(如上的time)相同的所有系列(Series)的数据(如上的r0、r1、r2、r3)均放置于在同一个JS对象内,当该系列在该X轴数据上没有数据时留空(如上第一个数据没有r0、r1、r3)。之后需要指定X轴和Y轴的数据项,X轴只能指定一个数据项,而Y轴可以指定多个。除此之外,Morris.js自带日期型字符串的支持,直接使用即可。例如这里指定time为X轴的数据,r0、r1、r2、r3为Y轴数据,默认效果如下:
其中完整代码如下:
<!DOCTYPE html>
<html lang="zh-cn">
<body>
<div id="bathroom-chart"></div> <script type="text/javascript" src="jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="raphael-2.1.2.min.js"></script>
<script type="text/javascript" src="morris-0.5.1.min.js"></script>
<script type="text/javascript">
var bathroomData = [
{ time: "2014-06-17T10:54:01", r2: 5 },
{ time: "2014-06-17T11:09:01", r0: 4, r1: 79, r2: 7, r3: 79 },
{ time: "2014-06-17T11:24:01", r0: 11, r1: 88, r2: 13, r3: 100 },
{ time: "2014-06-17T11:39:01", r0: 11, r1: 69, r2: 12, r3: 73 }
];
var bathroomIDs = [ "r0", "r1", "r2", "r3" ];
var bathroomNames = [ "校本部学生浴室(男)", "校本部学生浴室(女)", "XX校区学生浴室(男)", "XX校区学生浴室(女)" ]; Morris.Line({
element: "bathroom-chart",
data: bathroomData,
xkey: "time",
ykeys: bathroomIDs,
labels: bathroomNames
});
</script>
</body>
</html>
其中element为绘制到指定元素的元素ID,data为数据源,xKey为X轴的数据项名称,yKeys为Y轴的数据项名称数组,labels为每个系列的名称数组,与yKeys的顺序对应。这样就可以实现基本的图表,而且其自带鼠标悬停的效果,即鼠标在图上悬停时,图表下方会有各个系列当前选中的值。
除此之外还可以对每个系列的颜色进行设置(lineColors属性设置数组,与yKeys顺序对应)、线条粗细(lineWidth)、圆圈大小(pointSize)、Y轴最大值、最小值(ymax、ymin)、X轴数据间隔类型(xLabels)等等进行设置。此外还可以通过自定义函数来自定义X轴和Y轴的数据格式,或者自定义鼠标悬停时显示的内容。
稍加修改可以调整为更好看的效果,如下所示:
完整代码如下:
<!DOCTYPE html>
<html lang="zh-cn">
<body>
<div id="bathroom-chart"></div> <script type="text/javascript" src="jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="raphael-2.1.2.min.js"></script>
<script type="text/javascript" src="morris-0.5.1.min.js"></script>
<script type="text/javascript">
var bathroomData = [
{ time: "2014-06-17T10:54:01", r2: 5 },
{ time: "2014-06-17T11:09:01", r0: 4, r1: 79, r2: 7, r3: 79 },
{ time: "2014-06-17T11:24:01", r0: 11, r1: 88, r2: 13, r3: 100 },
{ time: "2014-06-17T11:39:01", r0: 11, r1: 69, r2: 12, r3: 73 }
];
var bathroomIDs = [ "r0", "r1", "r2", "r3" ];
var bathroomNames = [ "校本部学生浴室(男)", "校本部学生浴室(女)", "XX校区学生浴室(男)", "XX校区学生浴室(女)" ];
var bathroomMaxs = [ 100, 100, 113, 137 ];
var bathroomMax = 137;
var bathroomColors = [ "#5B9BD5", "#ED7D31", "#A5A5A5", "#FFC000" ]; Morris.Line({
element: "bathroom-chart",
data: bathroomData,
xkey: "time",
ykeys: bathroomIDs,
labels: bathroomNames,
lineColors: bathroomColors,
lineWidth: 3,
pointSize: 4,
ymax: bathroomMax,
ymin: 0,
hoverCallback: function (index, options, content) {
var row = options.data[index];
var result = '<div class="morris-hover-row-label">' + row.time.replace("T", " ") + '</div>'; for (var i = 0; i < bathroomNames.length; i++) {
result += '<div class="morris-hover-point" style="color: ' + bathroomColors[i] + '">' +
bathroomNames[i] + " : " + (row["r" + i] == undefined ? "-" : row["r" + i]) + " / " + bathroomMaxs[i] + "</div>";
} return result;
},
xLabels: "5min",
yLabelFormat: function (y) { return parseInt(y).toString(); }
});
</script>
</body>
</html>
【二、flot的使用】
flot也是非常常见的图表库,最新版本是0.8.3,采用MIT协议,可以从官方网站 http://www.flotcharts.org/ 或 GitHub https://github.com/flot/flot 下载。flot使用也很简单,其支持在一个图表中同时叠加折线图、面积图、柱形图/条形图等不同的形式,而且其支持扩展插件,通过扩展插件还可以支持堆积柱形图和饼图等以及支持缩放和选区等功能。flot使用Canvas绘制图表,在借助excanvas的情况下,可以支持IE 6+、FF 2+、Chrome、Safari 3+和Opera 9.5+。
还是以上面的数据为例,flot的数据要求与Morris.js不同,对于上述的数据,flot要求如下:
bathroomData = [
{ data: [ [1402974541000, 4], [1402975441000, 11], [1402976341000, 11], ... ] },
{ data: [ [1402974541000, 79], [1402975441000, 88], [1402976341000, 69], ... ] },
{ data: [ [1402973641000, 5], [1402974541000, 7], [1402975441000, 13], [1402976341000, 12], ... ] },
{ data: [ [1402974541000, 79], [1402975441000, 100], [1402976341000, 73], ... ] }
]
可以看到,与Morris.js不同主要有两点,第一点是flot不支持日期型字符串的使用,需要转换为JavaScript的Date对象,然后使用getTime获取时间(距1970年1月1日 UTC时区的毫秒数)才可。第二点是Morris以X轴对数据进行分类,而flot则以系列(Series)对数据进行分类,每一个系列为一个JS对象,包括一个名为data的数组,其中该数组包含该系列的所有数据,每一个数组又使用一个二维数组表示,其中第一维表示X轴的数据,第二维为Y轴的数据。默认效果如下:
完整代码如下:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<style>
.chart-container {
height: 330px;
}
.bathroom-chart {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="chart-container">
<div id="bathroom-chart" class="bathroom-chart"></div>
</div> <script type="text/javascript" src="jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="jquery.flot-0.8.3.min.js"></script>
<script type="text/javascript" src="jquery.flot-0.8.3.time.min.js"></script>
<script type="text/javascript">
var bathroomData = [
{ data: [ [1402974541000, 4], [1402975441000, 11], [1402976341000, 11] ], label: "校本部学生浴室(男)" },
{ data: [ [1402974541000, 79], [1402975441000, 88], [1402976341000, 69] ], label: "校本部学生浴室(女)" },
{ data: [ [1402973641000, 5], [1402974541000, 7], [1402975441000, 13], [1402976341000, 12] ], label: "XX校区学生浴室(男)" },
{ data: [ [1402974541000, 79], [1402975441000, 100], [1402976341000, 73] ], label: "XX校区学生浴室(女)" }
]; $.plot("#bathroom-chart", bathroomData, {
xaxis: { mode: "time", timezone: "browser" }
});
</script>
</body>
</html>
其中数据里每个系列还支持设置label参数,即设置这个系列的名称。与Morris.js不同的是,对于日期类型需要手动设置轴的类型,例如这里需要设置X轴的类型为time(设置为time类型时需要添加jquery.flot.time.js这个插件),除了设置类型为时间外,还需要设置时区,如果服务器、客户端的时区均为同一个,那么可以将时区设置为“browser”(与浏览器相同)。
除此之外,flot默认显示图表的边框,可以通过grid参数里的borderWidth为0取消;flot默认的图表margin也过小,可以设置grid的marigin和labelMargin;还可以在series参数里对lines(折线)、points(点)以及bars(柱形)进行设置,每一项均可同时显示或隐藏,具体设置方法可以参考文档,不再赘述。此外,Morris.js的图例默认都在图表内,可以设置legend的container来将图例设置在其他的地方。另外,由于flot没有默认的鼠标悬停效果,所以需要自己手动去定义,flot提供了一个叫“plothover”和“plotclick”的事件,可以绑定这两个事件实现悬停和点击的效果,其中事件处理函数包括三个参数,分别是event、pos以及item,可以通过item.seriesIndex获取系列的索引,或者直接通过item.series.label直接获取系列的名称,或者通过item.datapoint[0]或[1]获取当前X轴或Y轴的数据等等。最终实现效果如下:
完整代码如下:
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<style>
.chart-container {
height: 330px;
}
.chart-tooltip {
position: absolute;
display: none;
padding: 5px;
max-width: 200px;
font-size: 12px;
text-align: center;
color: #fff;
background-color: #000;
border-radius: 4px;
opacity: 0.8;
}
.bathroom-chart {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="chart-container">
<div id="bathroom-chart" class="bathroom-chart"></div>
</div>
<div id="bathroom-chart-tooltip" class="chart-tooltip"></div>
<div id="bathroom-legend"></div> <script type="text/javascript" src="jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="jquery.flot-0.8.3.min.js"></script>
<script type="text/javascript" src="jquery.flot-0.8.3.time.min.js"></script>
<script type="text/javascript">
var bathroomData = [
{ data: [ [1402974541000, 4], [1402975441000, 11], [1402976341000, 11] ], label: "校本部学生浴室(男)" },
{ data: [ [1402974541000, 79], [1402975441000, 88], [1402976341000, 69] ], label: "校本部学生浴室(女)" },
{ data: [ [1402973641000, 5], [1402974541000, 7], [1402975441000, 13], [1402976341000, 12] ], label: "XX校区学生浴室(男)" },
{ data: [ [1402974541000, 79], [1402975441000, 100], [1402976341000, 73] ], label: "XX校区学生浴室(女)" }
];
var bathroomMaxs = [ 100, 100, 113, 137 ];
var bathroomMax = 137;
var bathroomColors = [ "#5B9BD5", "#ED7D31", "#A5A5A5", "#FFC000" ]; $.plot("#bathroom-chart", bathroomData, {
xaxis: { mode: "time", timezone: "browser", timeformat: "%H:%S", tickLength: 0 },
yaxis: { min: 0, max: bathroomMax, color: "#EAEAEA" },
legend: { container: "#bathroom-legend" },
colors: bathroomColors,
series: {
lines: { show: true, lineWidth: 3, fillColor: bathroomColors },
points: { show: true } },
grid: { hoverable: true, borderWidth: 0, margin: 10, labelMargin: 10 }
}); $("#bathroom-chart").bind("plothover", function(event, pos, item) {
if (item) {
$("#bathroom-chart-tooltip").html(item.series.label + " : " + item.datapoint[1] + " / " + bathroomMaxs[item.seriesIndex])
.css({ top: item.pageY + 8, left: item.pageX + 8 })
.fadeIn(200);
} else {
$("#bathroom-chart-tooltip").hide();
}
});
</script>
</body>
</html>
【三、性能比较】
性能测试当然不是用上述的这么稀少的数据了,实际的数据是从上午11点到晚上11点这12个小时每5分钟一次的数据,显示出现大约如下图,其实也没多少。
性能测试采用在网页开始时记录当前时间,所有代码均执行完后求当前时间与开始时间的差,其中测试代码均使用上述提到的完整代码(即一种默认样式、一种自定义样式),测试平台选用我手头的几个设备Lenovo Thinkpad T420(i5-2520 2.5Ghz/8GB/Win8.1 x64)、一台服务器虚拟机(E5-4650 2.7Ghz/4GB/Server 2003 x64)、Dell Venue 8 Pro(Atom 3740D 1.33Ghz/2GB/Win8.1 x86)、小米1青春版(Qualcomm MSM8260 1.2Ghz/768MB/MIUI on Android 4.1.2)、小米2S(Snapdragon 600 1.7Ghz/2GB/MIUI on Android 4.1.1),分辨率都是设备默认的全屏分辨率,其中服务器上使用IE8,使用excanvas实现Canvas效果。测试结果如下:
看来SVG相对Canvas还真不是一般的慢呢。
附,原始测试结果:
Morris 0.5.1 基本样式 | |||||||
---|---|---|---|---|---|---|---|
设备类型 | 浏览器 | 第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 平均 |
Thinkpad T420 | IE11 桌面版 | 656 | 621 | 744 | 608 | 607 | 647.2 |
Thinkpad T420 | 搜狗浏览器(Webkit 537.36) | 361 | 339 | 334 | 320 | 332 | 337.2 |
服务器虚拟机 | IE8 | 2343 | 2421 | 2375 | 2391 | 2390 | 2384 |
Dell Venue 8 Pro | IE11 Metro版 | 1966 | 2151 | 2057 | 1978 | 2117 | 2053.8 |
小米1青春版 | UC 9.7国际版(Webkit 533.1) | 4407 | 4335 | 4233 | 4309 | 4442 | 4345.2 |
小米2S | UC 9.8(Webkit 533.1) | 2892 | 2536 | 2481 | 2509 | 2558 | 2595.2 |
Morris 0.5.1 自定义样式 | |||||||
---|---|---|---|---|---|---|---|
设备类型 | 浏览器 | 第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 平均 |
Thinkpad T420 | IE11 桌面版 | 1196 | 1139 | 1206 | 1113 | 1189 | 1168.6 |
Thinkpad T420 | 搜狗浏览器(Webkit 537.36) | 602 | 585 | 631 | 632 | 620 | 614 |
服务器虚拟机 | IE8 | 4468 | 4735 | 4797 | 4750 | 4703 | 4690.6 |
Dell Venue 8 Pro | IE11 Metro版 | 3897 | 4039 | 4207 | 4165 | 4380 | 4137.6 |
小米1青春版 | UC 9.7国际版(Webkit 533.1) | 8246 | 8472 | 8765 | 9003 | 8517 | 8600.6 |
小米2S | UC 9.8(Webkit 533.1) | 5219 | 4920 | 5674 | 4727 | 5152 | 5138.4 |
Flot 0.8.1 基本样式 | |||||||
---|---|---|---|---|---|---|---|
设备类型 | 浏览器 | 第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 平均 |
Thinkpad T420 | IE11 桌面版 | 67 | 52 | 48 | 52 | 50 | 53.8 |
Thinkpad T420 | 搜狗浏览器(Webkit 537.36) | 63 | 34 | 32 | 38 | 42 | 41.8 |
服务器虚拟机 | IE8 | 94 | 110 | 94 | 109 | 109 | 103.2 |
Dell Venue 8 Pro | IE11 Metro版 | 131 | 147 | 146 | 168 | 159 | 150.2 |
小米1青春版 | UC 9.7国际版(Webkit 533.1) | 351 | 471 | 379 | 393 | 409 | 400.6 |
小米2S | UC 9.8(Webkit 533.1) | 349 | 260 | 201 | 221 | 195 | 245.2 |
Flot 0.8.1 自定义样式 | |||||||
---|---|---|---|---|---|---|---|
设备类型 | 浏览器 | 第一次 | 第二次 | 第三次 | 第四次 | 第五次 | 平均 |
Thinkpad T420 | IE11 桌面版 | 87 | 89 | 87 | 78 | 83 | 84.8 |
Thinkpad T420 | 搜狗浏览器(Webkit 537.36) | 40 | 38 | 40 | 44 | 44 | 41.2 |
服务器虚拟机 | IE8 | 1234 | 1219 | 1282 | 1234 | 1235 | 1240.8 |
Dell Venue 8 Pro | IE11 Metro版 | 256 | 290 | 269 | 352 | 312 | 295.8 |
小米1青春版 | UC 9.7国际版(Webkit 533.1) | 719 | 600 | 775 | 749 | 622 | 693 |
小米2S | UC 9.8(Webkit 533.1) | 357 | 273 | 281 | 335 | 340 | 317.2 |
【相关链接】