mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-02 15:32:04 +00:00
init
This commit is contained in:
141
frontend-components/plotly/src/components/AutoScaling.tsx
vendored
Normal file
141
frontend-components/plotly/src/components/AutoScaling.tsx
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
//@ts-nocheck
|
||||
import { Figure } from "react-plotly.js";
|
||||
|
||||
export default async function autoScaling(
|
||||
eventdata: Readonly<Plotly.PlotRelayoutEvent>,
|
||||
graphs: Figure
|
||||
) {
|
||||
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];
|
||||
}
|
||||
});
|
||||
|
||||
return to_update;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`Error in AutoScaling: ${e}`);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
95
frontend-components/plotly/src/components/ChangeColor.tsx
vendored
Normal file
95
frontend-components/plotly/src/components/ChangeColor.tsx
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
//@ts-nocheck
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function ChangeColor({
|
||||
open,
|
||||
onColorChange,
|
||||
}: {
|
||||
open: boolean;
|
||||
onColorChange: (color: string) => void;
|
||||
}) {
|
||||
const [active, setActive] = useState(false);
|
||||
|
||||
function onChangeColor(color) {
|
||||
onColorChange(color);
|
||||
}
|
||||
|
||||
if (open && !active) {
|
||||
setActive(true);
|
||||
}
|
||||
if (!open && active) {
|
||||
setActive(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (active) {
|
||||
let color_picker = document.getElementById("changecolor");
|
||||
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 {
|
||||
document.getElementById("changecolor").style.display = "none";
|
||||
}
|
||||
}, [active]);
|
||||
|
||||
return (
|
||||
<div id="changecolor">
|
||||
<div id="changecolor_header">
|
||||
<input
|
||||
type="color"
|
||||
id="picked_color"
|
||||
value="#00ACFF"
|
||||
onChange={(e) => {
|
||||
let color = e.target.value;
|
||||
onChangeColor(color);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
469
frontend-components/plotly/src/components/Chart.tsx
vendored
Normal file
469
frontend-components/plotly/src/components/Chart.tsx
vendored
Normal file
@@ -0,0 +1,469 @@
|
||||
// @ts-nocheck
|
||||
import * as Plotly from "plotly.js-dist-min";
|
||||
import { usePostHog } from "posthog-js/react";
|
||||
import createPlotlyComponent from "react-plotly.js/factory";
|
||||
import { useState, useEffect } from "react";
|
||||
import { ICONS, DARK_CHARTS_TEMPLATE, LIGHT_CHARTS_TEMPLATE } from "./Config";
|
||||
import OverlayChartDialog from "./Dialogs/OverlayChartDialog";
|
||||
import TitleChartDialog from "./Dialogs/TitleChartDialog";
|
||||
import TextChartDialog from "./Dialogs/TextChartDialog";
|
||||
import { Icons as PlotlyIcons } from "plotly.js-dist-min";
|
||||
import { init_annotation } from "../utils/addAnnotation";
|
||||
import { PlotConfig, hideModebar } from "./PlotlyConfig";
|
||||
import autoScaling from "./AutoScaling";
|
||||
import { non_blocking, saveImage } from "../utils/utils";
|
||||
import ResizeHandler from "./ResizeHandler";
|
||||
import ChangeColor from "./ChangeColor";
|
||||
|
||||
const Plot = createPlotlyComponent(Plotly);
|
||||
|
||||
export default function Chart({
|
||||
json,
|
||||
date,
|
||||
cmd,
|
||||
title,
|
||||
info,
|
||||
}: {
|
||||
// @ts-ignore
|
||||
json: Figure;
|
||||
date: Date;
|
||||
cmd: string;
|
||||
title: string;
|
||||
info?: any;
|
||||
}) {
|
||||
const posthog = usePostHog();
|
||||
|
||||
if (info)
|
||||
useEffect(() => {
|
||||
if (posthog) posthog.capture("chart", info);
|
||||
}, [posthog]);
|
||||
|
||||
delete json.layout.width;
|
||||
delete json.layout.height;
|
||||
json.layout.title.text = "";
|
||||
|
||||
const [barButtons, setModeBarButtons] = useState({});
|
||||
const [LogYaxis, setLogYaxis] = useState(false);
|
||||
const [chartTitle, setChartTitle] = useState(title);
|
||||
const [axesTitles, setAxesTitles] = useState({});
|
||||
const [plotLoaded, setPlotLoaded] = useState(false);
|
||||
const [modal, setModal] = useState({ modal: "" });
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [plotDiv, setPlotDiv] = useState(null);
|
||||
const [Volume, setVolume] = useState({ old_nticks: {} });
|
||||
|
||||
const [plotData, setPlotData] = useState(json);
|
||||
const [annotations, setAnnotations] = useState([]);
|
||||
const [changeTheme, setChangeTheme] = useState(false);
|
||||
const [darkMode, setDarkMode] = useState(true);
|
||||
const [autoScale, setAutoScaling] = useState(false);
|
||||
const [changeColor, setChangeColor] = useState(false);
|
||||
const [colorActive, setColorActive] = useState(false);
|
||||
const [onAnnotationClick, setOnAnnotationClick] = useState({});
|
||||
|
||||
const onClose = () => setModal({ modal: "" });
|
||||
|
||||
// @ts-ignore
|
||||
function onDeleteAnnotation(annotation) {
|
||||
console.log("onDeleteAnnotation", annotation);
|
||||
const index = plotData?.layout?.annotations?.findIndex(
|
||||
(a: any) => a.text === annotation.text
|
||||
);
|
||||
console.log("index", index);
|
||||
if (index > -1) {
|
||||
plotData?.layout?.annotations?.splice(index, 1);
|
||||
setPlotData({ ...plotData });
|
||||
setAnnotations(plotData?.layout?.annotations);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function onAddAnnotation(data) {
|
||||
init_annotation({
|
||||
plotData,
|
||||
popupData: data,
|
||||
setPlotData,
|
||||
setModal,
|
||||
setOnAnnotationClick,
|
||||
setAnnotations,
|
||||
onAnnotationClick,
|
||||
annotations,
|
||||
plotDiv,
|
||||
});
|
||||
}
|
||||
|
||||
if (axesTitles && Object.keys(axesTitles).length > 0) {
|
||||
Object.keys(axesTitles).forEach((k) => {
|
||||
plotData.layout[k].title = {
|
||||
...(plotData.layout[k].title || {}),
|
||||
text: axesTitles[k],
|
||||
};
|
||||
plotData.layout[k].showticklabels = true;
|
||||
});
|
||||
setAxesTitles({});
|
||||
}
|
||||
|
||||
function onChangeColor(color) {
|
||||
// updates the color of the last added shape
|
||||
// this function is called when the color picker is used
|
||||
// if there are no shapes, we remove the color picker
|
||||
let shapes = plotDiv.layout.shapes;
|
||||
if (!shapes || shapes.length == 0) {
|
||||
return;
|
||||
}
|
||||
// we change last added shape color
|
||||
let last_shape = shapes[shapes.length - 1];
|
||||
last_shape.line.color = color;
|
||||
Plotly.update(plotDiv, {}, { shapes: shapes });
|
||||
}
|
||||
|
||||
function button_pressed(title, active = false) {
|
||||
// changes the style of the button when it is pressed
|
||||
// title is the title of the button
|
||||
// active is true if the button is active, false otherwise
|
||||
|
||||
let button =
|
||||
barButtons[title] || document.querySelector(`[data-title="${title}"]`);
|
||||
if (!active) {
|
||||
button.style.border = "1px solid rgba(0, 151, 222, 1.0)";
|
||||
button.style.borderRadius = "5px";
|
||||
button.style.borderpadding = "5px";
|
||||
button.style.boxShadow = "0 0 5px rgba(0, 151, 222, 1.0)";
|
||||
} else {
|
||||
button.style.border = "transparent";
|
||||
button.style.boxShadow = "none";
|
||||
}
|
||||
setModeBarButtons({ ...barButtons, [title]: button });
|
||||
}
|
||||
|
||||
function autoscaleButton() {
|
||||
// We need to check if the button is active or not
|
||||
let title = "Auto Scale (Ctrl+Shift+A)";
|
||||
let button =
|
||||
barButtons[title] || document.querySelector(`[data-title="${title}"]`);
|
||||
let active = true;
|
||||
|
||||
if (button.style.border == "transparent") {
|
||||
active = false;
|
||||
plotDiv.on(
|
||||
"plotly_relayout",
|
||||
non_blocking(async function (eventdata) {
|
||||
if (eventdata["xaxis.range[0]"] == undefined) return;
|
||||
|
||||
let to_update = await autoScaling(eventdata, plotDiv);
|
||||
Plotly.update(plotDiv, {}, to_update);
|
||||
}, 100)
|
||||
);
|
||||
}
|
||||
// If the button isn't active, we remove the listener so
|
||||
// the graphs don't autoscale anymore
|
||||
else plotDiv.removeAllListeners("plotly_relayout");
|
||||
|
||||
button_pressed(title, active);
|
||||
}
|
||||
|
||||
function changecolorButton() {
|
||||
// We need to check if the button is active or not
|
||||
let title = "Edit Color (Ctrl+E)";
|
||||
let button =
|
||||
barButtons[title] || document.querySelector(`[data-title="${title}"]`);
|
||||
let active = true;
|
||||
|
||||
if (button.style.border == "transparent") {
|
||||
active = false;
|
||||
}
|
||||
|
||||
setColorActive(!active);
|
||||
button_pressed(title, active);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (autoScale) {
|
||||
let scale = !autoScale;
|
||||
console.log("activateAutoScale", scale);
|
||||
autoscaleButton();
|
||||
setAutoScaling(false);
|
||||
}
|
||||
}, [autoScale]);
|
||||
|
||||
useEffect(() => {
|
||||
if (changeColor) {
|
||||
changecolorButton();
|
||||
setChangeColor(false);
|
||||
}
|
||||
}, [changeColor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (changeTheme) {
|
||||
try {
|
||||
console.log("changeTheme", changeTheme);
|
||||
const TRACES = plotData?.data.filter((trace) =>
|
||||
trace?.name?.startsWith("Volume")
|
||||
);
|
||||
let darkmode = !darkMode;
|
||||
|
||||
window.document.body.style.backgroundColor = darkmode ? "#000" : "#fff";
|
||||
|
||||
plotData.layout.font = {
|
||||
...(plotData.layout.font || {}),
|
||||
color: darkmode ? "#fff" : "#000",
|
||||
};
|
||||
|
||||
const changeIcon = darkmode ? ICONS.sunIcon : ICONS.moonIcon;
|
||||
|
||||
document
|
||||
.querySelector('[data-title="Change Theme"]')
|
||||
.getElementsByTagName("path")[0]
|
||||
.setAttribute("d", changeIcon.path);
|
||||
|
||||
document
|
||||
.querySelector('[data-title="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 = trace.marker.color.map((color) => {
|
||||
return volumeColors[color] || color;
|
||||
});
|
||||
});
|
||||
plotData.layout.template = darkmode
|
||||
? DARK_CHARTS_TEMPLATE
|
||||
: LIGHT_CHARTS_TEMPLATE;
|
||||
setPlotData({ ...plotData });
|
||||
Plotly.react(plotDiv, plotData.data, plotData.layout);
|
||||
setDarkMode(darkmode);
|
||||
setChangeTheme(false);
|
||||
} catch (e) {
|
||||
console.log("error", e);
|
||||
}
|
||||
}
|
||||
}, [changeTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (plotLoaded) {
|
||||
setDarkMode(true);
|
||||
setAutoScaling(false);
|
||||
const captureButtons = [
|
||||
"Download CSV",
|
||||
"Download Chart as Image",
|
||||
"Overlay chart from CSV",
|
||||
"Add Text",
|
||||
"Change Titles",
|
||||
"Auto Scale (Ctrl+Shift+A)",
|
||||
"Reset Axes",
|
||||
];
|
||||
const autoscale = document.querySelector('[data-title="Autoscale"]');
|
||||
if (autoscale) {
|
||||
autoscale
|
||||
.getElementsByTagName("path")[0]
|
||||
.setAttribute("d", PlotlyIcons.home.path);
|
||||
autoscale.setAttribute("data-title", "Reset Axes");
|
||||
}
|
||||
|
||||
window.MODEBAR = document.getElementsByClassName(
|
||||
"modebar-container"
|
||||
)[0] as HTMLElement;
|
||||
const modeBarButtons = window.MODEBAR.getElementsByClassName(
|
||||
"modebar-btn"
|
||||
) as HTMLCollectionOf<HTMLElement>;
|
||||
|
||||
window.MODEBAR.style.cssText = `${window.MODEBAR.style.cssText}; display:flex;`;
|
||||
|
||||
if (modeBarButtons) {
|
||||
let barbuttons: any = {};
|
||||
for (let i = 0; i < modeBarButtons.length; i++) {
|
||||
let btn = modeBarButtons[i];
|
||||
if (captureButtons.includes(btn.getAttribute("data-title"))) {
|
||||
btn.classList.add("ph-capture");
|
||||
}
|
||||
btn.style.border = "transparent";
|
||||
barbuttons[btn.getAttribute("data-title")] = btn;
|
||||
}
|
||||
setModeBarButtons(barbuttons);
|
||||
}
|
||||
|
||||
if (plotData.layout.yaxis.type != undefined) {
|
||||
if (plotData.layout.yaxis.type == "log" && !LogYaxis) {
|
||||
console.log("yaxis.type changed to log");
|
||||
setLogYaxis(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(plotDiv, layout_update);
|
||||
}
|
||||
if (plotData.layout.yaxis.type == "linear" && LogYaxis) {
|
||||
console.log("yaxis.type changed to linear");
|
||||
setLogYaxis(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(plotDiv, 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))
|
||||
non_blocking(async function () {
|
||||
await hideModebar();
|
||||
await saveImage("MainChart", filename.split(".")[0], extension);
|
||||
}, 2)();
|
||||
}
|
||||
|
||||
window.addEventListener("resize", async function () {
|
||||
let update = await ResizeHandler({ plotData, Volume });
|
||||
let layout_update = update.layout_update;
|
||||
let newPlotData = update.plotData;
|
||||
let volume_update = update.volume_update;
|
||||
|
||||
if (Object.keys(layout_update).length > 0) {
|
||||
setPlotData(newPlotData);
|
||||
setVolume(volume_update);
|
||||
Plotly.relayout(plotDiv, layout_update);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [plotLoaded]);
|
||||
|
||||
return (
|
||||
<div className="relative h-full">
|
||||
{loading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center z-[100]">
|
||||
<svg
|
||||
className="animate-spin h-20 w-20 text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8v8z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
<OverlayChartDialog
|
||||
addOverlay={(overlay) => {
|
||||
console.log(overlay);
|
||||
plotData.layout.showlegend = true;
|
||||
setPlotData(overlay);
|
||||
setPlotLoaded(false);
|
||||
}}
|
||||
plotlyData={plotData}
|
||||
setLoading={setLoading}
|
||||
open={modal.modal === "overlayChart"}
|
||||
close={onClose}
|
||||
/>
|
||||
<TitleChartDialog
|
||||
updateTitle={(title) => setChartTitle(title)}
|
||||
updateAxesTitles={(axesTitles) => setAxesTitles(axesTitles)}
|
||||
defaultTitle={chartTitle}
|
||||
plotlyData={plotData}
|
||||
open={modal.modal === "titleDialog"}
|
||||
close={onClose}
|
||||
/>
|
||||
<TextChartDialog
|
||||
popupData={modal.modal === "textDialog" ? modal?.data : null}
|
||||
open={modal.modal === "textDialog"}
|
||||
close={onClose}
|
||||
addAnnotation={(data) => onAddAnnotation(data)}
|
||||
deleteAnnotation={(data) => onDeleteAnnotation(data)}
|
||||
/>
|
||||
<ChangeColor open={colorActive} onColorChange={onChangeColor} />
|
||||
|
||||
<div className="relative h-full" id="MainChart">
|
||||
<div className="_header relative gap-4 py-2 text-center text-xs flex items-center justify-between px-4 text-white">
|
||||
<div className="w-1/3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="40"
|
||||
fill="none"
|
||||
viewBox="0 0 64 40"
|
||||
>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M61.283 3.965H33.608v27.757h25.699V19.826H37.561v-3.965H63.26V3.965h-1.977zM39.538 23.792h15.815v3.965H37.561v-3.965h1.977zM59.306 9.913v1.983H37.561V7.931h21.745v1.982zM33.606 0h-3.954v3.965H33.606V0zM25.7 3.966H0V15.86h25.7v3.965H3.953v11.896h25.7V3.966h-3.955zm0 21.808v1.983H7.907v-3.965h17.791v1.982zm0-15.86v1.982H3.953V7.931h21.745v1.982zM37.039 35.693v2.952l-.246-.246-.245-.245-.245-.247-.245-.246-.246-.246-.245-.245-.245-.247-.247-.246-.245-.246-.245-.246-.245-.246-.246-.246h-.49v3.936h.49v-3.198l.246.246.245.246.245.246.245.246.246.246.246.246.245.247.246.245.245.246.245.247.245.246.246.245.245.246h.245v-3.936h-.49zM44.938 37.17h-.491v-1.477h-2.944v3.937h3.93v-2.46h-.495zm-2.944-.246v-.739h1.962v.984h-1.962v-.245zm2.944.984v1.23h-2.944V37.66h2.944v.247zM52.835 37.17h-.49v-1.477h-2.946v3.937h3.925v-2.46h-.489zm-2.944-.246v-.739h1.963v.984h-1.965l.002-.245zm2.944.984v1.23H49.89V37.66h2.946v.247zM29.174 35.693H25.739v3.936H29.663v-.491H26.229v-.984h2.943v-.493H26.229v-1.476h3.434v-.492h-.489zM13.37 35.693H9.934v3.937h3.925v-3.937h-.49zm0 .738v2.709h-2.945v-2.955h2.943l.001.246zM21.276 35.693h-3.435v3.937h.491v-1.476h3.434v-2.461h-.49zm0 .738v1.23h-2.944v-1.476h2.944v.246z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<p className="font-bold w-1/3 flex flex-col gap-0.5 items-center">
|
||||
{chartTitle}
|
||||
{/* {source && (
|
||||
<span className="font-normal text-[10px]">{`[${source}]`}</span>
|
||||
)} */}
|
||||
</p>
|
||||
<p className="w-1/3 text-right text-xs">
|
||||
{new Intl.DateTimeFormat("en-GB", {
|
||||
dateStyle: "full",
|
||||
timeStyle: "long",
|
||||
})
|
||||
.format(date)
|
||||
.replace(/:\d\d /, " ")}
|
||||
<br />
|
||||
<span className="text-grey-400">{cmd}</span>
|
||||
</p>
|
||||
{/* {source && typeof source === "string" && source.includes("*") && (
|
||||
<p className="text-[8px] absolute bottom-0 right-4">
|
||||
*not affiliated
|
||||
</p>
|
||||
)} */}
|
||||
</div>
|
||||
<div className="w-full h-[calc(100%-50px)] sm:pb-12">
|
||||
<Plot
|
||||
onInitialized={() => {
|
||||
if (!plotDiv) {
|
||||
const plot = document.getElementById("plotlyChart");
|
||||
console.log("plot", plot);
|
||||
if (plot) setPlotDiv(plot);
|
||||
}
|
||||
if (!plotLoaded) setPlotLoaded(true);
|
||||
}}
|
||||
className="w-full h-full"
|
||||
divId="plotlyChart"
|
||||
data={plotData.data}
|
||||
layout={plotData.layout}
|
||||
config={PlotConfig({
|
||||
setModal: setModal,
|
||||
changeTheme: setChangeTheme,
|
||||
autoScaling: setAutoScaling,
|
||||
Loading: setLoading,
|
||||
changeColor: setChangeColor,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
756
frontend-components/plotly/src/components/Config.tsx
vendored
Normal file
756
frontend-components/plotly/src/components/Config.tsx
vendored
Normal file
@@ -0,0 +1,756 @@
|
||||
export const ICONS = {
|
||||
sunIcon: {
|
||||
viewBox: "0 0 16 16",
|
||||
width: 16,
|
||||
height: 16,
|
||||
path: "M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z",
|
||||
},
|
||||
moonIcon: {
|
||||
viewBox: "0 0 25 25",
|
||||
width: 25,
|
||||
height: 25,
|
||||
path: "M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z",
|
||||
},
|
||||
plotCsv: {
|
||||
width: 900,
|
||||
height: 900,
|
||||
path: "M170.666667 106.666667l0.192 736H906.666667v64H149.546667c-23.552 0-42.666667-19.093333-42.666667-42.666667L106.666667 106.666667h64z m686.506666 454.144l13.653334 16.362666a21.333333 21.333333 0 0 1-2.666667 30.058667l-171.157333 143.146667a21.333333 21.333333 0 0 1-21.546667 3.477333l-229.973333-91.285333-113.834667 94.997333a21.333333 21.333333 0 0 1-30.037333-2.709333l-13.653334-16.362667a21.333333 21.333333 0 0 1 2.688-30.058667l133.312-111.274666a21.333333 21.333333 0 0 1 21.546667-3.456l229.930667 91.264 151.68-126.826667a21.333333 21.333333 0 0 1 30.037333 2.666667z m-1.621333-417.962667l16.896 13.013333a21.333333 21.333333 0 0 1 3.925333 29.888L685.802667 433.706667a21.333333 21.333333 0 0 1-20.202667 8.085333l-226.794667-35.413333-150.186666 222.357333a21.333333 21.333333 0 0 1-27.477334 7.018667l-2.133333-1.28-17.685333-11.946667a21.333333 21.333333 0 0 1-5.738667-29.610667l165.354667-244.821333a21.333333 21.333333 0 0 1 20.992-9.130667L650.453333 374.613333l175.146667-227.882666a21.333333 21.333333 0 0 1 29.930667-3.904z",
|
||||
},
|
||||
addText: {
|
||||
path: "M896 928H128a32 32 0 0 1-32-32V128a32 32 0 0 1 32-32h768a32 32 0 0 1 32 32v768a32 32 0 0 1-32 32z m-736-64h704v-704h-704z M704 352H320a32 32 0 0 1 0-64h384a32 32 0 0 1 0 64z M512 736a32 32 0 0 1-32-32V320a32 32 0 0 1 64 0v384a32 32 0 0 1-32 32z",
|
||||
width: 950,
|
||||
height: 950,
|
||||
},
|
||||
changeTitle: {
|
||||
path: "M122.368 165.888h778.24c-9.216 0-16.384-7.168-16.384-16.384v713.728c0-9.216 7.168-16.384 16.384-16.384h-778.24c9.216 0 16.384 7.168 16.384 16.384V150.016c0 8.192-6.656 15.872-16.384 15.872z m-32.768 684.544c0 26.112 20.992 47.104 47.104 47.104h750.08c26.112 0 47.104-20.992 47.104-47.104V162.304c0-26.112-20.992-47.104-47.104-47.104H136.704c-26.112 0-47.104 20.992-47.104 47.104v688.128z M244.736 656.896h534.016v62.464H244.736z M373.76 358.4H307.2v219.136h-45.568V358.4H192v-41.472H373.76V358.4zM403.968 316.928h44.032v50.176h-44.032v-50.176z m0 67.072h44.032v194.048h-44.032V384zM576.512 541.184l8.704 31.744c-13.312 5.12-26.624 8.192-38.912 8.704-32.768 1.024-48.64-15.36-48.128-48.128V422.912h-26.624V384h26.624v-46.592l44.032-21.504V384h36.352v38.912h-36.352V532.48c-1.024 10.24 3.072 14.848 11.264 13.824 5.12 0 12.8-1.536 23.04-5.12zM619.008 316.928h44.032v260.608h-44.032V316.928zM813.056 509.952l41.472 12.8c-11.776 40.96-37.888 61.44-78.336 60.416-52.736-1.536-80.384-34.304-81.92-98.304 2.56-67.072 29.696-102.4 81.92-105.984 52.224 1.536 78.336 36.864 79.36 105.984v13.824h-117.248c3.584 30.208 15.872 45.568 37.888 46.592 19.968 0.512 32.256-11.264 36.864-35.328z m-72.704-51.712h70.656c-1.024-25.088-12.288-38.4-33.792-38.912-21.504 0.512-33.792 13.824-36.864 38.912z",
|
||||
width: 920,
|
||||
height: 900,
|
||||
},
|
||||
changeColor: {
|
||||
path: "M8 3C5.79 3 4 4.79 4 7V14C4 15.1 4.9 16 6 16H9V20C9 21.1 9.9 22 11 22H13C14.1 22 15 21.1 15 20V16H18C19.1 16 20 15.1 20 14V3H8M8 5H12V7H14V5H15V9H17V5H18V10H6V7C6 5.9 6.9 5 8 5M6 14V12H18V14H6Z",
|
||||
width: 22,
|
||||
height: 22,
|
||||
},
|
||||
uploadImage: {
|
||||
path: "M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5",
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
},
|
||||
downloadCsv: {
|
||||
path: `M486.2,196.121h-13.164V132.59c0-0.399-0.064-0.795-0.116-1.2c-0.021-2.52-0.824-5-2.551-6.96L364.656,3.677
|
||||
c-0.031-0.034-0.064-0.044-0.085-0.075c-0.629-0.707-1.364-1.292-2.141-1.796c-0.231-0.157-0.462-0.286-0.704-0.419
|
||||
c-0.672-0.365-1.386-0.672-2.121-0.893c-0.199-0.052-0.377-0.134-0.576-0.188C358.229,0.118,357.4,0,356.562,0H96.757
|
||||
C84.893,0,75.256,9.649,75.256,21.502v174.613H62.093c-16.972,0-30.733,13.756-30.733,30.73v159.81
|
||||
c0,16.966,13.761,30.736,30.733,30.736h13.163V526.79c0,11.854,9.637,21.501,21.501,21.501h354.777
|
||||
c11.853,0,21.502-9.647,21.502-21.501V417.392H486.2c16.966,0,30.729-13.764,30.729-30.731v-159.81
|
||||
C516.93,209.872,503.166,196.121,486.2,196.121z M96.757,21.502h249.053v110.006c0,5.94,4.818,10.751,10.751,10.751h94.973v53.861
|
||||
H96.757V21.502z M258.618,313.18c-26.68-9.291-44.063-24.053-44.063-47.389c0-27.404,22.861-48.368,60.733-48.368
|
||||
c18.107,0,31.447,3.811,40.968,8.107l-8.09,29.3c-6.43-3.107-17.862-7.632-33.59-7.632c-15.717,0-23.339,7.149-23.339,15.485
|
||||
c0,10.247,9.047,14.769,29.78,22.632c28.341,10.479,41.681,25.239,41.681,47.874c0,26.909-20.721,49.786-64.792,49.786
|
||||
c-18.338,0-36.449-4.776-45.497-9.77l7.38-30.016c9.772,5.014,24.775,10.006,40.264,10.006c16.671,0,25.488-6.908,25.488-17.396
|
||||
C285.536,325.789,277.909,320.078,258.618,313.18z M69.474,302.692c0-54.781,39.074-85.269,87.654-85.269
|
||||
c18.822,0,33.113,3.811,39.549,7.149l-7.392,28.816c-7.38-3.084-17.632-5.939-30.491-5.939c-28.822,0-51.206,17.375-51.206,53.099
|
||||
c0,32.158,19.051,52.4,51.456,52.4c10.947,0,23.097-2.378,30.241-5.238l5.483,28.346c-6.672,3.34-21.674,6.919-41.208,6.919
|
||||
C98.06,382.976,69.474,348.424,69.474,302.692z M451.534,520.962H96.757v-103.57h354.777V520.962z M427.518,380.583h-42.399
|
||||
l-51.45-160.536h39.787l19.526,67.894c5.479,19.046,10.479,37.386,14.299,57.397h0.709c4.048-19.298,9.045-38.352,14.526-56.693
|
||||
l20.487-68.598h38.599L427.518,380.583z`,
|
||||
width: 550,
|
||||
height: 550,
|
||||
transform: "translate(4, 0)",
|
||||
},
|
||||
downloadImage: {
|
||||
path: "M22.71,6.29a1,1,0,0,0-1.42,0L20,7.59V2a1,1,0,0,0-2,0V7.59l-1.29-1.3a1,1,0,0,0-1.42,1.42l3,3a1,1,0,0,0,.33.21.94.94,0,0,0,.76,0,1,1,0,0,0,.33-.21l3-3A1,1,0,0,0,22.71,6.29ZM19,13a1,1,0,0,0-1,1v.38L16.52,12.9a2.79,2.79,0,0,0-3.93,0l-.7.7L9.41,11.12a2.85,2.85,0,0,0-3.93,0L4,12.6V7A1,1,0,0,1,5,6h8a1,1,0,0,0,0-2H5A3,3,0,0,0,2,7V19a3,3,0,0,0,3,3H17a3,3,0,0,0,3-3V14A1,1,0,0,0,19,13ZM5,20a1,1,0,0,1-1-1V15.43l2.9-2.9a.79.79,0,0,1,1.09,0l3.17,3.17,0,0L15.46,20Zm13-1a.89.89,0,0,1-.18.53L13.31,15l.7-.7a.77.77,0,0,1,1.1,0L18,17.21Z",
|
||||
width: 21,
|
||||
height: 21,
|
||||
transform: "translate(-2, -2)",
|
||||
},
|
||||
};
|
||||
|
||||
export const DARK_CHARTS_TEMPLATE = {
|
||||
data: {
|
||||
candlestick: [
|
||||
{
|
||||
decreasing: {
|
||||
fillcolor: "#e4003a",
|
||||
line: {
|
||||
color: "#e4003a",
|
||||
},
|
||||
},
|
||||
increasing: {
|
||||
fillcolor: "#00ACFF",
|
||||
line: {
|
||||
color: "#00ACFF",
|
||||
},
|
||||
},
|
||||
type: "candlestick",
|
||||
},
|
||||
],
|
||||
},
|
||||
layout: {
|
||||
annotationdefaults: {
|
||||
showarrow: false,
|
||||
},
|
||||
autotypenumbers: "strict",
|
||||
colorway: [
|
||||
"#ffed00",
|
||||
"#ef7d00",
|
||||
"#e4003a",
|
||||
"#c13246",
|
||||
"#822661",
|
||||
"#48277c",
|
||||
"#005ca9",
|
||||
"#00aaff",
|
||||
"#9b30d9",
|
||||
"#af005f",
|
||||
"#5f00af",
|
||||
"#af87ff",
|
||||
],
|
||||
dragmode: "pan",
|
||||
font: {
|
||||
family: "Fira Code",
|
||||
size: 18,
|
||||
},
|
||||
hoverlabel: {
|
||||
align: "left",
|
||||
},
|
||||
mapbox: {
|
||||
style: "dark",
|
||||
},
|
||||
hovermode: "x",
|
||||
legend: {
|
||||
bgcolor: "rgba(0, 0, 0, 0)",
|
||||
x: 0.01,
|
||||
xanchor: "left",
|
||||
y: 0.99,
|
||||
yanchor: "top",
|
||||
font: {
|
||||
size: 15,
|
||||
},
|
||||
},
|
||||
paper_bgcolor: "#000000",
|
||||
plot_bgcolor: "#000000",
|
||||
xaxis: {
|
||||
automargin: true,
|
||||
autorange: true,
|
||||
rangeslider: {
|
||||
visible: false,
|
||||
},
|
||||
showgrid: true,
|
||||
showline: true,
|
||||
tickfont: {
|
||||
size: 14,
|
||||
},
|
||||
zeroline: false,
|
||||
tick0: 1,
|
||||
title: {
|
||||
standoff: 20,
|
||||
},
|
||||
linecolor: "#F5EFF3",
|
||||
mirror: true,
|
||||
ticks: "outside",
|
||||
},
|
||||
yaxis: {
|
||||
anchor: "x",
|
||||
automargin: true,
|
||||
fixedrange: false,
|
||||
zeroline: false,
|
||||
showgrid: true,
|
||||
showline: true,
|
||||
side: "right",
|
||||
tick0: 0.5,
|
||||
title: {
|
||||
standoff: 20,
|
||||
},
|
||||
gridcolor: "#283442",
|
||||
linecolor: "#F5EFF3",
|
||||
mirror: true,
|
||||
ticks: "outside",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const LIGHT_CHARTS_TEMPLATE = {
|
||||
data: {
|
||||
barpolar: [
|
||||
{
|
||||
marker: {
|
||||
line: {
|
||||
color: "white",
|
||||
width: 0.5,
|
||||
},
|
||||
pattern: {
|
||||
fillmode: "overlay",
|
||||
size: 10,
|
||||
solidity: 0.2,
|
||||
},
|
||||
},
|
||||
type: "barpolar",
|
||||
},
|
||||
],
|
||||
bar: [
|
||||
{
|
||||
error_x: {
|
||||
color: "#2a3f5f",
|
||||
},
|
||||
error_y: {
|
||||
color: "#2a3f5f",
|
||||
},
|
||||
marker: {
|
||||
line: {
|
||||
color: "white",
|
||||
width: 0.5,
|
||||
},
|
||||
pattern: {
|
||||
fillmode: "overlay",
|
||||
size: 10,
|
||||
solidity: 0.2,
|
||||
},
|
||||
},
|
||||
type: "bar",
|
||||
},
|
||||
],
|
||||
carpet: [
|
||||
{
|
||||
aaxis: {
|
||||
endlinecolor: "#2a3f5f",
|
||||
gridcolor: "#C8D4E3",
|
||||
linecolor: "#C8D4E3",
|
||||
minorgridcolor: "#C8D4E3",
|
||||
startlinecolor: "#2a3f5f",
|
||||
},
|
||||
baxis: {
|
||||
endlinecolor: "#2a3f5f",
|
||||
gridcolor: "#C8D4E3",
|
||||
linecolor: "#C8D4E3",
|
||||
minorgridcolor: "#C8D4E3",
|
||||
startlinecolor: "#2a3f5f",
|
||||
},
|
||||
type: "carpet",
|
||||
},
|
||||
],
|
||||
choropleth: [
|
||||
{
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
type: "choropleth",
|
||||
},
|
||||
],
|
||||
contourcarpet: [
|
||||
{
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
type: "contourcarpet",
|
||||
},
|
||||
],
|
||||
contour: [
|
||||
{
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
colorscale: [
|
||||
[0.0, "#0d0887"],
|
||||
[0.1111111111111111, "#46039f"],
|
||||
[0.2222222222222222, "#7201a8"],
|
||||
[0.3333333333333333, "#9c179e"],
|
||||
[0.4444444444444444, "#bd3786"],
|
||||
[0.5555555555555556, "#d8576b"],
|
||||
[0.6666666666666666, "#ed7953"],
|
||||
[0.7777777777777778, "#fb9f3a"],
|
||||
[0.8888888888888888, "#fdca26"],
|
||||
[1.0, "#f0f921"],
|
||||
],
|
||||
type: "contour",
|
||||
},
|
||||
],
|
||||
heatmapgl: [
|
||||
{
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
colorscale: [
|
||||
[0.0, "#0d0887"],
|
||||
[0.1111111111111111, "#46039f"],
|
||||
[0.2222222222222222, "#7201a8"],
|
||||
[0.3333333333333333, "#9c179e"],
|
||||
[0.4444444444444444, "#bd3786"],
|
||||
[0.5555555555555556, "#d8576b"],
|
||||
[0.6666666666666666, "#ed7953"],
|
||||
[0.7777777777777778, "#fb9f3a"],
|
||||
[0.8888888888888888, "#fdca26"],
|
||||
[1.0, "#f0f921"],
|
||||
],
|
||||
type: "heatmapgl",
|
||||
},
|
||||
],
|
||||
heatmap: [
|
||||
{
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
colorscale: [
|
||||
[0.0, "#0d0887"],
|
||||
[0.1111111111111111, "#46039f"],
|
||||
[0.2222222222222222, "#7201a8"],
|
||||
[0.3333333333333333, "#9c179e"],
|
||||
[0.4444444444444444, "#bd3786"],
|
||||
[0.5555555555555556, "#d8576b"],
|
||||
[0.6666666666666666, "#ed7953"],
|
||||
[0.7777777777777778, "#fb9f3a"],
|
||||
[0.8888888888888888, "#fdca26"],
|
||||
[1.0, "#f0f921"],
|
||||
],
|
||||
type: "heatmap",
|
||||
},
|
||||
],
|
||||
histogram2dcontour: [
|
||||
{
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
colorscale: [
|
||||
[0.0, "#0d0887"],
|
||||
[0.1111111111111111, "#46039f"],
|
||||
[0.2222222222222222, "#7201a8"],
|
||||
[0.3333333333333333, "#9c179e"],
|
||||
[0.4444444444444444, "#bd3786"],
|
||||
[0.5555555555555556, "#d8576b"],
|
||||
[0.6666666666666666, "#ed7953"],
|
||||
[0.7777777777777778, "#fb9f3a"],
|
||||
[0.8888888888888888, "#fdca26"],
|
||||
[1.0, "#f0f921"],
|
||||
],
|
||||
type: "histogram2dcontour",
|
||||
},
|
||||
],
|
||||
histogram2d: [
|
||||
{
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
colorscale: [
|
||||
[0.0, "#0d0887"],
|
||||
[0.1111111111111111, "#46039f"],
|
||||
[0.2222222222222222, "#7201a8"],
|
||||
[0.3333333333333333, "#9c179e"],
|
||||
[0.4444444444444444, "#bd3786"],
|
||||
[0.5555555555555556, "#d8576b"],
|
||||
[0.6666666666666666, "#ed7953"],
|
||||
[0.7777777777777778, "#fb9f3a"],
|
||||
[0.8888888888888888, "#fdca26"],
|
||||
[1.0, "#f0f921"],
|
||||
],
|
||||
type: "histogram2d",
|
||||
},
|
||||
],
|
||||
histogram: [
|
||||
{
|
||||
marker: {
|
||||
pattern: {
|
||||
fillmode: "overlay",
|
||||
size: 10,
|
||||
solidity: 0.2,
|
||||
},
|
||||
},
|
||||
type: "histogram",
|
||||
},
|
||||
],
|
||||
mesh3d: [
|
||||
{
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
type: "mesh3d",
|
||||
},
|
||||
],
|
||||
parcoords: [
|
||||
{
|
||||
line: {
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
},
|
||||
type: "parcoords",
|
||||
},
|
||||
],
|
||||
pie: [
|
||||
{
|
||||
automargin: true,
|
||||
type: "pie",
|
||||
},
|
||||
],
|
||||
scatter3d: [
|
||||
{
|
||||
line: {
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
},
|
||||
marker: {
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
},
|
||||
type: "scatter3d",
|
||||
},
|
||||
],
|
||||
scattercarpet: [
|
||||
{
|
||||
marker: {
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
},
|
||||
type: "scattercarpet",
|
||||
},
|
||||
],
|
||||
scattergeo: [
|
||||
{
|
||||
marker: {
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
},
|
||||
type: "scattergeo",
|
||||
},
|
||||
],
|
||||
scattergl: [
|
||||
{
|
||||
marker: {
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
},
|
||||
type: "scattergl",
|
||||
},
|
||||
],
|
||||
scattermapbox: [
|
||||
{
|
||||
marker: {
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
},
|
||||
type: "scattermapbox",
|
||||
},
|
||||
],
|
||||
scatterpolargl: [
|
||||
{
|
||||
marker: {
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
},
|
||||
type: "scatterpolargl",
|
||||
},
|
||||
],
|
||||
scatterpolar: [
|
||||
{
|
||||
marker: {
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
},
|
||||
type: "scatterpolar",
|
||||
},
|
||||
],
|
||||
scatter: [
|
||||
{
|
||||
fillpattern: {
|
||||
fillmode: "overlay",
|
||||
size: 10,
|
||||
solidity: 0.2,
|
||||
},
|
||||
type: "scatter",
|
||||
},
|
||||
],
|
||||
scatterternary: [
|
||||
{
|
||||
marker: {
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
},
|
||||
type: "scatterternary",
|
||||
},
|
||||
],
|
||||
surface: [
|
||||
{
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
colorscale: [
|
||||
[0.0, "#0d0887"],
|
||||
[0.1111111111111111, "#46039f"],
|
||||
[0.2222222222222222, "#7201a8"],
|
||||
[0.3333333333333333, "#9c179e"],
|
||||
[0.4444444444444444, "#bd3786"],
|
||||
[0.5555555555555556, "#d8576b"],
|
||||
[0.6666666666666666, "#ed7953"],
|
||||
[0.7777777777777778, "#fb9f3a"],
|
||||
[0.8888888888888888, "#fdca26"],
|
||||
[1.0, "#f0f921"],
|
||||
],
|
||||
type: "surface",
|
||||
},
|
||||
],
|
||||
table: [
|
||||
{
|
||||
cells: {
|
||||
fill: {
|
||||
color: "#EBF0F8",
|
||||
},
|
||||
line: {
|
||||
color: "white",
|
||||
},
|
||||
},
|
||||
header: {
|
||||
fill: {
|
||||
color: "#C8D4E3",
|
||||
},
|
||||
line: {
|
||||
color: "white",
|
||||
},
|
||||
},
|
||||
type: "table",
|
||||
},
|
||||
],
|
||||
candlestick: [
|
||||
{
|
||||
decreasing: {
|
||||
fillcolor: "#c80000",
|
||||
line: {
|
||||
color: "#990000",
|
||||
},
|
||||
},
|
||||
increasing: {
|
||||
fillcolor: "#009600",
|
||||
line: {
|
||||
color: "#007500",
|
||||
},
|
||||
},
|
||||
type: "candlestick",
|
||||
},
|
||||
],
|
||||
},
|
||||
layout: {
|
||||
annotationdefaults: {
|
||||
arrowcolor: "#2a3f5f",
|
||||
arrowhead: 0,
|
||||
arrowwidth: 1,
|
||||
showarrow: false,
|
||||
},
|
||||
autotypenumbers: "strict",
|
||||
coloraxis: {
|
||||
colorbar: {
|
||||
outlinewidth: 0,
|
||||
ticks: "",
|
||||
},
|
||||
},
|
||||
colorscale: {
|
||||
diverging: [
|
||||
[0, "#8e0152"],
|
||||
[0.1, "#c51b7d"],
|
||||
[0.2, "#de77ae"],
|
||||
[0.3, "#f1b6da"],
|
||||
[0.4, "#fde0ef"],
|
||||
[0.5, "#f7f7f7"],
|
||||
[0.6, "#e6f5d0"],
|
||||
[0.7, "#b8e186"],
|
||||
[0.8, "#7fbc41"],
|
||||
[0.9, "#4d9221"],
|
||||
[1, "#276419"],
|
||||
],
|
||||
sequential: [
|
||||
[0.0, "#0d0887"],
|
||||
[0.1111111111111111, "#46039f"],
|
||||
[0.2222222222222222, "#7201a8"],
|
||||
[0.3333333333333333, "#9c179e"],
|
||||
[0.4444444444444444, "#bd3786"],
|
||||
[0.5555555555555556, "#d8576b"],
|
||||
[0.6666666666666666, "#ed7953"],
|
||||
[0.7777777777777778, "#fb9f3a"],
|
||||
[0.8888888888888888, "#fdca26"],
|
||||
[1.0, "#f0f921"],
|
||||
],
|
||||
sequentialminus: [
|
||||
[0.0, "#0d0887"],
|
||||
[0.1111111111111111, "#46039f"],
|
||||
[0.2222222222222222, "#7201a8"],
|
||||
[0.3333333333333333, "#9c179e"],
|
||||
[0.4444444444444444, "#bd3786"],
|
||||
[0.5555555555555556, "#d8576b"],
|
||||
[0.6666666666666666, "#ed7953"],
|
||||
[0.7777777777777778, "#fb9f3a"],
|
||||
[0.8888888888888888, "#fdca26"],
|
||||
[1.0, "#f0f921"],
|
||||
],
|
||||
},
|
||||
colorway: [
|
||||
"#254495",
|
||||
"#c13246",
|
||||
"#48277c",
|
||||
"#e4003a",
|
||||
"#ef7d00",
|
||||
"#822661",
|
||||
"#ffed00",
|
||||
"#00aaff",
|
||||
"#9b30d9",
|
||||
"#af005f",
|
||||
"#5f00af",
|
||||
"#af87ff",
|
||||
],
|
||||
font: {
|
||||
color: "#2a3f5f",
|
||||
},
|
||||
geo: {
|
||||
bgcolor: "white",
|
||||
lakecolor: "white",
|
||||
landcolor: "white",
|
||||
showlakes: true,
|
||||
showland: true,
|
||||
subunitcolor: "#C8D4E3",
|
||||
},
|
||||
hoverlabel: {
|
||||
align: "left",
|
||||
},
|
||||
hovermode: "x",
|
||||
mapbox: {
|
||||
style: "light",
|
||||
},
|
||||
paper_bgcolor: "white",
|
||||
plot_bgcolor: "white",
|
||||
polar: {
|
||||
angularaxis: {
|
||||
gridcolor: "#EBF0F8",
|
||||
linecolor: "#EBF0F8",
|
||||
ticks: "",
|
||||
},
|
||||
bgcolor: "white",
|
||||
radialaxis: {
|
||||
gridcolor: "#EBF0F8",
|
||||
linecolor: "#EBF0F8",
|
||||
ticks: "",
|
||||
},
|
||||
},
|
||||
scene: {
|
||||
xaxis: {
|
||||
backgroundcolor: "white",
|
||||
gridcolor: "#DFE8F3",
|
||||
gridwidth: 2,
|
||||
linecolor: "#EBF0F8",
|
||||
showbackground: true,
|
||||
ticks: "",
|
||||
zerolinecolor: "#EBF0F8",
|
||||
},
|
||||
yaxis: {
|
||||
backgroundcolor: "white",
|
||||
gridcolor: "#DFE8F3",
|
||||
gridwidth: 2,
|
||||
linecolor: "#EBF0F8",
|
||||
showbackground: true,
|
||||
ticks: "",
|
||||
zerolinecolor: "#EBF0F8",
|
||||
},
|
||||
zaxis: {
|
||||
backgroundcolor: "white",
|
||||
gridcolor: "#DFE8F3",
|
||||
gridwidth: 2,
|
||||
linecolor: "#EBF0F8",
|
||||
showbackground: true,
|
||||
ticks: "",
|
||||
zerolinecolor: "#EBF0F8",
|
||||
},
|
||||
},
|
||||
shapedefaults: {
|
||||
line: {
|
||||
color: "#2a3f5f",
|
||||
},
|
||||
},
|
||||
ternary: {
|
||||
aaxis: {
|
||||
gridcolor: "#DFE8F3",
|
||||
linecolor: "#A2B1C6",
|
||||
ticks: "",
|
||||
},
|
||||
baxis: {
|
||||
gridcolor: "#DFE8F3",
|
||||
linecolor: "#A2B1C6",
|
||||
ticks: "",
|
||||
},
|
||||
bgcolor: "white",
|
||||
caxis: {
|
||||
gridcolor: "#DFE8F3",
|
||||
linecolor: "#A2B1C6",
|
||||
ticks: "",
|
||||
},
|
||||
},
|
||||
title: {
|
||||
x: 0.05,
|
||||
},
|
||||
xaxis: {
|
||||
automargin: true,
|
||||
ticks: "outside",
|
||||
zerolinewidth: 2,
|
||||
rangeslider: {
|
||||
visible: false,
|
||||
},
|
||||
showgrid: true,
|
||||
showline: true,
|
||||
tickfont: {
|
||||
size: 15,
|
||||
},
|
||||
title: {
|
||||
standoff: 20,
|
||||
},
|
||||
mirror: true,
|
||||
zeroline: false,
|
||||
},
|
||||
yaxis: {
|
||||
automargin: true,
|
||||
ticks: "outside",
|
||||
tickfont: {
|
||||
size: 15,
|
||||
},
|
||||
zerolinewidth: 2,
|
||||
fixedrange: false,
|
||||
title: {
|
||||
standoff: 20,
|
||||
},
|
||||
nticks: 8,
|
||||
showgrid: true,
|
||||
showline: true,
|
||||
side: "right",
|
||||
mirror: true,
|
||||
zeroline: false,
|
||||
},
|
||||
dragmode: "pan",
|
||||
legend: {
|
||||
bgcolor: "rgba(0, 0, 0, 0)",
|
||||
x: 1.03,
|
||||
xanchor: "left",
|
||||
y: 0.99,
|
||||
yanchor: "top",
|
||||
},
|
||||
},
|
||||
};
|
||||
47
frontend-components/plotly/src/components/Dialogs/CommonDialog.tsx
vendored
Normal file
47
frontend-components/plotly/src/components/Dialogs/CommonDialog.tsx
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import CloseIcon from "../Icons/Close";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export const styleDialog = {
|
||||
margin: "2px 0px 2px 10px",
|
||||
padding: "5px 2px 2px 5px",
|
||||
};
|
||||
|
||||
export default function CommonDialog({
|
||||
open,
|
||||
close,
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
}: {
|
||||
open: boolean;
|
||||
close: () => void;
|
||||
title: string;
|
||||
description: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<DialogPrimitive.Root open={open} onOpenChange={close}>
|
||||
<div id="loading" className="saving">
|
||||
<div id="loading_text" className="loading_text"></div>
|
||||
<div id="loader" className="loader"></div>
|
||||
</div>
|
||||
<DialogPrimitive.Overlay onClick={close} className="_modal-overlay" />
|
||||
<DialogPrimitive.Content className="_modal">
|
||||
<DialogPrimitive.Title className="_modal-title">
|
||||
{title}
|
||||
</DialogPrimitive.Title>
|
||||
<DialogPrimitive.Description className="_modal_description">
|
||||
{description}
|
||||
</DialogPrimitive.Description>
|
||||
<DialogPrimitive.Close>
|
||||
<CloseIcon />
|
||||
</DialogPrimitive.Close>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="_modal-close" onClick={close}>
|
||||
<CloseIcon className="w-6 h-6" />
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPrimitive.Root>
|
||||
);
|
||||
}
|
||||
617
frontend-components/plotly/src/components/Dialogs/OverlayChartDialog.tsx
vendored
Normal file
617
frontend-components/plotly/src/components/Dialogs/OverlayChartDialog.tsx
vendored
Normal file
@@ -0,0 +1,617 @@
|
||||
import CommonDialog, { styleDialog } from "../Dialogs/CommonDialog";
|
||||
import { useState } from "react";
|
||||
|
||||
const reader = new FileReader();
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
export default function OverlayChartDialog({
|
||||
open,
|
||||
close,
|
||||
setLoading,
|
||||
addOverlay,
|
||||
plotlyData,
|
||||
}: {
|
||||
open: boolean;
|
||||
close: () => void;
|
||||
setLoading: (loading: boolean) => void;
|
||||
addOverlay: (data: any) => void;
|
||||
plotlyData: any;
|
||||
}) {
|
||||
const [traceType, setTraceType] = useState("scatter");
|
||||
const [traceColor, setTraceColor] = useState("#FFDD00");
|
||||
const [increasingColor, setIncreasingColor] = useState("#00ACFF");
|
||||
const [decreasingColor, setDecreasingColor] = useState("#FF0000");
|
||||
const [traceName, setTraceName] = useState("");
|
||||
const [csvData, setCsvData] = useState<any[]>([]);
|
||||
const [csvColumns, setCsvColumns] = useState<string[]>([]);
|
||||
const [selectedColumns, setSelectedColumns] = useState<any>({});
|
||||
const [yaxisOptions, setYaxisOptions] = useState<any>({});
|
||||
const optionIds = ["x", "open", "high", "low", "close"];
|
||||
|
||||
let traceTypes: any = {
|
||||
scatter: "Scatter (Line)",
|
||||
candlestick: "Candlestick",
|
||||
bar: "Bar",
|
||||
};
|
||||
|
||||
const [options, setOptions] = useState<any>({});
|
||||
|
||||
function onClose() {
|
||||
close();
|
||||
setTraceType("scatter");
|
||||
setTraceName("");
|
||||
setCsvData([]);
|
||||
setCsvColumns([]);
|
||||
setSelectedColumns({});
|
||||
setOptions({});
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
if (csvData.length == 0) {
|
||||
document.getElementById("csv_file")?.focus();
|
||||
document.getElementById("csv_file")?.style.setProperty("border", "1px solid red");
|
||||
document.getElementById("csv_file_warning")!.style.display = "block";
|
||||
return;
|
||||
}
|
||||
const newPlotydata = CSVonSubmit({
|
||||
csvData: csvData,
|
||||
plotlyData: plotlyData,
|
||||
yaxisOptions: yaxisOptions,
|
||||
traceType: traceType,
|
||||
traceColor: traceColor,
|
||||
traceName: traceName,
|
||||
options: options,
|
||||
increasingColor: increasingColor,
|
||||
decreasingColor: decreasingColor,
|
||||
});
|
||||
addOverlay(newPlotydata);
|
||||
onClose();
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<CommonDialog
|
||||
title="Overlay Chart"
|
||||
description="Upload a CSV file to overlay a chart on the main chart."
|
||||
open={open}
|
||||
close={close}
|
||||
>
|
||||
<div id="popup_csv" className="popup_content">
|
||||
<div>
|
||||
<label htmlFor="csv_file">
|
||||
<b>CSV file:</b>
|
||||
<div id="csv_file_warning" className="popup_warning" style={{ marginLeft: "80px", marginBottom: "10px" }}>
|
||||
CSV file is required.
|
||||
</div>
|
||||
</label>
|
||||
<input
|
||||
onChange={(e) => {
|
||||
if (!e.target.files) {
|
||||
return;
|
||||
} else if (e.target.files[0].type !== "text/csv") {
|
||||
document.getElementById("csv_file")?.focus();
|
||||
document.getElementById("csv_file")?.style.setProperty("border", "1px solid red");
|
||||
document.getElementById("csv_file_warning")!.style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
if (csvColumns.length > 0) {
|
||||
setCsvColumns([]);
|
||||
setSelectedColumns({});
|
||||
setOptions({});
|
||||
setTraceType("scatter");
|
||||
}
|
||||
|
||||
reader.onload = (filebytes) => {
|
||||
if (
|
||||
!filebytes.target?.result ||
|
||||
typeof filebytes.target.result !== "string"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let lines = filebytes.target.result
|
||||
.split("\n")
|
||||
.map((x) => x.replace(/\r/g, ""));
|
||||
|
||||
const headers = lines[0].split(",");
|
||||
const headers_lower = headers.map((x) =>
|
||||
x.trim().toLowerCase()
|
||||
);
|
||||
|
||||
let updateOptions: any = {};
|
||||
for (let i = 0; i < optionIds.length; i++) {
|
||||
if (headers_lower.includes(optionIds[i])) {
|
||||
updateOptions[optionIds[i]] =
|
||||
headers[headers_lower.indexOf(optionIds[i])];
|
||||
} else if (
|
||||
optionIds[i] == "x" &&
|
||||
headers_lower.includes("date")
|
||||
) {
|
||||
updateOptions[optionIds[i]] =
|
||||
headers[headers_lower.indexOf("date")];
|
||||
}
|
||||
}
|
||||
|
||||
const candle_cols = ["open", "high", "low", "close"];
|
||||
const candle_cols_present = candle_cols.every((x) =>
|
||||
headers_lower.includes(x)
|
||||
);
|
||||
if (candle_cols_present) {
|
||||
setTraceType("candlestick");
|
||||
}
|
||||
|
||||
if (headers_lower.includes("close")) {
|
||||
setOptions({
|
||||
...options,
|
||||
y: headers[headers_lower.indexOf("close")],
|
||||
});
|
||||
updateOptions.y = headers[headers_lower.indexOf("close")];
|
||||
}
|
||||
|
||||
let data = [];
|
||||
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
let obj = {};
|
||||
let currentline = lines[i].split(",");
|
||||
for (let j = 0; j < headers.length; j++) {
|
||||
//@ts-ignore
|
||||
obj[headers[j]] = currentline[j];
|
||||
}
|
||||
data.push(obj);
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
let filename = e.target.files[0].name.split(".")[0];
|
||||
|
||||
try {
|
||||
if (filename.includes("_")) {
|
||||
let name_parts = filename.split("_");
|
||||
let date_regex = new RegExp("^[0-9]{8}$");
|
||||
|
||||
if (name_parts.length > 2) {
|
||||
// we check if the first 2 parts are date and time
|
||||
if (date_regex.test(name_parts[0])) {
|
||||
name_parts.splice(0, 2);
|
||||
}
|
||||
// we check if the last 2 parts are date and time
|
||||
else if (
|
||||
date_regex.test(name_parts[name_parts.length - 2])
|
||||
) {
|
||||
name_parts.splice(name_parts.length - 2, 2);
|
||||
}
|
||||
filename = name_parts.join("_").replace(/openbb_/g, "");
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
setTraceName(filename);
|
||||
setOptions(updateOptions);
|
||||
setCsvColumns(headers);
|
||||
setCsvData(data);
|
||||
};
|
||||
reader.readAsText(e.target.files[0]);
|
||||
}}
|
||||
type="file"
|
||||
id="csv_file"
|
||||
accept=".csv"
|
||||
style={{ marginLeft: 10 }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginTop: 15 }}>
|
||||
<label htmlFor="csv_trace_type">
|
||||
<b>Display data type:</b>
|
||||
</label>
|
||||
<select
|
||||
onChange={(e) => {
|
||||
setTraceType(e.target.value);
|
||||
}}
|
||||
id="csv_trace_type"
|
||||
style={styleDialog}
|
||||
defaultValue={traceTypes[traceType]}
|
||||
>
|
||||
{traceType && (
|
||||
<option key={traceType} value={traceType}>
|
||||
{traceTypes[traceType]}
|
||||
</option>
|
||||
)}
|
||||
{Object.keys(traceTypes).map(
|
||||
(x) =>
|
||||
traceType !== x && (
|
||||
<option key={x} value={x}>
|
||||
{traceTypes[x]}
|
||||
</option>
|
||||
)
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ marginTop: 12 }}>
|
||||
<label htmlFor="csv_name">
|
||||
<b>Trace Name:</b>
|
||||
</label>
|
||||
<textarea
|
||||
id="csv_name"
|
||||
value={traceName}
|
||||
onChange={(e) => {
|
||||
setTraceName(e.target.value);
|
||||
}}
|
||||
style={{
|
||||
padding: "5px 2px 2px 5px",
|
||||
width: "100%",
|
||||
maxWidth: "100%",
|
||||
maxHeight: 200,
|
||||
marginTop: 2,
|
||||
}}
|
||||
rows={2}
|
||||
cols={20}
|
||||
placeholder="Enter a name to give this trace"
|
||||
/>
|
||||
</div>
|
||||
{csvColumns.length > 0 && (
|
||||
<>
|
||||
{["scatter", "bar"].includes(traceType) && (
|
||||
<div
|
||||
style={{ marginTop: 15, marginBottom: 10 }}
|
||||
id="csv_columns"
|
||||
className="csv_column_container"
|
||||
>
|
||||
{["x", "y"].map((x) => (
|
||||
<div
|
||||
key={x}
|
||||
style={{
|
||||
marginTop: 10,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<label htmlFor={`csv_${x}`} style={{ width: "100px" }}>
|
||||
{x.toUpperCase()} Axis
|
||||
</label>
|
||||
<select
|
||||
onChange={(e) => {
|
||||
setSelectedColumns({
|
||||
...selectedColumns,
|
||||
[x]: e.target.value,
|
||||
});
|
||||
setOptions({
|
||||
...options,
|
||||
[x]: e.target.value,
|
||||
});
|
||||
}}
|
||||
id={`csv_${x}`}
|
||||
style={{ width: "100%" }}
|
||||
defaultValue={options[x]}
|
||||
>
|
||||
{options[x] && (
|
||||
<option key={x} value={options[x]}>
|
||||
{options[x]}
|
||||
</option>
|
||||
)}
|
||||
{csvColumns.map((x) => (
|
||||
<option key={x} value={x}>
|
||||
{x}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{traceType === "candlestick" && (
|
||||
<div
|
||||
id="csv_columns"
|
||||
className="csv_column_container"
|
||||
style={{ marginTop: 15 }}
|
||||
>
|
||||
{["x", "open", "high", "low", "close"].map((x) => (
|
||||
<div
|
||||
key={x}
|
||||
style={{
|
||||
marginTop: 10,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<label htmlFor={`csv_${x}`} style={{ width: "100px" }}>
|
||||
{x.charAt(0).toUpperCase() + x.slice(1)}
|
||||
</label>
|
||||
<select
|
||||
onChange={(e) => {
|
||||
setSelectedColumns({
|
||||
...selectedColumns,
|
||||
[x]: e.target.value,
|
||||
});
|
||||
setOptions({
|
||||
...options,
|
||||
[x]: e.target.value,
|
||||
});
|
||||
}}
|
||||
id={`csv_${x}`}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{options[x] && (
|
||||
<option key={x} value={options[x]}>
|
||||
{options[x]}
|
||||
</option>
|
||||
)}
|
||||
{csvColumns.map(
|
||||
(x) =>
|
||||
x != options[x] && (
|
||||
<option key={x} value={x}>
|
||||
{x}
|
||||
</option>
|
||||
)
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ marginTop: 20 }} id="csv_colors">
|
||||
{["scatter", "bar"].includes(traceType) && (
|
||||
<div>
|
||||
<label htmlFor="csv_color">{`${traceType
|
||||
.charAt(0)
|
||||
.toUpperCase()}${traceType.slice(1)} color`}</label>
|
||||
<input
|
||||
type="color"
|
||||
id="csv_color"
|
||||
defaultValue="#FFDD00"
|
||||
style={{ margin: "2px 2px 2px 10px" }}
|
||||
onChange={(e) => {
|
||||
console.log(e.target.value);
|
||||
setTraceColor(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{traceType === "candlestick" && (
|
||||
<>
|
||||
<label htmlFor="csv_increasing">Increasing color</label>
|
||||
<input
|
||||
type="color"
|
||||
id="csv_increasing"
|
||||
defaultValue="#00ACFF"
|
||||
style={{ margin: "2px 0px 2px 10px" }}
|
||||
onChange={(e) => {
|
||||
setIncreasingColor(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="csv_decreasing" style={{ marginLeft: 15 }}>
|
||||
Decreasing color
|
||||
</label>
|
||||
<input
|
||||
style={{ margin: "2px 0px 2px 10px" }}
|
||||
type="color"
|
||||
id="csv_decreasing"
|
||||
defaultValue="#FF0000"
|
||||
onChange={(e) => {
|
||||
setDecreasingColor(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ marginTop: 20 }} id="csv_plot_yaxis_options">
|
||||
{traceType !== "candlestick" && (
|
||||
<>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="csv_percent_change"
|
||||
name="csv_plot_yaxis_check"
|
||||
style={{ marginBottom: 2 }}
|
||||
onChange={(e) => {
|
||||
setYaxisOptions({
|
||||
...yaxisOptions,
|
||||
percentChange: e.target.checked,
|
||||
sameYaxis: false,
|
||||
});
|
||||
}}
|
||||
checked={
|
||||
!yaxisOptions.sameYaxis && yaxisOptions.percentChange
|
||||
}
|
||||
/>
|
||||
<label htmlFor="csv_percent_change" style={{ marginLeft: 5 }}>
|
||||
Plot as percent change from first value
|
||||
</label>
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
<input
|
||||
style={{ marginTop: 2 }}
|
||||
type="checkbox"
|
||||
id="csv_same_yaxis"
|
||||
name="csv_plot_yaxis_check"
|
||||
onChange={(e) => {
|
||||
setYaxisOptions({
|
||||
...yaxisOptions,
|
||||
sameYaxis: e.target.checked,
|
||||
percentChange: false,
|
||||
});
|
||||
}}
|
||||
checked={!yaxisOptions.percentChange && yaxisOptions.sameYaxis}
|
||||
/>
|
||||
<label htmlFor="csv_same_yaxis" style={{ marginLeft: 5 }}>
|
||||
Share Y-axis
|
||||
</label>
|
||||
|
||||
{traceType === "bar" && (
|
||||
<div style={{ marginTop: 2 }} id="csv_bar_orientation">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="csv_bar_horizontal"
|
||||
onChange={(e) => {
|
||||
setOptions({
|
||||
...options,
|
||||
orientation: e.target.checked ? "h" : "v",
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="csv_bar_horizontal" style={{ marginLeft: 5 }}>
|
||||
Plot horizontally
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<br />
|
||||
<div style={{ float: "right", marginTop: 20 }}>
|
||||
<button className="_btn-tertiary" id="csv_cancel" onClick={onClose}>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="_btn" id="csv_submit" onClick={onSubmit}>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</CommonDialog>
|
||||
);
|
||||
}
|
||||
|
||||
export function CSVonSubmit({
|
||||
csvData,
|
||||
plotlyData,
|
||||
yaxisOptions,
|
||||
traceType,
|
||||
traceColor,
|
||||
traceName,
|
||||
options,
|
||||
increasingColor,
|
||||
decreasingColor,
|
||||
}: {
|
||||
csvData: any[];
|
||||
plotlyData: any;
|
||||
yaxisOptions: any;
|
||||
traceType: string;
|
||||
traceColor: string;
|
||||
traceName: string;
|
||||
options: any;
|
||||
increasingColor: string;
|
||||
decreasingColor: string;
|
||||
}) {
|
||||
let main_trace = plotlyData.data[0];
|
||||
if (main_trace.xaxis == undefined) {
|
||||
main_trace.xaxis = "x";
|
||||
}
|
||||
if (main_trace.yaxis == undefined) {
|
||||
main_trace.yaxis = "y";
|
||||
}
|
||||
let yaxis_id = main_trace.yaxis;
|
||||
let yaxis;
|
||||
|
||||
const left_yaxis_ticks = Object.keys(plotlyData.layout)
|
||||
.filter((k) => k.startsWith("yaxis"))
|
||||
.map((k) => plotlyData.layout[k])
|
||||
.filter(
|
||||
(yaxis) =>
|
||||
yaxis.side == "left" &&
|
||||
(yaxis.overlaying == "y" ||
|
||||
(yaxis.fixedrange != undefined && yaxis.fixedrange == true))
|
||||
).length;
|
||||
|
||||
let ticksuffix = left_yaxis_ticks > 0 ? " " : "";
|
||||
|
||||
if (yaxisOptions.sameYaxis !== true) {
|
||||
const yaxes = Object.keys(plotlyData.layout)
|
||||
.filter((k) => k.startsWith("yaxis"))
|
||||
.map((k) => plotlyData.layout[k]);
|
||||
|
||||
yaxis = `y${yaxes.length + 1}`;
|
||||
yaxis_id = `yaxis${yaxes.length + 1}`;
|
||||
console.log(`yaxis: ${yaxis} ${yaxis_id}`);
|
||||
plotlyData.layout[yaxis_id] = {
|
||||
...layout_defaults,
|
||||
title: {
|
||||
text: traceName,
|
||||
font: {
|
||||
size: 14,
|
||||
},
|
||||
standoff: 0,
|
||||
},
|
||||
ticksuffix: ticksuffix,
|
||||
layer: "below traces",
|
||||
};
|
||||
} else {
|
||||
// Plot on the same yaxis
|
||||
yaxis = main_trace.yaxis.replace("yaxis", "y");
|
||||
}
|
||||
|
||||
const traceBase: any = {
|
||||
type: traceType,
|
||||
name: traceName,
|
||||
showlegend: true,
|
||||
yaxis: yaxis,
|
||||
};
|
||||
|
||||
let trace: any = {};
|
||||
|
||||
if (["scatter", "bar"].includes(traceType)) {
|
||||
const non_null = csvData.findIndex(
|
||||
(x: any) => x[options.y] != null && x[options.y] != 0
|
||||
);
|
||||
|
||||
trace = {
|
||||
...traceBase,
|
||||
x: csvData.map((row: any) => row[options.x]),
|
||||
y: csvData.map(function (row: any) {
|
||||
if (yaxisOptions.percentChange && traceType === "scatter") {
|
||||
return (
|
||||
(row[options.y] - csvData[non_null][options.y]) /
|
||||
csvData[non_null][options.y]
|
||||
);
|
||||
} else {
|
||||
return row[options.y];
|
||||
}
|
||||
}),
|
||||
customdata: csvData.map((row: any) => row[options.y]),
|
||||
hovertemplate: "%{customdata:.2f}<extra></extra>",
|
||||
connectgaps: true,
|
||||
marker: { color: traceColor },
|
||||
};
|
||||
|
||||
if (traceType === "bar") {
|
||||
trace.orientation = options.orientation;
|
||||
trace.marker.opacity = 0.7;
|
||||
delete trace.connectgaps;
|
||||
delete trace.hovertemplate;
|
||||
delete trace.customdata;
|
||||
}
|
||||
} else if (traceType === "candlestick") {
|
||||
trace = {
|
||||
...traceBase,
|
||||
x: csvData.map((row: any) => row[options.x]),
|
||||
open: csvData.map((row: any) => row[options.open]),
|
||||
high: csvData.map((row: any) => row[options.high]),
|
||||
low: csvData.map((row: any) => row[options.low]),
|
||||
close: csvData.map((row: any) => row[options.close]),
|
||||
increasing: { line: { color: increasingColor } },
|
||||
decreasing: { line: { color: decreasingColor } },
|
||||
};
|
||||
}
|
||||
|
||||
const newPlotlyData = {
|
||||
...plotlyData,
|
||||
data: [...plotlyData.data, trace],
|
||||
};
|
||||
|
||||
return newPlotlyData;
|
||||
}
|
||||
201
frontend-components/plotly/src/components/Dialogs/TextChartDialog.tsx
vendored
Normal file
201
frontend-components/plotly/src/components/Dialogs/TextChartDialog.tsx
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
import CommonDialog from "./CommonDialog";
|
||||
import { useState } from "react";
|
||||
|
||||
const style = {
|
||||
padding: "5px 2px 2px 5px",
|
||||
margin: "2px 0",
|
||||
};
|
||||
|
||||
export default function TextChartDialog({
|
||||
open,
|
||||
close,
|
||||
addAnnotation,
|
||||
deleteAnnotation,
|
||||
popupData,
|
||||
}: {
|
||||
plotlyData: any;
|
||||
open: boolean;
|
||||
close: () => void;
|
||||
addAnnotation: (annotation: any) => void;
|
||||
updateAnnotation: (annotation: any) => void;
|
||||
deleteAnnotation: (annotation: any) => void;
|
||||
popupData: any | null;
|
||||
}) {
|
||||
const defaultPopupData = {
|
||||
text: "",
|
||||
color: "#0088CC",
|
||||
size: 18,
|
||||
bordercolor: "#822661",
|
||||
yanchor: "above",
|
||||
};
|
||||
const [popUpData, setPopUpData] = useState<any>(defaultPopupData);
|
||||
const [newPopupData, setNewPopupData] = useState<any>(defaultPopupData);
|
||||
|
||||
if (popupData && popupData !== popUpData) {
|
||||
if (popupData.annotation) {
|
||||
popupData.annotation = popupData?.annotation || {};
|
||||
setPopUpData(popupData);
|
||||
setNewPopupData(popupData);
|
||||
}
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
console.log("closing");
|
||||
setPopUpData(defaultPopupData);
|
||||
setNewPopupData(defaultPopupData);
|
||||
close();
|
||||
}
|
||||
|
||||
function onChange(e: any) {
|
||||
console.log(e.target.id.replace("addtext_", ""), e.target.value);
|
||||
const name = e.target.id.replace("addtext_", "");
|
||||
const value =
|
||||
e.target.type === "checkbox"
|
||||
? e.target.checked
|
||||
? 1
|
||||
: 0
|
||||
: e.target.value;
|
||||
setNewPopupData({ ...newPopupData, [name]: value });
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
console.log("submitting", newPopupData);
|
||||
if (newPopupData.text !== "") {
|
||||
let above = ["above", 1].includes(newPopupData.yanchor);
|
||||
newPopupData.yanchor = above ? "above" : "below";
|
||||
|
||||
if (popUpData?.annotation) {
|
||||
setNewPopupData({ ...newPopupData, annotation: popUpData.annotation });
|
||||
}
|
||||
addAnnotation(newPopupData);
|
||||
close();
|
||||
} else {
|
||||
document.getElementById("popup_textarea_warning")!.style.display =
|
||||
"block";
|
||||
document.getElementById("addtext_text")!.style.border = "1px solid red";
|
||||
}
|
||||
}
|
||||
function onDelete() {
|
||||
deleteAnnotation(popUpData);
|
||||
onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<CommonDialog
|
||||
title="Add Text to Chart"
|
||||
description="Change the titles on the chart."
|
||||
open={open}
|
||||
close={onClose}
|
||||
>
|
||||
<div id="popup_title" className="popup_content">
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label htmlFor="popup_text">
|
||||
<b>Text:</b>
|
||||
<div id="popup_textarea_warning" className="popup_warning">
|
||||
Text is required
|
||||
</div>
|
||||
</label>
|
||||
<textarea
|
||||
id="addtext_text"
|
||||
style={{
|
||||
...style,
|
||||
width: "100%",
|
||||
maxWidth: "100%",
|
||||
maxHeight: "200px",
|
||||
marginTop: "8px",
|
||||
}}
|
||||
rows={4}
|
||||
cols={50}
|
||||
placeholder="Enter text here"
|
||||
onChange={onChange}
|
||||
defaultValue={popUpData?.annotation?.text || newPopupData?.text}
|
||||
></textarea>
|
||||
</div>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<div>
|
||||
<label htmlFor="addtext_color">
|
||||
<b>Font color:</b>
|
||||
</label>
|
||||
<input
|
||||
type="color"
|
||||
id="addtext_color"
|
||||
defaultValue={
|
||||
popUpData?.annotation?.color || newPopupData?.color
|
||||
}
|
||||
onChange={onChange}
|
||||
></input>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="addtext_bordercolor">
|
||||
<b>Border color:</b>
|
||||
</label>
|
||||
<input
|
||||
type="color"
|
||||
id="addtext_bordercolor"
|
||||
defaultValue={
|
||||
popUpData?.annotation?.bordercolor ||
|
||||
newPopupData?.bordercolor
|
||||
}
|
||||
onChange={onChange}
|
||||
></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<div>
|
||||
<label htmlFor="addtext_size">
|
||||
<b>Font size:</b>
|
||||
</label>
|
||||
<input
|
||||
style={{ ...style, width: "45px" }}
|
||||
type="number"
|
||||
id="addtext_size"
|
||||
onChange={onChange}
|
||||
defaultValue={popUpData?.annotation?.size || newPopupData?.size}
|
||||
></input>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="addtext_yanchor">
|
||||
<b>Position (Above):</b>
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="addtext_yanchor"
|
||||
name="check"
|
||||
defaultChecked={
|
||||
newPopupData?.yanchor === "above" ? true : false
|
||||
}
|
||||
onChange={onChange}
|
||||
></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ float: "right", marginTop: 20 }}>
|
||||
<button
|
||||
className="_btn-tertiary ph-capture"
|
||||
id="title_cancel"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="_btn ph-capture"
|
||||
id="title_delete"
|
||||
onClick={onDelete}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<button
|
||||
className="_btn ph-capture"
|
||||
id="title_submit"
|
||||
onClick={onSubmit}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</CommonDialog>
|
||||
);
|
||||
}
|
||||
137
frontend-components/plotly/src/components/Dialogs/TitleChartDialog.tsx
vendored
Normal file
137
frontend-components/plotly/src/components/Dialogs/TitleChartDialog.tsx
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
import CommonDialog, { styleDialog } from "../Dialogs/CommonDialog";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function TitleChartDialog({
|
||||
plotlyData,
|
||||
open,
|
||||
close,
|
||||
defaultTitle,
|
||||
updateTitle,
|
||||
updateAxesTitles,
|
||||
}: {
|
||||
plotlyData?: any;
|
||||
open: boolean;
|
||||
close: () => void;
|
||||
defaultTitle: string;
|
||||
updateTitle: (title: string) => void;
|
||||
updateAxesTitles: (axesTitles: any) => void;
|
||||
}) {
|
||||
const [title, setTitle] = useState(defaultTitle);
|
||||
|
||||
const yAxes = Object.keys(plotlyData.layout || {}).filter(
|
||||
(k) => k.startsWith("yaxis") && plotlyData.layout[k].range != undefined
|
||||
);
|
||||
const xAxes = Object.keys(plotlyData.layout || {}).filter(
|
||||
(k) =>
|
||||
k.startsWith("xaxis") &&
|
||||
plotlyData.layout[k].showticklabels != undefined &&
|
||||
plotlyData.layout[k]?.anchor
|
||||
);
|
||||
|
||||
const [axesTitles, setAxesTitles] = useState<any>({});
|
||||
|
||||
return (
|
||||
<CommonDialog
|
||||
title="Chart Titles"
|
||||
description="Change the titles on the chart."
|
||||
open={open}
|
||||
close={close}
|
||||
>
|
||||
<div id="popup_title" className="popup_content">
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 0 }}>
|
||||
<div>
|
||||
<label htmlFor="title_text">
|
||||
<b>Title:</b>
|
||||
</label>
|
||||
<textarea
|
||||
id="title_text"
|
||||
style={{
|
||||
...styleDialog,
|
||||
width: "100%",
|
||||
maxWidth: "100%",
|
||||
maxHeight: "200px",
|
||||
marginTop: "8px",
|
||||
marginLeft: "0px",
|
||||
}}
|
||||
rows={2}
|
||||
cols={20}
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
></textarea>
|
||||
</div>
|
||||
<div
|
||||
id="xaxis_div"
|
||||
className="csv_column_container"
|
||||
style={{ marginTop: 5, marginBottom: -5 }}
|
||||
>
|
||||
{xAxes.map((x, i) => (
|
||||
<div key={x} style={{ marginTop: 5, marginBottom: 5 }}>
|
||||
<label htmlFor={`title_${x}`}>
|
||||
{i === 0 ? <b>X axis:</b> : <b>X axis {i + 1}:</b>}
|
||||
</label>
|
||||
<input
|
||||
id={`title_${x}`}
|
||||
style={{ marginLeft: "0px", padding: "5px 2px 2px 5px" }}
|
||||
type="text"
|
||||
defaultValue={plotlyData?.layout[x]?.title?.text || ""}
|
||||
onChange={(e) => {
|
||||
setAxesTitles({
|
||||
...axesTitles,
|
||||
[x]: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
id="yaxis_div"
|
||||
className="csv_column_container"
|
||||
style={{ marginTop: 5, marginBottom: 5 }}
|
||||
>
|
||||
{yAxes.map((y, i) => (
|
||||
<div key={y} style={{ marginTop: 10 }}>
|
||||
<label htmlFor={`title_${y}`}>
|
||||
{i === 0 ? <b>Y axis:</b> : <b>Y axis {i + 1}:</b>}
|
||||
</label>
|
||||
<input
|
||||
id={`title_${y}`}
|
||||
style={{ marginLeft: "0px", padding: "5px 2px 2px 5px" }}
|
||||
type="text"
|
||||
defaultValue={plotlyData?.layout[y]?.title?.text || ""}
|
||||
onChange={(e) => {
|
||||
setAxesTitles({
|
||||
...axesTitles,
|
||||
[y]: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ float: "right", marginTop: 20 }}>
|
||||
<button
|
||||
className="_btn-tertiary ph-capture"
|
||||
id="title_cancel"
|
||||
onClick={close}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="_btn ph-capture"
|
||||
id="title_submit"
|
||||
onClick={() => {
|
||||
updateTitle(title);
|
||||
updateAxesTitles(axesTitles);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</CommonDialog>
|
||||
);
|
||||
}
|
||||
28
frontend-components/plotly/src/components/Icons/Close.tsx
vendored
Normal file
28
frontend-components/plotly/src/components/Icons/Close.tsx
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { SVGProps } from "react";
|
||||
interface SVGRProps {
|
||||
title?: string;
|
||||
titleId?: string;
|
||||
}
|
||||
|
||||
const CloseIcon = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: SVGProps<SVGSVGElement> & SVGRProps) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default CloseIcon;
|
||||
32
frontend-components/plotly/src/components/Icons/CloseCircle.tsx
vendored
Normal file
32
frontend-components/plotly/src/components/Icons/CloseCircle.tsx
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { SVGProps } from "react";
|
||||
interface SVGRProps {
|
||||
title?: string;
|
||||
titleId?: string;
|
||||
}
|
||||
|
||||
const CloseCircleIcon = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: SVGProps<SVGSVGElement> & SVGRProps) => (
|
||||
<svg
|
||||
viewBox="0 0 18 18"
|
||||
width={18}
|
||||
height={18}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
d="M9 16.5a7.5 7.5 0 1 0 0-15 7.5 7.5 0 0 0 0 15ZM11.25 6.75l-4.5 4.5M6.75 6.75l4.5 4.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default CloseCircleIcon;
|
||||
32
frontend-components/plotly/src/components/Icons/Info.tsx
vendored
Normal file
32
frontend-components/plotly/src/components/Icons/Info.tsx
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { SVGProps } from "react";
|
||||
interface SVGRProps {
|
||||
title?: string;
|
||||
titleId?: string;
|
||||
}
|
||||
|
||||
const InfoIcon = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: SVGProps<SVGSVGElement> & SVGRProps) => (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width={24}
|
||||
height={24}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10ZM12 16v-4M12 8h.01"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default InfoIcon;
|
||||
39
frontend-components/plotly/src/components/Icons/Success.tsx
vendored
Normal file
39
frontend-components/plotly/src/components/Icons/Success.tsx
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { SVGProps } from "react";
|
||||
interface SVGRProps {
|
||||
title?: string;
|
||||
titleId?: string;
|
||||
}
|
||||
|
||||
const SuccessIcon = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: SVGProps<SVGSVGElement> & SVGRProps) => (
|
||||
<svg
|
||||
width={18}
|
||||
height={18}
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
d="M16.5 8.31V9a7.5 7.5 0 1 1-4.447-6.855"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M16.5 3 9 10.508l-2.25-2.25"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default SuccessIcon;
|
||||
32
frontend-components/plotly/src/components/Icons/Warning.tsx
vendored
Normal file
32
frontend-components/plotly/src/components/Icons/Warning.tsx
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { SVGProps } from "react";
|
||||
interface SVGRProps {
|
||||
title?: string;
|
||||
titleId?: string;
|
||||
}
|
||||
|
||||
const WarningIcon = ({
|
||||
title,
|
||||
titleId,
|
||||
...props
|
||||
}: SVGProps<SVGSVGElement> & SVGRProps) => (
|
||||
<svg
|
||||
viewBox="0 0 18 18"
|
||||
width={18}
|
||||
height={18}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-labelledby={titleId}
|
||||
{...props}
|
||||
>
|
||||
{title ? <title id={titleId}>{title}</title> : null}
|
||||
<path
|
||||
d="M7.718 2.895 1.366 13.5a1.5 1.5 0 0 0 1.282 2.25h12.705a1.5 1.5 0 0 0 1.283-2.25L10.283 2.895a1.5 1.5 0 0 0-2.565 0v0ZM9 6.75v3M9 12.75h.008"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default WarningIcon;
|
||||
172
frontend-components/plotly/src/components/PlotlyConfig.tsx
vendored
Normal file
172
frontend-components/plotly/src/components/PlotlyConfig.tsx
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
import { Icons as PlotlyIcons } from "plotly.js-dist-min";
|
||||
import { ICONS } from "./Config";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import { downloadCSV, downloadImage } from "../utils/utils";
|
||||
|
||||
export function hideModebar(hide: boolean = true) {
|
||||
return new Promise((resolve) => {
|
||||
if (!window.MODEBAR) {
|
||||
window.MODEBAR = window.document.getElementsByClassName(
|
||||
"modebar-container"
|
||||
)[0] as HTMLElement;
|
||||
window.MODEBAR.style.cssText = `${window.MODEBAR.style.cssText}; display:flex;`;
|
||||
}
|
||||
let includes_text = "display: none";
|
||||
if (window.MODEBAR) {
|
||||
if (window.MODEBAR.style.cssText.includes("display: none") && !hide) {
|
||||
includes_text = "display: flex";
|
||||
window.MODEBAR.style.cssText = `${window.MODEBAR.style.cssText}; display:flex;`;
|
||||
} else {
|
||||
window.MODEBAR.style.cssText = `${window.MODEBAR.style.cssText}; display:none;`;
|
||||
}
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function PlotConfig({
|
||||
setModal,
|
||||
changeTheme,
|
||||
autoScaling,
|
||||
Loading,
|
||||
changeColor,
|
||||
}: {
|
||||
setModal: (modal: { modal: string; data?: any }) => void;
|
||||
changeTheme: (change: boolean) => void;
|
||||
autoScaling: (change: boolean) => void;
|
||||
Loading: (change: boolean) => void;
|
||||
changeColor: (change: boolean) => void;
|
||||
}) {
|
||||
useHotkeys(
|
||||
"ctrl+shift+t",
|
||||
() => {
|
||||
setModal({ modal: "titleDialog" });
|
||||
},
|
||||
{ preventDefault: true }
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+t",
|
||||
() => {
|
||||
setModal({ modal: "textDialog" });
|
||||
},
|
||||
{ preventDefault: true }
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+o",
|
||||
() => {
|
||||
setModal({ modal: "overlayChart" });
|
||||
},
|
||||
{ preventDefault: true }
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+shift+h",
|
||||
() => {
|
||||
hideModebar();
|
||||
},
|
||||
{ preventDefault: true }
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+w",
|
||||
() => {
|
||||
window.close();
|
||||
},
|
||||
{ preventDefault: true }
|
||||
);
|
||||
|
||||
|
||||
const 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: any) {
|
||||
setModal({ modal: "downloadCsv" });
|
||||
await downloadCSV(gd);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Download Chart as Image (Ctrl+S)",
|
||||
icon: ICONS.downloadImage,
|
||||
click: async function () {
|
||||
hideModebar();
|
||||
await downloadImage("MainChart", hideModebar, Loading);
|
||||
},
|
||||
},
|
||||
// {
|
||||
// name: "Upload Image (Ctrl+U)",
|
||||
// icon: Plotly.Icons.uploadImage,
|
||||
// click: function (gd) {
|
||||
// downloadImage();
|
||||
// },
|
||||
// },
|
||||
],
|
||||
[
|
||||
{
|
||||
name: "Edit Color (Ctrl+E)",
|
||||
icon: ICONS.changeColor,
|
||||
click: function () {
|
||||
changeColor(true);
|
||||
},
|
||||
},
|
||||
"drawline",
|
||||
"drawopenpath",
|
||||
"drawcircle",
|
||||
"drawrect",
|
||||
"eraseshape",
|
||||
],
|
||||
[
|
||||
{
|
||||
name: "Overlay chart from CSV (Ctrl+O)",
|
||||
icon: ICONS.plotCsv,
|
||||
click: function () {
|
||||
setModal({ modal: "overlayChart" });
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Add Text (Ctrl+T)",
|
||||
icon: ICONS.addText,
|
||||
click: function () {
|
||||
setModal({ modal: "textDialog", data: { text: "" } });
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Change Titles (Ctrl+Shift+T)",
|
||||
icon: ICONS.changeTitle,
|
||||
click: function () {
|
||||
setModal({ modal: "titleDialog" });
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Change Theme",
|
||||
icon: ICONS.sunIcon,
|
||||
click: function () {
|
||||
changeTheme(true);
|
||||
},
|
||||
},
|
||||
],
|
||||
["hoverClosestCartesian", "hoverCompareCartesian", "toggleSpikelines"],
|
||||
[
|
||||
{
|
||||
name: "Auto Scale (Ctrl+Shift+A)",
|
||||
icon: PlotlyIcons.autoscale,
|
||||
click: function () {
|
||||
autoScaling(true);
|
||||
},
|
||||
},
|
||||
"zoomIn2d",
|
||||
"zoomOut2d",
|
||||
"autoScale2d",
|
||||
"zoom2d",
|
||||
"pan2d",
|
||||
],
|
||||
],
|
||||
};
|
||||
return CONFIG;
|
||||
}
|
||||
84
frontend-components/plotly/src/components/ResizeHandler.tsx
vendored
Normal file
84
frontend-components/plotly/src/components/ResizeHandler.tsx
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
//@ts-nocheck
|
||||
import { Figure } from "react-plotly.js";
|
||||
import { hideModebar } from "./PlotlyConfig";
|
||||
|
||||
export default async function ResizeHandler({
|
||||
plotData,
|
||||
Volume,
|
||||
}: {
|
||||
plotData: Figure;
|
||||
Volume: any;
|
||||
}) {
|
||||
// We hide the modebar and set the number of ticks to 5
|
||||
const XAXIS = Object.keys(plotData.layout)
|
||||
.filter((x) => x.startsWith("xaxis"))
|
||||
.filter((x) => plotData.layout[x].showticklabels);
|
||||
|
||||
const TRACES = plotData.data.filter((trace) =>
|
||||
trace?.name?.startsWith("Volume")
|
||||
);
|
||||
|
||||
let layout_update: any = {};
|
||||
let volume: any = Volume || { old_nticks: {} };
|
||||
|
||||
const width = window.innerWidth;
|
||||
const height = window.innerHeight;
|
||||
let tick_size =
|
||||
height > 420 && width < 920 ? 9 : height > 420 && width < 500 ? 10 : 8;
|
||||
|
||||
if (width < 750) {
|
||||
// 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 (Volume.yaxis == undefined) {
|
||||
volume.yaxis = "yaxis" + trace.yaxis.replace("y", "");
|
||||
layout_update[volume.yaxis + ".tickfont.size"] = tick_size;
|
||||
volume.tickfont = plotData.layout[volume.yaxis].tickfont || {};
|
||||
|
||||
plotData.layout.margin.l -= 40;
|
||||
}
|
||||
}
|
||||
});
|
||||
XAXIS.forEach((x) => {
|
||||
if (volume.old_nticks?.[x] == undefined) {
|
||||
layout_update[x + ".nticks"] = 6;
|
||||
volume.old_nticks[x] = plotData.layout[x].nticks || 10;
|
||||
}
|
||||
});
|
||||
await hideModebar();
|
||||
} else if (window.MODEBAR.style.cssText.includes("display: none")) {
|
||||
// We show the modebar
|
||||
await hideModebar(false);
|
||||
|
||||
if (Volume.old_nticks != undefined) {
|
||||
XAXIS.forEach((x) => {
|
||||
if (volume.old_nticks[x] != undefined) {
|
||||
layout_update[x + ".nticks"] = volume.old_nticks[x];
|
||||
volume.old_nticks[x] = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (Volume.yaxis != undefined) {
|
||||
TRACES.forEach((trace) => {
|
||||
if (trace.type == "bar") {
|
||||
trace.opacity = 0.5;
|
||||
trace.marker.line.width = 0.2;
|
||||
layout_update[volume.yaxis + ".tickfont.size"] =
|
||||
volume.tickfont.size + 3;
|
||||
plotData.layout.margin.l += 40;
|
||||
volume.yaxis = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
volume_update: volume,
|
||||
layout_update: layout_update,
|
||||
plotData: plotData,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user