Source code for dtale.dash_application.saved_charts

import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate

from dtale.charts.utils import ANIMATION_CHARTS, ANIMATE_BY_CHARTS, ZAXIS_CHARTS
from dtale.dash_application import dcc, html
from dtale.dash_application.charts import build_chart, valid_chart
from dtale.dash_application.layout.utils import build_hoverable
from dtale.utils import dict_merge, is_app_root_defined, flatten_lists, make_list
from dtale.translations import text

MAX_SAVED_CHARTS = 10
SAVED_CHART_IDS = list(range(1, MAX_SAVED_CHARTS + 1))


[docs]def build_layout(): return flatten_lists( [ [ dcc.Store(id="saved-chart-config-{}".format(i)), dcc.Store(id="prev-saved-chart-config-{}".format(i)), dcc.Store(id="saved-deletes-{}".format(i), data=0), html.Div( [ html.Div( [ html.H3( "{} {}".format(text("Saved Chart"), i), className="col-auto pr-3", ), html.Div( id="saved-chart-header-{}".format(i), className="col pl-0", ), html.Div( dbc.Button( text("Delete"), id="delete-saved-btn-{}".format(i), color="primary", className="delete-chart", ), className="col-auto", ), ], className="row", ), html.Div(id="saved-chart-{}".format(i)), ], id="saved-chart-div-{}".format(i), className="saved-chart-div pt-5", style=dict(display="none"), ), ] for i in SAVED_CHART_IDS ] )
[docs]def build_saved_header(config): chart_type = config["chart_type"] final_data = [ ("Data ID", config["data_id"]), ("Query", config.get("query")), ("Chart Type", chart_type), ] if config.get("agg") not in [None, "raw"]: final_data.append(("Aggregation", config["agg"])) if chart_type == "maps": group_by = config["map_group"] map_type = config.get("map_type") if map_type == "scattergeo": map_props = ["map_type", "lat", "lon", "map_val", "scope", "proj"] elif map_type == "mapbox": map_props = ["map_type", "lat", "lon", "map_val", "mapbox_style"] else: map_props = ["map_type", "loc_mode", "loc", "map_val"] if config.get("loc_mode") == "geojson-id": map_props += ["geojson", "featureidkey"] for prop in map_props: final_data.append((prop, config.get(prop))) elif chart_type == "candlestick": group_by = config["cs_group"] for prop in ["cs_x", "cs_open", "cs_close", "cs_high", "cs_low"]: final_data.append((prop.split("_")[-1], config.get(prop))) elif chart_type == "treemap": group_by = config["treemap_group"] for prop in ["treemap_value", "treemap_label"]: final_data.append((prop.split("_")[-1], config.get(prop))) elif chart_type == "funnel": group_by = config["funnel_group"] for prop in ["funnel_value", "funnel_label"]: final_data.append((prop.split("_")[-1], config.get(prop))) elif chart_type == "clustergram": group_by = config["clustergram_group"] for prop in ["clustergram_value", "clustergram_label"]: final_data.append((prop.split("_")[-1], config.get(prop))) else: group_by = config.get("group") final_data.append(("X-Axis", config["x"])) y = make_list(config["y"]) final_data.append(("Y-Axes" if len(y) > 1 else "Y-Axis", ",".join(y))) if chart_type in ZAXIS_CHARTS: final_data.append(("z", config.get("z"))) if chart_type == "scatter" and config["trendline"]: final_data.append(("Trendline", "\u2714")) if group_by: group_by_cols = make_list(group_by) final_data.append(("Group By", ", ".join(group_by_cols))) group_type = config.get("group_type") final_data.append(("Group Type", group_type)) if group_type == "bins": final_data.append(("Bin Type", config["bin_type"])) final_data.append(("Bins", config["bin_val"])) else: final_data.append( ( "Selected Groups", ", ".join( [ "({})".format(",".join([v[c] for c in group_by_cols])) for v in make_list(config.get("group_val")) ] ), ) ) if config["cpg"]: final_data.append(("Chart Per Group", "\u2714")) if config["cpy"]: final_data.append(("Chart Per Y", "\u2714")) if chart_type in ANIMATION_CHARTS and config["animate"]: final_data.append(("Animate", "\u2714")) if chart_type in ANIMATE_BY_CHARTS and config["animate_by"]: final_data.append(("Animate By", ", ".join(make_list(config["animate_by"])))) return build_hoverable( html.I(className="ico-help-outline"), [ html.B("Chart Configuration"), html.Ul( [ html.Li( [html.B(text(prop)), html.Span(": {}".format(value))], className="mb-0", ) for prop, value in final_data if value is not None ], className="mb-0", ), ], hover_class="saved-chart-config", top="unset", )
[docs]def init_callbacks(dash_app): 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 load_saved_chart(_ts, config, prev_config): if config == prev_config: raise PreventUpdate if config is None: return dict(display="none"), None, None, None if is_app_root_defined(dash_app.server.config.get("APPLICATION_ROOT")): config["app_root"] = dash_app.server.config["APPLICATION_ROOT"] charts, _, _ = build_chart(**config) return dict(display="block"), charts, config, build_saved_header(config) dash_app.callback( [Output("save-clicks", "data")] + [Output("saved-chart-config-{}".format(i), "data") for i in SAVED_CHART_IDS] + [Output("saved-deletes-{}".format(i), "data") for i in SAVED_CHART_IDS], [Input("save-btn", "n_clicks")] + [Input("delete-saved-btn-{}".format(i), "n_clicks") for i in SAVED_CHART_IDS], [ State("input-data", "data"), State("chart-input-data", "data"), State("yaxis-data", "data"), State("map-input-data", "data"), State("candlestick-input-data", "data"), State("treemap-input-data", "data"), State("save-clicks", "data"), ] + [State("saved-chart-config-{}".format(i), "data") for i in SAVED_CHART_IDS] + [State("saved-deletes-{}".format(i), "data") for i in SAVED_CHART_IDS], )(save_chart) for i in SAVED_CHART_IDS: dash_app.callback( [ Output("saved-chart-div-{}".format(i), "style"), Output("saved-chart-{}".format(i), "children"), Output("prev-saved-chart-config-{}".format(i), "data"), Output("saved-chart-header-{}".format(i), "children"), ], [Input("saved-chart-config-{}".format(i), "modified_timestamp")], [ State("saved-chart-config-{}".format(i), "data"), State("prev-saved-chart-config-{}".format(i), "data"), ], )(load_saved_chart)