简体   繁体   中英

Ordering nodes in d3 sankey diagram

I'm trying to order the nodes in D3 sankey. I want nodes that have larger values to be drawn higher up.

This is the relevant code ( based on d3 noob's code ) snippet, where I try to achieve what I want by using D3's inbuilt sort function. The outcome is not what I would expect.

Upon inspecting sankey.js, it's not entirely clear to me how the plug-in orders the nodes. Any advise on the topic would be appreciated.

//set up graph in same style as original example but empty
    graph = {"nodes" : [], "links" : []};

    data.forEach(function (d) {
        graph.nodes.push({ "name": d.source });
        graph.nodes.push({ "name": d.target });
        graph.links.push({ "source": d.source,
            "target": d.target,
            "value": +d.value });
    });

//try to sort the nodes and links before indexing them (not working)
    graph.nodes.sort(function (a,b) {return d3.descending(a.value, b.value); });
    graph.links.sort(function (a,b) {return d3.descending(a.value, b.value); });

    // return only the distinct / unique nodes
    graph.nodes = d3.keys(d3.nest()
        .key(function (d) { return d.name; })
        .map(graph.nodes));

    // loop through each link replacing the text with its index from node
    graph.links.forEach(function (d, i) {
        graph.links[i].source = graph.nodes.indexOf(graph.links[i].source);
        graph.links[i].target = graph.nodes.indexOf(graph.links[i].target);
    });

    //now loop through each nodes to make nodes an array of objects
    // rather than an array of strings
    graph.nodes.forEach(function (d, i) {
        graph.nodes[i] = { "name": d };
    });

Removing the following line from the resolveCollisions() function in the sankey.js file will stop the order of the nodes from changing:

function resolveCollisions() {
    ...
    // nodes.sort(ascendingDepth);  // commented this out to prevent node reordering
    ...
}

The nodes will then be ordered vertically by however they are originally populated. So if you sort the nodes first before pushing the data, they will appear in sorted order.

If you set the layout iterations value to zero you'll get the nodes in their alphabetical (by name) order: that worked well enough for me.

var sankey = d3.sankey()
    .nodeWidth(36)
    .nodePadding(40)
    .size([width, height])
    .layout(0);  /* <<<<<<< setting the iterations to zero */

Having just run into this and it being particularly non-trivial, there's an abstraction of d3-sankey (unfortunately not drop-in) that allows you to set arbitrary orders (as well as do lots of other non-standard things like groups and cycles):

var layout = d3.sankey()
               .extent([[100, 10], [840, 580]]);

var diagram = d3.sankeyDiagram()
                .linkColor(function(d) { return d.color; });

d3.json('uk_energy.json', function(energy) {
  layout.ordering([
      [
        [
          "supply^Indigenous",
          "supply^Imports"
        ],
        [],
        []
      ],
      [
        [
          "primary^Natural gas",
          "primary^Bioenergy & waste",
          "primary^Coal",
          "primary^Manufactured fuel",
          "primary^Primary electricity",
          "primary^Electricity",
          "primary^Petroleum products",
          "primary^Primary oils"
        ],
        [
          "stocks^*"
        ],
        []
      ],
      [
        [
          "transfers^*",
          "transform^Power stns",
          "transform^Other transform",
          "transform^Refineries"
        ],
        [],
        [
          "sink^*"
        ]
      ],
      [
        [
          "secondary^Natural gas",
          "secondary^Bioenergy & waste",
          "secondary^Coal",
          "secondary^Electricity",
          "secondary^Manufactured fuel",
          "secondary^Heat sold",
          "secondary^Petroleum products"
        ],
        [
          "own use^*"
        ],
        [
          "loss^*"
        ]
      ],
      [
        [
          "use1^Other",
          "use1^Industry",
          "use1^Transport"
        ],
        [],
        []
      ],
      [
        [
          "use^Domestic",
          "use^Public administration",
          "use^Commercial",
          "use^Agriculture",
          "use^Miscellaneous",
          "use^Unclassified",
          "use^Iron and steel",
          "use^Non-ferrous metals",
          "use^Mineral products",
          "use^Chemicals",
          "use^Mechanical engineering etc",
          "use^Electrical engineering etc",
          "use^Vehicles",
          "use^Food, beverages etc",
          "use^Textiles, leather etc",
          "use^Paper, printing etc",
          "use^Other industries",
          "use^Construction",
          "use^Air",
          "use^Rail",
          "use^Road",
          "use^National navigation"
        ],
        [],
        []
      ]
    ]
  );
  d3.select('#sankey')
      .datum(layout(energy))
      .call(diagram);
});

According to the docs :

If ordering is specified, sets the node ordering to the specified value and returns this layout. If ordering is not specified, return the current value, which defaults to null.

When ordering is null, the node ordering will be calculated automatically.

When ordering is specified, it is used directly and no rank assignment or ordering algorithm takes place. The ordering structure has three nested lists: ordering is a list of layers, each of which is a list of bands, each of which is a list of node ids.

AFAICT, the carets ( ^ ) in the example denote groupName^nodeName .

This might not be particularly helpful if you're deep into a project already, but if starting one from scratch this would probably be my first port of call.

Below steps can be used to create sankey with custom node order.

 let data = {nodes:[],links:[]}

function to order/sort All nodes

 const sankey = d3.sankey()
    .size(graphSize)
    .nodeId(d => d.id)
    .nodeWidth(nodeWidth)
    .nodePadding(0.5)
    .nodeAlign(nodeAlignment)
    .nodeSort(null) //creates sankey nodes as ordered in the data 

 let graph = sankey(data)

Sankey documentation

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM