summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorteh_coderer <me@tehcoderer.com>2023-05-18 11:24:29 -0500
committerGitHub <noreply@github.com>2023-05-18 16:24:29 +0000
commita6a2268ddc3724ef69e9fb5e6c110a2fbdb11ffd (patch)
tree1c972acac50d334227f2c6a594285469e38e6f1e
parentb54c47e2e99d0b952f38a069430116a48e231982 (diff)
Hotfix/ Candle --log (#5039)
* init... might break stuff * fix * clean up * check if array for color keys
-rw-r--r--frontend-components/plotly/src/App.tsx359
-rw-r--r--frontend-components/plotly/src/components/Chart.tsx1097
-rw-r--r--frontend-components/plotly/src/data/mockup.ts24967
-rw-r--r--openbb_terminal/core/plots/plotly.html208
4 files changed, 11439 insertions, 15192 deletions
diff --git a/frontend-components/plotly/src/App.tsx b/frontend-components/plotly/src/App.tsx
index 632f5351d05..0b899b53b0c 100644
--- a/frontend-components/plotly/src/App.tsx
+++ b/frontend-components/plotly/src/App.tsx
@@ -19,185 +19,186 @@ declare global {
}
function App() {
- const [data, setData] = useState(
- process.env.NODE_ENV === "production" ? null : candlestickMockup
- );
- const [options, setOptions] = useState({});
-
- useEffect(() => {
- if (process.env.NODE_ENV === "production") {
- const interval = setInterval(() => {
- if (window.json_data) {
- const data = window.json_data;
- console.log(data);
- setData(data);
- clearInterval(interval);
- }
- }, 100);
- return () => clearInterval(interval);
- }
- }, []);
-
- const transformData = (data: any) => {
- if (!data) return null;
- let globals = {
- added_traces: [],
- csv_yaxis_id: null,
- cmd_src_idx: null,
- cmd_idx: null,
- cmd_src: "",
- old_margin: null,
- title: "",
- };
- let filename = data.layout?.title?.text
- .replace(/ -/g, "")
- .replace(/-/g, "")
- .replace(/<b>|<\/b>/g, "")
- .replace(/ /g, "_");
- let date = new Date().toISOString().slice(0, 10).replace(/-/g, "");
- let time = new Date().toISOString().slice(11, 19).replace(/:/g, "");
- window.title = `openbb_${filename}_${date}_${time}`;
-
- if (data.layout.annotations != undefined) {
- data.layout.annotations.forEach(function (annotation) {
- if (annotation.text != undefined)
- if (annotation.text[0] == "/") {
- globals.cmd_src = annotation.text;
- globals.cmd_idx = data.layout.annotations.indexOf(annotation);
- annotation.text = "";
-
- let margin = data.layout.margin;
- globals.old_margin = { ...margin };
- if (margin.t != undefined && margin.t > 40) margin.t = 40;
-
- if (data.cmd == "/stocks/candle") margin.r -= 50;
- }
- });
- }
-
- // We add spaces to all trace names, due to Fira Code font width issues
- // to make sure that the legend is not cut off
- data.data.forEach(function (trace) {
- if (trace.name != undefined) {
- const name_length = trace.name.length;
- trace.name = trace.name + " ";
- trace.hoverlabel = {
- namelength: name_length,
- };
- }
- });
-
- let title = data.layout?.title?.text || "Interactive Chart";
- globals.title = title;
- return {
- data: data,
- date: new Date(),
- globals: globals,
- cmd: data.command_location,
- posthog: data.posthog,
- python_version: data.python_version,
- pywry_version: data.pywry_version,
- terminal_version: data.terminal_version,
- theme: data.theme,
- title,
- };
- };
-
- const transformedData = transformData(data);
-
- if (transformedData) {
- if (transformedData.posthog.collect_logs && !options) {
- const opts = {
- api_host: "https://app.posthog.com",
- autocapture: {
- css_selector_allowlist: [".ph-capture"],
- },
- capture_pageview: false,
- loaded: function (posthog: any) {
- const log_id = transformedData?.log_id || "";
-
- if (log_id != "" && log_id != "REPLACE_ME") posthog.identify(log_id);
-
- posthog.onFeatureFlags(function () {
- if (
- !posthog.isFeatureEnabled("record-pywry", { send_event: false })
- )
- posthog.stopSessionRecording();
-
- if (
- !posthog.isFeatureEnabled("collect-logs-pywry", {
- send_event: false,
- })
- )
- posthog.opt_out_capturing();
- else if (posthog.has_opted_out_capturing())
- posthog.opt_in_capturing();
- });
- },
- };
- setOptions(opts);
- }
-
- const info = {
- INFO: {
- command: transformedData.cmd,
- title: transformedData.title,
- date: transformedData.date,
- python_version: transformedData.python_version,
- pywry_version: transformedData.pywry_version,
- terminal_version: transformedData.terminal_version,
- },
- };
-
- const chartDiv = (
- <Chart
- json={transformedData.data}
- date={transformedData.date}
- cmd={transformedData.cmd}
- title={transformedData.title}
- globals={transformedData.globals}
- theme={transformedData.theme}
- info={info}
- />
- );
-
- if (transformedData.posthog.collect_logs && options) {
- return (
- <PostHogProvider
- apiKey="phc_vhssDAMod5qIplznQ75Kdgz4aB1qPFmeVmfEOZ4hkRw"
- options={options}
- >
- {chartDiv}
- </PostHogProvider>
- );
- }
-
- return chartDiv;
- } else
- return (
- <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>
- );
+ const [data, setData] = useState(
+ process.env.NODE_ENV === "production" ? null : candlestickMockup,
+ );
+ const [options, setOptions] = useState({});
+
+ useEffect(() => {
+ if (process.env.NODE_ENV === "production") {
+ const interval = setInterval(() => {
+ if (window.json_data) {
+ const data = window.json_data;
+ console.log(data);
+ setData(data);
+ clearInterval(interval);
+ }
+ }, 100);
+ return () => clearInterval(interval);
+ }
+ }, []);
+
+ const transformData = (data: any) => {
+ if (!data) return null;
+ const globals = {
+ added_traces: [],
+ csv_yaxis_id: null,
+ cmd_src_idx: null,
+ cmd_idx: null,
+ cmd_src: "",
+ old_margin: null,
+ title: "",
+ };
+ const filename = data.layout?.title?.text
+ .replace(/ -/g, "")
+ .replace(/-/g, "")
+ .replace(/<b>|<\/b>/g, "")
+ .replace(/ /g, "_");
+ const date = new Date().toISOString().slice(0, 10).replace(/-/g, "");
+ const time = new Date().toISOString().slice(11, 19).replace(/:/g, "");
+ window.title = `openbb_${filename}_${date}_${time}`;
+
+ if (data.layout.annotations !== undefined) {
+ data.layout.annotations.forEach(function (annotation) {
+ if (annotation.text !== undefined)
+ if (annotation.text[0] === "/") {
+ globals.cmd_src = annotation.text;
+ globals.cmd_idx = data.layout.annotations.indexOf(annotation);
+ annotation.text = "";
+
+ const margin = data.layout.margin;
+ globals.old_margin = { ...margin };
+ if (margin.t !== undefined && margin.t > 40) margin.t = 40;
+
+ if (data.cmd === "/stocks/candle") margin.r -= 50;
+ }
+ });
+ }
+
+ // We add spaces to all trace names, due to Fira Code font width issues
+ // to make sure that the legend is not cut off
+ data.data.forEach(function (trace) {
+ if (trace.name !== undefined) {
+ const name_length = trace.name.length;
+ trace.name = `${trace.name} `;
+ trace.hoverlabel = {
+ namelength: name_length,
+ };
+ }
+ });
+
+ const title = data.layout?.title?.text || "Interactive Chart";
+ globals.title = title;
+ return {
+ data: data,
+ date: new Date(),
+ globals: globals,
+ cmd: data.command_location,
+ posthog: data.posthog,
+ python_version: data.python_version,
+ pywry_version: data.pywry_version,
+ terminal_version: data.terminal_version,
+ theme: data.theme,
+ title,
+ };
+ };
+
+ const transformedData = transformData(data);
+
+ if (transformedData) {
+ if (transformedData.posthog.collect_logs && !options) {
+ const opts = {
+ api_host: "https://app.posthog.com",
+ autocapture: {
+ css_selector_allowlist: [".ph-capture"],
+ },
+ capture_pageview: false,
+ loaded: function (posthog: any) {
+ const log_id = transformedData?.log_id || "";
+
+ if (log_id !== "" && log_id !== "REPLACE_ME")
+ posthog.identify(log_id);
+
+ posthog.onFeatureFlags(function () {
+ if (
+ !posthog.isFeatureEnabled("record-pywry", { send_event: false })
+ )
+ posthog.stopSessionRecording();
+
+ if (
+ !posthog.isFeatureEnabled("collect-logs-pywry", {
+ send_event: false,
+ })
+ )
+ posthog.opt_out_capturing();
+ else if (posthog.has_opted_out_capturing())
+ posthog.opt_in_capturing();
+ });
+ },
+ };
+ setOptions(opts);
+ }
+
+ const info = {
+ INFO: {
+ command: transformedData.cmd,
+ title: transformedData.title,
+ date: transformedData.date,
+ python_version: transformedData.python_version,
+ pywry_version: transformedData.pywry_version,
+ terminal_version: transformedData.terminal_version,
+ },
+ };
+
+ const chartDiv = (
+ <Chart
+ json={transformedData.data}
+ date={transformedData.date}
+ cmd={transformedData.cmd}
+ title={transformedData.title}
+ globals={transformedData.globals}
+ theme={transformedData.theme}
+ info={info}
+ />
+ );
+
+ if (transformedData.posthog.collect_logs && options) {
+ return (
+ <PostHogProvider
+ apiKey="phc_vhssDAMod5qIplznQ75Kdgz4aB1qPFmeVmfEOZ4hkRw"
+ options={options}
+ >
+ {chartDiv}
+ </PostHogProvider>
+ );
+ }
+
+ return chartDiv;
+ } else
+ return (
+ <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"
+ />
+ <path
+ className="opacity-75"
+ fill="currentColor"
+ d="M4 12a8 8 0 018-8v8z"
+ />
+ </svg>
+ </div>
+ );
}
export default App;
diff --git a/frontend-components/plotly/src/components/Chart.tsx b/frontend-components/plotly/src/components/Chart.tsx
index f83da6d388d..da8304a7654 100644
--- a/frontend-components/plotly/src/components/Chart.tsx
+++ b/frontend-components/plotly/src/components/Chart.tsx
@@ -19,499 +19,612 @@ import DownloadFinishedDialog from "./Dialogs/DownloadFinishedDialog";
const Plot = createPlotlyComponent(Plotly);
+function CreateDataXrangeChunks(data: Plotly.PlotData[], xrange?: any) {
+ const chunks = [];
+ let chunk = [];
+ const XDATA = data.filter(
+ (trace) =>
+ trace.x !== undefined && trace.x.length > 0 && trace.x[0] !== undefined,
+ );
+ const xaxis = XDATA[0]?.x ? XDATA[0].x : XDATA[1].x ? XDATA[1].x : [];
+ for (let i = 0; i < xaxis.length; i++) {
+ if (xaxis[i] >= xrange[0] && xaxis[i] <= xrange[1]) {
+ chunk.push(i);
+ } else if (chunk.length > 0) {
+ chunks.push(chunk);
+ chunk = [];
+ }
+ }
+
+ if (chunk.length > 0) chunks.push(chunk);
+ return chunks;
+}
+
+function CreateDataXrange(data: Plotly.PlotData[], xrange?: any) {
+ if (!xrange) {
+ xrange = [
+ data[0]?.x[data[0].x.length - 1000],
+ data[0]?.x[data[0].x.length - 1],
+ ];
+ }
+ const chunks = CreateDataXrangeChunks(data, xrange);
+ const new_data = [];
+ chunks.forEach((chunk) => {
+ data.forEach((trace) => {
+ const new_trace = { ...trace };
+ const data_keys = ["x", "y", "low", "high", "open", "close", "text"];
+ data_keys.forEach((key) => {
+ if (trace[key] && Array.isArray(trace[key])) {
+ new_trace[key] = trace[key].filter((_, i) => chunk.includes(i));
+ }
+ });
+ const color_keys = ["marker", "line"];
+ color_keys.forEach((key) => {
+ if (trace[key]?.color && Array.isArray(trace[key].color)) {
+ new_trace[key] = { ...trace[key] };
+ new_trace[key].color = trace[key].color.filter((_, i) =>
+ chunk.includes(i),
+ );
+ }
+ });
+ new_data.push(new_trace);
+ });
+ });
+
+ if (new_data.length === 0) return data;
+
+ return new_data;
+}
+
+async function DynamicLoad({
+ event,
+ figure,
+}: {
+ event?: any;
+ figure: any;
+}) {
+ try {
+ const XDATA = figure.data.filter(
+ (trace) =>
+ trace.x !== undefined && trace.x.length > 0 && trace.x[0] !== undefined,
+ );
+
+ if (XDATA.length === 0) return figure;
+ // We get the xaxis range, if no event is passed, we get the last 1000 points
+ const xaxis_range = event
+ ? [event["xaxis.range[0]"], event["xaxis.range[1]"]]
+ : [
+ XDATA[0]?.x[XDATA[0].x.length - 1000],
+ XDATA[0]?.x[XDATA[0].x.length - 1],
+ ];
+
+ const new_data = CreateDataXrange(figure.data, xaxis_range);
+ figure.data = new_data;
+ figure.layout.xaxis.range = xaxis_range;
+ return figure;
+ } catch (e) {
+ console.log("error", e);
+ }
+}
+
export default function Chart({
- json,
- date,
- cmd,
- title,
- globals,
- theme,
- info,
+ json,
+ date,
+ cmd,
+ title,
+ globals,
+ theme,
+ info,
}: {
- // @ts-ignore
- json: Figure;
- date: Date;
- cmd: string;
- title: string;
- globals: any;
- theme: string;
- info?: any;
+ // @ts-ignore
+ json: Figure;
+ date: Date;
+ cmd: string;
+ title: string;
+ globals: any;
+ theme: string;
+ info?: any;
}) {
- const posthog = usePostHog();
-
- useEffect(() => {
- if (posthog) posthog.capture("chart", info);
- }, []);
-
- delete json.layout.width;
- delete json.layout.height;
- if (json.layout?.title?.text) {
- 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({ name: "" });
- const [loading, setLoading] = useState(false);
- const [plotDiv, setPlotDiv] = useState(null);
- const [volumeBars, setVolumeBars] = useState({ old_nticks: {} });
- const [maximizePlot, setMaximizePlot] = useState(false);
- const [downloadFinished, setDownloadFinished] = useState(false);
-
- 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 [ohlcAnnotation, setOhlcAnnotation] = useState([]);
-
- const onClose = () => setModal({ name: "" });
-
- // @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,
- ohlcAnnotation,
- setOhlcAnnotation,
- annotations,
- plotDiv,
- });
- }
-
- useEffect(() => {
- if (downloadFinished) {
- setModal({ name: "downloadFinished" });
- setDownloadFinished(false);
- }
- }, [downloadFinished]);
-
- useEffect(() => {
- 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({});
- }
- }, [axesTitles]);
-
- 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")
- );
- const 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 volumeColorsDark = {
- "#009600": "#00ACFF",
- "#c80000": "#e4003a",
- };
- const volumeColorsLight = {
- "#e4003a": "#c80000",
- "#00ACFF": "#009600",
- };
-
- const volumeColors = darkmode ? volumeColorsDark : volumeColorsLight;
-
- 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.export_image is defined
- if (window.export_image != undefined) {
- // 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,
- volumeBars,
- setMaximizePlot,
- });
- 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);
- setVolumeBars(volume_update);
- Plotly.relayout(plotDiv, layout_update);
- }
- });
-
- if (theme !== "dark") {
- setChangeTheme(true);
- }
- }
- }, [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>
- )}
- <div id="loading" className="saving">
- <div id="loading_text" className="loading_text"></div>
- <div id="loader" className="loader"></div>
- </div>
- <OverlayChartDialog
- addOverlay={(overlay) => {
- console.log(overlay);
- plotData.layout.showlegend = true;
- setPlotData(overlay);
- setPlotLoaded(false);
- }}
- plotlyData={plotData}
- setLoading={setLoading}
- open={modal.name === "overlayChart"}
- close={onClose}
- />
- <TitleChartDialog
- updateTitle={(title) => setChartTitle(title)}
- updateAxesTitles={(axesTitles) => setAxesTitles(axesTitles)}
- defaultTitle={chartTitle}
- plotlyData={plotData}
- open={modal.name === "titleDialog"}
- close={onClose}
- />
- <TextChartDialog
- popupData={modal.name === "textDialog" ? modal?.data : null}