@bokeh/bokehjs
Version:
Interactive, novel data visualization
257 lines • 7.76 kB
JavaScript
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