demo.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
canvas {
border: 1px solid red;
}
</style>
</head>
<body>
<canvas id="cvs" width="500" height="500"></canvas>
<script>
var cvs = document.getElementById('cvs');
var ctx = cvs.getContext('2d');
ctx.lineWidth = 2;
// 坐标轴距离画布上右下左的边距
var padding = {
top: 20,
right: 20,
bottom: 20,
left: 20
}
// 坐标轴中箭头的宽和高
var arrow = {
width: 12,
height: 20
}
// 求坐标轴上顶点的坐标
var vertexTop = {
x: padding.left,
y: padding.top
}
// 求坐标轴原点的坐标
var origin = {
x: padding.left,
y: cvs.height - padding.bottom
}
// 求坐标轴右顶点的坐标
var vertexRight = {
x: cvs.width - padding.right,
y: cvs.height - padding.bottom
}
// 画坐标轴中的两条线
ctx.moveTo( vertexTop.x, vertexTop.y );
ctx.lineTo( origin.x, origin.y );
ctx.lineTo( vertexRight.x, vertexRight.y );
ctx.stroke();
// 画上顶点箭头
ctx.beginPath();
ctx.moveTo( vertexTop.x, vertexTop.y );
ctx.lineTo( vertexTop.x - arrow.width / 2, vertexTop.y + arrow.height );
ctx.lineTo( vertexTop.x, vertexTop.y + arrow.height / 2 );
ctx.lineTo( vertexTop.x + arrow.width / 2, vertexTop.y + arrow.height );
ctx.closePath();
ctx.fill();
// 画右顶点箭头
ctx.beginPath();
ctx.moveTo( vertexRight.x, vertexRight.y );
ctx.lineTo( vertexRight.x - arrow.height, vertexRight.y - arrow.width / 2 );
ctx.lineTo( vertexRight.x - arrow.height / 2, vertexRight.y );
ctx.lineTo( vertexRight.x - arrow.height, vertexRight.y + arrow.width / 2 );
ctx.closePath();
ctx.fill();
// 求坐标轴默认可显示数据的范围
coordMaxX = cvs.width - padding.left - padding.right - arrow.height;
coordMaxY = cvs.height - padding.top - padding.bottom - arrow.height;
var data = [ 100, 200, 400, 600, 1200, 1800, 1000, 500, 20 ];
// 求数据缩放的比例
var ratioX = coordMaxX / data.length;
var ratioY = coordMaxY / Math.max.apply( null, data );
// 根据比例,对元数据进行缩放
var ratioData = data.map( function( val, index ) {
return val * ratioY;
});
// 画点
ratioData.forEach( function( val, index ) {
ctx.fillRect( origin.x + ( index * ratioX) - 2, origin.y - val - 2, 4, 4 );
});
// 画折线
ctx.beginPath();
ratioData.forEach( function( val, index ) {
ctx.lineTo( origin.x + ( index * ratioX), origin.y - val );
});
ctx.stroke();
</script>
</body>
</html>
line.js(封装JS)(画折线图的外部JS):
/*
* constructor { line } 折线图构造函数
* param { ctx: Context } 绘图环境
* param { data: Array } 绘制折线图所需的数据
* param { padding: Object } 设置坐标轴到画布的边距
* param { arrow: Object } 设置箭头的宽高
* */
function Line( ctx, data, padding, arrow ) {
this.ctx = ctx;
this.data = data;
this.padding = padding || { top: 10, right: 10, bottom: 10, left: 10 };
this.arrow = arrow || { width: 10, height: 20 };
// 上顶点的坐标
this.vertexTop = {
x: this.padding.left,
y: this.padding.top
}
// 原点的坐标
this.origin = {
x: this.padding.left,
y: this.ctx.canvas.height - this.padding.bottom
}
// 右顶点的坐标
this.vertexRight = {
x: this.ctx.canvas.width - this.padding.right,
y: this.ctx.canvas.height - this.padding.bottom
}
// 计算坐标轴表示的最大刻度
this.coordWidth = this.ctx.canvas.width - this.padding.left - this.padding.right - this.arrow.height;
this.coordHeight = this.ctx.canvas.height - this.padding.top - this.padding.bottom - this.arrow.height;
}
// 给原型扩充方法
Line.prototype = {
constructor: Line,
draw: function() {
this.drawCoord();
this.drawArrow();
this.drawLine();
},
// 绘制坐标轴
drawCoord: function() {
this.ctx.beginPath();
this.ctx.moveTo( this.vertexTop.x, this.vertexTop.y );
this.ctx.lineTo( this.origin.x, this.origin.y );
this.ctx.lineTo( this.vertexRight.x, this.vertexRight.y );
this.ctx.stroke();
},
// 绘制建箭头
drawArrow: function() {
// 上箭头
this.ctx.beginPath();
this.ctx.moveTo( this.vertexTop.x, this.vertexTop.y );
this.ctx.lineTo( this.vertexTop.x - this.arrow.width / 2, this.vertexTop.y + this.arrow.height );
this.ctx.lineTo( this.vertexTop.x, this.vertexTop.y + this.arrow.height / 2 );
this.ctx.lineTo( this.vertexTop.x + this.arrow.width / 2, this.vertexTop.y + this.arrow.height );
this.ctx.closePath();
this.ctx.stroke();
// 右箭头
this.ctx.beginPath();
this.ctx.moveTo( this.vertexRight.x, this.vertexRight.y );
this.ctx.lineTo( this.vertexRight.x - this.arrow.height, this.vertexRight.y - this.arrow.width / 2 );
this.ctx.lineTo( this.vertexRight.x - this.arrow.height / 2, this.vertexRight.y );
this.ctx.lineTo( this.vertexRight.x - this.arrow.height, this.vertexRight.y + this.arrow.width / 2 );
this.ctx.closePath();
this.ctx.stroke();
},
// 根据数据绘制折线图
drawLine: function() {
// 先清除之前的路径
this.ctx.beginPath();
// 保存当前的this
var self = this;
/*
* 计算x和y轴坐标的缩放比值:
* ratioX = this.coordWidth / this.data.length
* ratioY = this.coordHeight / Math.max.apply( this.data )
* */
var ratioX = this.coordWidth / this.data.length,
ratioY = this.coordHeight / Math.max.apply( null, this.data );
/*
* 要根据原点的坐标来计算点的坐标
* x = self.origin.x + x
* y = self.origin.y - y
* */
// 遍历所有的数据,依次绘制点
this.data.forEach( function( y, x ) {
self.ctx.fillRect( self.origin.x + ( x * ratioX ) - 1, self.origin.y - ( y * ratioY ) - 1 , 2, 2 );
});
// 遍历所有的数据,依次绘制线
this.data.forEach( function( y, x ) {
self.ctx.lineTo( self.origin.x + ( x * ratioX ), self.origin.y - ( y * ratioY ) );
});
// 绘制线
this.ctx.stroke();
}
}
demo.html(利用line.js画折线图):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
canvas {
border: 1px solid red;
}
</style>
</head>
<body>
<canvas id="cvs" width="500" height="500"></canvas>
<script src="line.js"></script>
<script>
var cvs = document.getElementById('cvs');
var ctx = cvs.getContext('2d');
var line = new Line( ctx, [ 10, 30, 50, 430, 200, 50, 1500, 2000, 600 ] );
line.draw();
</script>
</body>
</html>