@bokeh/bokehjs
Version:
Interactive, novel data visualization
167 lines • 6.1 kB
JavaScript
import { XYGlyph, XYGlyphView } from "./xy_glyph";
import { generic_line_scalar_legend } from "./utils";
import * as mixins from "../../core/property_mixins";
import { StepMode } from "../../core/enums";
import { unreachable } from "../../core/util/assert";
export class StepView extends XYGlyphView {
static __name__ = "StepView";
async load_glglyph() {
const { StepGL } = await import("./webgl/step");
return StepGL;
}
_bounds(bounds) {
// Override to account for padding
const { pad_before, pad_after } = this.model;
return {
x0: bounds.x0 - pad_before,
x1: bounds.x1 + pad_after,
y0: bounds.y0,
y1: bounds.y1,
};
}
_paint(ctx, indices, data) {
const npoints = indices.length;
if (npoints < 2) {
return;
}
let indices_consecutive = [];
for (let i = 0; i < indices.length; i++) {
if (i == 0) {
indices_consecutive.push(indices[i]);
}
else if ((indices[i] - 1) != indices[i - 1]) {
this._paint_consecutive(ctx, indices_consecutive, data);
indices_consecutive = [indices[i]];
}
else {
indices_consecutive.push(indices[i]);
if (i + 1 == indices.length) {
this._paint_consecutive(ctx, indices_consecutive, data);
}
}
}
}
_build_step_path(indices, data) {
// Builds the step path points for the given indices, including padding
const { sx, sy } = { ...this, ...data };
const { mode, pad_before, pad_after } = this.model;
const xs = [];
const ys = [];
if (indices.length == 0) {
return { xs, ys };
}
const first_i = indices[0];
const last_i = indices[indices.length - 1];
if (pad_before != 0) {
const pad_sx = this.renderer.xscale.s_compute(this.x[first_i] - pad_before); // screen units
xs.push(pad_sx);
ys.push(sy[first_i]);
}
// Step from data points
for (let k = 0; k < indices.length; k++) {
const i = indices[k];
if (!isFinite(sx[i] + sy[i])) {
xs.push(NaN);
ys.push(NaN);
continue;
}
const has_next = k < indices.length - 1;
const next_i = has_next ? indices[k + 1] : -1;
const valid_next = has_next && isFinite(sx[next_i] + sy[next_i]);
switch (mode) {
case "before":
/* First adds vertical line
For each point: horizontal+vertical (┐ or ┘)
Finally horizontal */
xs.push(sx[i]);
ys.push(sy[i]);
if (valid_next) {
xs.push(sx[i]);
ys.push(sy[next_i]);
}
break;
case "after":
/* First: horizontal line
Middle: vertical+horizontal (┌ or L)
Last: vertical */
xs.push(sx[i]);
ys.push(sy[i]);
if (valid_next) {
xs.push(sx[next_i]);
ys.push(sy[i]);
}
break;
case "center":
/* Each point contributes a horizontal segment from left_edge to right_edge at y=sy[i].
First: Left edge is the first x point itself.
Middle points: Left edge is halfway to previous point, right edge is halfway to next point.
Last: Right edge is the last x point itself.
This works with gaps as well by breaking the path */
const prev_i = k > 0 ? indices[k - 1] : -1;
const valid_prev = k > 0 && isFinite(sx[prev_i] + sy[prev_i]);
const left = valid_prev ? (sx[prev_i] + sx[i]) / 2 : sx[i];
const right = valid_next ? (sx[i] + sx[next_i]) / 2 : sx[i];
xs.push(left);
ys.push(sy[i]);
xs.push(right);
ys.push(sy[i]);
break;
default:
unreachable();
}
}
if (pad_after != 0) {
const pad_sx = this.renderer.xscale.s_compute(this.x[last_i] + pad_after); // screen units
xs.push(pad_sx);
ys.push(sy[last_i]);
}
return { xs, ys };
}
_paint_consecutive(ctx, indices, data) {
this.visuals.line.set_value(ctx);
const { xs, ys } = this._build_step_path(indices, data);
let drawing = false;
for (let i = 0; i < xs.length; i++) {
drawing = this._render_xy(ctx, drawing, xs[i], ys[i]);
}
if (drawing) {
ctx.stroke();
}
}
_render_xy(ctx, drawing, x, y) {
if (isFinite(x + y)) {
if (drawing) {
ctx.lineTo(x, y);
}
else {
ctx.beginPath();
ctx.moveTo(x, y);
drawing = true;
}
}
else if (drawing) {
ctx.stroke();
drawing = false;
}
return drawing;
}
draw_legend_for_index(ctx, bbox, _index) {
generic_line_scalar_legend(this.visuals, ctx, bbox);
}
}
export class Step extends XYGlyph {
static __name__ = "Step";
constructor(attrs) {
super(attrs);
}
static {
this.prototype.default_view = StepView;
this.mixins(mixins.LineScalar);
this.define(({ Float, NonNegative }) => ({
mode: [StepMode, "before"],
pad_before: [NonNegative(Float), 0],
pad_after: [NonNegative(Float), 0],
}));
}
}
//# sourceMappingURL=step.js.map