In [2]:
import bokeh.embed
import bokeh.io
import bokeh.models
import bokeh.models.widgets
import bokeh.plotting
import pandas as pd
from pandas_datareader import wb

bokeh.plotting.output_notebook()


Loading BokehJS ...

In [3]:
df = wb.download(indicator='NY.GDP.PCAP.KD', country=['US', 'CA', 'MX'], start=2005, end=2008)
df = df.reset_index()


/Users/caged/miniconda3/envs/notebooks/lib/python3.4/site-packages/pandas_datareader/wb.py:159: FutureWarning: convert_objects is deprecated.  Use the data-type specific converters pd.to_datetime, pd.to_timedelta and pd.to_numeric.
  out = out.convert_objects(convert_numeric=True)

In [4]:
df.head()


Out[4]:
country year NY.GDP.PCAP.KD
0 Canada 2008 37086.898159
1 Canada 2007 37054.878287
2 Canada 2006 36679.368924
3 Canada 2005 36028.232490
4 Mexico 2008 8275.465345

In [12]:
source = bokeh.models.ColumnDataSource(df)
original_source = bokeh.models.ColumnDataSource(df)
columns = [
    bokeh.models.widgets.TableColumn(field="country", title="Country", width=10),
    bokeh.models.widgets.TableColumn(field="year", title="Year", width=10),
    bokeh.models.widgets.TableColumn(field="NY.GDP.PCAP.KD", title="NY.GDP.PCAP.KD", width=10),
]
data_table = bokeh.models.widgets.DataTable(source=source, columns=columns, width=0)

# callback code to be used by all the filter widgets
# requires (source, original_source, country_select_obj, year_select_obj, target_object)
combined_callback_code = """
var data = source.get('data');
var original_data = original_source.get('data');
var country = country_select_obj.get('value');
console.log("country: " + country);
var year = year_select_obj.get('value');
console.log("year: " + year);
for (var key in original_data) {
    data[key] = [];
    for (var i = 0; i < original_data['country'].length; ++i) {
        if ((country === "ALL" || original_data['country'][i] === country) &&
            (year === "ALL" || original_data['year'][i] === year)) {
            data[key].push(original_data[key][i]);
        }
    }
}
target_obj.trigger('change');
source.trigger('change');
"""

# define the filter widgets, without callbacks for now
country_list = ['ALL'] + df['country'].unique().tolist()
country_select = bokeh.models.widgets.Select(title="Country:", value=country_list[0], options=country_list)
year_list = ['ALL'] + df['year'].unique().tolist()
year_select = bokeh.models.widgets.Select(title="Year:", value=year_list[0], options=year_list)

# now define the callback objects now that the filter widgets exist
generic_callback = bokeh.models.CustomJS(
    args=dict(source=source, 
              original_source=original_source, 
              country_select_obj=country_select, 
              year_select_obj=year_select, 
              target_obj=data_table),
    code=combined_callback_code
)

# finally, connect the callbacks to the filter widgets
country_select.callback = generic_callback
year_select.callback = generic_callback

p = bokeh.io.vplot(country_select, year_select, data_table)
bokeh.plotting.show(p)


Out[12]:

<Bokeh Notebook handle for In[12]>


In [23]:
from bokeh.models import TableColumn, ColumnDataSource, DataTable, Select, Slider, CustomJS, Button, HBox
from bokeh.plotting import vplot
from bokeh.sampledata.periodic_table import elements

def plot_datatable(df):
    df = df.copy()
    # deal with some atomic mass values of the form '[98]'
    df['atomic mass'] = df['atomic mass'].str.extract('([\d\.]+)').astype(float)
    
    columns = [
        TableColumn(field='atomic number', title='Atomic Number'),
        TableColumn(field='symbol', title='Symbol'),
        TableColumn(field='name', title='Name'),
        TableColumn(field='metal', title='Type'),
        TableColumn(field='atomic mass', title='Atomic Mass')
    ]
    column_names = [tc.field for tc in columns]
    source = ColumnDataSource(df[column_names])
    original_source = ColumnDataSource(df)
    data_table = DataTable(source=source, columns=columns, height=600, editable=False)
    
    widget_callback_code = """
    var filtered_data = filtered_source.get('data');
    var original_data = original_source.get('data');
    
    var element_type = element_type_select.get('value');
    var min_mass = min_mass_slider.get('value');
    
    // now construct the new data object based on the filtered values
    for (var key in original_data) {
        filtered_data[key] = [];
        for (var i = 0; i < original_data[key].length; ++i) {
            if ((element_type === "ALL" || original_data["metal"][i] === element_type) &&
                (original_data['atomic mass'][i] >= min_mass)) {
                filtered_data[key].push(original_data[key][i]);
            }
        }
    }
    target_obj.trigger('change');
    filtered_source.trigger('change');
    """
    
    # define the filter widgets, without callbacks for now
    element_type_list = ['ALL'] + df['metal'].unique().tolist()
    element_type_select = Select(title="Element Type:", value=element_type_list[0], options=element_type_list)
    min_mass_slider = Slider(start=0, end=df['atomic mass'].max(), value=1, step=1, title="minimum atomic mass")
    
    # now define the callback objects now that the filter widgets exist
    arg_dct = dict(
        filtered_source=source,
        original_source=original_source,
        element_type_select=element_type_select,
        min_mass_slider=min_mass_slider,
        target_obj=data_table
    )
    generic_callback = CustomJS(args=arg_dct, code=widget_callback_code)

    # connect the callbacks to the filter widgets
    element_type_select.callback = generic_callback
    min_mass_slider.callback = generic_callback
    
    # create a button to collect the filtered results
    # for now, just send json to new window
    send_button_callback_code = """
    var filtered_data = filtered_source.get('data');
    
    var action_items = [];
    for (var i = 0; i < filtered_data['atomic number'].length; ++i) {
        var item = new Object();
        for (var key in filtered_data) {
            item[key] = filtered_data[key][i]
        }
        action_items.push(item);
    }
    var new_window = window.open("data:text/html," + encodeURIComponent(JSON.stringify(action_items)),
                                 "_blank", "location=yes,height=570,width=520,scrollbars=yes,status=yes");
    new_window.focus();
    """
    send_button_callback = CustomJS(args=dict(filtered_source=source), code=send_button_callback_code)
    send_button = Button(label="Send", type="success", callback=send_button_callback)

    input_widgets = HBox(
        children=[
            HBox(children=[element_type_select, ]),
            HBox(children=[min_mass_slider]),
            HBox(children=[send_button]),
        ]
    )
    p = vplot(input_widgets, data_table)
    bokeh.io.show(p)
    

plot_datatable(elements)



In [ ]: