Heim  >  Artikel  >  Web-Frontend  >  Beispiel für die Implementierung eines Histogramms in einem Canvas-Diagramm in HTML5

Beispiel für die Implementierung eines Histogramms in einem Canvas-Diagramm in HTML5

不言
不言Original
2018-06-05 16:13:433317Durchsuche

In diesem Artikel wird hauptsächlich das Beispiel der Verwendung von Canvas zur Implementierung eines Diagramms in HTML5 vorgestellt. Ich denke, es ist ziemlich gut, es mit Ihnen zu teilen und als Referenz zu verwenden.

Ich habe vor ein paar Tagen eine Diagrammbibliothek verwendet, von der Baidus ECharts die beste zu sein scheint. Canvas-Diagramme sind bei der Verarbeitung großer Datenmengen besser. Dann werde ich auch Canvas verwenden, um eine Diagrammbibliothek zu implementieren. Es fühlt sich nicht allzu schwierig an. Lassen Sie uns zuerst ein einfaches Balkendiagramm implementieren.

Der Effekt ist wie folgt:

Die Hauptfunktionspunkte umfassen:

  1. Textzeichnung

  2. XY-Achsenzeichnung;

  3. Datengruppenzeichnung;

  4. Implementierung von Datenanimationen;

  5. Behandlung von Mausereignissen.

Verwendungsmethode

Zuerst werfen wir einen Blick auf die Verwendungsmethode und den ersten Durchgang im HTML-Tag Um das Diagramm anzuzeigen, rufen Sie dann init auf und übergeben Sie während der Initialisierung Daten.

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]
            }
        ]
    });

Diagrammbasisklasse. Wir werden später auch Kreisdiagramme und Liniendiagramme schreiben, also extrahieren Sie die gemeinsamen Teile. Beachten Sie, dass „canvas.style.width“ und „canvas.width“ unterschiedlich sind. Ersteres streckt die Grafiken, während letzteres das ist, was wir normalerweise verwenden und die Grafiken nicht streckt. Der Zweck, hier zuerst die Erweiterung und dann die Reduzierung zu schreiben, besteht darin, das Problem der Unschärfe beim Zeichnen von Text auf Leinwand zu lösen.

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';
        }
    }

Um das Histogramm zu initialisieren, rufen Sie Object.assign(this,opt) in es6 auf. Dies entspricht der Extend-Methode in JQ, die die Eigenschaften in die aktuelle Instanz kopiert. Gleichzeitig wird auch ein Tip-Attribut erstellt, das ein HTML-Tag ist und zur späteren Anzeige von Dateninformationen verwendet wird. Zeichnen Sie dann die Grafiken und binden Sie Mausereignisse.

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(){//显示数据

    }

Zeichnen Sie die XY-Achse

Zeichnen Sie zuerst den Titel, dann die XY-Achse, durchlaufen Sie dann die gruppierte Datenreihe, die komplexe Berechnungen enthält, zeichnen Sie dann den Maßstab der XY-Achse und zeichnen Sie die Gruppenbezeichnung und schließlich Daten zeichnen. Bei der Datenelementreihe handelt es sich um gruppierte Daten, die eins zu eins den xAxis.data der X-Achse entsprechen. Jedes Element kann einen benutzerdefinierten Namen und eine benutzerdefinierte Farbe haben. Wenn nicht angegeben, wird der Name nunamed zugewiesen und die Farbe wird automatisch generiert. Das Legendenattribut wird hier auch zum Aufzeichnen der Tag-Listeninformationen verwendet, da es bei nachfolgenden Mausklicks nützlich ist, um festzustellen, ob der Klick korrekt ist.

Hauptwissenspunkte von Canvas:

  1. Das Gruppierungs-Tag verwendet die arcTo-Methode, sodass der Effekt abgerundeter Ecken gezeichnet werden kann.

  2. Zeichnungstext verwendet die Methode „measureText“, mit der die Breite des Textes gemessen werden kann, sodass die Position der nächsten Zeichnung angepasst werden kann, um Positionskonflikte zu vermeiden.

  3. Die Verschiebungsmethode übersetzen kann im Zeichnungskontext (zwischen Speichern und Wiederherstellen) platziert werden, wodurch komplexe Positionsberechnungen vermieden werden können.

  4. 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=&#39;hsl(200,100%,60%)&#39;;
            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);
        }
    }

Zeichnungsdaten

Da das Datenelement animiert und angezeigt werden muss, wenn die Maus darüber gleitet, fügen Sie es in die Animationswarteschlange ein animateArr. Hier müssen wir die gruppierten Daten erweitern, die beiden vorherigen verschachtelten Arrays in eine Ebene konvertieren und die Attribute jedes Datenelements berechnen, z. B. Name, X-Koordinate, Y-Koordinate, Breite, Geschwindigkeit und Farbe. Nachdem die Daten organisiert sind, wird die Animation ausgeführt.

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();
}

Animation ausführen

Zur Ausführung einer Animation gibt es nichts zu sagen, es handelt sich um eine selbstausführende Abschlussfunktion. Das Prinzip der Animation besteht darin, den Geschwindigkeitswert vy sequentiell auf der y-Achse zu akkumulieren. Denken Sie jedoch daran, dass die Warteschlange, wenn sie mit der Ausführung der Animation fertig ist, gestoppt werden muss. Daher gibt es ein isStop-Flag, das jedes Mal beurteilt wird, wenn die Warteschlange mit der Ausführung fertig ist.

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);
    }())
}

Bindungsereignis

Ereignis 1: Überprüfen Sie beim Bewegen der Maus, ob sich die Mausposition auf der Gruppenbeschriftung oder dem Datenelement befindet, und rufen Sie nach dem Zeichnen isPointInPath(x auf path , y), wenn true, canvas.style.cursor='pointer'; wenn es sich um ein Datenelement handelt, muss die Spalte neu gezeichnet, die Transparenz festgelegt und unterschieden werden. Der Inhalt muss auch angezeigt werden. Dies ist ein p, das relativ zum übergeordneten Container-Container positioniert ist. Es wurde während der Initialisierung festgelegt. Wir kapseln den Anzeigeteil in die showInfo-Methode.

Ereignis 2: Bestimmen Sie beim Herunterfahren, auf welche Gruppenbezeichnung die Maus klickt, und legen Sie dann das Ausblenden-Attribut in der entsprechenden Gruppendatenreihe fest. Wenn es wahr ist, bedeutet dies, dass das Element nicht angezeigt wird Rufen Sie dann die Draw-Methode auf, um das Rendern und Zeichnen zu überschreiben und die Animation auszuführen.

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>&#39;+obj.name+&#39;:&#39;+txt+&#39;</p>&#39;;
        this.tip.style.left=(pos.x+(box.left-con.left)+10)+&#39;px&#39;;
        this.tip.style.top=(pos.y+(box.top-con.top)+10)+&#39;px&#39;;
        this.tip.style.display=&#39;block&#39;;
    }

Zusammenfassung

Was hier gemacht wird, ist nur ein grundlegender Effekt. Tatsächlich gibt es noch viele Bereiche, die weiter optimiert werden müssen, wie z. B. der reaktionsfähige Support. Unterstützung für Mobilgeräte und Animationen, Unterstützung für mehrere Y-Achsen, Anzeigeinhaltseffekte und Unterstützung für Polylinienfunktionen usw.

Verwandte Empfehlungen:

Html realisiert den Berichtseffekt der dynamischen Anzeige von Farbblöcken (Beispielcode)

HTML5 generiert ein Histogramm Beispielcode für (Balkendiagramm-)Effekt


Das obige ist der detaillierte Inhalt vonBeispiel für die Implementierung eines Histogramms in einem Canvas-Diagramm in HTML5. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn