Canvas 根据比例画折线图

本文演示如何在HTML中利用Canvas API和line.js库,根据指定比例绘制出精准的折线图。通过这个Demo,开发者可以了解Canvas绘图的基础以及如何封装JS来实现动态图表生成。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


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>


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值