Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tornado improvements to the tornado component and to VolumetricAnalysis #825

Merged
merged 1 commit into from
Oct 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [UNRELEASED] - YYYY-MM-DD

### Added
- [#825](https://github.com/equinor/webviz-subsurface/pull/825) - Added options to create separate tornado's for e.g Region/Zone in `VolumetricAnalysis`. As well as various improvements to the tornado figure.
- [#734](https://github.com/equinor/webviz-subsurface/pull/645) - New plugin, SeismicMisfit, for comparing observed and modelled seismic attributes. Multiple views, including misfit quantification and coverage plots.
- [#809](https://github.com/equinor/webviz-subsurface/pull/809) - `GroupTree` - added more statistical options (P10, P90, P50/Median, Max, Min). Some improvements to the menu layout and behaviour

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

def test_volumetrics_no_sens(dash_duo, app, shared_settings) -> None:
plugin = VolumetricAnalysis(
app,
shared_settings["HM_SETTINGS"],
ensembles=shared_settings["HM_ENSEMBLES"],
volfiles={"geogrid": "geogrid--vol.csv", "simgrid": "simgrid--vol.csv"},
Expand All @@ -24,7 +23,6 @@ def test_volumetrics_no_sens(dash_duo, app, shared_settings) -> None:

def test_volumetrics_sens(dash_duo, app, shared_settings) -> None:
plugin = VolumetricAnalysis(
app,
shared_settings["SENS_SETTINGS"],
ensembles=shared_settings["SENS_ENSEMBLES"],
volfiles={"geogrid": "geogrid--vol.csv", "simgrid": "simgrid--vol.csv"},
Expand Down
71 changes: 52 additions & 19 deletions webviz_subsurface/_components/tornado/_tornado_bar_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
import plotly.graph_objects as go

from webviz_subsurface._abbreviations.number_formatting import si_prefixed
from webviz_subsurface._utils.formatting import printable_int_list

from ._tornado_data import TornadoData


class TornadoBarChart:
"""Creates a plotly bar figure from a TornadoData instance"""

# pylint: disable=too-many-arguments
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(
self,
tornado_data: TornadoData,
Expand All @@ -25,6 +24,9 @@ def __init__(
spaced: bool = True,
use_true_base: bool = False,
show_realization_points: bool = True,
show_reference: bool = True,
color_by_sensitivity: bool = False,
sensitivity_color_map: dict = None,
) -> None:
self._tornadotable = tornado_data.tornadotable
self._realtable = self.make_points(tornado_data.real_df)
Expand All @@ -37,6 +39,7 @@ def __init__(
self._locked_si_prefix_relative: Optional[int]
self._scale = tornado_data.scale
self._use_true_base = use_true_base
self._show_reference = show_reference
if self._scale == "Percentage":
self._unit_x = "%"
self._locked_si_prefix_relative = 0
Expand All @@ -46,6 +49,15 @@ def __init__(
self._figure_height = figure_height
self._label_options = label_options
self._show_scatter = show_realization_points
self._color_by_sens = color_by_sensitivity
self._sens_color_map = sensitivity_color_map

def create_color_list(self, sensitivities: list) -> list:
return (
[self._sens_color_map.get(sensname, "grey") for sensname in sensitivities]
if self._sens_color_map is not None
else self._plotly_theme["layout"]["colorway"]
)

def make_points(self, realdf: pd.DataFrame) -> pd.DataFrame:
dfs = []
Expand Down Expand Up @@ -105,21 +117,30 @@ def bar_labels(self, case: str) -> List:
return [
f"<b>{self._set_si_prefix_relative(x)}</b>, "
f"True: {self._set_si_prefix(val)}, "
f"<br><b>Case: {label}</b>, "
f"Reals: {printable_int_list(reals)}"
if reals
else None
for x, label, val, reals in zip(
f"<br><b>Case: {label}</b> "
for x, label, val in zip(
self._tornadotable[f"{case}_tooltip"],
self._tornadotable[f"{case}_label"],
self._tornadotable[f"true_{case}"],
self._tornadotable[f"{case}_reals"],
)
]
return []

def hover_label(self) -> List:
return [
f"<b>Sensname: {sens}</b>:<br>"
f"Low: <b>{self._set_si_prefix_relative(low)}</b>, "
f"High: <b>{self._set_si_prefix_relative(high)}</b>, "
for low, high, sens in zip(
self._tornadotable["low"],
self._tornadotable["high"],
self._tornadotable["sensname"],
)
]

@property
def data(self) -> List:
colors = self.create_color_list(self._tornadotable["sensname"].unique())
return [
dict(
type="bar",
Expand All @@ -133,9 +154,13 @@ def data(self) -> List:
text=self.bar_labels("low"),
textposition="auto",
insidetextanchor="middle",
hoverinfo="none",
hoverinfo="text",
hovertext=self.hover_label(),
orientation="h",
marker={"line": {"width": 1.5, "color": "black"}},
marker={
"line": {"width": 1.5, "color": "black"},
"color": colors if self._color_by_sens else None,
},
),
dict(
type="bar",
Expand All @@ -149,9 +174,13 @@ def data(self) -> List:
text=self.bar_labels("high"),
textposition="auto",
insidetextanchor="middle",
hoverinfo="none",
hoverinfo="text",
hovertext=self.hover_label(),
orientation="h",
marker={"line": {"width": 1.5, "color": "black"}},
marker={
"line": {"width": 1.5, "color": "black"},
"color": colors if self._color_by_sens else None,
},
),
]

Expand All @@ -173,7 +202,8 @@ def scatter_data(self) -> List[Dict]:
"y": df["sensname"],
"x": self.calculate_scatter_value(df["VALUE"]),
"text": df["REAL"],
"hoverinfo": "none",
"hovertemplate": "REAL: <b>%{text}</b><br>"
+ "X: <b>%{x:.1f}</b> <extra></extra>",
"marker": {
"size": 15,
"color": self._plotly_theme["layout"]["colorway"][0]
Expand Down Expand Up @@ -208,7 +238,9 @@ def layout(self) -> Dict:
"title": self._scale,
"range": self.range,
"autorange": self._show_scatter or self._tornadotable.empty,
"showgrid": False,
"gridwidth": 1,
"gridcolor": "whitesmoke",
"showgrid": True,
"zeroline": False,
"linecolor": "black",
"showline": True,
Expand All @@ -227,7 +259,8 @@ def layout(self) -> Dict:
"tickfont": {"size": 15},
},
"showlegend": False,
"hovermode": "y",
"hovermode": "closest",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing hovermode from "y" to "closest" causes the "on click" callback in TornadoWidget to fail as it previously received both low and high bars as input, but now only receives the one that is clicked.

This has either to be changed back or atleast set to "y" if the allow_click option is set.
Alternatively investigate if there is some way to separate hovermode from the data received from clickData.
This seems to be a bug/limitation in Plotly.

"hoverlabel": {"bgcolor": "white", "font_size": 16},
"annotations": [
{
"x": 0 if not self._use_true_base else self._reference_average,
Expand All @@ -239,7 +272,9 @@ def layout(self) -> Dict:
"showarrow": False,
"align": "center",
}
],
]
if self._show_reference
else None,
"shapes": [
{
"type": "line",
Expand All @@ -258,9 +293,7 @@ def layout(self) -> Dict:

@property
def figure(self) -> go.Figure:
data = self.data

fig = go.Figure({"data": data, "layout": self.layout})
fig = go.Figure({"data": self.data, "layout": self.layout})
if self._show_scatter:
fig.update_traces(marker_opacity=0.4, text=None)
for trace in self.scatter_data:
Expand Down
1 change: 1 addition & 0 deletions webviz_subsurface/_components/tornado/_tornado_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ def _cut_sensitivities_by_ref(self) -> None:

self._tornadotable = self._tornadotable.loc[
((self._tornadotable["low"] - self._tornadotable["high"]) != 0)
| (self._tornadotable["sensname"] == self._reference)
]

def _sort_sensitivities_by_max(self) -> None:
Expand Down
46 changes: 24 additions & 22 deletions webviz_subsurface/_components/tornado/tornado_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ def settings_layout(self) -> html.Div:
"label": "Show realization points",
"value": "Show realization points",
},
{
"label": "Color bars by sensitivity",
"value": "Color bars by sensitivity",
},
],
value=[],
labelStyle={"display": "block"},
Expand Down Expand Up @@ -439,6 +443,7 @@ def _calc_tornado(
locked_si_prefix=data.get("locked_si_prefix", None),
use_true_base=scale == "True value",
show_realization_points="Show realization points" in plot_options,
color_by_sensitivity="Color bars by sensitivity" in plot_options,
)
tornado_table = TornadoTable(tornado_data=tornado_data)
return (
Expand All @@ -455,37 +460,34 @@ def _calc_tornado(
[
Input(self.ids("tornado-graph"), "clickData"),
Input(self.ids("reset"), "n_clicks"),
State(self.ids("high-low-storage"), "data"),
],
)
def _save_click_data(data: dict, nclicks: Optional[int]) -> str:
if callback_context.triggered is None:
def _save_click_data(
data: dict, nclicks: Optional[int], sens_reals: dict
) -> str:
if (
callback_context.triggered is None
or sens_reals is None
or data is None
):
raise PreventUpdate

ctx = callback_context.triggered[0]["prop_id"].split(".")[0]
if ctx == self.ids("reset") and nclicks:

return json.dumps(
{
"real_low": [],
"real_high": [],
"sens_name": None,
}
)
try:

real_low = next(
x["customdata"] for x in data["points"] if x["curveNumber"] == 0
)
real_high = next(
x["customdata"] for x in data["points"] if x["curveNumber"] == 1
)
sens_name = data["points"][0]["y"]
return json.dumps(
{
"real_low": real_low,
"real_high": real_high,
"sens_name": sens_name,
}
)
except TypeError as exc:
raise PreventUpdate from exc
sensname = data["points"][0]["y"]
real_high = sens_reals[sensname]["real_high"]
real_low = sens_reals[sensname]["real_low"]
return json.dumps(
{
"real_low": real_low,
"real_high": real_high,
"sens_name": sensname,
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
from .fipfile_qc_controller import fipfile_qc_controller
from .layout_controllers import layout_controllers
from .selections_controllers import selections_controllers
from .tornado_controllers import tornado_controllers
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from dash import Dash, Input, Output, State, callback_context, dash_table, html
from dash import Input, Output, State, callback, callback_context, dash_table, html
from dash.exceptions import PreventUpdate

from webviz_subsurface._figures import create_figure
Expand All @@ -24,11 +24,10 @@

# pylint: disable=too-many-locals
def comparison_controllers(
app: Dash,
get_uuid: Callable,
volumemodel: InplaceVolumesModel,
) -> None:
@app.callback(
@callback(
Output({"id": get_uuid("main-src-comp"), "wrapper": "table"}, "children"),
Input(get_uuid("selections"), "data"),
Input({"id": get_uuid("main-src-comp"), "element": "display-option"}, "value"),
Expand Down Expand Up @@ -56,7 +55,7 @@ def _update_page_src_comp(
display_option=display_option,
)

@app.callback(
@callback(
Output({"id": get_uuid("main-ens-comp"), "wrapper": "table"}, "children"),
Input(get_uuid("selections"), "data"),
Input({"id": get_uuid("main-ens-comp"), "element": "display-option"}, "value"),
Expand Down
Loading