How do I use custom font in Holoviews?

I would like to change the font of my holoviews plots

  1. I would like to change my font to a “known” font like Verdana. How would I do that?
  2. I would like to use the custom font-family:OrstedSansRegular, sans;. This is what is used at in my company. I have the source files

image

Let’s take an example

import pandas as pd
import hvplot.pandas
data = {"x": [1,2], "y": [2,3]}
dataframe = pd.DataFrame(data)
dataframe.hvplot(x="x", y="y")

I would like to change the font for all text. How do I do that?

In the end I would deploy this in a Panel application.

I can see that I can change the font via a custom Template.

In the example below I change to Comic Sans MS.

from bokeh.themes.theme import Theme
theme = Theme(
    json={
    'attrs' : {
        'Figure' : {
            'background_fill_color': ORSTED_TEXT_DIGITAL,
            'border_fill_color': ORSTED_TEXT_DIGITAL,
            'outline_line_color': ORSTED_TEXT_DIGITAL,
        },
        'Grid': {
            'grid_line_dash': [6, 4],
            'grid_line_alpha': .3,
        },
        'Text':
            {
                'text_font': 'Courier',
            },
        'Axis': {
            "major_label_text_font": "Comic Sans MS",
            "axis_label_text_font": "Comic Sans MS",
            'major_label_text_color': ORSTED_WHITE,
            'axis_label_text_color': ORSTED_WHITE,
            'major_tick_line_color': ORSTED_WHITE,
            'minor_tick_line_color': ORSTED_WHITE,
            'axis_line_color': ORSTED_WHITE,
        }
    }
})
hv.renderer('bokeh').theme = theme

I’ve tried in a notebook to change the font to a custom font using CSS. But I cannot get the holoviews plot to use it. Panel uses it nicely though.

from bokeh.themes.theme import Theme
import hvplot.pandas
import holoviews as hv
theme = Theme(
    json={
    'attrs' : {
        'Figure' : {
            'background_fill_color': "rgb(59,73,86)",
            'border_fill_color': "rgb(59,73,86)",
            'outline_line_color': "rgb(59,73,86)",
        },
        'Grid': {
            'grid_line_dash': [6, 4],
            'grid_line_alpha': .3,
        },
        'Text':
            {
                'text_font': 'OrstedSansRegular',
                'text_color': "OrstedSansRegular"
            },
        'Axis': {
            "major_label_text_font": "OrstedSansRegular",
            "axis_label_text_font": "OrstedSansRegular",
            'major_label_text_color': "white",
            'axis_label_text_color': "white",
            'major_tick_line_color': "white",
            'minor_tick_line_color': "white",
            'axis_line_color': "white",
        }
    }
})
hv.renderer('bokeh').theme = theme

import pandas as pd
import hvplot.pandas
data = {"x": [1,2], "y": [2,3]}
dataframe = pd.DataFrame(data)
dataframe.hvplot(x="x", y="y")

CSS = """
@font-face {
  font-family: "OrstedSansRegular";
  src: url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Regular.eot");
  /* IE9 Compat Modes */
  src: url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Regular.eot?#iefix") format("embedded-opentype"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Regular.woff") format("woff"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Regular.ttf") format("truetype"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Regular.svg#52bc9fd504b621176d57f308965c98a4") format("svg");
  /* Legacy iOS */
  font-style: normal;
  font-weight: 400;
}
"""
style=f"""
<style>
{CSS}
div.orsted {{
    font-family: OrstedSansRegular
}}
</style>
<div class="orsted">Orsted</div>
"""
import panel
panel.Column(panel.pane.HTML(style), panel.Pane(dataframe.hvplot(x="x", y="y").options(title="Orsted"))).show()

And here is an extended version that does not work either. But i’ve included all the italic fonts etc. And in the title you can see how numbers look according to the Orsted font.

from bokeh.themes.theme import Theme
import hvplot.pandas
import holoviews as hv
theme = Theme(
    json={
    'attrs' : {
        'Figure' : {
            'background_fill_color': "rgb(59,73,86)",
            'border_fill_color': "rgb(59,73,86)",
            'outline_line_color': "rgb(59,73,86)",
        },
        'Grid': {
            'grid_line_dash': [6, 4],
            'grid_line_alpha': .3,
        },
        'Text':
            {
                'text_font': 'OrstedSansRegular',
                'text_color': "OrstedSansRegular"
            },
        'Axis': {
            "major_label_text_font": "OrstedSansRegular",
            "axis_label_text_font": "OrstedSansRegular",
            'major_label_text_color': "white",
            'axis_label_text_color': "white",
            'major_tick_line_color': "white",
            'minor_tick_line_color': "white",
            'axis_line_color': "white",
        }
    }
})
hv.renderer('bokeh').theme = theme

import pandas as pd
import hvplot.pandas
data = {"Orsted": [1,2], "y": [2,3]}
dataframe = pd.DataFrame(data)
plot=dataframe.hvplot(x="Orsted", y="y").options(title="Orsted")

CSS = """
@font-face {
  font-family: "OrstedSansRegular";
  src: url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Regular.eot");
  /* IE9 Compat Modes */
  src: url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Regular.eot?#iefix") format("embedded-opentype"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Regular.woff") format("woff"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Regular.ttf") format("truetype"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Regular.svg#52bc9fd504b621176d57f308965c98a4") format("svg");
  /* Legacy iOS */
  font-style: normal;
  font-weight: 400;
}
@font-face {
  font-family: "OrstedSansLight";
  src: url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Light.eot");
  /* IE9 Compat Modes */
  src: url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Light.eot?#iefix") format("embedded-opentype"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Light.woff") format("woff"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Light.ttf") format("truetype"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Light.svg#52bc9fd504b621176d57f308965c98a4") format("svg");
  /* Legacy iOS */
  font-style: normal;
  font-weight: 200;
}
@font-face {
  font-family: "OrstedSansBold";
  src: url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Bold.eot");
  /* IE9 Compat Modes */
  src: url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Bold.eot?#iefix") format("embedded-opentype"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Bold.woff") format("woff"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Bold.ttf") format("truetype"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Bold.svg#52bc9fd504b621176d57f308965c98a4") format("svg");
  /* Legacy iOS */
  font-style: normal;
  font-weight: 800;
}
@font-face {
  font-family: "OrstedSansBlack";
  src: url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Black.eot");
  /* IE9 Compat Modes */
  src: url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Black.eot?#iefix") format("embedded-opentype"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Black.woff") format("woff"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Black.ttf") format("truetype"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Black.svg#52bc9fd504b621176d57f308965c98a4") format("svg");
  /* Legacy iOS */
  font-style: normal;
  font-weight: 900;
}
@font-face {
  font-family: "OrstedSansItalic";
  src: url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Italic.eot");
  /* IE9 Compat Modes */
  src: url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Italic.eot?#iefix") format("embedded-opentype"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Italic.woff") format("woff"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Italic.ttf") format("truetype"), url("https://orstedcdn.azureedge.net/Assets_/dist/fonts/OrstedSans-Italic.svg#52bc9fd504b621176d57f308965c98a4") format("svg");
  /* Legacy iOS */
  font-style: italic;
  font-weight: 400;
}
"""
style=f"""
<style>
{CSS}
div.orsted {{
    font-family: OrstedSansRegular
}}
</style>
<div class="orsted">Orsted 1.2 1.4 1.6</div>
"""
import panel
panel.config.sizing_mode="stretch_width"
panel.Column(panel.pane.HTML(style), panel.Pane(plot)).show()

Sorry to hijack this thread, but how do you include another font in the first place? I.e. how do you discover font files/fonts installed on the computer and specify using them in holoviews?

Well i believe its about fonts available in the browser. Not nescessarily about installed fonts.

Hello, I’m very interested to see this topic revived (and that we solve this together :)). Do you experience too that by refreshing the webapp page then your holoviews plot renders with the right font?

To make things a bit clearer I provoke a redraw with a button push in the following hopefully reproducible code.

import panel as pn
import holoviews as hv
import param
from bokeh.themes.theme import Theme

theme = {
        'attrs' : {
            'Axis': {
            'major_label_text_color': 'green',
            'major_label_text_font' : 'scriptlook',
            'major_label_text_font_size': '18pt',
            'axis_label_text_color': 'red',
            'axis_label_text_font': 'scriptlook'
            },
            'Title': {
                'text_font': 'scriptlook',
                'text_color': 'blue',
                'text_alpha' : 1.0
            }
        }
}

hv.renderer('bokeh').theme = Theme(json=theme)

css = '''
@font-face {
 font-family: "scriptlook";
 src: url("http://fonts.gstatic.com/s/architectsdaughter/v11/KtkxAKiDZI_td1Lkx62xHZHDtgO_Y-bvfY5q4szgE-Q.ttf");
}

.bk.bk-btn {
    background-color: lightblue;
    font-family : "scriptlook"
}'''
        

pn.extension(raw_css=[css])


class visufont(param.Parameterized):

    action = param.Action(lambda x: x.param.trigger('action'), label='Click here!')
        
    def __init__(self, **params):
        super().__init__(**params)
        h = hv.Scatter([(0,0),(1,1)])
        h = h.opts(title='Scatter',fontsize=20,width=400,height=300,toolbar=None)
        self.view = pn.pane.HoloViews(h) 
                    
    @param.depends('action',watch=True)
    def view2(self):
        self.view.object = self.view.object
            
vf = visufont()
pn.Row(vf.param,vf.view).show() 

What we experience here is that with the first rendering, the css style applies since the colors of the letters are right. The font is right in the button, and the font is not right in the holoviews pane.
By clicking the button, it rerenders and now the font is correct (same by refreshing the page).

Is there a way to provoke this redraw? Is there a function like __init__
to put stuff that needs to happen only when all static resources are available?

I finally found a way by using the add_periodic_callback method. Setting period=10 and count=20 seems ok to minimize (but not avoid) the flash when the figure finally updates and to leave enough time for the font to be ready to be used.

vf = visufont()
v = pn.Row(vf.param,vf.view)
v.add_periodic_callback(vf.view2,period=10,count=20)
v.show()

Warning, that syntax evolves with panel 0.10 and will be pn.state.add_periodic_callback(...).

Questions : is there a way to trigger a callback when the static resources are available for use? Is there a way to avoid the FOUC (flash of unstyled content)?

Hi @marcbernot.

I’ve also tried loading a custom font without problems.

But maybe you could look into non-Panel techniques for not rendering before the fonts have loaded. Like for example https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content and http://www.bramstein.com/writing/preload-hints-for-web-fonts.html?