Fork me on GitHub

Calendar Heat Map

Cal-Heatmap is a javascript plugin to help create a calendar heat map, to visualize your time series data a la GitHub contribution graph.

Setup

  1. Download the plugin and copy cal-heatmap.min.js and cal-heatmap.css to your app.
  2. Install d3js in your app.
  3. Create your first calendar:
    <div id="cal-heatmap"></div>
    <script type="text/javascript">
    	var cal = new CalHeatMap();
    	cal.init({
    		data: "datas.json"
    	});
    </script>

This will insert a new calendar into #cal-heatmap in your page, and fill it with data from datas.json.

By default, the calendar will display 12 hours, divided into 60 minutes.

There's a lot of other settings available to customize your calendar.

API

Name Type Default Description
data String|Object "" Example

The calendar data source

The data source is an object with informations used to fill the calendar.
It generally consists of a set of dates, associated with a value, representing the number of occurence of an event at a certain date.

Those datas can be retrieved by two ways :

  • By passing the datas directly, with an array or json object
  • By passing the path to a file, containing those informations

In both cases, the datas must follow a formatting standard for cal-heatmap to process them. Refer to Date source section for accepted format.

If you're getting your data form an API, with an URL like api?start=xxx&end=yyy where xxx is the first unix timetstamp and yyy is the last unix timestamp of the calendar, data also accept date template, which will be automatically replaced by the current start and end date of the calendar.

Available templates are :

  • {{t:start}}: unix timestamp of the calendar's first date
  • {{t:end}}: unix timestamp of the calendar's last date
  • {{d:start}}: ISO-8601 formatted date of the calendar's first date
  • {{d:end}}: ISO-8601 formatted date of the calendar's last date

Example: api?start={{t:start}}&end={{d:end}} will fetch something like api?start=1362006000&end=2013-02-27T23:00:00.000Z

All timestamp are in seconds
dataType String json

Parser engine for the data source. Sometime, we don't have a word to say about how an API return its datas. Whereas it's usually a json object (default), it also can be a simple plain text, or csv.

You can tell cal-heatmap which parser to use to interpret the data source response :

  • json: Default engine
  • csv: Interpret the data as a csv formatted following the RFC-4180 standard.
  • txt: don't interpret the results
id String #cal-heatmap Example ID of the element to insert the calendar to
domain String hour Example Type of time unit for the domain

Can take one of the following values :

  • hour
  • day
  • week
  • month
  • year
subDomain String min Example Time division inside the domain

Can take one of the following values :

  • min
  • hour
  • day
  • x_day : same as day, but with row and column inverted
  • week
  • month
Obviously, the subDomain must be "smaller" than the domain. A day domain can only accept an hour or min subDomain, not day, week, month or year.
range integer 12 Example Number of domain to display
start Date new Date() Example Start Date of the calendar

Default is the current date.
Note that the calendar does not start at this date, but at the beginning of the domain of the date. If the domain is a day, the calendar will start at 00:00 on this date, if it's a month, it will start at the 1 of the month of the date.

loadOnInit Boolean true Whether to fill the calendar with the data, on calendar creation

Passing false will create a blank calendar.

Name Type Default Description
afterLoad callback null Called after drawing the empty calendar, with no datas in it yet
onClick callback null Example Called when clicking on a subdomain cell

2 arguments is passed to the callback :

  • date: Date of the cell that was clicked
  • itemNb: Number of items at this date
afterLoadPreviousDomain callback null Example Called after loading the previous domain

2 arguments is passed to the callback :

  • start: Start of the new domain that was loaded
  • end: End of the new domain that was loaded
afterLoadNextDomain callback null Example Called after loading the next domain

2 arguments is passed to the callback :

  • start: Start of the new domain that was loaded
  • end: End of the new domain that was loaded
onComplete callback null

Called after finish drawing and filling the graph, or just after drawing the graph if loadOnInit is set to false.

afterLoadData callback null

Called after fetching the data source, but before interpreting its contents. This callback takes the result of the data source as argument, after interpretation by the dataType engine. This argument can takes various format, depending on the data source.

This callback must return a json object, formatted like described in the data source section. This callback is used primary for converting data from various source and format to the format used by cal-heatmap.

If the data source is a :

  • String, it's interpreted as an URL, thus the arguments will contains the result of the remote URL. It can be anything, and depends on what the remote url is returning
  • object, it stays an object
Name Type Default Description
cellsize integer
10
Example Size of each subDomain cell, in pixel
cellpadding integer
2
Example Gutter between each subDomain cell, in pixel
cellradius integer
0
Example For rounding the corner of the subdomain cell, in pixels. You have to use another value than crispedges for the shape-rendering property in your css.
domainGutter integer
2
Gutter between each domain.
scale Array
[10, 20, 30, 40]
Example Threshold for the scale

Each couple of values represent a color on the calendar :

  • 0
  • 1 -> 9
  • 10 -> 19
  • 20 -> 29
  • 30 -> 39
  • More than 40

It also accepts negative and float values.

You can set more than 4 threshold. Each threshold correspond to 1 class in the css file. By default, the css support only 4 thresholds. Add the corresponding class to the css.

displayScale Boolean
true
Example Whether to display the scale
scaleLabel Object
{
  lower: "less than {min} {name}",
  inner: "between {down} and {up} {name}",
  upper: "more than {max} {name}"
}
Example

Template for the title when hovering on a scale/legend

See Example for usage

lower is used to format the title of the most left cell of the scale, and upper the most right cell. inner is used for all the cells in between.

Strings can take various tokens, that are replaced on runtime.

  • {min}: smallest value of the scale
  • {max}: biggest value of the scale
  • {down}: lower bound of the current stop
  • {up}: upper bound of the current stop
  • {name}: Refer to the item name, defined by itemName property below
itemName Array
["item", "items"]
Example Name of the data type you're representing on the calendar

First index is the singular form, second index is the plural form.

cellLabel Object
{
  empty: "{date}",
  filled: "{count} {name} {connector} {date}"
}
Example

Template for the title when hovering on date cell

See Example for usage

empty is used to format the title of the date cell when it's empty, and filled when the cell contains datas.

Strings can take various tokens, that are replaced on runtime.

  • {count}: the cell value
  • {name}: Item name, defined by itemName property
  • {connector}: an english preposition (at, on) before the date. Its default value depends on the subdomain, whether it's an hour (e.g: at 15:53) or what's left (e.g: on Monday)
  • {date}: the cell date, formatted as defined by the format property below
format Object
{}
Example Custom formatting of dates and labels

This object has two properties :

  • date : Formatting of the date displayed when hovering a subDomain cell
  • label : Formatting of the domain label, displayed below each domain

Refer to d3.js time formatting documentation for accepted format.

You can also pass a function to date, that will take a Date object as parameter, if you wish to use an external/more powerfull library to format the date. (see example)

You have to always set the 2 properties, even you wish to only change one. They're by default null, and depends on the domain and subdomain used.

browsing boolean
false
Example Whether to enable domain browsing. Will display 2 links to shift the calendar one domain back or forward.
browsingOptions Object
{
  nextLabel: "Next",
  previousLabel: "Previous"
}
Text to display inside the links for browsing domains. Can also accept html.

By default, will display links like in the second example.

You can customize the text and the css to have more beautiful buttons, like in the main example.

duration integer
500
Duration of the animation in milliseconds. Two things are animated in the calendar :
  • When displaying the scale
  • When browsing the calendar
startWeekOnMonday integer
1
ExampleWhether to start a week on Sunday or Monday

Examples

With Default values

By default, cal-heatmap will always display 12 hours, split into 60 minutes each.

Unless you set the date property in init(), the calendar will start at the beginning of the current hour.

var calendar = new CalHeatMap();
calendar.init({});

Days > hours

Display all the hours for 10 days, starting from January 15th, 2000.

JsFiddlevar calendar = new CalHeatMap();
calendar
	data: "datas-years.json",		// Fill the graph with datas from that json file
	start: new Date(2000, 0, 15), 	// Start the calendar on 15th January, 2000
	id : "graph_b",
	domain : "day",					// Group data by days
	subDomain : "hour",				// Split each day by hours
	range : 10,						// Number of days to display
	browsing: true, 				// Enable browsing
    afterLoadNextDomain: function (start, end) {
        alert("You just loaded a new domain starting on " + start + " and ending on " + end);
    }
});

Months > days

Display all days, for 3 months, from February 2000.
Let's try changing the scale threshold, since the number of data represented in a subdomain is larger, since it's now a day instead of hour. Hover on the scale to view the new range each color is representing.

JsFiddlevar calendar = new CalHeatMap();
calendar
	data: "datas-years.json",
	start: new Date(2000, 1),
	id : "graph_c",
	domain : "month",			// Group data by month
	subDomain : "day",			// Split each month by days
	range : 3,					// Just display 3 months
	scale: [40, 60, 80, 100] 	// Custom threshold for the scale
});

Months > days (horizontally)

Same as above, but with the days layered like a regular month calendar, with each column corresponding to a day, and each row to a week. Also start the week on Sunday.

var calendar = new CalHeatMap();
calendar
	data: "datas-years.json",
	start: new Date(2000, 5),
	id : "graph_k",
	domain : "month",
	subDomain : "x_day",
	range : 3,
	cellsize: 15,
	cellpadding: 3,
	cellradius: 5,
	domainGutter: 15,
	weekStartOnMonday: 0,
	scale: [40, 60, 80, 100]
});

Months > hours

Display all the hours of January 2000.
Also resize the cell to fit in the page.

JsFiddlevar calendar = new CalHeatMap();
calendar
	data: "datas-years.json",
	start: new Date(2000, 0),
	id : "graph_f",
	domain : "month",
	subDomain : "hour",
	range : 1,
	cellsize: 6,
	cellpadding: 1
});

Months > weeks

Display weeks for February, March and April 2000.

JsFiddlevar calendar = new CalHeatMap();
calendar
	data: "datas-years.json",
	start: new Date(2000, 1),
	id : "graph_c",
	domain : "month",				// Group data by month
	subDomain : "week",				// Split each month by week
	range : 3,						// Just display 3 months
	scale: [120, 400, 450, 500] 	// Custom threshold for the scale
});

Weeks > hours

Display all the hours of the first 2 weeks of Januray 2000

JsFiddlevar calendar = new CalHeatMap();
calendar
	data: "datas-years.json",
	start: new Date(2000, 0),
	id : "graph_h",
	domain : "week",
	subDomain : "hour",
	range : 2
});

Year > days

Display all days of year 2000.
Additionally, use a custom name for the data : we're representing the number of lost kittens by days, in french.

JsFiddlevar calendar = new CalHeatMap();
calendar.init({
	moment.lang("fr");
	var calendar = new CalHeatMap();
	calendar.init({
		data: "datas-years.json",
		start: new Date(2000, 0),
		id : "graph_d",
		domain : "year",
		subDomain : "day",
		range : 1,
		scale: [40, 60, 80, 100],
		itemName: ["chat perdu", "chats perdus"],
		cellLabel: {
			empty: "Aucune données pour le {date}",
			filled: "il y a eu {count} {name} le {date}"
		},
		scaleLabel: {
			lower: "Belle journée, il y a eu moins de {min} {name}",
			inner: "Pas mal, entre {down} et {up} {name}",
			upper: "Peut faire mieux, plus de {max} {name}"
		},
		format: {
			date: function(date) {
				return moment(date).format("LL");
			},
			legend: null,
		}
	});
});

Year > months

Display 1 year, split by months, with a custom legend and title formatting, as well as a custom callback when clicking on a month.

JsFiddlevar calendar = new CalHeatMap();
calendar.init({
	data: "datas-years.json",
	start: new Date(2000, 0),
	id : "graph_e",
	range : 1,
	domain : "year",
	subDomain : "month",
	scale: [1950, 2050, 2150, 2250],
	displayScale: false,					// Don't display the scale
	format: {
		date: "==%B==",						// Custom date format, hover on a month to view
		legend: "Thy year is %Y, yeah !"	// Custom domain legend
	},
	onClick: function(date, count) {
		alert("Oh gosh, " + count + " things occured on " + date.toISOString());
	}
});

Theme

All colors can be customized via the css

This graph represents the number of visitors per hour for one month. Datas are pulled from Google Analytics, in csv format. This example also highlight the datas format conversion, to transform your datas to something understandable by cal-heatmap.

var calendar = new CalHeatMap();
calendar.init({
	id: "graph_l",
	data: "ga.csv",
	dataType: "csv",
	domain: "day",
	subDomain: "hour",
	range: 31,
	cellpadding: 1,
	itemName : ["visitor", "visitors"],
	scale: [2, 4, 6, 8, 10],
	start: new Date(2013, 03, 14),
	afterLoadData: function(d) {
		var start = +new Date(2013, 03, 14) / 1000;
		var stats = {};
		for (var i = 0, total = d.length; i < total; i++ ) {
			o = d[i];
			stats[(parseInt(o.Hour, 10) * 3600) + start] = o.Visits;
		}
		return stats;
	}
});

Data Source

Expected Datas format

To display datas in the calendar, cal-heatmap takes a set of dates, associated with a count value. Those datas are grouped inside a json object, following the convention :

{
	"946702811" : 12,
	"946702812" : 53
	....
}

The object must contains one property by date, keyname is the unix timestamp of the date (in seconds), and the value, is the count number associated to this date.

See the example datas-hours.json file.

It's the same formatting for all domain type, data will automatically be grouped into min/days/week/hours/month, depending on the domain type. Having a count for each seconds lets you jump to the smaller domain without losing datas.

Exotic datas format

Of course, we can't expect all the API to return the results following the format above.

That's why there's a special callback (afterLoadData(d)) that you can use to convert your results to the required format. This function takes the result of your API as argument, after interpretation by the dataType engine, and must return a json object formatted as above.

// datas is an array of object
var datas = [
	{date: 946702811, value: 15},
	{date: 946702812, value: 25},
	{date: 946702813, value: 10}
]

var parser = function(data) {
	var stats = {};
	for (var d in data) {
		stats[data[d].date] = data[d].value;
	}
	return stats;
};

// Parser will output the object
//{
//	"946702811": 15,
//	"946702812": 25,
//	"946702813": 10
}

var calendar = new CalHeatMap();
calendar.init({
  data: datas,
  afterLoadData: parser
});
All timestamp are in seconds

Support

Support and bug reports are on tracked with the project github issues

Faq : https://github.com/kamisama/cal-heatmap/wiki/FAQ

Authors

Wan Qi Chen

Licence

CalHeatMap is released under the MIT License

Copyright (c) 2013 Wan Qi Chen

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.