Export png with background imge

We have been running some code to produce animated charts with a background image. That was working, but yesterday the same code started producing CORS errors.

Striping down the code to try to provide a minimal example, the problem seems to lie with export png. I am not sure this is related to the CORS error, but when I try this code with bokeh serve, the chart with background appears in the browser, but the png shows just the line, and no background.

import pandas as pd
from bokeh.models import ColumnDataSource, Range1d, Span, NumeralTickFormatter, ImageURL
from bokeh.io import curdoc, export_png
from bokeh.plotting import figure
from bokeh.layouts import column
import numpy as np
from bokeh.themes import Theme

theme_path = "theme.yml"
dates = pd.date_range("20210101", periods=6)
df = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=list("ABCD"))
df.rename_axis('Date', axis=0, inplace=True)
source = ColumnDataSource(df)

url = 'https://www.eastasiaecon.com/content/images/size/w2400/2023/04/Image-29-4-2023-at-7.34-PM.jpeg'

p = figure(aspect_ratio=16/9,
           x_range=Range1d(start=dates[0], end=dates[-1]), x_axis_type='datetime')

p.extra_y_ranges = {"secondary": Range1d(start=0, end=1)}

p.image_url(url=[url], x=dates[0], y=0, w=dates[-1] - dates[0], h=1, anchor="bottom_left", alpha=0.2, level='underlay', y_range_name="secondary")

p.line(x='Date', y='A', source=source, color='green', level='overlay')

export_png(p, filename=f"test.png",width=p.width, height=p.height)

layout = column(p)

curdoc().add_root(layout)
curdoc().theme = Theme(filename=theme_path)

The One issue is that the Bokeh app is at an http address, but the image URL is at an https address. Browser security policies will not allow embedding secure content into insecure pages, even including images that render into HTML canvases. Please note: we have absolutely no control over these policies, and cannot override them. This combination will not and cannot work, full stop. You have two options:

  • Configure the Bokeh app to use SSL and serve at an https address
  • Load the image from an http address, not an https address

Changing the image URL to be http causes the image to show up in the saved PNG for me. Note that the CORS error does still show up in the browser console. In order for that to go away, I think the image server ("eastasia.com") would have to start setting the appropriate CORS headers in its responses. Again, nothing we have any control over.

This is actually pretty weird. The eastasia.com server is set to redirect HTTP to HTTPS, so if I navigate to

http://www.eastasiaecon.com/content/images/size/w2400/2023/04/Image-29-4-2023-at-7.34-PM.jpeg

then I will actually land at

httpS://www.eastasiaecon.com/content/images/size/w2400/2023/04/Image-29-4-2023-at-7.34-PM.jpeg

so I think that is why the CORS error would still show up in the console. What I don’t understand, then, is why the image was ever able to load at all at any point. TBH that almost seems like a browser bug. In any case, given this redirect I think your best best is to configure the Bokeh app for SSL.

https://docs.bokeh.org/en/latest/docs/user_guide/server/deploy.html#ssl-termination

This is very helpful, thanks @Bryan.

Before you replied, I was trying to save the image in a ‘static’ folder in an app folder structure. I thought that would overcome the website restrictions, but this also fails to load the image, and produces the CORS error, “[bokeh]” “attempting to load /static/logo.png without a cross origin policy”.

import pandas as pd

from bokeh.models import ColumnDataSource, Range1d, Span, NumeralTickFormatter, ImageURL

from bokeh.io import curdoc, export_png

from bokeh.plotting import figure

from bokeh.layouts import column

import numpy as np

from bokeh.themes import Theme

theme_path = "theme.yml"

url = '/static/logo.png'

dates = pd.date_range("20210101", periods=6)

df = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=list("ABCD"))

df.rename_axis('Date', axis=0, inplace=True)

source = ColumnDataSource(df)

#url = 'https://www.eastasiaecon.com/content/images/size/w2400/2023/04/Image-29-4-2023-at-7.34-PM.jpeg'

p = figure(aspect_ratio=16/9,

x_range=Range1d(start=dates[0], end=dates[-1]), x_axis_type='datetime')

p.extra_y_ranges = {"secondary": Range1d(start=0, end=1)}

p.image_url(url=[url], x=dates[0], y=0, w=dates[-1] - dates[0], h=1, anchor="bottom_left", alpha=0.2, level='underlay', y_range_name="secondary")

p.line(x='Date', y='A', source=source, color='green', level='overlay')

export_png(p, filename=f"test.png",width=p.width, height=p.height)

layout = column(p)

curdoc().add_root(layout)

curdoc().theme = Theme(filename=theme_path)

@paul_c I am afraid I’m not sure. Maybe browsers have gotta more strict about something CORS related by default recently. Trying an SSL configuration is still my real suggestion.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.