ColumnDataSource converting numpy arrays to lists?

Hi,

Firstly, apologies for the length of the example code below - some of it can be ignored but it was simpler for me to include it all to get the point across. It’s similar to a previous example I posted - that for the topic on Inheritance - which wasn’t working: the colour bar range values should have updated when the slider was moved. This seems to be due to the ColumnDataSource converting numpy arrays to lists. If I convert them back to numpy arrays (i.e. the convertData class method) it works fine. Is this a bug or am I not using the ColumnDataSource correctly?

Additional (unrelated) question: as you can see I’m attempting to add a HoverTool with some custom JS (which simply dumps the 3D array values in one dimension at the cursor position to the browser console). If I add this inside the ColourMap class definition (not shown in the code) it works fine, but adding it afterwards as shown doesn’t work: if the relevant line (2nd from last) is uncommented the plot doesn’t render. Should it work? The reason for wanting to do it this way is that I can re-use the same ColourMap class and add specialised tools afterwards.

(By the way I am aware of the HeatMap class - I just need something particular for my applications - mainly the colourbar plotted underneath and use of numpy arrays).

Many thanks,

Marcus.

···

import numpy

from bokeh.plotting import Figure
from bokeh.models import ColumnDataSource, Plot, LinearAxis
from bokeh.models.mappers import LinearColorMapper
from bokeh.models.ranges import Range1d
from bokeh.models.widgets import Slider
from bokeh.models.widgets.layouts import Column
from bokeh.core.properties import Instance
from bokeh.palettes import RdYlBu11
from bokeh.io import curdoc
from bokeh.models.callbacks import CustomJS

from bokeh.models.tools import HoverTool

Define the class

class ColourMap(Column):

__view_model__ = "Column"
__subtype__ = "ColourMap"

plot = Instance(Plot)
cbar = Instance(Plot)

power = Instance(Slider)

datasrc = Instance(ColumnDataSource)
cbarrange = Instance(ColumnDataSource)

cmap = Instance(LinearColorMapper)

def __init__(self,x,y,D):

    super(ColourMap,self).__init__()

    self.power = Slider(title = 'Power',name = 'Power',start = 1,end = numslices,step = 1,
                        value = round(numslices/2))
    self.power.on_change('value',self.inputchange)

    d = D[self.power.value]
    self.datasrc = ColumnDataSource(data={'x':x,'y':y,'d':[d],'D':D})

    self.cmap = LinearColorMapper(palette = RdYlBu11)

    r = Range1d(start = d.min(),end = d.max())       
    self.cbarrange = ColumnDataSource(data = {'range':[r]})

    toolset = ['reset,pan,resize,wheel_zoom,box_zoom,save']

    self.plot = Figure(title="Colourmap plot",x_axis_label = 'x',y_axis_label = 'y',
                       x_range = [x[0],x[-1]],y_range=[y[0],y[-1]],
                       plot_height = 500,plot_width = 500,tools = toolset)

    dx = x[1] - x[0]
    dy = y[1] - y[0]

    self.plot.image('d',source = self.datasrc,x = x[0]-dx/2, y = y[0]-dy/2,
                    dw = [x[-1]-x[0]+dx],dh = [y[-1]-y[0]+dy],
                    color_mapper = self.cmap)

    self.generate_colorbar()

    self.children.append(self.power)
    self.children.append(self.plot)
    self.children.append(self.cbar)

def generate_colorbar(self,cbarlength = 500,cbarwidth = 50):

    pal = RdYlBu11

    minVal = self.datasrc.data['d'][0].min()
    maxVal = self.datasrc.data['d'][0].max()
    vals = numpy.linspace(minVal,maxVal,len(pal))

    self.cbar = Figure(tools = "",x_range = [minVal,maxVal],y_range = [0,1],
                       plot_width = cbarlength,plot_height = cbarwidth)

    self.cbar.toolbar_location = None
    self.cbar.min_border_left = 10
    self.cbar.min_border_right = 10
    self.cbar.min_border_top = 0
    self.cbar.min_border_bottom = 0
    self.cbar.xaxis.visible = False
    self.cbar.yaxis.visible = False
    self.cbar.extra_x_ranges = {'xrange':self.cbarrange.data['range'][0]}
    self.cbar.add_layout(LinearAxis(x_range_name = 'xrange'),'below')

    for r in self.cbar.renderers:
        if type(r).__name__ == 'Grid':
            r.grid_line_color = None

    self.cbar.rect(x = vals,y = 0.5,color = pal,width = vals[1]-vals[0],height = 1)

def updatez(self):

    data = self.datasrc.data
    newdata = data
    d = data['d']
    d[0] = data['D'][self.power.value - 1]
    newdata['d'] = d
    self.datasrc.trigger('data',data,newdata)

def updatecbar(self):

    minVal = self.datasrc.data['d'][0].min()
    maxVal = self.datasrc.data['d'][0].max()
    self.cbarrange.data['range'][0].start = minVal
    self.cbarrange.data['range'][0].end = maxVal

def inputchange(self,attrname,old,new):

    self.convertData()
    self.updatez()
    self.updatecbar()

def convertData(self): # Are numpy arrays getting converted to lists in ColumnDataSource?
    data = self.datasrc.data
    newdata = data
    for k in ['x','y','d','D']:
        if (type(newdata[k]) is list):
            newdata[k] = numpy.array(newdata[k])

Instantiate

numslices = 6
x = numpy.linspace(1,2,11)
y = numpy.linspace(2,4,21)
D = numpy.ndarray([numslices,y.size,x.size])
for i in range(numslices):
for j in range(y.size):
for k in range(x.size):
D[i,j,k] = (y[j]*x[k])**(i+1) + y[j]*x[k]

Add a custom hover tool

cm = ColourMap(x,y,D)

jsCode = ‘’’
var geom = cb_data[‘geometry’];
var imdata = imsrc.get(‘data’);

var hx = geom.x;
var hy = geom.y;

var x = imdata[‘x’];
var y = imdata[‘y’];

var dx = x[1] - x[0]
var dy = y[1] - y[0]
var xind = Math.floor((hx + dx/2 - x[0])/dx)
var yind = Math.floor((hy + dy/2 - y[0])/dy)

if ((xind < x.length) && (yind < y.length)) {
var D = imdata[‘D’]

console.log(‘Data at cursor…’)
for (i = 0; i < D.length; i++) {
console.log(D[i][yind][xind])
}
}
‘’’
cJS = CustomJS(args = {‘imsrc’:cm.datasrc},code = jsCode)
htool = HoverTool(callback = cJS)

#cm.plot.tools.append(htool) # If uncommented plot does not render

curdoc().add_root(cm)

I’ve sorted out the custom HoverTool problem - the 2nd last line should have been: cm.plot.add_tools(htool).

Any advice on the ColumnDataSource numpy array handling would be appreciated.

Thanks,
Marcus.

···

On Thursday, December 1, 2016 at 10:58:15 AM UTC, Marcus Donnelly wrote:

Hi,

Firstly, apologies for the length of the example code below - some of it can be ignored but it was simpler for me to include it all to get the point across. It’s similar to a previous example I posted - that for the topic on Inheritance - which wasn’t working: the colour bar range values should have updated when the slider was moved. This seems to be due to the ColumnDataSource converting numpy arrays to lists. If I convert them back to numpy arrays (i.e. the convertData class method) it works fine. Is this a bug or am I not using the ColumnDataSource correctly?

Additional (unrelated) question: as you can see I’m attempting to add a HoverTool with some custom JS (which simply dumps the 3D array values in one dimension at the cursor position to the browser console). If I add this inside the ColourMap class definition (not shown in the code) it works fine, but adding it afterwards as shown doesn’t work: if the relevant line (2nd from last) is uncommented the plot doesn’t render. Should it work? The reason for wanting to do it this way is that I can re-use the same ColourMap class and add specialised tools afterwards.

(By the way I am aware of the HeatMap class - I just need something particular for my applications - mainly the colourbar plotted underneath and use of numpy arrays).

Many thanks,

Marcus.


import numpy

from bokeh.plotting import Figure
from bokeh.models import ColumnDataSource, Plot, LinearAxis
from bokeh.models.mappers import LinearColorMapper
from bokeh.models.ranges import Range1d
from bokeh.models.widgets import Slider
from bokeh.models.widgets.layouts import Column
from bokeh.core.properties import Instance
from bokeh.palettes import RdYlBu11
from bokeh.io import curdoc
from bokeh.models.callbacks import CustomJS

from bokeh.models.tools import HoverTool

Define the class

class ColourMap(Column):

__view_model__ = "Column"
__subtype__ = "ColourMap"

plot = Instance(Plot)
cbar = Instance(Plot)

power = Instance(Slider)

datasrc = Instance(ColumnDataSource)
cbarrange = Instance(ColumnDataSource)

cmap = Instance(LinearColorMapper)

def __init__(self,x,y,D):

    super(ColourMap,self).__init__()

    self.power = Slider(title = 'Power',name = 'Power',start = 1,end = numslices,step = 1,
                        value = round(numslices/2))
    self.power.on_change('value',self.inputchange)

    d = D[self.power.value]
    self.datasrc = ColumnDataSource(data={'x':x,'y':y,'d':[d],'D':D})

    self.cmap = LinearColorMapper(palette = RdYlBu11)

    r = Range1d(start = d.min(),end = d.max())       
    self.cbarrange = ColumnDataSource(data = {'range':[r]})

    toolset = ['reset,pan,resize,wheel_zoom,box_zoom,save']

    self.plot = Figure(title="Colourmap plot",x_axis_label = 'x',y_axis_label = 'y',
                       x_range = [x[0],x[-1]],y_range=[y[0],y[-1]],
                       plot_height = 500,plot_width = 500,tools = toolset)

    dx = x[1] - x[0]
    dy = y[1] - y[0]

    self.plot.image('d',source = self.datasrc,x = x[0]-dx/2, y = y[0]-dy/2,
                    dw = [x[-1]-x[0]+dx],dh = [y[-1]-y[0]+dy],
                    color_mapper = self.cmap)

    self.generate_colorbar()

    self.children.append(self.power)
    self.children.append(self.plot)
    self.children.append(self.cbar)

def generate_colorbar(self,cbarlength = 500,cbarwidth = 50):

    pal = RdYlBu11

    minVal = self.datasrc.data['d'][0].min()
    maxVal = self.datasrc.data['d'][0].max()
    vals = numpy.linspace(minVal,maxVal,len(pal))

    self.cbar = Figure(tools = "",x_range = [minVal,maxVal],y_range = [0,1],
                       plot_width = cbarlength,plot_height = cbarwidth)

    self.cbar.toolbar_location = None
    self.cbar.min_border_left = 10
    self.cbar.min_border_right = 10
    self.cbar.min_border_top = 0
    self.cbar.min_border_bottom = 0
    self.cbar.xaxis.visible = False
    self.cbar.yaxis.visible = False
    self.cbar.extra_x_ranges = {'xrange':self.cbarrange.data['range'][0]}
    self.cbar.add_layout(LinearAxis(x_range_name = 'xrange'),'below')

    for r in self.cbar.renderers:
        if type(r).__name__ == 'Grid':
            r.grid_line_color = None

    self.cbar.rect(x = vals,y = 0.5,color = pal,width = vals[1]-vals[0],height = 1)

def updatez(self):

    data = self.datasrc.data
    newdata = data
    d = data['d']
    d[0] = data['D'][self.power.value - 1]
    newdata['d'] = d
    self.datasrc.trigger('data',data,newdata)

def updatecbar(self):

    minVal = self.datasrc.data['d'][0].min()
    maxVal = self.datasrc.data['d'][0].max()
    self.cbarrange.data['range'][0].start = minVal
    self.cbarrange.data['range'][0].end = maxVal

def inputchange(self,attrname,old,new):

    self.convertData()
    self.updatez()
    self.updatecbar()

def convertData(self): # Are numpy arrays getting converted to lists in ColumnDataSource?
    data = self.datasrc.data
    newdata = data
    for k in ['x','y','d','D']:
        if (type(newdata[k]) is list):
            newdata[k] = numpy.array(newdata[k])

Instantiate

numslices = 6
x = numpy.linspace(1,2,11)
y = numpy.linspace(2,4,21)
D = numpy.ndarray([numslices,y.size,x.size])
for i in range(numslices):
for j in range(y.size):
for k in range(x.size):
D[i,j,k] = (y[j]*x[k])**(i+1) + y[j]*x[k]

Add a custom hover tool

cm = ColourMap(x,y,D)

jsCode = ‘’’
var geom = cb_data[‘geometry’];
var imdata = imsrc.get(‘data’);

var hx = geom.x;
var hy = geom.y;

var x = imdata[‘x’];
var y = imdata[‘y’];

var dx = x[1] - x[0]
var dy = y[1] - y[0]
var xind = Math.floor((hx + dx/2 - x[0])/dx)
var yind = Math.floor((hy + dy/2 - y[0])/dy)

if ((xind < x.length) && (yind < y.length)) {
var D = imdata[‘D’]

console.log(‘Data at cursor…’)
for (i = 0; i < D.length; i++) {
console.log(D[i][yind][xind])
}
}
‘’’
cJS = CustomJS(args = {‘imsrc’:cm.datasrc},code = jsCode)
htool = HoverTool(callback = cJS)

#cm.plot.tools.append(htool) # If uncommented plot does not render

curdoc().add_root(cm)

Marcus, I’m working on something that seems similar - I’ve got a 2D image of raw intensity values (normalized to 1 or not, have it your way), and for a first pass, I want a hover that shows me the x,y coordinates, and the value, for the hovered pixel. This is useful to me on its own, but there’s further expansion to be done after that in adding more browsable data to each pixel.

Did you ever get your use case up and running, and is it actually similar to mine? I’m not sure of the applicability of the sublcassed colormap. I’m kind of new to Bokeh in general.

···

On Tuesday, December 6, 2016 at 4:49:11 AM UTC-8, Marcus Donnelly wrote:

I’ve sorted out the custom HoverTool problem - the 2nd last line should have been: cm.plot.add_tools(htool).

Any advice on the ColumnDataSource numpy array handling would be appreciated.

Thanks,
Marcus.

On Thursday, December 1, 2016 at 10:58:15 AM UTC, Marcus Donnelly wrote:

Hi,

Firstly, apologies for the length of the example code below - some of it can be ignored but it was simpler for me to include it all to get the point across. It’s similar to a previous example I posted - that for the topic on Inheritance - which wasn’t working: the colour bar range values should have updated when the slider was moved. This seems to be due to the ColumnDataSource converting numpy arrays to lists. If I convert them back to numpy arrays (i.e. the convertData class method) it works fine. Is this a bug or am I not using the ColumnDataSource correctly?

Additional (unrelated) question: as you can see I’m attempting to add a HoverTool with some custom JS (which simply dumps the 3D array values in one dimension at the cursor position to the browser console). If I add this inside the ColourMap class definition (not shown in the code) it works fine, but adding it afterwards as shown doesn’t work: if the relevant line (2nd from last) is uncommented the plot doesn’t render. Should it work? The reason for wanting to do it this way is that I can re-use the same ColourMap class and add specialised tools afterwards.

(By the way I am aware of the HeatMap class - I just need something particular for my applications - mainly the colourbar plotted underneath and use of numpy arrays).

Many thanks,

Marcus.


import numpy

from bokeh.plotting import Figure
from bokeh.models import ColumnDataSource, Plot, LinearAxis
from bokeh.models.mappers import LinearColorMapper
from bokeh.models.ranges import Range1d
from bokeh.models.widgets import Slider
from bokeh.models.widgets.layouts import Column
from bokeh.core.properties import Instance
from bokeh.palettes import RdYlBu11
from bokeh.io import curdoc
from bokeh.models.callbacks import CustomJS

from bokeh.models.tools import HoverTool

Define the class

class ColourMap(Column):

__view_model__ = "Column"
__subtype__ = "ColourMap"

plot = Instance(Plot)
cbar = Instance(Plot)

power = Instance(Slider)

datasrc = Instance(ColumnDataSource)
cbarrange = Instance(ColumnDataSource)

cmap = Instance(LinearColorMapper)

def __init__(self,x,y,D):

    super(ColourMap,self).__init__()

    self.power = Slider(title = 'Power',name = 'Power',start = 1,end = numslices,step = 1,
                        value = round(numslices/2))
    self.power.on_change('value',self.inputchange)

    d = D[self.power.value]
    self.datasrc = ColumnDataSource(data={'x':x,'y':y,'d':[d],'D':D})

    self.cmap = LinearColorMapper(palette = RdYlBu11)

    r = Range1d(start = d.min(),end = d.max())       
    self.cbarrange = ColumnDataSource(data = {'range':[r]})

    toolset = ['reset,pan,resize,wheel_zoom,box_zoom,save']

    self.plot = Figure(title="Colourmap plot",x_axis_label = 'x',y_axis_label = 'y',
                       x_range = [x[0],x[-1]],y_range=[y[0],y[-1]],
                       plot_height = 500,plot_width = 500,tools = toolset)

    dx = x[1] - x[0]
    dy = y[1] - y[0]

    self.plot.image('d',source = self.datasrc,x = x[0]-dx/2, y = y[0]-dy/2,
                    dw = [x[-1]-x[0]+dx],dh = [y[-1]-y[0]+dy],
                    color_mapper = self.cmap)

    self.generate_colorbar()

    self.children.append(self.power)
    self.children.append(self.plot)
    self.children.append(self.cbar)

def generate_colorbar(self,cbarlength = 500,cbarwidth = 50):

    pal = RdYlBu11

    minVal = self.datasrc.data['d'][0].min()
    maxVal = self.datasrc.data['d'][0].max()
    vals = numpy.linspace(minVal,maxVal,len(pal))

    self.cbar = Figure(tools = "",x_range = [minVal,maxVal],y_range = [0,1],
                       plot_width = cbarlength,plot_height = cbarwidth)

    self.cbar.toolbar_location = None
    self.cbar.min_border_left = 10
    self.cbar.min_border_right = 10
    self.cbar.min_border_top = 0
    self.cbar.min_border_bottom = 0
    self.cbar.xaxis.visible = False
    self.cbar.yaxis.visible = False
    self.cbar.extra_x_ranges = {'xrange':self.cbarrange.data['range'][0]}
    self.cbar.add_layout(LinearAxis(x_range_name = 'xrange'),'below')

    for r in self.cbar.renderers:
        if type(r).__name__ == 'Grid':
            r.grid_line_color = None

    self.cbar.rect(x = vals,y = 0.5,color = pal,width = vals[1]-vals[0],height = 1)

def updatez(self):

    data = self.datasrc.data
    newdata = data
    d = data['d']
    d[0] = data['D'][self.power.value - 1]
    newdata['d'] = d
    self.datasrc.trigger('data',data,newdata)

def updatecbar(self):

    minVal = self.datasrc.data['d'][0].min()
    maxVal = self.datasrc.data['d'][0].max()
    self.cbarrange.data['range'][0].start = minVal
    self.cbarrange.data['range'][0].end = maxVal

def inputchange(self,attrname,old,new):

    self.convertData()
    self.updatez()
    self.updatecbar()

def convertData(self): # Are numpy arrays getting converted to lists in ColumnDataSource?
    data = self.datasrc.data
    newdata = data
    for k in ['x','y','d','D']:
        if (type(newdata[k]) is list):
            newdata[k] = numpy.array(newdata[k])

Instantiate

numslices = 6
x = numpy.linspace(1,2,11)
y = numpy.linspace(2,4,21)
D = numpy.ndarray([numslices,y.size,x.size])
for i in range(numslices):
for j in range(y.size):
for k in range(x.size):
D[i,j,k] = (y[j]*x[k])**(i+1) + y[j]*x[k]

Add a custom hover tool

cm = ColourMap(x,y,D)

jsCode = ‘’’
var geom = cb_data[‘geometry’];
var imdata = imsrc.get(‘data’);

var hx = geom.x;
var hy = geom.y;

var x = imdata[‘x’];
var y = imdata[‘y’];

var dx = x[1] - x[0]
var dy = y[1] - y[0]
var xind = Math.floor((hx + dx/2 - x[0])/dx)
var yind = Math.floor((hy + dy/2 - y[0])/dy)

if ((xind < x.length) && (yind < y.length)) {
var D = imdata[‘D’]

console.log(‘Data at cursor…’)
for (i = 0; i < D.length; i++) {
console.log(D[i][yind][xind])
}
}
‘’’
cJS = CustomJS(args = {‘imsrc’:cm.datasrc},code = jsCode)
htool = HoverTool(callback = cJS)

#cm.plot.tools.append(htool) # If uncommented plot does not render

curdoc().add_root(cm)

Hi,

Previous versions of Bokeh serialized all arrays to JSON lists, and then on any round trip from the server, this could cause the original arrays to be turned into lists. The latest version of Bokeh (0.12.4) introduced a binary array protocol that can send *some* numpy array types (float32, float64, int32) as binary data, and then also round trip them back into arrays of the original type. This can be verified by changing the update title callback in the sliders example app to rely on a numpy array method:

  def update_title(attrname, old, new):
      plot.title.text = text.value
       print(source.data['x'].max())

which will function as expected when the title is updated.

Note that some types of numpy arrays (e.g int64) *cannot* be serialized with the binary protocol due to unavoidable and intrinsic browser limitations. These are still round tripped to lists, and I am not sure there is much that could be done about that.

Additionally, CDS created from Pandas Dataframes immediately converted individual Series into lists immediately in the Python initializer. This will be rectified in 0.12.5 release.

Thanks,

Bryan

···

On Dec 1, 2016, at 04:58, Marcus Donnelly <[email protected]> wrote:

Hi,

Firstly, apologies for the length of the example code below - some of it can be ignored but it was simpler for me to include it all to get the point across. It's similar to a previous example I posted - that for the topic on Inheritance - which wasn't working: the colour bar range values should have updated when the slider was moved. This seems to be due to the ColumnDataSource converting numpy arrays to lists. If I convert them back to numpy arrays (i.e. the convertData class method) it works fine. Is this a bug or am I not using the ColumnDataSource correctly?

Additional (unrelated) question: as you can see I'm attempting to add a HoverTool with some custom JS (which simply dumps the 3D array values in one dimension at the cursor position to the browser console). If I add this inside the ColourMap class definition (not shown in the code) it works fine, but adding it afterwards as shown doesn't work: if the relevant line (2nd from last) is uncommented the plot doesn't render. Should it work? The reason for wanting to do it this way is that I can re-use the same ColourMap class and add specialised tools afterwards.

(By the way I am aware of the HeatMap class - I just need something particular for my applications - mainly the colourbar plotted underneath and use of numpy arrays).

Many thanks,

Marcus.

------------

import numpy

from bokeh.plotting import Figure
from bokeh.models import ColumnDataSource, Plot, LinearAxis
from bokeh.models.mappers import LinearColorMapper
from bokeh.models.ranges import Range1d
from bokeh.models.widgets import Slider
from bokeh.models.widgets.layouts import Column
from bokeh.core.properties import Instance
from bokeh.palettes import RdYlBu11
from bokeh.io import curdoc
from bokeh.models.callbacks import CustomJS

from bokeh.models.tools import HoverTool

# Define the class

class ColourMap(Column):

    __view_model__ = "Column"
    __subtype__ = "ColourMap"

    plot = Instance(Plot)
    cbar = Instance(Plot)

    power = Instance(Slider)

    datasrc = Instance(ColumnDataSource)
    cbarrange = Instance(ColumnDataSource)

    cmap = Instance(LinearColorMapper)

    def __init__(self,x,y,D):

        super(ColourMap,self).__init__()

        self.power = Slider(title = 'Power',name = 'Power',start = 1,end = numslices,step = 1,
                            value = round(numslices/2))
        self.power.on_change('value',self.inputchange)

        d = D[self.power.value]
        self.datasrc = ColumnDataSource(data={'x':x,'y':y,'d':[d],'D':D})

        self.cmap = LinearColorMapper(palette = RdYlBu11)

        r = Range1d(start = d.min(),end = d.max())
        self.cbarrange = ColumnDataSource(data = {'range':[r]})

        toolset = ['reset,pan,resize,wheel_zoom,box_zoom,save']

        self.plot = Figure(title="Colourmap plot",x_axis_label = 'x',y_axis_label = 'y',
                           x_range = [x[0],x[-1]],y_range=[y[0],y[-1]],
                           plot_height = 500,plot_width = 500,tools = toolset)

        dx = x[1] - x[0]
        dy = y[1] - y[0]

        self.plot.image('d',source = self.datasrc,x = x[0]-dx/2, y = y[0]-dy/2,
                        dw = [x[-1]-x[0]+dx],dh = [y[-1]-y[0]+dy],
                        color_mapper = self.cmap)

        self.generate_colorbar()

        self.children.append(self.power)
        self.children.append(self.plot)
        self.children.append(self.cbar)

    def generate_colorbar(self,cbarlength = 500,cbarwidth = 50):

        pal = RdYlBu11

        minVal = self.datasrc.data['d'][0].min()
        maxVal = self.datasrc.data['d'][0].max()
        vals = numpy.linspace(minVal,maxVal,len(pal))

        self.cbar = Figure(tools = "",x_range = [minVal,maxVal],y_range = [0,1],
                           plot_width = cbarlength,plot_height = cbarwidth)

        self.cbar.toolbar_location = None
        self.cbar.min_border_left = 10
        self.cbar.min_border_right = 10
        self.cbar.min_border_top = 0
        self.cbar.min_border_bottom = 0
        self.cbar.xaxis.visible = False
        self.cbar.yaxis.visible = False
        self.cbar.extra_x_ranges = {'xrange':self.cbarrange.data['range'][0]}
        self.cbar.add_layout(LinearAxis(x_range_name = 'xrange'),'below')

        for r in self.cbar.renderers:
            if type(r).__name__ == 'Grid':
                r.grid_line_color = None

        self.cbar.rect(x = vals,y = 0.5,color = pal,width = vals[1]-vals[0],height = 1)

    def updatez(self):

        data = self.datasrc.data
        newdata = data
        d = data['d']
        d[0] = data['D'][self.power.value - 1]
        newdata['d'] = d
        self.datasrc.trigger('data',data,newdata)

    def updatecbar(self):

        minVal = self.datasrc.data['d'][0].min()
        maxVal = self.datasrc.data['d'][0].max()
        self.cbarrange.data['range'][0].start = minVal
        self.cbarrange.data['range'][0].end = maxVal

    def inputchange(self,attrname,old,new):

        self.convertData()
        self.updatez()
        self.updatecbar()

    def convertData(self): # Are numpy arrays getting converted to lists in ColumnDataSource?
        data = self.datasrc.data
        newdata = data
        for k in ['x','y','d','D']:
            if (type(newdata[k]) is list):
                newdata[k] = numpy.array(newdata[k])

# Instantiate

numslices = 6
x = numpy.linspace(1,2,11)
y = numpy.linspace(2,4,21)
D = numpy.ndarray([numslices,y.size,x.size])
for i in range(numslices):
    for j in range(y.size):
        for k in range(x.size):
            D[i,j,k] = (y[j]*x[k])**(i+1) + y[j]*x[k]

# Add a custom hover tool

cm = ColourMap(x,y,D)

jsCode = '''
var geom = cb_data['geometry'];
var imdata = imsrc.get('data');

var hx = geom.x;
var hy = geom.y;

var x = imdata['x'];
var y = imdata['y'];

var dx = x[1] - x[0]
var dy = y[1] - y[0]
var xind = Math.floor((hx + dx/2 - x[0])/dx)
var yind = Math.floor((hy + dy/2 - y[0])/dy)

if ((xind < x.length) && (yind < y.length)) {
var D = imdata['D']

console.log('Data at cursor...')
for (i = 0; i < D.length; i++) {
    console.log(D[i][yind][xind])
}
}
'''
cJS = CustomJS(args = {'imsrc':cm.datasrc},code = jsCode)
htool = HoverTool(callback = cJS)

#cm.plot.tools.append(htool) # If uncommented plot does not render

curdoc().add_root(cm)

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/83cacc8c-552f-43ef-9a31-e1c908687c72%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

Hi Alex,

I did get something running which sounds the sort of thing you need - see my post here.

I’ve developed this a bit further and am planning to release some code which you and others might find useful for image displays, keep an eye on the forum.

Regards,
Marcus.

···

On Tuesday, March 7, 2017 at 4:36:45 PM UTC, Alex Whittemore wrote:

Marcus, I’m working on something that seems similar - I’ve got a 2D image of raw intensity values (normalized to 1 or not, have it your way), and for a first pass, I want a hover that shows me the x,y coordinates, and the value, for the hovered pixel. This is useful to me on its own, but there’s further expansion to be done after that in adding more browsable data to each pixel.

Did you ever get your use case up and running, and is it actually similar to mine? I’m not sure of the applicability of the sublcassed colormap. I’m kind of new to Bokeh in general.

On Tuesday, December 6, 2016 at 4:49:11 AM UTC-8, Marcus Donnelly wrote:

I’ve sorted out the custom HoverTool problem - the 2nd last line should have been: cm.plot.add_tools(htool).

Any advice on the ColumnDataSource numpy array handling would be appreciated.

Thanks,
Marcus.

On Thursday, December 1, 2016 at 10:58:15 AM UTC, Marcus Donnelly wrote:

Hi,

Firstly, apologies for the length of the example code below - some of it can be ignored but it was simpler for me to include it all to get the point across. It’s similar to a previous example I posted - that for the topic on Inheritance - which wasn’t working: the colour bar range values should have updated when the slider was moved. This seems to be due to the ColumnDataSource converting numpy arrays to lists. If I convert them back to numpy arrays (i.e. the convertData class method) it works fine. Is this a bug or am I not using the ColumnDataSource correctly?

Additional (unrelated) question: as you can see I’m attempting to add a HoverTool with some custom JS (which simply dumps the 3D array values in one dimension at the cursor position to the browser console). If I add this inside the ColourMap class definition (not shown in the code) it works fine, but adding it afterwards as shown doesn’t work: if the relevant line (2nd from last) is uncommented the plot doesn’t render. Should it work? The reason for wanting to do it this way is that I can re-use the same ColourMap class and add specialised tools afterwards.

(By the way I am aware of the HeatMap class - I just need something particular for my applications - mainly the colourbar plotted underneath and use of numpy arrays).

Many thanks,

Marcus.


import numpy

from bokeh.plotting import Figure
from bokeh.models import ColumnDataSource, Plot, LinearAxis
from bokeh.models.mappers import LinearColorMapper
from bokeh.models.ranges import Range1d
from bokeh.models.widgets import Slider
from bokeh.models.widgets.layouts import Column
from bokeh.core.properties import Instance
from bokeh.palettes import RdYlBu11
from bokeh.io import curdoc
from bokeh.models.callbacks import CustomJS

from bokeh.models.tools import HoverTool

Define the class

class ColourMap(Column):

__view_model__ = "Column"
__subtype__ = "ColourMap"

plot = Instance(Plot)
cbar = Instance(Plot)

power = Instance(Slider)

datasrc = Instance(ColumnDataSource)
cbarrange = Instance(ColumnDataSource)

cmap = Instance(LinearColorMapper)

def __init__(self,x,y,D):

    super(ColourMap,self).__init__()

    self.power = Slider(title = 'Power',name = 'Power',start = 1,end = numslices,step = 1,
                        value = round(numslices/2))
    self.power.on_change('value',self.inputchange)

    d = D[self.power.value]
    self.datasrc = ColumnDataSource(data={'x':x,'y':y,'d':[d],'D':D})

    self.cmap = LinearColorMapper(palette = RdYlBu11)

    r = Range1d(start = d.min(),end = d.max())       
    self.cbarrange = ColumnDataSource(data = {'range':[r]})

    toolset = ['reset,pan,resize,wheel_zoom,box_zoom,save']

    self.plot = Figure(title="Colourmap plot",x_axis_label = 'x',y_axis_label = 'y',
                       x_range = [x[0],x[-1]],y_range=[y[0],y[-1]],
                       plot_height = 500,plot_width = 500,tools = toolset)

    dx = x[1] - x[0]
    dy = y[1] - y[0]

    self.plot.image('d',source = self.datasrc,x = x[0]-dx/2, y = y[0]-dy/2,
                    dw = [x[-1]-x[0]+dx],dh = [y[-1]-y[0]+dy],
                    color_mapper = self.cmap)

    self.generate_colorbar()

    self.children.append(self.power)
    self.children.append(self.plot)
    self.children.append(self.cbar)

def generate_colorbar(self,cbarlength = 500,cbarwidth = 50):

    pal = RdYlBu11

    minVal = self.datasrc.data['d'][0].min()
    maxVal = self.datasrc.data['d'][0].max()
    vals = numpy.linspace(minVal,maxVal,len(pal))

    self.cbar = Figure(tools = "",x_range = [minVal,maxVal],y_range = [0,1],
                       plot_width = cbarlength,plot_height = cbarwidth)

    self.cbar.toolbar_location = None
    self.cbar.min_border_left = 10
    self.cbar.min_border_right = 10
    self.cbar.min_border_top = 0
    self.cbar.min_border_bottom = 0
    self.cbar.xaxis.visible = False
    self.cbar.yaxis.visible = False
    self.cbar.extra_x_ranges = {'xrange':self.cbarrange.data['range'][0]}
    self.cbar.add_layout(LinearAxis(x_range_name = 'xrange'),'below')

    for r in self.cbar.renderers:
        if type(r).__name__ == 'Grid':
            r.grid_line_color = None

    self.cbar.rect(x = vals,y = 0.5,color = pal,width = vals[1]-vals[0],height = 1)

def updatez(self):

    data = self.datasrc.data
    newdata = data
    d = data['d']
    d[0] = data['D'][self.power.value - 1]
    newdata['d'] = d
    self.datasrc.trigger('data',data,newdata)

def updatecbar(self):

    minVal = self.datasrc.data['d'][0].min()
    maxVal = self.datasrc.data['d'][0].max()
    self.cbarrange.data['range'][0].start = minVal
    self.cbarrange.data['range'][0].end = maxVal

def inputchange(self,attrname,old,new):

    self.convertData()
    self.updatez()
    self.updatecbar()

def convertData(self): # Are numpy arrays getting converted to lists in ColumnDataSource?
    data = self.datasrc.data
    newdata = data
    for k in ['x','y','d','D']:
        if (type(newdata[k]) is list):
            newdata[k] = numpy.array(newdata[k])

Instantiate

numslices = 6
x = numpy.linspace(1,2,11)
y = numpy.linspace(2,4,21)
D = numpy.ndarray([numslices,y.size,x.size])
for i in range(numslices):
for j in range(y.size):
for k in range(x.size):
D[i,j,k] = (y[j]*x[k])**(i+1) + y[j]*x[k]

Add a custom hover tool

cm = ColourMap(x,y,D)

jsCode = ‘’’
var geom = cb_data[‘geometry’];
var imdata = imsrc.get(‘data’);

var hx = geom.x;
var hy = geom.y;

var x = imdata[‘x’];
var y = imdata[‘y’];

var dx = x[1] - x[0]
var dy = y[1] - y[0]
var xind = Math.floor((hx + dx/2 - x[0])/dx)
var yind = Math.floor((hy + dy/2 - y[0])/dy)

if ((xind < x.length) && (yind < y.length)) {
var D = imdata[‘D’]

console.log(‘Data at cursor…’)
for (i = 0; i < D.length; i++) {
console.log(D[i][yind][xind])
}
}
‘’’
cJS = CustomJS(args = {‘imsrc’:cm.datasrc},code = jsCode)
htool = HoverTool(callback = cJS)

#cm.plot.tools.append(htool) # If uncommented plot does not render

curdoc().add_root(cm)

Many thanks Bryan. I’m now using 0.12.4 and find the speedup due to the binary protocol very helpful.

Regards,
Marcus.

Hey, that code’s awesome Marcus! I’d love to see a packaged version for sale :slight_smile:

I actually have a slightly different use case, so I attacked the problem a bit differently. While the data I want to plot with hovertool is very image-like, it’s specifically 1) not as big as a typical image, only 100x100px max 2) potentially many more layers than an image - one intensity layer to be visualized, plus potentially 5 or 10 datapoints per “pixel” that I’d like to show in the hovertool (calculations on the value, coordinates in other reference frames, etc). I did all the work on the python side by processing the image into individual rect glyphs for each pixel. The packaged, ready-to-pull code is here: https://github.com/alexwhittemore/bokeh_im_hovertool

Advantage: you can include in the hovertool whatever dimension-matching data you like, quite easily

Disadvantage: hideously inefficient, basically unusable for data >100x100 elements.

Alex

···

On Monday, March 13, 2017 at 9:48:20 AM UTC-7, Marcus Donnelly wrote:

Hi Alex,

I did get something running which sounds the sort of thing you need - see my post here.

I’ve developed this a bit further and am planning to release some code which you and others might find useful for image displays, keep an eye on the forum.

Regards,
Marcus.

On Tuesday, March 7, 2017 at 4:36:45 PM UTC, Alex Whittemore wrote:

Marcus, I’m working on something that seems similar - I’ve got a 2D image of raw intensity values (normalized to 1 or not, have it your way), and for a first pass, I want a hover that shows me the x,y coordinates, and the value, for the hovered pixel. This is useful to me on its own, but there’s further expansion to be done after that in adding more browsable data to each pixel.

Did you ever get your use case up and running, and is it actually similar to mine? I’m not sure of the applicability of the sublcassed colormap. I’m kind of new to Bokeh in general.

On Tuesday, December 6, 2016 at 4:49:11 AM UTC-8, Marcus Donnelly wrote:

I’ve sorted out the custom HoverTool problem - the 2nd last line should have been: cm.plot.add_tools(htool).

Any advice on the ColumnDataSource numpy array handling would be appreciated.

Thanks,
Marcus.

On Thursday, December 1, 2016 at 10:58:15 AM UTC, Marcus Donnelly wrote:

Hi,

Firstly, apologies for the length of the example code below - some of it can be ignored but it was simpler for me to include it all to get the point across. It’s similar to a previous example I posted - that for the topic on Inheritance - which wasn’t working: the colour bar range values should have updated when the slider was moved. This seems to be due to the ColumnDataSource converting numpy arrays to lists. If I convert them back to numpy arrays (i.e. the convertData class method) it works fine. Is this a bug or am I not using the ColumnDataSource correctly?

Additional (unrelated) question: as you can see I’m attempting to add a HoverTool with some custom JS (which simply dumps the 3D array values in one dimension at the cursor position to the browser console). If I add this inside the ColourMap class definition (not shown in the code) it works fine, but adding it afterwards as shown doesn’t work: if the relevant line (2nd from last) is uncommented the plot doesn’t render. Should it work? The reason for wanting to do it this way is that I can re-use the same ColourMap class and add specialised tools afterwards.

(By the way I am aware of the HeatMap class - I just need something particular for my applications - mainly the colourbar plotted underneath and use of numpy arrays).

Many thanks,

Marcus.


import numpy

from bokeh.plotting import Figure
from bokeh.models import ColumnDataSource, Plot, LinearAxis
from bokeh.models.mappers import LinearColorMapper
from bokeh.models.ranges import Range1d
from bokeh.models.widgets import Slider
from bokeh.models.widgets.layouts import Column
from bokeh.core.properties import Instance
from bokeh.palettes import RdYlBu11
from bokeh.io import curdoc
from bokeh.models.callbacks import CustomJS

from bokeh.models.tools import HoverTool

Define the class

class ColourMap(Column):

__view_model__ = "Column"
__subtype__ = "ColourMap"

plot = Instance(Plot)
cbar = Instance(Plot)

power = Instance(Slider)

datasrc = Instance(ColumnDataSource)
cbarrange = Instance(ColumnDataSource)

cmap = Instance(LinearColorMapper)

def __init__(self,x,y,D):

    super(ColourMap,self).__init__()

    self.power = Slider(title = 'Power',name = 'Power',start = 1,end = numslices,step = 1,
                        value = round(numslices/2))
    self.power.on_change('value',self.inputchange)

    d = D[self.power.value]
    self.datasrc = ColumnDataSource(data={'x':x,'y':y,'d':[d],'D':D})

    self.cmap = LinearColorMapper(palette = RdYlBu11)

    r = Range1d(start = d.min(),end = d.max())       
    self.cbarrange = ColumnDataSource(data = {'range':[r]})

    toolset = ['reset,pan,resize,wheel_zoom,box_zoom,save']

    self.plot = Figure(title="Colourmap plot",x_axis_label = 'x',y_axis_label = 'y',
                       x_range = [x[0],x[-1]],y_range=[y[0],y[-1]],
                       plot_height = 500,plot_width = 500,tools = toolset)

    dx = x[1] - x[0]
    dy = y[1] - y[0]

    self.plot.image('d',source = self.datasrc,x = x[0]-dx/2, y = y[0]-dy/2,
                    dw = [x[-1]-x[0]+dx],dh = [y[-1]-y[0]+dy],
                    color_mapper = self.cmap)

    self.generate_colorbar()

    self.children.append(self.power)
    self.children.append(self.plot)
    self.children.append(self.cbar)

def generate_colorbar(self,cbarlength = 500,cbarwidth = 50):

    pal = RdYlBu11

    minVal = self.datasrc.data['d'][0].min()
    maxVal = self.datasrc.data['d'][0].max()
    vals = numpy.linspace(minVal,maxVal,len(pal))

    self.cbar = Figure(tools = "",x_range = [minVal,maxVal],y_range = [0,1],
                       plot_width = cbarlength,plot_height = cbarwidth)

    self.cbar.toolbar_location = None
    self.cbar.min_border_left = 10
    self.cbar.min_border_right = 10
    self.cbar.min_border_top = 0
    self.cbar.min_border_bottom = 0
    self.cbar.xaxis.visible = False
    self.cbar.yaxis.visible = False
    self.cbar.extra_x_ranges = {'xrange':self.cbarrange.data['range'][0]}
    self.cbar.add_layout(LinearAxis(x_range_name = 'xrange'),'below')

    for r in self.cbar.renderers:
        if type(r).__name__ == 'Grid':
            r.grid_line_color = None

    self.cbar.rect(x = vals,y = 0.5,color = pal,width = vals[1]-vals[0],height = 1)

def updatez(self):

    data = self.datasrc.data
    newdata = data
    d = data['d']
    d[0] = data['D'][self.power.value - 1]
    newdata['d'] = d
    self.datasrc.trigger('data',data,newdata)

def updatecbar(self):

    minVal = self.datasrc.data['d'][0].min()
    maxVal = self.datasrc.data['d'][0].max()
    self.cbarrange.data['range'][0].start = minVal
    self.cbarrange.data['range'][0].end = maxVal

def inputchange(self,attrname,old,new):

    self.convertData()
    self.updatez()
    self.updatecbar()

def convertData(self): # Are numpy arrays getting converted to lists in ColumnDataSource?
    data = self.datasrc.data
    newdata = data
    for k in ['x','y','d','D']:
        if (type(newdata[k]) is list):
            newdata[k] = numpy.array(newdata[k])

Instantiate

numslices = 6
x = numpy.linspace(1,2,11)
y = numpy.linspace(2,4,21)
D = numpy.ndarray([numslices,y.size,x.size])
for i in range(numslices):
for j in range(y.size):
for k in range(x.size):
D[i,j,k] = (y[j]*x[k])**(i+1) + y[j]*x[k]

Add a custom hover tool

cm = ColourMap(x,y,D)

jsCode = ‘’’
var geom = cb_data[‘geometry’];
var imdata = imsrc.get(‘data’);

var hx = geom.x;
var hy = geom.y;

var x = imdata[‘x’];
var y = imdata[‘y’];

var dx = x[1] - x[0]
var dy = y[1] - y[0]
var xind = Math.floor((hx + dx/2 - x[0])/dx)
var yind = Math.floor((hy + dy/2 - y[0])/dy)

if ((xind < x.length) && (yind < y.length)) {
var D = imdata[‘D’]

console.log(‘Data at cursor…’)
for (i = 0; i < D.length; i++) {
console.log(D[i][yind][xind])
}
}
‘’’
cJS = CustomJS(args = {‘imsrc’:cm.datasrc},code = jsCode)
htool = HoverTool(callback = cJS)

#cm.plot.tools.append(htool) # If uncommented plot does not render

curdoc().add_root(cm)

Many thanks Alex! Thanks also for the link to your code - looks like we’re solving quite similar but not identical use cases. I’ll post here as soon as I get my code properly packaged and into a repo, which will be ASAP.

···

On Monday, March 13, 2017 at 5:51:21 PM UTC, Alex Whittemore wrote:

Hey, that code’s awesome Marcus! I’d love to see a packaged version for sale :slight_smile:

I actually have a slightly different use case, so I attacked the problem a bit differently. While the data I want to plot with hovertool is very image-like, it’s specifically 1) not as big as a typical image, only 100x100px max 2) potentially many more layers than an image - one intensity layer to be visualized, plus potentially 5 or 10 datapoints per “pixel” that I’d like to show in the hovertool (calculations on the value, coordinates in other reference frames, etc). I did all the work on the python side by processing the image into individual rect glyphs for each pixel. The packaged, ready-to-pull code is here: https://github.com/alexwhittemore/bokeh_im_hovertool

Advantage: you can include in the hovertool whatever dimension-matching data you like, quite easily

Disadvantage: hideously inefficient, basically unusable for data >100x100 elements.

Alex

On Monday, March 13, 2017 at 9:48:20 AM UTC-7, Marcus Donnelly wrote:

Hi Alex,

I did get something running which sounds the sort of thing you need - see my post here.

I’ve developed this a bit further and am planning to release some code which you and others might find useful for image displays, keep an eye on the forum.

Regards,
Marcus.

On Tuesday, March 7, 2017 at 4:36:45 PM UTC, Alex Whittemore wrote:

Marcus, I’m working on something that seems similar - I’ve got a 2D image of raw intensity values (normalized to 1 or not, have it your way), and for a first pass, I want a hover that shows me the x,y coordinates, and the value, for the hovered pixel. This is useful to me on its own, but there’s further expansion to be done after that in adding more browsable data to each pixel.

Did you ever get your use case up and running, and is it actually similar to mine? I’m not sure of the applicability of the sublcassed colormap. I’m kind of new to Bokeh in general.

On Tuesday, December 6, 2016 at 4:49:11 AM UTC-8, Marcus Donnelly wrote:

I’ve sorted out the custom HoverTool problem - the 2nd last line should have been: cm.plot.add_tools(htool).

Any advice on the ColumnDataSource numpy array handling would be appreciated.

Thanks,
Marcus.

On Thursday, December 1, 2016 at 10:58:15 AM UTC, Marcus Donnelly wrote:

Hi,

Firstly, apologies for the length of the example code below - some of it can be ignored but it was simpler for me to include it all to get the point across. It’s similar to a previous example I posted - that for the topic on Inheritance - which wasn’t working: the colour bar range values should have updated when the slider was moved. This seems to be due to the ColumnDataSource converting numpy arrays to lists. If I convert them back to numpy arrays (i.e. the convertData class method) it works fine. Is this a bug or am I not using the ColumnDataSource correctly?

Additional (unrelated) question: as you can see I’m attempting to add a HoverTool with some custom JS (which simply dumps the 3D array values in one dimension at the cursor position to the browser console). If I add this inside the ColourMap class definition (not shown in the code) it works fine, but adding it afterwards as shown doesn’t work: if the relevant line (2nd from last) is uncommented the plot doesn’t render. Should it work? The reason for wanting to do it this way is that I can re-use the same ColourMap class and add specialised tools afterwards.

(By the way I am aware of the HeatMap class - I just need something particular for my applications - mainly the colourbar plotted underneath and use of numpy arrays).

Many thanks,

Marcus.


import numpy

from bokeh.plotting import Figure
from bokeh.models import ColumnDataSource, Plot, LinearAxis
from bokeh.models.mappers import LinearColorMapper
from bokeh.models.ranges import Range1d
from bokeh.models.widgets import Slider
from bokeh.models.widgets.layouts import Column
from bokeh.core.properties import Instance
from bokeh.palettes import RdYlBu11
from bokeh.io import curdoc
from bokeh.models.callbacks import CustomJS

from bokeh.models.tools import HoverTool

Define the class

class ColourMap(Column):

__view_model__ = "Column"
__subtype__ = "ColourMap"

plot = Instance(Plot)
cbar = Instance(Plot)

power = Instance(Slider)

datasrc = Instance(ColumnDataSource)
cbarrange = Instance(ColumnDataSource)

cmap = Instance(LinearColorMapper)

def __init__(self,x,y,D):

    super(ColourMap,self).__init__()

    self.power = Slider(title = 'Power',name = 'Power',start = 1,end = numslices,step = 1,
                        value = round(numslices/2))
    self.power.on_change('value',self.inputchange)

    d = D[self.power.value]
    self.datasrc = ColumnDataSource(data={'x':x,'y':y,'d':[d],'D':D})

    self.cmap = LinearColorMapper(palette = RdYlBu11)

    r = Range1d(start = d.min(),end = d.max())       
    self.cbarrange = ColumnDataSource(data = {'range':[r]})

    toolset = ['reset,pan,resize,wheel_zoom,box_zoom,save']

    self.plot = Figure(title="Colourmap plot",x_axis_label = 'x',y_axis_label = 'y',
                       x_range = [x[0],x[-1]],y_range=[y[0],y[-1]],
                       plot_height = 500,plot_width = 500,tools = toolset)

    dx = x[1] - x[0]
    dy = y[1] - y[0]

    self.plot.image('d',source = self.datasrc,x = x[0]-dx/2, y = y[0]-dy/2,
                    dw = [x[-1]-x[0]+dx],dh = [y[-1]-y[0]+dy],
                    color_mapper = self.cmap)

    self.generate_colorbar()

    self.children.append(self.power)
    self.children.append(self.plot)
    self.children.append(self.cbar)

def generate_colorbar(self,cbarlength = 500,cbarwidth = 50):

    pal = RdYlBu11

    minVal = self.datasrc.data['d'][0].min()
    maxVal = self.datasrc.data['d'][0].max()
    vals = numpy.linspace(minVal,maxVal,len(pal))

    self.cbar = Figure(tools = "",x_range = [minVal,maxVal],y_range = [0,1],
                       plot_width = cbarlength,plot_height = cbarwidth)

    self.cbar.toolbar_location = None
    self.cbar.min_border_left = 10
    self.cbar.min_border_right = 10
    self.cbar.min_border_top = 0
    self.cbar.min_border_bottom = 0
    self.cbar.xaxis.visible = False
    self.cbar.yaxis.visible = False
    self.cbar.extra_x_ranges = {'xrange':self.cbarrange.data['range'][0]}
    self.cbar.add_layout(LinearAxis(x_range_name = 'xrange'),'below')

    for r in self.cbar.renderers:
        if type(r).__name__ == 'Grid':
            r.grid_line_color = None

    self.cbar.rect(x = vals,y = 0.5,color = pal,width = vals[1]-vals[0],height = 1)

def updatez(self):

    data = self.datasrc.data
    newdata = data
    d = data['d']
    d[0] = data['D'][self.power.value - 1]
    newdata['d'] = d
    self.datasrc.trigger('data',data,newdata)

def updatecbar(self):

    minVal = self.datasrc.data['d'][0].min()
    maxVal = self.datasrc.data['d'][0].max()
    self.cbarrange.data['range'][0].start = minVal
    self.cbarrange.data['range'][0].end = maxVal

def inputchange(self,attrname,old,new):

    self.convertData()
    self.updatez()
    self.updatecbar()

def convertData(self): # Are numpy arrays getting converted to lists in ColumnDataSource?
    data = self.datasrc.data
    newdata = data
    for k in ['x','y','d','D']:
        if (type(newdata[k]) is list):
            newdata[k] = numpy.array(newdata[k])

Instantiate

numslices = 6
x = numpy.linspace(1,2,11)
y = numpy.linspace(2,4,21)
D = numpy.ndarray([numslices,y.size,x.size])
for i in range(numslices):
for j in range(y.size):
for k in range(x.size):
D[i,j,k] = (y[j]*x[k])**(i+1) + y[j]*x[k]

Add a custom hover tool

cm = ColourMap(x,y,D)

jsCode = ‘’’
var geom = cb_data[‘geometry’];
var imdata = imsrc.get(‘data’);

var hx = geom.x;
var hy = geom.y;

var x = imdata[‘x’];
var y = imdata[‘y’];

var dx = x[1] - x[0]
var dy = y[1] - y[0]
var xind = Math.floor((hx + dx/2 - x[0])/dx)
var yind = Math.floor((hy + dy/2 - y[0])/dy)

if ((xind < x.length) && (yind < y.length)) {
var D = imdata[‘D’]

console.log(‘Data at cursor…’)
for (i = 0; i < D.length; i++) {
console.log(D[i][yind][xind])
}
}
‘’’
cJS = CustomJS(args = {‘imsrc’:cm.datasrc},code = jsCode)
htool = HoverTool(callback = cJS)

#cm.plot.tools.append(htool) # If uncommented plot does not render

curdoc().add_root(cm)