def build_tab(label, value, additional_style=None, **kwargs): """ Builds a :dash:`dash_core_components.Tab <dash-core-components/tab>` with standard styling settings. """ base_style = {"borderBottom": "1px solid #d6d6d6", "padding": "6px"} return dcc.Tab(label=label, value=value, style=dict_merge(base_style, {"fontWeight": "bold"}, additional_style or {}), disabled_style=dict_merge( base_style, { "fontWeight": "bold", "backgroundColor": "LightGray", "color": "black", "cursor": "not-allowed", }, additional_style or {}, ), selected_style=dict_merge( base_style, { "borderTop": "1px solid #d6d6d6", "backgroundColor": "#2a91d1", "color": "white", }, additional_style or {}, ), **kwargs)
def scatter_builder(data, x, y, axes_builder, wrapper, group=None, z=None, agg=None): """ Builder function for :plotly:`plotly.graph_objects.Scatter <plotly.graph_objects.Scatter>` :param data: raw data to be represented within scatter chart :type data: dict :param x: column to use for the X-Axis :type x: str :param y: columns to use for the Y-Axes :type y: list of str :param axes_builder: function for building axis configurations :type axes_builder: func :param wrapper: wrapper function returned by :meth:`dtale.charts.utils.chart_wrapper` :type wrapper: func :param z: column to use for the Z-Axis :type z: str :param group: column(s) to use for grouping :type group: list of str or str :param agg: points to a specific function that can be applied to :func: pandas.core.groupby.DataFrameGroupBy. Possible values are: count, first, last mean, median, min, max, std, var, mad, prod, sum :type agg: str :return: scatter chart :rtype: :plotly:`plotly.graph_objects.Scatter <plotly.graph_objects.Scatter>` """ def layout(axes): if z is not None: return {'height': 700, 'margin': {'l': 0, 'r': 0, 'b': 0}, 'scene': dict_merge(axes, dict(aspectmode='data'))} return axes def marker(series): if z is not None: return {'size': 8, 'color': series[z], 'colorscale': 'Blackbody', 'opacity': 0.8, 'showscale': True, 'colorbar': {'thickness': 15, 'len': 0.5, 'x': 0.8, 'y': 0.6}} return {'size': 15, 'line': {'width': 0.5, 'color': 'white'}} scatter_func = go.Scatter3d if z is not None else go.Scatter return [ wrapper(dcc.Graph( id='scatter-{}-{}'.format(group or 'all', y2), figure={'data': [ scatter_func(**dict_merge( dict(x=d['x'], y=d[y2], mode='markers', opacity=0.7, name=series_key, marker=marker(d)), dict(z=d[z]) if z is not None else dict()) ) for series_key, d in data['data'].items() if y2 in d and (group is None or group == series_key) ], 'layout': build_layout(dict_merge(build_title(x, y2, group, z=z, agg=agg), layout(axes_builder([y2]))))} )) for y2 in y ]
def build_grouped_bars_with_multi_yaxis(series_cfgs, y): """ This generator is a hack for the lack of support plotly has for sorting :plotly:`plotly.graph_objects.Bar <plotly.graph_objects.Bar>` charts by an axis other than 'y'. This also helps with issues around displaying multiple y-axis. :param series_cfgs: configurations for all the series within a bar chart :type series_cfgs: list of dict :param y: columns to use for the Y-Axes :type y: list of str :return: updated series configurations :type: generator """ for i, y2 in enumerate(y, 1): for series in series_cfgs: curr_y = series.get('yaxis') yaxis = 'y{}'.format(i) if (i == 1 and curr_y is None) or (yaxis == curr_y): yield series else: yield dict_merge( { k: v for k, v in series.items() if k in ['x', 'name', 'type'] }, dict(hoverinfo='none', showlegend=False, y=[0]), dict(yaxis=yaxis) if i > 1 else {})
def main_input_class( ts_, ts2_, _ts3, _ts4, pathname, inputs, map_inputs, cs_inputs, treemap_inputs ): return main_inputs_and_group_val_display( dict_merge(inputs, map_inputs, cs_inputs, treemap_inputs), get_data_id(pathname), )
def test_client(self, reaper_on=False, port=None, app_root=None, *args, **kwargs): """ Overriding Flask's implementation of test_client so we can specify ports for testing and whether auto-reaper should be running :param reaper_on: whether to run auto-reaper subprocess :type reaper_on: bool :param port: port number of flask application :type port: int :param args: Optional arguments to be passed to :meth:`flask:flask.Flask.test_client` :param kwargs: Optional keyword arguments to be passed to :meth:`flask:flask.Flask.test_client` :return: Flask's test client :rtype: :class:`dtale.app.DtaleFlaskTesting` """ self.reaper_on = reaper_on self.app_root = app_root if app_root is not None: self.config["APPLICATION_ROOT"] = app_root self.jinja_env.globals["url_for"] = self.url_for self.test_client_class = DtaleFlaskTesting return super(DtaleFlask, self).test_client(*args, **dict_merge(kwargs, dict(port=port)))
def on_data( _ts1, _ts2, _ts3, _ts4, pathname, inputs, chart_inputs, yaxis_data, map_data, last_chart_inputs, ): """ dash callback controlling the building of dash charts """ all_inputs = dict_merge(inputs, chart_inputs, dict(yaxis=yaxis_data or {}), map_data) if all_inputs == last_chart_inputs: raise PreventUpdate if is_app_root_defined(dash_app.server.config.get("APPLICATION_ROOT")): all_inputs["app_root"] = dash_app.server.config["APPLICATION_ROOT"] charts, range_data, code = build_chart(get_data_id(pathname), **all_inputs) return ( charts, all_inputs, range_data, "\n".join(make_list(code)), get_yaxis_type_tabs(make_list(inputs.get("y") or [])), )
def build_pies(): for series_key, series in data['data'].items(): for y2 in y: negative_values = [] for x_val, y_val in zip(series['x'], series[y2]): if y_val < 0: negative_values.append('{} ({})'.format(x_val, y_val)) chart = wrapper(dcc.Graph( id='pie-{}-graph'.format(series_key), figure={ 'data': [go.Pie(**dict_merge( dict(labels=series['x'], values=series[y2]), name_builder(y2, series_key) ))], 'layout': build_layout(build_title(x, y2, group=series_key, agg=inputs.get('agg'))) } )) if len(negative_values): error_title = ( 'The following negative values could not be represented within the {}Pie chart' ).format('' if series_key == 'all' else '{} '.format(series_key)) error_div = html.Div([ html.I(className='ico-error'), html.Span(error_title), html.Div(html.Pre(', '.join(negative_values)), className='traceback') ], className='dtale-alert alert alert-danger') yield html.Div( [html.Div(error_div, className='col-md-12'), html.Div(chart, className='col-md-12')], className='row' ) else: yield chart
def main_input_class( _ts, _ts2, _ts3, _ts4, _ts5, _ts6, _ts7, inputs, map_inputs, cs_inputs, treemap_inputs, funnel_inputs, clustergram_inputs, pareto_inputs, ): return main_inputs_and_group_val_display( dict_merge( inputs, map_inputs, cs_inputs, treemap_inputs, funnel_inputs, clustergram_inputs, pareto_inputs, ))
def build_show_options(options=None): defaults = dict( host=None, port=None, debug=False, subprocess=True, reaper_on=True, open_browser=False, notebook=False, force=False, ignore_duplicate=False, app_root=None, allow_cell_edits=True, inplace=False, drop_index=False, precision=2, ) config_options = {} config = get_config() if config and config.has_section("show"): config_options["host"] = get_config_val(config, defaults, "host") config_options["port"] = get_config_val(config, defaults, "port") config_options["debug"] = get_config_val( config, defaults, "debug", "getboolean" ) config_options["subprocess"] = get_config_val( config, defaults, "subprocess", "getboolean" ) config_options["reaper_on"] = get_config_val( config, defaults, "reaper_on", "getboolean" ) config_options["open_browser"] = get_config_val( config, defaults, "open_browser", "getboolean" ) config_options["notebook"] = get_config_val( config, defaults, "notebook", "getboolean" ) config_options["force"] = get_config_val( config, defaults, "force", "getboolean" ) config_options["ignore_duplicate"] = get_config_val( config, defaults, "ignore_duplicate", "getboolean" ) config_options["app_root"] = get_config_val(config, defaults, "app_root") config_options["allow_cell_edits"] = get_config_val( config, defaults, "allow_cell_edits", "getboolean" ) config_options["inplace"] = get_config_val( config, defaults, "inplace", "getboolean" ) config_options["drop_index"] = get_config_val( config, defaults, "drop_index", "getboolean" ) config_options["precision"] = get_config_val( config, defaults, "precision", "getint" ) return dict_merge(defaults, config_options, options)
def _format_dtype(col_index, col): dtype = dtypes[col] dtype_data = dict(name=col, dtype=dtype, index=col_index) if classify_type(dtype) == 'F' and not data[col].isnull().all(): # floats col_ranges = ranges[col] if not any((np.isnan(v) or np.isinf(v) for v in col_ranges.values())): dtype_data = dict_merge(ranges[col], dtype_data) return dtype_data
def graph_wrapper(modal=False, export=False, **kwargs): global CHART_IDX curr_style = kwargs.pop("style", None) or {} if modal or export: id = "{}-chart".format("modal" if modal else "export") return dcc.Graph(id=id, style=dict_merge({"height": "100%"}, curr_style), **kwargs) CHART_IDX += 1 graph = dcc.Graph(id="chart-{}".format(CHART_IDX), style=dict_merge({"height": "100%"}, curr_style), **kwargs) graph.figure["id"] = "chart-figure-{}".format(CHART_IDX) click_data_store = dcc.Store(id="chart-click-data-{}".format(CHART_IDX)) return [graph, click_data_store, build_drilldown_modal(CHART_IDX)]
def on_data(_ts1, _ts2, _ts3, pathname, inputs, chart_inputs, yaxis_data, last_chart_inputs): """ dash callback controlling the building of dash charts """ all_inputs = dict_merge(inputs, chart_inputs, dict(yaxis=yaxis_data or {})) if all_inputs == last_chart_inputs: raise PreventUpdate charts, range_data = build_chart(get_data_id(pathname), **all_inputs) return charts, all_inputs, range_data
def combine_inputs(dash_app, inputs, chart_inputs={}, yaxis_data={}, map_data={}): """ Combines all managed state (inputs, chart inputs, map inputs & yaxis inputs) within Dash into one dictionary. """ all_inputs = dict_merge( inputs, chart_inputs, dict(yaxis=yaxis_data or {}), map_data ) if is_app_root_defined(dash_app.server.config.get("APPLICATION_ROOT")): all_inputs["app_root"] = dash_app.server.config["APPLICATION_ROOT"] return all_inputs
def build_tab(label, value, additional_style=None, **kwargs): """ Builds a :dash:`dash_core_components.Tab <dash-core-components/tab>` with standard styling settings. """ base_style = {'borderBottom': '1px solid #d6d6d6', 'padding': '6px'} return dcc.Tab( label=label, value=value, style=dict_merge(base_style, {'fontWeight': 'bold'}, additional_style or {}), disabled_style=dict_merge( base_style, {'fontWeight': 'bold', 'backgroundColor': 'LightGray', 'color': 'black', 'cursor': 'not-allowed'}, additional_style or {} ), selected_style=dict_merge( base_style, {'borderTop': '1px solid #d6d6d6', 'backgroundColor': '#2a91d1', 'color': 'white'}, additional_style or {} ), **kwargs)
def build_layout(cfg): """ Wrapper function for :plotly:`plotly.graph_objects.Layout <plotly.graph_objects.Layout>` :param cfg: layout configuration :type cfg: dict :return: layout object :rtype: :plotly:`plotly.graph_objects.Layout <plotly.graph_objects.Layout>` """ return go.Layout(**dict_merge(dict(legend=dict(orientation='h', y=1.2)), cfg))
def layout(axes): if z is not None: return { 'height': 700, 'margin': { 'l': 0, 'r': 0, 'b': 0 }, 'scene': dict_merge(axes, dict(aspectmode='data')) } return axes
def on_data(_ts1, _ts2, _ts3, _ts4, pathname, inputs, chart_inputs, yaxis_data, map_data, last_chart_inputs): """ dash callback controlling the building of dash charts """ all_inputs = dict_merge(inputs, chart_inputs, dict(yaxis=yaxis_data or {}), map_data) if all_inputs == last_chart_inputs: raise PreventUpdate charts, range_data, code = build_chart(get_data_id(pathname), **all_inputs) return charts, all_inputs, range_data, code, get_yaxis_type_tabs( make_list(inputs.get('y') or []))
def _build_axes(y): axes = {'xaxis': dict(title=update_label_for_freq(x))} positions = [] for i, y2 in enumerate(y, 0): right = i % 2 == 1 axis_ct = int(i / 2) title = update_label_for_freq(y2) if z is None and agg is not None: title = '{} ({})'.format(title, AGGS[agg]) value = dict(title=title) if i == 0: key = 'yaxis' else: key = 'yaxis{}'.format(i + 1) value = dict_merge( value, dict(overlaying='y', side='right' if right else 'left')) value['anchor'] = 'free' if axis_ct > 0 else 'x' if axis_ct > 0: pos = axis_ct / 20.0 value['position'] = (1 - pos) if right else pos positions.append(value['position']) if y2 in axis_inputs and not (axis_inputs[y2]['min'], axis_inputs[y2]['max']) == ( mins[y2], maxs[y2]): value['range'] = [ axis_inputs[y2]['min'], axis_inputs[y2]['max'] ] if classify_type(dtypes.get(y2)) == 'I': value['tickformat'] = '.0f' axes[key] = value if len(positions): if len(positions) == 1: domain = [positions[0] + 0.05, 1] elif len(positions) == 2: domain = sorted(positions) domain = [domain[0] + 0.05, domain[1] - 0.05] else: lower, upper = divide_chunks(sorted(positions), 2) domain = [lower[-1] + 0.05, upper[0] - 0.05] axes['xaxis']['domain'] = domain if classify_type(dtypes.get(x)) == 'I': axes['xaxis']['tickformat'] = '.0f' if z is not None: axes['zaxis'] = dict( title=z if agg is None else '{} ({})'.format(z, AGGS[agg])) if classify_type(dtypes.get(z)) == 'I': axes['zaxis']['tickformat'] = '.0f' return axes
def main_input_class( _ts, _ts2, _ts3, _ts4, _ts5, inputs, map_inputs, cs_inputs, treemap_inputs, funnel_inputs, ): return main_inputs_and_group_val_display( dict_merge(inputs, map_inputs, cs_inputs, treemap_inputs, funnel_inputs))
def _callback(selected_value, selected_label, group, data_id, **kwargs): label_value_data = { "{}_value".format(prop): selected_value, "{}_label".format(prop): selected_label, } if group is not None: label_value_data["{}_group".format(prop)] = group label_value_data = dict_merge(label_value_data, kwargs) df = global_state.get_data(data_id) value_options, label_options = build_label_value_options( df, selected_value=selected_value, selected_label=selected_label, ) return label_value_data, value_options, label_options
def execute(self): from dtale.views import startup data = global_state.get_data(self.data_id) try: df, code = self.checker.remove(data) instance = startup(data=df, **self.checker.startup_kwargs) curr_settings = global_state.get_settings(instance._data_id) global_state.set_settings( instance._data_id, dict_merge(curr_settings, dict(startup_code=code)), ) return instance._data_id except NoDuplicatesException: return self.data_id
def surface_builder(data, x, y, z, axes_builder, wrapper, agg=None): """ Builder function for :plotly:`plotly.graph_objects.Surface <plotly.graph_objects.Surface>` :param data: raw data to be represented within surface chart :type data: dict :param x: column to use for the X-Axis :type x: str :param y: columns to use for the Y-Axes :type y: list of str :param z: column to use for the Z-Axis :type z: str :param axes_builder: function for building axis configurations :type axes_builder: func :param wrapper: wrapper function returned by :meth:`dtale.charts.utils.chart_wrapper` :type wrapper: func :param group: column(s) to use for grouping :type group: list of str or str :param agg: points to a specific function that can be applied to :func: pandas.core.groupby.DataFrameGroupBy. Possible values are: count, first, last mean, median, min, max, std, var, mad, prod, sum :type agg: str :return: surface chart :rtype: :plotly:`plotly.graph_objects.Surface <plotly.graph_objects.Surface>` """ scene = dict(aspectmode='data', camera={'eye': {'x': 2, 'y': 1, 'z': 1.25}}) df = pd.DataFrame({k: v for k, v in data['data']['all'].items() if k in ['x', y[0], z]}) df = df.set_index(['x', y[0]]).unstack(0)[z] x_vals = df.columns y_vals = df.index.values z_data = df.values axes = axes_builder([y[0]]) layout = {'height': 700, 'autosize': True, 'margin': {'l': 0, 'r': 0, 'b': 0}, 'scene': dict_merge(axes, scene)} return [ wrapper(dcc.Graph( id='surface-{}'.format(y2), figure={'data': [ go.Surface(x=x_vals, y=y_vals, z=z_data, opacity=0.8, name='all', colorscale='YlGnBu', colorbar={'title': layout['scene']['zaxis']['title'], 'thickness': 15, 'len': 0.5, 'x': 0.8, 'y': 0.6}) ], 'layout': build_layout(dict_merge(build_title(x, y2, z=z, agg=agg), layout))} )) for y2 in y ]
def on_data( _ts1, _ts2, _ts3, _ts4, _ts5, _ts6, load, inputs, chart_inputs, yaxis_data, map_data, cs_data, treemap_data, last_chart_inputs, auto_load, prev_load_clicks, ): """ dash callback controlling the building of dash charts """ all_inputs = dict_merge( inputs, chart_inputs, dict(yaxis=yaxis_data or {}), map_data, cs_data, treemap_data, ) if not auto_load and load == prev_load_clicks: raise PreventUpdate if all_inputs == last_chart_inputs: raise PreventUpdate if is_app_root_defined(dash_app.server.config.get("APPLICATION_ROOT")): all_inputs["app_root"] = dash_app.server.config["APPLICATION_ROOT"] charts, range_data, code = build_chart(**all_inputs) return ( charts, all_inputs, range_data, "\n".join(make_list(code) + [CHART_EXPORT_CODE]), get_yaxis_type_tabs(make_list(inputs.get("y") or [])), load, dict(display="block" if valid_chart(**all_inputs) else "none"), )
def update_settings(data_id): """ :class:`flask:flask.Flask` route which updates global SETTINGS for current port :param data_id: integer string identifier for a D-Tale process's data :type data_id: str :param settings: JSON string from flask.request.args['settings'] which gets decoded and stored in SETTINGS variable :return: JSON """ try: global SETTINGS curr_settings = SETTINGS.get(data_id, {}) updated_settings = dict_merge(curr_settings, get_json_arg(request, 'settings', {})) SETTINGS[data_id] = updated_settings return jsonify(dict(success=True)) except BaseException as e: return jsonify(dict(error=str(e), traceback=str(traceback.format_exc())))
def save_chart(*args): args = list(args) save_clicks = args.pop(0) current_deletes = [args.pop(0) or 0 for _ in range(MAX_SAVED_CHARTS)] inputs = args.pop(0) chart_inputs = args.pop(0) yaxis_data = args.pop(0) map_data = args.pop(0) cs_data = args.pop(0) treemap_data = args.pop(0) prev_save_clicks = args.pop(0) updated_configs = [args.pop(0) for _ in range(MAX_SAVED_CHARTS)] prev_deletes = [args.pop(0) or 0 for _ in range(MAX_SAVED_CHARTS)] delete_idx = None for i, (curr_delete, prev_delete) in enumerate(zip(current_deletes, prev_deletes)): if curr_delete > prev_delete: delete_idx = i if delete_idx is None: if save_clicks == prev_save_clicks: raise PreventUpdate config = dict_merge( inputs, chart_inputs, dict(yaxis=yaxis_data or {}), map_data, cs_data, treemap_data, ) if not valid_chart(**config): raise PreventUpdate for index, saved_config in enumerate(updated_configs): if saved_config is None: updated_configs[index] = config break else: updated_configs[delete_idx] = None return tuple([save_clicks] + updated_configs + current_deletes)
def update_settings(): """ Flask route which updates global SETTINGS for current port :param port: number string from flask.request.environ['SERVER_PORT'] :param settings: JSON string from flask.request.args['settings'] which gets decoded and stored in SETTINGS variable :return: JSON """ try: global SETTINGS port = get_port() curr_settings = SETTINGS.get(port, {}) updated_settings = dict_merge( curr_settings, json.loads(get_str_arg(request, 'settings', '{}'))) SETTINGS[port] = updated_settings return jsonify(dict(success=True)) except BaseException as e: return jsonify( dict(error=str(e), traceback=str(traceback.format_exc())))
def build_chart(data_id=None, **inputs): """ Factory method that forks off into the different chart building methods (heatmaps are handled separately) - line - bar - scatter - pie - wordcloud - 3D scatter - surface :param data_id: identifier of data to build axis configurations against :type data_id: str :param inputs: Optional keyword arguments containing the following information: - x: column to be used as x-axis of chart - y: column to be used as y-axis of chart - z: column to use for the Z-Axis - agg: points to a specific function that can be applied to :func: pandas.core.groupby.DataFrameGroupBy :return: plotly chart object(s) :rtype: type of (:dash:`dash_core_components.Graph <dash-core-components/graph>`, dict) """ try: if inputs.get('chart_type') == 'heatmap': data = make_timeout_request(threaded_heatmap_builder, kwargs=dict_merge( dict(data_id=data_id), inputs)) data = data.pop() return data, None data = make_timeout_request(threaded_build_figure_data, kwargs=dict_merge(dict(data_id=data_id), inputs)) data = data.pop() if data is None: return None, None if 'error' in data: return build_error(data['error'], data['traceback']), None range_data = dict(min=data['min'], max=data['max']) axis_inputs = inputs.get('yaxis', {}) chart_builder = chart_wrapper(data_id, data, inputs) chart_type, x, y, z, agg = ( inputs.get(p) for p in ['chart_type', 'x', 'y', 'z', 'agg']) z = z if chart_type in ZAXIS_CHARTS else None chart_inputs = { k: v for k, v in inputs.items() if k not in ['chart_type', 'x', 'y', 'z', 'group'] } if chart_type == 'wordcloud': return (chart_builder( dash_components.Wordcloud(id='wc', data=data, y=y, group=inputs.get('group'))), range_data) axes_builder = build_axes(data_id, x, axis_inputs, data['min'], data['max'], z=z, agg=agg) if chart_type == 'scatter': if inputs['cpg']: scatter_charts = flatten_lists([ scatter_builder(data, x, y, axes_builder, chart_builder, group=group, agg=agg) for group in data['data'] ]) else: scatter_charts = scatter_builder(data, x, y, axes_builder, chart_builder, agg=agg) return cpg_chunker(scatter_charts), range_data if chart_type == '3d_scatter': return scatter_builder(data, x, y, axes_builder, chart_builder, z=z, agg=agg), range_data if chart_type == 'surface': return surface_builder(data, x, y, z, axes_builder, chart_builder, agg=agg), range_data if chart_type == 'bar': return bar_builder(data, x, y, axes_builder, chart_builder, **chart_inputs), range_data if chart_type == 'line': return line_builder(data, x, y, axes_builder, chart_builder, **chart_inputs), range_data if chart_type == 'pie': return pie_builder(data, x, y, chart_builder, **chart_inputs), range_data raise NotImplementedError('chart type: {}'.format(chart_type)) except BaseException as e: return build_error(str(e), str(traceback.format_exc())), None
def main_input_class(ts_, ts2_, inputs, map_inputs): return main_inputs_and_group_val_display(dict_merge( inputs, map_inputs))
def test_dict_merge(): assert utils.dict_merge(dict(a=1), dict(b=2)) == dict(a=1, b=2) assert utils.dict_merge(dict(a=1), dict(a=2)) == dict(a=2) assert utils.dict_merge(None, dict(b=2)) == dict(b=2) assert utils.dict_merge(dict(a=1), None) == dict(a=1)
def on_data( _ts1, _ts2, _ts3, _ts4, _ts5, _ts6, _ts7, _ts8, load_clicks, inputs, chart_inputs, yaxis_data, map_data, cs_data, treemap_data, funnel_data, last_chart_inputs, auto_load, prev_load_clicks, ext_aggs, ): """ dash callback controlling the building of dash charts """ all_inputs = dict_merge( inputs, chart_inputs, dict(yaxis=yaxis_data or {}), map_data, cs_data, treemap_data, funnel_data, dict(extended_aggregation=ext_aggs or []) if inputs.get("chart_type") not in NON_EXT_AGGREGATION else {}, ) if not auto_load and load_clicks == prev_load_clicks: raise PreventUpdate if all_inputs == last_chart_inputs: raise PreventUpdate if is_app_root_defined(dash_app.server.config.get("APPLICATION_ROOT")): all_inputs["app_root"] = dash_app.server.config["APPLICATION_ROOT"] charts, range_data, code = build_chart(**all_inputs) agg_disabled = len(ext_aggs) > 0 ext_agg_tt = text("ext_agg_desc") ext_agg_warning = show_style(agg_disabled) if agg_disabled: ext_agg_tt = html.Div([ html.Span(text("ext_agg_desc")), html.Br(), html.Ul([ html.Li( extended_aggregations.build_extended_agg_desc(ext_agg), className="mb-0", ) for ext_agg in ext_aggs ]), ]) final_cols = build_final_cols( make_list(inputs.get("y")), inputs.get("z"), inputs.get("agg"), ext_aggs if inputs.get("chart_type") not in NON_EXT_AGGREGATION else [], ) return ( charts, all_inputs, range_data, "\n".join(make_list(code) + [CHART_EXPORT_CODE]), get_yaxis_type_tabs(final_cols), load_clicks, dict(display="block" if valid_chart(**all_inputs) else "none"), agg_disabled, ext_agg_tt, ext_agg_warning, )