D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
Golodhros
Full window
Github gist
Britecharts Heatmap MVP
Built with
blockbuilder.org
<!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>
https://d3js.org/d3.v4.min.js