├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── advanced_demo.py ├── dash_ui ├── __init__.py ├── controlpanel.py ├── grid.py ├── style │ ├── css │ │ ├── grid.css │ │ └── grid.css.map │ └── sass │ │ └── grid.sass └── version.py ├── setup.py └── simple_demo.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | dash_ui/style/.sass-cache 3 | dash_ui/__pycache__ 4 | dist/ 5 | dash_ui.egg-info/ 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.4.0 2 | * Added collapsible section functionality to the `ControlPanel`. See `advanced_demo.py` for usage and an example. 3 | 4 | # 0.4.0 5 | * Added `menu` and `menu_height` attributes to `add_graph`, which places `menu` 6 | on top of the graph element with `menu_height`. 7 | 8 | # 0.3.1 9 | * Removed 'displayModeBar = False' which was accidentally set in `Grid.add_graph` 10 | 11 | # 0.3.0 12 | * Added a `ControlPanel` object. When added, this is a sidebar which holds input components separate from the plot grid. 13 | * Added the function `dash_ui.Layout` which creats the layout from a `Grid` Object and (optionally) a `ControlPanel` object 14 | * Changed the `grid_id` property of `Grid` to `_id` to remove redundancy. 15 | 16 | # 0.2.0 17 | Grid elements have a `dui-grid-element` class with which you can style them, and 18 | arbitrary classes can be added to grid components with the `element_class` argument of 19 | `add_element` 20 | 21 | # 0.1.0 22 | Added: Grid component 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2018 Ryan Marren 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dash-ui 2 | 3 | ### Grid 4 | `Grid` makes use of the 5 | [CSS grid](https://css-tricks.com/getting-started-css-grid/) 6 | to make creating dashboard-like layouts super easy in Dash. 7 | 8 | 9 | ### ControlPanel 10 | `ControlPanel` adds a panel to the side of the grid which contains input components to change the state of the plot grid. 11 | 12 | 13 | ##### A Simple Example Grid 14 | This example is found in `simple_demo.py` 15 | ![image](https://user-images.githubusercontent.com/10272301/39724456-dc7144c0-5216-11e8-8b01-18ddc19946e2.png) 16 | 17 | 18 | First we add the external css url `https://codepen.io/rmarren1/pen/mLqGRg.css`. 19 | This is necessary for the grid to work. If you cannot use this (e.g. you 20 | are serving stylesheets locally) you can just serve the file at https://github.com/rmarren1/dash-ui/blob/master/dash_ui/style/css/grid.css . 21 | 22 | Next, we create a grid with the following call: 23 | `grid = dui.Grid(_id="grid", num_rows=12, num_cols=12, grid_padding=5)` 24 | 25 | * `num_rows` is the number of rows in the grid, and must be between 1 and 12 26 | * `num_cols` is the number of columns in the grid, and must be between 1 and 12 27 | * `grid_padding` is the number of pixels to pad between grid elements, and must 28 | be one of [0, 1, 2, 5, 10, 25, 50, 100]. 29 | 30 | Now you can add any dash element as a grid element like so: 31 | 32 | ``` 33 | grid.add_element(col=1, row=1, width=3, height=4, element=html.Div( 34 | style={"background-color": "red", "height": "100%", "width": "100%"} 35 | )) 36 | ``` 37 | 38 | * `row` is the starting row in the grid layout 39 | * `col` is the starting column in the grid layout 40 | * `height` is the number of rows the element should span. 41 | * `width` is the number of columns the element should span 42 | 43 | Note that the rows and columns are one indexed. 44 | 45 | Finally, we create the layout using the function 46 | `dui.Layout(grid=grid,)` 47 | 48 | and set `app.layout` as the object this function returns. 49 | 50 | 51 | ##### A Grid with ControlPanel 52 | This example shows the grid with real plots (see `advanced_demo.py`) 53 | ![image](https://user-images.githubusercontent.com/10272301/40074922-03a07a74-5849-11e8-92f5-8a211d03b26f.png) 54 | 55 | If a `ControlPanel` is not included, the grid will take up the entire screen. 56 | To include the `ControlPanel`, create one using code like the following: 57 | ``` 58 | controlpanel = dui.ControlPanel(_id="controlpanel") 59 | controlpanel.create_group( 60 | group="State", 61 | group_title="Change the selected State." 62 | ) 63 | state_select = dcc.Dropdown( 64 | id="state-dropdown", 65 | options=[{ 66 | 'label': x.title(), 67 | 'value': x 68 | } for x in df["state"].tolist() 69 | ], 70 | value=df["state"].tolist()[0] 71 | ) 72 | controlpanel.add_element(state_select, "State") 73 | ``` 74 | then include the `controlpanel` instance as an argument to the `dui.Layout` function, e.g. 75 | ``` 76 | app.layout = dui.Layout( 77 | grid=grid, 78 | controlpanel=controlpanel 79 | ) 80 | ``` 81 | We can also add plotly graph elements with the shortcut 82 | `grid.add_graph(col=1, row=1, width=3, height=4, graph_id="all-pie")` 83 | which creates a grid element with a graph with the `id` declared in `graph_id`. 84 | 85 | 86 | 87 | We can end up with this really nice interactive dashboard, with only 88 | 200 lines of code. 89 | 90 | ### Customizing the style 91 | To have your own flavor of dashboard, simply fork the source SASS [here](https://codepen.io/rmarren1/pen/mLqGRg), make changes, then add the css from codepen to your project using: 92 | ``` 93 | my_css_urls = [ 94 | "https://codepen.io/your-codepen-name/pen/your-pen-identifier.css", 95 | ] 96 | 97 | for url in my_css_urls: 98 | app.css.append_css({ 99 | "external_url": url 100 | }) 101 | ``` 102 | -------------------------------------------------------------------------------- /advanced_demo.py: -------------------------------------------------------------------------------- 1 | from dash import Dash 2 | import dash_core_components as dcc 3 | import dash_html_components as html 4 | import sd_material_ui as mui 5 | from dash.dependencies import Input, Output 6 | import dash_ui as dui 7 | import pandas as pd 8 | import plotly.graph_objs as go 9 | 10 | df = pd.read_csv( 11 | 'https://gist.githubusercontent.com/chriddyp/' 12 | 'c78bf172206ce24f77d6363a2d754b59/raw/' 13 | 'c353e8ef842413cae56ae3920b8fd78468aa4cb2/' 14 | 'usa-agricultural-exports-2011.csv') 15 | 16 | app = Dash() 17 | app.config['suppress_callback_exceptions'] = True 18 | my_css_urls = [ 19 | "https://codepen.io/rmarren1/pen/mLqGRg.css", 20 | "https://use.fontawesome.com/releases/v5.1.0/css/all.css" 21 | ] 22 | 23 | for url in my_css_urls: 24 | app.css.append_css({ 25 | "external_url": url 26 | }) 27 | 28 | controlpanel = dui.ControlPanel(_id="controlpanel") 29 | controlpanel.create_section( 30 | section="StateSection", 31 | section_title="State Selection Section" 32 | ) 33 | controlpanel.create_group( 34 | group="StateGroup", 35 | group_title="Change the selected State." 36 | ) 37 | state_select = dcc.Dropdown( 38 | id="state-dropdown", 39 | options=[{ 40 | 'label': x.title(), 41 | 'value': x 42 | } for x in df["state"].tolist() 43 | ], 44 | value=df["state"].tolist()[0] 45 | ) 46 | controlpanel.add_element(state_select, "StateGroup") 47 | 48 | controlpanel.create_section( 49 | section="AnotherSection", 50 | section_title="Another Section", 51 | defaultOpen=False 52 | ) 53 | controlpanel.create_group( 54 | group="AnotherGroup", 55 | group_title="Another option group." 56 | ) 57 | another = dcc.Dropdown( 58 | id="another-element", 59 | options=[{ 60 | 'label': "example", 61 | 'value': "show" 62 | } 63 | ], 64 | value="show" 65 | ) 66 | controlpanel.add_element(html.P("Here is another group"), "AnotherGroup") 67 | controlpanel.add_element(another, "AnotherGroup") 68 | 69 | controlpanel.create_group( 70 | group="ThirdGroup", 71 | group_title="A third option group" 72 | ) 73 | third = dcc.RadioItems( 74 | id="third-element", 75 | options=[ 76 | { 77 | 'label': "example", 78 | 'value': "show" 79 | }, 80 | { 81 | 'label': "example2", 82 | 'value': "show2" 83 | }, 84 | ], 85 | value="show2" 86 | ) 87 | controlpanel.add_element(third, "ThirdGroup") 88 | 89 | controlpanel.add_groups_to_section("StateSection", ["StateGroup"]) 90 | controlpanel.add_groups_to_section("AnotherSection", ["AnotherGroup", "ThirdGroup"]) 91 | 92 | grid = dui.Grid( 93 | _id="grid", 94 | num_rows=12, 95 | num_cols=12, 96 | grid_padding=0 97 | ) 98 | 99 | _iconStyle = { 100 | "font-size": 16, 101 | "padding": 2, 102 | "color": "white" 103 | } 104 | 105 | _style = { 106 | "height": 32, 107 | "width": 32, 108 | "padding": 2, 109 | "border-radius": "2px", 110 | "flex": 1, 111 | "margin-right": 2 112 | } 113 | 114 | menu = html.Div( 115 | children=[ 116 | mui.IconButton( 117 | tooltip="Delete Plot", 118 | tooltipPosition="bottom-right", 119 | iconClassName="fas fa-trash-alt", 120 | touch=True, 121 | iconStyle=_iconStyle, 122 | style={"background": "#EBBAB9", **_style} 123 | ), 124 | mui.IconButton( 125 | tooltip="Save Plot", 126 | tooltipPosition="bottom-center", 127 | iconClassName="fas fa-save", 128 | touch=True, 129 | iconStyle=_iconStyle, 130 | style={"background": "#C9C5BA", **_style} 131 | ), 132 | mui.IconButton( 133 | tooltip="Upload Plot to Cloud", 134 | tooltipPosition="bottom-center", 135 | iconClassName="fas fa-cloud-upload-alt", 136 | touch=True, 137 | iconStyle=_iconStyle, 138 | style={"background": "#97B1A6", **_style} 139 | ), 140 | mui.IconButton( 141 | tooltip="Download Plot to My Computer", 142 | tooltipPosition="bottom-center", 143 | iconClassName="fas fa-download", 144 | touch=True, 145 | iconStyle=_iconStyle, 146 | style={"background": "#698996", **_style} 147 | ), 148 | ], style={"display": "flex"}) 149 | 150 | grid.add_graph(col=1, row=1, width=12, height=4, graph_id="all-bar", 151 | menu=menu, menu_height=32) 152 | grid.add_graph(col=1, row=5, width=12, height=4, graph_id="total-exports-bar") 153 | 154 | grid.add_graph(col=1, row=9, width=4, height=4, graph_id="all-pie") 155 | grid.add_graph(col=5, row=9, width=4, height=4, graph_id="produce-pie") 156 | grid.add_graph(col=9, row=9, width=4, height=4, graph_id="animal-pie", 157 | menu=menu, menu_height=32) 158 | 159 | 160 | app.layout = html.Div( 161 | dui.Layout( 162 | grid=grid, 163 | controlpanel=controlpanel 164 | ), 165 | style={ 166 | 'height': '100vh', 167 | 'width': '100vw' 168 | } 169 | ) 170 | 171 | 172 | @app.callback(Output('total-exports-pie', 'figure'), 173 | [Input('state-dropdown', 'value')]) 174 | def create_total_exports_pie(state): 175 | trace = go.Pie( 176 | labels=df['state'], 177 | values=df['total exports'], 178 | textinfo='none', 179 | marker=dict( 180 | colors=['red' if x == state else 'grey' for x in df['state']] 181 | )) 182 | return go.Figure(data=[trace], layout={ 183 | 'showlegend': False, 184 | 'title': 185 | "{:s}'s proportion of total US agriculture exports".format(state) 186 | }) 187 | 188 | 189 | @app.callback(Output('total-exports-bar', 'figure'), 190 | [Input('state-dropdown', 'value')]) 191 | def create_total_exports_bar(state): 192 | my_df = df.sort_values('total exports', ascending=False) 193 | trace = go.Bar( 194 | x=my_df['state'], 195 | y=my_df['total exports'], 196 | marker=dict( 197 | color=['red' if x == state else 'grey' for x in my_df['state']] 198 | )) 199 | return go.Figure(data=[trace], layout={ 200 | 'showlegend': False, 201 | 'autosize': True, 202 | 'title': 203 | "{:s}'s agriculture exports vs. other states".format(state) 204 | }) 205 | 206 | 207 | @app.callback(Output('produce-pie', 'figure'), 208 | [Input('state-dropdown', 'value')]) 209 | def create_produce_pie(state): 210 | produce_vars = ["total fruits", "total veggies", "corn", "wheat"] 211 | row = df[df["state"] == state].iloc[0] 212 | trace = go.Pie( 213 | labels=produce_vars, 214 | textinfo="label+percent", 215 | values=[row[v] for v in produce_vars]) 216 | return go.Figure(data=[trace], layout={ 217 | 'showlegend': False, 218 | 'title': 219 | "{:s}'s produce distribution".format(state) 220 | }) 221 | 222 | 223 | @app.callback(Output('animal-pie', 'figure'), 224 | [Input('state-dropdown', 'value')]) 225 | def create_animal_pie(state): 226 | animal_vars = ["beef", "pork", "poultry", "dairy"] 227 | row = df[df["state"] == state].iloc[0] 228 | trace = go.Pie( 229 | labels=animal_vars, 230 | textinfo="label+percent", 231 | values=[row[v] for v in animal_vars]) 232 | return go.Figure(data=[trace], layout={ 233 | 'showlegend': False, 234 | 'title': 235 | "{:s}'s animal product distribution".format(state), 236 | }) 237 | 238 | 239 | @app.callback(Output('all-pie', 'figure'), 240 | [Input('state-dropdown', 'value')]) 241 | def create_all_pie(state): 242 | vs = list(set(df.columns) - {"Unnamed: 0", "total exports", "state"}) 243 | row = df[df["state"] == state].iloc[0] 244 | trace = go.Pie( 245 | labels=vs, 246 | textinfo="label+percent", 247 | values=[row[v] for v in vs]) 248 | return go.Figure(data=[trace], layout={ 249 | 'showlegend': False, 250 | 'title': 251 | "{:s}'s agriculture distribution".format(state) 252 | }) 253 | 254 | 255 | @app.callback(Output('all-bar', 'figure'), 256 | [Input('state-dropdown', 'value')]) 257 | def create_all_bar(state): 258 | vs = list(set(df.columns) - {"Unnamed: 0", "total exports", "state"}) 259 | row = df[df["state"] == state].iloc[0] 260 | trace = go.Bar( 261 | x=vs, 262 | y=[row[v] for v in vs]) 263 | return go.Figure(data=[trace], layout={ 264 | 'showlegend': False, 265 | 'title': 266 | "{:s}'s agriculture distribution".format(state) 267 | }) 268 | 269 | 270 | if __name__ == "__main__": 271 | app.run_server(debug=True, port=8049) 272 | -------------------------------------------------------------------------------- /dash_ui/__init__.py: -------------------------------------------------------------------------------- 1 | import dash_html_components as html 2 | from .grid import Grid 3 | from .controlpanel import ControlPanel 4 | 5 | 6 | def Layout(grid=None, controlpanel=None): 7 | if not controlpanel: 8 | return html.Div(grid.get_component(), className="dui-layout") 9 | else: 10 | return html.Div([ 11 | controlpanel.get_component(), 12 | grid.get_component() 13 | ], className="dui-layout") 14 | -------------------------------------------------------------------------------- /dash_ui/controlpanel.py: -------------------------------------------------------------------------------- 1 | import dash_html_components as html 2 | from collections import OrderedDict 3 | 4 | 5 | class ControlPanel: 6 | def __init__(self, _id="default_controlpanel_id"): 7 | self.className = "dui-controlpanel" 8 | self._id = _id 9 | self.options = OrderedDict() 10 | self.sections = OrderedDict() 11 | 12 | def create_group(self, group, group_title="", group_class=""): 13 | if group in self.options.keys(): 14 | raise ValueError( 15 | "The group {:s} already exists in the control panel.") 16 | option_group_title = html.Strong( 17 | group_title, 18 | className="dui-controlpanel-group-title" 19 | ) 20 | option_group = html.Div( 21 | className=" ".join(["dui-controlpanel-group", group_class]), 22 | children=[option_group_title] 23 | ) 24 | self.options[group] = option_group 25 | 26 | def create_section(self, section, defaultOpen=True, 27 | section_title="", section_class=""): 28 | if section in self.options.keys(): 29 | raise ValueError( 30 | "The section {:s} already exists in the control panel.") 31 | option_section_title = html.Summary( 32 | section_title, 33 | className="dui-controlpanel-section-title", 34 | ) 35 | option_section = html.Details( 36 | className=" ".join(["dui-controlpanel-section", section_class]), 37 | open=defaultOpen, 38 | children=[option_section_title] 39 | ) 40 | self.sections[section] = option_section 41 | 42 | def add_groups_to_section(self, section, groups): 43 | for group in groups: 44 | if group not in self.options.keys(): 45 | raise ValueError( 46 | "{0} is not an element group. " 47 | "Add it with ControlPanel.create_group({0})".format(group) 48 | ) 49 | if section not in self.sections.keys(): 50 | raise ValueError( 51 | "{0} is not a valid section. " 52 | "Add it with ControlPanel.create_section({0})".format(section) 53 | ) 54 | for group in groups: 55 | self.sections[section].children.append(self.options[group]) 56 | 57 | def add_element(self, element, group): 58 | if group not in self.options.keys(): 59 | raise ValueError( 60 | "{0} is not an element group. " 61 | "Add it with ControlPanel.create_group({0})".format(group) 62 | ) 63 | option = html.Div( 64 | className="dui-controlpanel-element", 65 | children=element 66 | ) 67 | self.options[group].children.append(option) 68 | 69 | def get_component(self): 70 | control_panel = html.Div( 71 | className=self.className, 72 | id=self._id, 73 | children=list( 74 | (o for o in self.options.values()) 75 | if not self.sections else 76 | (s for s in self.sections.values()) 77 | ) 78 | ) 79 | return html.Div( 80 | className="dui-controlpanel-wrapper", 81 | children=control_panel) 82 | -------------------------------------------------------------------------------- /dash_ui/grid.py: -------------------------------------------------------------------------------- 1 | import dash_html_components as html 2 | import dash_core_components as dcc 3 | 4 | 5 | VALID_GRID_PADDINGS = [0, 1, 2, 5, 10, 25, 50, 100] 6 | 7 | 8 | class Grid: 9 | def __init__(self, _id="default_grid_id", 10 | children=None, num_rows=1, num_cols=1, grid_padding=1): 11 | if num_rows not in range(1, 13): 12 | return ValueError( 13 | "Only 1 to 12 rows supported, not {:d}".format(num_rows)) 14 | if num_cols not in range(1, 13): 15 | return ValueError( 16 | "Only 1 to 12 columns supported, not {:d}".format(num_cols)) 17 | if grid_padding not in VALID_GRID_PADDINGS: 18 | return ValueError( 19 | "Only grid paddings in " 20 | + str(VALID_GRID_PADDINGS) 21 | + "supported") 22 | self.className = "dui-grid" 23 | self.children = [] if children is None else children 24 | self._id = _id 25 | self.num_rows = num_rows 26 | self.num_cols = num_cols 27 | self.grid_padding = grid_padding 28 | 29 | def add_element(self, element, col, row, width, height, element_class=""): 30 | if row > self.num_rows: 31 | raise ValueError( 32 | "Grid only has {:d} rows, not {:d}".format( 33 | self.num_rows, row 34 | )) 35 | if row + height - 1 > self.num_rows: 36 | raise ValueError( 37 | "Grid only has {:d} rows, not {:d} = {:d} + {:d} - 1".format( 38 | self.num_rows, row + height - 1, row, height 39 | )) 40 | if col > self.num_cols: 41 | raise ValueError( 42 | "Grid only has {:d} columns, not {:d}".format( 43 | self.num_cols, col 44 | )) 45 | if col + width - 1 > self.num_cols: 46 | raise ValueError( 47 | "Grid only has {:d} cols, not {:d} = {:d} + {:d} - 1".format( 48 | self.num_cols, col + width - 1, col, width 49 | )) 50 | self.children.append(html.Div( 51 | style={ 52 | "grid-column": "{:d} / span {:d}".format(col, width), 53 | "grid-row": "{:d} / span {:d}".format(row, height) 54 | }, 55 | className="dui-grid-element " + element_class, 56 | children=element 57 | )) 58 | 59 | def add_graph(self, graph_id, col, row, width, height, menu=None, 60 | menu_height=50): 61 | graph_height = "calc(100% - {:d}px)".format(menu_height) \ 62 | if menu else "100%" 63 | graph = dcc.Graph( 64 | id=graph_id, 65 | style={ 66 | "width": "100%", 67 | "height": graph_height 68 | }, 69 | config=dict( 70 | autosizable=True 71 | ) 72 | ) 73 | if menu: 74 | graph = html.Div([ 75 | html.Div(menu, style={"height": menu_height}), 76 | graph 77 | ], style={"height": "100%"}) 78 | 79 | self.add_element( 80 | element=graph, 81 | col=col, 82 | row=row, 83 | width=width, 84 | height=height 85 | ) 86 | 87 | def get_component(self): 88 | grid_class = " ".join([ 89 | self.className, 90 | "dui-grid-{:d}-rows".format(self.num_rows), 91 | "dui-grid-{:d}-cols".format(self.num_cols), 92 | "dui-grid-{:d}-padding".format(self.grid_padding) 93 | ]) 94 | grid = html.Div( 95 | children=self.children, 96 | className=grid_class, 97 | id=self._id, 98 | ) 99 | return html.Div( 100 | className="dui-grid-wrapper", 101 | children=grid) 102 | -------------------------------------------------------------------------------- /dash_ui/style/css/grid.css: -------------------------------------------------------------------------------- 1 | /*------------------------------- */ 2 | /* Settings */ 3 | /*------------------------------- */ 4 | /*------------------------------- */ 5 | /* Layout */ 6 | /*------------------------------- */ 7 | .dui-layout { 8 | height: 100vh; 9 | width: 100vw; 10 | overflow: auto; 11 | display: flex; 12 | flex-wrap: wrap; } 13 | 14 | body { 15 | margin: 0; } 16 | 17 | /*------------------------------- */ 18 | /* Grid */ 19 | /*------------------------------- */ 20 | .dui-grid-wrapper { 21 | flex: 4; 22 | background-color: #ddd; 23 | border-left: 2px solid #333; } 24 | 25 | .dui-grid { 26 | display: grid; 27 | height: 100%; 28 | width: 100%; 29 | box-sizing: border-box; 30 | -moz-box-sizing: border-box; 31 | -webkit-box-sizing: border-box; 32 | padding: 5px; } 33 | 34 | .dui-grid-1-rows { 35 | grid-template-rows: repeat(1, 1fr); } 36 | 37 | .dui-grid-2-rows { 38 | grid-template-rows: repeat(2, 1fr); } 39 | 40 | .dui-grid-3-rows { 41 | grid-template-rows: repeat(3, 1fr); } 42 | 43 | .dui-grid-4-rows { 44 | grid-template-rows: repeat(4, 1fr); } 45 | 46 | .dui-grid-5-rows { 47 | grid-template-rows: repeat(5, 1fr); } 48 | 49 | .dui-grid-6-rows { 50 | grid-template-rows: repeat(6, 1fr); } 51 | 52 | .dui-grid-7-rows { 53 | grid-template-rows: repeat(7, 1fr); } 54 | 55 | .dui-grid-8-rows { 56 | grid-template-rows: repeat(8, 1fr); } 57 | 58 | .dui-grid-9-rows { 59 | grid-template-rows: repeat(9, 1fr); } 60 | 61 | .dui-grid-10-rows { 62 | grid-template-rows: repeat(10, 1fr); } 63 | 64 | .dui-grid-11-rows { 65 | grid-template-rows: repeat(11, 1fr); } 66 | 67 | .dui-grid-12-rows { 68 | grid-template-rows: repeat(12, 1fr); } 69 | 70 | .dui-grid-1-cols { 71 | grid-template-columns: repeat(1, 1fr); } 72 | 73 | .dui-grid-2-cols { 74 | grid-template-columns: repeat(2, 1fr); } 75 | 76 | .dui-grid-3-cols { 77 | grid-template-columns: repeat(3, 1fr); } 78 | 79 | .dui-grid-4-cols { 80 | grid-template-columns: repeat(4, 1fr); } 81 | 82 | .dui-grid-5-cols { 83 | grid-template-columns: repeat(5, 1fr); } 84 | 85 | .dui-grid-6-cols { 86 | grid-template-columns: repeat(6, 1fr); } 87 | 88 | .dui-grid-7-cols { 89 | grid-template-columns: repeat(7, 1fr); } 90 | 91 | .dui-grid-8-cols { 92 | grid-template-columns: repeat(8, 1fr); } 93 | 94 | .dui-grid-9-cols { 95 | grid-template-columns: repeat(9, 1fr); } 96 | 97 | .dui-grid-10-cols { 98 | grid-template-columns: repeat(10, 1fr); } 99 | 100 | .dui-grid-11-cols { 101 | grid-template-columns: repeat(11, 1fr); } 102 | 103 | .dui-grid-12-cols { 104 | grid-template-columns: repeat(12, 1fr); } 105 | 106 | .dui-grid-0-padding { 107 | grid-gap: 0px; } 108 | 109 | .dui-grid-1-padding { 110 | grid-gap: 1px; } 111 | 112 | .dui-grid-2-padding { 113 | grid-gap: 2px; } 114 | 115 | .dui-grid-5-padding { 116 | grid-gap: 5px; } 117 | 118 | .dui-grid-10-padding { 119 | grid-gap: 10px; } 120 | 121 | .dui-grid-25-padding { 122 | grid-gap: 25px; } 123 | 124 | .dui-grid-50-padding { 125 | grid-gap: 50px; } 126 | 127 | .dui-grid-100-padding { 128 | grid-gap: 100px; } 129 | 130 | .dui-grid-element { 131 | box-sizing: border-box; 132 | -moz-box-sizing: border-box; 133 | -webkit-box-sizing: border-box; 134 | overflow: auto; } 135 | 136 | /*------------------------------- */ 137 | /* Control Panel */ 138 | /*------------------------------- */ 139 | .dui-controlpanel-wrapper { 140 | flex: 1; 141 | border-right: 2px solid #333; } 142 | 143 | .dui-controlpanel-group { 144 | padding: 10px; } 145 | .dui-controlpanel-group:nth-child(2n+1) { 146 | background-color: #ddd; } 147 | .dui-controlpanel-group:nth-child(2n+2) { 148 | background-color: #fff; } 149 | 150 | /*# sourceMappingURL=grid.css.map */ 151 | -------------------------------------------------------------------------------- /dash_ui/style/css/grid.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "mappings": ";;;;;;AAgBA,WAAW;EACT,MAAM,EAAE,KAAK;EACb,KAAK,EAAE,KAAK;EACZ,QAAQ,EAAE,IAAI;EACd,OAAO,EAAE,IAAI;EACb,SAAS,EAAE,IAAI;;AAEjB,IAAI;EACF,MAAM,EAAE,CAAC;;;;;AAKX,iBAAiB;EACf,IAAI,EAtBiB,CAAC;EAuBtB,gBAAgB,EA1BM,IAAI;EA2B1B,WAAW,EAAE,cAAmC;;AAElD,SAAS;EACP,OAAO,EAAE,IAAI;EACb,MAAM,EAAE,IAAI;EACZ,KAAK,EAAE,IAAI;EACX,UAAU,EAAE,UAAU;EACtB,eAAe,EAAE,UAAU;EAC3B,kBAAkB,EAAE,UAAU;EAC9B,OAAO,EAAE,GAAG;;AAKZ,gBAA0B;EACxB,kBAAkB,EAAE,cAAe;;AADrC,gBAA0B;EACxB,kBAAkB,EAAE,cAAe;;AADrC,gBAA0B;EACxB,kBAAkB,EAAE,cAAe;;AADrC,gBAA0B;EACxB,kBAAkB,EAAE,cAAe;;AADrC,gBAA0B;EACxB,kBAAkB,EAAE,cAAe;;AADrC,gBAA0B;EACxB,kBAAkB,EAAE,cAAe;;AADrC,gBAA0B;EACxB,kBAAkB,EAAE,cAAe;;AADrC,gBAA0B;EACxB,kBAAkB,EAAE,cAAe;;AADrC,gBAA0B;EACxB,kBAAkB,EAAE,cAAe;;AADrC,iBAA0B;EACxB,kBAAkB,EAAE,eAAe;;AADrC,iBAA0B;EACxB,kBAAkB,EAAE,eAAe;;AADrC,iBAA0B;EACxB,kBAAkB,EAAE,eAAe;;AAGrC,gBAA0B;EACxB,qBAAqB,EAAE,cAAe;;AADxC,gBAA0B;EACxB,qBAAqB,EAAE,cAAe;;AADxC,gBAA0B;EACxB,qBAAqB,EAAE,cAAe;;AADxC,gBAA0B;EACxB,qBAAqB,EAAE,cAAe;;AADxC,gBAA0B;EACxB,qBAAqB,EAAE,cAAe;;AADxC,gBAA0B;EACxB,qBAAqB,EAAE,cAAe;;AADxC,gBAA0B;EACxB,qBAAqB,EAAE,cAAe;;AADxC,gBAA0B;EACxB,qBAAqB,EAAE,cAAe;;AADxC,gBAA0B;EACxB,qBAAqB,EAAE,cAAe;;AADxC,iBAA0B;EACxB,qBAAqB,EAAE,eAAe;;AADxC,iBAA0B;EACxB,qBAAqB,EAAE,eAAe;;AADxC,iBAA0B;EACxB,qBAAqB,EAAE,eAAe;;AAGxC,mBAA6B;EAC3B,QAAQ,EAAE,GAAO;;AADnB,mBAA6B;EAC3B,QAAQ,EAAE,GAAO;;AADnB,mBAA6B;EAC3B,QAAQ,EAAE,GAAO;;AADnB,mBAA6B;EAC3B,QAAQ,EAAE,GAAO;;AADnB,oBAA6B;EAC3B,QAAQ,EAAE,IAAO;;AADnB,oBAA6B;EAC3B,QAAQ,EAAE,IAAO;;AADnB,oBAA6B;EAC3B,QAAQ,EAAE,IAAO;;AADnB,qBAA6B;EAC3B,QAAQ,EAAE,KAAO;;AAErB,iBAAiB;EACf,UAAU,EAAE,UAAU;EACtB,eAAe,EAAE,UAAU;EAC3B,kBAAkB,EAAE,UAAU;EAC9B,QAAQ,EAAE,IAAI;;;;;AAKhB,yBAAyB;EACvB,IAAI,EA1DyB,CAAC;EA2D9B,YAAY,EAAE,cAAmC;;AAEnD,uBAAuB;EACrB,OAAO,EAAE,IAAI;EACb,uCAAiB;IACf,gBAAgB,EAnES,IAAI;EAoE/B,uCAAiB;IACf,gBAAgB,EApES,IAAI", 4 | "sources": ["../sass/grid.sass"], 5 | "names": [], 6 | "file": "grid.css" 7 | } -------------------------------------------------------------------------------- /dash_ui/style/sass/grid.sass: -------------------------------------------------------------------------------- 1 | /*-------------------------------*/ 2 | /* Settings */ 3 | /*-------------------------------*/ 4 | $divider-color: #333 5 | $divider-width: 2px // gets multiplied by 2 6 | $grid-background-color: #ddd 7 | $controlpanel-stripe-color1: #ddd 8 | $controlpanel-stripe-color2: #fff 9 | $grid-flex-proportion: 4 10 | $controlpanel-flex-proportion: 1 11 | $gaps: 0, 1, 2, 5, 10, 25, 50, 100 // possible gap pixel settings 12 | 13 | 14 | /*-------------------------------*/ 15 | /* Layout */ 16 | /*-------------------------------*/ 17 | .dui-layout 18 | height: 100vh 19 | width: 100vw 20 | overflow: auto 21 | display: flex 22 | flex-wrap: wrap 23 | 24 | body 25 | margin: 0 26 | 27 | /*-------------------------------*/ 28 | /* Grid */ 29 | /*-------------------------------*/ 30 | .dui-grid-wrapper 31 | flex: $grid-flex-proportion 32 | background-color: $grid-background-color 33 | border-left: $divider-width solid $divider-color 34 | 35 | .dui-grid 36 | display: grid 37 | height: 100% 38 | width: 100% 39 | box-sizing: border-box 40 | -moz-box-sizing: border-box 41 | -webkit-box-sizing: border-box 42 | padding: 5px 43 | 44 | $class-slug: dui-grid !default 45 | 46 | @for $i from 1 through 12 47 | .#{$class-slug}-#{$i}-rows 48 | grid-template-rows: repeat($i, 1fr) 49 | 50 | @for $j from 1 through 12 51 | .#{$class-slug}-#{$j}-cols 52 | grid-template-columns: repeat($j, 1fr) 53 | 54 | @each $k in $gaps 55 | .#{$class-slug}-#{$k}-padding 56 | grid-gap: #{$k}px 57 | 58 | .dui-grid-element 59 | box-sizing: border-box 60 | -moz-box-sizing: border-box 61 | -webkit-box-sizing: border-box 62 | overflow: auto 63 | 64 | /*-------------------------------*/ 65 | /* Control Panel */ 66 | /*-------------------------------*/ 67 | .dui-controlpanel-wrapper 68 | flex: $controlpanel-flex-proportion 69 | border-right: $divider-width solid $divider-color 70 | 71 | .dui-controlpanel-group 72 | padding: 10px 73 | &:nth-child(2n+1) 74 | background-color: $controlpanel-stripe-color1 75 | &:nth-child(2n+2) 76 | background-color: $controlpanel-stripe-color2 77 | -------------------------------------------------------------------------------- /dash_ui/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.5.0-rc1' 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | from setuptools import setup, find_packages 3 | 4 | main_ns = {} 5 | exec(open('dash_ui/version.py').read(), main_ns) # pylint: disable=exec-used 6 | 7 | setup( 8 | name='dash_ui', 9 | version=main_ns['__version__'], 10 | author='Ryan rmarren1', 11 | author_email='rymarr@tuta.io ', 12 | packages=find_packages(), 13 | license='MIT', 14 | description=('Some abstractions to make creating UIs easier in Dash.'), 15 | long_description=io.open('README.md', encoding='utf-8').read(), 16 | install_requires=[ 17 | 'Flask>=0.12', 18 | 'flask-compress', 19 | 'plotly', 20 | 'dash_renderer', 21 | 'dash' 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /simple_demo.py: -------------------------------------------------------------------------------- 1 | from dash import Dash 2 | import dash_html_components as html 3 | import dash_ui as dui 4 | 5 | app = Dash() 6 | my_css_urls = ["https://codepen.io/rmarren1/pen/mLqGRg.css"] 7 | 8 | for url in my_css_urls: 9 | app.css.append_css({ 10 | "external_url": url 11 | }) 12 | 13 | grid = dui.Grid(_id="grid", num_rows=12, num_cols=12, grid_padding=0) 14 | 15 | grid.add_element(col=1, row=1, width=3, height=4, element=html.Div( 16 | style={"background-color": "red", "height": "100%", "width": "100%"} 17 | )) 18 | 19 | grid.add_element(col=4, row=1, width=9, height=4, element=html.Div( 20 | style={"background-color": "blue", "height": "100%", "width": "100%"} 21 | )) 22 | 23 | grid.add_element(col=1, row=5, width=12, height=4, element=html.Div( 24 | style={"background-color": "green", "height": "100%", "width": "100%"} 25 | )) 26 | 27 | grid.add_element(col=1, row=9, width=9, height=4, element=html.Div( 28 | style={"background-color": "orange", "height": "100%", "width": "100%"} 29 | )) 30 | 31 | grid.add_element(col=10, row=9, width=3, height=4, element=html.Div( 32 | style={"background-color": "purple", "height": "100%", "width": "100%"} 33 | )) 34 | 35 | app.layout = html.Div( 36 | dui.Layout( 37 | grid=grid, 38 | ), 39 | style={ 40 | 'height': '100vh', 41 | 'width': '100vw' 42 | } 43 | ) 44 | 45 | if __name__ == "__main__": 46 | app.run_server(debug=True) 47 | --------------------------------------------------------------------------------