This project combines a Unity 3D scene with Grafana dashboards to create an interactive digital twin interface. A 3D model of an object (e.g. a solar panel) is embedded in Grafana — clicking on it selects the corresponding digital twin in Eclipse Ditto, and the connected dashboards update to show its live data.
Start by creating a new project in Unity Hub — select the 3D (URP) template. Once the project opens, you will see the Scene view (the 3D viewport), the Hierarchy panel on the left (lists all objects in the scene), the Inspector panel on the right (shows properties of the selected object), and the Project panel at the bottom (your file browser for assets).
Import your 3D model (e.g. a solar panel) by dragging the file into the Project panel. Then drag it from the Project panel into the Scene view to place it in the scene. It will also appear in the Hierarchy.
Grafana bridge
The scene communicates with Grafana via a JavaScript bridge. In the Project panel, create the folder path Assets/Plugins/WebGL/ and place the file GrafanaBridge.jslib inside it. This file defines the JavaScript functions that allow Unity and Grafana to exchange data when the build runs in the browser.
Scripts
Scripts define how objects behave. In the Project panel, create a folder /my_scripts. To create a new script, right click inside it → Create → Scripting → Empty C# Script. Each script is a C# class — you write methods inside it that Unity calls automatically at the right moment. All scripts needed for this project are already provided in the /my_scripts folder of this repository, but you are free to write your own.
The key method to implement is onMouseDown, which Unity calls when the user clicks the object. It should send the object's thingId to Grafana (via GrafanaBridge.jslib) and visually highlight the object. You can use the already written script PartLogic.cs for this.
To attach a script to an object, select the object in the Hierarchy, then in the Inspector click Add Component and search for your script name. For click detection to work, the object also needs a collider — add a Box Collider the same way via Add Component.
Each object has a thingId field marked as [SerializeField], which means it appears in the Inspector — this functionality is implemented in PartLogic.cs. Select the object, find the script component in the Inspector, and type the thingId value directly there — no code change needed. The value must exactly match the thing ID used in the Ditto API call (e.g. org.eclipse.ditto:pv-panel-01).
Camera rotation
To allow the user to orbit the camera around the scene, select Main Camera in the Hierarchy. In the Inspector, click Add Component and add the OrbitCamera.cs script. A Target field will appear — select your 3D object.
Additional scripts
Two more scripts complete the interaction: SelectionManager.cs (attach to an empty GameObject in the Hierarchy — it manages which object is currently highlighted) and NameLabel.cs (attach to each object to show its name as a label in the scene - font size and offset are [SerializeField] needed to be adjusted to your scene).
Once the scene is ready, build it for the web. Go to File → Build Profiles, select Web as the platform, and make sure your scene is added. In Player Settings, set Compression Format to Disabled (required for Grafana to load the files) and enable Run In Background so the scene stays active when the Grafana panel loses focus. Then build.
Grafana is run as a Docker container with two volume mounts. The mounts are important: one exposes your local plugins folder to Grafana (containing the Unity panel plugin), and the other makes your WebGL build accessible via Grafana's public file server.
Before running the container, create two local folders: grafana-plugins (contains the Unity panel plugin cloned from https://github.com/ertis-research/grafana-panel-unity.git) and grafana-public (will hold the Unity WebGL build output).
docker run -d \
-p 3000:3000 \
--name grafana \
-v <path-to-grafana-plugins>:/var/lib/grafana/plugins \
-v <path-to-grafana-public>:/usr/share/grafana/public/unity \
-e GF_DEFAULT_APP_MODE=development \
-e GF_INSTALL_PLUGINS=yesoreyeram-infinity-datasource \
grafana/grafanaOnce running, Grafana is available at http://localhost:3000.
Grafana reads digital twin data from Eclipse Ditto using the Infinity datasource plugin, which allows Grafana to make HTTP requests to any REST API.
In Grafana, go to Connections → Data sources → Add data source → Infinity. Name it Ditto and enable Basic Authentication with credentials ditto / ditto. Add http://host.docker.internal:8080/api/2/things/* to the allowed hosts so Grafana can query any thing in Ditto. Save & Test.
To make the Unity WebGL build accessible, copy the build output into Grafana's public folder:
grafana-public/WebGL/
Grafana serves this folder at the URL path /public/unity/WebGL/ — this is how the Unity panel plugin will load the scene.
In Grafana create a new dashboard and add a panel with the Unity visualization. In the Unity model settings, set Mode to Grafana public folder and fill in the WebGL file paths:
.data:/public/unity/WebGL/Build/WebGL.data- (set remaining paths accordingly:
.wasm,.js,.loader.js)
To capture which object the user clicked, first add a dashboard variable: Dashboard → Settings → Variables → New variable, Type: Textbox, Name: thingId, Label: selectedThing. Then in the panel, configure the Receive data from Unity section:
- Event name:
partClicked(must match the name inGrafanaBridge.jslib) - Type:
text - Mode:
store value in Grafana variable - Variable name:
selectedThing
Save the dashboard. At this point, clicking an object in the Unity scene should highlight it and update the selectedThing variable with its thingId.
To display thing data, there are two panel options. A Stat panel can be used if all your things share the same features. Use a Table panel if your things have different or unknown features, as it dynamically shows all properties without any manual configuration, but does not support per-field unit formatting.
In both add query datasource Ditto with Method: GET and URL: http://host.docker.internal:8080/api/2/things/${thingId}.
Option 1 — Stat panel (when all things have the same features): add a Stat panel and manually define each column in Parsing options & Results field. To set units: editor → Add field override → Fields with name → add override property → Unit.
Option 2 — Table panel (works for any thing structure): add a Table panel and use the following JSONata expression in Parsing options → Rows/Root to dynamically flatten all feature properties into rows:
[$merge($each($.features, function($v, $k) {
$merge($each($v.properties, function($pv, $pk) {
{$pk: $pv}
}))
}))]