UNPKG

@bokeh/bokehjs

Version:

Interactive, novel data visualization

494 lines 17.1 kB
import * as p from "../../core/properties"; import * as bbox from "../../core/util/bbox"; import * as visuals from "../../core/visuals"; import * as uniforms from "../../core/uniforms"; import { settings } from "../../core/settings"; import { DOMComponentView } from "../../core/dom_view"; import { Model } from "../../model"; import { build_views } from "../../core/build_views"; import { logger } from "../../core/logging"; import { ScreenArray, Indices } from "../../core/types"; import { isArrayable, isString } from "../../core/util/types"; import { RaggedArray } from "../../core/util/ragged_array"; import { every } from "../../core/util/array"; import { inplace_map } from "../../core/util/arrayable"; import { inplace, project_xy } from "../../core/util/projections"; import { is_equal, EqNotImplemented } from "../../core/util/eq"; import { SpatialIndex } from "../../core/util/spatial"; import { assert } from "../../core/util/assert"; import { BBox } from "../../core/util/bbox"; import { FactorRange } from "../ranges/factor_range"; import { Selection } from "../selections/selection"; import { Decoration } from "../graphics/decoration"; const { abs, ceil } = Math; export const inherit = Symbol("inherit"); export class GlyphView extends DOMComponentView { static __name__ = "GlyphView"; visuals; get renderer() { return this.parent; } /** @internal */ glglyph; has_webgl() { return this.glglyph != null && this._can_use_webgl; } _can_use_webgl = false; _compute_can_use_webgl() { return true; } _index = null; _data_size = null; _nohit_warned = new Set(); get index() { const { _index } = this; if (_index != null) { return _index; } else { throw new Error(`${this}.index_data() wasn't called`); } } get data_size() { const { base } = this; if (base != null) { return base.data_size; } else { const { _data_size } = this; if (_data_size != null) { return _data_size; } else { throw new Error(`${this}.set_data() wasn't called`); } } } initialize() { super.initialize(); this.visuals = new visuals.Visuals(this); } decorations = new Map(); children_views() { return [...super.children_views(), ...this.decorations.values()]; } async lazy_initialize() { await super.lazy_initialize(); await build_views(this.decorations, this.model.decorations, { parent: this.parent }); const { webgl } = this.canvas; if (webgl != null && this.load_glglyph != null) { const cls = await this.load_glglyph(); this.glglyph = new cls(webgl.regl_wrapper, this); } } request_paint() { this.parent.request_paint(); } get canvas() { return this.renderer.parent.canvas_view; } paint(ctx, indices, data) { if (this.has_webgl()) { this.glglyph.render(ctx, indices, this.base ?? this); } else if (this.canvas.webgl != null && settings.force_webgl) { throw new Error(`${this} doesn't support webgl rendering`); } else { this._paint(ctx, indices, data); } } has_finished() { return true; } notify_finished() { this.renderer.notify_finished(); } _bounds(bounds) { return bounds; } bounds(window_axis = "none") { switch (window_axis) { case "none": { return this._bounds(this.index.bbox); } case "x": { const x_range = this.renderer.coordinates.x_source; if (isNaN(x_range.start) || isNaN(x_range.end)) { return this._bounds(this.index.bbox); } const hit_box = bbox.x_range(x_range.start, x_range.end); const { x0, y0, x1, y1 } = this.index.bounds(hit_box); if (!isFinite(y0 + y1)) { return this._bounds(this.index.bbox); } return this._bounds({ x0, y0, x1, y1 }); } case "y": { const y_range = this.renderer.coordinates.y_source; if (isNaN(y_range.start) || isNaN(y_range.end)) { return this._bounds(this.index.bbox); } const hit_box = bbox.y_range(y_range.start, y_range.end); const { x0, y0, x1, y1 } = this.index.bounds(hit_box); if (!isFinite(x0 + x1)) { return this._bounds(this.index.bbox); } return this._bounds({ x0, y0, x1, y1 }); } } } log_bounds() { const { x0, x1 } = this.index.bounds(bbox.positive_x()); const { y0, y1 } = this.index.bounds(bbox.positive_y()); return this._bounds({ x0, y0, x1, y1 }); } get_anchor_point(anchor, i, [sx, sy]) { switch (anchor) { case "center": case "center_center": { const [x, y] = this.scenterxy(i, sx, sy); return { x, y }; } default: return null; } } sdist(scale, pts, spans, pts_location = "edge", dilate = false) { const n = pts.length; const sdist = new ScreenArray(n); const compute = scale.s_compute; if (pts_location == "center") { for (let i = 0; i < n; i++) { const pts_i = pts[i]; const halfspan_i = spans.get(i) / 2; const spt0 = compute(pts_i - halfspan_i); const spt1 = compute(pts_i + halfspan_i); sdist[i] = abs(spt1 - spt0); } } else { for (let i = 0; i < n; i++) { const pts_i = pts[i]; const spt0 = compute(pts_i); const spt1 = compute(pts_i + spans.get(i)); sdist[i] = abs(spt1 - spt0); } } if (dilate) { inplace_map(sdist, (sd) => ceil(sd)); } return sdist; } draw_legend_for_index(_ctx, _bbox, _index) { } hit_test(geometry) { const hit = (() => { switch (geometry.type) { case "point": return this._hit_point?.(geometry); case "span": return this._hit_span?.(geometry); case "rect": return this._hit_rect?.(geometry); case "poly": return this._hit_poly?.(geometry); } })(); if (hit != null) { return hit; } if (!this._nohit_warned.has(geometry.type)) { logger.debug(`'${geometry.type}' selection not available for ${this.model.type}`); this._nohit_warned.add(geometry.type); } return null; } _hit_rect_against_index(geometry) { const { sx0, sx1, sy0, sy1 } = geometry; const [x0, x1] = this.renderer.coordinates.x_scale.r_invert(sx0, sx1); const [y0, y1] = this.renderer.coordinates.y_scale.r_invert(sy0, sy1); const indices = [...this.index.indices({ x0, x1, y0, y1 })]; return new Selection({ indices }); } _project_xy(x, xs, y, ys) { const inherited_x = this._is_inherited(x); const inherited_y = this._is_inherited(y); if (!inherited_x && !inherited_y) { inplace.project_xy(xs, ys); } else if (!inherited_x || !inherited_y) { const [proj_x, proj_y] = project_xy(xs, ys); this._define_attr(x, proj_x); this._define_attr(y, proj_y); } } _project_data() { } *_iter_visuals() { for (const visual of this.visuals) { for (const prop of visual) { if (prop instanceof p.VectorSpec || prop instanceof p.ScalarSpec) { yield prop; } } } } _base = null; get base() { return this._base; } set_base(base) { if (base != this && base instanceof this.constructor) { this._base = base; } else { this._base = null; } } _define_or_inherit_attr(attr, fn) { const value = fn(); if (value === inherit) { this._inherit_attr(attr); } else { this._define_attr(attr, value); } } _define_attr(attr, value) { Object.defineProperty(this, attr, { configurable: true, enumerable: true, value, }); this._define_inherited(attr, false); } _inherit_attr(attr) { const { base } = this; assert(base != null); this._inherit_from(attr, base); } _inherit_from(attr, base) { Object.defineProperty(this, attr, { configurable: true, enumerable: true, get() { return base[attr]; }, }); this._define_inherited(attr, true); } _define_inherited(attr, value) { Object.defineProperty(this, `inherited_${attr}`, { configurable: true, enumerable: true, value, }); } _can_inherit_from(prop, base) { if (base == null) { return false; } const base_prop = base.model.property(prop.attr); const value = prop.get_value(); const base_value = base_prop.get_value(); try { return is_equal(value, base_value); } catch (error) { if (error instanceof EqNotImplemented) { return false; } else { throw error; } } } _is_inherited(prop) { const name = isString(prop) ? prop : prop.attr; return this[`inherited_${name}`]; } set_visuals(source, indices) { for (const prop of this._iter_visuals()) { const { base } = this; if (base != null && this._can_inherit_from(prop, base)) { this._inherit_from(prop.attr, base); } else { const uniform = prop.uniform(source).select(indices); this._define_attr(prop.attr, uniform); } } for (const visual of this.visuals) { visual.update(); } if (this.has_webgl()) { this.glglyph.set_visuals_changed(); } } _transform_array(prop, array) { // examine just the top level of a 2-d array to validate // that every subitem is an array of some kind, as expected if (prop instanceof p.CoordinateSeqSpec) { // work around issues with empty data sources (see #14424) const indeterminate_length = this.renderer.data_source.get_value().get_length() == null; if (!indeterminate_length && !every(array, isArrayable)) { const msg = `expected a 2-d array for ${this.model.type}.${prop.attr}`; logger.error(msg); throw new Error(msg); } } const { x_source, y_source } = this.renderer.coordinates; const range = prop.dimension == "x" ? x_source : y_source; if (range instanceof FactorRange) { if (prop instanceof p.CoordinateSpec) { array = range.v_synthetic(array); } else if (prop instanceof p.CoordinateSeqSpec) { for (let i = 0; i < array.length; i++) { array[i] = range.v_synthetic(array[i]); } } else if (prop instanceof p.CoordinateSeqSeqSeqSpec) { // TODO } } let final_array; if (prop instanceof p.CoordinateSeqSpec) { // TODO: infer precision final_array = RaggedArray.from(array, Float64Array); } else if (prop instanceof p.CoordinateSeqSeqSeqSpec) { // TODO RaggedArrayN final_array = array; } else { final_array = array; } return final_array; } async set_data(source, indices, indices_to_update) { const visuals = new Set(this._iter_visuals()); const { base } = this; this._data_size = indices.count; for (const prop of this.model) { if (!(prop instanceof p.VectorSpec || prop instanceof p.ScalarSpec)) { continue; } if (visuals.has(prop)) { // let set_visuals() do the work, at least for now continue; } if (base != null && this._can_inherit_from(prop, base)) { this._inherit_from(prop.attr, base); if (prop instanceof p.DistanceSpec || prop instanceof p.ScreenSizeSpec) { this._inherit_from(`max_${prop.attr}`, base); } } else { if (prop instanceof p.BaseCoordinateSpec) { const array = this._transform_array(prop, indices.select(prop.array(source))); this._define_attr(prop.attr, array); } else { const uniform = prop.uniform(source).select(indices); this._define_attr(prop.attr, uniform); if (prop instanceof p.DistanceSpec || prop instanceof p.ScreenSizeSpec) { const max_value = uniforms.max(uniform); this._define_attr(`max_${prop.attr}`, max_value); } } } } if (this.renderer.plot_view.model.use_map) { this._project_data(); } this._set_data(indices_to_update ?? null); // TODO doesn't take subset indices into account await this._set_lazy_data(indices_to_update ?? null); // TODO doesn't take subset indices into account for (const decoration of this.decorations.values()) { decoration.marking.set_data(source, indices); } if (this.glglyph != null) { this._can_use_webgl = this._compute_can_use_webgl(); } if (this.has_webgl()) { this.glglyph.set_data_changed(); } if (base == null) { this.index_data(); } } _set_data(_indices) { } async _set_lazy_data(_indices) { } /** * Any data transformations that require visuals. */ after_visuals() { } async after_lazy_visuals() { } get _index_size() { return this.data_size; } index_data() { const index = new SpatialIndex(this._index_size); this._index_data(index); index.finish(); this._index = index; } mask_data() { /** Returns subset indices in the viewport. */ if (this._mask_data == null) { return Indices.all_set(this.data_size); } else { return this._mask_data(); } } map_data() { const { x_scale, y_scale } = this.renderer.coordinates; const { base } = this; const v_compute = (prop) => { const scale = prop.dimension == "x" ? x_scale : y_scale; const array = this[prop.attr]; if (array instanceof RaggedArray) { return new RaggedArray(array.offsets, scale.v_compute(array.data)); } else { return scale.v_compute(array); } }; for (const prop of this.model) { if (prop instanceof p.BaseCoordinateSpec) { if (base != null && this._is_inherited(prop)) { this._inherit_from(`s${prop.attr}`, base); } else { const array = v_compute(prop); this._define_attr(`s${prop.attr}`, array); } } } this._map_data(); if (this.has_webgl()) { this.glglyph.set_data_mapped(); } } // This is where specs not included in coords are computed, e.g. radius. _map_data() { } get bbox() { if (this.base == null) { const { x0, y0, x1, y1 } = this.index.bbox; const { x_scale, y_scale } = this.renderer.coordinates; const [sx0, sx1] = x_scale.r_compute(x0, x1); const [sy0, sy1] = y_scale.r_compute(y0, y1); return BBox.from_rect({ x0: sx0, y0: sy0, x1: sx1, y1: sy1 }); } else { return undefined; } } } export class Glyph extends Model { static __name__ = "Glyph"; constructor(attrs) { super(attrs); } static { this.define(({ List, Ref }) => ({ decorations: [List(Ref(Decoration)), []], })); } } //# sourceMappingURL=glyph.js.map