def _show_graph(self, data_frame): #create the plot panels for the Agent_Tasks panels = self._create_task_panels(data_frame) top_left_x_range = panels[list(panels.keys())[0]]['panel1'].x_range #Altitude over ground pAltitude = figure(plot_width=800, plot_height=300, x_range=top_left_x_range) alti_legend = [] # Setting the second y axis range name and range pAltitude.extra_y_ranges = {"speed": Range1d(50, 120)} # Adding the second axis to the plot. pAltitude.add_layout( LinearAxis(y_range_name="speed", axis_label="IAS, TAS [Knots]"), 'right') altitudeLine = pAltitude.line(data_frame.index * self.step_time, data_frame['position_h_sl_ft'], line_width=2, color=Viridis4[2]) alti_legend.append(("Altitude [ftsl]", [altitudeLine])) kiasLine = pAltitude.line(data_frame.index * self.step_time, data_frame['velocities_vc_kts'], line_width=2, y_range_name="speed", color=Viridis4[1]) alti_legend.append(("Indicated Airspeed [KIAS]", [kiasLine])) tasLine = pAltitude.line(data_frame.index * self.step_time, data_frame['velocities_vtrue_kts'], line_width=2, y_range_name="speed", color=Viridis4[0]) alti_legend.append(("True Airspeed [KAS]", [tasLine])) pAltitude.extra_y_ranges.renderers = [ kiasLine, tasLine ] #this does not quite work: https://stackoverflow.com/questions/48631530/bokeh-twin-axes-with-datarange1d-not-well-scaling pAltitude.y_range.renderers = [altitudeLine] lg_alti = Legend(items=alti_legend, location=(0, 10), glyph_width=25, label_width=190) lg_alti.click_policy = "hide" pAltitude.add_layout(lg_alti, 'right') tAlti = Title() tAlti.text = 'Altitude and Speed [IAS, TAS] over Timesteps' pAltitude.title = tAlti pAltitude.xaxis.axis_label = 'timestep [s]' pAltitude.yaxis[0].axis_label = 'Altitude [ftsl]' pAltitude.legend.location = "center_right" pSideslip = figure(plot_width=800, plot_height=300, x_range=top_left_x_range) slip_legend = [] slip_skid_line = pSideslip.line(data_frame.index * self.step_time, data_frame['aero_beta_deg'], line_width=2, color=Viridis4[2]) slip_legend.append(("Sideslip", [slip_skid_line])) pSideslip.y_range.renderers = [slip_skid_line] lg_slip = Legend(items=slip_legend, location=(0, 10), glyph_width=25, label_width=190) lg_slip.click_policy = "hide" pSideslip.add_layout(lg_slip, 'right') tSlip = Title() tSlip.text = 'Sideslip' pSideslip.title = tSlip pSideslip.xaxis.axis_label = 'timestep [s]' pSideslip.yaxis[0].axis_label = 'Sideslip [deg]' pSideslip.legend.location = "center_right" #activate the zooming on all plots #this is not nice, but this not either: https://stackoverflow.com/questions/49282688/how-do-i-set-default-active-tools-for-a-bokeh-gridplot pAltitude.toolbar.active_scroll = pAltitude.toolbar.tools[ 1] #this selects the WheelZoomTool instance pSideslip.toolbar.active_scroll = pSideslip.toolbar.tools[ 1] #this selects the WheelZoomTool instance reset_output() # if self.env.meta_dict['model_type'] == 'trained': # discriminator = self.env.meta_dict['model_base_name']+"_%+.2f" % (data_frame['reward'].sum()) # self.env.meta_dict['model_discriminator'] = discriminator # else: # discriminator = self.env.meta_dict['model_discriminator'] ts = time.time() overshoot_frames_per_task = self._analyze_overshoot(data_frame) overshoot_divs = [ Div(text=ovs_fr.round(3).to_html(), width=600) for ovs_fr in overshoot_frames_per_task ] print("Overshoot analysis done in %.2f sec" % (time.time() - ts)) ts = time.time() settlement_times_per_task = self._analyze_settle_times(data_frame) settlement_divs = [ Div(text=settle_fr.round(3).to_html(), width=600) for settle_fr in settlement_times_per_task ] print("Settlement analysis done in %.2f sec" % (time.time() - ts)) panel_grid = [] panel_grid.append([ Div(text='<h3>' + t.name + '</h3>', id='div_' + t.name) for t in self.env.task_list ]) # to switch on and off the statistics panels, this is unfortuntely the best, I could achive # https://stackoverflow.com/a/52416676/2682209 cols = [] checkbox = CheckboxGroup( labels=["show stats"], active=[], width=100) #checkbox is added to header_col later on for i, t in enumerate(self.env.task_list): # overshoot_stat = overshoot_divs[i] c = column(Div()) #empty for the beginning cols.append(c) callback = CustomJS(args=dict(overshoot_divs=overshoot_divs, settlement_divs=settlement_divs, cols=cols, checkbox=checkbox), code=""" for (var j = 0; j < cols.length; j++) { console.log('col', j) const children = [] for (const i of checkbox.active) { console.log('active', i) children.push(overshoot_divs[j]) children.push(settlement_divs[j]) } console.log('children', children) cols[j].children = children } """) checkbox.js_on_change('active', callback) # show_stats_btn = [Div(text=""" # <button onclick="display_event(%s)">Try it</button> # """ %t.name for t in self.env.task_list] # for t, b in zip(self.env.task_list, show_stats_btn): # b.tags = ['id', 'btn_'+t.name] # panel_grid.append(show_stats_btn) # [b.js_on_event(events.ButtonClick, display_event(b, t.name)) for t, b in zip(self.env.task_list, show_stats_btn)] # panel_grid.append(chkbxs) panel_grid.append(cols) panel_grid_t = [[ panels[name]['panel1'], panels[name]['panel2'], panels[name]['panel3'] ] for name in self.task_names] [panel_grid.append(fig) for fig in list(zip(*panel_grid_t))] # add the additional plots panel_grid.append([pAltitude, pSideslip]) panel_grid_plot = gridplot(panel_grid, toolbar_location='right', sizing_mode='stretch_width') #for string formatting look here: https://pyformat.info/ titleString = '' if 'experiment_name' in self.env.meta_dict: titleString += "{}: ".format(self.env.meta_dict['experiment_name']) titleString += "Run Date: {}; ".format( datetime.datetime.now().strftime("%c")) if 'train_step' in self.env.meta_dict: titleString += "Training Step: {}; ".format( self.env.meta_dict['train_step']) if 'episode_number' in self.env.meta_dict: titleString += "Episode: {}; ".format( self.env.meta_dict['episode_number']) if 'csv_line_nr' in self.env.meta_dict: titleString += "Env in CSV line: {}; ".format( self.env.meta_dict['csv_line_nr']) # titleString += "Total Reward: {:.2f}; ".format(data_frame['reward'].sum()) # titleString += "Model Discriminator: {};".format(self.env.meta_dict['model_discriminator']) header_col = column( Div(text="<h1>" + self.env.unwrapped.spec.id + (" - " + self.env.meta_dict['env_info']) if 'env_info' in self.meta_dict else "" + "</h1>"), row(Div(text="<h2>" + titleString + "</h2>", width=1200), checkbox)) webpage = gridplot([[header_col], [panel_grid_plot]], toolbar_location=None, sizing_mode='stretch_width') base_filename = 'Run_' + '_'.join([tsk.name for tsk in self.task_list]) html_output_name = os.path.join(self.save_path, 'plots', base_filename + '_latest.html') os.makedirs(os.path.dirname(html_output_name), exist_ok=True) if self.showNextPlotFlag: output_file( html_output_name, mode='inline' ) #mode='absolute') #use mode='absolute' to make it work offline with the js and css installed in the bokeh package locally if self.firstRun: show(webpage) #opens up a new browser window self.firstRun = False else: save( webpage ) #just updates the HTML; Manual F5 in browser required :-(, (There must be a way to push...) if self.exportNextPlotFlag and self.save_path: #build the filename including the individual rewards task_rewards = [ self.reward_variables[i].get_legal_name() for i in range(len(self.task_list)) ] task_names_with_rewards = [ t.name + '_' + f'{data_frame[task_rewards[i]].sum():.2f}' for i, t in enumerate(self.task_list) ] name_with_rewards = 'Run_' + '_'.join( task_names_with_rewards) + 'time_{}'.format( datetime.datetime.now().strftime("%H-%M-%S")) base_filename = os.path.join( self.save_path, 'plots', name_with_rewards ) # 'glideAngle_Elevator_Reward_{:.2f}_time_{}'.format(data_frame['reward'].sum(), datetime.datetime.now().strftime("%H-%M-%S"))) if self.showNextPlotFlag: #we keep the html as well for easy exploration shutil.copyfile(html_output_name, base_filename + '.html') def export(webpage): png_filename = base_filename + '.png' webpage.width = 1800 #set the width of the page instead of passing a width parameter to the export; https://stackoverflow.com/a/61563173/2682209 export_png( webpage, filename=png_filename ) #TODO: the width parameter is ignored in bokeh/io/export.py get_layout_html() as webpage isn't a Plot export( gridplot([[header_col], [panel_grid_plot]], toolbar_location=None)) self.showNextPlotFlag = False #only show the plot once and then reset self.exportNextPlotFlag = False print("Output Plot generated: " + titleString)
def _create_task_panels(self, data_frame): panels = {} top_left_x_range = None for i, t in enumerate(self.env.task_list): panels[t.name] = {} # Panel 1: Setpoint and command panel pCtrl = figure(plot_width=800, plot_height=300) ctrl_legend = [] # Setting the second y axis range name and range pCtrl.extra_y_ranges = { t.name + '_cmd': Range1d(start=-1, end=1) } # this should query the action space # Adding the second axis to the plot. pCtrl.add_layout( LinearAxis(y_range_name=t.name + '_cmd', axis_label=t.name + '_cmd [norm.]'), 'right') try: name = self.panel_contents[ t.name]['panel1']['action_prop'].get_legal_name( ) #maybe there are more entries in the future action_Line = pCtrl.line(data_frame.index * self.step_time, data_frame[name], line_width=1, y_range_name=t.name + '_cmd', color=Viridis4[1]) ctrl_legend.append((t.name + ' Cmd.', [action_Line])) except KeyError: #we have no action prop, only setpoints to be evaluated pass try: name = self.panel_contents[ t.name]['panel1']['current_value_prop'].get_legal_name() current_value_line = pCtrl.line(data_frame.index * self.step_time, data_frame[name], line_width=2, color=Viridis4[0]) ctrl_legend.append((name, [current_value_line])) name = self.panel_contents[ t.name]['panel1']['setpoint_value_prop'].get_legal_name() setpoint_value_line = pCtrl.line(data_frame.index * self.step_time, data_frame[name], line_width=2, color=Viridis4[3]) ctrl_legend.append((name, [setpoint_value_line])) except KeyError: #there is no setpoint to be displayed, only actuations pass ctrl_lg = Legend(items=ctrl_legend, location=(0, 10), glyph_width=25, label_width=190) ctrl_lg.click_policy = "hide" pCtrl.add_layout(ctrl_lg, 'right') pCtrl.toolbar.active_scroll = pCtrl.toolbar.tools[ 1] #this selects the WheelZoomTool instance # Add the title... tCtrl = Title() tCtrl.text = 'Controlled Value over Time' pCtrl.title = tCtrl pCtrl.xaxis.axis_label = 'timestep [s]' pCtrl.yaxis[0].axis_label = 'Controlled Value' if not top_left_x_range: top_left_x_range = pCtrl.x_range else: pCtrl.x_range = top_left_x_range panels[t.name].update({'panel1': pCtrl}) #Panel 2: Rewards and reward components pRwd = figure(plot_width=1057, plot_height=300, x_range=top_left_x_range) rwd_cmp_lines = [] reward_legend = [] cmp_names = self.panel_contents[ t.name]['panel2']['reward_component_names'] for idx, rwd_component in enumerate(cmp_names): rwd_cmp_lines.append( pRwd.line(data_frame.index * self.step_time, data_frame[rwd_component], line_width=2, color=Viridis7[idx % 6])) reward_legend.append((rwd_component, [rwd_cmp_lines[-1]])) name = self.panel_contents[ t.name]['panel2']['reward_prop'].get_legal_name() reward_line = pRwd.line(data_frame.index * self.step_time, data_frame[name], line_width=2, color=Viridis7[6]) reward_legend.append((name, [reward_line])) reward_lg = Legend(items=reward_legend, location=(48, 10), glyph_width=25, label_width=190) reward_lg.click_policy = "hide" pRwd.add_layout(reward_lg, 'right') pRwd.toolbar.active_scroll = pRwd.toolbar.tools[ 1] #this selects the WheelZoomTool instance #Add the title tReward = Title() tReward.text = f'{t.name}: last Reward over {data_frame[name].size-1} Timesteps (∑ = {data_frame[name].sum():.2f})' pRwd.title = tReward pRwd.xaxis.axis_label = 'timestep [s]' pRwd.yaxis[0].axis_label = 'actual Reward [norm.]' panels[t.name].update({'panel2': pRwd}) #Panel 3: Presented Observations pState = figure(plot_width=1057, plot_height=300, x_range=top_left_x_range) # Setting the second y axis range name and range norm_state_extents = 10 pState.extra_y_ranges = { "normalized_data": Range1d(start=-norm_state_extents, end=norm_state_extents) } # Adding the second axis to the plot. pState.add_layout( LinearAxis(y_range_name="normalized_data", axis_label="normalized data"), 'right') state_lines = [] state_legend = [] normalized_state_lines = [] obs_props_names = [ o_prp.get_legal_name() for o_prp in self.panel_contents[t.name]['panel3']['obs_props'] ] for idx, state_name in enumerate(obs_props_names): if (data_frame[state_name].max() <= norm_state_extents and data_frame[state_name].min() >= -norm_state_extents): normalized_state_lines.append( pState.line(data_frame.index * self.step_time, data_frame[state_name], line_width=2, y_range_name="normalized_data", color=Inferno7[idx % 7], visible=False)) state_legend.append( ("norm_" + state_name, [normalized_state_lines[-1]])) else: state_lines.append( pState.line(data_frame.index * self.step_time, data_frame[state_name], line_width=2, color=Inferno7[idx % 7], visible=False)) state_legend.append((state_name, [state_lines[-1]])) pState.y_range.renderers = state_lines pState.extra_y_ranges.renderers = normalized_state_lines #this does not quite work: https://stackoverflow.com/questions/48631530/bokeh-twin-axes-with-datarange1d-not-well-scaling lg_state = Legend(items=state_legend, location=(0, 10), glyph_width=25, label_width=190) lg_state.click_policy = "hide" pState.add_layout(lg_state, 'right') pState.toolbar.active_scroll = pState.toolbar.tools[ 1] #this selects the WheelZoomTool instance #Add the title tState = Title() tState.text = 'Actual Observation presented to Agent' pState.title = tState pState.xaxis.axis_label = 'timestep [s]' pState.yaxis[0].axis_label = 'state data' panels[t.name].update({'panel3': pState}) return panels