首頁 web前端 H5教程 H5的canvas圖表實現長條圖

H5的canvas圖表實現長條圖

Mar 27, 2018 pm 02:27 PM
canvas html5

這次帶給大家H5的canvas圖表實現長條圖,canvas圖表實現長條圖的注意事項有哪些,下面就是實戰案例,一起來看一下。

前幾天用到了圖表庫,其中百度的ECharts,感覺做得最好,看它預設用的是canvas,canvas圖表在處理大數據方面比svg要好。那我也用canvas來實現一個圖表庫吧,感覺不會太難,先實現個簡單的長條圖。

效果如下:

#主要功能點包含:

  1. #文字的繪製

  2. #XY軸的繪製;

  3. 資料分組繪製;

資料動畫的實作;

滑鼠事件的處理。

使用方式

    首先我們看一下使用方式,參考了部分ECharts的使用方式,先傳入要顯示圖表的
  1. html標籤

    ,接著呼叫init,初始化的同時傳入資料。

    var con=document.getElementById('container');
        var chart=new Bar(con);
        chart.init({
            title:'全年降雨量柱状图',
            xAxis:{// x轴
                data:['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月']
            },
            yAxis:{//y轴
                name:'水量',
                formatter:'{value} ml'
            },
            series:[//分组数据
                {
                    name:'东部降水量',
                    data:[62,20,17,45,100,56,19,38,50,120,56,130]
                },
                {
                    name:'西部降水量',
                    data:[52,10,17,25,60,39,19,48,70,30,56,8]
                },
                {
                    name:'南部降水量',
                    data:[12,10,17,25,27,39,50,38,100,30,56,90]
                },
                {
                    color:'hsla(270,80%,60%,1)',
                    name:'北部降水量',
                    data:[12,30,17,25,7,39,49,38,60,30,56,10]
                }
            ]
        });
    登入後複製
  2. 圖表基類,我們後面還要寫圓餅圖,折線圖,所以把公共的部分抽出來。注意canvas.style.width與canvas.width是不一樣的,前者會拉伸圖形,後者才是我們正常用的,不會拉伸圖形。在這裡這樣寫先擴大再縮小是為了解決canvas繪製文字時模糊的問題。
  3. class Chart{
            constructor(container){
                this.container=container;
                this.canvas=document.createElement('canvas');
                this.ctx=this.canvas.getContext('2d');
                this.W=1000*2;
                this.H=600*2;
                this.padding=120;
                this.paddingTop=50;
                this.title='';
                this.legend=[];
                this.series=[];
                //通过缩小一倍,解决字体模糊问题
                this.canvas.width=this.W;
                this.canvas.height=this.H;
                this.canvas.style.width = this.W/2 + 'px';
                this.canvas.style.height = this.H/2 + 'px';
            }
        }
    登入後複製

    長條圖初始化,呼叫es6中的Object.assign(this,opt),這個相當於JQ中的extend方法,把屬性複製到目前實例。同時也建了個tip屬性,這是個html標籤,後面顯示數據資訊用。接著繪製圖形,然後綁定滑鼠事件。

    class Bar extends Chart{
        constructor(container){
            super(container);
            this.xAxis={};
            this.yAxis=[];
            this.animateArr=[];
        }
        init(opt){
            Object.assign(this,opt);
            if(!this.container)return;
            this.container.style.position='relative';
            this.tip=document.createElement('p');
            this.tip.style.cssText='display: none; position: absolute; opacity: 0.5; background: #000; color: #fff; border-radius: 5px; padding: 5px; font-size: 8px; z-index: 99;';
            this.container.appendChild(this.canvas);
            this.container.appendChild(this.tip);
            this.draw();
            this.bindEvent();
        }
        draw(){//绘制
        }
        showInfo(){//显示信息
        }
        animate(){//执行动画
        }
        showData(){//显示数据
        }
    登入後複製
  4. 繪製XY軸
  5. 先繪製標題,接著XY軸,然後遍歷分組資料series,裡面有複雜的計算,然後繪製XY軸的刻度,繪製分組標籤,最後是繪製數據。數據項series中是分組數據,它跟X軸的xAxis.data一一對應。每個項目可以自訂名稱和顏色,沒有指定的話,名稱賦予nunamed和自動產生顏色。這裡也用legend屬性記錄下了標籤清單訊息,因為後續滑鼠點擊判斷是否點中用的上。

  6. canvas主要知識點:

#分組標籤使用了arcTo方法,這樣就能繪製出圓角的效果。

繪製文字使用了measureText方法,可以用來測量文字所佔寬度,這樣就可以調整下一次繪製的位置,避免位置衝突。

translate位移方法,可以放在繪製上下文(save和restore的中間)中,這樣可以避免複雜的位置運算。

draw(){
    var that=this,
        ctx=this.ctx,
        canvas=this.canvas,
        W=this.W,
        H=this.H,
        padding=this.padding,
        paddingTop=this.paddingTop,
        xl=0,xs=0,xdis=W-padding*2,//x轴单位数,每个单位长度,x轴总长度
        yl=0,ys=0,ydis=H-padding*2-paddingTop;//y轴单位数,每个单位长度,y轴总长度
    ctx.fillStyle='hsla(0,0%,20%,1)';
    ctx.strokeStyle='hsla(0,0%,10%,1)';
    ctx.lineWidth=1;
    ctx.textAlign='center';
    ctx.textBaseLine='middle';
    ctx.font='24px arial';
    ctx.clearRect(0,0,W,H);
    if(this.title){
        ctx.save();
        ctx.textAlign='left';
        ctx.font='bold 40px arial';
        ctx.fillText(this.title,padding-50,70);
        ctx.restore();
    }
    if(this.yAxis&&this.yAxis.name){
        ctx.fillText(this.yAxis.name,padding,padding+paddingTop-30);
    }
    // x轴
    ctx.save();
    ctx.beginPath();
    ctx.translate(padding,H-padding);
    ctx.moveTo(0,0);
    ctx.lineTo(W-2*padding,0);
    ctx.stroke();
    // x轴刻度
    if(this.xAxis&&(xl=this.xAxis.data.length)){
        xs=(W-2*padding)/xl;
        this.xAxis.data.forEach((obj,i)=>{
            var x=xs*(i+1);
            ctx.moveTo(x,0);
            ctx.lineTo(x,10);
            ctx.stroke();
            ctx.fillText(obj,x-xs/2,40);
        });
    }
    ctx.restore();
    // y轴
    ctx.save();
    ctx.beginPath();
    ctx.strokeStyle='hsl(220,100%,50%)';
    ctx.translate(padding,H-padding);
    ctx.moveTo(0,0);
    ctx.lineTo(0,2*padding+paddingTop-H);
    ctx.stroke();
    ctx.restore();
    if(this.series.length){         
        var curr,txt,dim,info,item,tw=0;
        for(var i=0;i<this.series.length;i++){
            item=this.series[i];
            if(!item.data||!item.data.length){
                this.series.splice(i--,1);continue;
            }
            // 赋予没有颜色的项
            if(!item.color){
                var hsl=i%2?180+20*i/2:20*(i-1);
                item.color=&#39;hsla(&#39;+hsl+&#39;,70%,60%,1)&#39;;
            }
            item.name=item.name||&#39;unnamed&#39;;
            // 画分组标签
            ctx.save();
            ctx.translate(padding+W/4,paddingTop+40);
            that.legend.push({
                hide:item.hide||false,
                name:item.name,
                color:item.color,
                x:padding+that.W/4+i*90+tw,
                y:paddingTop+40,
                w:60,
                h:30,
                r:5
            });
            ctx.textAlign=&#39;left&#39;;
            ctx.fillStyle=item.color;
            ctx.strokeStyle=item.color;
            roundRect(ctx,i*90+tw,0,60,30,5);
            ctx.globalAlpha=item.hide?0.3:1;
            ctx.fill();
            ctx.fillText(item.name,i*90+tw+70,26);
            tw+=ctx.measureText(item.name).width;//计算字符长度
            ctx.restore();
            if(item.hide)continue;
            //计算数据在Y轴刻度
            if(!info){
                info=calculateY(item.data.slice(0,xl));
            }
            curr=calculateY(item.data.slice(0,xl));
            if(curr.max>info.max){
                info=curr;
            }
        }
        if(!info) return;
        yl=info.num;
        ys=ydis/yl;
        //画Y轴刻度
        ctx.save();
        ctx.fillStyle='hsl(200,100%,60%)';
        ctx.translate(padding,H-padding);
        for(var i=0;i<=yl;i++){
            ctx.beginPath();
            ctx.strokeStyle=&#39;hsl(220,100%,50%)&#39;;
            ctx.moveTo(-10,-Math.floor(ys*i));
            ctx.lineTo(0,-Math.floor(ys*i));
            ctx.stroke();
            ctx.beginPath();
            ctx.strokeStyle=&#39;hsla(0,0%,80%,1)&#39;;
            ctx.moveTo(0,-Math.floor(ys*i));
            ctx.lineTo(xdis,-Math.floor(ys*i));
            ctx.stroke();
            ctx.textAlign=&#39;right&#39;;
            dim=Math.min(Math.floor(info.step*i),info.max);
            txt=this.yAxis.formatter?this.yAxis.formatter.replace(&#39;{value}&#39;,dim):dim;
            ctx.fillText(txt,-20,-ys*i+10);
        }
        ctx.restore();
        //画数据
        this.showData(xl,xs,info.max);
    }
}
登入後複製

繪製資料

因為資料項目需要後續執行動畫和滑鼠滑過的時候顯示內容,所以把它放進動畫隊列animateArr中。這裡要把分組資料展開,把之前的兩次嵌套的陣列轉為一層,併計算好每個資料項的屬性,例如名稱,x座標,y座標,寬度,速度,顏色。資料組織完畢後,接著執行動畫。

showData(xl,xs,max){
    //画数据
    var that=this,
        ctx=this.ctx,
        ydis=this.H-this.padding*2-this.paddingTop,
        sl=this.series.filter(s=>!s.hide).length,
        sp=Math.max(Math.pow(10-sl,2)/3-4,5),
        w=(xs-sp*(sl+1))/sl,
        h,x,index=0;
    that.animateArr.length=0;
    // 展开数据项,填入动画队列
    for(var i=0,item,len=this.series.length;i<len;i++){
        item=this.series[i];
        if(item.hide)continue;
        item.data.slice(0,xl).forEach((d,j)=>{
            h=d/max*ydis;
            x=xs*j+w*index+sp*(index+1);
            that.animateArr.push({
                index:i,
                name:item.name,
                num:d,
                x:Math.round(x),
                y:1,
                w:Math.round(w),
                h:Math.floor(h+2),
                vy:Math.max(300,Math.floor(h*2))/100,
                color:item.color
            });
        });
        index++;
    }
    this.animate();
}
登入後複製

執行動畫#########執行動畫也沒啥好說的,裡面就是個自執行閉包函數。動畫原理就是給y軸依序累加速度值vy。但記得當隊列執行完動畫後,要停止它,所以有個isStop的標誌,每次執行完隊列的時候就判斷。 ###
animate(){
    var that=this,
        ctx=this.ctx,
        isStop=true;
    (function run(){
        isStop=true;
        for(var i=0,item;i<that.animateArr.length;i++){
            item=that.animateArr[i];
            if(item.y-item.h>=0.1){
                item.y=item.h;
            } else {
                item.y+=item.vy;
            }
            if(item.y<item.h){
                ctx.save();
                // ctx.translate(that.padding+item.x,that.H-that.padding);
                ctx.fillStyle=item.color;
                ctx.fillRect(that.padding+item.x,that.H-that.padding-item.y,item.w,item.y);
                ctx.restore();
                isStop=false;
            }
        }
        if(isStop)return;
        requestAnimationFrame(run);
    }())
}
登入後複製
######綁定事件#########事件一:mousemove的時候,看看滑鼠位置是否處於分組標籤還是資料項目上,繪製路徑後呼叫isPointInPath(x ,y),true則canvas.style.cursor='pointer';如果是資料項的話,還要給把該柱形重新繪製,設定透明度,區分出來。還需要把內容顯示出來,這裡是一個相對父容器container為###絕對定位###的p,初始化的時候已經建立為tip屬性了。我們把顯示部分封裝成showInfo方法。 ######事件二:mousedown的時候,判斷滑鼠點選哪個分組標籤,然後設定對應分組資料series中的hide屬性,如果是true,表示不顯示該項,然後呼叫draw方法,重寫渲染繪製,執行動畫。 ###
bindEvent(){
        var that=this,
            canvas=this.canvas,
            ctx=this.ctx;
        this.canvas.addEventListener(&#39;mousemove&#39;,function(e){
            var isLegend=false;
                // pos=WindowToCanvas(canvas,e.clientX,e.clientY);
            var box=canvas.getBoundingClientRect();
            var pos = {
                x:e.clientX-box.left,
                y:e.clientY-box.top
            };
            // 分组标签
            for(var i=0,item,len=that.legend.length;i<len;i++){
                item=that.legend[i];
                ctx.save();
                roundRect(ctx,item.x,item.y,item.w,item.h,item.r);
                // 因为缩小了一倍,所以坐标要*2
                if(ctx.isPointInPath(pos.x*2,pos.y*2)){
                    canvas.style.cursor=&#39;pointer&#39;;
                    ctx.restore();
                    isLegend=true;
                    break;
                }
                canvas.style.cursor=&#39;default&#39;;
                ctx.restore();
            }
            if(isLegend) return;
            //选择数据项
            for(var i=0,item,len=that.animateArr.length;i<len;i++){
                item=that.animateArr[i];
                ctx.save();
                ctx.fillStyle=item.color;
                ctx.beginPath();
                ctx.rect(that.padding+item.x,that.H-that.padding-item.h,item.w,item.h);
                if(ctx.isPointInPath(pos.x*2,pos.y*2)){
                    //清空后再重新绘制透明度为0.5的图形
                    ctx.clearRect(that.padding+item.x,that.H-that.padding-item.h,item.w,item.h);
                    ctx.globalAlpha=0.5;
                    ctx.fill();
                    canvas.style.cursor=&#39;pointer&#39;;
                    that.showInfo(pos,item);
                    ctx.restore();
                    break;
                }
                canvas.style.cursor=&#39;default&#39;;
                that.tip.style.display=&#39;none&#39;;
                ctx.globalAlpha=1;
                ctx.fill();
                ctx.restore();
            }
            
        },false);
        this.canvas.addEventListener(&#39;mousedown&#39;,function(e){
            e.preventDefault();
            var box=canvas.getBoundingClientRect();
            var pos = {
                x:e.clientX-box.left,
                y:e.clientY-box.top
            };
            for(var i=0,item,len=that.legend.length;i<len;i++){
                item=that.legend[i];
                roundRect(ctx,item.x,item.y,item.w,item.h,item.r);
                // 因为缩小了一倍,所以坐标要*2
                if(ctx.isPointInPath(pos.x*2,pos.y*2)){
                    that.series[i].hide=!that.series[i].hide;
                    that.animateArr.length=0;
                    that.draw();
                    break;
                }
            }
        },false);
    }
    //显示数据
    showInfo(pos,obj){
        var txt=this.yAxis.formatter?this.yAxis.formatter.replace(&#39;{value}&#39;,obj.num):obj.num;
        var box=this.canvas.getBoundingClientRect();
        var con=this.container.getBoundingClientRect();
        this.tip.innerHTML = &#39;<p>'+obj.name+':'+txt+'</p>';
        this.tip.style.left=(pos.x+(box.left-con.left)+10)+'px';
        this.tip.style.top=(pos.y+(box.top-con.top)+10)+'px';
        this.tip.style.display='block';
    }
登入後複製

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

H5的各种错误用法总结

H5的语义化标签

以上是H5的canvas圖表實現長條圖的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

HTML 中的表格邊框 HTML 中的表格邊框 Sep 04, 2024 pm 04:49 PM

HTML 表格邊框指南。在這裡,我們以 HTML 中的表格邊框為例,討論定義表格邊框的多種方法。

HTML 左邊距 HTML 左邊距 Sep 04, 2024 pm 04:48 PM

HTML 左邊距指南。在這裡,我們討論 HTML margin-left 的簡要概述及其範例及其程式碼實作。

HTML 中的巢狀表 HTML 中的巢狀表 Sep 04, 2024 pm 04:49 PM

這是 HTML 中巢狀表的指南。這裡我們討論如何在表中建立表格以及對應的範例。

HTML 表格佈局 HTML 表格佈局 Sep 04, 2024 pm 04:54 PM

HTML 表格佈局指南。在這裡,我們詳細討論 HTML 表格佈局的值以及範例和輸出。

HTML 輸入佔位符 HTML 輸入佔位符 Sep 04, 2024 pm 04:54 PM

HTML 輸入佔位符指南。在這裡,我們討論 HTML 輸入佔位符的範例以及程式碼和輸出。

HTML 有序列表 HTML 有序列表 Sep 04, 2024 pm 04:43 PM

HTML 有序列表指南。在這裡我們也分別討論了 HTML 有序列表和類型的介紹以及它們的範例

在 HTML 中移動文字 在 HTML 中移動文字 Sep 04, 2024 pm 04:45 PM

HTML 中的文字移動指南。在這裡我們討論一下marquee標籤如何使用語法和實作範例。

HTML onclick 按鈕 HTML onclick 按鈕 Sep 04, 2024 pm 04:49 PM

HTML onclick 按鈕指南。這裡我們分別討論它們的介紹、工作原理、範例以及各個事件中的onclick事件。

See all articles