Replicating GitHub's grid

The goal is to create a replica of the GitHub grid using a spreadsheet from Google Drive as the database.

Main component

Let's create a component in React called GitHubGrid.js:

jsx
import React, { useState, useEffect } from 'react';
import DrawGrid from './DrawGrid.js';
import GetDataFromDrive from './GetDataFromDrive.js';
import SortMonths from './SortMonths.js'
import '../Styles/GitHubGrid.css';

const GitHubGrid = () => {

// 🕒 Declaring new date: from one year ago until today
const today    = new Date();
const lastYear = new Date(today.getFullYear() - 1, today.getMonth(), today.getDate());

// 🗄️ Getting data from Google Drive => Change [YOUR_KEY] for yours
const url = 'https://spreadsheets.google.com/feeds/list/[YOUR_KEY]/od6/public/values?alt=json';
const data = GetDataFromDrive(url);
    
// 🖼️ Drawing squares
const grid = DrawGrid(lastYear, today, data);
const months = SortMonths();
    
return (
    <div className = 'Status'>
        <p>Last year habits:</p>
        <div className = 'Contribution'>
           <div className = 'Contribution-Wrap'>
               <div className = 'Months'>
                        {months}
               </div>
               <div className = 'Days-Squares'>
                   <div className = 'Squares'>
                        {grid}
                   </div>
               </div>
          </div>
        </div>
    </div>
);

}

export default GitHubGrid;

GitHubGrid.js does 3 actions:

  1. Creates two dates (today and last year).
  2. Gets the data from a spreadsheet in Google Drive.
  3. Draws the grid.

It requires to import GetDataFromDrive.js, SortMonths.js and DrawGrid.js (we'll see these components later).

Creating the data document

The spreadsheet allows us to track our habits:

Google Sheet Data

The key of the document is (you can see it in the URL of the picture above):

"1rtxEO8zm1Wea2WUPbzLnnWgWt3oaSVQ7s7kyLspjgQw"

You need to change this key for yours when you create the url variable in GitHubGrid.js. Also, it's important to set the document as "public" to fetch the data from the app:

Public document

Fetching the data (GetDataFromDrive.js)

The function to fetch the data:

jsx
import React, { useState, useEffect } from 'react'; 

const GetDataFromDrive = (url) => {

const [data, setData] = useState(null);

useEffect( () => { fetch(url).then(resp => resp.json()).then(data => setData(data.feed.entry)) }, [] );
    
return data;

}

export default GetDataFromDrive;

Sorting the months (SortMonths.js)

GitHub displays the current month as the last one on the right of the page. So, we replicate this behavior:

jsx
import React, { useState, useEffect } from 'react'; 

const SortMonths = () => {

    const [months, setMonths] = useState(null);
    
    useEffect( () => {
          
        var sorted = [];
        var today = new Date();
        var months =   [<div>Jan</div>,
                        <div>Feb</div>,
                        <div>Mar</div>,
                        <div>Apr</div>,
                        <div>May</div>,
                        <div>Jun</div>,
                        <div>Jul</div>,
                        <div>Aug</div>,
                        <div>Sep</div>,
                        <div>Oct</div>,
                        <div>Nov</div>,
                        <div>Dec</div>];
                  
         for(var i = 0; i < 12; i ++) sorted[i] = months[(today.getMonth() + i + 1) % 12];
        
         setMonths(sorted);
    
    }, [])
    
    return months;

}

export default SortMonths;

Drawing the grid (DrawGrid.js)

Now it's time to draw the grid:

jsx
import React, { useState, useEffect } from 'react'; 

const DrawGrid = (From, to, data) => {

    const [squares, setSquares] = useState(null);
    
    useEffect( () => {
        
        const array = [];
            
        if(data){

            for(var today = From; today <= to; today.setDate(today.getDate() + 1)) {

                  var nHabits = 0;
                  var classname;
                
                  data.map(data => {

                      var year  = parseInt(data['gsx$date'].$t.substring(6, 10));
                      var month = parseInt(data['gsx$date'].$t.substring(3, 5));
                      var day   = parseInt(data['gsx$date'].$t.substring(0, 2));
                      
                      if(year === today.getFullYear() && month === (today.getMonth() + 1) && day === today.getDate()){
                        data['gsx$code'].$t         === 'Yes' ? nHabits ++ : nHabits;
                        data['gsx$swim'].$t         === 'Yes' ? nHabits ++ : nHabits;
                        data['gsx$gym'].$t          === 'Yes' ? nHabits ++ : nHabits;
                        data['gsx$meditation'].$t   === 'Yes' ? nHabits ++ : nHabits;
                      }

                      classname = `Square Square-${nHabits}`;  

                  });

                  array.push(<div className = {classname}>
                                <div className = {'Invisible ' + classname}>{nHabits} habits, {today.getDate() + '/' + (today.getMonth() + 1) + '/' + today.getFullYear()}</div>
                             </div>);
                             
            }

            setSquares(array);

        }

    }, [data])
    
    return squares;

}

export default DrawGrid;

Last, the css:

css
.Status{
    margin: auto;
    max-width: 1000px;
}
.Contribution{
    display: flex;
    flex-direction: column;
    font-size: x-small;
    margin: auto;
}
.Contribution-Wrap{
    display: flex;
    flex-direction: column;
    font-size: x-small;
    right: 0;
}
.Months{
    display: flex;
    justify-content: space-between;
    overflow: hidden;
    width: 620px;
}
.Days{
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}
.Days-Squares{
    display: flex;
    height: 90px;
    overflow: hidden;
    width: 620px;
}
.Squares{
    display: flex;
    flex-direction: column;
    flex-wrap: wrap;
}
.Square{
    align-items: center;
    display: flex;
    justify-content: center;
    height: 8px;
    margin: 1.87px;
    width: 8px;
}
.Square:hover .Invisible{
    cursor: default;
    display: flex;
}
.Square-1{
    background: #e5faff;
}
.Square-2{
    background: #ccf6ff;
}
.Square-3{
    background: #99edff;
}
.Square-4{
    background: #007e99;
}
.Invisible{
    background: rgba(1, 1, 1, 0.7);
    border-radius: 5px;
    display: none;
    height: 20px;
    margin-top: -15px;
    padding: 5px;
    position: absolute;
    width: 150px;
}

Hi, I'm Erik, an engineer from Barcelona. If you like the post or have any comments, say hi.