def test_add_data(self): df = pd.DataFrame({ 'Time': [1, 2, 3, 5, 6], 'Incidence Number': [10, 3, 4, 6, 9] }) my_plot = bp.IncidenceNumberPlot() my_plot.add_data(df) npt.assert_array_equal(np.array([my_plot.figure['data'][0]['x']]), np.array([np.array([1, 2, 3, 5, 6])])) npt.assert_array_equal(np.array([my_plot.figure['data'][0]['y']]), np.array([np.array([10, 3, 4, 6, 9])])) with self.assertRaises(TypeError): bp.IncidenceNumberPlot().add_data(0) with self.assertWarns(UserWarning): df = pd.DataFrame({ 't': [1, 2, 3, 5, 6], 'Incidence Number': [10, 3, 4, 6, 9] }) my_plot.add_data(df, time_key='t') with self.assertWarns(UserWarning): df = pd.DataFrame({'Time': [1, 2, 4, 5, 6], 'i': [2, 3, 8, 10, 5]}) my_plot.add_data(df, inc_key='i')
def test_show_figure(self): with patch('plotly.graph_objs.Figure.show') as show_patch: df = pd.DataFrame({ 'Time': [1, 2, 3, 5, 6], 'Incidence Number': [10, 3, 4, 6, 9] }) my_plot = bp.IncidenceNumberPlot() my_plot.add_data(df) my_plot.show_figure() # Assert show_figure is called once assert show_patch.called
def test_update_labels(self): df = pd.DataFrame({ 'Time': [1, 2, 3, 5, 6], 'Incidence Number': [10, 3, 4, 6, 9] }) my_plot = bp.IncidenceNumberPlot() my_plot.add_data(df) new_time_label = 'Week' new_inc_label = 'Inc' my_plot.update_labels(time_label=new_time_label) self.assertEqual(my_plot.figure['layout']['xaxis']['title']['text'], 'Week') my_plot.update_labels(inc_label=new_inc_label) self.assertEqual(my_plot.figure['layout']['yaxis']['title']['text'], 'Inc')
def update_data_figure(self): """Update the data figure based on currently stored information. Returns ------- plotly.Figure Figure with updated data """ data = self.session_data.get('data_storage') if data is None: raise dash.exceptions.PreventUpdate() time_label, inc_label = data.columns[:2] plot = bp.IncidenceNumberPlot() if 'Imported Cases' in data.columns: # Separate data into local and imported cases imported_data = pd.DataFrame({ time_label: data[time_label], inc_label: data['Imported Cases'] }) # Bar plot of local cases plot.add_data(data, time_key=time_label, inc_key=inc_label, name='Local Cases') # Bar plot of imported cases plot.add_data(imported_data, time_key=time_label, inc_key=inc_label, name='Imported Cases') else: # If no imported cases are present plot.add_data(data, time_key=time_label, inc_key=inc_label) # Keeps traces visibility states fixed when changing sliders plot.figure['layout']['legend']['uirevision'] = True return plot.figure
def test__init__(self): bp.IncidenceNumberPlot()
def __init__(self): super().__init__() self.session_data = {'data_storage': None, 'interval_storage': None} self.app = dash.Dash(__name__, external_stylesheets=self.css) self.app.title = 'BranchproSim' button_style = { 'width': '100%', 'height': '60px', 'lineHeight': '60px', 'borderWidth': '1px', 'borderStyle': 'dashed', 'borderRadius': '5px', 'textAlign': 'center', 'margin': '10px' } self.app.layout = \ html.Div([ dbc.Container([ html.H1('Branching Processes'), html.Div([]), # Empty div for top explanation texts dbc.Row([ dbc.Col([ html.Button( 'Add new simulation', id='sim-button', n_clicks=0), dcc.Graph( figure=bp.IncidenceNumberPlot().figure, id='myfig') ]), dbc.Col( self.update_sliders(), id='all-sliders') ], align='center'), dbc.Row( [ dbc.Col( children=[ html.H4([ 'You can upload your own ', html.Span( 'incidence data', id='inc-tooltip', style={ 'textDecoration': 'underline', 'cursor': 'pointer'}, ), ' here.' ]), dbc.Modal( self._inc_modal, id='inc_modal', size='xl', ), html.Div([ 'It will appear as bars, while' ' the simulation will be a line.' ' You can upload both local and ' '/ or imported incidence data.' ]), dcc.Upload( id='upload-data', children=html.Div([ 'Drag and Drop or ', html.A( 'Select Files', style={ 'text-decoration': 'underline'}), ' to upload your Incidence Number ' 'data.' ]), style=button_style, # Allow multiple files to be uploaded multiple=True ), html.Div(id='incidence-data-upload')]), dbc.Col( children=[ html.H4([ 'You can upload your own ', html.Span( 'serial interval', id='si-tooltip', style={ 'textDecoration': 'underline', 'cursor': 'pointer'} ), ' here.' ]), dbc.Modal( self._si_modal, id='si_modal', size='lg', ), html.Div([ 'Data must contain one serial ' 'interval to be used for simulation' ' displayed as a column. If multiple ' 'serial intervals are uploaded, the ' 'first one will be used.']), dcc.Upload( id='upload-interval', children=html.Div( [ 'Drag and Drop or ', html.A( 'Select Files', style={ 'text-decoration': '\ underline' }), ' to upload your Serial \ Interval.' ]), style=button_style, # Allow multiple files to be uploaded multiple=True ), html.Div(id='ser-interval-upload')]) ], align='center', ), html.Div([]), # Empty div for bottom text html.Div(id='data_storage', style={'display': 'none'}), html.Div(id='interval_storage', style={'display': 'none'}), dcc.ConfirmDialog( id='confirm', message='Simulation failed due to overflow!', ), ], fluid=True), self.mathjax_script ]) # Set the app index string for mathjax self.app.index_string = self.mathjax_html # Save the locations of texts from the layout self.main_text = self.app.layout.children[0].children[1].children self.collapsed_text = self.app.layout.children[0].children[-4].children
def update_figure(self, fig=None, simulations=None, source=None): """Generate a plotly figure of incidence numbers and simulated cases. By default, this method uses the information saved in self.session_data to populate the figure with data. If a current figure and dash callback source are passed, it will try to just update the existing figure for speed improvements. Parameters ---------- fig : dict Current copy of the figure simulations : pd.DataFrame Simulation trajectories to add to the figure. source : str Dash callback source Returns ------- plotly.Figure Figure with updated data and simulations """ data = self.session_data.get('data_storage') if data is None: raise dash.exceptions.PreventUpdate() if fig is not None and simulations is not None: # Check if there is a faster way to update the figure if len(fig['data']) > 0 and source in [ 'epsilon', 'init_cond', 'r0', 'r1', 't1' ]: # Clear all traces except one simulation and the data if ('Imported Cases' in data.columns) and ('Incidence Number' in data.columns): fig['data'] = [ fig['data'][0], fig['data'][1], fig['data'][-1] ] else: fig['data'] = [fig['data'][0], fig['data'][-1]] # Set the y values of that trace equal to an updated simulation fig['data'][-1]['y'] = simulations.iloc[:, -1] return fig elif len(fig['data']) > 0 and source == 'sim-button': # Add one extra simulation, and set its y values fig['data'].append(copy.deepcopy(fig['data'][-1])) fig['data'][-1]['y'] = simulations.iloc[:, -1] if ('Imported Cases' in data.columns) and ('Incidence Number' in data.columns): sim_tuple = range(1, len(fig['data']) - 2) else: sim_tuple = range(len(fig['data']) - 2) for i in sim_tuple: # Change opacity of all traces in the figure but for the # first - the barplot of incidences # last - the latest simulation fig['data'][i + 1]['line']['color'] = 'rgba(255,0,0,0.25)' fig['data'][i + 1]['showlegend'] = False return fig time_label, inc_label = (data.columns[0], 'Incidence Number') num_simulations = len(simulations.columns) - 1 # Make a new figure plot = bp.IncidenceNumberPlot() if 'Imported Cases' in data.columns: # Separate data into local and imported cases imported_data = pd.DataFrame({ time_label: data[time_label], inc_label: data['Imported Cases'] }) if 'Incidence Number' in data.columns: # Bar plot of local cases plot.add_data(data.iloc[:, :2], time_key=time_label, inc_key=inc_label, name='Local Cases') # Bar plot of imported cases plot.add_data(imported_data, time_key=time_label, inc_key=inc_label, name='Imported Cases') else: # If no imported cases are present plot.add_data(data, time_key=time_label, inc_key=inc_label) # Keeps traces visibility states fixed when changing sliders plot.figure['layout']['legend']['uirevision'] = True for sim in range(num_simulations): df = simulations.iloc[:, [0, sim + 1]] df.columns = [time_label, inc_label] plot.add_simulation(df, time_key=time_label, inc_key=inc_label) # Unless it is the most recent simulation, decrease the opacity to # 25% and remove it from the legend if sim < num_simulations - 1: plot.figure['data'][-1]['line'].color = 'rgba(255,0,0,0.25)' plot.figure['data'][-1]['showlegend'] = False return plot.figure
def __init__(self, long_callback_manager=None): """ Parameters ---------- long_callback_manager Optional callback manager for long callbacks. See https://dash.plotly.com/long-callbacks """ super(BranchProInferenceApp, self).__init__() self.app = dash.Dash(__name__, external_stylesheets=self.css, long_callback_manager=long_callback_manager) self.app.title = 'BranchproInf' self.session_data = { 'data_storage': None, 'interval_storage': None, 'posterior_storage': None } button_style = { 'width': '100%', 'height': '60px', 'lineHeight': '60px', 'borderWidth': '1px', 'borderStyle': 'dashed', 'borderRadius': '5px', 'textAlign': 'center', 'margin': '10px' } self.app.layout = html.Div([ dbc.Container( [ html.H1('Branching Processes', id='page-title'), html.Div([]), # Empty div for top explanation texts html.H2('Incidence Data'), dbc.Row( dbc.Col( dcc.Graph(figure=bp.IncidenceNumberPlot().figure, id='data-fig'))), dbc.Row( [ dbc.Col(children=[ html.H6([ 'You can upload your own ', html.Span( 'incidence data', id='inc-tooltip', style={ 'textDecoration': 'underline', 'cursor': 'pointer' }, ), ' here. It will appear as bars.' ]), dbc.Modal( self._inc_modal, id='inc_modal', size='xl', ), html.Div([ 'Data must be in the following column ' 'format: `Time`, `Incidence number`, ' '`Imported Cases` (optional), ' '`R_t` (true value of R, optional).' ]), dcc.Upload( id='upload-data', children=html.Div([ 'Drag and Drop or ', html.A('Select Files', style={ 'text-decoration': 'underline' }), ' to upload your Incidence \ Number data.' ]), style=button_style, # Allow multiple files to be uploaded multiple=True), html.Div(id='incidence-data-upload') ]), dbc.Col(children=[ html.H6([ 'You can upload your own ', html.Span('serial interval', id='si-tooltip', style={ 'textDecoration': 'underline', 'cursor': 'pointer' }), ' here.' ]), dbc.Modal( self._si_modal, id='si_modal', size='lg', ), html.Div([ 'Data must contain one or more serial ' 'intervals to be used for constructing' ' the posterior distributions each ' 'included as a column.' ]), dcc.Upload( id='upload-interval', children=html.Div([ 'Drag and Drop or ', html.A('Select Files', style={ 'text-decoration': '\ underline' }), ' to upload your Serial \ Interval.' ]), style=button_style, # Allow multiple files to be uploaded multiple=True), html.Div(id='ser-interval-upload') ]) ], align='center', ), html.H2('Plot of R values'), html.Progress(id='progress_bar'), html.Div( id='first_run', # see flip_first_run() in the app children='True', style={'display': 'none'}), dbc.Row( [ dbc.Col(children=dcc.Graph( figure=bp.ReproductionNumberPlot().figure, id='posterior-fig', style={'display': 'block'})), dbc.Col(self.update_sliders(), id='all-sliders') ], align='center', ), html.Div([]), # Empty div for bottom text html.Div(id='data_storage', style={'display': 'none'}), html.Div(id='interval_storage', style={'display': 'none'}), html.Div(id='posterior_storage', style={'display': 'none'}) ], fluid=True), self.mathjax_script ]) # Set the app index string for mathjax self.app.index_string = self.mathjax_html # Save the locations of texts from the layout self.main_text = self.app.layout.children[0].children[1].children self.collapsed_text = self.app.layout.children[0].children[-4].children