ColumnDataSource: make dependencies between 2 columns

In ColumnDataSource i must create dependencies between to columns.
I have y and ys columns and Fourier transform formulas (fft)

signal_src = ColumnDataSource(data={'y': [], 'ys':[]})

When ‘y’ change, I want automatically calculate fft(signal_src.data[‘y’]) and save in ‘ys’ column,
How exactly i can do that? I tryied use on_change(), but attr_change() don’t call then y parameter is changed.

def __draw_signal_gui(self):
    '''GUI for 'signal' tab'''
    signal_src = ColumnDataSource(data={'y: [], 'ys':[]})
    signal_src.on_change("data", attr_change) '

First of all, why do you need to save it? If you don’t really need to have it saved in the data source, then you should be able to use a custom version of Transform or Expression. To be honest, I don’t really understand the difference between them, but one of them could definitely be used.

I must to save it, because i will draw ‘ys’ on another figure and will change ‘ys’.
Can you tell me, please, why on_cnahge didn’t work correct with ColumnDataSource.data?

I cannot really tell anything because you didn’t provide any code apart from a single call to on_change. I would be able to give some answer if you can provide a minimal working example that I can run on my side without having to change it.

import numpy as np

from bokeh.layouts import column
from bokeh.plotting import figure, curdoc
from bokeh.models import Panel, Tabs, Div, ColumnDataSource, HoverTool

PLOT_WIDTH = 1000
PLOT_HEIGHT = 570

class Some_class:
    def __init__(self):
        self.__canvas_signal = None
        self.__signal_src = None
        curdoc().add_root(column(Tabs(tabs=[self.__draw_signal_gui()])))

    def attr_change(self, attr, old, new):
        print('attr change')

    def __draw_signal_gui(self):
        '''GUI for 'signal' tab'''
        self.__signal_src = ColumnDataSource(data={'x': [], 'y':[], 'color':[], 'name':[]})
        self.__signal_src.on_change("data", self.attr_change)
        self.__canvas_signal = figure(plot_height=PLOT_HEIGHT, plot_width=PLOT_WIDTH,
                                      x_axis_label='Number of dots',
                                      y_axis_label='Amplitude',)
        self.__canvas_signal.multi_line(xs='x', ys='y', source=self.__signal_src,
                                        color='color', name='name',
                                        legend_field='name')
        return Panel(child=self.__canvas_signal, title="signal")

    def add_signal(self, signal, **kwargs):
        self.__signal_src.stream({'x':[np.arange(len(signal))], 'y':[signal],
                                  'color':[kwargs['color']],
                                  'name':[kwargs['name']]})

slen1 = 1000
ftone1 = .13
signal1 = np.sin(2 * np.pi * ftone1 * np.arange(slen1))
signal1_param = {'name': 'signal1', 'calc_spectrum':True, 'color': 'red',}
dataview = Some_class()
dataview.add_signal(signal1, **signal1_param)
signal1 += 1 # on_change didn't call
signal1 += 1 # on_change didn't call

Please format the code as a single block.

How do you serve the code? Do you save the result in an HTML file or do you call bokeh serve?

Formatted. bokeh serve --show script.py

Ah, I see.

So the callback is called once, when you call add_signal. It’s not called for the np.array mutations simply because Bokeh doesn’t care about NumPy. For Bokeh to understand that you have changed something, you have to explicitly change data sources. Call stream, call patch, directly assign data, or something else.

Intersting, but i see changes on the plot after

signal1 += 1

but i still don’t know how to make dependencies between 2 columns. Is it possible?

        self.signal_src = ColumnDataSource(data={'x': [], 'y':[], 'color':[], 'name':[]})
        self.signal_src.add({'ys':[np.fft.fft(self.signal_src.data['y'])]})

I don’t work, but i need something like that.

You see the changed data because you have changed it in-place. Bokeh synchronizes data only after the execution is returned back to the event loop - for your particular code it’s after all the statements have been executed. With that being said, I don’t think it’s a good thing - it will make it much harder to debug later. I wouldn’t rely on such behavior at all, I’d stay away from mutating shared data.

Regarding your question. It’s not possible to add a column, add some data to a different column, and expect the first one to be populated automatically. That’s because columns in column data sources need to always have the same length. There are two alternatives, if you want to stay in Python and not implement the FFT in JavaScript:

  1. Populate the ys column yourself whenever you change the y column
  2. Create a second data source just for the ys column and sync the data one way. So, when the y changes in the first data source, you’d compute and assign the ys values in the second data source. You can do that in your attr_change method.

Спасибо! Thank you!

1 Like