dashblox 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dashblox/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ from ._version import __version__
2
+ from . import run
3
+ from . import utils
4
+ from . import components
5
+
6
+ __all__ = [
7
+ '__version__',
8
+ 'run',
9
+ 'utils',
10
+ 'components',
11
+ ]
dashblox/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.0.2"
@@ -0,0 +1,3 @@
1
+ from .table import table
2
+ from .tabs import tabs
3
+ from .loading_message import loading_message
@@ -0,0 +1,33 @@
1
+ from dash import html
2
+
3
+
4
+ def loading_message(id='loading_message',
5
+ message_text='Loading Data...',
6
+ initially_hidden=False):
7
+ """
8
+ Return layout component for a loading message to prevent user access to content
9
+ while data is loading; can be toggled off by setting "hidden" property to True
10
+ """
11
+ return html.Div(id=id,
12
+ children=[
13
+ html.H4(children=message_text,
14
+ style={'position': 'absolute',
15
+ 'top': '200px',
16
+ 'height': '120px',
17
+ 'left': '300px',
18
+ 'width': '600px',
19
+ 'padding': '45px 215px',
20
+ 'color': 'rgba(19, 20, 23, 1)',
21
+ 'background-color': 'rgba(255, 255, 255, 1)',
22
+ 'z-index': '99'}
23
+ )
24
+ ],
25
+ hidden=initially_hidden,
26
+ style={'position': 'fixed',
27
+ 'top': '0px',
28
+ 'bottom': '0px',
29
+ 'left': '0px',
30
+ 'right': '0px',
31
+ 'background-color': 'rgba(0, 0, 0, 0.3)',
32
+ 'z-index': '98'})
33
+
@@ -0,0 +1,237 @@
1
+ import copy
2
+ from collections import OrderedDict
3
+ from dash import html, dash_table
4
+
5
+
6
+ def table(*args, **kwargs):
7
+ """
8
+ Generate and return layout component for data table based on input parameters
9
+ """
10
+ return Table(*args, **kwargs).component
11
+
12
+
13
+ class Table(object):
14
+ default_style = {
15
+ 'div': {
16
+ 'position': 'absolute',
17
+ 'top': '0px',
18
+ 'bottom': '0px',
19
+ 'left': '0px',
20
+ 'overflow-x': 'auto',
21
+ 'overflow-y': 'auto',
22
+ },
23
+ 'body_cell': {
24
+ 'font-size': '14px',
25
+ 'font-family': ['Arial', 'sans-serif'],
26
+ 'text-align': 'left',
27
+ 'background-color': '#FFFFFF',
28
+ 'color': '#131417',
29
+ 'box-sizing': 'border-box',
30
+ 'padding': '4px 6px',
31
+ 'overflow': 'hidden',
32
+ 'text-overflow': 'clip',
33
+ 'border-left': '1pt solid #737373',
34
+ 'border-right': '1pt solid #737373',
35
+ 'border-bottom': '1pt solid #737373',
36
+ },
37
+ 'header_cell': {
38
+ 'font-size': '14px',
39
+ 'font-family': ['Arial', 'sans-serif'],
40
+ 'font-weight': 'bold',
41
+ 'text-align': 'left',
42
+ 'background-color': '#02808A',
43
+ 'color': '#FFFFFF',
44
+ 'box-sizing': 'border-box',
45
+ 'padding': '4px 6px',
46
+ 'overflow': 'hidden',
47
+ 'text-overflow': 'clip',
48
+ 'border': '1pt solid #FFFFFF',
49
+ },
50
+ 'header_row': {
51
+ 'height': '30px',
52
+ }
53
+ }
54
+
55
+ def __init__(self, table_id, columns, **kwargs):
56
+ """
57
+ Create combined header/DataTable component for dashboard layout from columns list
58
+ """
59
+ columns_cfg = self.get_columns_cfg(columns)
60
+ apply_style = self.setup_styles(columns_cfg, kwargs)
61
+ col_defs, table_editable_flag = self.setup_columns(columns_cfg)
62
+ cond_style = self.get_conditional_styles(columns_cfg)
63
+ tot_width = sum([i['width'] for i in columns_cfg.values()])
64
+
65
+ self._component = html.Div(
66
+ [
67
+ html.Table(
68
+ self.get_head_components(columns_cfg, apply_style),
69
+ style={
70
+ 'border-collapse': 'separate',
71
+ 'border-spacing': '0px',
72
+ 'position': 'sticky',
73
+ 'top': '0px',
74
+ 'z-index': '1',
75
+ }
76
+ ),
77
+ dash_table.DataTable(id=table_id,
78
+ columns=col_defs,
79
+ data=[],
80
+ page_action='none',
81
+ style_table={'width': f'{tot_width}px',
82
+ 'z-index': '0'},
83
+ style_data=apply_style['body_cell'],
84
+ style_data_conditional=cond_style,
85
+ css=[
86
+ {'selector': 'tr:first-child', 'rule': 'display: none'},
87
+ {'selector': '.dash-spreadsheet tr', 'rule': 'height: 10px;'}
88
+ ],
89
+ editable=table_editable_flag,
90
+ cell_selectable=kwargs.get('selectable', False),
91
+ selected_cells=[])
92
+ ],
93
+ style=apply_style['div']
94
+ )
95
+
96
+ @staticmethod
97
+ def get_columns_cfg(columns):
98
+ """
99
+ Make OrderedDict from columns parameter
100
+ """
101
+ columns_cfg = OrderedDict([(k.lower(), v) for k,v in columns])
102
+ for k in columns_cfg:
103
+ if 'width' not in columns_cfg[k]:
104
+ columns_cfg[k]['width'] = 100
105
+ return columns_cfg
106
+
107
+ def setup_styles(self, columns_cfg, kwargs):
108
+ """
109
+ Define styles dicts for Div/DataTable objects from default+kwargs
110
+ """
111
+ apply_style = copy.deepcopy(self.default_style)
112
+ apply_style['div'].update(kwargs.get('style', {}))
113
+ if apply_style['div'].get('right') is None:
114
+ w = sum([i['width'] for i in columns_cfg.values()]) + 18
115
+ apply_style['div']['width'] = f'{w}px'
116
+
117
+ for k in {*apply_style}-{'div'}:
118
+ apply_style[k].update(kwargs.get(f'{k}_style', {}))
119
+
120
+ return apply_style
121
+
122
+ def setup_columns(self, columns_cfg):
123
+ """
124
+ Generate column definitions input for dash_table.DataTable
125
+ """
126
+ col_defs = list()
127
+ table_editable_flag = False
128
+ for c in columns_cfg:
129
+ col_def = {'id': c, 'name': c}
130
+ if (num_fmt:=columns_cfg[c].get('num_fmt')):
131
+ col_def['type'] = 'numeric'
132
+ if isinstance(num_fmt, str):
133
+ col_def['format'] = self.get_num_fmt(num_fmt)
134
+ else:
135
+ col_def['format'] = num_fmt
136
+ if columns_cfg[c].get('editable', False) and not (columns_cfg[c]['width'] == 0):
137
+ col_def['editable'] = True
138
+ table_editable_flag = True
139
+ col_defs += [col_def]
140
+
141
+ return col_defs, table_editable_flag
142
+
143
+ @staticmethod
144
+ def get_num_fmt(num_fmt):
145
+ """
146
+ Converts number format string into dash_table.Format or
147
+ dash_table.FormatTemplate object to set column format for table.
148
+ """
149
+ t,p = num_fmt.split('-')
150
+ p = int(p)
151
+ return {
152
+ 'money': dash_table.FormatTemplate.money(p).group(True),
153
+ 'comma': dash_table.Format.Format(precision=p, group=True, groups=[3]),
154
+ 'number': dash_table.Format.Format(precision=p,
155
+ scheme=dash_table.Format.Scheme.fixed),
156
+ 'pct': dash_table.FormatTemplate.percentage(p),
157
+ }[t]
158
+
159
+ @staticmethod
160
+ def get_conditional_styles(columns_cfg):
161
+ """
162
+ Create conditional style dict, populate rules for column widths,
163
+ number alignment and any conditional formatting specified in layout.
164
+ """
165
+ cond_style = {c: dict() for c in columns_cfg}
166
+ for c in columns_cfg:
167
+ if columns_cfg[c]['width'] == 0:
168
+ cond_style[c]['display'] = 'none'
169
+ else:
170
+ w = columns_cfg[c]['width']
171
+ for p in ['min-width', 'max-width']:
172
+ cond_style[c][p] = f'{w}px'
173
+ cond_style[c]['text-align'] = ['left','right']['num_fmt' in columns_cfg[c]]
174
+ cond_style = [{'if': {'column_id': c}, **cond_style[c]}
175
+ for c in columns_cfg]
176
+
177
+ for c,cfg in columns_cfg.items():
178
+ for query,style in cfg.get('cstyle', []):
179
+ cond_style += [{'if': {'filter_query': query,
180
+ 'column_id': c},
181
+ **style}]
182
+
183
+ return cond_style
184
+
185
+ @staticmethod
186
+ def get_head_components(columns_cfg, apply_style):
187
+ """
188
+ Return table head (html.Thead) object for table
189
+ """
190
+ # Organize column labels; detect where multi-row head needed
191
+ hlabels = OrderedDict([(k, z if isinstance((z:=v.get('label', k)), list) else [z])
192
+ for k,v in columns_cfg.items()
193
+ if v['width'] > 0])
194
+ n_hrows = max([len(i) for i in hlabels.values()])
195
+
196
+ hlabels_grid = [[*hlabels[k], *['']*n_hrows][:n_hrows] for k in hlabels]
197
+ row_spans = [*zip(*[[[0,cm[i+1:].index(1)+1][j]
198
+ for i,j in enumerate(cm[:-1])]
199
+ for cl in hlabels_grid
200
+ for cm in [[min(len(i),1) for i in cl+['X']]]])]
201
+
202
+ hlabels_grid = [*zip(*hlabels_grid)]
203
+ col_spans = [[[0,rm[i+1:].index(1)+1][j] for i,j in enumerate(rm[:-1])]
204
+ for rl in hlabels_grid
205
+ for rm in [[int(i==0 or j!=rl[i-1])
206
+ for i,j in enumerate([*rl,'X'])]]]
207
+
208
+ # Generate head components, with expanded cells where needed
209
+ head_components = []
210
+ for r in range(n_hrows):
211
+ row = []
212
+ for c,cid in enumerate(columns_cfg):
213
+ if (sp_r:=row_spans[r][c])*(sp_c:=col_spans[r][c]) != 0:
214
+ cell_style = copy.deepcopy(apply_style['header_cell'])
215
+ if sp_c == 1:
216
+ cell_style.update({i: f"{columns_cfg[cid]['width']}px"
217
+ for i in ['min-width','max-width']})
218
+ cell_style.update({
219
+ 'border-top-color': (cch:=[cell_style[i]
220
+ for i in ['color',
221
+ 'background-color']])[r==0],
222
+ 'border-bottom-color': cch[r+sp_r==n_hrows],
223
+ 'border-left-color': cch[c==0],
224
+ 'border-right-color': cch[c+sp_c==len(columns_cfg)],
225
+ })
226
+ row += [html.Th(children=hlabels_grid[r][c],
227
+ rowSpan=sp_r,
228
+ colSpan=sp_c,
229
+ style=cell_style)]
230
+ head_components += [html.Tr(row, style=apply_style['header_row'])]
231
+
232
+ return [html.Thead(head_components)]
233
+
234
+ @property
235
+ def component(self):
236
+ return self._component
237
+
@@ -0,0 +1,99 @@
1
+ from dash import dcc, html
2
+ import copy
3
+
4
+
5
+ def tabs(*args, **kwargs):
6
+ """
7
+ Generate and return layout component for tabs object based on input parameters
8
+ """
9
+ return Tabs(*args, **kwargs).component
10
+
11
+
12
+ class Tabs(object):
13
+ default_style = {
14
+ 'div': {
15
+ 'position': 'absolute',
16
+ 'top': '12px',
17
+ 'bottom': '12px',
18
+ 'left': '12px',
19
+ 'right': '12px',
20
+ },
21
+ 'selector': {
22
+ 'height': '44px',
23
+ 'width': '540px',
24
+ },
25
+ 'tab': {
26
+ 'border-top-left-radius': '3px',
27
+ 'border-top-right-radius': '3px',
28
+ 'border-bottom': '1px solid #D6D6D6',
29
+ 'border-top': '1px solid #D6D6D6',
30
+ 'padding': '6px',
31
+ 'font-weight': 'normal',
32
+ 'color': '#131417',
33
+ 'backgroundColor': '#D6D6D6',
34
+ },
35
+ 'selected_tab': {
36
+ 'border-top-left-radius': '3px',
37
+ 'border-top-right-radius': '3px',
38
+ 'border-bottom': '1px solid #D6D6D6',
39
+ 'border-top': '2px solid #02808A',
40
+ 'padding': '6px',
41
+ 'font-weight': 'bold',
42
+ 'color': '#02808A',
43
+ 'backgroundColor': '#FFFFFF',
44
+ },
45
+ 'tab_div': {
46
+ 'position': 'absolute',
47
+ 'top': '48px',
48
+ 'bottom': '0px',
49
+ 'left': '0px',
50
+ 'right': '0px',
51
+ },
52
+ }
53
+
54
+ def __init__(self,
55
+ tabs_id,
56
+ tabs,
57
+ **kwargs):
58
+ """
59
+ Create dcc.Tabs component for dashboard layout from input list
60
+ """
61
+ self.apply_style = self.setup_styles(kwargs)
62
+ self._component = html.Div(
63
+ [
64
+ dcc.Tabs(id=tabs_id,
65
+ value=tabs[kwargs.get('initial_select', 0)]['tab_id'],
66
+ children=[self.get_tab(**t) for t in tabs],
67
+ style=self.apply_style['selector'])
68
+ ],
69
+ style=self.apply_style['div']
70
+ )
71
+
72
+ def setup_styles(self, kwargs):
73
+ """
74
+ Define styles dicts for Div/Tab/Tabs objects from default+kwargs
75
+ """
76
+ apply_style = copy.deepcopy(self.default_style)
77
+ apply_style['div'].update(kwargs.get('style', {}))
78
+ for k in {*apply_style}-{'div'}:
79
+ apply_style[k].update(kwargs.get(f'{k}_style', {}))
80
+ # (Auto-adjust tab_div/top to match selector/height)
81
+ top_px = int(apply_style['selector']['height'].replace('px', '')) + 4
82
+ apply_style['tab_div']['top'] = f"{top_px}px"
83
+
84
+ return apply_style
85
+
86
+ def get_tab(self, tab_id, label, content):
87
+ """
88
+ Return layout component for tab content.
89
+ """
90
+ return dcc.Tab(label=label,
91
+ value=tab_id,
92
+ children=html.Div(content,
93
+ style=self.apply_style['tab_div']),
94
+ style=self.apply_style['tab'],
95
+ selected_style=self.apply_style['selected_tab'])
96
+
97
+ @property
98
+ def component(self):
99
+ return self._component
@@ -0,0 +1,6 @@
1
+ from .multipage_setup import multipage_setup, set_host_and_port
2
+ from .app_layout import app_layout
3
+ from .set_up_page import set_up_page
4
+ from .callback_utils import DashCallbackMgr, SourceData
5
+ from .set_trigger import set_trigger
6
+ from .notebook_test import notebook_test
@@ -0,0 +1,8 @@
1
+ from . import multipage_setup
2
+ import os
3
+ from subprocess import run
4
+
5
+ multipage_setup()
6
+ run("gunicorn -bind 0.0.0.0.${DASH_PORT} main:server", env=os.environ, shell=True)
7
+
8
+ # Run from Dockerfile: CMD python -m dashblox.run
@@ -0,0 +1,112 @@
1
+ from dash import html, dcc, page_registry, page_container
2
+ import dash_bootstrap_components as dbc
3
+
4
+
5
+ def app_layout(title=None):
6
+ """
7
+ Return layout for multi-page app
8
+ """
9
+ # Fix position of page container to sit under navbar
10
+ cntix = [i for i, j in enumerate(page_container.children)
11
+ if j.id == '_pages_content'][0]
12
+ page_container.children[cntix].style = {'position': 'absolute',
13
+ 'top': '50px',
14
+ 'bottom': '0px',
15
+ 'left': '0px',
16
+ 'right': '0px'}
17
+ return html.Div(
18
+ [
19
+ create_navbar(title=title or ''),
20
+ page_container
21
+ ]
22
+ )
23
+
24
+
25
+ logo = f'./assets/luna_logo_teal.png'
26
+
27
+ lnk_style = {
28
+ 'width': '128px',
29
+ 'height': '36px',
30
+ 'margin': '0px 5px',
31
+ 'color': 'white',
32
+ 'border': '2px solid white',
33
+ 'font-size': '14px',
34
+ 'font-weight': 'normal',
35
+ 'text-align': 'center',
36
+ 'vertical-align': 'middle',
37
+ 'line-height': '32px'
38
+ }
39
+
40
+
41
+ def create_navbar(title):
42
+ """
43
+ Return layout components for top-of-page navigation bar.
44
+ """
45
+ navbar = dbc.Navbar(
46
+ [
47
+ html.A(html.Img(src=logo,
48
+ height=48),
49
+ href='/',
50
+ style = {
51
+ 'position': 'fixed',
52
+ 'vertical-align': 'middle',
53
+ 'left': '20px',
54
+ }
55
+ ),
56
+ html.A(html.Div(title,
57
+ style = {
58
+ 'font-size': '20px',
59
+ 'font-weight': 'bold',
60
+ 'color': '#014A50',
61
+ }
62
+ ),
63
+ href='/',
64
+ style = {
65
+ 'position': 'fixed',
66
+ 'vertical-align': 'middle',
67
+ 'left': '80px',
68
+ 'textDecoration': 'none',
69
+ }
70
+ ),
71
+ html.A(html.Div(title,
72
+ style = {
73
+ 'font-size': '20px',
74
+ 'font-weight': 'bold',
75
+ 'color': '#014A50',
76
+ }
77
+ ),
78
+ href='/',
79
+ style = {
80
+ 'position': 'fixed',
81
+ 'vertical-align': 'middle',
82
+ 'left': '80px',
83
+ 'textDecoration': 'none',
84
+ }
85
+ ),
86
+ html.A(html.Img(src=logo,
87
+ height=36),
88
+ href='https://www.google.com',
89
+ target='_blank',
90
+ style = {
91
+ 'position': 'fixed',
92
+ 'vertical-align': 'middle',
93
+ 'right': '20px',
94
+ }
95
+ ),
96
+ ],
97
+ sticky='top',
98
+ style={
99
+ 'position': 'fixed',
100
+ 'top': '0px',
101
+ 'left': '0px',
102
+ 'height': '50px',
103
+ 'right': '0px',
104
+ 'font-family': ['Open Sans', 'sans-serif'],
105
+ 'background-image': 'url("./assets/navbar.png")',
106
+ 'background-size': 'cover',
107
+ 'min-height': '50px',
108
+ 'background-color': 'transparent',
109
+ }
110
+ )
111
+
112
+ return navbar