6 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
- Limit time ranges - Don’t query more data than needed
- Use materialized views - Pre-aggregate for common queries
- Set appropriate refresh - Balance freshness and performance
- Design for mobile - Test on different screen sizes
- Use parameters - Enable user customization
What’s Next
Tomorrow I’ll cover Data Activator GA and automated actions.