pyfemtet 0.4.10__py3-none-any.whl → 0.4.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyfemtet might be problematic. Click here for more details.

Files changed (71) hide show
  1. pyfemtet/__init__.py +1 -1
  2. pyfemtet/opt/_femopt.py +9 -3
  3. pyfemtet/opt/interface/_femtet.py +32 -10
  4. pyfemtet/opt/interface/_femtet_parametric.py +5 -25
  5. pyfemtet/opt/opt/_base.py +9 -1
  6. pyfemtet/opt/opt/_optuna.py +4 -0
  7. pyfemtet/opt/visualization/__init__.py +0 -7
  8. pyfemtet/opt/visualization/_create_wrapped_components.py +93 -0
  9. pyfemtet/opt/visualization/base.py +254 -0
  10. pyfemtet/opt/visualization/complex_components/__init__.py +0 -0
  11. pyfemtet/opt/visualization/complex_components/alert_region.py +71 -0
  12. pyfemtet/opt/visualization/complex_components/control_femtet.py +195 -0
  13. pyfemtet/opt/visualization/{_graphs.py → complex_components/main_figure_creator.py} +13 -49
  14. pyfemtet/opt/visualization/complex_components/main_graph.py +263 -0
  15. pyfemtet/opt/visualization/process_monitor/__init__.py +0 -0
  16. pyfemtet/opt/visualization/process_monitor/application.py +201 -0
  17. pyfemtet/opt/visualization/process_monitor/pages.py +276 -0
  18. pyfemtet/opt/visualization/result_viewer/.gitignore +1 -0
  19. pyfemtet/opt/visualization/result_viewer/__init__.py +0 -0
  20. pyfemtet/opt/visualization/result_viewer/application.py +44 -0
  21. pyfemtet/opt/visualization/result_viewer/pages.py +692 -0
  22. pyfemtet/opt/visualization/result_viewer/tutorial/tutorial.csv +18 -0
  23. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.jpg +0 -0
  24. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.log +81 -0
  25. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.pdt +0 -0
  26. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_heatflow.csv +28 -0
  27. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_heatflow_el.csv +22 -0
  28. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial1.jpg +0 -0
  29. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial1.pdt +0 -0
  30. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial10.jpg +0 -0
  31. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial10.pdt +0 -0
  32. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial11.jpg +0 -0
  33. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial11.pdt +0 -0
  34. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial12.jpg +0 -0
  35. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial12.pdt +0 -0
  36. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial13.jpg +0 -0
  37. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial13.pdt +0 -0
  38. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial14.jpg +0 -0
  39. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial14.pdt +0 -0
  40. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial15.jpg +0 -0
  41. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial15.pdt +0 -0
  42. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial2.jpg +0 -0
  43. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial2.pdt +0 -0
  44. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial3.jpg +0 -0
  45. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial3.pdt +0 -0
  46. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial4.jpg +0 -0
  47. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial4.pdt +0 -0
  48. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial5.jpg +0 -0
  49. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial5.pdt +0 -0
  50. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial6.jpg +0 -0
  51. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial6.pdt +0 -0
  52. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial7.jpg +0 -0
  53. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial7.pdt +0 -0
  54. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial8.jpg +0 -0
  55. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial8.pdt +0 -0
  56. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial9.jpg +0 -0
  57. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial9.pdt +0 -0
  58. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.femprj +0 -0
  59. pyfemtet/opt/visualization/wrapped_components/__init__.py +0 -0
  60. pyfemtet/opt/visualization/wrapped_components/dbc.py +1518 -0
  61. pyfemtet/opt/visualization/wrapped_components/dcc.py +609 -0
  62. pyfemtet/opt/visualization/wrapped_components/html.py +3570 -0
  63. pyfemtet/opt/visualization/wrapped_components/str_enum.py +43 -0
  64. {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.11.dist-info}/METADATA +1 -1
  65. pyfemtet-0.4.11.dist-info/RECORD +136 -0
  66. {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.11.dist-info}/entry_points.txt +1 -1
  67. pyfemtet/opt/visualization/_monitor.py +0 -1227
  68. pyfemtet/opt/visualization/result_viewer.py +0 -13
  69. pyfemtet-0.4.10.dist-info/RECORD +0 -83
  70. {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.11.dist-info}/LICENSE +0 -0
  71. {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.11.dist-info}/WHEEL +0 -0
@@ -0,0 +1,195 @@
1
+ # type hint
2
+ from dash.development.base_component import Component
3
+
4
+ # callback
5
+ from dash import Output, Input, State, no_update, callback_context
6
+ from dash.exceptions import PreventUpdate
7
+
8
+ # components
9
+ from pyfemtet.opt.visualization.wrapped_components import html, dcc, dbc
10
+
11
+ # the others
12
+ import logging
13
+ import os
14
+ from enum import Enum
15
+ import json
16
+ # noinspection PyUnresolvedReferences
17
+ from pythoncom import com_error
18
+
19
+ from pyfemtet.opt.visualization.base import PyFemtetApplicationBase, AbstractPage, logger
20
+ from pyfemtet.opt.interface._femtet import FemtetInterface
21
+
22
+
23
+ class FemtetState(Enum):
24
+ unconnected = -1
25
+ missing = 0
26
+ connected_but_empty = 1
27
+ connected = 2
28
+
29
+
30
+ class FemtetControl(AbstractPage):
31
+
32
+ def __init__(self):
33
+ super().__init__()
34
+ self.fem: FemtetInterface = None
35
+
36
+ def setup_component(self):
37
+
38
+ # noinspection PyAttributeOutsideInit
39
+ self.dummy = html.Div(
40
+ id='control-femtet-dummy',
41
+ hidden=True,
42
+ )
43
+
44
+ # button
45
+ # noinspection PyAttributeOutsideInit
46
+ self.connect_femtet_button = dbc.Button(
47
+ children='Connect to Femtet',
48
+ id='connect-femtet-button',
49
+ outline=True,
50
+ color='primary',
51
+ )
52
+
53
+ # noinspection PyAttributeOutsideInit
54
+ self.connect_femtet_button_spinner = dbc.Spinner(
55
+ self.connect_femtet_button,
56
+ color='primary',
57
+ )
58
+
59
+ # store femtet connection state
60
+ # noinspection PyAttributeOutsideInit
61
+ self.femtet_state_prop = 'data-femtet-state' # must start with "data-"
62
+ # noinspection PyAttributeOutsideInit
63
+ self.femtet_state = html.Data(
64
+ id='femtet-state',
65
+ **{
66
+ self.femtet_state_prop: {
67
+ 'pid': 0,
68
+ 'alert': '',
69
+ }
70
+ }
71
+ )
72
+
73
+ def setup_layout(self):
74
+ self.layout = [self.dummy, self.femtet_state]
75
+
76
+ def setup_callback(self):
77
+ # setup callback of subpages
78
+ super().setup_callback()
79
+
80
+ app = self.application.app
81
+
82
+ # ===== launch femtet and show spinner =====
83
+ @app.callback(
84
+ Output(self.femtet_state.id, self.femtet_state_prop),
85
+ Output(self.connect_femtet_button.id, self.connect_femtet_button.Prop.children), # for spinner
86
+ Input(self.connect_femtet_button.id, self.connect_femtet_button.Prop.n_clicks),)
87
+ def connect_femtet(_):
88
+
89
+ # ignore when loaded
90
+ if callback_context.triggered_id is None:
91
+ raise PreventUpdate
92
+
93
+ # create FemtetInterface arguments
94
+ kwargs, warning_msg = self.create_femtet_interface_args()
95
+
96
+ # try to launch femtet
97
+ try:
98
+ self.fem = FemtetInterface(**kwargs)
99
+
100
+ except com_error:
101
+ # model_name is not included in femprj
102
+ kwargs.update(dict(model_name=None))
103
+ self.fem = FemtetInterface(**kwargs)
104
+ warning_msg = 'Analysis model name described in csv does not exist in project.'
105
+
106
+ # if 'new' femtet, Interface try to terminate Femtet when the self.fem is reconstructed.
107
+ self.fem.quit_when_destruct = False
108
+
109
+ state_data = {
110
+ 'pid': self.fem.femtet_pid,
111
+ 'alert': warning_msg,
112
+ }
113
+
114
+ return state_data, no_update
115
+
116
+ def create_femtet_interface_args(self) -> [dict, str]:
117
+ """Returns the argument of FemtetInterface and warning message
118
+
119
+ # femprj_path=None,
120
+ # model_name=None,
121
+ # connect_method='auto', # dask worker では __init__ の中で 'new' にするので super() の引数にしない。(しても意味がない)
122
+ # save_pdt='all', # 'all' or None
123
+ # strictly_pid_specify=True, # dask worker では True にしたいので super() の引数にしない。
124
+ # allow_without_project=False, # main でのみ True を許容したいので super() の引数にしない。
125
+ # open_result_with_gui=True,
126
+ # parametric_output_indexes_use_as_objective=None,
127
+
128
+ """
129
+
130
+ kwargs = dict(
131
+ connect_method='auto',
132
+ femprj_path=None,
133
+ model_name=None,
134
+ allow_without_project=True,
135
+ )
136
+
137
+ # check holding history
138
+ if self.application.history is None:
139
+ return kwargs, 'History csv is not read yet. Open your project manually.'
140
+
141
+ # get metadata
142
+ additional_metadata = self.application.history.metadata[0]
143
+
144
+ # check metadata exists
145
+ if additional_metadata == '':
146
+ return kwargs, 'Cannot read project data from csv. Open your project manually.'
147
+
148
+ # check the metadata is valid json
149
+ try:
150
+ d = json.loads(additional_metadata)
151
+ femprj_path = os.path.abspath(d['femprj_path'])
152
+ except (TypeError, json.decoder.JSONDecodeError, KeyError):
153
+ return kwargs, ('Cannot read project data from csv because of the invalid format. '
154
+ 'Valid format is like: '
155
+ '"{""femprj_path"": ""c:\\path\\to\\sample.femprj"", ""model_name"": ""<model_name>""}". '
156
+ 'Open your project manually.')
157
+
158
+ # check containing femprj
159
+ if femprj_path is None:
160
+ return kwargs, 'Cannot read project data from csv. Open your project manually.'
161
+
162
+ # check femprj exists
163
+ if not os.path.exists(femprj_path):
164
+ return kwargs, '.femprj file described in csv is not found. Open your project manually.'
165
+
166
+ # at this point, femprj is valid at least.
167
+ kwargs.update({'femprj_path': femprj_path})
168
+
169
+ # check model name
170
+ model_name = d['model_name'] if 'model_name' in d.keys() else None
171
+ msg = '' if model_name is not None else 'Analysis model name is not specified. Open your model in the project manually.'
172
+ kwargs.update({'model_name': model_name})
173
+ return kwargs, msg
174
+
175
+ def check_femtet_state(self) -> FemtetState:
176
+
177
+ # check fem is initialized
178
+ if self.fem is None:
179
+ return FemtetState.missing
180
+
181
+ Femtet = self.fem.Femtet
182
+
183
+ # check Femtet is constructed
184
+ if Femtet is None:
185
+ return FemtetState.missing
186
+
187
+ # check Femtet is alive
188
+ if not self.fem.femtet_is_alive():
189
+ return FemtetState.missing
190
+
191
+ # check a project is opened
192
+ if Femtet.Project == '':
193
+ return FemtetState.connected_but_empty
194
+
195
+ return FemtetState.connected
@@ -1,6 +1,8 @@
1
1
  import plotly.graph_objs as go
2
2
  import plotly.express as px
3
3
 
4
+ from pyfemtet.opt._femopt_core import History
5
+
4
6
 
5
7
  class _ColorSet:
6
8
  non_domi = {True: '#007bff', False: '#6c757d'} # color
@@ -37,7 +39,7 @@ _cs = _ColorSet()
37
39
  _ss = _SymbolSet()
38
40
 
39
41
 
40
- def update_hypervolume_plot(history, df):
42
+ def get_hypervolume_plot(_: History, df):
41
43
  df = _ls.localize(df)
42
44
 
43
45
  # create figure
@@ -58,26 +60,28 @@ def update_hypervolume_plot(history, df):
58
60
  return fig
59
61
 
60
62
 
61
- def update_default_figure(history, df):
63
+ def get_default_figure(history, df):
64
+ # df = history.local_data # monitor process and history process is different workers, so history.local_data is not updated in monitor process.
65
+ # df = history.actor_data.copy() # access to actor from flask callback makes termination unstable.
62
66
 
63
67
  # data setting
64
68
  obj_names = history.obj_names
65
69
 
66
- if len(obj_names) == 0:
67
- fig = go.Figure()
70
+ fig = go.Figure()
68
71
 
69
- elif len(obj_names) == 1:
70
- fig = update_single_objective_plot(history, df)
72
+ if len(obj_names) == 1:
73
+ fig = _get_single_objective_plot(history, df)
71
74
 
72
75
  elif len(obj_names) >= 2:
73
- fig = update_multi_objective_pairplot(history, df)
76
+ fig = _get_multi_objective_pairplot(history, df)
74
77
 
75
78
  fig.update_traces(hoverinfo="none", hovertemplate=None)
79
+ fig.update_layout(clickmode='event+select')
76
80
 
77
81
  return fig
78
82
 
79
83
 
80
- def update_single_objective_plot(history, df):
84
+ def _get_single_objective_plot(history, df):
81
85
 
82
86
  df = _ls.localize(df)
83
87
  obj_name = history.obj_names[0]
@@ -125,7 +129,7 @@ def update_single_objective_plot(history, df):
125
129
  return fig
126
130
 
127
131
 
128
- def update_multi_objective_pairplot(history, df):
132
+ def _get_multi_objective_pairplot(history, df):
129
133
  df = _ls.localize(df)
130
134
 
131
135
  obj_names = history.obj_names
@@ -183,43 +187,3 @@ def update_multi_objective_pairplot(history, df):
183
187
  )
184
188
 
185
189
  return fig
186
-
187
-
188
- def _debug():
189
- import os
190
-
191
- os.chdir(os.path.dirname(__file__))
192
- csv_path = 'sample.csv'
193
-
194
- show_static_monitor(csv_path)
195
-
196
-
197
- def show_static_monitor(csv_path):
198
- from pyfemtet.opt._femopt_core import History
199
- from pyfemtet.opt.visualization._monitor import ResultViewerApp
200
- _h = History(history_path=csv_path)
201
- _monitor = ResultViewerApp(history=_h)
202
- _monitor.run()
203
-
204
-
205
- def entry_point():
206
- import argparse
207
- parser = argparse.ArgumentParser()
208
-
209
- parser.add_argument('csv_path', help='pyfemtet を実行した結果の csv ファイルのパスを指定してください。', type=str)
210
-
211
- # parser.add_argument(
212
- # "-c",
213
- # "--csv-path",
214
- # help="pyfemtet.opt による最適化結果 csv ファイルパス",
215
- # type=str,
216
- # )
217
-
218
- args = parser.parse_args()
219
-
220
- if args.csv_path:
221
- show_static_monitor(args.csv_path)
222
-
223
-
224
- if __name__ == '__main__':
225
- _debug()
@@ -0,0 +1,263 @@
1
+ # type hint
2
+ from dash.development.base_component import Component
3
+
4
+ # callback
5
+ from dash import Output, Input, State, no_update, callback_context
6
+ from dash.exceptions import PreventUpdate
7
+
8
+ # components
9
+ from dash import dash_table
10
+ from pyfemtet.opt.visualization.wrapped_components import html, dcc, dbc
11
+
12
+ # graph
13
+ import pandas as pd
14
+ # import plotly.express as px
15
+ import plotly.graph_objs as go
16
+
17
+ # the others
18
+ import os
19
+ import base64
20
+ import json
21
+ import numpy as np
22
+
23
+ from pyfemtet.opt.visualization.complex_components import main_figure_creator
24
+ from pyfemtet.opt.visualization.base import PyFemtetApplicationBase, AbstractPage, logger
25
+
26
+
27
+ FLEXBOX_STYLE_ALLOW_VERTICAL_FILL = {
28
+ 'display': 'flex',
29
+ 'flex-direction': 'column',
30
+ 'flex-grow': '1',
31
+ }
32
+
33
+
34
+ # noinspection PyAttributeOutsideInit
35
+ class MainGraph(AbstractPage):
36
+ """"""
37
+ """
38
+ +=================+
39
+ |tab1|tab2| | <- CardHeader
40
+ | +------------+
41
+ | +-------------+ |
42
+ | | Loading | | <- CardBody
43
+ | | +---------+ | |
44
+ | | | graph <-------- ToolTip
45
+ | | +---------+ | |
46
+ | +-------------+ |
47
+ +=================+
48
+
49
+ Data(data-selection)
50
+
51
+ """
52
+
53
+ def __init__(self):
54
+ self.setup_figure_creator()
55
+ super().__init__()
56
+
57
+ def setup_figure_creator(self):
58
+ # setup figure creators
59
+ # list[0] is the default tab
60
+ self.figure_creators = [
61
+ dict(
62
+ tab_id='tab-objective-plot',
63
+ label='objectives',
64
+ creator=main_figure_creator.get_default_figure,
65
+ ),
66
+ dict(
67
+ tab_id='tab-hypervolume-plot',
68
+ label='hypervolume',
69
+ creator=main_figure_creator.get_hypervolume_plot,
70
+ ),
71
+ ]
72
+
73
+ def setup_component(self):
74
+ self.dummy = html.Div(id='main-graph-dummy')
75
+ self.location = dcc.Location(id='main-graph-location', refresh=True)
76
+
77
+ # setup header
78
+ self.tab_list = [dbc.Tab(label=d['label'], tab_id=d['tab_id']) for d in self.figure_creators]
79
+ self.tabs = dbc.Tabs(self.tab_list, id='main-graph-tabs')
80
+ self.card_header = dbc.CardHeader(self.tabs)
81
+
82
+ # setup body
83
+ self.tooltip = dcc.Tooltip(id='main-graph-tooltip')
84
+
85
+ # set kwargs of Graph to reconstruct Graph in ProcessMonitor.
86
+ self.graph_kwargs = dict(
87
+ id='main-graph',
88
+ # animate=True, # THIS CAUSE THE UPDATED DATA / NOT-UPDATED FIGURE STATE.
89
+ clear_on_unhover=True,
90
+ style={
91
+ # 'flex-grow': '1', # Uncomment if the plotly's specification if fixed, and we can use dcc.Graph with FlexBox.
92
+ 'height': '60vh',
93
+ },
94
+ figure=go.Figure()
95
+ )
96
+
97
+ self.graph: dcc.Graph = dcc.Graph(
98
+ **self.graph_kwargs
99
+ ) # Graph make an element by js, so Flexbox CSS cannot apply to the graph (The element can be bigger, but cannot be smaller.)
100
+
101
+ self.loading = dcc.Loading(
102
+ children=self.graph,
103
+ id='loading-main-graph',
104
+ ) # Loading make an element that doesn't contain a style attribute, so Flexbox CSS cannot apply to the graph
105
+
106
+ self.card_body = dbc.CardBody(
107
+ children=html.Div([self.loading, self.tooltip]), # If list is passed to CardBody's children, create element that doesn't contain a style attribute, so Flexbox CSS cannot apply to graph
108
+ id='main-graph-card-body',
109
+ # style=FLEXBOX_STYLE_ALLOW_VERTICAL_FILL,
110
+ )
111
+
112
+ # setup selection data
113
+ self.selection_data_property = 'data-selection' # must be starts with "data-"
114
+ self.selection_data = html.Data(id='selection-data', **{self.selection_data_property: {}})
115
+
116
+ # set data length
117
+ self.data_length_prop = 'data-df-length' # must start with "data-"
118
+ self.data_length = html.Data(id='df-length-data', **{self.data_length_prop: None})
119
+
120
+ def setup_layout(self):
121
+ # setup component
122
+ self.graph_card = dbc.Card(
123
+ [
124
+ self.dummy,
125
+ self.location,
126
+ self.data_length,
127
+ self.card_header,
128
+ self.card_body,
129
+ self.selection_data,
130
+ ],
131
+ # style=FLEXBOX_STYLE_ALLOW_VERTICAL_FILL,
132
+ )
133
+
134
+ self.layout = self.graph_card
135
+
136
+ def setup_callback(self):
137
+ # setup callback of subpages
138
+ super().setup_callback()
139
+
140
+ app = self.application.app
141
+
142
+ # ===== Update Graph =====
143
+ @app.callback(
144
+ Output(self.graph.id, 'figure'),
145
+ Output(self.data_length.id, self.data_length_prop), # To determine whether Process Monitor should update the graph, the main graph remembers the current amount of data.
146
+ Input(self.tabs.id, 'active_tab'),
147
+ Input(self.dummy, 'children'),
148
+ prevent_initial_call=False,)
149
+ def redraw_main_graph(active_tab_id, _):
150
+ logger.debug('====================')
151
+ logger.debug(f'redraw_main_graph called by {callback_context.triggered_id}')
152
+ figure, length = self.get_fig_by_tab_id(active_tab_id, with_length=True)
153
+ return figure, length
154
+
155
+ # ===== Save Selection =====
156
+ @app.callback(
157
+ Output(self.selection_data.id, self.selection_data_property),
158
+ Input(self.graph.id, 'selectedData'))
159
+ def save_selection(data):
160
+ return data
161
+
162
+ # ===== Show Image and Parameter on Hover =====
163
+ @app.callback(
164
+ Output(self.tooltip.id, "show"),
165
+ Output(self.tooltip.id, "bbox"),
166
+ Output(self.tooltip.id, "children"),
167
+ Input(self.graph.id, "hoverData"),)
168
+ def show_hover(hoverData):
169
+ if hoverData is None:
170
+ return False, no_update, no_update
171
+
172
+ if self.application.history is None:
173
+ raise PreventUpdate
174
+
175
+ # get hover location
176
+ pt = hoverData["points"][0]
177
+ bbox = pt["bbox"]
178
+
179
+ # get row of the history from customdata defined in main_figure
180
+ trial = pt['customdata'][0]
181
+
182
+ df = self.data_accessor()
183
+ row = df[df['trial'] == trial]
184
+
185
+ # create component
186
+ title_component = html.H3(f"trial{trial}", style={"color": "darkblue"})
187
+ img_component = self.create_image_content_if_femtet(trial)
188
+ tbl_component = self.create_formatted_parameter(row)
189
+
190
+ # create layout
191
+ description = html.Div([
192
+ title_component,
193
+ tbl_component,
194
+ ])
195
+ tooltip_layout = html.Div([
196
+ html.Div(img_component, style={'display': 'inline-block', 'margin-right': '10px', 'vertical-align': 'top'}),
197
+ html.Div(description, style={'display': 'inline-block', 'margin-right': '10px'})
198
+ ])
199
+
200
+ return True, bbox, tooltip_layout
201
+
202
+ def create_formatted_parameter(self, row) -> Component:
203
+ metadata = self.application.history.metadata
204
+ pd.options.display.float_format = '{:.4e}'.format
205
+ parameters = row.iloc[:, np.where(np.array(metadata) == 'prm')[0]]
206
+ names = parameters.columns
207
+ values = [f'{value:.3e}' for value in parameters.values.ravel()]
208
+ data = pd.DataFrame(dict(
209
+ name=names, value=values
210
+ ))
211
+ table = dash_table.DataTable(
212
+ columns=[{'name': col, 'id': col} for col in data.columns],
213
+ data=data.to_dict('records')
214
+ )
215
+ return table
216
+
217
+ def create_image_content_if_femtet(self, trial) -> Component:
218
+ img_url = None
219
+ metadata = self.application.history.metadata
220
+ if metadata[0] != '':
221
+ # get img path
222
+ d = json.loads(metadata[0])
223
+ femprj_path = d['femprj_path']
224
+ model_name = d['model_name']
225
+ femprj_result_dir = femprj_path.replace('.femprj', '.Results')
226
+ img_path = os.path.join(femprj_result_dir, f'{model_name}_trial{trial}.jpg')
227
+ if os.path.exists(img_path):
228
+ # create encoded image
229
+ with open(img_path, 'rb') as f:
230
+ content = f.read()
231
+ encoded_image = base64.b64encode(content).decode('utf-8')
232
+ img_url = 'data:image/jpeg;base64, ' + encoded_image
233
+ return html.Img(src=img_url, style={"width": "200px"}) if img_url is not None else html.Div()
234
+
235
+ def get_fig_by_tab_id(self, tab_id, with_length=False):
236
+ # If the history is not loaded, do nothing
237
+ if self.application.history is None:
238
+ raise PreventUpdate
239
+
240
+ # else, get creator by tab_id
241
+ if tab_id == 'default':
242
+ creator = self.figure_creators[0]['creator']
243
+ else:
244
+ creators = [d['creator'] for d in self.figure_creators if d['tab_id'] == tab_id]
245
+ if len(creators) == 0:
246
+ raise PreventUpdate
247
+ creator = creators[0]
248
+
249
+ # create figure
250
+ df = self.data_accessor()
251
+ fig = creator(self.application.history, df)
252
+ if with_length:
253
+ return fig, len(df)
254
+ else:
255
+ return fig
256
+
257
+ def data_accessor(self) -> pd.DataFrame:
258
+ from pyfemtet.opt.visualization.process_monitor.application import ProcessMonitorApplication
259
+ if isinstance(self.application, ProcessMonitorApplication):
260
+ df = self.application.local_data
261
+ else:
262
+ df = self.application.history.local_data
263
+ return df
File without changes