canvas圖表(2) - 折線圖

地址:canvas圖表(2) - 折線圖
canvas圖表系列第2節(jié)吹散,我們接著實(shí)現(xiàn)折線圖
效果請(qǐng)看:折線圖

chartline

主要功能點(diǎn)包括:

  1. 組織數(shù)據(jù)施敢;
  2. 繪制缘挑;
  3. 數(shù)據(jù)動(dòng)畫的實(shí)現(xiàn)胶滋;
  4. 清屏并重繪畫面;
  5. 鼠標(biāo)事件的處理惭婿。

大部分的技術(shù)在上一節(jié)的canvas圖表(1) - 柱狀圖實(shí)現(xiàn)了, 所以這節(jié)內(nèi)容其實(shí)是比較簡(jiǎn)單的箕昭。比較麻煩一點(diǎn)的就是折線圖的動(dòng)畫了,所以重點(diǎn)就看一下這部分的代碼衣陶。

使用方式

使用方式柄瑰,和柱狀圖基本是一樣的,我們這里表示的是氣溫變化圖剪况。

    var con=document.getElementById('container');
    var line = new Line(con);
    line.init({
        title:'未來(lái)一周氣溫變化',
        xAxis:{
            data:['周一','周二','周三','周四','周五','周六','周日']
        },
        yAxis:{
            name:'溫度',
            formatter:'{value} °C'
        },
        series:[
            {
                name:'最高氣溫',
                data:[11, 11, 15, 13, 12, 13, 10]
            },
            {
                name:'最低氣溫',
                data:[1, -2, 2, 5, 3, 2, 0]
            }   
        ]
    })

代碼結(jié)構(gòu)

折線圖對(duì)象大體和柱狀圖一致教沾,只是部分方法經(jīng)過(guò)重構(gòu)。

    class Line extends Chart{
        constructor(container){
            super(container);
        }
        // 初始化
        init(opt){

        }
        // 綁定事件
        bindEvent(){

        }
        // 顯示信息
        showInfo(pos,arr){

        }
        // 清除內(nèi)容再繪制
        clearGrid(index){

        }
        // 執(zhí)行數(shù)據(jù)動(dòng)畫
        animate(){

        }
        // 執(zhí)行
        create(){

        }
        // 組織數(shù)據(jù)
        initData(){

        }
        // 繪制
        draw(){

        }
    }

數(shù)據(jù)動(dòng)畫

折線圖動(dòng)畫實(shí)現(xiàn)的是路徑繪制特效译断,懂canvas的基本都知道原理授翻,就是用lineTo繪制路徑,最后stroke出來(lái)孙咪。但這個(gè)折線圖是分段的堪唐,所以要分情況處理,主要難點(diǎn)就是獲取兩個(gè)點(diǎn)之間的坐標(biāo)翎蹈。

仔細(xì)思考下如何實(shí)現(xiàn)繪制路徑動(dòng)畫淮菠,因?yàn)槲覀冎纗軸總長(zhǎng)度,所以可以讓x依次遞增杨蛋,再求出x對(duì)應(yīng)的y坐標(biāo)即可兜材。既然知道了兩個(gè)點(diǎn)的坐標(biāo),還知道了x坐標(biāo)逞力,根據(jù)同角度等比例三角形原理曙寡,很容易求出y坐標(biāo)。

還有就是更新狀態(tài)的位移動(dòng)畫了寇荧,這個(gè)就更加簡(jiǎn)單了举庶,根據(jù)當(dāng)前位置和要將要移動(dòng)到的位置對(duì)比,進(jìn)行對(duì)應(yīng)的增減即可揩抡。

    animate(){
        var that=this,
            ctx=this.ctx,
            obj,h=0,
            isStop=true;

        (function run(){
            ctx.clearRect(0,that.padding+that.paddingTop-5,that.W,that.H-2*that.padding-that.paddingTop+4);
            that.drawY();

            ctx.save();
            ctx.translate(that.padding,that.H-that.padding);
            isStop=true;
            for(var i=0,item;i<that.animateArr.length;i++){
                item=that.animateArr[i];
                if(item.hide) continue;
                ctx.strokeStyle=item.color;
                ctx.lineWidth=item.data[0].w;
                item.isStop=true;
                if(item.create){//新增繪制路徑動(dòng)畫
                    for(var j=0,jl=item.data.length;j<jl;j++){
                        obj=item.data[j];
                        if(obj.y>=obj.h){
                            obj.y=obj.p=obj.h;
                        } else {
                            obj.y+=obj.vy;
                            item.isStop=false;
                        }
                        ctx.beginPath();
                        ctx.moveTo(obj.x+obj.w/2,-obj.y);
                        ctx.lineTo(obj.x+obj.w/2,-1);
                        ctx.stroke();
                    }
                } else { //更新位移動(dòng)畫
                    for(var j=0,jl=item.data.length;j<jl;j++){
                        obj=item.data[j];
                        if(obj.p>obj.h){
                            h=obj.y-4;
                            if(h<obj.h){
                                obj.y=obj.p=obj.h;
                            }
                        } else {
                            h=obj.y+4;
                            if(h>obj.h){
                                obj.y=obj.p=obj.h;
                            }
                        }
                        if(obj.p!=obj.h){
                            obj.y=h;
                            item.isStop=false;
                        }

                        ctx.beginPath();
                        ctx.moveTo(obj.x+obj.w/2,-obj.y);
                        ctx.lineTo(obj.x+obj.w/2,-1);
                        ctx.stroke();
                    }
                }
                if(!item.isStop){isStop=false; }
            }
            ctx.restore();
            if(isStop)return;
            requestAnimationFrame(run);
        }())
    }

清屏并重繪畫面

在畫面上要實(shí)現(xiàn)動(dòng)態(tài)效果的時(shí)候户侥,需要清屏镀琉,重新繪制畫面,如果指定了某個(gè)區(qū)間蕊唐,就在該區(qū)間上畫標(biāo)志線屋摔,同時(shí)該區(qū)間的圓心放大。

    clearGrid(index){
        var that=this,
            obj, r=5,
            ctx=this.ctx;
        ctx.clearRect(0,0,that.W,that.H);
        // 畫坐標(biāo)系
        this.drawAxis();
        // 畫標(biāo)簽
        this.drawTag();
        // 畫y軸刻度
        this.drawY();

        ctx.save();
        ctx.translate(that.padding,that.H-that.padding);
        // 畫標(biāo)志線
        if(typeof index== 'number'){
            obj=that.animateArr[0].data[index];
            ctx.lineWidth=1;
            ctx.strokeStyle='hsla(0,0%,70%,1)';
            ctx.moveTo(obj.x,-that.H+that.paddingTop+2*that.padding);
            ctx.lineTo(obj.x,0);
            ctx.stroke();
        }

        for(var i=0,item,il=that.animateArr.length;i<il;i++){
            item=that.animateArr[i];
            if(item.hide)continue;
            ctx.lineWidth=4;
            ctx.strokeStyle=item.color;
            ctx.fillStyle='#fff';
            ctx.beginPath();
            for(var j=0,obj,jl=item.data.length;j<jl;j++){
                obj=item.data[j];
                if(j==0){
                    ctx.moveTo(obj.x,-obj.h);
                } else {
                    ctx.lineTo(obj.x,-obj.h);
                }
            }
            ctx.stroke();

            //畫完曲線后再畫圓球
            for(var j=0,jl=item.data.length;j<jl;j++){
                obj=item.data[j];
                ctx.strokeStyle=item.color;
                ctx.lineWidth=index===j?6:4;
                r=index===j?10:5;
                ctx.beginPath();
                ctx.arc(obj.x,-obj.h,r,0,Math.PI*2,false);
                ctx.stroke();
                ctx.fill();
            }
        }
        ctx.restore();
    }

事件處理

mousemove 一是觸摸標(biāo)簽顯示手形替梨,二是滑過(guò)畫面區(qū)域的時(shí)候擦除并重繪畫面钓试,選中的折線的圓形擴(kuò)大,同時(shí)繪制指示線副瀑,具體看clearGrid方法弓熏。

mousedown某個(gè)擊標(biāo)簽就會(huì)顯示隱藏對(duì)應(yīng)分組,創(chuàng)建狀態(tài)執(zhí)行路徑繪制動(dòng)畫糠睡,而更新狀態(tài)這是執(zhí)行位移動(dòng)畫挽鞠。

    bindEvent(){
        var that=this,
            ctx=that.ctx,
            canvas=that.canvas,
            xl=this.xAxis.data.length,
            xs=(that.W-2*that.padding)/(xl-1),
            index=0;
        this.canvas.addEventListener('mousemove',function(e){
            var isLegend=false;
            // todo ...

            if(isLegend) return;
            // 鼠標(biāo)位置在圖表中時(shí)
            if(pos.y*2>that.padding+that.paddingTop && pos.y*2<that.H-that.padding && pos.x*2>that.padding && pos.x*2<that.W-that.padding){
                canvas.style.cursor='pointer';
                for(var i=0;i<xl;i++){
                    if(pos.x*2>i*xs){
                        index=i;
                    }
                }
                // 重繪并標(biāo)志選中信息
                that.clearGrid(index);
    
                // 獲取處于當(dāng)前位置的信息
                var arr=[];
                for(var j=0,item,l=that.animateArr.length;j<l;j++){
                    item=that.animateArr[j];
                    if(item.hide)continue;
                    arr.push({name:item.name, num:item.data[index].num})
                }
                that.showInfo(pos,arr);
                ctx.restore();
            } else {
                that.tip.style.display='none';
                that.clearGrid();
            }

        },false);

        this.canvas.addEventListener('mousedown',function(e){
            e.preventDefault();
            var box=that.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);
                if(ctx.isPointInPath(pos.x*2,pos.y*2)){
                    that.series[i].hide=!that.series[i].hide;
                    that.create();
                    break;
                }
            }
        },false);
    }

最后

所有圖表代碼請(qǐng)看chart.js

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市狈孔,隨后出現(xiàn)的幾起案子信认,更是在濱河造成了極大的恐慌,老刑警劉巖除抛,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狮杨,死亡現(xiàn)場(chǎng)離奇詭異母截,居然都是意外死亡到忽,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門清寇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)喘漏,“玉大人,你說(shuō)我怎么就攤上這事华烟◆媛酰” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵盔夜,是天一觀的道長(zhǎng)负饲。 經(jīng)常有香客問(wèn)我,道長(zhǎng)喂链,這世上最難降的妖魔是什么返十? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮椭微,結(jié)果婚禮上洞坑,老公的妹妹穿的比我還像新娘。我一直安慰自己蝇率,他們只是感情好迟杂,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布刽沾。 她就那樣靜靜地躺著,像睡著了一般排拷。 火紅的嫁衣襯著肌膚如雪侧漓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天监氢,我揣著相機(jī)與錄音火架,去河邊找鬼。 笑死忙菠,一個(gè)胖子當(dāng)著我的面吹牛何鸡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播牛欢,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼骡男,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了傍睹?” 一聲冷哼從身側(cè)響起隔盛,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拾稳,沒(méi)想到半個(gè)月后吮炕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡访得,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年龙亲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悍抑。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鳄炉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出搜骡,到底是詐尸還是另有隱情拂盯,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布记靡,位于F島的核電站谈竿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏摸吠。R本人自食惡果不足惜空凸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蜕便。 院中可真熱鬧劫恒,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至憔辫,卻和暖如春趣些,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贰您。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工坏平, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锦亦。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓舶替,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親杠园。 傳聞我的和親對(duì)象是個(gè)殘疾皇子顾瞪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容