Skip to content
Back to Blog
1 min read

Real-Time Dashboards in Microsoft Fabric

Real-time dashboards bring your streaming data to life. Today I’m exploring how to build effective real-time visualizations in Fabric.

Dashboard Components

Real-Time Dashboard:
├── Tiles (visualizations)
├── Parameters (filters)
├── Auto-refresh settings
├── Data sources (Eventhouses, KQL queries)
└── Sharing and permissions

Creating a Dashboard

Basic Structure

# Create Real-Time Dashboard via Fabric REST API
import requests
from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()
token = credential.get_token("https://api.fabric.microsoft.com/.default")

headers = {
    "Authorization": f"Bearer {token.token}",
    "Content-Type": "application/json"
}

workspace_id = "your-workspace-id"
url = f"https://api.fabric.microsoft.com/v1/workspaces/{workspace_id}/items"

# Create dashboard
dashboard_payload = {
    "displayName": "Operations Dashboard",
    "type": "Dashboard",
    "definition": {
        "pages": [
            {"name": "Overview", "tiles": []},
            {"name": "Details", "tiles": []}
        ],
        "autoRefresh": {"enabled": True, "defaultInterval": "1m"}
    }
}

response = requests.post(url, headers=headers, json=dashboard_payload)
dashboard_id = response.json().get("id")
print(f"Created dashboard: {dashboard_id}")

Adding Tiles

# Add metric tile
metric_tile = {
    "type": "stat",
    "title": "Active Devices",
    "position": {"x": 0, "y": 0, "width": 4, "height": 2},
    "query": {
        "database": "IoTAnalytics",
        "query": """
            SensorData
            | where EventTime > ago(5m)
            | summarize dcount(DeviceId)
        """
    },
    "visualization": {
        "type": "stat",
        "colorMode": "value",
        "thresholds": [
            {"value": 0, "color": "red"},
            {"value": 50, "color": "yellow"},
            {"value": 100, "color": "green"}
        ]
    }
}

# Add time chart tile
timechart_tile = {
    "type": "timechart",
    "title": "Sensor Values (Last Hour)",
    "position": {"x": 4, "y": 0, "width": 8, "height": 4},
    "query": {
        "database": "IoTAnalytics",
        "query": """
            SensorData
            | where EventTime > ago(1h)
            | summarize avg(Value) by bin(EventTime, 1m), DeviceId
        """
    },
    "visualization": {
        "type": "linechart",
        "xAxis": {"label": "Time"},
        "yAxis": {"label": "Value"},
        "legend": {"position": "right"}
    }
}

# Add table tile
table_tile = {
    "type": "table",
    "title": "Recent Alerts",
    "position": {"x": 0, "y": 4, "width": 12, "height": 4},
    "query": {
        "database": "IoTAnalytics",
        "query": """
            SensorData
            | where EventTime > ago(1h)
            | where Value > 100 or Value < 0
            | project EventTime, DeviceId, Value, SensorType
            | order by EventTime desc
            | take 20
        """
    },
    "visualization": {
        "type": "table",
        "columns": [
            {"field": "EventTime", "header": "Time", "format": "datetime"},
            {"field": "DeviceId", "header": "Device"},
            {"field": "Value", "header": "Value", "format": "number:2"},
            {"field": "SensorType", "header": "Type"}
        ]
    }
}

Parameter Configuration

# Define dashboard parameters
parameters = [
    {
        "name": "TimeRange",
        "type": "timespan",
        "displayName": "Time Range",
        "defaultValue": "1h",
        "options": [
            {"label": "Last 15 minutes", "value": "15m"},
            {"label": "Last hour", "value": "1h"},
            {"label": "Last 24 hours", "value": "24h"},
            {"label": "Last 7 days", "value": "7d"}
        ]
    },
    {
        "name": "DeviceFilter",
        "type": "string",
        "displayName": "Device",
        "defaultValue": "*",
        "source": {
            "type": "query",
            "query": "SensorData | distinct DeviceId | order by DeviceId"
        }
    },
    {
        "name": "SensorType",
        "type": "multi-select",
        "displayName": "Sensor Types",
        "defaultValue": ["temperature", "humidity"],
        "source": {
            "type": "query",
            "query": "SensorData | distinct SensorType"
        }
    }
]

# Use parameters in queries
parameterized_query = """
    SensorData
    | where EventTime > ago(['TimeRange'])
    | where DeviceId == ['DeviceFilter'] or ['DeviceFilter'] == "*"
    | where SensorType in (['SensorType'])
    | summarize avg(Value) by bin(EventTime, 5m), DeviceId
"""

Visualization Types

Statistical Tiles

// Single value
SensorData
| where EventTime > ago(5m)
| summarize avg(Value)

// With comparison
SensorData
| summarize
    Current = avgif(Value, EventTime > ago(1h)),
    Previous = avgif(Value, EventTime between (ago(2h) .. ago(1h)))
| extend Change = (Current - Previous) / Previous * 100

Time Charts

// Multi-series line chart
SensorData
| where EventTime > ago(24h)
| summarize avg(Value) by bin(EventTime, 15m), DeviceId
| render timechart with (
    title="Device Values Over Time",
    xtitle="Time",
    ytitle="Average Value"
)

// Area chart with stacking
SensorData
| where EventTime > ago(24h)
| summarize sum(Value) by bin(EventTime, 1h), SensorType
| render areachart with (kind=stacked)

Maps

// Geospatial visualization
SensorData
| where EventTime > ago(1h)
| extend Lat = todouble(Location.lat), Lon = todouble(Location.lon)
| summarize AvgValue = avg(Value), Count = count() by Lat, Lon, DeviceId
| render scatterchart with (kind=map)

Heatmaps

// Time-based heatmap
SensorData
| where EventTime > ago(7d)
| extend Hour = hourofday(EventTime), Day = dayofweek(EventTime)
| summarize avg(Value) by Hour, Day
| render columnchart with (kind=unstacked)

Auto-Refresh Configuration

auto_refresh_config = {
    "enabled": True,
    "minInterval": "30s",
    "defaultInterval": "1m",
    "maxInterval": "1h",
    "userOverride": True,
    "pauseOnInactivity": {
        "enabled": True,
        "timeout": "10m"
    }
}

Drill-Down and Cross-Filtering

# Configure cross-filtering
cross_filter_config = {
    "tiles": [
        {
            "id": "device-chart",
            "filterField": "DeviceId",
            "targetTiles": ["sensor-details", "alert-table"]
        },
        {
            "id": "time-selector",
            "filterField": "EventTime",
            "filterType": "range",
            "targetTiles": ["all"]
        }
    ]
}

# Drill-down configuration
drill_down_config = {
    "tile": "summary-chart",
    "levels": [
        {
            "field": "Location",
            "query": "SensorData | summarize count() by Location"
        },
        {
            "field": "Building",
            "query": "SensorData | where Location == [selected] | summarize count() by Building"
        },
        {
            "field": "DeviceId",
            "query": "SensorData | where Building == [selected] | summarize avg(Value) by DeviceId"
        }
    ]
}

Alerts from Dashboards

# Create alert based on tile
alert_config = {
    "name": "High Temperature Alert",
    "tile": "temperature-stat",
    "condition": {
        "type": "threshold",
        "operator": "greaterThan",
        "value": 100,
        "frequency": "1m"
    },
    "actions": [
        {
            "type": "email",
            "recipients": ["ops@company.com"]
        },
        {
            "type": "teams",
            "webhook": "https://outlook.office.com/webhook/..."
        }
    ]
}

Embedding Dashboards

<!-- Embed in web application -->
<iframe
    src="https://app.fabric.microsoft.com/embed/dashboard/{dashboard-id}?autoRefresh=true"
    frameborder="0"
    allowfullscreen="true"
    style="width: 100%; height: 600px;">
</iframe>

<!-- With authentication -->
<script>
async function embedDashboard() {
    const token = await getAccessToken();

    const embedConfig = {
        type: 'dashboard',
        id: 'dashboard-id',
        embedUrl: 'https://app.fabric.microsoft.com/embed/dashboard/...',
        accessToken: token,
        settings: {
            filterPaneEnabled: true,
            navContentPaneEnabled: false
        }
    };

    const dashboard = powerbi.embed(container, embedConfig);
}
</script>

Performance Optimization

Query Caching

tile_config = {
    "query": "...",
    "caching": {
        "enabled": True,
        "maxAge": "5m",
        "shareAcrossUsers": True
    }
}

Materialized Views for Dashboards

// Create materialized view for dashboard queries
.create materialized-view DashboardSummary on table SensorData
{
    SensorData
    | summarize
        AvgValue = avg(Value),
        MaxValue = max(Value),
        MinValue = min(Value),
        Count = count()
        by DeviceId, SensorType, bin(EventTime, 5m)
}

// Dashboard queries automatically use materialized view
// when query pattern matches

Best Practices

  1. Limit time ranges - Don’t query more data than needed
  2. Use materialized views - Pre-aggregate for common queries
  3. Set appropriate refresh - Balance freshness and performance
  4. Design for mobile - Test on different screen sizes
  5. Use parameters - Enable user customization

What’s Next

Tomorrow I’ll cover Data Activator GA and automated actions.

Resources

Michael John Peña

Michael John Peña

Data & AI Director and Microsoft MVP based in Sydney. Writing about Microsoft Fabric, Azure, data engineering, and AI.