pythonvisualizationbokeh

Fill the area under a step plot in Bokeh


I have a simple step graph and I would like to fill the area under the graph but Im missing something because it's not displayed correctly. This is my working code:

import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, NumeralTickFormatter, DaysTicker, DatetimeTickFormatter, HoverTool, Patch
from math import pi

from datetime import datetime

datetimes = [datetime(2022, 9, 30, 3, 8, 8, 239000, ),
             datetime(2022, 10, 1, 3, 8, 8, 239000, ),
             datetime(2022, 10, 20, 3, 8, 8, 239000,)]
             
values = np.random.random(3)

fig = figure(x_axis_type="datetime", toolbar_location=None, y_range=(0, 1), y_axis_location='right', height=250, width=635)
fig.yaxis[0].formatter = NumeralTickFormatter(format='0%')
fig.xaxis.major_label_orientation = pi/4
fig.xaxis.formatter=DatetimeTickFormatter(
        hours=["%d %B %Y"],
        days=["%d %B %Y"],
        months=["%d %B %Y"],
        years=["%d %B %Y"],
    )
hover = HoverTool(tooltips=[('values', '@values{0.0%}')])
fig.toolbar.active_drag = None
plot_source = ColumnDataSource(data=dict(date=datetimes, values=values))
line_plot = fig.step("date", "values", source=plot_source, line_width=1, color="#285e61")
fig.add_tools(hover)
fig.varea(source=plot_source, x="date", y1=0, y2="values",
          alpha=0.2, fill_color='#38b2ac')

show(fig)

And this is the resulting plot:

enter image description here


Solution

  • The trick is to have each timestamp for the area twice, one value for the left side of the step and on for the right.

    Miminal Example

    import numpy as np
    from datetime import datetime
    
    
    from bokeh.plotting import figure, show, output_notebook
    from bokeh.models import ColumnDataSource, NumeralTickFormatter, DaysTicker, DatetimeTickFormatter, HoverTool, Patch
    output_notebook()
    
    datetimes = np.array([
        datetime(2022, 9, 30, 3, 8, 8, 239000, ),
        datetime(2022, 10, 1, 3, 8, 8, 239000, ),
        datetime(2022, 10, 20, 3, 8, 8, 239000,)
    ])
    
    values = np.random.random(3)
    
    fig = figure(
        x_axis_type="datetime",
        toolbar_location=None,
        y_range=(0, 1),
        y_axis_location='right',
        height=250,
        width=635
    )
    fig.yaxis.formatter = NumeralTickFormatter(format='0%')
    fig.xaxis.major_label_orientation = np.pi/4
    fig.xaxis.formatter=DatetimeTickFormatter(days="%d %B %Y")
    hover = HoverTool(tooltips=[('values', '@values{0.0%}')])
    fig.toolbar.active_drag = None
    
    # main change here
    plot_source = ColumnDataSource(data=dict(
        date=datetimes.repeat(2)[1:],
        values=values.repeat(2)[:-1]
    ))
    
    line_plot = fig.step("date", "values", source=plot_source, line_width=1, color="#285e61")
    fig.add_tools(hover)
    fig.varea(source=plot_source, x="date", y1=0, y2="values",
              alpha=0.2, fill_color='#38b2ac')
    
    show(fig)
    

    Output

    filled area below step plot

    Comment

    I use np.reapeat() to repeat all values in the np.array.