1

I am working on d3.js. My code is as follows:

https://jsfiddle.net/g543kxoh/

code in js fiddle is as follows

const treeData = {
  "id": 1,
  "name": "Root",
  "checked": false,
  "color": "white",
  "children": [
    {
      "id": 2,
        "name": "Leaf A",
      "checked": false,
      "color": "red",
        "children": [
        {
          "id": 3,
          "name": "A - 1",
                "checked": false,
          "color": "brown",
          }, 
        {
          "id": 4,
          "name": "A - 2",
          "checked": false,
          "color": "orange",
          },
        {
          "id": 5,
          "name": "A - 3",
                "checked": false,
          "color": "yellow",
          },
      ]
    }, 
    {
      "id": 6,
        "name": "Leaf B",
      "checked": false,
      "color": "green",
        "children": [
        {
          "id": 7,
          "name": "B - 1",
          "checked": false,
          "color": "#00ff40",
          }, 
        {
          "id": 8,
          "name": "B - 2",
          "checked": false,
          "color": "#00ff80",
          }
      ]
    }
  ]  
};

const margin = {
    top: 20,
    right: 120,
    bottom: 20,
    left: 120
  };
  
const width = 600 - margin.right - margin.left;
const height = 400 - margin.top - margin.bottom;

var i = 0,duration = 750;
  
const tree = d3.layout.tree()
  .size([height, width]);

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


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

const root = treeData;
root.x0 = height / 2;
root.y0 = 0;

// Collapse after the second level
root.children.forEach(collapse);

update(root);

// Collapse the node and all it's children
function collapse(d) {
  if (d.children) {
    d._children = d.children
    d._children.forEach(collapse)
    d.children = null
  }
}

function update(source) {
  // Compute the new tree layout.
  const nodes = tree.nodes(root).reverse();
  const links = tree.links(nodes);

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

  // Update the nodes…
  const node = container.selectAll("g.node")
    .data(nodes, d => d.id);

  // Enter any new nodes at the parent's previous position.
  var nodeEnter = node.enter()
    .append("g")
    .attr("class", "node")
    .attr("transform", d => `translate(${source.y0},${source.x0})`)
    .on("click", onClickNode);

  nodeEnter.append("circle")
    .attr("r", 10)
    .style("fill", d => d.color);

  nodeEnter.append("text")
    .attr("x", 20)
    .attr("dy", 4)
    .attr("text-anchor", "start")
    .text(d => d.name);
    
  nodeEnter.append('foreignObject')
    .attr('width', '20')
    .attr('height', '20')
    .attr("x", -30)
    .attr("y", -8)
    .append('xhtml:input')
    .attr('type', 'checkbox')
    .attr("id", d => `checkbox-${d.id}`)
    .on("click", onClickCheckbox)

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + d.y + "," + d.x + ")";
    });

  // ???
  nodeUpdate.select("circle")
    .style("stroke", 'black');
  nodeUpdate.each(function(d) {
    const cb = d3.select(this).select('[type="checkbox"]').node();
    cb.checked = d.checked;
    cb.disabled = isParentChecked(d);
  });
    
  nodeUpdate.select("text")
    .style("fill-opacity", 1);

  // Transition exiting nodes to the parent's new position.
  const nodeExit = node.exit().transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + source.y + "," + source.x + ")";
    })
    .remove();

  nodeExit.select("circle")
    .attr("r", 0);

  nodeExit.select("text")
    .style("fill-opacity", 0);

  // Update the links…
  var link = container.selectAll("path.link")
    .data(links, d => d.target.id);

  // Enter any new links at the parent's previous position.
  link.enter().insert("path", "g")
    .attr("class", "link")
    .attr("stroke-width", 1)
    .attr("d", function(d) {
      var o = {
        x: source.x0,
        y: source.y0
      };
      return diagonal({
        source: o,
        target: o
      });
    })
    .attr("opacity", "0.3")
    .style("stroke", 'black');

  // Transition links to their new position.
  link.transition()
    .duration(duration)
    .attr("d", diagonal);

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
    .duration(duration)
    .attr("d", function(d) {
      var o = {
        x: source.x,
        y: source.y
      };
      return diagonal({
        source: o,
        target: o
      });
    })
    .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

function findParent(datum) {
  if (datum.depth < 2) {
    return datum.name
  } else {
    return findParent(datum.parent)
  }
}

function findParentLinks(datum) {
  if (datum.target.depth < 2) {
    return datum.target.name
  } else {
    return findParent(datum.target.parent)
  }
}

const checkNode = (d, checked, byParent) => {
  if (d.id === 2)
    console.log('CHECK TO: ', checked);
  d.checked = checked;
  const children = d.children || d._children;
  if (children)
    children.forEach(child => checkNode(child, checked, true));
    
  if (!byParent && checked && d.parent) {
    console.log('UNCHECK SIBLINGS');
    
    const siblings = d.parent.children || d.parent._children;
    
    siblings.forEach(sibling => {
      if (sibling.id !== d.id) {
        console.log('UNCHECK: ', sibling)
        checkNode(sibling, false, true);
      }
    });
    
  }  
  
}

function isParentChecked (d) {
  if (!d.parent) {
    return false;
  }
  if (d.parent.checked) {
    return true;
  }
  return isParentChecked(d.parent);
}

function onClickCheckbox(d) {
  d3.event.stopPropagation();
    checkNode(d, d3.event.target.checked, false);
  console.log('ROOT: ', root);
  update(root);   
}


// Toggle children on click.
function onClickNode(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } 
  else {
    d.children = d._children;
    d._children = null;
  }
  update(d);
}



.node {
  cursor: pointer;
}

.node circle {
  fill: #fff;
  stroke: #C0C0C0;
  stroke-width: 1.5px;
}

.node text {
  font: 10px sans-serif;
}

.link {
  fill: none;
  stroke: #C0C0C0;
  stroke-width: 1.5px;
}

I want to check arrow and border color on the basis of node checked or not.

  1. If node is checked then I want to have a line like as shown in following attached image. Not sure what we call it in d3.js(either diagonal or link).

checked.

Currently I have grey color normal line as shown in fiddle. But once node is checked I want to have all the checked nodes connect with lines as shown in attached image. Also border color of node should

  1. If node is unchecked then I want to have diagonal/link connecting nodes as shown in attached image

unchecked.

In this case also border color of node should be same as color of unchecked diagonal/link

How can I do that?

Michael Rovinsky
  • 6,807
  • 7
  • 15
  • 30
Julie
  • 87
  • 5
  • You want a blue link when the node is checked or when its parent is checked? – Michael Rovinsky May 18 '21 at 09:43
  • when the node is checked. suppose in fiddle if user selects sub child node A-2 then path from Root -> Leaf A -> A-2 should be blue link and each link should have dots at start and end as shown in image – Julie May 18 '21 at 12:07

1 Answers1

1

Depending on criterion to mark the link, do:

  // Transition links to their new position.
  link
    .style('stroke', d => d.target.checked ? 'blue' : 'gray')
    .transition()
    .duration(duration)
    .attr("d", diagonal);

if you want to color the links of checked nodes, or:

  link
    .style('stroke', d => d.source.checked ? 'blue' : 'gray')
    .transition...

if you want color the links of checked parents.

See it in a fiddle.

UPDATE: Add colored points:

  nodeEnter.append('circle')
    .classed('child-link-point', true)
    .attr('cx', 10)
    .attr('r', 4);
  nodeEnter.append('circle')
    .classed('parent-link-point', true)
    .attr('cx', -10)
    .attr('r', 4);

  ...

  nodeUpdate.select('.child-link-point')
    .style('visibility', d => d.children ? 'visible' : 'hidden')
    .style('fill', d => d.checked ? 'blue' : 'gray')
    .style('stroke', d => d.checked ? 'blue' : 'gray');
    
  nodeUpdate.select('.parent-link-point')
    .style('visibility', d => d.parent ? 'visible' : 'hidden')
    .style('fill', d => (d.parent || {}).checked ? 'blue' : 'gray')
    .style('stroke', d => (d.parent || {}).checked ? 'blue' : 'gray');

Michael Rovinsky
  • 6,807
  • 7
  • 15
  • 30
  • how can I have two dots at starting and end of blue lines? as shown in image – Julie May 18 '21 at 12:05
  • when the node is checked. suppose in fiddle if user selects sub child node A-2 then path from Root -> Leaf A -> A-2 should be blue link and each link should have dots at start and end as shown in image – Julie May 18 '21 at 12:07
  • Tried it here; https://jsfiddle.net/mrovinsky/9zeoLqbd/. Does it work for you? – Michael Rovinsky May 18 '21 at 13:43
  • blue dots are coming on line if a parent is clicked. If child is clicked not blue dots coming. if child is checked then blue line with blue dots and blue border color of nodes should be from child till parents. Like in example in fiddle if A-2 is checked, then Leaf A and Root are its parents so A-2, Leaf A and Root should have blue border also diagonal/link line of blue color each line starting as ending with dots as you have shown in fiddle – Julie May 18 '21 at 14:04
  • Sorry I'm not sure I understand the whole logic, but I think the answer covers the different scenatios. Please take the code _as an example_ and modify it according to your requirements – Michael Rovinsky May 18 '21 at 14:09
  • 1
    I don;t know d3.js that's why I asked. Anyways I will give it a try – Julie May 18 '21 at 14:11
  • Hi Michael could you please help me out with the second question https://stackoverflow.com/questions/67640038/checking-node-and-its-all-childrens-on-the-basis-of-id – Julie May 21 '21 at 17:34
  • I wrote a comment there regarding your question because I was not sure I got it right... Anyway, I'd be happy if you mark my answer here as correct and upvote it – Michael Rovinsky May 21 '21 at 20:24
  • I haven't tested it. I will be testing it and will mark correct and upvote it in sometime. The new question was on priority so I started working on it. Don't worry I will do it. Could you please help me out with the new question? – Julie May 22 '21 at 05:48
  • I need to understand the problem there. Please respond to the comment I wrote – Michael Rovinsky May 22 '21 at 06:05
  • I replied to your comment there. Please have a look – Julie May 22 '21 at 06:10