Creating sankey diagram with influxdb data in grafana

HIi everyone,

I am new to use this environment. I want to create sankey diagram for energy produced and consumed from sensors data stored at influxdb. I am not able to configure the data for sankey panel in grafana. Does anyone has any example how can we import the data for sankey from influxdb? I want to know how we can group data in 2 groups to create sankey diagram.

Any help would be appreciated. Thank you for your time and help.

Best regards,
Parth Rajnikant Suhagiya

@parthrsuhagiya, You can try to use transformation to join data in multiple queries (groups) for the Sankey panel.

Alternatively, take a look at the Apache ECharts, which support the Sankey diagram and require basic javascript knowledge to prepare data in a proper format.

1 Like

Hii @mikhailvolkov ,

I tried the join by the field transformation and it helps to see the Sankey diagram. But it only groups data by time. I want to group it in source and target. You can refer the screenshot attached below. I want the source as value 611,616,894. and the target value as 87,88,89,915. Now it will take all the columns as source and target. How can i convert this table as I wanted? I already tried series to rows transformation , but it doesn’t work for me. Do you know the way to solve this?

Best regards,
Parth Suhagiya

1 Like

Same with me, does anyone know the solution?

Hi,
I am also trying to create a sankey diagram for our energy dashboard.
The volkovlabs business chart seems to be the most versatile sankey available but I need to get the data formatted.

I will be able to get my data to look like the following flux query. With the source and destinations to create alle the required nodes and links.

Flux data example
import "array"
import "experimental"
import "internal/debug"

data = array.from(rows: [
  {Source: "Net", Destination: "IT", measurement: "MCC0801001", _field: "E", _value: 9000.0},
  {Source: "IT", Destination: "Motor 1", measurement: "MCC0801002", _field: "E", _value: 1000.0},
  {Source: "IT", Destination: "Motor 2", measurement: "MCC0801003", _field: "E", _value: 1000.0},
  {Source: "IT", Destination: "Motor 3", measurement: "MCC0801004", _field: "E", _value: 1000.0},
  {Source: "IT", Destination: "Motor 4", measurement: "MCC0801005", _field: "E", _value: 1000.0},
  {Source: "Net", Destination: "TNS", measurement: "MCC0801006", _field: "E", _value: 3000.0},
  {Source: "TNS", Destination: "Tracing", measurement: "MCC0801007", _field: "E", _value: 30.0},
  {Source: "TNS", Destination: "Verlichting", measurement: "MCC0801008", _field: "E", _value: 30.0},
  {Source: "TNS", Destination: "LS SHR", measurement: "MCC0801009", _field: "E", _value: 30.0},
  {Source: "TNS", Destination: "LS N2 SOL", measurement: "MCC0801010", _field: "E", _value: 30.0},
  {Source: "TNS", Destination: "LS Brand", measurement: "MCC0801011", _field: "E", _value: 30.0},
  {Source: "TNS", Destination: "UPS", measurement: "MCC0801012", _field: "E", _value: 30.0},
  {Source: "TNS", Destination: "VH1", measurement: "MCC0801013", _field: "E", _value: 30.0},
  {Source: "TNS", Destination: "VH2", measurement: "MCC0801014", _field: "E", _value: 30.0},
  {Source: "TNS", Destination: "PH", measurement: "MCC0801015", _field: "E", _value: 30.0},
  {Source: "TNS", Destination: "Spoel", measurement: "MCC0801016", _field: "E", _value: 30.0},
  {Source: "UPS", Destination: "24V", measurement: "MCC0801017", _field: "E", _value: 30.0},
  {Source: "24V", Destination: "PLC SOL", measurement: "MCC0801018", _field: "E", _value: 10.0},
  {Source: "24V", Destination: "PLC WZI", measurement: "MCC0801019", _field: "E", _value: 20.0},
])

data
  |> yield(name: "mean")

And I also have enough knowledge of javascript to transform the data into an object that is required:

Return object for chart
return {
  series: {
    type: 'sankey',
    layout: 'none',
    emphasis: {
      focus: 'adjacency'
    },
    data: [
      {
        name: 'a'
      },
      {
        name: 'a1'
      }
    ],
    links: [
      {
        source: 'a',
        target: 'a1',
        value: 5
      }
    ]
  }
};

This text will be hidden

But to to this, I need to know the JSON structure of context.panel.data.series. I already tried the guessing game but with no result so far.

The JSON returned by the query looks like this but has no mention of data or series.

Query result JSON
{
  "request": {
    "url": "api/ds/query?ds_type=influxdb&requestId=Q108",
    "method": "POST",
    "data": {
      "queries": [
        {
          "datasource": {
            "type": "influxdb",
            "uid": "d2e8207f-5c27-4d75-917f-1d18a39e5ecf"
          },
          "hide": false,
          "query": "import \"array\"\r\nimport \"experimental\"\r\nimport \"internal/debug\"\r\n\r\ndata = array.from(rows: [\r\n  {Source: \"Net\", Destination: \"IT\", measurement: \"MCC0801001\", _field: \"E\", _value: 9000.0},\r\n  {Source: \"IT\", Destination: \"Motor 1\", measurement: \"MCC0801002\", _field: \"E\", _value: 1000.0},\r\n  {Source: \"IT\", Destination: \"Motor 2\", measurement: \"MCC0801003\", _field: \"E\", _value: 1000.0},\r\n  {Source: \"IT\", Destination: \"Motor 3\", measurement: \"MCC0801004\", _field: \"E\", _value: 1000.0},\r\n  {Source: \"IT\", Destination: \"Motor 4\", measurement: \"MCC0801005\", _field: \"E\", _value: 1000.0},\r\n  {Source: \"Net\", Destination: \"TNS\", measurement: \"MCC0801006\", _field: \"E\", _value: 3000.0},\r\n  {Source: \"TNS\", Destination: \"Tracing\", measurement: \"MCC0801007\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"Verlichting\", measurement: \"MCC0801008\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"LS SHR\", measurement: \"MCC0801009\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"LS N2 SOL\", measurement: \"MCC0801010\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"LS Brand\", measurement: \"MCC0801011\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"UPS\", measurement: \"MCC0801012\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"VH1\", measurement: \"MCC0801013\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"VH2\", measurement: \"MCC0801014\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"PH\", measurement: \"MCC0801015\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"Spoel\", measurement: \"MCC0801016\", _field: \"E\", _value: 30.0},\r\n  {Source: \"UPS\", Destination: \"24V\", measurement: \"MCC0801017\", _field: \"E\", _value: 30.0},\r\n  {Source: \"24V\", Destination: \"PLC SOL\", measurement: \"MCC0801018\", _field: \"E\", _value: 10.0},\r\n  {Source: \"24V\", Destination: \"PLC WZI\", measurement: \"MCC0801019\", _field: \"E\", _value: 20.0},\r\n])\r\n\r\ndata\r\n  |> yield(name: \"mean\")",
          "refId": "A",
          "policy": "",
          "datasourceId": 7,
          "intervalMs": 20000,
          "maxDataPoints": 1090
        }
      ],
      "range": {
        "from": "2025-08-01T08:54:12.797Z",
        "to": "2025-08-01T14:54:12.797Z",
        "raw": {
          "from": "now-6h",
          "to": "now"
        }
      },
      "from": "1754038452797",
      "to": "1754060052797"
    },
    "hideFromInspector": false
  },
  "response": {
    "results": {
      "A": {
        "status": 200,
        "frames": [
          {
            "schema": {
              "refId": "A",
              "meta": {
                "typeVersion": [
                  0,
                  0
                ],
                "executedQueryString": "import \"array\"\r\nimport \"experimental\"\r\nimport \"internal/debug\"\r\n\r\ndata = array.from(rows: [\r\n  {Source: \"Net\", Destination: \"IT\", measurement: \"MCC0801001\", _field: \"E\", _value: 9000.0},\r\n  {Source: \"IT\", Destination: \"Motor 1\", measurement: \"MCC0801002\", _field: \"E\", _value: 1000.0},\r\n  {Source: \"IT\", Destination: \"Motor 2\", measurement: \"MCC0801003\", _field: \"E\", _value: 1000.0},\r\n  {Source: \"IT\", Destination: \"Motor 3\", measurement: \"MCC0801004\", _field: \"E\", _value: 1000.0},\r\n  {Source: \"IT\", Destination: \"Motor 4\", measurement: \"MCC0801005\", _field: \"E\", _value: 1000.0},\r\n  {Source: \"Net\", Destination: \"TNS\", measurement: \"MCC0801006\", _field: \"E\", _value: 3000.0},\r\n  {Source: \"TNS\", Destination: \"Tracing\", measurement: \"MCC0801007\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"Verlichting\", measurement: \"MCC0801008\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"LS SHR\", measurement: \"MCC0801009\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"LS N2 SOL\", measurement: \"MCC0801010\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"LS Brand\", measurement: \"MCC0801011\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"UPS\", measurement: \"MCC0801012\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"VH1\", measurement: \"MCC0801013\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"VH2\", measurement: \"MCC0801014\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"PH\", measurement: \"MCC0801015\", _field: \"E\", _value: 30.0},\r\n  {Source: \"TNS\", Destination: \"Spoel\", measurement: \"MCC0801016\", _field: \"E\", _value: 30.0},\r\n  {Source: \"UPS\", Destination: \"24V\", measurement: \"MCC0801017\", _field: \"E\", _value: 30.0},\r\n  {Source: \"24V\", Destination: \"PLC SOL\", measurement: \"MCC0801018\", _field: \"E\", _value: 10.0},\r\n  {Source: \"24V\", Destination: \"PLC WZI\", measurement: \"MCC0801019\", _field: \"E\", _value: 20.0},\r\n])\r\n\r\ndata\r\n  |> yield(name: \"mean\")"
              },
              "fields": [
                {
                  "name": "Source",
                  "type": "string",
                  "typeInfo": {
                    "frame": "string",
                    "nullable": true
                  },
                  "labels": {}
                },
                {
                  "name": "Destination",
                  "type": "string",
                  "typeInfo": {
                    "frame": "string",
                    "nullable": true
                  },
                  "labels": {}
                },
                {
                  "name": "measurement",
                  "type": "string",
                  "typeInfo": {
                    "frame": "string",
                    "nullable": true
                  },
                  "labels": {}
                },
                {
                  "name": "_field",
                  "type": "string",
                  "typeInfo": {
                    "frame": "string",
                    "nullable": true
                  },
                  "labels": {}
                },
                {
                  "name": "_value",
                  "type": "number",
                  "typeInfo": {
                    "frame": "float64",
                    "nullable": true
                  },
                  "labels": {}
                }
              ]
            },
            "data": {
              "values": [
                [
                  "Net",
                  "IT",
                  "IT",
                  "IT",
                  "IT",
                  "Net",
                  "TNS",
                  "TNS",
                  "TNS",
                  "TNS",
                  "TNS",
                  "TNS",
                  "TNS",
                  "TNS",
                  "TNS",
                  "TNS",
                  "UPS",
                  "24V",
                  "24V"
                ],
                [
                  "IT",
                  "Motor 1",
                  "Motor 2",
                  "Motor 3",
                  "Motor 4",
                  "TNS",
                  "Tracing",
                  "Verlichting",
                  "LS SHR",
                  "LS N2 SOL",
                  "LS Brand",
                  "UPS",
                  "VH1",
                  "VH2",
                  "PH",
                  "Spoel",
                  "24V",
                  "PLC SOL",
                  "PLC WZI"
                ],
                [
                  "MCC0801001",
                  "MCC0801002",
                  "MCC0801003",
                  "MCC0801004",
                  "MCC0801005",
                  "MCC0801006",
                  "MCC0801007",
                  "MCC0801008",
                  "MCC0801009",
                  "MCC0801010",
                  "MCC0801011",
                  "MCC0801012",
                  "MCC0801013",
                  "MCC0801014",
                  "MCC0801015",
                  "MCC0801016",
                  "MCC0801017",
                  "MCC0801018",
                  "MCC0801019"
                ],
                [
                  "E",
                  "E",
                  "E",
                  "E",
                  "E",
                  "E",
                  "E",
                  "E",
                  "E",
                  "E",
                  "E",
                  "E",
                  "E",
                  "E",
                  "E",
                  "E",
                  "E",
                  "E",
                  "E"
                ],
                [
                  9000,
                  1000,
                  1000,
                  1000,
                  1000,
                  3000,
                  30,
                  30,
                  30,
                  30,
                  30,
                  30,
                  30,
                  30,
                  30,
                  30,
                  30,
                  10,
                  20
                ]
              ]
            }
          }
        ],
        "refId": "A"
      }
    }
  }
}

On this page you can find all the info on how to manipulate the data, but not how the incoming data looks.
Is this only different when using Flux query?

Did anybody figure this out yet?

I was not able to find the context JSON in the function directly, but I did some reverse engineering displaying the keys of object inside values of the sankey diagram and found the folliowing datastructure:

context JSON available in function
{
  "context": {
    "grafana": {},
    "panel": {
      "data": {
        "state": {},
        "series": [
          {
            "redId": "Grafana query name",
            "meta": {
              "typeVerion": [
                0,
                0
              ],
              "executedQueryString": "Grafana query string"
            },
            "fields": [
              {
                "name": "Source",
                "type": "string",
                "typeInfo": {
                  "frame": "string",
                  "nullable": true
                },
                "labels": {},
                "config": {},
                "values": [],
                "entities": {},
                "state": {
                  "scopedVars": {},
                  "seriesIndex": {},
                  "range": {}
                },
                "display": "<function>",
                "getLinks": "<function>"
              }
            ],
            "length": 0
          }
        ],
        "annotations": {},
        "error": {},
        "errors": {},
        "request": {},
        "timerange": {},
        "timings": {},
        "structureRev": {}
      },
      "chart": {}
    },
    "echarts": {},
    "ecStat": {}
  }
}

This JSON does not look like te one returned by the query to my influxdb datasource. The most confusing thing for me was that the values are a sub-object of the fields, where the query returns the fields and data as two separate objects in the tree.

I did manage to write a script to display my data into the sankey diagram. Hope this can get some other people started using this panel.

Function
let nodes, links;

let frame = context.panel.data.series[0];
let sourceIndex = frame.fields.findIndex((item, index) => { return item.name.toUpperCase() === "SOURCE" });
let sources = frame.fields[sourceIndex].values;
let destinationsIndex = frame.fields.findIndex((item, index) => { return item.name.toUpperCase() === "DESTINATION" });
let destinations = frame.fields[destinationsIndex].values;
let valuesIndex = frame.fields.findIndex((item, index) => { return item.name.toUpperCase() === "_VALUE" });
let values = frame.fields[valuesIndex].values;

/* Make combined list of sources & destinations */
nodes = sources.concat(destinations);
//filter unique values
nodes = nodes.filter((x, i, a) => a.indexOf(x) == i);
//edit syntax to objext with "name" key
// data: [
//   {
//     name: 'a'
//   }
// ]
nodes = nodes.map((item, index, self) => ({ name: item }));

// links: [
//   {
//     source: 'a',
//     target: 'a1',
//     value: 5
//   }
// ]
links = sources.map((source, i) => ({ source: source, target: destinations[i], value: values[i] }));

return {
  tooltip: {
    trigger: 'item',
    triggerOn: 'mousemove'
  },
  animation: true,
  series: {
    type: 'sankey',
    top: '10%',
    bottom: '10%',
    layout: 'none',
    emphasis: {
      focus: 'adjacency'
    },
    nodeAlign: 'left',
    data: nodes,
    links: links,
    orient: 'horizontal',
    label: {
      position: 'right'
    },
    lineStyle: {
      color: 'source',
      curveness: 0.5
    }
  }
};

//https://echarts.volkovlabs.io/d/-LQcuVF4z/sankey?orgId=1&from=now-6h&to=now&timezone=browser

The only thing I am stil looking for is how to add the unit of measure to the tooltip or as part of the label. @mikhailvolkov maybe you can provide some assistance on this finishing touch?

PS: Thanks @mikhailvolkov for creating this panel. The learning curve is quite steep, but once you understand how to access the data, all the examples begin to make sense.

2 Likes

For units, check out the formatter in the Apache ECharts documentation:

The data format is Grafana’s data frames returned by any supported data source.

1 Like

@mikhailvolkov thanks for your feedback.

For others who find this topic: an easy way to view the data frame of your query is to add a transformation and click “debug”.

This JSON is the base for the JavaScript code of the panel.

In the code “Array” is available under: context.panel.data.series[].

1 Like