0

I've been working on a D3 sankey chart that shows the movement of volume between nodes over 2 time periods. My nodes are colour coordinated (based on their name). I have managed to set up hover behaviour on my links by just using simple CSS but ideally what I need is to be able to hover over a node on the left hand side and then all the links coming from that node are highlighted. Similarly, you should be able to hover over the respective node on the right hand side and highlight all the links going into that node.

This is my code so far. Any help would be much appreciated:

// set the dimensions and margins of the graph
var margin = {
    top: 75,
    right: 10,
    bottom: 10,
    left: 10
  },
  width = 900 - margin.left - margin.right,
  height = 500 - margin.top - margin.bottom;

// append the svg object to the body of the page
var svg = d3.select("#chart").append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform",
    "translate(" + margin.left + "," + margin.top + ")");

//Color scale used
var color = d3.scaleOrdinal().range(["#002060ff", "#164490ff", "#4d75bcff", "#98b3e6ff", "#d5e2feff", "#008cb0ff"]);


// Set the sankey diagram properties
var sankey = d3.sankey()
  .nodeWidth(175)
  .nodePadding(10)
  .size([width, height]);

// load the data

var data = {
  "years": [{
      "year": 2019
    }, {
      "year": 2020
    }

  ],
  "nodes": [{
      "node": 0,
      "id": "a",
      "name": "A"
    },
    {
      "node": 1,
      "id": "b",
      "name": "B"
    },
    {
      "node": 2,
      "id": "c",
      "name": "C"
    },
    {
      "node": 3,
      "id": "d",
      "name": "D"
    },
    {
      "node": 4,
      "id": "e",
      "name": "E"
    },
    {
      "node": 5,
      "id": "f",
      "name": "F"
    },
    {
      "node": 6,
      "id": "a",
      "name": "A"
    },
    {
      "node": 7,
      "id": "b",
      "name": "B"
    },
    {
      "node": 8,
      "id": "c",
      "name": "C"
    },
    {
      "node": 9,
      "id": "d",
      "name": "D"
    },
    {
      "node": 10,
      "id": "e",
      "name": "E"
    },
    {
      "node": 11,
      "id": "f",
      "name": "F"
    }
  ],
  "links": [{
      "source": 0,
      "target": 6,
      "value": 20
    },
    {
      "source": 0,
      "target": 7,
      "value": 10
    },
    {
      "source": 0,
      "target": 8,
      "value": 10
    },
    {
      "source": 0,
      "target": 9,
      "value": 10
    },
    {
      "source": 0,
      "target": 10,
      "value": 20
    },
    {
      "source": 0,
      "target": 11,
      "value": 10
    },

    {
      "source": 1,
      "target": 6,
      "value": 40
    },
    {
      "source": 1,
      "target": 7,
      "value": 10
    },
    {
      "source": 1,
      "target": 8,
      "value": 10
    },
    {
      "source": 1,
      "target": 9,
      "value": 10
    },
    {
      "source": 1,
      "target": 10,
      "value": 10
    },
    {
      "source": 1,
      "target": 11,
      "value": 10
    },

    {
      "source": 2,
      "target": 6,
      "value": 20
    },
    {
      "source": 2,
      "target": 7,
      "value": 10
    },
    {
      "source": 2,
      "target": 8,
      "value": 10
    },
    {
      "source": 2,
      "target": 9,
      "value": 10
    },
    {
      "source": 2,
      "target": 10,
      "value": 10
    },
    {
      "source": 2,
      "target": 11,
      "value": 10
    },

    {
      "source": 3,
      "target": 6,
      "value": 20
    },
    {
      "source": 3,
      "target": 7,
      "value": 10
    },
    {
      "source": 3,
      "target": 8,
      "value": 10
    },
    {
      "source": 3,
      "target": 9,
      "value": 10
    },
    {
      "source": 3,
      "target": 10,
      "value": 10
    },
    {
      "source": 3,
      "target": 11,
      "value": 10
    },

    {
      "source": 4,
      "target": 6,
      "value": 20
    },
    {
      "source": 4,
      "target": 7,
      "value": 10
    },
    {
      "source": 4,
      "target": 8,
      "value": 10
    },
    {
      "source": 4,
      "target": 9,
      "value": 10
    },
    {
      "source": 4,
      "target": 10,
      "value": 10
    },
    {
      "source": 4,
      "target": 11,
      "value": 10
    },

    {
      "source": 5,
      "target": 6,
      "value": 20
    },
    {
      "source": 5,
      "target": 7,
      "value": 10
    },
    {
      "source": 5,
      "target": 8,
      "value": 10
    },
    {
      "source": 5,
      "target": 9,
      "value": 10
    },
    {
      "source": 5,
      "target": 10,
      "value": 10
    },
    {
      "source": 5,
      "target": 11,
      "value": 10
    }


  ]
}

// Constructs a new Sankey
sankey
  .nodes(data.nodes)
  .links(data.links)
  .layout(0);

// add in the links
var link = svg.append("g")
  .selectAll(".link")
  .data(data.links)
  .enter()
  .append("path")
  .attr("class", "link")
  .attr("d", sankey.link())
  .style("stroke-width", function(d) {
    return Math.max(1, d.dy);
  })
  .sort(function(a, b) {
    return b.dy - a.dy;
  })


// add in the nodes
var node = svg.append("g")
  .selectAll(".node")
  .data(data.nodes)
  .enter().append("g")
  .attr("class", "node")
  .attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")";
  })


// add the rectangles for the nodes
node
  .append("rect")
  .attr("height", function(d) {
    return d.dy;
  })
  .attr("width", sankey.nodeWidth())
  .attr("rx", 3)
  .attr("ry", 3)
  .style("fill", function(d) {
    return d.color = color(d.id.replace(/ .*/, ""));
  })
  // Add hover text
  .append("title")
  .text(function(d) {
    return d.name + "\n" + d.value
  });

// add in the title for the nodes
node
  .append("text")
  .attr("x", 87)
  .attr("y", function(d) {
    return d.dy / 2;
  })
  .attr("dy", ".35em")
  .attr("text-anchor", "middle")
  .text(function(d) {
    return d.name;
  })
  .style("fill", "white")
@import url('https://fonts.googleapis.com/css2?family=Assistant:wght@600&display=swap');
text {
  font-family: 'Assistant', sans-serif
}

text {
  font-size: 14px
}

.link {
  fill: none;
  stroke: #000;
  stroke-opacity: .2;
}

.link:hover {
  stroke-opacity: .5;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Assistant:wght@700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/gh/holtzy/D3-graph-gallery@master/LIB/sankey.js"></script>
<div id="chart"></div>
Ruben Helsloot
  • 12,582
  • 6
  • 26
  • 49
Gangrel
  • 449
  • 4
  • 20

1 Answers1

2

d3 has its own event system, which supports hover, click, touch, and many more events. There is often no need to use CSS pseudo-classes, except for very simple things.

// set the dimensions and margins of the graph
var margin = {
    top: 75,
    right: 10,
    bottom: 10,
    left: 10
  },
  width = 900 - margin.left - margin.right,
  height = 500 - margin.top - margin.bottom;

// append the svg object to the body of the page
var svg = d3.select("#chart").append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform",
    "translate(" + margin.left + "," + margin.top + ")");

//Color scale used
var color = d3.scaleOrdinal().range(["#002060ff", "#164490ff", "#4d75bcff", "#98b3e6ff", "#d5e2feff", "#008cb0ff"]);


// Set the sankey diagram properties
var sankey = d3.sankey()
  .nodeWidth(175)
  .nodePadding(10)
  .size([width, height]);

// load the data

var data = {
  "years": [{
      "year": 2019
    }, {
      "year": 2020
    }

  ],
  "nodes": [{
      "node": 0,
      "id": "a",
      "name": "A"
    },
    {
      "node": 1,
      "id": "b",
      "name": "B"
    },
    {
      "node": 2,
      "id": "c",
      "name": "C"
    },
    {
      "node": 3,
      "id": "d",
      "name": "D"
    },
    {
      "node": 4,
      "id": "e",
      "name": "E"
    },
    {
      "node": 5,
      "id": "f",
      "name": "F"
    },
    {
      "node": 6,
      "id": "a",
      "name": "A"
    },
    {
      "node": 7,
      "id": "b",
      "name": "B"
    },
    {
      "node": 8,
      "id": "c",
      "name": "C"
    },
    {
      "node": 9,
      "id": "d",
      "name": "D"
    },
    {
      "node": 10,
      "id": "e",
      "name": "E"
    },
    {
      "node": 11,
      "id": "f",
      "name": "F"
    }
  ],
  "links": [{
      "source": 0,
      "target": 6,
      "value": 20
    },
    {
      "source": 0,
      "target": 7,
      "value": 10
    },
    {
      "source": 0,
      "target": 8,
      "value": 10
    },
    {
      "source": 0,
      "target": 9,
      "value": 10
    },
    {
      "source": 0,
      "target": 10,
      "value": 20
    },
    {
      "source": 0,
      "target": 11,
      "value": 10
    },

    {
      "source": 1,
      "target": 6,
      "value": 40
    },
    {
      "source": 1,
      "target": 7,
      "value": 10
    },
    {
      "source": 1,
      "target": 8,
      "value": 10
    },
    {
      "source": 1,
      "target": 9,
      "value": 10
    },
    {
      "source": 1,
      "target": 10,
      "value": 10
    },
    {
      "source": 1,
      "target": 11,
      "value": 10
    },

    {
      "source": 2,
      "target": 6,
      "value": 20
    },
    {
      "source": 2,
      "target": 7,
      "value": 10
    },
    {
      "source": 2,
      "target": 8,
      "value": 10
    },
    {
      "source": 2,
      "target": 9,
      "value": 10
    },
    {
      "source": 2,
      "target": 10,
      "value": 10
    },
    {
      "source": 2,
      "target": 11,
      "value": 10
    },

    {
      "source": 3,
      "target": 6,
      "value": 20
    },
    {
      "source": 3,
      "target": 7,
      "value": 10
    },
    {
      "source": 3,
      "target": 8,
      "value": 10
    },
    {
      "source": 3,
      "target": 9,
      "value": 10
    },
    {
      "source": 3,
      "target": 10,
      "value": 10
    },
    {
      "source": 3,
      "target": 11,
      "value": 10
    },

    {
      "source": 4,
      "target": 6,
      "value": 20
    },
    {
      "source": 4,
      "target": 7,
      "value": 10
    },
    {
      "source": 4,
      "target": 8,
      "value": 10
    },
    {
      "source": 4,
      "target": 9,
      "value": 10
    },
    {
      "source": 4,
      "target": 10,
      "value": 10
    },
    {
      "source": 4,
      "target": 11,
      "value": 10
    },

    {
      "source": 5,
      "target": 6,
      "value": 20
    },
    {
      "source": 5,
      "target": 7,
      "value": 10
    },
    {
      "source": 5,
      "target": 8,
      "value": 10
    },
    {
      "source": 5,
      "target": 9,
      "value": 10
    },
    {
      "source": 5,
      "target": 10,
      "value": 10
    },
    {
      "source": 5,
      "target": 11,
      "value": 10
    }
  ]
};

// Constructs a new Sankey
sankey
  .nodes(data.nodes)
  .links(data.links)
  .layout(0);

// add in the links
var link = svg.append("g")
  .selectAll(".link")
  .data(data.links)
  .enter()
  .append("path")
  .attr("class", "link")
  .attr("d", sankey.link())
  .style("stroke-opacity", 0.2)
  .style("stroke-width", function(d) {
    return Math.max(1, d.dy);
  })
  .sort(function(a, b) {
    return b.dy - a.dy;
  });

// add in the nodes
var node = svg.append("g")
  .selectAll(".node")
  .data(data.nodes)
  .enter().append("g")
  .attr("class", "node")
  .attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")";
  })

// add the rectangles for the nodes
node
  .append("rect")
  .attr("height", function(d) {
    return d.dy;
  })
  .attr("width", sankey.nodeWidth())
  .attr("rx", 3)
  .attr("ry", 3)
  .style("fill", function(d) {
    return d.color = color(d.id.replace(/ .*/, ""));
  })
  .on("mouseover", function(d) {
    link
      .transition()
      .duration(300)
      .style("stroke-opacity", function(l) {
        return l.source === d || l.target === d ? 0.5 : 0.2;
      });
  })
  .on("mouseleave", function(d) {
    link
      .transition()
      .duration(300)
      .style("stroke-opacity", 0.2);
  })
  // Add hover text
  .append("title")
  .text(function(d) {
    return d.name + "\n" + d.value
  });

// add in the title for the nodes
node
  .append("text")
  .attr("x", 87)
  .attr("y", function(d) {
    return d.dy / 2;
  })
  .attr("dy", ".35em")
  .attr("text-anchor", "middle")
  .text(function(d) {
    return d.name;
  })
  .style("fill", "white");
@import url('https://fonts.googleapis.com/css2?family=Assistant:wght@600&display=swap');
text {
  font-family: 'Assistant', sans-serif
}

text {
  font-size: 14px
}

.link {
  fill: none;
  stroke: #000;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Assistant:wght@700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/gh/holtzy/D3-graph-gallery@master/LIB/sankey.js"></script>
<div id="chart"></div>
Ruben Helsloot
  • 12,582
  • 6
  • 26
  • 49