UI-Components: Cards with embedded charts (and other cool cards)

Wanted to start a thread similar to the Sidebar thread @AnnMarieW began.

Cards are a pretty ubiquitous UI component so i thought i’d share one i figured out. If you have others to share add them on.

This one embeds a “spark chart” onto the card itself so alongside the KPI you can get a visualization of that KPI across a short period of time.

image

Minimal example:

from dash import html, Dash, dcc, Input, Output, callback
import dash_mantine_components as dmc
import pandas as pd
import plotly.express as px
from datetime import datetime

app = Dash(__name__)

app.layout = dmc.Grid([
    dmc.Col([
        dmc.Paper([
            dcc.Location(id='url', refresh=False),
            dmc.Group([
                html.Div([
                    dmc.Title(id='tot-inv-title', order=5),
                    dmc.Title(id='tot-inv', order=1)
                ], style={'position': 'relative', 'z-index': '999'}),
                dcc.Graph(
                    id='tot-spark',
                    config={
                        'displayModeBar': False,
                        'staticPlot': True
                    },
                    responsive=True,
                    style={'height': 60, 'margin': '-1rem'})
            ], direction='column', grow=True)
        ], shadow="xl", p="md", withBorder=True, style={'margin-bottom': "1rem"})
    ], span=6)
])


@callback(
    [Output('tot-inv', 'children'),
     Output('tot-spark', 'figure'),
     Output('tot-inv-title', 'children')],
    Input('url', 'pathname')
)
def update_page(url):

    area_df = pd.DataFrame({
        'x': [20, 18, 489, 675, 1776],
        'y': [4, 25, 281, 600, 1900]
    }, index=[1990, 1997, 2003, 2009, 2014])

    fig = px.area(
        area_df,
        x="x", y="y",
        template='simple_white',
        log_y=True
    )

    fig.update_yaxes(visible=False),
    fig.update_xaxes(visible=False),
    fig.update_traces(
        line={'color': 'rgba(31, 119, 180, 0.2)'},
        fillcolor='rgba(31, 119, 180, 0.2)'
    ),
    fig.update_layout(
        margin={'t': 0, 'l': 0, 'b': 0, 'r': 0}
    )

    kpi_val = f'42'

    now = datetime.now()
    tot_inv_title = now.strftime('%m/%d/%Y')

    return kpi_val, fig, tot_inv_title


if __name__ == '__main__':
    app.run_server(
        host='0.0.0.0',
        port=8050,
        debug=True,
        dev_tools_props_check=True
    )

7 Likes

@tphil10 That looks awesome - thanks for posting! And for those who missed it from the original thread, here’s how the card looks in the app: The Bourbonhuntr

3 Likes

Ok, Here’s another example. I hope it qualifies as a “cool card” :sunglasses:

It’s common to display KPIs in a group of cards. This app creates the cards in a loop. It’s styled dynamically based on the data using Bootstrap Utility classes and icons. It’s updated with “live data” every 10 seconds (to stay within the API’s free tier

import dash
from dash import Dash, dcc, html, Input, Output
import dash_bootstrap_components as dbc
import requests

app = Dash(__name__, external_stylesheets=[dbc.themes.SUPERHERO, dbc.icons.BOOTSTRAP])

coins = ["bitcoin", "ethereum", "binancecoin", "ripple"]
interval = 6000 # update frequency - adjust to keep within free tier
api_url = "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd"


def get_data():
    try:
        response = requests.get(api_url, timeout=1)
        return response.json()
    except requests.exceptions.RequestException as e:
        print(e)


def make_card(coin):
    change = coin["price_change_percentage_24h"]
    price = coin["current_price"]
    color = "danger" if change < 0 else "success"
    icon = "bi bi-arrow-down" if change < 0 else "bi bi-arrow-up"
    return dbc.Card(
        html.Div(
            [
                html.H4(
                    [
                        html.Img(src=coin["image"], height=35, className="me-1"),
                        coin["name"],
                    ]
                ),
                html.H4(f"${price:,}"),
                html.H5(
                    [f"{round(change, 2)}%", html.I(className=icon), " 24hr"],
                    className=f"text-{color}",
                ),
            ],
            className=f"border-{color} border-start border-5",
        ),
        className="text-center text-nowrap my-2 p-2",
    )


mention = html.A(
    "Data from CoinGecko", href="https://www.coingecko.com/en/api", className="small"
)
interval = dcc.Interval(interval=interval)
cards = html.Div()
app.layout = dbc.Container([interval, cards, mention], className="my-5")


@app.callback(Output(cards, "children"), Input(interval, "n_intervals"))
def update_cards(_):
    coin_data = get_data()
    if coin_data is None or type(coin_data) is dict:
        return dash.no_update
    # make a list of cards with updated prices
    coin_cards = []
    updated = None
    for coin in coin_data:
        if coin["id"] in coins:
            updated = coin.get("last_updated")
            coin_cards.append(make_card(coin))

    # make the card layout
    card_layout = [
        dbc.Row([dbc.Col(card, md=3) for card in coin_cards]),
        dbc.Row(dbc.Col(f"Last Updated {updated}")),
    ]
    return card_layout


if __name__ == "__main__":
    app.run_server(debug=True)

3 Likes

Ooooo @AnnMarieW I might have a use for this in another app of mine! Awesome!

1 Like