This commit is contained in:
teh_coderer
2023-05-01 10:46:48 -04:00
parent c603a360ec
commit 4100d393de
41 changed files with 28293 additions and 373 deletions

View 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 {};
}

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

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

View 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",
},
},
};

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

View 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;
}

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

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

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;
}

View 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,
};
}