diff --git a/dashboard.py b/dashboard.py index bbcde17..7fa0a20 100644 --- a/dashboard.py +++ b/dashboard.py @@ -29,7 +29,7 @@ from GHA_triaxial.gha2_approx import gha2_approx def _no_print(*args, **kwargs): pass -#builtins.print = _no_print +builtins.print = _no_print # Bootstrap (CSS) einbindung @@ -49,15 +49,28 @@ def inputfeld(left_text, input_id, right_text="", width=200, min=None, max=None) placeholder=f"{left_text}...[{right_text}]", min=min, max=max, + step="any", style={"width": width, "display": "block"}, persistence=True, - persistence_type="memory", # oder "session"/"local" + persistence_type="memory", ), html.Span(right_text, style={"marginLeft": 5}) if right_text else html.Span() ], style={"display": "flex", "alignItems": "center", "marginBottom": "10px"}, ) +# Positionierung der Input-Felder +def input_grid(*children, cols=2): + return html.Div( + list(children), + style={ + "display": "grid", + "gridTemplateColumns": f"repeat({cols}, minmax(0, 1fr))", + "columnGap": "1.2vw", + "rowGap": "0.6vh", + }, + ) + # Erzeugen der Checklisten inkl. Eingabefelder def method_row(label, cb_id, input_id=None, value="", info="", input_id2=None, value2="", info2="",): base_row_style = { @@ -329,10 +342,31 @@ def figure_lines(fig, line, name, color): # Tab 1 pane_gha1 = html.Div( [ - inputfeld("β₀", "input-GHA1-beta0", "°"), - inputfeld("λ₀", "input-GHA1-lamb0", "°"), - inputfeld("s", "input-GHA1-s", "m"), - inputfeld("α₀", "input-GHA1-a", "°"), + html.Div( + [ + html.Div( + [ + inputfeld("β₀", "input-GHA1-beta0", "°"), + inputfeld("λ₀", "input-GHA1-lamb0", "°"), + ], + style={"flex": "1"}, + ), + html.Div( + [ + inputfeld("s", "input-GHA1-s", "m"), + inputfeld("α₀", "input-GHA1-a", "°"), + ], + style={"flex": "1"}, + ), + ], + style={ + "display": "flex", + "gap": "1%", + "alignItems": "flex-start", + "flexWrap": "wrap", + }, + ), + method_row("Analytisch", "cb-ana-1", "input-ana-1", "70", info="Ordnung", input_id2="input-ana-1-2", value2="4", info2="max. Unterteilung"), method_row("Numerisch", "cb-num-1", "input-num-n-1", "2000", info="Anzahl Schritte"), @@ -373,10 +407,31 @@ pane_gha1 = html.Div( # Tab2 pane_gha2 = html.Div( [ - inputfeld("β₀", "input-GHA2-beta0", "°"), - inputfeld("λ₀", "input-GHA2-lamb0", "°"), - inputfeld("β₁", "input-GHA2-beta1", "°"), - inputfeld("λ₁", "input-GHA2-lamb1", "°"), + html.Div( + [ + html.Div( + [ + inputfeld("β₀", "input-GHA2-beta0", "°"), + inputfeld("λ₀", "input-GHA2-lamb0", "°"), + ], + style={"flex": "1"}, + ), + html.Div( + [ + inputfeld("β₁", "input-GHA2-beta1", "°"), + inputfeld("λ₁", "input-GHA2-lamb1", "°"), + ], + style={"flex": "1"}, + ), + ], + style={ + "display": "flex", + "gap": "1%", + "alignItems": "flex-start", + "flexWrap": "wrap", + }, + ), + method_row("Numerisch", "cb-num-2", "input-num-n-2", "2000", info="Anzahl Schritte"), method_row("Stochastisch", "cb-stoch-2", "input-stoch-n-2", "1000", info="Länge Streckensegment [m]"), @@ -415,8 +470,8 @@ pane_gha2 = html.Div( app.layout = html.Div( style={"fontFamily": "Arial", "padding": "10px", "width": "95%", "margin": "0 auto"}, children=[ - html.H1("Geodätische Hauptaufgaben"), - html.H2("für dreiachsige Ellipsoide"), + html.H2("Geodätische Hauptaufgaben für dreiachsige Ellipsoide"), + #html.H2("für dreiachsige Ellipsoide"), html.Div( style={ @@ -451,11 +506,22 @@ app.layout = html.Div( style={"width": "300px", "marginBottom": "16px"}, ), - html.P("Halbachsen:", style={"marginBottom": "10px"}), - inputfeld("aₓ", "input-ax", "m"), - inputfeld("aᵧ", "input-ay", "m"), - inputfeld("b", "input-b", "m"), - html.Div(id="axes-error", style={"marginLeft": "10px", "marginTop": "5px", "marginBottom": "10px"}), + html.P("Halbachsen:", style={"marginBottom": "0.8vh"}), + + html.Div( + [ + inputfeld("aₓ", "input-ax", "m", min=0, width="clamp(80px, 7vw, 200px)"), + inputfeld("aᵧ", "input-ay", "m", min=0, width="clamp(80px, 7vw, 200px)"), + inputfeld("b", "input-b", "m", min=0, width="clamp(80px, 7vw, 200px)"), + ], + style={ + "display": "grid", + "gridTemplateColumns": "repeat(auto-fit, minmax(200px, 1fr))", + "columnGap": "0.2vw", + "rowGap": "0.6vh", + "alignItems": "center", + }, + ), #html.Br(), @@ -481,10 +547,10 @@ app.layout = html.Div( "minWidth": "520px", "position": "sticky", "top": "0", - "marginTop": "-50px", + "marginTop": "0px", }, children=[ - html.Label("Koordinatenart wählen:", style={"marginLeft": "80px"},), + html.Label("Koordinatenart wählen:", style={"marginLeft": "5%"},), dcc.Dropdown( id="dropdown-coors-type", options=[ @@ -495,11 +561,11 @@ app.layout = html.Div( ], value="ell", clearable=False, - style={"width": "200px", "marginLeft": "80px"}, + style={"width": "200px", "marginLeft": "5%"}, ), dcc.Graph( id="ellipsoid-plot", - style={"height": "85vh", "width": "100%"}, + style={"height": "80vh", "width": "100%"}, config={"responsive": True} ) ], @@ -601,80 +667,222 @@ def toggle_ds(v): # Abfrage ob Berechnungsverfahren gewählt +from dash.exceptions import PreventUpdate +from dash import no_update, html + @app.callback( + Output("calc-token-gha1", "data"), Output("tabs-GHA1-out", "children"), + Output("gha1-header", "children", allow_duplicate=True), + Output("output-gha1-ana", "children", allow_duplicate=True), + Output("output-gha1-num", "children", allow_duplicate=True), + Output("output-gha1-stoch", "children", allow_duplicate=True), + Output("output-gha1-approx", "children", allow_duplicate=True), + Output("store-gha1-ana", "data", allow_duplicate=True), + Output("store-gha1-num", "data", allow_duplicate=True), + Output("store-gha1-stoch", "data", allow_duplicate=True), + Output("store-gha1-approx", "data", allow_duplicate=True), Input("button-calc-gha1", "n_clicks"), + State("calc-token-gha1", "data"), + State("cb-ana-1", "value"), State("cb-num-1", "value"), State("cb-stoch-1", "value"), State("cb-approx-1", "value"), - # Eingaben GHA1 + State("input-GHA1-beta0", "value"), State("input-GHA1-lamb0", "value"), State("input-GHA1-s", "value"), State("input-GHA1-a", "value"), - # Halbachsen + State("input-ax", "value"), State("input-ay", "value"), State("input-b", "value"), prevent_initial_call=True, ) -def gha1_method_hint(n, a, nu, st, ap, beta0, lamb0, s, a0, ax, ay, b): - # Ellipsoid - if ax is None or ay is None or b is None: - return html.Span("Bitte Ellipsoid wählen bzw. Halbachsen eingeben.", style={"color": "red"}) +def request_calc_gha1(n_clicks, token, + cb_ana, cb_num, cb_stoch, cb_approx, + beta0, lamb0, s, a0, + ax, ay, b): + + if not n_clicks: + raise PreventUpdate + + if ax is None or ay is None or b is None: + msg = html.Span("Bitte Ellipsoid wählen bzw. Halbachsen eingeben.", style={"color": "red"}) + return (no_update, msg, no_update, no_update, no_update, no_update, no_update, + no_update, no_update, no_update, no_update) - # Halbachsen try: ax_f, ay_f, b_f = float(ax), float(ay), float(b) except (TypeError, ValueError): - return html.Span("Bitte gültige Zahlen für aₓ, aᵧ und b eingeben.", style={"color": "red"}) + msg = html.Span("Bitte gültige Zahlen für aₓ, aᵧ und b eingeben.", style={"color": "red"}) + return (no_update, msg, no_update, no_update, no_update, no_update, no_update, + no_update, no_update, no_update, no_update) if ax_f <= 0 or ay_f <= 0 or b_f <= 0: - return html.Span("Halbachsen müssen > 0 sein.", style={"color": "red"}) + msg = html.Span("Halbachsen müssen > 0 sein.", style={"color": "red"}) + return (no_update, msg, no_update, no_update, no_update, no_update, no_update, + no_update, no_update, no_update, no_update) if not (ax_f >= ay_f >= b_f): - return html.Span("Ungültige Halbachsen! (aₓ ≥ aᵧ ≥ b)", style={"color": "red"}) + msg = html.Span("Ungültige Halbachsen! (aₓ ≥ aᵧ ≥ b)", style={"color": "red"}) + return (no_update, msg, no_update, no_update, no_update, no_update, no_update, + no_update, no_update, no_update, no_update) - # GHA1-Eingabefelder prüfen - missing = [] - if beta0 is None: - missing.append("β₀") - if lamb0 is None: - missing.append("λ₀") - if s is None: - missing.append("s") - if a0 is None: - missing.append("α₀") - - if missing: - return html.Span( - "Bitte " + ", ".join(missing) + " eingeben.", - style={"color": "red"}, - ) - - # Berechnungsverfahren - any_on = any("on" in (v or []) for v in (a, nu, st, ap)) + # --- Validierung Verfahren --- + any_on = any("on" in (v or []) for v in (cb_ana, cb_num, cb_stoch, cb_approx)) if not any_on: - return html.Span("Bitte Berechnungsverfahren wählen.", style={"color": "red"}) + msg = html.Span("Bitte Berechnungsverfahren wählen.", style={"color": "red"}) + return (no_update, msg, no_update, no_update, no_update, no_update, no_update, + no_update, no_update, no_update, no_update) + + missing = [] + if beta0 is None: missing.append("β₀") + if lamb0 is None: missing.append("λ₀") + if s is None: missing.append("s") + if a0 is None: missing.append("α₀") + if missing: + msg = html.Span("Bitte " + ", ".join(missing) + " eingeben.", style={"color": "red"}) + return (no_update, msg, no_update, no_update, no_update, no_update, no_update, + no_update, no_update, no_update, no_update) - # Eingaben Wertebereiche try: beta0_f = float(beta0) lamb0_f = float(lamb0) - s_f = float(s) - a0_f = float(a0) + s_f = float(s) + a0_f = float(a0) except (TypeError, ValueError): - return html.Span("Bitte gültige Zahlen für β₀, λ₀, s und α₀ eingeben.", style={"color": "red"}) + msg = html.Span("Bitte gültige Zahlen für β₀, λ₀, s und α₀ eingeben.", style={"color": "red"}) + return (no_update, msg, no_update, no_update, no_update, no_update, no_update, + no_update, no_update, no_update, no_update) if not (-90 <= beta0_f <= 90): - return html.Span("β₀ muss im Bereich [-90°, 90°] liegen.", style={"color": "red"}) - if not (-180 <= lamb0_f <= 180): - return html.Span("λ₀ muss im Bereich [-180°, 180°] liegen.", style={"color": "red"}) - if not (-180 <= lamb0_f <= 360): - return html.Span("α₀ muss im Bereich [-180°, 360°] liegen.", style={"color": "red"}) + msg = html.Span("β₀ muss im Bereich [-90°, 90°] liegen.", style={"color": "red"}) + return (no_update, msg, no_update, no_update, no_update, no_update, no_update, + no_update, no_update, no_update, no_update) - return "" + if not (-180 <= lamb0_f <= 180): + msg = html.Span("λ₀ muss im Bereich [-180°, 180°] liegen.", style={"color": "red"}) + return (no_update, msg, no_update, no_update, no_update, no_update, no_update, + no_update, no_update, no_update, no_update) + + if s_f <= 0: + msg = html.Span("s muss > 0 sein.", style={"color": "red"}) + return (no_update, msg, no_update, no_update, no_update, no_update, no_update, + no_update, no_update, no_update, no_update) + + if not (-180 <= a0_f <= 360): + msg = html.Span("α₀ muss im Bereich [-180°, 360°] liegen.", style={"color": "red"}) + return (no_update, msg, no_update, no_update, no_update, no_update, no_update, + no_update, no_update, no_update, no_update) + + new_token = (token or 0) + 1 + + return ( + new_token, + "", + "", + "", "", "", "", + None, None, None, None + ) + + +@app.callback( + Output("calc-token-gha2", "data"), + Output("tabs-GHA2-out", "children"), + Output("gha2-header", "children", allow_duplicate=True), + Output("output-gha2-num", "children", allow_duplicate=True), + Output("output-gha2-stoch", "children", allow_duplicate=True), + Output("output-gha2-approx", "children", allow_duplicate=True), + Output("store-gha2-num", "data", allow_duplicate=True), + Output("store-gha2-stoch", "data", allow_duplicate=True), + Output("store-gha2-approx", "data", allow_duplicate=True), + Input("button-calc-gha2", "n_clicks"), + State("calc-token-gha2", "data"), + + State("cb-num-2", "value"), + State("cb-stoch-2", "value"), + State("cb-approx-2", "value"), + + State("input-GHA2-beta0", "value"), + State("input-GHA2-lamb0", "value"), + State("input-GHA2-beta1", "value"), + State("input-GHA2-lamb1", "value"), + + State("input-ax", "value"), + State("input-ay", "value"), + State("input-b", "value"), + prevent_initial_call=True, +) +def request_calc_gha2(n_clicks, token, + cb_num, cb_stoch, cb_approx, + beta0, lamb0, beta1, lamb1, + ax, ay, b): + + if not n_clicks: + raise PreventUpdate + + if ax is None or ay is None or b is None: + msg = html.Span("Bitte Ellipsoid wählen bzw. Halbachsen eingeben.", style={"color": "red"}) + return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update + + try: + ax_f, ay_f, b_f = float(ax), float(ay), float(b) + except (TypeError, ValueError): + msg = html.Span("Bitte gültige Zahlen für aₓ, aᵧ und b eingeben.", style={"color": "red"}) + return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update + + if ax_f <= 0 or ay_f <= 0 or b_f <= 0: + msg = html.Span("Halbachsen müssen > 0 sein.", style={"color": "red"}) + return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update + + if not (ax_f >= ay_f >= b_f): + msg = html.Span("Ungültige Halbachsen! (aₓ ≥ aᵧ ≥ b)", style={"color": "red"}) + return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update + + any_on = any("on" in (v or []) for v in (cb_num, cb_stoch, cb_approx)) + if not any_on: + msg = html.Span("Bitte Berechnungsverfahren wählen.", style={"color": "red"}) + return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update + + missing = [] + if beta0 is None: missing.append("β₀") + if lamb0 is None: missing.append("λ₀") + if beta1 is None: missing.append("β₁") + if lamb1 is None: missing.append("λ₁") + if missing: + msg = html.Span("Bitte " + ", ".join(missing) + " eingeben.", style={"color": "red"}) + return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update + + try: + beta0_f = float(beta0); lamb0_f = float(lamb0) + beta1_f = float(beta1); lamb1_f = float(lamb1) + except (TypeError, ValueError): + msg = html.Span("Bitte gültige Zahlen für β₀, λ₀, β₁ und λ₁ eingeben.", style={"color": "red"}) + return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update + + if not (-90 <= beta0_f <= 90): + msg = html.Span("β₀ muss im Bereich [-90°, 90°] liegen.", style={"color": "red"}) + return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update + if not (-180 <= lamb0_f <= 180): + msg = html.Span("λ₀ muss im Bereich [-180°, 180°] liegen.", style={"color": "red"}) + return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update + if not (-90 <= beta1_f <= 90): + msg = html.Span("β₁ muss im Bereich [-90°, 90°] liegen.", style={"color": "red"}) + return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update + if not (-180 <= lamb1_f <= 180): + msg = html.Span("λ₁ muss im Bereich [-180°, 180°] liegen.", style={"color": "red"}) + return no_update, msg, no_update, no_update, no_update, no_update, no_update, no_update, no_update + + new_token = (token or 0) + 1 + return ( + new_token, + "", + "", + "", "", "", + None, None, None + ) # -- GHA 1 --- @@ -760,13 +968,15 @@ def compute_gha1_num(n1, cb_num, n_in, beta0, lamb0, s, a0, ax, ay, b): n_in = int(n_in) if n_in else 2000 - ell = EllipsoidTriaxial(ax, ay, b) - beta_rad = wu.deg2rad(float(beta0)) - lamb_rad = wu.deg2rad(float(lamb0)) - alpha_rad = wu.deg2rad(float(a0)) - s_val = float(s) - try: + + ell = EllipsoidTriaxial(ax, ay, b) + beta_rad = wu.deg2rad(float(beta0)) + lamb_rad = wu.deg2rad(float(lamb0)) + alpha_rad = wu.deg2rad(float(a0)) + s_val = float(s) + + P0 = ell.ell2cart(beta_rad, lamb_rad) P1_num, alpha1, werte = gha1_num(ell, P0, alpha_rad, s_val, n_in, all_points=True) @@ -1162,7 +1372,7 @@ def render_all(ax, ay, b, coords_type, tab, t1, t2, yanchor="bottom", y=1.02, xanchor="left", - x=0.06, + x=0.05, groupclick="togglegroup", itemclick="toggle", itemdoubleclick="toggleothers", @@ -1191,50 +1401,6 @@ def clear_all_stores_on_ellipsoid_change(ax, ay, b): return (None, None, None, None, None, None, None) - -# Funktionen zur separaten Darstellung der Tabs -@app.callback( - Output("calc-token-gha1", "data"), - Output("gha1-header", "children", allow_duplicate=True), - Output("output-gha1-ana", "children", allow_duplicate=True), - Output("output-gha1-num", "children", allow_duplicate=True), - Output("output-gha1-stoch", "children", allow_duplicate=True), - Output("output-gha1-approx", "children", allow_duplicate=True), - Output("store-gha1-ana", "data", allow_duplicate=True), - Output("store-gha1-num", "data", allow_duplicate=True), - Output("store-gha1-stoch", "data", allow_duplicate=True), - Output("store-gha1-approx", "data", allow_duplicate=True), - Input("button-calc-gha1", "n_clicks"), - State("calc-token-gha1", "data"), - prevent_initial_call=True, -) -def start_calc_gha1(n, token): - if not n: - raise PreventUpdate - token = (token or 0) + 1 - return token, "", "", "", "", "", None, None, None, None - -@app.callback( - Output("calc-token-gha2", "data"), - Output("gha2-header", "children", allow_duplicate=True), - Output("output-gha2-num", "children", allow_duplicate=True), - Output("output-gha2-stoch", "children", allow_duplicate=True), - Output("output-gha2-approx", "children", allow_duplicate=True), - Output("store-gha2-num", "data", allow_duplicate=True), - Output("store-gha2-stoch", "data", allow_duplicate=True), - Output("store-gha2-approx", "data", allow_duplicate=True), - Input("button-calc-gha2", "n_clicks"), - State("calc-token-gha2", "data"), - prevent_initial_call=True, -) -def start_calc_gha2(n, token): - if not n: - raise PreventUpdate - token = (token or 0) + 1 - return token, "", "", "", "", None, None, None - - - # Funktionen zur Erzeugung der Überschriften @app.callback( Output("gha1-header", "children"),