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