Круговая и кольцевая svg диаграммы на HTML5 с нуля. Как быстро построить круговую диаграмму на CSS и JavaScript

Обратите внимание, что в этой главе мы будем загружать код напрямую с локального жёсткого диска, а не через веб-сервер. Вам, возможно, потребуется отключить безопасность в Chrome на время разработки. Если в Chrome у вас возникли проблемы с загрузкой изображений или других файлов непосредственно с диска, попробуйте добавить некоторые флаги безопасности в командной строке.

На Mac OS X это будет так:

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --allow-file-access-from-files --disable-web-security

Chromium-browser --disable-web-security

Chrome.exe --disable-web-security

Кроме того, вы можете загрузить страницы через локальный веб-сервер.

В этой главе мы отобразим некоторые данные путём рисования гистограммы. Это покажет вам основы рисования линий, фигур и текста, затем мы сделаем круговую диаграмму с градиентом.

Создание новой страницы

Начните с создания нового текстового файла с именем barchart.html и наберите в нём:

var data = [ 16, 68, 20, 30, 54 ];

Страница выше содержит элементы и . Элемент представляет собой прямоугольник на экране, в котором будет происходить рисование. width и height определяют, насколько он будет большой. это блочный элемент похожий на , так что вы можете стилизовать его или позиционировать так же, как и всё остальное на странице.

Переменная data в скрипте хранит набор точек данных, которые мы отобразим в гистограмме.

Теперь получим указатель на холст и зальём фон серым цветом. Добавьте это в скрипт после переменной data .

// получаем указатель на холст var canvas = document.getElementById("canvas"); // получаем указатель на контекст рисования var c = canvas.getContext("2d"); // рисуем c.fillStyle = "gray"; c.fillRect(0,0,500,500);

Добавляем данные

Теперь вы можете нарисовать некоторые данные. Сделайте это пробегаясь в цикле по массиву data . Для каждой точки данных заливаем прямоугольник, координата х которого определяется индексом массива, а высота - его значением.

Результат:


Добавляем текст внутрь диаграммы

Это не была бы кольцевая диаграмма без текста внутри и посередине; для этого и предназначена дыра, правильно? Что ж, добавить текст просто. Мы просто используем элемент , который нативен для SVG-фигур.

10 Beers

Вы заметите, что я разделил текст диаграммы на 2 элемента и сгруппировал их (). Это было сделано затем, чтобы мы могли легче наложить заголовок и число друг на друга, а также расположить их, как одну единицу. Я настроил атрибуты X и Y так, чтобы начать с центра фигуры на линии основания и выровнить их по левому краю.


Как вы можете увидеть, позиционирование не совсем верно, так что нам надо будет украсить его капелькой CSS:

@import url(https://fonts.googleapis.com/css?family=Montserrat:400); .chart-text { font: 16px/1.4em "Montserrat", Arial, sans-serif; fill: #000; -moz-transform: translateY(0.25em); -ms-transform: translateY(0.25em); -webkit-transform: translateY(0.25em); transform: translateY(0.25em); } .chart-number { font-size: 0.6em; line-height: 1; text-anchor: middle; -moz-transform: translateY(-0.25em); -ms-transform: translateY(-0.25em); -webkit-transform: translateY(-0.25em); transform: translateY(-0.25em); } .chart-label { font-size: 0.2em; text-transform: uppercase; text-anchor: middle; -moz-transform: translateY(0.7em); -ms-transform: translateY(0.7em); -webkit-transform: translateY(0.7em); transform: translateY(0.7em); }

Для начала, давайте добавим шрифт Montserrat (просто потому, что он мне нравится). Затем нам надо скорректировать font-size ("размер шрифта") и line-height ("высота строки"). После добавления " translateY " в 0.25em текст немного выравнивается по вертикали, но на этом мы еще не закончили.

Нам надо сделать оба текстовых блока меньше и выровнять их до центра, корректируя font-size дотуда, докуда нам нравится (0.6em для более крупного числа и 0.2em для названия выглядят, что надо) и используя свойство " text-anchor " со значением " middle ".

Теперь текст находится в центре. Все, что осталось, это сделать его в верхнем регистре (это легко) и расположить его индивидуальные элементы, используя " translateY ", чтобы они сложились должным образом. И вот что мы имеем сейчас:


Проблемы с доступностью

Как я уже упомянул ранее, контент SVG является частью DOM, так что он по своей сути более доступный. Это позволяет невероятно легко предоставлять дополнительный контекст для программ, читающих с экрана, через атрибуты и элементы типа заголовков.

Первый уровень проблем с доступностью - сделать SVG-диаграмму более семантической, обернув ее в элемент (поскольку это - диаграмма). Как фигура, мы можем воспользоваться элементом , чтобы предоставить заголовок или описание для кольцевой диаграммы.

Помимо этого, мы можем добавить контент-теги и (которые нативны для SVG) и связать их с заголовками ARIA, ID и ролью "предоставления большего количества контекста для программ чтения с экрана".

@import url(https://fonts.googleapis.com/css?family=Montserrat:400); body { font: 16px/1.4em "Montserrat", Arial, sans-serif; } * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .chart-text { /*font: 16px/1.4em "Montserrat", Arial, sans-serif;*/ fill: #000; -moz-transform: translateY(0.25em); -ms-transform: translateY(0.25em); -webkit-transform: translateY(0.25em); transform: translateY(0.25em); } .chart-number { font-size: 0.6em; line-height: 1; text-anchor: middle; -moz-transform: translateY(-0.25em); -ms-transform: translateY(-0.25em); -webkit-transform: translateY(-0.25em); transform: translateY(-0.25em); } .chart-label { font-size: 0.2em; text-transform: uppercase; text-anchor: middle; -moz-transform: translateY(0.7em); -ms-transform: translateY(0.7em); -webkit-transform: translateY(0.7em); transform: translateY(0.7em); } figure { display: flex; justify-content: space-around; flex-direction: column; margin-left: -15px; margin-right: -15px; } @media (min-width: 768px) { figure { flex-direction: row; } } .figure-content, .figure-key { flex: 1; padding-left: 15px; padding-right: 15px; align-self: center; } .figure-content svg { height: auto; } .figure-key { min-width: calc(8 / 12); } .figure-key { margin-right: 6px; } .figure-key-list { margin: 0; padding: 0; list-style: none; } .figure-key-list li { margin: 0 0 8px; padding: 0; } .shape-circle { display: inline-block; vertical-align: middle; width: 32px; height: 32px; -webkit-border-radius: 50%; -moz-border-radius: 50%; border-radius: 50%; } .shape-fuschia { background-color: #ce4b99; } .shape-lemon-lime { background-color: #b1c94e; } .shape-blue { background-color: #377bbc; } .sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0,0,0,0); border: 0; } Beers in My Cellar Belgian Quadrupels Pink chart segment spanning 40% of the whole, which is 4 Belgian Quadrupels out of 10 total. Imperial India Pale Ales Green chart segment spanning 20% of the whole, which is 2 Imperial India Pale Ales out of 10 total. Russian Imperial Stouts Blue chart segment spanning 3% of the whole, which is 3 Russian Imperial Stouts out of 10 total. 10 Beers

Donut chart showing 10 total beers. Two beers are Imperial India Pale Ales, four beers are Belgian Quadrupels, and three are Russian Imperial Stouts. The last remaining beer is unlabeled.

Мы также можем добавить ключ легенды для диаграммы в качестве части , отмечая его ролью "представления" и атрибутом aria-hidden , поскольку он и вправду нужен для наглядности.

С щепоткой магии CSS Flexbox мы можем расположить ключ справа и выровнять его по вертикали с диаграммой. Что даст нам финальный продукт:


Поскольку CSS-дизайн с ключом, к которому применяется свойство border-radius и Flexbox могли бы быть отдельными статьями (второе требует гораздо более долгого объяснения), я оставлю эти темы на потом.

Конечные результаты

Как я упоминал ранее, написание кода для объектов SVG вручную может занимать много времени, особенно если принять во внимание тот факт, что существует множество прекрасных JavaScript библиотек, которые построят эти объекты за вас. Но могут быть случаи (как моя ситуация), где вам или вашей команде нужен метод, который не затрагивает JavaScript, тот, что будет собран на стороне сервера.

Хорошие новости в том, что, как только вы разберетесь с подобным способом, он сможет быть применен вами где-нибудь еще с минимальной затратой усилий. Плюс, замарать руки в работе с объектами SVG - это хороший способ узнать больше о том, как они работают, и узнать преимущества их использования.

Мои результаты:

  • Применять математику к чему-то вроде этого гораздо более интересно, чем в старшей школе.
  • Оглядываясь назад, я понимаю, что логика, стоящая за stroke-dasharray и stroke-dashoffset не такая сложная, как я думал изначально.
  • Не забудьте добавить сюда дополнительные уровни доступности. В конце концов, доступность нужна не только в угоду программам чтения с экрана. Она также нужна, чтобы сделать ваш контент более доступным и потребляемым всеми людьми и всеми устройствами (включая поисковые системы). Если ваша JavaScript-библиотека не имеет функций доступности, подумайте над ее изменением.

Для front-end разработчика существует огромное количество способов, которыми можно построить круговую диаграмму -  разнообразные библиотеки, плагины, потрясающие возможности SVG графики и т.д. Однако стоит, пожалуй, рассмотреть и самый простой способ создания «пирожкового» чарта, который может быть полезен, когда задача поставлена максимально просто и нет необходимости или возможности использовать дополнительные инструменты. Далее речь пойдёт о примере такой реализации.

Предположим, что имеются исходные данные в виде такого массива, которые нужно представить на круговой диаграмме:

Var dataset = [ { value: 5, color: "#dc3912" }, { value: 40, color: "#ff9900" }, { value: 30, color: "#109618" }, { value: 25, color: "#990099" } ];

value -  это значение в процентах, color -  цвет сектора.

Создадим для начала контейнер, который станет окружностью диаграммы и будет содержать в себе элементы секторов:

Зададим ему также стили:

Container { width: 300px; height: 300px; overflow: hidden; border-radius: 50%; position: relative; }

В результате получаем такой круг. Всё, что выходит за пределы его границ (изображены пунктиром) будет скрыто.

Далее закономерно возникает вопрос -  как мы превратим прямоугольные элементы div в секторы круга и как зададим им нужный угол? Собственно, для первой цели и было установлено свойство overflow:hidden контейнеру, т.к. это позволяет скрыть часть элемента, сделав его таким образом округлым, а для второй нам помогут CSS-трансформации. Далее на примере для одного сектора.

Добавим внутрь контейнера div с классом sector.

CSS:

Sector { width: 50%; height: 50%; position: absolute; left: 50%; top: 0; transform-origin: left bottom; background: #000; }

Таким образом, получился сектор со значением 90 градусов, начинающийся на отметке в 0 градусов.

Для того, чтобы задать нужный угол этому сектору, применим CSS-трансформацию skewY. Эта трансформация сдвигает правое ребро квадрата по оси y так, чтобы горизонтальные рёбра образовывали заданный угол со своим исходным положением:

Значение value в первом элементе массива dataset равно 5, переводя в градусы, получаем 18. Для того, чтобы наклон происходил в нужную сторону, ко всем углам будем добавлять 90 градусов.

Таким образом, угол трансформации = 90 + 18 = 108.

transform: skewY(108deg);

Для поворота на заданный угол будем пользоваться трансформацией rotate. Важно, что сначала применяется трансформация поворота, только затем трансформация наклона.

Transform: rotate(45deg) skewY(108deg);

У этого способа есть один недостаток -  как видно на рисунке, иллюстрирующем значения трансформации skewY, таким образом можно корректно изображать сектора со значением не более 90 градусов. Поэтому для изображения значений, больших, чем 90, придётся рисовать несколько секторов одинакового цвета. Например, если значение угла 100 градусов, отобразим 2 сектора: 90 градусов и 10. Если значение равно 200 градусам, получится 3 сектора -  по 90, 90 и 20 градусов и т.д.

В результате получаем следующий код для динамического добавления секторов:

Var maxValue = 25; var container = $(".container"); var addSector = function(data, startAngle, collapse) { var sectorDeg = 3.6 * data.value; var skewDeg = 90 + sectorDeg; var rotateDeg = startAngle; if (collapse) { skewDeg++; } var sector = $("", { "class": "sector" }).css({ "background": data.color, "transform": "rotate(" + rotateDeg + "deg) skewY(" + skewDeg + "deg)" }); container.append(sector); return startAngle + sectorDeg; }; dataset.reduce(function (prev, curr) { return (function addPart(data, angle) { if (data.value


Top