Adding text to a bokeh plot with a background fill

Viewed 468

I currently have a bokeh.models.glyphs.Text to add some text to a plot. Now I want to fill the background of the text label, but bokeh.models.glyphs.Text had no background_fill argument.

Is there maybe an alternative way to add text with a background fill to a bokeh plot?

2 Answers

You cannot do it with the Text glyph itself. But there are alternatives:

  • Draw a rectangle of the required color right under the text. Very easy to implement but hard to measure the text so that all of the rectangles are always of the required width
  • Create a custom Text model that draws the rectangle for you. A bit harder to implement because it requires some JavaScript/TypeScript, but it will be able to measure the text automatically

here's a simple example that is not exactly a text-box, but it's a list of lines using the LabelSet() object. The LabelSet() is a way of placing labels at multiple locations within a figure window. It has better support generally including:

  • background colors
  • source= field for ColumnDataSource
  • either data or screen units

If you put the LabelSet locations close enough, with the right sized font it'll look like a text box.

This is an example with a button to update the text with the current date every time it's pressed:

import numpy as np
from datetime import datetime

from bokeh.layouts import layout
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource, Button, LabelSet
from bokeh.plotting import figure


nLines=4 # max number of lines
text_str = ['{0}'.format(line) for line in range(nLines)] # init with empty strings


# create some other data coordinates for a figure
xData=np.arange(-3,+12,0.5)
yData=xData
plot = figure(title="Text placement example, in screen units", 
              height=300, sizing_mode='scale_width',
              x_range=(min(xData), max(xData)), y_range=(min(yData), max(yData)),
              x_axis_label="my X-axis", y_axis_label="my Y-axis")





# ------------ text box with a LabelSet() object ----------------
x_scr_lo=20; x_scr_hi=67
label_data = ColumnDataSource(data=dict(
       x=[x_scr_lo]*nLines,
       y=np.linspace(x_scr_lo,x_scr_hi,nLines),
       text=text_str))
plot_label = LabelSet(x='x', y='y', text='text',
              text_font_size='11pt',
              background_fill_color='lightskyblue',
              x_units='screen', y_units='screen',
              background_fill_alpha=0.2,
              text_color='blue', source=label_data)
plot.add_layout(plot_label)


def bt_add_lines_callback():
    new_text=label_data.data['text'] # grab current string list
    new_text.pop() # drop the oldest (last)
    new_text.insert(0,    'update {0}'.format( datetime.now().strftime('%c') )    )
    label_data.data=dict(x=[20]*nLines,
    y=np.linspace(x_scr_lo,x_scr_hi,nLines), text=new_text)

bt_add_lines = Button(label="Update next line" ,
               button_type="success", width=200)
bt_add_lines.on_click(bt_add_lines_callback)

## arrange all map views in a grid layout then add to the document
layout = layout([
    [bt_add_lines],
    [plot] ], sizing_mode='stretch_width')

doc = curdoc()
doc.add_root(layout)
Related