remove plotly charts legacy web modules (#5034)

This commit is contained in:
teh_coderer
2023-05-16 16:05:48 -05:00
committed by GitHub
parent fd0ad21873
commit 0f3497a3c1
11 changed files with 0 additions and 4442 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 727 B

View File

@@ -1,41 +0,0 @@
<html>
<head>
<meta charset="utf-8" />
<meta name="color-scheme" content="dark" />
<link rel="stylesheet" href="/{{MAIN_PATH}}/web/css/table.css" />
<script src="/{{MAIN_PATH}}/web/helpers.js" type="text/javascript"></script>
<script
src="/{{MAIN_PATH}}/web/bar_menus.js"
type="text/javascript"
></script>
<script src="/{{MAIN_PATH}}/web/popups.js" type="text/javascript"></script>
<script
src="/{{MAIN_PATH}}/web/main_table.js"
type="text/javascript"
></script>
<link
href="https://unpkg.com/tabulator-tables@5.4.3/dist/css/tabulator_midnight.min.css"
rel="stylesheet"
/>
<script
type="text/javascript"
src="https://unpkg.com/tabulator-tables@5.4.3/dist/js/tabulator.min.js"
></script>
</head>
<body>
<div id="example-table"></div>
<script type="text/javascript">
var interval = setInterval(function () {
// Waits for OpenBBMain and plotly_figure to be defined before
// calling OpenBBMain to avoid errors
if (typeof TableProcessor != "undefined" && window.plotly_figure) {
clearInterval(interval);
TableProcessor(window.plotly_figure);
}
}, 20);
</script>
</body>
</html>

View File

@@ -1,460 +0,0 @@
// Custom Menu functions for Plotly charts
async function autoScaling(eventdata, graphs) {
try {
if (eventdata["xaxis.range[0]"] != undefined) {
const x_min = eventdata["xaxis.range[0]"];
const x_max = eventdata["xaxis.range[1]"];
let to_update = {};
let y_min, y_max;
const YaxisData = graphs.data.filter((trace) => trace.yaxis != undefined);
const yaxis_unique = [
...new Set(
YaxisData.map(
(trace) =>
trace.yaxis || trace.y != undefined || trace.type == "candlestick"
)
),
];
const get_all_yaxis_traces = (yaxis) => {
return graphs.data.filter(
(trace) =>
trace.yaxis == yaxis && (trace.y || trace.type == "candlestick")
);
};
yaxis_unique.forEach((unique) => {
if (typeof unique != "string") {
return;
}
let yaxis = "yaxis" + unique.replace("y", "");
let y_candle = [];
let y_values = [];
let log_scale = graphs.layout[yaxis].type == "log";
get_all_yaxis_traces(unique).forEach((trace2) => {
let x = trace2.x;
log_scale = graphs.layout[yaxis].type == "log";
let y = trace2.y != undefined ? trace2.y : [];
let y_low = trace2.type == "candlestick" ? trace2.low : [];
let y_high = trace2.type == "candlestick" ? trace2.high : [];
if (log_scale) {
y = y.map(Math.log10);
if (trace2.type == "candlestick") {
y_low = trace2.low.map(Math.log10);
y_high = trace2.high.map(Math.log10);
}
}
let yx_values = x.map((x, i) => {
let out = null;
if (x >= x_min && x <= x_max) {
if (trace2.y != undefined) {
console.log(trace2.name, trace2.type, trace2.yaxis);
out = y[i];
}
if (trace2.type == "candlestick") {
y_candle.push(y_low[i]);
y_candle.push(y_high[i]);
}
}
return out;
});
y_values = y_values.concat(yx_values);
});
y_values = y_values.filter((y2) => y2 != undefined && y2 != null);
y_min = Math.min(...y_values);
y_max = Math.max(...y_values);
if (y_candle.length > 0) {
y_candle = y_candle.filter((y2) => y2 != undefined && y2 != null);
y_min = Math.min(...y_candle);
y_max = Math.max(...y_candle);
}
let org_y_max = y_max;
let is_volume =
graphs.layout[yaxis].fixedrange != undefined &&
graphs.layout[yaxis].fixedrange == true;
if (y_min != undefined && y_max != undefined) {
let y_range = y_max - y_min;
let y_mult = 0.15;
if (y_candle.length > 0) {
y_mult = 0.3;
}
y_min -= y_range * y_mult;
y_max += y_range * y_mult;
if (is_volume) {
if (graphs.layout[yaxis].tickvals != undefined) {
const range_x = 7;
let volume_ticks = org_y_max;
let round_digits = -3;
let first_val = Math.round(volume_ticks * 0.2, round_digits);
let x_zipped = [2, 5, 6, 7, 8, 9, 10];
let y_zipped = [1, 4, 5, 6, 7, 8, 9];
for (let i = 0; i < x_zipped.length; i++) {
if (String(volume_ticks).length > x_zipped[i]) {
round_digits = -y_zipped[i];
first_val = Math.round(volume_ticks * 0.2, round_digits);
}
}
let tickvals = [
Math.floor(first_val),
Math.floor(first_val * 2),
Math.floor(first_val * 3),
Math.floor(first_val * 4),
];
let volume_range = [0, Math.floor(volume_ticks * range_x)];
to_update[yaxis + ".tickvals"] = tickvals;
to_update[yaxis + ".range"] = volume_range;
to_update[yaxis + ".tickformat"] = ".2s";
return;
}
y_min = 0;
y_max = graphs.layout[yaxis].range[1];
}
to_update[yaxis + ".range"] = [y_min, y_max];
}
});
Plotly.update(graphs, {}, to_update);
}
} catch (e) {
console.log(`Error in AutoScaling: ${e}`);
}
}
function changeColor() {
if (!globals.color_picker) {
let color_picker = document.getElementById("changecolor");
globals.color_picker = color_picker;
color_picker.style.display = "block";
color_picker.style.width = null;
dragElement(color_picker);
function dragElement(elmnt) {
let pos1 = 0,
pos2 = 0,
pos3 = 0,
pos4 = 0;
if (document.getElementById(elmnt.id + "_header")) {
// if present, the header is where you move the DIV from:
document.getElementById(elmnt.id + "_header").onmousedown =
dragMouseDown;
} else {
// otherwise, move the DIV from anywhere inside the DIV:
elmnt.onmousedown = dragMouseDown;
}
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// get the mouse cursor position at startup:
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
// call a function whenever the cursor moves:
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// calculate the new cursor position:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
elmnt.style.top = elmnt.offsetTop - pos2 + "px";
elmnt.style.left = elmnt.offsetLeft - pos1 + "px";
}
function closeDragElement() {
// stop moving when mouse button is released:
document.onmouseup = null;
document.onmousemove = null;
}
}
} else {
globals.color_picker.style.display = "none";
globals.color_picker = null;
}
}
function uploadImage() {
const loader = document.getElementById("loader");
loader.classList.add("show");
Plotly.toImage(globals.CHART_DIV, {
format: "png",
height: 627,
width: 1200,
})
.then(function (url) {
let data = {
image: url,
title: globals.title,
description: "Check out this chart from OpenBB",
};
fetch("https://uppy-self.vercel.app/api/upload-image", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
})
.then(function (response) {
return response.json();
})
.then(function (data) {
const { url, id } = data;
loader.classList.remove("show");
openPopup("popup_upload", { url });
})
.catch(function (err) {
console.log(err);
loader.classList.remove("show");
});
})
.catch(function (err) {
console.log(err);
loader.classList.remove("show");
});
}
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");
let imageDownload = undefined;
if (extension == "png") {
imageDownload = domtoimage.toPng;
} else if (extension == "jpeg") {
imageDownload = domtoimage.toJpeg;
// } else if (extension == "svg") {
// imageDownload = domtoimage.toSvg;
} else if (["svg", "pdf"].includes(extension)) {
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(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) {
console.error("oops, something went wrong!", error);
loader.classList.remove("show");
hideModebar();
});
}
function downloadURI(uri, name) {
let link = document.createElement("a");
link.download = name;
link.href = uri;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
async function downloadData(gd, filename, writable = undefined) {
let data = gd.data;
let candlestick = false;
let csv = undefined;
data.forEach(function (trace) {
// check if candlestick
if (trace.type == "candlestick") {
candlestick = true;
return;
}
});
let xaxis =
"title" in gd.layout["xaxis"] &&
gd.layout["xaxis"]["title"]["text"] != undefined
? gd.layout["xaxis"]["title"]["text"]
: "x";
let yaxis =
"title" in gd.layout["yaxis"] &&
gd.layout["yaxis"]["title"]["text"] != undefined
? gd.layout["yaxis"]["title"]["text"]
: "y";
if (candlestick) {
csv = "Date,Open,High,Low,Close\n";
data.forEach(function (trace) {
if (trace.type == "candlestick") {
let x = trace.x;
let open = trace.open;
let high = trace.high;
let low = trace.low;
let close = trace.close;
for (let i = 0; i < x.length; i++) {
csv += `${x[i]},${open[i]},${high[i]},${low[i]},${close[i]}\n`;
}
}
});
} else {
let traces = 0;
data.forEach(function (trace) {
if (trace.type == "scatter") {
traces++;
}
});
if (traces == 1) {
csv = `${data[0].name},${xaxis},${yaxis}\n`;
data.forEach(function (trace) {
if (trace.type == "scatter") {
let x = trace.x;
let y = trace.y;
for (let i = 0; i < x.length; i++) {
csv += `${x[i]},${y[i]}\n`;
}
}
});
} else if (traces > 1) {
csv = `${xaxis}`;
data.forEach(function (trace) {
if (trace.type == "scatter") {
csv += `,${trace.name}`;
}
});
csv += "\n";
let x = data[0].x;
for (let i = 0; i < x.length; i++) {
csv += `${x[i]}`;
data.forEach(function (trace) {
if (trace.type == "scatter") {
csv += `,${trace.y[i]}`;
}
});
csv += "\n";
}
} else {
return;
}
}
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
if (navigator.msSaveBlob) {
// IE 10+
navigator.msSaveBlob(blob, filename);
} else {
// 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);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
body {
font-family: "Fira Code", monospace, "Arial Black";
font-size: 1.2rem;
color: #ffffff;
background-color: #000000;
overflow: hidden;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,646 +0,0 @@
let globals = {
dark_mode: false,
modebarHidden: false,
volume: {},
added_traces: [],
old_nticks: {},
csv_yaxis_id: null,
cmd_src_idx: null,
};
function loadingOverlay(message) {
const loading = document.getElementById("loading");
const loading_text = document.getElementById("loading_text");
loading_text.innerHTML = message;
loading.classList.add("show");
let is_loaded = setInterval(function () {
if (loading.classList.contains("show")) {
clearInterval(is_loaded);
}
}, 10);
}
const non_blocking = (func, delay) => {
let timeout;
return function () {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), delay);
};
};
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.dark_mode = plotly_figure.theme === "dark";
globals.DOWNLOAD_DIV = downloaddiv;
console.log("main.js loaded");
console.log("plotly_figure", plotly_figure);
let graphs = plotly_figure;
const loading = document.getElementById("loading");
// We add the event listeners for csv file/type changes
globals.CSV_DIV.querySelector("#csv_file").addEventListener(
"change",
function () {
console.log("file changed");
loadingOverlay("Loading CSV");
setTimeout(function () {
non_blocking(function () {
checkFile(globals.CSV_DIV);
}, 2)();
setTimeout(function () {
loading.classList.remove("show");
}, 200);
}, 1000);
}
);
globals.CSV_DIV.querySelector("#csv_trace_type").addEventListener(
"change",
function () {
console.log("type changed");
checkFile(globals.CSV_DIV, true);
}
);
globals.filename = openbbFilename(graphs);
// Sets the config with the custom buttons
CONFIG = {
plotGlPixelRatio: 1,
scrollZoom: true,
responsive: true,
displaylogo: false,
displayModeBar: true,
modeBarButtonsToRemove: ["lasso2d", "select2d", "downloadImage"],
modeBarButtons: [
[
{
name: "Download CSV (Ctrl+Shift+S)",
icon: ICONS.downloadCsv,
click: async function (gd) {
await downloadCsvButton(gd);
},
},
{
name: "Download Chart as Image (Ctrl+S)",
icon: ICONS.downloadImage,
click: async function () {
await downloadImageButton();
},
},
// {
// name: "Upload Image (Ctrl+U)",
// icon: Plotly.Icons.uploadImage,
// click: function (gd) {
// downloadImage();
// },
// },
],
[
{
name: "Edit Color (Ctrl+E)",
icon: ICONS.changeColor,
click: function () {
// We need to check if the button is active or not
let title = "Edit Color (Ctrl+E)";
let button = globals.barButtons[title];
let active = true;
if (button.style.border == "transparent") {
active = false;
}
// We call the function that changes the border color
button_pressed(title, active);
changeColor();
},
},
"drawline",
"drawopenpath",
"drawcircle",
"drawrect",
"eraseshape",
],
[
{
name: "Overlay chart from CSV (Ctrl+O)",
icon: ICONS.plotCsv,
click: function () {
// We make sure to close any other popup that might be open
// before opening the CSV popup
closePopup();
openPopup("popup_csv");
},
},
{
name: "Add Text (Ctrl+T)",
icon: ICONS.addText,
click: function () {
openPopup("popup_text");
},
},
{
name: "Change Titles (Ctrl+Shift+T)",
icon: ICONS.changeTitle,
click: function () {
openPopup("popup_title");
},
},
{
name: "Change Theme",
icon: ICONS.sunIcon,
click: function () {
changeTheme();
},
},
],
["hoverClosestCartesian", "hoverCompareCartesian", "toggleSpikelines"],
[
{
name: "Auto Scale (Ctrl+Shift+A)",
icon: Plotly.Icons.autoscale,
click: function () {
autoscaleButton();
},
},
"zoomIn2d",
"zoomOut2d",
"autoScale2d",
"zoom2d",
"pan2d",
],
],
};
graphs.layout.title = "";
// We make sure to fill in any missing layout properties with default values
if (!("font" in graphs.layout)) {
graphs.layout["font"] = {
family: "Fira Code, monospace, Arial Black",
size: 18,
};
}
graphs.layout.annotations = !graphs.layout.annotations
? []
: graphs.layout.annotations;
if (!("margin" in graphs.layout)) {
graphs.layout["margin"] = {
l: 0,
r: 0,
b: 0,
t: 0,
pad: 2,
};
}
// RIP: Juan hated it so had to remove it
// graphs.layout.xaxis.tickangle = -20;
graphs.layout.autosize = true;
// We set the height and width to undefined, so that plotly.js can
// automatically resize the chart to fit the PyWry window
delete graphs.layout.height;
delete graphs.layout.width;
if (graphs.layout.annotations != undefined) {
graphs.layout.annotations.forEach(function (annotation) {
// We make sure to fill in any missing annotation properties with default values
// We also make sure to set the font size to a reasonable value based on the
// width of the client window
if (!("font" in annotation) || !("size" in annotation.font)) {
annotation["font"] = {
family: "Fira Code, monospace, Arial Black",
size: 16,
};
}
if (annotation.text != undefined) {
if (annotation.text[0] == "/") {
globals.cmd_src_idx = graphs.layout.annotations.indexOf(annotation);
}
}
annotation.font.size = Math.min(
globals.CHART_DIV.clientWidth / 50,
annotation.font.size
);
});
}
// We add spaces to all trace names, due to Fira Code font width issues
// to make sure that the legend is not cut off
graphs.data.forEach(function (trace) {
if (trace.name != undefined) {
let name_length = trace.name.length;
trace.name = trace.name + " ";
trace.hoverlabel = {
namelength: name_length,
};
}
});
// Set the default dragmode to pan and set the font size to a reasonable value
graphs.layout.dragmode = "pan";
graphs.layout.font.size = Math.min(
globals.CHART_DIV.clientWidth / 50,
graphs.layout.font.size
);
// We check for the dark mode
if (graphs.layout.template.layout.mapbox.style == "dark") {
document.body.style.backgroundColor = "#000000";
document.getElementById("openbb_footer").style.color = "#000000";
graphs.layout.template.layout.paper_bgcolor = "#000000";
graphs.layout.template.layout.plot_bgcolor = "#000000";
} else {
document.body.style.backgroundColor = "#FFFFFF";
document.getElementById("openbb_footer").style.color = "#FFFFFF";
}
// We set the plot config and plot the chart
Plotly.setPlotConfig(CONFIG);
Plotly.react(globals.CHART_DIV, graphs, { responsive: true });
let AXES = Object.keys(graphs.layout).filter(
(x) => x.startsWith("xaxis") || x.startsWith("yaxis")
);
// We set all the axes automargin to false
if (AXES.length > 0) {
AXES.forEach((axis) => {
let margin = axis.startsWith("y") ? "l+r" : "t+b";
graphs.layout[axis].automargin = margin;
});
Plotly.react(globals.CHART_DIV, graphs, { responsive: true });
}
// Create global variables to for use later
const modebar = document.getElementsByClassName("modebar-container")[0];
const modebar_buttons = modebar.getElementsByClassName("modebar-btn");
globals.barButtons = {};
const captureButtons = [
"Download CSV",
"Download Chart as Image",
"Overlay chart from CSV",
"Add Text",
"Change Titles",
"Auto Scale (Ctrl+Shift+A)",
"Reset Axes",
];
for (let i = 0; i < modebar_buttons.length; i++) {
// We add the buttons to the global variable for later use
// and set the border to transparent so we can change the
// color of the buttons when they are pressed
let button = modebar_buttons[i];
if (captureButtons.includes(button.getAttribute("data-title"))) {
button.classList.add("ph-capture");
}
button.style.border = "transparent";
globals.barButtons[button.getAttribute("data-title")] = button;
}
// We change the Plotly default Autoscale button icon and title to Reset Axes
globals.barButtons["Reset Axes"] = globals.barButtons["Autoscale"];
globals.barButtons["Autoscale"]
.getElementsByTagName("path")[0]
.setAttribute("d", Plotly.Icons.home.path);
globals.barButtons["Autoscale"].setAttribute("data-title", "Reset Axes");
if (globals.CHART_DIV.layout.yaxis.type != undefined) {
if (globals.CHART_DIV.layout.yaxis.type == "log" && !globals.logYaxis) {
console.log("yaxis.type changed to log");
globals.logYaxis = true;
// We update the yaxis exponent format to SI,
// set the tickformat to '.0s' and the exponentbase to 100
let layout_update = {
"yaxis.exponentformat": "SI",
"yaxis.tickformat": ".0s",
"yaxis.exponentbase": 100,
};
Plotly.update(globals.CHART_DIV, layout_update);
}
if (globals.CHART_DIV.layout.yaxis.type == "linear" && globals.logYaxis) {
console.log("yaxis.type changed to linear");
globals.logYaxis = false;
// We update the yaxis exponent format to none,
// set the tickformat to null and the exponentbase to 10
let layout_update = {
"yaxis.exponentformat": "none",
"yaxis.tickformat": null,
"yaxis.exponentbase": 10,
};
Plotly.update(globals.CHART_DIV, layout_update);
}
}
if (window.plotly_figure.layout.template.layout.mapbox.style === "light") {
for (const el of document.styleSheets[0].cssRules) {
if (el.selectorText === ".modebar-group") {
el.style.backgroundColor = "#FFFFFF";
}
}
}
function hideModebar() {
if (globals.modebarHidden) {
modebar.style.display = "flex";
globals.modebarHidden = false;
} else {
modebar.style.display = "none";
globals.modebarHidden = true;
}
}
function autoscaleButton() {
// We need to check if the button is active or not
let title = "Auto Scale (Ctrl+Shift+A)";
let button = globals.barButtons[title];
let active = true;
if (button.style.border == "transparent") {
active = false;
globals.CHART_DIV.on(
"plotly_relayout",
non_blocking(async function (eventdata) {
if (eventdata["xaxis.range[0]"] == undefined) {
return;
}
await autoScaling(eventdata, globals.CHART_DIV);
}, 100)
);
} else {
// If the button isn't active, we remove the listener so
// the graphs don't autoscale anymore
globals.CHART_DIV.removeAllListeners("plotly_relayout");
}
button_pressed(title, active);
}
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(async function () {
await downloadImage(filename, ext, writable);
setTimeout(function () {
setTimeout(function () {
loading.classList.remove("show");
hideModebar();
}, 50);
}, 25);
}, 2)();
}
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(async function () {
await downloadData(gd, filename, writable);
}, 500);
setTimeout(function () {
loading.classList.remove("show");
}, 1000);
}
// We setup keyboard shortcuts custom to OpenBB
window.document.addEventListener("keydown", function (e) {
if (e.key.toLowerCase() == "h" && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
hideModebar();
}
if (e.ctrlKey) {
if (e.shiftKey && e.key.toLowerCase() == "t") {
openPopup("popup_title");
} else if (e.key.toLowerCase() == "t") {
openPopup("popup_text");
} else if (e.key.toLowerCase() == "e") {
changeColor();
} else if (e.shiftKey && e.key.toLowerCase() == "s") {
e.preventDefault();
downloadCsvButton(globals.CHART_DIV);
} else if (e.key.toLowerCase() == "s") {
e.preventDefault();
downloadImageButton();
} else if (e.key.toLowerCase() == "o") {
e.preventDefault();
openPopup("popup_csv");
} else if (e.shiftKey && e.key.toLowerCase() == "a") {
e.preventDefault();
autoscaleButton();
}
} else if (e.key == "Escape") {
closePopup();
}
});
// send a relayout event to trigger the initial zoom/bars-resize
// check if the xaxis.range is defined
if (
graphs.layout.xaxis != undefined &&
graphs.layout.xaxis.range != undefined
) {
Plotly.relayout(globals.CHART_DIV, {
"xaxis.range[0]": graphs.layout.xaxis.range[0],
"xaxis.range[1]": graphs.layout.xaxis.range[1],
});
}
// We find all xaxis that have showticklabels set to true
let XAXIS = Object.keys(globals.CHART_DIV.layout)
.filter((x) => x.startsWith("xaxis"))
.filter((x) => globals.CHART_DIV.layout[x].showticklabels);
let TRACES = globals.CHART_DIV.data.filter((trace) =>
trace?.name?.startsWith("Volume")
);
window.addEventListener("resize", function () {
// We check to see if the window.innerWidth is smaller than 900px
let layout_update = {};
let width = window.innerWidth;
let height = window.innerHeight;
let tick_size =
height > 420 && width < 920 ? 9 : height > 420 && width < 500 ? 10 : 8;
if (width < 920) {
// We hide the modebar and set the number of ticks to 5
TRACES.forEach((trace) => {
if (trace.type == "bar") {
trace.opacity = 1;
trace.marker.line.width = 0.09;
if (globals.volume.yaxis == undefined) {
globals.volume.yaxis = "yaxis" + trace.yaxis.replace("y", "");
layout_update[globals.volume.yaxis + ".tickfont.size"] = tick_size;
globals.volume.tickfont =
globals.CHART_DIV.layout[globals.volume.yaxis].tickfont;
globals.CHART_DIV.layout.margin.l -= 40;
}
}
});
XAXIS.forEach((x) => {
if (globals.old_nticks?.[x] == undefined) {
layout_update[x + ".nticks"] = 6;
globals.old_nticks[x] = globals.CHART_DIV.layout[x].nticks || 10;
}
});
modebar.style.display = "none";
globals.modebarHidden = true;
} else if (globals.modebarHidden) {
// We show the modebar
modebar.style.display = "flex";
globals.modebarHidden = false;
if (globals.old_nticks != undefined) {
XAXIS.forEach((x) => {
if (globals.old_nticks[x] != undefined) {
layout_update[x + ".nticks"] = globals.old_nticks[x];
globals.old_nticks[x] = undefined;
}
});
}
if (globals.volume.yaxis != undefined) {
TRACES.forEach((trace) => {
if (trace.type == "bar") {
trace.opacity = 0.5;
trace.marker.line.width = 0.2;
layout_update[globals.volume.yaxis + ".tickfont.size"] =
globals.volume.tickfont.size + 3;
globals.CHART_DIV.layout.margin.l += 40;
globals.volume.yaxis = undefined;
}
});
}
}
if (Object.keys(layout_update).length > 0) {
Plotly.relayout(globals.CHART_DIV, layout_update);
}
});
// We check to see if window.save_png is defined and true
if (window.save_image != undefined && window.export_image) {
// We get the extension of the file and check if it is valid
let filename = window.export_image.split("/").pop();
const extension = filename.split(".").pop().replace("jpg", "jpeg");
if (["jpeg", "png", "svg", "pdf"].includes(extension)) {
hideModebar();
non_blocking(function () {
downloadImage(filename.split(".")[0], extension);
}, 2)();
}
}
function changeTheme() {
globals.dark_mode = !globals.dark_mode;
document.body.style.backgroundColor = globals.dark_mode ? "#000" : "#fff";
globals.CHART_DIV.layout.font.color = globals.dark_mode ? "#fff" : "#000";
const changeIcon = globals.dark_mode ? ICONS.sunIcon : ICONS.moonIcon;
globals.barButtons["Change Theme"]
.getElementsByTagName("path")[0]
.setAttribute("d", changeIcon.path);
globals.barButtons["Change Theme"]
.getElementsByTagName("svg")[0]
.setAttribute("viewBox", changeIcon.viewBox);
const volumeColors = {
"#e4003a": "#c80000",
"#00ACFF": "#009600",
"#009600": "#00ACFF",
"#c80000": "#e4003a",
};
TRACES.forEach((trace) => {
if (trace.type == "bar") {
trace.marker.color.forEach((color, i) => {
trace.marker.color[i] = volumeColors[color] || color;
});
}
});
Plotly.update(
globals.CHART_DIV,
{},
{
template: globals.dark_mode
? DARK_CHARTS_TEMPLATE
: LIGHT_CHARTS_TEMPLATE,
}
);
}
}

View File

@@ -1,119 +0,0 @@
function TableProcessor(data) {
const parsedData = JSON.parse(data);
console.log(parsedData);
const processedData = processData(parsedData);
console.log(processedData);
const body = document.getElementsByTagName("body")[0];
const table = new Tabulator("#example-table", {
height: body.clientHeight - 54,
layout: "fitColumns",
data: processedData.data,
columns: processedData.columns,
});
const div = document.createElement("div");
div.innerHTML = `
<div style="margin-top:10px;">
<select id="filter-field">
<option></option>
${processedData.columns.map((column) => {
return `<option value="${column.field}">${column.title}</option>`;
})}
</select>
<select id="filter-type">
<option value="=">=</option>
<option value="<"><</option>
<option value="<="><=</option>
<option value=">">></option>
<option value=">=">>=</option>
<option value="!=">!=</option>
<option value="like">like</option>
</select>
<input id="filter-value" type="text" placeholder="value to filter">
<button id="filter-clear">Clear Filter</button>
</div>
`;
body.appendChild(div);
const filterField = document.getElementById("filter-field");
const filterType = document.getElementById("filter-type");
const filterValue = document.getElementById("filter-value");
const filterClear = document.getElementById("filter-clear");
filterField.addEventListener("change", function (e) {
table.setFilter(filterField.value, filterType.value, filterValue.value);
});
filterType.addEventListener("change", function (e) {
table.setFilter(filterField.value, filterType.value, filterValue.value);
});
filterValue.addEventListener("keyup", function (e) {
table.setFilter(filterField.value, filterType.value, filterValue.value);
});
filterClear.addEventListener("click", function (e) {
table.clearFilter();
});
}
function processData(data) {
/*
data is coming as {
columns: ["Name", "Age"],
data: ["John", 30]
}
*/
/*
final data should be {
columns: [
{
title: "Name",
field: "name",
sorter: "string",
},
{
title: "Age",
field: "age",
sorter: "number",
}
],
data: {
name: "John",
age: "30",
}
}
*/
const columns = data.columns.map((column) => {
return {
title: column,
field: column,
sorter: column.includes("Change"),
/*formatter: (cell) => {
if (column.startsWith("Change")) {
if (cell.getValue() > 0) {
return `<span style="color: green">${cell.getValue()}</span>`;
} else {
return `<span style="color: red">${cell.getValue()}</span>`;
}
} else {
return cell.getValue();
}
},*/
};
});
const finalData = data.data.map((row) => {
const obj = {};
row.forEach((item, index) => {
obj[data.columns[index]] = item;
});
return obj;
});
return {
columns,
data: finalData,
};
}

View File

@@ -1,738 +0,0 @@
function get_popup(data = null, popup_id = null) {
let popup = null;
popup_id = popup_id.replace("popup_", "");
const style =
"padding: 5px 2px 2px 5px !important; margin: 2px 0 !important;";
if (popup_id == "title") {
let title = globals.title;
data = globals.CHART_DIV.layout;
let yaxes = Object.keys(data).filter((k) => k.startsWith("yaxis"));
let xaxes = Object.keys(data).filter((k) => k.startsWith("xaxis"));
globals.TITLE_DIV.innerHTML = `
<div style="display:flex; flex-direction: column; gap: 6px;">
<div>
<label for="title_text">Title:</label>
<textarea
id="title_text" style="${style} width: 100%; max-width: 100%;
max-height: 200px; margin-top: 8px;" rows="2" cols="20"
value="${title}">${title}</textarea>
</div>
<div id="xaxis_div"></div>
<div id="yaxis_div"></div>
</div>
<div style="float: right; margin-top: 20px;">
<button class="_btn-tertiary ph-capture" id="title_cancel" onclick="closePopup()">Cancel</button>
<button class="_btn ph-capture" id="title_submit" onclick="on_submit('title')">Submit</button>
</div>
`;
// if there are multiple titles we create an input for each
let yaxis_div = "";
for (let i = 0; i < yaxes.length; i++) {
if (data[yaxes[i]]?.title?.text != undefined) {
let title = data[yaxes[i]].title.text;
yaxis_div += `
<div style="margin-top: 10px;">
<label for="title_${yaxes[i]}">Y axis ${i + 1}:</label>
<input id="title_${
yaxes[i]
}" style="${style}" type="text" value="${title}"></input>
</div>
`;
}
}
if (yaxis_div != "") {
globals.TITLE_DIV.querySelector("#yaxis_div").innerHTML = yaxis_div;
}
let xaxis_div = "";
for (let i = 0; i < xaxes.length; i++) {
if (data[xaxes[i]]?.title?.text != undefined) {
let title = data[xaxes[i]].title.text;
xaxis_div += `
<div style="margin-top: 10px;">
<label for="title_${xaxes[i]}">X axis ${i + 1}:</label>
<input id="title_${
xaxes[i]
}" style="${style}" type="text" value="${title}"></input>
</div>
`;
}
}
if (xaxis_div != "") {
globals.TITLE_DIV.querySelector("#xaxis_div").innerHTML = xaxis_div;
}
// when opening the popup, we make sure to focus on the title input
globals.TITLE_DIV.style.display = "inline-block";
globals.TITLE_DIV.querySelector("#title_text").focus();
popup = globals.TITLE_DIV;
} else if (popup_id == "text") {
let has_annotation = data == undefined ? false : true;
if (data == undefined) {
data = {
text: "",
font: {
color: "#0088CC",
size: 18,
},
bordercolor: "#822661",
};
}
// we replace <br> with \n so that the textarea can display the text properly
data.text = data.text.replace(/<br>/g, "\n");
let yanchor =
globals.TEXT_DIV.querySelector("#addtext_above") == null
? "above"
: globals.TEXT_DIV.querySelector("#addtext_above").checked
? "above"
: "below";
globals.TEXT_DIV.innerHTML = `
<div style="margin-bottom: 20px;">
<label for="popup_textarea"><b>Text:</b>
<div id="popup_textarea_warning" class="popup_warning">Text is required</div></label><br>
<textarea id="addtext_textarea" style="${style}
width: 100%; max-width: 100%; max-height: 200px;
margin-top: 8px;" rows="4" cols="50" value="${data.text}"
placeholder="Enter text here">${data.text}</textarea><br>
</div>
<div style="display:flex;justify-content: space-between;">
<div>
<label for="addtext_color"><b>Font color:</b></label>
<input type="color" id="addtext_color" value="${
data.font.color
}"></input>
</div>
<div>
<label for="addtext_border"><b>Border color:</b></label>
<input type="color" id="addtext_border" value="${
data.bordercolor
}"></input>
</div>
</div><br>
<div style="display:flex;justify-content: space-between;">
<div>
<label for="addtext_size"><b>Font size:</b></label>
<input style="${style} width: 45px;" type="number" id="addtext_size" value="${
data.font.size
}"></input>
</div>
<div>
<label for="addtext_above"><b>Position (Above):</b></label>
<input type="checkbox" id="addtext_above" name="check"
value="above" ${yanchor == "above" ? "checked" : ""}></input>
</div>
</div><br>
`;
if (has_annotation) {
globals.TEXT_DIV.innerHTML += `
<div style="float: right; margin-top: 20px;">
<button class="_btn-tertiary ph-capture" id="addtext_cancel" onclick="closePopup()">Cancel</button>
<button class="_btn ph-capture" id="addtext_delete" onclick="on_delete('text')">Delete</button>
<button class="_btn ph-capture" id="addtext_submit" onclick="on_submit('text', true)">Submit</button>
<input id="addtext_annotation" type="hidden" value='${JSON.stringify(
data
)}'></input>
</div>
`;
} else {
globals.TEXT_DIV.innerHTML += `
<div style="float: right; margin-top: 20px;">
<button class="_btn-tertiary ph-capture" id="addtext_cancel" onclick="closePopup()">Cancel</button>
<button class="_btn ph-capture" id="addtext_submit" onclick="on_submit('text')">Submit</button>
</div>
`;
if (globals.TEXT_DIV.querySelector("#addtext_annotation") != null) {
globals.TEXT_DIV.querySelector("#addtext_annotation").remove();
}
}
// when opening the popup, we make sure to focus on the textarea
globals.TEXT_DIV.style.display = "inline-block";
globals.TEXT_DIV.querySelector("#addtext_textarea").focus();
popup = globals.TEXT_DIV;
} else if (popup_id == "csv") {
globals.CSV_DIV.style.display = "inline-block";
popup = globals.CSV_DIV;
console.log("csv");
} else if (popup_id == "upload") {
globals.TEXT_DIV.style.display = "inline-block";
globals.TEXT_DIV.innerHTML = `
<div>
<p>Media preview (you can share it on twitter):</p>
<a style="margin-top: 10px;" href="${data.url}" target="_blank" rel="noreferrer noopener">${data.url}</a>
<div style="margin-top: 10px; float: right;">
<button onclick="closePopup()" style="margin-top: 10px;" class="_btn-tertiary">Close</button>
<a
class="_btn"
href="https://twitter.com/intent/tweet?text=Check this chart from @openbb_finance - ${data.url}"
>Share</a>
<button onclick="navigator.clipboard.writeText('${data.url}')" style="margin-top: 10px;"
class="_btn">Copy to clipboard</button>
</div>
</div>
`;
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,
globals.DOWNLOAD_DIV,
];
popup_divs.forEach(function (div) {
if (div.id != popup.id) {
div.style.display = "none";
}
});
return popup;
}
function get_popup_data(popup_id = null) {
// popup_id is either 'title', 'text', or 'csv' (for now)
// and is used to determine which popup to get data from
let data = null;
if (popup_id == "title") {
data = { title: globals.TITLE_DIV.querySelector("#title_text").value };
let xaxis_div = globals.TITLE_DIV.querySelector("#xaxis_div");
let yaxis_div = globals.TITLE_DIV.querySelector("#yaxis_div");
console.log("xaxis_div: ", xaxis_div, "yaxis_div: ", yaxis_div);
if (xaxis_div != null) {
// We query all inputs that start with 'title_xaxis'
let xaxis_inputs = xaxis_div.querySelectorAll(
"input[id^=title_xaxis], select[id^=title_xaxis]"
);
xaxis_inputs.forEach(function (input) {
let xaxis_id = input.id.replace("title_", "");
data[xaxis_id] = input.value;
});
}
if (yaxis_div != null) {
// We query all inputs that start with 'title_yaxis'
let yaxis_inputs = yaxis_div.querySelectorAll(
"input[id^=title_yaxis], select[id^=title_yaxis]"
);
yaxis_inputs.forEach(function (input) {
let yaxis_id = input.id.replace("title_", "");
data[yaxis_id] = input.value;
});
}
console.log("Title data:", data);
} else if (popup_id == "text") {
data = {
text: globals.TEXT_DIV.querySelector("#addtext_textarea").value,
color: globals.TEXT_DIV.querySelector("#addtext_color").value,
size: globals.TEXT_DIV.querySelector("#addtext_size").value,
yanchor: globals.TEXT_DIV.querySelector("#addtext_above").checked
? "above"
: "below",
bordercolor: globals.TEXT_DIV.querySelector("#addtext_border").value,
};
if (globals.TEXT_DIV.querySelector("#addtext_annotation") != null) {
data.annotation = JSON.parse(
globals.TEXT_DIV.querySelector("#addtext_annotation").value
);
}
// we replace \n with <br> so that line breaks are displayed properly on the graph
data.text = data.text.replace(/\n/g, "<br>");
console.log("Text data:", data);
} else if (popup_id == "csv") {
let trace_type = globals.CSV_DIV.querySelector("#csv_trace_type").value;
if (trace_type == "candlestick") {
data = {
x: globals.CSV_DIV.querySelector("#csv_x").value,
open: globals.CSV_DIV.querySelector("#csv_open").value,
high: globals.CSV_DIV.querySelector("#csv_high").value,
low: globals.CSV_DIV.querySelector("#csv_low").value,
close: globals.CSV_DIV.querySelector("#csv_close").value,
increasing: globals.CSV_DIV.querySelector("#csv_increasing").value,
decreasing: globals.CSV_DIV.querySelector("#csv_decreasing").value,
same_yaxis: globals.CSV_DIV.querySelector("#csv_same_yaxis").checked
? true
: false,
};
} else if (trace_type == "scatter") {
data = {
x: globals.CSV_DIV.querySelector("#csv_x").value,
y: globals.CSV_DIV.querySelector("#csv_y").value,
color: globals.CSV_DIV.querySelector("#csv_color").value,
percent_change: globals.CSV_DIV.querySelector("#csv_percent_change")
.checked
? true
: false,
same_yaxis: globals.CSV_DIV.querySelector("#csv_same_yaxis").checked
? true
: false,
};
} else if (trace_type == "bar") {
let orientation = globals.CSV_DIV.querySelector("#csv_bar_horizontal")
.checked
? "h"
: "v";
data = {
x: globals.CSV_DIV.querySelector("#csv_x").value,
y: globals.CSV_DIV.querySelector("#csv_y").value,
color: globals.CSV_DIV.querySelector("#csv_color").value,
same_yaxis: globals.CSV_DIV.querySelector("#csv_same_yaxis").checked
? true
: false,
orientation: orientation,
};
}
data.trace_type = trace_type;
data.name = globals.CSV_DIV.querySelector("#csv_name").value;
console.log("CSV data:", data);
data.file = globals.CSV_DIV.querySelector("#csv_file");
}
return data;
}
const layout_defaults = {
overlaying: "y",
side: "left",
tickfont: { size: 12 },
tickpadding: 5,
showgrid: false,
showline: false,
showticklabels: true,
showlegend: true,
zeroline: false,
anchor: "x",
type: "linear",
autorange: true,
};
function on_submit(popup_id, on_annotation = null) {
// popup_id is either 'title', 'text', or 'csv' (for now)
// and is used to determine which popup to get data from
let popup_data = get_popup_data(popup_id);
let gd = globals.CHART_DIV;
Plotly.update(gd, {}, { hovermode: "closest" });
if (popup_id == "text") {
if (!popup_data.text == "") {
if ("annotation" in popup_data) {
let current_text = popup_data.annotation.text;
let data = {
x: popup_data.annotation.x,
y: popup_data.annotation.y,
yref: popup_data.annotation.yref,
};
plot_text(data, popup_data, current_text);
return;
}
gd.on("plotly_clickannotation", function (eventData) {
console.log("plotly_clickannotation", eventData);
let annotation = eventData.annotation;
openPopup("popup_text");
get_popup(annotation, (popup_id = "text"));
if (on_annotation != null) {
let data = {
x: popup_data.annotation.x,
y: popup_data.annotation.y,
yref: popup_data.annotation.yref,
};
plot_text(data, popup_data, popup_data.annotation.text);
}
Plotly.update(gd, {}, { hovermode: "x" });
});
let clickHandler = function (eventData) {
console.log("plotly_click", eventData);
let x = eventData.points[0].x;
let yaxis = eventData.points[0].fullData.yaxis;
let y = 0;
// We need to check if the trace is a candlestick or not
// this is because the y value is stored in the high or low
if (eventData.points[0].y != undefined) {
y = eventData.points[0].y;
} else if (eventData.points[0].low != undefined) {
if (popup_data.yanchor == "below") {
y = eventData.points[0].low;
} else {
y = eventData.points[0].high;
}
}
let data = {
x: x,
y: y,
yref: yaxis,
};
plot_text(data, popup_data);
Plotly.update(gd, {}, { hovermode: "x" });
};
Plotly.update(gd, {}, { dragmode: "select" });
gd.on("plotly_click", clickHandler);
} else {
let textarea = globals.TEXT_DIV.querySelector("#addtext_textarea");
document.getElementById("popup_textarea_warning").style.display = "block";
textarea.style.border = "1px solid red";
textarea.focus();
globals.TEXT_DIV.style.display = "inline-block";
return;
}
} else if (popup_id == "title") {
document.getElementById("title").innerHTML = popup_data.title;
globals.title = popup_data.title;
let to_update = {};
Object.keys(popup_data).forEach(function (key) {
if (key != "title") {
to_update[key + ".title"] = popup_data[key];
}
});
console.log(to_update);
Plotly.update(gd, {}, to_update);
} else if (popup_id == "csv") {
console.log("got popup file");
let popup_file = popup_data.file;
if (popup_file.files.length > 0) {
console.log("file selected");
let file = popup_file.files[0];
let popup_file_reader = new FileReader();
popup_file_reader.onload = function (e) {
let lines = e.target.result
.split("\n")
.map((x) => x.replace(/\r/g, ""));
let data = [];
let headers = lines[0].split(",");
let trace = {};
let new_yaxis = false;
for (let i = 1; i < lines.length; i++) {
let obj = {};
let currentline = lines[i].split(",");
for (let j = 0; j < headers.length; j++) {
obj[headers[j]] = currentline[j];
}
data.push(obj);
}
// We get main plotly trace to get the x/y axis
let main_trace = gd.data[0];
// We check how many yaxis have ticklabels on the left
let left_yaxis_ticks = Object.keys(gd.layout)
.filter((k) => k.startsWith("yaxis"))
.map((k) => gd.layout[k])
.filter(
(yaxis) =>
yaxis.side == "left" &&
(yaxis.overlaying == "y" ||
(yaxis.fixedrange != undefined && yaxis.fixedrange == true))
).length;
// Multiply by 5 to get the xshift for cmd source text
let add_xshift = Math.min(left_yaxis_ticks * 5, 30);
// We set showlegend's to true
main_trace.showlegend = true;
gd.layout.showlegend = true;
// Just in case xaxis/yaxis is not defined
if (main_trace.xaxis == undefined) {
main_trace.xaxis = "x";
}
if (main_trace.yaxis == undefined) {
main_trace.yaxis = "y";
}
// Set the yaxis id
let yaxis_id = main_trace.yaxis;
let yaxis;
// If we want to plot on a secondary yaxis
// we get the number of yaxis and add 1 to it
if (popup_data.same_yaxis == false) {
let yaxes = Object.keys(gd.layout)
.filter((k) => k.startsWith("yaxis"))
.map((k) => gd.layout[k]);
yaxis = `y${yaxes.length + 1}`;
yaxis_id = `yaxis${yaxes.length + 1}`;
console.log(`yaxis: ${yaxis} ${yaxis_id}`);
// If percent change is true we set the yaxis id
// in the globals so we can use it later
if (
globals.csv_yaxis_id == null &&
popup_data.percent_change == true
) {
globals.csv_yaxis_id = yaxis_id;
globals.csv_yaxis = yaxis;
}
} else {
// Plot on the same yaxis
yaxis = main_trace.yaxis.replace("yaxis", "y");
}
// We get the yaxis ticksuffix
let ticksuffix = left_yaxis_ticks > 0 ? " " : "";
if (globals.percent_yaxis_added || globals.added_traces.length > 0) {
ticksuffix = " ".repeat(left_yaxis_ticks);
}
// Bar trace
if (popup_data.trace_type == "bar") {
trace = {
x: data.map(function (row) {
return row[popup_data.x];
}),
y: data.map(function (row) {
return row[popup_data.y];
}),
type: popup_data.trace_type,
name: popup_data.name,
marker: { color: popup_data.color, opacity: 0.7 },
orientation: popup_data.orientation,
showlegend: true,
yaxis: yaxis,
};
}
// Candlestick trace
if (popup_data.trace_type == "candlestick") {
trace = {
x: data.map(function (row) {
return row[popup_data.x];
}),
open: data.map(function (row) {
return row[popup_data.open];
}),
high: data.map(function (row) {
return row[popup_data.high];
}),
low: data.map(function (row) {
return row[popup_data.low];
}),
close: data.map(function (row) {
return row[popup_data.close];
}),
type: popup_data.trace_type,
name: popup_data.name,
increasing: {
line: { color: popup_data.increasing },
fillcolor: popup_data.increasing,
},
decreasing: {
line: { color: popup_data.decreasing },
fillcolor: popup_data.decreasing,
},
showlegend: true,
yaxis: yaxis,
};
}
// Scatter trace
if (popup_data.trace_type == "scatter") {
// We get the first non null value
orginal_data = data;
let non_null = data.findIndex(
(x) => x[popup_data.y] != null && x[popup_data.y] != 0
);
console.log(`non_null: ${non_null} ${data[non_null][popup_data.y]}`);
trace = {
x: data.map(function (row) {
return row[popup_data.x];
}),
y: data.map(function (row) {
if (popup_data.percent_change) {
return (
(row[popup_data.y] - data[non_null][popup_data.y]) /
data[non_null][popup_data.y]
);
} else {
return row[popup_data.y];
}
}),
type: popup_data.trace_type,
mode: "lines",
name: popup_data.name,
line: { color: popup_data.color },
customdata: data.map(function (row) {
return row[popup_data.y];
}),
hovertemplate: "%{customdata}<extra></extra>",
showlegend: true,
connectgaps: true,
xaxis: main_trace.xaxis,
yaxis: popup_data.percent_change ? globals.csv_yaxis : yaxis,
};
// For the percent change we add a new yaxis
// if it doesn't exist
if (
!globals.percent_yaxis_added &&
popup_data.percent_change == true &&
popup_data.same_yaxis == false
) {
gd.layout[yaxis_id] = {
...layout_defaults,
title: {
text: "% Change",
font: {
size: 14,
},
standoff: 0,
},
ticksuffix: ticksuffix,
tickformat: ".0%",
};
new_yaxis = true;
globals.percent_yaxis_added = true;
if (globals.cmd_src_idx != null) {
let xshift = gd.layout.annotations[globals.cmd_src_idx].xshift;
xshift -= left_yaxis_ticks > 0 ? 50 + add_xshift : 35;
// just in case we have a lot of yaxis
xshift += xshift < -320 ? 10 + 2 * left_yaxis_ticks : 0;
gd.layout.annotations[globals.cmd_src_idx].xshift = xshift;
}
gd.layout.margin.l += left_yaxis_ticks > 0 ? 50 + add_xshift : 45;
}
}
// New yaxis and not percent change
if (!popup_data.percent_change && popup_data.same_yaxis == false) {
gd.layout[yaxis_id] = {
...layout_defaults,
title: {
text: popup_data.name,
font: {
size: 14,
},
standoff: 0,
},
ticksuffix: ticksuffix,
layer: "below traces",
};
new_yaxis = true;
if (globals.cmd_src_idx != null) {
let xshift = gd.layout.annotations[globals.cmd_src_idx].xshift;
xshift -= left_yaxis_ticks > 0 ? 40 + add_xshift : 40;
// just in case we have a lot of yaxis
xshift += xshift < -320 ? 10 + 2 * left_yaxis_ticks : 0;
gd.layout.annotations[globals.cmd_src_idx].xshift = xshift;
}
gd.layout.margin.l += left_yaxis_ticks > 0 ? 50 : 45;
}
console.log("trace: ", trace);
globals.added_traces.push(trace.name);
Plotly.addTraces(gd, trace);
Plotly.react(gd, gd.data, gd.layout);
if (new_yaxis) {
gd.layout[yaxis_id].automargin = false;
Plotly.react(gd, gd.data, gd.layout);
}
// We empty the fields and innerHTML after the plot is made
globals.CSV_DIV.querySelector("#csv_colors").innerHTML = "";
globals.CSV_DIV.querySelector("#csv_columns").innerHTML = "";
globals.CSV_DIV.querySelector("#csv_plot_yaxis_options").style.display =
"none";
globals.CSV_DIV.querySelectorAll("input").forEach(function (input) {
input.value = "";
});
globals.CSV_DIV.querySelectorAll("textarea").forEach(function (input) {
input.value = "";
});
};
popup_file_reader.readAsText(file);
}
}
closePopup();
}
function on_cancel() {
// We close any popup that is open
closePopup();
}
function on_delete(popup_id) {
let gd = globals.CHART_DIV;
closePopup();
if (popup_id == "text") {
let annotation = JSON.parse(
globals.TEXT_DIV.querySelector("#addtext_annotation").value
);
gd.layout.annotations.splice(gd.layout.annotations.indexOf(annotation), 1);
gd.layout.dragmode = "pan";
Plotly.react(gd, gd.data, gd.layout);
globals.TEXT_DIV.style.display = "none";
}
}
function closePopup() {
// We set the display to none to hide the popup overlay
var popup = document.getElementById("popup_overlay");
popup.style.display = "none";
}
function openPopup(popup_id, popup_data = null) {
// We make sure to close the incase there is a popup already open
closePopup();
var overlay = document.getElementById("popup_overlay");
// We get the popup div and set the display to block to show the popup
overlay.style.display = "block";
get_popup(popup_data, popup_id);
}