Круглый график на Canvas
Приветствую!
Совсем недавно для одного проекта мне понадобилось отображать проценты в круглых графиках (?)
И как обычно я принялся искать готовое решение в интернете, однако ничего путного найти не удалось (возможно из-за того что я точно не знаю как этот элемент правильно называется)
Более-менее то что мне нужно я нашел в библиотеке Knob, но его функционал оказался излишен, т.к изменять значения в графике нет необходимости, помимо этого в библиотеке затесался баг. В итоге пришлось сочинять очередной велосипед.
Сперва я смотрел в сторону css, ну сами посудите — круг делаем через border-radius, а border-color покажет сколько процентов… Разумеется это применимо к 0%, 25%, 50%, 75% и 100%:
html:
0%
25%
50%
75%
100%
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, для этого нужно просто рассчитать на сколько градусов повернуть псевдоэлемент:
html:
33%
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();
context.arc (x, y, r, sAngle, eAngle, counterclockwise);
На счёт x, y — всё понятно, r(радиус) тоже не вызывает вопросов, необязательная опция counterclockwise — говорит направление в котором рисовать окружность, по умолчанию false = по часовой, true = против часовой.
sAngle и eAngle это начальная и конечная точка на окружности в радианах, более понятно что такое радианы объяснит гифка:
Что бы перевести проценты в радианы нужно использовать формулу:
Собственно это и всё что нужно для того что бы нарисовать график.
html:
66.233467
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('' + procent + '%
');
// Выравниваем текст по вертикали
$("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();
}
});
что бы добавить круглый график (?) нужно добавить на страницу:
проценты (float)
в качестве атрибутов можно добавить:
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: ***»
Демо
Всем спасибо за внимание.
Комментарии (2)
2 апреля 2017 в 08:56
+1↑
↓
Поздравляю, вы изобрели SVG:-)
2 апреля 2017 в 10:27
0↑
↓
Я пользуюсь вот этой библиотекой: https://github.com/kottenator/jquery-circle-progress
Позволяет использовать картинку как «заливку», что довольно удобно.Не рекламы ради, но можете посмотреть результат например тут:
https://sp-yuga.ru/s1313/