DataRange1d min_interval not working

I am using DataRange1d to define properties of the y axis for my web application, viewable here:
https://www.snowpacktracker.com/btac/stormtracker/tetons

The code to support this is somewhat extensive so I don’t have a minimal, reproducible example, however, the code to set up the figure panels looks like:

  p = figure(
      title="Relative Humidity",
      name="relative_humidity_fig",
      x_axis_type='datetime',
      x_range = (start,end),
      y_axis_label='%',
      y_range=DataRange1d(
        only_visible=True,
        min_interval=20.
      ),
      width=width, height=height,
      tools=tools,
      toolbar_location='above'
  )

The only_visible parameter works well, allowing the range to scale only to the line glyphs that are toggled on in the legend. However, I would like to set a minimum range for the y axes using min_interval (ranges — Bokeh 2.4.2 Documentation). For example, I would like to enforce a minimum range of 20% for the relative humidity panel. I have tried various values for this parameter, but cannot see any changes in the y-axis range. Am I misunderstanding this parameter, or is there a different way to do this?

Other relevant info:
bokeh==2.4.1
ColumnDataSource for each panel built from a Pandas Dataframe
Python 3.8.6

I also hope to do a write-up soon to include in the Showcase gallery, as we have been developing this web application using Bokeh for years now, and have learned some things along the way regarding Flask, Heroku, and Bokeh. Thanks for the continued support!

Hi @Patrick_Wright unfortunately without an MRE it’s not really possible to speculate whether somethign like this is a usage issue, or an actual bug, or something else. Perhaps you can construct a purpose-written, much simpler toy example that replicates what you are seeing in isolation so that it can be investigated directly? Otherwise there’s not much I can do beyond pointing at this example with min_interval as a possible basis for comparison:

@Bryan I was able to make a minimal, reproducible example. The following code snippet (180 lines) makes one figure, similar to how I make all the figures on my production web app (Snowpack Tracker). I simply grabbed some snippets of recent data and pasted them into the code, then making a Pandas dataframe and ColumnDataSource with this data exactly as I do in my full code.

Note lines 35-37, where I have:

y_range=DataRange1d(
  only_visible=True,
  min_interval=50.
)

My interpretation of the docs is that the min_interval setting should not allow the y-axis to zoom in further than 50 data units. In this case, I am plotting relative humidity data with units of “%”, and the full range of the data is from approximately 88% to 100% (12 data units). So, with min_interval=50., I would expect the y-axis range to not reduce beyond 50 units (e.g. 50% to 100%). Would this require setting a minimum or maximum bound to have one end of the interval bounded? I tried playing with the bounds and end parameters, but did not see any changes.

My end goal is to be able to update my figures with the date range slider or radio buttons to various date ranges (which updates the CDS for each figure), and then let the y-axis auto-scale to the range of visible data for the glyphs that are toggled on in the legend. However, I would like to set a minimum range that the y-axis cannot “zoom” beyond (using min_interval). I can also drop my date range slider and radio buttons into this example, but because the issue is present without these widgets, I did not include them.

If you put this code snippet in a file named main.py, put in a directory and run with bokeh serve, it should work.
bokeh==2.4.1

Thanks!

#!/usr/bin/env python
'''
min_span not working, minimal reproducible example
Author: P. Wright, Inversion Labs
Created: Nov 14, 2021
'''
import pandas as pd
from bokeh.models import Legend,DataRange1d,ColumnDataSource
from bokeh.plotting import figure, curdoc
from bokeh.layouts import gridplot

def make_rh_fig(panel_df, start, end):
  '''
  Create the relative humidity panel and plot data
  CDS created in-place

  Parameters
  ----------
  panel_df: the pandas dataframe for this panel
  start: the global start date
  end: the global end date

  Returns
  -------
  p: Bokeh Figure object
  '''
  panel_df['Date'] = panel_df.index.tz_localize(None)
  cds = ColumnDataSource(data=panel_df)
  p = figure(
      title="Relative Humidity",
      name="relative_humidity_fig",
      x_axis_type='datetime',
      x_range = (start,end),
      y_axis_label='%',
      y_range=DataRange1d(
        only_visible=True,
        min_interval=50.,
        end=100.
      )
  )
  legenddict={}
  items=[]
  tooltips=[("Date", "@Date_hover")]
  legenddict['foo'] = p.line(x='Date', y='foo',
                      line_width=2, color='blue',
                      alpha=0.8, name='foo', source=cds)

  items.append(('foo',[legenddict['foo']]))
  legend1 = Legend(items=items[0:],)
  p.add_layout(legend1, 'below')
  return p

#---------------------------------------------
# TEST DATA
#---------------------------------------------

datetime = [
'2021-11-11 12:00:00',
'2021-11-11 13:00:00',
'2021-11-11 14:00:00',
'2021-11-11 15:00:00',
'2021-11-11 16:00:00',
'2021-11-11 17:00:00',
'2021-11-11 18:00:00',
'2021-11-11 19:00:00',
'2021-11-11 20:00:00',
'2021-11-11 21:00:00',
'2021-11-11 22:00:00',
'2021-11-11 23:00:00',
'2021-11-12 00:00:00',
'2021-11-12 01:00:00',
'2021-11-12 02:00:00',
'2021-11-12 03:00:00',
'2021-11-12 04:00:00',
'2021-11-12 05:00:00',
'2021-11-12 06:00:00',
'2021-11-12 07:00:00',
'2021-11-12 08:00:00',
'2021-11-12 09:00:00',
'2021-11-12 10:00:00',
'2021-11-12 11:00:00',
'2021-11-12 12:00:00',
'2021-11-12 13:00:00',
'2021-11-12 14:00:00',
'2021-11-12 15:00:00',
'2021-11-12 16:00:00',
'2021-11-12 17:00:00',
'2021-11-12 18:00:00',
'2021-11-12 19:00:00',
'2021-11-12 20:00:00',
'2021-11-12 21:00:00',
'2021-11-12 22:00:00',
'2021-11-12 23:00:00',
'2021-11-13 00:00:00',
'2021-11-13 01:00:00',
'2021-11-13 02:00:00',
'2021-11-13 03:00:00',
'2021-11-13 04:00:00',
'2021-11-13 05:00:00',
'2021-11-13 06:00:00',
'2021-11-13 07:00:00',
'2021-11-13 08:00:00',
'2021-11-13 09:00:00',
'2021-11-13 10:00:00',
'2021-11-13 11:00:00',
'2021-11-13 12:00:00']

rh = [
 88.0,
 88.0,
 92.0,
 93.0,
 93.0,
 94.0,
 94.0,
 93.0,
 93.0,
 93.0,
 93.0,
 94.0,
 94.0,
 94.0,
 94.0,
 94.0,
 95.0,
 95.0,
 97.0,
 98.0,
 98.0,
 98.0,
 98.0,
 99.0,
 99.0,
 99.0,
 99.0,
 99.0,
 99.0,
 99.0,
 99.0,
 99.0,
 99.0,
 99.0,
100.0,
 99.0,
 98.0,
 98.0,
 97.0,
 99.0,
 99.0,
100.0,
100.0,
100.0,
100.0,
 99.0,
 99.0,
 99.0,
 98.0]

df_dict = {'dt':datetime,'data':rh}
df_test = pd.DataFrame(data=df_dict['data'],
  columns=['foo'],
  index=pd.to_datetime(df_dict['dt'])
  )

#---------------------------------------------
# MAKE FIGURE
#---------------------------------------------
global_start = df_test.index.min()
global_end = df_test.index.max()
relative_humidity_fig = make_rh_fig(
    df_test,
    global_start,
    global_end
)

grid = gridplot(
  children = [relative_humidity_fig],
  ncols=1,
)

curdoc().add_root(grid)

@Patrick_Wright min_interval really only applies to user interactions (e.g. manual zooming with a scroll wheel) If you tell the range to have a smaller interval, either explicitly by setting start and end yourself, or by asking for auto-ranging and supplying data with a smaller range than the min interval, then Bokeh assumes you know what you want, and gives those precedence. I guess the user interaction part could be made more evident in the docs. cc @Timo

In the code above, it starts out with an interval smaller than 50 due to auto-ranging (which has precedence) If you scroll zoom out so the range is larger than 50, and then try to zoom back in, the range stops you from going back down below 50 with the zoom tool. All of that is expected behavior to me. Since it sounds like that is not what you want, it seems like you might want to manually manage your range start and end according to your specific needs (i.e. just use a “dumb” Range1d instead of DataRange1d).

@Bryan OK, makes sense. I did not understand that this applies only to user interactions, which could be made more clear in the docs. I will try a more manual solution to get what I need. However, I need to stick with DataRange1d since I really like the functionality of only_visible=True. One easy solution I might try to enforce a minimum range for the y range is to plot a max and min “dummy” line with alpha=0.0 that won’t show up but will force the y-range when the data is of a smaller range. Such as:
dummy_min = p.line(x='Date', y=80, alpha=0.0, name='dummy_min')
dummy_max = p.line(x='Date', y=100, alpha=0.0, name='dummy_max')

Thanks again for the continued assistance!