def verbose_formatter(): ''' Format bokeh time axis verbosely ''' vf = DatetimeTickFormatter() vf.microseconds = ['%f us'] vf.milliseconds = ['%S.%3N s'] # vf.milliseconds = ['%H:%M:%S.%3N'] vf.seconds = ['%H:%M:%S'] vf.minsec = ['%H:%M:%S'] vf.minutes = ['%m/%d %H:%M'] vf.hourmin = ['%m/%d %H:%M'] vf.hours = ['%m/%d %H:%M'] vf.days = ['%m/%d', '%a%d'] vf.months = ['%m/%Y', '%b %Y'] vf.years = ['%Y'] return vf
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