summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDidierRLopes <dro.lopes@campus.fct.unl.pt>2022-09-08 14:25:55 +0300
committerGitHub <noreply@github.com>2022-09-08 12:25:55 +0100
commit3381a9a907fe6f99ba6a190475a55e1325d9a4a9 (patch)
tree092e0ad9750e82e8d7f801a574827741aef562ba
parent1766e2fe3f1caf7c9c6d740b4c24d1dc3496338a (diff)
Allow reports comments to be saved in a new HTML (#2507)
* allow reports comments to be saved in a new HTML * clear output of equity report Co-authored-by: minhhoang1023 <40023817+minhhoang1023@users.noreply.github.com>
-rw-r--r--openbb_terminal/reports/equity.ipynb144
-rw-r--r--openbb_terminal/reports/floppy-disc.pngbin0 -> 2782 bytes
-rw-r--r--openbb_terminal/reports/reports_controller.py2
-rw-r--r--openbb_terminal/reports/widget_helpers.py131
4 files changed, 174 insertions, 103 deletions
diff --git a/openbb_terminal/reports/equity.ipynb b/openbb_terminal/reports/equity.ipynb
index 37da5eff9f3..6c510575db0 100644
--- a/openbb_terminal/reports/equity.ipynb
+++ b/openbb_terminal/reports/equity.ipynb
@@ -27,8 +27,8 @@
"\n",
"from IPython.display import HTML\n",
"\n",
- "# import sys\n",
- "# sys.path.append('../../')\n",
+ "#import sys\n",
+ "#sys.path.append('../../')\n",
"\n",
"from openbb_terminal import api as openbb\n",
"from openbb_terminal.helper_classes import TerminalStyle\n",
@@ -56,10 +56,7 @@
"try:\n",
" theme = TerminalStyle(\"light\", \"light\", \"light\")\n",
"except:\n",
- " pass\n",
- "stylesheet = openbb.widgets.html_report_stylesheet()\n",
- "with open(\"./openbb_terminal/reports/OpenBB_reports_logo.png\", \"rb\") as image_file:\n",
- " openbb_image_encoded = base64.b64encode(image_file.read())"
+ " pass"
]
},
{
@@ -82,7 +79,7 @@
"outputs": [],
"source": [
"# Parameters that will be replaced when calling this notebook\n",
- "ticker = \"TSLA\"\n",
+ "symbol = \"GME\"\n",
"report_name = \"\""
]
},
@@ -93,7 +90,7 @@
"metadata": {},
"outputs": [],
"source": [
- "if \".\" in ticker:\n",
+ "if \".\" in symbol:\n",
" import sys\n",
"\n",
" sys.exit(0)"
@@ -107,16 +104,16 @@
"outputs": [],
"source": [
"ticker_data = openbb.stocks.load(\n",
- " ticker, start=datetime.datetime.now() - datetime.timedelta(days=4 * 30)\n",
+ " symbol, start_date=datetime.datetime.now() - datetime.timedelta(days=4 * 30)\n",
")\n",
"ticker_data = openbb.stocks.process_candle(ticker_data)\n",
"\n",
"author = \"Didier Rodrigues Lopes\"\n",
- "report_title = f\"INVESTMENT RESEARCH REPORT ON {ticker.upper()}\"\n",
+ "report_title = f\"INVESTMENT RESEARCH REPORT: {symbol.upper()}\"\n",
"report_date = datetime.datetime.now().strftime(\"%d %B, %Y\")\n",
"report_time = datetime.datetime.now().strftime(\"%H:%M\")\n",
- "report_timezone = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo\n",
- "report_title, report_date, report_time, report_timezone"
+ "report_tz = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo\n",
+ "report_title, report_date, report_time, report_tz"
]
},
{
@@ -126,7 +123,7 @@
"metadata": {},
"outputs": [],
"source": [
- "info = openbb.stocks.fa.models.yahoo_finance.get_info(ticker=ticker).transpose()\n",
+ "info = openbb.stocks.fa.models.yahoo_finance.get_info(symbol=symbol).transpose()\n",
"\n",
"if info[\"Long business summary\"][0] != \"NA\":\n",
" overview = info[\"Long business summary\"][0]\n",
@@ -154,7 +151,7 @@
" df_year_estimates,\n",
" df_quarter_earnings,\n",
" df_quarter_revenues,\n",
- ") = openbb.stocks.dd.models.business_insider.get_estimates(ticker)"
+ ") = openbb.stocks.dd.models.business_insider.get_estimates(symbol)"
]
},
{
@@ -196,7 +193,7 @@
"metadata": {},
"outputs": [],
"source": [
- "df_sec_filings = openbb.stocks.dd.models.marketwatch.get_sec_filings(ticker)[\n",
+ "df_sec_filings = openbb.stocks.dd.models.marketwatch.get_sec_filings(symbol)[\n",
" [\"Type\", \"Category\", \"Link\"]\n",
"].head(5)\n",
"df_sec_filings[\"Link\"] = df_sec_filings[\"Link\"].apply(\n",
@@ -212,7 +209,7 @@
"metadata": {},
"outputs": [],
"source": [
- "df_analyst = openbb.stocks.dd.models.finviz.get_analyst_data(ticker)\n",
+ "df_analyst = openbb.stocks.dd.models.finviz.get_analyst_data(symbol)\n",
"if not df_analyst.empty:\n",
" if \"target\" in df_analyst.columns:\n",
" df_analyst[\"target_to\"] = df_analyst[\"target_to\"].combine_first(\n",
@@ -239,8 +236,8 @@
"fig, ax1 = plt.subplots(figsize=(11, 5), dpi=150)\n",
"ax2 = ax1.twinx()\n",
"openbb.stocks.dps.spos(\n",
- " ticker,\n",
- " num=84,\n",
+ " symbol,\n",
+ " limit=84,\n",
" raw=False,\n",
" export=\"\",\n",
" external_axes=[ax1, ax2],\n",
@@ -261,7 +258,7 @@
"source": [
"fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1, figsize=(11, 5), dpi=150)\n",
"openbb.stocks.dps.dpotc(\n",
- " ticker,\n",
+ " symbol=symbol,\n",
" external_axes=[ax1, ax2],\n",
")\n",
"fig.tight_layout()\n",
@@ -281,7 +278,7 @@
"fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1, figsize=(11, 5), dpi=150)\n",
"ax3 = ax1.twinx()\n",
"openbb.stocks.dps.psi_sg(\n",
- " ticker,\n",
+ " symbol,\n",
" external_axes=[ax1, ax2, ax3],\n",
")\n",
"fig.tight_layout()\n",
@@ -300,8 +297,8 @@
"source": [
"fig, (candles, volume) = plt.subplots(nrows=2, ncols=1, figsize=(11, 5), dpi=150)\n",
"openbb.stocks.candle(\n",
- " s_ticker=ticker,\n",
- " df_stock=ticker_data,\n",
+ " symbol=symbol,\n",
+ " data=ticker_data,\n",
" use_matplotlib=True,\n",
" external_axes=[candles, volume],\n",
")\n",
@@ -322,11 +319,10 @@
"source": [
"fig, ax = plt.subplots(figsize=(11, 3), dpi=150)\n",
"openbb.stocks.dd.pt(\n",
- " ticker=ticker,\n",
- " start=\"2022-01-01\",\n",
- " interval=\"1440min\",\n",
- " stock=ticker_data,\n",
- " num=10,\n",
+ " ticker_data,\n",
+ " symbol=symbol,\n",
+ " start_date=\"2022-01-01\",\n",
+ " limit=10,\n",
" raw=False,\n",
" external_axes=[ax],\n",
")\n",
@@ -343,17 +339,23 @@
"metadata": {},
"outputs": [],
"source": [
- "df = openbb.stocks.dd.models.business_insider.get_price_target_from_analysts(ticker)\n",
- "avg_ratings_last_30_days = 0\n",
+ "df = openbb.stocks.dd.models.business_insider.get_price_target_from_analysts(symbol)\n",
+ "avg_ratings = 0\n",
+ "days = 30\n",
"if not df.empty:\n",
- " avg_ratings_last_30_days = round(\n",
- " np.mean(\n",
- " df[datetime.datetime.now() - datetime.timedelta(days=30) :][\n",
- " \"Price Target\"\n",
- " ].values\n",
- " ),\n",
- " 2,\n",
- " )\n",
+ " df_ratings = df[datetime.datetime.now() - datetime.timedelta(days=days) :]\n",
+ " while df_ratings.empty:\n",
+ " days += 30\n",
+ " df_ratings = df[datetime.datetime.now() - datetime.timedelta(days=days) :]\n",
+ " \n",
+ " if days > 100:\n",
+ " break\n",
+ " \n",
+ " if not df_ratings.empty:\n",
+ " avg_ratings = round(np.mean(df_ratings[\"Price Target\"].values), 2)\n",
+ " else:\n",
+ " avg_ratings = 0\n",
+ " \n",
"last_price = round(ticker_data[\"Close\"][-1], 2)"
]
},
@@ -366,10 +368,9 @@
"source": [
"fig, ax = plt.subplots(figsize=(11, 3), dpi=150)\n",
"openbb.stocks.dd.rot(\n",
- " ticker=ticker,\n",
- " num=10,\n",
+ " symbol=symbol,\n",
+ " limit=10,\n",
" raw=False,\n",
- " export=\"\",\n",
" external_axes=[ax],\n",
")\n",
"fig.tight_layout()\n",
@@ -431,7 +432,7 @@
"import pandas as pd\n",
"\n",
"df_insider = pd.DataFrame.from_dict(\n",
- " openbb.stocks.ins.models.finviz.get_last_insider_activity(ticker)\n",
+ " openbb.stocks.ins.models.finviz.get_last_insider_activity(symbol)\n",
").head(10)\n",
"df_insider[\"Val ($)\"] = df_insider[\"Value ($)\"].replace({\",\": \"\"}, regex=True)\n",
"df_insider[\"Trade\"] = df_insider.apply(\n",
@@ -453,7 +454,7 @@
"outputs": [],
"source": [
"fig, ax = plt.subplots(figsize=(11, 3), dpi=150)\n",
- "openbb.stocks.ba.headlines(ticker, external_axes=[ax])\n",
+ "openbb.stocks.ba.headlines(symbol, external_axes=[ax])\n",
"fig.tight_layout()\n",
"f = io.BytesIO()\n",
"fig.savefig(f, format=\"svg\")\n",
@@ -467,7 +468,7 @@
"metadata": {},
"outputs": [],
"source": [
- "df_sentiment_finbrain = openbb.stocks.ba.models.finbrain.get_sentiment(ticker)\n",
+ "df_sentiment_finbrain = openbb.stocks.ba.models.finbrain.get_sentiment(symbol)\n",
"finbrain_sentiment_val = float(df_sentiment_finbrain.values[-1][0])"
]
},
@@ -483,7 +484,7 @@
" n_cases,\n",
" n_bull,\n",
" n_bear,\n",
- ") = openbb.stocks.ba.models.stocktwits.get_bullbear(ticker)\n",
+ ") = openbb.stocks.ba.models.stocktwits.get_bullbear(symbol)\n",
"stocktwits_sentiment = f\"Watchlist count: {watchlist_count}</br>\"\n",
"if n_cases > 0:\n",
" stocktwits_sentiment += f\"\\nLast {n_cases} sentiment messages:</br>\"\n",
@@ -501,7 +502,7 @@
"outputs": [],
"source": [
"fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1, figsize=(11, 5), dpi=150)\n",
- "openbb.stocks.ba.snews(ticker, external_axes=[ax1, ax2])\n",
+ "openbb.stocks.ba.snews(symbol, external_axes=[ax1, ax2])\n",
"fig.tight_layout()\n",
"f = io.BytesIO()\n",
"fig.savefig(f, format=\"svg\")\n",
@@ -516,7 +517,7 @@
"outputs": [],
"source": [
"ticker_data_all = openbb.stocks.load(\n",
- " ticker, start=datetime.datetime.now() - datetime.timedelta(days=5 * 12 * 21)\n",
+ " symbol, start_date=datetime.datetime.now() - datetime.timedelta(days=5 * 12 * 21)\n",
")\n",
"ticker_data_all[\"Returns\"] = ticker_data_all[\"Adj Close\"].pct_change()"
]
@@ -529,7 +530,7 @@
"outputs": [],
"source": [
"fig, ax = plt.subplots(figsize=(11, 3), dpi=150)\n",
- "openbb.stocks.qa.bw(ticker, ticker_data_all, \"Returns\", False, external_axes=[ax])\n",
+ "openbb.stocks.qa.bw(ticker_data_all, \"Returns\", symbol, yearly=False, external_axes=[ax])\n",
"fig.tight_layout()\n",
"f = io.BytesIO()\n",
"fig.savefig(f, format=\"svg\")\n",
@@ -544,7 +545,7 @@
"outputs": [],
"source": [
"fig, ax = plt.subplots(figsize=(11, 3), dpi=150)\n",
- "openbb.stocks.qa.bw(ticker, ticker_data_all, \"Returns\", True, external_axes=[ax])\n",
+ "openbb.stocks.qa.bw(ticker_data_all, \"Returns\", symbol, yearly=True, external_axes=[ax])\n",
"fig.tight_layout()\n",
"f = io.BytesIO()\n",
"fig.savefig(f, format=\"svg\")\n",
@@ -578,7 +579,7 @@
"if predictions:\n",
" fig, ax = plt.subplots(figsize=(11, 3), dpi=150)\n",
" openbb.stocks.pred.regression(\n",
- " ticker, ticker_data_all[\"Close\"], 1, 80, 20, 1, external_axes=[ax]\n",
+ " symbol, ticker_data_all[\"Close\"], 1, 80, 20, 1, external_axes=[ax]\n",
" )\n",
" fig.tight_layout()\n",
" f = io.BytesIO()\n",
@@ -612,7 +613,7 @@
"if predictions:\n",
" fig, ax = plt.subplots(figsize=(11, 3), dpi=150)\n",
" openbb.stocks.pred.regression(\n",
- " ticker, ticker_data_all[\"Close\"], 1, 80, 20, 1, external_axes=[ax]\n",
+ " symbol, ticker_data_all[\"Close\"], 1, 80, 20, 1, external_axes=[ax]\n",
" )\n",
" fig.tight_layout()\n",
" f = io.BytesIO()\n",
@@ -636,15 +637,14 @@
"outputs": [],
"source": [
"body = \"\"\n",
- "\n",
- "img = f'<img src=\"data:image/png;base64,{openbb_image_encoded.decode()}\" alt=\"OpenBB\" style=\"width:144px;\">'\n",
"body += openbb.widgets.header(\n",
- " img,\n",
- " author,\n",
- " report_date,\n",
- " report_time,\n",
- " report_timezone,\n",
- " f\"<b>INVESTMENT RESEARCH REPORT:</b> {ticker}\",\n",
+ " openbb_img=\"./openbb_terminal/reports/OpenBB_reports_logo.png\",\n",
+ " floppy_disk_img=\"./openbb_terminal/reports/floppy-disc.png\",\n",
+ " author=author,\n",
+ " report_date=report_date,\n",
+ " report_time=report_time,\n",
+ " report_tz=report_tz,\n",
+ " title=f\"<b>{report_title}</b>\",\n",
")\n",
"\n",
"body += openbb.widgets.tablinks(\n",
@@ -663,14 +663,16 @@
")\n",
"\n",
"htmlcode = openbb.widgets.h(3, \"KPIs\")\n",
- "htmlcode += openbb.widgets.kpi(\n",
- " [last_price],\n",
- " [\n",
- " \"Last closing price is above the average price ratings of last 30 days\",\n",
- " \"Average price ratings of last 30 day is above last closing price\",\n",
- " ],\n",
- " avg_ratings_last_30_days,\n",
- ")\n",
+ "\n",
+ "if avg_ratings > 0:\n",
+ " htmlcode += openbb.widgets.kpi(\n",
+ " [last_price],\n",
+ " [\n",
+ " f\"Last closing price is above the average price ratings of last {days} days\",\n",
+ " f\"Average price ratings of last {days} days is above last closing price\",\n",
+ " ],\n",
+ " avg_ratings,\n",
+ " )\n",
"if predictions:\n",
" htmlcode += openbb.widgets.kpi(\n",
" [0],\n",
@@ -771,9 +773,13 @@
" htmlcode = openbb.widgets.row([\"Prediction features not enabled.\"])\n",
"body += openbb.widgets.add_tab(\"Prediction Techniques\", htmlcode)\n",
"\n",
- "body += openbb.widgets.tab_clickable_evt()\n",
+ "body += openbb.widgets.tab_clickable_and_save_evt()\n",
"\n",
- "report = openbb.widgets.html_report(title=report_name, stylesheet=stylesheet, body=body)\n",
+ "report = openbb.widgets.html_report(\n",
+ " title=report_name, \n",
+ " stylesheet=openbb.widgets.html_report_stylesheet(), \n",
+ " body=body\n",
+ ")\n",
"\n",
"# to save the results\n",
"with open(report_name + \".html\", \"w\", encoding=\"utf-8\") as fh:\n",
@@ -809,7 +815,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.8.13"
+ "version": "3.8.6"
}
},
"nbformat": 4,
diff --git a/openbb_terminal/reports/floppy-disc.png b/openbb_terminal/reports/floppy-disc.png
new file mode 100644
index 00000000000..a318f414aca
--- /dev/null
+++ b/openbb_terminal/reports/floppy-disc.png
Binary files differ
diff --git a/openbb_terminal/reports/reports_controller.py b/openbb_terminal/reports/reports_controller.py
index 12ec798fd72..a8c4bae7aae 100644
--- a/openbb_terminal/reports/reports_controller.py
+++ b/openbb_terminal/reports/reports_controller.py
@@ -79,6 +79,8 @@ class ReportController(BaseController):
for param in literal_eval(params.strip('source": '))
if param[0] not in ["#", "\n"]
]
+ else:
+ l_params = []
d_params[report_to_run] = l_params
# On the menu of choices add the parameters necessary for each template report
diff --git a/openbb_terminal/reports/widget_helpers.py b/openbb_terminal/reports/widget_helpers.py
index 600e2e8eac8..d0456b41e44 100644
--- a/openbb_terminal/reports/widget_helpers.py
+++ b/openbb_terminal/reports/widget_helpers.py
@@ -76,7 +76,7 @@ def html_report(title: str = "", stylesheet: str = "", body: str = "") -> str:
os.path.join(os.path.dirname(os.path.abspath(__file__)), "widgets", "report.j2")
) as f:
template = Template(f.read())
- return template.render(title=title, stylesheet=stylesheet, body=body)
+ return template.render(title=title, stylesheet=stylesheet, body=body + "</html>")
def h(level: int, text: str) -> str:
@@ -185,17 +185,21 @@ def add_tab(title: str, htmlcode: str, comment_cell: bool = True) -> str:
"""
html_text = f'<div id="{title}" class="tabcontent"></br>'
if comment_cell:
- html_text += """<p style="border:3px; border-style:solid;
- border-color:#000000; padding: 1em; width: 1050px;" contentEditable="true">
- No comment.
- </p>"""
+ html_text += """<form><input style="border:3px; border-style:solid;
+ border-color:#000000; padding: 1em; width: 1050px;" type="text" value="No comment.">
+ </form>"""
html_text += f"{htmlcode}</div>"
return html_text
-def tab_clickable_evt() -> str:
+def tab_clickable_and_save_evt() -> str:
"""Adds javascript code within HTML at the bottom that allows the interactivity with tabs.
+ Parameters
+ ----------
+ report_name : str
+ Report name for the file to be saved
+
Returns
-------
str
@@ -204,25 +208,51 @@ def tab_clickable_evt() -> str:
return """
<script>
function menu(evt, menu_name) {
- var i, tabcontent, tablinks;
- tabcontent = document.getElementsByClassName("tabcontent");
- for (i = 0; i < tabcontent.length; i++) {
- tabcontent[i].style.display = "none";
- }
- tablinks = document.getElementsByClassName("tablinks");
- for (i = 0; i < tablinks.length; i++) {
- tablinks[i].className = tablinks[i].className.replace(" active", "");
- tablinks[i].style.backgroundColor = "white";
- tablinks[i].style.color = "black";
- }
- document.getElementById(menu_name).style.display = "block";
-
- evt.currentTarget.className += " active";
- evt.currentTarget.style.backgroundColor = "black";
- evt.currentTarget.style.color = "white";
+ var i, tabcontent, tablinks;
+ tabcontent = document.getElementsByClassName("tabcontent");
+ for (i = 0; i < tabcontent.length; i++) {
+ tabcontent[i].style.display = "none";
+ }
+ tablinks = document.getElementsByClassName("tablinks");
+ for (i = 0; i < tablinks.length; i++) {
+ tablinks[i].className = tablinks[i].className.replace(" active", "");
+ tablinks[i].style.backgroundColor = "white";
+ tablinks[i].style.color = "black";
+ }
+ document.getElementById(menu_name).style.display = "block";
+
+ evt.currentTarget.className += " active";
+ evt.currentTarget.style.backgroundColor = "black";
+ evt.currentTarget.style.color = "white";
+ }
+
+ function saveReport() {
+ const markup = document.documentElement.innerHTML;
+ var bl = new Blob([markup], { type: "text/html" });
+ var a = document.createElement("a");
+ a.href = URL.createObjectURL(bl);
+ a.download = "openbb_report.html";
+ a.hidden = true;
+ document.body.appendChild(a);
+ a.innerHTML = "Download";
+ a.click();
+ }
+
+ function readCommentsAndUpdateValues() {
+ var inputs, index;
+
+ inputs = document.getElementsByTagName('input');
+ for (index = 0; index < inputs.length; ++index) {
+ const elem = inputs[index];
+ elem.addEventListener('input', (e) => {
+ console.log(elem.name, elem.value, e.target.value);
+ elem.setAttribute("value", e.target.value)
+ });
+ }
}
window.onload=function(){
+ readCommentsAndUpdateValues();
menu(event, 'SUMMARY');
};
</script>"""
@@ -251,13 +281,17 @@ def tablinks(tabs: List[str]) -> str:
return htmlcode
-def header(img, author, report_date, report_time, report_tz, title) -> str:
+def header(
+ openbb_img, floppy_disk_img, author, report_date, report_time, report_tz, title
+) -> str:
"""Creates reports header
Parameters
----------
- img : str
- Image for customizable report
+ openbb_img : str
+ Image of OpenBB logo
+ floppy_disk_img : str
+ Image of floppy disk containing the save button
author : str
Name of author responsible by report
report_date : str
@@ -274,17 +308,46 @@ def header(img, author, report_date, report_time, report_tz, title) -> str:
str
HTML code for interactive tabs
"""
+ try:
+ with open(openbb_img, "rb") as image_file:
+ openbb_image_encoded = base64.b64encode(image_file.read())
+ openbb_img = f"""
+ <img src="data:image/png;base64,{openbb_image_encoded.decode()}"
+ alt="OpenBB" style="width:144px;">"""
+ except Exception:
+ openbb_img = ""
+
+ try:
+ with open(floppy_disk_img, "rb") as image_file:
+ floppy_disk_encoded = base64.b64encode(image_file.read())
+ flask_disk_save = f"""
+ <center><img src="data:image/png;base64,{floppy_disk_encoded.decode()}"
+ alt="OpenBB" style="width:40px;"></center>"""
+ except Exception:
+ flask_disk_save = ""
+
return f"""
- <div style="display:flex; margin-bottom:1cm;">
- {img}
- <div style="margin-left:2em">
- <p><b>Analyst:</b> {author}</p>
- <p><b>Date :</b> {report_date}</p>
- <p><b>Time :</b> {report_time} {report_tz}</p>
- <br/>
- <p>{title}</p>
+ <html lang="en" class="" data-lt-installed="true">
+ <head>
+ <meta charset="UTF-8">
+ <title>OpenBB Terminal Report</title>
+ <meta name="robots" content="noindex">
+ </head>
+ <div style="display:flex; margin-bottom:1cm;">
+ {openbb_img}
+ <div style="margin-left:2em">
+ <p><b>Analyst:</b> {author}</p>
+ <p><b>Date :</b> {report_date}</p>
+ <p><b>Time :</b> {report_time} {report_tz}</p>
+ <br/>
+ <p>{title}</p>
+ </div>
+ <button style="margin-left:7em; border:0px solid black;
+ background-color: transparent;" onclick="saveReport()">
+ {flask_disk_save}Save changes
+ </button>
</div>
- </div>"""
+ """
def add_external_fig(figloc: str, style: str = "") -> str: