По работе мне часто приходится рисовать разные схемы, диаграммы процессов и графики, в том числе и те, которые потом используются в качестве иллюстраций для сайта, статей и презентаций. Диаграммы я привык рисовать в app.diagrams.net (бывший draw.io), а графики — в datawrapper.de. Всё бы ничего, но есть у диаграмм, сделанных в draw.io, lucidcharts или visio и графиков, построенных в аналитических сервисах одна беда — они выглядят как-то слишком уныло и «олдскульно», в духе «90-х».
Всю эту визуализацию и инфографику хотелось бы сделать более заметной, привлекательной и душевной (и, желательно, без привлечения дизайнера). Что-то вроде этого:
Сначала я пробовал перерисовывать диаграммы руками на листе бумаги и сканировать, потом пытался отрисовывать их в векторном редакторе, используя разные кисти. Наконец, экспериментировал со стилусом на планшете — во всех случаях получалась какая-то непотребщина, на которую ещё и уходило много времени. Благо, программирую я чуть лучше, чем рисую — то даёт шанс компенсировать недостаток одного навыка другим. Так у меня возникла идея создания инструмента для отрисовки диаграмм и графиков в стиле «нарисовано от руки».
Поскольку в стеке технологий у меня в основном javascript и php, я начал смотреть, какие есть библиотеки для рендеринга скетчей. Вариантов оказалось немного — выбор пал на крутую библиотеку roughjs, которая как раз умела генерировать графические примитивы (от прямоугольников до кривых) в нужном «рисованном» стиле и была написана на JS. Если в ней хорошенько покрутить параметры, можно было получить достойный результат. Оставалось только придумать, где брать исходный материал для отрисовки, потому что каждый график или UML-диаграмму рисовать на JS непрактично. Нужен был какой-то векторный редактор для генерирования диаграм. Изобретать diagrams.net и datawrapper.de не хотелось, поэтому решил стилизовать готовое SVG-изображение, сгенерированное в любом из этих сервисов. То есть нужно было парсить SVG с графиком или диаграммой и конвертировать каждый примитив (<rect>, <circle>, <ellipse>, <line>, …) или путь (<path>) в аналогичный, но сгенерированный уже библиотекой roughjs. Так от идеи инструмента для генерации рисованных диаграм я пришёл к сервису стилизации векторных изображений.
В итоге придумалась следующая схема:
-
Загружаем готовый векторный SVG-рисунок со схемой, графиком или диаграммой. Его можно нарисовать в draw.io или даже в GoogleSheets.
-
Заменяем в исходном SVG все примитивы типа <rect>, <circle>, <path> и пр. на <path>, который генерирует roughjs.
-
Примеряем к надписям рисованные шрифты (мне понравились бесплатные гарнитуры XKCD, Indie, Caveat и Pangolin), после чего подгоняем размеры надписей, чтобы высота и ширина не отличалась от исходных, и при этом надписи читались.
-
Меняем цвета на приятные, переносим часть стилей и фильтров из оригинального SVG, чтобы диаграмма максимально сохраняла пропорции, позиции элементов и свойства фигур.
-
Экспортируем в PNG в высоком разрешении или сохраняем обратно в SVG.
-
???
-
Профит.
Вернее, профита с ней не получилось, так как для сервиса найти product-market fit пока не удалось. Как говорится, «все пробуют, хвалят, но замуж не берут».
Но вернёмся к технической стороне вопроса. Несмотря на очевидность и простоту подхода, пришлось решить достаточно много технических нюансов. Например, учесть то, что SVG — это достаточно вольный формат, который позволяет один и тот же прямоугольник нарисовать десятком разных способов. Иногда попадались SVG-изображения, где прямоугольник — это классический <rect> с атрибутами fill и stroke. В других — <rect> с CSS-стилями. А в каких-то случаях это вообще <path> с фильтрами. Всё это пришлось учитывать при отрисовке: парсить стили, фильтры, атрибуты и некоторые добавлять после конвертации в roughjs <path>.
Пришлось повозиться и с рендерингом текста. Так, например, файл, экспортированный из draw.io, сохраняет большую часть данных не в каноническом SVG, а как контейнеры <foreignObject> внутри который <DIV> с обычным HTML-кодом. А там всё в кучу:
Ещё оказалось непросто поддерживать правильные цвета контура и заливки для фигур, поскольку они не всегда заданы SVG-атрибутами fill / stroke внутри примитива. Иногда цвета наследуются от родительского элемента, иногда объявлены в локальном стиле через style, а иногда — в глобальном стиле или даже в фильтре. Одним словом — бардак ).
Отдельная беда была с производительностью и потреблением памяти. Когда конвертируется график с 30 примитивами — всё работает шустро и незаметно. А вот векторизованное растровое изображение с 10K+ объектами или дашборд из Tableau, сохранённый в PDF, страшно тормозил. Но тут, конечно, всё зависело от производительности компьютера, потому что рендерится оно в браузере на клиенте (привет пользователям новых маков на M1 Max, у которых всё быстро). Первую версию SVG-конвертера я сделал на jQuery, но потом переписал на обычном js и оно стало работать в 2.5 раз быстрее и потреблять процентов на 70 меньше памяти. Такая вот плата за удобство.
После некоторого периода тестирования стало понятно, что не во всех редакторах диаграмм и генерилках графиков есть экспорт в SVG. Или есть, но с побочными эффектами. Например, при сохранении графиков из GoogleSheets в SVG текст переводится в кривые. В этом случае надпись становится картинкой и её уже не поменять. Но том же GoogleSheets можно сохранить график в PDF, где надписи остаются без изменений. Поэтому пришлось прикрутить загрузку других форматов (PDF, EPS) – cпасибо консольному inkscape за возможность конвертации векторных форматов.
Постепенно сервис обрастал всякими фичами, наподобие работы с буфером обмена: можно скопировать в буфер код SVG и вставить его в сервис для рестайлинга (это особенно удобно, если копируете непосредственно со страницы с SVG-графиком или диаграммой). А результат можно скопировать в буфер и затем вставить в документ.
Кроме векторных графиков и диаграмм неплохо получаются рисованные карты и схемы:
В одной из последних версий я даже решил добавить цветной трейсер растровых изображений, чтобы любой JPEG или PNG можно было сконвертировать в векторный SVG и затем применить к нему «скетч-эффект». Готового векторизатора не было (potrace не в счёт), поэтому пришлось написать свой. Но как оказалось, для качественной векторизации нужно трейсить изображение в высоком разрешении, примерно 12000x12000 пикселей (предварительно изображение апскейлится с помощью нейронки). То есть нужны мощные сервера с GPU — на стандартном 8-ядерном железе даже небольшая растровая картинка 2000x2000 векторизуется минуты две (а пользователи не любят ждать). Поэтому в некотором обрезанном виде я оставил векторизатор в сервисе, но никому про него не рассказываю, чтобы не позориться.
Большая часть сервиса сделана на самом обыкновенном Vanilla JS, UI — на Bootstrap + jQuery. Небольшая серверная часть — на PHP. Всё крутится в докере. Для авторизации без регистраций прикрутил Google и Facebook.
Напоследок проиллюстрирую работу сервиса на Хабра-схеме модерации постов из справочного раздела про «Песочницу»:
Сервис бесплатный, потестировать его можно здесь. Будет здорово, если вы тоже найдёте ему применение при решении своих повседневных графических задач. За информацию о багах также буду премного благодарен!
Автор: Григорий