Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SVG backend doesn't respect NaN/non-finite values #10305

Closed
mosc9575 opened this issue Jul 15, 2020 · 4 comments · Fixed by #10312
Closed

SVG backend doesn't respect NaN/non-finite values #10305

mosc9575 opened this issue Jul 15, 2020 · 4 comments · Fixed by #10312

Comments

@mosc9575
Copy link
Contributor

mosc9575 commented Jul 15, 2020

software version info (bokeh, python)

Python: 3.6.10
Bokeh: 2.1.1

Description of the observed behavior

The export_svg() function creates an image where some data is missing. If the figure is shown before and after the export function the changes become obvious.

Minimal example

example_data.zip

import pandas as pd

from bokeh.plotting import figure, show, output_notebook
from bokeh.palettes import d3
from bokeh.models import BoxAnnotation, Legend, HoverTool, LinearAxis, Range1d,  Circle
from bokeh.io import export_png, export_svgs

output_notebook()

data = pd.read_hdf('example_data.h5')

p = figure(title='missing values in svg export', 
           x_axis_label='Datetime', 
           y_axis_label='Data', 
           x_axis_type="datetime", 
           plot_width=1400, 
           plot_height=500,
           active_drag='box_zoom'
        )

colorset = list(d3['Category20'][20])

p.xaxis[0].formatter.days = '%d.%m.%Y'
p.xaxis.major_label_orientation = 3.14/3

index_min = data.index.min()
index_max = data.index.max()

_min = data.min().min()
_max = data.max().max()
_d = _max - _min if 0 < _max - _min else 1

p.y_range  = Range1d(_min-_d*0.05, _max+_d*0.05)

################# COLORED AREAS ################# 
boxes = []
my_renderers = []
# draw all sundays with a light gray shade into the figure
alldays = pd.date_range(start=index_min.date(), end=index_max.date())
saturdays = alldays[alldays.dayofweek == 6]
for date in saturdays:
    start = date+pd.DateOffset(minutes=15)
    end = date+pd.DateOffset(days=1)
    boxes.append(BoxAnnotation(left=start, right=end, fill_alpha=0.1, fill_color='grey'))
p.renderers.extend(boxes)

########## DATA HANDLING ################
names = data.columns
for i, name in enumerate(names):
    my_renderers.append(p.line(x=data.index.tz_localize(None), y=data[name], line_width=2, color=colorset[2*i], legend_label=name))
                
p.legend.visible = False
p.add_layout(Legend(items=p.legend[0].items, click_policy="hide", orientation='horizontal', spacing=20))
    
p.add_tools(HoverTool(tooltips="@y",renderers=my_renderers,mode='vline'))

print("Let's show the generated figure.")
show(p)

print("Export to png after this line.")
filepath = 'example_export'
export_png(p, filename = filepath + '.png')

print("Let's show the generated figure again.")
show(p)

print("Export to svg after this line.")
p.output_backend = "svg"
export_svgs(p, filename = filepath + '.svg')

print("Let's show the generated figure again after svg export.")
show(p)
print('This is unexpected.')

Screenshots

This is the output if the code runs in a jupyter notebook.

First the figure is shown right before any export.
Step1

Now the figure is exported to an image in png format. This works fine, nothing to complain about.
Step2

Now the figure is exported to an image in svg format. The stored svg file contains the figure below.
Step3

Comment

I was trying to creat a simplier minimal example, but I was not successful.

I was able to reproduce this problem with different data sets, similar to the given one. As far as I know this problem only occurs with a datetime index.

@bryevdv
Copy link
Member

bryevdv commented Jul 15, 2020

@mosc9575 It would be a helpful experiment if you can vary the data size and see if there is any particular threshold for the problem to start showing up. My speculation is that SVG output does not actually modify any input data [1], but that the third-party canvas2svg library we use to generate SVGs simply fails to work properly with inputs above a certain size.

[1] Though if you can demonstrate the input data structures are actually changed before/after an svg export, please do

@mosc9575
Copy link
Contributor Author

mosc9575 commented Jul 16, 2020

@bryevdv I was working on this problem a bit and today I was successful to build a real minimal example. I figured out that the problem is conected to NaN-values in the data.

import numpy as np

from bokeh.plotting import figure, show, output_notebook
from bokeh.io import  export_svgs

output_notebook()

p = figure(title='missing values in svg export',
           plot_width=1400, 
           plot_height=500,
        )
y = [0, 1, np.nan, np.nan, 3, 4, np.nan, np.nan,  5, 6]
p.line(x=list(range(len(y))), y=y)
show(p)
p.output_backend = "svg"
export_svgs(p, filename = 'test.svg')
show(p)

Screenshots

HTML-Plot in jupyter notebook before svg export.
Step1

HTML-Plot in jupyter notebook after svg export.
Step2

@mattpap
Copy link
Contributor

mattpap commented Jul 16, 2020

Output in JS console:

svg.js:503 Error: <path> attribute d: Expected number, "….71212121212127 NaN L 265.570707…".
svg.js:503 Error: <path> attribute d: Expected number, "…3.1464646464648 NaN L 489.005050…".

@mattpap
Copy link
Contributor

mattpap commented Jul 16, 2020

The problem is that our SVG backend, that imitates canvas 2d rendering context, doesn't implement corner cases properly.

@mattpap mattpap added this to the 2.2 milestone Jul 16, 2020
@mattpap mattpap changed the title [BUG] export_svgs() functions modifies input data SVG backend doesn't respect NaN/non-finite values Jul 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants