diff --git a/openbb_terminal/core/plots/plotly.html b/openbb_terminal/core/plots/plotly.html
index 070eb5cea66..5138d8058a8 100644
--- a/openbb_terminal/core/plots/plotly.html
+++ b/openbb_terminal/core/plots/plotly.html
@@ -160,6 +160,23 @@
+
+
+
@@ -209,9 +226,13 @@
!["svg", "pdf"].includes(extension)
) {
if (annotation.text[0] == "/") {
- cmd_src = annotation.text;
+ globals.cmd_src = annotation.text;
+ globals.cmd_idx =
+ window.plotly_figure.layout.annotations.indexOf(annotation);
annotation.text = "";
+
let margin = window.plotly_figure.layout.margin;
+ globals.old_margin = { ...margin };
if (margin.t != undefined && margin.t > 40) {
margin.t = 40;
}
@@ -219,7 +240,6 @@
if (cmd_src == "/stocks/candle") {
margin.r -= 40;
}
- window.plotly_figure.layout.margin = margin;
}
}
});
@@ -235,6 +255,8 @@
let title =
window.plotly_figure?.layout?.title?.text ?? "Interactive Chart";
+ globals.title = title;
+
if (title.length > 50) {
document.getElementById("title").style.fontSize = "12px";
}
@@ -248,12 +270,14 @@
CSV_DIV = document.getElementById("popup_csv");
TEXT_DIV = document.getElementById("popup_text");
TITLE_DIV = document.getElementById("popup_title");
+ DOWNLOAD_DIV = document.getElementById("popup_download");
OpenBBMain(
window.plotly_figure,
CHART_DIV,
CSV_DIV,
TEXT_DIV,
- TITLE_DIV
+ TITLE_DIV,
+ DOWNLOAD_DIV
);
}
}, 20);
diff --git a/openbb_terminal/core/plots/web/bar_menus.js b/openbb_terminal/core/plots/web/bar_menus.js
index 93f4146afb0..c3f8fd32848 100644
--- a/openbb_terminal/core/plots/web/bar_menus.js
+++ b/openbb_terminal/core/plots/web/bar_menus.js
@@ -234,7 +234,49 @@ function uploadImage() {
});
}
-function downloadImage(filename, extension) {
+const openbb_watermark = {
+ yref: "paper",
+ xref: "paper",
+ x: 1,
+ y: 0,
+ text: "OpenBB Terminal",
+ font_size: 17,
+ font_color: "gray",
+ opacity: 0.5,
+ xanchor: "right",
+ yanchor: "bottom",
+ yshift: -80,
+ xshift: 40,
+};
+
+async function setWatermarks(margin, old_index, init = false) {
+ if (init) {
+ globals.CHART_DIV.layout.annotations.push(openbb_watermark);
+ if (globals.cmd_idx && globals.cmd_src) {
+ globals.CHART_DIV.layout.annotations[globals.cmd_idx].text =
+ globals.cmd_src;
+ }
+
+ Plotly.relayout(globals.CHART_DIV, {
+ "title.text": globals.title,
+ margin: globals.old_margin,
+ });
+ }
+
+ if (!init) {
+ if (globals.cmd_idx && globals.cmd_src) {
+ globals.CHART_DIV.layout.annotations[globals.cmd_idx].text = "";
+ }
+ globals.CHART_DIV.layout.annotations.splice(old_index, 1);
+
+ Plotly.relayout(globals.CHART_DIV, {
+ "title.text": "",
+ margin: margin,
+ });
+ }
+}
+
+async function downloadImage(filename, extension, writable = undefined) {
const loader = document.getElementById("loader");
loader.classList.add("show");
@@ -247,20 +289,47 @@ function downloadImage(filename, extension) {
// } else if (extension == "svg") {
// imageDownload = domtoimage.toSvg;
} else if (["svg", "pdf"].includes(extension)) {
- Plotly.downloadImage(globals.CHART_DIV, {
- format: "svg",
- height: globals.CHART_DIV.clientHeight,
- width: globals.CHART_DIV.clientWidth,
- filename: filename,
- });
+ const margin = globals.CHART_DIV.layout.margin;
+ const old_index = globals.CHART_DIV.layout.annotations.length;
+
+ await setWatermarks(margin, old_index, true);
+
+ if (window.showSaveFilePicker && writable) {
+ await Plotly.toImage(globals.CHART_DIV, {
+ format: "svg",
+ height: globals.CHART_DIV.clientHeight,
+ width: globals.CHART_DIV.clientWidth,
+ }).then(async function (dataUrl) {
+ const blob = await fetch(dataUrl).then((r) => r.blob());
+ await writable.write(blob);
+ await writable.close();
+ });
+ } else {
+ Plotly.downloadImage(globals.CHART_DIV, {
+ format: "svg",
+ height: globals.CHART_DIV.clientHeight,
+ width: globals.CHART_DIV.clientWidth,
+ filename: filename,
+ });
+ }
+
+ await setWatermarks(margin, old_index, false);
+
return;
} else {
console.log("Invalid extension");
return;
}
+
imageDownload(document.getElementById("openbb_container"))
- .then(function (dataUrl) {
- downloadURI(dataUrl, filename + "." + extension);
+ .then(async function (dataUrl) {
+ if (window.showSaveFilePicker && writable) {
+ const blob = await fetch(dataUrl).then((r) => r.blob());
+ await writable.write(blob);
+ await writable.close();
+ } else {
+ downloadURI(dataUrl, filename + "." + extension);
+ }
loader.classList.remove("show");
})
.catch(function (error) {
@@ -279,7 +348,7 @@ function downloadURI(uri, name) {
document.body.removeChild(link);
}
-function downloadData(gd) {
+async function downloadData(gd, filename, writable = undefined) {
let data = gd.data;
let candlestick = false;
let csv = undefined;
@@ -363,23 +432,29 @@ function downloadData(gd) {
}
}
- let filename = globals.filename;
- let blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
+ const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
if (navigator.msSaveBlob) {
// IE 10+
navigator.msSaveBlob(blob, filename);
} else {
- let link = window.document.createElement("a");
- if (link.download !== undefined) {
- // feature detection
- // Browsers that support HTML5 download attribute
- let url = URL.createObjectURL(blob);
- link.setAttribute("href", url);
- link.setAttribute("download", filename);
- link.style.visibility = "hidden";
- window.document.body.appendChild(link);
- link.click();
- window.document.body.removeChild(link);
+ // feature detection
+ // Browsers that support showSaveFilePicker or HTML5 download attribute
+ if (window.showSaveFilePicker && writable) {
+ await writable.write(blob);
+ await writable.close();
+ } else {
+ let link = window.document.createElement("a");
+ if (link.download !== undefined) {
+ // feature detection
+ // Browsers that support HTML5 download attribute
+ let url = URL.createObjectURL(blob);
+ link.setAttribute("href", url);
+ link.setAttribute("download", filename);
+ link.style.visibility = "hidden";
+ window.document.body.appendChild(link);
+ link.click();
+ window.document.body.removeChild(link);
+ }
}
}
}
diff --git a/openbb_terminal/core/plots/web/main.js b/openbb_terminal/core/plots/web/main.js
index 548636e64c5..ff8480c0aef 100644
--- a/openbb_terminal/core/plots/web/main.js
+++ b/openbb_terminal/core/plots/web/main.js
@@ -32,12 +32,20 @@ const non_blocking = (func, delay) => {
};
};
-function OpenBBMain(plotly_figure, chartdiv, csvdiv, textdiv, titlediv) {
+function OpenBBMain(
+ plotly_figure,
+ chartdiv,
+ csvdiv,
+ textdiv,
+ titlediv,
+ downloaddiv
+) {
// Main function that plots the graphs and initializes the bar menus
globals.CHART_DIV = chartdiv;
globals.TITLE_DIV = titlediv;
globals.TEXT_DIV = textdiv;
globals.CSV_DIV = csvdiv;
+ globals.DOWNLOAD_DIV = downloaddiv;
console.log("main.js loaded");
console.log("plotly_figure", plotly_figure);
let graphs = plotly_figure;
@@ -82,15 +90,15 @@ function OpenBBMain(plotly_figure, chartdiv, csvdiv, textdiv, titlediv) {
{
name: "Download CSV (Ctrl+Shift+S)",
icon: ICONS.downloadCsv,
- click: function (gd) {
- downloadCsvButton(gd);
+ click: async function (gd) {
+ await downloadCsvButton(gd);
},
},
{
name: "Download PNG (Ctrl+S)",
icon: ICONS.downloadImage,
- click: function () {
- downloadImageButton();
+ click: async function () {
+ await downloadImageButton();
},
},
// {
@@ -349,11 +357,44 @@ function OpenBBMain(plotly_figure, chartdiv, csvdiv, textdiv, titlediv) {
button_pressed(title, active);
}
- function downloadImageButton() {
- loadingOverlay("Saving PNG");
+ async function downloadImageButton() {
+ let filename = globals.filename;
+ let ext = "png";
+ let writable;
+ if (window.showSaveFilePicker) {
+ const handle = await showSaveFilePicker({
+ suggestedName: `${filename}.${ext}`,
+ types: [
+ {
+ description: "PNG Image",
+ accept: {
+ "image/png": [".png"],
+ },
+ },
+ {
+ description: "JPEG Image",
+ accept: {
+ "image/jpeg": [".jpeg"],
+ },
+ },
+ {
+ description: "SVG Image",
+ accept: {
+ "image/svg+xml": [".svg"],
+ },
+ },
+ ],
+ excludeAcceptAllOption: true,
+ });
+ writable = await handle.createWritable();
+ filename = handle.name;
+ ext = handle.name.split(".").pop();
+ }
+
+ loadingOverlay(`Saving ${ext.toUpperCase()}`);
hideModebar();
- non_blocking(function () {
- downloadImage(globals.filename, "png");
+ non_blocking(async function () {
+ await downloadImage(filename, ext, writable);
setTimeout(function () {
setTimeout(function () {
loading.classList.remove("show");
@@ -363,10 +404,30 @@ function OpenBBMain(plotly_figure, chartdiv, csvdiv, textdiv, titlediv) {
}, 2)();
}
- function downloadCsvButton(gd) {
+ async function downloadCsvButton(gd) {
+ let filename = globals.filename;
+ let writable;
+
+ if (window.showSaveFilePicker) {
+ const handle = await showSaveFilePicker({
+ suggestedName: `${filename}.csv`,
+ types: [
+ {
+ description: "CSV File",
+ accept: {
+ "image/csv": [".csv"],
+ },
+ },
+ ],
+ excludeAcceptAllOption: true,
+ });
+ writable = await handle.createWritable();
+ filename = handle.name;
+ }
+
loadingOverlay("Saving CSV");
- setTimeout(function () {
- downloadData(gd);
+ setTimeout(async function () {
+ await downloadData(gd, filename, writable);
}, 500);
setTimeout(function () {
loading.classList.remove("show");
diff --git a/openbb_terminal/core/plots/web/popups.js b/openbb_terminal/core/plots/web/popups.js
index bfb90633983..5e1ab8ac0b5 100644
--- a/openbb_terminal/core/plots/web/popups.js
+++ b/openbb_terminal/core/plots/web/popups.js
@@ -183,9 +183,35 @@ function get_popup(data = null, popup_id = null) {
`;
popup = globals.TEXT_DIV;
console.log("upload");
+ } else if (popup_id == "download") {
+ popup = globals.DOWNLOAD_DIV;
+
+ popup.querySelector("#download_path").innerHTML = `
+
+ `;
+
+ openfile_button = popup.querySelector("#openfile_button");
+ openfile_button.onclick = function () {
+ window.ipc.postMessage(`#OPEN:${data}`);
+ };
+
+ openfolder_button = popup.querySelector("#openfolder_button");
+ openfolder_button.onclick = function () {
+ folder_path = data.split("\\").slice(0, -1).join("\\");
+ window.ipc.postMessage(`#OPEN:${folder_path}`);
+ };
+
+ globals.DOWNLOAD_DIV.style.display = "inline-block";
}
- let popup_divs = [globals.TITLE_DIV, globals.TEXT_DIV, globals.CSV_DIV];
+ let popup_divs = [
+ globals.TITLE_DIV,
+ globals.TEXT_DIV,
+ globals.CSV_DIV,
+ globals.DOWNLOAD_DIV,
+ ];
popup_divs.forEach(function (div) {
if (div.id != popup.id) {
div.style.display = "none";