mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-30 05:52:23 +00:00
Hotfix/ improve tables/charts (#5228)
* improve charts, tables * update --------- Co-authored-by: James Maslek <jmaslek11@gmail.com>
This commit is contained in:
14
frontend-components/plotly/src/App.tsx
vendored
14
frontend-components/plotly/src/App.tsx
vendored
@@ -19,7 +19,7 @@ declare global {
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [data, setData] = useState(
|
||||
const [json_data, setData] = useState(
|
||||
process.env.NODE_ENV === "production" ? null : candlestickMockup,
|
||||
);
|
||||
const [options, setOptions] = useState({});
|
||||
@@ -28,9 +28,9 @@ function App() {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
const interval = setInterval(() => {
|
||||
if (window.json_data) {
|
||||
const data = window.json_data;
|
||||
console.log(data);
|
||||
setData(data);
|
||||
const plotly_json = window.json_data;
|
||||
console.log(plotly_json);
|
||||
setData(plotly_json);
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 100);
|
||||
@@ -79,10 +79,8 @@ function App() {
|
||||
// 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,
|
||||
namelength: -1,
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -103,7 +101,7 @@ function App() {
|
||||
};
|
||||
};
|
||||
|
||||
const transformedData = transformData(data);
|
||||
const transformedData = transformData(json_data);
|
||||
|
||||
if (transformedData) {
|
||||
if (transformedData.posthog.collect_logs && !options) {
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
//@ts-nocheck
|
||||
import { Figure } from "react-plotly.js";
|
||||
|
||||
export const isoDateRegex = new RegExp(
|
||||
"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}",
|
||||
);
|
||||
|
||||
function merge(target, source) {
|
||||
Object.keys(source).forEach((key) => {
|
||||
if (typeof source[key] === "object") {
|
||||
Object.assign(source[key], merge(target[key], source[key]));
|
||||
}
|
||||
});
|
||||
Object.assign(target || {}, source);
|
||||
return target;
|
||||
}
|
||||
|
||||
export default async function autoScaling(
|
||||
eventdata: Readonly<Plotly.PlotRelayoutEvent>,
|
||||
graphs: Figure,
|
||||
@@ -9,10 +23,19 @@ export default async function autoScaling(
|
||||
if (eventdata["xaxis.range[0]"] !== undefined) {
|
||||
const x_min = eventdata["xaxis.range[0]"];
|
||||
const x_max = eventdata["xaxis.range[1]"];
|
||||
let x0_min = x_min;
|
||||
let x1_max = x_max;
|
||||
|
||||
if (isoDateRegex.test(x_min.replace(" ", "T").split(".")[0])) {
|
||||
x0_min = new Date(x_min.replace(" ", "T").split(".")[0]);
|
||||
x1_max = new Date(x_max.replace(" ", "T").split(".")[0]);
|
||||
}
|
||||
|
||||
const to_update = {};
|
||||
const yaxis_fixedrange = [];
|
||||
let y_min;
|
||||
let y_max;
|
||||
let y_min: number;
|
||||
let y_max: number;
|
||||
let min_xrange: any;
|
||||
|
||||
const get_all_yaxis_traces = {};
|
||||
const get_all_yaxis_annotations = {};
|
||||
@@ -20,12 +43,14 @@ export default async function autoScaling(
|
||||
|
||||
const yaxis_unique = [
|
||||
...new Set(
|
||||
graphs.data.map((trace) => {
|
||||
if (
|
||||
trace.yaxis ||
|
||||
trace.y !== undefined ||
|
||||
trace.type === "candlestick"
|
||||
) {
|
||||
graphs.data.map((trace: Plotly.PlotData) => {
|
||||
if (trace.y !== undefined || trace.type === "candlestick") {
|
||||
if (
|
||||
trace.yaxis === undefined &&
|
||||
trace?.name?.trim() !== "Volume"
|
||||
) {
|
||||
trace.yaxis = "y";
|
||||
}
|
||||
if (trace.type === "bar" && trace?.name?.trim() === "Volume") {
|
||||
volumeTraceYaxis = `yaxis${trace.yaxis.replace("y", "")}`;
|
||||
}
|
||||
@@ -38,7 +63,7 @@ export default async function autoScaling(
|
||||
),
|
||||
];
|
||||
|
||||
graphs.layout.annotations.map((annotation, i) => {
|
||||
graphs.layout.annotations.map((annotation: any, i: number) => {
|
||||
if (annotation.yref !== undefined && annotation.yref !== "paper") {
|
||||
annotation.index = i;
|
||||
const yaxis = `yaxis${annotation.yref.replace("y", "")}`;
|
||||
@@ -73,16 +98,25 @@ export default async function autoScaling(
|
||||
}
|
||||
}
|
||||
|
||||
const yx_values = x.map((x, i) => {
|
||||
let out = null;
|
||||
const isoDateRegex = new RegExp(
|
||||
"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}",
|
||||
);
|
||||
if (isoDateRegex.test(x)) {
|
||||
const x_time = new Date(x).getTime();
|
||||
const x0_min = new Date(x_min.replace(" ", "T")).getTime();
|
||||
const x1_max = new Date(x_max.replace(" ", "T")).getTime();
|
||||
if (x_time >= x0_min && x_time <= x1_max) {
|
||||
const yx_values = x.map(
|
||||
(x: string | number | Date, i: string | number) => {
|
||||
let out = null;
|
||||
|
||||
if (isoDateRegex.test(x.toString())) {
|
||||
const x_time = new Date(x).getTime();
|
||||
if (x_time >= x0_min.getTime() && x_time <= x1_max.getTime()) {
|
||||
if (trace2.y !== undefined && y[i] !== undefined) {
|
||||
out = y[i];
|
||||
}
|
||||
if (trace2.type === "candlestick") {
|
||||
y_candle.push(y_low[i]);
|
||||
y_candle.push(y_high[i]);
|
||||
}
|
||||
if (!min_xrange || x_time < min_xrange) {
|
||||
min_xrange = x_time;
|
||||
}
|
||||
}
|
||||
} else if (x >= x_min && x <= x_max) {
|
||||
if (trace2.y !== undefined) {
|
||||
out = y[i];
|
||||
}
|
||||
@@ -90,28 +124,27 @@ export default async function autoScaling(
|
||||
y_candle.push(y_low[i]);
|
||||
y_candle.push(y_high[i]);
|
||||
}
|
||||
if (!min_xrange || x < min_xrange) {
|
||||
min_xrange = x;
|
||||
}
|
||||
}
|
||||
} else if (x >= x_min && x <= x_max) {
|
||||
if (trace2.y !== undefined) {
|
||||
out = y[i];
|
||||
}
|
||||
if (trace2.type === "candlestick") {
|
||||
y_candle.push(y_low[i]);
|
||||
y_candle.push(y_high[i]);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
});
|
||||
return out;
|
||||
},
|
||||
);
|
||||
|
||||
y_values = y_values.concat(yx_values);
|
||||
});
|
||||
|
||||
y_values = y_values.filter((y2) => y2 !== undefined && y2 !== null);
|
||||
y_values = y_values
|
||||
.flat()
|
||||
.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_candle = y_candle
|
||||
.flat()
|
||||
.filter((y2) => y2 !== undefined && y2 !== null);
|
||||
y_min = Math.min(...y_candle);
|
||||
y_max = Math.max(...y_candle);
|
||||
}
|
||||
@@ -127,12 +160,16 @@ export default async function autoScaling(
|
||||
|
||||
y_min -= y_range * y_mult;
|
||||
y_max += y_range * y_mult;
|
||||
if (to_update[yaxis] === undefined) {
|
||||
to_update[yaxis] = {};
|
||||
}
|
||||
|
||||
if (yaxis === volumeTraceYaxis) {
|
||||
if (graphs.layout[yaxis].tickvals !== undefined) {
|
||||
const range_x = 7;
|
||||
const volume_ticks = org_y_max;
|
||||
let round_digits = -3;
|
||||
// @ts-ignore
|
||||
let first_val = Math.round(volume_ticks * 0.2, round_digits);
|
||||
const x_zipped = [2, 5, 6, 7, 8, 9, 10];
|
||||
const y_zipped = [1, 4, 5, 6, 7, 8, 9];
|
||||
@@ -140,6 +177,7 @@ export default async function autoScaling(
|
||||
for (let i = 0; i < x_zipped.length; i++) {
|
||||
if (String(volume_ticks).length > x_zipped[i]) {
|
||||
round_digits = -y_zipped[i];
|
||||
// @ts-ignore
|
||||
first_val = Math.round(volume_ticks * 0.2, round_digits);
|
||||
}
|
||||
}
|
||||
@@ -151,16 +189,16 @@ export default async function autoScaling(
|
||||
];
|
||||
const 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";
|
||||
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];
|
||||
to_update[`${yaxis}.fixedrange`] = true;
|
||||
to_update[yaxis].range = [y_min, y_max];
|
||||
to_update[yaxis].fixedrange = true;
|
||||
yaxis_fixedrange.push(yaxis);
|
||||
|
||||
if (get_all_yaxis_annotations[yaxis] !== undefined) {
|
||||
@@ -172,14 +210,20 @@ export default async function autoScaling(
|
||||
y_max - y_range * 0.2,
|
||||
);
|
||||
|
||||
to_update[`annotations[${annotation.index}].ay`] = yshift_new;
|
||||
if (to_update.annotations === undefined) {
|
||||
to_update.annotations = graphs.layout.annotations;
|
||||
}
|
||||
|
||||
to_update.annotations[annotation.index].ay = yshift_new;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { to_update, yaxis_fixedrange };
|
||||
graphs.layout = merge(graphs.layout, to_update);
|
||||
|
||||
return { to_update: graphs.layout, yaxis_fixedrange };
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`Error in AutoScaling: ${e}`);
|
||||
|
||||
266
frontend-components/plotly/src/components/Chart.tsx
vendored
266
frontend-components/plotly/src/components/Chart.tsx
vendored
@@ -5,11 +5,10 @@ import * as Plotly from "plotly.js-dist-min";
|
||||
import { Icons as PlotlyIcons } from "plotly.js-dist-min";
|
||||
import { usePostHog } from "posthog-js/react";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import createPlotlyComponent from "react-plotly.js/factory";
|
||||
import { init_annotation } from "../utils/addAnnotation";
|
||||
import { non_blocking, saveImage } from "../utils/utils";
|
||||
import autoScaling from "./AutoScaling";
|
||||
import autoScaling, { isoDateRegex } from "./AutoScaling";
|
||||
import ChangeColor from "./ChangeColor";
|
||||
import { DARK_CHARTS_TEMPLATE, ICONS, LIGHT_CHARTS_TEMPLATE } from "./Config";
|
||||
import AlertDialog from "./Dialogs/AlertDialog";
|
||||
@@ -17,7 +16,7 @@ import DownloadFinishedDialog from "./Dialogs/DownloadFinishedDialog";
|
||||
import OverlayChartDialog from "./Dialogs/OverlayChartDialog";
|
||||
import TextChartDialog from "./Dialogs/TextChartDialog";
|
||||
import TitleChartDialog from "./Dialogs/TitleChartDialog";
|
||||
import { PlotConfig, hideModebar } from "./PlotlyConfig";
|
||||
import { PlotConfig, hideModebar, ChartHotkeys } from "./PlotlyConfig";
|
||||
import ResizeHandler from "./ResizeHandler";
|
||||
|
||||
const Plot = createPlotlyComponent(Plotly);
|
||||
@@ -63,13 +62,43 @@ class PlotComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
function CreateDataXrange(data: Plotly.PlotData[], xrange?: any) {
|
||||
export const getXRange = (min: string, max: string) => {
|
||||
if (isoDateRegex.test(min.replace(" ", "T").split(".")[0])) {
|
||||
const check_min = new Date(min.replace(" ", "T").split(".")[0]);
|
||||
const check_max = new Date(max.replace(" ", "T").split(".")[0]);
|
||||
check_min.setSeconds(0);
|
||||
check_max.setSeconds(0);
|
||||
check_min.setMilliseconds(0);
|
||||
check_max.setMilliseconds(0);
|
||||
|
||||
const multiplier =
|
||||
[5, 0, 1].includes(check_min.getDay()) ||
|
||||
[4, 5, 6].includes(check_max.getDay())
|
||||
? 2
|
||||
: 0;
|
||||
|
||||
const x0_min = new Date(check_min.getTime() - 86400000 * multiplier);
|
||||
const x1_max = new Date(check_max.getTime() + 86400000 * multiplier);
|
||||
|
||||
const xrange = [x0_min.toISOString(), x1_max.toISOString()];
|
||||
return { x0_min, x1_max, xrange };
|
||||
}
|
||||
|
||||
return { x0_min: min, x1_max: max, xrange: [min, max] };
|
||||
};
|
||||
|
||||
function CreateDataXrange(figure: Figure, xrange?: any) {
|
||||
const new_figure = { ...figure };
|
||||
const data = new_figure.data;
|
||||
if (!xrange) {
|
||||
xrange = [
|
||||
data[0]?.x[data[0].x.length - 2000],
|
||||
data[0]?.x[data[0].x.length - 1],
|
||||
];
|
||||
}
|
||||
const { x0_min, x1_max, range } = getXRange(xrange[0], xrange[1]);
|
||||
xrange = range;
|
||||
|
||||
const new_data = [];
|
||||
data.forEach((trace) => {
|
||||
const new_trace = { ...trace };
|
||||
@@ -83,25 +112,22 @@ function CreateDataXrange(data: Plotly.PlotData[], xrange?: any) {
|
||||
"text",
|
||||
"customdata",
|
||||
];
|
||||
const xaxis = trace.x ? trace.x : [];
|
||||
const xaxis: any[] = trace.x ? trace.x : [];
|
||||
const chunks = [];
|
||||
for (let i = 0; i < xaxis.length; i++) {
|
||||
const isoDateRegex = new RegExp(
|
||||
"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}",
|
||||
);
|
||||
if (isoDateRegex.test(xaxis[i])) {
|
||||
const x_time = new Date(xaxis[i]).getTime();
|
||||
const x0_min = new Date(xrange[0].replace(" ", "T")).getTime();
|
||||
const x1_max = new Date(xrange[1].replace(" ", "T")).getTime();
|
||||
if (x_time >= x0_min && x_time <= x1_max) {
|
||||
const xval = xaxis[i];
|
||||
|
||||
if (isoDateRegex.test(xval)) {
|
||||
const x_time = new Date(xval).getTime();
|
||||
if (x_time >= x0_min.getTime() && x_time <= x1_max.getTime()) {
|
||||
chunks.push(i);
|
||||
}
|
||||
} else if (xaxis[i] >= xrange[0] && xaxis[i] <= xrange[1]) {
|
||||
} else if (xval >= xrange[0] && xval <= xrange[1]) {
|
||||
chunks.push(i);
|
||||
}
|
||||
}
|
||||
data_keys.forEach((key) => {
|
||||
if (trace[key] && Array.isArray(trace[key])) {
|
||||
if (trace[key] !== undefined && Array.isArray(trace[key])) {
|
||||
new_trace[key] = trace[key].filter((_, i) => chunks.includes(i));
|
||||
}
|
||||
});
|
||||
@@ -114,12 +140,22 @@ function CreateDataXrange(data: Plotly.PlotData[], xrange?: any) {
|
||||
);
|
||||
}
|
||||
});
|
||||
new_data.push(new_trace);
|
||||
|
||||
if (chunks.length > 0) new_data.push(new_trace);
|
||||
});
|
||||
|
||||
if (new_data.length === 0) return data;
|
||||
if (new_data.length === 0)
|
||||
return {
|
||||
...figure,
|
||||
layout: {
|
||||
...figure.layout,
|
||||
xaxis: { ...figure.layout.xaxis, range: xrange },
|
||||
},
|
||||
};
|
||||
|
||||
return new_data;
|
||||
new_figure.layout.xaxis.range = xrange;
|
||||
new_figure.data = new_data;
|
||||
return new_figure;
|
||||
}
|
||||
|
||||
async function DynamicLoad({
|
||||
@@ -144,14 +180,25 @@ async function DynamicLoad({
|
||||
XDATA[0]?.x[XDATA[0].x.length - 1],
|
||||
];
|
||||
|
||||
figure.data = CreateDataXrange(figure.data, xaxis_range);
|
||||
figure.layout.xaxis.range = xaxis_range;
|
||||
figure = CreateDataXrange(figure, xaxis_range);
|
||||
|
||||
return figure;
|
||||
} catch (e) {
|
||||
console.log("error", e);
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(date) {
|
||||
const d = new Date(date);
|
||||
const month = `${d.getMonth() + 1}`.padStart(2, "0");
|
||||
const day = `${d.getDate()}`.padStart(2, "0");
|
||||
const year = d.getFullYear();
|
||||
const hour = `${d.getHours()}`.padStart(2, "0");
|
||||
const minute = `${d.getMinutes()}`.padStart(2, "0");
|
||||
const second = `${d.getSeconds()}`.padStart(2, "0");
|
||||
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
|
||||
}
|
||||
|
||||
function Chart({
|
||||
json,
|
||||
date,
|
||||
@@ -196,7 +243,7 @@ function Chart({
|
||||
const [downloadFinished, setDownloadFinished] = useState(false);
|
||||
const [dateSliced, setDateSliced] = useState(false);
|
||||
|
||||
const [plotData, setPlotData] = useState(originalData);
|
||||
const [plotData, setPlotDataState] = useState(originalData);
|
||||
const [annotations, setAnnotations] = useState([]);
|
||||
const [changeTheme, setChangeTheme] = useState(false);
|
||||
const [darkMode, setDarkMode] = useState(true);
|
||||
@@ -207,68 +254,17 @@ function Chart({
|
||||
const [ohlcAnnotation, setOhlcAnnotation] = useState([]);
|
||||
const [yaxisFixedRange, setYaxisFixedRange] = useState([]);
|
||||
|
||||
function setPlotData(data: any) {
|
||||
data.layout.datarevision = data.layout.datarevision
|
||||
? data.layout.datarevision + 1
|
||||
: 1;
|
||||
setPlotDataState(data);
|
||||
if (plotDiv && plotData) {
|
||||
Plotly.react(plotDiv, data.data, data.layout);
|
||||
}
|
||||
}
|
||||
|
||||
const onClose = () => setModal({ name: "" });
|
||||
useHotkeys(
|
||||
"ctrl+shift+t",
|
||||
() => {
|
||||
setModal({ name: "titleDialog" });
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+t",
|
||||
() => {
|
||||
setModal({ name: "textDialog" });
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+o",
|
||||
() => {
|
||||
setModal({ name: "overlayChart" });
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
useHotkeys(
|
||||
["ctrl+shift+h", "ctrl+h"],
|
||||
() => {
|
||||
hideModebar();
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+e",
|
||||
() => {
|
||||
changeColor(true);
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+shift+s",
|
||||
async () => {
|
||||
setModal({ name: "downloadCsv" });
|
||||
await downloadCSV(
|
||||
document.getElementById("plotlyChart") as any,
|
||||
downloadFinished,
|
||||
);
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+s",
|
||||
async () => {
|
||||
hideModebar();
|
||||
downloadImage("MainChart", hideModebar, Loading, downloadFinished);
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+w",
|
||||
() => {
|
||||
window.close();
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
const onDeleteAnnotation = useCallback(
|
||||
@@ -314,12 +310,6 @@ function Chart({
|
||||
}
|
||||
}, [downloadFinished]);
|
||||
|
||||
useEffect(() => {
|
||||
if (plotDiv) {
|
||||
Plotly.update(plotDiv, plotData.data, plotData.layout, plotData.config);
|
||||
}
|
||||
}, [plotData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (axesTitles && Object.keys(axesTitles).length > 0) {
|
||||
Object.keys(axesTitles).forEach((k) => {
|
||||
@@ -365,28 +355,27 @@ function Chart({
|
||||
}
|
||||
setModeBarButtons({ ...barButtons, [title]: button });
|
||||
}
|
||||
const debouncedDynamicLoad = useCallback(
|
||||
debounce(async (eventData, figure) => {
|
||||
if (dateSliced) {
|
||||
const data = { ...figure };
|
||||
await DynamicLoad({
|
||||
event: eventData,
|
||||
figure: data,
|
||||
}).then(async (toUpdate) => {
|
||||
setPlotData(toUpdate);
|
||||
Plotly.react(plotDiv, toUpdate.data, toUpdate.layout);
|
||||
const scaled = await autoScaling(eventData, plotDiv);
|
||||
|
||||
const debouncedDynamicLoad = async (eventData, figure) => {
|
||||
if (dateSliced) {
|
||||
const data = { ...figure };
|
||||
DynamicLoad({
|
||||
event: eventData,
|
||||
figure: data,
|
||||
}).then(async (toUpdate) => {
|
||||
autoScaling(eventData, toUpdate).then((scaled) => {
|
||||
if (!scaled.to_update) return;
|
||||
setYaxisFixedRange(scaled.yaxis_fixedrange);
|
||||
Plotly.update(plotDiv, {}, scaled.to_update);
|
||||
setPlotData({ ...toUpdate, layout: scaled.to_update });
|
||||
});
|
||||
} else {
|
||||
const scaled = await autoScaling(eventData, plotDiv);
|
||||
setYaxisFixedRange(scaled.yaxis_fixedrange);
|
||||
Plotly.update(plotDiv, {}, scaled.to_update);
|
||||
}
|
||||
}, 150),
|
||||
[setPlotData, plotDiv, setYaxisFixedRange, dateSliced],
|
||||
);
|
||||
});
|
||||
} else {
|
||||
const scaled = await autoScaling(eventData, figure);
|
||||
if (!scaled.to_update) return;
|
||||
setYaxisFixedRange(scaled.yaxis_fixedrange);
|
||||
setPlotData({ ...figure, layout: scaled.to_update });
|
||||
}
|
||||
};
|
||||
|
||||
const autoscaleButton = useCallback(() => {
|
||||
// We need to check if the button is active or not
|
||||
@@ -398,13 +387,32 @@ function Chart({
|
||||
if (button.style.border === "transparent") {
|
||||
plotDiv.removeAllListeners("plotly_relayout");
|
||||
active = false;
|
||||
plotDiv.on(
|
||||
"plotly_relayout",
|
||||
debounce(async (eventdata) => {
|
||||
if (eventdata["xaxis.range[0]"] === undefined) return;
|
||||
plotDiv.on("plotly_relayout", async (eventdata) => {
|
||||
if (eventdata["xaxis.range[0]"] === undefined) return;
|
||||
const debounceTimer = eventdata["relayout"] ? 0 : 300;
|
||||
if (
|
||||
!eventdata["relayout"] &&
|
||||
isoDateRegex.test(
|
||||
eventdata["xaxis.range[0]"].toString().replace(" ", "T"),
|
||||
)
|
||||
) {
|
||||
const date1 = new Date(eventdata["xaxis.range[0]"].replace(" ", "T"));
|
||||
const date2 = new Date(eventdata["xaxis.range[1]"].replace(" ", "T"));
|
||||
|
||||
if (date2.getTime() - date1.getTime() < 3600000 * 2) {
|
||||
const d1 = new Date(date1.getTime() - 3600000 * 2);
|
||||
const d2 = new Date(date2.getTime() + 3600000 * 2);
|
||||
|
||||
eventdata["xaxis.range[0]"] = formatDate(d1);
|
||||
eventdata["xaxis.range[1]"] = formatDate(d2);
|
||||
eventdata["relayout"] = true;
|
||||
return Plotly.relayout(plotDiv, eventdata);
|
||||
}
|
||||
}
|
||||
debounce(async () => {
|
||||
debouncedDynamicLoad(eventdata, originalData);
|
||||
}, 500),
|
||||
);
|
||||
}, debounceTimer)();
|
||||
});
|
||||
}
|
||||
// If the button isn't active, we remove the listener so
|
||||
// the graphs don't autoscale anymore
|
||||
@@ -420,7 +428,7 @@ function Chart({
|
||||
debounce(async (eventdata) => {
|
||||
if (eventdata["xaxis.range[0]"] === undefined) return;
|
||||
debouncedDynamicLoad(eventdata, originalData);
|
||||
}, 500),
|
||||
}, 300),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -515,7 +523,6 @@ function Chart({
|
||||
? DARK_CHARTS_TEMPLATE
|
||||
: LIGHT_CHARTS_TEMPLATE;
|
||||
setPlotData({ ...originalData });
|
||||
Plotly.react(plotDiv, originalData.data, originalData.layout);
|
||||
setDarkMode(darkmode);
|
||||
setChangeTheme(false);
|
||||
} catch (e) {
|
||||
@@ -643,8 +650,8 @@ function Chart({
|
||||
Please use the zoom tool to see more data.`,
|
||||
},
|
||||
});
|
||||
const new_data = CreateDataXrange(originalData.data);
|
||||
setPlotData({ ...originalData, data: new_data });
|
||||
const new_figure = CreateDataXrange(originalData);
|
||||
setPlotData(new_figure);
|
||||
setDateSliced(true);
|
||||
setAutoScaling(true);
|
||||
}
|
||||
@@ -653,12 +660,12 @@ function Chart({
|
||||
const plotComponent = useMemo(
|
||||
() => (
|
||||
<PlotComponent
|
||||
onInitialized={() => {
|
||||
onInitialized={(_figure, graphDiv) => {
|
||||
if (!plotDiv) {
|
||||
const plot = document.getElementById("plotlyChart");
|
||||
console.log("plot", plot);
|
||||
if (plot) setPlotDiv(plot);
|
||||
plot.globals = globals;
|
||||
if (graphDiv) {
|
||||
graphDiv.globals = globals;
|
||||
setPlotDiv(graphDiv);
|
||||
}
|
||||
}
|
||||
if (!plotLoaded) setPlotLoaded(true);
|
||||
}}
|
||||
@@ -712,7 +719,6 @@ function Chart({
|
||||
overlay.layout.showlegend = true;
|
||||
setOriginalData(overlay);
|
||||
setPlotData(overlay);
|
||||
Plotly.react(plotDiv, overlay.data, overlay.layout);
|
||||
}}
|
||||
plotlyData={originalData}
|
||||
setLoading={setLoading}
|
||||
@@ -767,6 +773,17 @@ function Chart({
|
||||
);
|
||||
}, [modal, onClose]);
|
||||
|
||||
const memoizedChartHotkeys = useMemo(() => {
|
||||
return (
|
||||
<ChartHotkeys
|
||||
setModal={setModal}
|
||||
Loading={setLoading}
|
||||
changeColor={setChangeColor}
|
||||
downloadFinished={setDownloadFinished}
|
||||
/>
|
||||
);
|
||||
}, [setModal, setLoading, setChangeColor, setDownloadFinished]);
|
||||
|
||||
return (
|
||||
<div className="relative h-full">
|
||||
{loading && (
|
||||
@@ -803,6 +820,7 @@ function Chart({
|
||||
{memoizedTextChartDialog}
|
||||
{memoizedChangeColor}
|
||||
{memoizedDownloadFinishedDialog}
|
||||
{memoizedChartHotkeys}
|
||||
|
||||
<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">
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Icons as PlotlyIcons } from "plotly.js-dist-min";
|
||||
import { downloadCSV, downloadImage } from "../utils/utils";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import { ICONS } from "./Config";
|
||||
|
||||
|
||||
export function hideModebar(hide?: boolean) {
|
||||
return new Promise((resolve) => {
|
||||
if (!window.MODEBAR) {
|
||||
@@ -139,3 +141,78 @@ export function PlotConfig({
|
||||
};
|
||||
return CONFIG;
|
||||
}
|
||||
|
||||
|
||||
export function ChartHotkeys({
|
||||
setModal,
|
||||
Loading,
|
||||
changeColor,
|
||||
downloadFinished,
|
||||
}: {
|
||||
setModal: (modal: { name: string; data?: any }) => void;
|
||||
Loading: (change: boolean) => void;
|
||||
changeColor: (change: boolean) => void;
|
||||
downloadFinished: (change: boolean) => void;
|
||||
}) {
|
||||
useHotkeys(
|
||||
"ctrl+shift+t",
|
||||
() => {
|
||||
setModal({ name: "titleDialog" });
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+t",
|
||||
() => {
|
||||
setModal({ name: "textDialog" });
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+o",
|
||||
() => {
|
||||
setModal({ name: "overlayChart" });
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
useHotkeys(
|
||||
["ctrl+shift+h", "ctrl+h"],
|
||||
() => {
|
||||
hideModebar();
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+e",
|
||||
() => {
|
||||
changeColor(true);
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+shift+s",
|
||||
async () => {
|
||||
setModal({ name: "downloadCsv" });
|
||||
await downloadCSV(
|
||||
document.getElementById("plotlyChart") as any,
|
||||
downloadFinished,
|
||||
);
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+s",
|
||||
async () => {
|
||||
hideModebar();
|
||||
downloadImage("MainChart", hideModebar, Loading, downloadFinished);
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
useHotkeys(
|
||||
"ctrl+w",
|
||||
() => {
|
||||
window.close();
|
||||
},
|
||||
{ preventDefault: true },
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user