Built with blockbuilder.org
Last active
August 1, 2017 22:35
-
-
Save Golodhros/b08748182e62c40b089d0c3934b48974 to your computer and use it in GitHub Desktop.
Britecharts Heatmap MVP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: mit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
.container { | |
text-align: center; | |
} | |
.container div { | |
margin: 0 auto; | |
} | |
.info { | |
font-size: 1.2em; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<div class="js-heatmap-chart-container"></div> | |
<div class="js-info info"></div> | |
</div> | |
<script> | |
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; | |
const url = "https://api.github.com/repos/Golodhros/britecharts/stats/punch_card"; | |
/** | |
* @typedef HeatmapData | |
* @type {Array[]} | |
* @property {Number[]} values | |
* {Number} values[0] weekday (0: Monday, 6: Sunday) | |
* {Number} values[1] hour | |
* {Number} values[2] quantity | |
* | |
* @example | |
* [ | |
* [0, 1, 3], | |
* [1, 2, 4] | |
* ] | |
*/ | |
/** | |
* Heatmap reusable API class that renders a | |
* simple and configurable heatmap. | |
* | |
* @module Heatmap | |
* @tutorial heatmap | |
* @requires d3-array, d3-axis, d3-dispatch, d3-scale, d3-selection | |
* | |
* @example | |
* var heatmapChart = heatmap(); | |
* | |
* heatmapChart | |
* .height(500) | |
* .width(800); | |
* | |
* d3.select('.css-selector') | |
* .datum(dataset) | |
* .call(heatmapChart); | |
* | |
*/ | |
function HeatMap() { | |
let margin = { | |
top: 10, | |
right: 10, | |
bottom: 10, | |
left: 10 | |
}, | |
width = 900, | |
height = 300, | |
chartWidth, chartHeight, | |
svg, | |
data, | |
boxes, | |
boxSize = 35, | |
colorSchema = [ | |
'#ffd8d4', | |
'#ffb5b0', | |
'#ff938c', | |
'#ff766c', | |
'#ff584c', | |
'#f04b42', | |
'#e03d38', | |
'#be2e29', | |
'#9c1e19' | |
], | |
colorScale, | |
// Dispatcher object to broadcast the mouse events | |
// Ref: https://github.com/mbostock/d3/wiki/Internals#d3_dispatch | |
dispatcher = d3.dispatch('customMouseOver', 'customMouseOut'); | |
/** | |
* This function creates the graph using the selection as container | |
* @param {d3} _selection A d3 selection that represents | |
* the container(s) where the chart(s) will be rendered | |
* @param {HeatmapData} _data The data to attach and generate the chart | |
*/ | |
function exports(_selection) { | |
_selection.each(function(_data) { | |
chartWidth = width - margin.left - margin.right; | |
chartHeight = height - margin.top - margin.bottom; | |
data = cleanData(_data); | |
buildScales(); | |
// buildAxis(); | |
buildSVG(this); | |
drawBoxes(); | |
// drawAxis(); | |
}); | |
} | |
/** | |
* Creates the d3 x and y axis, setting orientations | |
* @private | |
*/ | |
function buildAxis() { | |
if (isHorizontal) { | |
xAxis = d3.axisBottom(xScale) | |
.ticks(numOfHorizontalTicks, valueLabelFormat) | |
.tickSizeInner([-chartHeight]); | |
yAxis = d3Axis.axisLeft(yScale); | |
} else { | |
xAxis = d3Axis.axisBottom(xScale); | |
yAxis = d3Axis.axisLeft(yScale) | |
.ticks(numOfVerticalTicks, valueLabelFormat) | |
} | |
} | |
/** | |
* Builds containers for the chart, the axis and a wrapper for all of them | |
* Also applies the Margin convention | |
* @private | |
*/ | |
function buildContainerGroups() { | |
let container = svg | |
.append('g') | |
.classed('container-group', true) | |
.attr('transform', `translate(${margin.left}, ${margin.top})`); | |
container | |
.append('g').classed('chart-group', true); | |
container | |
.append('g').classed('x-axis-group axis', true); | |
container | |
.append('g').classed('y-axis-group axis', true); | |
container | |
.append('g').classed('metadata-group', true); | |
} | |
/** | |
* Builds the SVG element that will contain the chart | |
* @param {HTMLElement} container DOM element that will work as the container of the graph | |
* @private | |
*/ | |
function buildSVG(container) { | |
if (!svg) { | |
svg = d3.select(container) | |
.append('svg') | |
.classed('britechart heatmap-chart', true); | |
buildContainerGroups(); | |
} | |
svg | |
.attr('width', width) | |
.attr('height', height); | |
} | |
/** | |
* Creates the x and y scales of the graph | |
* @private | |
*/ | |
function buildScales() { | |
colorScale = d3.scaleLinear() | |
.range([colorSchema[0], colorSchema[colorSchema.length - 1]]) | |
.domain(d3.extent(data, function (d) { return d[2] })) | |
.interpolate(d3.interpolateHcl); | |
} | |
/** | |
* Cleaning data adding the proper format | |
* @param {HeatmapData} originalData Data | |
* @private | |
*/ | |
function cleanData(originalData) { | |
let data = originalData.map((d) => { | |
return [ +d[0], +d[1], +d[2]]; | |
}); | |
return data; | |
} | |
/** | |
* Custom OnMouseOut event handler | |
* @return {void} | |
* @private | |
*/ | |
function customOnMouseOut(e, d, chartWidth, chartHeight) { | |
dispatcher.call('customMouseOut', e, d, d3.mouse(e), [chartWidth, chartHeight]); | |
} | |
/** | |
* Custom OnMouseOver event handler | |
* @return {void} | |
* @private | |
*/ | |
function customOnMouseOver(e, d, chartWidth, chartHeight) { | |
dispatcher.call('customMouseOver', e, d, d3.mouse(e), [chartWidth, chartHeight]); | |
} | |
/** | |
* Draws the boxes that form the heatmap | |
* @private | |
*/ | |
function drawBoxes() { | |
boxes = svg.select('.chart-group').selectAll('.box') | |
.data(data); | |
boxes.enter() | |
.append('rect') | |
.attr('x', function (d) { return d[1] * boxSize; }) | |
.attr('y', function (d) { return d[0] * boxSize; }) | |
.attr('width', boxSize) | |
.attr('height', boxSize) | |
.style('fill', function (d) { return colorScale(d[2]); }) | |
.classed('box', true) | |
.on('mouseover', function (d) { | |
customOnMouseOver(this, d, chartWidth, chartHeight); | |
}) | |
.on('mouseout', function (d) { | |
customOnMouseOut(this, d, chartWidth, chartHeight); | |
}); | |
} | |
// API | |
/** | |
* Gets or Sets the height of the chart | |
* @param {number} _x Desired width for the graph | |
* @return { height | module} Current height or Heatmap Chart module to chain calls | |
* @public | |
*/ | |
exports.height = function(_x) { | |
if (!arguments.length) { | |
return height; | |
} | |
height = _x; | |
return this; | |
}; | |
/** | |
* Gets or Sets the margin of the chart | |
* @param {object} _x Margin object to get/set | |
* @return { margin | module} Current margin or Heatmap Chart module to chain calls | |
* @public | |
*/ | |
exports.margin = function(_x) { | |
if (!arguments.length) { | |
return margin; | |
} | |
margin = _x; | |
return this; | |
}; | |
/** | |
* Exposes an 'on' method that acts as a bridge with the event dispatcher | |
* We are going to expose this events: | |
* customMouseOver and customMouseOut | |
* | |
* @return {module} Heatmap Chart | |
* @public | |
*/ | |
exports.on = function() { | |
let value = dispatcher.on.apply(dispatcher, arguments); | |
return value === dispatcher ? exports : value; | |
}; | |
/** | |
* Gets or Sets the width of the chart | |
* @param {number} _x Desired width for the graph | |
* @return { width | module} Current width or Heatmap Chart module to chain calls | |
* @public | |
*/ | |
exports.width = function(_x) { | |
if (!arguments.length) { | |
return width; | |
} | |
width = _x; | |
return this; | |
}; | |
return exports; | |
}; | |
d3.json(url, function (data) { | |
let heatmapChart = HeatMap(), | |
heatmapContainer = d3.select('.js-heatmap-chart-container'); | |
heatmapChart | |
.width(840) | |
.height(280) | |
.on('customMouseOver', function(d) { | |
let message = d[2] + ' commits between ' + d[1] + ':00 and ' + d[1] + ':59 at ' + days[d[0]]; | |
d3.select('.js-info').text(message); | |
}); | |
heatmapContainer.datum(data).call(heatmapChart); | |
}); | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment