Dieses Mal zeige ich Ihnen ein H5-Canvas-Diagramm zur Implementierung eines Histogramms. Was sind die Vorsichtsmaßnahmen für die Implementierung eines Histogramms in einem Canvas-Diagramm?
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:
Textzeichnung
XY-Achsenzeichnung;
Datengruppenzeichnung;
Verwendungsmethode
Werfen wir zunächst einen Blick auf die Verwendungsmethode einiger ECharts Übergeben Sie das html-Taganzuzeigen, rufen Sie dann init auf und übergeben Sie Daten während der Initialisierung.
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 normalerweise verwendet wird und die Grafiken nicht streckt. Der Zweck, den Text hier zunächst zu vergrößern und dann zu verkleinern, besteht darin, das Problem der Unschärfe beim Zeichnen von Text auf der Leinwand zu lösen.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] } ] });
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'; } }
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 zuerst den Titel, dann die XY-Achse, durchlaufen Sie dann die gruppierte Datenreihe, die komplexe Berechnungen enthält, und zeichnen Sie dann den Maßstab der XY-Achse. Zeichnen Sie die Gruppenbeschriftung und schließlich die aufgezeichneten Daten. 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 an nunamed vergeben 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:
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='hsla('+hsl+',70%,60%,1)'; } item.name=item.name||'unnamed'; // 画分组标签 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='left'; 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='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.stroke(); ctx.textAlign='right'; dim=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(); //画数据 this.showData(xl,xs,info.max); } }
Da das Datenelement nachfolgende Animationen ausführen und Inhalte anzeigen muss, wenn die Maus darüber gleitet, fügen Sie es ein Animationswarteschlange 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(); }
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); }()) }
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 des Pfads isPointInPath auf (x, 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. Hier muss auch ein p mit
absoluter Positionierungrelativ zum übergeordneten Container angezeigt werden. Es wurde bei der Initialisierung als Tip-Attribut festgelegt. Wir kapseln den Anzeigeteil in die showInfo-Methode. Ereignis 2: Bestimmen Sie beim Mousedown, 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('mousemove',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='pointer'; ctx.restore(); isLegend=true; break; } canvas.style.cursor='default'; 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='pointer'; that.showInfo(pos,item); ctx.restore(); break; } canvas.style.cursor='default'; that.tip.style.display='none'; ctx.globalAlpha=1; ctx.fill(); 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); // 因为缩小了一倍,所以坐标要*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('{value}',obj.num):obj.num; var 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'; }
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
Das obige ist der detaillierte Inhalt vonDas H5-Canvas-Diagramm implementiert ein Balkendiagramm. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!