From 543ac1ff8540359184263f40a24f3b4096bc02b5 Mon Sep 17 00:00:00 2001 From: Nikolay Date: Wed, 22 Feb 2023 15:17:55 +0300 Subject: [PATCH] modify app, impove callbacks and fix bugs --- .gitignore | 2 + pyproject.toml | 1 + pyquac/assets/bootstrap.css | 22 +++ pyquac/assets/scripts.js | 62 +++++++ pyquac/components/__init__.py | 4 +- pyquac/components/heatmap.py | 248 ++++++++++++++++++---------- pyquac/components/modal.py | 102 ++++++++++++ pyquac/components/property_nav.py | 263 ++++++++++++++++++++++-------- pyquac/components/sidebar.py | 257 ++++++++++++++++++----------- pyquac/datatools.py | 173 ++++++++++++++------ pyquac/fmn_app.py | 169 +++++++++++-------- pyquac/settings.py | 9 +- 12 files changed, 952 insertions(+), 360 deletions(-) create mode 100644 pyquac/assets/scripts.js create mode 100644 pyquac/components/modal.py diff --git a/.gitignore b/.gitignore index e42318d..d6faed6 100644 --- a/.gitignore +++ b/.gitignore @@ -160,3 +160,5 @@ tech_tests.ipynb /examples/style.css /pyquac/random_example.py /pyquac/example_data.csv +/pyquac/raw_test.csv +/pyquac/delete.csv \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index d7f3977..50e7327 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ matplotlib = "^3.5.2" dash-bootstrap-components = "^1.2.1" dash-bootstrap-templates = "^1.0.7" dash-daq = "^0.5.0" +dash-iconify = "^0.1.2" [tool.poetry.dev-dependencies] pytest = "^7.1.2" diff --git a/pyquac/assets/bootstrap.css b/pyquac/assets/bootstrap.css index 43cc881..ebb5e03 100644 --- a/pyquac/assets/bootstrap.css +++ b/pyquac/assets/bootstrap.css @@ -11097,4 +11097,26 @@ thead th { .modal-content { box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +.rc-slider-track { + background-color: #3459e6; +} + +.rc-slider-dot-active { + border-color: #3459e6; + /* border: solid 2px red; */ +} + +.rc-slider-handle { + background-color: #3459e6; + border-color: #3459e6; +} + +.rc-slider-handle:hover { + border-color: #3459e6; +} + +.rc-slider-handle-active:active { + border-color: #3459e6; } \ No newline at end of file diff --git a/pyquac/assets/scripts.js b/pyquac/assets/scripts.js new file mode 100644 index 0000000..a31755e --- /dev/null +++ b/pyquac/assets/scripts.js @@ -0,0 +1,62 @@ +window.dash_clientside = Object.assign({}, window.dash_clientside, { + clientside: { + refresh_graph: function ( + i, + x_click, + y_click, + click, + close_modal, + x, + y, + z, + fig, + y_scatter, + yz_scatter, + x_scatter, + xz_scatter, + line_switch, + x_title, + y_title + ) { + const triggered_id = dash_clientside.callback_context.triggered.map( + (t) => t.prop_id + )[0]; + + if (fig === undefined) { + return window.dash_clientside.no_update; + } + figure = JSON.parse(JSON.stringify(fig)); + + if (x_click === undefined) { + figure["layout"]["shapes"][0]["visible"] = false; + figure["layout"]["shapes"][1]["visible"] = false; + } else { + figure["layout"]["shapes"][0]["visible"] = line_switch; + figure["layout"]["shapes"][1]["visible"] = line_switch; + } + if (triggered_id === "interval-graph-update.n_intervals") { + figure["data"][0]["x"] = x; + figure["data"][0]["y"] = y; + figure["data"][0]["z"] = z; + } + if (triggered_id === "heatmap.clickData") { + figure["data"][1]["x"] = yz_scatter; + figure["data"][1]["y"] = y_scatter; + + figure["data"][2]["x"] = x_scatter; + figure["data"][2]["y"] = xz_scatter; + + figure["layout"]["shapes"][0]["x0"] = x_click; + figure["layout"]["shapes"][0]["x1"] = x_click; + + figure["layout"]["shapes"][1]["y0"] = y_click; + figure["layout"]["shapes"][1]["y1"] = y_click; + } + if (triggered_id === "modal_close.n_clicks") { + figure["layout"]["xaxis"]["title"]["text"] = x_title; + figure["layout"]["yaxis"]["title"]["text"] = y_title; + } + return figure; + }, + }, +}); diff --git a/pyquac/components/__init__.py b/pyquac/components/__init__.py index 97111f2..e8517a9 100644 --- a/pyquac/components/__init__.py +++ b/pyquac/components/__init__.py @@ -2,4 +2,6 @@ from .navbar import navbar from .sidebar import sidebar, content -from .property_nav import property_nav +from .modal import modal + +# from .property_nav import property_nav diff --git a/pyquac/components/heatmap.py b/pyquac/components/heatmap.py index a397085..84779c2 100644 --- a/pyquac/components/heatmap.py +++ b/pyquac/components/heatmap.py @@ -2,8 +2,9 @@ This file is for creating a graph layout """ -from dash.dependencies import Input, Output -from dash import dcc, callback +from dash.dependencies import Input, Output, State, ClientsideFunction +from dash import dcc, callback, clientside_callback, ctx +from dash.exceptions import PreventUpdate import plotly.graph_objects as go from pyquac.settings import settings @@ -11,13 +12,13 @@ GRAPH_STYLE = { "position": "fixed", - "top": "110px", + # "top": "90px", "left": "16rem", - "bottom": 0, - "width": "45rem", - "height": "37rem", + # "bottom": 0, + # "width": "45rem", + # "height": 500, # "z-index": 1, - "overflow-x": "hidden", + # "overflow-x": "hidden", "transition": settings.transition_time, # "transition-delay": "width 500ms", # "transition-property": "margin-right", @@ -26,13 +27,13 @@ GRAPH_HIDEN = { "position": "fixed", - "top": "110px", + # "top": "90px", "left": 0, - "bottom": 0, - "right": 0, + # "bottom": 0, + # "right": 0, "width": "45rem", # "width": "70rem", - "height": "37rem", + "height": "50rem", # "z-index": 1, # "overflow-x": "hidden", "transition": settings.transition_time, @@ -60,40 +61,33 @@ def define_figure( """ fig = go.Figure(data=go.Heatmap(z=z, x=x, y=y, colorscale=cmap)) - fig.update_layout( - xaxis_title=x_axis_title, - yaxis_title=y_axis_title, - autosize=False, - separators=".", - ) - - fig.update_yaxes(title_font={"size": AXIS_SIZE}, tickfont_size=AXIS_SIZE) - fig.update_xaxes(title_font={"size": AXIS_SIZE}, tickfont_size=AXIS_SIZE) - fig.update_layout(yaxis=dict(showexponent="none", exponentformat="e")) - fig.update_traces(zhoverformat=".2f") - fig.update_layout(width=650, height=550) - return fig - + fig.add_trace(go.Scatter(x=None, y=None, mode="lines", xaxis="x2")) -def define_figure_extend( - z, - x, - y, - x_axis_title: str, - y_axis_title: str, - cmap: str, -): - """sets figure layout + fig.add_trace(go.Scatter(x=None, y=None, mode="lines", yaxis="y2")) - Args: - data (Spectroscopy): spectroscopy-like object - - Returns: - go.gigure: plotly figure - """ - fig = go.Figure(data=go.Heatmap(z=z, x=x, y=y, colorscale=cmap)) + fig.add_vline( + x=None, + visible=False, + line=dict( + color="Black", + width=2, + dash="dash", + ), + opacity=1.0, + name="vline", + ) - fig.add_trace(go.Scatter(x=None, y=None, mode="lines", xaxis="x2")) + fig.add_hline( + y=None, + visible=False, + line=dict( + color="Black", + width=2, + dash="dash", + ), + opacity=1.0, + name="hline", + ) fig.update_layout( xaxis_title=x_axis_title, @@ -104,19 +98,38 @@ def define_figure_extend( fig.update_layout( autosize=False, - xaxis=dict(zeroline=False, domain=[0, 0.60], showgrid=False), - # yaxis=dict(zeroline=False, domain=[0, 0.85], showgrid=False), - xaxis2=dict(zeroline=False, domain=[0.60, 1], showgrid=False), - # yaxis2=dict(zeroline=False, domain=[0.85, 1], showgrid=False), + xaxis=dict(zeroline=False, domain=[0, 0.72], showgrid=True), + yaxis=dict(zeroline=False, domain=[0, 0.72], showgrid=True), + xaxis2=dict(zeroline=False, domain=[0.76, 1], showgrid=True, visible=True), + yaxis2=dict(zeroline=False, domain=[0.76, 1], showgrid=True), bargap=0, hovermode="closest", ) + fig.update_layout(paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)") + fig.update_traces(showlegend=False) + fig.update_yaxes(title_font={"size": AXIS_SIZE}, tickfont_size=AXIS_SIZE) fig.update_xaxes(title_font={"size": AXIS_SIZE}, tickfont_size=AXIS_SIZE) fig.update_layout(yaxis=dict(showexponent="none", exponentformat="e")) # fig.update_traces(zhoverformat=".2f") - fig.update_layout(width=850, height=550) + fig.update_layout(height=600) + # https://plotly.com/python/hover-text-and-formatting/ + fig.update_xaxes( + showspikes=True, + spikecolor="black", + spikethickness=2, + spikesnap="cursor", + spikemode="across", + ) + fig.update_yaxes( + showspikes=True, + spikecolor="black", + spikethickness=2, + spikesnap="cursor", + spikemode="across", + ) + fig.update_traces(hoverinfo="x+y+z", selector=dict(type="heatmap")) return fig @@ -144,6 +157,10 @@ def figure_layout( y_axis_title=y_axis_title, cmap=cmap, ), + animate=False, + config={ + "scrollZoom": False, + }, style=GRAPH_STYLE, ) return figure @@ -151,17 +168,10 @@ def figure_layout( @callback( Output("heatmap", "style"), - Output("heatmap", "figure"), Input("btn_sidebar", "n_clicks"), Input("side_click", "data"), - Input("z_store", "data"), - Input("x_store", "data"), - Input("y_store", "data"), - Input("x_label", "data"), - Input("y_label", "data"), - Input("cmap", "data"), ) -def toggle_graph(n, nclick, z, x, y, x_label, y_label, cmap): +def toggle_graph(n, nclick): """function to hide and reveal sidebar Args: @@ -171,49 +181,119 @@ def toggle_graph(n, nclick, z, x, y, x_label, y_label, cmap): Returns: dict: style objects """ - fig = define_figure( - z, - x, - y, - x_label, - y_label, - cmap, - ) if n: if nclick == "SHOW": graph_style = GRAPH_STYLE - fig = define_figure( - z, - x, - y, - x_label, - y_label, - cmap, - ) else: graph_style = GRAPH_HIDEN - fig = define_figure_extend( - z, - x, - y, - x_label, - y_label, - cmap, - ) else: graph_style = GRAPH_STYLE - return graph_style, fig + return graph_style # @callback( # Output("heatmap", "figure"), # Input("interval-graph-update", "n_intervals"), -# Input("z_store", "data"), +# State("x_store", "data"), +# State("y_store", "data"), +# State("z_store", "data"), # State("heatmap", "figure"), +# State("y_scatter", "data"), +# State("yz_scatter", "data"), +# State("x_scatter", "data"), +# State("xz_scatter", "data"), +# Input("x_click", "data"), +# Input("y_click", "data"), +# Input("heatmap", "clickData"), +# State("line-switches", "on"), +# Input("temp", "data") +# # Input("xy_lines_state", "data"), # ) -# def update_graph(i, z, fig): - +# def update_graph( +# i, +# x, +# y, +# z, +# fig, +# y_scatter, +# yz_scatter, +# x_scatter, +# xz_scatter, +# x_click, +# y_click, +# click, +# line_switch, +# # xy_state, +# temp, +# ): +# triggered_id = ctx.triggered_id # if i == 0: # raise PreventUpdate -# return go.Figure(fig).update_traces(z=z) +# print(temp) +# # if triggered_id == "interval-graph-update": +# # return go.Figure(fig).update_traces(x=x, y=y, z=z, selector={"type": "heatmap"}) +# # elif (triggered_id == "heatmap") or (triggered_id == "x-slider"): +# # return go.Figure(fig).update_traces( +# # x=z_scatter, y=y_scatter, selector={"type": "scatter"} +# # ) + +# if x_click is None: +# fig["layout"]["shapes"][0].update({"visible": False}) +# fig["layout"]["shapes"][1].update({"visible": False}) +# else: +# fig["layout"]["shapes"][0].update({"visible": line_switch}) +# fig["layout"]["shapes"][1].update({"visible": line_switch}) + +# if triggered_id == "interval-graph-update": +# fig["data"][0].update({"x": x, "y": y, "z": z}) +# return fig +# elif triggered_id == "heatmap": +# fig["data"][1].update({"x": yz_scatter, "y": y_scatter}) +# fig["data"][2].update({"x": x_scatter, "y": xz_scatter}) +# fig["layout"]["shapes"][0].update( +# { +# "x0": x_click, +# "x1": x_click, +# # "visible": line_switch, +# "y0": 0.05, +# "y1": 0.95, +# } +# ) +# fig["layout"]["shapes"][1].update({"y0": y_click, "y1": y_click}) +# return fig + + +clientside_callback( + ClientsideFunction(namespace="clientside", function_name="refresh_graph"), + Output("heatmap", "figure"), + Input("interval-graph-update", "n_intervals"), + Input("x_click", "data"), + Input("y_click", "data"), + Input("heatmap", "clickData"), + Input("modal_close", "n_clicks"), + State("x_store", "data"), + State("y_store", "data"), + State("z_store", "data"), + State("heatmap", "figure"), + State("y_scatter", "data"), + State("yz_scatter", "data"), + State("x_scatter", "data"), + State("xz_scatter", "data"), + State("line-switches", "on"), + State("x-title", "value"), + State("y-title", "value"), + prevent_initial_call=True, +) + +# clientside_callback( +# """ +# function(i) { +# const triggered_id = dash_clientside.callback_context.triggered.map(t => t.prop_id)[0]; +# return triggered_id; +# } +# """, +# Output("temp", "data"), +# Input("modal_close", "n_clicks"), +# prevent_initial_call=True, +# ) diff --git a/pyquac/components/modal.py b/pyquac/components/modal.py new file mode 100644 index 0000000..b2907c3 --- /dev/null +++ b/pyquac/components/modal.py @@ -0,0 +1,102 @@ +from dash.dependencies import Input, Output, State +from dash import html, dcc +from dash.exceptions import PreventUpdate +from dash import callback +import dash_bootstrap_components as dbc +import dash_daq as daq +from pyquac.settings import settings + +tooltip = html.Div( + [ + html.P( + [ + "for colormap you can use ", + html.Span( + "pre-defined or specified", + id="tooltip-target", + style={"textDecoration": "underline", "cursor": "pointer"}, + ), + " values", + ] + ), + dbc.Tooltip( + """ + - A list of 2-element lists where the first element is the normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) + - One of the following named colorscales: 'aggrnyl', 'agsunset', 'algae', 'amp', 'armyrose', 'balance' ... + """, + target="tooltip-target", + autohide=False, + ), + ] +) +path_input = html.Div( + [ + dbc.Label("Save path"), + dbc.Input( + id="default-path", + placeholder="Enter root saving path", + value=settings.default_root_path, + type="text", + ), + dbc.FormText( + "Enter new root path to save data. Example: D:/Scripts/qubit", + ), + ], + className="mb-3", +) + +xaxis_input = html.Div( + [ + dbc.Label("X-axis title"), + dbc.Input( + id="x-title", + placeholder="Enter new x-axis title", + value=settings.init_x_label, + type="text", + ), + dbc.Label("Y-axis title", class_name="mt-2"), + dbc.Input( + id="y-title", + placeholder="Enter new y-axis title", + value=settings.init_y_label, + type="text", + ), + dbc.Label("heatmap color map", class_name="mt-2"), + dbc.Input( + id="cmap-title", + placeholder="Enter new cmap", + value=settings.init_cmap, + type="text", + disabled=True, + ), + tooltip, + ], + className="mb-3", +) + +modal = dbc.Modal( + [ + dbc.ModalHeader(dbc.ModalTitle("Settings")), + dbc.ModalBody([path_input, xaxis_input]), + dbc.ModalFooter( + dbc.Button( + "Save and close", id="modal_close", className="ms-auto", n_clicks=0 + ) + ), + ], + id="modal", + is_open=False, + keyboard=False, + backdrop="static", +) + + +@callback( + Output("modal", "is_open"), + [Input("open_modal", "n_clicks"), Input("modal_close", "n_clicks")], + [State("modal", "is_open")], +) +def toggle_modal(n1, n2, is_open): + if n1 or n2: + return not is_open + return is_open diff --git a/pyquac/components/property_nav.py b/pyquac/components/property_nav.py index 3a9ddeb..8fa251f 100644 --- a/pyquac/components/property_nav.py +++ b/pyquac/components/property_nav.py @@ -1,25 +1,88 @@ -# notes -""" -This file is for creating a property nav that will sit under main navigation bar. -Dash Bootstrap Components documentation linked below: -https://dash-bootstrap-components.opensource.faculty.ai/docs/components/nav/ -""" - -from dash.dependencies import Input, Output -from dash import callback +# # # notes +# # """ +# # This file is for creating a property nav that will sit under main navigation bar. +# # Dash Bootstrap Components documentation linked below: +# # https://dash-bootstrap-components.opensource.faculty.ai/docs/components/nav/ +# # """ + +import pandas as pd +import numpy as np +import os +from datetime import date, datetime + +from dash.dependencies import Input, Output, State +from dash import callback, html, ctx +import plotly.graph_objects as go from dash.exceptions import PreventUpdate import dash_bootstrap_components as dbc -import dash_daq as daq from pyquac.settings import settings + +def _get_result_(x, y, z): + return pd.DataFrame({"x_value": x, "y_value": y, "z_value": z}) + + +def _get_raw_result_(get_res_df): + y_l = (get_res_df.groupby("x_value")["y_value"].apply(list)).reset_index() + z_l = get_res_df.groupby("x_value")["z_value"].apply(list) + x_l = z_l.index.to_series(index=np.arange(len(z_l))).to_frame() + z_l = z_l.reset_index() + + return x_l.merge(y_l, on="x_value", how="left").merge(z_l, on="x_value", how="left") + + +def _file_name_(qubit_id: str, time): + return f"q{qubit_id}{time}" + + +def _save_path_(filename: str, chip_id: str, default_path: str, spectroscopy_type: str): + spectroscopy = ( + "two_tone_spectroscopy" + if spectroscopy_type == "TTS" + else "single_tone_spectroscopy" + ) + + current_date = str(date.today()) + + parent_dir = default_path + dir_qubit = os.path.join(default_path, str(chip_id)) + dir_date = os.path.join(dir_qubit, current_date) + dir_tts = os.path.join(dir_date, spectroscopy) + + if os.path.exists(parent_dir): + + # Checking qubit dir existance + if not os.path.exists(dir_qubit): + os.mkdir(dir_qubit) + else: + pass + + # Checking date dir existance + if not os.path.exists(dir_date): + os.mkdir(dir_date) + else: + pass + + # Checking tts dir existance + if not os.path.exists(dir_tts): + os.mkdir(dir_tts) + else: + pass + + dir_final = os.path.join(dir_tts, filename) + return dir_final + else: + return filename + + PROPERTY_NAV_STYLE = { "position": "fixed", - # "top": "70px", + "top": "60px", "left": "16rem", # "bottom": 0, # "width": "16rem", # "height": "100%", - "z-index": 1, + # "z-index": "zIndex", "overflow-x": "hidden", "transition": settings.transition_time, "padding": "0.5rem 1rem", @@ -27,45 +90,25 @@ PROPERTY_NAV_HIDEN = { "position": "fixed", - # "top": "70px", + "top": "60px", "left": 0, # "bottom": 0, # "width": "16rem", # "height": "100%", - "z-index": 1, + # "z-index": "zIndex", "overflow-x": "hidden", "transition": settings.transition_time, "padding": "0.5rem 1rem", } property_nav = dbc.Nav( [ - dbc.NavItem(dbc.Label("Status")), - # dbc.NavItem( - # dbc.Spinner(color="danger", type="grow", size="sm"), - # style={"margin-left": "0.5rem"}, - # ), - dbc.NavItem( - dbc.Label("Heatmap updating"), - style={"margin-left": "2rem"}, - ), - dbc.NavItem( - [ - daq.BooleanSwitch(id="interval-switches", on=True, color="#3459e6"), - ], - style={"margin-left": "0.5rem"}, - ), - dbc.NavItem( - dbc.Input( - id="update-interval-value", - type="number", - min=800, - step=1, - placeholder="Update graph in... ms", - disabled=False, - html_size=16, - size="sm", - ), - style={"margin-left": "1rem"}, + # dbc.NavItem(dbc.Label("Status")), + dbc.Alert( + "App is now running", + color="light", + id="status-alert", + is_open=False, + duration=10000, ), ], id="property-nav", @@ -73,6 +116,90 @@ ) +@callback( + Output("status-alert", "children"), + Output("status-alert", "is_open"), + Input("csv", "n_clicks"), + Input("raw csv", "n_clicks"), + Input("pdf", "n_clicks"), + Input("svg", "n_clicks"), + Input("html", "n_clicks"), + Input("heatmap", "clickData"), + Input("x_click", "data"), + Input("y_click", "data"), + Input("interval-switches", "on"), + Input("update-interval-value", "value"), + Input("default-path", "value"), + State("save_attributes", "data"), + State("status-alert", "is_open"), + State("x_store", "data"), + State("y_store", "data"), + State("z_store", "data"), + State("heatmap", "figure"), +) +def save_func( + _, + __, + ___, + ____, + _____, + click, + x_click, + y_click, + on, + new_interval, + default_path, + save_attributes, + is_open, + x_result, + y_result, + z_result, + fig, +): + button_clicked = ctx.triggered_id + qubit_id, chip_id, spectroscopy_type = ( + save_attributes[0], + save_attributes[1], + save_attributes[2], + ) + filename = _file_name_(qubit_id, datetime.now().strftime("_%H-%M-%S")) + path = _save_path_(filename, chip_id, default_path, spectroscopy_type) + + if button_clicked == "csv": + _get_result_(x=x_result, y=y_result, z=z_result).to_csv(f"{path}.csv") + return f"current data saved to {path}.csv", True + + elif button_clicked == "raw csv": + _get_raw_result_(_get_result_(x=x_result, y=y_result, z=z_result)).to_csv( + f"{path}_stacked.csv" + ) + return f"current data saved to {path}_stacked.csv", True + + elif button_clicked == "pdf": + go.Figure(fig).write_image(f"{path}.pdf") + return f"current data saved to {path}.pdf", True + + elif button_clicked == "svg": + go.Figure(fig).write_image(f"{path}.svg") + return f"current data saved to {path}.svg", True + + elif button_clicked == "html": + go.Figure(fig).write_html(f"{path}.html") + return f"current data saved to {path}.html", True + + elif button_clicked == "heatmap": + return f"clicked on x: {x_click}\ty: {y_click}", True + + elif button_clicked == "interval-switches": + return f"Graph update is {on}", True + + elif button_clicked == "update-interval-value": + return f"New update interval is {new_interval} ms.", True + + else: + raise PreventUpdate + + @callback( Output("property-nav", "style"), Input("btn_sidebar", "n_clicks"), @@ -99,36 +226,36 @@ def toggle_nav(n, nclick): return nav_style -@callback( - Output("interval-graph-update", "max_intervals"), Input("interval-switches", "on") -) -def toggle_checklist(switch_state): - """function to change max interval property +# # @callback( +# # Output("interval-graph-update", "max_intervals"), Input("interval-switches", "on") +# # ) +# # def toggle_checklist(switch_state): +# # """function to change max interval property - Args: - n (_type_): _description_ - max_interval (_type_): _description_ +# # Args: +# # n (_type_): _description_ +# # max_interval (_type_): _description_ - Returns: - _type_: _description_ - """ +# # Returns: +# # _type_: _description_ +# # """ - if switch_state is True: - new_max_interval = -1 - print("I will update") - else: - new_max_interval = 0 - print("I stop updating") - return new_max_interval +# # if switch_state is True: +# # new_max_interval = -1 +# # print("I will update") +# # else: +# # new_max_interval = 0 +# # print("I stop updating") +# # return new_max_interval -@callback( - Output("interval-graph-update", "interval"), Input("update-interval-value", "value") -) -def change_interval_update(new_interval): +# # @callback( +# # Output("interval-graph-update", "interval"), Input("update-interval-value", "value") +# # ) +# # def change_interval_update(new_interval): - if new_interval is not None: - print(f"HEY! now I update in {new_interval} ms") - return new_interval - else: - raise PreventUpdate +# # if new_interval is not None: +# # print(f"HEY! now I update in {new_interval} ms") +# # return new_interval +# # else: +# # raise PreventUpdate diff --git a/pyquac/components/sidebar.py b/pyquac/components/sidebar.py index 06d7a42..b5b27b8 100644 --- a/pyquac/components/sidebar.py +++ b/pyquac/components/sidebar.py @@ -6,8 +6,10 @@ """ from dash.dependencies import Input, Output, State from dash import html +from dash.exceptions import PreventUpdate from dash import callback import dash_bootstrap_components as dbc +import dash_daq as daq from pyquac.settings import settings # the style arguments for the sidebar. We use position:fixed and a fixed width @@ -44,14 +46,14 @@ # the styles for the main content position it to the right of the sidebar and # add some padding. CONTENT_STYLE = { - "transition": "margin-left .5s", + "transition": settings.margin_transition_time, "margin-left": "18rem", "margin-right": "2rem", "padding": "2rem 1rem", } CONTENT_STYLE1 = { - "transition": "margin-left .5s", + "transition": settings.margin_transition_time, "margin-left": "2rem", "margin-right": "2rem", "padding": "2rem 1rem", @@ -65,99 +67,123 @@ ] ########################## Sidebar ########################## -sidebar = html.Div( - [ - html.H5("Spectroscopy"), - html.P("control panel", className="summary"), - html.Hr(), - dbc.Nav( - [ - dbc.DropdownMenu( - label="Select data", - children=items, - direction="end", - className="mt-2", - ), - ], - vertical=True, - pills=True, - ), - html.Hr(), - dbc.Nav( - [ - dbc.NavItem(html.P("Data saving"), class_name="mt-3"), - dbc.NavItem( - dbc.ButtonGroup( +def sidebar(data): + return html.Div( + [ + html.H5("Spectroscopy"), + html.P("control panel", className="summary"), + # html.Hr(), + # dbc.Nav( + # [ + # dbc.DropdownMenu( + # label="Select data", + # children=items, + # direction="end", + # className="mt-2", + # ), + # ], + # vertical=True, + # pills=True, + # ), + html.Hr(), + dbc.Nav( + [ + dbc.NavItem( [ - dbc.Button("CSV file", title="save current"), - dbc.Button(class_name="fa fa-server", title="save all"), + dbc.Row( + [ + dbc.Col(html.Div("heatmap updating"), width=8), + dbc.Col( + daq.BooleanSwitch( + id="interval-switches", + on=True, + color="#3459e6", + ), + width=4, + ), + ] + ), ], - size="md", - # className="my-3", - ) - ), - dbc.NavItem( - dbc.ButtonGroup( + # style={"margin-left": "2rem"}, + ), + dbc.NavItem( + dbc.Input( + id="update-interval-value", + type="number", + min=500, + step=1, + placeholder="Update graph in... ms", + disabled=False, + html_size=16, + size="sm", + ), + # style={"margin-left": "1rem"}, + ), + html.Hr(), + dbc.NavItem( + dbc.Row( + [ + dbc.Col(html.Div("data saving", id="log")), + ] + ) + ), + dbc.NavItem( + dbc.ButtonGroup( + [ + dbc.Button("csv", id="csv", title="save current CSV"), + dbc.Button( + "stack csv", + id="raw csv", + title="save current stacked CSV", + ), + dbc.Button("pdf", id="pdf", title="save current PDF"), + dbc.Button("svg", id="svg", title="save current SVG"), + dbc.Button( + "html", id="html", title="save current HTML" + ), + # dbc.Button(class_name="fa fa-server", title="save all"), + ], + size="sm", + # className="my-3", + className="mt-2", + ) + ), + dbc.NavItem( [ - dbc.Button("raw CSV file", title="save current"), - dbc.Button(class_name="fa fa-server", title="save all"), + dbc.Row( + [ + dbc.Col(html.Div("show XY lines"), width=8), + dbc.Col( + daq.BooleanSwitch( + id="line-switches", + on=settings.init_xy_lines_state, + color="#3459e6", + ), + width=4, + ), + ] + ), ], - size="md", - className="my-3", - ) - ), - ], - vertical=True, - pills=True, - ), - dbc.Nav( - [ - dbc.NavItem( - dbc.ButtonGroup( - [ - dbc.Button("PDF file", title="save current"), - dbc.Button(class_name="fa fa-server", title="save all"), - ], - size="md", - # className="my-3", - ) - ), - dbc.NavItem( - dbc.ButtonGroup( - [ - dbc.Button("SVG file", title="save current"), - dbc.Button(class_name="fa fa-server", title="save all"), - ], - size="md", - className="my-3", - ) - ), - dbc.NavItem( - dbc.ButtonGroup( - [ - dbc.Button("HTML file", title="save current"), - dbc.Button(class_name="fa fa-server", title="save all"), - ], - size="md", - # className="my-3", - ) - ), - ], - vertical=True, - pills=True, - ), - html.Hr(), - dbc.Button( - class_name="fa fa-gear", - size="lg", - outline=True, - color="#f8f9fa", - title="project settings", - ), - ], - id="sidebar", - style=SIDEBAR_STYLE, -) + class_name="mt-3", + ), + ], + vertical=True, + pills=True, + ), + html.Hr(), + dbc.Button( + id="open_modal", + class_name="fa fa-gear", + size="lg", + outline=True, + color="#f8f9fa", + title="project settings", + ), + ], + id="sidebar", + style=SIDEBAR_STYLE, + ) + content = html.Div(id="page-content", style=CONTENT_STYLE) @@ -194,3 +220,52 @@ def toggle_sidebar(n, nclick): cur_nclick = "SHOW" return sidebar_style, content_style, cur_nclick + + +@callback( + Output("interval-graph-update", "max_intervals"), + Output("line-switches", "disabled"), + Input("interval-switches", "on"), +) +def toggle_checklist(switch_state): + """function to change max interval property + + Args: + n (_type_): _description_ + max_interval (_type_): _description_ + + Returns: + _type_: _description_ + """ + + if switch_state is True: + new_max_interval = -1 + disabled = False + else: + new_max_interval = 0 + disabled = True + return new_max_interval, disabled + + +@callback( + Output("interval-graph-update", "interval"), Input("update-interval-value", "value") +) +def change_interval_update(new_interval): + + if new_interval is not None: + return new_interval + else: + raise PreventUpdate + + +@callback( + Output("xy_lines_state", "data"), + Input("line-switches", "on"), +) +def toggle_xy_lines(switch_state): + + if switch_state is True: + new_xy_state = True + else: + new_xy_state = False + return new_xy_state diff --git a/pyquac/datatools.py b/pyquac/datatools.py index 91e2976..b16a1f8 100644 --- a/pyquac/datatools.py +++ b/pyquac/datatools.py @@ -11,14 +11,83 @@ import peakutils +def spectroscopy_configure( + filename: str = None, + x_column_name: str = "x_value", + y_column_name: str = "y_value", + z_column_name: str = "z_value", + x_arr: Iterable = None, + y_arr: Iterable = None, + x_min: np.float32 = None, + x_max: np.float32 = None, + y_min: np.float32 = None, + y_max: np.float32 = None, + x_step: np.float32 = None, + y_step: np.float32 = None, + nx_points: int = None, + ny_points: int = None, +): + if filename is not None: + + file = pd.read_csv(filename) + x_arr = np.unique(file[x_column_name].values) + y_arr = np.unique(file[y_column_name].values) + + x_min = x_arr.min() + x_max = x_arr.max() + nx_points = len(x_arr) + + y_min = y_arr.min() + y_max = y_arr.max() + ny_points = len(y_arr) + + data_instance = Spectroscopy( + x_min=x_min, + x_max=x_max, + nx_points=nx_points, + y_min=y_min, + y_max=y_max, + ny_points=ny_points, + ) + + data_instance.x_raw = list(file[x_column_name].values) + data_instance.y_raw = list(file[y_column_name].values) + data_instance.z_raw = list(file[z_column_name].values) + + return data_instance + else: + data_instance = Spectroscopy( + x_arr=x_arr, + y_arr=y_arr, + x_min=x_min, + x_max=x_max, + nx_points=nx_points, + x_step=x_step, + y_min=y_min, + y_max=y_max, + ny_points=ny_points, + y_step=y_step, + ) + return data_instance + + @nb.jit(nopython=True) -def _complicated(raw_array_x, raw_array_y, x_min_, x_step_, y_min_, y_step_, len_y_): +def _complicated( + raw_array_x: np.float32, + raw_array_y: np.float32, + x_min_: np.float32, + x_step_: np.float32, + y_min_: np.float32, + y_step_: np.float32, + len_y_, +): ind_array = np.zeros(len(raw_array_x)) - + x_ind: int = 0 + y_ind: int = 0 if len(ind_array) >= 2: for k in range(len(raw_array_x)): - x_ind = np.around((raw_array_x[k] - x_min_) / x_step_) - y_ind = np.around((raw_array_y[k] - y_min_) / y_step_) + x_ind = (raw_array_x[k] - x_min_) // x_step_ + y_ind = (raw_array_y[k] - y_min_) // y_step_ ind_array[k] = x_ind * len_y_ + y_ind pass @@ -170,12 +239,12 @@ def __init__( *, x_arr: Iterable = None, y_arr: Iterable = None, - x_min: float = None, - x_max: float = None, - y_min: float = None, - y_max: float = None, - x_step: float = None, - y_step: float = None, + x_min: np.float32 = None, + x_max: np.float32 = None, + y_min: np.float32 = None, + y_max: np.float32 = None, + x_step: np.float32 = None, + y_step: np.float32 = None, nx_points: int = None, ny_points: int = None, ): @@ -206,10 +275,12 @@ def __init__( self.x_max = x_max if nx_points is not None: - _, self.x_step = np.linspace(x_min, x_max, nx_points, retstep=True) - self.x_step = round(self.x_step, 10) + _, self.x_step = np.linspace( + x_min, x_max, nx_points, retstep=True, dtype=np.float32 + ) + self.x_step = np.float32(round(self.x_step, 10)) else: - self.x_step = float(x_step) + self.x_step = np.float32(x_step) self.__x_step_DP = len(str(self.x_step).split(".")[1]) self.x_list = mrange.orange( @@ -236,10 +307,12 @@ def __init__( self.y_max = y_max if ny_points is not None: - _, self.y_step = np.linspace(y_min, y_max, ny_points, retstep=True) - self.y_step = round(self.y_step, 2) + _, self.y_step = np.linspace( + y_min, y_max, ny_points, retstep=True, dtype=np.float32 + ) + self.y_step = np.float32(round(self.y_step, 2)) else: - self.y_step = float(y_step) + self.y_step = np.float32(y_step) self.__y_step_DP = len(str(self.y_step).split(".")[1]) self.y_list = mrange.orange( @@ -316,12 +389,12 @@ def __getattr__(self, name): def iter_setup( self, *, - x_key: Union[float, int, Iterable] = None, - y_key: Union[float, int, Iterable] = None, - x_min: float = None, - x_max: float = None, - y_min: float = None, - y_max: float = None, + x_key: Union[np.float32, int, Iterable] = None, + y_key: Union[np.float32, int, Iterable] = None, + x_min: np.float32 = None, + x_max: np.float32 = None, + y_min: np.float32 = None, + y_max: np.float32 = None, ): """Measurement setup. Defines the range of values that will be written to the heatmap. If all optional params are None then setup self.load and self.frequency for measuring all data @@ -473,9 +546,9 @@ def iter_setup( def write( self, *, - x: Union[float, int] = None, - y: Union[float, int] = None, - z: Union[float, int] = None, + x: Union[np.float32, int] = None, + y: Union[np.float32, int] = None, + z: Union[np.float32, int] = None, ): """writes one x coord value, y coord value and z coord value to class entity. You can use this function in a loop to sequentially write the values in the heat map :param z: z value @@ -547,8 +620,8 @@ def get_result(self, *, imshow: bool = False) -> pd.DataFrame: for i in range(len(self.x_raw)): self.__z_2d[ - round((self.__y_container[i] - self.y_min) / self.y_step), - round((self.__x_container[i] - self.x_min) / self.x_step), + int((self.__y_container[i] - self.y_min) // self.y_step), + int((self.__x_container[i] - self.x_min) // self.x_step), ] = self.__z_container[i] z_1d = self.__z_2d.ravel(order="F") @@ -596,8 +669,8 @@ def non_njit_result(self): for i in range(len(z_val)): self.__z_2d[ - round((y_val[i] - self.y_min) / self.y_step), - round((x_val[i] - self.x_min) / self.x_step), + int((y_val[i] - self.y_min) // self.y_step), + int((x_val[i] - self.x_min) // self.x_step), ] = z_val[i] return self.__z_2d @@ -632,7 +705,7 @@ def njit_result(self): def xyz_peak( self, x_key: Iterable = None, - thres: float = 0.7, + thres: np.float32 = 0.7, min_dist: int = 75, n_last: int = 20, ): @@ -669,7 +742,7 @@ def xyz_peak( # calculation of the k indices of largest values _, n_last_idxs = SortingTools.k_max_idxs(deltas, n_last) # finding peak values using peak utils - peak_idxs = peakutils.indexes(abs(z), thres=thres, min_dist=min_dist) + peak_idxs = peakutils.indexes(z, thres=thres, min_dist=min_dist) # searching for intersecting values peak_and_delta = np.where(np.isin(peak_idxs, n_last_idxs))[0] @@ -849,8 +922,8 @@ def clean_up(self): cleans data after approximation """ i = 0 - for x in np.array(self.x_list, dtype=float): - array2 = np.array(self.__approximation_y_keys[i], dtype=float) + for x in np.array(self.x_list, dtype=np.float32): + array2 = np.array(self.__approximation_y_keys[i], dtype=np.float32) mask_arr = ~np.isclose( self.raw_frame["y_value"].values[:, None], array2, atol=0.1 ).any(axis=1) @@ -887,12 +960,12 @@ def cls(self): def drop( self, - x: Union[float, int, Iterable] = None, - y: Union[float, int, Iterable] = None, - x_min: float = None, - x_max: float = None, - y_min: float = None, - y_max: float = None, + x: Union[np.float32, int, Iterable] = None, + y: Union[np.float32, int, Iterable] = None, + x_min: np.float32 = None, + x_max: np.float32 = None, + y_min: np.float32 = None, + y_max: np.float32 = None, ): """ delete specific values (x, y) @@ -972,7 +1045,7 @@ def load_data( self.y_raw = list(raw_csv[y_col_name].values) self.z_raw = list(raw_csv[z_col_name].values) - def __drop_the(self, column: str, value_s: Union[float, int, Iterable]): + def __drop_the(self, column: str, value_s: Union[np.float32, int, Iterable]): decimals = self.__x_step_DP if column == "x_value" else self.__y_step_DP @@ -1009,8 +1082,8 @@ def __drop_the(self, column: str, value_s: Union[float, int, Iterable]): def __drop_the_cols( self, - x_values: Union[float, int, Iterable], - y_values: Union[float, int, Iterable], + x_values: Union[np.float32, int, Iterable], + y_values: Union[np.float32, int, Iterable], ): x_decimals, y_decimals = self.__x_step_DP, self.__y_step_DP @@ -1070,7 +1143,7 @@ def __drop_the_cols( pass def __config_closest_values( - self, input_value: Union[float, int, Iterable], base_array: Iterable + self, input_value: Union[np.float32, int, Iterable], base_array: Iterable ): """ @@ -1080,7 +1153,7 @@ def __config_closest_values( :return: """ - if isinstance(input_value, float) or isinstance(input_value, int): + if isinstance(input_value, np.float32) or isinstance(input_value, int): input_value = self.__find_nearest_universal(base_array, input_value) else: input_value_temp = [] @@ -1093,11 +1166,11 @@ def __config_closest_values( def __config_arrays_from( self, - min_value: float, - max_value: float, - step: float, - array: Union[float, int, Iterable], - array_set_input_in_func: Union[float, int, Iterable], + min_value: np.float32, + max_value: np.float32, + step: np.float32, + array: Union[np.float32, int, Iterable], + array_set_input_in_func: Union[np.float32, int, Iterable], column: str = "x_value", ): @@ -1141,7 +1214,7 @@ def __smooth_list_gaussian(self, list1, degree=5): weightGauss = [] for i in range(window): i = i - degree + 1 - frac = i / float(window) + frac = i / np.float32(window) gauss = 1 / (np.exp((4 * frac) ** 2)) weightGauss.append(gauss) weight = np.array(weightGauss) * weight diff --git a/pyquac/fmn_app.py b/pyquac/fmn_app.py index 7180d8b..011b040 100644 --- a/pyquac/fmn_app.py +++ b/pyquac/fmn_app.py @@ -4,9 +4,10 @@ This is where we define the various css items to fetch as well as the layout of our application. """ -from dash import dcc, callback, ctx -from dash.dependencies import Input, Output -from dash import html +from dash import dcc, callback, callback_context +from dash.dependencies import Input, Output, State +from dash import html, ctx +import plotly.graph_objects as go import dash_bootstrap_components as dbc from dash_bootstrap_templates import load_figure_template from jupyter_dash import JupyterDash @@ -14,8 +15,13 @@ from pyquac.settings import settings from pyquac.components.navbar import navbar from pyquac.components.sidebar import sidebar, content +from pyquac.components.modal import modal + +# from pyquac.components.property_nav import property_nav + from pyquac.components.property_nav import property_nav from pyquac.components.heatmap import figure_layout +from dash.exceptions import PreventUpdate import numpy as np @@ -25,15 +31,47 @@ ICONS = dbc.icons.BOOTSTRAP load_figure_template("ZEPHYR") +STORE = { + "temp": None, + "side_click": "SHOW", + "xy_lines_state": settings.init_xy_lines_state, + "x_raw": None, + "y_raw": None, + "z_raw": None, + "x_scatter": None, + "y_scatter": None, + "xz_scatter": None, + "yz_scatter": None, + "x_click": None, + "y_click": None, + "x_label": settings.init_x_label, + "y_label": settings.init_y_label, +} app = JupyterDash(__name__, external_stylesheets=[THEME, CSS, ICONS]) app.title = settings.app_name +def is_port_in_use(port: int) -> bool: + import socket + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + return s.connect_ex(("localhost", port)) == 0 + + ########################## App Layout ########################## -def conf_app(data): +def conf_app(spectroscopy: dict, cmap: str = settings.init_cmap): + + data = spectroscopy["data"] + qubit_id = spectroscopy["qubit_id"] + chip_id = spectroscopy["chip_id"] + spectroscopy_type = spectroscopy["type"].upper() + + if spectroscopy_type not in ["TTS", "STS"]: + raise AttributeError + def serve_layout(): """Define the layout of the application @@ -42,24 +80,26 @@ def serve_layout(): """ return html.Div( children=[ - dcc.Store(id="side_click", data="SHOW"), - dcc.Store(id="z_store", data=data.njit_result), - dcc.Store(id="x_store", data=data.x_1d), - dcc.Store(id="y_store", data=data.y_1d), - dcc.Store(id="y_scatter", data=None), - dcc.Store(id="z_scatter", data=None), - # dcc.Store(id="update_interval", data=settings.init_interval), - # dcc.Store(id="max_interval_value", data=settings.init_max_interval), - dcc.Store(id="x_label", data=settings.init_x_label), - dcc.Store(id="y_label", data=settings.init_y_label), - dcc.Store(id="cmap", data=settings.init_cmap), + *[ + dcc.Store(id=store[0], data=store[1], storage_type="session") + for store in STORE.items() + ], + dcc.Store(id="z_store", data=data.njit_result, storage_type="session"), + dcc.Store(id="x_store", data=data.x_1d, storage_type="session"), + dcc.Store(id="y_store", data=data.y_1d, storage_type="session"), + dcc.Store( + id="save_attributes", + data=[qubit_id, chip_id, spectroscopy_type], + storage_type="session", + ), + dcc.Store(id="cmap", data=cmap, storage_type="session"), dcc.Interval( id="interval-graph-update", interval=settings.init_interval, n_intervals=0, max_intervals=settings.init_max_interval, ), - dcc.Interval(id="clientside-interval", n_intervals=0, interval=250), + # dcc.Interval(id="clientside-interval", n_intervals=0, interval=250), dbc.Row( [ dbc.Col(navbar), @@ -67,14 +107,17 @@ def serve_layout(): ), dbc.Row( [ - dbc.Col(children=[sidebar(data), content], width=3), + dbc.Col( + children=[sidebar(data), content, modal], + width=3, + ), dbc.Col( [ - # dbc.Row( - # children=[ - # property_nav, - # ] - # ), + dbc.Row( + children=[ + property_nav, + ] + ), dbc.Row( children=[ figure_layout( @@ -83,13 +126,6 @@ def serve_layout(): settings.init_y_label, settings.init_cmap, ), - # dcc.Graph( - # id="empty", - # figure=go.Scatter( - # x=[1, 2, 3], y=[1, 2, 3], mode="lines" - # ), - # style={"display": "none"}, - # ), ] ), ] @@ -103,54 +139,59 @@ def serve_layout(): Output("x_store", "data"), Output("y_store", "data"), Output("z_store", "data"), + Output("x_raw", "data"), + Output("y_raw", "data"), + Output("z_raw", "data"), Input("interval-graph-update", "n_intervals"), + # State("temp", "data"), ) def update_fig_data(i): + # print(d) return ( data.x_1d, data.y_1d, data.njit_result, + data.x_raw, + data.y_raw, + data.z_raw, ) @callback( + Output("x_scatter", "data"), Output("y_scatter", "data"), - Output("z_scatter", "data"), + Output("xz_scatter", "data"), + Output("yz_scatter", "data"), + Output("x_click", "data"), + Output("y_click", "data"), Input("heatmap", "clickData"), - Input("x-slider", "value"), + State("x_raw", "data"), + State("y_raw", "data"), + State("z_raw", "data"), ) - def update_click_data(click, x_slider): - triggered_id = ctx.triggered_id - if (click == None) and (x_slider == None): - y_scatter, z_scatter = None, None - - if triggered_id == "heatmap": - if click is None: - y_scatter, z_scatter = None, None - else: - x_click = click["points"][0]["x"] - print(x_click) - mask = np.equal(data.x_raw, np.array(x_click)) - y_scatter = np.array(data.y_raw)[mask] - z_scatter = np.array(data.z_raw)[mask] - - if triggered_id == "x-slider": - print("check") - mask = np.equal(data.x_raw, np.array(x_slider)) - y_scatter = np.array(data.y_raw)[mask] - z_scatter = np.array(data.z_raw)[mask] - return y_scatter, z_scatter - - # @callback( - # Output("y_scatter", "data"), - # Output("z_scatter", "data"), - # Input("x-slider", "value"), - # ) - # def update_slide_data_x(x_val): - - # mask = np.equal(data.x_raw, np.array(x_val)) - # y_scatter = np.array(data.y_raw)[mask] - # z_scatter = np.array(data.z_raw)[mask] - # return y_scatter, z_scatter + def update_click_data(click, x_raw, y_raw, z_raw): + if (click is None) or (click["points"][0]["curveNumber"] != 0): + raise PreventUpdate + + data_click = click["points"][0] + x_click = data_click["x"] + y_click = data_click["y"] + + x_mask = np.equal(np.array(x_raw), np.array(x_click)) + y_mask = np.equal(np.array(y_raw), np.array(y_click)) + x_scatter = np.array(x_raw)[y_mask] + y_scatter = np.array(y_raw)[x_mask] + + xz_scatter = np.array(z_raw)[y_mask] + yz_scatter = np.array(z_raw)[x_mask] + + return ( + x_scatter, + y_scatter, + xz_scatter, + yz_scatter, + x_click, + y_click, + ) app.layout = serve_layout return app diff --git a/pyquac/settings.py b/pyquac/settings.py index 0aa22d0..6329930 100644 --- a/pyquac/settings.py +++ b/pyquac/settings.py @@ -11,7 +11,7 @@ class Settings(BaseSettings): # Dash/Plotly debug: bool = True css_url: str = r"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" - app_name: str = "FMN lab | pyquac" + app_name: str = "fmn | pyquac" app_logo: str = r"/assets/logo_sign.png" app_logo_url: str = ( r"https://github.com/ikaryss/pyquac/blob/main/images/logo_sign.png?raw=true" @@ -20,7 +20,8 @@ class Settings(BaseSettings): app_link: str = r"https://fmn.bmstu.ru/" app_linkedin_url: str = r"https://fmn.bmstu.ru/" app_github_url: str = r"https://github.com/ikaryss/pyquac" - transition_time: str = "all 0.1s" + transition_time: str = "all 0.5s" + margin_transition_time: str = "margin-left .5s" # App settings init_interval: int = 3000 @@ -28,6 +29,10 @@ class Settings(BaseSettings): init_x_label: str = "Voltages, V" init_y_label: str = "Frequencies, GHz" init_cmap: str = "rdylbu" + init_xy_lines_state: bool = False + + # Saving settings + default_root_path: str = r"D:/Scripts/Measurement_automation/data/qubits/" class Config: """