Search code examples
pythonbokehbokehjs

How to get the y values from Bokeh RangeTool selection pan?


I'm working in an app which will show the temperatures recorded by meteorological stations.

I'm using Bokeh 3.5.1 with two figures: one is the main graph, and the other one holds a RangeTool to get a view of the data.

What I want is to get the y values (maximum, minimum and mean temperatures) in the selected range to calculate its arithmetic mean in javascript client side. I'm inspecting the RangeTool object (rngt parameter on the callback function), the Plot object (select parameter) and the ColumnDataSource object (source parameter) when a RangesUpdate event is triggered.

But I'm unable to see a method or a property to be used to get the start and end indices of the RangeTool selected range. Any help would be appreciated.

The max, min, start or end properties of the RangeTool x_range object (rngt in the javascript event) gives me the dates in time format of the selected view. But this integer does not exist in the source data: source.data['x_axis'].indexOf(rngt.x_range.min) returns -1.

The relevant code (bok_data is the ColumnDataSource object):

def _configure_main_graph(self, dict_keys, station_data, bok_data, legend):
    """Configure the main graph to plot into the html file."""
    station_id = station_data[1]
    station_name = station_data[2].capitalize()
    cn = station_data[3]
    lat = station_data[4]
    lon = station_data[5]
    height = station_data[6]
    country = countries.get(alpha_2=cn)

    # Adapted code from https://docs.bokeh.org/en/latest/docs/user_guide/topics/timeseries.html RangeTool
    # Normal graph
    lat = self.dd_to_dms(float(lat), 'lat')
    lon = self.dd_to_dms(float(lon), 'lon')
    
    title = f'Station: {station_id} - {station_name} - Country: {country.name} - Latitude: {lat} - Longitude: {lon} - Height: {height} m.'

    maing = figure(tools="xpan", toolbar_location=None,
               x_axis_type="datetime", x_axis_location="above",
               background_fill_color="#efefef", sizing_mode="scale_both", title=title)

    line = None
    legend_items = []
    tooltips = []
    tooltips.append(("Date:", "@x_axis{%F}"))
    renderers = []

    # For each y axis list of data, plot a line
    for ind, y_name in enumerate(dict_keys):
        line = maing.line('x_axis', y_name, source=bok_data, line_color=self.graph_lines_colors[ind])

        legend_items.append((legend[ind], [line]))

        format_y = f"@{y_name}"
        format_y += '{0.0} ºC'
        tooltips.append((f"Temp. {legend[ind]}:", format_y))

    renderers.append(line)
        
    maing.add_tools(HoverTool(tooltips=tooltips, renderers=renderers, formatters={"@x_axis": "datetime"}, mode="vline"))

    lines_legend = Legend(items=legend_items, location="center")
    maing.add_layout(lines_legend, 'right')

    # Set the y axis label
    maing.yaxis.axis_label = 'Temperature ºC'

    return maing

def _configure_selection_graph(self, maing, dict_keys, bok_data):
    # Range selection graph
    select = figure(title="Drag the middle and edges of the selection box to change the range above",
                    height=300, width_policy="max", y_range=maing.y_range,
                    x_axis_type="datetime", y_axis_type=None,
                    tools="", toolbar_location=None, background_fill_color="#efefef")

    # For each y axis list of data, plot a line
    for ind, y_name in enumerate(dict_keys):
        select.line(x='x_axis', y=y_name, source=bok_data, line_color=self.graph_lines_colors[ind])

    select.ygrid.grid_line_color = None

    # Selection tool
    range_tool = RangeTool(x_range=maing.x_range, start_gesture="pan")
    range_tool.overlay.fill_color = "navy"
    range_tool.overlay.fill_alpha = 0.2

    select.add_tools(range_tool)

    callback = CustomJS(args=dict(source=bok_data, rngt=range_tool, select=select), code="""debugger;""")

    select.js_on_event(RangesUpdate, callback)
    
    if self.acknowledgement:
        select.add_layout(Title(text=self.acknowledgement, align="center", text_align="center", text_font_style="normal", text_color="gray"), "below")
        
    return select

And here is a screen capture:

enter image description here


Solution

  • I managed to understand what's going on.

    The source data contains dates without time: only year, month and day. But the RangeTool computes the date in its selection bounds including time. This is the reason because I couldn't get the index.

    So, the answer is to set the hours, minutes, seconds and milliseconds to zero and then the resulting time can be found in the sources.data['x_axis'] and the index can be retrieved.

    For example:

    var timeMin = rngt.x_range.min;
    var dateMin = new Date(timeMin);
    dateMin.setHours(0);
    dateMin.setMinutes(0);
    dateMin.setSeconds(0);
    var tMin = dateMin.setMilliseconds(0);
    
    var aMinIndex = source.data['x_axis'].indexOf(tMin);