Skip to content

Commit

Permalink
Highlight selected date on chart (#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
HansKallekleiv authored Oct 8, 2019
1 parent fc27e6c commit 5019eab
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 35 deletions.
4 changes: 2 additions & 2 deletions webviz_subsurface/containers/_parameter_correlation.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,9 @@ def control_div(self):
"grid-template-columns": "3fr 1fr 4fr",
},
children=[
# pylint: disable=no-member
# ToggleSwitch module attribute in dash-daq is set at runtime
html.Label("Show density plot", style={"font-weight": "bold"}),
# ToggleSwitch module attribute in dash-daq is set at runtime
# pylint: disable=no-member, not-callable
daq.ToggleSwitch(id=self.density_id, value=True),
],
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ class ReservoirSimulationTimeSeriesOneByOne(WebvizContainerABC):
]

TABLE_STAT = [
"Sens Name",
"Sens Case",
"Sensitivity",
"Case",
"Mean",
"Stddev",
"Standard Deviation",
"Minimum",
"P90",
"P10",
Expand Down Expand Up @@ -102,7 +102,7 @@ def __init__(
def make_uuids(self):
uuid = f"{uuid4()}"
self.smry_col_id = f"smry-col-{uuid}"
self.date_label = f"date-label{uuid}"
self.store_date_id = f"date-store{uuid}"
self.ensemble_id = f"ensemble-{uuid}"
self.table_id = f"table-{uuid}"
self.graph_id = f"graph-{uuid}"
Expand All @@ -112,10 +112,8 @@ def make_uuids(self):
@property
def tour_steps(self):
return [
{"id": self.smry_col_id, "content": "Select time series"},
{"id": self.ensemble_id, "content": "Select ensemble"},
{
"id": self.graph_id,
"id": self.graph_wrapper_id,
"content": (
"Selected time series displayed per realization. "
"Click in the plot to calculate tornadoplot for the "
Expand All @@ -132,6 +130,8 @@ def tour_steps(self):
"relevant realizations in the main chart."
),
},
{"id": self.smry_col_id, "content": "Select time series"},
{"id": self.ensemble_id, "content": "Select ensemble"},
]

@property
Expand Down Expand Up @@ -159,7 +159,7 @@ def smry_selector(self):
return html.Div(
style={"paddingBottom": "30px"},
children=[
html.Label("Vector"),
html.Label("Time Series"),
dcc.Dropdown(
id=self.smry_col_id,
options=[{"label": i, "value": i} for i in self.smry_cols],
Expand Down Expand Up @@ -214,7 +214,7 @@ def layout(self):
children=[
self.ensemble_selector,
self.smry_selector,
html.Label(id=self.date_label),
dcc.Store(id=self.store_date_id),
],
),
html.Div(
Expand All @@ -234,6 +234,12 @@ def layout(self):
{"name": i, "id": i}
for i in ReservoirSimulationTimeSeriesOneByOne.TABLE_STAT
],
style_cell_conditional=[
{
"if": {"column_id": "Standard Deviation"},
"width": "5%",
}
],
),
]
),
Expand All @@ -247,9 +253,14 @@ def set_callbacks(self, app):
@app.callback(
Output(self.graph_wrapper_id, "children"),
[Input(self.ensemble_id, "value"), Input(self.smry_col_id, "value")],
[State(self.graph_id, "figure"), State(self.store_date_id, "children")],
)
def _render_lines(ensemble, vector):
"""Callback to update graph, and tornado"""
def _render_lines(ensemble, vector, figure, date):
"""Callback to update graph, and tornado
Since it is not possible to use the same Output object in two different
callbacks, a parent div is used to re-render the graph when changing
vector or ensemble
"""

# Filter dataframe based on dropdown choices
data = filter_ensemble(self.data, ensemble, vector)
Expand All @@ -265,23 +276,28 @@ def _render_lines(ensemble, vector):
for r, df in data.groupby(["REAL"])
]
traces[0]["hoverinfo"] = "x"

# Check if a data has been clicked previously
# If so, add the vertical line to the figure
if date:
ymin = min([min(trace["y"]) for trace in figure["data"]])
ymax = max([max(trace["y"]) for trace in figure["data"]])
date = json.loads(date)
layout = {
"shapes": [
{"type": "line", "x0": date, "x1": date, "y0": ymin, "y1": ymax}
],
"showlegend": False,
}
else:
layout = {"showlegend": False}
return [
wcc.Graph(
id=self.graph_id,
figure={
"data": traces,
"layout": {
"title": "Click on a date to calculate tornado plot. "
+ "Click on a bar in tornadoplot to highlight relevant realizations",
"showlegend": False,
},
},
)
wcc.Graph(id=self.graph_id, figure={"data": traces, "layout": layout})
]

@app.callback(
[
Output(self.date_label, "children"),
Output(self.store_date_id, "children"),
Output(self.table_id, "data"),
Output(self.tornadoplot.storage_id, "children"),
],
Expand All @@ -291,16 +307,19 @@ def _render_lines(ensemble, vector):
Input(self.smry_col_id, "value"),
],
)
def _render_tornado(ensemble, clickdata, vector):
def _render_date(ensemble, clickdata, vector):
"""Store selected date and tornado input. Write statistics
to table"""
try:
date = clickdata["points"][0]["x"]
except TypeError:
raise PreventUpdate

data = filter_ensemble(self.data, ensemble, vector)
data = data.loc[data["DATE"].astype(str) == date]
table_rows = calculate_table_rows(data, vector)
return (
f"Selected data {date}",
json.dumps(f"{date}"),
table_rows,
json.dumps(
{
Expand All @@ -312,12 +331,17 @@ def _render_tornado(ensemble, clickdata, vector):

@app.callback(
Output(self.graph_id, "figure"),
[Input(self.tornadoplot.click_id, "children")],
[
Input(self.tornadoplot.click_id, "children"),
Input(self.store_date_id, "children"),
],
[State(self.graph_id, "figure")],
)
def _render_tornado(clickdata, figure):
def _render_tornado(clickdata, date, figure):
"""Update graph with line coloring, vertical line and title"""
if not clickdata:
return figure

clickdata = json.loads(clickdata)
for trace in figure["data"]:
if trace["customdata"] in clickdata["real_low"]:
Expand All @@ -329,23 +353,34 @@ def _render_tornado(clickdata, figure):
else:
trace["marker"] = {"color": "grey"}
trace["opacity"] = 0.02
figure["layout"]["title"] = ""
if date:
ymin = min([min(trace["y"]) for trace in figure["data"]])
ymax = max([max(trace["y"]) for trace in figure["data"]])
date = json.loads(date)
figure["layout"]["shapes"] = [
{"type": "line", "x0": date, "x1": date, "y0": ymin, "y1": ymax}
]
figure["layout"][
"title"
] = f"Date: {date}, sensitivity: {clickdata['sens_name']}"

return figure


@CACHE.memoize(timeout=CACHE.TIMEOUT)
def calculate_table_rows(df, vector):
table = []
for (sensname, senscase), dframe in df.groupby(["SENSNAME", "SENSCASE"]):
values = dframe[vector]
try:
table.append(
{
"Sens Name": str(sensname),
"Sens Case": str(senscase),
"Sensitivity": str(sensname),
"Case": str(senscase),
"Minimum": f"{values.min():.2e}",
"Maximum": f"{values.max():.2e}",
"Mean": f"{values.mean():.2e}",
"Stddev": f"{values.std():.2e}",
"Standard Deviation": f"{values.std():.2e}",
"P10": f"{np.percentile(values, 90):.2e}",
"P90": f"{np.percentile(values, 10):.2e}",
}
Expand Down
18 changes: 16 additions & 2 deletions webviz_subsurface/private_containers/_tornado_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,14 @@ def _save_hover_data(data):
try:
real_low = data["points"][0]["customdata"]
real_high = data["points"][1]["customdata"]
return json.dumps({"real_low": real_low, "real_high": real_high})
sens_name = data["points"][0]["y"]
return json.dumps(
{
"real_low": real_low,
"real_high": real_high,
"sens_name": sens_name,
}
)
except TypeError:
raise PreventUpdate

Expand All @@ -176,7 +183,14 @@ def _save_hover_data(data):
try:
real_low = data["points"][0]["customdata"]
real_high = data["points"][1]["customdata"]
return json.dumps({"real_low": real_low, "real_high": real_high})
sens_name = data["points"][0]["y"]
return json.dumps(
{
"real_low": real_low,
"real_high": real_high,
"sens_name": sens_name,
}
)
except TypeError:
raise PreventUpdate

Expand Down

0 comments on commit 5019eab

Please sign in to comment.