Black Lives Matter. Please consider donating to Black Girls Code today.
Dash HoloViews is now available! Check out the docs.

Trouble displaying three graphs from two upload sources

Hi,

I’m writing an application where the user can upload two files, and three graphs will populate. Two of the graphs are simple line plots, but the third plot is a three dimensional sphere. My general plan is that the 3d plot should not be generated until the first two plots are generated. I’m trying to ensure this by showing a “plot 3-d” button once the other two data sources have been loaded.

Here is some simple data that the user might upload:
theta = [0.0000000e+00, 2.6586253e+00, 2.7570542e+00, 1.5821534e+00, 6.3988400e-01]
phi = [0.0000000e+00, 1.3663791e+00, 1.1650666e+00, 1.1522869e+00, 2.3086835e-01]

If I comment out the callbacks: Input(‘output-theta-upload’, ‘children’), Input(‘output-phi-upload’, ‘children’), on the third graph, the first two will plot (see generate_3d_plot() below). I have tried also adding these two inputs to the callback as States, but no luck.

However, if I leave them in, the application does not work at all. I think it has to do with the initial state trying to access data that does not exist, but I’m not sure of the way around it. Here is the console error I’m seeing:

Here is my code. I’ve pieced most of this together from the user guide and other topics in this forum.

import base64
import datetime
import io

import dash
from dash.dependencies import Input, Output, Event, State
import dash_core_components as dcc
import dash_html_components as html
import dash_table_experiments as dt
import plotly.graph_objs as go

import pandas as pd
import json
from textwrap import dedent as d

import numpy as np


app=dash.Dash()
app.scripts.config.serve_locally=True

server=app.server

app.layout=html.Div([
# Plot the line charts
html.Div([
    html.Div([
        html.Div([
            # Uploaded thetha data
            dcc.Upload(
                id='upload-theta',
                children=html.Div([
                    html.A('Upload theta data')
                ]),
                style={
                    'width': '100%',
                    'height': '60px',
                    'lineHeight': '60px',
                    'borderWidth': '1px',
                    'borderStyle': 'dashed',
                    'borderRadius': '5px',
                    'textAlign': 'center',
                    'margin': '10px'
                },
                # Allow multiple files to be uploaded
                multiple=False
            ),
            # Div that will store the data
            html.Div(id='output-theta-upload', style={'display': 'none'}),

            # Div that will store the range
            html.Div(id='output-theta-selected-range', style={'display': 'none'}),

        ],className='six columns'),

        html.Div([
            dcc.Upload(
                id='upload-phi',
                children=html.Div([
                    html.A('Upload Phi Data')
                ]),
                style={
                    'width': '100%',
                    'height': '60px',
                    'lineHeight': '60px',
                    'borderWidth': '1px',
                    'borderStyle': 'dashed',
                    'borderRadius': '5px',
                    'textAlign': 'center',
                    'margin': '10px'
                },
                # Allow multiple files to be uploaded
                multiple=False
            ),
            # Div that will store the data
            html.Div(id='output-phi-upload', style={'display': 'none'}),

            # Div that will store the selected range
            html.Div(id='output-phi-selected-range', style={'display': 'none'}),

        ],className='six columns'),
    ],className='row'),
], className='container'),

html.Div([
    html.Div([
            html.Button(
                'Plot 3-d',
                className='two columns',
                id='button-click-3d',
                style={'display': 'none'},
                n_clicks=0
            ),
            dcc.Graph(id='plot-3d-sphere', style={'height': 600},className='eight columns')
    ],className='row')
],className='container'),

#Phi and Theta Graphs
html.Div([
    html.Div([
        html.Div([
            dcc.Graph(id='plot-theta', style={'height': 200})
        ],className='six columns'),
        html.Div([
            dcc.Graph(id='plot-phi', style={'height': 200})
        ],className='six columns')
    ],className='columns')
],className='container')
])

def sphr_to_cart(phi,theta, r=1, units='radians'):
    """
    Convert Spherical to Cartesian coordinates

    :phi:       angle phi in Spherical coordinates
    :theta:     angle theta in Spherical coordinates
    :units:     'radians' or 'degrees'
    """

    assert units in ['radians','degrees'], "units must be one of ['radians','degrees']"

    if units == 'degrees':
        phi=np.deg2rad(phi)
        theta=np.deg2rad(theta)

    # Cartesian coordinates
    x=r * np.sin(phi) * np.cos(theta)
    y=r * np.sin(phi) * np.sin(theta)
    z=r * np.cos(phi)

    return x,y,z

def parse_contents(contents, filename, date, header):
    print(filename)
    content_type, content_string=contents.split(',')
    decoded=base64.b64decode(content_string)
    try:
        if 'csv' in filename or 'txt' in filename:
            # Assume that the user uploaded a CSV file
            df=pd.read_csv(
                io.StringIO(decoded.decode('utf-8')),
                header=None,
                names=[header]
            )
        elif 'xls' in filename:
            # Assume that the user uploaded an excel file
            df=pd.read_excel(io.BytesIO(decoded))
    except Exception as e:
        print(e)
        return html.Div([
            'There was an error processing this file.'
        ])

    return df.to_json()

# Parse the uploaded theta data, store in div
@app.callback(Output('output-theta-upload', 'children'),
          [Input('upload-theta', 'contents'),
           Input('upload-theta', 'filename'),
           Input('upload-theta', 'last_modified')])
def update_output_theta(list_of_contents, list_of_names, list_of_dates):
    if list_of_contents is not None:
        children=parse_contents(list_of_contents, list_of_names, list_of_dates,'theta')
        return children
    else:
        return None

# parse the uploaded phi data, stor ein div
@app.callback(Output('output-phi-upload', 'children'),
          [Input('upload-phi', 'contents'),
           Input('upload-phi', 'filename'),
           Input('upload-phi', 'last_modified')])
def update_output_phi(list_of_contents, list_of_names, list_of_dates):
    if list_of_contents is not None:
        children=parse_contents(list_of_contents, list_of_names, list_of_dates,'phi')
        return children
    else:
        return None

# generate the theta figure
@app.callback(
    Output('plot-theta', 'figure'),
    [Input('output-theta-upload', 'children')]
)
def plot_theta(data):
    df=np.rad2deg(pd.read_json(data).sort_index(axis =0))

    figure=go.Figure(
        dict(
            data=[
                    go.Scatter(
                        x=df.index.values,
                        y=df['theta'].values,
                        name='Theta',
                    )
                ],
            layout=go.Layout(
                title='Theta',
                margin=go.Margin(l=40, r=0, t=40, b=30),
                xaxis=dict(
                    title='step',
                    titlefont=dict(
                        family='Times',
                        size=10,
                        color='#7f7f7f'
                    )
                ),
                yaxis=dict(
                    title='degrees',
                    titlefont=dict(
                        family='Times',
                        size=10,
                        color='#7f7f7f'
                    )
                )
            )

        )
    )
return figure

# generate the phi data
@app.callback(
    Output('plot-phi', 'figure'),
    [Input('output-phi-upload', 'children')]
)
def plot_phi(data):
    df=np.rad2deg(pd.read_json(data).sort_index(axis =0))

    figure=go.Figure(
        dict(
            data=[
                    go.Scatter(
                        x=df.index.values,
                        y=df['phi'].values,
                        name='Phi',
                    )
                ],
            layout=go.Layout(
                title='Phi',
                margin=go.Margin(l=40, r=0, t=40, b=30),
                xaxis=dict(
                    title='step',
                    titlefont=dict(
                        family='Times',
                        size=10,
                        color='#7f7f7f'
                    )
                ),
                yaxis=dict(
                    title='degrees',
                    titlefont=dict(
                        family='Times',
                        size=10,
                        color='#7f7f7f'
                    )
                )
            )
        )
    )
return figure

@app.callback(
    Output('plot-3d-sphere', 'figure'),
    [Input('button-click-3d','n_clicks'),
     Input('output-theta-upload', 'children'),
     Input('output-phi-upload', 'children')],
)
def generate_3d_plot(n_clicks,theta,phi):


    # Unit sphere for plotting,
    u, v=np.mgrid[0:2*np.pi:0.001*np.pi, 0:np.pi:0.001*np.pi]
    XS=np.cos(u)*np.sin(v)
    YS=np.sin(u)*np.sin(v)
    ZS=np.cos(v)

    if n_clicks > 0:

        # parse the data
        theta=pd.read_json(theta)
        phi=pd.read_json(phi)

        x,y,z=sphr_to_cart(phi, theta)
        color=np.arange(len(x))

        scatter=dict(
            type='scatter3d',
            x=x,
            y=y,
            z=z,
            mode='markers+lines',
            marker=dict(
                size=4,
                color=np.arange(100),
                colorscale='Viridis',
                colorbar=dict(title='Step Number')
            ),
            line=dict(
                color='rgb(0,0,0)',
                width=2
            ),
        )

        color=np.nan

        scatter=dict(
            type='scatter3d',
            x=x,
            y=y,
            z=z,
            mode='markers+lines',
            marker=dict(
                size=4,
                color=np.arange(100),
                colorscale='Viridis',
                colorbar=dict(title='Step Number')
            ),
            line=dict(
                color='rgb(0,0,0)',
                width=2
            ),
        )

        sphere=go.Surface(
                    x=XS,
                    y=YS,
                    z=ZS,
                    surfacecolor=np.zeros(XS.shape),
                    colorscale=[[0, 'rgb(255, 255, 255)'], [1, 'rgb(255, 255, 255)']],
                    opacity=0.3,
                    visible=True,
                    showscale=False
                )

        # Set the layout
        noaxis=dict(showbackground=False,
                      showgrid=False,
                      showline=True,
                      showticklabels=True,
                      ticks='',
                      title='',
                      zeroline=False)

        layout3d=go.Layout(title='Brownian Motion on Unit Sphere',
                        font=dict(family='Balto', size=14),
                        # scene=dict(xaxis=noaxis.update(title='X'),
                        #            yaxis=noaxis.update(title='Y'),
                        #            zaxis=noaxis.update(title='Z'),
                        #            aspectratio=dict(x=1,
                        #                             y=1,
                        #                             z=1),
                        #            camera=dict(
                        #                eye=dict(
                        #                    x=1.15,
                        #                    y=1.15,
                        #                    z=1.15
                        #                ),
                        #            )
                        #            ),

                        )

        fig=go.Figure(dict(data=[sphere], layout=layout3d))
    return fig

# Display 3d plot button when theta and phi are uploaded
@app.callback(
    Output('button-click-3d', 'style'),
    [Input('output-theta-upload', 'children'),
     Input('output-phi-upload', 'children')]
)
def display_button(theta,phi):
    try:
        theta=pd.read_json(theta)
        phi=pd.read_json(phi)

        if len(theta) > 0 and len(phi) > 0:
            return {'display': 'inline'}
    except:
        return {'display': 'none'}

external_css=[
    "https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css",
    "https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css",
    "https://codepen.io/chriddyp/pen/brPBPO.css"
]
for css in external_css:
    app.css.append_css({
        'external_url': css
    })

if __name__ == '__main__':
    app.run_server(debug=False)

Maybe you can try and hard-code your example data into your app such that as the app starts, it will just draw some initial start up figure. Then as you upload new phi and theta data, this initial data will be overwritten? This may be a workaround.
I am not strong enough in dash to tell if or where the error might be, sorry.
(Totally awesome idea btw! What are you gonna use the brownian sphere for? Teaching? … I am curious…)

Thanks, I’ll give it a shot, it seems like the null values are really the trouble makers. I’ll load some standard time series and then have the upload overwrite them. I’ll let you know if it works!

The sphere will be used as a teaching aid and research aid, if all goes well!

1 Like

It does seem that the 3d graph needed to be populated with data and drawn for this all to work, and it was the null values that were breaking the 3d plot. I also simplified the upload so that one file was loaded with the required column headers.

1 Like

Would you be able to share your code (and a test file to use to upload would be great)?

I think what I am trying to do is similar but I haven’t got it working yet.

@badgertgb this has evolved much since I wrote this post, I hope this is helpful.

Here is the current version of the app.

import base64
import datetime
import io

import dash
from dash.dependencies import Input, Output, Event, State
import dash_core_components as dcc
import dash_html_components as html
import dash_table_experiments as dt
import plotly.graph_objs as go

import pandas as pd
import json
from textwrap import dedent as d

import numpy as np


app=dash.Dash()
app.scripts.config.serve_locally=True

server=app.server


app.layout=html.Div([
    html.Div([
        html.H1('Brownian Motion on the Unit Sphere'),
        html.H5('Warning, do not refresh the page or all data will be lost!'),
    ],className = 'container'),
    # Plot the line charts
    html.Div([
        html.Div([
            html.Div([
                # Uploaded thetha data
                html.P('Upload your data. Input format should be a .csv with columns headers [theta,phi]'),
                dcc.Upload(
                    id='upload-data',
                    children=html.Div([
                        html.A('Click to Upload')
                    ]),
                    style={
                        'width': '100%',
                        'height': '60px',
                        'lineHeight': '60px',
                        'borderWidth': '1px',
                        'borderStyle': 'dashed',
                        'borderRadius': '5px',
                        'textAlign': 'center',
                        'margin': '10px'
                    },
                    # Allow multiple files to be uploaded
                    multiple=False
                ),

                # Div that will store the data
                html.Div(id='output-data-upload', style={'display': 'none'}),

                # Div that will store the range
                html.Div(id='output-data-selected-range', style={'display': 'none'}),
            ],className='twelve columns'),
        ],className='row'),
    ], className='container'),

    # Button to refresh sphere
    html.Div([
        html.Div([
            html.Button("Refresh Sphere",id='refresh-sphere',n_clicks = 0),
        ],className = 'row'),
    ],className='container'),

    # 3d chart
    html.Div([
        html.Div([
            dcc.Graph(id='plot-3d-sphere', style={'height': 600, 'align': 'center'},className='twelve columns')
        ],className='row')
    ],className='container'),

    #Phi and Theta Graphs
    html.Div([
        html.Div([
            html.Div([
                dcc.Graph(id='plot-theta', style={'height': 200})
            ],className='six columns'),
            html.Div([
                dcc.Graph(id='plot-phi', style={'height': 200})
            ],className='six columns')
        ],className='columns')
    ],className='container')
])

def sphr_to_cart(phi,theta, r=1, units='radians'):
    """
    Convert Spherical to Cartesian coordinates

    :phi:       angle phi in Spherical coordinates
    :theta:     angle theta in Spherical coordinates
    :units:     'radians' or 'degrees'
    """

    assert units in ['radians','degrees'], "units must be one of ['radians','degrees']"

    if units == 'degrees':
        phi=np.deg2rad(phi)
        theta=np.deg2rad(theta)

    # Cartesian coordinates
    x=r * np.sin(phi) * np.cos(theta)
    y=r * np.sin(phi) * np.sin(theta)
    z=r * np.cos(phi)

    return x,y,z

def rotate_2d(x, y, angle):
    """Use numpy to build a rotation matrix and take the dot product."""

    c, s = np.cos(angle), np.sin(angle)
    j = np.array([[c, s], [-s, c]])
    m = np.dot(j, [x, y])

    return float(m.T[0]), float(m.T[1])


def parse_contents(contents, filename, date, header):

    content_type, content_string=contents.split(',')
    decoded=base64.b64decode(content_string)
    try:
        if 'csv' in filename or 'txt' in filename:
            # Assume that the user uploaded a CSV file
            df=pd.read_csv(
                io.StringIO(decoded.decode('utf-8')),
                header=None,
                names=[header]
            )
        elif 'xls' in filename:
            # Assume that the user uploaded an excel file
            df=pd.read_excel(io.BytesIO(decoded))
    except Exception as e:
        print(e)
        return html.Div([
            'There was an error processing this file.'
        ])

    return df.to_json()

# Parse the uploaded theta data, store in div
@app.callback(Output('output-data-upload', 'children'),
              [Input('upload-data', 'contents'),
               Input('upload-data', 'filename'),
               Input('upload-data', 'last_modified')])
def update_output_theta(list_of_contents, list_of_names, list_of_dates):
    if list_of_contents is None:
        df = pd.read_csv('./data/default_data.csv')
        df.columns = [i.lower() for i in df.columns]
        return df.to_json()
    else:
        children=parse_contents(list_of_contents, list_of_names, list_of_dates,'theta')
        return children

# generate the theta figure
@app.callback(
    Output('plot-theta', 'figure'),
    [Input('output-data-upload', 'children'),
     Input('plot-phi','relayoutData')]
)
def plot_theta(data, phi_relay):
    df=pd.read_json(data).sort_index(axis=0)
    df=np.rad2deg(df)

    print(phi_relay)

    if phi_relay is None:
        start = df.index[0]
        stop = df.index[-1]
    elif 'autosize' in phi_relay or 'xaxis.autorange' in phi_relay:
        start = df.index[0]
        stop = df.index[-1]
    elif 'xaxis.range[0]' in phi_relay and 'xaxis.range[1]' in phi_relay:
        start = round(phi_relay['xaxis.range[0]'])
        stop = round(phi_relay['xaxis.range[1]'])
    else:
        raise ValueError('Something is wrong with relay out, neither autosize or xaxis.range[0],xaxis.range[1] was found')

    figure=go.Figure(
        dict(
            data=[
                go.Scattergl(
                    x=df.loc[start:stop].index.values,
                    y=df.loc[start:stop,'theta'].values,
                    name='Theta',
                )
            ],
            layout=go.Layout(
                title='Theta',
                margin=go.Margin(l=40, r=0, t=40, b=30),
                xaxis=dict(
                    title='step',
                    titlefont=dict(
                        family='Times',
                        size=10,
                        color='#7f7f7f'
                    )
                ),
                yaxis=dict(
                    title='degrees',
                    titlefont=dict(
                        family='Times',
                        size=10,
                        color='#7f7f7f'
                    ),
                range = [0, 180],
                tick0 = 0,
                dtick = 30,
                fixedrange = True
                )
            )

        )
    )
    return figure

# generate the phi data
@app.callback(
    Output('plot-phi', 'figure'),
    [Input('output-data-upload', 'children'),
     Input('plot-theta','relayoutData')]
)
def plot_phi(data, theta_relay):
    df = pd.read_json(data).sort_index(axis=0)
    df=np.rad2deg(df)

    if theta_relay is None:
        start = df.index[0]
        stop = df.index[-1]
    elif 'autosize' in theta_relay or 'xaxis.autorange' in theta_relay:
        start = df.index[0]
        stop = df.index[-1]
    elif 'xaxis.range[0]' in theta_relay and 'xaxis.range[1]' in theta_relay:
        start = round(theta_relay['xaxis.range[0]'])
        stop = round(theta_relay['xaxis.range[1]'])
    else:
        raise ValueError('Something is wrong with relay out, neither autosize or xaxis.range[0],xaxis.range[1] was found')

    figure=go.Figure(
        dict(
            data=[
                    go.Scattergl(
                        x=df.loc[start:stop].index.values,
                        y=df.loc[start:stop,'phi'].values,
                        name='Phi',
                    )
                ],
            layout=go.Layout(
                title='Phi',
                margin=go.Margin(l=40, r=0, t=40, b=30),
                xaxis=dict(
                    title='step',
                    titlefont=dict(
                        family='Times',
                        size=10,
                        color='#7f7f7f'
                    )
                ),
                yaxis=dict(
                    title='degrees',
                    titlefont=dict(
                        family='Times',
                        size=10,
                        color='#7f7f7f'
                    ),
                    range = [0, 360],
                    tick0 = 0,
                    dtick = 45,
                    fixedrange = True
                )
            )
        )
    )
    return figure

@app.callback(
    Output('plot-3d-sphere', 'figure'),
    [Input('output-data-upload', 'children'),
     Input('refresh-sphere','n_clicks')],
    [State('plot-phi','relayoutData'),
     State('plot-theta','relayoutData')],
)
def generate_3d_plot(data,n_clicks,phi_relay,theta_relay):

    # parse the data
    df = pd.read_json(data).sort_index(axis=0)

    start = df.index[0]
    stop = df.index[-1]

    if theta_relay is not None and 'xaxis.range[0]' in theta_relay and 'xaxis.range[1]' in theta_relay:
        start = round(theta_relay['xaxis.range[0]'])
        stop = round(theta_relay['xaxis.range[1]'])
    elif phi_relay is not None and 'xaxis.range[0]' in phi_relay and 'xaxis.range[1]' in phi_relay:
        start = round(phi_relay['xaxis.range[0]'])
        stop = round(phi_relay['xaxis.range[1]'])

    # Unit sphere for plotting,
    u, v=np.mgrid[0:2*np.pi:0.001*np.pi, 0:np.pi:0.001*np.pi]
    XS=0.95 * np.cos(u)*np.sin(v)
    YS=0.95 * np.sin(u)*np.sin(v)
    ZS=0.95 * np.cos(v)

    x,y,z=sphr_to_cart(df.loc[start:stop,'phi'], df.loc[start:stop,'theta'],1)

    #print(start, stop,df.index.max())

    scatter=dict(
        type='scatter3d',
        x=x,
        y=y,
        z=z,
        mode='markers+lines',
        marker=dict(
            size=4,
            color=np.arange(len(x)),
            colorscale='Viridis',
            colorbar=dict(
                title='Step Number',
                tickmode='array',
                tickvals=np.linspace(0,len(np.arange(start,stop)),10),
                ticktext=np.linspace(start,stop,10,dtype = 'int'),
            ),
        ),
        line=dict(
            color='rgb(0,0,0)',
            width=0.8
        ),
    )

    sphere=go.Surface(
                x=XS,
                y=YS,
                z=ZS,
                surfacecolor=np.zeros(XS.shape),
                colorscale=[[0, 'rgb(255, 255, 255)'], [1, 'rgb(255, 255, 255)']],
                opacity=0.7,
                visible=True,
                showscale=False,
                text=None,
                hoverinfo='text'
            )

    # Set the layout
    noaxis=dict(showbackground=False,
                  showgrid=False,
                  showline=True,
                  showticklabels=True,
                  ticks='',
                  title='',
                  zeroline=False)

    layout3d=go.Layout(title='Brownian Motion on Unit Sphere',
            font=dict(family='Balto', size=14),
        )

    fig=go.Figure(dict(data=[sphere,scatter], layout=layout3d))
    return fig

external_css=[
    "https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css",
    "https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css",
    "https://codepen.io/chriddyp/pen/brPBPO.css"
]
for css in external_css:
    app.css.append_css({
        'external_url': css
    })

if __name__ == '__main__':
    app.run_server(debug=True)

And here are a few lines of a data file, you would need to save this and update the file path in the above @ line 153 :

Phi,Theta
0,0
1.3663791,2.6586253
1.1650666,2.7570542
1.1522869,1.5821534
0.23086835,0.639884
0.17213735,0.60399357
0.06165467,0.44302712
0.37670543,0.4760842
0.47901627,0.32338713
0.67239553,0.33327552
0.79108284,0.35386934
0.55493942,0.42983617
6.0950534,0.36591827
5.9352533,0.36164368
5.7387427,0.32630791
5.8665844,0.34904307
5.8068464,0.33424189
6.1570235,0.35289976
5.9989301,0.46776778
0.051315998,0.4530412
6.248908,0.41584046
6.1525176,0.3351289
0.28032152,0.34870436
0.095561483,0.31978411
0.028010566,0.43044801
0.23498646,0.44129812
6.1282527,0.37909081
6.0028433,0.32309259
5.9899367,0.27781175
5.9625902,0.2056943
6.0294468,0.20948835
5.7956091,0.15873726
6.075267,0.25934498
5.8278979,0.35897549
5.7580106,0.29712554
5.69221,0.32192199
5.6180938,0.26294348
5.9196341,0.38369833
6.1482648,0.32667777
6.1870161,0.31382337
5.9938702,0.27539009
0.085968628,0.33342184
6.2141714,0.30962341
6.2087456,0.34230779
5.9953692,0.30338183
5.8573731,0.20995769
5.5914657,0.26419506
5.6160972,0.28778854
5.4729219,0.39995279
5.3959189,0.5042287
5.2490288,0.57712121
5.2923682,0.57935787
5.2459692,0.52614563
5.2018548,0.45088181
5.1742723,0.51343816
4.8976806,0.27842561
5.0360084,0.39066836
5.0712356,0.39167133
4.7748625,0.51170316
4.8071341,0.4801658
4.8078998,0.46368417
4.6703631,0.50868456
4.5363406,0.42779249
4.2285091,0.376377
4.2628078,0.39612191
4.2304742,0.40512241
3.9945972,0.29821962
3.8233273,0.28917921
3.5640407,0.32517418
3.4352627,0.31507804
3.4933508,0.36170261
3.2490065,0.45137089
3.4347272,0.52594216
3.4047168,0.451507
3.4980878,0.38717677
3.6045764,0.3834049
3.6173872,0.39352747
3.5211589,0.39355526
3.7049675,0.47588841
3.7419736,0.49872253
3.7040385,0.56521262
3.5920361,0.59632047
3.6526936,0.67378697
3.493261,0.68873723
3.565417,0.67938282
3.5426429,0.69683125
3.4582199,0.89926117
3.3702911,0.72974391
3.452726,0.8107608
3.6024661,0.73983303
3.5563458,0.7441246
3.4015994,0.68836799
3.4297266,0.77408272
3.3581438,0.83728588
3.255736,0.82244088
3.1079163,0.79339247
3.1874312,0.80508647
3.3047447,0.72573435
3.2113728,0.76262835
3.3407747,0.9227989
3.6353376,1.0669411
3.6897473,1.0163082
3.6688111,1.0677693
3.6267109,0.97261186
3.5604752,0.97531627
3.5719479,1.2412305
3.6872987,1.2851397
3.6897053,1.4235463
3.6778916,1.4779583
3.7023338,1.6239196
3.7994019,1.4145737
3.869088,1.4388078
3.8510492,1.5296016
4.0035778,1.8368957
4.0979595,1.814486
4.0907167,1.8709182
4.1747559,1.7976212
4.1625026,1.676712
4.2955052,1.8743177
4.4037648,1.8146557
4.4789346,1.9113899
4.3809452,1.7444105
4.299597,1.7401065
4.3282862,1.7522213
4.3458141,1.7010081
4.2947671,1.6799436
4.2465318,1.656048
4.0222069,1.4680696
4.0463129,1.4010944
3.8738043,1.3529279
3.9282668,1.2917729
3.9563805,1.3576089
3.9793346,1.388609
3.9122666,1.4434163
3.8933397,1.3894685
3.9233475,1.4407736
3.8931351,1.4848169
3.8437777,1.3622715
3.952122,1.4865483
3.9354282,1.5276817
3.9859033,1.6361851
3.9745947,1.6348525
3.9430216,1.6405099
3.8947386,1.4368756
3.8592272,1.4872466
3.9600875,1.7264152
3.9533495,1.6623506
3.9744796,1.5525567
4.0164499,1.4843354
3.9279491,1.529239
3.971324,1.4298453
4.1033267,1.4987865
4.1828097,1.5919939
4.2017972,1.7022854
4.1661471,1.5025443
4.1973593,1.6677314
4.1672697,1.6328565
4.2114626,1.6549393
4.2153222,1.6442931
4.2779747,1.7404171
4.3534248,1.770709
4.3721764,1.7730142
4.393793,1.8053016
4.4006656,1.7709336
4.4151124,1.7230756
4.3527631,1.5394697
4.4278236,1.7128111
4.4608747,1.7170966
4.4853224,1.6718151
4.4800942,1.6646573
4.7058531,1.778201
4.6720306,1.8271022
4.6370938,1.7808313
4.5626536,1.6177378
4.7281973,1.787371
4.7438541,1.7452428
4.7667687,1.767942
4.736104,1.7603606
4.7935753,1.8542945
4.8357992,1.9076544
4.8786489,1.8816709
4.8506783,1.7887
4.8203083,1.7567773
4.8541802,1.8310599
4.8595253,1.8614681
4.8281726,1.7612487
4.8500223,1.8146216
4.892035,1.8467262
4.8622275,1.7963864
4.8738168,1.844466
5.0198927,1.9866542
5.1143994,1.9726725
5.2508435,2.0356655
5.1157043,2.0330503
5.1764178,2.0806508
5.144464,2.1161147
5.1389742,2.1481555
5.1291288,2.2323845
5.1429175,2.2619181
5.2019375,2.253832
5.2077465,2.1503038
5.3175279,2.1569746
5.4742884,2.2359635
5.5517032,2.2323518
5.6843577,2.1478785
5.7938568,2.3241553
5.8775045,2.2889311
6.063599,2.1212793
6.111703,2.0433747
5.9931641,1.9152041
6.1118911,2.0611155
6.2506914,2.2205722
6.2710666,2.31152
6.1603635,2.5281976
6.1219786,2.6598782
0.034179544,2.7739942
0.13166426,2.7424767
0.15395101,2.7952409
0.17994363,2.7084807
6.2654546,2.7753936
6.2666255,2.8016969
5.8496828,2.7927995
5.7430738,2.8397488
5.5757707,2.8158624
5.7154854,2.8030599
5.7026928,2.6776526
5.8716518,2.6256441
5.8593976,2.7162186
5.983009,2.753314
5.9394003,2.801124
5.7466897,2.7802439
5.9897284,2.7728537
5.8560988,2.8303232
5.782553,2.8079063
6.1385006,2.8115443
6.0653603,2.7820748
6.1715651,2.7745722
6.1726568,2.8791542
6.1181304,2.8938661
0.057944593,2.9021663
0.24687565,2.8795012
0.22510698,2.8523019
0.35439722,2.8602793
0.27806537,2.9103694
0.43852472,2.9475267
5.9838601,3.0804601
4.3751443,3.1219667
0.81300053,3.0734981
2.397151,3.1291261
2.2966522,3.086407
3.0075545,3.0919364
0.59173359,3.089234
0.944281,3.0392199
1.0199835,3.0080639
1.2493842,2.9647271
1.1301873,2.9416065
0.99195991,2.9672759
1.0643924,2.9961784
0.97132934,3.015653
1.4600838,3.0577917
1.1646132,3.012763
1.0679492,3.057534
1.1854862,3.009662
1.0276648,3.0458181
1.9448861,3.0808241
1.8961779,3.0402272
1.5551026,3.0285483
1.1053661,2.9365827
1.392194,2.932148
1.2761324,2.9148814
1.4209511,2.9422658
1.6420629,3.0324324
1.8036123,3.05797
2.3922868,3.0721706
2.6914206,3.0639572
3.4238061,3.0119556
3.4429183,2.9593183
3.191444,2.9517958
3.0793102,2.9476946
3.0008208,2.974749
3.4561699,2.9820446
3.7110857,2.9916359
3.829494,2.9699575
3.9345162,2.9583863
3.9797683,2.9073934
4.2066175,2.9267927
3.9088665,2.9261543
4.0781631,2.9287067
4.0794875,2.8937471
3.9712794,2.8637838
3.90876,2.8658887
3.7981323,2.8440018
3.9214182,2.8511503
3.639908,2.7731129
3.7557841,2.7884658
3.597469,2.7953486
3.4898897,2.7762429
3.3552836,2.8002029
3.477526,2.7921095