Русский
Русский
English
Статистика
Реклама

Svg

Перевод Исправление странной ошибки и стратегии отладки, проверенные временем

08.09.2020 16:22:26 | Автор: admin

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

Недавно мне пришлось вспомнить о том, какими запутанными могут быть причины возникновения ошибок пользовательских интерфейсов. А именно, речь идёт об исправлении интересной ошибки, влияющей на вывод SVG-изображений в браузере Safari. Эта ошибка возникала без какой-то определённой системы и без каких-то очевидных причин. Я, столкнувшись с проблемой, попытался найти похожие случаи, надеясь на то, что описания таких случаев дадут мне намёк на то, что происходит. Но мне не удалось отыскать ничего полезного. Правда, несмотря на все стоящие передо мной препятствия, я смог с этой ошибкой справиться.

Я проанализировал проблему, используя некоторые стратегии отладки, о которых я собираюсь рассказать в этой статье. После того, как от ошибки я избавился, я вспомнил совет, который Крис Койер дал читателям своего Твиттера несколько лет назад. Этот совет звучит так: Напишите статью, которую вы хотели бы найти, когда обращались к поисковику. Собственно говоря, так я и поступил.

Обзор проблемы


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


Кнопка в нормальном состоянии

А вот та же кнопка после возникновения проблемы.


Часть кнопки обрезана

Я воспроизводил эту ошибку в различных ситуациях, вызывающих перерисовку страницы. Например, она возникала при изменении размеров окна браузера.

Для того чтоб продемонстрировать проблему, я создал пример на CodePen. Подробнее его мы обсудим ниже. Но вы можете самостоятельно с этим примером поэкспериментировать. А именно, речь идёт о том, что если открыть этот пример в браузере Safari, то, при загрузке страницы, кнопки будут выглядеть так, как ожидается. Но если щёлкнуть по одной из двух более крупных кнопок, ошибка высунет свою уродливую голову.


Почему SVG-изображение оказывается обрезанным?

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

Обзор проекта, в котором происходила ошибка


Я так думаю, что, рассказывая об ошибке, хорошо будет раскрыть подробности о проекте и об условиях, в которых она происходит.

  • В проекте используется React (но читателю этой статьи необязательно знать React для того чтобы в ней разобраться).
  • SVG-изображения импортируются в проект в виде React-компонентов и встраиваются в HTML средствами webpack.
  • Изображения экспортируются из дизайнерской программы. В их коде нет синтаксических ошибок.
  • Изображения стилизуются средствами CSS.
  • Изображения, которые выводятся с ошибкой, расположены внутри HTML-элемента <button>.
  • Проблема появляется только в браузере Safari (она была замечена в его 13 версии).

Исследование ошибки


Давайте рассмотрим ошибку и поразмыслим о том, можно ли сделать какие-то предположения относительно того, что происходит. Причины подобных ошибок обычно не лежат где-то на поверхности, поэтому, столкнувшись с такими ошибками, нельзя сразу с уверенностью сказать о том, что происходит. Предпринимая первую попытку разобраться в проблеме, мы не обязаны стремиться к выявлению её причины со 100% точностью. Мы будем исследовать ошибку пошагово, формулируя и проверяя гипотезы, которые помогут нам сузить список возможных причин происходящего.

Формулирование гипотезы


Происходящее, на первый взгляд, выглядит как ошибка CSS. Возможно, при наведении указателя мыши на кнопку к ней применяются какие-то стили, которые и ломают макет. Возможно, во всём виноват атрибут overflow SVG-изображения. Кроме того, возникает такое ощущение, что ошибка происходит без какой-то определённой системы при перерисовке страницы по разным поводам (событие paint при изменении размеров окна браузера, при наведении указателя мыши на кнопку, при щелчке по ней и так далее).

Начнём с самого простого и очевидного предположения. Представим, что ошибка кроется в CSS. Мы можем исходить из предположения о том, что в браузере Safari есть ошибка, которая приводит к неправильному выводу SVG-изображений при применении к SVG-элементам каких-то специфических стилей. Например, вроде тех стилей, что используются для построения flex-макетов.

Только что мы сформулировали гипотезу. Нашим следующим шагом будет проведение испытания, которое либо подтвердит, либо опровергнет эту гипотезу. Результат каждого испытания даст нам новые сведения об ошибке и поможет в формулировании следующих гипотез.

Упрощение проблемы


Мы будем пользоваться стратегией отладки, которая называется упрощение проблемы. Это позволит нам точно определить место возникновения ошибки. В одной лекции по информатике Корнеллского университета эту стратегию описывают как подход по постепенному избавлению от кода, который не имеет отношения к ошибке.

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

Давайте испытаем нашу гипотезу. Попытаемся её подтвердить. В таком случае, если временно отключить от страницы все стили, не являющиеся стандартными, это должно привести к тому, что ошибка появляться перестанет.

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

import 'css/app.css';

Я, для демонстрации вывода элементов без CSS, создал этот CodePen-проект. В React SVG-графика импортируется в проект в виде компонента, потом соответствующий код встраивается в HTML с помощью webpack.


Обычный вид кнопок

Если открыть вышеупомянутый проект в Safari и щёлкнуть по одной из крупных кнопок, то окажется, что ошибка никуда не делась. Она происходит и при загрузке страницы, но при использовании CodePen для вызова ошибки нужно щёлкнуть по кнопке.


Ошибка никуда не делась и при отключении CSS (Safari 13)

В результате мы можем сделать вывод о том, что CSS тут ни при чём. Правда, мы можем обратить внимание на то, что в таких условиях неправильно выводятся только две из пяти кнопок. Запомним это и перейдём к следующей гипотезе.

Изоляция ошибки


Наша следующая гипотеза заключается в том, что в Safari есть ошибка, возникающая при выводе SVG-изображений внутри HTML-элементов <button>. Так как проблема возникает при выводе двух первых кнопок изолируем первую из них и посмотрим на то, что произойдёт.

Сара Дразнер в этом материале объясняет важность изоляции. Я очень рекомендую почитать этот материал тем, кого интересуют подробности об инструментах отладки и о различных подходах к поиску ошибок. Вот цитата из этого материала: Изоляция это, возможно, самый главный базовый принцип отладки. Код, работающий в наших проектах, может быть разбросан по разным библиотекам и фреймворкам. В работе над проектами могут принимать участие многие люди, а некоторые из тех, кто внёс вклад в развитие проектов, больше над ними не работают. Изоляция проблемы помогает нам медленно отсекать то, что не влияет на появление ошибки. Это позволяет, в итоге, найти источник проблемы и сосредоточиться на нём.

Изоляцию ошибки часто называют сокращённым тестовым случаем.

Я перенёс кнопку на отдельную пустую страницу (создал отдельный тест для неё). А именно, был создан этот CodePen-проект, предназначенный для исследования кнопки в изоляции. Хотя мы пришли к выводу о том, что CSS причиной проблемы не является, нам нужно оставить стили отключёнными до тех пор, пока мы не обнаружим реальную причину происходящего. Это позволит нам максимально упростить проблему.


Страница, на которой присутствует лишь кнопка

Если открыть этот проект в Safari, то окажется, что ошибку нам больше вызвать не удаётся. Даже после щелчка по кнопке изображения не меняется. Но это нельзя счесть приемлемым решением проблемы. Однако код этого CodePen-проекта даёт нам отличную базу для создания минимального воспроизводимого примера.

Минимальный воспроизводимый пример


Основное различие двух предыдущих CodePen-проектов это комбинация кнопок. Исследование всех возможных комбинаций кнопок позволяет нам прийти к выводу о том, что проблема возникает только тогда, когда событие paint возникает для более крупного SVG-изображения, рядом с которым на странице расположено более мелкое SVG-изображение.

Знание об этом позволило создать минимальный воспроизводимый пример, который позволил воспроизвести ошибку и при этом избавиться от каких-либо ненужных элементов. Благодаря минимальному воспроизводимому примеру мы можем более глубоко изучить проблему и выделить именно тот участок кода, который её вызывает.

Вот CodePen-проект, о котором идёт речь.


Минимальный воспроизводимый пример

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


Рисунок, на котором второе изображение размещено поверх первого, искажённого в результате возникновения ошибки

Разделяй и властвуй


Мы смогли воспроизвести ошибку, воспользовавшись минимальным количеством элементов, представленных парой SVG-изображений. Теперь мы собираемся ещё сильнее сократить область поиска проблемы, сузив её до конкретного фрагмента SVG-кода, который является источником проблемы. Если мы понимаем принципы функционирования SVG-кода лишь в общих чертах и при этом хотим найти источник проблемы, это значит, что мы можем воспользоваться стратегией поиска по бинарному дереву, используя подход, известный как разделяй и властвуй. Вот ещё одно извлечение из лекции по информатике Корнеллского университета: Например, вы можете, начиная с исследования большого фрагмента кода, поместить код проверки в середине изучаемого фрагмента. Если ошибка здесь не произойдёт это значит, что её источник находится во второй половине кода. В противном случае её источник находится в первой половине кода.

При исследовании SVG-кода можно попытаться удалить элемент <filter> из описания первого изображения (и ещё <defs>, так как в этом блоке, всё равно, ничего нет). Давайте поинтересуемся тем, какие именно задачи решает элемент <filter>. Отличное объяснение этого можно найти здесь. А именно, речь идёт о следующем: Для применения фильтров к SVG-изображениям существует специальный элемент, который называется <filter>. Он, по сути, напоминает элементы, предназначенные для работы с линейными градиентами, с масками, шаблонами и с другими графическими эффектами. Элемент <filter> никогда не выводится на экран самостоятельно. Он используется лишь как что-то такое, на что можно ссылаться, используя атрибут filter в SVG-коде или функцию url() в CSS.

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

Я создал ещё один CodePen-проект для того чтобы продемонстрировать результаты этого испытания.


Последствия удаления элемента <filter>

Проблема, как несложно заметить, никуда не делась. А внутренняя тень продолжает выводиться даже после удаления кода фильтра. Но теперь, кроме прочего, проблема появляется во всех браузерах. Это позволяет нам сделать вывод о том, что ошибка находится где-то в оставшемся коде описания кнопки. Если удалить оставшийся id из <g filter="url(#filter0_ii)">, то тень исчезает. Что же тут происходит?

Взглянем ещё раз на вышеприведённое определение элемента <filter> и обратим внимание на следующие слова: Элемент <filter> никогда не выводится на экран самостоятельно. Он используется лишь как что-то такое, на что можно ссылаться, используя атрибут filter в SVG. (Фрагмент текста выделил я.)

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

Исправление ошибки


Теперь мы знаем о том, что наша проблема связана с элементом <filter>. Мы знаем и то, что оба SVG-изображения имеют такой элемент, так как фильтр используется для создания внутренней тени круглой формы. Сравним код двух SVG-изображений и подумаем о том, сможем ли мы объяснить ошибку и исправить её.

Я упростил код обоих изображений, что позволит чётко увидеть то, что в нём происходит.

Вот код первого SVG-изображения:

<svg width="46" height="46" viewBox="0 0 46 46"><g filter="url(#filter0_ii)"><!-- ... --></g><!-- ... --><defs><filter id="filter0_ii" x="0" y="0" width="46" height="46"><!-- ... --></filter></defs></svg>

Вот код второго изображения:

<svg width="28" height="28" viewBox="0 0 28 28"><g filter="url(#filter0_ii)"><!-- ... --></g><!-- ... --><defs><filter id="filter0_ii" x="0" y="0" width="28" height="28"><!-- ... --></filter></defs></svg>

Анализируя эти два фрагмента, можно заметить то, что в конструкции id=filter0_ii используется один и тот же идентификатор. Safari применяет к элементам определение фильтра, разобранное браузером последним (в нашем случае это фильтр второго изображения). Это и приводит к тому, что первое изображение оказывается обрезанным. Его исходный размер 48px, а после применения фильтра из него вырезается кусок размером 26px. Свойство id в DOM должно иметь уникальное значение. Если же на странице имеется несколько одинаковых id, браузер не может разобраться в том, какое из них ему нужно использовать. А так как свойство filter переопределяется при возникновении каждого события paint, то, в зависимости от того, какое из определений будет готово первым (тут возникает нечто вроде состояния гонок), ошибка или появляется, или нет.

Попробуем назначить уникальные значения id в коде каждого из изображений и посмотрим на результаты. Вот соответствующий CodePen-проект.


Назначение уникальных id решило проблему

Если теперь открыть проект в Safari и щёлкнуть по кнопке, можно будет убедиться в том, что проблему мы решили, назначив уникальный id фильтрам, используемым в SVG-изображениях. Если задуматься над тем фактом, что в проекте были неуникальные значения для атрибута вроде id, это позволит прийти к выводу о том, что проблема должна была появляться во всех браузерах, а не только в Safari. Но по какой-то причине другие браузеры (включая Chrome и Firefox), как кажется, обрабатывали эту необычную ситуацию без ошибок. Хотя, это может быть простым совпадением.

Итоги


Это было то ещё приключение! Мы начали, зная лишь о том, что в проекте есть некая ошибка, которая то появляется, то нет, а в итоге полностью поняли причины происходящего и справились с проблемой. Отладка кода пользовательского интерфейса и выяснение причин искажений графических элементов могут вызывать сложности в том случае, если суть происходящего неясна. Но, к нашему счастью, существуют стратегии отладки, способные помочь в поиске причин даже самых запутанных ошибок.

Сначала мы упростили проблему, сформулировав гипотезы, которые позволили нам убрать из проекта компоненты, не имеющие отношения к ошибке (стили, разметку, динамические события и прочее). После этого мы изолировали разметку и нашли минимальный воспроизводимый пример. Это позволило нам сосредоточиться на небольшом фрагменте кода. И мы, наконец, выявили проблему, воспользовавшись стратегией разделяй и властвуй, что позволило избавиться от ошибки.

Благодарю всех, кто нашёл время на чтение этой статьи. Но, прежде чем мы закончим разговор, позволю себе рассказать вам об ещё одной стратегии отладки, упомянутой в лекциях Корнеллского университета. Речь там идёт о том, что в процессе работы нужно устраивать перерывы, отдыхать и освобождать голову от всех мыслей: Если на отладку уходит слишком много времени, то программист устаёт. Может оказаться так, что он, пребывая в таком состоянии, трудится впустую. В подобной ситуации стоит устроить перерыв и выбросить всё из головы. А через некоторое время стоит попытаться взглянуть на проблему с другой точки зрения.

Как вы занимаетесь исправлением непонятных ошибок?

Подробнее..

Нативный способ покрасить SVG-иконки

22.07.2020 18:16:54 | Автор: admin
Когда вам нужна возможность менять цвет иконок через CSS, что вы делаете? Вариантов не так много.

Обычно используются либо шрифты иконок, либо исходный код SVG скачивается и вставляется в HTML вручную. Шрифт нужно оптимизировать, иначе пользователь загрузит разом все иконки без надобности. Работа с исходным кодом требует тяжелых DOM-операций и потенциально опасна.

Чтобы защититься от вредоносного кода SVG нужно почистить. Встроенный в Angular санитайзер, к примеру, не работает с SVG и превращает их в пустую строку. Можно воспользоваться проверенным инструментом DOMPurify и подключить его с помощью нашей библиотеки ng-dompurify, о чем я подробно рассказывал.

Давайте посмотрим на еще один способ, доступный в современных браузерах, тэг USE.



Чем нам полезен USE?


Этот тэг задуман для переиспользования символов и целых SVG-блоков на странице. Но в современных браузерах (прости, IE) он может даже доставать внешние ресурсы!

Внешние SVG должны быть на том же домене, так что CDN не подойдет. Пока.

Это позволяет нативным образом вставить SVG в Shadow DOM, почти как тэг IMG с атрибутом src, только с возможностью использовать CSS. И оно даже само работает с кэшем! Но нужно слегка подготовить иконки. Вот что надо сделать:



Сначала в каждой иконке нужно сделать символ с уникальным id и переместить viewBox в него.

Затем надо назначить fill (или stroke) на currentColor, чтобы потом использовать CSS-правило color для задания цвета. Можно также задать эти атрибуты в inherit на других элементах, что позволит сделать двухцветные иконки (подробнее в примере ниже).

Когда наши иконки подготовлены, остается только скинуть их в папку assets и использовать:



Компонент именованных иконок для Angular


Писать путь и обращаться к символу каждый раз утомительно. Давайте сделаем Angular-компонент, который будет находить иконки по имени. С помощью Dependency Injection это сделать очень просто.

Нам понадобится токен для предоставления пути до всех наших иконок и простой компонент. Он будет формировать href исходя из имени и заданного пути. Мы даже можем повесить его на нативный SVG с помощью селектора: так мы вынесем наружу заботу о размере.

Надо иметь в виду, что Safari до 12.1 поддерживает только устаревший синтаксис xlink:href. Так что лучше использовать оба варианта.

Сделаем stroke и fill прозрачными для использования нескольких цветов в CSS:



Живой пример: stackblitz.com/edit/angular-colored-svg

Заключение


У этого подхода есть ограничения отсутствие поддержки IE и кросс-доменности. Однако, если они для вас не критичны, данное решение может стать хорошей альтернативой другим способам.

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

Перевод Три способа создания клякс с помощью CSS и SVG

07.03.2021 22:07:07 | Автор: admin

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

Итак, как же они устроены? Разумеется, можно открыть какой-то графический редактор и сделайте их, верно? Конечно, это круто. Но мы пишем здесь о CSS финтах, и было бы гораздо интереснее посмотреть на возможности, которые нам дают CSS и SVG - двух наших любимых ингредиентов!

У нас есть несколько способов сделать кляксы. Давай проверим их.

Рисуем круги в SVG

Начнем с чего-то простенького. Мы можем рисовать SVG в чем-то вроде Illustrator, Sketch, Figma или чем-то ещё, но вместо этого мы будем программировать SVG.

<circle cx="100" cy="100" r="40" fill="red" />

SVG делает рисование круга довольно тривиальным благодаря элементу <circle> с соответствующим значениями:

cxопределяет координату X центра окружности.

cy определяет координату Y.

r - радиус.

fill используется для заливки формы цветом.

Этот фрагмент создает круг с радиусом 40px с центром в точке 100px по оси X и 100px по оси Y. Координаты отсчитываются с верхнего левого угла родительского контейнера.

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

<svg height="300" width="300">  <circle cx="80" cy="80" r="40" fill="red" />  <circle cx="120" cy="80" r="40" fill="red" />  <circle cx="150" cy="80" r="40" fill="red" />  <circle cx="150" cy="120" r="40" fill="red" />  <circle cx="100" cy="100" r="40" fill="red" /></svg>

<svg>действует как холст, на котором нарисованы все различные формы и фигуры. Его значения высоты и ширины указывают на размер, в который должен быть заключен весь рисунок. Если какая-то часть фигуры выходит за пределы размера SVG, эта часть будет обрезана.

Но кляксы не всегда такие идеально круглые... Мы можем смешать объекты, используя <ellipse> вместо <circle>:

<ellipse cx="200" cy="80" rx="100" ry="50" fill="red" />

Этот элемент очень похож на круг, за исключением имени тега и двух значений радиуса, чтобы определить горизонтальный (rx) и вертикальный (ry) радиусы по отдельности. Забавно то, что мы все равно можем получить правильный круг, если значения радиусов будут одинаковыми. Так что в некотором смысле <ellipse> немного более универсальный круг.

Если бы всё, что нам было нужно - это круг, мы, вероятно, могли бы использовать CSS без SVG. Любой прямоугольный элемент может стать кругом или эллипсом с правильно заданным значением border-radius.

.circle {  border-radius: 50%;  height: 50px;  width: 50px;}

но об этом немного позже.

Фристайл с SVG-путями

Благодаря тегу <path> в SVG мы можем создавать любые формы. Это как рисовать карандашом или ручкой. Вы начинаете с точки и рисуете линии, кривые, формы, а потом замыкаете путь.

В <path> есть много параметров для различных задач, например:

M Переход к точке

L Рисование линии

C Рисование кривой

Q Кривые Безье

Z Закрывание пути

Нам просто нужен параметр кривой (C) для примитивной кляксы. Но мы также переместим начальную точку и закроем путь, так что мы также воспользуемся параметрами M и Z.

Координаты играют большую роль в теге, поэтому то, что мы собираемся рассмотреть, будет выглядеть как данные Google Maps, заблокированные внутри нашего кода. Но это становится более логичным, когда мы знаем, что они делают.

Попробуем это

<svg xmlns="http://personeltest.ru/away/www.w3.org/2000/svg">  <path    fill="#24A148"    d=""  /></svg>

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

<path d="M 10 10 C 20 20, 40 20, 50 10" stroke="black" fill="transparent"/>

Это показывает, что наш путь начинается с координат (10 10), обозначенных буквой M перед ними. Затем он устанавливает кубическую кривую Безье (C) с двумя контрольными точками. Кривые Безье похожи на ручки на обоих концах пути, которые контролируют изгиб между ними. У нас есть две ручки Безье: одна для начального положения (20 20) кривой, а другая для конечного положения (40 20).

Давайте воспользуемся этими знаниями для создания нашего большого двоичного объекта.

Нарисованная мной клякса, на самом деле, немного сложна, с её множеством кривых и контрольных точек. Несколько смущает, что многие координаты не являются целыми числами, но, тем не менее, теперь, когда мы знаем, что делает параметр d в теге <path> и какие атрибуты он использует для рисования точек на пути, это не выглядит так страшно.

Очевидно, что это большой объем кода, который нужно не только написать вручную, но и сделать все правильно. Но благо, для этого есть инструменты, которые облегчат вашу жизнь.

www.blobmaker.app

Эффекты липкости с фильтрами CSS и SVG

Правда SVG <path> сложен? Что, если я представлю вам способ преобразовать множество пользовательских фигур (которые вы можете создавать с помощью div) в липкие кляксы? Идея состоит в следующем - мы собираемся создать два пересекающихся прямоугольника. Они одного цвета, но имеют небольшую прозрачность, чтобы затемнить места пересечения.

Затем мы используем функции размытия SVG, чтобы размазать прямоугольники, создав дополнительное липкое пятно с более мягкими краями.

Два пересекающихся прямоугольника превратятся в это

Давайте сначала разберемся, как работают фильтры в SVG. Они объявляются с помощью тега <filter> в элементах HTML или других элементах SVG, например, для тега <circle> можем применить CSS правило.

circle {  filter: url("#id_of_filter");}

<filter> - это, по сути, оболочка для фактических эффектов фильтра, которые включают:

  • <feGaussianBlur>

  • <feImage>

  • <feMerge>

  • <feColorMatrix>

Наша клякса размытая и цветная, поэтому мы собираемся использовать <feGaussianBlur>и <feColorMatrix>.

<feGaussianBlur>принимает несколько атрибутов, но нас интересуют только два из них: сколько размытия мы хотим и где мы этого хотим. Стандартное отклонение (stdDeviation) и свойства in соответствуют нашим запросам.

in принимает одно из двух значений:

SourceGraphic - размывает всю форму

SourceAlpha - размывает альфа-значение и используется для создания теневых эффектов.

Немного поигравшись, я обнаружил эффект <feGaussianBlur>:

<feGaussianBlur in="SourceGraphic" stdDeviation="30" />

Это вставляется прямо в разметке HTML с идентификатором, который мы вызываем для родительского элемента нашего большого двоичного объекта:

<!-- The SVG filter --><svg style="position: absolute; width: 0; height: 0;">  <filter id="goo">    <feGaussianBlur in="SourceGraphic" stdDeviation="30" />  </filter></svg><!-- The blob --><div class="hooks-main">  <div></div>  <div></div></div>

Фильтр на самом деле не отображается, даже если он находится в разметке. Вместо этого мы ссылаемся на него как на фильтр CSS в родительском элементе большого двоичного объекта:

Мдассс, это пока не то что нам надо. Размытие расфокусировано, а форма элемента потеряла границу и цвет. Нам нужен эффект выпуклости с размытием границ и сплошной цвет для заливки формы. Здесь в игру вступает наш следующий фильтр SVG, <feColorMatrix>.

Нам нужны два атрибута <feColorMatrix> :

in - указывает, где применяется эффект, как и <feGaussianBlur>.

values - матрица из четырех строк и пяти столбцов.

Атрибут values имеет немного больше нюансов. Он содержит матрицу, которая умножается на значения цвета и альфа канала каждого пикселя и генерирует новое значение цвета для этого пикселя. Математически говоря:

new pixel color value = ( values matrix )  ( current pixel color value )

Немного математики в наших трансформациях. В этом уравнении матрица значений равна:

[F-red1 F-green1 F-blue1 F-alpha1 F-constant1 F-red2 F-green2 F-blue2 F-alpha2 F-constant2 F-red3 F-green3 F-blue3 F-alpha3 F-constant3 F-red4 F-green4 F-blue4 F-alpha4 F-constant4]

Здесь F-red означает долю красного в пикселях со значением от 0 до 1.

F-constant - это некоторое постоянное значение, которое нужно добавить (или вычесть) из значения цвета.

Разбиваем эти значения дальше

У нас есть цветной пиксель со значением RGBA rgba (214, 232, 250, 1). Чтобы преобразовать его в новый цвет, мы умножим его на нашу матрицу значений.

Значение пикселя не изменилось, потому что мы умножили его на единичную матрицу, но если вы измените, значения матрицы, то изменится и ее значение в пикселях.

Узнайте больше о матрице значений из документации MDN.

В нашем случае эти значения работают очень хорошо:

<filter id="goo">  <feGaussianBlur in="SourceGraphic" stdDeviation="30" />  <feColorMatrix    in="blur"    values="1 0 0 0 0             0 1 0 0 0             0 0 1 0 0             0 0 0 30 -7"  /></filter>

Я добавил еще несколько стилей в кляксу, чтобы растянуть её из угла.

Попробуйте использовать эти значения фильтра в других формах и дайте мне знать, как они работают для вас, в комментариях.

Использование CSS border-radius

Как упоминалось раньше, можно создать кляксу и при помощи CSS свойстваborder-radius. Он также может создавать формы, похожие на капли, благодаря способности сглаживать углы элемента. Это возможно, потому что каждый угловой радиус разделен на два радиуса, по одному для каждой кромки. Вот почему у нас может быть больше форм, кроме круга и эллипса.

Возможно, вы привыкли использовать border-radius как сокращение для всех четырех углов элемента:

.rounded {  border-radius: 25%;}

Это хороший способ добиться единообразия всех углов. Но кляксы не такие однородные. Мы хотим, чтобы одни углы были более округлыми, чем другие, чтобы некоторые из них выглядели липкими. Вот почему мы выбираем составляющие свойства border-radius, например:

.element {  border-top-left-radius: 70% 60%;  border-top-right-radius: 30% 40%;  border-bottom-right-radius: 30% 60%;  border-bottom-left-radius: 70% 40%;}

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

Подробнее..

Как мы разработали интерактивную веб-схему для зрительных залов

26.06.2020 08:09:31 | Автор: admin
Иногда в приложении надо показать модель помещения допустим, кинотеатра или даже целого стадиона, если вы продаете билеты на концерт Metallica. Если в зале 50-100 тысяч мест, то для их вывода на экран нужно продумать плавный zoom, скроллинг и другие детали. Итак, главный вопрос как показывать тысячи элементов на экране, чтобы это было удобно для пользователей?

Недавно мы писали о скроллинге диаграмм с помощью d3.js, а сейчас хотим поделиться другим кейсом. Рассказываем, как с помощью Canvas можно разработать интерактивную схему зала, которую просто встраивать в любые веб-приложения.



В одном из проектов мы разработали интерактивную схему зрительного зала с удобным выбором билетов. В основе проекта достаточно стандартный стек технологий: HTML, CSS, JS, библиотека React.

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



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

  1. Плавный zoom и scrolling необходимы для отображения залов с большим количеством мест.
  2. Мини-карта для навигации по залу при максимальном приближении.
  3. Группировка мест по стоимости билетов, с выделением ценовых групп с помощью цвета.
  4. Тултипы для отображения информации о месте или секторе при наведении курсора.
  5. Возможность изменять наименования секторов, мест и рядов при проведении различных мероприятий в одном и том же зале.
  6. Кроссбраузерность и поддержка мобильных устройств.
  7. Гибкая конфигурируемость, которая позволит задать необходимые для конкретного мероприятия параметры, например, настройки увеличения и числа уровней zoom.
  8. Удобный и простой способ встраивания схемы в любые веб-приложения.

Далее расскажем об этапах реализации проекта, о том, какие были технические сложности и как мы их решали.

Выбор технологий


Для разработки приложения мы выбрали библиотеку React, чтобы повысить удобство и скорость разработки благодаря модульности, Virtual DOM и JSX. Для сборки мы взяли Webpack, который позволяет использовать множество различных плагинов для упрощения процесса разработки и поддержки большого числа браузеров.

Для решения задачи рендеринга мы выбирали из двух технологий:

  • SVG
  • Canvas

SVG позволяет создавать элементы любой формы и взаимодействовать с ними, однако, по оценкам заказчика, при количестве узлов более 40 000 значительно падает производительность.

Canvas обладает высокой производительностью, но в нём взаимодействие с элементами сложной формы требует реализации достаточно трудоемких алгоритмов. Эту проблему мы сначала попробовали решить с помощью библиотеки konva.js, но на схемах большого размера производительность была низкой.

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

  • Квота

Это список мест, которые не проданы и доступны пользователю. Квота содержит бизнес-данные, например, стоимость.

  • Список мест

Файл JSON для хранения массива мест с их координатами.

  • SVG-подложка

SVG-документ для хранения элементов, которые отвечают за взаимодействие с секторами, и различных декоративных элементов.

  • Объект конфигурации

Файл js для хранения объекта конфигурации (подробнее об этом мы расскажем далее в разделе Гибкая конфигурируемость).

После загрузки этих данных происходит парсинг SVG-документа с разделением на два отдельных документа: первый содержит элементы, предназначенные для взаимодействия с секторами, а второй декоративные элементы. Первый документ рендерится поверх Canvas, а второй в Canvas.

Рендеринг списка мест тоже осуществляем на Canvas, с учётом их доступности в квоте. Доступность мест выводится в зависимости от свойств конфигурации.

После этого получаем подобную схему:



Плавный zoom и scrolling


Для плавной анимации zoom на Canvas необходимо осуществлять множество перерисовок, что недопустимо при большом количестве мест, поскольку приложение начнет тормозить. Мы нашли следующее решение: анимация либо уменьшает, либо увеличивает Canvas. При этом качество изображения снижается, поскольку Canvas растровое изображение, но анимация происходит быстро, и человек не успевает увидеть искажения. С точки зрения пользователя происходит плавный zoom.

Пример работы:

1) До применения zoom.



2) После применения zoom к Canvas. Изображение немного размыто, но пользователь этого почти не замечает.



3) После перерисовки мест (изображение снова чёткое).



При реализации скроллинга Canvas двигается вместе с курсором. Рассмотрим, что происходит, когда пользователь отпускает левую кнопку мыши (курсор) либо использует свайп в смартфоне. В этот момент Canvas перерендеривается с учётом смещения и возвращается в исходное положение. Поскольку для пользователя это происходит очень быстро, ему кажется, что отсутствующие элементы просто рендерятся.

Пример работы:

1) До начала скроллинга.



2) Пользователь совершил скроллинг вправо, но не отпустил курсор (некоторые места в левой части схемы отсутствуют).



3) Пользователь отпустил курсор (отобразились отсутствующие места).



Таким образом, мы обратились к поэтапному рендерингу и реализовали плавную анимацию.

Далее расскажем о прочих задачах, реализованных при создании приложения.

  • Мини-карта


Мини-карта нужна для навигации по схеме зала. На карте показана видимая в данный момент область, которая помогает понять, в какой части общей схемы пользователь сейчас находится. При клике по мини-карте схема зала сразу же перемещается в новое положение. В приложении реализована связь между схемой зала и мини-картой. Если в одной части положение изменилось, то изменения моментально применяются и к другой части.

  • Группировка мест по ценам, с выделением групп с помощью цвета


Если мероприятие предусматривает несколько видов билетов с разной стоимостью, то для удобства пользователей мы автоматически группируем места по ценовой категории (на основе квоты).

Каждую группу мы выделяем заранее выбранным цветом. Для удобства пользователя предусмотрено верхнее меню, которое помогает определить цены в группе, а также сфокусироваться на подходящих группах.

В примере ниже пользователь фокусируется на билетах, стоимость которых выше 7000 рублей.



  • Тултипы


Тултипы нужны для того, чтобы отобразить пользователю данные о выбранном секторе или месте при наведении или нажатии. Для этого мы определяем все возможные варианты расположения мест и, соответственно, различные форматы тултипов.

Например, ниже представлен тултип места, которое находится в углу схемы:



В нашем приложении предусмотрено два вида тултипов: для секторов и для мест. С помощью файла конфигурации и HTML-макетов можно настроить их содержимое (например, названия секторов, стилизацию тултипов).

  • Маппинги мест


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

Для того, чтобы не создавать новую схему для каждого мероприятия, мы использовали маппинг мест. Это JS-метод, который принимает имена секторов, рядов и мест, а затем формирует имена, соответствующие квоте конкретного мероприятия. Мы реализовали специальные методы в конфигурационном JS-файле для секторов, рядов и мест, чтобы иметь возможность переименовать их. В результате в приложении все обозначения можно изменить.

  • Кроссбраузерность


Для охвата наибольшего числа пользователей нужно поддерживать как новые, так и старые версии браузеров, включая Internet Explorer, и мобильные устройства. Сейчас приложение поддерживает все привычные пользователям жесты, в том числе мультитач. Для этого в приложении подключена библиотека hammer.js.

Мы провели масштабное тестирование на большом количестве устройств, а также в приложении BrowserStack для эмуляции других видов устройств, и убедились в корректной работе продукта.

  • Гибкая конфигурируемость


В приложении использован специальный файл javascript для формирования конфигурационного объекта, содержащего все свойства конфигурации, которые мы упоминали ранее (стилизация мест, стилизация тултипов, алгоритм zoom, маппинги, группировка мест), а также множество других свойств.

  • Удобство встраивания


Для легкой интеграции с любыми JS приложениями мы предусмотрели нативную обёртку javascript. Также для решения проблем с кроссдоменностью мы используем iframe. В результате схему можно встраивать в любое веб- или мобильное приложение.

Подводя итоги


В статье мы описали способ создания интерактивной веб-схемы помещения на Canvas. С помощью такого приложения пользователь может быстро и просто ориентироваться в схеме зала и выбирать места (билеты), которые ему подходят.

Спасибо за внимание! Надеемся, что статья была вам полезна.
Подробнее..

Визуализация сложных данных с использованием D3 и React

15.10.2020 16:12:34 | Автор: admin

Существует много возможный вариантов реализации сложных графиков в ваших проектах. Я за несколько лет попробовал все возможные варианты. Сначала это были готовые библиотеки типа AmCharts 4. AmCharts сразу же оказался большим и неповоротливым. После этого были более гибкие и дружелюбные библиотеки, такие как Recharts. Recharts был поначалу очень хорош, но со временем сложные фичи создавались такими костылями, которые даже показывать стыдно, а какие-то фичи и вовсе были невозможны в реализации. Таким образом, я пришел к D3 и решаю на нем любые задачи, связанные с графиками. Иногда это занимает немного больше времени по сравнению с готовыми инструментами. Но остается одно неоспоримое преимущество мы всегда знаем, что никогда не упремся в рамки и ваш код не захочется отправить в помойку через пару месяцев.


Какая цель этой статьи? Я хочу рассказать вам про крутой инструмент и о том, как его максимально эффективно использовать в связке с React. Мы последовательно разберем универсальный рецепт для построения компонентов любой сложности.




Посмотреть на результат (спойлер)

Сложные данные


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


Пару примеров эффективного восприятия информации:


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

Что из себя представляет D3


D3.js это javaScript библиотека для обработки и визуализации данных. Она включает в себя функции для масштабирования, утилиты для манипуляции с данными и DOM-узлами.


При этом, скажу сразу, что большая часть этой библиотеки уже устарела, и ее не стоит использовать. Именно ту часть, где идут манипуляции с DOM узлами, эту задачу мы будем максимально перекладывать на React.


1. Абстрагирование от физических размеров


Я не просто так начинаю с этого пункта. Самое первое, что необходимо сделать, когда приступаем к разработке графика это абстрагироваться от физических размеров. Ну скажем, чтобы мы могли получать координаты точек и сразу же записывать их в атрибуты. Соответственно, нам нужны какие-то методы, которые получают на вход значение или название категории, а на выходе отдают координаты в пикселях.


getY(`значение`); \\ возвращает координату по оси y в пикселяхgetX(`название категории`); \\ возвращает координату по оси x в пикселях

Мы один раз создаем такие функции на один компонент, какой бы он сложный не оказался в итоге. А дальше используем эти функции везде, где нужно создать какой-то элемент, позиция которого зависит от данных.


К счастью в D3 это сделать очень просто.


Получение координат по оси Y (ось значения)


На изображении показано положение точек из массива [4, 15, 28, 35, 40] в контейнере выстой 300px:



Теперь посмотрите как с помощью D3 создать функцию для получения физических координат для отрисовки этих точек:


const getY = d3.scaleLinear()  .domain([0, 40])  .range([300, 0]);

Мы создаем функцию getY с помощью D3 функции scaleLinear(). В метод domain передаем область данных, а в range передаем физические размеры от 300px до 0px. Так как в svg отчет начинается с левого верхнего угла, то нужно именно в таком порядке передавать аргументы в range сначала 300, потом 0.


Мы только один раз работаем с физическими размерами, когда создаем эту функцию и передаем в нее высоту графика. После этого мы работаем только с реальными данными и сразу же выводим полученные размеры в svg атрибуты.


Пример применения функции getY:


getY(4);  // 270getY(15); // 187.5getY(28); // 90getY(35); // 37.5getY(40); // 0

В качестве аргумента мы передаем значение, а на выходе получаем координату по оси y. Обратите внимание, что это отступ сверху контейнера.


Получение координат по оси X (ось категории)


Аналогичная ситуация по оси X. Мы хотим один раз подвязаться к категориям, а дальше передавать название категории и получать ее координаты.


На изображении мы видим контейнер шириной 600px и 5 месяцев. Месяца будут служить подписями по оси X:



Создадим такую функцию:


const getX = d3.scaleBand()  .domain(['Jan', 'Feb', 'Mar', 'Apr', 'May'])  .range([0, 600]);

Мы используем функцию scaleBand из D3. В domain мы передаем все возможные категории в нужном порядке, а в range область, выделенную под график.


Смотрим пример применения нашей функции getX:


getX('Jan'); // 0getX('Feb'); // 120getX('Mar'); // 240getX('Apr'); // 360getX('May'); // 480

В качестве аргумента мы передаем название категории, а на выходе получаем координату по оси X (отступ слева).


2. Отрисовка простых фигур


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


  • rect прямоугольник;
  • circle круг;
  • line линия;
  • text обычный блок текста.

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


Точки


Для примера попробуем нарисовать точки с использованием svg-фигуры circle:


const data = [  { name: 'Jan', value: 40 },  { name: 'Feb', value: 35 },  { name: 'Mar', value: 4 },  { name: 'Apr', value: 28 },  { name: 'May', value: 15 },];return (  <svg width={600} height={300}>    {data.map((item, index) => {      return (        <circle          key={index}          cx={getX(item.name) + getX.bandwidth() / 2}          cy={getY(item.value)}          r={4}          fill="#7cb5ec"        />      );    })}  </svg>);

Фигура circle абсолютно примитивна. В данном случае она принимает координаты центра cx, cy, радиус r и цвет заливки fill.


Здесь мы использовали новый метод bandwidth:


getX.bandwidth()

Данный метод возвращает ширину колонки расстояние от одного месяца до соседнего. Мы применяем этот метод для того, чтобы сдвинуть наши точки до центра колонки:


getX(item.name) + getX.bandwidth() / 2

Вот, что у нас получится в результате:



Подписи


Для создания текстовых узлов в svg используется фигура text. Она также принимает координаты и содержит свои личные атрибуты для стилизации.


Подпишем значения на наших точках:


return (  <svg ...>    {data.map((item, index) => {      return (        <g key={index}>          <circle ... />          <text            fill="#666"            x={getX(item.name) + getX.bandwidth() / 2}            y={getY(item.value) - 10}            textAnchor="middle"          >            {item.value}          </text>        </g>      );    })}  </svg>);

Что здесь нового? Мы обернули наш круг и текст элементом g. Элемент g один из самых распространенных в svg, обычно он просто группирует элементы и двигает их вместе при необходимости через свойство transform.


Вот как выглядят наши подписи к точкам:



3. Оси


Для осей существуют готовые элементы в D3.


const getYAxis = ref => {  const yAxis = d3.axisLeft(getY);  d3.select(ref).call(yAxis);};const getXAxis = ref => {  const xAxis = d3.axisBottom(getX);  d3.select(ref).call(xAxis);};return (  <svg ...>    <g ref={getYAxis} />    <g      ref={getXAxis}      transform={`translate(0,${getY(0)})`} // нужно сдвинуть ось в самый низ svg    />    ...  </svg>);

Вот что получается, если ничего не менять и не настраивать:



Попробуем добавить немного красоты и переопределим изначальные стили:


const getYAxis = ref => {  const yAxis = d3.axisLeft(getY)    .tickSize(-600) // ширина горизонтальных линий на графике    .tickPadding(7); // отступ значений от самого графика  d3.select(ref).call(yAxis);};const getXAxis = ref => {  const xAxis = d3.axisBottom(getX);  d3.select(ref).call(xAxis);};return (  <svg  ...>    <g className="axis" ref={getYAxis} />    <g      className="axis xAxis"      ref={getXAxis}      transform={`translate(0,${getY(0)})`}    />    ...  </svg>);

И немного стилей:


.axis {  color: #ccd6eb;  & text {    color: #666;  }  & .domain {    display: none;  }}.xAxis {  & line {    display: none;  }}

Посмотрим как сейчас выглядит наш пример:



4. Отрисовка сложных фигур


У svg нет каких-то встроенных простых методов для построения кривых по точкам, секций круга и так далее. Это достаточно сложный процесс на низком уровне. D3 предоставляет методы для построения таких сложных фигур.


Кривые линии


Начнем с обычной кривой линии, для которой мы уже построили точки:


const linePath = d3  .line()  .x(d => getX(d.name) + getX.bandwidth() / 2)  .y(d => getY(d.value))  .curve(d3.curveMonotoneX)(data);// M60,0C100,6.25,140,12.5,180,37.5C220,62.5,260,270,300,270C340,270,380,90,420,90C460,90,500,138.75,540,187.5

В качестве аргумента line() мы передаем наш массив с данными data, а D3 уже под капотом проходится по этому массиву и вызывает функции для поиска координат, которые мы передали в методы x и y. В curve мы передаем тип линии, в данном случае это curveNatural (таких типов достаточно много).


Теперь немного разберем полученную строку. Команда M используется в строки для указания точки, откуда нужно начать рисовать. Команда С это кубическая кривая Безье, которая принимает три набора координат, по которым строит кривую. Подробнее можно почитать здесь https://developer.mozilla.org/ru/docs/Web/SVG/Tutorial/Paths.


Теперь просто вставляем полученную строку в качестве атрибута d для элемента path:


return (  <svg  ...>        <path      strokeWidth={3}      fill="none"      stroke="#7cb5ec"      d={linePath}    />    </svg>);

Path одна из самых распространенных фигур в svg из которой можно сделать практически что угодно. Мы еще будем использовать эту фигуру дальше.


Смотрим на результат:



Замкнутые области


Теперь мы попробуем построить замкнутую области с одной кривой стороной. Она будет использоваться в качестве заливки для графика.


В построении области с кривой стороной похожая ситуация, как и с кривой линией. Здесь используется функция area, а методов становится больше, потому что нужно передать отдельно функцию для поиска нижней линии. Если нам нужна прямая нижняя линия, то просто передаем нулевое значение по низу.


const areaPath = d3.area()  .x(d => getX(d.name) + getX.bandwidth() / 2)  .y0(d => getY(d.value))  .y1(() => getY(0))  .curve(d3.curveMonotoneX)(data);// M60,300C100,300,140,300,180,300C220,300,260,300,300,300C340,300,380,300,420,300C460,300,500,300,540,300L540,187.5C500,138.75,460,90,420,90C380,90,340,270,300,270C260,270,220,62.5,180,37.5C140,12.5,100,6.25,60,0Z

На выходе также получаем путь, который нужно передать в фигуру path. Здесь в конце пути появляется новая команда Z, которая замыкает контур, рисуя прямую линию от текущего положения обратно к первой точке пути. А также в середине строки есть команда L, которая рисует прямую линию от текущей точки.


Добавляем полученную строку в path:


return (  <svg  ...>        <path      fill="#7cb5ec"      d={areaPath}      opacity={0.2}    />      </svg>);

Смотрим на нашу красоту:



5. События


Мы игнорируем все методы для навешивания событий из D3. Эту задачу мы также перекладываем на React и вешаем все события прям в разметке JSX. А для хранения состояний используем знакомый всем хук useState.


Эффект наведения


Подробнее рассмотрим эффект наведения, остальные события делаются аналогично.


Наша задача сделать эффект увеличения точки при наведении на всю область категории. Так как у нас нет определенного прямоугольника в DOM, на которое можно повесить событие напрямую, то мы будем вешать событие на всю svg, а затем вычислять позицию.


Но для начало заведем состояние активной категории:


// null  если ничего не активно (по умолчанию)const [activeIndex, setActiveIndex] = useState(null);

После этого пишем наш обработчик:


const handleMouseMove = (e) => {  const x = e.nativeEvent.offsetX; // количество пикселей от левого края svg  const index = Math.floor(x / getX.step()); // делим количество пикселей на ширину одной колонки и получаем индекс  setActiveIndex(index); // обновляем наше состояние};return (  <svg        onMouseMove={handleMouseMove}  ></svg>)

И добавим событие, которое будет сбрасывать активный индекс, когда мы убираем мышку с svg:


const handleMouseMove = (e) => {  };const handleMouseLeave = () => {  setActiveIndex(null);};return (  <svg        onMouseMove={handleMouseMove}    onMouseLeave={handleMouseLeave}  ></svg>)

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


data.map((item, index) => {  return (    <g key={index}>      <circle        cx={getX(item.name) + getX.bandwidth() / 2}        cy={getY(item.value)}        r={index === activeIndex ? 6 : 4} // при наведении просто немного увеличиваем круг        fill="#7cb5ec"        strokeWidth={index === activeIndex ? 2 : 0} // обводка появляется только при наведении        stroke="#fff" // добавили белый цвет для обводки        style={{ transition: `ease-out .1s` }}      />          </g>  );})

И теперь смотрим на результат:



Итог


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



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


Мы выкидываем из D3 все устаревшие методы для прямой манипуляции элементами DOMа и делам это как знали и умели до этого.


В интернете будет много примеров, которые будут сбивать вас с толку и заставлять писать в стиле jQuery, будьте внимательны. Надеюсь эта статья вам поможет сделать всё красиво!

Подробнее..

Дефицит цветов в современных фильмах

26.10.2020 02:07:54 | Автор: admin

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

Well, enough said. Источник: https://gridfiti.com/visually-stunning-movies/Well, enough said. Источник: https://gridfiti.com/visually-stunning-movies/

Я в таких своих наблюдениях оказался совсем не одинок, вот, например, критическая статья о злоупотреблении этими вашими Голливудами оранжевым и голубовато-зелёным. Однако, одиночные кадры - это не серьёзно для доказательства. Тут нужен научный подход. Как астроном-любитель я знаю, что информацию о цвете чего-либо принято сравнивать в виде спектра.

Эмиссионный спектр бора. Источник: https://commons.wikimedia.org/wiki/File:Boronemissionspectrum.pngЭмиссионный спектр бора. Источник: https://commons.wikimedia.org/wiki/File:Boronemissionspectrum.png

Хочется получить такой вот примерно "спектр" для целого видео. Осталось придумать, как это сделать.

Идея

Упрощённо, видео есть последовательность изображений. Каждое изображение - массив RGB значений цветов пикселей. Можно заметить, что в спектре есть две оси: цвет (или длина волны) по горизонтали и уровень сигнала по вертикали. Значит, необходимо превратить трёхмерный (RGB) цвет в одномерный. И тут на помощь приходит цветовая модель HSL, где для цвета есть только одно измерение - Hue, в дополнение к Saturation и Lightness.

Теперь, для каждого пикселя видео и соответствующего ему значения "чистого цвета" (Hue) нужно определить вес чистого цвета на основе значений Saturation и Lightness. В случае с Saturation всё понятно и просто - вес должен быть пропорционален насыщенности цвета. С Lightness немножко сложнее: начения 0% и 100% будут соответствовать чёрному и белому цветам вне зависимости от значения Hue, поэтому максимальный вес (1) должен быть для значения Lightness = 50%, снижаясь "по краям" до 0, так как совсем светлые и совсем тёмные оттенки должны иметь меньший вес. Таким образом, можно вывести формулу уровня цветового сигнала:

W (S, L) = S * (0.52 - (0.5 - L)2) / 0.52

Это значение W (weight) будет численной мерой, показывающей, насколько данный цвет выглядит ярким и выраженным. Далее, для получения спектра достаточно сложить все полученные из пикселей веса (W). Стоит отдельно заметить то, что полученный "вес выразительности" цвета всё-таки субъективен, так как зависимость от яркости (L) может быть иной, например линейной. Или какой-нибудь ещё. Но я считаю свой вариант довольно честным.

Реализация

Осталось реализовать всё это в коде и я решил сделать это на Golang. К счастью, все необходимые биндинги для превращения видео в отдельные кадры, конвертации RGB в HSL и прочего уже доступны. Исходное видео конвертируется в кадры с разрешением 256х144, то есть ~37 килопикселей. Чтобы избежать слишком задумчивого переваривания при скармливании больших видеофайлов код может пропускать промежуточные кадры, чтобы не превысить лимит количества обрабатываемых кадров (хард код 2712 на данный момент). То есть любое видео будет ограничено более-менее равномерной выборкой из 100 миллионов пикселей. Для цветовой статистики вроде бы должно быть достаточно. Есть небольшая проблема, что не все промежуточные кадры одинаково полезны могут быть успешно сконвертированы. Судя по всему это происходит при попадании на не-ключевые кадры. Это может немного уменьшать выборку из-за ошибок.

Изначально это была консольная утилита, получающая на вход имя видеофайла и сохраняющая файл спектра в файл с тем же именем плюс разрешение ".svg". Позже я также добавил простейший web-cервис и форму для upload видео файла в газоанализатор для получения "спектра". Сервис раздеплоен временно-бесплатно в первом попавшемся облаке, так что желающие могут попробовать немножко поэкспериментировать: https://moviespectrum.azurewebsites.net/.

На размер загружаемого видеофайла стоит ограничение 128 Мб, поэтому рекомендуется использовать не самое лучшее разрешение, тем более что оно слабо влияет на цветовую статистику и видео всё равно конвертируется в кадры 256х144 перед обработкой.

В облаке сервис работает на минимальных ресурсах и никак не приспособлен для масштабирования, так что если он вдруг приляжет вследствие хабраэффекта или по какой другой причине, то вы всегда можете собрать его из безисходников и запустить локально, например в докере: https://github.com/akurilov/moviespectrum

Также сначала была идея прикрутить это всё к тытубу, чтобы пользователь мог просто кинуть ссылку на видео, однако из-за инцидента с youtube-dl я решил не рисковать и отпилить всю подобную функциональность. Даже вспомнилось Unix-заклинание "один инструмент = одна функция" в качестве оправдания. Так что теперь придётся сначала скачать видео, а потом уже скармливать его.

Результаты

Возьмём в качестве baseline пару олдскульных фильмов и посмотрим на их "спектры".

1991 - Терминатор 2:

1994 - Форрест Гамп:

Теперь возьмём фильмы посвежее и сравним:

1999 - Матрица aka 50 оттенков зелёного, если не считать фиолетового артефакта:

2003 - Властелин колец:

2005 - Город грехов. В качестве малоцветного baseline:

2007 - Трансформеры. Очень малоцветный фильм, не считая фиолетового артефакта:

2009 - Аватар:

2013 - Обливион:

2019 - Джокер. Всё почти в красном цвете, но есть немножно зеленовато-голубого.

Выводы вы можете сделать самостоятельно. На мой взгляд, есть тенденция к обеднению цветов со временем.

Подробнее..
Категории: Работа с видео , Rgb , Golang , Svg , Ffmpeg , Opensource , Movies , Hsl

Перевод Симулируем сцену подбора PIN из Терминатора 2

08.01.2021 12:15:36 | Автор: admin
В начале фильма Терминатор 2: Судный день Джон Коннор использует лэптоп для подбора PIN украденной дебетовой карты.


Кажется, я не смотрел Терминатор 2 в кинотеатре. Вероятно, я посмотрел его несколько лет спустя на LaserDisc, но эта сцена произвела довольно сильное впечатление. Похожую реакцию вызвали номеронабиратель из Военных игр и чёрный ящик из Тихушников.


Номеронабиратель (War Dialer) из Военных игр (1983 год)


Чёрный ящик из Тихушников (1992 год)

Недавно я вспомнил эту сцену из Терминатора 2, поэтому начал гуглить лэптоп из Терминатора 2.

Оказалось, что это Atari Portfolio первый в мире палмтоп-компьютер (наладонный компьютер). Он был выпущен в июне 1989 года.


Компьютер имел монохромный ЖК-дисплей с разрешением 240x64 пикселей или 40 символов x 8 строк и работал от трёх батареек AA.

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

Для начала изучим нужные технические требования!

Если посмотреть видео внимательно, то видно, что первым делом отображается баннер программы.

banner

Изображение достаточно чёткое, чтобы мы могли с лёгкостью скопировать баннер.

PPPPP   IIIIIII   N    NP   PP     I      NN   N IDENTIFICATIONP   PP     I      N N  NPPPPP      I      N  N N   PROGRAMP          I      N   NNP       IIIIIII   N    NStrike a key when ready ...

После этого Джон нажимает на Enter и на экране начинают прокручиваться числа. Если посмотреть спустя несколько кадров:


то мы увидим, что первая строка чисел выглядит так:

12345678901234567890123457890123456780

Можно было бы предположить, что это просто повторяющиеся четыре раза цифры с 1 по 0, но при внимательном изучении выясняется, что строка длиной всего 38 символов. В третьем повторе пропущена цифра 6, а в последнем цифра 9.

Принцип уменьшения чисел тоже неочевиден, но похоже, что программа выводит примерно по 5 строк определённой длины, а затем уменьшает длину на 1, однако после следующего набора она уменьшает длину на 2, а затем попеременно использует уменьшение на 1 и 2, пока не определит PIN из четырёх цифр, который выводит в командную строку.

Ну, всё выглядит довольно просто. Я осваивал Python, поэтому написал скрипт на Python 3:

#!/usr/bin/env python3import timeimport randomdelay = 0.025print("PPPPP   IIIIIII   N    N")time.sleep(delay)print("P   PP     I      NN   N IDENTIFICATION")time.sleep(delay)print("P   PP     I      N N  N")time.sleep(delay)print("PPPPP      I      N  N N   PROGRAM")time.sleep(delay)print("P          I      N   NN")time.sleep(delay)print("P       IIIIIII   N    N")time.sleep(delay)print('')input("Strike a key when ready ...")print("\n\n12345678901234567890123457890123456780")lines = 1length = 38decrease = 1while True:    for i in range(0, length):        print(random.randint(0,9), end='')    print('')    time.sleep(delay)    lines += 1    if (lines == 5):        lines = 0        length -= decrease        if (decrease == 1):            decrease = 2        else:            decrease = 1    if (length <= 4):        breakfor i in range(0, 10):    print("9003")print("\nPIN IDENTIFICATION NUMBER: 9003")print("\na>", end='')

Скрипт выполняется очень быстро, поэтому я добавил задержку между строками, чтобы скорость была такая же, как в отрывке из фильма. Я уверен, что код можно ещё оптимизировать, но если бы я рассматривал это как задачу написания плохого кода на техническом собеседовании, то считал бы, что справился с заданием.

При помощи поиска Google по картинкам я нашёл сайт, продающий пластмассовые панели для Atari Portfolio с нанесённой на экран красивой графикой:


Немного поэкспериментировав с termtosvg, в частности, с функцией SVG-шаблонов, я смог создать этот безумный SVG:


Несмотря на то, что я уже больше десяти лет поддерживаю html5zombo.com, до создания этого SVG я не ценил всех их возможностей. Они могут встраивать изображения? CSS? Javascript? Любой сайт, позволяющий пользователям загружать произвольные SVG и рендерить их, теперь получил моё величайшее уважение.

Пока я развлекался созданием своего небольшого автономного SVG, меня не покидала мысль о том, что мой код на Python на самом деле никогда бы не запустился на Atari Portfolio. В Atari Portfolio установлена DIP Operating System 2.11 (DIP DOS), по большей части совместимая с MS-DOS.

В первых классах старшей школы, ещё до того, как мне начали платить за профессиональное написание ПО, я писал софт для BBS, моды и игры на смеси Turbo Pascal и скриптового языка PCBoard Programming Language, напоминавшего BASIC. Проведя минимальные исследования, я выяснил, что если смогу написать программу на Turbo Pascal и скомпилировать её, то она, вероятно, будет работать на Atari Portfolio.

Я не писал на Turbo Pascal почти 25 лет, но ведь такое не забывается?

Мне нравится форк DOSBox под названием DOSBox-X, поэтому я скачал и установил самую последнюю SDL2-версию для OS X. Затем я нашёл Borland Turbo Pascal 7.0, который помещу сюда, потому что искать его было настоящим мучением.

В этом ZIP вы найдёте четыре файла, которые являются образами гибких дисков. Если поместить их в папку, например, ~/tp, после запуска DOSBox-X и монтирования диска C вы сможете смонтировать их как диск A следующим образом:

imgmount a ~/tp/Disk01.img ~/tp/Disk02.img ~/tp/Disk03.img ~/tp/Disk04.img -floppy

после чего переключиться на диск A: и запустить INSTALL:

A:INSTALL

turbo pascal install

turbo pascal install

turbo pascal install

turbo pascal install

Время от времени придётся заменять гибкие диски, ведь это был 1992 год.

turbo pascal install

Это можно сделать, выбрав в DOSBox-X Drive -> A -> Swap disk. Выполнится переход с Disk 1 на Disk 2. Затем просто продолжайте повторять процесс и нажимать на Ввод, пока не установятся все четыре диска.

После завершения установки она попросить настроить CONFIG.SYS и AUTOEXEC.BAT (не забывайте, это 1992 год).


Ни то, ни другое необязательно. DOSBox-X и так задаёт значение FILES выше необходимого, а добавление к путям просто позволяет запускать TURBO из любого места. После завершения можно выполнить следующую команду:

C:cd tp\binTURBO



В детстве я провёл столько времени с этим IDE, что теперь испытал своего рода ностальгию. Но потом я начал портировать свой скрипт Python на Pascal и ностальгия быстро рассеялась. Хотел бы я сказать, что написал всё целиком в IDE, но в какой-то момент мне пришлось перейти в VSCode, а потом скопировать файл обратно в папку DOS. Люди, которые до сих пор работают в WordPerfect for DOS, я вас и понимаю, и не понимаю.

Вот скрипт, который я получил, потратив много времени на этот туториал по Pascal:

program pinid;uses crt;var i: byte;var pos: byte;var lines: byte;var length: byte;var decrease: byte;var delay_amount: integer;begin     randomize;     delay_amount := 25;     clrscr;     writeln('PPPPP   IIIIIII   N    N');     delay(delay_amount);     writeln('P   PP     I      NN   N IDENTIFICATION');     delay(delay_amount);     writeln('P   PP     I      N N  N');     delay(delay_amount);     writeln('PPPPP      I      N  N N   PROGRAM');     delay(delay_amount);     writeln('P          I      N   NN');     delay(delay_amount);     writeln('P       IIIIIII   N    N');     delay(delay_amount);     writeln('');     write('Strike a key when ready ...');     readln;     writeln('');     writeln('');     writeln('12345678901234567890123457890123456780');     pos := 0;     lines := 1;     length := 38;     decrease := 1;     while true do     begin          for i:= 1 to length do                write(random(9));          writeln('');          delay(delay_amount);          lines := lines + 1;          if (lines = 5) then          begin               lines := 0;               length := length - decrease;               if (decrease = 1) then                   decrease := 2               else                   decrease := 1;          end;          if (length <= 4) then               break;     end;     for i:= 1 to 10 do     begin          writeln('9003');          delay(delay_amount);     end;     writeln('');     writeln('PIN IDENTIFICATION NUMBER: 9003');     writeln('');end.

Краткие объяснения:

  • В Pascal есть объявления типов. Тип byte может быть числом в интервале 0-255.
  • Файлы начинаются с program и названия программы, вероятно потому, что все модули имеют общее пространство имён, но имя файла не важно.
  • Модули импортируются словом uses. Модуль crt используется для манипулирования экраном.
  • := это синтаксис присвоения значения переменной, чтобы можно было сравнивать при помощи = и отличать их друг от друга.
  • Если блоки длиннее одной строки, их нужно оборачивать в begin and end, а не в фигурные скобки или пробелы.
  • Если в начале скрипта не выполнить randomize, то создаваемые случайные числа всегда будут одинаковыми, как и выходные строки.
  • WRITE выводит строку, WRITELN выводит строку с переводом строки. READLN получает ввод до получения перевода строки.

Работает ли код? Вот запущенная в DOSBox-X программа:


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

  1. Купить на Ebay Atari Portfolio.
  2. Купить параллельный интерфейс Atari Portfolio и, вероятно, новую переднюю панель, потому что старая наверняка поцарапана.
  3. Найти в моей коробке с кабелями параллельный кабель.
  4. Найти PC или лэптоп с параллельным портом, установить на него MS-DOS v6.22.
  5. Скачать FT.COM и установить его на PC.
  6. Собрать EXE в Dosbox-X и передать его на Atari Portfolio.
  7. Украсть дебетовую карту.
  8. Обернуть часть карты алюминиевой фольгой, купить последовательный интерфейс Atari Portfolio, подключить кабель к карте.
  9. Запустить программу.
  10. Лёгкие деньги!




На правах рекламы


Закажи и сразу работай! Создание VDS любой конфигурации и с любой операционной системой в течение минуты. Максимальная конфигурация позволит оторваться на полную 128 ядер CPU, 512 ГБ RAM, 4000 ГБ NVMe. Эпичненько :)

Подробнее..

SVGator.com на практике

20.05.2021 18:13:57 | Автор: admin

Привет, мы дизайнеры экосистемы Своё для фермеров и банковских сервисов Россельхозбанка. Рассказываем, зачем нам понадобился SVGator.

Как мы пришли к SVGator.com

Digital-рынок сегодня насыщен хорошими решениями, тренды не стоят на месте, одним
из доминантных признаков качества становится анимация интерфейса. В рамках разработки группы продуктов Своё и банковских сервисов Россельхозбанка мы с командой рассматривали
различные решения.

Наш путь начался с lottie, как с более популярного сервиса. Ключевой недостаток: встроенная api. Нам хотелось найти более прагматичное решение без интеграции, поэтому выбор пал на SVGator. В наши цели попадало закрытие микроанимаций элементов интерфейса, оформительские детали и т.п.

Что за зверь и какие задачи решает

SVGator.com это web-платформа для создания svg-анимаций, то есть svg-файлов со встроенными анимациями, которые без каких-либо проблем интегрируются в html. Можно задать последовательную обработку таких анимаций. Возможен экспорт как js, так и чистым CSS. Возможности js немного шире, но разница не существенная.

В рамках продуктов группы Своё мы создавали различные анимации для лоадеров, микроэлементов (таких как стрелка дропдауна), логотипов и элементов оформления. Мы ещё находимся на стадии экспериментов с SVGator, но уже чётко понимаем, в каких моментах он имеет преимущества и что можно создать с его помощью:

Небольшие функциональные элементы интерфейса. Например, интерактивные состояния компонентов и вытекающая микроанимация

Регулярные объекты, которые можно усилить посредством анимации. Например, логотипы, в частности, кейс с кастомизацией лого под праздники

Элементы дизайна. Например, иллюстрации

Интерфейс и функционал

На данный момент SVGator располагает исключительно веб-платформой, но весь необходимый функционал присутствует. Интерфейс напоминает классическое решение канувшего в Лету Flash и взявшего бразды правления After Effects. Слева слои файла. Сверху панель инструментов. Снизу таймлайн, а по центру сама рабочая область. Если есть навыки работы с АЕ, совершенно точно SVGator покажется очень простым. Но в этом и его преимущество порог входа значительно ниже.

Инструмент включает в себя следующие функции: работа с размером, положением, поворотом, наклоном, цветом, прозрачностью, а также богатый функционал для работы с обводкой. Помимо этого, поддерживается работа с масками и векторами движения. Этого достаточно для работы с классическими решениями анимации. Ещё один важный момент работа с easing, которые также встраиваются в svg-файл. Это упрощает работу на этапе front-end, и дизайнеру не приходится тревожить разработчика скоростью анимации.

Механика работы несколько отличается от привычных решений After Effects, вы выбираете необходимый элемент в меню слева и добавляете на него нужный эффект. После этого слой появляется в области таймлайна и можно менять значения, добиваясь нужной анимации. Сами значения находятся на панели справа. Easing могут быть привязаны как ко всему таймлайну, так и к конкретному отрезку между keyframes.

Чем svg отличается от sequence, gif, video? А также основные конкуренты:
Lottie и Keyshape

Ключевые преимущества svg вес, отсутствие api и возможность давать плавную анимацию. Разберём каждую из альтернатив.

Sequence. Работа с секвенциями никогда не была популярна, поскольку вес каждой отдельной png с прозрачностью может быть существенным и это скажется на производительности.

Gif. Решение потеряло свою актуальность также из-за веса. Однако есть и другой минус отсутствие плавности анимации. Часто в погоне за оптимизацией размера дизайнерам приходится поскупиться количеством кадров в секунду, а также сама постобработка оставляет желать лучшего.

Video. Безусловно занимало и занимает свою нишу, обладая весьма большими перспективами. Одна из ключевых проблем данного решения отсутствие альфа-канала на выходе, что не позволяет сделать элементы прозрачными. Но сейчас очень популярны решения,
в которых видео является частью слоя, а управляется, например, через скрол. У данного решения много поддержки в комьюнити.

Lottie. Это один из ключевых конкурентов SVGator. Компания раньше вышла на рынок и имеет значительно больше почитателей. Плюс возможности анимации значительно больше, поскольку базовая анимация собирается в Аfter Еffects, а lottie является своего рода оболочкой для такой анимации. Но для работы с данным решением необходимо предустановить api на платформу, что не всегда хорошо для продакшена больших нагруженных систем.

Keyshape. Очень близкая по возможностям платформа, различия лишь в деталях. Текущий функционал Keyshape несколько выше и позволяет делать экспорт чистым svg + css. К недостаткам можно отнести её стационарность и возможность работать исключительно на macOS.

Вот поэтому и стоит обратить внимание на SVGator. Плавные анимации, реализованные кодом, кроссплатформенные, без каких-либо api и с нормальным весом.

SVGator в экосистеме Своё

На наших продуктах Своё Фермерство, Своё Жильё, Своё Родное, Монеты, Подбор персонала, Своё Село и т.д. уже присутствуют наработки в SVGator: лоадер, интерактивные состояния компонентов, логотипы, а также иллюстрации для оформления разделов. Мы планируем увеличивать их долю и продолжать экспериментировать.

В настоящее время работаем над возможностью покадровой анимации на примере движения коровы. Если наш эксперимент закончится успехом, вероятно, мы ещё увеличим долю подобных решений.

Перспективы

Плюсов масса, они были перечислены раньше. Но без ложки дёгтя, к сожалению, не обойтись. SVGator.com практически идеален для небольших элементов. Но когда речь заходит о чём-то более масштабном, например, вы хотите реализовать глобальную фоновую анимацию, которая перекрывает всю страницу вы рискуете столкнуться с высокими требованиями к серверному
оборудованию.

Объёмные решения склонны к излишней нагрузке системы и могут стать камнем преткновения. Поэтому в наших планах использовать этот инструмент поступательно, небольшими шагами, анимируя поначалу небольшие элементы.

Важно понимать, что продукт ещё очень молодой. Поэтому мы наберёмся терпения, продолжим использовать и изучать его.

Подробнее..

Формирование диаграммы телефонных звонков в SVG формате при помощи Excel

22.07.2020 10:16:05 | Автор: admin
В данной статье описывается, как с помощью программы Microsoft Excel обрабатывать информацию из детализации телефонных вызовов, получая на выходе векторную диаграмму, которая наглядно отражает данные телефонные вызовы во времени и по дням. Сама по себе данная диаграмма напоминает диаграмму Ганта, которая чаще всего применяется для иллюстрации плана работ по какому-либо проекту.

Диаграмма Ганта представляет собой совокупность горизонтальных полос на плоскости. Горизонтальное направление соответствует величине времени, и данная величина, в общем случае, может быть непрерывной. А в вертикальном направлении данная плоскость разбита на множество горизонтальных зон фиксированной ширины. Для классической диаграммы Ганта, отражающая график работ, каждая такая зона соответствует определённому виду работы (рис. 1). Полосы диаграммы изображаются в пределах данных зон. Полоса, изображённая в конкретной зоне, характеризует соответствующий данной зоне вид работы, а левая и правая границы полосы характеризуют соответственно время начала и конца данной работы. Следовательно, длина полосы характеризует продолжительность данной работы.


Рис. 1. Диаграмма Ганта для иллюстрации графика работ.

В случае с диаграммой телефонных звонков, описываемой в данной статье, зоны в вертикальном направлении будут характеризовать дни (сутки). При этом по горизонтали шкала времени диаграммы соответствует интервалу от 0 до 24 часов, протяжённостью в одни сутки. Каждая полоса на такой диаграмме будет соответствовать одному телефонному звонку. Левая и правая границы полосы время начала и конца вызова, а номер зоны (по вертикали) день, когда был произведён вызов. Диаграмма подобной конфигурации позволяет наглядно проиллюстрировать и оценить, насколько часто совершаются звонки, оценить их среднюю продолжительность, распределение по времени суток и т.д. Более того, к данной диаграмме можно добавить ещё одно свойство: цвет полосы. Раскрашивать полосы можно по разным признакам. Во-первых по типу вызова (входящий или исходящий). Во-вторых по телефонному номеру вызова. В первом случае достаточно двух цветов. Во втором гораздо больше, но, как правило, достаточно не более десятка цветов на самые популярные телефонные номера, фигурирующие в вызовах чаще всего. В данной статье описывается формирование диаграммы за период в пять календарных месяцев и с учётом наличия двух мобильных операторов (двухсимочного телефона). Цвета полос на диаграмме будут выбираться по признаку SIM1/SIM2 входящий/исходящий, то есть, потребуется четыре различных цвета.

Формирование диаграммы, в отличие от построения, предусматривает именно генерацию выходного файла с данной диаграммой. А что касается построения, то, как правило, построение диаграммы в Excel подразумевало бы соответствующую операцию именно в Excel одним из стандартных средств. Даже если такая операция и возможна (диаграмма Ганта), то она вряд ли будет удобной в отображении и масштабировании на больших объёмах входных данных. В случае с формированием файла векторного формата SVG с подобной диаграммой программа Excel применяется в качестве программного инструмента, где удобно работать с табличными данными. Вместо Excel можно было написать стороннюю отдельную программу и формировать SVG файл с помощью неё. Но Excel в данном случае я выбрал не случайно. Во-первых, в своём роде, имеется некая наглядность обработки информации, а во-вторых специфичность выходного формата SVG. Данный формат является форматом масштабируемой векторной графики и содержит внутри текстовые данные, форматированные по принципу XML. Это своеобразный язык разметки, содержащий определённый набор команд и параметров, характерные для рисования того или иного графического элемента. Команды, к примеру, могут быть такие: нарисовать линию, многоугольник, окружность, написать текст. А параметры координаты углов многоугольника, цвет заливки, размер и шрифт текста и т.д. По сути, зная язык разметки SVG, можно с помощью обычного текстового редактора (Блокнот) вручную создать ту или иную картинку из разряда простейших. SVG файлы для просмотра можно открыть любым распространённым Интернет браузером.

Прежде чем приступить к формированию SVG диаграммы, необходимо не только скачать детализации вызовов с сайтов мобильных операторов, но и предварительно их обработать. Как я уже отмечал, будут рассмотрены два мобильных оператора. Один из них Теле2, другой Мегафон. Детализация звонков Теле2, которую можно скачать в личном кабинете на соответствующем сайте, представляет собой PDF документ с большой таблицей, которая поделена на страницы (рис. 2).


Рис. 2. Вид детализации вызовов Теле2.

В случае с Мегафоном всё практически аналогично, за исключением, что детализация представлена в XLS (Excel) файле (рис. 3).


Рис. 3. Вид детализации вызовов Мегафон.

И ту, и другую детализацию необходимо по-разному обработать, отсеить лишнее и привести в порядок. Данный текст имеет некую регулярность, поэтому легко подвергается автоматической обработке. Её я производил в отдельном документе с помощью функций (формул) Excel. Думаю, что не стоит останавливаться подробно на данном вопросе. В результате такой обработки получилась аккуратная большая таблица с минимально необходимыми полями: дата, время, длительность, тип звонка, номер телефона, симкарта (рис. 4). Всего получилось 2102 записи телефонных вызовов. Кстати, на рисунке 3, где изображён лист Excel с исходным текстом детализации, можно видеть наличие других листов. Данные листы я добавил как раз для реализации промежуточных этапов обработки, как продолжение исходного документа.


Рис. 4. Смешанная детализация, приведённая в порядок.

Получившуюся таблицу я скопировал в новый документ на лист A, тут же дополнив её дополнительными полями: адрес цвета полосы, левая граница полосы (a) (в секундах от начала суток), правая граница полосы (b) (рис. 5).


Рис. 5. Дополнительные параметры на первом листе.

Данные поля легко рассчитываются при помощи формул Excel. Адрес цвета указывает на один из четырёх адресов ячеек конфигурационного листа C, в которых он прописан в HEX-RGB формате. На данном листе указаны не только цвета, но и все дополнительные параметры SVG документа: координаты, сдвиги, масштаб и т.д. (рис. 6).


Рис. 6. Лист с параметрами.

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

Забегая вперёд, диаграмма получилась размером 4420 на 1800 пикселей. На самом деле сложно говорить о пикселях в векторной графике, но в описании SVG формата присутствует дискретная система координат, отсчёты которой я называю пикселями. А вообще, даже исходя из аббревиатуры, данная графика масштабируемая. Как я уже писал, диаграмма будет отражать звонки за 5 месяцев, а именно с мая по сентябрь включительно. Если посчитать, это соответствует 153 суткам. Ровно столько должно быть зон для полос на диаграмме. Заранее я определился с масштабами. По вертикали я решил отвести 10 пикселей на одну зону. При этом ширина полосы в зоне будет 8 пикселей, (с зазором в один пиксель сверху и снизу). Величиной зазора (отступа) в ячейке B8 листа C можно регулировать ширину полос в зоне. Масштаб по горизонтали можно выбрать, в принципе, любой, однако имеет место быть практическая наглядность диаграммы, приемлемое соотношение её сторон и вместимость. В итоге я решил взять 3 пикселя за протяжённость одной минуты, или иными словами, 20 секунд на пиксель. Итого, активная область диаграммы имеет следующие размеры. По горизонтали: 24*60*3=4320; по вертикали: 153*10=1530. Слева на диаграмме напротив каждой зоны должно быть написано её название. Названия зон полностью соответствуют датам. Для этой цели я решил отвести место шириной 100 пикселей. Вверху над диаграммой желательно (для удобства) написать метки времени, хотя бы часы. А внизу под диаграммой будет располагаться гистограмма, про которую я писал выше, а также дополнительная информация. Для этих целей я отвёл 270 пикселей, округлив высоту всей диаграммы до 1800. Кроме всего сказанного, на диаграмме я решил отразить светлые горизонтальные линии между зонами (днями), чуть более тёмными между неделями, а чёрными между месяцами. Кроме горизонтальных линий присутствуют также и вертикальные, расставленные через каждый час для границ часов. И ещё одна немаловажная деталь. На левой границе каждой изображаемой цветной полосы будет изображаться чёрная метка её начала в виде квадратной открывающейся скобки. Это нужно для того, чтобы предотвратить слияние двух полос, которые могут соответствовать подряд идущим телефонным вызовам.

Основная обработка информации происходит на листе B (рис. 7). Там можно видеть кучу лишних промежуточных столбов, значения ячеек которых можно было рассчитать в уме или сразу же учесть в окончательной формуле. Это касается координат углов каждой полосы. Однако мне это всё показалось очень громоздким, что на первых порах можно было запутаться.


Рис. 7. Лист с основными расчётами.

В колонке A извлекается номер дня (зоны) из даты вызова. В колонке B время вызова в секундах от начала суток. Это такое же значение, что и в колонке I листа C. В колонке C округлённая в большую сторону продолжительность вызова в минутах. Здесь стоит сделать оговорку, для чего введена такая неточность. Казалось бы, нужно брать продолжительность вызова с точностью до 20 сек, то есть до одного пикселя диаграммы (исходя из принятого масштаба). Однако очевидно, что очень короткие полосы шириной в 1-2 пикселя будут плохо отображаться на диаграмме. Поэтому минимальная длина полосы будет соответствовать хотя бы как минимум трём пикселям. И вообще, длина любой полосы будет кратна трём. За счёт округления длительности вызова в большую сторону (с точностью до минуты) диаграмма будет слегка переполненной, по сравнению с реальной ситуацией, однако это переполнение весьма незначительное. В колонке D с помощью формулы ДВССЛ извлекается значение цвета из параметров (Лист C) по рассчитанному адресу на листе A. Далее рассчитываются координаты углов полосы. Как я уже писал, там много лишних промежуточных расчётов, но я не стал переделывать. В колонке U вычисляется наличие выделения и цвет окантовки полосы, если телефонный номер текущего вызова совпадает с одним из четырёх приведённых телефонных номеров для выделения (на листе C). Забыл выше написать о том, что в выделяемом вызове на соответствующую полосу накладывается не только метка, но и серый цвет окантовки полосы (этот цвет также можно поменять на листе с параметрами). В обычном случае полоса не имеет окантовки. Наконец, в следующих трёх колонках происходит окончательное формирование текста на языке разметки SVG графики. В данной статье я не буду рассматривать описание и синтаксис данного языка. На самом деле в этом нет ничего сложного, я разобрался за несколько минут. В столбце V формируется код, рисующий полосу с окантовкой.
Пример:
<path fill="#FF5050" stroke="#808080" d="M1598,51L1598,59L1601,59L1601,51L1598,51" style="stroke-width: 1px;" stroke-width="1" stroke-dasharray="0"></path>.
В столбце W код для левого края полосы.
Пример:
<path fill="none" stroke="black" d="M1599,52L1598,52L1598,58L1599,58" style="stroke-width: 1px;" stroke-width="1" stroke-dasharray="0"></path>.
В столбце X код для отображения текста метки (цифра 1, 2, 3 или 4) только для тех вызовов, где она нужна. Данная избирательность выполняется с помощью формулы ЕСЛИ(U2<>none;;).
Пример текста 3:
<text x="1601" y="58" style="text-anchor: middle; font-family: times; font-weight: bolder; font-size: 8px;" stroke="none" fill="black"><tspan>3</tspan></text>.
На рисунке 8 представлен скриншот этих трёх столбцов в очень маленьком масштабе, так как иначе продемонстрировать практически невозможно из-за громоздкости текста. Также видно, насколько громоздкой получается запись формулы СЦЕПИТЬ со всеми её аргументами.


Рис. 8. Столбцы с результатами основных расчётов.

На листе Надписи происходит формирование надписей над диаграммой (метки часов) и слева от диаграммы (даты) (рис. 9). В формулах прописаны параметры шрифтов: размер, стиль, цвет шрифта и окантовки. Основной акцент расчёта автозаполнение ячеек по датам и часам, расчёт координат положения текста через равномерный шаг.


Рис. 9. Лист, формирующий надписи.

На листе Границы формируются все вспомогательные линии диаграммы, служащие границами зон (дат) и часов. На рисунке 10 показан скриншот, где видно формирование горизонтальных линий по зонам. В первых двух столбцах номер зоны (начиная с нуля) и её относительная вертикальная координата. В третьем столбце формируется код для SVG, рисующий линии. Здесь в формировании кода применяется не только привычная формула СЦЕПИТЬ, но и две формулы ЕСЛИ, вложенные одна в другую. Это необходимо для реализации рисования линии трёх различных цветов, в зависимости от ситуации. Как писалось выше, чёрные линии отделяет месяцы, серые недели, а светло-серые дни. Последние два цвета задаются на листе C в ячейках B17 и C17. В аргументах формулы ЕСЛИ присутствуют формулы ДЕНЬ и ОСТАТ. Первая формула распознаёт число из даты, заданной в виде целого числа, которое получено путём смещения значений номера зоны (из первого столбца) на заранее подобранную константу 42491. В частности, производится проверка на равенство числа из даты с единицей, распознавая тем самым начало нового месяца. Формула ОСТАТ применяется для распознавания начала новой недели (классический алгоритм). Во втором аргументе данной формулы стоит число 7, так как в неделе 7 дней. В частности, производится сравнение остатка от деления со значением 1. Этим значением (от 0 до 6) можно регулировать смещение дней недели на диаграмме, и оно подобрано таким образом, чтобы было соответствие реальному календарю. После формирования горизонтальных линий формируются 25 вертикальных линий уже более простым способом (23 линии на каждый час и ещё две граничные).


Рис. 10. Лист, формирующий границы.

На листе Мелочи (рис. 11) прописаны формирования дополнительной информации о свойствах диаграммы. В столбцах B и C прописаны координаты смещения для каждого элемента.


Рис. 11. Лист, формирующий дополнительную информацию.

На вкладке Занятость формируется гистограмма распределения плотности звонков во времени (рис. 12). Она представляет собой набор вертикальных линий различных длин, плотно стоящих рядом друг с другом и расположенных прямо под диаграммой. Число таких линий соответствует числу элементов времени (по 20 сек.), а именно 24*60*3=4320.


Рис. 12. Лист, формирующий гистограмму плотности вызовов.

Длина линии (высота столбика гистограммы) в точности соответствует сумме занятых элементов времени по всем 153 дням. То есть, если на текущий элемент времени в текущем дне приходится телефонный вызов, то он учитывается в гистограмме. Такой числовой массив я рассчитывал с помощью отдельной простейшей программы на Си. С помощью ячеек Excel такой расчёт сделать невозможно из-за многомерности операций. Можно было воспользоваться VBA, поместив туда соответствующий программный код, но на тот момент времени я этим средством вообще не владел. Код программы для расчёта массива значений гистограммы приведён ниже.
#include <stdio.h>#include <windows.h>int main(){int a,b,n,c,k;int q[4320];for(n=0;n<4320;n++){q[n]=0;}FILE *f,*f1;f=fopen("ab.txt","r");f1=fopen("Out.txt","w");for(c=0;c<2102;c++){fscanf(f,"%i\t%i\n",&a,&b);for(k=a;k<b;k++){q[k/20]+=1;}}for(n=0;n<4320;n++){fprintf(f1,"%i\n",q[n]);}fclose(f);fclose(f1);system("PAUSE");return 0;}

Входными данными программы служит текстовый файл ab.txt. В данный файл скопированы два столбца из листа A значений секунд начала и конца каждого вызова (об этом я уже писал выше, см. рис. 5). Рассчитанные значения массива выводятся в выходной файл Out.txt. Алгоритм расчёта простой, поэтому описывать его не стоит. Данные из выходного файла копируются в столбец D на листе Занятость. Первые три столбца условные обозначения элементов интервалов времени и их номер. Столбец E те же значение гистограммы, но масштабированные в 5 раз с округлением до целой части. Это сделано для удобного размещения гистограммы, наглядности и исключения громоздкости. Кроме того, каждое значение смещено на единицу. Это нужно для псевдо прорисовки горизонтальной оси. Даже если значение гистограммы нулевое (что характерно для ночного времени суток), один пиксель гистограммы всё равно будет изображён. Таким образом, будет прорисована ось абсцисс.

Наконец, на листе Результат объединены все сформированные коды SVG по каждому листу документа в определённом порядке (надписи и границы в первую очередь). Данное объединение я произвёл при помощи обычного копирования столбцов вручную (рис. 13). При необходимости можно написать в VBA функцию автоматического экспорта SVG файла, пробегаясь по результирующим столбцам всех листов. В самой первой строке сформирован заголовок файла. В нём указаны, прежде всего, ширина и высота картинки. Самая последняя строка, дописанная вручную, закрывает документ, точнее главный блок svg. Всего получилось около 6800 строк.


Рис. 13. Лист с объединением результатов.

Затем нужно скопировать всё содержимое этого листа в текстовый редактор (я пользовался программой AkelPad) и сохранить документ в файл с расширением svg в кодировке UTF-8. После этого, при отсутствии ошибок, файл открывается в Интернет браузере для просмотра. На рисунках ниже приведены виды различных участков получившейся картинки с различными масштабами.


Рис. 14. Общий вид получившейся диаграммы в Chrome.


Рис. 15. Левый верхний угол диаграммы (виды различных границ и названия зон).


Рис. 16. Полосы диаграммы с метками.


Рис. 17. Полосы диаграммы и гистограмма под ними.


Рис. 18. Дополнительная информация на диаграмме.


Рис. 19. Полосы диаграммы и метки часов над ними.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru