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:
teh_coderer
2023-04-13 13:37:09 -05:00
committed by GitHub
parent 8bb059b6cf
commit ebbc7ccc01
4 changed files with 225 additions and 39 deletions

View File

@@ -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);

View File

@@ -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);
}
}
}
}

View File

@@ -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");

View File

@@ -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";