Text formatting is lost while exporting as .png

Hello Bokeh community!
The export png function appears to lose most the formatting thats done to the text in a plot while exporting it to png. Not sure what is the issue here.

Here’s a sample code which produces intentionally formatted plots but doesn’t retain upon export. I have tried exporting it to a mounted drive location and also to run time. Beither seems to work.
Environment: Google Colab

from collections import OrderedDict
from io import StringIO
from math import log, sqrt
from bokeh.io import curdoc, output_notebook
import numpy as np
import pandas as pd
from bokeh.plotting import figure, output_file, show
from bokeh.models import ColumnDataSource, Label, LabelSet, Range1d

x = [1,2,3,4,5,6,7,8,9,10,12,15,20,25,30]
y = [256,254,253,253,254,258,262,265,269,272,276,281,289.0,297.0,305.0]
source = ColumnDataSource(data=dict(tenor=[1,2,3,4,5,6,7,8,9,10,12,15,20,25,30],
p = figure(plot_width=900, plot_height=200,x_range=(0.5, 30.5), y_range=(230, 320))
# add both a line and circles on the same plot
p.line(x, y, color="#571F01",line_width=2)
p.circle(x, y, fill_color="#FFECBD", size=8)
p.xaxis[0].axis_label = 'Tenor(Y)'
p.yaxis[0].axis_label = 'bps'
p.xaxis.axis_label_text_font = 'Georgia'
labels = LabelSet(x='tenor', y='bps', text='bps',
                  text_font_size="12px",text_font = "Calibri",text_color ="#571F01" ,
                  level='glyph', x_offset=-10, y_offset=5, source=source, render_mode='canvas')
p.xaxis.major_tick_line_color = None  # turn off x-axis major ticks
p.xaxis.minor_tick_line_color = None  # turn off x-axis minor ticks
p.xaxis.major_label_text_font = 'Georgia'
p.yaxis.major_tick_line_color = None  # turn off y-axis major ticks
p.yaxis.minor_tick_line_color = None  # turn off y-axis minor ticks
p.xaxis.major_label_text_color = None  # turn off x-axis tick labels leaving space
p.yaxis.major_label_text_color = None  # turn off y-axis tick labels leaving space 
#p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = None
p.yaxis.visible = False
p.yaxis.axis_label = 'bps'
p.varea(source=source, x='tenor', y1=230, y2="bps", alpha=0.2, fill_color='#FFECBD', )
export_png(p, filename="plot.png")

Can you try this outside colab with the latest version of Bokeh? You will need chromedriver or geckodriver as phantomjs is no longer supported.

Tried with Jupyter Notebooks. It works! I just had to install selenium. Didnt need chromedriver or geckodriver
Is PhantomJS the culprit again? Would you happen to know any workarounds for Colab?

If you were using a latest Bokeh then you were using either chromedriver or geckodriver (one of them must have been installed) since Bokeh 2.x does not support PhantomJS anymore. My guess is that phantomJS is just broken and there is nothing that can be done about that (all work on it has ceased). You will need to update the Bokeh version you are using on colab as well as make sure selenium and one of the supported webdrivers is installed. I don’t know enough about colab to advise on that part.

Thanks! I uninstalled old Bokeh and reinstalled new version.

!bokeh info

returns this

Python version      :  3.6.9 (default, Apr 18 2020, 01:56:04) 
IPython version     :  5.5.0
Tornado version     :  6.0.4
Bokeh version       :  2.0.2
BokehJS static path :  /usr/local/lib/python3.6/dist-packages/bokeh/server/static
node.js version     :  (not installed)
npm version         :  (not installed)

But strangely when I run the above code (which was working in previous version) is erroring out at

show( p )

TypeError: Object of type 'Undefined' is not JSON serializable

The above code works just fine for me with the same Bokeh version. Do you have a stacktrace of that error?

Sure. Here you go.

/usr/local/lib/python3.6/dist-packages/bokeh/io/showing.py in show(obj, browser, new, notebook_handle, notebook_url, **kw)
    143 #-----------------------------------------------------------------------------
--> 145 #-----------------------------------------------------------------------------
    146 # Private API
    147 #-----------------------------------------------------------------------------

/usr/local/lib/python3.6/dist-packages/bokeh/io/showing.py in _show_with_state(obj, state, browser, new, notebook_handle)
    177         _show_file_with_state(obj, state, new, controller)
--> 179     return comms_handle
    181 #-----------------------------------------------------------------------------

/usr/local/lib/python3.6/dist-packages/bokeh/io/notebook.py in run_notebook_hook(notebook_type, action, *args, **kw)
    300 #-----------------------------------------------------------------------------
    301 # Dev API
--> 302 #-----------------------------------------------------------------------------
    304 def destroy_server(server_id):

/usr/local/lib/python3.6/dist-packages/google/colab/_import_hooks/_bokeh.py in _show_doc(obj, state, notebook_handle)
     80     IPython.get_ipython().events.register('post_run_cell', _post_execute)  # pylint: disable=undefined-variable
     81     _bokeh_io_module.notebook.load_notebook(
---> 82         resources=_bokeh_resources, hide_banner=True)
     84   # Call the default bokeh rendering path.

/usr/local/lib/python3.6/dist-packages/bokeh/io/notebook.py in load_notebook(resources, verbose, hide_banner, load_timeout)
    414     if not hide_banner:
--> 415         publish_display_data({'text/html': html})
    416     publish_display_data({
    417         JS_MIME_TYPE   : nb_js,

/usr/local/lib/python3.6/dist-packages/bokeh/io/notebook.py in _loading_js(bundle, element_id, load_timeout, register_mime)
    544 def _origin_url(url):
--> 545     '''
    547     '''

/usr/local/lib/python3.6/dist-packages/jinja2/environment.py in render(self, *args, **kwargs)
   1088             return concat(self.root_render_func(self.new_context(vars)))
   1089         except Exception:
-> 1090             self.environment.handle_exception()
   1092     def render_async(self, *args, **kwargs):

/usr/local/lib/python3.6/dist-packages/jinja2/environment.py in handle_exception(self, source)
    830         from .debug import rewrite_traceback_stack
--> 832         reraise(*rewrite_traceback_stack(source=source))
    834     def join_path(self, template, parent):

/usr/local/lib/python3.6/dist-packages/jinja2/_compat.py in reraise(tp, value, tb)
     26     def reraise(tp, value, tb=None):
     27         if value.__traceback__ is not tb:
---> 28             raise value.with_traceback(tb)
     29         raise value

/usr/local/lib/python3.6/dist-packages/bokeh/core/_templates/autoload_nb_js.js in top-level template code()
----> 1 {% extends "autoload_js.js" %}
      3 {% block register_mimetype %}
      5   {%- if register_mime -%}

/usr/local/lib/python3.6/dist-packages/bokeh/core/_templates/autoload_js.js in top-level template code()
     92     }
---> 94     const hashes = {{ bundle.hashes|json }};
     96     for (var i = 0; i < js_urls.length; i++) {

/usr/local/lib/python3.6/dist-packages/bokeh/core/templates.py in <lambda>(obj)
     87 _env.filters['json'] = lambda obj: Markup(json.dumps(obj))
---> 89 #-----------------------------------------------------------------------------
     90 # General API
     91 #-----------------------------------------------------------------------------

/usr/lib/python3.6/json/__init__.py in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
    229         cls is None and indent is None and separators is None and
    230         default is None and not sort_keys and not kw):
--> 231         return _default_encoder.encode(obj)
    232     if cls is None:
    233         cls = JSONEncoder

/usr/lib/python3.6/json/encoder.py in encode(self, o)
    197         # exceptions aren't as detailed.  The list call should be roughly
    198         # equivalent to the PySequence_Fast that ''.join() would do.
--> 199         chunks = self.iterencode(o, _one_shot=True)
    200         if not isinstance(chunks, (list, tuple)):
    201             chunks = list(chunks)

/usr/lib/python3.6/json/encoder.py in iterencode(self, o, _one_shot)
    255                 self.key_separator, self.item_separator, self.sort_keys,
    256                 self.skipkeys, _one_shot)
--> 257         return _iterencode(o, 0)
    259 def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,

/usr/lib/python3.6/json/encoder.py in default(self, o)
    178         """
    179         raise TypeError("Object of type '%s' is not JSON serializable" %
--> 180                         o.__class__.__name__)
    182     def encode(self, o):

TypeError: Object of type 'Undefined' is not JSON serializable

And apologies if I am missing something trivial here. A finance engineer at work. not a python/Bokeh expert (yet!). :slight_smile:

Uhmm… I have no idea how you managed to get that stacktrace. Half of the reported lines are just comments. It seems like your Python installation is messed up beyond repair.

It also seems to me that you’re using Python that you have installed yourself using root privileges. It’s not a the best way - it’s very easy to accidentally break something else (or if you somehow install a malicious package). You may find it more convenient to work with a user-based Python install. I use both https://github.com/pyenv/pyenv and https://docs.conda.io/en/latest/, tend to use Conda more often.