UNPKG

@uwdata/mosaic-plot

Version:

A Mosaic-powered plotting framework based on Observable Plot.

88 lines (80 loc) 2.64 kB
import { channelScale } from './util/channel-scale.js'; import { Grid2DMark } from './Grid2DMark.js'; import { channelOption } from './Mark.js'; export class Density2DMark extends Grid2DMark { constructor(source, options) { const { type = 'dot', ...channels } = options; super(type, source, { bandwidth: 20, interpolate: 'linear', pad: 0, pixelSize: 2, ...channels }); } convolve() { super.convolve(); const { bins, pad, extentX, extentY } = this; const [nx, ny] = bins; const scaleX = channelScale(this, 'x'); const scaleY = channelScale(this, 'y'); const [x0, x1] = extentX.map(v => scaleX.apply(v)); const [y0, y1] = extentY.map(v => scaleY.apply(v)); const deltaX = (x1 - x0) / (nx - pad); const deltaY = (y1 - y0) / (ny - pad); const offset = pad ? 0 : 0.5; this.data = points( this.grids, bins, x0, y0, deltaX, deltaY, scaleX.invert, scaleY.invert, offset ); return this; } plotSpecs() { // @ts-expect-error Correct the data column type const { type, channels, densityMap, data: { numRows: length, columns } } = this; const options = {}; for (const c of channels) { const { channel } = c; options[channel] = (channel === 'x' || channel === 'y') ? columns[channel] // use generated x/y data fields : channelOption(c, columns); } for (const channel in densityMap) { if (densityMap[channel]) { options[channel] = columns.density; } } return [{ type, data: { length }, options }]; } } function points(data, bins, x0, y0, deltaX, deltaY, invertX, invertY, offset) { const scale = 1 / (deltaX * deltaY); const [nx, ny] = bins; const batch = nx * ny; const numRows = batch * data.numRows; const x = new Float64Array(numRows); const y = new Float64Array(numRows); const density = new Float64Array(numRows); const columns = { x, y, density }; const { density: grids, ...rest } = data.columns; for (const name in rest) { columns[name] = new rest[name].constructor(numRows); } let r = 0; for (let row = 0; row < data.numRows; ++row) { // copy repeated values in batch for (const name in rest) { columns[name].fill(rest[name][row], r, r + batch); } // copy individual grid values const grid = grids[row]; for (let k = 0, j = 0; j < ny; ++j) { for (let i = 0; i < nx; ++i, ++r, ++k) { x[r] = invertX(x0 + (i + offset) * deltaX); y[r] = invertY(y0 + (j + offset) * deltaY); density[r] = grid[k] * scale; } } } return { numRows, columns }; }