Template-->Theme-->text_font. Putting it all together

What I’m trying to do:

Apply a theme that harnesses a stylesheet from the internet to retrieve custom fonts.

This very closely follows this github issue: [BUG] Google Fonts not loading on Glyph on standalone HTML until interacting with Glyph · Issue #9448 · bokeh/bokeh · GitHub but combined with the standalone embed example here: bokeh/embed_themed.py at branch-3.0 · bokeh/bokeh · GitHub

Except I’m running into one additional hurdle.

Sandbox code below:

from jinja2 import Template
from bokeh.embed import components
from bokeh.plotting import figure
from bokeh.resources import INLINE, CDN
from bokeh.plotting import figure, save
from bokeh.layouts import layout
from bokeh.models import ColumnDataSource, Scatter
from bokeh.models import Label, LabelSet
from bokeh.themes import Theme

                             
f = figure()
d = {'NAME':['Banana','Orange','Apple'],'x':[0,1,2],'y':[5,2,3]}
lblsrc = ColumnDataSource(data=d)
lbls = LabelSet(x='x',y='y',text='NAME',source= lblsrc)
# lbls.text_font = 'Uncial Antiqua'
f.add_layout(lbls)
sc = Scatter(x='x',y='y')
scr = f.add_glyph(lblsrc,sc)

lo = layout([f])

theme = Theme(json={
    'attrs': {
        'LabelSet': {
            'text_font': 'Unicial Antiqua',
            },
        }
    })

script, div = components(lo,theme=theme)

#NOTE the link to the google font in the template
template = Template('''<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Basemap</title>
        <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Uncial+Antiqua:400" />
        {{ resources }}
        {{ script }}
    </head>
    <body>
        {{ div }}
    </body>
</html>
''')

resources = CDN.render()

filename = r'chk.html'

html = template.render(resources=resources,
                       script=script,
                       div=div)

with open(filename, mode="w", encoding="utf-8") as f:
    f.write(html)

What’s happening:
-Font is being rendered in times new roman, inspection of javascript console says:
image
So it’s not finding the font.

Now what’s kinda funny here is if I inspect the html shown in github example linked above and find the specifier for text_font, it looks like this:

image

Now if I go looking in my html for the same thing, it actually points to an object instead of a string:

image

If I replace that object directly in the html with “Uncial Antiqua”:

image

I am able to reproduce the github issue:

test

My questions…

  1. What is the {“field”:“Uncial Antiqua”} business all about? Do I need to write a subroutine here to search and replace {“field”:“Uncial Antiqua”} with “Uncial Antiqua” in the html string or is there a better way? There’s obviously another layer to this that I’m missing in my understanding.

  2. I’ve read the docs on the json_item or embed_item API etc as mentioned by @Bryan in the github thread and would really appreciate a flesh-out of how it might be used here as an alternative?

As always any help/advice much appreciated.

I think I’ve answered 1) for myself → it’s because i’m using a CDS and a LabelSet (i.e. something that takes an array of values). It thinks that I’m pointing it to a key in the CDS to retrieve text_font on a per label basis.
If I create a field in the CDS to store font:

d = {'NAME':['Banana','Orange','Apple'],'x':[0,1,2],'y':[5,2,3],'f':['Helvetica','Uncial Antiqua','Uncial Antiqua']}

And point to ‘f’ in the Theme:

theme = Theme(json={
    'attrs': {
        'LabelSet': {
            'text_font': 'f',
            },
        }
    })

I now get essentially the same thing as the github issue, but now I’m able to control each label’s font individually. This is quite powerful, I think as it means I can really expand out my LabelSet to hold a wide variety of label formats! Neat.

Still in the same position as the “have to move the canvas to load the font” as outlined in the github issue, and of course this also tells me that I can’t assign a singular text_font property to the LabelSet glyph… i.e. If I have a LabelSet being driven by a CDS I HAVE to control the text_font property by creating a field in the CDS to control each label individually… which in some cases might not be ideal from a data efficiency POV (e.g. storing an array of 100 ‘arial’ s)… but yeah overall I think I understand what I was missing here.

It’s always been absolute voodoo to me how bokeh/the devs allow for me to feed in string args to a glyph instance and seemingly it’ll magically recognize whether that string is pointing to a field in the CDS or it’s a singular value…

I guess this is one use case where that awesomeness hasn’t been implemented yet?

Any ideas/elaboration on 2) still much appreciated :slight_smile:

Some properties in Bokeh can accept either a single value (x=10) or a reference to a column of values in a CDS (x="distance"). These are called “DataSpecs”. Under the covers, things are always denoted explicitly:

x = dict(value=10)          # single value
x = dict(field="distance")  # reference to a column

Where possible, we try to shield users from having to care about this. If a given DataSpec property can only use numeric values, then it is easy Bokeh us to assume: numbers are values, strings are fields. Where things get trickier is if values can also be strings. In some cases, we can still auto-magically make good assumptions, e.g. a ColorSpec property will assume strings that are valid color values are value and otherwise assume they are field.

But, it’s always possible to be explicit and pass dict(value=...) or dict(field=...) explicity, and in some cases (like above) where Bokeh can’t make the right assumption, it may be necessary for users to be explicit.

Note there is actual API for this now:

x = value(10)          # single value
x = field("distance")  # reference to a column

and that is slightly preferable for users to use.

I’m not quite sure I understand question 2, the use of a theme and a given embedding method seem like completely orthogonal considerations to me.

Awesome thanks so much for the explanation. It’s absolutely ideal to “not have to care about this” but still to be able to control it when I want/need to. Passing the dict(value=…) or dict(field=…) accomplishes the later. Sweet.

Yeah re 2 I was just wondering how the json_item API fits into the theme/template thing and if there was an alternate approach I’m missing.

Thanks again!

I don’t think so, json_item is another way to save off and embed Bokeh content, and it should respect any theme that is set when it is called.