poweredbygt
poweredbygt

Reputation: 71

How to make one function instead of 3 or more for a visualization in d3 to then add highlight when hovering over nodes

I have visualized 3 trees with d3 but I have repeated my code, and now I want to add highlighting the same nodes in different trees when hovering over a node in a tree but this I cannot do as I see with this structure of code that I have now.

The d3 code I have now for the three different trees (so I have put the code three times in three different functions draw(), draw1() and draw2()):

<script>

//here i get the data
var data = {{data.chart_data | safe }}    

draw("#svg1", "#url");
draw1("#svg2", "#url");
draw2("#svg3", "#url");
draw3("#svg4", "#url");

function draw(selector, url){

    console.log(data);

    // ************** Generate the tree diagram  *****************
var margin = {top: 40, right: 120, bottom: 20, left: 120},
    width = 960 - margin.right - margin.left,
    height = 500 - margin.top - margin.bottom;

var i = 0;

var tree = d3.layout.tree()
    .size([height, width]);

var diagonal = d3.svg.diagonal()
    .projection(function(d) { return [d.x, d.y]; });

var svg = d3.select(selector).append("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

root = data[0]

update(root);

function update(source) {

  // Compute the new tree layout.
  var nodes = tree.nodes(root).reverse(),
      links = tree.links(nodes);

  // Normalize for fixed-depth.
  nodes.forEach(function(d) { d.y = d.depth * 100; });

  // Declare the nodes…
  var node = svg.selectAll("g.node")
      .data(nodes, function(d) { return d.id || (d.id = ++i); });

  // Enter the nodes.
  var nodeEnter = node.enter().append("g")
      .attr("class", "node")
      .attr("transform", function(d) { 
          return "translate(" + d.x + "," + d.y + ")"; });

  nodeEnter.append("circle")
      .attr("r", 10)
      .style("fill", "#fff");

  nodeEnter.append("text")
      .attr("y", function(d) { 
          return d.children || d._children ? -18 : 18; })
      .attr("dy", ".35em")
      .attr("text-anchor", "middle")
      .text(function(d) { return d.cues; })
      .style("fill-opacity", 1);

  // Declare the links…
  var link = svg.selectAll("path.link")
      .data(links, function(d) { return d.target.id; });

  // Enter the links.
  link.enter().insert("path", "g")
      .attr("class", "link")
      .attr("d", diagonal);

  }
};


// Second tree
function draw1(selector, url){


    console.log(data);

    // ************** Generate the tree diagram  *****************
var margin = {top: 40, right: 120, bottom: 20, left: 120},
    width = 960 - margin.right - margin.left,
    height = 500 - margin.top - margin.bottom;

var i = 0;

var tree = d3.layout.tree()
    .size([height, width]);

var diagonal = d3.svg.diagonal()
    .projection(function(d) { return [d.x, d.y]; });

var svg = d3.select(selector).append("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

root = data[1]

update(root);

function update(source) {

  // Compute the new tree layout.
  var nodes = tree.nodes(root).reverse(),
      links = tree.links(nodes);

  // Normalize for fixed-depth.
  nodes.forEach(function(d) { d.y = d.depth * 100; });

  // Declare the nodes…
  var node = svg.selectAll("g.node")
      .data(nodes, function(d) { return d.id || (d.id = ++i); });

  // Enter the nodes.
  var nodeEnter = node.enter().append("g")
      .attr("class", "node")
      .attr("transform", function(d) { 
          return "translate(" + d.x + "," + d.y + ")"; });

  nodeEnter.append("circle")
      .attr("r", 10)
      .style("fill", "#fff");

  nodeEnter.append("text")
      .attr("y", function(d) { 
          return d.children || d._children ? -18 : 18; })
      .attr("dy", ".35em")
      .attr("text-anchor", "middle")
      .text(function(d) { return d.cues; })
      .style("fill-opacity", 1);

  // Declare the links…
  var link = svg.selectAll("path.link")
      .data(links, function(d) { return d.target.id; });

  // Enter the links.
  link.enter().insert("path", "g")
      .attr("class", "link")
      .attr("d", diagonal);

  }
};


// third tree
function draw2(selector, url){

    console.log(data);

    // ************** Generate the tree diagram  *****************
var margin = {top: 40, right: 120, bottom: 20, left: 120},
    width = 960 - margin.right - margin.left,
    height = 500 - margin.top - margin.bottom;

var i = 0;

var tree = d3.layout.tree()
    .size([height, width]);

var diagonal = d3.svg.diagonal()
    .projection(function(d) { return [d.x, d.y]; });

var svg = d3.select(selector).append("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

root = data[2]

update(root);

function update(source) {

  // Compute the new tree layout.
  var nodes = tree.nodes(root).reverse(),
      links = tree.links(nodes);

  // Normalize for fixed-depth.
  nodes.forEach(function(d) { d.y = d.depth * 100; });

  // Declare the nodes…
  var node = svg.selectAll("g.node")
      .data(nodes, function(d) { return d.id || (d.id = ++i); });

  // Enter the nodes.
  var nodeEnter = node.enter().append("g")
      .attr("class", "node")
      .attr("transform", function(d) { 
          return "translate(" + d.x + "," + d.y + ")"; });

  nodeEnter.append("circle")
      .attr("r", 10)
      .style("fill", "#fff");

  nodeEnter.append("text")
      .attr("y", function(d) { 
          return d.children || d._children ? -18 : 18; })
      .attr("dy", ".35em")
      .attr("text-anchor", "middle")
      .text(function(d) { return d.cues; })
      .style("fill-opacity", 1);

  // Declare the links…
  var link = svg.selectAll("path.link")
      .data(links, function(d) { return d.target.id; });

  // Enter the links.
  link.enter().insert("path", "g")
      .attr("class", "link")
      .attr("d", diagonal);

  }
};


// fourth tree
function draw3(selector, url){


    console.log(data);

    // ************** Generate the tree diagram  *****************
var margin = {top: 40, right: 120, bottom: 20, left: 120},
    width = 960 - margin.right - margin.left,
    height = 500 - margin.top - margin.bottom;

var i = 0;

var tree = d3.layout.tree()
    .size([height, width]);

var diagonal = d3.svg.diagonal()
    .projection(function(d) { return [d.x, d.y]; });

var svg = d3.select(selector).append("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

root = data[3]

update(root);

function update(source) {

  // Compute the new tree layout.
  var nodes = tree.nodes(root).reverse(),
      links = tree.links(nodes);

  // Normalize for fixed-depth.
  nodes.forEach(function(d) { d.y = d.depth * 100; });

  // Declare the nodes…
  var node = svg.selectAll("g.node")
      .data(nodes, function(d) { return d.id || (d.id = ++i); });

  // Enter the nodes.
  var nodeEnter = node.enter().append("g")
      .attr("class", "node")
      .attr("transform", function(d) { 
          return "translate(" + d.x + "," + d.y + ")"; });

  nodeEnter.append("circle")
      .attr("r", 10)
      .style("fill", "#fff");

  nodeEnter.append("text")
      .attr("y", function(d) { 
          return d.children || d._children ? -18 : 18; })
      .attr("dy", ".35em")
      .attr("text-anchor", "middle")
      .text(function(d) { return d.cues; })
      .style("fill-opacity", 1);

  // Declare the links…
  var link = svg.selectAll("path.link")
      .data(links, function(d) { return d.target.id; });

  // Enter the links.
  link.enter().insert("path", "g")
      .attr("class", "link")
      .attr("d", diagonal);

  }
};
</script>

(notice that the only change is the root = data[] for every tree, so that I can access different elements of the list). Can this be done differently so that I have only one function draw() and I change the root = data[0] dynamically in d3? And then as I said previously I want to add highlighting, so that when I click on one node the node with the same name in the second and third tree will be highlighted as well. I got this example in jsfiddle and modified to look the same as my issue. Now for example when I hover over the node that has the name Son of A I want this node with this name to be highlighted in the second and third tree as well. Thank you for your help!

Upvotes: 0

Views: 51

Answers (1)

lemming
lemming

Reputation: 1863

I have edited your fiddle to show how to render the trees with a single function, and implemented the highlighting: https://jsfiddle.net/j2mhokbz/1/

With regards to creating a single function, you have identified what is changing... the data object from the array. So simply add that as a parameter of the draw function and pass in the slice of the data that you want. Note the new data parameter, and the fact that this is passed to the update function.

draw('#svg1', treeData[0]);
draw('#svg2', treeData[1]);
draw('#svg3', treeData[2]);

function draw(selector, data) {
  ...
  update(data);

  function update(dataForUpdate) {
    ...
    var nodes = tree.nodes(dataForUpdate).reverse(),
    ...
  }
}

For the highlighting, the basic principle is to add a highlighted class to the circle and a new css rule that defines how the circle should look when it has a highlighted class.

On mouseover, you select the sibling text element of a circle by first selecting the circle's parent node (a g element) and then selecting that node's child text element.

Then extract the text from the text element and use the filter method to only return elements that are bound to data with the same name. Then add the highlighted class.

On mouseout, the same procedure is used except that an empty string set as the class, which removes the highlighting.

As it is basically the same procedure, you can create a single function to set or unset the highlighted class for nodes with the same name. See the code below:

d3.selectAll('circle')
  .on('mouseover', function() {
    setHighlightedForSimilarNodes(this, true);
  })
  .on('mouseout', function() {
    setHighlightedForSimilarNodes(this, false);
  });

function setHighlightedForSimilarNodes(node, isHighlighted) {
  const text = d3.select(node.parentNode).select('text').text();
  d3.selectAll('circle').filter((d, i) => {
    return d.name === text;
  })
  .attr('class', isHighlighted ? 'highlighted' : '');
}

Upvotes: 1

Related Questions