Sankey Diagrams in JavaScript

How to make D3.js-based sankey diagrams in Plotly.js.


Plotly Studio: Transform any dataset into an interactive data application in minutes with AI. Try Plotly Studio now.

var trace = {
  type: "sankey",
  orientation: "h",
  node: {
    pad: 15,
    thickness: 30,
    line: {
      color: "black",
      width: 0.5
    },
   label: ["A1", "A2", "B1", "B2", "C1", "C2"],
   color: ["blue", "blue", "blue", "blue", "blue", "blue"]
      },

  link: {
    source: [0,1,0,2,3,3],
    target: [2,3,3,4,4,5],
    value:  [8,4,2,8,4,2]
  }
}

var data = [trace]

var layout = {
  title: {
    text: "Basic Sankey"
  },
  font: {
    size: 10
  }
}

Plotly.newPlot('myDiv', data, layout)

A sankey trace is defined by its link arrays — source and target hold indices into node.label, and value sets the width of each flow. Node positions and heights are derived from the links attached to them.

d3.json('https://raw.githubusercontent.com/plotly/plotly.js/master/test/image/mocks/sankey_energy.json', function(fig){

var trace = {
  type: "sankey",
  domain: {
    x: [0,1],
    y: [0,1]
  },
  orientation: "h",
  valueformat: ".0f",
  valuesuffix: "TWh",
  node: {
    pad: 15,
    thickness: 15,
    line: {
      color: "black",
      width: 0.5
    },
   label: fig.data[0].node.label,
   color: fig.data[0].node.color
      },

  link: {
    source: fig.data[0].link.source,
    target: fig.data[0].link.target,
    value: fig.data[0].link.value,
    label: fig.data[0].link.label
  }
}

var data = [trace]

var layout = {
  title: {
    text: "Energy forecast for 2050<br>Source: Department of Energy & Climate Change, Tom Counsell via <a href='https://bost.ocks.org/mike/sankey/'>Mike Bostock</a>"
  },
  width: 950,
  height: 772,
  font: {
    size: 10
  }
}

Plotly.newPlot('myDiv', data, layout)
});
d3.json('https://raw.githubusercontent.com/plotly/plotly.js/master/test/image/mocks/sankey_energy_dark.json', function(fig){

var trace = {
  type: "sankey",
  domain: {
    x: [0,1],
    y: [0,1]
  },
  orientation: "h",
  valueformat: ".0f",
  valuesuffix: "TWh",
  node: {
    pad: 15,
    thickness: 15,
    line: {
      color: "black",
      width: 0.5
    },
   label: fig.data[0].node.label,
   color: fig.data[0].node.color
      },
  link: {
    source: fig.data[0].link.source,
    target: fig.data[0].link.target,
    value: fig.data[0].link.value,
    label: fig.data[0].link.label
  }
}

var data = [trace]

var layout = {
  title: {
    text: "Energy forecast for 2050<br>Source: Department of Energy & Climate Change, Tom Counsell via <a href='https://bost.ocks.org/mike/sankey/'>Mike Bostock</a>"
  },
  width: 950,
  height: 772,
  font: {
    size: 10,
    color: 'white'
  },
  plot_bgcolor: 'black',
  paper_bgcolor: 'black'
}

Plotly.newPlot('myDiv', data, layout)
});

The following example sets node.x and node.y to place nodes in the specified locations, except in the snap arrangement (default behaviour when node.x and node.y are not defined) to avoid overlapping of the nodes, therefore, an automatic snapping of elements will be set to define the padding between nodes via nodepad. The other possible arrangements are: 1) perpendicular 2) freeform 3) fixed

var data = [{
  type: "sankey",
    arrangement: "snap",
    node:{
        label: ["A", "B", "C", "D", "E", "F"],
        x: [0.2, 0.1, 0.5, 0.7, 0.3, 0.5],
        y: [0.7, 0.5, 0.2, 0.4, 0.2, 0.3],
        pad:10}, // 10 Pixels
    link: {
        source: [0, 0, 1, 2, 5, 4, 3, 5],
        target: [5, 3, 4, 3, 0, 2, 2, 3],
        value: [1, 2, 1, 1, 1, 1, 1, 2]}
    }]

var layout = {
  title: {
    text: "Sankey with manually positioned node"
  }
}

Plotly.newPlot('myDiv', data, layout)


You can set the alignment of nodes using node.align. In this example, we align nodes to the "right". node.align can also be set to "left", "center", or "justify". The default is "justify" if node.align is not set, and is similar to aligning to the "left", except that nodes without outgoing links are moved to the right of the figure.

node.align only decides which column a node is placed in, for nodes whose position isn't already pinned by the links attached to them. It does not change which end of the diagram the sources sit on — for that, see Flow Direction.

var trace = {
  type: "sankey",
  orientation: "h",
  node: {
    label: ["0", "1", "2", "3", "4", "5"],
    align: "right",
  },

  link: {
    source: [0, 1, 4, 2, 1],
    target: [1, 4, 5, 4, 3],
    value: [4, 2, 3, 1, 2],
  },
};

var data = [trace];

var layout = {
  title: {
    text: "Align Nodes (Right)"
  },
  font: {
    size: 10,
  },
};

Plotly.newPlot('myDiv', data, layout);

By default (sort: 'auto') the layout reorders nodes within a column, and links within a node, to reduce crossings. Set node.sort and link.sort to 'input' to keep the order given in node.label and in link.source / link.target instead. Use 'input' when the order carries meaning, or when you need a deterministic layout across renders — for animations or side-by-side comparisons.

This figure pairs sources A, B, C, D one-to-one with targets Z, Y, X, W, a maximally crossed input. With 'auto', the source column is flipped so the flows run horizontally; with 'input', every crossing is preserved. sort is not honored on diagrams whose links form a cycle, which use a different layout algorithm.

var data = [{
  type: 'sankey',
  node: {
    label: ['A', 'B', 'C', 'D', 'Z', 'Y', 'X', 'W'],
    pad: 15,
    thickness: 20,
    sort: 'input'
  },
  link: {
    source: [0, 1, 2, 3],
    target: [4, 5, 6, 7],
    value: [4, 3, 2, 1],
    sort: 'input'
  }
}];

var layout = {
  title: {text: "Input Order Preserved (sort: 'input')"},
  width: 700,
  height: 450
};

Plotly.newPlot('myDiv', data, layout);

direction sets which way flows run along the orientation axis, so a diagram can be mirrored without reversing your link.source and link.target data. The default, 'forward', orders sources left-to-right when horizontal or top-to-bottom when vertical; 'reversed' is the opposite. Combined with orientation ('h' or 'v') this gives four layouts: sources on the left, right, top, or bottom. Node labels stay upright in every combination — only the flow geometry is mirrored.

direction mirrors the finished layout: it does not reassign nodes to different columns. To change which column a node is placed in, use Node Alignment.

var data = [{
  type: 'sankey',
  orientation: 'h',
  direction: 'reversed',
  node: {
    label: ['Coal', 'Gas', 'Solar', 'Grid', 'Residential', 'Industrial'],
    pad: 15,
    thickness: 20
  },
  link: {
    source: [0, 1, 2, 3, 3],
    target: [3, 3, 3, 4, 5],
    value: [8, 4, 3, 9, 6]
  }
}];

var layout = {
  title: {text: "Reversed Flow (sources on the right)"},
  width: 700,
  height: 450
};

Plotly.newPlot('myDiv', data, layout);