Usei uma biblioteca de gráficos há alguns dias, entre as quais o ECharts do Baidu parece ser o melhor. Ele usa o canvas por padrão e é melhor que o SVG no processamento de big data. Então também usarei o canvas para implementar uma biblioteca de gráficos. Não parece muito difícil. Vamos implementar primeiro um gráfico de barras simples.
O efeito é o seguinte: Os principais pontos de função incluem:Primeiro, vamos dar uma olhada em como usá-lo. Referimo-nos a alguns métodos de uso de ECharts. Primeiro, passamos a tag html para exibir o gráfico, depois chamamos o init e passamos os dados durante a inicialização.
var con=document.getElementById('container'); var chart=new Bar(con); dados do eixo x: ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'Dezembro'] }, eixo y:{//nome do eixo y:'volume de água', formatador:'{valor} ml' }, série:[//dados do grupo{ nome:'Precipitação no Leste', dados:[62,20,17,45,100,56,19,38,50,120,56,130] }, { nome:'Precipitação no Oeste', dados:[52,10,17 ,25 ,60,39,19,48,70,30,56,8] }, { nome:'Precipitação do sul', dados:[12,10,17,25,27,39,50,38,100,30,56,90] }, { cor:'hsla(270,80%,60%,1)', nome:'Precipitação do norte Quantidade', dados:[12,30,17,25,7,39,49,38,60,30,56,10] } ] });
Para a classe base do gráfico, também escreveremos gráficos de pizza e de linhas posteriormente, para extrair as partes comuns. Observe que canvas.style.width e canvas.width são diferentes. O primeiro esticará os gráficos, enquanto o último é o que usamos normalmente e não esticará os gráficos. O objetivo de escrever primeiro a expansão e depois a redução aqui é resolver o problema de desfoque ao desenhar texto na tela.
class Chart{construtor(container){ this.container=container; this.canvas=document.createElement('canvas'); this.H=600*2; this.padding=120; this.paddingTop=50; this.series=[]; //Resolva o problema de desfoque da fonte dobrando o tamanho this.canvas.width=this.W; 2 + 'px'; this.canvas.style.height = this.H/2 + 'px';
Para inicializar o histograma, chame Object.assign(this,opt) em es6. Isso é equivalente ao método extend em JQ, que copia as propriedades para a instância atual. Ao mesmo tempo, também é criado um atributo tip, que é uma tag html e é usado para exibir informações de dados posteriormente. Em seguida, desenhe os gráficos e vincule os eventos do mouse.
classe Bar estende Gráfico{ construtor(container){ super(container); this.xAxis={}; this.animateArr=[]; ; if(!this.container)return; this.container.style.position='relativo'; this.tip.style.cssText='display: nenhum; preenchimento: 5px; : 99;'; this.container.appendChild(this.canvas); this.container.appendChild(this.tip); this.draw(); this.bindEvent(); } draw(){//Desenho} showInfo(){//Exibir informações} animate(){//Executar animação} showData(){//Exibir dados}
Desenhar eixo XY
Primeiro desenhe o título, depois o eixo XY, depois percorra a série de dados agrupados, que contém cálculos complexos, depois desenhe a escala do eixo XY, desenhe os rótulos dos grupos e, finalmente, desenhe os dados. A série de itens de dados são dados agrupados, que correspondem a xAxis.data do eixo X um a um. Cada item pode ter nome e cor personalizados. Caso não seja especificado, o nome é dado a nunamed e a cor é gerada automaticamente. O atributo legend também é usado aqui para registrar as informações da lista de tags, porque é útil para cliques subsequentes do mouse para determinar se o clique está correto.
Principais pontos de conhecimento do canvas:
draw(){ var isso = isto, ctx = isto.ctx, canvas = isto.canvas, W = isto.W, H = isto.H, padding=this.padding, paddingTop=this.paddingTop, xl=0,xs=0,xdis=W-preenchimento*2,//Número de unidades do eixo x, comprimento de cada unidade, comprimento total do eixo x yl=0,ys=0,ydis=H-preenchimento* 2-paddingTop; //O número de unidades do eixo y, o comprimento de cada unidade, o comprimento total do eixo y ctx.fillStyle='hsla(0,0%,20%,1)'; ctx.strokeStyle='hsla(0,0%,10%,1)'; ctx.lineWidth=1; ctx.textAlign='center'; ctx.clearRect(0,0,W,H); if(this.title){ ctx.save(); ctx.textAlign='esquerda'; { ctx.fillText(this.yAxis.name,padding,padding+paddingTop-30 }); // eixo x ctx.save(); ctx.beginPath(); ctx .stroke(); // escala do eixo x if(this.xAxis&&(xl=this.xAxis.data.length)){ xs=(W-2*preenchimento)/xl; this.xAxis.data.forEach((obj,i)=>{ var x=xs*(i+1); ctx.moveTo(x,0); ctx. lineTo(x,10);ctx.stroke(); ctx.restore(); // eixo y ctx.save(); ctx.beginPath(); moveTo(0,0).lineTo(0,2*preenchimento+preenchimentoTop-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);continuar } // Atribuir itens sem cor 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'; // Desenha rótulos de agrupamento 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.fillStyle=item.color; ctx.strokeStyle=item.color; ();ctx.fillText(item.nome,i*90+tw+70,26); tw+=ctx.measureText(item.name).width;//Calcula o comprimento do caractere ctx.restore(); if(item.hide)continue; //Calcula os dados na escala do eixo Y if(!info){ info=calcularY( item.data.slice(0,xl) } curr=calcularY(item.data.slice(0,xl)); info=curr; } } if(!info) return; ys=ydis/yl; //Desenhar escala do eixo Y ctx.fillStyle='hsl(200,100%,60%; ) '; ctx.translate(preenchimento, preenchimento H for(var i=0;i<=yl;i++){ ctx.beginPath(); ctx.strokeStyle='hsl(220,100%,50%)'; ctx.stroke(); ctx.beginPath(); ctx.moveTo(0,-Math.floor(ys*i)); ctx.lineTo(xdis,-Math.floor(ys*i)); Math.min(Math.floor(info.step*i),info.max); txt=this.yAxis.formatter?this.yAxis.formatter.replace('{valor}',dim):dim ctx.fillText(txt,-20,-ys*i+10 } ctx.restore()); ; //Desenhe dados this.showData(xl,xs,info.max }}dados do gráfico
Como o item de dados precisa ser subsequentemente animado e exibido quando o mouse desliza sobre ele, ele é colocado na fila de animação animateArr. Aqui precisamos expandir os dados agrupados, converter as duas matrizes aninhadas anteriores em uma camada e calcular os atributos de cada item de dados, como nome, coordenada x, coordenada y, largura, velocidade e cor. Após a organização dos dados, a animação é executada.
showData(xl,xs,max){ //Desenha dados var that=this, ctx=this.ctx, ydis=this.H-this.padding*2-this.paddingTop, sl=this.series.filter(s= >!s.hide).comprimento, 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; // Expanda os itens de dados e preencha a fila de animação for(var i=0; ,item ,len=this.series.length;i<len;i++){ item=this.series[i]; item.data.slice(0,xl).forEach((d,j)=>{ h=d/max*ydis; x=xs*j+w*index+sp*(index+1); that.animateArr .push({ índice:i, nome:item.nome, 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 } });Executar animação
Não há muito a dizer sobre a execução da animação, é apenas uma função de fechamento autoexecutável. O princípio da animação é acumular sequencialmente o valor da velocidade vy no eixo y. Mas lembre-se que quando a fila termina de executar a animação, ela precisa ser parada, então existe um sinalizador isStop, que é julgado toda vez que a fila termina de executar.
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(aquele.padding+item.x,aquele.H-aquele.padding-item.y,item.w,item.y); isStop=false; } } if(isStop)return; requestAnimationFrame(run);Evento de vinculação
Evento 1: Ao mover o mouse, verifique se a posição do mouse está no rótulo do grupo ou no item de dados. Após desenhar o caminho, chame isPointInPath(x,y). um item de dados. Você também precisa redesenhar a coluna, definir a transparência e distingui-la. O conteúdo também precisa ser exibido. Aqui está um div que está absolutamente posicionado em relação ao contêiner pai. O atributo tip foi estabelecido durante a inicialização. Encapsulamos a parte de exibição no método showInfo.
Evento 2: Quando ocorre o mousedown, determine em qual rótulo de grupo o mouse clica e, em seguida, defina o atributo ocultar na série de dados do grupo correspondente. Se for verdade, significa que o item não será exibido e, em seguida, chame o método draw. , substitua a renderização e o desenho e execute a animação.
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 }; // Rótulo de agrupamento 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); Por ser duplicado, as coordenadas são *2 if(ctx.isPointInPath(pos.x*2,pos.y*2)){ canvas.style.cursor='pointer'; break; } canvas.style.cursor='default'; ctx.restore(); i=0,item,len=that.animateArr.length;i<len;i++){ item=that.animateArr[i]; ctx.rect(aquele.padding+item.x,aquele.H-aquele.padding-item.h,item.w,item.h); if(ctx.isPointInPath(pos.x*2,pos.y*2)){ //Limpa e redesenha os gráficos com uma transparência de 0,5 ctx.clearRect(that.padding+item.x,that.H-that .padding-item.h,item.w,item.h); that.showInfo(pos,item); );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 }; =that.legend.length;i<len;i++){ item=that.legend[i]; roundRect(ctx,item.x,item.y,item.w,item.h,item.r); // Como é duplicado, as coordenadas são *2 if(ctx.isPointInPath(pos.x*2, pos). .y*2)){ that.series[i].hide=!that.series[i].hide; that.animateArr.length=0; that.draw(); },false); } //Exibir dados 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';Resumir
O que é concluído aqui é apenas um efeito básico. Na verdade, há muitas áreas que precisam ser otimizadas ainda mais, como suporte responsivo, suporte móvel, efeitos de animação, suporte a vários eixos y, efeitos de conteúdo de exibição e suporte à função de polilinha. .
O texto acima é todo o conteúdo deste artigo. Espero que seja útil para o estudo de todos. Também espero que todos apoiem a Rede VeVb Wulin.