В недавнем посте мы обсудили современное состояние визуализации данных в браузере, в частности, в библиотеках SVG. Там мы сосредоточились на Snap.svg , но мы представили Paths.js как жизнеспособную альтернативу — используемую вместе с механизмом шаблонов или библиотекой привязки данных. Чтобы быть справедливым, Paths способен на гораздо большее, как мы собираемся показать в следующих разделах, с реальными вариантами использования, которые помогут вам разобраться в его логике.
Вступление
Основная цель Paths — помочь внешним разработчикам создавать SVG-пути с лучшей производительностью через интуитивно понятный интерфейс. Как следует из названия, несмотря на наличие примитивов для таких форм, как rect
или circle
, все можно свести к контурам. Этот подход объединяет различные диаграммы, предоставляя согласованный интерфейс, где команды рисования всегда возвращают список путей, готовых для рисования. Заменив статические движки шаблонов (такие как Mustache или Handlebars) библиотеками привязки данных, такими как Ractive.js , Angular или React , вы даже можете получить анимированную графику бесплатно.
Вероятно, лучшая вещь в Paths.js — это то, что он предлагает три дополнительных API с возрастающим уровнем абстракции. Самый низкий уровень — это цепочечный API, который генерирует произвольный путь SVG. Кроме того, определены пути для простых геометрических фигур, таких как многоугольники или секторы круга. API самого высокого уровня позволяет создавать некоторые простые графики, которые могут быть снабжены набором данных. (Проверьте это демо, чтобы увидеть доступные примитивы.)
На самом деле, обратите внимание на то, что: Лучшая особенность Paths заключается в том, что вы можете использовать библиотеку на стороне сервера с Node.js, поскольку она напрямую не зависит от какой-либо библиотеки 1 . Таким образом, вы можете перенести создание структуры диаграммы и деталей на сервер. Помимо ускорения работы приложений, вы можете полностью исключить отправку необработанных данных клиенту, экономя время ожидания и уменьшая объем информации, которой вы делитесь с клиентами.
Почему Пути?
Наибольшим преимуществом использования Paths.js вместо, скажем, D3 или Snap.svg, является то, что последние являются обязательными, в то время как Paths по своей природе поддерживает декларативное программирование в сочетании с механизмами шаблонов или (даже лучше) средами привязки данных.
Использование Paths с такими фреймворками, как Ractive или React, вызывает, в свою очередь, еще одно преимущество. Эти структуры, на самом деле, используют определенные оптимизации, чтобы уменьшить количество повторений и перерисовок, необходимых каждый раз, когда необходимо изменить DOM; они сохраняют «теневые» копии DOM, для которых выполняют обновление в своего рода «пакетном режиме», и, наконец, обновляют реальный DOM с наименьшим количеством возможных изменений.
Другая область, где эти структуры имеют значение, — обработка событий. По умолчанию они используют делегирование событий, улучшая производительность в тех ситуациях, когда одно и то же событие присоединяется к ряду элементов. Решение состоит в том, чтобы просто прикрепить эти обработчики событий к некоторому общему контейнеру элементов, но слишком легко упустить такой шаблон при использовании императивного подхода (с тяжелыми последствиями, такими как не отвечающие страницы).
Наконец, Paths является легким и модульным: вы можете загружать только те компоненты, которые вам действительно нужны, фокусируясь на графиках или просто манипуляции SVG. Paths — одна из тех библиотек, которые фокусируются на нескольких вещах, пытаясь их оптимизировать. В общем, вы можете объединить несколько таких библиотек для выполнения сложных задач. В D3, с другой стороны, есть много дополнительных служебных методов — это прекрасно, если они вам нужны, поскольку у вас есть все, что вам нужно, в одном месте, но немного тяжело, если вы этого не делаете.
Использование путей
Как уже упоминалось, вы можете использовать пути с Node.js или в браузере. В последнем случае вы можете загрузить его как модули AMD или как отдельную библиотеку.
Пути на узле
Если вы хотите использовать его на сервере, сначала установите его, набрав эту команду на консоли (при условии, что вы правильно установили узел и он находится в глобальном пути):
npm install paths-js
После установки вы можете загрузить отдельные модули:
var Pie = require('paths-js/pie');
Пути в браузере: модули AMD
Paths.js распространяется вместе с Bower , и вы можете установить его из командной строки:
bower install paths-js
Или, конечно, просто загрузите его вручную из репозитория на GitHub.
Paths структурирован в различные модули AMD и может быть загружен с помощью загрузчиков модулей AMD. Используя RequireJS (при условии, что вы установили пути с Bower), вы можете настроить его следующим образом:
require.config({ 'paths': 'components/paths-js/dist/amd' });
Фактический путь будет зависеть от вашей конфигурации Bower или, для загрузки вручную, от структуры ваших папок. (Будьте осторожны, когда размещаете папку amd
указанную выше.)
После правильной настройки вы можете легко потребовать отдельные модули:
var Pie = require('paths/pie');
Пути в браузере: автономный скрипт
Если вы предпочитаете избегать модулей AMD, вы можете смело включать Paths как отдельный скрипт: вам нужен файл dist/global/paths.js
. Как только он будет включен в вашу страницу, объект paths
будет доступен в глобальной области видимости, так что отдельные модули будут доступны как paths.Pie
, paths.Polygon
и так далее. Помимо многословия, вы теряете возможность импортировать только те модули, которые вам нужны, но если вам нужно много из них, это окажет незначительное влияние.
API низкого уровня
Как упоминалось, целью API самого низкого уровня является создание путей. Создание целей так же просто, как вызов одного конструктора: Path()
. Весь API является цепным, так что вы можете создать сложный путь, вызывая методы в результате предыдущих вызовов. Объекты Path
предлагают методы для постепенного расширения текущего пути; сохраняется ссылка на последнюю точку пути, и из этой точки можно добавлять линии или кривые, имитируя синтаксис SVG для путей. Основные методы, которые вам понадобятся:
-
moveto(x, y)
: перемещает курсор к пройденным координатам. -
lineto(x, y)
: рисует линию от конца пути к этим координатам. -
curveto(x1, y1, x2, y2, x, y)
: рисует кубическую кривую Безье из текущей точки в (x, y), используя (x1, y1) в качестве контрольной точки в начале кривой и (x2, у2) в качестве контрольной точки в конце кривой. -
smoothcurveto(x2, y2, x, y)
: рисует кубическую кривую Безье из текущей точки в (x, y), неявно вычисляя первую контрольную точку на основе второй и предыдущей команды (если есть). -
arc('rx', 'ry', 'xrot', 'large_arc_flag', 'sweep_flag', 'x', 'y')
: рисует эллиптическую дугу из текущей точки в (x, y), управляя радиусами эллипса и вращение через другие параметры. -
closepath()
: закрывает путь, превращая его в многоугольник.
Все доступные методы также поддерживают «подробный» API, так что именованные параметры (в форме объекта конфигурации) можно без проблем передавать каждому из них. Например, moveto
выше метод moveto
может называться Paths().moveto({x: 10, y: 3})
или Paths().moveto(10, 3)
. Названия параметров соответствуют спецификации SVG.
Доступно больше методов, и в общем случае есть однозначное соответствие командам SVG Paths . Например, qcurveto(x1, y1, x, y)
и smoothqcurveto(x, y)
являются аналогом smoothcurveto
для smoothcurveto
для квадратичных кривых.
В целом, это работает лучше с движком шаблонов, но это не является строго обязательным. Вы можете использовать Path с императивным стилем, как показано в следующих примерах. Однако это не лучшая практика:
<title>PathsJs test</title> <style type="text/css"> .ocean { fill: blue; } </style> <svg width="640px" height="480px"><path id="testpath" class="ocean"></path></svg> <script type="text/javascript" src="lib/paths.js"></script>
var Path = require('paths/path'); var path = Path() .moveto(10, 20) .lineto(30, 50) .lineto(25, 28) .qcurveto(27, 30, 32, 27) .closepath(); document.getElementById("testpath").setAttribute("d", path.print());
Метод print()
объектов Path
преобразует путь, составленный в соответствующую строку данных SVG, как это будет указано в атрибуте путей d
(data). Как только мы получим это значение, мы можем вручную установить соответствующий атрибут на любом пути, просто используя селекторы CSS и методы getElementById
/ getElementsBy*
.
Конечно, использование шаблонизатора сильно повлияет на объем стандартного кода, который нам нужно написать:
<svg width="640px" height="480px"><path d="{{ path.print() }}" fill="blue"></path></svg>
Это устраняет необходимость вручную устанавливать атрибут d
для #testpath
и даже назначать идентификатор элементу пути. Это лучший практический стиль для создания рисунков SVG с помощью Paths.
API среднего уровня
Как бы он ни был силен, на практике вам редко понадобится низкоуровневый API. Причина в том, что Paths предлагает построенный на его основе API с более высоким уровнем абстракции, который позволяет напрямую создавать полигоны и фигуры с интуитивно понятным синтаксисом.
Прежде чем мы рассмотрим пример, важно понять, как работает этот API. Мы уже заявили, что все, что создается этой библиотекой, является путем. Действительно, каждый из методов API среднего уровня возвращает объект с двумя полями:
{ path: <path object=""> centroid: [<x>, <y>] }
Поле path
содержит объект Path
, совместимый с низкоуровневым API, и поэтому является расширяемым: строку, описывающую атрибут данных пути, можно, как обычно, получить с помощью метода print()
.
Вместо этого поле centroid
формально не связано с полученным путем, но, тем не менее, оно может быть очень полезным: оно эмпирически вычисляется как точка, как-то центральная по отношению к фигуре, которую можно использовать, например, для позиционирования этикетка для формы.
Чтобы оценить разницу между двумя уровнями абстракции, мы создадим многоугольник, почти идентичный предыдущему разделу, на этот раз с использованием объекта Polygon
:
<svg width="640px" height="480px"><path id="testpath" class="ocean"></path><path id="testpath2" class="ocean" transform="translate(100)"></path></svg>
var Polygon = require('paths/polygon'); var polygon2 = Polygon({ points: [[10, 20], [30, 50], [25, 28], [32, 27]], closed: true }); document.getElementById("testpath2").setAttribute("d", polygon2.path.print());
Если вы протестируете этот код, вы увидите, что эти две фигуры на первый взгляд выглядят очень похоже. Разница в том, что первая, построенная с помощью низкоуровневого API, имеет одну сторону, построенную с квадратичной кривой вместо сегмента. Действительно, низкоуровневый API имеет то преимущество, что позволяет смешивать разные типы линий в одной форме.
В API среднего уровня нет такого метода, который позволял бы вам удобно их смешивать. Но не бойтесь, ничего не потеряно: как мы уже говорили, вы всегда можете отредактировать Path
возвращаемый Polygon(...)
:
<svg width="640px" height="480px"><path id="testpath" class="ocean"></path><path id="testpath2" class="ocean" transform="translate(100)"></path><path id="testpath3" class="ocean" transform="translate(50)"></path></svg>
var polygon3 = Polygon({ points: [[10, 20], [30, 50], [25, 28]], closed: false }); console.log(polygon3.path.print()) var polygon3Path = polygon3.path .qcurveto(27, 30, 32, 27) .closepath(); document.getElementById("testpath3").setAttribute("d", polygon3Path.print());
Полный список объектов, доступных для интерфейса второго уровня:
- Полигон : закрытые и открытые полигоны.
- Полурегулярный многоугольник : особый случай многоугольника: позволяет создавать треугольники, квадраты, пятиугольники и т. Д. (Правильные многоугольники и их неправильные варианты). Он определяется относительно центра, и углы между сегментами от центра до точек все одинаковы, в то время как расстояния этих точек могут быть постоянными (правильные многоугольники) или могут варьироваться (нерегулярные).
- Прямоугольник : еще один особый тип многоугольника, хотя на этот раз более интуитивно понятный.
- Безье : рисует гладкую кривую Безье, проходящую через список вершин. Возвращенный путь всегда открыт.
- Сектор : круговой сектор.
- Соединитель : определяется как S-образный путь между двумя заданными точками. (Лучший способ понять это — попробовать.)
Несколько примеров, чтобы лучше показать полурегулярные полигоны:
Треугольник (равносторонний)
var SemiRegularPolygon = require('paths/semi-regular-polygon'); var triangle = SemiRegularPolygon({ center: [50, 50], radii: [20, 20, 20] }); document.getElementById("triangle").setAttribute("d", triangle.path.print());
Треугольник (равнобедренный)
var triangleIrregular = SemiRegularPolygon({ center: [50, 50], radii: [20, 30, 30] });
Площадь
var square = SemiRegularPolygon({ center: [50, 50], radii: [20, 20, 20, 20] });
пятиугольник
var pentagon = SemiRegularPolygon({ center: [50, 50], radii: [20, 20, 20, 20, 20] });
Пентагон (Нерегулярный)
var pentagonIrregular = SemiRegularPolygon({ center: [50, 50], radii: [25, 20, 40, 30, 20] });
API высокого уровня
Это API высшего уровня, предоставляемый Paths. Цель его методов — позволить создавать полные диаграммы, начиная с набора данных, для визуализации. Как всегда, все переводится на путь! В частности, все эти методы возвращают объект, содержащий поле curves
, массив с формами, созданными для каждой точки данных. Фигуры на curves
— это объекты с несколькими соответствующими полями:
-
item
: ссылка на соответствующий элемент данных. -
index
: индекс соответствующего элемента данных в массиве данных. - Одно или несколько полей, содержащих объекты фигур (например,
sector
для круговых диаграмм,line
иarea
для линейных диаграмм).
Возвращенные объекты могут иметь дополнительные поля помимо curves
, в зависимости от каждой диаграммы. Но каждый метод диаграммы принимает аргумент compute
на входе. Этот параметр позволяет пользователям передавать любое количество функций для вычисления дополнительных полей на основе входных данных. (Вычисление цветов было бы типичным примером.)
При использовании этих высокоуровневых методов механизмы шаблонов практически необходимы, поскольку они избавляют разработчиков от большого количества стандартного кода. Они также упрощают создание диаграммы, автоматизируя итерацию по набору форм сложной диаграммы.
Однако, как всегда, они не являются строго необходимыми. Давайте рассмотрим пример того, как можно обойтись без них:
<svg id="chart-test" width="200px" height="200px"></svg>
var somePalette = ['blue', 'green', 'red', 'yellow', 'orange'], Pie = require('paths/pie'), pie = Pie({ data: [ { name: 'Italy', population: 59859996 }, { name: 'Mexico', population: 118395054 }, { name: 'France', population: 65806000 }, { name: 'Argentina', population: 40117096 }, { name: 'Japan', population: 127290000 } ], accessor: function(x) { return x.population; }, compute: { color: function(i) { return somePalette[i]; } }, center: [50, 50], r: 30, R: 50 }), chartSvg = document.getElementById("chart-test"), chartFragment = document.createDocumentFragment(), dx = parseInt(chartSvg.getAttribute('width'), 10) / 2, dy = parseInt(chartSvg.getAttribute('height'), 10) / 2; pie.curves.forEach(function (d, i){ var path = document.createElementNS('http://www.w3.org/2000/svg',"path"); path.setAttributeNS(null, 'd', d.sector.path.print()); path.setAttributeNS(null, 'style', 'fill:' + d.color); var label = document.createElementNS('http://www.w3.org/2000/svg',"text"); label.textContent = d.item.name; label.setAttributeNS(null, 'x', d.sector.centroid[0]); label.setAttributeNS(null, 'y', d.sector.centroid[1]); chartFragment.appendChild(path); chartFragment.appendChild(label); }); chartSvg.appendChild(chartFragment);
В приведенном выше коде мы используем фрагмент кода, чтобы собрать все сектора перед тем, как фактически добавить их на страницу — и, таким образом, инициируем перекомпоновку только один раз, а не дважды для каждого сектора (один раз для пути и один раз для метки). Элементы фрагмента документа вставляются за один раз, в то время как, если бы мы использовали элемент svg:g
для их группировки, каждый узел был бы вставлен отдельно. (Более того, в результирующем SVG будет возможность создания избыточной группы.) Еще большее преимущество фрагментов документа состоит в том, что если нам нужно клонировать всю диаграмму и добавить ее несколько раз на страницу, каждая операция клонирования требует постоянной количество вставок узлов вместо их линейного числа.
Теперь давайте сравним предыдущий код с созданием того же графика, что и выше, используя Ractive:
<div id="pie-chart"></div><script id="myChartTemplate" type="text/ractive"> <svg width=375 height=400> {{# pie }} {{# curves:num }} <path on-click="expand" d="{{ sector.path.print() }}" fill="{{ color }}" ></path> <text text-anchor="middle" x="d.sector.centroid[0]" y="d.sector.centroid[1]">{{ item.name }}</text> </g> {{/ curves }} {{/ end of pie}} </svg> </script>
var Pie = require('paths/pie'); var ractive = new Ractive({ el: 'pie-chart', template: '#myChartTemplate', data: { pie: Pie({ data: [ { name: 'Italy', population: 59859996 }, { name: 'Mexico', population: 118395054 }, { name: 'France', population: 65806000 }, { name: 'Argentina', population: 40117096 }, { name: 'Japan', population: 127290000 } ], accessor: function(x) { return x.population; }, compute: { color: function(i) { return somePalette[i]; } }, center: [50, 50], r: 30, R: 50 }) } });
Результат выглядит лучше, чище, и структура графика сразу становится очевидной, если посмотреть на разметку.
В настоящее время доступно 9 различных типов диаграмм:
- Круговая диаграмма
- Гистограмма : позволяет отображать несколько гистограмм рядом.
- График акций : представляет один или несколько временных рядов с линейными графиками.
- График гладких линий : как и графики запасов, но он интерполирует линии между точками данных, используя плавные кривые Безье.
- Радар Диаграмма
- Древовидная диаграмма
- Waterfall Chart : гистограмма, которая позволяет разбивать значения на части.
- Диаграмма направленного усилия : физическое моделирование в форме графа, вершины которого отталкиваются друг от друга, если они не связаны ребром.
- Диаграмма Санки : блок-схемы, где стрелки пропорциональны потоку.
Вы можете взглянуть на пути [витрина] (http://andreaferretti.github.io/paths-js-demo/), чтобы увидеть, как эти диаграммы выглядят. Все представленные там примеры используют Ractive для создания великолепных анимаций.
Вывод
В этот момент вы можете спросить, действительно ли Paths правильный выбор для вас. Нет, конечно, простого ответа на это. По-разному. Если вам нужны готовые виджеты и графики, вероятно, нет — возможно, вам лучше использовать Highcharts , Flotcharts или Dimple .
Однако мы не можем не подчеркнуть преимущества использования декларативного стиля программирования в визуализации данных. Если вы обязательно используете Paths, результат, вероятно, не будет стоить усилий по изучению новой библиотеки.
Пути полезны в тех случаях, когда вам нужно создавать собственные диаграммы с персонализированным стилем или анимацией или пользовательским поведением в ответ на взаимодействие с пользователем. Но Paths — реальное изменение игры, где вам нужно генерировать графику на сервере. Paths чрезвычайно упрощает создание разметки в логике и отправку ее клиенту в виде JSON или строк.
Наконец, вот несколько ссылок для дальнейшего чтения:
- CodePen для примеров кода в этой статье.
- Мое слайд-шоу по визуализации данных для браузера: реактивные SVG-диаграммы с Ractive.js .
- Paths-Js дома на GitHub.
- Слайды для выступления Андреа Ферретти о функциональном подходе к визуализации в браузере.
- Paths.js зависит только от основных методов EcmaScript 5 , что является проблемой для старых браузеров. Этот polyfill добавляет необходимую поддержку.