UNPKG

@bokeh/bokehjs

Version:

Interactive, novel data visualization

257 lines 7.76 kB
import { HasProps } from "./has_props"; import { Signal0, Signal } from "./signaling"; import { isArray, isString, isNumber, isFunction } from "./util/types"; import { Node } from "../models/coordinates/node"; import { XY as XY_ } from "../models/coordinates/xy"; import { Indexed } from "../models/coordinates/indexed"; import { ViewManager, ViewQuery } from "./view_manager"; import { equals } from "./util/eq"; export class View { static __name__ = "View"; removed = new Signal0(this, "removed"); model; parent; root; owner; views = new ViewQuery(this); _ready = Promise.resolve(undefined); get ready() { return this._ready; } _await_ready(promise) { this._ready = this._ready.then(() => promise); if (this.root != this) { this.root._ready = this.root._ready.then(() => this._ready); } } /** @internal */ _slots = new WeakMap(); connect(signal, slot) { let new_slot = this._slots.get(slot); if (new_slot == null) { new_slot = (args, sender) => { this._await_ready(Promise.resolve(slot.call(this, args, sender))); }; this._slots.set(slot, new_slot); } return signal.connect(new_slot, this); } disconnect(signal, slot) { return signal.disconnect(slot, this); } constructor(options) { const { model, parent, owner } = options; this.model = model; this.parent = isFunction(parent) ? parent(this.model) : parent; if (this.parent == null) { this.root = this; this.owner = owner ?? new ViewManager([this]); } else { this.root = this.parent.root; this.owner = this.root.owner; } } initialize() { } async lazy_initialize() { } _destroyed = false; remove() { this.disconnect_signals(); this.owner.remove(this); this.removed.emit(); this._destroyed = true; } get is_destroyed() { return this._destroyed; } toString() { return `${this.model.type}View(${this.model.id})`; } [equals](that, _cmp) { return Object.is(this, that); } /** @deprecated use children_views */ *children() { yield* this.children_views(); } children_views() { return []; } _has_finished = false; mark_finished() { this._has_finished = true; } /** * Mark as finished even if e.g. external resources were not loaded yet. */ force_finished() { this.mark_finished(); } finish() { this.mark_finished(); this.notify_finished(); } _idle_notified = false; notify_finished() { if (!this.is_root) { this.root.notify_finished(); } else { if (!this._idle_notified && this.has_finished()) { const { document } = this.model; if (document != null) { this._idle_notified = true; document.notify_idle(this.model); } } } } serializable_children() { return [...this.children()].filter((view) => view.model.is_syncable); } serializable_state() { const children = this.serializable_children() .map((view) => view.serializable_state()) .filter((item) => item.bbox != null && item.bbox.is_valid && !item.bbox.is_empty); // TODO move this to a common base class for UI views return { type: this.model.type, children, }; } get is_root() { return this.parent == null; } has_finished() { return this._has_finished; } get is_idle() { return this.has_finished(); } connect_signals() { } disconnect_signals() { Signal.disconnect_receiver(this); } on_change(properties, fn) { for (const property of isArray(properties) ? properties : [properties]) { this.connect(property.change, fn); } } on_transitive_change(property, fn, { recursive = false, signal = (obj) => obj.change } = {}) { const collect = () => { const value = property.is_unset ? [] : property.get_value(); return HasProps.references(value, { recursive }); }; const connect = (models) => { for (const model of models) { this.connect(signal(model), () => fn()); } }; const disconnect = (models) => { for (const model of models) { this.disconnect(signal(model), () => fn()); } }; let models = collect(); connect(models); this.on_change(property, () => { disconnect(models); models = collect(); connect(models); fn(); }); } cursor(_sx, _sy) { return null; } resolve_frame() { return null; } resolve_canvas() { return null; } resolve_plot() { return null; } resolve_target(target) { if (isString(target)) { const ascend = (fn) => { let obj = this; while (obj != null) { const view = fn(obj); if (view != null) { return view; } else { obj = obj.parent; } } return null; }; switch (target) { case "parent": return this.parent; case "frame": return ascend((view) => view.resolve_frame()); case "canvas": return ascend((view) => view.resolve_canvas()); case "plot": return ascend((view) => view.resolve_plot()); } } else { const queue = [this.root]; while (true) { const child = queue.shift(); if (child == null) { break; } else if (child.model == target) { return child; } else { queue.push(...child.children()); } } return null; } } resolve_symbol(_node) { return { x: NaN, y: NaN }; } resolve_node(node) { const target = this.resolve_target(node.target); if (target != null) { return target.resolve_symbol(node); } else { return { x: NaN, y: NaN }; } } resolve_coordinate(coord) { if (coord instanceof XY_) { let obj = this; while (obj != null && obj.resolve_xy == null) { obj = obj.parent; } return obj?.resolve_xy?.(coord) ?? { x: NaN, y: NaN }; } else if (coord instanceof Indexed) { let obj = this; while (obj != null && obj.resolve_indexed == null) { obj = obj.parent; } return obj?.resolve_indexed?.(coord) ?? { x: NaN, y: NaN }; } else if (coord instanceof Node) { return this.resolve_node(coord); } else { return { x: NaN, y: NaN }; } } resolve_as_xy(coord) { const value = this.resolve_coordinate(coord); return isNumber(value) ? { x: NaN, y: NaN } : value; } resolve_as_scalar(coord, dim) { const value = this.resolve_coordinate(coord); return isNumber(value) ? value : value[dim]; } } //# sourceMappingURL=view.js.map