D3 JS — Heat Map

Aravind S
4 min readJun 8, 2018

--

Here sharing my experience in creating a Heat Map using D3.JS. You can find out the code here. Requirement was to show the APIs up and down time during different time. The output will look like this

Here the Y-axis is time slot, for eg: 8.30,9.00,9.30… and X-axis is APIs, for eg: API1,API2…

A JSON with the APIs up and down time is available. If the server is up means, it will be 1(Green) and down means it will be 0(Red). A preview of JSON is like this

{ 
“apis”:[“api1”,”api2",”api3"],
“timeSlot”: [“8.30”,”9.00",”9.30"],
“timeSlot1”:[“0”,”3",”1"],
“timeSlot2”:[“1”,”0",”1"],
“timeSlot3”:[“1”,”0",”0"]}

Here apis is the list of all APIs and timeSlot is the list of all time slot. I will describe how to read the JSON - timeSlot1 is 8.30,timeSlot2 is 9…For timeSlot1 the data is 0,3,1, Which means api1 was down, api2 status is unknown(since the value is other than 0 and 1)and api3 is up.

Now we can start coding!…first create 4 global variables

var dataStatus = [];
var xAxis = [];
var yAxis = [];
var dataStatusWhole = [];

All the variable are of type array. dataStatus will have the value timeSlot1,timeSlot2… xAxis is for creating the x-cord(APIs)and yAxis is for creating the y-cord(timeslot).dataStatusWhole will have the data to create each block in the svg

Next step is to get our JSON data. For that use the d3.json method.

d3.json(“JSON PATH”, function (error, data) {}

On the callback we will get the data/error. All the code which i am writing now onward will be inside this block.

Next step is to push the timeSlots data into dataStatus array.

 var dataKeys = Object.keys(data); 
for (var keyIndex in dataKeys) {
if (dataKeys[keyIndex] != “apis” &&
dataKeys[keyIndex] != “timeSlot”) {
dataStatus.push(data[dataKeys[keyIndex]]);
}
}

Here we will skip the props apis and timeSlot as we are storing it in xAxis and yAxis array.That is the next step

for (var xIndex in data.apis) { 
xAxis.push(data.apis[xIndex]);
}
for (var yIndex in data.timeSlot) { yAxis.push(data.timeSlot[yIndex]);
}

Next is to save data in dataStatusWhole with props slot, status and pos. This object will be very useful when we are creating the svg.

for (var dataCopyOuterIndex in dataStatus) { 
for (var dataCopyInnerIndex in dataStatus[dataCopyOuterIndex]) { dataStatusWhole.push({
slot: +dataCopyOuterIndex + 1,
status: dataStatus[dataCopyOuterIndex][dataCopyInnerIndex],
pos: dataCopyInnerIndex
})
}
}

The dataWholeStatus is an array of object. The slot prop is the timeSlot, for eg : 1,2,3.. and status is whether the server is up or down, for eg: 0 or 1 and pos is the position of the element in array, for eg ; 0,1,2…One object in the array will look like

{
pos:”0",
slot:1,
status:”0"
}

the above object mean, this is the first element in the timeSlot1 array status is 0.So next step is to create the SVG. Here goes the code block for that.

//Setting props to create SVG
var margin = {
top: 50,
bottom: 50,
left: 50,
right: 50
};
var times = yAxis.length;
var innerWidth = window.innerWidth;
var width = Math.max(900, innerWidth) — margin.right — margin.left;
var gridSize = Math.floor(width / times);
var height = gridSize * (yAxis.length + 2);
// Create SVG
var svg = d3.select(‘#chart’)
.append(‘svg’)
.attr(‘width’, width)
.attr(‘height’, height)
.append(‘g’)
.attr(‘transform’, ‘translate(‘ + margin.left + ‘,’ + margin.top + ‘)’);

Here one catch is the no of columns you needed. That you can identify by using the yAxis array length. Size of the small block in the svg will the grid-size.

Next step is to create the xAxis and yAxis. here goes the code for that.

// Add API labels 
var apiLabels = svg.selectAll(“.apiLabel”)
.data(yAxis)
.enter().append(“text”)
.text(function (d) { return d; })
.attr(“x”, 0)
.attr(“y”, function (d, i) { return i * gridSize; })
.style(“text-anchor”, “end”)
.attr(“transform”, “translate(-10,” + gridSize / 1.5 + “)”);
// Add Time labels
var timeLabels = svg.selectAll(“.timeLabel”)
.data(xAxis)
.enter().append(“text”)
.text(function (d) { return d; })
.attr(“x”, function (d, i) { return i * gridSize; })
.attr(“y”, 0)
.style(“text-anchor”, “middle”)
.attr(“transform”, “translate(“ + gridSize / 2 + “, -10)”);

When we create the xAxis, y cord will be zero always and in case of yAxis, x-cord will be zero. .enter will add elements to the DOM. .enter can only be used after a .data() method call. enter() is used to indicate that new elements will be added to the current selection.

Next step is creating the small blocks on the svg based on the APIs up or down status. here is the code for that

var heatMap = svg.selectAll(“apiData”) 
.data(dataStatusWhole)
.enter().append(“rect”)
.attr(“x”, function (d) { return (d.pos) * gridSize; })
.attr(“y”, function (d) { return (d.slot — 1) * gridSize; }) .attr(“class”, “data”)
.attr(“rx”, 4)
.attr(“ry”, 4)
.attr(“width”, gridSize)
.attr(“height”, gridSize)
.style(“fill”, function (d) {
if((d.status != 0 && d.status != 1) || d.status == “”){
return colorScale(2);
}
return colorScale(d.status);
}
);

Here the catch is x and y cord. based on the sample JSON shown above, the total number of objects in dataStatusWhole is (xAxis.length * yAxis.length). So here it will be 9. It will have 3 columns and 3 rows. For the first column the y-cord will be zero always because we are returning (d.slot — 1) * gridSize, where d.slot is zero for the timeSlot1. x-cord will be (d.pos) * gridSize, where d.pos will the array position, It will be starting 0,1,2..Another one important feature is style. Here we will be styling the blocks based the server up or down value. The colorScale function is

var colorScale = d3.scaleLinear()
.domain([0, 1, 2])
.range([“#FF0000”, “#008000”, “#000000”]);

we will return colorScale(d.status), where the values for d.status will be 0 or 1. so colorScale(0) is #FF0000, and colorScale(1) is #008000. For another value or status unknown, we will be sending colorScale(2).

That’s it! your heat map is ready. If you have any questions or issues while doing this project, please comment — Happy to help.

Happy Learning !

--

--