/**
* Adds heatmap coloring to cells. By default colors based on min and max
* in the data and the magma color scheme via `d3-scale`.
*
* This plugin provides the following fields:
* - id: `heatmap`
* - tdClassName: `has-heatmap`
* - summarize: minMaxSummarizer
* - tdStyle
* - columnTest
* - ColorSchemes
*
* ## Plugin Options
*
* Plugin options found in `column.plugins.heatmap` since the plugin has id `heatmap`.
*
* - **domain** {Number[]} the domain to use for the color scale, if not provided, uses columnSummary min and max
* - **backgroundScale** {Function} the scale to use for the background-color
* - **colorScale** {Function} the scale to use for the color
* - **colorShift** {Number} if provided, shifts the background color to create the foreground color.
* It is a number between 0 and 1 describing how far to shift.
* - **colorScheme** {String} if provided, specifies which d3-scale/d3-scale-chromatic special
* scale to use. Options are: Viridis, Inferno, Magma, Plasma, Warm, Cool, Rainbow, CubehelixDefault,
* BrBG, PRGn, PiYG, PuOr, RdBu, RdGy, RdYlBu, RdYlGn, Spectral, Blues, Greens, Greys,
* Oranges, Purples, Reds, BuGn, BuPu, GnBu, OrRd, PuBuGn, PuBu, PuRd, RdPu, YlGnBu,
* YlGn, YlOrBr, YlOrRd
* - **reverseColors** {Boolean} If true, the colors in the scheme will be applied in reverse order
*
* @module plugins/HeatmapPlugin
*/
import * as Utils from '../Utils';
import * as Summarizers from '../Summarizers';
import DataType from '../DataType';
import * as d3Scale from 'd3-scale';
import * as d3ScaleChromatic from 'd3-scale-chromatic';
// combine the modules into one big d3 module
const d3 = Object.assign({}, d3Scale, d3ScaleChromatic);
/**
* Special color schemes available via d3-scale and d3-scale-chromatic
* `interpolate${scheme}(t)` functions.
*
* See https://github.com/d3/d3-scale-chromatic
* and https://github.com/d3/d3-scale#interpolateViridis
*
* @enum {String}
*/
const ColorSchemes = {
// from d3-scale
Viridis: 'Viridis',
Inferno: 'Inferno',
Magma: 'Magma',
Plasma: 'Plasma',
Warm: 'Warm',
Cool: 'Cool',
Rainbow: 'Rainbow',
CubehelixDefault: 'CubehelixDefault',
// from d3-scale-chromatic
BrBG: 'BrBG',
PRGn: 'PRGn',
PiYG: 'PiYG',
PuOr: 'PuOr',
RdBu: 'RdBu',
RdGy: 'RdGy',
RdYlBu: 'RdYlBu',
RdYlGn: 'RdYlGn',
Spectral: 'Spectral',
Blues: 'Blues',
Greens: 'Greens',
Greys: 'Greys',
Oranges: 'Oranges',
Purples: 'Purples',
Reds: 'Reds',
BuGn: 'BuGn',
BuPu: 'BuPu',
GnBu: 'GnBu',
OrRd: 'OrRd',
PuBuGn: 'PuBuGn',
PuBu: 'PuBu',
PuRd: 'PuRd',
RdPu: 'RdPu',
YlGnBu: 'YlGnBu',
YlGn: 'YlGn',
YlOrBr: 'YlOrBr',
YlOrRd: 'YlOrRd',
};
const defaultColorScheme = ColorSchemes.Blues;
/**
* Compute the style for the td elements by setting the background and color
* based on the sort value.
*
* @param {Object} cellData the data for the cell
* @param {Object} props Additional properties for the cell
* @param {Object} props.columnSummary the column summary
* @param {Object} props.column The column definition
* @param {Object} props.rowData the data for the row
* @param {Boolean} props.isBottomData whether the row is in bottom data area
* @return {Object} the style object
*/
function tdStyle(cellData, { columnSummary, column, rowData, isBottomData }) {
let domain;
let backgroundScale;
let colorScale;
let colorShift;
let colorScheme;
let reverseColors;
let includeBottomData;
// read in from plugin options
if (column.plugins && column.plugins.heatmap) {
domain = column.plugins.heatmap.domain;
backgroundScale = column.plugins.heatmap.backgroundScale;
colorScale = column.plugins.heatmap.colorScale;
colorShift = column.plugins.heatmap.colorShift;
colorScheme = column.plugins.heatmap.colorScheme;
reverseColors = column.plugins.heatmap.reverseColors;
includeBottomData = column.plugins.heatmap.includeBottomData;
}
// skip in bottom data area unless this column explicitly says to include it
if (isBottomData && !includeBottomData) {
return undefined;
}
// compute the sort value
const sortValue = Utils.getSortValueFromCellData(cellData, column, rowData);
// do not heatmap null or undefined values
if (sortValue == null) {
return undefined;
}
// default domain if not provided comes from columnSummary
if (!domain) {
// if we didn't get a min/max, just use [0, 1]
const colMin = columnSummary.min == null ? 0 : columnSummary.min;
const colMax = columnSummary.max == null ? 1 : columnSummary.max;
domain = [colMin, colMax];
}
// reverse the domain if specified to get the color scheme inverted
if (reverseColors) {
domain = domain.slice().reverse();
}
const domainScale = d3.scaleLinear().domain(domain)
.range([0, 1])
.clamp(true);
// if a background scale is provided, use it
let backgroundColor;
if (backgroundScale) {
if (backgroundScale.domain) {
backgroundScale.domain(domain);
}
backgroundColor = backgroundScale(sortValue);
}
// if a color scale is provided, use it
let color;
if (colorScale) {
if (colorScale.domain) {
colorScale.domain(domain);
}
color = colorScale(sortValue);
// if a color shift is defined, shift the background color by this amount (0, 1)
} else if (backgroundScale && colorShift) {
const shiftedValue = domainScale.invert((domainScale(sortValue) + colorShift) % 1);
color = backgroundScale(shiftedValue);
}
// if no background scale and color scale provided, use default - magma
if (backgroundScale == null) {
colorScheme = colorScheme || defaultColorScheme;
backgroundColor = d3[`interpolate${colorScheme}`](domainScale(sortValue));
if (!colorScale) {
colorShift = colorShift || 0.5;
color = d3[`interpolate${colorScheme}`]((domainScale(sortValue) + colorShift) % 1);
}
}
return {
backgroundColor,
color,
};
}
/**
* Apply this plugin if it is a number and plugins.heatmap isn't explicitly set to false
* OR if plugins.heatmap is set
*
* @param {Object} column The column definition
* @return {Boolean} true to run the plugin on the column, false to not
*/
function columnTest(column) {
return (column.type === DataType.Number &&
(!column.plugins || (column.plugins && column.plugins.heatmap !== false))) ||
(column.plugins && column.plugins.heatmap);
}
const Plugin = {
id: 'heatmap',
summarize: Summarizers.minMaxSummarizer,
tdStyle,
tdClassName: 'has-heatmap',
columnTest,
ColorSchemes,
};
export default Plugin;