canvas实现折线图

这篇博客介绍了如何使用HTML5的canvas元素来实现折线图的绘制。主要分为两种情况,一是处理少于7个点的数据,二是处理大于等于7个点的数据。关键在于数据处理、极点确定、点的位置计算以及画布初始化、描线、标注和标点等步骤。博客提供了完整的代码实现。

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

效果图如下:
这里写图片描述这里写图片描述

一.思路

折线的实现可以分为两种情况:
1.少于七个点
对于少于7个点的情况,主要是描点,画线,标注三部分
2.大于等于七个点
对于大于等于七个点的情况,主要是画线,标注极点

二.实现要点

最重要的是每个点位置的确定

<1>对数据进行处理
首先对数据进行处理
A.将空数组填充一定的内容

var noData= chartEntity.originArr.length==0?true:false;
//小于7的时候是7天数据,补全后面数据
var originArr2=[];
if(noData){
    originArr2=[50,50,50,50,50,50,50];
}else{
    originArr2=chartEntity.originArr.concat();
}

B.将少于7个点的数据补充为7个点,补充值为原数组最后一个数据

//补充数据
while(originArr2.length < 7){
    originArr2.push(chartEntity.originArr[chartEntity.originArr.length-1]);
}

<2>极点的确定
因为是以最低点,最高点定边界,所以首先要先确定极点的值以及极点之间的差值

var canScale=window.devicePixelRatio;
//获得数组最大值
var maxWeight= chartEntity.originArr.length == 0 ? originArr2[0]:Math.max.apply(null, chartEntity.originArr);
//获得数组最大值的下标
var maxWeightIndex=chartEntity.originArr.lastIndexOf(maxWeight);
//获得数组最小值
var minWeight= chartEntity.originArr.length == 0 ? originArr2[0]:Math.min.apply(null, chartEntity.originArr);
//获得数组最小值的下标
var minWeightIndex=chartEntity.originArr.lastIndexOf(minWeight);
//获得最大值最小值差值
var weightDef= maxWeight-minWeight;

<3>各个点位置的确定

//最大值最小值一样
if(weightDef == 0){
    weightDef = 1;
    dateIsSame=true;
}

//初始坐标数据处理
for(var i=0;i<originArr2.length;i++){
    hPercentArr[i] = (maxWeight-originArr2[i])/weightDef*chartEntity.waveHeight*canScale+minHeight;
    wPercentArr[i] = dateArr[i]*($("#"+chartEntity.chartID).width()-15)*canScale+15;
    console.info(hPercentArr[i]+"||"+wPercentArr[i]);
}
var diffWidth=(wPercentArr[1]-wPercentArr[0])/canScale;

<4>初始化画布

//画布初始化
var canvas=document.getElementById(chartEntity.chartID);
var context=canvas.getContext("2d");
canvas.width=$("#"+chartEntity.chartID).width()*canScale;
canvas.height=$("#"+chartEntity.chartID).height()*canScale;

<5>描线,标注

//画线
for(var i=0;i<originArr2.length;i++){
    if(dateIsSame){
        $("#"+chartEntity.chartID).css("marginTop","1.75rem");
    }
    context.lineTo(wPercentArr[i],hPercentArr[i]);
    if(i < chartEntity.originArr.length){
        if(dateIsSame){
            $("#"+chartEntity.chartNumID).append("<div class='weightNum-item' style='position:absolute;top:"+(hPercentArr[i]/canScale-fontSize+0.75*fontSize)+"px;left:"+(wPercentArr[i]/canScale-13)+"px;'>"+chartEntity.originArr[i]+"</div>");
        }else{
            $("#"+chartEntity.chartNumID).append("<div class='weightNum-item' style='position:absolute;top:"+(hPercentArr[i]/canScale-1.5*fontSize+0.75*fontSize)+"px;left:"+(wPercentArr[i]/canScale-16)+"px;'>"+chartEntity.originArr[i]+"</div>");
        }
    }
}
if(chartEntity.originArr.length < 7){
    context.strokeStyle = chartEntity.lineStrokeColor2;
}else{
    context.strokeStyle = chartEntity.lineStrokeColor;
}
context.lineWidth = 1*canScale;
//绘制路径
context.stroke();

<6>标点

//描点
for(var i=0;i<originArr2.length;i++){
    context.lineWidth=1*canScale;
    context.beginPath();
    context.arc(wPercentArr[i],hPercentArr[i],4*canScale,0,2*Math.PI);
    context.closePath();
    context.fillStyle= chartEntity.dotFillColor1;
    context.fill();

    context.beginPath();
    context.arc(wPercentArr[i],hPercentArr[i],2*canScale,0,2*Math.PI);
    context.closePath();


    if(i < chartEntity.originArr.length){
        context.fillStyle= chartEntity.dotFillColor2;
    }else{
        context.fillStyle= chartEntity.dotFillColor3;
    }
    context.fill();
}

三.完整代码

<!DOCTYPE html>
<html>
    <head>
    <title>canvas实现折线图</title>
    <meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
    <!-- 引入 ECharts 文件 -->
    <script src="js/echarts.js"></script>
    <script src="js/jquery-3.0.0.min.js"></script>

</head>
<style type="text/css">
.chartBottom {
    margin: 1rem 1.25rem;
    margin-bottom: 0;
    height: 6.25rem;
    position: relative;
    /* border: 1px solid #1daffa; */
    border: 1px solid rgba(29, 175, 250, 0.5);
    border-top: none;
    background-color: #fff;
    border-right: none;
}
.line {
    width: 80%;
    margin-left: 10%;
    margin-top: .75rem;
}
.lineNum {
    position: absolute;
    top: 0rem;
    left: 0;
    width: 88%;
    margin-left: 12%;
    font-size: .5625rem;
    color: #1daffa;
}
</style>
    <body>
        <div class="row chartBottom weightContent">
            <canvas class="line" id="weight" width="100px" height="100px"></canvas>
            <div id="weightNum" class="lineNum"></div>
        </div>
    </body>
    <script type="text/javascript">
    //初始化echarts实例
    /*画图区域--开始*/
        $(".line").css("height",$(window).width()/750*120);
        $(".lineNum").css("height",$(window).width()/750*158);
        /*$(".chartBottom").css("width","100%");*/
        $("#weight").css("width",$(".chartBottom").width()*0.8);
        $("#fat").css("width",$(".chartBottom").width()*0.8);
        $("#waist").css("width",$(".chartBottom").width()*0.8);
        var weight=[55.4,55.9,53];
        //var weight=[55.4,55.9,53,56,57,58,56.7,53,55,54,53];
        //体重折线图
        var weightEntity={
            "originArr":weight,             //初始数组       
            "chartID":"weight",                 //折线canvas的id名称
            "chartNumID":"weightNum",           //折线点的区域的名称
            "lineFillColor":"#c1e9fb",          //折线填充颜色
            "lineStrokeColor":"#1daffa",        //折线颜色
            "lineStrokeColor2":"#d2effe",       //折线颜色(数据时不满足7个时)
            "dotFillColor1":"#d2effe",          //折线点的外圈颜色
            "dotFillColor2":"#1daffa",          //折线点的内圈颜色
            "dotFillColor3":"#fff",             //折线点的内圈颜色(没有数据时)
            "waveHeight":(2*parseFloat($("html").css("font-size")))
        };
        getLineChart(weightEntity);


function getLineChart(chartEntity){
    $("#"+chartEntity.chartNumID).empty();
    console.info(chartEntity);
    var fontSize=parseFloat($("html").css("font-size"));
    console.info(fontSize);
    var dateIsSame=false;
    var noData= chartEntity.originArr.length==0?true:false;
    var hPercentArr=[];
    var wPercentArr=[];
    //小于7的时候是7天数据,补全后面数据
    var originArr2=[];
    if(noData){
        originArr2=[50,50,50,50,50,50,50];
    }else{
        originArr2=chartEntity.originArr.concat();
    }

    //补充数据
    while(originArr2.length < 7){
        originArr2.push(chartEntity.originArr[chartEntity.originArr.length-1]);
    }
    console.info(originArr2);
    var canScale=window.devicePixelRatio;
    //获得数组最大值
    var maxWeight= chartEntity.originArr.length == 0 ? originArr2[0]:Math.max.apply(null, chartEntity.originArr);
    //获得数组最大值的下标
    var maxWeightIndex=chartEntity.originArr.lastIndexOf(maxWeight);
    //获得数组最小值
    var minWeight= chartEntity.originArr.length == 0 ? originArr2[0]:Math.min.apply(null, chartEntity.originArr);
    //获得数组最小值的下标
    var minWeightIndex=chartEntity.originArr.lastIndexOf(minWeight);
    //获得最大值最小值差值
    var weightDef= maxWeight-minWeight;
    console.info(maxWeight+"||"+minWeight);

    var minHeight=2*fontSize;


    //获取每天间隔
    var dateArr=[];
    var date=0;
    var dateLength=0;
    if(chartEntity.originArr.length > 7){
        dateLength=chartEntity.originArr.length;
    }else{
        dateLength=7;
    }
    for(var i=0;i<dateLength;i++){
        if(i == 0){
            dateArr.push(0);
        }else{
            date+=1/(dateLength-1);
            dateArr.push(date);
        }

    }
    console.info(dateArr);
    console.info(chartEntity.waveHeight);

    //最大值最小值一样
    if(weightDef == 0){
        weightDef = 1;
        dateIsSame=true;
    }

    console.info("--------"+$("#"+chartEntity.chartID).width());

    //初始坐标数据处理
    for(var i=0;i<originArr2.length;i++){
        hPercentArr[i] = (maxWeight-originArr2[i])/weightDef*chartEntity.waveHeight*canScale+minHeight;
        wPercentArr[i] = dateArr[i]*($("#"+chartEntity.chartID).width()-15)*canScale+15;
        console.info(hPercentArr[i]+"||"+wPercentArr[i]);
    }
    var diffWidth=(wPercentArr[1]-wPercentArr[0])/canScale;


    //画布初始化
    var canvas=document.getElementById(chartEntity.chartID);
    var context=canvas.getContext("2d");
    canvas.width=$("#"+chartEntity.chartID).width()*canScale;
    canvas.height=$("#"+chartEntity.chartID).height()*canScale;

    //小于7个点
    if(chartEntity.originArr.length <= 7){
        console.info("originArr2.length"+originArr2.length);
        //画线
        for(var i=0;i<originArr2.length;i++){
            if(dateIsSame){
                $("#"+chartEntity.chartID).css("marginTop","1.75rem");
            }
            context.lineTo(wPercentArr[i],hPercentArr[i]);
            if(i < chartEntity.originArr.length){
                if(dateIsSame){
                    $("#"+chartEntity.chartNumID).append("<div class='weightNum-item' style='position:absolute;top:"+(hPercentArr[i]/canScale-fontSize+0.75*fontSize)+"px;left:"+(wPercentArr[i]/canScale-13)+"px;'>"+chartEntity.originArr[i]+"</div>");
                }else{
                    $("#"+chartEntity.chartNumID).append("<div class='weightNum-item' style='position:absolute;top:"+(hPercentArr[i]/canScale-1.5*fontSize+0.75*fontSize)+"px;left:"+(wPercentArr[i]/canScale-16)+"px;'>"+chartEntity.originArr[i]+"</div>");
                }
            }
        }
        //alert($("#weightNum").children("div:eq("+maxWeightIndex+")").attr("style")+"("+maxWeightIndex+")");
        if(chartEntity.originArr.length < 7){
            context.strokeStyle = chartEntity.lineStrokeColor2;
        }else{
            context.strokeStyle = chartEntity.lineStrokeColor;
        }
        context.lineWidth = 1*canScale;
        //绘制路径
        context.stroke();
        //描点
        for(var i=0;i<originArr2.length;i++){
            context.lineWidth=1*canScale;
            context.beginPath();
            context.arc(wPercentArr[i],hPercentArr[i],4*canScale,0,2*Math.PI);
            context.closePath();
            context.fillStyle= chartEntity.dotFillColor1;
            context.fill();

            context.beginPath();
            context.arc(wPercentArr[i],hPercentArr[i],2*canScale,0,2*Math.PI);
            context.closePath();


            if(i < chartEntity.originArr.length){
                context.fillStyle= chartEntity.dotFillColor2;
            }else{
                context.fillStyle= chartEntity.dotFillColor3;
            }
            context.fill();
        }

    }else{
        for(var i=0;i<chartEntity.originArr.length;i++){
            context.lineTo(wPercentArr[i],hPercentArr[i]);
            var hNumPosition=0;
            //用户数据不变
            if(dateIsSame){
                $("#"+chartEntity.chartID).css("marginTop","1.75rem");
                if(i == chartEntity.originArr.length-1){
                    $("#"+chartEntity.chartNumID).append("<div class='weightNum-item' style='position:absolute;top:"+(hPercentArr[i]/canScale-fontSize+0.75*fontSize)+"px;left:"+(wPercentArr[i]/canScale-10)+"px;'>"+chartEntity.originArr[i]+"</div>");
                }
            }else{
            //用户数据变化
                hNumPosition=hPercentArr[i]/canScale;
                if(i == maxWeightIndex){
                //最高点
                    if((chartEntity.originArr.length-1-maxWeightIndex)*diffWidth <= 24){
                        $("#"+chartEntity.chartNumID).append("<div class='weightNum-item' style='position:absolute;top:"+(hNumPosition-1.5*fontSize+0.75*fontSize)+"px;left:"+(wPercentArr[chartEntity.originArr.length-1]/canScale-10-24)+"px;'>"+chartEntity.originArr[i]+"</div>");
                    }else{
                        $("#"+chartEntity.chartNumID).append("<div class='weightNum-item' style='position:absolute;top:"+(hNumPosition-1.5*fontSize+0.75*fontSize)+"px;left:"+(wPercentArr[i]/canScale-10)+"px;'>"+chartEntity.originArr[i]+"</div>");
                    }
                }else if(i == minWeightIndex){
                    //最低点
                    $("#"+chartEntity.chartNumID).append("<div class='weightNum-item' style='position:absolute;top:"+(hNumPosition+0.5*fontSize+0.75*fontSize)+"px;left:"+(wPercentArr[i]/canScale-16)+"px;'>"+chartEntity.originArr[i]+"</div>");
                }
                //最后一点
                if(i == chartEntity.originArr.length-1 && minWeight != chartEntity.originArr[chartEntity.originArr.length-1] && maxWeight != chartEntity.originArr[chartEntity.originArr.length-1]){
                    //当前一个点比最后一个点高,这时候数据应该展示在折线下方
                    if(hPercentArr[i] > hPercentArr[i-1]){
                        $("#"+chartEntity.chartNumID).append("<div class='weightNum-item' style='position:absolute;top:"+(hNumPosition+0.5*fontSize+0.75*fontSize)+"px;left:"+(wPercentArr[i]/canScale-10)+"px;'>"+chartEntity.originArr[i]+"</div>");
                    }else{
                        $("#"+chartEntity.chartNumID).append("<div class='weightNum-item' style='position:absolute;top:"+(hNumPosition-1.5*fontSize+0.75*fontSize)+"px;left:"+(wPercentArr[i]/canScale-10)+"px;'>"+chartEntity.originArr[i]+"</div>");
                    }
                }
            }           
        }
        //alert($("#weightNum").children("div:eq("+maxWeightIndex+")").attr("style")+"("+maxWeightIndex+")");
        console.info(chartEntity.lineStrokeColor);
        context.strokeStyle = chartEntity.lineStrokeColor;
        context.lineWidth = 1*canScale;
        context.stroke();
    }

}
    </script>
</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值