Interactive data visualization [Python]

Utpal Kumar     3 minute read

Download Codes

You can download all the codes from my github repository.

Installation:

The codes contain environment.yml file that contains all the details of the used Python packages. Install all the libraries using conda in a separate environment:

conda env create -f environment.yml

Topics Covered (for details see my Jupyter Notebook):

  1. Setting the dimension of the figure and plotting markers
     import numpy as np
     from bokeh.plotting import figure
     from bokeh.io import output_file, save, show
    
     x = [1,2,3,4,5]
     y = [6,7,8,9,10]
     p = figure(plot_width=300, plot_height=300)
     circ=p.circle(x,y)
     show(p)
    
  2. Styling the markers and the figure background

     # output_file("bokeh_tutorial.html")
     x = np.random.rand(10)*10
     y = np.random.rand(10)*10
     p = figure(plot_width=300, plot_height=300)
     p.circle(x,y,radius=0.1,fill_color='red',fill_alpha=0.7,line_dash="dashed",legend='circle')
     p.triangle(np.random.rand(10)*10,np.random.rand(10)*10,legend='triangle')
     p.line(np.random.rand(10)*10,np.random.rand(10)*10,line_color='black',legend='line')
    
     p.background_fill_color='lightgray'
     show(p)
    
  3. Setting and styling the title of the figure
     p.title.text = "Random Scatter"
     p.title.text_color = "black"
     p.title.text_font = "times"
     p.title.text_font_size = "25px"
     p.title.align = "center"
    
     show(p)
    
  4. Styling the axes of the figure
     from bokeh.models import Range1d
     p.axis.minor_tick_line_color = "blue"
     p.xaxis.minor_tick_line_color = "red"
     # p.xaxis.minor_tick_line_color = None
     # p.yaxis.minor_tick_line_color = None
    
     p.yaxis.major_label_orientation = "vertical"
     p.xaxis.visible = True
     p.xaxis.axis_label = "X-axis"
     p.yaxis.axis_label = "Y-axis"
    
     p.axis.axis_label_text_color = "blue"
     p.axis.major_label_text_color = "orange"
    
     # Axes Geometry
     # p.x_range = Range1d(start=0,end=15)
     p.x_range = Range1d(start=0,end=10,bounds=(-10,20))
     p.y_range = Range1d(start=0,end=10)
     p.xaxis[0].ticker.desired_num_ticks=2
     p.yaxis[0].ticker.desired_num_ticks=10
     show(p)
    
  5. Styling the grid and legends
     p.xgrid.grid_line_color = "gray"
     p.xgrid.grid_line_alpha = 0.3
     p.ygrid.grid_line_color = "gray"
     p.ygrid.grid_line_alpha = 0.3
     p.grid.grid_line_dash = [5,3]
     # p.legend.location = (1,1)
     p.legend.location = 'top_left'
     p.legend.background_fill_alpha = 0.6
     p.legend.border_line_color = None
     p.legend.margin = 10
     p.legend.padding = 18
     p.legend.label_text_color = 'olive'
     p.legend.label_text_font = 'times'
     show(p)
    
  6. Introduction to ColumnDataSource
     from bokeh.sampledata.iris import flowers
     from bokeh.models import ColumnDataSource
     colormap={'setosa':'red','versicolor':'green','virginica':'blue'}
     flowers['color'] = [colormap[x] for x in flowers['species']]
     flowers['size'] = flowers['sepal_width'] * 5
    
     setosa = ColumnDataSource(flowers[flowers["species"]=="setosa"])
     versicolor = ColumnDataSource(flowers[flowers["species"]=="versicolor"])
     virginica = ColumnDataSource(flowers[flowers["species"]=="virginica"])
    
     p = figure(height=500,width=500)
     p.circle(x="petal_length", y="petal_width", size='size', fill_alpha=0.2, 
     color="color", legend='Setosa', source=setosa)
    
     p.circle(x="petal_length", y="petal_width", size='size', fill_alpha=0.2, 
     color="color", legend='Versicolor', source=versicolor)
    
     p.circle(x="petal_length", y="petal_width", size='size', fill_alpha=0.2,
     color="color", legend='Virginica', source=virginica)
     p.legend.location = 'top_left'
     p.xaxis.axis_label = "Petal Length"
     p.yaxis.axis_label = "Petal Width"
     p.title.text = "Petal plot"
     p.legend.click_policy='hide'
     show(p)
    
  7. Configuring toolbars for the plot
     from bokeh.models import PanTool, ResetTool, HoverTool, WheelZoomTool, BoxZoomTool, HoverTool, SaveTool
     p.tools = [PanTool(),ResetTool(), WheelZoomTool(), BoxZoomTool(), SaveTool()]
     hover = HoverTool(tooltips=[("Species","@species"), ("Sepal Width","@sepal_width")])
     p.add_tools(hover)
     p.toolbar_location = 'above'
     p.toolbar.logo = None
     show(p)
    
  8. Bokeh layouts - column, row, gridplot
     p2 = figure(height=500,width=500)
     p2.circle(x="sepal_length", y="sepal_width", size='size', fill_alpha=0.2, 
     color="color", legend='Setosa', source=setosa)
    
     p2.circle(x="sepal_length", y="sepal_width", size='size', fill_alpha=0.2, 
     color="color", legend='Versicolor', source=versicolor)
    
     p2.circle(x="sepal_length", y="sepal_width", size='size', fill_alpha=0.2,
     color="color", legend='Virginica', source=virginica)
     p2.legend.location = 'top_left'
     p2.xaxis.axis_label = "Sepal Length"
     p2.yaxis.axis_label = "Sepal Width"
     p2.tools = [PanTool(),ResetTool(), WheelZoomTool(), BoxZoomTool(), SaveTool()]
     hover2 = HoverTool(tooltips=[("Species","@species"), ("Sepal Width","@petal_width")])
     p2.add_tools(hover2)
     p2.toolbar_location = 'above'
     p2.toolbar.logo = None
     p2.title.text = "Sepal plot"
     p2.legend.click_policy='hide'
     show(p2)
    
     from bokeh.layouts import column
     show(column(p, p2))  
    
     from bokeh.layouts import row
     show(row(p, p2))
    
     from bokeh.layouts import gridplot
     layout1 = gridplot([[p, p2]], toolbar_location='right')
     show(layout1)
    
  9. Bokeh widgets - Tabs, Panel
     from bokeh.models.widgets import Tabs, Panel
     # Create two panels
     panel1 = Panel(child=p, title='Petal')
     panel2 = Panel(child=p2, title='Sepal')
     # Assign the panels to Tabs
     tabs = Tabs(tabs=[panel1, panel2])
    
     # Show the tabbed layout
     show(tabs)
    
  10. Selecting data points in the bokeh figure
    select_tools = ['box_select', 'lasso_select', 'poly_select', 'tap', 'reset']
    p = figure(height=500,width=500,x_axis_label='Petal Length',y_axis_label="Petal Width"
            ,title="Petal Plot",toolbar_location='above',tools=select_tools)
    p.circle(x="petal_length", y="petal_width", size='size', fill_alpha=0.2, 
    color="color", legend='Setosa', source=setosa,selection_color='deepskyblue',
            nonselection_color='lightgray',
            nonselection_alpha=0.3)
    
    p.circle(x="petal_length", y="petal_width", size='size', fill_alpha=0.2, 
    color="color", legend='Versicolor', source=versicolor,selection_color='deepskyblue',
            nonselection_color='lightgray',
            nonselection_alpha=0.3)
    
    p.circle(x="petal_length", y="petal_width", size='size', fill_alpha=0.2,
    color="color", legend='Virginica', source=virginica,selection_color='deepskyblue',
            nonselection_color='lightgray',
            nonselection_alpha=0.3)
    p.legend.location = 'top_left'
    p.legend.click_policy='hide'
    
    p.toolbar.logo = None
    show(p)
    
  11. Linking Plots
    plot_options = dict(width=250, plot_height=250, tools='pan,wheel_zoom,reset')
    x = list(range(11))
    y0, y1, y2 = x, [10-i for i in x], [abs(i-5) for i in x]
    
    # create a new plot
    s1 = figure(**plot_options,title='s1: linked x and y with s2')
    s1.circle(x, y0, size=10, color="navy")
    
    # create a new plot and share both ranges
    s2 = figure(x_range=s1.x_range, y_range=s1.y_range, **plot_options,title='s2: linked x and y with s1')
    s2.triangle(x, y1, size=10, color="firebrick")
    
    # create a new plot and share only one range
    s3 = figure(x_range=s1.x_range, **plot_options,title='s3: linked x with s1 and s2')
    s3.square(x, y2, size=10, color="olive")
    
    p = gridplot([[s1, s2, s3]])
    
    # show the results
    show(p)
    

Real World Examples

Other than the basic introduction, this tutorial includes two examples:

  1. Plot of the Earthquake events (the event information are obtained using the FDSN service from Obspy package)

python EQviz.py for the plot without widgets

–> See here for the end result.

bokeh serve EQviz_with_widgets.py for plot with the input box for the starting and end year for the search of events. This is quite slow as the program need to request data using the Obspy method each time.

  1. Interactive plot of the global population statistics. Introduction of the slider widget. The widgets can be similarly implemented. To execute this program, you need to run it on the bokeh server using the command:

bokeh serve plot_global_stats.py

Global Statistics GIF

  1. Streaming random data: randomly plot 10 circles glyphs.

bokeh serve streaming_data.py

Leave a comment