py-pilecore 0.5.0__py3-none-any.whl → 0.6.0__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 py-pilecore might be problematic. Click here for more details.
- {py_pilecore-0.5.0.dist-info → py_pilecore-0.6.0.dist-info}/METADATA +5 -2
- {py_pilecore-0.5.0.dist-info → py_pilecore-0.6.0.dist-info}/RECORD +21 -10
- {py_pilecore-0.5.0.dist-info → py_pilecore-0.6.0.dist-info}/WHEEL +1 -1
- pypilecore/_version.py +1 -1
- pypilecore/results/__init__.py +2 -0
- pypilecore/results/cases_multi_cpt_results.py +244 -0
- pypilecore/results/multi_cpt_results.py +1 -1
- pypilecore/results/result_definitions.py +195 -0
- pypilecore/results/single_cpt_results.py +1 -1
- pypilecore/results/soil_properties.py +2 -2
- pypilecore/viewers/__init__.py +9 -0
- pypilecore/viewers/interactive_figures/__init__.py +15 -0
- pypilecore/viewers/interactive_figures/figure_cpt_group_results_versus_ptls.py +193 -0
- pypilecore/viewers/interactive_figures/figure_cpt_results_plan_view.py +268 -0
- pypilecore/viewers/interactive_figures/figure_cpt_results_versus_ptls.py +176 -0
- pypilecore/viewers/interactive_figures/utils.py +47 -0
- pypilecore/viewers/viewer_cpt_group_results.py +75 -0
- pypilecore/viewers/viewer_cpt_results.py +87 -0
- pypilecore/viewers/viewer_cpt_results_plan_view.py +101 -0
- {py_pilecore-0.5.0.dist-info → py_pilecore-0.6.0.dist-info}/LICENSE +0 -0
- {py_pilecore-0.5.0.dist-info → py_pilecore-0.6.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from __future__ import annotations # noqa: F404
|
|
2
|
+
|
|
3
|
+
from typing import Hashable, List
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import plotly.express as px
|
|
7
|
+
import plotly.graph_objects as go
|
|
8
|
+
from natsort import natsort_keygen
|
|
9
|
+
|
|
10
|
+
from pypilecore.results.cases_multi_cpt_results import CasesMultiCPTBearingResults
|
|
11
|
+
from pypilecore.results.result_definitions import CPTGroupResultDefinitions
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FigureCPTGroupResultsVersusPtls:
|
|
15
|
+
"""
|
|
16
|
+
Interactive figure to show the CPT grouped results of the bearing capacity calculations
|
|
17
|
+
versus the pile tip levels (PTLs).
|
|
18
|
+
|
|
19
|
+
The layout of the figure is:
|
|
20
|
+
- X axis: result values.
|
|
21
|
+
- Y axis: pile tip level w.r.t. NAP.
|
|
22
|
+
- Each trace represents a different case.
|
|
23
|
+
|
|
24
|
+
The figure has a method to switch between results.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, cases_multi_results: CasesMultiCPTBearingResults) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Initializes the figure.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
cases_multi_results : CasesMultiCPTBearingResults
|
|
34
|
+
The results of the bearing capacity calculations.
|
|
35
|
+
|
|
36
|
+
Raises
|
|
37
|
+
------
|
|
38
|
+
TypeError
|
|
39
|
+
If `cases_multi_results` are not of type 'CasesMultiCPTBearingResults'.
|
|
40
|
+
"""
|
|
41
|
+
# Validate the data
|
|
42
|
+
self._set_results(cases_multi_results)
|
|
43
|
+
|
|
44
|
+
# Initialize the figure
|
|
45
|
+
self._figure = go.FigureWidget(
|
|
46
|
+
layout=go.Layout(
|
|
47
|
+
height=800,
|
|
48
|
+
width=800,
|
|
49
|
+
title="CPT Group Results vs. Pile tip level for all cases<br>Result: ",
|
|
50
|
+
legend_title="Case",
|
|
51
|
+
colorway=px.colors.qualitative.Plotly,
|
|
52
|
+
xaxis_title="",
|
|
53
|
+
yaxis=go.layout.YAxis(
|
|
54
|
+
title="Pile tip level [m NAP]",
|
|
55
|
+
title_font_size=18,
|
|
56
|
+
),
|
|
57
|
+
autosize=False,
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def _set_results(self, value: CasesMultiCPTBearingResults) -> None:
|
|
62
|
+
"""Private setter for the results."""
|
|
63
|
+
if not isinstance(value, CasesMultiCPTBearingResults):
|
|
64
|
+
raise TypeError(
|
|
65
|
+
f"Expected type 'CasesMultiCPTBearingResults' for 'cases_multi_results', but got {type(value)}"
|
|
66
|
+
)
|
|
67
|
+
self._results = value
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def results(self) -> CasesMultiCPTBearingResults:
|
|
71
|
+
"""The results of the bearing capacity calculations."""
|
|
72
|
+
return self._results
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def data(self) -> pd.DataFrame:
|
|
76
|
+
"""The dataframe used to plot the results."""
|
|
77
|
+
return self.results.cpt_group_results_dataframe
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def cases(self) -> List[Hashable]:
|
|
81
|
+
"""The case names of each MultiCPTBearingResults."""
|
|
82
|
+
return self.results.cases
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def figure(self) -> go.FigureWidget:
|
|
86
|
+
"""The figure widget."""
|
|
87
|
+
return self._figure
|
|
88
|
+
|
|
89
|
+
def get_visible_cases(self) -> List[Hashable]:
|
|
90
|
+
"""
|
|
91
|
+
Returns the visible cases in the figure widget.
|
|
92
|
+
|
|
93
|
+
Notes
|
|
94
|
+
-----
|
|
95
|
+
- If there are no traces in the figure, it returns all the case names.
|
|
96
|
+
- If a case is None, then it is represented as "None".
|
|
97
|
+
"""
|
|
98
|
+
if len(self.figure.data) == 0:
|
|
99
|
+
# Select all cases if there are no traces in the figure yet.
|
|
100
|
+
cases: List[Hashable] = []
|
|
101
|
+
for case in self.cases:
|
|
102
|
+
if case is None:
|
|
103
|
+
cases.append("None")
|
|
104
|
+
else:
|
|
105
|
+
cases.append(case)
|
|
106
|
+
return cases
|
|
107
|
+
return [trace.name for trace in self.figure.data if trace.visible is True]
|
|
108
|
+
|
|
109
|
+
def show_result(self, result_name: str) -> None:
|
|
110
|
+
"""Shows the group results for all pile tip levels for the requested `result_name`.
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
result_name : str
|
|
115
|
+
The name of the result to show.
|
|
116
|
+
|
|
117
|
+
Raises
|
|
118
|
+
------
|
|
119
|
+
ValueError
|
|
120
|
+
If the `result_name` is not found in the CPTResultDefinitions.
|
|
121
|
+
"""
|
|
122
|
+
# Get the result definition that corresponds to the result name.
|
|
123
|
+
result_definition = CPTGroupResultDefinitions.get(result_name)
|
|
124
|
+
|
|
125
|
+
# Get the visible cases
|
|
126
|
+
visible_cases = self.get_visible_cases()
|
|
127
|
+
|
|
128
|
+
# Select data for result type
|
|
129
|
+
selected_data = self.data.loc[
|
|
130
|
+
self.data["result_name"] == result_definition.name
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
# Organize data and format depending on result type
|
|
134
|
+
mode = "lines+markers"
|
|
135
|
+
marker_size = 6
|
|
136
|
+
if result_definition in [
|
|
137
|
+
CPTGroupResultDefinitions.cpt_Rc_min,
|
|
138
|
+
CPTGroupResultDefinitions.cpt_Rc_max,
|
|
139
|
+
CPTGroupResultDefinitions.cpt_normative,
|
|
140
|
+
CPTGroupResultDefinitions.use_group_average,
|
|
141
|
+
CPTGroupResultDefinitions.xi_normative,
|
|
142
|
+
CPTGroupResultDefinitions.n_cpts,
|
|
143
|
+
]:
|
|
144
|
+
selected_data = selected_data.sort_values(by="result", key=natsort_keygen())
|
|
145
|
+
mode = "markers"
|
|
146
|
+
marker_size = 10
|
|
147
|
+
|
|
148
|
+
traces = []
|
|
149
|
+
for case in self.cases:
|
|
150
|
+
if case is not None:
|
|
151
|
+
df = selected_data.loc[selected_data["case_name"] == case]
|
|
152
|
+
else:
|
|
153
|
+
df = selected_data.loc[selected_data["case_name"].isna()]
|
|
154
|
+
|
|
155
|
+
traces.append(
|
|
156
|
+
go.Scatter(
|
|
157
|
+
x=df["result"],
|
|
158
|
+
y=df["pile_tip_level_nap"],
|
|
159
|
+
mode=mode,
|
|
160
|
+
name=case if case is not None else "None",
|
|
161
|
+
marker_size=marker_size,
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
with self.figure.batch_update():
|
|
166
|
+
# Empty traces
|
|
167
|
+
self.figure.data = []
|
|
168
|
+
|
|
169
|
+
# Apply changes
|
|
170
|
+
self.figure.add_traces(traces)
|
|
171
|
+
|
|
172
|
+
self.figure.update_layout(
|
|
173
|
+
title=f"CPT Group Results vs. Pile tip level for all cases<br>Result: {result_definition.value.html}",
|
|
174
|
+
xaxis=go.layout.XAxis(
|
|
175
|
+
title=f"{result_definition.value.html} [{result_definition.value.unit}]",
|
|
176
|
+
title_font_size=18,
|
|
177
|
+
),
|
|
178
|
+
showlegend=True,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
self.figure.update_traces(
|
|
182
|
+
selector=lambda x: x.name in visible_cases,
|
|
183
|
+
patch=dict(
|
|
184
|
+
visible=True,
|
|
185
|
+
),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
self.figure.update_traces(
|
|
189
|
+
selector=lambda x: x.name not in visible_cases,
|
|
190
|
+
patch=dict(
|
|
191
|
+
visible="legendonly",
|
|
192
|
+
),
|
|
193
|
+
)
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
from __future__ import annotations # noqa: F404
|
|
2
|
+
|
|
3
|
+
from typing import Hashable, List
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import plotly.express as px
|
|
8
|
+
import plotly.graph_objects as go
|
|
9
|
+
|
|
10
|
+
from pypilecore.results.cases_multi_cpt_results import CasesMultiCPTBearingResults
|
|
11
|
+
from pypilecore.results.result_definitions import CPTResultDefinitions
|
|
12
|
+
from pypilecore.viewers.interactive_figures.utils import get_continuous_color
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FigureCPTResultsPlanView:
|
|
16
|
+
"""
|
|
17
|
+
Interactive figure to show the CPT results of the bearing capacity calculations
|
|
18
|
+
in plan view for a fixed pile tip level (PTL).
|
|
19
|
+
|
|
20
|
+
The layout of the figure is:
|
|
21
|
+
- X axis: X coordinate.
|
|
22
|
+
- Y axis: Y coordinate.
|
|
23
|
+
- Each point represents a different CPT, but the same pile tip level.
|
|
24
|
+
|
|
25
|
+
The figure has a method to switch between case, result and pile tip level.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, cases_multi_results: CasesMultiCPTBearingResults) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Initializes the figure.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
cases_multi_results : CasesMultiCPTBearingResults
|
|
35
|
+
The results of the bearing capacity calculations.
|
|
36
|
+
|
|
37
|
+
Raises
|
|
38
|
+
------
|
|
39
|
+
TypeError
|
|
40
|
+
If `cases_multi_results` are not of type 'CasesMultiCPTBearingResults'.
|
|
41
|
+
"""
|
|
42
|
+
# Validate the data
|
|
43
|
+
self._set_results(cases_multi_results)
|
|
44
|
+
|
|
45
|
+
# Initialize the figure
|
|
46
|
+
self._figure = go.FigureWidget(
|
|
47
|
+
layout=go.Layout(
|
|
48
|
+
height=800,
|
|
49
|
+
width=1200,
|
|
50
|
+
title="CPT Results in Plan View<br>Case: , Pile tip level [m NAP]: <br>Result: ",
|
|
51
|
+
legend_title="CPT",
|
|
52
|
+
colorway=px.colors.qualitative.Plotly,
|
|
53
|
+
xaxis=go.layout.XAxis(
|
|
54
|
+
title="X [m]",
|
|
55
|
+
title_font_size=18,
|
|
56
|
+
),
|
|
57
|
+
yaxis=go.layout.YAxis(
|
|
58
|
+
title="Y [m]",
|
|
59
|
+
title_font_size=18,
|
|
60
|
+
),
|
|
61
|
+
autosize=False,
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def _set_results(self, value: CasesMultiCPTBearingResults) -> None:
|
|
66
|
+
"""Private setter for the results."""
|
|
67
|
+
if not isinstance(value, CasesMultiCPTBearingResults):
|
|
68
|
+
raise TypeError(
|
|
69
|
+
f"Expected type 'CasesMultiCPTBearingResults' for 'cases_multi_results', but got {type(value)}"
|
|
70
|
+
)
|
|
71
|
+
self._results = value
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def results(self) -> CasesMultiCPTBearingResults:
|
|
75
|
+
"""The results of the bearing capacity calculations."""
|
|
76
|
+
return self._results
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def data(self) -> pd.DataFrame:
|
|
80
|
+
"""The dataframe used to plot the results."""
|
|
81
|
+
return self.results.cpt_results_dataframe
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def cases(self) -> List[Hashable]:
|
|
85
|
+
"""The case names of each MultiCPTBearingResults."""
|
|
86
|
+
return self.results.cases
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def test_ids(self) -> List[str]:
|
|
90
|
+
"""The test_ids (cpt names) of all the MultiCPTBearingResults."""
|
|
91
|
+
return self.results.test_ids
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def pile_tip_levels_nap(self) -> List[float]:
|
|
95
|
+
"""The pile tip levels w.r.t. NAP of all the MultiCPTBearingResults."""
|
|
96
|
+
return self.results.pile_tip_levels_nap
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def figure(self) -> go.FigureWidget:
|
|
100
|
+
"""The figure widget."""
|
|
101
|
+
return self._figure
|
|
102
|
+
|
|
103
|
+
def get_visible_test_ids(self) -> List[str]:
|
|
104
|
+
"""Returns the visible `test_id` (s) in the figure widget."""
|
|
105
|
+
return [trace.name for trace in self.figure.data if trace.visible is True]
|
|
106
|
+
|
|
107
|
+
def show_case_result_and_ptl(
|
|
108
|
+
self, case_name: Hashable, result_name: str, pile_tip_level_nap: float
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Shows the results for all CPTs for the requested `case_name`, `result_name` and `pile_tip_level`.
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
case_name : str
|
|
115
|
+
The name of the case to show.
|
|
116
|
+
result_name : str
|
|
117
|
+
The name of the result to show.
|
|
118
|
+
pile_tip_level_nap : float
|
|
119
|
+
The pile tip level w.r.t. NAP to show.
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
Raises
|
|
123
|
+
------
|
|
124
|
+
TypeError
|
|
125
|
+
If the `pile_tip_level_nap` is not of type 'float'.
|
|
126
|
+
ValueError
|
|
127
|
+
If the `case_name` is not found in the cases.
|
|
128
|
+
If the `result_name` is not found in the CPTResultDefinitions.
|
|
129
|
+
If the `pile_tip_level_nap` is not found in the pile tip levels.
|
|
130
|
+
"""
|
|
131
|
+
# Check that case name is in cases.
|
|
132
|
+
if case_name not in self.cases:
|
|
133
|
+
raise ValueError(f"Case name '{case_name}' not found in cases.")
|
|
134
|
+
|
|
135
|
+
# Get the result definition that corresponds to the result name.
|
|
136
|
+
result_definition = CPTResultDefinitions.get(result_name)
|
|
137
|
+
|
|
138
|
+
# Check that pile tip level NAP is in pile tip levels.
|
|
139
|
+
if not isinstance(pile_tip_level_nap, (int, float)):
|
|
140
|
+
raise TypeError(
|
|
141
|
+
f"Expected type 'float' for 'pile_tip_level_nap', but got {type(pile_tip_level_nap)}"
|
|
142
|
+
)
|
|
143
|
+
if not any(
|
|
144
|
+
np.isclose(pile_tip_level_nap, self.pile_tip_levels_nap, rtol=0, atol=1e-4)
|
|
145
|
+
):
|
|
146
|
+
raise ValueError(
|
|
147
|
+
f"Pile tip level NAP '{pile_tip_level_nap}' not found in pile tip levels NAP."
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Get the visible test_ids
|
|
151
|
+
if len(self.figure.data) == 0:
|
|
152
|
+
# Select all test_ids if there are no traces in the figure yet.
|
|
153
|
+
visible_test_ids = self.test_ids
|
|
154
|
+
else:
|
|
155
|
+
visible_test_ids = self.get_visible_test_ids()
|
|
156
|
+
|
|
157
|
+
# Select data for case name and result name.
|
|
158
|
+
mask_case_name = (
|
|
159
|
+
self.data["case_name"] == case_name
|
|
160
|
+
if case_name is not None
|
|
161
|
+
else self.data["case_name"].isna()
|
|
162
|
+
)
|
|
163
|
+
mask_pile_tip_level = np.isclose(
|
|
164
|
+
pile_tip_level_nap,
|
|
165
|
+
self.data["pile_tip_level_nap"].to_numpy(),
|
|
166
|
+
rtol=0,
|
|
167
|
+
atol=1e-4,
|
|
168
|
+
)
|
|
169
|
+
selected_data = self.data.loc[
|
|
170
|
+
(mask_case_name)
|
|
171
|
+
& (self.data["result_name"] == result_definition.name)
|
|
172
|
+
& (mask_pile_tip_level)
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
# Get the min and max result values for the color scale.
|
|
176
|
+
result_max = selected_data["result"].max()
|
|
177
|
+
result_min = selected_data["result"].min()
|
|
178
|
+
colorscale = px.colors.get_colorscale("picnic")
|
|
179
|
+
|
|
180
|
+
traces = []
|
|
181
|
+
for test_id in self.test_ids:
|
|
182
|
+
df = selected_data.loc[selected_data["test_id"] == test_id]
|
|
183
|
+
result = round(df["result"].values[0], 1)
|
|
184
|
+
color = get_continuous_color(
|
|
185
|
+
colorscale=colorscale,
|
|
186
|
+
intermed=(result - result_min) / (result_max - result_min),
|
|
187
|
+
)
|
|
188
|
+
traces.append(
|
|
189
|
+
go.Scatter(
|
|
190
|
+
x=df["x"],
|
|
191
|
+
y=df["y"],
|
|
192
|
+
text=f"CPT {test_id}<br>{result}",
|
|
193
|
+
mode="markers+text",
|
|
194
|
+
name=test_id,
|
|
195
|
+
marker=dict(
|
|
196
|
+
size=8,
|
|
197
|
+
color=color,
|
|
198
|
+
line=dict(
|
|
199
|
+
width=0.5,
|
|
200
|
+
color="black",
|
|
201
|
+
),
|
|
202
|
+
),
|
|
203
|
+
textposition="top center",
|
|
204
|
+
hoverinfo="none",
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Add the colorbar
|
|
209
|
+
traces.append(
|
|
210
|
+
go.Scatter(
|
|
211
|
+
x=[None],
|
|
212
|
+
y=[None],
|
|
213
|
+
mode="markers",
|
|
214
|
+
name="__colorbar__",
|
|
215
|
+
marker=dict(
|
|
216
|
+
colorscale=colorscale,
|
|
217
|
+
showscale=True,
|
|
218
|
+
cmin=result_min,
|
|
219
|
+
cmax=result_max,
|
|
220
|
+
colorbar=dict(
|
|
221
|
+
thickness=16,
|
|
222
|
+
orientation="h",
|
|
223
|
+
x=0.75,
|
|
224
|
+
y=1.0,
|
|
225
|
+
len=0.5,
|
|
226
|
+
title=f"{result_definition.value.html} [{result_definition.value.unit}]",
|
|
227
|
+
title_font_size=14,
|
|
228
|
+
),
|
|
229
|
+
),
|
|
230
|
+
hoverinfo="none",
|
|
231
|
+
showlegend=False,
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
with self.figure.batch_update():
|
|
236
|
+
# Empty traces
|
|
237
|
+
self.figure.data = []
|
|
238
|
+
|
|
239
|
+
# Apply changes
|
|
240
|
+
self.figure.add_traces(traces)
|
|
241
|
+
|
|
242
|
+
self.figure.update_layout(
|
|
243
|
+
title=f"CPT Results in Plan View<br>Case: {case_name}, "
|
|
244
|
+
+ f"Pile tip level [m NAP]: {pile_tip_level_nap}<br>"
|
|
245
|
+
+ f"Result: {result_definition.value.html} [{result_definition.value.unit}]"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
self.figure.update_traces(
|
|
249
|
+
selector=lambda x: x.name in visible_test_ids,
|
|
250
|
+
patch=dict(
|
|
251
|
+
visible=True,
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
self.figure.update_traces(
|
|
256
|
+
selector=lambda x: x.name not in visible_test_ids,
|
|
257
|
+
patch=dict(
|
|
258
|
+
visible="legendonly",
|
|
259
|
+
),
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
self.figure.update_traces(
|
|
263
|
+
selector=lambda x: x.name == "__colorbar__",
|
|
264
|
+
patch=dict(
|
|
265
|
+
visible=True,
|
|
266
|
+
showlegend=False,
|
|
267
|
+
),
|
|
268
|
+
)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
from __future__ import annotations # noqa: F404
|
|
2
|
+
|
|
3
|
+
from typing import Hashable, List
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import plotly.express as px
|
|
7
|
+
import plotly.graph_objects as go
|
|
8
|
+
|
|
9
|
+
from pypilecore.results.cases_multi_cpt_results import CasesMultiCPTBearingResults
|
|
10
|
+
from pypilecore.results.result_definitions import CPTResultDefinitions
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FigureCPTResultsVersusPtls:
|
|
14
|
+
"""
|
|
15
|
+
Interactive figure to show the CPT results of the bearing capacity calculations
|
|
16
|
+
versus the pile tip levels (PTLs).
|
|
17
|
+
|
|
18
|
+
The layout of the figure is:
|
|
19
|
+
- X axis: result values.
|
|
20
|
+
- Y axis: pile tip level w.r.t. NAP.
|
|
21
|
+
- Each trace represents a different CPT.
|
|
22
|
+
|
|
23
|
+
The figure has a method to switch between cases and results.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, cases_multi_results: CasesMultiCPTBearingResults) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Initializes the figure.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
cases_multi_results : CasesMultiCPTBearingResults
|
|
33
|
+
The results of the bearing capacity calculations.
|
|
34
|
+
|
|
35
|
+
Raises
|
|
36
|
+
------
|
|
37
|
+
TypeError
|
|
38
|
+
If `cases_multi_results` are not of type 'CasesMultiCPTBearingResults'.
|
|
39
|
+
"""
|
|
40
|
+
# Validate the data
|
|
41
|
+
self._set_results(cases_multi_results)
|
|
42
|
+
|
|
43
|
+
# Initialize the figure
|
|
44
|
+
self._figure = go.FigureWidget(
|
|
45
|
+
layout=go.Layout(
|
|
46
|
+
height=800,
|
|
47
|
+
width=800,
|
|
48
|
+
title="CPT Results vs. Pile tip level<br>Case: , Result: ",
|
|
49
|
+
legend_title="CPT",
|
|
50
|
+
colorway=px.colors.qualitative.Plotly,
|
|
51
|
+
xaxis_title="",
|
|
52
|
+
yaxis=go.layout.YAxis(
|
|
53
|
+
title="Pile tip level [m NAP]",
|
|
54
|
+
title_font_size=18,
|
|
55
|
+
),
|
|
56
|
+
autosize=False,
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def _set_results(self, value: CasesMultiCPTBearingResults) -> None:
|
|
61
|
+
"""Private setter for the results."""
|
|
62
|
+
if not isinstance(value, CasesMultiCPTBearingResults):
|
|
63
|
+
raise TypeError(
|
|
64
|
+
f"Expected type 'CasesMultiCPTBearingResults' for 'cases_multi_results', but got {type(value)}"
|
|
65
|
+
)
|
|
66
|
+
self._results = value
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def results(self) -> CasesMultiCPTBearingResults:
|
|
70
|
+
"""The results of the bearing capacity calculations."""
|
|
71
|
+
return self._results
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def data(self) -> pd.DataFrame:
|
|
75
|
+
"""The dataframe used to plot the results."""
|
|
76
|
+
return self.results.cpt_results_dataframe
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def cases(self) -> List[Hashable]:
|
|
80
|
+
"""The case names of each MultiCPTBearingResults."""
|
|
81
|
+
return self.results.cases
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def test_ids(self) -> List[str]:
|
|
85
|
+
"""The test_ids (cpt names) of all the MultiCPTBearingResults."""
|
|
86
|
+
return self.results.test_ids
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def figure(self) -> go.FigureWidget:
|
|
90
|
+
"""The figure widget."""
|
|
91
|
+
return self._figure
|
|
92
|
+
|
|
93
|
+
def get_visible_test_ids(self) -> List[go.Scatter]:
|
|
94
|
+
"""Returns the visible `test_id` (s) in the figure widget."""
|
|
95
|
+
return [trace.name for trace in self.figure.data if trace.visible is True]
|
|
96
|
+
|
|
97
|
+
def show_case_and_result(self, case_name: Hashable, result_name: str) -> None:
|
|
98
|
+
"""Shows the results for all CPTs and pile tip levels for the requested `case_name` and `result_name`.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
case_name : str
|
|
103
|
+
The name of the case to show.
|
|
104
|
+
result_name : str
|
|
105
|
+
The name of the result to show.
|
|
106
|
+
|
|
107
|
+
Raises
|
|
108
|
+
------
|
|
109
|
+
ValueError
|
|
110
|
+
If the `case_name` is not found in the cases.
|
|
111
|
+
If the `result_name` is not found in the CPTResultDefinitions.
|
|
112
|
+
"""
|
|
113
|
+
# Check that case name is in cases.
|
|
114
|
+
if case_name not in self.cases:
|
|
115
|
+
raise ValueError(f"Case name '{case_name}' not found in cases.")
|
|
116
|
+
|
|
117
|
+
# Get the result definition that corresponds to the result name.
|
|
118
|
+
result_definition = CPTResultDefinitions.get(result_name)
|
|
119
|
+
|
|
120
|
+
# Get the visible test_ids
|
|
121
|
+
if len(self.figure.data) == 0:
|
|
122
|
+
# Select all test_ids if there are no traces in the figure yet.
|
|
123
|
+
visible_test_ids = self.test_ids
|
|
124
|
+
else:
|
|
125
|
+
visible_test_ids = self.get_visible_test_ids()
|
|
126
|
+
|
|
127
|
+
# Select data for case name and result name.
|
|
128
|
+
mask_case_name = (
|
|
129
|
+
self.data["case_name"] == case_name
|
|
130
|
+
if case_name is not None
|
|
131
|
+
else self.data["case_name"].isna()
|
|
132
|
+
)
|
|
133
|
+
selected_data = self.data.loc[
|
|
134
|
+
(mask_case_name) & (self.data["result_name"] == result_definition.name)
|
|
135
|
+
]
|
|
136
|
+
traces = []
|
|
137
|
+
for test_id in self.test_ids:
|
|
138
|
+
df = selected_data.loc[selected_data["test_id"] == test_id]
|
|
139
|
+
traces.append(
|
|
140
|
+
go.Scatter(
|
|
141
|
+
x=df["result"],
|
|
142
|
+
y=df["pile_tip_level_nap"],
|
|
143
|
+
mode="lines+markers",
|
|
144
|
+
name=test_id,
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
with self.figure.batch_update():
|
|
149
|
+
# Empty traces
|
|
150
|
+
self.figure.data = []
|
|
151
|
+
|
|
152
|
+
# Apply changes
|
|
153
|
+
self.figure.add_traces(traces)
|
|
154
|
+
|
|
155
|
+
self.figure.update_layout(
|
|
156
|
+
title=f"CPT Results vs. Pile tip level<br>Case: {case_name}, Result: {result_definition.value.html}",
|
|
157
|
+
xaxis=go.layout.XAxis(
|
|
158
|
+
title=f"{result_definition.value.html} [{result_definition.value.unit}]",
|
|
159
|
+
title_font_size=18,
|
|
160
|
+
),
|
|
161
|
+
showlegend=True,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
self.figure.update_traces(
|
|
165
|
+
selector=lambda x: x.name in visible_test_ids,
|
|
166
|
+
patch=dict(
|
|
167
|
+
visible=True,
|
|
168
|
+
),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
self.figure.update_traces(
|
|
172
|
+
selector=lambda x: x.name not in visible_test_ids,
|
|
173
|
+
patch=dict(
|
|
174
|
+
visible="legendonly",
|
|
175
|
+
),
|
|
176
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import plotly
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_continuous_color(colorscale: Any, intermed: float) -> Any:
|
|
7
|
+
"""
|
|
8
|
+
Plotly continuous colorscales assign colors to the range [0, 1]. This function computes the intermediate
|
|
9
|
+
color for any value in that range.
|
|
10
|
+
|
|
11
|
+
Plotly doesn't make the colorscales directly accessible in a common format.
|
|
12
|
+
Some are ready to use:
|
|
13
|
+
|
|
14
|
+
colorscale = plotly.colors.PLOTLY_SCALES["Greens"]
|
|
15
|
+
|
|
16
|
+
Others are just swatches that need to be constructed into a colorscale:
|
|
17
|
+
|
|
18
|
+
viridis_colors, scale = plotly.colors.convert_colors_to_same_type(plotly.colors.sequential.Viridis)
|
|
19
|
+
colorscale = plotly.colors.make_colorscale(viridis_colors, scale=scale)
|
|
20
|
+
|
|
21
|
+
:param colorscale: A plotly continuous colorscale defined with RGB string colors.
|
|
22
|
+
:param intermed: value in the range [0, 1]
|
|
23
|
+
:return: color in rgb string format
|
|
24
|
+
:rtype: str
|
|
25
|
+
"""
|
|
26
|
+
if len(colorscale) < 1:
|
|
27
|
+
raise ValueError("colorscale must have at least one color")
|
|
28
|
+
|
|
29
|
+
if intermed <= 0 or len(colorscale) == 1:
|
|
30
|
+
return colorscale[0][1]
|
|
31
|
+
if intermed >= 1:
|
|
32
|
+
return colorscale[-1][1]
|
|
33
|
+
|
|
34
|
+
for cutoff, color in colorscale:
|
|
35
|
+
if intermed > cutoff:
|
|
36
|
+
low_cutoff, low_color = cutoff, color
|
|
37
|
+
else:
|
|
38
|
+
high_cutoff, high_color = cutoff, color
|
|
39
|
+
break
|
|
40
|
+
|
|
41
|
+
# noinspection PyUnboundLocalVariable
|
|
42
|
+
return plotly.colors.find_intermediate_color(
|
|
43
|
+
lowcolor=low_color,
|
|
44
|
+
highcolor=high_color,
|
|
45
|
+
intermed=((intermed - low_cutoff) / (high_cutoff - low_cutoff)),
|
|
46
|
+
colortype="rgb",
|
|
47
|
+
)
|