Reunindo React, D3 e seu ecossistema
Publicados: 2022-03-10Desde sua criação em 2011, o D3.js se tornou o padrão de fato para a criação de visualizações de dados complexos na web. O React também está amadurecendo rapidamente como a biblioteca de escolha para criar interfaces de usuário baseadas em componentes.
Tanto o React quanto o D3 são duas excelentes ferramentas projetadas com objetivos que às vezes colidem. Ambos assumem o controle dos elementos da interface do usuário e o fazem de maneiras diferentes. Como podemos fazê-los trabalhar juntos otimizando suas vantagens distintas de acordo com seu projeto atual?
Neste post, veremos como podemos abordar a construção de projetos React que precisam da poderosa qualidade de gráficos do D3. Descobriremos diferentes técnicas e como escolher a melhor biblioteca para suas necessidades em seus principais trabalhos e projetos paralelos.
D3 e o DOM
O D3 em D3.js significa documentos orientados a dados. D3.js é uma biblioteca de baixo nível que fornece os blocos de construção necessários para criar visualizações interativas. Ele usa padrões da web como SVG, HTML, canvas e CSS para montar uma caixa de ferramentas de front-end com uma vasta API e controle quase ilimitado sobre a aparência e o comportamento das visualizações. Ele também fornece várias funções matemáticas que ajudam os usuários a calcular caminhos SVG complexos.
Como funciona?
Em poucas palavras, D3.js carrega dados e os anexa ao DOM. Em seguida, ele vincula esses dados aos elementos DOM e transforma esses elementos, fazendo a transição entre os estados, se necessário.
As seleções D3.js são semelhantes aos objetos jQuery, porque nos ajudam a lidar com a complexidade do SVG. A maneira como isso é feito é comparável à maneira como o jQuery lida com elementos HTML DOM. Ambas as bibliotecas também compartilham uma API baseada em cadeia semelhante e o uso do DOM como armazenamento de dados.
Junções de dados
Junções de dados, conforme explicado no artigo de Mike Bostocks “Pensando com junções”, são o processo pelo qual D3 vincula dados a elementos DOM por meio do uso de seleções.
As junções de dados nos ajudam a combinar os dados que fornecemos aos elementos já criados, adicionar itens que estão faltando e remover elementos que não são mais necessários. Eles usam seleções D3.js, que, quando combinadas com dados, dividem os elementos selecionados em três grupos diferentes: elementos que precisam ser criados (o grupo enter), elementos que precisam ser atualizados (o grupo de atualização) e elementos que precisam a ser removido (o grupo de saída).

Na prática, um objeto JavaScript com dois arrays representa uma junção de dados. Podemos acionar operações nos grupos de entrada e saída chamando os métodos de entrada e saída da seleção, enquanto podemos operar diretamente no grupo de atualização na versão mais recente do D3.js.
Conforme descrito por Bostock, com as junções de dados, “você pode visualizar dados em tempo real, permitir a exploração interativa e fazer uma transição suave entre conjuntos de dados”. Eles são efetivamente um algoritmo diff, semelhante ao modo como o React gerencia a renderização de elementos filhos, como veremos nas seções a seguir.
Bibliotecas D3
A comunidade D3 não encontrou uma maneira padrão de criar componentes a partir do código D3, o que é uma necessidade frequente porque o D3.js é notavelmente de baixo nível. Poderíamos dizer que existem quase tantos padrões de encapsulamento quanto bibliotecas baseadas em D3, embora eu vá classificá-los – por meio de sua API – em quatro grupos: orientado a objetos, declarativo, funcional e encadeado (ou D3-like).
Fiz algumas pesquisas sobre o ecossistema D3.js e selecionei um subconjunto pequeno e de alta qualidade. São bibliotecas atualizadas com D3.js versão 4 e com boa cobertura de teste. Eles diferem no tipo de API e granularidade de suas abstrações.
Plotagem
Plottable é uma biblioteca de gráficos orientada a objetos popular que apresenta baixa granularidade; então, precisamos configurar eixos, escalas e plotagens manualmente para compor os gráficos. Você pode ver um exemplo aqui.

Painel publicitário
O Billboard é um fork da famosa biblioteca C3.js, atualizado com compatibilidade com a versão 4 do D3.js e teve como objetivo dar continuidade a esta biblioteca clássica. Ele é escrito usando ECMAScript 6 e novas ferramentas modernas, como Webpack. Sua API é baseada em objetos de configuração passados para gráficos, então podemos dizer que é uma API declarativa.

Vega
Vega leva o caminho declarativo um pouco mais longe, evoluindo as configurações de objetos JavaScript em arquivos JSON puros. Ele visa implementar uma gramática de visualização inspirada em The Grammar of Graphics , um livro de Leland Wilkinson que formaliza os blocos de construção das visualizações de dados e que também serviu de inspiração para o D3.js. Você pode brincar com seu editor, selecionando um dos exemplos como ponto de partida.

D3FC
O D3FC usa D3.js e blocos de construção personalizados para ajudá-lo a criar gráficos interativos poderosos tanto em SVG quanto em tela. Ele apresenta uma interface funcional de baixa granularidade e uma grande quantidade de código D3.js, portanto, embora poderoso, provavelmente requer algum aprendizado. Confira seus exemplos.

Britecharts
A Britecharts — uma biblioteca criada pela Eventbrite, da qual sou um dos principais contribuidores — faz uso da Reusable Chart API, um padrão de encapsulamento popularizado por Mike Bostock em seu post “Towards Reusable Charts” e empregado em outras bibliotecas, como o NVD3. O Britecharts cria uma abstração de alto nível, facilitando a criação de gráficos, mantendo uma baixa complexidade por dentro, permitindo que os desenvolvedores do D3 personalizem o Britecharts para seu uso. Passamos muito tempo construindo uma interface de usuário polida e muitas demos acessíveis.

Resumindo as bibliotecas por suas APIs, poderíamos representá-las assim:

Reagir e o DOM
React é uma biblioteca JavaScript que nos ajuda a construir interfaces de usuário compondo componentes . Esses componentes rastreiam seu estado e passam propriedades para se renderizar de forma eficaz, otimizando o desempenho do aplicativo.
Como funciona?
O DOM virtual, que é uma representação do estado atual do DOM, é a tecnologia que permite as otimizações de rerenderização do React. A biblioteca usa um algoritmo de comparação complexo para entender quais partes do aplicativo precisam ser renderizadas novamente quando as condições mudam. Esse algoritmo diff é chamado de “algoritmo de reconciliação”.
Componentes filho dinâmicos
Ao renderizar componentes que contêm uma lista de itens, os desenvolvedores precisam usar uma propriedade “chave” exclusiva anexada aos componentes filhos. Esse valor ajuda o algoritmo diff a descobrir se o item precisa ser renderizado novamente quando novos dados — ou estado, como é chamado no mundo React — são passados para o componente. O algoritmo de reconciliação verifica os valores das chaves para ver se o item precisa ser adicionado ou removido. Isso parece familiar depois de aprender sobre as junções de dados do D3.js?
Desde a versão 0.14, o React também mantém o renderizador em um módulo separado. Dessa forma, podemos usar os mesmos componentes para renderizar em diferentes mídias, como aplicativos nativos (React Native), realidade virtual (React VR) e o DOM (react-dom). Essa flexibilidade é semelhante à maneira como o código D3.js pode ser renderizado em diferentes contextos, como SVG e canvas.
Reagir e D3.js
Tanto o React quanto o D3 compartilham o objetivo de nos ajudar a lidar com o DOM e sua complexidade de maneira altamente otimizada. Eles também compartilham a preferência por funções puras — código que, para uma determinada entrada, sempre retorna a mesma saída sem incorrer em efeitos colaterais — e componentes sem estado.
No entanto, a preocupação compartilhada sobre o DOM faz com que essas duas bibliotecas opiniáveis entrem em conflito ao determinar qual deve renderizar e animar os elementos da interface do usuário. Vamos ver diferentes maneiras de resolver essa disputa, e que não há uma resposta fácil. No entanto, poderíamos estabelecer uma regra rígida: eles nunca devem compartilhar o controle do DOM . Isso seria uma receita para o desastre.
Abordagens
Ao integrar React e D3.js, podemos fazê-lo em diferentes níveis, inclinando-se mais para o lado do D3.js ou do lado do React. Vamos ver nossas quatro escolhas principais.
D3.js dentro do React
A primeira abordagem que podemos seguir é dar ao nosso código D3 o máximo de controle DOM possível. Ele emprega um componente React para renderizar um elemento SVG vazio que funciona como o elemento raiz de nossa visualização de dados. Em seguida, ele usa o método de ciclo de vida componentDidUpdate
para, usando esse elemento raiz, criar o gráfico usando o código D3.js que empregaríamos em um cenário JavaScript vanilla. Também podemos bloquear quaisquer atualizações de componentes adicionais fazendo com que o método shouldComponentUpdate
sempre retorne 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)} /> } }
Avaliando essa abordagem, reconhecemos que ela oferece algumas vantagens e desvantagens. Entre os benefícios, esta é uma solução simples que funciona bem na maioria das vezes. Também é a solução mais natural quando você está portando código existente para o React, ou quando você está usando gráficos D3.js que já estavam funcionando em outro lugar.
No lado negativo, misturar código React e código D3.js dentro de um componente React pode ser visto como um pouco grosseiro, incorporando muitas dependências e tornando esse arquivo muito longo para ser considerado código de qualidade. Além disso, essa implementação não parece React-idiomática. Por fim, como o servidor de renderização React não chama o método componentDidUpdate
, não podemos enviar uma versão renderizada do gráfico no HTML inicial.
Reagir Faux DOM
Implementado por Oliver Caldwell, o React Faux DOM “é uma maneira de usar as ferramentas D3 existentes, mas renderizá-las através do React no ethos do React”. Ele usa uma implementação falsa do DOM para enganar o D3.js e fazê-lo pensar que está lidando com um DOM real. Dessa forma, mantemos a árvore do React DOM enquanto usamos o D3.js em — quase — todo o seu potencial.
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);
Uma vantagem dessa abordagem é que ela permite que você use a maioria das APIs D3.js, facilitando a integração com o código D3.js já criado. Também permite renderização do lado do servidor. Uma desvantagem dessa estratégia é que ela tem menos desempenho, porque estamos colocando outra implementação de DOM falso antes do DOM virtual do React, virtualizando o DOM duas vezes. Esse problema limita seu uso a visualizações de dados de pequeno e médio porte.
Agrupamento de métodos de ciclo de vida
Essa abordagem, primeiramente declarada por Nicolas Hery, faz uso dos métodos de ciclo de vida presentes em componentes React baseados em classes. Ele envolve elegantemente a criação, atualização e remoção de gráficos D3.js, estabelecendo uma fronteira nítida entre o código React e 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)} /> } }
A D3Line é algo assim:
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;
Codificar dessa maneira produz um componente React leve que se comunica com uma instância de gráfico baseada em D3.js por meio de uma API simples (criar, atualizar e remover), empurrando para baixo quaisquer métodos de retorno de chamada que queremos ouvir.

Essa estratégia promove uma separação clara de interesses, usando uma fachada para ocultar os detalhes de implementação do gráfico. Ele pode encapsular qualquer gráfico, e a interface gerada é simples. Outro benefício é que é fácil de integrar com qualquer código D3.js já escrito e nos permite usar as excelentes transições do D3.js. A principal desvantagem desse método é que a renderização do lado do servidor não é possível.
Reagir para o DOM, D3 para matemática
Nesta estratégia, limitamos o uso de D3.js ao mínimo. Isso significa realizar cálculos para caminhos SVG, escalas, layouts e quaisquer transformações que peguem os dados do usuário e os transformem em algo que podemos desenhar com React.
Usar D3.js apenas para matemática é possível graças a um grande número de submódulos D3.js que não se relacionam com o DOM. Esse caminho é o mais amigável ao React, dando à biblioteca do Facebook total controle sobre o DOM, algo que faz notavelmente bem.
Vejamos um exemplo simplificado:
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> } }
Essa técnica é a favorita dos desenvolvedores experientes do React porque é consistente com o modo React. Além disso, uma vez implementado, criar gráficos com seu código é ótimo. Outro benefício seria a renderização no lado do servidor e possivelmente React Native ou React VR.
Paradoxalmente, essa é a abordagem que requer mais conhecimento sobre como o D3.js funciona, pois precisamos integrar com seus submódulos em um nível baixo. Devemos reimplementar algumas funcionalidades do D3.js — eixos e formas os mais comuns; pincéis, zooms e arrastar provavelmente são os mais difíceis - e isso implica uma quantidade significativa de trabalho inicial.
Também precisaríamos implementar todas as animações. Temos ótimas ferramentas no ecossistema React que nos permitem gerenciar animações - veja react-transition-group, react-motion e react-move - embora nenhuma delas nos permita criar interpolações complexas de caminhos SVG. Uma questão pendente seria como essa abordagem pode ser aproveitada para renderizar gráficos usando o elemento canvas do HTML5.
No diagrama a seguir, podemos ver todas as abordagens descritas de acordo com o nível de integração com React e D3.js:

Bibliotecas React-D3.js
Eu fiz algumas pesquisas sobre as bibliotecas D3.js-React que esperamos ajudá-lo quando você estiver enfrentando a decisão de escolher uma biblioteca para trabalhar, contribuir ou bifurcar. Ele contém algumas métricas subjetivas, portanto, leve-o com cautela.
Esta pesquisa revelou que, embora existam muitas bibliotecas, poucas delas estão sendo mantidas. Como mantenedor, posso entender o quão difícil é acompanhar as mudanças em uma grande biblioteca e como ter que cuidar de duas seria uma tarefa assustadora.
Além disso, o número de bibliotecas prontas para produção (da versão 1.0.0 e superior) ainda é muito baixo. Provavelmente tem a ver com a quantidade de trabalho necessária para enviar uma biblioteca desse tipo.
Vamos ver alguns dos meus favoritos.
Vitória
Um projeto da empresa de consultoria Formidable Labs, Victory é uma biblioteca de componentes de baixo nível de elementos gráficos. Devido a essa característica de baixo nível, os componentes Victory podem ser combinados com diferentes configurações para criar visualizações de dados complexas. Sob o capô, ele reimplementa recursos do D3.js, como pincel e zoom, embora use interpolação d3 para animações.

Usá-lo para um gráfico de linhas ficaria assim:
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> ); } }
Isso produz um gráfico de linhas como este:

Começar com o Victory é fácil e tem alguns bônus interessantes, como o zoom e o contêiner Voronoi para dicas de ferramentas. É uma biblioteca moderna, embora ainda esteja em estado de pré-lançamento e tenha um grande número de bugs pendentes. Victory é a única biblioteca no momento que você pode usar com React Native.
Recharts
Esteticamente polido, com uma experiência de usuário agradável, animações suaves e uma dica de ferramenta de ótima aparência, o Recarts é uma das minhas bibliotecas React-D3.js favoritas. Recharts só faz uso de escala d3, interpolação d3 e forma d3. Ele oferece um nível de granularidade mais alto que o Victory, limitando a quantidade de visualizações de dados que podemos compor.

Usando Recharts fica assim:
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> ); } }

Esta biblioteca também está muito bem testada e, embora ainda em versão beta, apresenta alguns dos gráficos usuais, um gráfico de radar, mapas de árvores e até pincéis. Você pode verificar seus exemplos para ver mais. Os desenvolvedores que contribuíram para este projeto colocaram um trabalho sério nele, conseguindo animações suaves com seu projeto de animação react-smooth.
Nivo
Nivo é uma biblioteca de gráficos React-D3.js de alto nível. Ele oferece muitas opções para renderização: SVG, canvas, até mesmo uma versão HTML baseada em API dos gráficos que é ideal para renderização no lado do servidor. Ele usa React Motion para animações.

Sua API é um pouco diferente, pois revela apenas um componente configurável para cada gráfico. Vejamos um exemplo:
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" } ]} /> ); } }

Raphael Benitte fez um trabalho incrível com o Nivo. A documentação é adorável e suas demos configuráveis. Devido ao maior nível de abstração dessa biblioteca, ela é supersimples de usar, e podemos dizer que oferece menos potencial para criação de visualizações. Um bom recurso do Nivo é a possibilidade de usar padrões e gradientes SVG para preencher seus gráficos.
VX
VX é uma coleção de componentes de visualização de baixo nível para criar visualizações. Não tem opinião e deve ser usado para produzir outras bibliotecas de gráficos ou como está.

Vamos ver algum código:
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> ); } };

Dada essa granularidade de baixo nível, penso no VX como um D3.js para o mundo React. É agnóstico sobre a biblioteca de animação que o usuário deseja usar. No momento, ainda está em beta inicial, embora esteja sendo usado em produção pelo Airbnb. Seus déficits neste momento são a falta de suporte para interações como brushing e zooming.
Britecharts Reagir
O Britecharts React ainda está na versão beta, e é a única dessas bibliotecas que usa a abordagem de encapsulamento do método de ciclo de vida. Ele visa permitir o uso de visualizações do Britecharts no React, criando um wrapper de código fácil de usar.

Aqui está um código simples para um gráfico de linhas:
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} /> )} /> ); } }

Eu não posso ser objetivo sobre este. O Britecharts React pode ser usado como scaffold para renderizar seus gráficos D3.js como componentes do React. Ele não suporta renderização no lado do servidor, embora tenhamos incluído estados de carregamento para superar isso de alguma forma.
Sinta-se à vontade para verificar as demos online e brincar com o código.
Escolhendo uma abordagem ou biblioteca
Agrupei as considerações na construção de aplicativos com gráficos em quatro categorias: qualidade, tempo, escopo e custo. Eles são muitos, então devemos simplificar.
Digamos que consertamos a qualidade . Poderíamos ter como objetivo ter uma base de código bem testada, atualizada com D3.js versão 4 e com documentação abrangente.
Se pensarmos no tempo , uma pergunta útil a se fazer é: “Este é um investimento de longo prazo?” Se a resposta for “sim”, aconselho você a criar uma biblioteca baseada em D3.js e envolvê-la com React usando a abordagem de métodos de ciclo de vida. Essa abordagem separa nosso código por tecnologias e é mais resistente ao tempo.
Se, pelo contrário, o projeto tiver prazos apertados e a equipe não precisar mantê-lo por muito tempo, aconselho pegar a biblioteca React-D3.js ou D3.js mais próxima das especificações, bifurcá-la e usá-la , tentando contribuir ao longo do caminho.
Quando lidamos com escopo , devemos pensar se o que precisamos é um pequeno número de gráficos básicos, uma visualização complexa única ou vários gráficos altamente personalizados. No primeiro caso, eu escolheria novamente a biblioteca mais próxima das especificações e faria um fork. Para visualizações de dados personalizadas que contêm muitas animações ou interações, construir com D3.js regular é a melhor opção. Por fim, se você planeja usar gráficos diferentes com especificações específicas - talvez com o suporte de UX's e designers -, criar sua biblioteca D3 do zero ou bifurcar e personalizar uma biblioteca existente funcionaria melhor.
Por fim, o lado custo da decisão está relacionado ao orçamento e treinamento da equipe. Que tipo de habilidades sua equipe tem? Se você tem desenvolvedores de D3.js, eles preferem uma separação clara entre D3.js e React, então provavelmente uma abordagem usando o método de encapsulamento do ciclo de vida funcionaria muito bem. No entanto, se sua equipe é composta principalmente por desenvolvedores React, eles gostariam de estender qualquer uma das bibliotecas React-D3.js atuais. Além disso, usar os métodos do ciclo de vida junto com os exemplos do D3.js pode funcionar. O que eu raramente recomendo é lançar sua própria biblioteca React-D3.js. A quantidade de trabalho necessário no início é assustadora, e os ritmos de atualização de ambas as bibliotecas tornam os custos de manutenção não triviais.
Resumo
React e D3.js são ótimas ferramentas para nos ajudar a lidar com o DOM e seus desafios. Eles certamente podem trabalhar juntos, e temos o poder de escolher onde traçar a linha entre eles. Existe um ecossistema saudável de bibliotecas para nos ajudar a usar o D3.js. Muitas opções interessantes também existem para o React-D3.js, e ambas as bibliotecas estão em constante evolução, então projetos que combinam ambas terão dificuldade em acompanhar.
A escolha dependerá de tantas variáveis que não podem ser contabilizadas em um único artigo. No entanto, cobrimos a maioria das principais considerações, esperamos capacitá-lo a tomar sua própria decisão informada.
Com essa base, encorajo você a ficar curioso, verificar as bibliotecas mencionadas e adicionar algumas qualidades de gráficos aos seus projetos.
Você já utilizou algum desses projetos? Se sim, qual foi sua experiência? Compartilhe algumas palavras conosco nos comentários.