DEV Community

Armaan Khan
Armaan Khan

Posted on

Function to creaes better charts

import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import streamlit as st
import numpy as np
from typing import Optional, List, Union
import warnings
warnings.filterwarnings('ignore')

Color palettes for professional look

ENTERPRISE_COLORS = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']
STATUS_COLORS = {'Active': '#28a745', 'Inactive': '#dc3545', 'Pending': '#ffc107', 'Completed': '#17a2b8', 'In Progress': '#6f42c1'}
GRADIENT_COLORS = ['#667eea', '#764ba2', '#f093fb', '#f5576c', '#4facfe', '#00f2fe']

def pie_chart_single_row(df: pd.DataFrame, columns: Optional[List[str]] = None, title: str = "Data Distribution") -> go.Figure:
"""
Creates a beautiful pie chart from a single row DataFrame with equal slice sizes.

Parameters:
df (pd.DataFrame): DataFrame with typically one row
columns (list, optional): List of column names to include. If None, uses all columns
title (str): Title for the pie chart

Returns:
plotly.graph_objects.Figure: Professional pie chart figure
"""
try:
    if df.empty:
        st.error("DataFrame is empty!")
        return go.Figure()

    # Use first row of data
    row_data = df.iloc[0]

    # Select columns
    if columns is None:
        columns = [col for col in df.columns if pd.notna(row_data[col])]
    else:
        columns = [col for col in columns if col in df.columns and pd.notna(row_data[col])]

    if not columns:
        st.warning("No valid columns found!")
        return go.Figure()

    # Filter data for selected columns
    filtered_data = row_data[columns]

    # Create labels and values (equal sizes)
    labels = []
    display_values = []
    for col, val in filtered_data.items():
        if isinstance(val, (int, float)):
            if val >= 1000000:
                display_val = f"{val/1000000:.1f}M"
            elif val >= 1000:
                display_val = f"{val/1000:.1f}K"
            else:
                display_val = f"{val:,.0f}" if val == int(val) else f"{val:.2f}"
        else:
            display_val = str(val)
        labels.append(f"<b>{col}</b><br>{display_val}")
        display_values.append(display_val)

    values = [1] * len(filtered_data)  # Equal slice sizes

    # Create pie chart
    fig = go.Figure(data=[go.Pie(
        labels=labels,
        values=values,
        hole=0.4,  # Donut style
        textinfo='label',
        textposition='outside',
        textfont=dict(size=12, family='Arial', color='#2c3e50'),
        marker=dict(
            colors=ENTERPRISE_COLORS[:len(labels)],
            line=dict(color='#ffffff', width=3)
        ),
        hovertemplate='<b>%{label}</b><extra></extra>',
        showlegend=False
    )])

    fig.update_layout(
        title={
            'text': f"<b style='color:#2c3e50; font-size:24px'>{title}</b>",
            'x': 0.5,
            'xanchor': 'center',
            'y': 0.95
        },
        font=dict(size=14, family='Arial'),
        paper_bgcolor='rgba(0,0,0,0)',
        plot_bgcolor='rgba(0,0,0,0)',
        height=500,
        margin=dict(t=100, b=50, l=50, r=50),
        annotations=[
            dict(
                text=f"<b style='color:#34495e; font-size:16px'>Total Items<br><span style='font-size:20px; color:#3498db'>{len(labels)}</span></b>",
                x=0.5, y=0.5, font_size=16, showarrow=False
            )
        ]
    )

    return fig

except Exception as e:
    st.error(f"Error creating pie chart: {str(e)}")
    return go.Figure()
Enter fullscreen mode Exit fullscreen mode

def pie_chart_column_values(df: pd.DataFrame, column_name: str, title: Optional[str] = None) -> go.Figure:
"""
Creates a pie chart based on unique values and their counts in a specific column.

Parameters:
df (pd.DataFrame): Input DataFrame
column_name (str): Name of the column to analyze
title (str, optional): Title for the pie chart

Returns:
plotly.graph_objects.Figure: Professional pie chart figure
"""
try:
    if df.empty:
        st.error("DataFrame is empty!")
        return go.Figure()

    if column_name not in df.columns:
        st.error(f"Column '{column_name}' not found in DataFrame!")
        return go.Figure()

    # Remove NaN values
    clean_data = df[column_name].dropna()
    if clean_data.empty:
        st.warning(f"No valid data found in column '{column_name}'!")
        return go.Figure()

    # Get value counts
    value_counts = clean_data.value_counts()

    if title is None:
        title = f"Distribution of {column_name}"

    # Create pie chart
    fig = go.Figure(data=[go.Pie(
        labels=value_counts.index,
        values=value_counts.values,
        hole=0.4,
        textinfo='label+percent',
        textposition='auto',
        textfont=dict(size=12, family='Arial', color='white', weight='bold'),
        marker=dict(
            colors=ENTERPRISE_COLORS[:len(value_counts)],
            line=dict(color='#ffffff', width=2)
        ),
        hovertemplate='<b>%{label}</b><br>Count: %{value}<br>Percentage: %{percent}<extra></extra>'
    )])

    fig.update_layout(
        title={
            'text': f"<b style='color:#2c3e50; font-size:22px'>{title}</b>",
            'x': 0.5,
            'xanchor': 'center',
            'y': 0.95
        },
        font=dict(size=14, family='Arial'),
        showlegend=True,
        legend=dict(
            orientation="v",
            yanchor="middle",
            y=0.5,
            xanchor="left",
            x=1.02,
            font=dict(size=12, color='#2c3e50')
        ),
        paper_bgcolor='rgba(0,0,0,0)',
        plot_bgcolor='rgba(0,0,0,0)',
        height=600,
        margin=dict(t=100, b=50, l=50, r=200),
        annotations=[
            dict(
                text=f"<b style='color:#34495e; font-size:14px'>Total Records<br><span style='font-size:18px; color:#e74c3c'>{len(clean_data):,}</span></b>",
                x=0.5, y=0.5, font_size=14, showarrow=False
            )
        ]
    )

    return fig

except Exception as e:
    st.error(f"Error creating column values pie chart: {str(e)}")
    return go.Figure()
Enter fullscreen mode Exit fullscreen mode

def create_kpi_cards(df: pd.DataFrame, columns: Optional[List[str]] = None, cards_per_row: int = 4, title: str = "KPI Dashboard") -> None:
"""
Creates beautiful KPI/metric cards using Streamlit's native metric component.

Parameters:
df (pd.DataFrame): Input DataFrame
columns (list, optional): List of column names to include. If None, uses all columns
cards_per_row (int): Number of cards per row (default: 4)
title (str): Title for the dashboard
"""
try:
    if df.empty:
        st.error("DataFrame is empty!")
        return

    # Use first row of data
    row_data = df.iloc[0]

    # Select columns
    if columns is None:
        columns = [col for col in df.columns if pd.notna(row_data[col])]
    else:
        columns = [col for col in columns if col in df.columns and pd.notna(row_data[col])]

    if not columns:
        st.warning("No valid columns found!")
        return

    # Display title
    st.markdown(f"<h2 style='text-align: center; color: #2c3e50; margin-bottom: 30px;'>{title}</h2>", unsafe_allow_html=True)

    # Create rows of cards
    for i in range(0, len(columns), cards_per_row):
        cols = st.columns(cards_per_row)

        for j, col_name in enumerate(columns[i:i+cards_per_row]):
            if j < len(cols):
                with cols[j]:
                    value = row_data[col_name]

                    # Format value based on type
                    if isinstance(value, (int, float)):
                        if value >= 1000000:
                            display_val = f"{value/1000000:.1f}M"
                            delta_color = "normal"
                        elif value >= 1000:
                            display_val = f"{value/1000:.1f}K"
                            delta_color = "normal"
                        else:
                            display_val = f"{value:,.0f}" if value == int(value) else f"{value:.2f}"
                            delta_color = "normal"

                        # Add some styling with custom CSS
                        st.markdown(
                            f"""
                            <div style="
                                background: linear-gradient(135deg, {GRADIENT_COLORS[j % len(GRADIENT_COLORS)]}, {GRADIENT_COLORS[(j+1) % len(GRADIENT_COLORS)]});
                                padding: 20px;
                                border-radius: 15px;
                                box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
                                margin-bottom: 20px;
                                text-align: center;
                            ">
                                <h4 style="color: white; margin: 0; font-size: 14px; font-weight: 600;">{col_name}</h4>
                                <h2 style="color: white; margin: 10px 0 0 0; font-size: 32px; font-weight: bold;">{display_val}</h2>
                            </div>
                            """,
                            unsafe_allow_html=True
                        )
                    else:
                        # For non-numeric values
                        st.markdown(
                            f"""
                            <div style="
                                background: linear-gradient(135deg, {GRADIENT_COLORS[j % len(GRADIENT_COLORS)]}, {GRADIENT_COLORS[(j+1) % len(GRADIENT_COLORS)]});
                                padding: 20px;
                                border-radius: 15px;
                                box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
                                margin-bottom: 20px;
                                text-align: center;
                            ">
                                <h4 style="color: white; margin: 0; font-size: 14px; font-weight: 600;">{col_name}</h4>
                                <h3 style="color: white; margin: 10px 0 0 0; font-size: 20px; font-weight: bold;">{str(value)}</h3>
                            </div>
                            """,
                            unsafe_allow_html=True
                        )

except Exception as e:
    st.error(f"Error creating KPI cards: {str(e)}")
Enter fullscreen mode Exit fullscreen mode

def cause_solution_chart(df: pd.DataFrame, cause_col: str, solution_col: str, title: str = "Cause & Solution Analysis") -> go.Figure:
"""
Creates a beautiful visual showing cause and solution relationships.

Parameters:
df (pd.DataFrame): Input DataFrame
cause_col (str): Column name for causes/problems
solution_col (str): Column name for solutions
title (str): Title for the chart

Returns:
plotly.graph_objects.Figure: Professional cause-solution chart
"""
try:
    if df.empty:
        st.error("DataFrame is empty!")
        return go.Figure()

    if cause_col not in df.columns or solution_col not in df.columns:
        st.error(f"Columns '{cause_col}' or '{solution_col}' not found!")
        return go.Figure()

    # Clean data
    clean_df = df[[cause_col, solution_col]].dropna()
    if clean_df.empty:
        st.warning("No valid cause-solution pairs found!")
        return go.Figure()

    # Create cause-solution pairs
    pairs = clean_df.groupby([cause_col, solution_col]).size().reset_index(name='count')

    # Create Sankey diagram
    causes = pairs[cause_col].unique()
    solutions = pairs[solution_col].unique()

    # Create labels and indices
    all_labels = list(causes) + list(solutions)
    cause_indices = {cause: i for i, cause in enumerate(causes)}
    solution_indices = {solution: i + len(causes) for i, solution in enumerate(solutions)}

    # Create links
    source = []
    target = []
    values = []

    for _, row in pairs.iterrows():
        source.append(cause_indices[row[cause_col]])
        target.append(solution_indices[row[solution_col]])
        values.append(row['count'])

    # Create Sankey diagram
    fig = go.Figure(data=[go.Sankey(
        node=dict(
            pad=15,
            thickness=20,
            line=dict(color="black", width=0.5),
            label=all_labels,
            color=[f"rgba{tuple(list(np.random.choice(range(50, 200), size=3)) + [0.8])}" for _ in all_labels]
        ),
        link=dict(
            source=source,
            target=target,
            value=values,
            color=[f"rgba{tuple(list(np.random.choice(range(100, 200), size=3)) + [0.4])}" for _ in values]
        )
    )])

    fig.update_layout(
        title={
            'text': f"<b style='color:#2c3e50; font-size:24px'>{title}</b>",
            'x': 0.5,
            'xanchor': 'center'
        },
        font=dict(size=12, family='Arial', color='#2c3e50'),
        paper_bgcolor='rgba(0,0,0,0)',
        height=600,
        margin=dict(t=80, b=50, l=50, r=50)
    )

    return fig

except Exception as e:
    st.error(f"Error creating cause-solution chart: {str(e)}")
    return go.Figure()
Enter fullscreen mode Exit fullscreen mode

def user_status_chart(df: pd.DataFrame, user_col: str, status_col: str, title: str = "User Status Overview") -> go.Figure:
"""
Creates a beautiful chart showing users and their status with color coding.

Parameters:
df (pd.DataFrame): Input DataFrame
user_col (str): Column name for users/distinct values
status_col (str): Column name for status/categories
title (str): Title for the chart

Returns:
plotly.graph_objects.Figure: Professional user-status chart
"""
try:
    if df.empty:
        st.error("DataFrame is empty!")
        return go.Figure()

    if user_col not in df.columns or status_col not in df.columns:
        st.error(f"Columns '{user_col}' or '{status_col}' not found!")
        return go.Figure()

    # Clean data
    clean_df = df[[user_col, status_col]].dropna()
    if clean_df.empty:
        st.warning("No valid user-status pairs found!")
        return go.Figure()

    # Get unique statuses and assign colors
    unique_statuses = clean_df[status_col].unique()
    color_map = {}
    for i, status in enumerate(unique_statuses):
        if status in STATUS_COLORS:
            color_map[status] = STATUS_COLORS[status]
        else:
            color_map[status] = ENTERPRISE_COLORS[i % len(ENTERPRISE_COLORS)]

    # Create horizontal bar chart
    fig = go.Figure()

    for status in unique_statuses:
        status_data = clean_df[clean_df[status_col] == status]
        users = status_data[user_col].tolist()

        fig.add_trace(go.Bar(
            name=status,
            y=users,
            x=[1] * len(users),
            orientation='h',
            marker=dict(
                color=color_map[status],
                line=dict(color='white', width=1)
            ),
            text=[status] * len(users),
            textposition='middle center',
            textfont=dict(color='white', size=12, family='Arial Bold'),
            hovertemplate=f'<b>User:</b> %{{y}}<br><b>Status:</b> {status}<extra></extra>'
        ))

    fig.update_layout(
        title={
            'text': f"<b style='color:#2c3e50; font-size:22px'>{title}</b>",
            'x': 0.5,
            'xanchor': 'center'
        },
        xaxis=dict(
            title="",
            showgrid=False,
            showticklabels=False,
            zeroline=False
        ),
        yaxis=dict(
            title=f"<b>{user_col}</b>",
            titlefont=dict(size=14, color='#2c3e50'),
            tickfont=dict(size=12, color='#2c3e50')
        ),
        barmode='stack',
        showlegend=True,
        legend=dict(
            orientation="h",
            yanchor="bottom",
            y=1.02,
            xanchor="center",
            x=0.5,
            font=dict(size=12, color='#2c3e50')
        ),
        paper_bgcolor='rgba(0,0,0,0)',
        plot_bgcolor='rgba(0,0,0,0)',
        height=max(400, len(clean_df[user_col].unique()) * 30),
        margin=dict(t=100, b=50, l=150, r=50)
    )

    return fig

except Exception as e:
    st.error(f"Error creating user status chart: {str(e)}")
    return go.Figure()
Enter fullscreen mode Exit fullscreen mode

def single_metric_display(df: pd.DataFrame, column_name: str, title: Optional[str] = None) -> None:
"""
Creates a beautiful single metric display for showcasing one important value.

Parameters:
df (pd.DataFrame): Input DataFrame
column_name (str): Name of the column to display
title (str, optional): Custom title for the metric
"""
try:
if df.empty:
st.error("DataFrame is empty!")
return
if column_name not in df.columns:
    st.error(f"Column '{column_name}' not found!")
    return

# Get the value (first non-null value or aggregate if multiple rows)
col_data = df[column_name].dropna()
if col_data.empty:
    st.warning(f"No valid data in column '{column_name}'!")
    return

# Calculate display value
if len(col_data) == 1:
    display_value = col_data.iloc[0]
else:
    # If multiple values, show mean for numeric, most common for categorical
    if pd.api.types.is_numeric_dtype(col_data):
        display_value = col_data.mean()
    else:
        display_value = col_data.mode().iloc[0] if not col_data.mode().empty else col_data.iloc[0]

# Format the value
if isinstance(display_value, (int, float)):
    if display_value &gt;= 1000000:
        formatted_value = f"{display_value/1000000:.1f}M"
    elif display_value &gt;= 1000:
        formatted_value = f"{display_value/1000:.1f}K"
    else:
        formatted_value = f"{display_value:,.0f}" if display_value == int(display_value) else f"{display_value:.2f}"
else:
    formatted_value = str(display_value)

# Use custom title or column name
display_title = title if title else column_name.replace('_', ' ').title()

# Create beautiful metric display
st.markdown(
    f"""
    &lt;div style="
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        padding: 40px;
        border-radius: 20px;
        box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
        text-align: center;
        margin: 20px 0;
        border: 1px solid rgba(255, 255, 255, 0.1);
    "&gt;
        &lt;h2 style="
            color: white; 
            margin: 0 0 20px 0; 
            font-size: 24px; 
            font-weight: 600;
            text-shadow: 0 2px 4px rgba(0,0,0,0.3);
        "&gt;{display_title}&lt;/h2&gt;
        &lt;h1 style="
            color: white; 
            margin: 0; 
            font-size: 48px; 
            font-weight: bold;
            text-shadow: 0 2px 4px rgba(0,0,0,0.3);
        "&gt;{formatted_value}&lt;/h1&gt;
        &lt;p style="
            color: rgba(255, 255, 255, 0.8); 
            margin: 10px 0 0 0; 
            font-size: 14px;
        "&gt;Based on {len(col_data)} record{'s' if len(col_data) != 1 else ''}&lt;/p&gt;
    &lt;/div&gt;
    """,
    unsafe_allow_html=True
)

# Add additional stats if multiple records
if len(col_data) &gt; 1 and pd.api.types.is_numeric_dtype(col_data):
    col1, col2, col3 = st.columns(3)

    with col1:
        st.metric("Minimum", f"{col_data.min():,.2f}" if col_data.min() != int(col_data.min()) else f"{int(col_data.min()):,}")

    with col2:
        st.metric("Maximum", f"{col_data.max():,.2f}" if col_data.max() != int(col_data.max()) else f"{int(col_data.max()):,}")

    with col3:
        st.metric("Standard Deviation", f"{col_data.std():,.2f}")
Enter fullscreen mode Exit fullscreen mode

except Exception as e:
st.error(f"Error creating single metric display: {str(e)}")

Enter fullscreen mode Exit fullscreen mode




Example usage function for testing

def demo_all_functions():
"""
Demonstrates all visualization functions with sample data.
"""
st.title("🎯 Professional Visualization Dashboard")

# Sample data
sample_data = {
'Sales': [25000, 30000, 22000],
'Customers': [150, 180, 140],
'Revenue': [75000, 90000, 68000],
'Region': ['North', 'South', 'North'],
'Status': ['Active', 'Active', 'Pending'],
'User': ['John', 'Jane', 'Bob'],
'Problem': ['Login Issue', 'Payment Failed', 'Login Issue'],
'Solution': ['Reset Password', 'Update Card', 'Reset Password'],
'Score': [85, 92, 78]
}
df = pd.DataFrame(sample_data)

st.subheader("πŸ“Š Function 1: Single Row Pie Chart")
fig1 = pie_chart_single_row(df, title="Business Metrics Overview")
st.plotly_chart(fig1, use_container_width=True)

st.subheader("πŸ“ˆ Function 2: Column Values Pie Chart")
fig2 = pie_chart_column_values(df, 'Status', title="Status Distribution")
st.plotly_chart(fig2, use_container_width=True)

st.subheader("πŸ“‹ Function 3: KPI Cards")
create_kpi_cards(df, cards_per_row=3, title="Business Dashboard")

st.subheader("πŸ”„ Function 4: Cause & Solution Analysis")
fig4 = cause_solution_chart(df, 'Problem', 'Solution')
st.plotly_chart(fig4, use_container_width=True)

st.subheader("πŸ‘₯ Function 5: User Status Chart")
fig5 = user_status_chart(df, 'User', 'Status')
st.plotly_chart(fig5, use_container_width=True)

st.subheader("⭐ Function 6: Single Metric Display")
single_metric_display(df, 'Score', 'Overall User Score')

Enter fullscreen mode Exit fullscreen mode




Uncomment to run demo

demo_all_functions()

Top comments (0)