ฉันใช้ไลบรารีแผนภูมิเมื่อไม่กี่วันก่อน ซึ่ง ECharts ของ Baidu ดูเหมือนจะดีที่สุด โดยค่าเริ่มต้นจะใช้แผนภูมิ Canvas ดีกว่า SVG จากนั้นฉันก็จะใช้ Canvas เพื่อใช้งานไลบรารีแผนภูมิด้วย เรามาลองใช้แผนภูมิแท่งแบบง่ายๆ กันก่อน
ผลกระทบมีดังนี้: จุดทำงานหลัก ได้แก่ :ขั้นแรก มาดูวิธีการใช้งานกันก่อน เราอ้างถึงวิธีการใช้งาน ECharts บางอย่าง ขั้นแรก เราจะส่งผ่านแท็ก html เพื่อแสดงแผนภูมิ จากนั้นเรียก init และส่งผ่านข้อมูลระหว่างการกำหนดค่าเริ่มต้น
var con=document.getElementById('container'); var chart=new Bar(con); chart.init({ title:'กราฟปริมาณน้ำฝนตลอดทั้งปี', xAxis:{// ข้อมูลแกน x: ['มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน', 'พฤษภาคม', 'มิถุนายน', 'กรกฎาคม', 'สิงหาคม', 'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม'] }, yAxis:{//y-axis name:'water Volume', formatter:'{value} ml' }, series:[//Group data{ ชื่อ:'ปริมาณน้ำฝนในภาคตะวันออก', ข้อมูล:[62,20,17,45,100,56,19,38,50,120,56,130] }, { ชื่อ:'ปริมาณฝนในภาคตะวันตก' ข้อมูล:[52,10,17 ,25 ,60,39,19,48,70,30,56,8] }, { ชื่อ:'ฝนภาคใต้', ข้อมูล:[12,10,17,25,27,39,50,38,100,30,56,90] }, { color:'hsla(270,80%,60%,1)', ชื่อ:'ฝนภาคเหนือ ปริมาณ' ข้อมูล:[12,30,17,25,7,39,49,38,60,30,56,10] } ] -
สำหรับคลาสฐานแผนภูมิ เราจะเขียนแผนภูมิวงกลมและแผนภูมิเส้นในภายหลัง ดังนั้นเราจะแยกส่วนทั่วไปออก โปรดทราบว่า canvas.style.width และ canvas.width นั้นแตกต่างกัน โดยแบบแรกจะขยายกราฟิก ในขณะที่แบบหลังคือสิ่งที่เราใช้ตามปกติและจะไม่ขยายกราฟิก จุดประสงค์ของการเขียนการขยายครั้งแรกแล้วลดขนาดที่นี่คือเพื่อแก้ปัญหาการเบลอเมื่อวาดข้อความบนผืนผ้าใบ
แผนภูมิคลาส { ตัวสร้าง (คอนเทนเนอร์) { 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.title=''; this.series=[]; //แก้ปัญหาแบบอักษรเบลอโดยเพิ่มขนาดเป็นสองเท่า this.canvas.width=this.W; this.canvas.height=this.H; 2 + 'px'; this.canvas.style.height = this.H/2 + 'px';
ในการเริ่มต้นฮิสโตแกรม ให้เรียก Object.assign(this,opt) ใน es6 ซึ่งเทียบเท่ากับวิธีการขยายใน JQ ซึ่งคัดลอกคุณสมบัติไปยังอินสแตนซ์ปัจจุบัน ในเวลาเดียวกัน คุณลักษณะ tip ก็ถูกสร้างขึ้นเช่นกัน ซึ่งเป็นแท็ก html และใช้เพื่อแสดงข้อมูลข้อมูลในภายหลัง จากนั้นจึงวาดกราฟิกและผูกเหตุการณ์ของเมาส์
class Bar ขยายแผนภูมิ { ตัวสร้าง (คอนเทนเนอร์) { super (คอนเทนเนอร์); this.xAxis={}; this.yAxis=[]; this.animateArr=[]; ; if(!this.container)return; this.container.style.position='relative'; this.tip=document.createElement('div'); this.tip.style.cssText='display: none; ตำแหน่ง: สัมบูรณ์; ความทึบ: 0.5; พื้นหลัง: #000; สี: #fff; ช่องว่างภายใน: 5px; : 99;'; this.container.appendChild(this.canvas); this.container.appendChild(this.tip); this.draw(); this.bindEvent(); } วาด(){//การวาดภาพ} showInfo(){//แสดงข้อมูล} เคลื่อนไหว(){//แสดงภาพเคลื่อนไหว} showData(){//แสดงข้อมูล}
วาดแกน XY
ขั้นแรก วาดชื่อเรื่อง จากนั้นวาดแกน XY จากนั้นสำรวจชุดข้อมูลที่จัดกลุ่มซึ่งมีการคำนวณที่ซับซ้อน จากนั้นวาดมาตราส่วนของแกน XY วาดป้ายกำกับกลุ่ม และสุดท้ายวาดข้อมูล ชุดรายการข้อมูลเป็นข้อมูลที่จัดกลุ่ม ซึ่งสอดคล้องกับ xAxis.data ของแกน X แบบหนึ่งต่อหนึ่ง แต่ละรายการสามารถมีชื่อและสีที่กำหนดเองได้ หากไม่ได้ระบุ ชื่อจะถูกกำหนดให้กับ nunamed และสร้างสีโดยอัตโนมัติ คุณลักษณะคำอธิบายแผนภูมิยังใช้ที่นี่เพื่อบันทึกข้อมูลรายการแท็ก เนื่องจากมีประโยชน์สำหรับการคลิกเมาส์ครั้งต่อไปเพื่อตรวจสอบว่าการคลิกนั้นถูกต้องหรือไม่
จุดความรู้หลักของผืนผ้าใบ:
วาด () { 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.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(); } ถ้า(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 . stroke(); // สเกลแกน x if(this.xAxis&&(xl=this.xAxis.data.length)){ xs=(W-2*ช่องว่างภายใน)/xl; this.xAxis.data.forEach((obj,i)=>{ var x=xs*(i+1); ctx.moveTo(x,0); ctx. lineTo(x,10); ctx.จังหวะ(); ctx.fillText(obj,x-xs/2,40 }); ctx.restore(); // 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,สลัว,ข้อมูล,รายการ,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);ดำเนินการต่อ; กำหนดรายการที่ไม่มีสี 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({ ซ่อน:item.hide||false, ชื่อ:item.name, สี: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.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)); ถ้า(curr.max>info.max){ info=curr; } } if(!info) return; yl=info.num; //วาดมาตราส่วนแกน 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.จังหวะ(); 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}',สลัว):สลัว; ctx.fillText(txt,-20,-ys*i+10); } ctx.restore() ; //วาดข้อมูล this.showData(xl,xs,info.max);ข้อมูลพล็อต
เนื่องจากรายการข้อมูลจำเป็นต้องเคลื่อนไหวในภายหลังและแสดงเมื่อเลื่อนเมาส์ไปเหนือรายการข้อมูล รายการข้อมูลนั้นจะถูกใส่ลงในคิวแอนิเมชัน animateArr ที่นี่เราจำเป็นต้องขยายข้อมูลที่จัดกลุ่ม แปลงอาร์เรย์ที่ซ้อนกันสองตัวก่อนหน้าให้เป็นชั้นเดียว และคำนวณคุณลักษณะของแต่ละรายการข้อมูล เช่น ชื่อ พิกัด x พิกัด y ความกว้าง ความเร็ว และสี หลังจากจัดระเบียบข้อมูลแล้ว ภาพเคลื่อนไหวก็จะถูกดำเนินการ
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; // ขยายรายการข้อมูลและกรอกข้อมูลในคิวแอนิเมชันสำหรับ(var i=0 ,รายการ ,len=this.series.length;i<len;i++){ item=this.series[i]; ถ้า(item.hide)ดำเนินการต่อ; item.data.slice(0,xl).forEach((d,j)=>{ h=d/max*ydis; x=xs*j+w*index+sp*(ดัชนี+1); that.animateArr .push({ index:i, ชื่อ: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, สี:item.color }); ดัชนี ++; } this.animate();}ดำเนินการภาพเคลื่อนไหว
ไม่มีอะไรจะพูดมากนักเกี่ยวกับการสร้างแอนิเมชัน มันเป็นเพียงฟังก์ชันปิดที่ดำเนินการเอง หลักการของแอนิเมชันคือการสะสมค่าความเร็ว vy บนแกน y ตามลำดับ แต่โปรดจำไว้ว่าเมื่อคิวดำเนินการภาพเคลื่อนไหวเสร็จแล้ว จะต้องหยุดการทำงานดังกล่าว จึงมีธง isStop ซึ่งจะตัดสินทุกครั้งที่คิวดำเนินการเสร็จสิ้น
animate(){ var that=this, ctx=this.ctx, isStop=true; (ฟังก์ชั่น 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; } อื่น ๆ { 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; } } ถ้า(isStop)return;เหตุการณ์ผูกพัน
เหตุการณ์ที่ 1: เมื่อเลื่อนเมาส์ ให้ตรวจสอบว่าตำแหน่งเมาส์อยู่บนป้ายกำกับกลุ่มหรือรายการข้อมูล หลังจากวาดเส้นทางแล้ว ให้เรียก isPointInPath(x,y) หากเป็นเช่นนั้น รายการข้อมูล คุณต้องวาดคอลัมน์ใหม่ ตั้งค่าความโปร่งใส และแยกแยะความแตกต่าง จำเป็นต้องแสดงเนื้อหาด้วย นี่คือ div ที่อยู่ในตำแหน่งที่แน่นอนโดยสัมพันธ์กับคอนเทนเนอร์หลัก แอตทริบิวต์ tip ได้รับการสร้างขึ้นระหว่างการเริ่มต้น เราสรุปส่วนการแสดงผลไว้ในเมธอด showInfo
เหตุการณ์ที่ 2: เมื่อเมาส์ดาวน์เกิดขึ้น ให้พิจารณาว่ากลุ่มใดที่เมาส์คลิก จากนั้นตั้งค่าแอตทริบิวต์ซ่อนในชุดข้อมูลกลุ่มที่เกี่ยวข้อง ถ้ามันเป็นจริง หมายความว่ารายการนั้นจะไม่ปรากฏขึ้น จากนั้นเรียกวิธีการวาด แทนที่การเรนเดอร์และการวาดภาพ และดำเนินการแอนิเมชัน
bindEvent(){ var that=this, canvas=this.canvas, ctx=this.ctx; this.canvas.addEventListener('mousemove',function(e){ var isLegend=false; // pos=WindowToCanvas(ผ้าใบ,e .clientX,e.clientY); var box=canvas.getBoundingClientRect(); x:e.clientX-box.left, y:e.clientY-box.top }; // ป้ายกำกับการจัดกลุ่มสำหรับ (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='ctx.restore(); isLegend=true ; break; } canvas.style.cursor='default'; ctx.restore(); } if(isLegend) กลับ; //เลือกรายการข้อมูลสำหรับ(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)){/ //ล้างแล้ววาดกราฟิกใหม่ด้วยความโปร่งใส 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='ตัวชี้'; that.showInfo(pos,item); ctx.restore(); } canvas.style.cursor='default'; that.tip.style.display='none'; ctx.restore(); } },false); this.canvas.addEventListener('mousedown',ฟังก์ชั่น(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); // เนื่องจากเป็นสองเท่า พิกัดจึงเป็น *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); } //แสดงข้อมูล showInfo(pos,obj){ var txt=this.yAxis.formatter?this.yAxis.formatter.replace('{value}',obj.num):obj.num; กล่อง=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='บล็อก';สรุป
สิ่งที่เสร็จสมบูรณ์ที่นี่เป็นเพียงเอฟเฟกต์พื้นฐานเท่านั้น จริงๆ แล้วมีหลายพื้นที่ที่ต้องได้รับการปรับปรุงเพิ่มเติม เช่น การสนับสนุนแบบตอบสนอง การสนับสนุนบนมือถือ เอฟเฟกต์ภาพเคลื่อนไหว การสนับสนุนแบบหลายแกน y เอฟเฟกต์เนื้อหาที่แสดง และการสนับสนุนฟังก์ชันโพลีไลน์ .
ข้างต้นคือเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการศึกษาของทุกคน ฉันหวังว่าทุกคนจะสนับสนุน VeVb Wulin Network