J'ai utilisé une bibliothèque de graphiques il y a quelques jours, parmi laquelle ECharts de Baidu semble être le meilleur. Il utilise les graphiques Canvas par défaut, qui sont meilleurs que SVG pour le traitement du 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 comprennent :Voyons d'abord comment l'utiliser. Nous faisons référence à certaines méthodes d'utilisation d'ECharts. Tout d'abord, nous transmettons la balise html pour afficher le graphique, puis appelons init et transmettons les données lors de l'initialisation.
var con=document.getElementById('container'); var chart=new Bar(con); Données sur l'axe des X : ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', "Décembre"] }, axe y :{//nom de l'axe y : "volume d'eau", formateur : "{valeur} ml" }, série :[//données de groupe{ name:'Précipitations à l'Est', data:[62,20,17,45,100,56,19,38,50,120,56,130] }, { name:'Précipitations à l'Ouest', data:[52,10,17 ,25 ,60,39,19,48,70,30,56,8] }, { nom : 'Précipitations du sud', data:[12,10,17,25,27,39,50,38,100,30,56,90] }, { color:'hsla(270,80%,60%,1)', name:'Précipitations du Nord Quantité', données :[12,30,17,25,7,39,49,38,60,30,56,10] } ] });
Pour la classe de base des graphiques, nous écrirons également des diagrammes circulaires et des graphiques linéaires plus tard, nous extrairons donc 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.H=600*2; this.padding=120; this.paddingTop=50; this.legend=[]; this.series=[]; //Résoudre le problème de flou de police en doublant la taille this.canvas.width=this.W; this.canvas.height=this.H; 2 + 'px'; ceci.canvas.style.hauteur = ceci.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('div'); this.tip.style.cssText='affichage : aucun ; position : absolue ; opacité : 0,5 ; couleur : #fff ; remplissage : 5 px ; : 99;'; ceci.container.appendChild(this.canvas); this.container.appendChild(this.tip); this.draw(); this.bindEvent(); } draw(){//Drawing} showInfo(){//Afficher les informations} animate(){//Effectuer une animation} showData(){//Afficher les données}
Dessiner l'axe XY
Dessinez d'abord le titre, puis l'axe XY, puis parcourez la série de données groupées, qui contient des calculs complexes, puis dessinez l'échelle de l'axe XY, dessinez les étiquettes de groupe et enfin dessinez les donné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 :
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,//Nombre d'unités de l'axe x, longueur de chaque unité, longueur totale de l'axe x yl=0,ys=0,ydis=H-padding* 2-paddingTop; //Le nombre d'unités de l'axe y, la longueur de chaque unité, la longueur totale de l'axe 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.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(); { ctx.fillText(this.yAxis.name,padding,padding+paddingTop-30 }); // Axe des x ctx.save(); ctx.beginPath(); ctx.translate(padding,H-padding); ctx.moveTo(0,0); ctx .Stroke(); // échelle de l'axe 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.AVC(); ctx.fillText(obj,x-xs/2,40); ctx.restore(); // axe y ctx.save(); ctx.beginPath(); ctx.strokeStyle='hsl(220,100%,50%)'; . 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; ;i++){ item=this.series[i]; if(!item.data||!item.data.length){ this.series.splice(i--,1);continue } // Attribuer des éléments sans couleur if(!item.color){ var hsl=i%2?180+20*i/2:20*(i-1); item.color='hsla('+hsl+',70% , 60%,1)'; } item.name=item.name||'unnamed'; // Dessine des étiquettes de regroupement ctx.save(); ctx.translate(padding+W/4,paddingTop+40); that.legend.push({ hide:item.hide||false, nom:item.name, couleur:item.color, x:padding+that.W /4+i*90+tw, y:paddingTop+40, w:60, h:30, r:5 }); 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.fillText(item.name,i*90+tw+70,26); tw+=ctx.measureText(item.name).width;//Calculer la longueur des caractères ctx.restore(); if(item.hide)continue; //Calculer les données sur l'échelle de l'axe Y if(!info){ info=calculerY( 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; //Dessiner l'échelle de l'axe Y ctx.save(); ) '; ctx.translate(padding,H-padding); for(var i=0;i<=yl;i++){ ctx.beginPath(); ctx.StrokeStyle='hsl(220,100%,50%)'; ctx.moveTo(-10,-Math.floor(ys*i)); ctx.lineTo(0,-Math.floor(ys*i)); ctx.Stroke(); ctx.beginPath(); ctx.StrokeStyle='hsla(0,0%,80%,1)'; ctx.moveTo(0,-Math.floor(ys*i)); ctx.lineTo(xdis,-Math.floor(ys*i)); ctx.textAlign='right'; Math.min(Math.floor(info.step*i),info.max); txt=this.yAxis.formatter?this.yAxis.formatter.replace('{value}',dim):dim; ctx.fillText(txt,-20,-ys*i+10 } ctx.restore()); ; //Dessiner les données this.showData(xl,xs,info.max }}données de tracé
Étant donné que l'élément de données doit ensuite être animé et affiché lorsque la souris passe dessus, il est placé dans la 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){ //Dessiner des données 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; // Développe les éléments de données et remplit la file d'attente d'animation pour (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 } });Exécuter une animation
Il n'y a pas grand chose à dire sur l'exécution de l'animation, c'est juste 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); .fillRect(that.padding+item.x,that.H-that.padding-item.y,item.w,item.y ctx.restore(); isStop=false; } } if(isStop)retour; requestAnimationFrame(run }())}Événement contraignant
É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 sur l'élément de données. Après avoir dessiné le chemin, appelez isPointInPath(x,y) Si c'est vrai, canvas.style.cursor='pointer' ; un élément de données, vous devez également redessiner la colonne, définir la transparence et la distinguer. Le contenu doit également être affiché. Voici un div positionné de manière absolue par rapport au conteneur parent. L'attribut tip a été établi lors de l'initialisation. Nous encapsulons la partie affichage dans la méthode showInfo.
Événement 2 : lorsque mousedown se produit, 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é, puis appelez la méthode draw. , remplacez le rendu et le dessin et effectuez une animation.
bindEvent(){ var that=this, canvas=this.canvas, ctx=this.ctx; this.canvas.addEventListener('mousemove',function(e){ var isLegend=false; // pos=WindowToCanvas(canvas,e .clientX,e.clientY); var box=canvas.getBoundingClientRect(); x:e.clientX-box.left, y:e.clientY-box.top }; // Étiquette de regroupement pour (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); Parce qu'il est doublé, les coordonnées sont *2 if(ctx.isPointInPath(pos.x*2,pos.y*2)){ canvas.style.cursor='pointer'; break; } canvas.style.cursor='default'; ctx.restore(); } if(isLegend) return; //Sélectionner les éléments de données pour (var) i=0,item,len=that.animateArr.length;i<len;i++){ item=that.animateArr[i]; ctx.fillStyle=item.color(); 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)){ //Effacer puis redessiner les graphiques avec une transparence de 0,5 ctx.clearRect(that.padding+item.x,that.H-that . padding-item.h,item.w,item.h); ctx.globalAlpha=0.5; ctx.fill(); that.showInfo(pos,item); ctx.restore(); break; } canvas.style.cursor='default'; that.tip.style.display='none'; ); ctx.restore(); } },false); this.canvas.addEventListener('mousedown',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); // Comme il est doublé, les coordonnées sont *2 if(ctx.isPointInPath(pos.x*2, pos .y*2)){ that.series[i].hide=!that.series[i].hide; that.animateArr.length=0; },false); } //Afficher les données showInfo(pos,obj){ var txt=this.yAxis.formatter?this.yAxis.formatter.replace('{value}',obj.num):obj.num; box=this.canvas.getBoundingClientRect(); var con=this.container.getBoundingClientRect(); this.tip.innerHTML = '<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' }Résumer
Ce qui est complété ici n'est qu'un effet de base. En fait, de nombreux domaines doivent être encore optimisés, tels que la prise en charge réactive, la prise en charge mobile, les effets d'animation, la prise en charge de plusieurs axes Y, les effets de contenu d'affichage et la prise en charge des fonctions polylignes. .
Ce qui précède représente l’intégralité du contenu de cet article. J’espère qu’il sera utile à l’étude de chacun. J’espère également que tout le monde soutiendra le réseau VeVb Wulin.