Несколько дней назад я использовал библиотеку диаграмм, среди которых ECharts от Baidu кажется лучшей. Она по умолчанию использует холст. Диаграммы Canvas лучше, чем SVG, при обработке больших данных. Затем я также воспользуюсь холстом для реализации библиотеки диаграмм. Давайте сначала реализуем простую гистограмму.
Эффект следующий: Основные функциональные точки включают в себя:Сначала давайте посмотрим, как его использовать. Мы обратимся к некоторым методам использования ECharts. Сначала мы передаем тег html для отображения диаграммы, затем вызываем init и передаем данные во время инициализации.
var con=document.getElementById('container'); varchart=new Bar(con);chart.init({title:'Гистограмма осадков в течение года', xAxis:{// данные по оси X: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'] }, yAxis:{//имя оси Y:'объем воды', форматтер:'{value} ml' }, series:[//Group data{ 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:'Северные осадки Количество', данные:[12,30,17,25,7,39,49,38,60,30,56,10] } ] });
Для базового класса диаграммы позже мы также напишем круговые и линейные диаграммы, поэтому выделим общие части. Обратите внимание, что Canvas.style.width и Canvas.width разные. Первый растягивает графику, а второй мы обычно используем и не растягиваем графику. Цель написания здесь сначала расширения, а затем уменьшения — решить проблему размытия при рисовании текста на холсте.
класс Chart {конструктор(контейнер){ this.container=container; this.canvas=document.createElement('canvas'); this.ctx=this.canvas.getContext('2d'); this.W=1000*2; this.H=600*2; 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 + 'пикселей'; this.canvas.style.height = this.H/2 + 'px' } };
Чтобы инициализировать гистограмму, вызовите Object.assign(this,opt) в es6. Это эквивалентно методу расширения в JQ, который копирует свойства в текущий экземпляр. В то же время также создается атрибут Tip, который представляет собой HTML-тег и используется для последующего отображения информации о данных. Затем нарисуйте графику и привяжите события мыши.
класс Bar расширяет Chart { конструктор (контейнер) { super (container); this.xAxis = {}; 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 = 'display: none; позиция: абсолютная; непрозрачность: #000; цвет: #fff; радиус границы: 5 пикселей; размер шрифта: 8 пикселей; : 99;'; this.container.appendChild(this.canvas); this.container.appendChild(this.tip); this.draw(); this.bindEvent(); } draw(){//Drawing} showInfo(){//Отобразить информацию} animate(){//Выполнить анимацию} showData(){//Отобразить данные}
Нарисовать ось XY
Сначала нарисуйте заголовок, затем ось XY, затем пройдите по сгруппированному ряду данных, который содержит сложные вычисления, затем нарисуйте масштаб оси XY, нарисуйте метки групп и, наконец, нарисуйте данные. Серия элементов данных представляет собой сгруппированные данные, которые соответствуют xAxis.data оси X один к одному. Каждый элемент может иметь собственное имя и цвет. Если не указано иное, имя присваивается nunamed, а цвет генерируется автоматически. Атрибут легенды также используется здесь для записи информации списка тегов, поскольку он полезен при последующих щелчках мыши, чтобы определить, является ли щелчок правильным.
Основные знания о холсте:
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-заполнение*2,//Количество единиц оси X, длина каждой единицы, общая длина оси X yl=0,ys=0,ydis=H-заполнение* 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='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); } 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.strokeStyle='hsl(220,100%,50%)'; ctx.translate(padding,H-padding); . 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({ скрыть: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.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; 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.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()); ; //Рисуем данные 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; // Разворачиваем элементы данных и заполняем очередь анимации for(var i=0; ,item ,len=this.series.length;i<len;i++){ item=this.series[i]; if(item.hide)продолжить; item.data.slice(0,xl).forEach((d,j)=>{ h=d/max*ydis; x=xs*j+w*index+sp*(index+1); that.animateArr .push({ индекс:i, имя:item.name, число: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();}Выполнить анимацию
О выполнении анимации сказать особо нечего, это просто самовыполняющаяся функция закрытия. Принцип анимации заключается в последовательном накоплении значения скорости vy по оси y. Но помните, что когда очередь завершает выполнение анимации, ее необходимо остановить, поэтому существует флаг isStop, который оценивается каждый раз, когда очередь завершает выполнение.
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; .fillRect(that.padding+item.x,that.H-that.padding-item.y,item.w,item.y); isStop = false; }} Если (isStop) return; requestAnimationFrame (запустить);Привязывающее событие
Событие 1: при перемещении мыши проверьте, находится ли позиция мыши на метке группы или на элементе данных. После рисования пути вызовите isPointInPath(x,y). Если это правда, Canvas.style.cursor='pointer'; элемент данных. Вам также необходимо перерисовать столбец, установить прозрачность и выделить его. Содержимое также должно отображаться. Вот элемент 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(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'; isLegend=true ; перерыв; } Canvas.style.cursor='default'; ctx.restore(); } if(isLegend) return; //Выбираем элементы данных для(var). я=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; Canvas.style.cursor='pointer'; that.showInfo(pos,item); перерыв } Canvas.style.cursor='default'; that.tip.style.display='none'; ctx.globalAlpha=1; ); 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); // Поскольку он удвоен, координаты равны *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; box=this.canvas.getBoundingClientRect(); вар 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';Подвести итог
То, что здесь завершено, — это только базовый эффект. На самом деле есть много областей, которые нуждаются в дальнейшей оптимизации, такие как адаптивная поддержка, поддержка мобильных устройств, анимационные эффекты, поддержка нескольких осей Y, эффекты отображения контента и поддержка функций полилиний. .
Выше приведено все содержание этой статьи. Я надеюсь, что она будет полезна для изучения всеми. Я также надеюсь, что все поддержат сеть VeVb Wulin.