from __future__ import division import math from collections import OrderedDict from bokeh.io import curdoc from bokeh.plotting import Figure from bokeh.models import ColumnDataSource, CustomJS from bokeh.tile_providers import get_provider from bokeh.models import VBox, HBox, Paragraph, Select from bokeh.palettes import BuGn9 import pandas as pd import datashader as ds import datashader.transfer_functions as tf def bin_data(): global time_period, grouped, group_count, counter, times, groups grouped = df.groupby([times.hour, times.minute // time_period]) groups = sorted(grouped.groups.keys(), key=lambda r: (r[0], r[1])) group_count = len(groups) counter = 0 def on_time_select_change(attr, old, new): global time_period, counter, time_select_options time_period = time_select_options[new] counter = 0 bin_data() counter = 0 def update_data(): global dims, grouped, group_count, counter, time_text, time_period dims_data = dims.data if not dims_data['width'] or not dims_data['height']: return group_num = counter % group_count group = groups[group_num] grouped_df = grouped.get_group(group) update_image(grouped_df) # update time text num_minute_groups = 60 // time_period mins = group[1] * time_period hr = group[0] end_mins = ((group[1] + 1) % num_minute_groups) * time_period end_hr = hr if end_mins > 0 else (hr + 1) % 24 time_text.text = 'Time Period: {}:{} - {}:{}'.format(str(hr).zfill(2), str(mins).zfill(2), str(end_hr).zfill(2), str(end_mins).zfill(2)) counter += 1 def update_image(dataframe): global dims dims_data = dims.data if not dims_data['width'] or not dims_data['height']: return plot_width = int(math.ceil(dims_data['width'][0])) plot_height = int(math.ceil(dims_data['height'][0])) x_range = (dims_data['xmin'][0], dims_data['xmax'][0]) y_range = (dims_data['ymin'][0], dims_data['ymax'][0]) canvas = ds.Canvas(plot_width=plot_width, plot_height=plot_height, x_range=x_range, y_range=y_range) agg = canvas.points(dataframe, 'dropoff_x', 'dropoff_y', ds.count('trip_distance')) img = tf.shade(agg, cmap=BuGn9, how='log') new_data = {} new_data['image'] = [img.data] new_data['x'] = [x_range[0]] new_data['y'] = [y_range[0]] new_data['dh'] = [y_range[1] - y_range[0]] new_data['dw'] = [x_range[1] - x_range[0]] image_source.stream(new_data, 1) time_select_options = OrderedDict() time_select_options['1 Hour'] = 60 time_select_options['30 Minutes'] = 30 time_select_options['15 Minutes'] = 15 time_period = list(time_select_options.values())[0] time_select = Select.create(name="Time Period", options=time_select_options) time_select.on_change('value', on_time_select_change) time_text = Paragraph(text='Time Period') # load nyc taxi data path = './data/nyc_taxi.csv' datetime_field = 'tpep_dropoff_datetime' cols = ['dropoff_x', 'dropoff_y', 'trip_distance', datetime_field] df = pd.read_csv(path, usecols=cols, parse_dates=[datetime_field]).dropna(axis=0) times = pd.DatetimeIndex(df[datetime_field]) group_count = grouped = groups = None bin_data() # manage client-side dimensions dims = ColumnDataSource(data=dict(width=[], height=[], xmin=[], xmax=[], ymin=[], ymax=[])) dims_jscode = """ var update_dims = function () { var new_data = { height: [plot.frame.height], width: [plot.frame.width], xmin: [plot.x_range.start], ymin: [plot.y_range.start], xmax: [plot.x_range.end], ymax: [plot.y_range.end] }; dims.data = new_data; }; if (typeof throttle != 'undefined' && throttle != null) { clearTimeout(throttle); } throttle = setTimeout(update_dims, 100, "replace"); """ # Create plot ------------------------------- xmin = -8240227.037 ymin = 4974203.152 xmax = -8231283.905 ymax = 4979238.441 fig = Figure(x_range=(xmin, xmax), y_range=(ymin, ymax), plot_height=600, plot_width=900, tools='pan,wheel_zoom') fig.background_fill_color = 'black' fig.add_tile(get_provider("STAMEN_TONER"), alpha=.3) fig.x_range.callback = CustomJS(code=dims_jscode, args=dict(plot=fig, dims=dims)) fig.y_range.callback = CustomJS(code=dims_jscode, args=dict(plot=fig, dims=dims)) fig.axis.visible = False fig.grid.grid_line_alpha = 0 fig.min_border_left = 0 fig.min_border_right = 0 fig.min_border_top = 0 fig.min_border_bottom = 0 image_source = ColumnDataSource(dict(image=[], x=[], y=[], dw=[], dh=[])) fig.image_rgba(source=image_source, image='image', x='x', y='y', dw='dw', dh='dh', dilate=False) time_text = Paragraph(text='Time Period: 00:00 - 00:00') controls = HBox(children=[time_text, time_select], width=fig.plot_width) layout = VBox(children=[fig, controls]) curdoc().add_root(layout) curdoc().add_periodic_callback(update_data, 1000)