Best-practice CompositeChart rendering for stacked discharge/temperature/pressure dashboards across 7d-live to 1yr+ date ranges (~2k to 150k+ points).

Composite Render BP

Best-practice CompositeChart rendering for stacked discharge/temperature/pressure dashboards across 7d-live to 1yr+ date ranges (~2k to 150k+ points).


Overview

Best-practice CompositeChart rendering for stacked discharge/temperature/pressure dashboards across 7d-live to 1yr+ date ranges (~2k to 150k+ points).

The examples on this page read a MUI X Pro license key from the MUI_PRO_API_KEY environment variable and pass it as licenseKey. Without it the charts render with the unlicensed watermark.


Live examples

# File: docs/composite_render_bp/demo.py

"""Composite Render BP demo — rendered on /composite-render-bp via `.. exec::`.

Ported verbatim from the pre-migration pages/composit_render_bp.py (same ids, same callbacks).
"""
import os
import math
import random
from datetime import datetime, timedelta
import dash_mantine_components as dmc
from dash import html, dcc, callback, Input, Output, State, ctx, no_update

from dash_mui_charts import CompositeChart

MUI_LICENSE_KEY = os.environ.get('MUI_PRO_API_KEY', '')

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
FIVE_MIN_MS = 5 * 60 * 1000
DAY_MS = 86400 * 1000

# Live 7d config
LIVE_INITIAL_POINTS = 1440  # 5 days of history to start (5*288)
LIVE_WINDOW_DEFAULT = 288   # 1 day visible window by default
LIVE_MAX_POINTS = 2016      # 7 days max


# ---------------------------------------------------------------------------
# Data generation
# ---------------------------------------------------------------------------
def generate_initial_live_data():
    """Build the initial 5 days of history with realistic random walk data."""
    random.seed(42)
    base_ts = int(datetime(2025, 4, 1).timestamp()) * 1000

    timestamps, discharge, temp, init_p, min_p = [], [], [], [], []

    # Random-walk state with mean reversion
    d_val = 4.0    # discharge (sec), mean ~4
    t_val = 45.0   # temp (C), mean ~45
    ip_val = 110.0  # init pressure (PSI), mean ~110
    mp_val = 90.0   # min pressure (PSI), mean ~90

    for i in range(LIVE_INITIAL_POINTS):
        ts = base_ts + i * FIVE_MIN_MS
        # Daily cycle component
        daily = math.sin(2 * math.pi * i / 288)

        # Discharge: mean-reverting random walk + daily cycle
        d_val += 0.02 * (4.0 - d_val) + random.gauss(0, 0.08) + 0.15 * daily
        d_val = max(1.5, min(7.0, d_val))

        # Temperature: slower drift + daily cycle
        t_val += 0.01 * (45.0 - t_val) + random.gauss(0, 0.4) + 1.0 * daily
        t_val = max(25, min(65, t_val))

        # Init pressure: gradual decline + daily cycle
        ip_val += 0.005 * (110.0 - ip_val) - 0.002 + random.gauss(0, 0.3) + 0.8 * daily
        ip_val = max(85, min(130, ip_val))

        # Min pressure: tracks below init with its own noise
        mp_val += 0.01 * (ip_val - 20 - mp_val) + random.gauss(0, 0.25)
        mp_val = max(60, min(ip_val - 5, mp_val))

        timestamps.append(ts)
        discharge.append(round(d_val, 2))
        temp.append(round(t_val, 1))
        init_p.append(round(ip_val, 1))
        min_p.append(round(mp_val, 1))

    # Scatter events at fixed indices
    event_indices = sorted(random.sample(range(LIVE_INITIAL_POINTS), 5))
    events_d = [{'x': timestamps[i], 'y': round(discharge[i] + random.choice([-1, 1]) * 2, 2), 'id': j}
                for j, i in enumerate(event_indices)]
    events_t = [{'x': timestamps[i], 'y': round(temp[i] + random.choice([-1, 1]) * 10, 1), 'id': j}
                for j, i in enumerate(event_indices)]
    events_p = [{'x': timestamps[i], 'y': round(min_p[i] - 8, 1), 'id': j}
                for j, i in enumerate(event_indices)]

    return {
        'timestamps': timestamps,
        'discharge': discharge,
        'temp': temp,
        'init_pressure': init_p,
        'min_pressure': min_p,
        'events_discharge': events_d,
        'events_temp': events_t,
        'events_pressure': events_p,
        'tick_index': LIVE_INITIAL_POINTS,
        'base_ts': base_ts,
        'event_counter': len(event_indices),
        # Carry forward last values for random walk continuation
        'last_d': round(d_val, 2),
        'last_t': round(t_val, 1),
        'last_ip': round(ip_val, 1),
        'last_mp': round(mp_val, 1),
    }


def generate_dataset(n_days, seed=42):
    """Generate realistic equipment data for n_days at 5-min intervals."""
    random.seed(seed)
    n_points = n_days * 288
    base = datetime(2025, 4, 1)
    timestamps = [int((base + timedelta(minutes=5 * i)).timestamp()) * 1000 for i in range(n_points)]
    discharge = [round(4 + 1.5 * math.sin(2 * math.pi * i / 288) + random.gauss(0, 0.3), 2) for i in range(n_points)]
    temp = [round(45 + 10 * math.sin(2 * math.pi * i / (288 * 90)) + 5 * math.sin(2 * math.pi * i / 288) + random.gauss(0, 1.5), 1) for i in range(n_points)]
    init_pressure = [round(110 - 0.03 * (i / 288) + 5 * math.sin(2 * math.pi * i / 288) + random.gauss(0, 1.5), 1) for i in range(n_points)]
    min_pressure = [round(init_pressure[i] - 20 + 3 * math.sin(2 * math.pi * i / (288 * 7)) + random.gauss(0, 1), 1) for i in range(n_points)]
    n_events = max(3, n_days // 5)
    event_indices = sorted(random.sample(range(n_points), min(n_events, n_points)))
    events_discharge = [{'x': timestamps[i], 'y': round(discharge[i] + random.choice([-1, 1]) * random.uniform(1.5, 3), 2), 'id': j} for j, i in enumerate(event_indices)]
    events_temp = [{'x': timestamps[i], 'y': round(temp[i] + random.choice([-1, 1]) * random.uniform(8, 15), 1), 'id': j} for j, i in enumerate(event_indices)]
    events_pressure = [{'x': timestamps[i], 'y': round(min_pressure[i] - random.uniform(5, 12), 1), 'id': j} for j, i in enumerate(event_indices)]
    return {
        'timestamps': timestamps, 'discharge': discharge, 'temp': temp,
        'init_pressure': init_pressure, 'min_pressure': min_pressure,
        'events_discharge': events_discharge, 'events_temp': events_temp,
        'events_pressure': events_pressure, 'n_points': n_points,
    }


# ---------------------------------------------------------------------------
# Static range profiles (30d+)
# ---------------------------------------------------------------------------
RANGE_PROFILES = {
    '30d': {
        'label': '30 Days', 'n_days': 30,
        'description': '~8,640 points. Good balance of detail and overview. Ticks every ~5 days.',
        'tick_number': 6, 'tick_min_step': DAY_MS * 5,
        'date_format': 'dd/MM/YYYY HH:mm', 'date_tick_format': 'dd/MM',
        'tick_angle': 35, 'tick_font_size': 11, 'text_anchor': 'start',
        'x_height': 50, 'bottom_margin': 65,
        'chart_heights': (180, 180, 220), 'event_marker_size': 6,
    },
    '3mo': {
        'label': '3 Months', 'n_days': 90,
        'description': '~25,920 points. Medium density, ticks every ~2 weeks. Trends emerge.',
        'tick_number': 6, 'tick_min_step': DAY_MS * 14,
        'date_format': 'dd/MM/YYYY', 'date_tick_format': 'dd/MM/YY',
        'tick_angle': 35, 'tick_font_size': 11, 'text_anchor': 'start',
        'x_height': 55, 'bottom_margin': 70,
        'chart_heights': (180, 180, 220), 'event_marker_size': 5,
    },
    '6mo': {
        'label': '6 Months', 'n_days': 180,
        'description': '~51,840 points. Ticks monthly. Seasonal patterns visible.',
        'tick_number': 6, 'tick_min_step': DAY_MS * 30,
        'date_format': 'dd/MM/YYYY', 'date_tick_format': 'MMM YY',
        'tick_angle': 35, 'tick_font_size': 11, 'text_anchor': 'start',
        'x_height': 50, 'bottom_margin': 65,
        'chart_heights': (170, 170, 210), 'event_marker_size': 5,
    },
    '1yr': {
        'label': '1 Year', 'n_days': 365,
        'description': '~105,120 points. Ticks monthly. Full seasonal cycle.',
        'tick_number': 12, 'tick_min_step': DAY_MS * 30,
        'date_format': 'dd MMM YYYY', 'date_tick_format': 'MMM YY',
        'tick_angle': 35, 'tick_font_size': 10, 'text_anchor': 'start',
        'x_height': 50, 'bottom_margin': 65,
        'chart_heights': (170, 170, 210), 'event_marker_size': 4,
    },
    '1yr+': {
        'label': '18 Months', 'n_days': 548,
        'description': '~157,824 points. Ticks every 2 months. Long-term degradation visible.',
        'tick_number': 9, 'tick_min_step': DAY_MS * 60,
        'date_format': 'dd MMM YYYY', 'date_tick_format': 'MMM YY',
        'tick_angle': 35, 'tick_font_size': 10, 'text_anchor': 'start',
        'x_height': 50, 'bottom_margin': 65,
        'chart_heights': (170, 170, 210), 'event_marker_size': 4,
    },
}

DATASETS = {key: generate_dataset(p['n_days'], seed=hash(key) % 10000) for key, p in RANGE_PROFILES.items()}

# ---------------------------------------------------------------------------
# Styles
# ---------------------------------------------------------------------------
section_style = {'marginBottom': '50px'}
description_style = {'color': 'var(--mantine-color-dimmed)', 'marginBottom': '10px', 'fontSize': '14px'}
code_style = {
    'backgroundColor': 'var(--mantine-color-default)', 'padding': '12px', 'borderRadius': '5px',
    'whiteSpace': 'pre-wrap', 'fontSize': '11px', 'overflow': 'auto',
}
stat_card = {
    'backgroundColor': 'var(--mantine-color-default)', 'padding': '8px 16px', 'borderRadius': '8px',
    'textAlign': 'center', 'minWidth': '100px',
}
badge_style = {
    'backgroundColor': '#1565c0', 'color': 'white', 'padding': '2px 8px',
    'borderRadius': '4px', 'fontSize': '10px', 'fontWeight': 'bold',
    'marginLeft': '8px', 'verticalAlign': 'middle',
}
stat_style = {
    'fontSize': '12px', 'color': 'var(--mantine-color-dimmed)', 'fontFamily': 'monospace', 'marginBottom': '8px',
}


# ---------------------------------------------------------------------------
# Reference line generators
# ---------------------------------------------------------------------------
MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
               'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

_REF_LINE_STYLE = {'stroke': '#bdbdbd', 'strokeDasharray': '4 4', 'strokeWidth': 1}
_REF_LABEL_STYLE = {'fill': '#757575', 'fontSize': 10}


def _month_reference_lines(n_days):
    """Vertical reference lines at the 1st of each month within the range."""
    base = datetime(2025, 4, 1)
    end = base + timedelta(days=n_days)
    lines = []
    # Start from the first month boundary after the base
    d = datetime(base.year, base.month, 1)
    if d <= base:
        if d.month == 12:
            d = datetime(d.year + 1, 1, 1)
        else:
            d = datetime(d.year, d.month + 1, 1)
    while d < end:
        ts = int(d.timestamp()) * 1000
        label = f"{MONTH_NAMES[d.month - 1]}"
        lines.append({
            'x': ts, 'label': label,
            'lineStyle': _REF_LINE_STYLE,
            'labelStyle': _REF_LABEL_STYLE,
            'labelAlign': 'start',
        })
        if d.month == 12:
            d = datetime(d.year + 1, 1, 1)
        else:
            d = datetime(d.year, d.month + 1, 1)
    return lines


def _quarter_reference_lines(n_days):
    """Vertical reference lines at Q1–Q4 boundaries within the range."""
    base = datetime(2025, 4, 1)
    end = base + timedelta(days=n_days)
    quarter_starts = [1, 4, 7, 10]  # Jan, Apr, Jul, Oct
    lines = []
    # Scan from base year through end year
    for year in range(base.year, end.year + 1):
        for qi, month in enumerate(quarter_starts, 1):
            d = datetime(year, month, 1)
            if d <= base or d >= end:
                continue
            ts = int(d.timestamp()) * 1000
            label = f"{str(year)[-2:]}Q{qi}"
            lines.append({
                'x': ts, 'label': label,
                'lineStyle': _REF_LINE_STYLE,
                'labelStyle': {**_REF_LABEL_STYLE, 'fontWeight': 'bold'},
                'labelAlign': 'start',
            })
    return lines


# ---------------------------------------------------------------------------
# Static section builder (30d+)
# ---------------------------------------------------------------------------
def build_range_section(range_key):
    """Build a full 3-chart stacked section for a given range profile."""
    p = RANGE_PROFILES[range_key]
    d = DATASETS[range_key]
    prefix = f'bp-{range_key}'
    h_top, h_mid, h_bot = p['chart_heights']
    tls = {'angle': p['tick_angle'], 'fontSize': p['tick_font_size'], 'textAnchor': p['text_anchor']}

    # Reference lines: months for 3mo/6mo, quarters for 1yr/1yr+
    if range_key in ('3mo', '6mo'):
        ref_lines = _month_reference_lines(p['n_days'])
    elif range_key in ('1yr', '1yr+'):
        ref_lines = _quarter_reference_lines(p['n_days'])
    else:
        ref_lines = None

    x_hidden = [{'id': 'x', 'data': d['timestamps'], 'scaleType': 'time',
                 'tickNumber': p['tick_number'], 'tickMinStep': p['tick_min_step'],
                 'dateFormat': p['date_format'], 'dateTickFormat': p['date_tick_format'],
                 'position': 'none', 'tickLabelStyle': tls}]
    x_visible = [{'id': 'x', 'data': d['timestamps'], 'scaleType': 'time',
                  'label': 'Date', 'height': p['x_height'],
                  'tickNumber': p['tick_number'], 'tickMinStep': p['tick_min_step'],
                  'dateFormat': p['date_format'], 'dateTickFormat': p['date_tick_format'],
                  'tickLabelStyle': tls, 'labelStyle': {'fontSize': 12}}]
    m_top = {'left': 65, 'right': 15, 'top': 10, 'bottom': 5}
    m_bot = {'left': 65, 'right': 15, 'top': 10, 'bottom': p['bottom_margin']}

    return html.Div([
        html.H3(f"{p['label']} Range", style={'marginBottom': '4px'}),
        html.P(p['description'], style=description_style),
        html.Div(
            f"{d['n_points']:,} data points  |  {len(d['events_discharge'])} events  |  "
            f"tickMinStep={p['tick_min_step'] // DAY_MS}d  |  tickNumber={p['tick_number']}  |  "
            f"format=\"{p['date_tick_format']}\"",
            style=stat_style),
        html.Div([dmc.Switch(id=f'{prefix}-sync', label='Sync tooltips', checked=True, size='sm')],
                 style={'marginBottom': '8px'}),
        html.Div([
            html.Div([
                html.Div("Discharge Time (sec)", style={'fontSize': '13px', 'fontWeight': 500, 'marginBottom': '2px'}),
                CompositeChart(
                    id=f'{prefix}-discharge', licenseKey=MUI_LICENSE_KEY, height=h_top,
                    series=[
                        {'type': 'line', 'id': 'discharge', 'label': 'Discharge Time', 'data': d['discharge'], 'color': '#1976d2', 'showMark': False},
                        {'type': 'scatter', 'id': 'events', 'label': 'Events', 'data': d['events_discharge'], 'color': '#e53935', 'markerSize': p['event_marker_size']},
                    ],
                    xAxis=x_hidden, yAxis=[{'label': 'sec', 'width': 60, 'domainLimit': 'nice'}],
                    tooltip={'trigger': 'axis'}, axisHighlight={'x': 'line'}, hideLegend=True, margin=m_top,
                    **({'referenceLines': ref_lines} if ref_lines else {})),
            ]),
            html.Div([
                html.Div("Temperature (C)", style={'fontSize': '13px', 'fontWeight': 500, 'marginBottom': '2px'}),
                CompositeChart(
                    id=f'{prefix}-temp', licenseKey=MUI_LICENSE_KEY, height=h_mid,
                    series=[
                        {'type': 'line', 'id': 'temp', 'label': 'Temperature', 'data': d['temp'], 'color': '#e65100', 'showMark': False},
                        {'type': 'scatter', 'id': 'events', 'label': 'Events', 'data': d['events_temp'], 'color': '#e53935', 'markerSize': p['event_marker_size']},
                    ],
                    xAxis=x_hidden, yAxis=[{'label': 'C', 'width': 60, 'domainLimit': 'nice'}],
                    tooltip={'trigger': 'axis'}, axisHighlight={'x': 'line'}, hideLegend=True, margin=m_top,
                    **({'referenceLines': ref_lines} if ref_lines else {})),
            ]),
            html.Div([
                html.Div("Pressure (PSI)", style={'fontSize': '13px', 'fontWeight': 500, 'marginBottom': '2px'}),
                CompositeChart(
                    id=f'{prefix}-pressure', licenseKey=MUI_LICENSE_KEY, height=h_bot,
                    series=[
                        {'type': 'line', 'id': 'init-pressure', 'label': 'Init Pressure', 'data': d['init_pressure'], 'color': '#7b1fa2', 'showMark': False},
                        {'type': 'line', 'id': 'min-pressure', 'label': 'Min Pressure', 'data': d['min_pressure'], 'color': '#00838f', 'showMark': False},
                        {'type': 'scatter', 'id': 'events', 'label': 'Events', 'data': d['events_pressure'], 'color': '#e53935', 'markerSize': p['event_marker_size']},
                    ],
                    xAxis=x_visible, yAxis=[{'label': 'PSI', 'width': 60, 'domainLimit': 'nice'}],
                    tooltip={'trigger': 'axis'}, axisHighlight={'x': 'line'}, hideLegend=True, margin=m_bot,
                    **({'referenceLines': ref_lines} if ref_lines else {})),
            ]),
        ]),
    ], style=section_style)


# ---------------------------------------------------------------------------
# Live 7d section builder
# ---------------------------------------------------------------------------
def build_live_7d_section():
    """Build the live-streaming 7-day dashboard section."""
    return html.Div([
        html.H3("7 Days Range — Live", style={'marginBottom': '4px'}),
        html.P(
            "Live streaming equipment data at 5-min intervals. Starts with 5 days of history "
            "and streams new readings in real time. Adjust speed and visible window size.",
            style=description_style),

        # Controls row
        html.Div([
            html.Div([
                dmc.Button("Start", id='bp-live-start', color='green', size='sm', style={'marginRight': '8px'}),
                dmc.Button("Stop", id='bp-live-stop', color='yellow', size='sm', variant='outline', style={'marginRight': '8px'}),
                dmc.Button("Reset", id='bp-live-reset', color='red', size='sm', variant='outline'),
            ], style={'display': 'flex', 'alignItems': 'center'}),
            html.Div([
                dmc.Switch(id='bp-live-sync', label='Sync tooltips', checked=True, size='sm'),
            ], style={'display': 'flex', 'alignItems': 'center'}),
        ], style={'display': 'flex', 'justifyContent': 'space-between', 'marginBottom': '10px'}),

        # Sliders row
        html.Div([
            html.Div([
                html.Label("Speed (ms/tick)", style={'fontWeight': 500, 'fontSize': '12px', 'display': 'block', 'marginBottom': '4px'}),
                dmc.Slider(
                    id='bp-live-speed', value=300, min=50, max=2000, step=50,
                    marks=[{'value': 50, 'label': '50ms'}, {'value': 300, 'label': '300ms'},
                           {'value': 1000, 'label': '1s'}, {'value': 2000, 'label': '2s'}],
                    style={'maxWidth': '260px'}),
            ], style={'flex': '1'}),
            html.Div([
                html.Label("Window (points)", style={'fontWeight': 500, 'fontSize': '12px', 'display': 'block', 'marginBottom': '4px'}),
                dmc.Slider(
                    id='bp-live-window', value=LIVE_WINDOW_DEFAULT, min=72, max=2016, step=72,
                    marks=[{'value': 72, 'label': '6h'}, {'value': 288, 'label': '1d'},
                           {'value': 1008, 'label': '3.5d'}, {'value': 2016, 'label': '7d'}],
                    style={'maxWidth': '260px'}),
            ], style={'flex': '1'}),
            html.Div([
                html.Label("Estimate (points ahead)", style={'fontWeight': 500, 'fontSize': '12px', 'display': 'block', 'marginBottom': '4px'}),
                dmc.Slider(
                    id='bp-live-estimate', value=36, min=0, max=144, step=12,
                    marks=[{'value': 0, 'label': '0'}, {'value': 36, 'label': '3h'},
                           {'value': 72, 'label': '6h'}, {'value': 144, 'label': '12h'}],
                    style={'maxWidth': '260px'}),
            ], style={'flex': '1'}),
        ], style={'display': 'flex', 'gap': '25px', 'marginBottom': '12px'}),

        # Stats row
        html.Div([
            html.Div([
                html.Div("Points", style={'fontSize': '10px', 'color': 'var(--mantine-color-dimmed)'}),
                html.Div(id='bp-live-stat-points', children=str(LIVE_INITIAL_POINTS),
                         style={'fontSize': '18px', 'fontWeight': 'bold'}),
            ], style=stat_card),
            html.Div([
                html.Div("Discharge", style={'fontSize': '10px', 'color': 'var(--mantine-color-dimmed)'}),
                html.Div(id='bp-live-stat-discharge', children="—",
                         style={'fontSize': '18px', 'fontWeight': 'bold', 'color': '#1976d2'}),
            ], style=stat_card),
            html.Div([
                html.Div("Temp", style={'fontSize': '10px', 'color': 'var(--mantine-color-dimmed)'}),
                html.Div(id='bp-live-stat-temp', children="—",
                         style={'fontSize': '18px', 'fontWeight': 'bold', 'color': '#e65100'}),
            ], style=stat_card),
            html.Div([
                html.Div("Init PSI", style={'fontSize': '10px', 'color': 'var(--mantine-color-dimmed)'}),
                html.Div(id='bp-live-stat-initp', children="—",
                         style={'fontSize': '18px', 'fontWeight': 'bold', 'color': '#7b1fa2'}),
            ], style=stat_card),
            html.Div([
                html.Div("Min PSI", style={'fontSize': '10px', 'color': 'var(--mantine-color-dimmed)'}),
                html.Div(id='bp-live-stat-minp', children="—",
                         style={'fontSize': '18px', 'fontWeight': 'bold', 'color': '#00838f'}),
            ], style=stat_card),
            html.Div([
                html.Div("Status", style={'fontSize': '10px', 'color': 'var(--mantine-color-dimmed)'}),
                html.Div(id='bp-live-status', children="Stopped",
                         style={'fontSize': '18px', 'fontWeight': 'bold', 'color': 'var(--mantine-color-dimmed)'}),
            ], style=stat_card),
        ], style={'display': 'flex', 'gap': '10px', 'marginBottom': '10px', 'flexWrap': 'wrap'}),

        # Charts
        html.Div([
            html.Div([
                html.Div("Discharge Time (sec)", style={'fontSize': '13px', 'fontWeight': 500, 'marginBottom': '2px'}),
                CompositeChart(
                    id='bp-live-discharge', licenseKey=MUI_LICENSE_KEY, height=180,
                    series=[], xAxis=[{'id': 'x', 'data': [], 'scaleType': 'time', 'position': 'none'}],
                    yAxis=[{'label': 'sec', 'width': 60, 'domainLimit': 'nice'}],
                    tooltip={'trigger': 'axis'}, axisHighlight={'x': 'line'}, hideLegend=True,
                    margin={'left': 65, 'right': 15, 'top': 10, 'bottom': 5}),
            ]),
            html.Div([
                html.Div("Temperature (C)", style={'fontSize': '13px', 'fontWeight': 500, 'marginBottom': '2px'}),
                CompositeChart(
                    id='bp-live-temp', licenseKey=MUI_LICENSE_KEY, height=180,
                    series=[], xAxis=[{'id': 'x', 'data': [], 'scaleType': 'time', 'position': 'none'}],
                    yAxis=[{'label': 'C', 'width': 60, 'domainLimit': 'nice'}],
                    tooltip={'trigger': 'axis'}, axisHighlight={'x': 'line'}, hideLegend=True,
                    margin={'left': 65, 'right': 15, 'top': 10, 'bottom': 5}),
            ]),
            html.Div([
                html.Div("Pressure (PSI)", style={'fontSize': '13px', 'fontWeight': 500, 'marginBottom': '2px'}),
                CompositeChart(
                    id='bp-live-pressure', licenseKey=MUI_LICENSE_KEY, height=220,
                    series=[], xAxis=[{'id': 'x', 'data': [], 'scaleType': 'time'}],
                    yAxis=[{'label': 'PSI', 'width': 60, 'domainLimit': 'nice'}],
                    tooltip={'trigger': 'axis'}, axisHighlight={'x': 'line'}, hideLegend=True,
                    margin={'left': 65, 'right': 15, 'top': 10, 'bottom': 55}),
            ]),
        ]),

        # Hidden stores
        dcc.Store(id='bp-live-store', data=generate_initial_live_data()),
        dcc.Store(id='bp-live-running', data=False),
        dcc.Interval(id='bp-live-interval', interval=300, disabled=True),
    ], style=section_style)


# ---------------------------------------------------------------------------
# Layout — lightweight shell; heavy chart sections loaded via callback
# ---------------------------------------------------------------------------
component = dmc.Box(
    pos="relative",
    children=[
        dmc.LoadingOverlay(
            id='bp-loading-overlay',
            visible=True,
            zIndex=10,
            overlayProps={"blur": 4},
            loaderProps={
                "variant": "custom",
                "children": dmc.Stack(
                    [
                        html.Img(
                            src='/assets/light_mode_2plot.png',
                            className='bp-loading-logo',
                            style={'width': '100px', 'height': '100px', 'objectFit': 'contain'},
                        ),
                        dmc.Text("Loading charts...", size="sm", c="dimmed", fw=500),
                    ],
                    align="center",
                    gap="sm",
                ),
            },
        ),

        html.Div([
            html.Div([
                html.H1("Composite Render Best Practices", style={'display': 'inline'}),
                html.Span("v1.2.0", style=badge_style),
            ]),
            html.P(
                "Optimal rendering of stacked equipment dashboards across date ranges. "
                "The 7-day range is a live streaming demo; the rest are static previews "
                "tuned per data volume. Equipment pings every ~5 minutes.",
                style={'fontSize': '15px', 'color': 'var(--mantine-color-dimmed)', 'marginBottom': '10px'}),
            html.Div([
                html.Div("Configuration tuned per range:", style={'fontWeight': 500, 'marginBottom': '4px'}),
                html.Ul([
                    html.Li("tickNumber / tickMinStep: controls label density to avoid overlap"),
                    html.Li("dateTickFormat: shorter at wider ranges (dd/MM -> MMM YY)"),
                    html.Li("dateFormat: full precision in tooltips"),
                    html.Li("event marker size: smaller at denser ranges for clarity"),
                ], style={'fontSize': '13px', 'color': 'var(--mantine-color-dimmed)', 'marginBottom': '20px'}),
            ]),

            html.Hr(),

            # Deferred content — populated by callback on page load
            html.Div(id='bp-deferred-content', style={'minHeight': '600px'}),

        ], style={'maxWidth': '1200px', 'margin': '0 auto', 'padding': '20px'}),

        # Hidden trigger — fires once on page load
        dcc.Store(id='bp-page-loaded', data=True),
    ],
)


# ===========================================================================
# Deferred content — builds all chart sections on page load
# ===========================================================================
@callback(
    Output('bp-deferred-content', 'children'),
    Output('bp-loading-overlay', 'visible'),
    Input('bp-page-loaded', 'data'),
)
def load_chart_sections(_trigger):
    """Build all chart sections server-side. The overlay shows while this runs."""
    content = [
        # Live 7d section
        build_live_7d_section(),
        html.Hr(),
        # Static range sections
        *[build_range_section(key) for key in RANGE_PROFILES],
        # Config reference table
        html.Div([
            html.H3("Configuration Reference"),
            html.Pre(
                "Range      | Points   | tickNum | tickMinStep | tickFormat | markerSize\n"
                "-----------+----------+---------+-------------+------------+-----------\n"
                "7 Days     |    2,016 |       7 |       1d    |      dd/MM |      7  (live)\n"
                + "\n".join(
                    f"{p['label']:10s} | {DATASETS[k]['n_points']:>8,} | {p['tick_number']:>7} | "
                    f"{p['tick_min_step'] // DAY_MS:>8}d    | {p['date_tick_format']:>10s} | "
                    f"{p['event_marker_size']:>6}"
                    for k, p in RANGE_PROFILES.items()),
                style=code_style),
        ], style={'marginBottom': '40px'}),
    ]
    return content, False  # Hide overlay when done


# ===========================================================================
# Live 7d Callbacks
# ===========================================================================

@callback(
    Output('bp-live-interval', 'disabled'),
    Output('bp-live-running', 'data'),
    Output('bp-live-status', 'children'),
    Output('bp-live-status', 'style'),
    Input('bp-live-start', 'n_clicks'),
    Input('bp-live-stop', 'n_clicks'),
    prevent_initial_call=True,
)
def live_toggle(start, stop):
    running = ctx.triggered_id == 'bp-live-start'
    return (
        not running,
        running,
        "Streaming" if running else "Stopped",
        {'fontSize': '18px', 'fontWeight': 'bold', 'color': '#4caf50' if running else '#999'},
    )


@callback(
    Output('bp-live-store', 'data', allow_duplicate=True),
    Output('bp-live-interval', 'disabled', allow_duplicate=True),
    Output('bp-live-running', 'data', allow_duplicate=True),
    Output('bp-live-status', 'children', allow_duplicate=True),
    Output('bp-live-status', 'style', allow_duplicate=True),
    Input('bp-live-reset', 'n_clicks'),
    prevent_initial_call=True,
)
def live_reset(_):
    return (
        generate_initial_live_data(),
        True, False, "Stopped",
        {'fontSize': '18px', 'fontWeight': 'bold', 'color': 'var(--mantine-color-dimmed)'},
    )


@callback(Output('bp-live-interval', 'interval'), Input('bp-live-speed', 'value'))
def live_speed(val):
    return val or 300


@callback(
    Output('bp-live-store', 'data'),
    Input('bp-live-interval', 'n_intervals'),
    State('bp-live-store', 'data'),
    State('bp-live-running', 'data'),
    prevent_initial_call=True,
)
def live_tick(_, store, running):
    if not running or not store:
        return no_update

    i = store['tick_index']
    base_ts = store['base_ts']
    ts = base_ts + i * FIVE_MIN_MS
    daily = math.sin(2 * math.pi * i / 288)

    # Continue random walk from last stored values
    d_val = store.get('last_d', 4.0)
    t_val = store.get('last_t', 45.0)
    ip_val = store.get('last_ip', 110.0)
    mp_val = store.get('last_mp', 90.0)

    d_val += 0.02 * (4.0 - d_val) + random.gauss(0, 0.08) + 0.15 * daily
    d_val = max(1.5, min(7.0, d_val))

    t_val += 0.01 * (45.0 - t_val) + random.gauss(0, 0.4) + 1.0 * daily
    t_val = max(25, min(65, t_val))

    ip_val += 0.005 * (110.0 - ip_val) - 0.002 + random.gauss(0, 0.3) + 0.8 * daily
    ip_val = max(85, min(130, ip_val))

    mp_val += 0.01 * (ip_val - 20 - mp_val) + random.gauss(0, 0.25)
    mp_val = max(60, min(ip_val - 5, mp_val))

    d = round(d_val, 2)
    t = round(t_val, 1)
    ip = round(ip_val, 1)
    mp = round(mp_val, 1)

    store['timestamps'].append(ts)
    store['discharge'].append(d)
    store['temp'].append(t)
    store['init_pressure'].append(ip)
    store['min_pressure'].append(mp)
    store['tick_index'] = i + 1
    store['last_d'] = d
    store['last_t'] = t
    store['last_ip'] = ip
    store['last_mp'] = mp

    # Random event (~1% chance per tick)
    if random.random() < 0.01:
        eid = store['event_counter']
        store['events_discharge'].append({'x': ts, 'y': round(d + random.choice([-1, 1]) * 2, 2), 'id': eid})
        store['events_temp'].append({'x': ts, 'y': round(t + random.choice([-1, 1]) * 10, 1), 'id': eid})
        store['events_pressure'].append({'x': ts, 'y': round(mp - 8, 1), 'id': eid})
        store['event_counter'] = eid + 1

    # Cap total points
    n = len(store['timestamps'])
    if n > LIVE_MAX_POINTS * 2:
        trim = n - LIVE_MAX_POINTS
        store['timestamps'] = store['timestamps'][trim:]
        store['discharge'] = store['discharge'][trim:]
        store['temp'] = store['temp'][trim:]
        store['init_pressure'] = store['init_pressure'][trim:]
        store['min_pressure'] = store['min_pressure'][trim:]
        min_ts = store['timestamps'][0]
        store['events_discharge'] = [e for e in store['events_discharge'] if e['x'] >= min_ts]
        store['events_temp'] = [e for e in store['events_temp'] if e['x'] >= min_ts]
        store['events_pressure'] = [e for e in store['events_pressure'] if e['x'] >= min_ts]

    return store


def _build_forecast(timestamps, values, n_ahead, mean_target=None):
    """Build a smooth forecast with expanding uncertainty band.

    Uses exponential moving average over a long lookback to compute a stable
    trend, then projects forward with mean-reversion toward mean_target.
    Uncertainty band widens proportionally to sqrt(steps_ahead).
    """
    if not values or len(values) < 20 or n_ahead <= 0:
        return []

    last_ts = timestamps[-1]
    last_val = values[-1]

    # --- Smooth trend via EMA over last 72 points (~6 hours) ---
    lookback = min(72, len(values))
    recent = values[-lookback:]
    # EMA of per-step changes (alpha=0.05 for heavy smoothing)
    alpha = 0.05
    ema_delta = 0.0
    for k in range(1, len(recent)):
        delta = recent[k] - recent[k - 1]
        ema_delta = alpha * delta + (1 - alpha) * ema_delta

    # --- Volatility from standard deviation of recent diffs ---
    diffs = [recent[k] - recent[k - 1] for k in range(1, len(recent))]
    if diffs:
        mean_diff = sum(diffs) / len(diffs)
        variance = sum((d - mean_diff) ** 2 for d in diffs) / len(diffs)
        vol = math.sqrt(variance) if variance > 0 else abs(last_val) * 0.005
    else:
        vol = abs(last_val) * 0.005

    # --- Project forward with decaying trend + mean reversion ---
    forecast = []
    center = last_val
    trend = ema_delta

    for j in range(n_ahead + 1):
        ts = last_ts + j * FIVE_MIN_MS

        # Uncertainty grows with sqrt of distance
        spread = vol * 2.0 * math.sqrt(1 + j)

        forecast.append({
            'x': ts,
            'y': round(center, 2),
            'upper': round(center + spread, 2),
            'lower': round(center - spread, 2),
        })

        # Advance center: decaying trend + gentle mean reversion
        trend *= 0.98  # trend decays toward zero
        if mean_target is not None:
            # Pull gently toward the long-term mean
            reversion = 0.003 * (mean_target - center)
            center += trend + reversion
        else:
            center += trend

    return forecast


@callback(
    Output('bp-live-discharge', 'series'),
    Output('bp-live-discharge', 'xAxis'),
    Output('bp-live-discharge', 'forecast'),
    Output('bp-live-temp', 'series'),
    Output('bp-live-temp', 'xAxis'),
    Output('bp-live-temp', 'forecast'),
    Output('bp-live-pressure', 'series'),
    Output('bp-live-pressure', 'xAxis'),
    Output('bp-live-pressure', 'forecast'),
    Output('bp-live-stat-points', 'children'),
    Output('bp-live-stat-discharge', 'children'),
    Output('bp-live-stat-temp', 'children'),
    Output('bp-live-stat-initp', 'children'),
    Output('bp-live-stat-minp', 'children'),
    Input('bp-live-store', 'data'),
    State('bp-live-window', 'value'),
    State('bp-live-estimate', 'value'),
)
def live_render(store, window, estimate):
    if not store or not store['timestamps']:
        return no_update

    window = window or LIVE_WINDOW_DEFAULT
    estimate = estimate or 0
    n = len(store['timestamps'])
    start = max(0, n - window)

    # Windowed data (copies to avoid mutating store)
    ts_win = list(store['timestamps'][start:])
    dis_win = list(store['discharge'][start:])
    temp_win = list(store['temp'][start:])
    initp_win = list(store['init_pressure'][start:])
    minp_win = list(store['min_pressure'][start:])

    # Build forecast overlays with mean-reversion targets
    fc_discharge = _build_forecast(ts_win, dis_win, estimate, mean_target=4.0)
    fc_temp = _build_forecast(ts_win, temp_win, estimate, mean_target=45.0)
    fc_initp = _build_forecast(ts_win, initp_win, estimate, mean_target=110.0)
    fc_minp = _build_forecast(ts_win, minp_win, estimate, mean_target=90.0)

    # Extend x-axis to include forecast range so the band is visible
    if estimate > 0:
        last_ts = ts_win[-1]
        for j in range(1, estimate + 1):
            ts_win.append(last_ts + j * FIVE_MIN_MS)
            dis_win.append(None)
            temp_win.append(None)
            initp_win.append(None)
            minp_win.append(None)

    # Filter events to visible time range
    min_ts = ts_win[0]
    max_ts = ts_win[-1]
    ev_d = [e for e in store['events_discharge'] if min_ts <= e['x'] <= max_ts]
    ev_t = [e for e in store['events_temp'] if min_ts <= e['x'] <= max_ts]
    ev_p = [e for e in store['events_pressure'] if min_ts <= e['x'] <= max_ts]

    x_hidden = [{'id': 'x', 'data': ts_win, 'scaleType': 'time',
                 'tickNumber': 7, 'tickMinStep': DAY_MS,
                 'dateFormat': 'dd/MM/YYYY HH:mm', 'dateTickFormat': 'dd/MM HH:mm',
                 'position': 'none'}]
    x_visible = [{'id': 'x', 'data': ts_win, 'scaleType': 'time',
                  'label': 'Date', 'height': 40,
                  'tickNumber': 7, 'tickMinStep': DAY_MS,
                  'dateFormat': 'dd/MM/YYYY HH:mm', 'dateTickFormat': 'dd/MM HH:mm',
                  'tickLabelStyle': {'fontSize': 11},
                  'labelStyle': {'fontSize': 12}}]

    series_d = [
        {'type': 'line', 'id': 'discharge', 'label': 'Discharge Time',
         'data': dis_win, 'color': '#1976d2', 'showMark': False, 'connectNulls': False},
        {'type': 'scatter', 'id': 'events', 'label': 'Events',
         'data': ev_d, 'color': '#e53935', 'markerSize': 7},
    ]
    series_t = [
        {'type': 'line', 'id': 'temp', 'label': 'Temperature',
         'data': temp_win, 'color': '#e65100', 'showMark': False, 'connectNulls': False},
        {'type': 'scatter', 'id': 'events', 'label': 'Events',
         'data': ev_t, 'color': '#e53935', 'markerSize': 7},
    ]
    series_p = [
        {'type': 'line', 'id': 'init-pressure', 'label': 'Init Pressure',
         'data': initp_win, 'color': '#7b1fa2', 'showMark': False, 'connectNulls': False},
        {'type': 'line', 'id': 'min-pressure', 'label': 'Min Pressure',
         'data': minp_win, 'color': '#00838f', 'showMark': False, 'connectNulls': False},
        {'type': 'scatter', 'id': 'events', 'label': 'Events',
         'data': ev_p, 'color': '#e53935', 'markerSize': 7},
    ]

    # For pressure, merge init + min forecasts into a combined band
    # showing the wider envelope of both
    fc_pressure = []
    if fc_initp and fc_minp:
        for i in range(len(fc_initp)):
            ip, mp = fc_initp[i], fc_minp[i]
            fc_pressure.append({
                'x': ip['x'],
                'y': round((ip['y'] + mp['y']) / 2, 2),
                'upper': ip['upper'],
                'lower': mp['lower'],
            })

    # Latest values for stats
    last_d = store['discharge'][-1]
    last_t = store['temp'][-1]
    last_ip = store['init_pressure'][-1]
    last_mp = store['min_pressure'][-1]

    return (
        series_d, x_hidden, fc_discharge,
        series_t, x_hidden, fc_temp,
        series_p, x_visible, fc_pressure,
        f"{n:,}",
        f"{last_d:.2f}s",
        f"{last_t:.1f}C",
        f"{last_ip:.1f}",
        f"{last_mp:.1f}",
    )


# Live sync callback
@callback(
    Output('bp-live-discharge', 'highlightedAxis'),
    Output('bp-live-temp', 'highlightedAxis'),
    Output('bp-live-pressure', 'highlightedAxis'),
    Output('bp-live-discharge', 'syncedTooltipIndex'),
    Output('bp-live-temp', 'syncedTooltipIndex'),
    Output('bp-live-pressure', 'syncedTooltipIndex'),
    Input('bp-live-discharge', 'highlightedAxis'),
    Input('bp-live-temp', 'highlightedAxis'),
    Input('bp-live-pressure', 'highlightedAxis'),
    State('bp-live-sync', 'checked'),
    prevent_initial_call=True,
)
def live_sync(ax_d, ax_t, ax_p, synced):
    source = ctx.triggered_id
    ids = ['bp-live-discharge', 'bp-live-temp', 'bp-live-pressure']
    axes = dict(zip(ids, [ax_d, ax_t, ax_p]))
    axis = axes.get(source, ax_d)
    idx = axis[0]['dataIndex'] if axis and len(axis) > 0 else -1
    if not synced:
        return axis, axis, axis, -1, -1, -1
    tips = [idx if cid != source else -1 for cid in ids]
    return axis, axis, axis, tips[0], tips[1], tips[2]


# ===========================================================================
# Static range sync callbacks
# ===========================================================================
def make_sync_callback(range_key):
    prefix = f'bp-{range_key}'
    ids = [f'{prefix}-discharge', f'{prefix}-temp', f'{prefix}-pressure']

    @callback(
        *[Output(cid, 'highlightedAxis') for cid in ids],
        *[Output(cid, 'syncedTooltipIndex') for cid in ids],
        *[Input(cid, 'highlightedAxis') for cid in ids],
        State(f'{prefix}-sync', 'checked'),
        prevent_initial_call=True,
    )
    def sync_charts(*args):
        ax_d, ax_t, ax_p, synced = args[0], args[1], args[2], args[3]
        source = ctx.triggered_id
        axes_map = dict(zip(ids, [ax_d, ax_t, ax_p]))
        axis = axes_map.get(source, ax_d)
        idx = axis[0]['dataIndex'] if axis and len(axis) > 0 else -1
        if not synced:
            return axis, axis, axis, -1, -1, -1
        tips = [idx if cid != source else -1 for cid in ids]
        return axis, axis, axis, tips[0], tips[1], tips[2]

    sync_charts.__name__ = f'sync_{range_key}'
    return sync_charts


for _range_key in RANGE_PROFILES:
    make_sync_callback(_range_key)

:defaultExpanded: false :withExpandedButton: true


Source: /composite-render-bp

Note for AI agents: This is the static, prerendered view of an interactive Dash application served because we detected a non-JS user agent. Full prose docs: