Source code for visualization_toolkit.helpers.dash.export

from tempfile import NamedTemporaryFile

import pandas as pd
from dash import Input, Output, State, MATCH, Dash, dcc, no_update

import yipit_dash_mui_components as ymc
from dash_iconify import DashIconify

from visualization_toolkit.helpers.dash.core import get_triggered_id
from visualization_toolkit.helpers.plotly.charts.core.figure import (
    generate_pdf_from_figure,
)


class DownloadComponentTypes:
    CHART_DOWNLOAD = "chart-download"
    CHART_DOWNLOAD_ICON = "chart-download-icon"
    CHART_DOWNLOAD_MENU = "chart-download-options"
    CSV_FILE = "csv-file"
    XLSX_FILE = "xlsx-file"
    DOWNLOAD_BUFFER = "download-buffer"
    CHART = "chart"


[docs] def export_chart_button_component(index: str): """ Adds a button + dropdown to download a given chart's data. This function is intended to be used with the ``chart_component`` function. Export buttons should be placed in the top-right section of a ``ymc.ChartCardHeader``. Typically inside the ``ymc.ChartCartControls`` :param index: Index to set for the subcomponents of the chart button. This should match the index passed into the corresponding ``chart_component``. :return: Examples ^^^^^^^^^^^^^ .. code-block:: python :caption: Example of adding a chart component to a chart card with an export button in the chart controls. from visualization_toolkit.helpers.dash import setup_chart_exports, ymc app = Dash(__name__) # Create a dash app with a chart and an export chart button app.layout = [ ymc.YipitMUIProvider( children=ymc.ChartCard([ ymc.ChartCardHeader([ ymc.ChartCartControls([ export_chart_component(index="example-chart"), ]), ]), chart_component(index="example-chart"), ]), ) ] # enable export functionality via callbacks setup_chart_exports(app) """ download_container_id = f"{index}-download-container" return ymc.Box( id=download_container_id, sx={"align-self": "center"}, children=[ # Export Button ymc.Button( id={"type": DownloadComponentTypes.CHART_DOWNLOAD, "index": index}, variant="text", children=[ DashIconify( id={ "type": DownloadComponentTypes.CHART_DOWNLOAD_ICON, "index": index, }, icon="material-symbols:download-2", style={ "height": "1.4375rem", "width": "1.4375rem", "color": "#9E9E9E", "margin": "auto 0", }, ), ], ), # Export options ymc.Menu( id={"type": DownloadComponentTypes.CHART_DOWNLOAD_MENU, "index": index}, anchorEl={"id": download_container_id}, open=False, children=[ ymc.MenuItem( "Export as CSV", {"type": DownloadComponentTypes.CSV_FILE, "index": index}, ), ymc.MenuItem( "Export as XLSX", {"type": DownloadComponentTypes.XLSX_FILE, "index": index}, ), ], ), # Download File integration with Dash dcc.Download( id={"type": DownloadComponentTypes.DOWNLOAD_BUFFER, "index": index} ), ], )
[docs] def setup_chart_exports(app: Dash): """ Sets up file exports for each chart in a dash app that is using the standard ``chart_component``. In addition, the dash app must include the ``export_chart_button`` component for each chart that needs to support exports. A dropdown will then appear with options to export a chart data as CSV or XLSX files. :param app: :return: Examples ^^^^^^^^^^^^^ .. code-block:: python :caption: Example of enabling chart downloads for a dash page with a given chart and export button. from visualization_toolkit.helpers.dash import setup_chart_exports, ymc app = Dash(__name__) # Create a dash app with a chart and an export chart button app.layout = [ ymc.YipitMUIProvider( children=ymc.Box([ chart_component(index="example-chart"), export_chart_component(index="example-chart"), ]), ) ] # enable export functionality via callbacks setup_chart_exports(app) """ @app.callback( Output( {"type": DownloadComponentTypes.CHART_DOWNLOAD_MENU, "index": MATCH}, "open" ), Input( {"type": DownloadComponentTypes.CHART_DOWNLOAD, "index": MATCH}, "n_clicks" ), Input({"type": DownloadComponentTypes.CSV_FILE, "index": MATCH}, "n_clicks"), Input({"type": DownloadComponentTypes.XLSX_FILE, "index": MATCH}, "n_clicks"), State( {"type": DownloadComponentTypes.CHART_DOWNLOAD_MENU, "index": MATCH}, "open" ), prevent_initial_call=True, ) def toggle_export( dropdown_clicks, csv_clicks, xlsx_clicks, is_open, ): parsed_id = get_triggered_id() if parsed_id["type"] == DownloadComponentTypes.CHART_DOWNLOAD: if not is_open: return True return False return False @app.callback( Output( {"type": DownloadComponentTypes.DOWNLOAD_BUFFER, "index": MATCH}, "data" ), Input({"type": DownloadComponentTypes.CSV_FILE, "index": MATCH}, "n_clicks"), Input({"type": DownloadComponentTypes.XLSX_FILE, "index": MATCH}, "n_clicks"), State({"type": DownloadComponentTypes.CHART, "index": MATCH}, "figure"), prevent_initial_call=True, ) def process_export( csv_clicks, xlsx_clicks, figure, ): parsed_id = get_triggered_id() pdf = generate_pdf_from_figure(figure) match parsed_id["type"]: case DownloadComponentTypes.CSV_FILE: with NamedTemporaryFile("wb") as f: pdf.to_csv(f.name, index=False) f.seek(0) return dcc.send_file(f.name, filename=f"export.csv") case DownloadComponentTypes.XLSX_FILE: with NamedTemporaryFile("wb") as f: with pd.ExcelWriter(f.name, engine="xlsxwriter") as writer: pdf.to_excel(writer, sheet_name="export", index=False) f.seek(0) return dcc.send_file(f.name, filename="export.xlsx") return no_update