UNPKG

@bokeh/bokehjs

Version:

Interactive, novel data visualization

235 lines 9.16 kB
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