/*protected*/ _get_label_extent(): number { const major_labels = this.tick_info().labels.major let label_extent: number if (this.model.color_mapper.low != null && this.model.color_mapper.high != null && !isEmpty(major_labels)) { const {ctx} = this.plot_view.canvas_view ctx.save() this.visuals.major_label_text.set_value(ctx) switch (this.model.orientation) { case "vertical": label_extent = max((major_labels.map((label) => ctx.measureText(label.toString()).width))) break case "horizontal": label_extent = text_util.measure_font(this.visuals.major_label_text.font_value()).height break default: throw new Error("unreachable code") } label_extent += this.model.label_standoff ctx.restore() } else label_extent = 0 return label_extent }
compute_legend_bbox(): BBox { const legend_names = this.model.get_legend_names() const {glyph_height, glyph_width} = this.model const {label_height, label_width} = this.model this.max_label_height = max( [measure_font(this.visuals.label_text.font_value()).height, label_height, glyph_height], ) // this is to measure text properties const { ctx } = this.plot_view.canvas_view ctx.save() this.visuals.label_text.set_value(ctx) this.text_widths = {} for (const name of legend_names) { this.text_widths[name] = max([ctx.measureText(name).width, label_width]) } this.visuals.title_text.set_value(ctx) this.title_height = this.model.title ? measure_font(this.visuals.title_text.font_value()).height + this.model.title_standoff : 0 this.title_width = this.model.title ? ctx.measureText(this.model.title).width : 0 ctx.restore() const max_label_width = Math.max(max(values(this.text_widths)), 0) const legend_margin = this.model.margin const {legend_padding} = this const legend_spacing = this.model.spacing const {label_standoff} = this.model let legend_height: number, legend_width: number if (this.model.orientation == "vertical") { legend_height = legend_names.length*this.max_label_height + Math.max(legend_names.length - 1, 0)*legend_spacing + 2*legend_padding + this.title_height legend_width = max([(max_label_width + glyph_width + label_standoff + 2*legend_padding), this.title_width + 2*legend_padding]) } else { let item_width = 2*legend_padding + Math.max(legend_names.length - 1, 0)*legend_spacing for (const name in this.text_widths) { const width = this.text_widths[name] item_width += max([width, label_width]) + glyph_width + label_standoff } legend_width = max([this.title_width + 2*legend_padding, item_width]) legend_height = this.max_label_height + this.title_height + 2*legend_padding } const panel = this.panel != null ? this.panel : this.plot_view.frame const [hr, vr] = panel.bbox.ranges const {location} = this.model let sx: number, sy: number if (isString(location)) { switch (location) { case 'top_left': sx = hr.start + legend_margin sy = vr.start + legend_margin break case 'top_center': sx = (hr.end + hr.start)/2 - legend_width/2 sy = vr.start + legend_margin break case 'top_right': sx = hr.end - legend_margin - legend_width sy = vr.start + legend_margin break case 'bottom_right': sx = hr.end - legend_margin - legend_width sy = vr.end - legend_margin - legend_height break case 'bottom_center': sx = (hr.end + hr.start)/2 - legend_width/2 sy = vr.end - legend_margin - legend_height break case 'bottom_left': sx = hr.start + legend_margin sy = vr.end - legend_margin - legend_height break case 'center_left': sx = hr.start + legend_margin sy = (vr.end + vr.start)/2 - legend_height/2 break case 'center': sx = (hr.end + hr.start)/2 - legend_width/2 sy = (vr.end + vr.start)/2 - legend_height/2 break case 'center_right': sx = hr.end - legend_margin - legend_width sy = (vr.end + vr.start)/2 - legend_height/2 break default: throw new Error("unreachable code") } } else if (isArray(location) && location.length == 2) { const [vx, vy] = location sx = panel.xview.compute(vx) sy = panel.yview.compute(vy) - legend_height } else throw new Error("unreachable code") return new BBox({left: sx, top: sy, width: legend_width, height: legend_height}) }
protected _render(ctx: Context2d, indices: number[], {sx, sy, _x_offset, _y_offset, _angle, _text}: TextData): void { this._sys = [] this._sxs = [] for (const i of indices) { if (isNaN(sx[i] + sy[i] + _x_offset[i] + _y_offset[i] + _angle[i]) || _text[i] == null) continue this._sxs[i] = [] this._sys[i] = [] if (this.visuals.text.doit) { const text = `${_text[i]}` ctx.save() ctx.translate(sx[i] + _x_offset[i], sy[i] + _y_offset[i]) ctx.rotate(_angle[i]) this.visuals.text.set_vectorize(ctx, i) const font = this.visuals.text.cache_select("font", i) const {height} = measure_font(font) const line_height = this.visuals.text.text_line_height.value()*height if (text.indexOf("\n") == -1){ ctx.fillText(text, 0, 0) const x0 = sx[i] + _x_offset[i] const y0 = sy[i] + _y_offset[i] const width = ctx.measureText(text).width const [xvalues, yvalues] = this._text_bounds(x0, y0, width, line_height) this._sxs[i].push(xvalues) this._sys[i].push(yvalues) } else { const lines = text.split("\n") const block_height = line_height*lines.length const baseline = this.visuals.text.cache_select("text_baseline", i) let y: number switch (baseline) { case "top": { y = 0 break } case "middle": { y = (-block_height/2) + (line_height/2) break } case "bottom": { y = -block_height + line_height break } default: { y = 0 console.warn(`'${baseline}' baseline not supported with multi line text`) } } for (const line of lines) { ctx.fillText(line, 0, y) const x0 = sx[i] + _x_offset[i] const y0 = y + sy[i] + _y_offset[i] const width = ctx.measureText(line).width const [xvalues, yvalues] = this._text_bounds(x0, y0, width, line_height) this._sxs[i].push(xvalues) this._sys[i].push(yvalues) y += line_height } } ctx.restore() } } }
_title_extent(): number { const font_value = this.model.title_text_font + " " + this.model.title_text_font_size + " " + this.model.title_text_font_style const title_extent = this.model.title ? text_util.measure_font(font_value).height + this.model.title_standoff : 0 return title_extent }