def get_checkbox_btn_with_filter(labels: List[str], column_label: str, source): """ :param labels: names for checkbox labels as list (dataframe must contain it as value to filter) :param column_label: name of column in dataframe :param source: dataframe :return: checkboxes and filter for graph """ # routes checkboxes checkboxes = CheckboxButtonGroup(labels=labels, width=65) filter = CustomJSFilter(code=''' var selected = checkboxes.active.map(i=>true); var indices = []; var column = source.data[column_label]; // iterate through rows of data source and see if each satisfies some constraint if(!selected.length){ return Array(column.length).fill(true) } for (var i = 0; i < column.length; i++){ if(selected.includes(column[i])){ indices.push(true); } else { indices.push(false); } } console.log("filter completed"); return indices; ''', args=dict(checkboxes=checkboxes, column_label=column_label)) checkboxes.js_on_change( "active", CustomJS(code="source.change.emit();", args=dict(source=source))) widgets = [checkboxes] return Column(*widgets), filter
def plot_time_series(activity: models.Activity): """ Plotting function to create the time series plots shown in tha activity page. Depending on what data is available this creates the following plots: - Altitude - Heart Rate - Speed - Cadence - Temperature All plots share a connected vertical cross hair tools. Parameters ---------- activity : models.Activity Activity model containing the required activity for which the plots should be generated Returns ------- script, div : tuple(str, str) the html script and div elements used to render the plots in the html templates """ attributes = activity.trace_file.__dict__ coordinates = json.loads(attributes["coordinates_list"]) initial_list_of_distances = [] list_of_distances = [] if coordinates: initial_list_of_distances = convert_list_to_km( json.loads(attributes['distance_list'])) list_of_distances = extend_list_to_have_length( length=len(coordinates), input_list=initial_list_of_distances) lap_data = models.Lap.objects.filter(trace=activity.trace_file) plots = [] lap_lines = [] for attribute, values in attributes.items(): if attribute in attributes_to_create_time_series_plot_for: values = json.loads(values) if values: attribute = attribute.replace("_list", "") if activity.distance: x_axis = extend_list_to_have_length( length=len(values), input_list=initial_list_of_distances) p = figure(plot_height=int(settings.PLOT_HEIGHT / 2.5), sizing_mode='stretch_width', y_axis_label=plot_matrix[attribute]["axis"], x_range=(0, x_axis[-1])) lap = _add_laps_to_plot(laps=lap_data, plot=p, y_values=values) x_hover = ("Distance", "@x km") else: # activity has no distance data, use time for x-axis instead timestamps_list = json.loads(attributes["timestamps_list"]) start = timestamp_to_local_time(timestamps_list[0]) x_axis = [ timestamp_to_local_time(t) - start for t in timestamps_list ] x_axis, values = cut_list_to_have_same_length( x_axis, values) p = figure(x_axis_type='datetime', plot_height=int(settings.PLOT_HEIGHT / 2.5), sizing_mode='stretch_width', y_axis_label=plot_matrix[attribute]["axis"]) lap = _add_laps_to_plot(laps=lap_data, plot=p, y_values=values, x_start_value=x_axis[0], use_time=True) x_hover = ("Time", "@x") lap_lines += lap p.toolbar.logo = None p.toolbar_location = None p.xgrid.grid_line_color = None if attribute == 'cadence': p.scatter(x_axis, values, radius=0.01, fill_alpha=1, color=plot_matrix[attribute]["color"], legend_label=plot_matrix[attribute]["title"]) elif attribute == 'altitude': p.varea(x_axis, values, [min(values) for i in range(len(values))], color=plot_matrix[attribute]["second_color"], fill_alpha=0.5) p.line(x_axis, values, line_width=2, color=plot_matrix[attribute]["color"], legend_label=plot_matrix[attribute]["title"]) else: p.line(x_axis, values, line_width=2, color=plot_matrix[attribute]["color"], legend_label=plot_matrix[attribute]["title"]) hover = HoverTool(tooltips=[ (plot_matrix[attribute]['title'], f"@y {plot_matrix[attribute]['axis']}"), x_hover ], mode='vline') p.add_tools(hover) cross = CrosshairTool(dimensions="height") p.add_tools(cross) p.legend.location = "top_left" p.legend.label_text_font = "ubuntu" p.legend.background_fill_alpha = 0.9 plots.append(p) _link_all_plots_with_each_other(all_plots=plots, x_values=list_of_distances) # TODO # connect all plots and share hovering line # add hover info for each lap line with lap data info all_plots = column(*plots) all_plots.sizing_mode = "stretch_width" if lap_data: log.debug(f"found some Lap data for {activity}: {lap_data}") checkbox = CheckboxButtonGroup(labels=["Show Laps"], active=[0], width=100) js = """ for (line in laps) { laps[line].visible = false; if (typeof markerGroup != "undefined") { markerGroup.removeFrom(map); } } for (i in cb_obj.active) { if (cb_obj.active[i] == 0) { for (line in laps) { laps[line].visible = true; if (typeof markerGroup != "undefined") { markerGroup.addTo(map); } } } } """ callback = CustomJS(args=dict(laps=lap_lines, checkbox=checkbox), code=js) checkbox.js_on_change('active', callback) layout = column(all_plots, checkbox) layout.sizing_mode = 'stretch_width' script, div = components(layout) else: script, div = components(all_plots) return script, div
def plot_time_series(activity: models.Activity): """ Plotting function to create the time series plots shown in tha activity page. Depending on what data is available this creates the following plots: - Altitude - Heart Rate - Speed - Cadence - Temperature All plots share a connected vertical cross hair tools. Parameters ---------- activity : models.Activity Activity model containing the required activity for which the plots should be generated Returns ------- script, div : tuple(str, str) the html script and div elements used to render the plots in the html templates """ attributes = activity.trace_file.__dict__ lap_data = models.Lap.objects.filter(trace=activity.trace_file) plots = [] lap_lines = [] timestamps = pd.to_datetime(pd.Series(json.loads( attributes["timestamps_list"]), dtype=float), unit="s") x_axis = pd.to_datetime(timestamps).dt.tz_localize("utc").dt.tz_convert( settings.TIME_ZONE) x_axis = x_axis - x_axis.min() for attribute, values in attributes.items(): if attribute in attributes_to_create_time_series_plot_for: values = pd.Series(json.loads(values), dtype=float) if values.any(): attribute = attribute.replace("_list", "") p = figure( x_axis_type="datetime", plot_height=int(settings.PLOT_HEIGHT / 2.5), sizing_mode="stretch_width", y_axis_label=plot_matrix[attribute]["axis"], ) lap = _add_laps_to_plot(laps=lap_data, plot=p, y_values=values) lap_lines += lap if attribute == "altitude": p.varea( x=x_axis, y1=values, y2=values.min(), color=plot_matrix[attribute]["second_color"], fill_alpha=0.5, ) p.line( x_axis, values, line_width=2, color=plot_matrix[attribute]["color"], legend_label=plot_matrix[attribute]["title"], ) else: p.line( x_axis, values, line_width=2, color=plot_matrix[attribute]["color"], legend_label=plot_matrix[attribute]["title"], ) x_hover = ("Time", "@x") hover = HoverTool( tooltips=[(plot_matrix[attribute]["title"], f"@y {plot_matrix[attribute]['axis']}"), x_hover], mode="vline", ) p.add_tools(hover) cross = CrosshairTool(dimensions="height") p.add_tools(cross) p.toolbar.logo = None p.toolbar_location = None p.xgrid.grid_line_color = None p.legend.location = "top_left" p.legend.label_text_font = "ubuntu" p.legend.background_fill_alpha = 0.7 dtf = DatetimeTickFormatter() dtf.minutes = ["%M:%S"] p.xaxis.formatter = dtf p.xaxis.major_label_overrides = {0: "0:00"} plots.append(p) values.ffill(inplace=True) values.bfill(inplace=True) x_axis.ffill(inplace=True) x_axis.bfill(inplace=True) _link_all_plots_with_each_other(all_plots=plots, x_values=x_axis) all_plots = column(*plots) all_plots.sizing_mode = "stretch_width" if lap_data: # include button to toggle rendering of laps log.debug(f"found some Lap data for {activity}: {lap_data}") checkbox = CheckboxButtonGroup(labels=["Show Laps"], active=[0], width=100) js = """ for (line in laps) { laps[line].visible = false; if (typeof markerGroup != "undefined") { markerGroup.removeFrom(map); } } for (i in cb_obj.active) { if (cb_obj.active[i] == 0) { for (line in laps) { laps[line].visible = true; if (typeof markerGroup != "undefined") { markerGroup.addTo(map); } } } } """ callback = CustomJS(args=dict(laps=lap_lines, checkbox=checkbox), code=js) checkbox.js_on_change("active", callback) layout = column(all_plots, checkbox) layout.sizing_mode = "stretch_width" script, div = components(layout) else: script, div = components(all_plots) return script, div