Source code for trajectory_supervisor.visualization.src.PlotHandler

import io
import dill
import matplotlib.pyplot as plt
import matplotlib.patches as ptch
import matplotlib.gridspec as gridspec
import matplotlib.patheffects as patheffcts
from matplotlib.widgets import RadioButtons, Button
from sklearn.metrics.pairwise import euclidean_distances
import numpy as np
import numpy.matlib as npm

# Show plot in serif font (e.g. for publications)
# import matplotlib
# matplotlib.rcParams['mathtext.fontset'] = 'stix'
# matplotlib.rcParams['font.family'] = 'STIXGeneral'

# TUM Colors
TUM_colors = {
    'TUM_blue': '#3070b3',
    'TUM_blue_dark': '#003359',
    'TUM_blue_medium': '#64A0C8',
    'TUM_blue_light': '#98C6EA',
    'TUM_grey_dark': '#9a9a9a',
    'TUM_orange': '#E37222',
    'TUM_green': '#A2AD00'
}


[docs]class PlotHandler(object): """ This class provides several functions to plot the debug visualization for safety assessment logs. :Authors: * Tim Stahl <tim.stahl@tum.de> :Created on: 24.01.2020 """ def __init__(self, plot_title="Log Visualization Tool") -> None: """ :param plot_title: string specifying the figure title """ # define canvas self.__fig = plt.figure(plot_title) # define axes gs = gridspec.GridSpec(3, 1, height_ratios=[1, 2, 4]) # -- TIME EVENT PLOT (RUN OVERVIEW) ---------------------------------------------------------------------------- plt.subplot(gs[0]) self.__time_ax = self.__fig.gca() self.__time_ax.set_ylim([-0.5, 3.5]) self.__time_ax.set_yticks([0, 1, 2, 3]) self.__time_ax.set_yticklabels(['DATA', 'INFO', 'WARNING', 'CRITICAL']) self.__time_ax2 = self.__time_ax.twinx() self.__time_ax2.set_title("Run analysis") self.__time_ax2.set_xlabel('$t$ in s') self.__time_ax2.set_ylabel('Safety Scores') self.__time_ax2.set_ylim([-0.5, 4.5]) self.__time_ax2.set_yticks([0.5, 2.0, 3.5]) self.__time_ax2.set_yticklabels(['Static', 'Dynamic', 'Overall']) self.__time_ax2.grid() # in order to still enable onhover event with twinx self.__time_event_ax = self.__time_ax.figure.add_axes(self.__time_ax.get_position(True), sharex=self.__time_ax, sharey=self.__time_ax, frameon=False) self.__time_event_ax.xaxis.set_visible(False) self.__time_event_ax.yaxis.set_visible(False) # force cursor coordinates to not use scientific mode (exponential notation) self.__time_event_ax.format_coord = lambda x, y: f"x={x:.2f}, y={y:.2f}" # -- TIME EVENT PLOT (AT CURRENT TIME STEP) -------------------------------------------------------------------- plt.subplot(gs[1]) self.__time_rel_ax = self.__fig.gca() self.__time_rel_ax.set_title("Time step analysis") self.__time_rel_ax.set_xlabel('$t$ in s') self.__time_rel_ax.set_ylabel('$v_x$ in m/s') self.__time_rel_ax.set_xlim([0.0, 5.0]) self.__time_rel_ax.set_ylim([0.0, 60.0]) self.__time_rel_ax.grid() self.__time_rel_ax.grid(which='minor', linestyle=':', linewidth='0.5', color='gray') self.__time_rel_ax.grid(which='major', linestyle='-', linewidth='0.6', color='gray') self.__time_rel_ax.minorticks_on() self.__time_rel_ax2 = self.__time_rel_ax.twinx() self.__time_rel_ax2.set_ylabel(r'$\kappa$ in 1/m\n' r'$\psi$ in rad/(10$\pi$)') self.__time_rel_ax2.set_ylim([-0.1, 0.1]) red_patch = ptch.Patch(color=TUM_colors['TUM_orange'], label='$v_x$') blue_patch = ptch.Patch(color=TUM_colors['TUM_blue'], label=r'$\kappa$') green_patch = ptch.Patch(color=TUM_colors['TUM_green'], label=r'$\psi$') self.__time_rel_ax2.legend(handles=[red_patch, blue_patch, green_patch]) # -- MAIN PLOT (MAP OVERVIEW) ---------------------------------------------------------------------------------- plt.subplot(gs[2]) self.__main_ax = self.__fig.gca() # configure main axis self.__main_ax.grid() self.__main_ax.set_aspect("equal", "datalim") self.__main_ax.set_xlabel("East in m") self.__main_ax.set_ylabel("North in m") # containers self.__highlight_paths = dict() self.__highlight_pos = dict() self.__highlight_time = dict() self.__veh_ptch_hdl = dict() self.__polygon_ptch_hdl = dict() self.__reach_sets_ptch_hdl = dict() self.__track_hdl = dict() self.__veh_text_handle = dict() self.__text_display = None self.__text_display2 = None self.__time_annotation = None self.__time_rel_line_handle = None self.__fig.canvas.mpl_connect('motion_notify_event', lambda event: self.onhover(event=event)) self.__text_display = self.__main_ax.text(0.02, 0.95, "", transform=plt.gcf().transFigure) self.__text_display2 = self.__main_ax.text(0.8, 0.9, "", transform=plt.gcf().transFigure) self.__time_annotation = self.__time_ax.annotate("", xy=(0, 0), xytext=(0.14, 0.95), textcoords='figure fraction', bbox=dict(boxstyle="round", fc="w"), arrowprops=dict(arrowstyle="->")) # Radio buttons for traj.-type selection rax = self.__fig.add_axes([0.01, 0.89, 0.12, 0.1], facecolor='lightgrey') self.__radio = RadioButtons(rax, ('perf', 'emerg'), active=0) self.__radio.on_clicked(self.toggled_radio) # Open plot window button self.__button_plt_wndw = Button(self.__fig.add_axes([0.84, 0.01, 0.15, 0.04]), 'Open Plot', color='lightgray', hovercolor='0.975') self.__button_plt_wndw.on_clicked(self.open_plot_window) # event handler variables self.__node_plot_marker = None self.__edge_plot_marker = None self.__annotation = None self.__time_marker = None self.__lambda_fct = None self.__last_t_stamp = 0.0 # friction plot self.__fig_acc = None self.__data_acc_valid = None self.__data_acc_invalid = None
[docs] def toggled_radio(self, _) -> None: """ Function called when the radio buttons are toggled. :param _: parameter that is handed by event, but unused """ self.force_update()
[docs] def plot_map(self, bound_l: np.ndarray, bound_r: np.ndarray) -> None: """ Visualization of the map. :param bound_l: coordinates of the left bound :param bound_r: coordinates of the right bound """ # delete highlighted positions with handle for key in self.__track_hdl.keys(): self.__track_hdl[key].remove() del self.__track_hdl[key] if bound_l is not None and bound_r is not None: # close path, if relevant if (np.hypot(bound_l[0, 0] - bound_l[-1, 0], bound_l[0, 1] - bound_l[-1, 1]) < 31.0 and np.hypot(bound_r[0, 0] - bound_r[-1, 0], bound_r[0, 1] - bound_r[-1, 1]) < 31.0): bound_l = np.vstack((bound_l, bound_l[0, :])) bound_r = np.vstack((bound_r, bound_r[0, :])) # track patch patch_xy = np.vstack((bound_l, np.flipud(bound_r))) track_ptch = ptch.Polygon(patch_xy, facecolor="black", alpha=0.2, zorder=1) self.__track_hdl['patch'] = self.__main_ax.add_artist(track_ptch) # connecting lines n_shared_el = int(min(bound_l.size / 2, bound_r.size / 2)) data_bound_lines = [[], []] if n_shared_el > 1: for i in range(n_shared_el): data_bound_lines[0].extend([bound_l[i, 0], bound_r[i, 0], None]) data_bound_lines[1].extend([bound_l[i, 1], bound_r[i, 1], None]) self.__track_hdl['normals'], = self.__main_ax.plot(data_bound_lines[0], data_bound_lines[1], '--', color=TUM_colors['TUM_grey_dark'], zorder=5) # bound lines x = list(bound_l[:, 0]) y = list(bound_l[:, 1]) x.append(None) y.append(None) x.extend(list(bound_r[:, 0])) y.extend(list(bound_r[:, 1])) self.__track_hdl['bounds'], = self.__main_ax.plot(x, y, "k-", linewidth=1.4, label="Bounds", zorder=6)
[docs] def plot_vehicle(self, pos: np.ndarray, heading: list, width: float, length: float, zorder: int = 10, color_str: str = 'blue', id_in: str = 'default', alpha: float = 1.0) -> None: """ Plot the pose and shape of one or multiple vehicles. :param pos: numpy array holding one or multiple positions :param heading: list of floats holding the heading for each position :param width: width of the vehicle in meters :param length: length of the vehicle in meters :param zorder: (optional) z-order of the vehicle :param color_str: (optional) string specifying the color of the plotted vehicle :param id_in: (optional) string specifying the id (used to delete the same type of vehicle plotted prev.) :param alpha: (optional) transparency setting [0, 1] """ # check if color string defined in TUM colors if color_str in TUM_colors.keys(): color_str = TUM_colors[color_str] # force position to be 2-dimensional pos = np.atleast_2d(pos) # delete highlighted positions with handle if id_in in self.__veh_ptch_hdl.keys(): self.__veh_ptch_hdl[id_in].remove() del self.__veh_ptch_hdl[id_in] # check for further time instances from the last time-step counter = 1 while id_in + "_i" + str(counter) in self.__veh_ptch_hdl.keys(): self.__veh_ptch_hdl[id_in + "_i" + str(counter)].remove() del self.__veh_ptch_hdl[id_in + "_i" + str(counter)] counter += 1 counter = 0 # for every position to be plotted for head in heading: theta = head - np.pi / 2 pos_i = pos[counter, :] bbox = (npm.repmat([[pos_i[0]], [pos_i[1]]], 1, 4) + np.matmul([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]], [[-length / 2, length / 2, length / 2, -length / 2], [-width / 2, -width / 2, width / 2, width / 2]])) patch = np.array(bbox).transpose() patch = np.vstack((patch, patch[0, :])) # for counter >0 generate further ids: handle_id = id_in if counter > 0: handle_id += "_i" + str(counter) plt_patch = ptch.Polygon(patch, facecolor=color_str, linewidth=1, edgecolor='k', zorder=zorder, alpha=alpha) self.__veh_ptch_hdl[handle_id] = self.__main_ax.add_artist(plt_patch) counter += 1
[docs] def plot_text(self, pos: np.ndarray, heading: list, text_list: list, text_dist: float = 10.0, plot_ith: int = 2, avoid_pos: np.ndarray = None, zorder: int = 100, id_in: str = 'default') -> None: """ Plot text (perpendicular) next to poses. The heading is used to determine in a perpendicular direction the offset of the text to the pose (allows to plot text next to certain poses on a spline). If empty arrays are provided, all previous instances with the specified 'id_in' will be removed. :param pos: numpy array holding one or multiple positions (columns x, y; each row a pose) :param heading: list of floats holding the heading for each position (if only one pose, list with one float) :param text_list: list of texts to be printed next to each pose (if only one pose, list with one text) :param text_dist: (optional) distance the text should be away from 'pos' in m :param plot_ith: (optional) plot only the text for every i-th pose (e.g. if '1' every pose, if '2' every 2nd) :param avoid_pos: (optional) numpy array of positions to be avoided by text -> Select favored side (left/right) of heading :param zorder: (optional) z-order of the vehicle :param id_in: (optional) string specifying the id (used to delete same type of vehicle plotted previously) :return: """ # force position to be 2-dimensional pos = np.atleast_2d(pos) # delete highlighted positions with handle if id_in in self.__veh_text_handle.keys(): self.__veh_text_handle[id_in].remove() del self.__veh_text_handle[id_in] # check for further time instances from the last time-step counter = 0 + plot_ith while id_in + "_i" + str(counter) in self.__veh_text_handle.keys(): self.__veh_text_handle[id_in + "_i" + str(counter)].remove() del self.__veh_text_handle[id_in + "_i" + str(counter)] counter += plot_ith # return, if no data provided if not heading: return # check which side of heading occupies less instances of "avoid_pos" theta_l = np.array(heading) + np.pi / 2 theta_r = np.array(heading) - np.pi / 2 pos_l = np.column_stack((pos[:, 0] - np.sin(theta_l) * text_dist, pos[:, 1] + np.cos(theta_l) * text_dist)) pos_r = np.column_stack((pos[:, 0] - np.sin(theta_r) * text_dist, pos[:, 1] + np.cos(theta_r) * text_dist)) if avoid_pos is not None: # force position to be 2-dimensional avoid_pos = np.atleast_2d(avoid_pos) # calculate distances between all combinations of pos and avoid pos dist_l = euclidean_distances(pos_l, avoid_pos) dist_r = euclidean_distances(pos_r, avoid_pos) # get number of points that are smaller than the plot text distance num_dist_l = sum(i < text_dist for i in dist_l.min(axis=1)) num_dist_r = sum(i < text_dist for i in dist_r.min(axis=1)) if num_dist_l < num_dist_r: pos_text = pos_l else: pos_text = pos_r else: pos_text = pos_l counter = 0 # for every position to be plotted for head, text in zip(heading, text_list): if counter % plot_ith != 0: counter += 1 continue pos_i = pos_text[counter, :] # for counter >0 generate further ids: handle_id = id_in if counter > 0: handle_id += "_i" + str(counter) self.__veh_text_handle[handle_id] = self.__main_ax.text(pos_i[0], pos_i[1], text, rotation=0.0, verticalalignment="center", horizontalalignment="center", clip_on=True, zorder=zorder) self.__veh_text_handle[handle_id].set_path_effects([patheffcts.withStroke(linewidth=2, foreground='w')]) counter += 1
[docs] def plot_polygon(self, polygon_list: list, zorder: int = 10, color_str: str = 'blue', color_e_str: str = 'k', id_in: str = 'default', alpha: float = 0.5) -> None: """ Plot one / multiple polygon patches. :param polygon_list: list of polygon coordinates (each given as an np.ndarray with columns [x, y]) :param zorder: (optional) z-order of the vehicle :param color_str: (optional) string specifying the color of the plotted vehicle :param color_e_str: (optional) string specifying the color of the outline for the plotted vehicle :param id_in: (optional) string specifying the id (used to delete the same type of vehicle plotted prev.) :param alpha: (optional) transparency setting [0, 1] """ # check if color string defined in TUM colors if color_str in TUM_colors.keys(): color_str = TUM_colors[color_str] # Delete highlighted reachable sets with handle if id_in in self.__polygon_ptch_hdl.keys(): self.__polygon_ptch_hdl[id_in].remove() del self.__polygon_ptch_hdl[id_in] # Check for further time instances from the last time-step counter = 1 while id_in + '_i' + str(counter) in self.__polygon_ptch_hdl.keys(): self.__polygon_ptch_hdl[id_in + "_i" + str(counter)].remove() del self.__polygon_ptch_hdl[id_in + "_i" + str(counter)] counter += 1 counter = 0 # For every polygon in list for polygon_coords in polygon_list: # for counter >0 generate further ids: handle_id = id_in if counter > 0: handle_id += "_i" + str(counter) plt_patch = ptch.Polygon(polygon_coords, closed=True, facecolor=color_str, alpha=alpha, zorder=zorder, linewidth=1, edgecolor=color_e_str, fill=(color_str is not None)) self.__polygon_ptch_hdl[handle_id] = self.__main_ax.add_artist(plt_patch) counter += 1
[docs] def update_text_field(self, text_str: str, color_str: str = 'k', text_field_id: int = 1) -> None: """ Update the text field in the plot window :param text_str: text string to be displayed :param color_str: (optional) string specifying the color of the plotted text :param text_field_id: (optional) id specifying the text field to be updated (default: 1) """ if text_field_id == 1: self.__text_display.set_text(text_str) self.__text_display.set_color(color_str) elif text_field_id == 2: self.__text_display2.set_text(text_str) self.__text_display2.set_color(color_str) else: print("No text_field_id '%i' defined!" % text_field_id)
[docs] def highlight_pos(self, pos_coords: list, color_str: str = 'y', zorder: int = 10, radius=None, marker: str = 'o', id_in: str = 'default') -> None: """ Highlight a position with an plot marker ('´marker´') or a circle with a parametrizable radius ('`radius`'). If '`radius`' is not provided or 'None', the marker option is used. :param pos_coords: coordinates of the position ot be highlighted :param color_str: (optional) string specifying the color of the highlight :param zorder: (optional) z-order of the highlight marker (default: 10) :param radius: (optional) radius of the marker to be displayed (if not provided, a standard marker is used) :param marker: (optional) string specifying the marker style :param id_in: (optional) string specifying the id (used to delete the same type of vehicle plotted prev.) """ # check if color string defined in TUM colors if color_str in TUM_colors.keys(): color_str = TUM_colors[color_str] # delete highlighted positions with handle if id_in in self.__highlight_pos.keys(): self.__highlight_pos[id_in].remove() del self.__highlight_pos[id_in] # plot pos if pos_coords: if radius is None: self.__highlight_pos[id_in], = \ self.__main_ax.plot(pos_coords[0], pos_coords[1], 's', color=color_str, marker=marker, zorder=zorder) else: plt_circle = plt.Circle(tuple(pos_coords), radius, color=color_str, fill=True, zorder=zorder) self.__highlight_pos[id_in] = self.__main_ax.add_artist(plt_circle)
[docs] def highlight_time(self, time_coords: list, color_str: str = 'y', zorder: int = 10, marker: str = 'o', id_in: str = 'default') -> None: """ Highlight a position with an plot marker ('´marker´') in the temporal plot. :param time_coords: coordinates of the time entry to be highlighted :param color_str: (optional) string specifying the color of the highlight :param zorder: (optional) z-order of the highlight marker (default: 10) :param marker: (optional) string specifying the marker style :param id_in: (optional) string specifying the id (used to delete the same type of vehicle plotted prev.) """ # check if color string defined in TUM colors if color_str in TUM_colors.keys(): color_str = TUM_colors[color_str] # delete highlighted positions with handle if id_in in self.__highlight_time.keys(): self.__highlight_time[id_in].remove() del self.__highlight_time[id_in] # plot pos if time_coords: self.__highlight_time[id_in], = \ self.__time_rel_ax.plot(time_coords[0], time_coords[1], 's', color=color_str, marker=marker, zorder=zorder)
[docs] def highlight_path(self, path_coords: np.ndarray, id_in: str = 'default', color_str: str = 'red', linewidth: float = 1.4, zorder: int = 99) -> None: """ Highlight a given coordinate sequence. :param path_coords: coordinates of path to be plotted (each coordinate in separate column) :param id_in: (optional) id used for the handle (plots with same id will be removed before plot of next) :param color_str: (optional) color to be used for path highlight :param linewidth: (optional) linewidth of the highlighted path (default: 1.4) :param zorder: (optional) z-order of the highlighted path (default: 99) """ # check if color string defined in TUM colors if color_str in TUM_colors.keys(): color_str = TUM_colors[color_str] # delete highlighted paths with handle if id_in in self.__highlight_paths.keys(): self.__highlight_paths[id_in].remove() del self.__highlight_paths[id_in] # plot the spline if path_coords.size > 0: self.__highlight_paths[id_in], = self.__main_ax.plot(path_coords[:, 0], path_coords[:, 1], color=color_str, linewidth=linewidth, label="Local Path", zorder=zorder)
[docs] def plot_timeline_stamps(self, time_stamps: list, types: list, lambda_fct) -> None: """ Plot a timeline information for a log file :param time_stamps: list of float values holding time in seconds :param types: list of strings indicating the type of the provided timestamp :param lambda_fct: lambda function to be handed (to be called when mouse hovers over time-line) """ type_names = ["DATA", "INFO", "WARNING", "CRITICAL", "DEBUG"] type_marker = {"DATA": "gx", "INFO": "bx", "WARNING": "yx", "CRITICAL": "rx", "DEBUG": "cx"} for i, type_name in enumerate(type_names): if type_name in types: rel_time_stamps = [x for x, t in zip(time_stamps, types) if t == type_name] self.__time_ax.plot(rel_time_stamps, np.zeros((len(rel_time_stamps), 1)) + i, type_marker[type_name]) time_line_marker, = self.__time_ax.plot([], [], 'r-') self.set_time_markers(time_line_marker=time_line_marker, lambda_fct=lambda_fct)
[docs] def plot_timeline_course(self, line_coords_list: list) -> None: """ Plot three curves in the temporal information subplot. :param line_coords_list: list of cuvre courses """ # color masks color = [TUM_colors['TUM_green'], TUM_colors['TUM_blue'], TUM_colors['TUM_orange']] group_axes = [self.__time_ax2, self.__time_ax2, self.__time_ax2] # plot lines in array (plot reversed, such that the last option is on the lowest layer in the plot) for idx, line_coords in enumerate(line_coords_list): line_coords = np.array(line_coords) group_axes[idx].plot(line_coords[:, 0], line_coords[:, 1], color[idx], linewidth=1.4, label="Local Path", zorder=0)
[docs] def highlight_timeline(self, time_stamp: float, type_in: str, message: str) -> None: """ Display a text message from the log file, with an arrow pointing to the relevant timestamp. :param time_stamp: timestamp the arrow should point to :param type_in: string specifying the type of error (relevant for color) :param message: string of message to be displayed """ type_names = ["DATA", "INFO", "WARNING", "CRITICAL", "DEBUG"] type_marker = {"DATA": "g", "INFO": "b", "WARNING": "y", "CRITICAL": "r", "DEBUG": "c"} pos = [time_stamp, type_names.index(type_in)] self.__time_annotation.xy = pos self.__time_annotation.set_text(message) self.__time_annotation.get_bbox_patch().set_facecolor(type_marker[type_in]) self.__time_annotation.get_bbox_patch().set_alpha(0.4) self.__time_annotation.get_visible()
[docs] def plot_time_rel_line(self, line_coords_list: list) -> None: """ Highlight a list of given coordinate sequences (each with a different color) :param line_coords_list: list of lists (grouped sets) of lists holding the coordinates of paths each """ # color masks color_masks = [TUM_colors['TUM_orange'], TUM_colors['TUM_blue'], TUM_colors['TUM_green']] group_axes = [self.__time_rel_ax, self.__time_rel_ax2, self.__time_rel_ax2] # delete existing time courses with stored handle if self.__time_rel_line_handle is not None: for handle in self.__time_rel_line_handle: handle.remove() # self.__time_rel_line_handle = [] # plot lines in array (plot reversed, such that the last option is on the lowest layer in the plot) for group_idx, group_data in enumerate(line_coords_list): for idx, line_coords in enumerate(reversed(group_data)): # generate a color for the line fade_in_clr = (idx + 1) / len(group_data) # plot the spline line_coords = np.array(line_coords) temp_handle, = group_axes[group_idx].plot(line_coords[:, 0], line_coords[:, 1], color=color_masks[group_idx], linewidth=1.4, label="Local Path", zorder=99 + idx, alpha=fade_in_clr) # append handle to array self.__time_rel_line_handle.append(temp_handle)
[docs] def init_acc_plot(self, a_lat_max_tires: float, a_lon_max_tires: float, dyn_model_exp: float) -> None: """ Initialize acceleration data plot (friction circle) :param a_lat_max_tires: maximum allowed pure lateral acceleration :param a_lon_max_tires: maximum allowed pure longitudinal acceleration :param dyn_model_exp: dynamic model exponent """ if self.__fig_acc is None: self.__fig_acc = plt.figure("Acceleration Analysis") else: plt.figure(self.__fig_acc.number) # plot bounds a_lat_tmp = np.linspace(0, a_lat_max_tires, 100) a_lon_tmp = a_lon_max_tires * np.power(1.0 - np.power(a_lat_tmp / a_lat_max_tires, dyn_model_exp), 1.0 / dyn_model_exp) a_lat_plt = np.concatenate((a_lat_tmp, [None], -a_lat_tmp, [None], -a_lat_tmp, [None], a_lat_tmp)) a_lon_plt = np.concatenate((a_lon_tmp, [None], a_lon_tmp, [None], -a_lon_tmp, [None], -a_lon_tmp)) plt.plot(a_lat_plt, a_lon_plt, color=TUM_colors['TUM_blue'], label='Acc. limits') # generate data plot handles self.__data_acc_valid, = plt.plot([], [], 'x', color=TUM_colors['TUM_green'], label='Acc. limits respected') self.__data_acc_invalid, = plt.plot([], [], 'x', color=TUM_colors['TUM_orange'], label='Acc. limits violated') # plot setup plt.grid() plt.axis('equal') plt.xlabel('Lateral acceleration in m/s²') plt.ylabel('Longitudinal acceleration m/s²') plt.legend(loc='upper left') plt.draw() # set main figure as active figure plt.figure(self.__fig.number)
[docs] def update_acc_plot(self, acc_limit_valid: list, acc_limit_invalid: list) -> None: """ Update acceleration data in friction circle plot. :param acc_limit_valid: acceleration coordinates (lat / long) holding an valid entry, plotted in green :param acc_limit_invalid: acceleration coordinates (lat / long) holding an invalid entry, plotted in red """ # if acceleration plot is not initialized yet if self.__fig_acc is None: print("Could not update plot, since acceleration plot was not initialized") return # set acceleration figure as active figure plt.figure(self.__fig_acc.number) # update data self.__data_acc_valid.set_data(acc_limit_valid[0], acc_limit_valid[1]) self.__data_acc_invalid.set_data(acc_limit_invalid[0], acc_limit_invalid[1]) # set main figure as active figure plt.figure(self.__fig.number)
# ------------------------------------------------------------------------------------------------------------------ # - EVENTS --------------------------------------------------------------------------------------------------------- # ------------------------------------------------------------------------------------------------------------------
[docs] def open_plot_window(self, _) -> None: """ Called when the 'Open Plot'-button is clicked. """ # hide text displays self.__text_display.set_visible(False) self.__text_display2.set_visible(False) # dump the whole plot in a pickle inx = list(self.__fig.axes).index(self.__main_ax) buf = io.BytesIO() dill.dump(self.__fig, buf) buf.seek(0) # load pickle in new plot figure (without buttons) fig_plot = dill.load(buf) fig = plt.gcf() fig.set_size_inches(3.8, 5.0, forward=True) # 3.5in is IEEE double-column width fig_plot.set_tight_layout(True) # delete everything except main axes for i, ax in enumerate(fig_plot.axes): if i != inx: fig_plot.delaxes(ax) fig_plot.show() # un-hide text displays self.__text_display.set_visible(True) self.__text_display2.set_visible(True)
[docs] def set_time_markers(self, time_line_marker, lambda_fct) -> None: """ Store the timeline-marker and event lambda function. :param time_line_marker: timeline-marker to be stored :param lambda_fct: lambda function ot be stored """ self.__time_marker = time_line_marker self.__lambda_fct = lambda_fct
[docs] def onhover(self, event): """ Function called, when the mouse is moved over the plot window. :param event: mouse event (also holding the position of the pointer) """ if self.__time_ax.get_window_extent().contains(event.x, event.y) and event.xdata is not None: self.__time_marker.set_data([event.xdata, event.xdata], self.__time_ax.get_ylim()) self.__fig.canvas.draw() self.__lambda_fct(event.xdata, self.__radio.value_selected) self.__last_t_stamp = event.xdata else: self.__lambda_fct(self.__last_t_stamp, self.__radio.value_selected)
[docs] def force_update(self) -> None: """ This function triggers an forced update. """ self.__lambda_fct(self.__last_t_stamp, self.__radio.value_selected)
[docs] @staticmethod def show_plot(non_blocking=False): if non_blocking: plt.draw() plt.pause(0.0001) else: plt.show()