Visualization and Interaction Enhancement
EnerOS v0.46.0 significantly enhances visualization and interaction capabilities, adding 3D scene glTF 2.0 export, natural language query engine (8 intent recognition), and PDF/Excel/Word report export. Operators can interact with grid data through natural language without learning complex query languages. The visualization capability is provided by the eneros-viz crate.
Visualization Architecture
┌──────────────────────────────────────────────────────────────┐
│ Interaction Layer │
│ Natural Language Query / Dashboard / Web UI / Mobile │
└────────┬─────────────────────────────────────────────────────┘
│
┌────────▼─────────────────────────────────────────────────────┐
│ Rendering Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 3D Scene │ │ Topology │ │ Charts │ │ Reports │ │
│ │ glTF 2.0 │ │ SVG/Canvas│ │ ECharts │ │ PDF/XLSX│ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└────────┬─────────────────────────────────────────────────────┘
│
┌────────▼─────────────────────────────────────────────────────┐
│ Data Layer │
│ Time-Series Database / Topology Model / Alerts / Agent State│
└──────────────────────────────────────────────────────────────┘
| Module | Output Format | Applicable |
|---|
| 3D Scene | glTF 2.0 / GLB | In-station inspection, equipment inspection |
| Natural Language Query | JSON / Text | Operations interaction |
| Report Export | PDF / Excel / Word | Report archival |
| Dashboard | HTML / WebSocket | Real-time monitoring |
| Topology Diagram | SVG / Canvas | Grid structure |
| Charts | PNG / SVG | Trend analysis |
3D Scene
Renders grid topology and device status as 3D scenes, supporting glTF 2.0 standard export, loadable in rendering engines such as Three.js / Babylon.js / Cesium:
use eneros_viz::scene::{Scene, SceneBuilder, GltfExporter, SubstationMesh, LineMesh, ColorScheme};
let mut scene = SceneBuilder::new()
.scale(Scale::kilometers())
.terrain(Terrain::from_dem("dem/srtm.tif"));
// Add substation
scene.add_substation(SubstationMesh::new("sub_a")
.position(31.23, 121.47, 0.0)
.voltage_level(220.0)
.equipment(EquipmentMesh::all()));
scene.add_substation(SubstationMesh::new("sub_b")
.position(31.25, 121.50, 0.0)
.voltage_level(220.0));
// Add line
scene.add_line(LineMesh::between("sub_a", "sub_b")
.voltage(220.0)
.load_ratio(0.65)
.conductor(ConductorType::Lgj400)
.towers(TowerPlacement::every(500.0)));
// Add device status
scene.add_device(DeviceMesh::transformer("t1")
.position_in("sub_a")
.load(0.78)
.temperature(65.0));
// Color by status
scene.color_by(ColorScheme::VoltageBand); // Color by voltage
// Add annotations
scene.annotation(Annotation::label("sub_a", "220kV Substation A"));
// Export glTF
let gltf = GltfExporter::export(&scene.build())
.embed_textures(true)
.draco_compression(true)
.version(GltfVersion::V2_0)?;
std::fs::write("grid.gltf", gltf)?;
Scene Hierarchy
Scene
├── Substations
│ ├── Bays
│ │ ├── Breakers
│ │ ├── Disconnectors
│ │ └── Transformers
│ └── Control House
├── Lines
│ ├── Towers
│ └── Conductors
├── Generators
├── Loads
└── Annotations
Color Schemes
| Scheme | Description | Color Mapping |
|---|
| VoltageBand | By voltage level | Red (>1.05) / Green (0.95-1.05) / Blue (<0.95) |
| LoadRatio | By load ratio | Red (>90%) / Yellow (70-90%) / Green (<70%) |
| DeviceStatus | By device status | Red (fault) / Yellow (warning) / Green (normal) |
| OutageArea | Outage area | Gray (power loss) / Colorful (normal) |
| RealtimeFlow | Real-time power flow | Flow direction animation |
Natural Language Query
8 intent recognition types, no need to learn query syntax, supports Chinese natural language:
use eneros_viz::nl::{NlQuery, Intent, NlResponse};
let response = NlQuery::ask("What was the bus 5 voltage at 10 AM yesterday?").await?;
match response.intent {
Intent::PointInTimeQuery => {
println!("Bus 5 10:00 voltage: {:.2} kV", response.value);
println!("SQL equivalent: {}", response.translated_sql);
}
Intent::RangeQuery => {
println!("Data points count: {}", response.data_points.len());
}
_ => {}
}
// Other example queries
let queries = vec![
"Which lines were overloaded in the past 24 hours?", // → AnomalySearch
"Compare today's load of transformer 1 and 2", // → Comparison
"Devices that may trip within 5 minutes", // → Prediction
"Generate last month's operation report", // → Report
"Why is bus 5 voltage low", // → RootCause
"Add 10MW to generator 3", // → Action
];
for q in queries {
let resp = NlQuery::ask(q).await?;
println!("'{}' → intent: {:?}", q, resp.intent);
}
Intent Matrix
| Intent | Description | Example | Output |
|---|
| PointInTimeQuery | Point-in-time query | Bus 5 voltage at 10 AM yesterday | Single value |
| RangeQuery | Range query | Frequency curve over past 1 hour | Time-series data |
| AnomalySearch | Anomaly search | Which devices are overloaded | Anomaly list |
| Comparison | Comparison analysis | Compare substations A and B | Comparison table |
| Prediction | Prediction query | Next hour load forecast | Prediction curve |
| Report | Report generation | Generate operation report | Document |
| RootCause | Root cause analysis | Why voltage is low | Causal chain |
| Action | Action execution | Add 10MW to generator 3 | Command confirmation |
Multi-turn Dialogue
use eneros_viz::nl::{NlConversation, Context};
let mut conversation = NlConversation::new()
.context(Context::new()
.user("alice")
.role("dispatcher"));
// First turn
let r1 = conversation.ask("What is bus 5 voltage?").await?;
// r1: Bus 5 current voltage 1.02 pu
// Second turn (context continuation)
let r2 = conversation.ask("What about the past 1 hour?").await?;
// r2: Bus 5 voltage curve over past 1 hour + statistics
// Third turn
let r3 = conversation.ask("Why is it low?").await?;
// r3: Root cause analysis + suggested actions
Report Export
Supports PDF, Excel, Word formats, with built-in power industry templates:
use eneros_viz::report::{Report, ReportFormat, Template, Chart, Range};
let report = Report::new()
.template(Template::daily_operation())
.period(Range::last(Duration::from_secs(86400)))
.data(daily_data)
.charts(vec![
Chart::load_curve()
.width(800).height(400),
Chart::voltage_profile()
.width(800).height(300),
Chart::frequency_deviation()
.width(800).height(300),
Chart::generation_mix()
.ty(ChartType::Pie),
])
.tables(vec![
Table::outage_statistics(),
Table::equipment_status(),
])
.metadata(Metadata::new()
.author("EnerOS")
.title("2026-07-06 Daily Report")
.confidentiality(Confidentiality::Internal));
// Export to different formats
report.export(ReportFormat::Pdf, "daily.pdf").await?;
report.export(ReportFormat::Excel, "daily.xlsx").await?;
report.export(ReportFormat::Word, "daily.docx").await?;
report.export(ReportFormat::Html, "daily.html").await?;
Report Templates
| Template | Content | Applicable |
|---|
| daily_operation | Daily operation report | Daily operations |
| weekly_summary | Weekly summary | Weekly report |
| monthly_review | Monthly review | Monthly report |
| incident_analysis | Incident analysis | Post-incident review |
| equipment_health | Equipment health | Maintenance planning |
| economic_dispatch | Economic dispatch | Dispatch optimization |
| compliance_audit | Compliance audit | Regulatory reporting |
Scheduled Reports
use eneros_viz::report::{ReportScheduler, Schedule, Day};
let scheduler = ReportScheduler::new()
.schedule("daily", Template::daily_operation(),
Schedule::daily(8, 0))
.schedule("weekly", Template::weekly_summary(),
Schedule::weekly(Day::Monday, 8, 0))
.schedule("monthly", Template::monthly_review(),
Schedule::monthly(1, 8, 0))
.recipients(vec!["ops@grid.com", "manager@grid.com"])
.format(ReportFormat::Pdf)
.subject("EnerOS {{template}} - {{date}}")
.on_failure(|err| async move {
notify_admin("Report generation failed").await
});
scheduler.start().await?;
Real-time Dashboard
use eneros_viz::dashboard::{Dashboard, Widget, Range, MapLayer, AlertFilter};
let dashboard = Dashboard::new("Real-time Monitoring")
.layout(Layout::grid(3, 3))
.widget(Widget::single_line("Frequency", "grid.frequency",
Range::last(Duration::from_secs(60))))
.widget(Widget::gauge("Total Load", "grid.total_load", 0.0, 1000.0)
.thresholds(vec![
Threshold::warn(800.0),
Threshold::critical(950.0),
]))
.widget(Widget::map("Grid Map", MapLayer::substations()
.zoom(8)
.center(31.23, 121.47)))
.widget(Widget::alert_list("Alerts", AlertFilter::active()
.severity_gte(Severity::Warn)))
.widget(Widget::bar_chart("Feeder Load", "feeder.load"))
.widget(Widget::heatmap("Device Temperature", "device.temperature"))
.widget(Widget::topology("Topology", TopologyConfig::auto()))
.widget(Widget::status_grid("Device Status", DeviceFilter::all()))
.refresh(Duration::from_secs(1));
dashboard.serve("0.0.0.0:8080").await?;
| Widget | Data Type | Update Frequency |
|---|
| single_line | Time-series | 1s |
| gauge | Real-time value | 1s |
| bar_chart | Discrete values | 5s |
| pie_chart | Proportions | 30s |
| map | Geographic | 5s |
| topology | Topology | Event |
| heatmap | Matrix | 5s |
| alert_list | Alerts | Real-time |
| status_grid | Status | 5s |
| table | Table | 30s |
| 3d_scene | 3D | On-demand |
Data Visualization Types
| Type | Purpose | Interaction |
|---|
| Single value card | KPI monitoring | Drill-down |
| Line chart | Time-series data | Zoom / Select |
| Bar chart | Comparison analysis | Drill-down |
| Pie chart | Proportion distribution | Drill-down |
| Map | Geographic distribution | Click |
| Topology diagram | Grid structure | Drag / Drill-down |
| Heatmap | Load distribution | Select |
| 3D scene | In-station inspection | Rotate / Roam |
| Scatter plot | Correlation analysis | Select |
| Sankey diagram | Energy flow | Drill-down |
| Operation | Latency | Remarks |
|---|
| glTF scene generation (1000 devices) | < 500ms | Including Draco compression |
| glTF scene generation (10000 devices) | < 3s | Including Draco compression |
| Natural language intent recognition | < 200ms | LLM call |
| Natural language query execution | < 1s | Including SQL translation |
| PDF report generation (with charts) | < 2s | A4 10 pages |
| Excel report generation (10k rows) | < 1s | Including formatting |
| Word report generation | < 3s | Including charts |
| Dashboard refresh (10 widgets) | < 100ms | WebSocket push |
| Topology rendering (1000 nodes) | < 500ms | SVG |
| Multi-turn dialogue response | < 2s | Including context |
Relationship with Other Capabilities