Круглый график на Canvas

от автора

Приветствую!

Совсем недавно для одного проекта мне понадобилось отображать проценты в круглых графиках(?)
И как обычно я принялся искать готовое решение в интернете, однако ничего путного найти не удалось (возможно из-за того что я точно не знаю как этот элемент правильно называется)
Более-менее то что мне нужно я нашел в библиотеке Knob, но его функционал оказался излишен, т.к изменять значения в графике нет необходимости, помимо этого в библиотеке затесался баг. В итоге пришлось сочинять очередной велосипед.

image

Сперва я смотрел в сторону css, ну сами посудите — круг делаем через border-radius, а border-color покажет сколько процентов… Разумеется это применимо к 0%, 25%, 50%, 75% и 100%:

html:

<div class="dial procent0"><p>0%</p></div> <div class="dial procent25"><p>25%</p></div> <div class="dial procent50"><p>50%</p></div> <div class="dial procent75"><p>75%</p></div> <div class="dial procent100"><p>100%</p></div> 

scss:

.dial { 	width: 80px; 	height: 80px; 	font-size: 20px; 	text-align: center; 	line-height: 80px; 	border: 6px solid #262832; 	border-radius: 100%; 	margin: 20px; 	display: inline-block; 	transform: rotate(-45deg); 	p { 		margin: 0; 		transform: rotate(45deg); 	} 	&.procent25 { 		border-right-color: #689f38; 	} 	&.procent50 { 		border-right-color: #689f38; 		border-bottom-color: #689f38; 	} 	&.procent75 { 		border-right-color: #689f38; 		border-bottom-color: #689f38; 		border-left-color: #689f38; 	} 	&.procent100 { 		border-color: #689f38; 	} } 

jsfiddle

Если рассуждать в этом ключе дальше то можно задействовать псевдоэлементы, и рисовать один круг над другим. т.е если нам нужно показать 33% то мы рисуем 2 круга по 25%, просто второй круг поворачиваем так что бы при наложении закрашенным оказалось 33% border, для этого нужно просто рассчитать на сколько градусов повернуть псевдоэлемент:

$procent*3,6-90$

html:

<div class="dial"><p>33%</p></div>

scss:

.dial { 	width: 80px; 	height: 80px; 	font-size: 20px; 	text-align: center; 	line-height: 80px; 	border: 6px solid #262832; 	border-radius: 100%; 	margin: 20px; 	display: inline-block; 	transform: rotate(-45deg); 	border-right-color: #1390d4; 	p { 		margin: 0; 		transform: rotate(45deg); 	} 	&::before { 		content: ''; 		display: block; 		position: absolute; 		width: 80px; 		height: 80px; 		border-radius: 100%; 		border: 6px solid transparent; 		border-right-color:#1390d4; 		margin: -6px -6px; 		transform: rotate(28.8deg); 	} } 

jsfiddle

Разумеется если нужно будет показать график 51% и больше то нужно будет закрасить border-bottom. Загвоздка остаётся в том что проценты в моём графике не статичны, а рисовать для каждого графика свой стиль — мягко говоря не правильно, а ведь возможны дробные значения… Тут то нам и понадобится JavaScript, правда доступа к псевдоэлементам в джаве нет ибо находятся они вне DOM-дерева и к ним нельзя обратиться как к простым HTML-элементам. Конечно можно ::before заменить на span и крутить его… Но если пришлось использовать JavaScript то тогда можно рисовать график на canvas, тем более что в canvas есть специальная функция arc — которая рисует окружности.

Всё что я писал выше, лишь для того что бы показать ход моих мыслей

Рисуем круг:

var c=document.getElementById("myCanvas"); var ctx=c.getContext("2d"); ctx.beginPath(); ctx.arc(100,75,50,0,2*Math.PI); ctx.stroke(); 

image
context.arc(x,y,r,sAngle,eAngle,counterclockwise);

На счёт x,y — всё понятно, r(радиус) тоже не вызывает вопросов, необязательная опция counterclockwise — говорит направление в котором рисовать окружность, по умолчанию false = по часовой, true = против часовой.
sAngle и eAngle это начальная и конечная точка на окружности в радианах, более понятно что такое радианы объяснит гифка:
image

Что бы перевести проценты в радианы нужно использовать формулу:

$radian=2*π*procent/100$

Собственно это и всё что нужно для того что бы нарисовать график.

html:

<div class="dial blue" data-width="180" data-lineWidth="41">66.233467</div>

scss:

.dial { 	border-color: #22262f; 	color: #689F38; 	display: inline-block; 	text-align: left; 	p { 		text-align:center; 		font-weight: bold; 		color: #fff; 		white-space: nowrap; 		position: relative; 		overflow: hidden; 		z-index: 1; 		margin: 0; 	} 	canvas { 		position: absolute; 	} } 

JavaScript + jQuery:

$(function(){ 	// Ищем все элементы с class="dial" 	var dials = $(".dial"); 	// Перебираем все .dial и пихуем туда canvas с графиком. 	for (i=0; i < dials.length; i++){ 		var width = (typeof $(dials[i]).attr("data-width") != 'undefined') ? Math.round($(dials[i]).attr("data-width")) : 80; 		var procent = (Number($(dials[i]).html()) > 0 && Number($(dials[i]).html()) < 100) ? Math.round(Number($(dials[i]).html()) * 10)/10 : 0; 		var lineWidth = (typeof $(dials[i]).attr("data-lineWidth") != 'undefined') ? Number($(dials[i]).attr("data-lineWidth")) : width / 10;                 if(lineWidth >= width) lineWidth = width+1; 		var size = width+lineWidth; 		var lineRound = (typeof $(dials[i]).attr("data-lineRound") != 'undefined') ? true : false; 		var borderColor = $(dials[i]).css("border-color"); 		var color = $(dials[i]).css("color"); 		// Меняем размер .dial в зависимости от data-width="80" 		// Устанавливаем размер шрифта так что бы он вмещался в круг не задевая border 		$(dials[i]).css({"width": size + 'px', "height": size + 'px', "font-size": Math.floor((width-lineWidth) / 4) + 'px'}); 		// Вставляем canvas такого же размера что и родитель. 		$(dials[i]).html('<canvas id="dial' + i + '" width="' + size + '" height="' + size + '"></canvas><p>' + procent + '%</p>'); 		// Выравниваем текст по вертикали 		$("p", dials[i]).css({"line-height": size + 'px'}); 		var canvas = document.getElementById("dial" + i);     var context = canvas.getContext("2d"); 		// считаем по формуле радианы 		var radian = 2*Math.PI*procent/100; 		// рисуем круг для фона 		context.arc(width/2+lineWidth/2, width/2+lineWidth/2, width/2, 0, 2*Math.PI, false); 		context.lineWidth = lineWidth; 		context.strokeStyle = borderColor; 		context.stroke(); 		context.beginPath(); 		// рисуем круг с процентами 		context.arc(width/2+lineWidth/2, width/2+lineWidth/2, width/2, 1.5 * Math.PI, radian+1.5 * Math.PI, false); 		context.strokeStyle = color; 		// Можно скруглить концы отрезка если передан параметр data-lineRound 		if (lineRound == true && lineWidth < width) context.lineCap = "round";     context.stroke(); 	} }); 

что бы добавить круглый график(?) нужно добавить на страницу:

<div class="dial">проценты (float)</div>

в качестве атрибутов можно добавить:
data-width — диаметр (по умолчанию 80)
data-lineWidth — ширина линии (по умолчанию 1/10 от диаметра)
* размер графика равен data-width + data-lineWidth
data-lineRound — округлять края отрезка.

процент внутри графика округляется до десятых, можно было и до сотых, но тогда шрифт получается совсем маленький, а шрифт напрямую зависит от размера графика.
Цвета графика я решил не добавлять в атрибуты, а записывать их в стили:

/* разные цветовые схемы */ .dial { 	&.error { 		color: #d46d71; 		p { 			color: #d46d71; 		} 	} 	&.success { 		color: #689F38; 		border-color: rgba(#d46d71, 0.4); 		p{ 			color: #689F38; 		} 	} 	&.blue { 		color: #1390d4; 	} } 

— на мой взгляд так удобнее, но всегда остаётся возможность указать цвет в style=«border-color: ***; color: ***»

Демо

Всем спасибо за внимание.
ссылка на оригинал статьи https://habrahabr.ru/post/325428/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *