@bokeh/bokehjs
Version:
Interactive, novel data visualization
235 lines • 9.16 kB
JavaScript
import { XYGlyph, XYGlyphView } from "./xy_glyph";
import { inherit } from "./glyph";
import { to_screen } from "../../core/types";
import { ImageOrigin } from "../../core/enums";
import * as p from "../../core/properties";
import * as mixins from "../../core/property_mixins";
import { Selection } from "../selections/selection";
import { assert } from "../../core/util/assert";
import { Anchor } from "../common/kinds";
import { anchor } from "../common/resolve";
import { LogScale } from "../scales/log_scale";
export class ImageBaseView extends XYGlyphView {
static __name__ = "ImageBaseView";
connect_signals() {
super.connect_signals();
this.connect(this.model.properties.global_alpha.change, () => this.renderer.request_paint());
}
get image_dimension() {
return 2;
}
get xy_scale() {
switch (this.model.origin) {
case "bottom_left": return { x: 1, y: -1 };
case "top_left": return { x: 1, y: 1 };
case "bottom_right": return { x: -1, y: -1 };
case "top_right": return { x: -1, y: 1 };
}
}
get xy_offset() {
switch (this.model.origin) {
case "bottom_left": return { x: 0.0, y: 1.0 };
case "top_left": return { x: 0.0, y: 0.0 };
case "bottom_right": return { x: 1.0, y: 1.0 };
case "top_right": return { x: 1.0, y: 0.0 };
}
}
get xy_anchor() {
return anchor(this.model.anchor);
}
get xy_sign() {
const xr = this.renderer.xscale.source_range;
const yr = this.renderer.yscale.source_range;
return {
x: xr.is_reversed ? -1 : 1,
y: yr.is_reversed ? -1 : 1,
};
}
_paint(ctx, indices, data) {
const { image_data, sx, sy, sdw, sdh } = { ...this, ...data };
const { xy_sign, xy_scale, xy_offset, xy_anchor } = this;
assert(image_data != null);
ctx.save();
ctx.imageSmoothingEnabled = false;
if (this.visuals.image.doit) {
for (const i of indices) {
const image_data_i = image_data[i];
const sx_i = sx[i];
const sy_i = sy[i];
const sdw_i = sdw[i];
const sdh_i = sdh[i];
if (image_data_i == null || !isFinite(sx_i + sy_i + sdw_i + sdh_i)) {
continue;
}
const tx_i = xy_sign.x * xy_anchor.x * sdw_i;
const ty_i = xy_sign.y * xy_anchor.y * sdh_i;
ctx.save();
ctx.translate(sx_i - tx_i, sy_i - ty_i);
ctx.scale(xy_sign.x * xy_scale.x, xy_sign.y * xy_scale.y);
this.visuals.image.set_vectorize(ctx, i);
ctx.drawImage(image_data_i, -xy_offset.x * sdw_i, -xy_offset.y * sdh_i, sdw_i, sdh_i);
ctx.restore();
}
}
ctx.restore();
}
get _can_inherit_image_data() {
return this.inherited_image;
}
_set_data(indices) {
const n = this.data_size;
if (!this._can_inherit_image_data) {
if (typeof this.image_data === "undefined" || this.image_data.length != n) {
this._define_attr("image_data", new Array(n).fill(null));
this._define_attr("image_width", new Uint32Array(n));
this._define_attr("image_height", new Uint32Array(n));
}
const { image_dimension } = this;
for (let i = 0; i < n; i++) {
if (indices != null && !indices.includes(i)) {
continue;
}
const img = this.image.get(i);
assert(img.dimension == image_dimension, `expected a ${image_dimension}D array, not ${img.dimension}D`);
const [height, width] = img.shape;
this.image_width[i] = width;
this.image_height[i] = height;
const buf8 = this._flat_img_to_buf8(img);
this._set_image_data_from_buffer(i, buf8);
}
}
else {
this._inherit_attr("image_data");
this._inherit_attr("image_width");
this._inherit_attr("image_height");
}
}
_index_data(index) {
const { data_size } = this;
for (let i = 0; i < data_size; i++) {
const [l, r, t, b] = this._lrtb(i);
index.add_rect(l, b, r, t);
}
}
_lrtb(i) {
const dw_i = this.dw.get(i);
const dh_i = this.dh.get(i);
const x_i = this.x[i];
const y_i = this.y[i];
const { xy_anchor } = this;
const [x0, x1] = [x_i - xy_anchor.x * dw_i, x_i + (1 - xy_anchor.x) * dw_i];
const [y0, y1] = [y_i + xy_anchor.y * dh_i, y_i - (1 - xy_anchor.y) * dh_i];
const [l, r] = x0 <= x1 ? [x0, x1] : [x1, x0];
const [b, t] = y0 <= y1 ? [y0, y1] : [y1, y0];
return [l, r, t, b];
}
_get_or_create_canvas(i) {
assert(this.image_data != null);
const image_data_i = this.image_data[i];
if (image_data_i != null && image_data_i.width == this.image_width[i]
&& image_data_i.height == this.image_height[i]) {
return image_data_i;
}
else {
const canvas = document.createElement("canvas");
canvas.width = this.image_width[i];
canvas.height = this.image_height[i];
return canvas;
}
}
_set_image_data_from_buffer(i, buf8) {
assert(this.image_data != null);
const canvas = this._get_or_create_canvas(i);
const ctx = canvas.getContext("2d");
const image_data = ctx.getImageData(0, 0, this.image_width[i], this.image_height[i]);
image_data.data.set(buf8);
ctx.putImageData(image_data, 0, 0);
this.image_data[i] = canvas;
}
_map_data() {
this._define_or_inherit_attr("sdw", () => {
if (this.model.properties.dw.units == "data") {
if (this.inherited_x && this.inherited_dw) {
return inherit;
}
else {
return this.sdist(this.renderer.xscale, this.x, this.dw, "edge", this.model.dilate);
}
}
else {
return this.inherited_dw ? inherit : to_screen(this.dw);
}
});
this._define_or_inherit_attr("sdh", () => {
if (this.model.properties.dh.units == "data") {
if (this.inherited_y && this.inherited_dh) {
return inherit;
}
else {
return this.sdist(this.renderer.yscale, this.y, this.dh, "edge", this.model.dilate);
}
}
else {
return this.inherited_dh ? inherit : to_screen(this.dh);
}
});
}
_image_index(index, x, y) {
const [l, r, t, b] = this._lrtb(index);
const nx = this.image_width[index];
const ny = this.image_height[index];
// The handling of log scales here assumes that users themselves have
// generated images that are "pre-transformed", e.g. an np.meshgrid where
// np.logspace was used for one or both inputs. Bokeh images always assume
// square pixels and do not do any sort of transformation on the canvas
const dx = (() => {
if (this.renderer.xscale instanceof LogScale) {
return Math.log(x / l) / Math.log(r / l);
}
return (x - l) / (r - l);
})();
const dy = (() => {
if (this.renderer.yscale instanceof LogScale) {
return Math.log(y / b) / Math.log(t / b);
}
return (y - b) / (t - b);
})();
const i = Math.floor(dx * nx);
const j = Math.floor(dy * ny);
return { index, i, j, flat_index: j * nx + i };
}
_hit_point(geometry) {
const { sx, sy } = geometry;
const x = this.renderer.xscale.invert(sx);
const y = this.renderer.yscale.invert(sy);
const candidates = this.index.indices({ x0: x, x1: x, y0: y, y1: y });
const result = new Selection();
const indices = [];
for (const index of candidates) {
if (isFinite(sx) && isFinite(sy)) {
indices.push(index);
result.image_indices.push(this._image_index(index, x, y));
}
}
result.indices = indices;
return result;
}
}
export class ImageBase extends XYGlyph {
static __name__ = "ImageBase";
constructor(attrs) {
super(attrs);
}
static {
this.mixins(mixins.ImageVector);
this.define(({ Bool }) => ({
image: [p.NDArraySpec, { field: "image" }],
dw: [p.DistanceSpec, { field: "dw" }],
dh: [p.DistanceSpec, { field: "dh" }],
dilate: [Bool, false],
origin: [ImageOrigin, "bottom_left"],
anchor: [Anchor, "bottom_left"],
}));
}
}
//# sourceMappingURL=image_base.js.map