mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 20:42:26 +00:00
Feature/ Interactive Chart Save As (#4771)
* init * Update plotly.html * save as * move savefilepicker to before csv overlay * adds watermarks to svg saves
This commit is contained in:
30
openbb_terminal/core/plots/plotly.html
vendored
30
openbb_terminal/core/plots/plotly.html
vendored
@@ -160,6 +160,23 @@
|
||||
|
||||
<!-- Title Popup -->
|
||||
<div class="popup_content" id="popup_title"></div>
|
||||
|
||||
<!-- Download Popup -->
|
||||
<div class="popup_content" id="popup_download">
|
||||
<p>File saved to:</p>
|
||||
<p id="download_path"></p>
|
||||
<div style="float: right; margin-top: 20px">
|
||||
<button class="_btn" id="openfile_button">Open File</button>
|
||||
<button class="_btn" id="openfolder_button">Open Folder</button>
|
||||
<button
|
||||
class="_btn-tertiary"
|
||||
id="close_button"
|
||||
onclick="closePopup()"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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);
|
||||
|
||||
121
openbb_terminal/core/plots/web/bar_menus.js
vendored
121
openbb_terminal/core/plots/web/bar_menus.js
vendored
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
85
openbb_terminal/core/plots/web/main.js
vendored
85
openbb_terminal/core/plots/web/main.js
vendored
@@ -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");
|
||||
|
||||
28
openbb_terminal/core/plots/web/popups.js
vendored
28
openbb_terminal/core/plots/web/popups.js
vendored
@@ -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 = `
|
||||
<textarea style="${style} width: 100%; max-width: 100%; max-height: 200px;
|
||||
margin-top: 8px;" rows="4" cols="50" value="${data}"
|
||||
placeholder="Enter text here">${data}</textarea><br>
|
||||
`;
|
||||
|
||||
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";
|
||||
|
||||
Reference in New Issue
Block a user