4

Create a GitHub-Style Calendar Heatmap

 2 years ago
source link: https://blog.bitsrc.io/how-to-code-github-style-calendar-heatmap-a197f26c919e
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

1. Understand the design requirements

The image above is showing you the end product of GitHub. Let’s see what we can code and understand from that design.

The below image is going to be our end product:

0*rChFKsdA_CIxkN8I.png?q=20
how-to-code-github-style-calendar-heatmap-a197f26c919e

Let me edit this a bit:

0*9jjs1AKHkDw1ew7E.jpg?q=20
how-to-code-github-style-calendar-heatmap-a197f26c919e

We need a 6X7 area of tiles for each month. we don’t want any overlapping of dates when a month starts on a day other than Sunday of the week for e.g. Aug 2020 starts on Saturday and ends on Monday, which means this month is overlapping a total of 6 weeks from 52 weeks in of a year.

Next, we should have a separate 6X7 tile area for each month of our calendar and of course a month label.

We also need a separate area for y-axis labels (week names in this case) and an area for our legends.

2. What type of data do we need to draw such a heatmap

Below is the JSON data format you should have to achieve a heat map. loads of date values in different formats and numbers we need! No need to say we are dealing with a calendar here.

I tried explaining in the JSON data below what all fields are required and the desired format.

There are a couple of optional fields you can skip or change as per your choice but a few are mandatory ones.

data.json[
{
"DATEID":"20210401", //date of that day (YYYYMMDD)
"DB_DATE":"2021-04-01", //date of that day (YYYY-MM-DD)
"DAY":"1", // day of the month, from 1 to 31
"DAY_NAME_SHORT":"Thu", //Name of the day
"WEEKDAYNO":"4", // day number in that week(0 for Sunday 6 for Saturday)
"WEEKNO":"13", //week no of the year (1 to 54 or 0 to 53)
"WEEK_OF_MONTH":"0", //week no of the month(0 to 6)
"MONTHID":"202104", // month id (YYYYMM)
"M_NAME":"Apr-21", // Month Name, format can be your choice
"YEARID":"2021", //year id
"ORDER_ID":"1", //one KPI you can have any other KPI
"TOTAL_AMOUNT":"504", //another optional KPI
"RECIEVED_AMOUNT":"500", //optional KPI
"DISCOUNT":"4", //optional
"ORDER_DATE":"01-04-2021", //KPI date
"BAL_FLAG":"1" // this flag will decide cell color
}
]

3. Setup our application files

Including the above data.json file we need 3 more files, index.html, stylesheet file style.css, and our hotcake d3.js code file calendarHeatMap.js

index.html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Calendar Heat Map</title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<div id = "orderHeatMap" style = "overflow-x:scroll;width:80%;margin:0 auto;">
<center><h4 style = "font-family: Arial, Helvetica, sans-serif;color:grey;">Order Month Calendar</h4></center>
</div>
<script src="https://d3js.org/d3.v6.js"></script>
<script src="calendarHeatMap.js"></script>
</body>
</html>style.css.svg-area
{
background-color: rgb(255, 255, 255) !important;
border:1px solid rgb(226, 226, 226);
font-family: Arial, Helvetica, sans-serif;
color:#272727 !important;
}.heatMapMonths-labels{
/* text-anchor: middle; */
font-size: 12px;
}

4. D3.js code

let’s start now with the code we are going to write in our calendarHeatMap.js file.

Create a function in this file drawHeatMap() which will have our code and call this function at the end of the file.

Line 3-37: Grouping of data and legend arrays

We need grouping of the datasetarray at the month level. we need separate arrays for each month to show a month-wise heatmap and month_name grouping to use for the title purpose of that month. along with this, we need two arrays for our y-axis labels and legends.

Legends array values can be set according to the measure or Key values you are using in your dataset array.

We need one variable to set up the cell size, this code works perfectly with the cell size of 12 (height -> 12, width →12) and can be setup up to 25 to get the best look and feel of the heatmap.

var months = d3.groups(dataset, d => d.MONTHID);
var months_name = d3.groups(dataset, d => d.M_NAME);
var week = ["Sun","Tue","Thu","Sat"];
var legends = ["Balance Pending","No Order Recieved","Order Recieved"];
var box = 12; //cell size - ideal is 12 to 25

Now we need our canvas dimensions, the SVG element dimensions where all the heatmaps will be shown:

//Dimensions
var dim = {
width:months.length*80+60,
height:box*7+80,
margin:{
top:10,
bottom:10,
left:10,
right:10
}
}

dim.ctrWidth = dim.width - dim.margin.left - dim.margin.right;
dim.ctrHeight = dim.height - dim.margin.top - dim.margin.bottom;

Line 29-36: Setting up accessor functions

We need an accessor function to get each individual value for a month.

We need JavaScript callback functions which are there to give us the individual value of specific JSON objects within our dataset array, or our grouped arrays months and months_name.

The below code-snippet is setting up our call-back accessor functions:

// Accessor Functions:
var balanceFlagAccessor = (d) => d.BAL_FLAG;
var dayNameAccessor = (d)=>d.DAY_NAME_SHORT;
var orderAmountAccessor = (d)=>d.TOTAL_AMOUNT;
var balanceAmountAccessor = (d)=>(d.TOTAL_AMOUNT-d.DISCOUNT-d.RECIEVED_AMOUNT);
var dateAccessor = (d) => d.DB_DATE;
var weekDayNoAccessor = (d)=>d.WEEKDAYNO;
var weekOfMonthAccessor = (d)=>d.WEEK_OF_MONTH;

Line 38–57: colorScaling and canvas setup

Our colorScalefunction would usescaleQuantize() scaling method of d3 because we need each cell to be colored from 3 choices of colors (red=negative, green=positive, and grey=absence of data).

Note: You can change these color codes to suit what you prefer.

Next, we need our canvas which will be the parent SVG tag, then one group “g” for accommodating all the heatmaps, and one set of groups “g” for each month.

// colorScale
var colorScale = d3.scaleQuantize()
.domain([-1,1])
.range(['#c4405e','#dfe1e4','#40c463']);

//Parent SVG
var svg = d3.select('#orderHeatMap')
.append('svg')
.attr('width',dim.width)
.attr('height',dim.height)
.classed('svg-area',true);

//container
var ctr = svg.append('g')
.attr('transform',`translate(${dim.margin.left},${dim.margin.top})`);


//Heat Maps Container
var heatMapCtr = ctr.append("g")
.attr('transform',`translate(50,30)`);

Line 72: The main for loop

Here we have initialized a for loop on our grouped dataset named months above.

Months is a multidimensional array having one array for each month and an element of day’s data in each row of the inner array.

Line 74–92: Drawing each month label and the chart of that month

// Month Labels
heatMapCtr.append("g")
.attr("transform", `translate(${j*(box*6+20)},10)`)
.append("text")
.attr("x",0)
.classed("heatMapMonths-labels",true)
.text(months_name[j][0]);

// Monthly Heatmap
heatMapCtr.append("g")
.attr("transform", `translate(${j*(box*6+20)},20)`)
.selectAll("rect")
.data(months[j][1])
.join("rect")
.attr("width",box)
.attr("height",box)
.attr("y",(d,i)=>(box+1)*((weekDayNoAccessor(d))%7))
.attr("x",(d,i)=>(box+1)*(weekOfMonthAccessor(d)))
.attr("fill",(d)=>colorScale(balanceFlagAccessor(d)))

In the above line of codes we are accessing an individual array of each month and appending our cell using d3.js functions.

To do blank selection using the selectAllmethod onrect(rectangle, our cell) SVG element.

Line 93–105: Adding hover over function and the cell titles

.on("mouseenter",function(e){
d3.select(this)
.attr("stroke","black")
.attr("stroke-width",2);
})
.on("mouseleave",function(e){
d3.select(this)
.attr("stroke","black")
.attr("stroke-width",0);
})
.append("text")
.append("title")
.text((d)=>"Date : "+dateAccessor(d)+", Day : "+dayNameAccessor(d)+", Sale : "+orderAmountAccessor(d)+", Balance : "+balanceAmountAccessor(d))

In the above mouseenterand mouseleave function, you can write your code to perform any action, and the title is appended to each cell so that on mouse hovering over a cell, it can show the relevant data of that cell.

Line 110–129: Adding legends to the chart

Last but not the least, the below lines are adding legends to our heatmap.

// Legends
var legend = ctr.append("g")
.attr("transform", `translate(0,10)`);

legend.selectAll("rect")
.data(legends)
.join("rect")
.attr("width",box)
.attr("height",box)
.attr("x",(d,i)=>140*(i%3))
.attr("y",-11)
.attr("fill",(d,i)=>colorScale(i-1));

legend.selectAll("text")
.data(legends)
.join("text")
.attr("x",(d,i)=>140*(i%3)+box+2)
.attr("y",0)
.text((d)=>d)
.style("font-size","12px");

I hope you have found this useful. If so, be sure to leave a comment, share on social media, and follow me for more similar content.

Happy reading, keep sharing!

Tip: Build & share independent D3 components with Bit

Have you considered turning your D3 elements into reusable, independent components that can be consumed anywhere? Try Bit.

Bit is an ultra-extensible tool that lets you create truly modular applicationswith independently authored, versioned, and maintained components.

Use it to build modular apps & design systems, author and deliver micro frontends, or simply share components between applications.

It’s fun, give it a try →

0*KjjOHjGBvI_tKBV_.png?q=20
how-to-code-github-style-calendar-heatmap-a197f26c919e

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK