Nivo - это прекрасно написанная библиотека визуализации данных на основе React, написанная Raphaël Benitte, которая предоставляет 14 различных типов компонентов для демонстрации ваших данных. Каждый элемент можно сильно модифицировать в соответствии с вашими потребностями. Тем не менее, всегда есть некоторые функции, которые необходимо удалить из конечного продукта, потому что их сложно реализовать, или они не соответствуют видению проекта.

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

Инструменты

Я хотел создать этот компонент, используя те же инструменты, что и Nivo, чтобы иметь возможность добавить его в библиотеку с помощью запроса на перенос, если автору нравится реализация. Три центральные библиотеки, помимо React, используются для создания Line компонента Nivo.

  1. d3 - «Библиотека JavaScript для управления документами на основе данных».
  2. recompose - «Вспомогательный пояс React для функциональных компонентов и компонентов более высокого порядка».
  3. react-measure - «Вычислить измерения компонентов React».

Первая из них, вероятно, является самой известной библиотекой визуализации данных в экосистеме JavaScript. Разработанный Майком Бостоком, это невероятно мощный инструмент с множеством различных функций для преобразования ваших данных во все, что вы хотите. Чтобы упростить импорт в ваш проект, теперь он объединен в несколько небольших пакетов. Мы будем использовать пакет d3-scale для сборки нашего компонента.

Библиотека recompose предоставляет значительный объем HoC, который можно применить к вашим компонентам, чтобы добавить к ним новые функции. Я никогда раньше с этим не работал, потому что не видел ценности, которую он приносил. Теперь, заставив себя использовать его, я нашел его фантастическим. Он дает много возможностей для создания компонентов React и устраняет некоторые из наиболее распространенных проблем, связанных с ними. Например, устранение ненужных проблем с производительностью рендеринга.

Наконец, у нас есть библиотека компонентов react-measure. Работать с width и height веб-элементами всегда сложно. Однако эта библиотека помогает смягчить эти проблемы. Он предоставляет компонент с именем Measure, который реализует шаблон «Функция как дочерний», в котором вы указываете функцию как дочернюю для компонента, которая вызывается с набором аргументов, связанных с размером и положением родительского компонента.

Вызовы

Прежде чем я начал работать с этими компонентами, я знал, что мне придется решить две основные задачи:

  1. Преобразование координат мыши в координаты SVG.
  2. Масштабирование координат SVG до точек данных.

После небольшого поиска в Google: Я нашел этот ответ о переполнении стека, в котором упоминается способ сделать именно это. Единственная проблема заключается в том, что ему нужна ссылка на компонент SVG, а Nivo по умолчанию не предоставляет способ доступа к нему.

Вторую задачу решить оказалось труднее. Компонент Nivo Line ожидает данные в виде списка точек со значениями x и y плюс некоторые метаданные. На основе этой информации Nivo создает шкалу для преобразования data в координаты SVG. К сожалению, шкалы этого типа d3 не предоставляют invert метода для перехода от координат SVG к data точкам. Майк Босток объясняет почему в этом выпуске GitHub. По сути, для этого типа шкал не требуется, чтобы range значения были уникальными.

Я нашел несколько решений для этой проблемы и в итоге остановился на следующем: использовать шкалу квантования d3. Шкала квантователя похожа на линейную шкалу; только диапазон дискретный, а не непрерывный. Используя width диаграммы в качестве домена, мы можем преобразовать из координат SVG в data точек. Это работает, потому что Nivo требует, чтобы ось x компонента Line содержала только уникальные значения.

Реализация

Компонент кисти

Кисть должна позволять пользователю:

  1. Выберите часть диаграммы для отображения.
  2. Разрешите увеличивать выделение с каждой стороны.
  3. Переместите текущий выбор, не теряя его ширины.
  4. Создайте новое выделение.

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

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

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

LineWithBrush

Line компонент Nivo не принимает child компонентов. Так что у меня не было возможности добавить его поверх диаграммы. Мне пришлось создать новый компонент, включающий компонент Brush внутри, и настроить его с соответствующим props.

<Brush
 margin={margin}
 width={width}
 height={height}
 onBrush={onBrush}
 initialMinEdge={initialMinEdge}
 initialMaxEdge={initialMaxEdge}
/>

Только еще 3 props теперь могут быть настроены на LineWithProps компоненте, потребляемом Brush. Требуется опора onBrush.

SvgWrapper

Как я упоминал ранее, одна из проблем при создании этого компонента заключалась в том, чтобы узнать, как преобразовать координаты мыши в координаты SVG, и единственный способ, которым я узнал, как это сделать, заключался в ссылке на элемент svg. Компонент Line Nivo использует другой компонент для определения элемента svg с именем SvgWrapper. Как и в случае с Line компонентом, я создал свой SvgWrapper компонент на его основе. Затем я изменил новый компонент, чтобы он мог передавать ссылку на элемент svg всем его дочерним элементам с помощью специальной функции-обработчика.

Я не хотел использовать context API React; вместо этого я создал новый обработчик с именем getSvgRef, который возвращает ссылку на элемент svg. Затем, вместо простого рендеринга дочерних компонентов, я использовал функции React.children.map и React.cloneElement для создания клонов каждого дочернего элемента, добавив к нему новый prop, связанный с функцией getSvgRef, вызываемой с тем же именем.

Я не уверен на 100% в этом решении, но оно работает.

LineChart

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

Поскольку BrushChart должен иметь меньше деталей, чем основная диаграмма, я выставляю prop в компоненте LineChart, который принимает те же свойства, что и диаграмма Line. Затем он использует их для переопределения props на основном графике. Таким образом, вам не придется повторять каждый prop фактического графика на BrushChart.

Еще я добавил способ управления tickLabels на оси x. Я не хотел, чтобы эти ярлыки перекрывались, поэтому вместо того, чтобы просто отображать их все, я показываю максимальное количество ярлыков, разрешенное пробелом.

Заключение

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

Https://guzmonne.github.io/nivo-with-brush/

График должен отображать более 9000 пунктов, что представляет собой близкую стоимость фиктивной акции. Используя кисть внизу, мы можем выбрать количество точек для отображения на верхней диаграмме. Даже когда требуется рендерить много точек, производительность рендеринга довольно высока. Следует принять во внимание, что animate prop, вероятно, должен бытьfalse. Когда вы обновляете много точек одновременно, частота кадров падает как скала.

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

Если вы хотите предложить изменения или оставить комментарий, вы можете сделать это в следующем репо:



В качестве альтернативы вы можете оставить комментарий к этой проблеме Nivo:



Как всегда, большое спасибо за то, что прочитали мою статью. Если у вас есть какие-либо предложения или вопросы, не стесняйтесь оставлять комментарии в разделе ниже и давать им несколько «аплодисментов» или «лайков», если вам понравился контент.