const defaults = { // width of chart width: 400, // height of chart height: 400, // color range from 'cold' to 'hot' colorInterpolator: d3.interpolateViridis, // gap size gap: 1, // mouseover callback for tooltips or value display mouseover: _ => {}, // mouseout callback for tooltips or value display mouseout: _ => {} } function d3_rectheatmap(config) { Object.assign(this, defaults, config) this.points = (fn, scaleX, scaleY) => { this.rows = scaleY.range().length; this.cols = scaleX.range().length; let proper_height = this.height - (this.rows - 1) * this.gap; let proper_width = this.width - (this.cols - 1) * this.gap; this.rect_height = proper_height / this.rows; this.rect_width = proper_width / this.cols; this.scaleY = d3.scaleLinear() .domain(scaleY.domain()) .range([0, (this.rows) * (this.rect_height + this.gap)]) this.scaleX = d3.scaleLinear() .domain(scaleX.domain()) .range([0, (this.cols) * (this.rect_width + this.gap)]) points = d3.range(rows * cols) this.z = d3.range(rows * cols) scaleY.range().forEach((valueY, iY) => { extentY = scaleY.invertExtent(valueY); y = (extentY[0] + extentY[1]) / 2 scaleX.range().forEach((valueX, iX) => { extentX = scaleX.invertExtent(valueX); x = (extentX[0] + extentX[1]) / 2 this.z[iY * cols + iX] = fn(x, y) points[iY * cols + iX] = {ix: iX, iy: iY, z: this.z[iY * cols + iX]} }) }) return points; } this.rect = (rect) => { let colorScale = d3.scaleLinear().domain(d3.extent(this.z)).range([0, 1]) rect.attr("x", d => d.ix * (this.rect_width + this.gap)) .attr("y", d => d.iy * (this.rect_height + this.gap)) .attr("width", this.rect_width) .attr("height", this.rect_height) .attr("fill", d => this.colorInterpolator(colorScale(d.z))); } return this; }