IoT Data Visualization
Raw numbers in a database are not useful until they are visualized. Good dashboards let operators spot trends, anomalies, and patterns at a glance. This topic covers Grafana (production dashboards), Node-RED (visual pipelines), and Python-based custom dashboards.
Grafana — Production Dashboards
Grafana is the industry-standard open-source dashboarding tool. It connects directly to InfluxDB, Prometheus, PostgreSQL, and dozens of other data sources.
Installation
# Docker (fastest way to start)
docker run -d \
--name=grafana \
-p 3000:3000 \
-v grafana-storage:/var/lib/grafana \
grafana/grafana-oss
# Access: http://localhost:3000
# Default login: admin / adminConnecting Grafana to InfluxDB
Grafana → Configuration → Data Sources → Add data source → InfluxDB
Settings:
Query Language: Flux
URL: http://localhost:8086
Auth: Basic auth → ON
Organization: bigxstar
Token: your-influxdb-api-token
Default Bucket: iot_sensors
Click "Save & Test" — should show "Data source connected"Flux Queries for Panels
// Panel 1: Current temperature gauge
from(bucket: "iot_sensors")
|> range(start: -5m)
|> filter(fn: (r) => r._measurement == "environment")
|> filter(fn: (r) => r._field == "temperature")
|> filter(fn: (r) => r.device_id == "sensor-01")
|> last()
// Panel 2: Temperature time-series (last 24 hours)
from(bucket: "iot_sensors")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "environment")
|> filter(fn: (r) => r._field == "temperature")
|> aggregateWindow(every: 5m, fn: mean, createEmpty: false)
// Panel 3: Min/Max/Avg stat panel
from(bucket: "iot_sensors")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "environment")
|> filter(fn: (r) => r._field == "temperature")
|> reduce(fn: (r, accumulator) => ({
min: if r._value < accumulator.min then r._value else accumulator.min,
max: if r._value > accumulator.max then r._value else accumulator.max,
sum: accumulator.sum + r._value,
count: accumulator.count + 1
}), identity: {min: 9999.0, max: -9999.0, sum: 0.0, count: 0})
// Panel 4: Multi-device comparison
from(bucket: "iot_sensors")
|> range(start: -6h)
|> filter(fn: (r) => r._field == "temperature")
|> group(columns: ["device_id"])
|> aggregateWindow(every: 10m, fn: mean)Grafana Alerting
// Alert rule configuration (via UI or API)
{
"name": "High Temperature Alert",
"condition": {
"type": "threshold",
"query": "last(temperature) WHERE device_id = 'sensor-01'",
"threshold": {
"above": 35.0
},
"for": "5m"
},
"notifications": [
{
"type": "email",
"to": "admin@bigxstar.com",
"subject": "High Temperature: {{ device_id }}"
},
{
"type": "webhook",
"url": "https://hooks.slack.com/your-webhook"
}
]
}Node-RED — Visual IoT Pipelines
Node-RED is a flow-based programming tool that connects IoT devices, cloud services, and databases through a drag-and-drop interface. It runs on Node.js and is pre-installed on Raspberry Pi OS.
# Install Node-RED
npm install -g --unsafe-perm node-red
# Start
node-red
# Access: http://localhost:1880Common Node-RED Flow
[MQTT in] → [JSON parse] → [Function] → [InfluxDB out]
→ [Dashboard gauge]
→ [Debug]
Example flow JSON (import via Node-RED menu):[
{
"id": "mqtt_in",
"type": "mqtt in",
"broker": "mqtt-broker-config",
"topic": "sensors/#",
"qos": "1"
},
{
"id": "json_parse",
"type": "json",
"action": "obj"
},
{
"id": "validate",
"type": "function",
"func": "// Validate and enrich message\nif (!msg.payload.temperature) { return null; }\n\nmsg.payload.received_at = new Date().toISOString();\n\n// Route high temp to alert output\nif (msg.payload.temperature > 35) {\n return [msg, msg]; // two outputs\n}\nreturn [msg, null];\n",
"outputs": 2
},
{
"id": "influx_out",
"type": "influxdb out",
"measurement": "environment"
},
{
"id": "alert_email",
"type": "e-mail",
"to": "admin@example.com",
"subject": "High Temperature Alert"
}
]Python Streamlit — Custom Dashboards in Minutes
Streamlit lets you build live-updating dashboards entirely in Python.
# pip install streamlit plotly influxdb-client pandas
# Run with: streamlit run dashboard.py
import streamlit as st
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
from influxdb_client import InfluxDBClient
from datetime import datetime
import time
# Page config
st.set_page_config(
page_title="IoT Dashboard",
page_icon="📡",
layout="wide"
)
@st.cache_resource
def get_influx_client():
return InfluxDBClient(
url="http://localhost:8086",
token="your-token",
org="bigxstar"
)
def query_sensor_data(device_id, hours=24):
client = get_influx_client()
query = f'''
from(bucket: "iot_sensors")
|> range(start: -{hours}h)
|> filter(fn: (r) => r._measurement == "environment")
|> filter(fn: (r) => r.device_id == "{device_id}")
|> filter(fn: (r) => r._field == "temperature" or r._field == "humidity")
|> aggregateWindow(every: 5m, fn: mean, createEmpty: false)
|> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value")
'''
df = client.query_api().query_data_frame(query, org="bigxstar")
if isinstance(df, list):
df = pd.concat(df)
return df
# Dashboard layout
st.title("🌡️ IoT Sensor Dashboard")
st.markdown(f"*Last updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*")
# Sidebar controls
device_id = st.sidebar.selectbox("Device", ["sensor-01", "sensor-02", "sensor-03"])
time_range = st.sidebar.slider("Hours", 1, 72, 24)
auto_refresh = st.sidebar.toggle("Auto-refresh (30s)", value=True)
# Fetch data
df = query_sensor_data(device_id, hours=time_range)
if df.empty:
st.warning("No data available for the selected period.")
else:
# Metric cards
col1, col2, col3, col4 = st.columns(4)
with col1:
latest_temp = df['temperature'].iloc[-1]
st.metric("Current Temp", f"{latest_temp:.1f} °C",
delta=f"{latest_temp - df['temperature'].iloc[-2]:.1f}")
with col2:
latest_hum = df['humidity'].iloc[-1]
st.metric("Current Humidity", f"{latest_hum:.1f} %")
with col3:
st.metric("Max Temp (period)", f"{df['temperature'].max():.1f} °C")
with col4:
st.metric("Min Temp (period)", f"{df['temperature'].min():.1f} °C")
# Temperature chart
fig_temp = px.line(df, x="_time", y="temperature",
title=f"Temperature — {device_id}",
labels={"_time": "Time", "temperature": "°C"})
fig_temp.add_hline(y=35, line_dash="dash", line_color="red",
annotation_text="Alert threshold (35°C)")
st.plotly_chart(fig_temp, use_container_width=True)
# Temperature + Humidity dual axis
fig = go.Figure()
fig.add_trace(go.Scatter(x=df['_time'], y=df['temperature'],
name="Temperature (°C)", yaxis="y1"))
fig.add_trace(go.Scatter(x=df['_time'], y=df['humidity'],
name="Humidity (%)", yaxis="y2",
line=dict(color="orange")))
fig.update_layout(
title="Temperature & Humidity",
yaxis=dict(title="Temperature (°C)"),
yaxis2=dict(title="Humidity (%)", overlaying="y", side="right")
)
st.plotly_chart(fig, use_container_width=True)
# Raw data table
with st.expander("Raw Data"):
st.dataframe(df[["_time", "temperature", "humidity"]].tail(50))
# Auto-refresh
if auto_refresh:
time.sleep(30)
st.rerun()Flask + Chart.js — Lightweight Custom Dashboard
# pip install flask influxdb-client
from flask import Flask, render_template, jsonify
from influxdb_client import InfluxDBClient
app = Flask(__name__)
influx = InfluxDBClient(url="http://localhost:8086", token="token", org="bigxstar")
@app.route('/')
def dashboard():
return render_template('dashboard.html')
@app.route('/api/readings/<device_id>')
def get_readings(device_id):
query = f'''
from(bucket: "iot_sensors")
|> range(start: -1h)
|> filter(fn: (r) => r.device_id == "{device_id}")
|> filter(fn: (r) => r._field == "temperature")
|> aggregateWindow(every: 1m, fn: last)
'''
tables = influx.query_api().query(query, org="bigxstar")
data = []
for table in tables:
for record in table.records:
data.append({
"time": record.get_time().isoformat(),
"value": round(record.get_value(), 2)
})
return jsonify(data)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)Summary
- Grafana is the go-to tool for production IoT dashboards — rich panels, alerting, and dozens of data source plugins
- Node-RED enables visual flow-based IoT pipelines — connect MQTT to databases and dashboards without writing a single function
- Streamlit lets Python developers build interactive dashboards in minutes using just Python code
- Connect Grafana to InfluxDB using Flux queries for powerful time-series analysis
- Configure Grafana alerts to notify via email, Slack, or webhook when thresholds are exceeded
- Always enable auto-refresh on production dashboards and set appropriate refresh intervals (30s–5min)
Next, we'll explore edge computing — running intelligence on the device rather than the cloud.