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

/portfolio/po : allow getting plotly figure with the SDK #4449

Merged
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
32 changes: 28 additions & 4 deletions openbb_terminal/helper_classes.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
"""Helper classes."""
__docformat__ = "numpy"
import argparse
import io
import json
import os
from importlib import machinery, util
from pathlib import Path
from typing import Dict, List, Optional, Union

import matplotlib.pyplot as plt
import plotly.express as px
from matplotlib import font_manager, ticker
from PIL import Image

from openbb_terminal.core.config.paths import MISCELLANEOUS_DIRECTORY
from openbb_terminal.core.session.current_user import get_current_user
Expand Down Expand Up @@ -346,7 +349,9 @@ def add_cmd_source(
)

# pylint: disable=import-outside-toplevel
def visualize_output(self, force_tight_layout: bool = True):
def visualize_output(
self, force_tight_layout: bool = True, external_axes: bool = False
):
"""Show chart in an interactive widget."""
current_user = get_current_user()

Expand All @@ -356,9 +361,28 @@ def visualize_output(self, force_tight_layout: bool = True):
self.add_label(plt.gcf())
if force_tight_layout:
plt.tight_layout(pad=self.tight_layout_padding)
if current_user.preferences.USE_ION:
plt.ion()
plt.show()

if external_axes:
img_buf = io.BytesIO()
plt.savefig(img_buf, format="jpg")
im = Image.open(img_buf)
fig = px.imshow(im)
fig.update_layout(
xaxis=dict(visible=False, showticklabels=False),
yaxis=dict(visible=False, showticklabels=False),
margin=dict(l=0, r=0, t=0, b=0),
autosize=False,
width=im.width,
height=im.height,
)
else:
if current_user.preferences.USE_ION:
plt.ion()

fig = None
plt.show()

return fig


class AllowArgsWithWhiteSpace(argparse.Action):
Expand Down
104 changes: 48 additions & 56 deletions openbb_terminal/portfolio/portfolio_optimization/optimizer_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -2080,7 +2080,7 @@ def display_ef(
seed: int = 123,
tangency: bool = False,
plot_tickers: bool = True,
external_axes: Optional[List[plt.Axes]] = None,
external_axes: bool = False,
):
"""
Display efficient frontier
Expand Down Expand Up @@ -2147,7 +2147,7 @@ def display_ef(
Seed used to generate random portfolios. The default value is 123.
tangency: bool, optional
Adds the optimal line with the risk-free asset.
external_axes: Optional[List[plt.Axes]]
external_axes: bool
Optional axes to plot data on
plot_tickers: bool
Whether to plot the tickers for the assets
Expand Down Expand Up @@ -2175,12 +2175,9 @@ def display_ef(
try:
risk_free_rate = risk_free_rate / time_factor[freq.upper()]

if external_axes is None:
_, ax = plt.subplots(
figsize=plot_autoscale(), dpi=get_current_user().preferences.PLOT_DPI
)
else:
ax = external_axes[0]
_, ax = plt.subplots(
figsize=plot_autoscale(), dpi=get_current_user().preferences.PLOT_DPI
)

ax = rp.plot_frontier(
w_frontier=frontier,
Expand Down Expand Up @@ -2277,10 +2274,13 @@ def display_ef(
ax1 = ax.get_figure().axes
ll, bb, ww, hh = ax1[-1].get_position().bounds
ax1[-1].set_position([ll * 1.02, bb, ww, hh])
if external_axes is None:
theme.visualize_output(force_tight_layout=False)

return theme.visualize_output(
force_tight_layout=False, external_axes=external_axes
)
except Exception as _:
console.print("[red]Error plotting efficient frontier.[/red]")
return None


@log_start_end(log=logger)
Expand Down Expand Up @@ -3616,7 +3616,9 @@ def my_autopct(x):

@log_start_end(log=logger)
def pie_chart_weights(
weights: dict, title_opt: str, external_axes: Optional[List[plt.Axes]]
weights: dict,
title_opt: str,
external_axes: bool = False,
):
"""Show a pie chart of holdings

Expand All @@ -3626,11 +3628,11 @@ def pie_chart_weights(
Weights to display, where keys are tickers, and values are either weights or values if -v specified
title_opt: str
Title to be used on the plot title
external_axes:Optiona[List[plt.Axes]]
external_axes: bool
Optional external axes to plot data on
"""
if not weights:
return
return None

init_stocks = list(weights.keys())
init_sizes = list(weights.values())
Expand All @@ -3644,12 +3646,9 @@ def pie_chart_weights(
total_size = np.sum(sizes)
colors = theme.get_colors()

if external_axes is None:
_, ax = plt.subplots(
figsize=plot_autoscale(), dpi=get_current_user().preferences.PLOT_DPI
)
else:
ax = external_axes[0]
_, ax = plt.subplots(
figsize=plot_autoscale(), dpi=get_current_user().preferences.PLOT_DPI
)

if math.isclose(sum(sizes), 1, rel_tol=0.1):
_, _, autotexts = ax.pie(
Expand Down Expand Up @@ -3714,8 +3713,7 @@ def pie_chart_weights(
title += "Portfolio Composition"
ax.set_title(title)

if external_axes is None:
theme.visualize_output()
return theme.visualize_output(force_tight_layout=True, external_axes=external_axes)


@log_start_end(log=logger)
Expand All @@ -3737,7 +3735,7 @@ def additional_plots(
dd: bool = False,
rc_chart: bool = False,
heat: bool = False,
external_axes: Optional[List[plt.Axes]] = None,
external_axes: bool = False,
):
"""
Plot additional charts
Expand Down Expand Up @@ -3816,7 +3814,7 @@ def additional_plots(
Display a risk contribution chart for assets, by default False
heat : float, optional
Display a heatmap of correlation matrix with dendrogram, by default False
external_axes: Optional[List[plt.Axes]]
external_axes: bool
Optional axes to plot data on
"""

Expand Down Expand Up @@ -3868,12 +3866,9 @@ def additional_plots(
pie_chart_weights(weights, title_opt, external_axes)

if hist:
if external_axes is None:
_, ax = plt.subplots(
figsize=plot_autoscale(), dpi=get_current_user().preferences.PLOT_DPI
)
else:
ax = external_axes[0]
_, ax = plt.subplots(
figsize=plot_autoscale(), dpi=get_current_user().preferences.PLOT_DPI
)

ax = rp.plot_hist(data, w=pd.Series(weights).to_frame(), alpha=alpha, ax=ax)
ax.legend(fontsize="x-small", loc="best")
Expand All @@ -3894,16 +3889,14 @@ def additional_plots(
title += ax.get_title(loc="left")
ax.set_title(title)

if external_axes is None:
theme.visualize_output()
return theme.visualize_output(
force_tight_layout=False, external_axes=external_axes
)

if dd:
if external_axes is None:
_, ax = plt.subplots(
figsize=plot_autoscale(), dpi=get_current_user().preferences.PLOT_DPI
)
else:
ax = external_axes[0]
_, ax = plt.subplots(
figsize=plot_autoscale(), dpi=get_current_user().preferences.PLOT_DPI
)

nav = data.cumsum()
ax = rp.plot_drawdown(
Expand Down Expand Up @@ -3932,16 +3925,14 @@ def additional_plots(
title += ax.get_title(loc="left")
ax.set_title(title)

if external_axes is None:
theme.visualize_output()
return theme.visualize_output(
force_tight_layout=False, external_axes=external_axes
)

if rc_chart:
if external_axes is None:
_, ax = plt.subplots(
figsize=plot_autoscale(), dpi=get_current_user().preferences.PLOT_DPI
)
else:
ax = external_axes[0]
_, ax = plt.subplots(
figsize=plot_autoscale(), dpi=get_current_user().preferences.PLOT_DPI
)

ax = rp.plot_risk_con(
w=pd.Series(weights).to_frame(),
Expand All @@ -3968,23 +3959,21 @@ def additional_plots(
title += ax.get_title(loc="left")
ax.set_title(title)

if external_axes is None:
theme.visualize_output()
return theme.visualize_output(
force_tight_layout=False, external_axes=external_axes
)

if heat:
if len(weights) == 1:
single_key = list(weights.keys())[0].upper()
console.print(
f"[yellow]Heatmap needs at least two values for '{category}', only found '{single_key}'.[/yellow]"
)
return
return None

if external_axes is None:
_, ax = plt.subplots(
figsize=plot_autoscale(), dpi=get_current_user().preferences.PLOT_DPI
)
else:
ax = external_axes[0]
_, ax = plt.subplots(
figsize=plot_autoscale(), dpi=get_current_user().preferences.PLOT_DPI
)

if len(weights) <= 3:
number_of_clusters = len(weights)
Expand Down Expand Up @@ -4047,8 +4036,11 @@ def additional_plots(
title += ax[3].get_title(loc="left")
ax[3].set_title(title)

if external_axes is None:
theme.visualize_output(force_tight_layout=True)
return theme.visualize_output(
force_tight_layout=False, external_axes=external_axes
)

return None


def display_show(weights: Dict, tables: List[str], categories_dict: Dict[Any, Any]):
Expand Down
Loading