Source code for dtale.dash_application.layout

import dash_core_components as dcc
import dash_daq as daq
import dash_html_components as html

from dtale.charts.utils import YAXIS_CHARTS, ZAXIS_CHARTS
from dtale.utils import classify_type, flatten_lists, get_dtypes, make_list


[docs]def base_layout(github_fork, **kwargs): """ Base layout to be returned by :meth:`dtale.dash_application.views.DtaleDash.interpolate_index` :param github_fork: `True` if "Fork me on Github" banner should be displayed, `False` otherwise :type github_fork: bool :param kwargs: Optional keyword arguments to be passed to 'dash.Dash.interplolate_index' :type kwargs: dict :return: HTML :rtype: str """ back_to_data_padding, github_fork_html = ('', '') if github_fork: back_to_data_padding = 'padding-right: 125px' github_fork_html = ''' <span id="forkongithub"> <a href="https://github.com/man-group/dtale">Fork me on GitHub</a> </span> ''' return ''' <!DOCTYPE html> <html> <head> {metas} <title>D-Tale Charts</title> <link rel="shortcut icon" href="../../images/favicon.png"> {css} </head> <body> {github_fork_html} <div class="container-fluid charts"> <div class="row" style="margin: 0"> <div class="col-auto"> <header> <span class="title-font">D-TALE</span> <span style="font-size: 16px" class="pl-4">Charts</span> </header> </div> <div class="col"></div> <div class="col-auto mt-4" style="{back_to_data_padding}"> <a href="#" onclick="javascript:backToData()"> <i class="fas fa-th mr-4"></i> <span>Back To Data</span> </a> </div> </div> {app_entry} </div> <footer> {config} {scripts} <script type="text/javascript"> const pathSegs = window.location.pathname.split('/'); const dataId = pathSegs[pathSegs.length - 1]; const backToData = () => window.open('/dtale/main/' + dataId); const goToLegacy = () => location.replace('/dtale/popup/charts/' + dataId); </script> {renderer} {css} </footer> </body> </html> '''.format( metas=kwargs['metas'], css=kwargs['css'], app_entry=kwargs['app_entry'], config=kwargs['config'], scripts=kwargs['scripts'], renderer=kwargs['renderer'], back_to_data_padding=back_to_data_padding, github_fork_html=github_fork_html )
[docs]def build_input(label, input, className='col-auto', **kwargs): """ Helper function to build a standard label/input component in dash. :param label: name of the input you are displaying :type label: str :param input: dash component for storing state :param className: style class to be applied to encapsulating div :type className: str :param kwargs: Optional keyword arguments to be applied to encapsulating div (style, title, id...) :type kwargs: dict :return: dash components for label/input :rtype: :dash:`dash_html_components.Div <dash-html-components/div>` """ return html.Div( [ html.Div( [html.Span(label, className='input-group-addon'), input], className='input-group mr-3', ) ], className=className, **kwargs )
[docs]def build_tab(label, value, **kwargs): """ Builds a :dash:`dash_core_components.Tab <dash-core-components/tab>` with standard styling settings. """ return dcc.Tab( label=label, value=value, style={'borderBottom': '1px solid #d6d6d6', 'padding': '6px', 'fontWeight': 'bold'}, selected_style={ 'borderTop': '1px solid #d6d6d6', 'borderBottom': '1px solid #d6d6d6', 'backgroundColor': '#2a91d1', 'color': 'white', 'padding': '6px' }, **kwargs)
[docs]def build_option(value, label=None): """ Returns value/label inputs in a dictionary for use in :dash:`dash_core_components.Dropdown <dash-core-components/Dropdown>` """ return {'label': label or value, 'value': value}
CHARTS = [ dict(value='line'), dict(value='bar'), dict(value='scatter'), dict(value='pie'), dict(value='wordcloud'), dict(value='heatmap'), dict(value='3d_scatter', label='3D Scatter'), dict(value='surface') ] CHART_INPUT_SETTINGS = { 'line': dict(x=dict(type='single'), y=dict(type='multi'), z=dict(display=False), group=dict(display=True, type='single')), 'bar': dict(x=dict(type='single'), y=dict(type='multi'), z=dict(display=False), group=dict(display=True, type='single')), 'scatter': dict(x=dict(type='single'), y=dict(type='multi'), z=dict(display=False), group=dict(display=True, type='single')), 'pie': dict(x=dict(type='single'), y=dict(type='multi'), z=dict(display=False), group=dict(display=True, type='single')), 'wordcloud': dict(x=dict(type='single'), y=dict(type='multi'), z=dict(display=False), group=dict(display=True, type='single')), 'heatmap': dict(x=dict(type='single'), y=dict(type='single'), z=dict(display=True, type='single'), group=dict(display=False)), '3d_scatter': dict(x=dict(type='single'), y=dict(type='single'), z=dict(display=True, type='single'), group=dict(display=True)), 'surface': dict(x=dict(type='single'), y=dict(type='single'), z=dict(display=True, type='single'), group=dict(display=False)), } AGGS = dict( count='Count', nunique='Unique Count', sum='Sum', mean='Mean', rolling='Rolling', corr='Correlation', first='First', last='Last', median='Median', min='Minimum', max='Maximum', std='Standard Deviation', var='Variance', mad='Mean Absolute Deviation', prod='Product of All Items' ) FREQS = ['H', 'H2', 'WD', 'D', 'W', 'M', 'Q', 'Y'] FREQ_LABELS = dict(H='Hourly', H2='Hour', WD='Weekday', W='Weekly', M='Monthly', Q='Quarterly', Y='Yearly')
[docs]def show_input_handler(chart_type): settings = CHART_INPUT_SETTINGS.get(chart_type) or {} def _show_input(input_id, input_type='single'): cfg = settings.get(input_id, {}) return cfg.get('display', True) and cfg.get('type', 'single') == input_type return _show_input
[docs]def update_label_for_freq(val): """ Formats sub-values contained within 'val' to display date frequencies if included. - (val=['a', 'b', 'c']) => 'a, b, c' - (val=['a|H', 'b', 'c']) => 'a (Hour), b, c' """ def _freq_handler(sub_val): for freq in FREQS: if sub_val.endswith('|{}'.format(freq)): col, freq = sub_val.split('|') if freq in FREQ_LABELS: return '{} ({})'.format(col, FREQ_LABELS[freq]) return col return sub_val return ', '.join([_freq_handler(sub_val) for sub_val in make_list(val)])
[docs]def build_error(error, tb): """ Returns error/traceback information in standard component with styling :param error: execption message :type error: str :param tb: traceback :type tb: str :return: error component :rtype: :dash:`dash_html_components.Div <dash-html-components/div>` """ return html.Div([ html.I(className='ico-error'), html.Span(str(error)), html.Div(html.Pre(tb), className='traceback') ], className='dtale-alert alert alert-danger')
[docs]def build_cols(cols, dtypes): """ Helper function to add additional column entries for columns of type datetime so that users can make use of different frequencies of dates. For example, hour, weekday, month, quarter, year :param cols: columns in dataframe :type cols: list of strings :param dtypes: datatypes of columns in dataframe :type dtypes: dict :return: generator or columns + any additional (datetime column + frequency) options """ for c in cols: if classify_type(dtypes[c]) == 'D': for freq in FREQS: if freq in FREQ_LABELS: yield '{}|{}'.format(c, freq), '{} ({})'.format(c, FREQ_LABELS[freq]) else: yield c, c else: yield c, c
[docs]def build_selections(*args): """ simple helper function to build a single level list of values based on variable number of inputs which could be equal to None. """ return flatten_lists([[] if a is None else make_list(a) for a in args])
[docs]def build_input_options(df, **inputs): """ Builds dropdown options for (X, Y, Z, Group, Barsort & Y-Axis Ranges) with filtering based on currently selected values for the following inputs: x, y, z, group. """ [chart_type, x, y, z, group] = [inputs.get(p) for p in ['chart_type', 'x', 'y', 'z', 'group']] col_opts = list(build_cols(df.columns, get_dtypes(df))) group_val, z_val = (None, z) if chart_type in ZAXIS_CHARTS else (group, None) x_options = [build_option(c, l) for c, l in col_opts if c not in build_selections(y, z_val, group_val)] y_filter = build_selections(x, group_val, z_val) y_multi_options = [build_option(c, l) for c, l in col_opts if c not in y_filter] y_single_options = [build_option(c, l) for c, l in col_opts if c not in y_filter] z_options = [build_option(c) for c in df.columns if c not in build_selections(x, y, group_val)] group_options = [build_option(c, l) for c, l in col_opts if c not in build_selections(x, y, z_val)] barsort_options = [build_option(o) for o in build_selections(x, y)] yaxis_options = [build_option(y2) for y2 in y or []] return x_options, y_multi_options, y_single_options, z_options, group_options, barsort_options, yaxis_options
[docs]def bar_input_style(**inputs): """ Sets display CSS property for bar chart inputs """ return dict(display='block' if inputs.get('chart_type') == 'bar' else 'none')
[docs]def show_chart_per_group(**inputs): """ Boolean function to determine whether "Chart Per Group" toggle should be displayed or not """ [chart_type, group] = [inputs.get(p) for p in ['chart_type', 'group']] return show_input_handler(chart_type)('group') and len(group or []) and chart_type not in ['pie', 'wordcloud']
[docs]def show_yaxis_ranges(**inputs): """ Boolean function to determine whether "Y-Axis Range" inputs should be displayed or not """ [chart_type, y] = [inputs.get(p) for p in ['chart_type', 'y']] return chart_type in YAXIS_CHARTS and len(y or [])
[docs]def charts_layout(df, settings, **inputs): """ Builds main dash inputs with dropdown options populated with the columns of the dataframe associated with the page. Inputs included are: chart tabs, query, x, y, z, group, aggregation, rolling window/computation, chart per group toggle, bar sort, bar mode, y-axis range editors :param df: dataframe to drive the charts built on page :type df: :class:`pandas:pandas.DataFrame` :param settings: global settings associated with this dataframe (contains properties like "query") :type param: dict :return: dash markup """ [chart_type, x, y, z, group, agg] = [inputs.get(p) for p in ['chart_type', 'x', 'y', 'z', 'group', 'agg']] y = y or [] show_input = show_input_handler(chart_type) show_cpg = show_chart_per_group(**inputs) show_yaxis = show_yaxis_ranges(**inputs) bar_style = bar_input_style(**inputs) options = build_input_options(df, **inputs) x_options, y_multi_options, y_single_options, z_options, group_options, barsort_options, yaxis_options = options query_placeholder = ( "Enter pandas query (ex: col1 == 1)" ) query_label = html.Div([ html.Span('Query'), html.A(html.I(className='fa fa-info-circle ml-4'), href='https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#indexing-query', target='_blank', style={'color': 'white'}) ], className='input-group-addon', style={'min-width': '7em'}) return html.Div([ dcc.Store(id='query-data', data=inputs.get('query')), dcc.Store(id='input-data', data={k: v for k, v in inputs.items() if k not in ['cpg', 'barmode', 'barsort']}), dcc.Store(id='chart-input-data', data={k: v for k, v in inputs.items() if k in ['cpg', 'barmode', 'barsort']}), dcc.Store(id='range-data'), dcc.Store(id='yaxis-data', data=inputs.get('yaxis')), dcc.Store(id='last-chart-input-data', data=inputs), dcc.Input(id='chart-code', type='hidden'), html.Div(html.Div(dcc.Tabs( id='chart-tabs', value=chart_type or 'line', children=[build_tab(t.get('label', t['value'].capitalize()), t['value']) for t in CHARTS], style=dict(height='36px') ), className='col-md-12'), className='row pt-3 pb-3 charts-filters'), html.Div(html.Div([ html.Div([ query_label, dcc.Input( id='query-input', type='text', placeholder=query_placeholder, className='form-control', value=inputs.get('query') or settings.get('query'), style={'line-height': 'inherit'}) ], className='input-group mr-3')], className='col' ), className='row pt-3 pb-3 charts-filters'), html.Div([ build_input('X', dcc.Dropdown( id='x-dropdown', options=x_options, placeholder='Select a column', value=x, style=dict(width='inherit'), )), build_input('Y', dcc.Dropdown( id='y-multi-dropdown', options=y_multi_options, multi=True, placeholder='Select a column(s)', style=dict(width='inherit'), value=y if show_input('y', 'multi') else None ), className='col', id='y-multi-input', style={'display': 'block' if show_input('y', 'multi') else 'none'}), build_input('Y', dcc.Dropdown( id='y-single-dropdown', options=y_single_options, placeholder='Select a column', style=dict(width='inherit'), value=y[0] if show_input('y') and len(y) else None ), className='col', id='y-single-input', style={'display': 'block' if show_input('y') else 'none'}), build_input('Z', dcc.Dropdown( id='z-dropdown', options=z_options, placeholder='Select a column', style=dict(width='inherit'), value=z ), className='col', id='z-input', style={'display': 'block' if show_input('z') else 'none'}), build_input('Group', dcc.Dropdown( id='group-dropdown', options=group_options, multi=True, placeholder='Select a group(s)', value=group, style=dict(width='inherit'), ), className='col', id='group-input', style={'display': 'block' if show_input('group') else 'none'}), ], className='row pt-3 pb-3 charts-filters'), html.Div([ build_input('Aggregation', dcc.Dropdown( id='agg-dropdown', options=[build_option(v, AGGS[v]) for v in ['count', 'nunique', 'sum', 'mean', 'rolling', 'corr', 'first', 'last', 'median', 'min', 'max', 'std', 'var', 'mad', 'prod']], placeholder='Select an aggregation', style=dict(width='inherit'), value=agg, )), html.Div([ build_input('Window', dcc.Input( id='window-input', type='number', placeholder='Enter days', className='form-control text-center', style={'line-height': 'inherit'}, value=inputs.get('window') )), build_input('Computation', dcc.Dropdown( id='rolling-comp-dropdown', options=[ build_option('corr', 'Correlation'), build_option('count', 'Count'), build_option('cov', 'Covariance'), build_option('kurt', 'Kurtosis'), build_option('max', 'Maximum'), build_option('mean', 'Mean'), build_option('median', 'Median'), build_option('min', 'Minimum'), build_option('skew', 'Skew'), build_option('std', 'Standard Deviation'), build_option('sum', 'Sum'), build_option('var', 'Variance'), ], placeholder='Select an computation', style=dict(width='inherit'), value=inputs.get('rolling_comp') )) ], id='rolling-inputs', style=dict(display='block' if agg == 'rolling' else 'none')) ], className='row pt-3 pb-3 charts-filters'), html.Div( [ build_input('Chart Per\nGroup', html.Div(daq.BooleanSwitch(id='cpg-toggle', on=inputs.get('cpg') or False), className='toggle-wrapper'), id='cpg-input', style={'display': 'block' if show_cpg else 'none'}, className='col-auto'), build_input('Barmode', dcc.Dropdown( id='barmode-dropdown', options=[ build_option('group', 'Group'), build_option('stack', 'Stack'), build_option('relative', 'Relative'), ], value=inputs.get('barmode') or 'group', placeholder='Select a mode', ), className='col-auto addon-min-width', style=bar_style, id='barmode-input'), build_input('Barsort', dcc.Dropdown( id='barsort-dropdown', options=barsort_options, value=inputs.get('barsort') ), className='col-auto addon-min-width', style=bar_style, id='barsort-input'), html.Div( html.Div( [ html.Span('Y-Axis', className='input-group-addon'), dcc.Dropdown(id='yaxis-dropdown', options=yaxis_options), html.Span('Min:', className='input-group-addon col-auto'), dcc.Input( id='yaxis-min-input', type='number', className='form-control col-auto', style={'line-height': 'inherit'} ), html.Span('Max:', className='input-group-addon col-auto'), dcc.Input( id='yaxis-max-input', type='number', className='form-control col-auto', style={'line-height': 'inherit'} ) ], className='input-group', ), className='col-auto addon-min-width', id='yaxis-input', style=dict(display='block' if show_yaxis else 'none') ), ], className='row pt-3 pb-5 charts-filters' ), dcc.Loading(html.Div(id='chart-content', style={'height': '70vh'}), type='circle'), dcc.Textarea(id="copy-text", style=dict(position='absolute', left='-110%')) ], className='charts-body')