Como recriar o gráfico de contribuição do GitHub com o Node.js e o Google Sheets

Anne-Laure Le Cunff em Level Up Your Code Seguir 6 de jul · 13 min ler

Este ano, decidi focar no crescimento pessoal , com dois desafios principais: aprender a codificar e entrar em melhor forma. Ambos envolvem a criação de hábitos diários. Desde janeiro, tenho usado uma planilha como rastreador para me manter responsável em ambas as frentes.

Mas eu notei um formato que eu realmente gosto e que vários produtos usam: a grade de quadrados coloridos que o GitHub popularizou com seu gráfico de contribuição.

Você pode vê-lo no Makerlog para acompanhar tarefas diárias, o Puregym para rastrear as sessões de ginástica, e Steph Smith recentemente criou um aplicativo da web usando este gráfico para rastrear as sessões de codificação.

Eu queria me desafiar e ver se poderia criar um gráfico tão dinâmico com base na minha planilha.

Este tutorial usa o Node.js, o Express, a Google Sheets API e o Bootstrap. Vou passar por cada etapa, desde extrair dados da planilha para estilizá-la. Você pode ver uma demonstração aqui e os arquivos finais estão aqui . Sinta-se à vontade para usá-los como quiser.

1) Crie a planilha do Google

Eu já tinha uma planilha, mas criei uma cópia para começar com um arquivo limpo. Se você quiser pular esta seção, você pode fazer uma cópia desta planilha . Estes são dados do meu rastreador real se você está curioso sobre minha jornada de codificação.

Se você quiser acompanhar, vamos passar por cada etapa juntos.

Primeiro, adicione os seguintes cabeçalhos:

  • Encontro
  • Tópico
  • Tempo
  • Nível
  • Notas (opcional)

Não coletaremos dados da coluna "Anotações" no aplicativo da web, é apenas um lugar para você escrever facilmente qualquer coisa que valha a pena lembrar sobre um determinado dia.

Em seguida, congele os cabeçalhos clicando em View > Freeze > 1 Row .

Preencha a primeira coluna com as datas de 1º de janeiro a 31 de dezembro do ano atual.

Adicione alguns dados fictícios na segunda e terceira coluna. Por exemplo, adicione "jQuery" como um tópico que você estudou na coluna "Topic" e "120" como o número de minutos que você estudou na coluna "Time". Faça isso por algumas datas para que possamos ver mais tarde se as coisas estão funcionando corretamente ou não.

Por fim, adicione a seguinte fórmula na coluna “Level”, começando com a célula D2:

 = SE (C2 = 0, "0", SE (C2 <= 60, "1", SE (C2 <= 120, "2", "3"))) 

O que essa fórmula faz é calcular seu nível de atividade com base na entrada da coluna "Tempo".

  • Se time = 0, level = 0
  • Se o tempo for <= 60 minutos, nível = 1
  • Se o tempo for <= 120 minutos, nível = 2
  • Outro nível = 3

Estes serão úteis para nos permitir escolher uma cor diferente para os quadrados com base em quanto tempo dedicamos à atividade escolhida, neste caso, aprender a codificar. Você pode editar a fórmula acima para fazer com que os níveis correspondam a tempos diferentes que façam mais sentido para você e para a atividade escolhida.

É isso aí! Nossa planilha está pronta. E não, não precisamos publicar.

2) Construa sua aplicação

Você pode pular as primeiras etapas desta seção se estiver confortável com o Node.js já. Eu quero escrever essa parte, pois eu sempre luto com tutoriais que pulam em algumas partes que o autor julga óbvias.

Se é a primeira vez que você usa o Node.js, primeiro precisamos instalá-lo. Baixe o instalador e siga as instruções.

Então, abra seu terminal. Eu recomendo instalar o Hyper , mas não é absolutamente necessário para este tutorial. Se você quiser ficar com o Terminal padrão, pressione WinKey + R no Windows, digite cmd e pressione Enter, ou no Mac, basta procurar por “Terminal”.

Primeiro, vamos criar um diretório para nosso aplicativo. Digite mkdir pixel-progress e pressione enter. mkdir significa “make directory” e este comando cria um diretório chamado “pixel-progress”.

Vamos agora entrar nesse diretório. Digite cd pixel-progress ( cd significa “mudar diretório”) e pressione enter, então estamos dentro do nosso diretório.

Nós vamos criar duas pastas dentro deste diretório. Digite mkdir public views , que criará dois diretórios, respectivamente chamados de “public” e “views”. Eu explicarei mais tarde para que servem, mas eles basicamente armazenam toda a parte front-end de nosso aplicativo.

Agora digite touch app.js – enquanto o mkdir é para criar pastas, o touch é para criar arquivos, então este comando criará um arquivo chamado “app.js”, que será a espinha dorsal do nosso aplicativo.

Ok, temos todos os arquivos necessários para começar! Agora, vamos torná-los um pouco mais inteligentes.

O Node.js vem empacotado com algo chamado npm , que é basicamente uma maneira super fácil de reutilizar código de outros desenvolvedores de JavaScript, para que você não precise sempre fazer tudo do zero.

Para este tutorial, vamos usar alguns pacotes npm (ou seja, código escrito por outros desenvolvedores incríveis) que tornarão nossa vida mais fácil.

Primeiro, vamos inicializar o npm em nosso diretório para que possamos usá-lo. Digite npm init no Terminal e pressione enter. Você verá uma série de prompts fazendo perguntas, como a licença do aplicativo, o autor, etc. Não há problema em manter tudo como padrão apenas pressionando continuamente enter até que pare de perguntar qualquer coisa.

Então, vamos instalar esses pacotes que precisaremos em nosso aplicativo. Digite o seguinte no Terminal e pressione enter:

 npm install pedido expresso ejs googleapis @ 39 - save 

Vamos descompactar isso.

Estamos pedindo ao npm para instalar os seguintes pacotes:

  • Express: uma estrutura para o Node.js que nos ajudará a criar páginas dinâmicas
  • Pedido: para que o cliente possa solicitar informações do servidor
  • EJS: uma linguagem de templates que nos permitirá gerar HTML com JavaScript
  • googleapis @ 39: a Google Sheets API

Os três primeiros são pacotes básicos que você geralmente instala sempre que cria um novo aplicativo Node.js. O último que encontrei na documentação da Google Sheets API , que analisaremos com mais detalhes posteriormente.

Ufa Isso foi muito para começar. Agora vamos realmente começar a quebrar.

No Terminal, digite {name of your programming text editor} . incluindo o ponto final. Se você usar o código VS, isso seria code . , para Atom, é o atom . e por sublime, sublime . – o . diz ao terminal para abrir esta pasta.

Digite o seguinte dentro app.js e salve as alterações:

 const express = require ('express'); pedido const = require ('request'); 
app = express ();
app.set ('view engine', 'ejs');
app.use (express.static ('public'));
app.listen (process.env.PORT || 3000, function () { console.log ('Servidor em execução na porta 3000.'); });

As duas primeiras linhas dizem ao nosso aplicativo para exigir os módulos "Express" e "Request" que instalamos anteriormente. Em seguida, criamos uma nova instância do Express, definimos nosso mecanismo de modelagem para EJS e informamos ao Express para fornecer arquivos estáticos a partir do diretório "Público" que criamos anteriormente.

Finalmente, dizemos ao nosso servidor qual porta escutar. Como ainda não implantamos nosso aplicativo, usaremos a porta 3000, mas o bit process.env.PORT permitirá que nosso ambiente (por exemplo, Heroku) defina a variável posteriormente.

Vamos verificar se tudo está funcionando criando nossa primeira rota. Entre as app.use e app.listen , digite o seguinte:

 app.get ("/", function (req, res) { console.log ("Olá!"); } 

Agora abra seu navegador e digite localhost:3000 na barra de URL. Então, abra seu terminal. Se tudo correu bem até agora, você deve ver “Olá!” Impresso no Terminal.

3) Conecte sua planilha do Google e seu aplicativo Node.js

Para esta parte, vamos simplesmente seguir a documentação da Google Sheets API . Você deve se certificar de que está logado com sua conta do Google.

Primeiro, clique em "Ativar a API do Google Sheets". Na janela pop-up exibida, clique em “Download client configuration”. Coloque o arquivo credentials.json que você acabou de baixar em seu diretório de trabalho, o mesmo que o seu arquivo app.js

Como já instalamos a biblioteca, podemos pular para copiar e colar o exemplo de código fornecido na documentação em nosso arquivo app.js

Primeiro, adicione essa parte abaixo dos outros pacotes obrigatórios no topo do seu arquivo:

 const fs = require ('fs'); const readline = require ('readline'); const {google} = require ('googleapis'); 

Em seguida, substitua a linha console.log("Hello!") Que foi testada com esta parte:

 // Carrega segredos do cliente de um arquivo local. fs.readFile ('credentials.json', (err, content) => { if (err) retorna console.log ('Erro ao carregar o arquivo secreto do cliente:', err); // Autoriza um cliente com credenciais, então chama o API do Planilhas Google autorizar (JSON.parse (conteúdo), listMajors); }); / ** * Imprime os nomes e as notas dos alunos em uma planilha de exemplo: * @see https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit * @param {google.auth.OAuth2} auth O Google autenticado Cliente OAuth. * / function listMajors (auth) { const folhas = google.sheets ({versão: 'v4', auth}); sheets.spreadsheets.values.get ({ spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', intervalo: 'Dados de Classe! A2: E', }, (err, res) => { if (err) return console.log ('A API retornou um error: '+ err); const linhas = res.data.values; if (rows.length) { console.log (' Nome, Major: ') // Imprime as colunas A e E, que correspondem aos índices 0 e 4 . rows.map ((linha) => {console.log ( `$ {row [0]}, $ {linha [4]}`);});} else {console.log ( 'Não foram encontrados dados.' ); } }); } 

Finalmente, copie e cole tudo isso antes da parte app.listen :

 // Se estiver modificando esses escopos, exclua token.json. const SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly']; // O arquivo token.json armazena os tokens de acesso e de atualização do usuário e é // criado automaticamente quando o fluxo de autorização é concluído pela primeira // hora. const TOKEN_PATH = 'token.json'; / ** * Crie um cliente OAuth2 com as credenciais fornecidas e, em seguida, execute a função de retorno de chamada fornecida. * @param {Object} credenciais As credenciais do cliente de autorização. * @param {function} callback O callback para chamar com o cliente autorizado. * / function authorize (credenciais, retorno de chamada) { const {client_secret, client_id, redirect_uris} = credentials.installed; const oAuth2Client = novo google.auth.OAuth2 ( client_id, client_secret, redirect_uris [0]); // Verifique se já armazenamos um token anteriormente. fs.readFile (TOKEN_PATH, (err, token) => { if (err) retorna getNewToken (oAuth2Client, callback); oAuth2Client.setCredentials (JSON.parse (token)); retorno de chamada (oAuth2Client); }); } / ** * Obtenha e armazene o novo token após solicitar a autorização do usuário e, em seguida, * execute o retorno de chamada fornecido com o cliente OAuth2 autorizado. * @param {google.auth.OAuth2} oAuth2Client O cliente OAuth2 para obter o token. * @param {getEventsCallback} callback O retorno de chamada para o cliente autorizado. * / function getNewToken (oAuth2Client, retorno de chamada) { const authUrl = oAuth2Client.generateAuthUrl ({ access_type: 'off-line', escopo: SCOPES, }); console.log ('Autorize este aplicativo, visitando este url:', authUrl); const rl = readline.createInterface ({ input: process.stdin, saída: process.stdout, }); rl.question ('Digite o código daquela página aqui:', (código) => { rl.close (); oAuth2Client.getToken (código, (err, token) => { if (err) return console.error ( 'Erro ao tentar recuperar o token de acesso', err); oAuth2Client.setCredentials (token); // Armazena o token em disco para execuções posteriores do programa. Fs.writeFile (TOKEN_PATH, JSON.stringify (token), (err) => { if (err) retorna console.error (err); console.log ('Token armazenado em', TOKEN_PATH); }); retorno de chamada (oAuth2Client); }); }); } 

Execute a amostra digitando o node . dentro do terminal e siga as instruções para se autenticar. Você obterá um código de autorização que precisará colar no Terminal. Essa etapa é importante, pois é isso que permite que seu aplicativo acesse sua planilha, mesmo que você não a tenha tornado pública.

Agora que tudo está funcionando, vamos modificar o código para que, na verdade, usemos nossa própria planilha em vez da amostra.

  • Renomeie a função de listMajors para pullData – tenha cuidado, pois ela aparece duas vezes em app.js , primeiro para autorizar o acesso à nossa planilha e, em seguida, para executar a função.
  • Substitua o ID da planilha pela sua – você encontrará isso no URL da sua planilha.

A função atual extrai dados de apenas um intervalo. Queremos extrair dados de vários intervalos na nossa planilha. Para nossa sorte, existe um método fornecido pela API do Google Sheets, chamado batchGet, que nos permite "retornar um ou mais intervalos de valores de uma planilha".

Primeiro, vamos substituir sheets.spreadsheets.values.get with sheets.spreadsheets.values.batchGet para usar este método.

Em seguida, substitua o range por ranges e insira os intervalos que desejamos acessar em nossa planilha:

 intervalos: ['2019! A2: A366', '2019! B2: B366', '2019! C2: C366', '2019! D2: D366'] 

Nesse caso, “2019” é o nome da planilha específica dentro da planilha, cujo nome você pode ver na guia na parte inferior.

Agora, vamos criar variáveis para estes, para que possamos acessá-los e fazer coisas com eles. Substitua a linha const rows = res.data.values pelo seguinte:

 const date = response.data.valueRanges [0] .values; const topic = response.data.valueRanges [1] .values; const time = response.data.valueRanges [2] .values; const level = response.data.valueRanges [3] .valores; 
dados const = [data, tópico, tempo, nível];

Isso cria quatro variáveis para armazenar nossos dados – data, tópico, hora e nível – e, em seguida, coloca-os em um array de data que os tornará mais fáceis de manipular. Você pode testar se tudo funciona pelo console registrando os dados resultantes da mesma forma mostrada na documentação:

 if (data.length) { console.log ('Data:'); // Imprime as colunas A a C, que correspondem aos índices 0 a 2. data.map ((linha) => { console.log (`$ {row [0]}, $ {row [1]}, $ {row [2]} `); }); } else { console.log ('Nenhum dado encontrado.'); } 

Isso deve imprimir os dados da sua planilha no terminal.

É isso aí! Agora estamos conectados e obtendo dados de nossa planilha. Agora, vamos exibi-lo em nosso aplicativo.

4) Exibir os dados da planilha do Google no aplicativo

Agora é hora de usar o EJS , nosso motor de templates de escolha. A modelagem nos permite usar tags especiais em nossa marcação HTML para inserir variáveis ou executar a lógica de programação.

Substitua o bit de log do console que acabamos de usar para testar se tudo estava funcionando com o seguinte:

 res.render ('home', {data: data}); 

Se dermos um passo para trás e analisarmos nosso código, o que informamos ao nosso aplicativo é: "Se alguém acessar a / route, execute todo o código que acessa minha planilha do Google e renderize a página inicial, incluindo os dados resultantes".

Ok, mas não temos uma home page, então vamos criá-la. Dentro da pasta de views , crie um arquivo home.ejs . Preencha-o com seu clichê normal em HTML.

Dentro de uma div, adicione o seguinte:

 <% = data%> 

Isso informa ao arquivo para exibir os dados que passamos anteriormente. Agora, atualize a página em seu navegador em localhost:3000 para ver se tudo está funcionando. Você deve ver algo realmente feio com basicamente todos os dados de marcadores de posição incluídos em sua planilha no início deste tutorial.

Impressionante! Agora vamos deixar isso bonito.

5) Estilize os dados para que pareçam com o gráfico de contribuições do GitHub

Eu não sou grande em CSS e não queria reinventar a roda aqui, então eu usei este tutorial legal de Ire Aderinokun . Provavelmente existem maneiras melhores de fazer isso, e essa não é a abordagem mais acessível (as tabelas teriam sido melhores), mas funcionou. Por favor, compartilhe se você fizer isso de outra maneira!

Aqui está a caneta de código onde você pode copiar o HTML e CSS.

Primeiro, cole essa parte no seu arquivo home.ejs :

 <div class = "graph"> <ul classe = "meses"> <li> Jan </ li> <li> fev </ li> <li> Mar </ li> <li> Apr </ li> <li > Maio </ li> <li> Jun </ li> <li> Jul </ li> <li> Agosto </ li> <li> Setembro </ li> <li> Outubro </ li> <li> Novembro </ li> <li> Dec </ li> </ ul> <ul class = "days"> <li> Dom </ li> <li> Mon </ li> <li> Ter </ li> <li > Qua </ li> <li> Qui </ li> <li> Sex </ li> <li> Sat </ li> </ ul> <ul classe = "quadrados"> <! -- added via javascript --> </ ul> </ div> 

Em seguida, vá para sua pasta public , crie uma pasta css e, em seguida, dentro dessa pasta, crie um arquivo styles.css para colar o CSS fornecido na Caneta de código acima.

Dentro das tags <head> no seu arquivo home.ejs , adicione o seguinte link para a planilha:

 <link href = "css / styles.css" rel = "folha de estilo"> 

Vamos ignorar o JavaScript no Code Pen, pois ele gera valores aleatórios para preencher a grade e vamos escrever os nossos. Substitua <! -- added via javascript --> com o seguinte:

 <% para (var i = 0; i <364; i ++) {%> <li nível de dados = "<% = data [3] [i]%>"> </ li> <%}%> 

Isso puxa os dados da quarta posição em nossa matriz de data (o const data = [date, topic, time, level] em nosso arquivo app.js ), que corresponde ao nível de atividade que definimos anteriormente. Passamos pelo dia 1 ao dia 365 do ano.

Atualize sua página, que agora deve exibir o gráfico, bem como os quadrados coloridos dos dias que você preencheu com os dados de espaço reservado!

Outro dado que seria interessante exibir é o que você realmente fez naquele dia, como uma aula de ginástica ou um tópico específico que você estudou. Vamos usar o componente Tooltips no Bootstrap.

Primeiro, vamos adicionar isso dentro das tags <head> em nosso arquivo home.ejs :

 <link href = "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel = "folha de estilo"> 

E isso na parte inferior do arquivo, logo antes da tag de fechamento </body> :

 <script src = "https://code.jquery.com/jquery-3.4.1.min.js"> </ script> <script src = "https://cdnjs.cloudflare.com/ajax/libs/popper .js / 1.12.9 / umd / popper.min.js "> </ script> <script src =" https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.bundle.min.js "> </ script> 

Esses scripts são apenas jQuery, bem como o script Bootstrap básico e Popper.js , que a documentação da Tooltips nos pede para incluir a fim de exibir dicas de ferramentas.

A documentação também nos diz para inicializar nossas dicas de ferramentas com o seguinte:

 $ (function () { $ ('[data-toggle = "tooltip"]'). tooltip () }) 

Vamos criar um arquivo tooltips.js dentro de uma pasta public/js contendo o código acima, e incluí-lo assim, logo antes do script Bootstrap em nossa pasta home.ejs:

 <script src = "js / tooltips.js"> </ script> 

Por fim, vamos exibir as dicas de ferramentas em foco com os dados do "Tópico" da nossa planilha.

Substitua <li data-level="<%= data[3][i] %>"></li> pelo seguinte:

 <li data-toggle = " tooltip " data-placement = " inferior " data-animation = " false " atraso = " 0 " title = " <% = data [1] [i]%> " nível de dados = " < % = data [3] [i]%> " > </ li> 

Isso informa nosso aplicativo para:

  • Alternar as dicas de ferramentas ao passar o mouse
  • Exibe as dicas de ferramentas na parte inferior dos quadrados
  • Não animá-los quando eles aparecem
  • Faça-os aparecer com um atraso de 0

O conteúdo da dica é extraído por <%= data[1][i] %> , que é a segunda coluna da nossa planilha.

Atualize sua página e passe o mouse sobre alguns dos quadrados coloridos para ver se está funcionando. Você pode brincar com o CSS para estilizar as dicas de ferramentas ou alterar as cores dos quadrados.

Este bit em particular permite que você altere as cores dos quadrados:

 .squares li { background-color: # D7DDF2; } .squares li [data-level = "1"] { cor de fundo: # 577AF9; } .squares li [data-level = "2"] { cor de fundo: # 3960EF; } .squares li [data-level = "3"] { cor de fundo: # 1B3699; } 

Por exemplo, decidi usar variações de azul em vez de verde.

Voilà! Espero que você tenha achado isso divertido. Você pode encontrar os arquivos finais aqui e a demonstração aqui .