diff options
author | teh_coderer <me@tehcoderer.com> | 2023-05-07 18:23:40 -0400 |
---|---|---|
committer | teh_coderer <me@tehcoderer.com> | 2023-05-07 18:23:40 -0400 |
commit | e9a9d4e83310deb0140060d7d76a402dc117ba0e (patch) | |
tree | 424df372dd5eebb2ddda6b42338f309a1088949f | |
parent | 24fb86f4e8d5f92dbae095d728f5a057e667a80a (diff) |
bump pywry, fix tables, added ichimoku ta indicator
-rw-r--r-- | build/pyinstaller/.env | 1 | ||||
-rw-r--r-- | frontend-components/plotly/src/App.tsx | 9 | ||||
-rw-r--r-- | frontend-components/plotly/src/main.tsx | 13 | ||||
-rw-r--r-- | frontend-components/tables/src/components/Table/ColumnHeader.tsx | 15 | ||||
-rw-r--r-- | frontend-components/tables/src/utils/utils.ts | 4 | ||||
-rw-r--r-- | openbb_terminal/common/technical_analysis/momentum_view.py | 77 | ||||
-rw-r--r-- | openbb_terminal/core/plots/backend.py | 97 | ||||
-rw-r--r-- | openbb_terminal/core/plots/no_import.py | 3 | ||||
-rw-r--r-- | openbb_terminal/core/plots/plotly.html | 2 | ||||
-rw-r--r-- | openbb_terminal/core/plots/plotly_ta/data_classes.py | 2 | ||||
-rw-r--r-- | openbb_terminal/core/plots/plotly_ta/plugins/momentum_plugin.py | 99 | ||||
-rw-r--r-- | openbb_terminal/core/plots/plotly_ta/ta_class.py | 2 | ||||
-rw-r--r-- | openbb_terminal/core/plots/table.html | 12 | ||||
-rw-r--r-- | openbb_terminal/stocks/technical_analysis/ta_controller.py | 79 | ||||
-rw-r--r-- | poetry.lock | 39 | ||||
-rw-r--r-- | pyproject.toml | 4 | ||||
-rw-r--r-- | requirements-full.txt | 4 | ||||
-rw-r--r-- | requirements.txt | 4 |
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? |