Day11
今日份内容:使用Server-Sent Events制作基于Node.js的动态图形报表
Node.js是一种基于事件驱动、非阻塞时I/O模型,具有轻量、高效的特点,目前广泛得到Web前端开发的使用。在使用Node.js前,需要去官网下载安装包,过程比较简单,网上也有非常多的教程,这里就不多说啦。
Server-Sent Events
- 了解Server-Sent Events消息推送机制
- 熟悉MessageEvent、EventSource接口
- 通过服务端向客户端推送消息
- 在客户端使用SSE技术接收数据
- 使用jQuery自定义图形插件
内容
- 配置Node.js环境
- 项目初始化
- 安装Express框架
- 创建服务端路由文件
- 客户端实现
代码
// 该文件作为服务端的路由文件,在该文件中完成服务端路由功能
var express = require('express');
var app = express();
//配置静态资源的访问方式
app.use(express.static('public'));
app.get('/', function(req, res) {
res.type('text/html');
//推荐使用sendFile(),该方法需要使用文件的绝对路径
res.sendFile(__dirname + '/public/chartReport.html');
});
var clientId = 0;
var clients = {};
//记录用户连接请求
app.get('/events/', function(req, res) {
req.socket.setTimeout(Number.MAX_VALUE);
res.writeHead(200, {
'Content-type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
res.write('\n');
(function(clientId) {
//将连接保存到集合中
clients[clientId] = res;
//当连接关闭后,从连接集合中移除
req.on("close", function() {
delete clients[clientId]
});
})(++clientId);
});
//对所有客户端发送数据
setInterval(function() {
for (clientId in clients) {
clients[clientId].write('data:' + createRandomData() + '\n\n');
};
}, 500);
//生成随机数据,并以JSON字符串形式返回
function createRandomData() {
let drawData = [];
let items = ['女装', '男装', '童装', '运动', '内衣'];
for (let i = 0; i < 5; i++) {
let random = parseInt(Math.random() * 100);
let item = {
'name': items[i],
'amount': random
};
drawData.push(item);
}
return JSON.stringify({
'drawData': drawData
});
}
app.listen(process.env.PORT || 3000);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>图形报表统计</title>
<script type="text/javascript" src="js/jquery-1.x.js"></script>
<script src="js/jquert.chart.js" type="text/javascript" charset="utf-8"></script>
<link rel="stylesheet" type="text/css" href="css/layout.css" />
</head>
<body>
<div class="place"><span>位置:</span>
<ul class="placeul">
<li><a href="#">首页</a></li>
<li><a href="#">报表统计</a></li>
</ul>
</div>
<div class="canvasbody">
<div class="usual">
<canvas id="myCanvas1" width="300" height="300"
style="border: 1px solid #ccc;margin-right: 10px;"></canvas>
<canvas id="myCanvas2" width="300" height="300"
style="border: 1px solid #ccc;margin-right: 10px;"></canvas>
<canvas id="myCanvas3" width="300" height="300" style="border: 1px solid #ccc;"></canvas>
</div>
</div>
<script type="text/javascript">
//创建EventSource对象
var dataEventSource = new EventSource("/events/");
//设置onmessage事件处理方法,当接收到服务器发送的数据时调用该事件
dataEventSource.onmessage = function(event) {
//获得服务端返回的销售数据
var data = JSON.parse(event.data);
//更新图形报表
updateChart(data);
};
//设置onerror事件处理方法,当发生错误时调用该事件
dataEventSource.onerror = function(event) {
dataEventSource.close();
console.log(event);
}
//使用指定的数据更新图形报表
function updateChart(data) {
//预设图形报表中各部分颜色
var options = {
bgColor: [{
drawColor: "#9cc507"
},
{
drawColor: "#8b86ca"
},
{
drawColor: "#ff4400"
},
{
drawColor: "#ffb81d"
},
{
drawColor: "#00b3e3"
}
],
frontColor: {
font: "16px microsoft",
color: "black"
}
};
//绘制扇形图
$("#myCanvas1").drawChart(data, options, "PieChart");
//绘制柱状图
$("#myCanvas2").drawChart(data, options, "Columnar");
//绘制折线图
$("#myCanvas3").drawChart(data, options, "FoldLine");
}
</script>
</body>
</html>
// 自定义表格插件
// @author jCuckoo
;
(function($, window, document, undefined) {
var defaults = {
bgColor: [{
drawColor: "red"
},
{
drawColor: "green"
},
{
drawColor: "yellow"
},
{
drawColor: "blue"
},
{
drawColor: "gray"
}
],
frontColor: {
font: "12px 宋体",
color: "black"
}
};
//构造方法
function DataDrawer(element, data, options) {
this.$element = element;
this.drawType = data.drawType; //绘制类型
this.drawData = data.drawData; //绘制数据
this.setting = $.extend({}, defaults, options);
};
//添加属性或方法
DataDrawer.prototype = {
//绘制圆饼图
drawPieChart: function() {
var startPoint = 1.5 * Math.PI; //开始位置
var endPoint = 0;
var context = this.$element.get(0).getContext("2d");
this.clearCanvas(context);
for (var i = 0; i < this.drawData.length; i++) {
context.fillStyle = this.setting.bgColor[i].drawColor;
context.strokeStyle = this.setting.bgColor[i].drawColor;
//开始创建路径
context.beginPath();
//开始创建路径(圆心)
context.moveTo(150, 150);
//计算弧形结束位置的角度
endPoint = startPoint - Math.PI * 2 * (
this.drawData[i].amount / this.allData);
//开始创建路径 (弧形圆心)
context.arc(150, 150, 90, startPoint, endPoint, true);
context.fill();
context.stroke();
//保存状态
context.save();
//计算文本角度
var textAngle = (startPoint + endPoint) / 2;
//每部分所占比重
var textScale = this.drawData[i].amount / this.allData;
//将坐标原点移动到绘制文本处(根据圆心进行计算)
context.translate(150 + 110 * Math.cos(textAngle),
150 + 110 * Math.sin(textAngle));
//旋转文本
context.rotate(textAngle + Math.PI * 1 / 2);
context.fillStyle = this.setting.frontColor.color;
context.font = this.setting.frontColor.font;
context.fillText(this.drawData[i].name, -20, 0);
//恢复到保存点
context.restore();
startPoint -= Math.PI * 2 * (this.drawData[i].amount / this.allData);
}
},
drawColumnar: function() { //绘制柱状图标
var context = this.$element.get(0).getContext("2d");
this.clearCanvas(context);
//绘制坐标系
this.drawCoordinateSystem(context);
var width = 20;
var margin = 20;
for (var i = 0; i < this.drawData.length; i++) {
context.fillStyle = this.setting.bgColor[i].drawColor;
context.strokeStyle = this.setting.bgColor[i].drawColor;
//计算绘制的矩形的左上角的x,y坐标(以绘制的x坐标轴为参考基准)
var x = 40 + (width + margin) * i;
var y = 260 - 260 * (this.drawData[i].amount / this.allData);
//绘制圆柱
context.fillRect(x, y, width, 260 * (
this.drawData[i].amount / this.allData));
//绘制文本内容
context.fillStyle = this.setting.frontColor.color;
context.font = this.setting.frontColor.font;
context.fillText(this.drawData[i].name, x, 280);
}
},
drawFoldLine: function() { //绘制折线
var padding = 50;
var context = this.$element.get(0).getContext("2d");
this.clearCanvas(context);
//绘制坐标系
this.drawCoordinateSystem(context);
context.beginPath();
context.moveTo(20, 260);
for (var i = 0; i < this.drawData.length; i++) {
//计算折线点的坐标
var x = 40 + padding * i;
var y = 260 - 260 * (this.drawData[i].amount / this.allData);
context.setLineDash([5, 5]); //设置绘制线段的样式为虚线
context.lineTo(x, y);
context.stroke();
context.fillStyle = "gray";
context.fillRect(x, y, 1, 260 * (
this.drawData[i].amount / this.allData));
context.fillStyle = this.setting.frontColor.color;
context.font = this.setting.frontColor.font;
context.fillText(this.drawData[i].name, x - 10, 280);
}
},
drawCoordinateSystem: function(context) { //绘制坐标系
context.setLineDash([0, 0]);
context.beginPath();
context.moveTo(20, 20);
context.lineTo(20, 260);
context.lineTo(260, 260);
context.strokeStyle = "black";
context.lineWidth = 2;
context.stroke();
},
countData: function() { //统计数据的总量
var allData = 0;
for (var i = 0; i < this.drawData.length; i++) {
allData += this.drawData[i].amount;
}
this.allData = allData;
},
clearCanvas: function(context) {
var canvas = context.canvas;
context.fillStyle = "white";
context.fillRect(0, 0, canvas.width, canvas.height);
}
};
//在插件中使用DataList对象
$.fn.drawChart = function(data, options, drawType) {
//创建DataList的实体
var dataDrawer = new DataDrawer(this, data, options);
//调用其方法
dataDrawer.countData();
if ("PieChart" == drawType) {
return dataDrawer.drawPieChart();
} else if ("Columnar" == drawType) {
return dataDrawer.drawColumnar();
} else if ("FoldLine" == drawType) {
return dataDrawer.drawFoldLine();
}
}
})(jQuery, window, document);
结果
步骤详解
- 控制台使用npm init命令对项目初始化
- 设置package.json
- 控制台使用 npm install express --save-dev 命令来安装Express框架
- 创建app.js文件作为服务端路由文件,该文件中完成路由功能。
- 在根目录下创建public目录,用于存放静态资源,项目整体目录结构如图
- 在chartReport.html页面中完成客户端的实现
- 最后在控制台中使用 node app.js 命令启动Node.js服务器,在浏览器中输入 localhost:3000 网址进入测试。
PS
在编写完使用 node app.js 命令启动Node.js服务器过程中出现了这个错误:
这个报错也很友好,在上面提示了是标点符号使用错了(系统觉得你用了奇奇怪怪的标点),单引号之间应该使用逗号的,改完之后就可以正常运行啦!