Package: dash-oop-components
dash_oop_componentsdash_oop_componentsis a small helper library with object-oriented dashboard building blocks for the plotly dash libraryInstallpip install dash_oop_componentsDocumentationDocumentation can be found at:https://oegedijk.github.io/dash_oop_components/ExampleAn example covid tracking dashboard has been deployed todash-oop-demo.herokuapp.com(code atgithub.com/oegedijk/dash_oop_demo), showcasing:The use of re-usable, nestable componentsKeeping track of state in the querystringSeperating data from dashboard logicLoading the dashboard from a config yaml filePurposePlotly'sdashis an awesome library that allows you to build rich interactive data driven web apps with pure python code. However the default style of dash apps is quite declarative, which for large projects can lead to code that becomes unwieldy, hard to maintain, and hard to collaborate on:Data wrangling and plot generating logic is mixed up with dashboard interactivity logic
and is spread all over the layout and callback functions.Configuration of the dashboard is hardcoded somewhere deep in the layout or callbacks,
instead of with tunable hyperparameters.Callbacks definitions are all mixed up, often far from the relevant layout, instead of being grouped togetherTo reuse similar components multiple time in your dashboard you need to copy-paste layout and callbacks, violating the DRY principle.You need to be able to read and edit python in order to reconfigure and restart the dashboardThis library provides a number object-oriented wrappers for organizing your dash code that allow you to write clean, modular, composable, re-usable and fully configurable dash code.It includes:DashFigureFactory: a wrapper for your data/plotting functionality, keeping data/plotting logic
seperate from your dashboard interaction logic.DashComponent: a self-contained, modular, configurable unit that combines a dash layout with dash callbacks.Keeps layout and callbacks in one place, grouped together.Makes use of aDashFigureFactoryfor plots or other data outputDashComponentsare composable, meaning that you can nest them into new composite components.You can store component configuration to yaml, and then rebuild from yaml.You can useDashConnectorsto connect callbacks between componentsDashApp: Build a dashboard out of aDashComponentand run it.Includes the possibility of tracking dashboard state in the querystring url,
allowing for shareable stateful urls.UsingDashComponentTabsyou can also track state for current tab onlyYou can launch a dashboard from the commandline from a dashboard.yaml file,
meaning that anyone can reconfigure the dashboard and relaunch it, even
without coding experience.All wrappers:Cool extras:All wrappers automagically store all params to attributesComponent and dashboard configuration can be exported to.yamlfile,
including import details, and be fully reloaded from this config file.You can track the state of your dashboard with querystrings and reload the state from url!Launch from the commandline with thedashappCLI!Example CodeA full example dashboard can be found atgithub.com/oegedijk/dash_oop_demoand has been deployed tohttps://dash-oop-demo.herokuapp.com/Below is the code for similar but slightly simpler example. Full explanation for thedash_oop_demodashboard can be foundin the example documentation.The example is a rewrite of thisCharming Data dash instruction video(go check out his other vids, they're awesome!).CovidPlots: a DashFigureFactoryFirst we define a basicDashFigureFactorythat loads a covid dataset, and provides a single plotting functionality, namelyplot_time_series(countries, metric). Make sure to callsuper().__init__()in order to store params to attributes (that's how the datafile parameters gets automatically assigned to self.datafile for example), and store them to a._stored_paramsdict so that they can later be exported to a config file.classCovidPlots(DashFigureFactory):def__init__(self,datafile="covid.csv"):super().__init__()self.df=pd.read_csv(self.datafile)self.countries=self.df.countriesAndTerritories.unique().tolist()self.metrics=['cases','deaths']defplot_time_series(self,countries,metric):returnpx.line(data_frame=self.df[self.df.countriesAndTerritories.isin(countries)],x='dateRep',y=metric,color='countriesAndTerritories',labels={'countriesAndTerritories':'Countries','dateRep':'date'},)figure_factory=CovidPlots("covid.csv")print(figure_factory.to_yaml())dash_figure_factory:
  class_name: CovidPlots
  module: __main__
  params:
    datafile: covid.csvCovidTimeSeries: a DashComponentThen we define aDashComponentthat takes a plot_factory and build a layout with two dropdowns and a graph.The layout is defined in thelayout(self, params=None)methodCallbacks are defined in thecomponent_callbacks(self, app)methodBy callingsuper().__init__()all parameters are automatically stored to attributes (so that we can access
e.g.self.hide_country_dropdown), and to a._stored_paramsdict (which can then be exported to.yaml)This layout makes use of themake_hideable()staticmethod, to conditionally
wrap certain layout elements in a hidden div.We track the state of the dropdowns'value'attribute by wrapping it inself.querystring(params)(dcc.Dropdown)(..), and passing the urls's querystring params
down to the layout function upon pageload.You can make sure that allcomponent_id's are unique by adding+self.name. However if you useself.id(component_id), thenself.namegets automatically tagged on, and you can useself.Input(),self.Output()andself.State()instead of the regulardashInput(),Output()andState()functions.If you don't explicitly pass aname,  gets a random uuid string automatically gets assigned.Note that the callbacks are registered usingcomponent_callbacks(self, app)methodNote that the callback uses theplot_factoryfor the plotting logic.classCovidTimeSeries(DashComponent):def__init__(self,plot_factory,hide_country_dropdown=False,countries=None,hide_metric_dropdown=False,metric='cases',name=None):super().__init__()ifnotself.countries:self.countries=self.plot_factory.countriesdeflayout(self,params=None):returndbc.Container([dbc.Row([dbc.Col([html.H3("Covid Time Series"),self.make_hideable(self.querystring(params)(dcc.Dropdown)(id=self.id('timeseries-metric-dropdown'),options=[{'label':metric,'value':metric}formetricin['cases','deaths']],value=self.metric,),hide=self.hide_metric_dropdown),self.make_hideable(self.querystring(params)(dcc.Dropdown)(id=self.id('timeseries-country-dropdown'),options=[{'label':country,'value':country}forcountryinself.plot_factory.countries],value=self.countries,multi=True,),hide=self.hide_country_dropdown),dcc.Graph(id=self.id('timeseries-figure'))]),])])defcomponent_callbacks(self,app):@app.callback(self.Output('timeseries-figure','figure'),self.Input('timeseries-country-dropdown','value'),self.Input('timeseries-metric-dropdown','value'))defupdate_timeseries_plot(countries,metric):ifcountriesandmetricisnotNone:returnself.plot_factory.plot_time_series(countries,metric)raisePreventUpdateDuoPlots: a composition of two subcomponentsA compositeDashComponentthat combines twoCovidTimeSeriesinto a single layout.
Both subcomponents are passed the sameplot_factorybut assigned different initial values.The layouts of subcomponents can be included in the composite layout withself.plot_left.layout(params)andself.plot_right.layout(params)Composite callbacks should again be defined underself.component_callbacks(app)calling.register_callbacks(app)first registers all callbacks of subcomponents,
and then callscomponent_callbacks(app).composite callbacks can access elements of subcomponents by using thesubcomponent.namefields in the ids.When tracking the state of the dashboard in the querystring it is important to name your components, so that
the next time you start the dashboard the elements will have the same id's. In this case we
passname="left"andname="right".Make sure to pass the params parameter of the layout down to the subcomponent layouts!classDuoPlots(DashComponent):def__init__(self,plot_factory):super().__init__()self.plot_left=CovidTimeSeries(plot_factory,countries=['China','Vietnam','Taiwan'],metric='cases',name='left')self.plot_right=CovidTimeSeries(plot_factory,countries=['Italy','Germany','Sweden'],metric='deaths',name='right')deflayout(self,params=None):returndbc.Container([html.H1("Covid Dashboard"),dbc.Row([dbc.Col([self.plot_left.layout(params)]),dbc.Col([self.plot_right.layout(params)])])],fluid=True)dashboard=DuoPlots(figure_factory)print(dashboard.to_yaml())dash_component:
  class_name: DuoPlots
  module: __main__
  params:
    plot_factory:
      dash_figure_factory:
        class_name: CovidPlots
        module: __main__
        params:
          datafile: covid.csv
    name: DAtVxQgozoBuild and startDashApp:Pass thedashboardto theDashAppto create a dash flask application.You can passmode='inline','external'or'jupyterlab'when you are working in a notebook in order to keep
the notebook interactive while the app is runningBy passingquerystrings=Trueyou automatically keep track of the state of the dashboard int the url querystringBy passingbootstrap=Truethe default bootstrap css gets automatically included. You can also choose particular themes, e.g.bootstrap=dbc.themes.FLATLYYou can pass other dash parameters in the**kwargsapp=DashApp(dashboard,querystrings=True,bootstrap=True)print(app.to_yaml())dash_app:
  class_name: DashApp
  module: dash_oop_components.core
  params:
    dashboard_component:
      dash_component:
        class_name: DuoPlots
        module: __main__
        params:
          plot_factory:
            dash_figure_factory:
              class_name: CovidPlots
              module: __main__
              params:
                datafile: covid.csv
          name: DAtVxQgozo
    port: 8050
    mode: dash
    querystrings: true
    bootstrap: true
    kwargs:
      external_stylesheets:
      - https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css
      suppress_callback_exceptions: trueifrun_app:app.run(9051)app.to_yaml("covid_dashboard.yaml")launch from the commandline withdashappCLINow we could launch the dashboard from the command line with thedashappCLI tool:$dashappcovid_dashboard.yamlreload dashboard from config:app2=DashApp.from_yaml("covid_dashboard.yaml")We can check that the configuration of this newapp2is indeed the same asapp:print(app2.to_yaml())dash_app:
  class_name: DashApp
  module: dash_oop_components.core
  params:
    dashboard_component:
      dash_component:
        class_name: DuoPlots
        module: __main__
        params:
          plot_factory:
            dash_figure_factory:
              class_name: CovidPlots
              module: __main__
              params:
                datafile: covid.csv
          name: 95vGiZRAt2
    port: 8050
    mode: dash
    querystrings: true
    bootstrap: true
    kwargs:
      external_stylesheets:
      - https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css
      - https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css
      suppress_callback_exceptions: trueAnd if we run it it still works!ifrun_app:app2.run()