Utilicé una biblioteca de gráficos hace unos días, entre los cuales ECharts de Baidu parece ser el mejor. Utiliza lienzos de forma predeterminada que son mejores que SVG para procesar grandes datos. Luego, también usaré lienzo para implementar una biblioteca de gráficos. No parece demasiado difícil, primero implementemos un gráfico de barras.
El efecto es el siguiente: Los puntos de función principales incluyen:Primero, echemos un vistazo a cómo usarlo. Nos referimos a algunos métodos de uso de ECharts. Primero, pasamos la etiqueta html para mostrar el gráfico, luego llamamos a init y pasamos los datos durante la inicialización.
var con=document.getElementById('container'); var chart=new Bar(con); datos del eje x: ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'Diciembre'] }, eje y:{//nombre del eje y:'volumen de agua', formateador:'{valor} ml' }, serie:[//Datos del grupo{ nombre:'Precipitación en el Este', datos:[62,20,17,45,100,56,19,38,50,120,56,130] }, { nombre:'Precipitación en el Oeste', datos:[52,10,17 ,25 ,60,39,19,48,70,30,56,8] }, { nombre:'Precipitación del sur', data:[12,10,17,25,27,39,50,38,100,30,56,90] }, { color:'hsla(270,80%,60%,1)', nombre:'Precipitación del norte Cantidad', datos:[12,30,17,25,7,39,49,38,60,30,56,10] } ] });
Para la clase base del gráfico, también escribiremos gráficos circulares y gráficos de líneas más adelante, por lo que extraeremos las partes comunes. Tenga en cuenta que canvas.style.width y canvas.width son diferentes. El primero estirará los gráficos, mientras que el segundo es lo que usamos normalmente y no estirará los gráficos. El propósito de escribir aquí primero expansión y luego reducción es resolver el problema de la borrosidad al dibujar texto en un lienzo.
gráfico de clase{ constructor(contenedor){ 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.series=[]; // Resuelve el problema de desenfoque de fuente duplicando el tamaño this.canvas.width=this.W; 2 + 'px'; este.canvas.style.height = esto.H/2 + 'px';
Para inicializar el histograma, llame a Object.assign(this,opt) en es6. Esto es equivalente al método extend en JQ, que copia las propiedades a la instancia actual. Al mismo tiempo, también se crea un atributo de sugerencia, que es una etiqueta html y se utiliza para mostrar información de datos más adelante. Luego dibuje los gráficos y vincule los eventos del mouse.
barra de clase extiende Gráfico{ constructor(contenedor){ super(contenedor); this.xAxis={}; this.yAxis=[]; this.animateArr=[]; ; if(!this.container)return; this.container.style.position='relativo'; this.tip=document.createElement('div'); this.tip.style.cssText='pantalla: ninguna; posición: absoluta; opacidad: 0,5; tamaño de fuente: 8px; : 99;'; este.contenedor.appendChild(este.canvas); este.contenedor.appendChild(este.tip); this.draw(); this.bindEvent(); } draw(){//Dibujo} showInfo(){//Mostrar información} animate(){//Realizar animación} showData(){//Mostrar datos}
Dibujar el eje XY
Primero dibuje el título, luego el eje XY, luego recorra la serie de datos agrupados, que contiene cálculos complejos, luego dibuje la escala del eje XY, dibuje las etiquetas del grupo y finalmente dibuje los datos. La serie de elementos de datos son datos agrupados, que corresponden a xAxis.data del eje X uno a uno. Cada elemento puede tener un nombre y color personalizados. Si no se especifica, el nombre se asigna a nunamed y el color se genera automáticamente. El atributo de leyenda también se utiliza aquí para registrar la información de la lista de etiquetas, porque es útil para que los clics posteriores del mouse determinen si el clic es correcto.
Principales puntos de conocimiento del lienzo:
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,//Número de unidades del eje x, longitud de cada unidad, longitud total del eje x yl=0,ys=0,ydis=H-padding* 2-paddingTop; // El número de unidades del eje y, la longitud de cada unidad, la longitud total del eje 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(este.título){ ctx.save(); ctx.textAlign='izquierda'; ctx.font='bold 40px arial'; ctx.fillText(this.title,padding-50,70); {ctx.fillText(this.yAxis.name,padding,padding+paddingTop-30); // eje x ctx.save(); ctx.beginPath(); ctx.translate(padding,H-padding); ctx .stroke(); // escala del eje 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(); // eje y ctx.save(); ctx.beginPath(); ctx.strokeStyle='hsl(220,100%,50%)'; moverA(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++){ elemento=this.series[i]; if(!item.data||!item.data.length){ this.series.splice(i--,1);continuar } // Asignar elementos sin color 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'; // Dibujar etiquetas de agrupación ctx.save(); ctx.translate(padding+W/4,paddingTop+40); that.legend.push({ hide:item.hide||false, nombre: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; roundRect(ctx,i*90+tw,0,60,30,5); (); ctx.fillText(elemento.nombre,i*90+tw+70,26); tw+=ctx.measureText(item.name).width;//Calcular la longitud del carácter ctx.restore(); if(item.hide)continue //Calcular los datos en la escala del eje Y if(!info){ info=calcularY( item.data.slice(0,xl)); } curr=calcularY(item.data.slice(0,xl)); if(curr.max>info.max){ info=curr; } } if(!info) return; yl=info.num; ys=ydis/yl; //Dibujar la escala del eje Y 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.textAlign='derecha'; Math.min(Math.floor(info.paso*i),info.max); txt=this.yAxis.formatter?this.yAxis.formatter.replace('{valor}',dim):dim; ctx.fillText(txt,-20,-ys*i+10); ; //Dibujar datos this.showData(xl,xs,info.max }}datos de la trama
Debido a que el elemento de datos debe animarse y mostrarse posteriormente cuando el mouse se desliza sobre él, se coloca en la cola de animación animateArr. Aquí necesitamos expandir los datos agrupados, convertir las dos matrices anidadas anteriores en una capa y calcular los atributos de cada elemento de datos, como nombre, coordenada x, coordenada y, ancho, velocidad y color. Una vez organizados los datos, se ejecuta la animación.
showData(xl,xs,max){ //Dibujar datos 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 // Expande los elementos de datos y completa la cola de animación for(var i=0; ,elemento,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, nombre:item.nombre, 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 } });Ejecutar animación
No hay mucho que decir sobre la ejecución de la animación, es solo una función de cierre autoejecutable. El principio de la animación es acumular secuencialmente el valor de velocidad vy en el eje y. Pero recuerde que cuando la cola termina de ejecutar la animación, es necesario detenerla, por lo que hay un indicador isStop, que se juzga cada vez que la cola termina de ejecutarse.
animate(){ var that=this, ctx=this.ctx, isStop=true; (función 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(ese.padding+item.x,that.H-that.padding); .fillRect(ese.padding+item.x,ese.H-ese.padding-item.y,item.w,item.y); isStop=false; } } if(isStop)return; requestAnimationFrame(ejecutar}())}Evento vinculante
Evento 1: cuando mueva el mouse, verifique si la posición del mouse está en la etiqueta del grupo o en el elemento de datos. Después de dibujar la ruta, llame a isPointInPath(x,y). Si es verdadero, canvas.style.cursor='pointer'; un elemento de datos. También debe volver a dibujar la columna, establecer la transparencia y distinguirla. El contenido también debe mostrarse. Aquí hay un div que está absolutamente posicionado en relación con el contenedor principal. El atributo tip se estableció durante la inicialización. Encapsulamos la parte de visualización en el método showInfo.
Evento 2: cuando se produce el descenso del mouse, determine en qué etiqueta de grupo hace clic el mouse y luego configure el atributo ocultar en la serie de datos del grupo correspondiente. Si es verdadero, significa que el elemento no se mostrará y luego llame al método de dibujo. , anular el renderizado y el dibujo, y realizar animación.
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 }; // Etiqueta de agrupación for(var i=0,item,len=that.legend.length;i<len;i++){ item =esa.leyenda[i]; ctx.save(); roundRect(ctx,item.x,item.y,item.w,item.h,item.r); Debido a que está duplicado, las coordenadas son *2 if(ctx.isPointInPath(pos.x*2,pos.y*2)){ canvas.style.cursor='pointer'; isLegend=true; break; } canvas.style.cursor='default'; ctx.restore(); } if(isLegend) //Seleccionar elementos de datos para(var) i=0,item,len=that.animateArr.length;i<len;i++){ item=that.animateArr[i]; ctx.rect(ese.relleno+elemento.x,ese.H-ese.relleno-elemento.h,elemento.w,elemento.h); if(ctx.isPointInPath(pos.x*2,pos.y*2)){ //Borrar y luego volver a dibujar los gráficos con una transparencia de 0,5 ctx.clearRect(that.padding+item.x,that.H-that .relleno-item.h,item.w,item.h); ctx.globalAlpha=0.5; that.showInfo(pos,item); ctx.restore(); break; } canvas.style.cursor='default'; that.tip.style.display='none'; ); ctx.restore(); } },falso); this.canvas.addEventListener('mousedown',función(e){ e.preventDefault(); var box=canvas.getBoundingClientRect(); var pos = { x:e.clientX-box.left, y:e.clientY-box.top }; =esa.leyenda.longitud;i<len;i++){ elemento=esa.leyenda[i]; roundRect(ctx,item.x,item.y,item.w,item.h,item.r); // Debido a que está duplicado, las coordenadas son *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 } //Mostrar datos 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
Lo que se completa aquí es solo un efecto básico. De hecho, hay muchas áreas que deben optimizarse aún más, como soporte de respuesta, soporte móvil, efectos de animación, soporte de múltiples ejes, efectos de visualización de contenido y soporte de función de polilínea. .
Lo anterior es el contenido completo de este artículo. Espero que sea útil para el estudio de todos. También espero que todos apoyen VeVb Wulin Network.