In [ ]:
from __future__ import division

import numpy as np

from bokeh.models import ColumnDataSource, CustomJS, Rect
from bokeh.plotting import output_notebook, figure, show
from bokeh.layouts import row

output_notebook()

In [ ]:
N = 20
img = np.empty((N,N), dtype=np.uint32)
view = img.view(dtype=np.uint8).reshape((N, N, 4))
for i in range(N):
    for j in range(N):
        view[i, j, 0] = int(i/N*255)
        view[i, j, 1] = 158
        view[i, j, 2] = int(j/N*255)
        view[i, j, 3] = 255

In [ ]:
source = ColumnDataSource({'x':[], 'y':[], 'width':[], 'height':[]})

In [ ]:
xrange_callback = CustomJS(args=dict(source=source), code="""
    var data = source.data;
    var start = cb_obj.start;
    var end = cb_obj.end;
    data['x'] = [start + (end - start) / 2];
    data['width'] = [end - start];
    source.trigger('change');
""")

yrange_callback = CustomJS(args=dict(source=source), code="""
    var data = source.data;
    var start = cb_obj.start;
    var end = cb_obj.end;
    data['y'] = [start + (end - start) / 2];
    data['height'] = [end - start];
    source.trigger('change');
""")

In [ ]:
p1 = figure(title='Box Zoom Here', plot_width=400, plot_height=400,
            x_range=(0,10), y_range=(0,10), tools ='box_zoom,wheel_zoom,pan,reset')
p1.image_rgba(image=[img], x=[0], y=[0], dw=[10], dh=[10])
p1.x_range.callback = xrange_callback
p1.y_range.callback = yrange_callback

p2 = figure(title='See Zoom Window Here', plot_width=400, plot_height=400, 
            x_range=(0,10), y_range=(0,10), tools="")
p2.image_rgba(image=[img], x=[0], y=[0], dw=[10], dh=[10])
rect = Rect(x='x', y='y', width='width', height='height', fill_alpha=0, line_color='black')
p2.add_glyph(source, rect)

In [ ]:
show(row(p1, p2))

In [ ]: