Maison  >  Article  >  interface Web  >  Le graphique en toile H5 implémente un graphique à barres

Le graphique en toile H5 implémente un graphique à barres

php中世界最好的语言
php中世界最好的语言original
2018-03-27 14:27:235338parcourir

Cette fois, je vais vous apporter un graphique en toile H5 pour implémenter l'histogramme. Quelles sont les précautions pour implémenter l'histogramme dans un graphique en toile. Voici un cas pratique, jetons un coup d'œil.

J'ai utilisé une bibliothèque de graphiques il y a quelques jours, parmi laquelle les ECharts de Baidu semblent être les meilleurs. Ils utilisent Canvas par défaut. Les graphiques Canvas sont meilleurs que les SVG pour traiter le Big Data. Ensuite, j'utiliserai également Canvas pour implémenter une bibliothèque de graphiques. Cela ne semble pas trop difficile. Implémentons d'abord un simple graphique à barres.

L'effet est le suivant :

Les principaux points de fonction incluent :

  1. Dessin de texte

  2. Dessin d'axe XY

  3. Dessin de groupe de données

  4. Mise en place de l'animation des données ;

  5. Gestion des événements souris.

Méthode d'utilisation

Tout d'abord, examinons la méthode d'utilisation En référence aux méthodes d'utilisation de certains ECharts, d'abord. passez le balise html , puis appelez init et transmettez les données lors de l'initialisation.

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]
            }
        ]
    });
Classe de base de graphique. Nous écrirons également des diagrammes circulaires et des graphiques linéaires plus tard, alors extrayez les parties communes. Notez que canvas.style.width et canvas.width sont différents. Le premier étirera les graphiques, tandis que le second est ce que nous utilisons normalement et n'étirera pas les graphiques. Le but de l'écriture ici d'abord d'une expansion puis d'une réduction est de résoudre le problème du flou lors du dessin de texte sur la toile.

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';
        }
    }
Pour initialiser l'histogramme, appelez Object.assign(this,opt) dans es6. Cela équivaut à la méthode extend de JQ, qui copie les propriétés dans l'instance actuelle. Dans le même temps, un attribut tip est également créé, qui est une balise HTML et est utilisé pour afficher les informations sur les données ultérieurement. Dessinez ensuite les graphiques et liez les événements de la souris.

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(){//显示数据
    }
Dessinez l'axe XY

Dessinez d'abord le titre, puis l'axe XY, puis parcourez la série de données groupées, qui comporte des calculs complexes, puis dessinez l'échelle de l'axe XY, dessinez l'étiquette du groupe et enfin les données tracées. La série d'éléments de données est constituée de données groupées, qui correspondent à xAxis.data de l'axe X un à un. Chaque élément peut avoir un nom et une couleur personnalisés. S'il n'est pas spécifié, le nom est donné à nunamed et la couleur est automatiquement générée. L'attribut de légende est également utilisé ici pour enregistrer les informations de la liste de balises, car il est utile pour les clics de souris ultérieurs afin de déterminer si le clic est correct.

Principaux points de connaissance de Canvas :

  1. La balise de regroupement utilise la méthode arcTo, afin que l'effet des coins arrondis puisse être dessiné.

  2. Le dessin de texte utilise la méthode MeasureText, qui peut être utilisée pour mesurer la largeur du texte, afin que la position du dessin suivant puisse être ajustée pour éviter les conflits de position.

  3. La méthode de déplacement par translation peut être placée dans le contexte du dessin (entre la sauvegarde et la restauration), ce qui peut éviter des calculs de position complexes.

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

Dessiner des données

Étant donné que l'élément de données doit effectuer des animations ultérieures et afficher du contenu lorsque la souris glisse dessus, placez-le dans File d'attente d'animation animateArr. Ici, nous devons développer les données groupées, convertir les deux tableaux imbriqués précédents en un seul calque et calculer les attributs de chaque élément de données, tels que le nom, la coordonnée x, la coordonnée y, la largeur, la vitesse et la couleur. Une fois les données organisées, l'animation est exécutée.

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

Exécuter l'animation

Il n'y a rien à dire sur l'exécution de l'animation C'est une fonction de fermeture auto-exécutable. Le principe de l'animation est d'accumuler séquentiellement la valeur de vitesse vy sur l'axe y. Mais rappelez-vous que lorsque la file d'attente termine l'exécution de l'animation, elle doit être arrêtée, il y a donc un indicateur isStop, qui est jugé à chaque fois que l'exécution de la file d'attente est terminée.

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

Événement de liaison

Événement 1 : lors du déplacement de la souris, vérifiez si la position de la souris est sur l'étiquette du groupe ou l'élément de données, et appelez isPointInPath après avoir tracé le chemin (x, y), si vrai, canvas.style.cursor='pointer'; s'il s'agit d'un élément de données, la colonne doit être redessinée, définir la transparence et différenciée. Le contenu doit également être affiché. Voici un p avec un

positionnement absolu par rapport au conteneur parent. Il a été établi comme attribut tip lors de l'initialisation. Nous encapsulons la partie affichage dans la méthode showInfo.

Événement 2 : pendant le passage de la souris, déterminez sur quelle étiquette de groupe la souris clique, puis définissez l'attribut hide dans la série de données de groupe correspondante. Si c'est vrai, cela signifie que l'élément ne sera pas affiché, et. puis appelez la méthode draw pour remplacer le rendu et le dessin, exécutez l'animation.

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的语义化标签

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn