UNPKG

@quartic/bokehjs

Version:

Interactive, novel data visualization

175 lines (138 loc) 5.45 kB
import {TextAnnotation, TextAnnotationView} from "./text_annotation" import {ColumnDataSource} from "../sources/column_data_source" import {div, show, hide} from "core/dom" import * as p from "core/properties" import {isString, isArray} from "core/util/types" export class LabelSetView extends TextAnnotationView initialize: (options) -> super(options) @xmapper = @plot_view.frame.x_mappers[@model.x_range_name] @ymapper = @plot_view.frame.y_mappers[@model.y_range_name] @set_data(@model.source) if @model.render_mode == 'css' for i in [0...@_text.length] @title_div = div({class: 'bk-annotation-child', style: {display: "none"}}) @el.appendChild(@title_div) bind_bokeh_events: () -> if @model.render_mode == 'css' # dispatch CSS update immediately @listenTo(@model, 'change', () -> @set_data(@model.source) @render()) @listenTo(@model.source, 'change', () -> @set_data(@model.source) @render()) else @listenTo(@model, 'change', () -> @set_data(@model.source) @plot_view.request_render()) @listenTo(@model.source, 'change', () -> @set_data(@model.source) @plot_view.request_render()) set_data: (source) -> super(source) @visuals.warm_cache(source) _map_data: () -> if @model.x_units == "data" vx = @xmapper.v_map_to_target(@_x) else vx = @_x.slice(0) # make deep copy to not mutate sx = @canvas.v_vx_to_sx(vx) if @model.y_units == "data" vy = @ymapper.v_map_to_target(@_y) else vy = @_y.slice(0) # make deep copy to not mutate sy = @canvas.v_vy_to_sy(vy) return [sx, sy] render: () -> if not @model.visible and @model.render_mode == 'css' hide(@el) if not @model.visible return ctx = @plot_view.canvas_view.ctx [sx, sy] = @_map_data() if @model.render_mode == 'canvas' for i in [0...@_text.length] @_v_canvas_text(ctx, i, @_text[i], sx[i] + @_x_offset[i], sy[i] - @_y_offset[i], @_angle[i]) else for i in [0...@_text.length] @_v_css_text(ctx, i, @_text[i], sx[i] + @_x_offset[i], sy[i] - @_y_offset[i], @_angle[i]) _get_size: () -> ctx = @plot_view.canvas_view.ctx @visuals.text.set_value(ctx) side = @model.panel.side if side == "above" or side == "below" height = ctx.measureText(@_text[0]).ascent return height if side == 'left' or side == 'right' width = ctx.measureText(@_text[0]).width return width _v_canvas_text: (ctx, i, text, sx, sy, angle) -> @visuals.text.set_vectorize(ctx, i) bbox_dims = @_calculate_bounding_box_dimensions(ctx, text) ctx.save() ctx.beginPath() ctx.translate(sx, sy) ctx.rotate(angle) ctx.rect(bbox_dims[0], bbox_dims[1], bbox_dims[2], bbox_dims[3]) if @visuals.background_fill.doit @visuals.background_fill.set_vectorize(ctx, i) ctx.fill() if @visuals.border_line.doit @visuals.border_line.set_vectorize(ctx, i) ctx.stroke() if @visuals.text.doit @visuals.text.set_vectorize(ctx, i) ctx.fillText(text, 0, 0) ctx.restore() _v_css_text: (ctx, i, text, sx, sy, angle) -> el = @el.childNodes[i] el.textContent = text @visuals.text.set_vectorize(ctx, i) bbox_dims = @_calculate_bounding_box_dimensions(ctx, text) # attempt to support vector-style ("8 4 8") line dashing for css mode ld = @visuals.border_line.line_dash.value() if isArray(ld) line_dash = if ld.length < 2 then "solid" else "dashed" if isString(ld) line_dash = ld @visuals.border_line.set_vectorize(ctx, i) @visuals.background_fill.set_vectorize(ctx, i) el.style.position = 'absolute' el.style.left = "#{sx + bbox_dims[0]}px" el.style.top = "#{sy + bbox_dims[1]}px" el.style.color = "#{@visuals.text.text_color.value()}" el.style.opacity = "#{@visuals.text.text_alpha.value()}" el.style.font = "#{@visuals.text.font_value()}" el.style.lineHeight = "normal" # needed to prevent ipynb css override if angle el.style.transform = "rotate(#{angle}rad)" if @visuals.background_fill.doit el.style.backgroundColor = "#{@visuals.background_fill.color_value()}" if @visuals.border_line.doit el.style.borderStyle = "#{line_dash}" el.style.borderWidth = "#{@visuals.border_line.line_width.value()}px" el.style.borderColor = "#{@visuals.border_line.color_value()}" show(el) export class LabelSet extends TextAnnotation default_view: LabelSetView type: 'Label' @mixins ['text', 'line:border_', 'fill:background_'] @define { x: [ p.NumberSpec ] y: [ p.NumberSpec ] x_units: [ p.SpatialUnits, 'data' ] y_units: [ p.SpatialUnits, 'data' ] text: [ p.StringSpec, { field: "text" } ] angle: [ p.AngleSpec, 0 ] x_offset: [ p.NumberSpec, { value: 0 } ] y_offset: [ p.NumberSpec, { value: 0 } ] source: [ p.Instance, () -> new ColumnDataSource() ] x_range_name: [ p.String, 'default' ] y_range_name: [ p.String, 'default' ] render_mode: [ p.RenderMode, 'canvas' ] } @override { background_fill_color: null border_line_color: null }