Объединяем React, D3 и их экосистему
Опубликовано: 2022-03-10С момента своего создания в 2011 году D3.js стал стандартом де-факто для создания сложных визуализаций данных в Интернете. React также быстро развивается как предпочтительная библиотека для создания пользовательских интерфейсов на основе компонентов.
И React, и D3 — два превосходных инструмента, разработанных с целями, которые иногда противоречат друг другу. Оба берут на себя управление элементами пользовательского интерфейса, и делают это по-разному. Как мы можем заставить их работать вместе, оптимизируя их явные преимущества в соответствии с вашим текущим проектом?
В этом посте мы увидим, как мы можем подойти к созданию проектов React, которые нуждаются в мощных графических возможностях D3. Мы узнаем о различных методах и о том, как выбрать лучшую библиотеку для ваших нужд в вашей основной работе и побочных проектах.
D3 и дом
D3 в D3.js означает документы, управляемые данными. D3.js — это низкоуровневая библиотека , предоставляющая строительные блоки, необходимые для создания интерактивных визуализаций. Он использует веб-стандарты, такие как SVG, HTML, холст и CSS, для сборки внешнего набора инструментов с обширным API и почти безграничным контролем над внешним видом и поведением визуализаций. Он также предоставляет несколько математических функций, которые помогают пользователям вычислять сложные пути SVG.
Как это работает?
В двух словах, D3.js загружает данные и прикрепляет их к DOM. Затем он привязывает эти данные к элементам DOM и преобразует эти элементы, при необходимости переходя из одного состояния в другое.
Выборки D3.js похожи на объекты jQuery, потому что они помогают нам справиться со сложностью SVG. То, как это делается, сравнимо с тем, как jQuery работает с элементами HTML DOM. Обе библиотеки также имеют схожий API на основе цепочки и используют модель DOM в качестве хранилища данных.
Объединение данных
Соединения данных, как объясняется в статье Майка Бостокса «Думаем с соединениями», представляют собой процесс, с помощью которого D3 связывает данные с элементами DOM с помощью выборок.
Объединение данных помогает нам сопоставлять данные, которые мы предоставляем, с уже созданными элементами, добавлять отсутствующие элементы и удалять элементы, которые больше не нужны. Они используют выборки D3.js, которые в сочетании с данными разбивают выбранные элементы на три разные группы: элементы, которые необходимо создать (группа ввода), элементы, которые необходимо обновить (группа обновления), и элементы, которые необходимо для удаления (выходная группа).

На практике объект JavaScript с двумя массивами представляет соединение данных. Мы можем инициировать операции с группами входа и выхода, вызывая методы входа и выхода выбора, а мы можем напрямую работать с группой обновления в последней версии D3.js.
Как описывает Босток, с помощью объединения данных «вы можете визуализировать данные в реальном времени, обеспечить интерактивное исследование и плавный переход между наборами данных». По сути, это алгоритм сравнения, аналогичный тому, как React управляет отрисовкой дочерних элементов, как мы увидим в следующих разделах.
Библиотеки D3
Сообщество D3 не нашло стандартного способа создания компонентов из кода D3, что является частой необходимостью, поскольку D3.js чрезвычайно низкоуровневый. Можно сказать, что шаблонов инкапсуляции почти столько же, сколько библиотек на основе D3, хотя я собираюсь классифицировать их — через их API — на четыре группы: объектно-ориентированные, декларативные, функциональные и связанные (или подобные D3).
Я провел некоторое исследование экосистемы D3.js и выбрал небольшое, высококачественное подмножество. Это современные библиотеки с D3.js версии 4 и с хорошим тестовым покрытием. Они отличаются типом API и степенью детализации своих абстракций.
График
Plottable — популярная объектно-ориентированная библиотека построения диаграмм с низкой степенью детализации; поэтому нам нужно настроить оси, масштабы и графики вручную, чтобы составить диаграммы. Вы можете увидеть пример здесь.

Рекламный щит
Billboard — это форк знаменитой библиотеки C3.js, обновленный для обеспечения совместимости с D3.js версии 4 и призванный обеспечить преемственность этой классической библиотеки. Он написан с использованием ECMAScript 6 и новых современных инструментов, таких как Webpack. Его API основан на объектах конфигурации, передаваемых на диаграммы, поэтому можно сказать, что это декларативный API.

Вега
Vega идет дальше декларативного пути, превращая конфигурации из объектов JavaScript в чистые файлы JSON. Он направлен на реализацию грамматики визуализации, вдохновленной The Grammar of Graphics , книгой Леланда Уилкинсона, которая формализует строительные блоки визуализации данных и также послужила источником вдохновения для D3.js. Вы можете поиграть с его редактором, выбрав один из примеров в качестве отправной точки.

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

Бритечарты
Britecharts — библиотека, созданная Eventbrite, основным участником которой я являюсь, — использует Reusable Chart API, шаблон инкапсуляции, популяризированный Майком Бостоком в его статье «Towards Reusable Charts» и используемый в других библиотеках, таких как NVD3. Britecharts создает высокоуровневую абстракцию, упрощая создание диаграмм, сохраняя при этом низкую внутреннюю сложность, позволяя разработчикам D3 настраивать Britecharts для своего использования. Мы потратили много времени на создание отточенного пользовательского интерфейса и множество доступных демонстраций.

Суммируя библиотеки по их API, мы могли бы представить их так:

Реакция и DOM
React — это библиотека JavaScript, которая помогает нам создавать пользовательские интерфейсы путем составления компонентов . Эти компоненты отслеживают свое состояние и передают свойства для эффективного повторного рендеринга, оптимизируя производительность приложения.
Как это работает?
Виртуальный DOM, представляющий текущее состояние DOM, — это технология, позволяющая оптимизировать повторный рендеринг React. Библиотека использует сложный алгоритм сравнения, чтобы понять, какие части приложения необходимо повторно отображать при изменении условий. Этот алгоритм сравнения называется «алгоритм согласования».
Динамические дочерние компоненты
При рендеринге компонентов, содержащих список элементов, разработчикам необходимо использовать уникальное «ключевое» свойство, прикрепленное к дочерним компонентам. Это значение помогает алгоритму сравнения определить, нужно ли повторно отображать элемент, когда новые данные — или состояние, как это называется в мире React — передаются компоненту. Алгоритм согласования проверяет значения ключей, чтобы определить, нужно ли добавить элемент или удалить его. Вам это кажется знакомым после того, как вы узнали о соединениях данных D3.js?
Начиная с версии 0.14 React также хранит средство визуализации в отдельном модуле. Таким образом, мы можем использовать одни и те же компоненты для рендеринга в разных средах, таких как нативные приложения (React Native), виртуальная реальность (React VR) и DOM (react-dom). Эта гибкость аналогична тому, как код D3.js может отображаться в различных контекстах, таких как SVG и холст.
Реагировать и D3.js
И React, и D3 разделяют цель — помочь нам справиться с DOM и его сложностью высокооптимизированным способом. Они также отдают предпочтение чистым функциям — коду, который для заданного ввода всегда возвращает один и тот же вывод без побочных эффектов — и компонентам без сохранения состояния.
Однако общая озабоченность по поводу DOM приводит к конфликту этих двух самоуверенных библиотек при определении того, какая из них должна отображать и анимировать элементы пользовательского интерфейса. Мы собираемся увидеть различные способы решения этого спора, и что нет простого ответа. Однако мы могли бы установить жесткое правило: они никогда не должны совместно использовать управление DOM . Это был бы рецепт катастрофы.
подходы
При интеграции React и D3.js мы можем делать это на разных уровнях, больше опираясь на сторону D3.js или сторону React. Давайте посмотрим на наши четыре основных варианта.
D3.js в React
Первый подход, которому мы можем следовать, — дать нашему коду D3 как можно больше контроля над DOM. Он использует компонент React для рендеринга пустого элемента SVG, который работает как корневой элемент нашей визуализации данных. Затем он использует метод жизненного цикла componentDidUpdate
, чтобы с помощью этого корневого элемента создать диаграмму с использованием кода D3.js, который мы использовали бы в сценарии ванильного JavaScript. Мы также можем заблокировать любые дальнейшие обновления компонентов, заставив метод shouldComponentUpdate
всегда возвращать false
.
class Line extends React.Component { static propTypes = {...} componentDidMount() { // D3 Code to create the chart // using this._rootNode as container } shouldComponentUpdate() { // Prevents component re-rendering return false; } _setRef(componentNode) { this._rootNode = componentNode; } render() { <div className="line-container" ref={this._setRef.bind(this)} /> } }
Оценивая этот подход, мы признаем, что он предлагает некоторые преимущества и недостатки. Среди преимуществ это простое решение, которое отлично работает в большинстве случаев. Это также наиболее естественное решение, когда вы портируете существующий код в React или используете диаграммы D3.js, которые уже где-то работали.
С другой стороны, смешивание кода React и кода D3.js в компоненте React может показаться немного грубым, включающим слишком много зависимостей и делающим этот файл слишком длинным, чтобы считать его качественным кодом. Кроме того, эта реализация совсем не похожа на React-идиоматику. Наконец, поскольку сервер рендеринга React не вызывает метод componentDidUpdate
, мы не можем отправить отрендеренную версию диаграммы в исходном HTML.
Реагировать на искусственный DOM
Реализованный Оливером Колдуэллом, React Faux DOM «представляет собой способ использовать существующие инструменты D3, но визуализировать их с помощью React в духе React». Он использует фальшивую реализацию DOM, чтобы заставить D3.js думать, что он имеет дело с настоящим DOM. Таким образом, мы сохраняем дерево React DOM, используя при этом D3.js почти весь его потенциал.
import {withFauxDOM} from 'react-faux-dom' class Line extends React.Component { static propTypes = {...} componentDidMount() { const faux = this.props.connectFauxDOM('div', 'chart'); // D3 Code to create the chart // using faux as container d3.select(faux) .append('svg') {...} } render() { <div className="line-container"> {this.props.chart} </div> } } export default withFauxDOM(Line);
Преимущество этого подхода заключается в том, что он позволяет использовать большинство API-интерфейсов D3.js, упрощая интеграцию с уже созданным кодом D3.js. Он также позволяет выполнять рендеринг на стороне сервера. Недостатком этой стратегии является то, что она менее эффективна, потому что мы размещаем еще одну поддельную реализацию DOM перед виртуальной DOM React, дважды виртуализируя DOM. Эта проблема ограничивает его использование для визуализации данных малого и среднего размера.
Обертывание методов жизненного цикла
Этот подход, впервые сформулированный Николасом Хери, использует методы жизненного цикла, присутствующие в компонентах React на основе классов. Он элегантно объединяет создание, обновление и удаление диаграмм D3.js, устанавливая четкую границу между кодом React и D3.js.
import D3Line from './D3Line' class Line extends React.Component { static propTypes = {...} componentDidMount() { // D3 Code to create the chart this._chart = D3Line.create( this._rootNode, this.props.data, this.props.config ); } componentDidUpdate() { // D3 Code to update the chart D3Line.update( this._rootNode, this.props.data, this.props.config, this._chart ); } componentWillUnmount() { D3Line.destroy(this._rootNode); } _setRef(componentNode) { this._rootNode = componentNode; } render() { <div className="line-container" ref={this._setRef.bind(this)} /> } }
D3Line выглядит примерно так:
const D3Line = {}; D3Line.create = (el, data, configuration) => { // D3 Code to create the chart }; D3Line.update = (el, data, configuration, chart) => { // D3 Code to update the chart }; D3Line.destroy = () => { // Cleaning code here }; export default D3Line;
Кодирование таким образом создает легкий компонент React, который взаимодействует с экземпляром диаграммы на основе D3.js через простой API (создание, обновление и удаление), отбрасывая вниз любые методы обратного вызова, которые мы хотим прослушивать.

Эта стратегия способствует четкому разделению задач, используя фасад, чтобы скрыть детали реализации диаграммы. Он может инкапсулировать любой график, а сгенерированный интерфейс прост. Еще одним преимуществом является то, что его легко интегрировать с любым уже написанным кодом D3.js, и это позволяет нам использовать отличные переходы D3.js. Основным недостатком этого метода является то, что рендеринг на стороне сервера невозможен.
React для DOM, D3 для математики
В этой стратегии мы ограничиваем использование D3.js до минимума. Это означает выполнение вычислений для путей SVG, масштабов, макетов и любых преобразований, которые берут пользовательские данные и преобразуют их во что-то, что мы можем нарисовать с помощью React.
Использование D3.js только для математики возможно благодаря большому количеству подмодулей D3.js, которые не относятся к DOM. Этот путь наиболее удобен для React, предоставляя библиотеке Facebook полное управление DOM, что она делает замечательно хорошо.
Давайте посмотрим на упрощенный пример:
class Line extends React.Component { static propTypes = {...} drawLine() { let xScale = d3.scaleTime() .domain(d3.extent(this.props.data, ({date}) => date)); .rangeRound([0, this.props.width]); let yScale = d3.scaleLinear() .domain(d3.extent(this.props.data, ({value}) => value)) .rangeRound([this.props.height, 0]); let line = d3.line() .x((d) => xScale(d.date)) .y((d) => yScale(d.value)); return ( <path className="line" d={line(this.props.data)} /> ); } render() { <svg className="line-container" width={this.props.width} height={this.props.height} > {this.drawLine()} </svg> } }
Этот метод является фаворитом опытных разработчиков React, потому что он соответствует способу React. Кроме того, после того, как он будет установлен, построение диаграмм с его кодом будет прекрасным. Еще одним преимуществом будет рендеринг на стороне сервера и, возможно, React Native или React VR.
Как ни парадоксально, это подход, который требует больше знаний о том, как работает D3.js, потому что нам нужно интегрироваться с его подмодулями на низком уровне. Мы должны повторно реализовать некоторые функции D3.js — оси и формы более распространены; кисти, масштабирование и перетаскивание, вероятно, самые сложные — и это подразумевает значительный объем предварительной работы.
Нам также нужно будет реализовать все анимации. У нас есть отличные инструменты в экосистеме React, которые позволяют нам управлять анимацией — см. react-transition-group, react-motion и react-move — хотя ни один из них не позволяет нам создавать сложные интерполяции путей SVG. Одним из нерешенных вопросов будет то, как этот подход можно использовать для отображения диаграмм с использованием элемента холста HTML5.
На следующей диаграмме мы можем увидеть все описанные подходы по уровню интеграции как с React, так и с D3.js:

Библиотеки React-D3.js
Я провел некоторое исследование библиотек D3.js-React, которые, надеюсь, помогут вам, когда вы столкнетесь с решением о выборе библиотеки для работы, участия или форка. Он содержит некоторые субъективные показатели, поэтому, пожалуйста, относитесь к нему с долей скептицизма.
Это исследование показало, что, хотя библиотек много, не многие из них поддерживаются. Как специалист по сопровождению, я понимаю, насколько сложно следить за изменениями в одной крупной библиотеке и насколько сложной задачей будет следить за двумя.
Кроме того, количество готовых к работе библиотек (начиная с версии 1.0.0 и выше) все еще довольно мало. Вероятно, это связано с объемом работы, необходимой для выпуска библиотеки такого типа.
Давайте посмотрим на некоторые из моих любимых.
Победа
Victory, проект консалтинговой компании Formidable Labs, представляет собой низкоуровневую библиотеку элементов диаграмм. Благодаря этой низкоуровневой характеристике компоненты Victory можно комбинировать с различными конфигурациями для создания сложных визуализаций данных. Под капотом он повторно реализует функции D3.js, такие как кисть и масштабирование, хотя для анимации используется интерполяция d3.

Использование его для линейного графика будет выглядеть так:
class LineChart extends React.Component { render() { return ( <VictoryChart height={400} width={400} containerComponent={<VictoryVoronoiContainer/>} > <VictoryGroup labels={(d) => `y: ${dy}`} labelComponent={ <VictoryTooltip style={{ fontSize: 10 }} /> } data={data} > <VictoryLine/> <VictoryScatter size={(d, a) => {return a ? 8 : 3;}} /> </VictoryGroup> </VictoryChart> ); } }
Получается такая линейная диаграмма:

Начать работу с Победой легко, и у нее есть несколько приятных бонусов, таких как масштабирование и контейнер Вороного для всплывающих подсказок. Это модная библиотека, хотя она все еще находится в состоянии предварительного выпуска и имеет большое количество ожидающих ошибок. Victory — единственная на данный момент библиотека, которую можно использовать с React Native.
Речарты
Эстетически отполированный, с приятным пользовательским интерфейсом, плавной анимацией и красивой всплывающей подсказкой, Recharts — одна из моих любимых библиотек React-D3.js. Recharts использует только шкалу d3, интерполяцию d3 и форму d3. Он предлагает более высокий уровень детализации, чем Victory, ограничивая количество визуализаций данных, которые мы можем создать.

Использование Recharts выглядит так:
class LineChart extends React.Component { render () { return ( <LineChart width={600} height={300} data={data} margin={{top: 5, right: 30, left: 20, bottom: 5}} > <XAxis dataKey="name"/> <YAxis/> <CartesianGrid strokeDasharray="3 3"/> <Tooltip/> <Legend /> <Line type="monotone" dataKey="pv" stroke="#8884d8" activeDot={{r: 8}}/> <Line type="monotone" dataKey="uv" stroke="#82ca9d" /> </LineChart> ); } }

Эта библиотека также действительно хорошо протестирована, и, хотя она все еще находится в бета-версии, в ней есть некоторые из обычных диаграмм, радарная диаграмма, древовидные карты и даже кисти. Вы можете проверить его примеры, чтобы увидеть больше. Разработчики, участвовавшие в этом проекте, вложили в него серьезную работу, добившись плавной анимации с помощью своего анимационного проекта React-Smooth.
Ниво
Nivo — это высокоуровневая библиотека диаграмм React-D3.js. Он предлагает множество вариантов рендеринга: SVG, холст и даже HTML-версию диаграмм на основе API, которая идеально подходит для рендеринга на стороне сервера. Он использует React Motion для анимации.

Его API немного отличается, потому что он показывает только один настраиваемый компонент для каждой диаграммы. Давайте посмотрим пример:
class LineChart extends React.Component { render () { return ( <ResponsiveLine data={data} margin={{ "top": 50, "right": 110, "bottom": 50, "left": 60 }} minY="auto" stacked={true} axisBottom={{ "orient": "bottom", "tickSize": 5, "tickPadding": 5, "tickRotation": 0, "legend": "country code", "legendOffset": 36, "legendPosition": "center" }} axisLeft={{ "orient": "left", "tickSize": 5, "tickPadding": 5, "tickRotation": 0, "legend": "count", "legendOffset": -40, "legendPosition": "center" }} dotSize={10} dotColor="inherit:darker(0.3)" dotBorderWidth={2} dotBorderColor="#ffffff" enableDotLabel={true} dotLabel="y" dotLabelYOffset={-12} animate={true} motionStiffness={90} motionDamping={15} legends={[ { "anchor": "bottom-right", "direction": "column", "translateX": 100, "itemWidth": 80, "itemHeight": 20, "symbolSize": 12, "symbolShape": "circle" } ]} /> ); } }

Рафаэль Бенитт проделал поразительную работу с Nivo. Документация прекрасна, а ее демонстрации можно настроить. Из-за более высокого уровня абстракции этой библиотеки она очень проста в использовании, и можно сказать, что она предлагает меньше возможностей для создания визуализации. Приятной особенностью Nivo является возможность использовать шаблоны и градиенты SVG для заполнения ваших диаграмм.
VX
VX — это набор низкоуровневых компонентов визуализации для создания визуализаций. Он не имеет мнения и должен использоваться для создания других библиотек диаграмм или как есть.

Давайте посмотрим код:
class VXLineChart extends React.Component { render () { let {width, height, margin} = this.props; // bounds const xMax = width - margin.left - margin.right; const yMax = height - margin.top - margin.bottom; // scales const xScale = scaleTime({ range: [0, xMax], domain: extent(data, x), }); const yScale = scaleLinear({ range: [yMax, 0], domain: [0, max(data, y)], nice: true, }); return ( <svg width={width} height={height} > <rect x={0} y={0} width={width} height={height} fill="white" rx={14} /> <Group top={margin.top}> <LinePath data={data} xScale={xScale} yScale={yScale} x={x} y={y} stroke='#32deaa' strokeWidth={2} /> </Group> </svg> ); } };

Учитывая эту низкую степень детализации, я думаю о VX как о D3.js для мира React. Он не зависит от библиотеки анимации, которую хочет использовать пользователь. Сейчас он все еще находится в ранней бета-версии, хотя Airbnb использует его в производстве. Его недостатками на данный момент является отсутствие поддержки таких взаимодействий, как чистка и масштабирование.
Реакция Бритечартов
Britecharts React все еще находится в стадии бета-тестирования, и это единственная из этих библиотек, которая использует метод переноса методов жизненного цикла. Он направлен на то, чтобы разрешить использование визуализаций Britecharts в React путем создания простой в использовании оболочки кода.

Вот простой код для линейного графика:
class LineChart extends React.Component { render () { const margin = { top: 60, right: 30, bottom: 60, left: 70, }; return ( <TooltipComponent data={lineData.oneSet()} topicLabel="topics" title="Tooltip Title" render={(props) => ( <LineComponent margin={margin} lineCurve="basis" {...props} /> )} /> ); } }

Я не могу быть объективным в этом вопросе. Britecharts React можно использовать в качестве каркаса для отображения ваших диаграмм D3.js в виде компонентов React. Он не поддерживает рендеринг на стороне сервера, хотя мы включили состояния загрузки, чтобы каким-то образом решить эту проблему.
Не стесняйтесь проверить онлайн-демонстрации и поиграть с кодом.
Выбор подхода или библиотеки
Я сгруппировал соображения по созданию приложений с диаграммами в четыре категории: качество, время, объем и стоимость. Их слишком много, поэтому следует упростить.
Допустим, мы исправляем качество . Мы могли бы стремиться к тому, чтобы кодовая база была хорошо протестирована, обновлена до D3.js версии 4 и содержала исчерпывающую документацию.
Если мы подумаем о времени , полезно задать себе вопрос: «Являются ли это долгосрочными инвестициями?» Если ответ «да», то я бы посоветовал вам создать библиотеку на основе D3.js и обернуть ее React с использованием подхода методов жизненного цикла. Этот подход разделяет наш код по технологиям и более устойчив ко времени.
Если, наоборот, у проекта сжатые сроки и команде не нужно его поддерживать долго, я бы посоветовал взять наиболее близкую по спецификациям библиотеку React-D3.js или D3.js, форкнуть ее и использовать , пытаясь внести свой вклад на этом пути.
Когда мы имеем дело с масштабом , мы должны думать о том, что нам нужно: небольшое количество базовых диаграмм, разовая сложная визуализация или несколько специально настроенных графиков. В первом случае я бы снова выбрал наиболее близкую по спецификациям библиотеку и форкнул ее. Для индивидуальных визуализаций данных, которые содержат много анимаций или взаимодействий, лучше всего использовать обычный D3.js. Наконец, если вы планируете использовать разные диаграммы с определенными характеристиками — возможно, при поддержке UX-специалистов и дизайнеров — тогда лучше всего будет создать свою библиотеку D3 с нуля или разветвить и настроить существующую библиотеку.
Наконец, стоимостная сторона решения связана с бюджетом и обучением команды. Какими навыками обладает ваша команда? Если у вас есть разработчики D3.js, они предпочли бы четкое разделение между D3.js и React, поэтому, вероятно, подход, использующий обертку метода жизненного цикла, отлично сработает. Однако, если ваша команда в основном состоит из разработчиков React, им понравится расширять любую из текущих библиотек React-D3.js. Кроме того, может сработать использование методов жизненного цикла вместе с примерами D3.js. Что я редко рекомендую, так это развертывание собственной библиотеки React-D3.js. Объем работы, необходимой заранее, пугает, а темпы обновления обеих библиотек делают затраты на обслуживание нетривиальными.
Резюме
React и D3.js — отличные инструменты, помогающие нам справиться с DOM и его проблемами. Они, безусловно, могут работать вместе, и мы вправе выбирать, где провести границу между ними. Существует здоровая экосистема библиотек, помогающих нам использовать D3.js. Для React-D3.js также существует множество интересных опций, и обе библиотеки находятся в постоянном развитии, поэтому проектам, объединяющим обе библиотеки, будет трудно угнаться за ними.
Выбор будет зависеть от очень многих переменных, которые не могут быть учтены в одной статье. Тем не менее, мы рассмотрели большинство основных соображений, которые, надеюсь, дадут вам возможность принять собственное обоснованное решение.
С этой основой я призываю вас проявить любопытство, проверить упомянутые библиотеки и добавить в свои проекты некоторые улучшения построения диаграмм.
Пользовались ли вы какими-либо из этих проектов? Если да, то каков был ваш опыт? Поделитесь с нами несколькими словами в комментариях.