From d098ecfbd9689e5dcce88598e8f6b52b31cb86e3 Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Tue, 1 Oct 2024 13:59:14 -0400 Subject: [PATCH 1/9] get_val with units --- aviary/visualization/aircraft_3d_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aviary/visualization/aircraft_3d_model.py b/aviary/visualization/aircraft_3d_model.py index e517dc008..65178d7f0 100644 --- a/aviary/visualization/aircraft_3d_model.py +++ b/aviary/visualization/aircraft_3d_model.py @@ -298,7 +298,7 @@ def get_variable_from_case(self, var_prom_name, units=None): Value of the variable. """ try: - val = self._final_case[var_prom_name] + val = self._final_case.get_val(var_prom_name, units=units) return float(val) except KeyError as e: pass From 0c59cddf992c712aa404c867def8a8b29591abaa Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Tue, 1 Oct 2024 13:59:39 -0400 Subject: [PATCH 2/9] partial solution to optimization plot --- aviary/visualization/dashboard.py | 281 +++++++++++++++++++++++++++++- 1 file changed, 275 insertions(+), 6 deletions(-) diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index ee652845e..25651b572 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -12,6 +12,7 @@ import numpy as np +import bokeh import bokeh.palettes as bp from bokeh.models import Legend, CheckboxGroup, CustomJS from bokeh.plotting import figure @@ -329,7 +330,8 @@ def create_report_frame(format, text_filepath, documentation): if format == "markdown": report_pane = pn.pane.Markdown(file_text) elif format == "text": - report_pane = pn.pane.Markdown(f"```\n{file_text}\n```\n") + # report_pane = pn.pane.Markdown(f"```\n{file_text}\n```\n") + report_pane = pn.pane.Str(file_text) report_pane = pn.Column( pn.pane.HTML(f"

{documentation}

", styles={'text-align': 'left'}), report_pane @@ -568,6 +570,249 @@ def _get_interactive_plot_sources(data_by_varname_and_phase, x_varname, y_varnam return [], [] +def create_optimization_history_plot_test(): + # testing the plotting + + import pandas as pd + import numpy as np + import panel as pn + import bokeh.plotting as bp + from bokeh.models import ColumnDataSource, LegendItem, Legend + + # Enable the Panel extension (for Jupyter notebooks; optional if running as a script) + pn.extension() + + # Create sample time series data + dates = pd.date_range(start='2020-01-01', periods=100) + data = pd.DataFrame({ + 'date': dates, + 'Series 1': np.random.randn(100).cumsum(), + 'Series 2': np.random.randn(100).cumsum(), + 'Series 3': np.random.randn(100).cumsum(), + }) + + # Create a ColumnDataSource + source = ColumnDataSource(data) + + # Create a Bokeh figure with a datetime x-axis + p = bp.figure(x_axis_type='datetime', title='Time Series Plot', width=800, height=400) + p.xaxis.axis_label = 'Date' + p.yaxis.axis_label = 'Value' + + # Plot each time series and keep references to the renderers + renderers = {} + colors = ['blue', 'red', 'green'] + series_list = ['Series 1', 'Series 2', 'Series 3'] + for i, series in enumerate(series_list): + renderers[series] = p.line( + x='date', + y=series, + source=source, + color=colors[i], + line_width=2 + ) + + # Create the legend manually using LegendItem + legend_items = [LegendItem(label=series, renderers=[renderers[series]]) for series in series_list] + legend = Legend(items=legend_items) + print(f"{id(legend)=}") + p.add_layout(legend, 'right') + print(f"{id(p)=}") + # Create a CheckBoxGroup widget using Panel + checkbox_group = pn.widgets.CheckBoxGroup( + name='Time Series', + value=series_list, # All series are active by default + options=series_list + ) + + # Define a callback function to update visibility and legend based on checkbox selection + def update(event): + active_labels = checkbox_group.value + # Update renderers' visibility + for series in series_list: + renderers[series].visible = series in active_labels + # Update legend items to only include visible series + print(f"update legend {active_labels=}") + # p.legend.items = [LegendItem(label=series, renderers=[renderers[series]]) for series in active_labels] + print(f"{id(legend)=}") + legend.items = [LegendItem(label=series, renderers=[renderers[series]]) for series in active_labels] + print(f"{id(p)=}") + print(f"{legend.items=}") + + # Attach the callback to the CheckBoxGroup + checkbox_group.param.watch(update, 'value') + + # Arrange the layout using Panel + layout = pn.Column(checkbox_group, p) + return layout + +def create_optimization_history_plot(df): + # testing the plotting + + import pandas as pd + import numpy as np + import panel as pn + import bokeh.plotting as bp + from bokeh.models import ColumnDataSource, LegendItem, Legend + + # Enable the Panel extension (for Jupyter notebooks; optional if running as a script) + pn.extension() + + # Create a ColumnDataSource + source = ColumnDataSource(df) + + # Create a Bokeh figure + p = bp.figure(title='Optimization History', width=600, height=600) + p.xaxis.axis_label = 'Iterations' + p.yaxis.axis_label = 'Variables' + # p.legend.visible = True + + + # # Plot each time series and keep references to the renderers + renderers = {} + colors = ['blue', 'red', 'green'] + series_list = list(df.columns)[1:] + for i, series in enumerate(series_list): + renderers[series] = p.line( + x='iter_count', + y=series, + source=source, + color=colors[i % 3], + line_width=2, + visible=False, + ) + + + # Create the legend manually using LegendItem + # legend_items = [LegendItem(label=series, renderers=[renderers[series]]) for series in series_list] + legend_items = [] + legend = Legend(items=legend_items, location=(-50, 0)) + + # # Create a CheckBoxGroup widget using Panel + # checkbox_group = pn.widgets.CheckBoxGroup( + # name='Time Series', + # value=[], + # options=series_list + # ) + + # # Define a callback function to update visibility and legend based on checkbox selection + # def update(event): + # active_labels = checkbox_group.value + # # Update renderers' visibility + # for series in series_list: + # renderers[series].visible = series in active_labels + # # Update legend items to only include visible series + # # p.legend.items = [LegendItem(label=series, renderers=[renderers[series]]) for series in active_labels] + + # legend.items = [LegendItem(label=series, renderers=[renderers[series]]) for series in active_labels] + # print(f"{id(p)=}") + # print(f"{legend.items=}") + + # # Attach the callback to the CheckBoxGroup + # checkbox_group.param.watch(update, 'value') + + + # legend.items = [LegendItem(label=series, renderers=[renderers[series]]) for series in series_list] + # legend.items = [LegendItem(label=series, renderers=[renderers[series]]) for series in series_list[:5]] + + legend_items = [LegendItem(label=series, renderers=[renderers[series]]) for series in series_list] + + p.add_layout(legend, 'below') + + + + + from bokeh.models import TextInput, ColumnDataSource, CustomJS, Div + import random + import string + from bokeh.io import show + from bokeh.layouts import column + from bokeh.models import TextInput, ColumnDataSource, CustomJS, Div + + # def random_str(): + # return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(20)) + + # # Generate a larger set of options + # options = [random_str() for _ in range(100)] + ds = ColumnDataSource(data=dict(options=series_list, checked=[False]*len(series_list))) + # Create a Div to act as a scrollable container + scroll_box = Div( + styles={ + 'overflow-y': 'scroll', + 'height': '300px', + 'border': '1px solid #ddd', + 'padding': '10px' + } + ) + + ti = TextInput(placeholder='Enter filter') + + # CustomJS callback for checkbox changes + checkbox_callback = CustomJS(args=dict(ds=ds,renderers=renderers,legend=legend, legend_items=legend_items), code=""" + // The incoming Legend is empty. The items are passed in separately + var doc = Bokeh.documents[0]; + + const checkedIndex = cb_obj.index; + const isChecked = cb_obj.checked; + ds.data['checked'][checkedIndex] = isChecked; + renderers[ds.data['options'][checkedIndex]].visible = isChecked; + + // empty the Legend items and then add in the ones for the variables that are checked + legend.items = []; + for (let i =0; i < legend_items.length; i++){ + if ( ds.data['checked'][i] ) { + legend.items.push(legend_items[i]); + } + } + +ds.change.emit(); """) + + # Update the main CustomJS callback + callback = CustomJS(args=dict(ds=ds, scroll_box=scroll_box, checkbox_callback=checkbox_callback), code=""" + + const filter_text = cb_obj.value.toLowerCase(); + const all_options = ds.data['options']; + const checked_states = ds.data['checked']; + + // Filter options + const filtered_options = all_options.filter(option => + option.toLowerCase().includes(filter_text) + ); + + // Update the scroll box content + let checkboxes_html = ''; + filtered_options.forEach((label) => { + const index = all_options.indexOf(label); + checkboxes_html += ` + + `; + }); + scroll_box.text = checkboxes_html; + """) + + ti.js_on_change('value', callback) + + # Initial population of the scroll box + initial_html = ''.join(f""" + + """ for i, option in enumerate(series_list)) + scroll_box.text = initial_html + + + # Arrange the layout using Panel + # layout = pn.Row(pn.Column(ti, scroll_box), checkbox_group, p) + layout = pn.Row(pn.Column(ti, scroll_box), p) + # layout = pn.Row(checkbox_group,bokeh_layout) + return layout + # The main script that generates all the tabs in the dashboard def dashboard(script_name, problem_recorder, driver_recorder, port, run_in_background=False): """ @@ -649,9 +894,6 @@ def dashboard(script_name, problem_recorder, driver_recorder, port, run_in_backg ) model_tabs_list.append(("Trajectory Linkage", traj_linkage_report_pane)) - ####### Optimization Tab ####### - optimization_tabs_list = [] - # Driver scaling driver_scaling_report_pane = create_report_frame( "html", f"{reports_dir}/driver_scaling_report.html", ''' @@ -663,6 +905,19 @@ def dashboard(script_name, problem_recorder, driver_recorder, port, run_in_backg ) model_tabs_list.append(("Driver Scaling", driver_scaling_report_pane)) + ####### Optimization Tab ####### + optimization_tabs_list = [] + + opt_plot_testing_pane = create_optimization_history_plot_test() + optimization_tabs_list.append(("Optimization History test", opt_plot_testing_pane)) + + # Optimization History Plot + if driver_recorder: + if os.path.isfile(driver_recorder): + df = convert_case_recorder_file_to_df(f"{driver_recorder}") + opt_history_pane = create_optimization_history_plot(df) + optimization_tabs_list.append(("Optimization History", opt_history_pane)) + # Desvars, cons, opt interactive plot if driver_recorder: if os.path.isfile(driver_recorder): @@ -682,14 +937,23 @@ def dashboard(script_name, problem_recorder, driver_recorder, port, run_in_backg color=list(bp.Category10[10]), yformatter="%.0f", title="Model Optimization using OpenMDAO", + legend=False, + # legend="bottom", + # legend_cols=True, + # legend_muted=True, + # legend_position='right', legend_offset=(-200, -200) ) + # ihvplot.opts(show_legend='bottom') # does not work. Still on right + # print(dir(ihvplot)) + # hm = hm.opts(legend_position='top') + optimization_plot_pane = pn.Column( pn.Row( pn.Column( variables, pn.VSpacer(height=30), pn.VSpacer(height=30), - width=300, + # width=300, ), ihvplot.panel(), ) @@ -931,7 +1195,7 @@ def dashboard(script_name, problem_recorder, driver_recorder, port, run_in_backg ], ) - colors = bp.d3['Category20'][20][0::2] + bp.d3['Category20'][20][1::2] + colors = bokeh.palettes.d3['Category20'][20][0::2] + bokeh.palettes.d3['Category20'][20][1::2] legend_data = [] phases = sorted(phases, key=str.casefold) for i, phase in enumerate(phases): @@ -1089,6 +1353,11 @@ def save_dashboard(event): home_dir = "." if port == 0: port = get_free_port() + + + + print(f"{show=}") + print(f"{threaded=}") server = pn.serve( template, port=port, From 6dfd08bf22bd3d759611d7f7073b2ae344b35fb2 Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Wed, 16 Oct 2024 11:45:12 -0400 Subject: [PATCH 3/9] Axes left and right working --- aviary/visualization/dashboard.py | 154 ++++++++++++++++++++++++++---- 1 file changed, 137 insertions(+), 17 deletions(-) diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index 25651b572..ba4c6f68c 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -456,7 +456,7 @@ def create_aviary_variables_table_data_nested(script_name, recorder_file): return table_data_nested -def convert_case_recorder_file_to_df(recorder_file_name): +def convert_driver_case_recorder_file_to_df(recorder_file_name): """ Convert a case recorder file into a Pandas data frame. @@ -646,8 +646,9 @@ def update(event): layout = pn.Column(checkbox_group, p) return layout -def create_optimization_history_plot(df): - # testing the plotting +def create_optimization_history_plot(cr, df): + + import pandas as pd import numpy as np @@ -662,30 +663,95 @@ def create_optimization_history_plot(df): source = ColumnDataSource(df) # Create a Bokeh figure - p = bp.figure(title='Optimization History', width=600, height=600) + p = bp.figure(title='Optimization History', width=1000, height=600) + + p.yaxis.visible = False + + # p = figure(sizing_mode="stretch_width", max_width=1000, height=600) + + p.xaxis.axis_label = 'Iterations' p.yaxis.axis_label = 'Variables' # p.legend.visible = True - + + + from bokeh.models import NumeralTickFormatter, PrintfTickFormatter + # p.xaxis.formatter = NumeralTickFormatter(format="0.00") + # p.yaxis.formatter = NumeralTickFormatter(format="0.00") + # p.xaxis.formatter = PrintfTickFormatter(format="%0.3g") + # p.yaxis.formatter = PrintfTickFormatter(format="%0.3g") + p.yaxis.formatter = PrintfTickFormatter(format="%5.2e") + # # Plot each time series and keep references to the renderers renderers = {} - colors = ['blue', 'red', 'green'] series_list = list(df.columns)[1:] + + from bokeh.palettes import Category10, Category20 + # Choose a palette + palette = Category20[20] + + # series_list = series_list[:4] + for i, series in enumerate(series_list): renderers[series] = p.line( x='iter_count', y=series, source=source, - color=colors[i % 3], + color=palette[i%20], line_width=2, visible=False, ) + from bokeh.models import Range1d, LinearAxis + + if True: + color = palette[i%20] + extra_y_axis = LinearAxis(y_range_name=f"extra_y_{series}", + axis_label=f"{series}", + # axis_line_color=color, + # major_tick_line_color=color, + # minor_tick_line_color=color, + axis_label_text_color=color) + p.add_layout(extra_y_axis, 'right') + p.right[i].visible = False + + extra_y_axis = LinearAxis(y_range_name=f"extra_y_{series}", + axis_label=f"{series}", + # axis_line_color=color, + # major_tick_line_color=color, + # minor_tick_line_color=color, + axis_label_text_color=color) + p.add_layout(extra_y_axis, 'left') + + if len(p.left)<=3: + print(f"{p.left=}") + len_series_list = len(series_list) + print(f"{len_series_list=} {len(p.left)=} {i=} {len_series_list - i - 1=}") + # p.left[len_series_list - i - 1].visible = False + p.left[i + 1].visible = False + + # set the range + y_min = df[series].min() + y_max = df[series].max() + # if the range is zero, the axis will not be displayed. Plus need some range to make it + # look good + if y_min == y_max: + y_min = y_min - 1 + y_max = y_max + 1 + # p.extra_y_ranges[f"extra_y_{series}"] = Range1d(df[series].min(), df[series].max()) + p.extra_y_ranges[f"extra_y_{series}"] = Range1d(y_min, y_max) + # p.extra_y_ranges[f"extra_y_{series}"] = Range1d(0,10.234456) + + + # p.add_layout(extra_y_axis, 'left') + # p.left[i].visible = True + + # Create the legend manually using LegendItem # legend_items = [LegendItem(label=series, renderers=[renderers[series]]) for series in series_list] - legend_items = [] + legend_items = [] # TODO need this? legend = Legend(items=legend_items, location=(-50, 0)) # # Create a CheckBoxGroup widget using Panel @@ -715,7 +781,17 @@ def create_optimization_history_plot(df): # legend.items = [LegendItem(label=series, renderers=[renderers[series]]) for series in series_list] # legend.items = [LegendItem(label=series, renderers=[renderers[series]]) for series in series_list[:5]] + + # Need this? legend_items = [LegendItem(label=series, renderers=[renderers[series]]) for series in series_list] + + legend_items = [] + for series in series_list: + units = cr.problem_metadata['variables'][series]['units'] + # print(f"{series} units are '{units}'") + legend_item = LegendItem(label=f"{series} ({units})", renderers=[renderers[series]]) + legend_items.append(legend_item) + p.add_layout(legend, 'below') @@ -746,9 +822,11 @@ def create_optimization_history_plot(df): ) ti = TextInput(placeholder='Enter filter') + + # CustomJS callback for checkbox changes - checkbox_callback = CustomJS(args=dict(ds=ds,renderers=renderers,legend=legend, legend_items=legend_items), code=""" + checkbox_callback = CustomJS(args=dict(ds=ds,p=p, renderers=renderers,legend=legend, legend_items=legend_items), code=""" // The incoming Legend is empty. The items are passed in separately var doc = Bokeh.documents[0]; @@ -756,11 +834,51 @@ def create_optimization_history_plot(df): const isChecked = cb_obj.checked; ds.data['checked'][checkedIndex] = isChecked; renderers[ds.data['options'][checkedIndex]].visible = isChecked; - + + + var default_y_axis_left = p.left[0]; + default_y_axis_left.visible = false; + + + // empty the Legend items and then add in the ones for the variables that are checked legend.items = []; + + let put_on_left_side = true; + + for (let i =0; i < legend_items.length; i++){ + var extra_y_axis = p.left[i + 1]; + extra_y_axis.visible = false ; + + var extra_y_axis = p.right[i]; + extra_y_axis.visible = false ; + } + + for (let i =0; i < legend_items.length; i++){ + if (ds.data['checked'][i]){ + if (put_on_left_side){ + p.left[i + 1].visible = true; + } else { + p.right[i].visible = true; + } + put_on_left_side = ! put_on_left_side ; + } + } + for (let i =0; i < legend_items.length; i++){ - if ( ds.data['checked'][i] ) { + + //var extra_y_axis = p.left[i + 1]; + //extra_y_axis.visible = ds.data['checked'][i] ; + + //var extra_y_axis = p.right[i]; + //extra_y_axis.visible = ds.data['checked'][i] ; + + + //var extra_y_axis = p.left[i]; + //extra_y_axis.visible = ds.data['checked'][i] ; + + + if ( ds.data['checked'][i] ) { legend.items.push(legend_items[i]); } } @@ -908,20 +1026,22 @@ def dashboard(script_name, problem_recorder, driver_recorder, port, run_in_backg ####### Optimization Tab ####### optimization_tabs_list = [] - opt_plot_testing_pane = create_optimization_history_plot_test() - optimization_tabs_list.append(("Optimization History test", opt_plot_testing_pane)) + # TODO - remove! + # opt_plot_testing_pane = create_optimization_history_plot_test() + # optimization_tabs_list.append(("Optimization History test", opt_plot_testing_pane)) # Optimization History Plot if driver_recorder: if os.path.isfile(driver_recorder): - df = convert_case_recorder_file_to_df(f"{driver_recorder}") - opt_history_pane = create_optimization_history_plot(df) + df = convert_driver_case_recorder_file_to_df(f"{driver_recorder}") + cr = om.CaseReader(f"{driver_recorder}") + opt_history_pane = create_optimization_history_plot(cr,df) optimization_tabs_list.append(("Optimization History", opt_history_pane)) - # Desvars, cons, opt interactive plot + # Desvars, cons, opt interactive plot TODO remove becaus the one above supercedes it if driver_recorder: if os.path.isfile(driver_recorder): - df = convert_case_recorder_file_to_df(f"{driver_recorder}") + df = convert_driver_case_recorder_file_to_df(f"{driver_recorder}") if df is not None: variables = pn.widgets.CheckBoxGroup( name="Variables", From 9afc796fce391584c2673916cc020b015efef7fb Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Thu, 17 Oct 2024 21:03:06 -0400 Subject: [PATCH 4/9] removed commented out code and re-did some imports --- .../test/test_height_energy_mission.py | 2 +- aviary/visualization/dashboard.py | 210 +----------------- 2 files changed, 12 insertions(+), 200 deletions(-) diff --git a/aviary/interface/test/test_height_energy_mission.py b/aviary/interface/test/test_height_energy_mission.py index 29d24b244..b6ef3970f 100644 --- a/aviary/interface/test/test_height_energy_mission.py +++ b/aviary/interface/test/test_height_energy_mission.py @@ -12,7 +12,7 @@ from aviary.variable_info.variables import Dynamic -@use_tempdirs +# @use_tempdirs class AircraftMissionTestSuite(unittest.TestCase): def setUp(self): diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index ba4c6f68c..03c212ffd 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -12,16 +12,18 @@ import numpy as np +import pandas as pd + import bokeh import bokeh.palettes as bp -from bokeh.models import Legend, CheckboxGroup, CustomJS +from bokeh.models import Legend, LegendItem, CheckboxGroup, CustomJS, TextInput, ColumnDataSource, CustomJS, Div, Range1d, LinearAxis, PrintfTickFormatter from bokeh.plotting import figure -from bokeh.models import ColumnDataSource +from bokeh.layouts import column import hvplot.pandas # noqa # need this ! Otherwise hvplot using DataFrames does not work -import pandas as pd + import panel as pn -from panel.theme import DefaultTheme +from panel.theme import DefaultTheme # TODO need? import openmdao.api as om from openmdao.utils.general_utils import env_truthy @@ -59,8 +61,6 @@ def get_free_port(): documentation_text_align = 'left' # functions for the aviary command line command - - def _none_or_str(value): """ Get the value of the argparse option. @@ -569,120 +569,24 @@ def _get_interactive_plot_sources(data_by_varname_and_phase, x_varname, y_varnam else: return [], [] - -def create_optimization_history_plot_test(): - # testing the plotting - - import pandas as pd - import numpy as np - import panel as pn - import bokeh.plotting as bp - from bokeh.models import ColumnDataSource, LegendItem, Legend - - # Enable the Panel extension (for Jupyter notebooks; optional if running as a script) - pn.extension() - - # Create sample time series data - dates = pd.date_range(start='2020-01-01', periods=100) - data = pd.DataFrame({ - 'date': dates, - 'Series 1': np.random.randn(100).cumsum(), - 'Series 2': np.random.randn(100).cumsum(), - 'Series 3': np.random.randn(100).cumsum(), - }) - - # Create a ColumnDataSource - source = ColumnDataSource(data) - - # Create a Bokeh figure with a datetime x-axis - p = bp.figure(x_axis_type='datetime', title='Time Series Plot', width=800, height=400) - p.xaxis.axis_label = 'Date' - p.yaxis.axis_label = 'Value' - - # Plot each time series and keep references to the renderers - renderers = {} - colors = ['blue', 'red', 'green'] - series_list = ['Series 1', 'Series 2', 'Series 3'] - for i, series in enumerate(series_list): - renderers[series] = p.line( - x='date', - y=series, - source=source, - color=colors[i], - line_width=2 - ) - - # Create the legend manually using LegendItem - legend_items = [LegendItem(label=series, renderers=[renderers[series]]) for series in series_list] - legend = Legend(items=legend_items) - print(f"{id(legend)=}") - p.add_layout(legend, 'right') - print(f"{id(p)=}") - # Create a CheckBoxGroup widget using Panel - checkbox_group = pn.widgets.CheckBoxGroup( - name='Time Series', - value=series_list, # All series are active by default - options=series_list - ) - - # Define a callback function to update visibility and legend based on checkbox selection - def update(event): - active_labels = checkbox_group.value - # Update renderers' visibility - for series in series_list: - renderers[series].visible = series in active_labels - # Update legend items to only include visible series - print(f"update legend {active_labels=}") - # p.legend.items = [LegendItem(label=series, renderers=[renderers[series]]) for series in active_labels] - print(f"{id(legend)=}") - legend.items = [LegendItem(label=series, renderers=[renderers[series]]) for series in active_labels] - print(f"{id(p)=}") - print(f"{legend.items=}") - - # Attach the callback to the CheckBoxGroup - checkbox_group.param.watch(update, 'value') - - # Arrange the layout using Panel - layout = pn.Column(checkbox_group, p) - return layout - def create_optimization_history_plot(cr, df): - - - import pandas as pd - import numpy as np - import panel as pn - import bokeh.plotting as bp - from bokeh.models import ColumnDataSource, LegendItem, Legend - # Enable the Panel extension (for Jupyter notebooks; optional if running as a script) - pn.extension() + # pn.extension() TODO remove? # Create a ColumnDataSource source = ColumnDataSource(df) # Create a Bokeh figure - p = bp.figure(title='Optimization History', width=1000, height=600) + p = bokeh.plotting.figure(title='Optimization History', width=1000, height=600) # TODO how to handle imports? p.yaxis.visible = False - - # p = figure(sizing_mode="stretch_width", max_width=1000, height=600) - - p.xaxis.axis_label = 'Iterations' + p.xaxis.axis_label = 'Iterations' # TODO need these? p.yaxis.axis_label = 'Variables' - # p.legend.visible = True - - from bokeh.models import NumeralTickFormatter, PrintfTickFormatter - # p.xaxis.formatter = NumeralTickFormatter(format="0.00") - # p.yaxis.formatter = NumeralTickFormatter(format="0.00") - # p.xaxis.formatter = PrintfTickFormatter(format="%0.3g") - # p.yaxis.formatter = PrintfTickFormatter(format="%0.3g") p.yaxis.formatter = PrintfTickFormatter(format="%5.2e") - # # Plot each time series and keep references to the renderers renderers = {} series_list = list(df.columns)[1:] @@ -691,8 +595,6 @@ def create_optimization_history_plot(cr, df): # Choose a palette palette = Category20[20] - # series_list = series_list[:4] - for i, series in enumerate(series_list): renderers[series] = p.line( x='iter_count', @@ -703,32 +605,21 @@ def create_optimization_history_plot(cr, df): visible=False, ) - from bokeh.models import Range1d, LinearAxis if True: color = palette[i%20] extra_y_axis = LinearAxis(y_range_name=f"extra_y_{series}", axis_label=f"{series}", - # axis_line_color=color, - # major_tick_line_color=color, - # minor_tick_line_color=color, axis_label_text_color=color) p.add_layout(extra_y_axis, 'right') p.right[i].visible = False extra_y_axis = LinearAxis(y_range_name=f"extra_y_{series}", axis_label=f"{series}", - # axis_line_color=color, - # major_tick_line_color=color, - # minor_tick_line_color=color, axis_label_text_color=color) p.add_layout(extra_y_axis, 'left') - if len(p.left)<=3: - print(f"{p.left=}") len_series_list = len(series_list) - print(f"{len_series_list=} {len(p.left)=} {i=} {len_series_list - i - 1=}") - # p.left[len_series_list - i - 1].visible = False p.left[i + 1].visible = False # set the range @@ -739,77 +630,24 @@ def create_optimization_history_plot(cr, df): if y_min == y_max: y_min = y_min - 1 y_max = y_max + 1 - # p.extra_y_ranges[f"extra_y_{series}"] = Range1d(df[series].min(), df[series].max()) p.extra_y_ranges[f"extra_y_{series}"] = Range1d(y_min, y_max) - # p.extra_y_ranges[f"extra_y_{series}"] = Range1d(0,10.234456) - - - # p.add_layout(extra_y_axis, 'left') - # p.left[i].visible = True - - # Create the legend manually using LegendItem - # legend_items = [LegendItem(label=series, renderers=[renderers[series]]) for series in series_list] legend_items = [] # TODO need this? legend = Legend(items=legend_items, location=(-50, 0)) - # # Create a CheckBoxGroup widget using Panel - # checkbox_group = pn.widgets.CheckBoxGroup( - # name='Time Series', - # value=[], - # options=series_list - # ) - - # # Define a callback function to update visibility and legend based on checkbox selection - # def update(event): - # active_labels = checkbox_group.value - # # Update renderers' visibility - # for series in series_list: - # renderers[series].visible = series in active_labels - # # Update legend items to only include visible series - # # p.legend.items = [LegendItem(label=series, renderers=[renderers[series]]) for series in active_labels] - - # legend.items = [LegendItem(label=series, renderers=[renderers[series]]) for series in active_labels] - # print(f"{id(p)=}") - # print(f"{legend.items=}") - - # # Attach the callback to the CheckBoxGroup - # checkbox_group.param.watch(update, 'value') - - - # legend.items = [LegendItem(label=series, renderers=[renderers[series]]) for series in series_list] - # legend.items = [LegendItem(label=series, renderers=[renderers[series]]) for series in series_list[:5]] - - - # Need this? + # TODO Need this? legend_items = [LegendItem(label=series, renderers=[renderers[series]]) for series in series_list] legend_items = [] for series in series_list: units = cr.problem_metadata['variables'][series]['units'] - # print(f"{series} units are '{units}'") legend_item = LegendItem(label=f"{series} ({units})", renderers=[renderers[series]]) legend_items.append(legend_item) - p.add_layout(legend, 'below') - - - - from bokeh.models import TextInput, ColumnDataSource, CustomJS, Div - import random - import string - from bokeh.io import show - from bokeh.layouts import column - from bokeh.models import TextInput, ColumnDataSource, CustomJS, Div - - # def random_str(): - # return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(20)) - - # # Generate a larger set of options - # options = [random_str() for _ in range(100)] + ds = ColumnDataSource(data=dict(options=series_list, checked=[False]*len(series_list))) # Create a Div to act as a scrollable container scroll_box = Div( @@ -822,8 +660,6 @@ def create_optimization_history_plot(cr, df): ) ti = TextInput(placeholder='Enter filter') - - # CustomJS callback for checkbox changes checkbox_callback = CustomJS(args=dict(ds=ds,p=p, renderers=renderers,legend=legend, legend_items=legend_items), code=""" @@ -835,12 +671,9 @@ def create_optimization_history_plot(cr, df): ds.data['checked'][checkedIndex] = isChecked; renderers[ds.data['options'][checkedIndex]].visible = isChecked; - var default_y_axis_left = p.left[0]; default_y_axis_left.visible = false; - - // empty the Legend items and then add in the ones for the variables that are checked legend.items = []; @@ -867,17 +700,6 @@ def create_optimization_history_plot(cr, df): for (let i =0; i < legend_items.length; i++){ - //var extra_y_axis = p.left[i + 1]; - //extra_y_axis.visible = ds.data['checked'][i] ; - - //var extra_y_axis = p.right[i]; - //extra_y_axis.visible = ds.data['checked'][i] ; - - - //var extra_y_axis = p.left[i]; - //extra_y_axis.visible = ds.data['checked'][i] ; - - if ( ds.data['checked'][i] ) { legend.items.push(legend_items[i]); } @@ -926,9 +748,7 @@ def create_optimization_history_plot(cr, df): # Arrange the layout using Panel - # layout = pn.Row(pn.Column(ti, scroll_box), checkbox_group, p) layout = pn.Row(pn.Column(ti, scroll_box), p) - # layout = pn.Row(checkbox_group,bokeh_layout) return layout # The main script that generates all the tabs in the dashboard @@ -1026,10 +846,6 @@ def dashboard(script_name, problem_recorder, driver_recorder, port, run_in_backg ####### Optimization Tab ####### optimization_tabs_list = [] - # TODO - remove! - # opt_plot_testing_pane = create_optimization_history_plot_test() - # optimization_tabs_list.append(("Optimization History test", opt_plot_testing_pane)) - # Optimization History Plot if driver_recorder: if os.path.isfile(driver_recorder): @@ -1474,10 +1290,6 @@ def save_dashboard(event): if port == 0: port = get_free_port() - - - print(f"{show=}") - print(f"{threaded=}") server = pn.serve( template, port=port, From fa45b08a8d544451175d0a37a1e7c4fc9cf99bd3 Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Mon, 21 Oct 2024 14:13:16 -0400 Subject: [PATCH 5/9] cleaned up imports, removed commented out code, re-ordered code, renamed variables for clarity --- aviary/visualization/dashboard.py | 285 ++++++++++++------------------ 1 file changed, 115 insertions(+), 170 deletions(-) diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index 03c212ffd..d228245d4 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -14,16 +14,12 @@ import pandas as pd -import bokeh -import bokeh.palettes as bp from bokeh.models import Legend, LegendItem, CheckboxGroup, CustomJS, TextInput, ColumnDataSource, CustomJS, Div, Range1d, LinearAxis, PrintfTickFormatter from bokeh.plotting import figure from bokeh.layouts import column - -import hvplot.pandas # noqa # need this ! Otherwise hvplot using DataFrames does not work +from bokeh.palettes import Category10, Category20, d3 import panel as pn -from panel.theme import DefaultTheme # TODO need? import openmdao.api as om from openmdao.utils.general_utils import env_truthy @@ -569,150 +565,156 @@ def _get_interactive_plot_sources(data_by_varname_and_phase, x_varname, y_varnam else: return [], [] -def create_optimization_history_plot(cr, df): +def create_optimization_history_plot(case_recorder, df): - # Enable the Panel extension (for Jupyter notebooks; optional if running as a script) - # pn.extension() TODO remove? - # Create a ColumnDataSource source = ColumnDataSource(df) # Create a Bokeh figure - p = bokeh.plotting.figure(title='Optimization History', width=1000, height=600) # TODO how to handle imports? - - p.yaxis.visible = False - - p.xaxis.axis_label = 'Iterations' # TODO need these? - p.yaxis.axis_label = 'Variables' + plotting_figure = figure(title='Optimization History', + width=1000, + height=600, + ) + plotting_figure.title.align = 'center' + plotting_figure.yaxis.visible = False + plotting_figure.xaxis.axis_label = 'Iterations' + plotting_figure.yaxis.formatter = PrintfTickFormatter(format="%5.2e") + plotting_figure.title.text_font_size = "25px" - p.yaxis.formatter = PrintfTickFormatter(format="%5.2e") - - # # Plot each time series and keep references to the renderers - renderers = {} - series_list = list(df.columns)[1:] - - from bokeh.palettes import Category10, Category20 # Choose a palette palette = Category20[20] - - for i, series in enumerate(series_list): - renderers[series] = p.line( + + # Plot each time series and keep references to the renderers + renderers = {} + variable_names = list(df.columns)[1:] + for i, variable_name in enumerate(variable_names): + color = palette[i%20] + + renderers[variable_name] = plotting_figure.line( x='iter_count', - y=series, + y=variable_name, source=source, - color=palette[i%20], + color=color, line_width=2, - visible=False, + visible=False, # hide them all initially. clicking checkboxes makes them visible ) - - - if True: - color = palette[i%20] - extra_y_axis = LinearAxis(y_range_name=f"extra_y_{series}", - axis_label=f"{series}", - axis_label_text_color=color) - p.add_layout(extra_y_axis, 'right') - p.right[i].visible = False - - extra_y_axis = LinearAxis(y_range_name=f"extra_y_{series}", - axis_label=f"{series}", - axis_label_text_color=color) - p.add_layout(extra_y_axis, 'left') - - len_series_list = len(series_list) - p.left[i + 1].visible = False - - # set the range - y_min = df[series].min() - y_max = df[series].max() - # if the range is zero, the axis will not be displayed. Plus need some range to make it - # look good - if y_min == y_max: - y_min = y_min - 1 - y_max = y_max + 1 - p.extra_y_ranges[f"extra_y_{series}"] = Range1d(y_min, y_max) - - # Create the legend manually using LegendItem - legend_items = [] # TODO need this? - legend = Legend(items=legend_items, location=(-50, 0)) - - # TODO Need this? - legend_items = [LegendItem(label=series, renderers=[renderers[series]]) for series in series_list] - + + # create axes both to the right and left of the plot. + # hide them initially + # as the user selects/deselects variables to be plotted, they get turned on/off + extra_y_axis = LinearAxis(y_range_name=f"extra_y_{variable_name}", + axis_label=f"{variable_name}", + axis_label_text_color=color) + plotting_figure.add_layout(extra_y_axis, 'right') + plotting_figure.right[i].visible = False + + extra_y_axis = LinearAxis(y_range_name=f"extra_y_{variable_name}", + axis_label=f"{variable_name}", + axis_label_text_color=color) + plotting_figure.add_layout(extra_y_axis, 'left') + plotting_figure.left[i + 1].visible = False + + # set the range + y_min = df[variable_name].min() + y_max = df[variable_name].max() + # if the range is zero, the axis will not be displayed. Plus need some range to make it + # look good. Some other code seems to do +- 1 for the range in this case. + if y_min == y_max: + y_min = y_min - 1 + y_max = y_max + 1 + plotting_figure.extra_y_ranges[f"extra_y_{variable_name}"] = Range1d(y_min, y_max) + + # Make a Legend with no items in it. those will be added in JavaScript + # as users select variables to be plotted + legend = Legend(items=[], location=(-50, -10), border_line_width=0) + + # make the legend items in Python. Pass them to JavaScript where they can be added to the Legend legend_items = [] - for series in series_list: - units = cr.problem_metadata['variables'][series]['units'] - legend_item = LegendItem(label=f"{series} ({units})", renderers=[renderers[series]]) + for variable_name in variable_names: + units = case_recorder.problem_metadata['variables'][variable_name]['units'] + legend_item = LegendItem(label=f"{variable_name} ({units})", renderers=[renderers[variable_name]]) legend_items.append(legend_item) - p.add_layout(legend, 'below') - + plotting_figure.add_layout(legend, 'below') - ds = ColumnDataSource(data=dict(options=series_list, checked=[False]*len(series_list))) + # make the list of variables with checkboxes + data_source = ColumnDataSource(data=dict(options=variable_names, checked=[False]*len(variable_names))) # Create a Div to act as a scrollable container - scroll_box = Div( + variable_scroll_box = Div( styles={ 'overflow-y': 'scroll', - 'height': '300px', + 'height': '500px', 'border': '1px solid #ddd', 'padding': '10px' } ) - ti = TextInput(placeholder='Enter filter') + # make the text box used to filter variables + filter_variables_text_box = TextInput(placeholder='Variable name filter') # CustomJS callback for checkbox changes - checkbox_callback = CustomJS(args=dict(ds=ds,p=p, renderers=renderers,legend=legend, legend_items=legend_items), code=""" + variable_checkbox_callback = CustomJS(args=dict(data_source=data_source, + plotting_figure=plotting_figure, + renderers=renderers, + legend=legend, + legend_items=legend_items), + code=""" + // Three things happen in this code. + // 1. turn on/off the plot lines + // 2. show the legend items for the items being plotted + // 3. show the y axes for each of the lines being plotted // The incoming Legend is empty. The items are passed in separately - var doc = Bokeh.documents[0]; - + + // 1. Plots + // turn off or on the line plot for the clicked on variable const checkedIndex = cb_obj.index; const isChecked = cb_obj.checked; - ds.data['checked'][checkedIndex] = isChecked; - renderers[ds.data['options'][checkedIndex]].visible = isChecked; - - var default_y_axis_left = p.left[0]; - default_y_axis_left.visible = false; + data_source.data['checked'][checkedIndex] = isChecked; + renderers[data_source.data['options'][checkedIndex]].visible = isChecked; + // 2. Legend // empty the Legend items and then add in the ones for the variables that are checked legend.items = []; + for (let i =0; i < legend_items.length; i++){ + if ( data_source.data['checked'][i] ) { + legend.items.push(legend_items[i]); + } + } - let put_on_left_side = true; - + // 3. Y axes + // first hide all of them for (let i =0; i < legend_items.length; i++){ - var extra_y_axis = p.left[i + 1]; + var extra_y_axis = plotting_figure.left[i + 1]; extra_y_axis.visible = false ; - var extra_y_axis = p.right[i]; + var extra_y_axis = plotting_figure.right[i]; extra_y_axis.visible = false ; } - + // alternate between making visible the axes on the left and the right to make it more even. + // this variable keeps track of which side to add the axes to. + let put_on_left_side = true; for (let i =0; i < legend_items.length; i++){ - if (ds.data['checked'][i]){ + if (data_source.data['checked'][i]){ if (put_on_left_side){ - p.left[i + 1].visible = true; + plotting_figure.left[i + 1].visible = true; } else { - p.right[i].visible = true; + plotting_figure.right[i].visible = true; } put_on_left_side = ! put_on_left_side ; } } - - for (let i =0; i < legend_items.length; i++){ - - if ( ds.data['checked'][i] ) { - legend.items.push(legend_items[i]); - } - } - -ds.change.emit(); """) + data_source.change.emit(); + """) - # Update the main CustomJS callback - callback = CustomJS(args=dict(ds=ds, scroll_box=scroll_box, checkbox_callback=checkbox_callback), code=""" + # CustomJS callback for the variable filtering + filter_variables_callback = CustomJS(args=dict(data_source=data_source, + variable_scroll_box=variable_scroll_box, + variable_checkbox_callback=variable_checkbox_callback), + code=""" const filter_text = cb_obj.value.toLowerCase(); - const all_options = ds.data['options']; - const checked_states = ds.data['checked']; + const all_options = data_source.data['options']; + const checked_states = data_source.data['checked']; // Filter options const filtered_options = all_options.filter(option => @@ -726,29 +728,29 @@ def create_optimization_history_plot(cr, df): checkboxes_html += ` `; }); - scroll_box.text = checkboxes_html; + variable_scroll_box.text = checkboxes_html; """) - ti.js_on_change('value', callback) + filter_variables_text_box.js_on_change('value', filter_variables_callback) # Initial population of the scroll box initial_html = ''.join(f""" - """ for i, option in enumerate(series_list)) - scroll_box.text = initial_html - + """ for i, variable_name in enumerate(variable_names)) + variable_scroll_box.text = initial_html # Arrange the layout using Panel - layout = pn.Row(pn.Column(ti, scroll_box), p) + layout = pn.Row(pn.Column(filter_variables_text_box, variable_scroll_box), plotting_figure) + return layout # The main script that generates all the tabs in the dashboard @@ -854,63 +856,6 @@ def dashboard(script_name, problem_recorder, driver_recorder, port, run_in_backg opt_history_pane = create_optimization_history_plot(cr,df) optimization_tabs_list.append(("Optimization History", opt_history_pane)) - # Desvars, cons, opt interactive plot TODO remove becaus the one above supercedes it - if driver_recorder: - if os.path.isfile(driver_recorder): - df = convert_driver_case_recorder_file_to_df(f"{driver_recorder}") - if df is not None: - variables = pn.widgets.CheckBoxGroup( - name="Variables", - options=list(df.columns), - # just so all of them aren't plotted from the beginning. Skip the iter count - value=list(df.columns)[1:2], - ) - ipipeline = df.interactive() - ihvplot = ipipeline.hvplot( - y=variables, - responsive=True, - min_height=400, - color=list(bp.Category10[10]), - yformatter="%.0f", - title="Model Optimization using OpenMDAO", - legend=False, - # legend="bottom", - # legend_cols=True, - # legend_muted=True, - # legend_position='right', legend_offset=(-200, -200) - ) - # ihvplot.opts(show_legend='bottom') # does not work. Still on right - # print(dir(ihvplot)) - # hm = hm.opts(legend_position='top') - - optimization_plot_pane = pn.Column( - pn.Row( - pn.Column( - variables, - pn.VSpacer(height=30), - pn.VSpacer(height=30), - # width=300, - ), - ihvplot.panel(), - ) - ) - else: - optimization_plot_pane = pn.pane.Markdown( - f"# Recorder file '{driver_recorder}' does not have Driver case recordings." - ) - else: - optimization_plot_pane = pn.pane.Markdown( - f"# Recorder file containing optimization history,'{driver_recorder}', not found.") - - optimization_plot_pane_with_doc = pn.Column( - pn.pane.HTML(f"

Plot of design variables, constraints, and objectives.

", - styles={'text-align': documentation_text_align}), - optimization_plot_pane - ) - optimization_tabs_list.append( - ("History", optimization_plot_pane_with_doc) - ) - # IPOPT report if os.path.isfile(f"{reports_dir}/IPOPT.out"): ipopt_pane = create_report_frame("text", f"{reports_dir}/IPOPT.out", ''' @@ -1131,7 +1076,7 @@ def dashboard(script_name, problem_recorder, driver_recorder, port, run_in_backg ], ) - colors = bokeh.palettes.d3['Category20'][20][0::2] + bokeh.palettes.d3['Category20'][20][1::2] + colors = d3['Category20'][20][0::2] + d3['Category20'][20][1::2] legend_data = [] phases = sorted(phases, key=str.casefold) for i, phase in enumerate(phases): @@ -1266,7 +1211,7 @@ def save_dashboard(event): header_background="rgb(0, 212, 169)", header=header, background_color="white", - theme=DefaultTheme, + theme=pn.theme.DefaultTheme, theme_toggle=False, main_layout=None, css_files=["assets/aviary_styles.css"], From 7d83d6b03c7a78ba47dad1c4fe53e96081e8d51b Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Mon, 21 Oct 2024 15:36:04 -0400 Subject: [PATCH 6/9] Move legend up a bit so less white space --- aviary/visualization/dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index d228245d4..499ba4bfe 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -626,7 +626,7 @@ def create_optimization_history_plot(case_recorder, df): # Make a Legend with no items in it. those will be added in JavaScript # as users select variables to be plotted - legend = Legend(items=[], location=(-50, -10), border_line_width=0) + legend = Legend(items=[], location=(-50, -5), border_line_width=0) # make the legend items in Python. Pass them to JavaScript where they can be added to the Legend legend_items = [] From ef4e7c64fbdc5ae8f95e6ba27f9be4f3dcfaf1b6 Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Mon, 21 Oct 2024 15:40:10 -0400 Subject: [PATCH 7/9] fixed up issues after looking at the PR Files Changed tab --- aviary/interface/test/test_height_energy_mission.py | 2 +- aviary/visualization/dashboard.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/aviary/interface/test/test_height_energy_mission.py b/aviary/interface/test/test_height_energy_mission.py index b6ef3970f..29d24b244 100644 --- a/aviary/interface/test/test_height_energy_mission.py +++ b/aviary/interface/test/test_height_energy_mission.py @@ -12,7 +12,7 @@ from aviary.variable_info.variables import Dynamic -# @use_tempdirs +@use_tempdirs class AircraftMissionTestSuite(unittest.TestCase): def setUp(self): diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index 499ba4bfe..23ce03331 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -326,7 +326,6 @@ def create_report_frame(format, text_filepath, documentation): if format == "markdown": report_pane = pn.pane.Markdown(file_text) elif format == "text": - # report_pane = pn.pane.Markdown(f"```\n{file_text}\n```\n") report_pane = pn.pane.Str(file_text) report_pane = pn.Column( pn.pane.HTML(f"

{documentation}

", styles={'text-align': 'left'}), From 23ab2e0a074679b3b742250da8faec457daf7a12 Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Tue, 22 Oct 2024 09:13:51 -0400 Subject: [PATCH 8/9] PEP8 fixes --- aviary/visualization/dashboard.py | 77 +++++++++++++++++-------------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index 23ce03331..216adf93e 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -57,6 +57,8 @@ def get_free_port(): documentation_text_align = 'left' # functions for the aviary command line command + + def _none_or_str(value): """ Get the value of the argparse option. @@ -564,16 +566,17 @@ def _get_interactive_plot_sources(data_by_varname_and_phase, x_varname, y_varnam else: return [], [] + def create_optimization_history_plot(case_recorder, df): - + # Create a ColumnDataSource source = ColumnDataSource(df) # Create a Bokeh figure plotting_figure = figure(title='Optimization History', - width=1000, - height=600, - ) + width=1000, + height=600, + ) plotting_figure.title.align = 'center' plotting_figure.yaxis.visible = False plotting_figure.xaxis.axis_label = 'Iterations' @@ -585,9 +588,9 @@ def create_optimization_history_plot(case_recorder, df): # Plot each time series and keep references to the renderers renderers = {} - variable_names = list(df.columns)[1:] + variable_names = list(df.columns)[1:] for i, variable_name in enumerate(variable_names): - color = palette[i%20] + color = palette[i % 20] renderers[variable_name] = plotting_figure.line( x='iter_count', @@ -597,33 +600,34 @@ def create_optimization_history_plot(case_recorder, df): line_width=2, visible=False, # hide them all initially. clicking checkboxes makes them visible ) - + # create axes both to the right and left of the plot. # hide them initially # as the user selects/deselects variables to be plotted, they get turned on/off - extra_y_axis = LinearAxis(y_range_name=f"extra_y_{variable_name}", - axis_label=f"{variable_name}", - axis_label_text_color=color) + extra_y_axis = LinearAxis(y_range_name=f"extra_y_{variable_name}", + axis_label=f"{variable_name}", + axis_label_text_color=color) plotting_figure.add_layout(extra_y_axis, 'right') plotting_figure.right[i].visible = False - extra_y_axis = LinearAxis(y_range_name=f"extra_y_{variable_name}", - axis_label=f"{variable_name}", - axis_label_text_color=color) - plotting_figure.add_layout(extra_y_axis, 'left') + extra_y_axis = LinearAxis(y_range_name=f"extra_y_{variable_name}", + axis_label=f"{variable_name}", + axis_label_text_color=color) + plotting_figure.add_layout(extra_y_axis, 'left') plotting_figure.left[i + 1].visible = False # set the range y_min = df[variable_name].min() y_max = df[variable_name].max() - # if the range is zero, the axis will not be displayed. Plus need some range to make it + # if the range is zero, the axis will not be displayed. Plus need some range to make it # look good. Some other code seems to do +- 1 for the range in this case. if y_min == y_max: y_min = y_min - 1 y_max = y_max + 1 - plotting_figure.extra_y_ranges[f"extra_y_{variable_name}"] = Range1d(y_min, y_max) + plotting_figure.extra_y_ranges[f"extra_y_{variable_name}"] = Range1d( + y_min, y_max) - # Make a Legend with no items in it. those will be added in JavaScript + # Make a Legend with no items in it. those will be added in JavaScript # as users select variables to be plotted legend = Legend(items=[], location=(-50, -5), border_line_width=0) @@ -631,18 +635,20 @@ def create_optimization_history_plot(case_recorder, df): legend_items = [] for variable_name in variable_names: units = case_recorder.problem_metadata['variables'][variable_name]['units'] - legend_item = LegendItem(label=f"{variable_name} ({units})", renderers=[renderers[variable_name]]) + legend_item = LegendItem(label=f"{variable_name} ({units})", renderers=[ + renderers[variable_name]]) legend_items.append(legend_item) - plotting_figure.add_layout(legend, 'below') + plotting_figure.add_layout(legend, 'below') # make the list of variables with checkboxes - data_source = ColumnDataSource(data=dict(options=variable_names, checked=[False]*len(variable_names))) + data_source = ColumnDataSource( + data=dict(options=variable_names, checked=[False]*len(variable_names))) # Create a Div to act as a scrollable container variable_scroll_box = Div( styles={ - 'overflow-y': 'scroll', - 'height': '500px', + 'overflow-y': 'scroll', + 'height': '500px', 'border': '1px solid #ddd', 'padding': '10px' } @@ -653,11 +659,11 @@ def create_optimization_history_plot(case_recorder, df): # CustomJS callback for checkbox changes variable_checkbox_callback = CustomJS(args=dict(data_source=data_source, - plotting_figure=plotting_figure, + plotting_figure=plotting_figure, renderers=renderers, - legend=legend, - legend_items=legend_items), - code=""" + legend=legend, + legend_items=legend_items), + code=""" // Three things happen in this code. // 1. turn on/off the plot lines // 2. show the legend items for the items being plotted @@ -706,10 +712,10 @@ def create_optimization_history_plot(case_recorder, df): """) # CustomJS callback for the variable filtering - filter_variables_callback = CustomJS(args=dict(data_source=data_source, - variable_scroll_box=variable_scroll_box, - variable_checkbox_callback=variable_checkbox_callback), - code=""" + filter_variables_callback = CustomJS(args=dict(data_source=data_source, + variable_scroll_box=variable_scroll_box, + variable_checkbox_callback=variable_checkbox_callback), + code=""" const filter_text = cb_obj.value.toLowerCase(); const all_options = data_source.data['options']; @@ -748,11 +754,14 @@ def create_optimization_history_plot(case_recorder, df): variable_scroll_box.text = initial_html # Arrange the layout using Panel - layout = pn.Row(pn.Column(filter_variables_text_box, variable_scroll_box), plotting_figure) + layout = pn.Row(pn.Column(filter_variables_text_box, + variable_scroll_box), plotting_figure) return layout # The main script that generates all the tabs in the dashboard + + def dashboard(script_name, problem_recorder, driver_recorder, port, run_in_background=False): """ Generate the dashboard app display. @@ -846,13 +855,13 @@ def dashboard(script_name, problem_recorder, driver_recorder, port, run_in_backg ####### Optimization Tab ####### optimization_tabs_list = [] - + # Optimization History Plot if driver_recorder: if os.path.isfile(driver_recorder): df = convert_driver_case_recorder_file_to_df(f"{driver_recorder}") cr = om.CaseReader(f"{driver_recorder}") - opt_history_pane = create_optimization_history_plot(cr,df) + opt_history_pane = create_optimization_history_plot(cr, df) optimization_tabs_list.append(("Optimization History", opt_history_pane)) # IPOPT report @@ -1233,7 +1242,7 @@ def save_dashboard(event): home_dir = "." if port == 0: port = get_free_port() - + server = pn.serve( template, port=port, From 531ee6493cfc5b2dfe88fc337c83e86ecd6a4507 Mon Sep 17 00:00:00 2001 From: Herb Schilling Date: Tue, 5 Nov 2024 15:35:35 -0500 Subject: [PATCH 9/9] Missing setting the y_range_name in the call to the line function meant the plots were not making use of the ranges set in other parts of the code --- aviary/visualization/dashboard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index 216adf93e..8dcf5eee5 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -596,6 +596,7 @@ def create_optimization_history_plot(case_recorder, df): x='iter_count', y=variable_name, source=source, + y_range_name=f"extra_y_{variable_name}", color=color, line_width=2, visible=False, # hide them all initially. clicking checkboxes makes them visible