StackGenVis: Alignment of Data, Algorithms, and Models for Stacking Ensemble Learning Using Performance Metrics
https://doi.org/10.1109/TVCG.2020.3030352
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
4.5 KiB
149 lines
4.5 KiB
import {max} from 'd3-array';
|
|
import {rgb} from 'd3-color';
|
|
import {canvas} from 'vega-canvas';
|
|
import {Transform} from 'vega-dataflow';
|
|
import {
|
|
accessorFields, constant, extend, identity,
|
|
inherits, isFunction, toSet, zero
|
|
} from 'vega-util';
|
|
|
|
/**
|
|
* Render a heatmap image for input raster grid data.
|
|
* @constructor
|
|
* @param {object} params - The parameters for this operator.
|
|
* @param {function(object): *} [params.field] - The field with raster grid
|
|
* data. If unspecified, the tuple itself is interpreted as a raster grid.
|
|
* @param {string} [params.color] - A constant color value or function for
|
|
* individual pixel color. If a function, it will be invoked with an input
|
|
* object that includes $x, $y, $value, and $max fields for the grid.
|
|
* @param {number} [params.opacity] - A constant opacity value or function for
|
|
* individual pixel opacity. If a function, it will be invoked with an input
|
|
* object that includes $x, $y, $value, and $max fields for the grid.
|
|
* @param {string} [params.resolve] - The method for resolving maximum values
|
|
* across multiple input grids. If 'independent' (the default), maximum
|
|
* calculation will be performed separately for each grid. If 'shared',
|
|
* a single global maximum will be used for all input grids.
|
|
* @param {string} [params.as='image'] - The output field in which to store
|
|
* the generated bitmap canvas images (default 'image').
|
|
*/
|
|
export default function Heatmap(params) {
|
|
Transform.call(this, null, params);
|
|
}
|
|
|
|
Heatmap.Definition = {
|
|
"type": "heatmap",
|
|
"metadata": {"modifies": true},
|
|
"params": [
|
|
{ "name": "field", "type": "field" },
|
|
{ "name": "color", "type": "string", "expr": true},
|
|
{ "name": "opacity", "type": "number", "expr": true},
|
|
{ "name": "resolve", "type": "enum", "values": ["shared", "independent"], "default": "independent" },
|
|
{ "name": "as", "type": "string", "default": "image" }
|
|
]
|
|
};
|
|
|
|
var prototype = inherits(Heatmap, Transform);
|
|
|
|
prototype.transform = function(_, pulse) {
|
|
if (!pulse.changed() && !_.modified()) {
|
|
return pulse.StopPropagation;
|
|
}
|
|
|
|
var source = pulse.materialize(pulse.SOURCE).source,
|
|
shared = _.resolve === 'shared',
|
|
field = _.field || identity,
|
|
opacity = opacity_(_.opacity, _),
|
|
color = color_(_.color, _),
|
|
as = _.as || 'image',
|
|
obj = {
|
|
$x: 0, $y: 0, $value: 0,
|
|
$max: shared ? max(source.map(t => max(field(t).values))) : 0
|
|
};
|
|
|
|
source.forEach(t => {
|
|
const v = field(t);
|
|
|
|
// build proxy data object
|
|
const o = extend({}, t, obj);
|
|
// set maximum value if not globally shared
|
|
if (!shared) o.$max = max(v.values || []);
|
|
|
|
// generate canvas image
|
|
// optimize color/opacity if not pixel-dependent
|
|
t[as] = toCanvas(v, o,
|
|
color.dep ? color : constant(color(o)),
|
|
opacity.dep ? opacity : constant(opacity(o))
|
|
);
|
|
});
|
|
|
|
return pulse.reflow(true).modifies(as);
|
|
};
|
|
|
|
// get image color function
|
|
function color_(color, _) {
|
|
let f;
|
|
if (isFunction(color)) {
|
|
f = obj => rgb(color(obj, _));
|
|
f.dep = dependency(color);
|
|
} else {
|
|
// default to mid-grey
|
|
f = constant(rgb(color || '#888'));
|
|
}
|
|
return f;
|
|
}
|
|
|
|
// get image opacity function
|
|
function opacity_(opacity, _) {
|
|
let f;
|
|
if (isFunction(opacity)) {
|
|
f = obj => opacity(obj, _);
|
|
f.dep = dependency(opacity);
|
|
} else if (opacity) {
|
|
f = constant(opacity);
|
|
} else {
|
|
// default to [0, max] opacity gradient
|
|
f = obj => (obj.$value / obj.$max) || 0;
|
|
f.dep = true;
|
|
}
|
|
return f;
|
|
}
|
|
|
|
// check if function depends on individual pixel data
|
|
function dependency(f) {
|
|
if (!isFunction(f)) return false;
|
|
const set = toSet(accessorFields(f));
|
|
return set.$x || set.$y || set.$value || set.$max;
|
|
}
|
|
|
|
// render raster grid to canvas
|
|
function toCanvas(grid, obj, color, opacity) {
|
|
const n = grid.width,
|
|
m = grid.height,
|
|
x1 = grid.x1 || 0,
|
|
y1 = grid.y1 || 0,
|
|
x2 = grid.x2 || n,
|
|
y2 = grid.y2 || m,
|
|
val = grid.values,
|
|
value = val ? i => val[i] : zero,
|
|
can = canvas(x2 - x1, y2 - y1),
|
|
ctx = can.getContext('2d'),
|
|
img = ctx.getImageData(0, 0, x2 - x1, y2 - y1),
|
|
pix = img.data;
|
|
|
|
for (let j=y1, k=0; j<y2; ++j) {
|
|
obj.$y = j - y1;
|
|
for (let i=x1, r=j*n; i<x2; ++i, k+=4) {
|
|
obj.$x = i - x1;
|
|
obj.$value = value(i + r);
|
|
|
|
const v = color(obj);
|
|
pix[k+0] = v.r;
|
|
pix[k+1] = v.g;
|
|
pix[k+2] = v.b;
|
|
pix[k+3] = ~~(255 * opacity(obj));
|
|
}
|
|
}
|
|
|
|
ctx.putImageData(img, 0, 0);
|
|
return can;
|
|
}
|
|
|