@bokeh/bokehjs
Version:
Interactive, novel data visualization
222 lines • 7.69 kB
JavaScript
import { settings } from "../../core/settings";
import { logger } from "../../core/logging";
import { div, px } from "../../core/dom";
import { OutputBackend } from "../../core/enums";
import { UIEventBus } from "../../core/ui_events";
import { load_module } from "../../core/util/modules";
import { CanvasLayer } from "../../core/util/canvas";
import { UIElement, UIElementView } from "../ui/ui_element";
import { InlineStyleSheet } from "../../core/dom";
import * as canvas_css from "../../styles/canvas.css";
import icons_css from "../../styles/icons.css";
async function init_webgl() {
// We use a global invisible canvas and gl context. By having a global context,
// we avoid the limitation of max 16 contexts that most browsers have.
const canvas = document.createElement("canvas");
const gl = canvas.getContext("webgl", { alpha: true, antialias: false, depth: false, premultipliedAlpha: true });
// If WebGL is available, we store a reference to the ReGL wrapper on
// the ctx object, because that's what gets passed everywhere.
if (gl != null) {
const webgl = await load_module(import("../glyphs/webgl"));
if (webgl != null) {
const regl_wrapper = webgl.get_regl(gl);
if (regl_wrapper.has_webgl) {
return { canvas, regl_wrapper };
}
else {
logger.trace("WebGL is supported, but not the required extensions");
}
}
else {
logger.trace("WebGL is supported, but bokehjs(.min).js bundle is not available");
}
}
else {
logger.trace("WebGL is not supported");
}
return null;
}
const global_webgl = (() => {
let _global_webgl;
return async () => {
if (_global_webgl !== undefined) {
return _global_webgl;
}
else {
return _global_webgl = await init_webgl();
}
};
})();
export class CanvasView extends UIElementView {
static __name__ = "CanvasView";
webgl = null;
underlays_el;
primary;
overlays;
overlays_el;
events_el;
ui_event_bus;
_size = new InlineStyleSheet("", "size");
touch_action = new InlineStyleSheet("", "touch-action");
initialize() {
super.initialize();
this.underlays_el = div({ class: canvas_css.layer });
this.primary = this.create_layer();
this.overlays = this.create_layer();
this.overlays_el = div({ class: canvas_css.layer });
this.events_el = div({ class: [canvas_css.layer, canvas_css.events], tabIndex: 0 });
this.ui_event_bus = new UIEventBus(this);
}
get layers() {
return [
this.underlays_el,
this.primary,
this.overlays,
this.overlays_el,
this.events_el,
];
}
async lazy_initialize() {
await super.lazy_initialize();
if (this.model.output_backend == "webgl") {
this.webgl = await global_webgl();
if (settings.force_webgl && this.webgl == null) {
throw new Error("webgl is not available");
}
}
}
remove() {
this.ui_event_bus.remove();
super.remove();
}
stylesheets() {
return [...super.stylesheets(), canvas_css.default, icons_css, this._size, this.touch_action];
}
render() {
super.render();
const elements = [
this.underlays_el,
this.primary.el,
this.overlays.el,
this.overlays_el,
this.events_el,
];
this.shadow_el.append(...elements);
}
get pixel_ratio() {
return this.primary.pixel_ratio;
}
get pixel_ratio_changed() {
return this.primary.pixel_ratio_changed;
}
_update_bbox() {
const changed = super._update_bbox() || this.pixel_ratio_changed;
if (changed) {
const { width, height } = this.bbox;
this._size.replace(`.${canvas_css.layer}`, {
width: px(width),
height: px(height),
});
this.primary.resize(width, height);
this.overlays.resize(width, height);
}
return changed;
}
after_resize() {
if (this.plot_views.length != 0) {
// Canvas is being managed by a plot, thus it should not attempt
// self-resize, as it would result in inconsistent state and
// possibly invalid layout and/or lack of repaint of a plot.
this.finish();
}
else {
super.after_resize();
}
}
_after_resize() {
super._after_resize();
const { width, height } = this.bbox;
this.primary.resize(width, height);
this.overlays.resize(width, height);
}
resize() {
const changed = this._update_bbox();
this._after_resize();
return changed;
}
prepare_webgl(frame_box) {
// Prepare WebGL for a drawing pass
const { webgl } = this;
if (webgl != null) {
// Sync canvas size
const { width, height } = this.bbox;
webgl.canvas.width = this.pixel_ratio * width;
webgl.canvas.height = this.pixel_ratio * height;
const { x: sx, y: sy, width: w, height: h } = frame_box;
const { xview, yview } = this.bbox;
const vx = xview.compute(sx);
const vy = yview.compute(sy + h);
const ratio = this.pixel_ratio;
webgl.regl_wrapper.set_scissor(ratio * vx, ratio * vy, ratio * w, ratio * h);
this._clear_webgl();
}
}
blit_webgl(ctx) {
// This should be called when the ctx has no state except the HIDPI transform
const { webgl } = this;
if (webgl != null && webgl.canvas.width * webgl.canvas.height > 0) {
// Blit gl canvas into the 2D canvas. To do 1-on-1 blitting, we need
// to remove the hidpi transform, then blit, then restore.
// ctx.globalCompositeOperation = "source-over" -> OK; is the default
logger.debug("Blitting WebGL canvas");
ctx.restore();
ctx.drawImage(webgl.canvas, 0, 0);
// Set back hidpi transform
ctx.save();
if (this.model.hidpi) {
const ratio = this.pixel_ratio;
ctx.scale(ratio, ratio);
ctx.translate(0.5, 0.5);
}
this._clear_webgl();
}
}
_clear_webgl() {
const { webgl } = this;
if (webgl != null) {
// Prepare GL for drawing
const { regl_wrapper, canvas } = webgl;
regl_wrapper.clear(canvas.width, canvas.height);
}
}
compose() {
const composite = this.create_layer();
const { width, height } = this.bbox;
composite.resize(width, height);
composite.ctx.drawImage(this.primary.canvas, 0, 0);
composite.ctx.drawImage(this.overlays.canvas, 0, 0);
return composite;
}
create_layer() {
const { output_backend, hidpi } = this.model;
return new CanvasLayer(output_backend, hidpi);
}
to_blob() {
return this.compose().to_blob();
}
plot_views = [];
}
export class Canvas extends UIElement {
static __name__ = "Canvas";
constructor(attrs) {
super(attrs);
}
static {
this.prototype.default_view = CanvasView;
this.define(({ Bool }) => ({
hidpi: [Bool, true],
output_backend: [OutputBackend, "canvas"],
}));
}
}
//# sourceMappingURL=canvas.js.map