function densityPlot(density, size) {
let interpolator = cacheInterpolator(d3.interpolateViridis);
let color = buf => d3.scaleSequential(d3.extent(buf), interpolator);
let background = null;
let drawAxes = true;
let xAxisScale, yAxisScale;
let ret = (...data) => {
let margin = { left: 30, top: 5, right: 0, bottom: 25 };
let dense = density.copy();
let xBins = dense.xBins();
let yBins = dense.yBins();
let [width, height] = size || [xBins, yBins];
if (drawAxes) {
if (!drawAxes.preserveCanvasSize) {
width = width - margin.left - margin.right;
height = height - margin.top - margin.bottom;
}
}
let canvas = DOM.canvas(xBins, yBins, 1);
let ctx = canvas.getContext('2d');
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
canvas.style.imageRendering = 'pixelated';
ctx.imageSmoothingEnabled = false;
let container = d3.create('div');
if (!dense.xDomain()) {
let domains = data.map(data => dense.defaultXDomain(data));
dense.xDomain([
d3.min(domains, values => d3.min(values)),
d3.max(domains, values => d3.max(values))
]);
}
if (!dense.yDomain()) {
let domains = data.map(data => dense.defaultYDomain(data));
dense.yDomain([
d3.min(domains, values => d3.min(values)),
d3.max(domains, values => d3.max(values))
]);
}
let xAxisG, yAxisG;
if (drawAxes) {
container
.style('position', 'relative')
.style('width', width + margin.left + margin.right + 'px')
.style('height', height + margin.bottom + margin.top + 'px');
let axesSel = container
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.bottom + margin.top)
.style('position', 'absolute')
.style('z-index', '0')
.style('overflow', 'visible');
xAxisG = axesSel
.append('g')
.attr('transform', `translate(${margin.left}, ${height + margin.top})`);
yAxisG = axesSel
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
ctx.canvas.style.width = width + 'px';
ctx.canvas.style.height = height + 'px';
d3.select(container.node().appendChild(ctx.canvas))
.style('position', 'absolute')
.style('z-index', '1')
.style(
'left',
`${
margin.left + 1
}px`
)
.style('top', `${margin.top}px`);
}
let render = buffers => {
if (drawAxes) {
xAxisG.call(
d3.axisBottom(
xAxisScale
? xAxisScale.copy().range([0, width])
: d3.scaleLinear(dense.xDomain(), [0, width])
)
);
yAxisG.call(
d3.axisLeft(
yAxisScale
? yAxisScale.copy().range([height, 0])
: d3.scaleLinear(dense.yDomain(), [height, 0])
)
);
}
let colorScale = color(...buffers);
let values = new Array(buffers.length).fill(0.0);
let colorScaleReturnsString = typeof colorScale(...values) == 'string';
ctx.fillStyle = background || d3.rgb(colorScale(...values)).toString();
ctx.fillRect(0, 0, xBins, yBins);
let img = ctx.getImageData(0, 0, xBins, yBins);
let imgData = img.data;
for (let x = 0; x < xBins; x++) {
for (let y = 0; y < yBins; y++) {
let draw = false;
for (let i = 0; i < buffers.length; i++) {
let value = buffers[i][yBins * x + y];
values[i] = value;
if (value) draw = true;
}
if (!draw) continue;
let c = colorScaleReturnsString
? d3.rgb(colorScale(...values))
: colorScale(...values);
let i = xBins * y + x;
imgData[4 * i] = c.r;
imgData[4 * i + 1] = c.g;
imgData[4 * i + 2] = c.b;
imgData[4 * i + 3] = 255 * c.opacity;
}
}
ctx.putImageData(img, 0, 0);
let node = drawAxes ? container.node() : ctx.canvas;
dispatchValue(node, {
colorScale,
buffers,
canvas: ctx.canvas,
density: dense,
update
});
return node;
};
let update = (...data) => {
let results = Array.from(data, data => dense(data));
return results.some(isGenerator)
? Generators.map(zipGenerators(results), render)
: render(results);
};
return update(...data);
};
ret.density = function(_) {
return arguments.length ? ((density = _), ret) : density;
};
ret.size = function(_) {
return arguments.length ? ((size = _), ret) : size;
};
ret.color = function(_) {
return arguments.length ? ((color = _), ret) : color;
};
ret.background = function(_) {
return arguments.length ? ((background = _), ret) : background;
};
ret.drawAxes = function(_) {
return arguments.length ? ((drawAxes = _), ret) : drawAxes;
};
ret.xAxisScale = function(_) {
return arguments.length ? ((xAxisScale = _), ret) : xAxisScale;
};
ret.yAxisScale = function(_) {
return arguments.length ? ((yAxisScale = _), ret) : yAxisScale;
};
return ret;
}