首页  >  问答  >  正文

在 dash 应用程序中,如何交换不同容器的内容?

我在做以下事情时遇到了很大的困难。我有一个由 css 格式化的破折号应用程序,以显示在不同的容器中。标记为“hexgrid-1-container”的容器是最大的容器,而其他容器较小,并围绕该容器组织。我想更新我的破折号应用程序,以便每当单击另一个容器中的图形/图形时,它都会出现在“hexgrid-1-container”中,而之前位于“hexgrid-1-container”中的图形会出现在较小的容器中容器。

我有一个破折号应用程序,如下所示:

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
from jupyter_dash import JupyterDash
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import sqlite3
import locale
import numpy as np
import plotly.figure_factory as ff
import plotly.express as px

# Set the locale for formatting
locale.setlocale(locale.LC_ALL, '')
app = dash.Dash(__name__)
app.layout = html.Div(className= 'container glass', children=[
    html.Div(className='hexgrid-1-container', style={'border': '1px solid black'}, children=[
        dcc.Graph(id='hexgrid-1', style={"height": "75%", "width": "100%"}, className='hexgrid1')
    ]),

    html.Div(className='hexgrid-2-container', style={'border': '1px solid black'}, children=[
        dcc.Graph(id='hexgrid-2', style={"height": "100%", "width": "100%"}, className='hexgrid2')
    ]),

    html.Div(className='base-plot-container', style={'border': '1px solid black'}, children=[
        dcc.Graph(id='base-plot', style={"height": "100%", "width": "100%"})
    ]),

    html.Div(className='us-map-container', style={'border': '1px solid black'}, children=[
        dcc.Graph(id='us-map-graph', style={"height": "100%", "width": "100%"})
    ]),

    html.Div(className='fifth-container', style={'border': '1px solid black'}, children=[
        # dcc.Graph(id='us-map-graph', style={"height": "100%", "width": "100%"})
        #html.H2("5th Container")
    ]),

    html.Div(className='sixth-container', style={'border': '1px solid black'}, children=[
        # dcc.Graph(id='us-map-graph', style={"height": "100%", "width": "100%"})
        #html.H2("6th Container")
    ])
])


@app.callback(
    Output('hexgrid-1', 'figure'),
    Input('hexgrid-1', 'id')
)

def render_hexgrid_1(_):
    # Open a new database connection
    conn = sqlite3.connect(r"C:\Users\HituJani\Downloads\txns.db")
    
    # Fetching all columns in the transactions table
    query = '''
        PRAGMA table_info(transactions)
    '''
    
    # Fetch data
    data = pd.read_sql_query(query, conn)
    
    # Filtering out the relevant columns, this step can be skipped if you want all columns.
    relevant_columns = data[data['name'].isin(['TranType', 'MessageTypeIdentifier', 'MerchantType', 'IResponseCode'])]
    
    # Creating the hex grid with column names
    plot_data = go.Scatter(x=relevant_columns.index, y=relevant_columns['name'], text=relevant_columns['name'],
                           marker=dict(symbol='hexagon', size=30), mode='markers+text')
    layout = go.Layout(title="Select a Category")
    return go.Figure(data=[plot_data], layout=layout)
@app.callback(
    Output('hexgrid-2', 'figure'),
    Output('hexgrid-2', 'style'),
    Input('hexgrid-1', 'clickData')
)
def render_hexgrid_2(clickData):
    # Open a new database connection
    conn = sqlite3.connect(r"C:\Users\HituJani\Downloads\txns.db")
    
    if clickData is None:
        return go.Figure(), {"display": "none"}
    else:
        category = clickData['points'][0]['text']
        
        # Fetching all columns in the transactions table
        query = '''
            PRAGMA table_info(transactions)
        '''
        
        # Fetch data
        data = pd.read_sql_query(query, conn)
        
        # Filtering out the relevant numerical features columns
        relevant_columns = data[data['name'].isin(['TransactionAmount', 'OutstandingAmount', 'CurrentBalance', 'TotalOutStgAuthAmt'])]
        
        # Generating random points on the surface of a sphere
        phi = np.random.uniform(0, np.pi, len(relevant_columns))
        theta = np.random.uniform(0, 2*np.pi, len(relevant_columns))
        
        x = np.cos(theta) * np.sin(phi)
        y = np.sin(theta) * np.sin(phi)
        z = np.cos(phi)
        
        # Create a 3D scatter plot with sphere markers
        scatter = go.Scatter3d(
            x=x,
            y=y,
            z=z,
            mode='markers+text',
            marker=dict(
                size=12,
                color=np.arange(len(relevant_columns)),
                colorscale='Viridis',
                symbol='circle',
                opacity=0.8
            ),
            text=relevant_columns['name'],
            hoverinfo='text'
        )
        
        # Create a wireframe sphere using a mesh
        u = np.linspace(0, 2 * np.pi, 100)
        v = np.linspace(0, np.pi, 100)
        x_sphere = np.outer(np.cos(u), np.sin(v))
        y_sphere = np.outer(np.sin(u), np.sin(v))
        z_sphere = np.outer(np.ones(np.size(u)), np.cos(v))
        
        sphere = go.Mesh3d(x=x_sphere.ravel(),
                           y=y_sphere.ravel(),
                           z=z_sphere.ravel(),
                           opacity=0.1,
                           color='cyan')
        
        # Combine the scatter plot and wireframe into a single figure
        fig = go.Figure(data=[scatter, sphere])
        
        fig.update_layout(
            margin=dict(l=0, r=0, b=0, t=0),
            scene=dict(
                xaxis=dict(title=None, visible=False),
                yaxis=dict(title=None, visible=False),
                zaxis=dict(title=None, visible=False),
            ),
            template='plotly_dark'
        )
        
        return fig, {"height": "50vh", "width": "100%", "display": "inline-block"}
# Define the callback function that will update the plot, highlight the sphere and enable drilling down
@app.callback(
    Output('base-plot', 'figure'),
    Input('hexgrid-2', 'clickData'),
    State('hexgrid-1', 'clickData')
)
def update_base_plot(clickData_hexgrid_2, clickData_hexgrid_1):
    try:
        # Open a new database connection
        conn = sqlite3.connect(r"C:\Users\HituJani\Downloads\txns.db")

        category = clickData_hexgrid_1['points'][0]['text']
        numerical_feature = clickData_hexgrid_2['points'][0]['text']

        # SQL query to retrieve aggregated data based on selected category and numerical feature
        query = f'''
            SELECT {category}, WeekOfMonth, SUM({numerical_feature}) AS TotalAmount
            FROM transactions
            GROUP BY {category}, WeekOfMonth
            ORDER BY TotalAmount DESC
        '''

        # Fetch the data from the database
        df_base = pd.read_sql(query, conn)

        # Close the database connection
        conn.close()

        # Formatting the Total column
        df_base['TotalAmount'] = df_base['TotalAmount'].apply(lambda x: locale.currency(x, grouping=True))

        # Creating 3D scatter plot with sphere markers
        fig = go.Figure()

        # Define colorscale for WeekOfMonth values
        colorscale = [
            [0, 'blue'],        # Week 1: Blue
            [0.25, 'purple'],   # Week 2: Purple
            [0.5, 'darkorange'],   # Week 3: Dark Orange
            [0.75, 'yellow'],   # Week 4: Yellow
            [1, 'pink']         # Week 5: Pink
        ]

        # Add the scatter plot trace
        fig.add_trace(
            go.Scatter3d(
                x=df_base[category],
                y=df_base['WeekOfMonth'].astype(int),
                z=df_base['TotalAmount'],
                mode='markers+text',
                marker=dict(
                    size=5,  # Adjust the size of markers to make them smaller
                    symbol='circle',  # Use 'circle' symbol for spheres
                    color=df_base['WeekOfMonth'],
                    colorscale=colorscale,
                ),
                textposition='top center',
                hovertemplate=(
                    f"<b>{category}</b>: %{{x}}<br>" +
                    "<b>Total Amount</b>: %{z}<br>" +
                    "<b>WeekOfMonth</b>: %{y}<br>"
                )
            )
        )

        # Dynamically set y-axis range
        y_max = df_base['WeekOfMonth'].max()

        fig.update_layout(
            scene=dict(
                xaxis=dict(
                    title=category,
                    title_font=dict(size=14, color='darkorange'),
                    visible=False  # Hide the x-axis
                ),
                yaxis=dict(
                    title='Week of Month',
                    title_font=dict(size=14, color='purple'),
                    tickmode='array',
                    tickvals=[0.5, 1.5, 2.5, 3.5, 4.5, 5.5],
                    ticktext=['1', '2', '3', '4', '5'],  # Display tick labels as 1, 2, 3, 4, 5],
                    visible=False  # Hide the y-axis
                ),
                zaxis=dict(
                    title=f'Total {numerical_feature} ($)',
                    title_font=dict(size=14, color='yellow'),
                    autorange='reversed',
                    visible=False  # Hide the z-axis
                ),
            ),
            xaxis=dict(
                type='category',
                tickmode='linear',
                tickangle=45,
                automargin=True,
                visible=False  # Hide the x-axis labels
            ),
            margin=dict(l=10, r=10, t=10, b=10),  # Increase the b value to enlarge the viewing window
            template='plotly_dark',
        )

        return fig

    except Exception as e:
        print(f"Error: {e}")
        return go.Figure()


@app.callback(
    Output('us-map-graph', 'figure'),
    Output('us-map-graph', 'style'),
    Input('base-plot', 'clickData'),
    State('hexgrid-1', 'clickData'),
    State('hexgrid-2', 'clickData')
)
def display_transaction_amount(base_plot_click_data, hexgrid_1_clickData, hexgrid_2_clickData):
    try:
        # Check if data from hexgrid-1 and hexgrid-2 is available
        if hexgrid_1_clickData is None or hexgrid_2_clickData is None:
            # Return an empty figure and hide the map
            return go.Figure(), {"display": "none"}

        # Get the selected category from hexgrid-1 and numerical feature from hexgrid-2
        selected_category = hexgrid_1_clickData['points'][0]['text']
        numerical_feature = hexgrid_2_clickData['points'][0]['text']

        # Get the selected subcategory from base_plot_click_data
        selected_subcategory = None
        if base_plot_click_data is not None:
            selected_subcategory = base_plot_click_data['points'][0]['x']

        # Open a new database connection
        conn = sqlite3.connect(r"C:\Users\HituJani\Downloads\txns.db")

        # SQL query to retrieve transaction data by state for the selected category, subcategory, and numerical feature
        query = f'''
            SELECT StateCode, {selected_category}, SUM({numerical_feature}) AS TotalTransactionAmount
            FROM transactions
            WHERE {selected_category} = ?
            GROUP BY StateCode, {selected_category}
        '''

        # Execute the query and fetch the results into a DataFrame
        state_data = pd.read_sql(query, conn, params=(selected_subcategory,))

        # Close the database connection
        conn.close()

        # Create a Choropleth map using the filtered data
        fig = px.choropleth(
            data_frame=state_data,
            locationmode='USA-states',
            locations='StateCode',
            scope='usa',
            color='TotalTransactionAmount',
            hover_data={'StateCode': True, 'TotalTransactionAmount': ':$,f'},
            color_continuous_scale='Reds',
            labels={'TotalTransactionAmount': 'Total Transaction Amount'},
            template='plotly_dark'
        )

        fig.update_traces(
            hovertemplate="<b>%{customdata[0]}</b><br>" +
                          "<b>TotalTransactionAmount</b>: $%{customdata[1]:,.2f}<br>",
            customdata=list(zip(state_data['StateCode'], state_data['TotalTransactionAmount']))
        )

        fig.update_layout(
            title_text=f'Total Transaction Amount by State for Category: {selected_category}, Subcategory: {selected_subcategory}',
            title_xanchor='center',
            title_font=dict(size=12),
            title_x=0.5,
            geo=dict(scope='usa'),
        )

        # Return the figure and set the display style to block (visible)
        return fig, {"display": "block"}

    except Exception as e:
        print(f"Error: {e}")
        return go.Figure(), {"display": "none"}
if __name__ == '__main__':
    app.run_server(debug=True, use_reloader=False)

此外,我还有这个格式化网页的css文件:

/* styles.css */

.container {
  display: grid;
  grid-template-rows: 1fr 1fr 1fr;
  grid-template-columns: 1fr 1fr 1fr;
  grid-column-gap: 15px;
  grid-row-gap: 15px;
  background-image: url("https://plainbackground.com/plain1024/383c3d.png");
  background-size: 100%;
  background-position: grid-ce;
}

.hexgrid-1-container {
  /* grid-area: 1/1/3/3; */
  grid-row: span 2;
  grid-column: span 2;
  padding: 2px 2px 2px 2px;
  border: 1px solid hsl(176, 87%, 7%, 0.6);
  border-radius: 10px;
  box-shadow: rgba(250, 118, 3, 0.4) -5px 5px, rgba(250, 118, 3, 0.3) -10px 10px, rgba(250, 118, 3, 0.2) -15px 15px;
  background: hsla(0, 7%, 11%, 0.9);
  position: relative;
}

.hexgrid-2-container {
  grid-area: 1/3/2/4;
  padding: 3rem 4rem 4rem;
  width: 70%;
  border: 1px solid hsl(176, 87%, 7%, 0.6);
  border-radius: 10px;
  box-shadow: rgba(250, 118, 3, 0.4) -5px 5px, rgba(250, 118, 3, 0.3) -10px 10px, rgba(250, 118, 3, 0.2) -15px 15px;
  background: hsl(0, 7%, 11%, 0.9);
  position: relative;
}

.base-plot-container {
  /* Add your custom styles for base-plot container here */
  grid-area: 2/3/3/4;
  padding: 3rem 4rem 4rem;
  width: 70%;
  border: 1px solid hsl(176, 87%, 7%, 0.6);
  border-radius: 10px;
  box-shadow: rgba(250, 118, 3, 0.4) -5px 5px, rgba(250, 118, 3, 0.3) -10px 10px, rgba(250, 118, 3, 0.2) -15px 15px;
  background: hsl(0, 7%, 11%, 0.9);
  position: relative;
}

.us-map-container {
  grid-area: 3/3/4/4;
  padding: 3rem 4rem 4rem;
  width: 70%;
  /* position: fixed; */
  border: 1px solid hsl(176, 87%, 7%, 0.6);
  border-radius: 10px;
  box-shadow: rgba(250, 118, 3, 0.4) -5px 5px, rgba(250, 118, 3, 0.3) -10px 10px, rgba(250, 118, 3, 0.2) -15px 15px;
  background: hsl(0, 7%, 11%, 0.9);
  position: relative;
}

.fifth-container {
  grid-area: 3/2/4/3;
  border: 1px solid hsl(176, 87%, 7%, 0.6);
  border-radius: 10px;
  box-shadow: rgba(250, 118, 3, 0.4) -5px 5px, rgba(250, 118, 3, 0.3) -10px 10px, rgba(250, 118, 3, 0.2) -15px 15px;
  background: hsl(0, 7%, 11%, 0.9);
  position: relative;
}

.sixth-container {
  grid-area: 3/1/4/2;
  border: 1px solid hsl(176, 87%, 7%, 0.6);
  border-radius: 10px;
  box-shadow: rgba(250, 118, 3, 0.4) -5px 5px, rgba(250, 118, 3, 0.3) -10px 10px, rgba(250, 118, 3, 0.2) -15px 15px;
  background: hsl(0, 7%, 11%, 0.9);
  position: relative;
}

我尝试更新回调函数,但进展不大,而且似乎我做错了很多事情。实现此功能的最直接方法是什么?预先感谢您。

P粉378890106P粉378890106254 天前549

全部回复(1)我来回复

  • P粉252423906

    P粉2524239062024-02-27 15:34:56

    您可以进行回调来侦听单击事件,交换图形位置,然后将图形返回到更新的位置。您需要根据您的用例设置点击逻辑。你可以实现这样的东西:

    @app.callback(
        Output('hexgrid-1-container', 'children'),
        Output('hexgrid-2-container', 'children'),
        Input('hexgrid-2', 'clickData'),
        State('hexgrid-1-container', 'children'),
        State('hexgrid-2-container', 'children')
    )
    def swap_graphs(clickData, hexgrid_1_children, hexgrid_2_children):
        clicked_graph_id = 1  # Assuming hexgrid-2 was clicked, you can change this logic based on your use case
    
        if clicked_graph_id == 1:
            # Swap graph between hexgrid-1 and hexgrid-2
            return hexgrid_2_children, hexgrid_1_children
    
        return hexgrid_1_children, hexgrid_2_children

    回复
    0
  • 取消回复