Browse Source

Fix echarts-streamlit state bug.
Improve |S| echart.
Add dB scaling for |S|.
Show k and ks in output.
Move some completed frontend functions into separate files

ricet8ur 2 years ago
parent
commit
86e6d85352
4 changed files with 330 additions and 256 deletions
  1. 18 12
      source/backend/calc.py
  2. 74 0
      source/frontend/data_parsing_utils.py
  3. 91 244
      source/frontend/front.py
  4. 147 0
      source/frontend/show_echart.py

+ 18 - 12
source/backend/calc.py

@@ -26,7 +26,7 @@ def prepare_data(freq, re, im, fl=None):
 
 
     # frequency of unloaded resonance.
     # frequency of unloaded resonance.
     f0 = fl
     f0 = fl
-    # f0 = fl does not decrease the accuracy if Q >> 100 
+    # f0 = fl does not decrease the accuracy if Q >> 100
     e1, e2, e3, gamma, p = [], [], [], [], []
     e1, e2, e3, gamma, p = [], [], [], [], []
     for i in range(0, len(freq)):
     for i in range(0, len(freq)):
         # filling vectors
         # filling vectors
@@ -121,7 +121,7 @@ def fl_fitting(freq, re, im, correction):
     a, c, d = solution(data)
     a, c, d = solution(data)
     Ql, Q, sigmaQ0, sigmaQl = None, None, None, None
     Ql, Q, sigmaQ0, sigmaQl = None, None, None, None
     # Repeated curve fitting
     # Repeated curve fitting
-    # 1.189 of Qfactor Matlab 
+    # 1.189 of Qfactor Matlab
     # fl2 = 0
     # fl2 = 0
     # g_d=0
     # g_d=0
     # g_c=0
     # g_c=0
@@ -136,21 +136,27 @@ def fl_fitting(freq, re, im, correction):
     a, c, d, Ql, diam, k, Q, sigma2A, sigmaQ0, sigmaQl, data = recalculating(data, a, c, d, 20)
     a, c, d, Ql, diam, k, Q, sigma2A, sigmaQ0, sigmaQl, data = recalculating(data, a, c, d, 20)
 
 
     # taking into account coupling losses on page 69 of Qfactor Matlab
     # taking into account coupling losses on page 69 of Qfactor Matlab
-    # to get results similar to example program 
+    # to get results similar to example program
+    ks = 0
     if correction:
     if correction:
-        phi1=np.arctan(np.double(g_d.imag/g_d.real)) # 1.239
-        phi2=np.arctan(np.double((g_c.imag-g_d.imag)/(g_c.real-g_d.real)))
-        phi=-phi1+phi2
-        d_s=(1-np.abs(g_d)**2)/(1-np.abs(g_d)*np.cos(phi))
+        phi1 = np.arctan(np.double(g_d.imag / g_d.real))  # 1.239
+        phi2 = np.arctan(
+            np.double((g_c.imag - g_d.imag) / (g_c.real - g_d.real)))
+        phi = -phi1 + phi2
+        d_s = (1 - np.abs(g_d)**2) / (1 - np.abs(g_d) * np.cos(phi))
         diam = abs(a[1] - a[0] / a[2])
         diam = abs(a[1] - a[0] / a[2])
-        qk=1/(d_s/diam-1)
-    
+
+        qk = 1 / (d_s / diam - 1)
+        k = qk
+
+        ks = (2 / d_s - 1) / (2 / diam - 2 / d_s)
+
         sigma2A = recalculation_of_data(data, a, c, d, error=True)
         sigma2A = recalculation_of_data(data, a, c, d, error=True)
         sigmaQ0, sigmaQl = random_deviation(a, sigma2A, diam, k, Ql)
         sigmaQ0, sigmaQl = random_deviation(a, sigma2A, diam, k, Ql)
-        Q = Ql * (1 + qk)  # Q-factor = result
-        # print(f"Q0 = {Q} +- {sigmaQ0}")
+        Q = Ql * (1 + k)  # Q-factor = result
+
 
 
     t = 2*(np.array(freq)-fl)/fl
     t = 2*(np.array(freq)-fl)/fl
     fitted_mag_s = abs((a[0]*t+a[1])/(a[2]*t+1))
     fitted_mag_s = abs((a[0]*t+a[1])/(a[2]*t+1))
 
 
-    return Q, sigmaQ0, Ql, sigmaQl, a, fl, fitted_mag_s
+    return Q, sigmaQ0, Ql, sigmaQl, k, ks, a, fl, fitted_mag_s

+ 74 - 0
source/frontend/data_parsing_utils.py

@@ -0,0 +1,74 @@
+import math
+from typing import List, Tuple, Union
+
+def is_float(element) -> bool:
+    try:
+        float(element)
+        val = float(element)
+        if math.isnan(val) or math.isinf(val):
+            raise ValueError
+        return True
+    except ValueError:
+        return False
+
+
+# (to utf-8)
+# status returned
+def read_data(data: list) -> str:
+    for x in range(len(data)):
+        if type(data[x]) == bytes:
+            try:
+                data[x] = data[x].decode('utf-8-sig', 'ignore')
+            except:
+                return 'Not an utf-8-sig line №: ' + str(x)
+    return 'data read, but not parsed'
+
+
+# check if line has comments
+# first is a comment line according to .snp documentation,
+# others detects comments in various languages
+def check_line_comments(line: str) -> Union[str, None]:
+    if len(line) < 2 or line[0] == '!' or line[0] == '#' or line[
+            0] == '%' or line[0] == '/':
+        return None
+    else:
+        # generally we expect these chars as separators
+        line = line.replace(';', ' ').replace(',', ' ').replace('|', ' ')
+        if '!' in line:
+            line = line[:line.find('!')]
+        return line
+
+# unpack a few first lines of the file to get number of ports
+def count_columns(data: List[str]) -> Tuple[int, str]:
+    return_status = 'data parsed'
+    column_count = 0
+    for x in range(len(data)):
+        line = check_line_comments(data[x])
+        if line is None:
+            continue
+        line = line.split()
+        # always at least 3 values for single data point
+        if len(line) < 3:
+            return_status = 'Can\'t parse line № ' + \
+                str(x) + ',\n not enough arguments (less than 3)'
+            break
+        column_count = len(line)
+        break
+    return (column_count, return_status)
+
+
+def prepare_snp(data: List[str], number: int) -> Tuple[List[str], str]:
+    prepared_data = []
+    return_status = 'data read, but not parsed'
+    for x in range(len(data)):
+        line = check_line_comments(data[x])
+        if line is None:
+            continue
+        splitted_line = line.split()
+        if number * 2 + 1 == len(splitted_line):
+            prepared_data.append(line)
+        elif number * 2 == len(splitted_line):
+            prepared_data[-1] += line
+        else:
+            return_status = "Parsing error for .snp format on line №" + str(x)
+    return prepared_data, return_status

+ 91 - 244
source/frontend/front.py

@@ -1,122 +1,12 @@
 import math
 import math
+from time import perf_counter
 import streamlit as st
 import streamlit as st
 import matplotlib.pyplot as plt
 import matplotlib.pyplot as plt
 import numpy as np
 import numpy as np
 import sigfig
 import sigfig
 from streamlit_ace import st_ace
 from streamlit_ace import st_ace
-from streamlit_echarts import st_echarts, JsCode
-
-
-# So that you can choose an interval of points on which we apply q-calc algorithm
-def plot_interact_abs_from_f(f, r, i, interval_range):
-    if interval_range is None:
-        interval_range = (0, 100)
-
-    # fix the new file upload without echart interval refresh - dataZoom does not update it itself
-    if 'interval_range' not in st.session_state:
-        st.session_state.interval_range = (0, 100)
-
-    interval_range = st.session_state.interval_range
-
-    abs_S = list(abs(np.array(r) + 1j * np.array(i)))
-    # echarts for datazoom https://discuss.streamlit.io/t/streamlit-echarts/3655
-    # datazoom https://echarts.apache.org/examples/en/editor.html?c=line-draggable&lang=ts
-    # axis pointer values https://echarts.apache.org/en/option.html#axisPointer
-    options = {
-        "xAxis": {
-            "type": "category",
-            "data": f,
-            "name": "Hz",
-            "nameTextStyle": {
-                "fontSize": 16
-            },
-            "axisLabel": {
-                "fontSize": 16
-            },
-        },
-        "yAxis": {
-            "type": "value",
-            "name": "abs(S)",
-            "nameTextStyle": {
-                "fontSize": 16
-            },
-            "axisLabel": {
-                "fontSize": 16
-            },
-            # "axisPointer": {
-            #     "type": 'cross',
-            #     "label": {
-            #     "show":"true",
-            #     "formatter": JsCode(
-            #     "function(info){console.log(info);return 'line ' ;};"
-            #     ).js_code
-            #     }
-            # }
-        },
-        "series": [{
-            "data": abs_S,
-            "type": "line",
-            "name": "abs(S)"
-        }],
-        "height":
-        300,
-        "dataZoom": [{
-            "type": "slider",
-            "start": interval_range[0],
-            "end": interval_range[1],
-            "height": 100,
-            "bottom": 10
-        }],
-        "tooltip": {
-            "trigger": "axis",
-            "axisPointer": {
-                "type": 'cross',
-                # "label": {
-                # "show":"true",
-                # "formatter": JsCode(
-                # "function(info){console.log(info);return 'line ' ;};"
-                # ).js_code
-                # }
-            }
-        },
-        "toolbox": {
-            "feature": {
-                # "dataView": { "show": "true", "readOnly": "true" },
-                "restore": {
-                    "show": "true"
-                },
-            }
-        },
-    }
-    # DataZoom event is not fired on new file upload. There are no default event to fix it.
-    events = {
-        "dataZoom":
-        "function(params) { console.log('a');return ['dataZoom', params.start, params.end] }",
-        "restore": "function() { return ['restore'] }",
-    }
-
-    # show echart with dataZoom and update intervals based on output
-
-    get_event = st_echarts(options=options,
-                           events=events,
-                           height="500px",
-                           key="render_basic_bar_events")
-
-    if not get_event is None:
-        if get_event[0] == 'dataZoom':
-            interval_range = get_event[1:]
-            st.session_state.interval_range = interval_range
-        else:
-            if interval_range != (0, 100):
-                interval_range = (0, 100)
-                st.session_state.interval_range = interval_range
-                st.experimental_rerun()
-    # print(st.session_state.interval_range, interval_range)
-
-    n = len(f)
-    interval_start, interval_end = (int(n * interval_range[id] * 0.01)
-                                    for id in (0, 1))
-    return interval_range, interval_start, interval_end
+from .show_echart import plot_interact_abs_from_f
+from .data_parsing_utils import is_float, read_data, check_line_comments, count_columns, prepare_snp
 
 
 
 
 def circle(ax, x, y, radius, color='#1946BA'):
 def circle(ax, x, y, radius, color='#1946BA'):
@@ -128,11 +18,12 @@ def circle(ax, x, y, radius, color='#1946BA'):
                            zorder=2,
                            zorder=2,
                            linewidth=2,
                            linewidth=2,
                            edgecolor=color,
                            edgecolor=color,
-                           facecolor=(0, 0, 0, .0125))
+                           facecolor=(0, 0, 0, .0))
     ax.add_artist(drawn_circle)
     ax.add_artist(drawn_circle)
 
 
 
 
 def plot_smith(r, i, g, r_cut, i_cut):
 def plot_smith(r, i, g, r_cut, i_cut):
+    from matplotlib.image import AxesImage
     show_excluded_points = True
     show_excluded_points = True
     show_Abs_S_scale = False
     show_Abs_S_scale = False
     show_Re_Z_scale = False
     show_Re_Z_scale = False
@@ -141,13 +32,14 @@ def plot_smith(r, i, g, r_cut, i_cut):
     with st.expander("Smith chart options"):
     with st.expander("Smith chart options"):
         show_excluded_points = st.checkbox("Show excluded points",
         show_excluded_points = st.checkbox("Show excluded points",
                                            value=show_excluded_points)
                                            value=show_excluded_points)
-        show_Abs_S_scale = st.checkbox("Show abs(S) lines",
+        show_grid = st.checkbox("Show grid", value=show_grid)
+        
+        show_Abs_S_scale = st.checkbox("Show |S| gridlines",
                                        value=show_Abs_S_scale)
                                        value=show_Abs_S_scale)
-        show_Re_Z_scale = st.checkbox("Show Re(Z) lines",
+        show_Re_Z_scale = st.checkbox("Show Re(Z) gridlines",
                                       value=show_Re_Z_scale)
                                       value=show_Re_Z_scale)
-        show_Im_Z_scale = st.checkbox("Show Im(Z) lines",
+        show_Im_Z_scale = st.checkbox("Show Im(Z) gridlines",
                                       value=show_Im_Z_scale)
                                       value=show_Im_Z_scale)
-        show_grid = st.checkbox("Show grid", value=show_grid)
 
 
     fig = plt.figure(figsize=(10, 10))
     fig = plt.figure(figsize=(10, 10))
     ax = fig.add_subplot()
     ax = fig.add_subplot()
@@ -160,8 +52,8 @@ def plot_smith(r, i, g, r_cut, i_cut):
     ax.set_yticks(minor_ticks, minor=True)
     ax.set_yticks(minor_ticks, minor=True)
     ax.grid(which='major', color='grey', linewidth=1.5)
     ax.grid(which='major', color='grey', linewidth=1.5)
     ax.grid(which='minor', color='grey', linewidth=0.5, linestyle=':')
     ax.grid(which='minor', color='grey', linewidth=0.5, linestyle=':')
-    plt.xlabel('$Re(\Gamma)$', color='gray', fontsize=16, fontname="Cambria")
-    plt.ylabel('$Im(\Gamma)$', color='gray', fontsize=16, fontname="Cambria")
+    plt.xlabel('$Re(S)$', color='gray', fontsize=16, fontname="Cambria")
+    plt.ylabel('$Im(S)$', color='gray', fontsize=16, fontname="Cambria")
     plt.title('Smith chart', fontsize=24, fontname="Cambria")
     plt.title('Smith chart', fontsize=24, fontname="Cambria")
 
 
     # unit circle
     # unit circle
@@ -173,22 +65,25 @@ def plot_smith(r, i, g, r_cut, i_cut):
     background_img_x = -1.981
     background_img_x = -1.981
     background_img_y = -1.949
     background_img_y = -1.949
     background_img_box = [
     background_img_box = [
-        background_img_x, 
-        background_img_x + 3.87, 
+        background_img_x,
+        background_img_x + 3.87,
         background_img_y,
         background_img_y,
         background_img_y + 3.87
         background_img_y + 3.87
     ]
     ]
     if show_Abs_S_scale:
     if show_Abs_S_scale:
+        # imshow is extremely slow
+        # TODO draw primitives in place
         background = plt.imread("./source/frontend/images/s.png")
         background = plt.imread("./source/frontend/images/s.png")
-        background = ax.imshow(background, extent=background_img_box)
+        background = ax.imshow(background, extent=background_img_box, interpolation= 'none')
+
 
 
     if show_Re_Z_scale:
     if show_Re_Z_scale:
         background = plt.imread("./source/frontend/images/re(z).png")
         background = plt.imread("./source/frontend/images/re(z).png")
-        background = ax.imshow(background, extent=background_img_box)
+        background = ax.imshow(background, extent=background_img_box, interpolation= 'none')
 
 
     if show_Im_Z_scale:
     if show_Im_Z_scale:
         background = plt.imread("./source/frontend/images/im(z).png")
         background = plt.imread("./source/frontend/images/im(z).png")
-        background = ax.imshow(background, extent=background_img_box)
+        background = ax.imshow(background, extent=background_img_box, interpolation= 'none')
 
 
     # input data points
     # input data points
     if show_excluded_points:
     if show_excluded_points:
@@ -210,17 +105,28 @@ def plot_smith(r, i, g, r_cut, i_cut):
     st.pyplot(fig)
     st.pyplot(fig)
 
 
 
 
-# plot (abs(S))(f) chart with pyplot
+# plot abs(S) vs f chart with pyplot
 def plot_abs_vs_f(f, r, i, fitted_mag_s):
 def plot_abs_vs_f(f, r, i, fitted_mag_s):
     fig = plt.figure(figsize=(10, 10))
     fig = plt.figure(figsize=(10, 10))
-    abs_S = list((r[n]**2 + i[n]**2)**0.5 for n in range(len(r)))
+    s = np.abs(np.array(r) + 1j * np.array(i))
+    if st.session_state.legendselection == '|S| (dB)':
+        m = np.min(np.where(s==0, np.inf, s))
+        s = list(20*np.where(s==0, np.log10(m), np.log10(s)))
+        m = np.min(np.where(s==0, np.inf, fitted_mag_s))
+        fitted_mag_s = list(20*np.where(s==0, np.log10(m), np.log10(fitted_mag_s)))
+    s = list(s)
+    min_f = min(f)
+    max_f = max(f)
     xlim = [
     xlim = [
-        min(f) - abs(max(f) - min(f)) * 0.1,
-        max(f) + abs(max(f) - min(f)) * 0.1
+        min_f - abs(max_f - min_f) * 0.1,
+        max_f + abs(max_f - min_f) * 0.1
     ]
     ]
+    min_s = min(s)
+    max_s = max(s)
+    print(min_s,max_s)
     ylim = [
     ylim = [
-        min(abs_S) - abs(max(abs_S) - min(abs_S)) * 0.5,
-        max(abs_S) + abs(max(abs_S) - min(abs_S)) * 0.5
+        min_s - abs(max_s - min_s) * 0.5,
+        max_s + abs(max_s - min_s) * 0.5
     ]
     ]
     ax = fig.add_subplot()
     ax = fig.add_subplot()
     ax.set_xlim(xlim)
     ax.set_xlim(xlim)
@@ -228,41 +134,21 @@ def plot_abs_vs_f(f, r, i, fitted_mag_s):
     ax.grid(which='major', color='k', linewidth=1)
     ax.grid(which='major', color='k', linewidth=1)
     ax.grid(which='minor', color='grey', linestyle=':', linewidth=0.5)
     ax.grid(which='minor', color='grey', linestyle=':', linewidth=0.5)
     plt.xlabel(r'$f,\; 1/c$', color='gray', fontsize=16, fontname="Cambria")
     plt.xlabel(r'$f,\; 1/c$', color='gray', fontsize=16, fontname="Cambria")
-    plt.ylabel('$|S|$', color='gray', fontsize=16, fontname="Cambria")
-    plt.title('Abs(S) vs frequency', fontsize=24, fontname="Cambria")
+    if st.session_state.legendselection == '|S| (dB)':
+        plt.ylabel('$|S|$ (dB)', color='gray', fontsize=16, fontname="Cambria")
+        plt.title('|S| (dB) vs frequency', fontsize=24, fontname="Cambria")
+    else:
+        plt.ylabel('$|S|$', color='gray', fontsize=16, fontname="Cambria")
+        plt.title('|S| vs frequency', fontsize=24, fontname="Cambria")
 
 
-    ax.plot(f, abs_S, '+', ms=8, mew=2, color='#1946BA')
+    ax.plot(f, s, '+', ms=8, mew=2, color='#1946BA')
 
 
     ax.plot(f, fitted_mag_s, '-', linewidth=3, color='#FF8400')
     ax.plot(f, fitted_mag_s, '-', linewidth=3, color='#FF8400')
 
 
-    # radius = abs(g[1] - g[0] / g[2]) / 2
-    # x = ((g[1] + g[0] / g[2]) / 2).real
-    # y = ((g[1] + g[0] / g[2]) / 2).imag
     st.pyplot(fig)
     st.pyplot(fig)
 
 
 
 
 def run(calc_function):
 def run(calc_function):
-
-    def is_float(element) -> bool:
-        try:
-            float(element)
-            val = float(element)
-            if math.isnan(val) or math.isinf(val):
-                raise ValueError
-            return True
-        except ValueError:
-            return False
-
-    # to utf-8
-    def read_data(data):
-        for x in range(len(data)):
-            if type(data[x]) == bytes:
-                try:
-                    data[x] = data[x].decode('utf-8-sig', 'ignore')
-                except:
-                    return 'Not an utf-8-sig line №: ' + str(x)
-        return 'data read, but not parsed'
-
     # for Touchstone .snp format
     # for Touchstone .snp format
     def parse_heading(data):
     def parse_heading(data):
         nonlocal data_format_snp
         nonlocal data_format_snp
@@ -291,57 +177,6 @@ def run(calc_function):
                     break
                     break
         return 1, 0, 0, 50
         return 1, 0, 0, 50
 
 
-    # check if line has comments
-    # first is a comment line according to .snp documentation,
-    # others detects comments in various languages
-    def check_line_comments(line):
-        if len(line) < 2 or line[0] == '!' or line[0] == '#' or line[
-                0] == '%' or line[0] == '/':
-            return None
-        else:
-            # generally we expect these chars as separators
-            line = line.replace(';', ' ').replace(',', ' ')
-            if '!' in line:
-                line = line[:line.find('!')]
-            return line
-
-    # unpack a few first lines of the file to get number of ports
-    def count_columns(data):
-        return_status = 'data parsed'
-        column_count = 0
-        for x in range(len(data)):
-            line = check_line_comments(data[x])
-            if line is None:
-                continue
-            line = line.split()
-            # always at least 3 values for single data point
-            if len(line) < 3:
-                return_status = 'Can\'t parse line № ' + \
-                    str(x) + ',\n not enough arguments (less than 3)'
-                break
-            column_count = len(line)
-            break
-        return column_count, return_status
-
-    def prepare_snp(data, number):
-        prepared_data = []
-        return_status = 'data read, but not parsed'
-        for x in range(len(data)):
-            line = check_line_comments(data[x])
-            if line is None:
-                continue
-
-            splitted_line = line.split()
-            if number * 2 + 1 == len(splitted_line):
-                prepared_data.append(line)
-            elif number * 2 == len(splitted_line):
-                prepared_data[-1] += line
-            else:
-                return_status = "Parsing error for .snp format on line №" + str(
-                    x)
-
-        return prepared_data, return_status
-
     def unpack_data(data, first_column, column_count, ref_resistance,
     def unpack_data(data, first_column, column_count, ref_resistance,
                     ace_preview_markers):
                     ace_preview_markers):
         nonlocal select_measurement_parameter
         nonlocal select_measurement_parameter
@@ -448,9 +283,6 @@ def run(calc_function):
 
 
         return f, r, i, return_status
         return f, r, i, return_status
 
 
-    # make accessible a specific range of numerical data choosen with interactive plot
-    # percent, line id, line id
-    interval_range, interval_start, interval_end = None, None, None
 
 
     # info
     # info
     with st.expander("Info"):
     with st.expander("Info"):
@@ -544,8 +376,6 @@ def run(calc_function):
 
 
             # Ace editor to show choosen data columns and rows
             # Ace editor to show choosen data columns and rows
             with col2.expander("File preview"):
             with col2.expander("File preview"):
-                # st.button(copy selection)
-
                 # So little 'official' functionality in libs and lack of documentation
                 # So little 'official' functionality in libs and lack of documentation
                 # therefore beware: css hacks
                 # therefore beware: css hacks
 
 
@@ -562,12 +392,14 @@ def run(calc_function):
                 # color: rgb(49, 51, 63);
                 # color: rgb(49, 51, 63);
                 # }</style>''', unsafe_allow_html=True)
                 # }</style>''', unsafe_allow_html=True)
 
 
-                # markdown injection does not seems to work, since ace is in a different .html accessible via iframe
+                # markdown injection does not seems to work, 
+                # since ace is in a different .html accessible via iframe
 
 
                 # markers format:
                 # markers format:
                 #[{"startRow": 2,"startCol": 0,"endRow": 2,"endCol": 3,"className": "ace_error-marker","type": "text"}]
                 #[{"startRow": 2,"startCol": 0,"endRow": 2,"endCol": 3,"className": "ace_error-marker","type": "text"}]
 
 
-                # add marking for choosen data lines TODO
+                # add marking for choosen data lines?
+                # todo or not todo?
                 ace_preview_markers.append({
                 ace_preview_markers.append({
                     "startRow": input_start_line - 1,
                     "startRow": input_start_line - 1,
                     "startCol": 0,
                     "startCol": 0,
@@ -597,13 +429,13 @@ def run(calc_function):
         if column_count > 3:
         if column_count > 3:
             pair_count = (column_count - 1) // 2
             pair_count = (column_count - 1) // 2
             input_ports_pair = st.number_input(
             input_ports_pair = st.number_input(
-                "Choosen pair of ports with network parameters:",
+                "Choose pair of ports with network parameters:",
                 min_value=1,
                 min_value=1,
                 max_value=pair_count,
                 max_value=pair_count,
                 value=1)
                 value=1)
             input_ports_pair_id = input_ports_pair - 1
             input_ports_pair_id = input_ports_pair - 1
             ports_count = round(pair_count**0.5)
             ports_count = round(pair_count**0.5)
-            st.write(select_measurement_parameter +
+            st.write('Choosen ports: ' + select_measurement_parameter +
                      str(input_ports_pair_id // ports_count + 1) +
                      str(input_ports_pair_id // ports_count + 1) +
                      str(input_ports_pair_id % ports_count + 1))
                      str(input_ports_pair_id % ports_count + 1))
         f, r, i, validator_status = unpack_data(data,
         f, r, i, validator_status = unpack_data(data,
@@ -614,8 +446,12 @@ def run(calc_function):
         f = [x * hz for x in f]  # to hz
         f = [x * hz for x in f]  # to hz
 
 
     st.write("Use range slider to choose best suitable data interval")
     st.write("Use range slider to choose best suitable data interval")
-    interval_range, interval_start, interval_end = plot_interact_abs_from_f(
-        f, r, i, interval_range)
+
+    # make accessible a specific range of numerical data choosen with interactive plot
+    # line id, line id
+    interval_start, interval_end = plot_interact_abs_from_f(f,r,i)
+
+    # plot_interact_abs_from_f( f, r, i, interval_range)
 
 
     f_cut, r_cut, i_cut = [], [], []
     f_cut, r_cut, i_cut = [], [], []
     if validator_status == "data parsed":
     if validator_status == "data parsed":
@@ -640,7 +476,7 @@ def run(calc_function):
         col1, col2 = st.columns(2)
         col1, col2 = st.columns(2)
 
 
         check_coupling_loss = col1.checkbox(
         check_coupling_loss = col1.checkbox(
-            'Apply correction for coupling loss')
+            'Apply correction for coupling losses')
 
 
         if check_coupling_loss:
         if check_coupling_loss:
             col1.write("Option: Lossy coupling")
             col1.write("Option: Lossy coupling")
@@ -648,7 +484,7 @@ def run(calc_function):
             col1.write("Option: Cable attenuation")
             col1.write("Option: Cable attenuation")
 
 
         select_autoformat = col2.checkbox("Autoformat output", value=True)
         select_autoformat = col2.checkbox("Autoformat output", value=True)
-        precision = None
+        precision = '0.0f'
         if not select_autoformat:
         if not select_autoformat:
             precision = col2.slider("Precision",
             precision = col2.slider("Precision",
                                     min_value=0,
                                     min_value=0,
@@ -656,34 +492,45 @@ def run(calc_function):
                                     value=4)
                                     value=4)
             precision = '0.' + str(precision) + 'f'
             precision = '0.' + str(precision) + 'f'
 
 
-        Q0, sigmaQ0, QL, sigmaQL, circle_params, fl, fitted_mag_s = calc_function(
+        Q0, sigmaQ0, QL, sigmaQL, k, ks, circle_params, fl, fitted_mag_s = calc_function(
             f_cut, r_cut, i_cut, check_coupling_loss)
             f_cut, r_cut, i_cut, check_coupling_loss)
 
 
         if Q0 <= 0 or QL <= 0:
         if Q0 <= 0 or QL <= 0:
             st.write("Negative Q detected, fitting may be inaccurate!")
             st.write("Negative Q detected, fitting may be inaccurate!")
 
 
-        if select_autoformat:
-            st.latex(
-                r'Q_0 =' +
-                f'{sigfig.round(Q0, uncertainty=sigmaQ0, style="PDG")},  ' +
-                r'\;\;\varepsilon_{Q_0} =' +
-                f'{sigfig.round(sigmaQ0 / Q0, sigfigs=1, style="PDG")}')
-            st.latex(
-                r'Q_L =' +
-                f'{sigfig.round(QL, uncertainty=sigmaQL, style="PDG")},  ' +
-                r'\;\;\varepsilon_{Q_L} =' +
-                f'{sigfig.round(sigmaQL / QL, sigfigs=1, style="PDG")}')
-        else:
-            st.latex(r'Q_0 =' + f'{format(Q0, precision)} \pm ' +
-                     f'{format(sigmaQ0, precision)},  ' +
-                     r'\;\;\varepsilon_{Q_0} =' +
-                     f'{format(sigmaQ0 / Q0, precision)}')
-            st.latex(r'Q_L =' + f'{format(QL, precision)} \pm ' +
-                     f'{format(sigmaQL, precision)},  ' +
-                     r'\;\;\varepsilon_{Q_L} =' +
-                     f'{format(sigmaQL / QL, precision)}')
-        st.latex(r'f_L =' + f'{fl}' + 'Hz')
+        def show_result_in_latex(name, value, uncertainty=None):
+            nonlocal select_autoformat
+            if uncertainty is not None:
+                if select_autoformat:
+                    st.latex(
+                        name + ' =' +
+                        f'{sigfig.round(value, uncertainty=uncertainty, style="PDG")},  '
+                        + r'\;\;\varepsilon_{' + name + '} =' +
+                        f'{sigfig.round(uncertainty / value, sigfigs=1, style="PDG")}'
+                    )
+                else:
+                    st.latex(name + ' =' + f'{format(value, precision)} \pm ' +
+                             f'{format(uncertainty, precision)},  ' +
+                             r'\;\;\varepsilon_{' + name + '} =' +
+                             f'{format(uncertainty / value, precision)}')
+            else:
+                if select_autoformat:
+                    st.latex(name + ' =' +
+                             f'{sigfig.round(value, sigfigs=5, style="PDG")}')
+                else:
+                    st.latex(name + ' =' + f'{format(value, precision)}')
+
+        show_result_in_latex('Q_0', Q0, sigmaQ0)
+        show_result_in_latex('Q_L', QL, sigmaQL)
+        show_result_in_latex(r'\kappa', k)
+        if check_coupling_loss:
+            show_result_in_latex(r'\kappa_s', ks)
+
+        st.latex('f_L =' + f'{format(fl, precision)}' + r'\text{ }Hz')
+
         with st.expander("Show static abs(S) plot"):
         with st.expander("Show static abs(S) plot"):
             plot_abs_vs_f(f_cut, r_cut, i_cut, fitted_mag_s)
             plot_abs_vs_f(f_cut, r_cut, i_cut, fitted_mag_s)
 
 
+        t1= perf_counter()
         plot_smith(r, i, circle_params, r_cut, i_cut)
         plot_smith(r, i, circle_params, r_cut, i_cut)
+        print(perf_counter()-t1)

+ 147 - 0
source/frontend/show_echart.py

@@ -0,0 +1,147 @@
+import streamlit as st
+import numpy as np
+from streamlit_echarts import st_echarts, JsCode
+
+
+# So that you can choose an interval of points on which we apply q-calc algorithm
+def plot_interact_abs_from_f(f, r, i):
+    # for making it work smoothly with streamlit
+    if 'start' not in st.session_state or 'end' not in st.session_state:
+        st.session_state.start = 0
+        st.session_state.end = 100
+    if 'legendselection' not in st.session_state:
+        st.session_state.legendselection = '|S|'
+
+    # data
+    s = np.abs(np.array(r) + 1j * np.array(i))
+    abs_S = list(s)
+    # case (s[x] = 0) => (log10(s[x]) = log10(min(s)))
+    m = np.min(np.where(s == 0, np.inf, s))
+    db_abs_S = list(20 * np.where(s == 0, np.log10(m), np.log10(s)))
+
+    # echarts for datazoom https://discuss.streamlit.io/t/streamlit-echarts/3655
+    # datazoom https://echarts.apache.org/examples/en/editor.html?c=line-draggable&lang=ts
+    # axis pointer values https://echarts.apache.org/en/option.html#axisPointer
+    options = {
+        "legend": {
+            "data": ['|S|', '|S| (dB)'],
+            "selectedMode": "single",
+            "selected": {
+                '|S|': st.session_state.legendselection == '|S|',
+                '|S| (dB)': st.session_state.legendselection == '|S| (dB)'
+            }
+        },
+        "xAxis": {
+            "type": "category",
+            "data": f,
+            "name": "Hz",
+            "nameTextStyle": {
+                "fontSize": 16
+            },
+            "axisLabel": {
+                "fontSize":
+                16,
+                "formatter":
+                JsCode(
+                    "function(x){return Intl.NumberFormat('en-US').format(x).replaceAll(',',\"_\")}"
+                ).js_code
+            },
+            "axisPointer": {
+                "label": {
+                    "formatter":
+                    JsCode(
+                        "function(x){return Intl.NumberFormat('en-US').format(x.value).replaceAll(',',\"_\")}"
+                    ).js_code
+                }
+            }
+        },
+        "yAxis": {
+            "type": "value",
+            "axisLabel": {
+                "fontSize": 16,
+            },
+        },
+        "series": [{
+            "name": "|S|",
+            "data": abs_S,
+            "type": "line",
+        }, {
+            "name": "|S| (dB)",
+            "data": db_abs_S,
+            "type": "line",
+        }],
+        "height":
+        300,
+        "dataZoom": [{
+            "type": "slider",
+            "start": st.session_state.start,
+            "end": st.session_state.end,
+            "height": 100,
+            "bottom": 10
+        }],
+        "tooltip": {
+            "trigger":
+            "axis",
+            "axisPointer": {
+                "type": 'cross',
+                "label": {
+                    "show": "true",
+                }
+            },
+            "formatter":
+            JsCode("function(x){\
+                return \"frequency: \" + Intl.NumberFormat('en-US').format(x[0].name).replaceAll(',',\"_\")\
+                    + \" Hz<br>\" + x[0].seriesName + \": \" + x[0].data.toFixed(3) \
+            }").js_code
+        },
+        "toolbox": {
+            "feature": {
+                "restore": {
+                    "show": "true"
+                },
+            }
+        },
+    }
+
+    events = {
+        "dataZoom":
+        "function(params) { return ['dataZoom', params.start, params.end] }",
+        "restore":
+        "function() { return ['restore'] }",
+        "legendselectchanged":
+        "function(params){ return ['legendselectchanged', params.name] }"
+    }
+
+    # show echart with dataZoom and update intervals based on output
+    e = st_echarts(options=options,
+                   events=events,
+                   height="500px",
+                   key="echart_S")
+    # e - event from echarts
+    if e:
+        # DataZoom event is not fired on new file upload (and in some other cases)
+        # There is no 'default event' to fix it, so use st.experimental_rerun() with session_state
+        if e[0] == "restore":
+            if 0 != st.session_state.start or 100 != st.session_state.end:
+                st.session_state.start = 0
+                st.session_state.end = 100
+                st.experimental_rerun()
+
+        elif e[0] == "dataZoom":
+            if e[1] != st.session_state.start or e[2] != st.session_state.end:
+                st.session_state.start = e[1]
+                st.session_state.end = e[2]
+                st.experimental_rerun()
+
+        elif e[0] == "legendselectchanged":
+            if e[1] != st.session_state.legendselection:
+                print('c')
+                # Save selected type of series to state
+                st.session_state.legendselection = e[1]
+                # make chart state the same as actual state
+                st.experimental_rerun()
+
+    n = len(f)
+    interval_start, interval_end = int(n * st.session_state.start * 0.01), int(
+        n * st.session_state.end * 0.01)
+    return interval_start, interval_end