@bokeh/bokehjs
Version:
Interactive, novel data visualization
259 lines • 8.96 kB
JavaScript
import { Sizeable } from "./types";
import { BBox } from "../util/bbox";
import { isNumber } from "../util/types";
import { assert } from "../util/assert";
const { abs, min, max, round } = Math;
export class Layoutable {
static __name__ = "Layoutable";
*[Symbol.iterator]() { }
absolute = false;
position = { left: 0, top: 0 };
_bbox = new BBox();
_inner_bbox = new BBox();
get bbox() {
return this._bbox;
}
get inner_bbox() {
return this._inner_bbox;
}
_sizing = null;
get sizing() {
assert(this._sizing != null);
return this._sizing;
}
_dirty = false;
set dirty(dirty) {
this._dirty = dirty;
}
get dirty() {
return this._dirty;
}
get visible() {
return this.sizing.visible;
}
set visible(visible) {
if (this.sizing.visible != visible) {
this.sizing.visible = visible;
this._dirty = true;
}
}
set_sizing(sizing = {}) {
const width_policy = sizing.width_policy ?? "fit";
const width = sizing.width;
const min_width = sizing.min_width;
const max_width = sizing.max_width;
const height_policy = sizing.height_policy ?? "fit";
const height = sizing.height;
const min_height = sizing.min_height;
const max_height = sizing.max_height;
const aspect = sizing.aspect;
const margin = sizing.margin ?? { top: 0, right: 0, bottom: 0, left: 0 };
const visible = sizing.visible ?? true;
const halign = sizing.halign ?? "start";
const valign = sizing.valign ?? "start";
this._sizing = {
width_policy, min_width, width, max_width,
height_policy, min_height, height, max_height,
aspect,
margin,
visible,
halign,
valign,
size: { width, height },
};
this._init();
}
_init() { }
_set_geometry(outer, inner) {
this._bbox = outer;
this._inner_bbox = inner;
}
set_geometry(outer, inner) {
const { fixup_geometry } = this;
if (fixup_geometry != null) {
[outer, inner] = fixup_geometry(outer, inner);
}
this._set_geometry(outer, inner ?? outer);
for (const handler of this._handlers) {
handler(this._bbox, this._inner_bbox);
}
}
_handlers = [];
on_resize(handler) {
this._handlers.push(handler);
}
is_width_expanding() {
return this.sizing.width_policy == "max";
}
is_height_expanding() {
return this.sizing.height_policy == "max";
}
apply_aspect(viewport, { width, height }) {
const { aspect } = this.sizing;
if (aspect != null) {
const { width_policy, height_policy } = this.sizing;
const gt = (width, height) => {
const policies = { max: 4, fit: 3, min: 2, fixed: 1 };
return policies[width] > policies[height];
};
if (width_policy != "fixed" && height_policy != "fixed") {
if (width_policy == height_policy) {
const w_width = width;
const w_height = round(width / aspect);
const h_width = round(height * aspect);
const h_height = height;
const w_diff = abs(viewport.width - w_width) + abs(viewport.height - w_height);
const h_diff = abs(viewport.width - h_width) + abs(viewport.height - h_height);
if (w_diff <= h_diff) {
width = w_width;
height = w_height;
}
else {
width = h_width;
height = h_height;
}
}
else if (gt(width_policy, height_policy)) {
height = round(width / aspect);
}
else {
width = round(height * aspect);
}
}
else if (width_policy == "fixed") {
height = round(width / aspect);
}
else if (height_policy == "fixed") {
width = round(height * aspect);
}
}
return { width, height };
}
measure(viewport_size) {
if (this._sizing == null) {
this.set_sizing();
}
if (!this.sizing.visible) {
return { width: 0, height: 0 };
}
const exact_width = (width) => {
return this.sizing.width_policy == "fixed" && this.sizing.width != null ? this.sizing.width : width;
};
const exact_height = (height) => {
return this.sizing.height_policy == "fixed" && this.sizing.height != null ? this.sizing.height : height;
};
const viewport = new Sizeable(viewport_size)
.shrink_by(this.sizing.margin)
.map(exact_width, exact_height);
const computed = this._measure(viewport);
const clipped = this.clip_size(computed, viewport);
const width = exact_width(clipped.width);
const height = exact_height(clipped.height);
const size = this.apply_aspect(viewport, { width, height });
return { ...computed, ...size };
}
compute(viewport = {}) {
const size_hint = this.measure({
width: viewport.width != null && this.is_width_expanding() ? viewport.width : Infinity,
height: viewport.height != null && this.is_height_expanding() ? viewport.height : Infinity,
});
const { width, height } = size_hint;
const { left, top } = this.position;
const outer = new BBox({ left, top, width, height });
let inner = undefined;
if (size_hint.inner != null) {
const { left, top, right, bottom } = size_hint.inner;
inner = new BBox({ left, top, right: width - right, bottom: height - bottom });
}
this.set_geometry(outer, inner);
}
get xview() {
return this.bbox.xview;
}
get yview() {
return this.bbox.yview;
}
clip_size(size, viewport) {
function clip(size, vsize, min_size, max_size) {
if (min_size == null) {
min_size = 0;
}
else if (!isNumber(min_size)) {
min_size = round(min_size.percent * vsize);
}
if (max_size == null) {
max_size = Infinity;
}
else if (!isNumber(max_size)) {
max_size = round(max_size.percent * vsize);
}
return max(min_size, min(size, max_size));
}
return {
width: clip(size.width, viewport.width, this.sizing.min_width, this.sizing.max_width),
height: clip(size.height, viewport.height, this.sizing.min_height, this.sizing.max_height),
};
}
has_size_changed() {
const { _dirty } = this;
this._dirty = false;
return _dirty;
}
}
export class ContentLayoutable extends Layoutable {
static __name__ = "ContentLayoutable";
_measure(viewport) {
const content_size = this._content_size();
const bounds = viewport
.bounded_to(this.sizing.size)
.bounded_to(content_size);
const width = (() => {
switch (this.sizing.width_policy) {
case "fixed":
return this.sizing.width != null ? this.sizing.width : content_size.width;
case "min":
return content_size.width;
case "fit":
return bounds.width;
case "max":
return max(content_size.width, bounds.width);
}
})();
const height = (() => {
switch (this.sizing.height_policy) {
case "fixed":
return this.sizing.height != null ? this.sizing.height : content_size.height;
case "min":
return content_size.height;
case "fit":
return bounds.height;
case "max":
return max(content_size.height, bounds.height);
}
})();
return { width, height };
}
}
export class TextLayout extends ContentLayoutable {
text;
static __name__ = "TextLayout";
constructor(text) {
super();
this.text = text;
}
_content_size() {
return new Sizeable(this.text.size());
}
}
export class FixedLayout extends ContentLayoutable {
size;
static __name__ = "FixedLayout";
constructor(size = {}) {
super();
this.size = size;
}
_content_size() {
return new Sizeable(this.size);
}
}
//# sourceMappingURL=layoutable.js.map