summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorteh_coderer <me@tehcoderer.com>2023-05-07 18:23:40 -0400
committerteh_coderer <me@tehcoderer.com>2023-05-07 18:23:40 -0400
commite9a9d4e83310deb0140060d7d76a402dc117ba0e (patch)
tree424df372dd5eebb2ddda6b42338f309a1088949f
parent24fb86f4e8d5f92dbae095d728f5a057e667a80a (diff)
bump pywry, fix tables, added ichimoku ta indicator
-rw-r--r--build/pyinstaller/.env1
-rw-r--r--frontend-components/plotly/src/App.tsx9
-rw-r--r--frontend-components/plotly/src/main.tsx13
-rw-r--r--frontend-components/tables/src/components/Table/ColumnHeader.tsx15
-rw-r--r--frontend-components/tables/src/utils/utils.ts4
-rw-r--r--openbb_terminal/common/technical_analysis/momentum_view.py77
-rw-r--r--openbb_terminal/core/plots/backend.py97
-rw-r--r--openbb_terminal/core/plots/no_import.py3
-rw-r--r--openbb_terminal/core/plots/plotly.html2
-rw-r--r--openbb_terminal/core/plots/plotly_ta/data_classes.py2
-rw-r--r--openbb_terminal/core/plots/plotly_ta/plugins/momentum_plugin.py99
-rw-r--r--openbb_terminal/core/plots/plotly_ta/ta_class.py2
-rw-r--r--openbb_terminal/core/plots/table.html12
-rw-r--r--openbb_terminal/stocks/technical_analysis/ta_controller.py79
-rw-r--r--poetry.lock39
-rw-r--r--pyproject.toml4
-rw-r--r--requirements-full.txt4
-rw-r--r--requirements.txt4
18 files changed, 349 insertions, 117 deletions
diff --git a/build/pyinstaller/.env b/build/pyinstaller/.env
index 46c4afb6847..e2146f1e0b4 100644
--- a/build/pyinstaller/.env
+++ b/build/pyinstaller/.env
@@ -24,3 +24,4 @@ OPENBB_USE_DATETIME=true
OPENBB_OPEN_REPORT_AS_HTML=true
OPENBB_ENABLE_QUICK_EXIT=false
OPENBB_ENABLE_EXIT_AUTO_HELP=false
+PYWRY_EXECUTABLE=OpenBBPlotsBackend
diff --git a/frontend-components/plotly/src/App.tsx b/frontend-components/plotly/src/App.tsx
index ece111b3147..7ae757a0fa8 100644
--- a/frontend-components/plotly/src/App.tsx
+++ b/frontend-components/plotly/src/App.tsx
@@ -7,7 +7,7 @@ import { plotlyMockup, candlestickMockup } from "./data/mockup";
declare global {
[(Exposed = Window), SecureContext];
interface Window {
- plotly_figure: any;
+ json_data: any;
export_image: string;
save_image: boolean;
title: string;
@@ -27,8 +27,8 @@ function App() {
useEffect(() => {
if (process.env.NODE_ENV === "production") {
const interval = setInterval(() => {
- if (window.plotly_figure) {
- const data = window.plotly_figure;
+ if (window.json_data) {
+ const data = window.json_data;
console.log(data);
setData(data);
clearInterval(interval);
@@ -68,8 +68,7 @@ function App() {
globals.old_margin = { ...margin };
if (margin.t != undefined && margin.t > 40) margin.t = 40;
- margin.l = 70;
- if (data.cmd == "/stocks/candle") margin.r -= 40;
+ if (data.cmd == "/stocks/candle") margin.r -= 50;
}
});
}
diff --git a/frontend-components/plotly/src/main.tsx b/frontend-components/plotly/src/main.tsx
index 9733d2ea77c..a174d829045 100644
--- a/frontend-components/plotly/src/main.tsx
+++ b/frontend-components/plotly/src/main.tsx
@@ -3,20 +3,9 @@ import ReactDOM from "react-dom";
import App from "./App";
import "./index.css";
-declare global {
- interface Window {
- plotly_figure: any;
- export_image: string;
- save_image: boolean;
- title: string;
- Plotly: any;
- MODEBAR: HTMLElement;
- }
-}
-
ReactDOM.render(
<React.StrictMode>
- <App />
+ <App />
</React.StrictMode>,
document.getElementById("root") as HTMLElement
);
diff --git a/frontend-components/tables/src/components/Table/ColumnHeader.tsx b/frontend-components/tables/src/components/Table/ColumnHeader.tsx
index f0126efbf69..38bc97e57cf 100644
--- a/frontend-components/tables/src/components/Table/ColumnHeader.tsx
+++ b/frontend-components/tables/src/components/Table/ColumnHeader.tsx
@@ -5,6 +5,15 @@ import { formatNumberMagnitude, includesDateNames } from "../../utils/utils";
import { useDrag, useDrop } from "react-dnd";
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
+function checkIfNumber(value: string | number | Date) {
+ return (value?.toString().replace(/[^0-9]/g, "") ?? "").trim().length !== 0 &&
+ value
+ ?.toString()
+ .replace(/[^a-zA-Z]/g, "" ?? "")
+ .trim().length === 1
+ ? value
+ : 0;
+}
function Filter({
column,
table,
@@ -20,10 +29,14 @@ function Filter({
row.getValue(column.id)
);
+ const numberValues = values.map((value: string | number | Date) =>
+ checkIfNumber(value)
+ );
+
const areAllValuesString = values.every(
(value: null) => typeof value === "string" || value === null
);
- const areAllValuesNumber = values.every(
+ const areAllValuesNumber = numberValues.every(
(value: null | number | string) =>
typeof value === "number" ||
value === null ||
diff --git a/frontend-components/tables/src/utils/utils.ts b/frontend-components/tables/src/utils/utils.ts
index 72248a166f8..69d3472a0a2 100644
--- a/frontend-components/tables/src/utils/utils.ts
+++ b/frontend-components/tables/src/utils/utils.ts
@@ -48,7 +48,7 @@ export function formatNumberMagnitude(
export function includesDateNames(column: string) {
return ["date", "day", "time", "timestamp", "year"].some((dateName) =>
- column.toLowerCase().includes(dateName)
+ column?.toLowerCase().includes(dateName)
);
}
@@ -62,7 +62,7 @@ export function includesPriceNames(column: string) {
"close",
"high",
"low",
- ].some((priceName) => column.toLowerCase().includes(priceName));
+ ].some((priceName) => column?.toLowerCase().includes(priceName));
}
function loadingOverlay(message?: string, is_close?: boolean) {
diff --git a/openbb_terminal/common/technical_analysis/momentum_view.py b/openbb_terminal/common/technical_analysis/momentum_view.py
index a71b890192f..66e5a5afc42 100644
--- a/openbb_terminal/common/technical_analysis/momentum_view.py
+++ b/openbb_terminal/common/technical_analysis/momentum_view.py
@@ -444,3 +444,80 @@ def display_demark(
)
return fig.show(external=external_axes)
+
+
+# pylint: disable=too-many-arguments,R0913
+@log_start_end(log=logger)
+def display_ichimoku(
+ data: pd.DataFrame,
+ symbol: str = "",
+ conversion_period: int = 9,
+ base_period: int = 26,
+ lagging_line_period: int = 52,
+ displacement: int = 26,
+ export: str = "",
+ sheet_name: Optional[str] = None,
+ external_axes: bool = False,
+) -> Union[None, OpenBBFigure]:
+ """Plots Ichimoku clouds
+
+ Parameters
+ ----------
+ data: pd.DataFrame
+ OHLC data
+ conversion_period: int
+ Conversion line period
+ base_period: int
+ Base line period
+ lagging_line_period: int
+ Lagging line period
+ displacement: int
+ Displacement variable
+ symbol: str
+ Ticker symbol
+ sheet_name: str
+ Optionally specify the name of the sheet the data is exported to.
+ export: str
+ Format to export data
+ external_axes : bool, optional
+ Whether to return the figure object or not, by default False
+
+ Examples
+ --------
+ >>> from openbb_terminal.sdk import openbb
+ >>> df = openbb.stocks.load(symbol="aapl")
+ >>> openbb.ta.ichimoku_clouds(data=df)
+ """
+
+ data = pd.DataFrame(data)
+ data.index.name = "date"
+
+ if ta_helpers.check_columns(data) is None:
+ return None
+
+ ta = PlotlyTA()
+ fig = ta.plot(
+ data,
+ dict(
+ ichimoku=dict(
+ conversion_period=conversion_period,
+ base_period=base_period,
+ lagging_line_period=lagging_line_period,
+ displacement=displacement,
+ )
+ ),
+ f"Ichimoku Clouds for {symbol.upper()}",
+ True,
+ volume=False,
+ )
+
+ export_data(
+ export,
+ os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"),
+ "ichimoku",
+ ta.df_ta,
+ sheet_name,
+ fig,
+ )
+
+ return fig.show(external=external_axes)
diff --git a/openbb_terminal/core/plots/backend.py b/openbb_terminal/core/plots/backend.py
index 7ffc2e2e966..22de1791823 100644
--- a/openbb_terminal/core/plots/backend.py
+++ b/openbb_terminal/core/plots/backend.py
@@ -16,13 +16,16 @@ import plotly.graph_objects as go
from packaging import version
from reportlab.graphics import renderPDF
+# pylint: disable=C0415
try:
- from pywry.core import PyWry
-
- PYWRY_AVAILABLE = True
+ from pywry import PyWry
except ImportError as e:
print(f"\033[91m{e}\033[0m")
- PYWRY_AVAILABLE = False
+ from openbb_terminal.core.plots.no_import import DummyBackend
+
+ class PyWry(DummyBackend):
+ pass
+
from svglib.svglib import svg2rlg
@@ -30,9 +33,6 @@ from openbb_terminal.base_helpers import console
from openbb_terminal.core.session.current_system import get_current_system
from openbb_terminal.core.session.current_user import get_current_user
-if not PYWRY_AVAILABLE:
- from openbb_terminal.core.plots.no_import import DummyBackend as PyWry # noqa
-
try:
from IPython import get_ipython
@@ -109,11 +109,11 @@ class Backend(PyWry):
self.init_engine: list = []
return pending
- def get_plotly_html(self) -> str:
+ def get_plotly_html(self) -> Path:
"""Get the plotly html file."""
self.set_window_dimensions()
if self.plotly_html.exists():
- return str(self.plotly_html)
+ return self.plotly_html
console.print(
"[bold red]plotly.html file not found, check the path:[/]"
@@ -122,11 +122,11 @@ class Backend(PyWry):
self.max_retries = 0 # pylint: disable=W0201
raise FileNotFoundError
- def get_table_html(self) -> str:
+ def get_table_html(self) -> Path:
"""Get the table html file."""
self.set_window_dimensions()
if self.table_html.exists():
- return str(self.table_html)
+ return self.table_html
console.print(
"[bold red]table.html file not found, check the path:[/]"
f"[green]{PLOTS_CORE_PATH / 'table.html'}[/]"
@@ -134,12 +134,12 @@ class Backend(PyWry):
self.max_retries = 0 # pylint: disable=W0201
raise FileNotFoundError
- def get_window_icon(self) -> str:
+ def get_window_icon(self) -> Optional[Path]:
"""Get the window icon."""
icon_path = PLOTS_CORE_PATH / "assets" / "Terminal_icon.png"
if icon_path.exists():
- return str(icon_path)
- return ""
+ return icon_path
+ return None
def get_json_update(self, cmd_loc: str, theme: Optional[str] = None) -> dict:
"""Get the json update for the backend."""
@@ -201,16 +201,14 @@ class Backend(PyWry):
json_data.update(self.get_json_update(command_location))
- self.outgoing.append(
- json.dumps(
- {
- "html_path": self.get_plotly_html(),
- "json_data": json_data,
- "export_image": str(export_image),
- **self.get_kwargs(command_location),
- }
- )
+ outgoing = dict(
+ html_path=self.get_plotly_html(),
+ json_data=json_data,
+ export_image=export_image,
+ **self.get_kwargs(command_location),
)
+ self.send_outgoing(outgoing)
+
if export_image and isinstance(export_image, Path):
self.loop.run_until_complete(self.process_image(export_image))
@@ -300,35 +298,14 @@ class Backend(PyWry):
)
)
- self.outgoing.append(
- json.dumps(
- {
- "html_path": self.get_table_html(),
- "json_data": json.dumps(json_data),
- "width": width,
- "height": self.HEIGHT - 100,
- **self.get_kwargs(command_location),
- }
- )
- )
-
- def send_html(self, html_str: str = "", html_path: str = "", title: str = ""):
- """Send HTML to the backend to be displayed in a window.
-
- Parameters
- ----------
- html_str : str
- HTML string to send to backend.
- html_path : str, optional
- Path to html file to send to backend, by default ""
- title : str, optional
- Title to display in the window, by default ""
- """
- self.loop.run_until_complete(self.check_backend())
- message = json.dumps(
- {"html_str": html_str, "html_path": html_path, **self.get_kwargs(title)}
+ outgoing = dict(
+ html_path=self.get_table_html(),
+ json_data=json.dumps(json_data),
+ width=width,
+ height=self.HEIGHT - 100,
+ **self.get_kwargs(command_location),
)
- self.outgoing.append(message)
+ self.send_outgoing(outgoing)
def send_url(
self,
@@ -356,15 +333,13 @@ class Backend(PyWry):
window.location.replace("{url}");
</script>
"""
- message = json.dumps(
- {
- "html_str": script,
- **self.get_kwargs(title),
- "width": width or self.WIDTH,
- "height": height or self.HEIGHT,
- }
+ outgoing = dict(
+ html_str=script,
+ **self.get_kwargs(title),
+ width=width or self.WIDTH,
+ height=height or self.HEIGHT,
)
- self.outgoing.append(message)
+ self.send_outgoing(outgoing)
def get_kwargs(self, title: str = "") -> dict:
"""Get the kwargs for the backend."""
@@ -383,7 +358,7 @@ class Backend(PyWry):
"""Override to check if isatty."""
if self.isatty:
message = (
- "[bold red]PyWry version 0.3.5 or higher is required to use the "
+ "[bold red]PyWry version 0.5.2 or higher is required to use the "
"OpenBB Plots backend.[/]\n"
"[yellow]Please update pywry with 'pip install pywry --upgrade'[/]"
)
@@ -398,7 +373,7 @@ class Backend(PyWry):
PyWry.__version__ = pywry_version # pylint: disable=W0201
- if version.parse(PyWry.__version__) < version.parse("0.3.5"):
+ if version.parse(PyWry.__version__) < version.parse("0.5.2"):
console.print(message)
self.max_retries = 0 # pylint: disable=W0201
return
diff --git a/openbb_terminal/core/plots/no_import.py b/openbb_terminal/core/plots/no_import.py
index c1b631d2306..22478d9b04a 100644
--- a/openbb_terminal/core/plots/no_import.py
+++ b/openbb_terminal/core/plots/no_import.py
@@ -84,6 +84,9 @@ class DummyBackend:
def start(self, debug: bool = False): # pylint: disable=W0613
pass
+ def send_outgoing(self, outgoing: dict):
+ pass
+
async def check_backend(self):
"""Dummy check backend method to avoid errors and revert to browser."""
raise Exception
diff --git a/openbb_terminal/core/plots/plotly.html b/openbb_terminal/core/plots/plotly.html
index f44654fce2f..79dbfa3abd8 100644
--- a/openbb_terminal/core/plots/plotly.html
+++ b/openbb_terminal/core/plots/plotly.html
@@ -3698,7 +3698,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
`).map(N=>N.replace(/\r/g,""));const C=A[0].split(","),L=C.map(N=>N.trim().toLowerCase());let O={};for(let N=0;N<c.length;N++)L.includes(c[N])?O[c[N]]=C[L.indexOf(c[N])]:c[N]=="x"&&L.includes("date")&&(O[c[N]]=C[L.indexOf("date")]);["open","high","low","close"].every(N=>L.includes(N))&&E("candlestick"),L.includes("close")&&(v({...d,y:C[L.indexOf("close")]}),O.y=C[L.indexOf("close")]);let U=[];for(let N=1;N<A.length;N++){let j={},V=A[N].split(",");for(let H=0;H<C.length;H++)j[C[H]]=V[H];U.push(j)}let B=m.target.files[0].name.split(".")[0];try{if(B.includes("_")){let N=B.split("_"),j=new RegExp("^[0-9]{8}$");N.length>2&&(j.test(N[0])?N.splice(0,2):j.test(N[N.length-2])&&N.splice(N.length-2,2),B=N.join("_").replace(/openbb_/g,""))}}catch(N){console.log(N)}l(B),v(O),r(C),i(U)},Fb.readAsText(m.target.files[0])},type:"file",id:"csv_file",accept:".csv",style:{marginLeft:10}})]}),ni("div",{style:{marginTop:15},children:[br("label",{htmlFor:"csv_trace_type",children:br("b",{children:"Display data type:"})}),ni("select",{onChange:m=>{E(m.target.value)},id:"csv_trace_type",style:Rw,defaultValue:b[D],children:[D&&br("option",{value:D,children:b[D]},D),Object.keys(b).map(m=>D!==m&&br("option",{value:m,children:b[m]},m))]})]}),ni("div",{style:{marginTop:12},children:[br("label",{htmlFor:"csv_name",children:br("b",{children:"Trace Name:"})}),br("textarea",{id:"csv_name",value:f,onChange:m=>{l(m.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"})]}),n.length>0&&ni(tg,{children:[["scatter","bar"].includes(D)&&br("div",{style:{marginTop:15,marginBottom:10},id:"csv_columns",className:"csv_column_container",children:["x","y"].map(m=>ni("div",{style:{marginTop:10,display:"flex",alignItems:"center",justifyContent:"space-between"},children:[ni("label",{htmlFor:`csv_${m}`,style:{width:"100px"},children:[m.toUpperCase()," Axis"]}),ni("select",{onChange:k=>{a({...o,[m]:k.target.value}),v({...d,[m]:k.target.value})},id:`csv_${m}`,style:{width:"100%"},defaultValue:d[m],children:[d[m]&&br("option",{value:d[m],children:d[m]},m),n.map(k=>br("option",{value:k,children:k},k))]})]},m))}),D==="candlestick"&&br("div",{id:"csv_columns",className:"csv_column_container",style:{marginTop:15},children:["x","open","high","low","close"].map(m=>ni("div",{style:{marginTop:10,display:"flex",alignItems:"center",justifyContent:"space-between"},children:[br("label",{htmlFor:`csv_${m}`,style:{width:"100px"},children:m.charAt(0).toUpperCase()+m.slice(1)}),ni("select",{onChange:k=>{a({...o,[m]:k.target.value}),v({...d,[m]:k.target.value})},id:`csv_${m}`,style:{width:"100%"},children:[d[m]&&br("option",{value:d[m],children:d[m]},m),n.map(k=>k!=d[k]&&br("option",{value:k,children:k},k))]})]},m))}),ni("div",{style:{marginTop:20},id:"csv_colors",children:[["scatter","bar"].includes(D)&&ni("div",{children:[br("label",{htmlFor:"csv_color",children:`${D.charAt(0).toUpperCase()}${D.slice(1)} color`}),br("input",{type:"color",id:"csv_color",defaultValue:"#FFDD00",style:{margin:"2px 2px 2px 10px"},onChange:m=>{console.log(m.target.value),_(m.target.value)}})]}),D==="candlestick"&&ni(tg,{children:[br("label",{htmlFor:"csv_increasing",children:"Increasing color"}),br("input",{type:"color",id:"csv_increasing",defaultValue:"#00ACFF",style:{margin:"2px 0px 2px 10px"},onChange:m=>{s(m.target.value)}}),br("label",{htmlFor:"csv_decreasing",style:{marginLeft:15},children:"Decreasing color"}),br("input",{style:{margin:"2px 0px 2px 10px"},type:"color",id:"csv_decreasing",defaultValue:"#FF0000",onChange:m=>{y(m.target.value)}})]})]}),ni("div",{style:{marginTop:20},id:"csv_plot_yaxis_options",children:[D!=="candlestick"&&ni(tg,{children:[br("input",{type:"checkbox",id:"csv_percent_change",name:"csv_plot_yaxis_check",style:{marginBottom:2},onChange:m=>{p({...u,percentChange:m.target.checked,sameYaxis:!1})},checked:!u.sameYaxis&&u.percentChange}),br("label",{htmlFor:"csv_percent_change",style:{marginLeft:5},children:"Plot as percent change from first value"}),br("br",{})]}),br("input",{style:{marginTop:2},type:"checkbox",id:"csv_same_yaxis",name:"csv_plot_yaxis_check",onChange:m=>{p({...u,sameYaxis:m.target.checked,percentChange:!1})},checked:!u.percentChange&&u.sameYaxis}),br("label",{htmlFor:"csv_same_yaxis",style:{marginLeft:5},children:"Share Y-axis"}),D==="bar"&&ni("div",{style:{marginTop:2},id:"csv_bar_orientation",children:[br("input",{type:"checkbox",id:"csv_bar_horizontal",onChange:m=>{v({...d,orientation:m.target.checked?"h":"v"})}}),br("label",{htmlFor:"csv_bar_horizontal",style:{marginLeft:5},children:"Plot horizontally"})]})]})]}),br("br",{}),ni("div",{style:{float:"right",marginTop:20},children:[br("button",{className:"_btn-tertiary",id:"csv_cancel",onClick:x,children:"Cancel"}),br("button",{className:"_btn",id:"csv_submit",onClick:g,children:"Submit"})]})]})})}function F7({csvData:P,plotlyData:F,yaxisOptions:J,traceType:ce,traceColor:Pe,traceName:D,options:E,increasingColor:e,decreasingColor:_}){let w=F.data[0];w.xaxis==null&&(w.xaxis="x"),w.yaxis==null&&(w.yaxis="y");let s=w.yaxis,T,f=Object.keys(F.layout).filter(n=>n.startsWith("yaxis")).map(n=>F.layout[n]).filter(n=>n.side=="left"&&(n.overlaying=="y"||n.fixedrange!=null&&n.fixedrange==!0)).length>0?" ":"";if(J.sameYaxis!==!0){const n=Object.keys(F.layout).filter(r=>r.startsWith("yaxis")).map(r=>F.layout[r]);T=`y${n.length+1}`,s=`yaxis${n.length+1}`,console.log(`yaxis: ${T} ${s}`),F.layout[s]={...z7,title:{text:D,font:{size:14},standoff:0},ticksuffix:f,layer:"below traces"}}else T=w.yaxis.replace("yaxis","y");const l={type:ce,name:D,showlegend:!0,yaxis:T};let t={};if(["scatter","bar"].includes(ce)){const n=P.findIndex(r=>r[E.y]!=null&&r[E.y]!=0);t={...l,x:P.map(r=>r[E.x]),y:P.map(function(r){return J.percentChange&&ce==="scatter"?(r[E.y]-P[n][E.y])/P[n][E.y]:r[E.y]}),customdata:P.map(r=>r[E.y]),hovertemplate:"%{customdata:.2f}<extra></extra>",connectgaps:!0,marker:{color:Pe}},ce==="bar"&&(t.orientation=E.orientation,t.marker.opacity=.7,delete t.connectgaps,delete t.hovertemplate,delete t.customdata)}else ce==="candlestick"&&(t={...l,x:P.map(n=>n[E.x]),open:P.map(n=>n[E.open]),high:P.map(n=>n[E.high]),low:P.map(n=>n[E.low]),close:P.map(n=>n[E.close]),increasing:{line:{color:e}},decreasing:{line:{color:_}}});return{...F,data:[...F.data,t]}}function B7({plotlyData:P,open:F,close:J,defaultTitle:ce,updateTitle:Pe,updateAxesTitles:D}){const[E,e]=Un.useState(ce),_=Object.keys(P.layout||{}).filter(y=>y.startsWith("yaxis")&&P.layout[y].range!=null),w=Object.keys(P.layout||{}).filter(y=>{var f;return y.startsWith("xaxis")&&P.layout[y].showticklabels!=null&&((f=P.layout[y])==null?void 0:f.anchor)}),[s,T]=Un.useState({});return br(h1,{title:"Chart Titles",description:"Change the titles on the chart.",open:F,close:J,children:ni("div",{id:"popup_title",className:"popup_content",children:[ni("div",{style:{display:"flex",flexDirection:"column",gap:0},children:[ni("div",{children:[br("label",{htmlFor:"title_text",children:br("b",{children:"Title:"})}),br("textarea",{id:"title_text",style:{...Rw,width:"100%",maxWidth:"100%",maxHeight:"200px",marginTop:"8px",marginLeft:"0px"},rows:2,cols:20,value:E,onChange:y=>e(y.target.value)})]}),br("div",{id:"xaxis_div",className:"csv_column_container",style:{marginTop:5,marginBottom:-5},children:w.map((y,f)=>{var l,t;return ni("div",{style:{marginTop:5,marginBottom:5},children:[br("label",{htmlFor:`title_${y}`,children:f===0?br("b",{children:"X axis:"}):ni("b",{children:["X axis ",f+1,":"]})}),br("input",{id:`title_${y}`,style:{marginLeft:"0px",padding:"5px 2px 2px 5px"},type:"text",defaultValue:((t=(l=P==null?void 0:P.layout[y])==null?void 0:l.title)==null?void 0:t.text)||"",onChange:i=>{T({...s,[y]:i.target.value})}})]},y)})}),br("div",{id:"yaxis_div",className:"csv_column_container",style:{marginTop:5,marginBottom:5},children:_.map((y,f)=>{var l,t;return ni("div",{style:{marginTop:10},children:[br("label",{htmlFor:`title_${y}`,children:f===0?br("b",{children:"Y axis:"}):ni("b",{children:["Y axis ",f+1,":"]})}),br("input",{id:`title_${y}`,style:{marginLeft:"0px",padding:"5px 2px 2px 5px"},type:"text",defaultValue:((t=(l=P==null?void 0:P.layout[y])==null?void 0:l.title)==null?void 0:t.text)||"",onChange:i=>{T({...s,[y]:i.target.value})}})]},y)})})]}),ni("div",{style:{float:"right",marginTop:20},children:[br("button",{className:"_btn-tertiary ph-capture",id:"title_cancel",onClick:J,children:"Cancel"}),br("button",{className:"_btn ph-capture",id:"title_submit",onClick:()=>{Pe(E),D(s),J()},children:"Submit"})]})]})})}const Bb={padding:"5px 2px 2px 5px",margin:"2px 0"};function N7({open:P,close:F,addAnnotation:J,deleteAnnotation:ce,popupData:Pe}){var l,t,i,n,r;const D={text:"",color:"#0088CC",size:18,bordercolor:"#822661",yanchor:"above"},[E,e]=Un.useState(D),[_,w]=Un.useState(D);Pe&&Pe!==E&&Pe.annotation&&(Pe.annotation=(Pe==null?void 0:Pe.annotation)||{},e(Pe),w(Pe));function s(){console.log("closing"),e(D),w(D),F()}function T(o){console.log(o.target.id.replace("addtext_",""),o.target.value);const a=o.target.id.replace("addtext_",""),u=o.target.value;w({..._,[a]:u})}function y(){console.log("submitting",_),_.text!==""?(E!=null&&E.annotation&&w({..._,annotation:E.annotation}),J(_),F()):(document.getElementById("popup_textarea_warning").style.display="block",document.getElementById("addtext_text").style.border="1px solid red")}function f(){ce(E),s()}return br(h1,{title:"Add Text to Chart",description:"Change the titles on the chart.",open:P,close:s,children:ni("div",{id:"popup_title",className:"popup_content",children:[ni("div",{style:{display:"flex",flexDirection:"column",gap:6},children:[ni("div",{style:{marginBottom:20},children:[ni("label",{htmlFor:"popup_text",children:[br("b",{children:"Text:"}),br("div",{id:"popup_textarea_warning",className:"popup_warning",children:"Text is required"})]}),br("textarea",{id:"addtext_text",style:{...Bb,width:"100%",maxWidth:"100%",maxHeight:"200px",marginTop:"8px"},rows:4,cols:50,placeholder:"Enter text here",onChange:T,defaultValue:((l=E==null?void 0:E.annotation)==null?void 0:l.text)||(_==null?void 0:_.text)})]}),ni("div",{style:{display:"flex",gap:15,alignItems:"center",flexWrap:"wrap",columnCount:2,justifyContent:"space-between",marginBottom:20},children:[br("label",{htmlFor:"addtext_color",children:br("b",{children:"Font color"})}),br("input",{type:"color",id:"addtext_color",style:{margin:"2px 2px 2px 15px"},defaultValue:((t=E==null?void 0:E.annotation)==null?void 0:t.color)||(_==null?void 0:_.color),onChange:T}),br("label",{htmlFor:"addtext_bordercolor",style:{marginLeft:20},children:br("b",{children:"Border color"})}),br("input",{type:"color",id:"addtext_bordercolor",style:{margin:"2px 2px 10px 15px"},defaultValue:((i=E==null?void 0:E.annotation)==null?void 0:i.bordercolor)||(_==null?void 0:_.bordercolor),onChange:T}),br("label",{htmlFor:"addtext_size",children:br("b",{children:"Font size"})}),br("input",{style:{...Bb,width:"52px",margin:"0px 0px 0px 2px"},type:"number",id:"addtext_size",onChange:T,defaultValue:((n=E==null?void 0:E.annotation)==null?void 0:n.size)||(_==null?void 0:_.size)}),ni("div",{children:[br("label",{htmlFor:"addtext_yanchor",style:{marginRight:31},children:br("b",{children:"Position"})}),ni("select",{id:"addtext_yanchor",name:"yanchor",style:{width:"100px"},defaultValue:((r=E==null?void 0:E.annotation)==null?void 0:r.yanchor)||(_==null?void 0:_.yanchor),onChange:T,children:[br("option",{value:"above",children:"Above"}),br("option",{value:"below",children:"Below"})]})]})]})]}),ni("div",{style:{float:"right",marginTop:20},children:[br("button",{className:"_btn-tertiary ph-capture",id:"title_cancel",onClick:s,children:"Cancel"}),br("button",{className:"_btn ph-capture",id:"title_delete",onClick:f,children:"Delete"}),br("button",{className:"_btn ph-capture",id:"title_submit",onClick:y,children:"Submit"})]})]})})}function j7({plotData:P,popup_data:F,current_text:J}){var _;let ce=F.x,Pe=F.y,D=F.yref,E=((_=P==null?void 0:P.layout)==null?void 0:_.annotations)||[],e=-1;for(let w=0;w<E.length;w++)if(E[w].x==ce&&E[w].y==Pe&&E[w].text==J){e=w;break}if(F.high!=null&&(Pe=F.yanchor=="above"?F.high:F.low),e==-1){let w={x:ce,y:Pe,xref:"x",yref:D,xanchor:"center",text:F.text,showarrow:!0,arrowhead:2,arrowsize:1,arrowwidth:2,ax:ce,ay:Pe+F.yshift,ayref:D,axref:"x",bordercolor:F.bordercolor,bgcolor:"#000000",borderwidth:2,borderpad:4,opacity:.8,font:{color:F.color,size:F.size},clicktoshow:"onoff",captureevents:!0,high:F.high||void 0,low:F.low||void 0};E.push(w)}else E[e].y=Pe,E[e].text=F.text,E[e].font.color=F.color,E[e].font.size=F.size,E[e].ay=Pe+F.yshift,E[e].bordercolor=F.bordercolor,E[e].high=F.high||void 0,E[e].low=F.low||void 0;return{annotations:E,annotation:E[e]}}function Nb({plotData:P,popup_data:F,current_text:J}){console.log("plot_text: current_text",J);let ce,Pe=F.yref.replace("y","yaxis"),D=P.layout[Pe].range,E=(D[1]-D[0])*.2;F.yanchor=="below"&&(E=-E),F.yshift=E,ce=j7({plotData:P,popup_data:F,current_text:J});let e={annotations:ce.annotations,dragmode:"pan"};return e[Pe+".type"]="linear",{update:e,annotation:ce.annotation}}function U7({plotData:P,popupData:F,setPlotData:J,setModal:ce,setOnAnnotationClick:Pe,setAnnotations:D,onAnnotationClick:E,ohlcAnnotation:e,setOhlcAnnotation:_,annotations:w,plotDiv:s}){if(F.text!=null&&F.text!=""){let T=function(l){var u,p,c,b,d,v;console.log("plotly_click",l);let t=l.points[0].x,i=l.points[0].fullData.yaxis,n=0,r,o;l.points[0].y!=null?n=l.points[0].y:l.points[0].low!=null&&(r=l.points[0].high,o=l.points[0].low,(y==null?void 0:y.yanchor)=="below"?n=l.points[0].low:n=l.points[0].high),y={x:((u=E==null?void 0:E.annotation)==null?void 0:u.x)??t,y:((p=E==null?void 0:E.annotation)==null?void 0:p.y)??n,yref:((c=E==null?void 0:E.annotation)==null?void 0:c.yref)??i,high:((b=E==null?void 0:E.annotation)==null?void 0:b.high)??r,low:((d=E==null?void 0:E.annotation)==null?void 0:d.low)??o,...F},r!=null&&(e.push(y),_(e),console.log("ohlcAnnotation",e));let a=Nb({plotData:P,popup_data:y,current_text:(v=E==null?void 0:E.annotation)==null?