UNPKG

nvd3-fork

Version:

FORK! of NVD3, a reusable charting library written in d3.js

376 lines (297 loc) 13.5 kB
/* Heatmap Chart Type A heatmap is a graphical representation of data where the individual values contained in a matrix are represented as colors within cells. Furthermore, metadata can be associated with each of the matrix rows or columns. By grouping these rows/columns together by a given metadata value, data trends can be spotted. Format for input data should be: var data = [ {day: 'mo', hour: '1a', value: 16, timeperiod: 'early morning', weekperiod: 'week', category: 1}, {day: 'mo', hour: '2a', value: 20, timeperiod: 'early morning', weekperiod: 'week', category: 2}, {day: 'mo', hour: '3a', value: 0, timeperiod: 'early morning', weekperiod: 'week', category: 1}, ... ] where the keys 'day' and 'hour' specify the row/column of the heatmap, 'value' specifies the cell value and the keys 'timeperiod', 'weekperiod' and 'week' are extra metadata that can be associated with rows/columns. Options for chart: */ nv.models.heatMapChart = function() { "use strict"; //============================================================ // Public Variables with Default Settings //------------------------------------------------------------ var heatMap = nv.models.heatMap() , legend = nv.models.legend() , legendRowMeta = nv.models.legend() , legendColumnMeta = nv.models.legend() , tooltip = nv.models.tooltip() , xAxis = nv.models.axis() , yAxis = nv.models.axis() ; var margin = {top: 20, right: 10, bottom: 50, left: 60} , marginTop = null , width = null , height = null , color = nv.utils.getColor() , showLegend = true , staggerLabels = false , showXAxis = true , showYAxis = true , alignYAxis = 'left' , alignXAxis = 'top' , rotateLabels = 0 , title = false , x , y , noData = null , dispatch = d3.dispatch('beforeUpdate','renderEnd') , duration = 250 ; xAxis .orient(alignXAxis) .showMaxMin(false) .tickFormat(function(d) { return d }) ; yAxis .orient(alignYAxis) .showMaxMin(false) .tickFormat(function(d) { return d }) ; tooltip .duration(0) .headerEnabled(true) .keyFormatter(function(d, i) { return xAxis.tickFormat()(d, i); }) //============================================================ // Private Variables //------------------------------------------------------------ // https://bl.ocks.org/mbostock/4573883 // get max/min range for all the quantized cell values // returns an array where each element is [start,stop] // of color bin function quantizeLegendValues() { var e = heatMap.colorScale(), legendVals; if (typeof e.domain()[0] === 'string') { // if color scale is ordinal legendVals = e.domain(); } else { // if color scale is numeric legendVals = e.range().map(function(color) { var d = e.invertExtent(color); if (d[0] === null) d[0] = e.domain()[0]; if (d[1] === null) d[1] = e.domain()[1]; return d; }) } return legendVals } // return true if row metadata specified by user function hasRowMeta() { return typeof heatMap.yMeta() === 'function' } // return true if col metadata specified by user function hasColumnMeta() { return typeof heatMap.xMeta() === 'function' } var renderWatch = nv.utils.renderWatch(dispatch, duration); function chart(selection) { renderWatch.reset(); renderWatch.models(heatMap); renderWatch.models(xAxis); renderWatch.models(yAxis); selection.each(function(data) { var container = d3.select(this), that = this; nv.utils.initSVG(container); var availableWidth = nv.utils.availableWidth(width, container, margin), availableHeight = nv.utils.availableHeight(height, container, margin); chart.update = function() { dispatch.beforeUpdate(); container.transition().duration(duration).call(chart); }; chart.container = this; // Display No Data message if there's nothing to show. if (!data || !data.length) { nv.utils.noData(chart, container); return chart; } else { container.selectAll('.nv-noData').remove(); } // Setup Scales x = heatMap.xScale(); y = heatMap.yScale(); // Setup containers and skeleton of chart var wrap = container.selectAll('g.nv-wrap').data([data]); var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap').append('g'); var g = wrap.select('g'); gEnter.append('g').attr('class', 'nv-heatMap'); gEnter.append('g').attr('class', 'nv-legendWrap'); gEnter.append('g').attr('class', 'nv-x nv-axis'); gEnter.append('g').attr('class', 'nv-y nv-axis') g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); heatMap .width(availableWidth) .height(availableHeight); var heatMapWrap = g.select('.nv-heatMap') .datum(data.filter(function(d) { return !d.disabled })); heatMapWrap.transition().call(heatMap); if (heatMap.cellAspectRatio()) { availableHeight = heatMap.cellHeight() * y.domain().length; heatMap.height(availableHeight); } // Setup Axes xAxis .scale(x) ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) .tickSize(-availableHeight, 0); var axisX = g.select('.nv-x.nv-axis') axisX.call(xAxis) .watchTransition(renderWatch, 'heatMap: axisX') .selectAll('.tick') .style('opacity', function() { return showXAxis ? 1 : 0 } ) var xTicks = axisX.selectAll('g'); xTicks .selectAll('.tick text') .attr('transform', function(d,i,j) { var rot = rotateLabels != 0 ? rotateLabels : '0'; var stagger = staggerLabels ? j % 2 == 0 ? '5' : '17' : '0'; return 'translate(0, ' + stagger + ') rotate(' + rot + ' 0,0)'; }) .style('text-anchor', rotateLabels > 0 ? 'start' : rotateLabels < 0 ? 'end' : 'middle'); // position text in center of meta rects var yPos = -5; if (hasColumnMeta()) { axisX.selectAll('text').style('text-anchor', 'middle') yPos = -heatMap.xMetaHeight()()/2 - heatMap.metaOffset() + 3; } // adjust position of axis based on presence of metadata group if (alignXAxis == 'bottom') { axisX .watchTransition(renderWatch, 'heatMap: axisX') .attr("transform", "translate(0," + (availableHeight - yPos) + ")"); if (heatMap.xMeta() !== false) { // if showing x metadata var pos = availableHeight+heatMap.metaOffset()+heatMap.cellBorderWidth() g.select('.xMetaWrap') .watchTransition(renderWatch, 'heatMap: xMetaWrap') .attr("transform", function(d,i) { return "translate(0," + pos + ")" }) } } else { axisX .watchTransition(renderWatch, 'heatMap: axisX') .attr("transform", "translate(0," + yPos + ")"); } yAxis .scale(y) ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) .tickSize( -availableWidth, 0); var axisY = g.select('.nv-y.nv-axis') axisY.call(yAxis) .watchTransition(renderWatch, 'heatMap: axisY') .selectAll('.tick') .style('opacity', function() { return showYAxis ? 1 : 0 } ) // position text in center of meta rects var xPos = -5; if (hasRowMeta()) { axisY.selectAll('text').style('text-anchor', 'middle') xPos = -heatMap.yMetaWidth()()/2 - heatMap.metaOffset(); } // adjust position of axis based on presence of metadata group if (alignYAxis == 'right') { axisY.attr("transform", "translate(" + (availableWidth - xPos) + ",0)"); if (heatMap.yMeta() !== false) { // if showing y meatdata var pos = availableWidth+heatMap.metaOffset()+heatMap.cellBorderWidth() g.select('.yMetaWrap') .watchTransition(renderWatch, 'heatMap: yMetaWrap') .attr("transform", function(d,i) { return "translate(" + pos + ",0)" }) } } else { axisY.attr("transform", "translate(" + xPos + ",0)"); } // Legend var legendWrap = g.select('.nv-legendWrap') legend .width(availableWidth) .color(heatMap.colorScale().range()) var legendVal = quantizeLegendValues().map(function(d) { if (Array.isArray(d)) { // if cell values are numeric return {key: d[0].toFixed(1) + " - " + d[1].toFixed(1)}; } else { // if cell values are ordinal return {key: d}; } }) legendWrap .datum(legendVal) .call(legend) .attr('transform', 'translate(0,' + (alignXAxis == 'top' ? availableHeight : -30) + ')'); // TODO: more intelligent offset (-30) when top aligning legend legendWrap .watchTransition(renderWatch, 'heatMap: nv-legendWrap') .style('opacity', function() { return showLegend ? 1 : 0 } ) }); // axis don't have a flag for disabling the zero line, so we do it manually d3.selectAll('.nv-axis').selectAll('line') .style('stroke-opacity', 0) d3.select('.nv-y').select('path.domain').remove() renderWatch.renderEnd('heatMap chart immediate'); return chart; } //============================================================ // Event Handling/Dispatching (out of chart's scope) //------------------------------------------------------------ heatMap.dispatch.on('elementMouseover.tooltip', function(evt) { tooltip.data(evt).hidden(false); }); heatMap.dispatch.on('elementMouseout.tooltip', function(evt) { tooltip.hidden(true); }); heatMap.dispatch.on('elementMousemove.tooltip', function(evt) { tooltip(); }); //============================================================ // Expose Public Variables //------------------------------------------------------------ chart.dispatch = dispatch; chart.heatMap = heatMap; chart.legend = legend; chart.xAxis = xAxis; chart.yAxis = yAxis; chart.tooltip = tooltip; chart.options = nv.utils.optionsFunc.bind(chart); chart._options = Object.create({}, { // simple options, just get/set the necessary values width: {get: function(){return width;}, set: function(_){width=_;}}, height: {get: function(){return height;}, set: function(_){height=_;}}, showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, noData: {get: function(){return noData;}, set: function(_){noData=_;}}, showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}}, rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}}, // options that require extra logic in the setter margin: {get: function(){return margin;}, set: function(_){ if (_.top !== undefined) { margin.top = _.top; marginTop = _.top; } margin.right = _.right !== undefined ? _.right : margin.right; margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; margin.left = _.left !== undefined ? _.left : margin.left; }}, duration: {get: function(){return duration;}, set: function(_){ duration = _; renderWatch.reset(duration); heatMap.duration(duration); xAxis.duration(duration); yAxis.duration(duration); }}, alignYAxis: {get: function(){return alignYAxis;}, set: function(_){ alignYAxis = _; yAxis.orient(_); }}, alignXAxis: {get: function(){return alignXAxis;}, set: function(_){ alignXAxis = _; xAxis.orient(_); }}, }); nv.utils.inheritOptions(chart, heatMap); nv.utils.initOptions(chart); return chart; }