☰Read-only archive⌕
Looking for help with Bokeh?

This is a read-only archive. Ask questions and join the conversation on GitHub.

Visit Community Support on GitHub →
Archived discussions · Captured October 28, 2026

CustomJS not updating plot

Community Supportcustomjs
Dennis_M_Reddyhoff
Dennis_M_Reddyhoff
# Get map tiles
tile_provider = get_provider(CARTODBPOSITRON)

# Create ColumnDataSource from coordinates, sizes and predicted means
source = ColumnDataSource(dict(x=x_filtered, y=y_filtered, sizes=sizes_filtered, means=mean_filtered, 
                                vars=np.sqrt(vars_filtered), alphas=alphas, lat=lat_filtered, lon=lon_filtered))

# Create linear colour map for pollution data
mapper = linear_cmap(field_name='means', palette=YlOrBr[9][::-1] ,low=0 ,high=max(mean_filtered)[0])

# Tooltips
tooltips = [("Lat/long", "@lat, @lon"),
            ("Predicted PM2.5", "@means"),
            ("Predicted Variance", "@vars")]

# Plot figure and add tiles 
scaled_map = figure(title=None, x_range=(min(x), max(x)), y_range=(min(y), max(y)),
           x_axis_type="mercator", y_axis_type="mercator", tooltips=tooltips, tools = "pan,wheel_zoom,box_zoom,reset,hover")

scaled_map.add_tile(tile_provider)


# Create and plot glyphs for predictions
glyph = Circle(x="x", y="y", radius="sizes", line_color="white", fill_color=mapper, fill_alpha=0.8, line_width=1, 
                line_alpha=1)
scaled_map.add_glyph(source, glyph)

# Add colour bar 
color_bar = ColorBar(color_mapper=mapper['transform'], width=8,  location=(0,0))
scaled_map.add_layout(color_bar, 'right')

callback = CustomJS(args=dict(source=source, xr=scaled_map.x_range, yr=scaled_map.y_range), code="""
    
    var xstart = xr.start;
    var xend = xr.end;
    var ystart = yr.start;
    var yend = yr.end;
    var gridsize = 20;
    var url = "http://127.0.0.1:5000/?xstart="+xstart+"&xend="+xend+"&ystart="+ystart+"&yend="+yend+"&gridsize="+gridsize;
    var data = source.data;
    var x = data["x"];
    var y = data["y"];
    var means = data["means"];
    
    $.getJSON(url, function(request){
        x = request.xgrid;
        y = request.ygrid;
        means = request.means;});
            
    source.change.emit();    
    """)

scaled_map.js_on_event(LODEnd, callback)

output_notebook()
show(scaled_map)
# Save the plot by passing the plot -object and output path
#save(obj=scaled_map, filename=outfp)

Hi, I’m trying to get a CustomJS script to update a grid from an API when I zoom in. I’m using getJSON to call the API endpoint using the current x and y ranges and returning an array. I think I have the callback set up correctly (but possibly not, this is my first time using javascript) and it’s pinging the server but I’m not getting new points. Can anyone help? I feel like I’ve missed something simple but struggling to spot it!

Many Thanks

⌁   Link to post
Bryan
Bryan

Hi @Dennis_M_Reddyhoff You have to actually modify source.data by assigning to it (or assigning into it, e.g. source.data['x'] = ... ) The code above creates new local variables x, y, and means that have no bearing on the value of source.data, and then immediately throws them away.

⌁   Link to post
Dennis_M_Reddyhoff
Dennis_M_Reddyhoff

Hi Bryan, I’ve changed the code a little to create the ColumnDataSource from the API too for consistency, I changed what you said but still no luck

init_json = requests.get(url).json()

# Get map tiles
tile_provider = get_provider(CARTODBPOSITRON)

# Create ColumnDataSource from coordinates, sizes and predicted means
source = ColumnDataSource(dict(x=init_json["xgrid"], 
                               y=init_json["ygrid"], 
                               sizes=init_json["sizes"], 
                               means=init_json["means"], 
                               vars=np.sqrt(init_json["vars"]).tolist(), 
                               lat=init_json["lat"], 
                               lon=init_json["lon"]))

# Create linear colour map for pollution data
mapper = linear_cmap(field_name='means', palette=YlOrBr[9][::-1] ,low=0 ,high=max(init_json["means"]))

# Tooltips
tooltips = [("Lat/long", "@lat, @lon"),
            ("Predicted PM2.5", "@means"),
            ("Predicted Variance", "@vars")]

# Plot figure and add tiles 
scaled_map = figure(title=None, x_range=(min(x), max(x)), y_range=(min(y), max(y)),
           x_axis_type="mercator", y_axis_type="mercator", tooltips=tooltips, tools = "pan,wheel_zoom,box_zoom,reset,hover")

scaled_map.add_tile(tile_provider)


# Create and plot glyphs for predictions
glyph = Circle(x="x", y="y", radius= "sizes", line_color="white", fill_color=mapper, fill_alpha=0.8, line_width=1, 
                line_alpha=1)
scaled_map.add_glyph(source, glyph)

# Add colour bar 
color_bar = ColorBar(color_mapper=mapper['transform'], width=8,  location=(0,0))
scaled_map.add_layout(color_bar, 'right')

callback = CustomJS(args=dict(source=source, xr=scaled_map.x_range, yr=scaled_map.y_range), code="""
    
    var xstart = xr.start;
    var xend = xr.end;
    var ystart = yr.start;
    var yend = yr.end;
    var gridsize = 20;
    var url = "http://127.0.0.1:5000/?xstart="+xstart+"&xend="+xend+"&ystart="+ystart+"&yend="+yend+"&gridsize="+gridsize;
    
    $.getJSON(url, function(request){
        source.data['x'] = request.xgrid;
        source.data['y'] = request.ygrid;
        source.data['means'] = request.means;
        source.data['vars'] = request.vars;});
        source.data['sizes'] = request.sizes;
        source.data['lat'] = request.lat;
        source.data['lon'] = request.lon;});
        
    source.change.emit();    
    """)

scaled_map.js_on_event(LODEnd, callback)

output_notebook()
show(scaled_map)
⌁   Link to post
Bryan
Bryan

@Dennis_M_Reddyhoff I am afraid I can’t say much more without complete code to run. At this stage pesonally I would be adding console.log and/or debugger statements to the getJSON code to make sure it is executing and that the state I expect to be present is present.

⌁   Link to post
Dennis_M_Reddyhoff
Dennis_M_Reddyhoff

Thanks, I’ll try and think of a toy example. The full code requires you to start a Flask server to serve as the API so not sure that it will be easy to work through on here. I’ll look in to the debugging options you posted as well

⌁   Link to post
Dennis_M_Reddyhoff
Dennis_M_Reddyhoff

Debugging in my browser, the error is Uncaught ReferenceError: $ is not defined. Browsing on the web it looks like I need to include

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

Currently I’ve just been pushing the bokeh map to an html file, do I need to do something different to include this line when the plots are output so I can use jquery in CustomJS?

⌁   Link to post
Bryan
Bryan

Oh, Bokeh does not use or provide jquery. Normally you would need to embed Bokeh content in to your own HTML template that loads jquery. I suppose you could do that in the notebook, but there is probably some way to load jquery using the %javascript cell magic (I think that’s it).

Alternatively you can use plain native JS code instead of jquery.

⌁   Link to post
Dennis_M_Reddyhoff
Dennis_M_Reddyhoff

I ended up using

fetch(url)        
        .then(
            function(response) {
              response.json().then(function(data) {
                source.data['x'] = data.xgrid;
                source.data['y'] = data.ygrid;
                source.data['means'] = data.means;
                source.data['vars'] = data.vars
                source.data['sizes'] = data.sizes;
                source.data['lat'] = data.lat;
                source.data['lon'] = data.lon;
                });
            }
      )

instead of jquery and that has kind of worked, it’s at least updating stuff although still a bit buggy.

⌁   Link to post
Bryan
Bryan

@Dennis_M_Reddyhoff There is a hard assumption that all CDS columns are always all the same length at all times (e.g. just like a DataFrame). So if your new data can have a different lenght, then updating it one column at a time will break that assumption and I would expect weird behavior. It’s almost always a better pattern to update things “all at once”

const new_data = { ... }
source.data = new_data

Another nice benefit of this approach is that the explicit source.change.emit() is not necessary (full assignments to .data can be detected and handled automatically).

⌁   Link to post
Dennis_M_Reddyhoff
Dennis_M_Reddyhoff

You answered my next question! I’ve renamed a few bits and now can just use

fetch(url)        
        .then(
            function(response) {
              response.json().then(function(data) {
                source.data = data;
                });
            }
      )

Without using emit, it’s now super responsive and working exactly how I pictured it! Thanks for all your help

⌁   Link to post

End of archived discussion · Replies are disabled