0

I am trying to create a bokeh plot in a Django app to graph swimming events for an athlete. I am plotting duration (time swam) vs date (date of swim meet). The idea is to have one plot and be able to use a SelectBox widget to choose which event is shown on the graph. The problem is that when I change the source of the data in a CallbackJS function, the graph does not update and instead goes blank.

The data comes from Event objects of the form

class Event(models.Model):
    swimmer = models.ForeignKey(Swimmer, on_delete=models.SET_NULL, null=True, blank=True)
    team = models.ForeignKey(Team, on_delete=models.CASCADE, null=True, blank=True)
    name = models.CharField(max_length=50, null=True, blank=True)
    gender = models.CharField(max_length=1, choices=GENDER_CHOICE, null=True, blank=True)
    event = models.CharField(max_length=10, choices=EVENT_CHOICE)
    time = models.DurationField()
    place = models.IntegerField(null=True, blank=True)
    date = models.DateField(null=True)

First, I iterate through each of the events (17 currently) and makes a list of the date (datetime.date) and time (datetime.timedelta) fields. The unmodified fields are used for x and y respectively, and the values are edited slightly (mostly type cast to string) to be used for the hover tool. If there is no data for the particular event, the data_source{} entry is set to None.

Example data:

data_source = {
    'x_50_free': [date(2017,9,7), date(2017,9,8)]
    'y_50_free': [timedelta(seconds=22.96), timedelta(seconds=22.32)]
    'date_50_free': ['9/7/2017', '9/8/2017']
    'time_50_free': ['00:22.96', '00:22.32']
    'x_100_free': [date(2017,9,7)]
    'y_100_free': [timedelta(seconds=49.86)]
    'date_100_free': ['9/7/2017']
    'time_100_free': ['00:49.86']
}

Then, I plot an initial line so that a line is displayed when the page loads.

source = ColumnDataSource(data=dict(
    x=data_source['x_'+first_event],
    y=data_source['y_'+first_event],
    date=data_source['date_'+first_event],
    time=data_source['time_'+first_event]
))
plot.line('x', 'y', source=source)

I update the source data in my callback function

callback = CustomJS(args=dict(source=source), code="""
    data = %s;
    f = cb_obj.value;

    if (f == "50 Freestyle") {
        source.data['x'] = data.x_50_free;
        source.data['y'] = data.y_50_free;
        source.data['date'] = data.date_50_free;
        source.data['time'] = data.time_50_free;
    } else if (f == "100 Freestyle") {
        source.data['x'] = data.x_100_free;
        source.data['y'] = data.y_100_free;
        source.data['date'] = data.date_100_free;
        source.data['time'] = data.time_100_free;
    }

    ...

    } else if (f == "400 IM") {
        source.data['x'] = data.x_400_im;
        source.data['y'] = data.y_400_im;
        source.data['date'] = data.date_400_im;
        source.data['time'] = data.time_400_im;
    }

    source.change.emit();
""" % json.dumps(data_source, cls=DatetimeEncoder)) # encoder to handle datetimes for x-axis

From what I understand, source.change.emit() is used to update the ColumnDataSource. This seems to work as I am able to log source.data[] to the console and see it update depending on the Select widget option, but the plot itself does not update it simply goes blank. How do I also update the plot to reflect the change in source data?

2
  • Could you please post a complete (but minimal) example of what you are doing? Especially including some mock data and your definition of the callback. Commented Sep 7, 2017 at 15:11
  • @mc51 yes just did it! Let me know if you need anymore information. I feel like I'm so close, it's just a matter of the plot itself not updating. Commented Sep 7, 2017 at 16:49

1 Answer 1

1

Your example is neither minimal nor complete. A complete example can be run right away. A minimal one leaves out aspects not related to your issue. Hence, I am not really sure that I've understood everything but I tried to adapt it. Here is some code to get you started:

from bokeh.io import show, output_file, output_notebook
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.layouts import column, widgetbox
from bokeh.models import CustomJS, Select

output_file("swim_plot.html")

first_event = "_50_free"
second_event = "_100_free"

data_source = {
    'x': [date(2017,9,3), date(2017,9,4)],
    'y': [0, 0],
    'x_50_free': [date(2017,9,7), date(2017,9,8)],
    'y_50_free': [22.23, 24.34],
    'x_100_free': [date(2017,9,12), date(2017,9,14)],
    'y_100_free': [23.22, 25,12]
}    
source = ColumnDataSource(data=data_source)

callback = CustomJS(args=dict(source=source), code="""
    data = source.data;
    f = cb_obj.value;
    if (f == "_50_free") {
        data['x'] = data.x_50_free;
        data['y'] = data.y_50_free;
    } else if (f == "_100_free") {
        data['x'] = data.x_100_free;
        data['y'] = data.y_100_free;
    }
    source.change.emit();
""")

select = Select(title="Option:", value="default", options=["default",
                first_event, second_event])
select.js_on_change('value', callback)   

plot = figure(plot_width=400, plot_height=400, x_axis_type='datetime')
plot.line('x', 'y', source=source)    
show(column(widgetbox(select),plot))

The main issue I see with your code is the definition of ColumnDataSource. While you add all data to your data_dict you add only part of that to your source. Consequently, trying to access the missing data in your JS callback will fail.

Sign up to request clarification or add additional context in comments.

3 Comments

So sorry about the poor example, the whole thing is a little long and I wasn't sure what might be relevant but it seemed unnecessary to include everything I'll work on that in the future. Is there anything I can provide right now that might help?
As for your answer, why do you define an 'x' and 'y' separate from 'x_50_free', 'y_50_free', etc.? The reason I defined my source the way I did was to establish the keys 'x', 'y', 'date', and 'time' in the ColumnDataSource, and then could change the values later. Do I have to add all data to ColumnDataSource in order to access it later? Because like I said before I could log source.data to the console when the Select was changed and the data changed, but the plot wasn't updating, so I originally thought it was an issue with triggering a plot update and not getting the data to change properly.
yes, you have to add your data to ColumnDataSource. that's what i meant in the last sentences.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.