D3 v4 change opacity for kontext related nodes

Viewed 54

I am completely new to D3 and trying to solve the following scenario. I got several context related nodes in place. All of them are displayed fine as I want to, further I want to add an HTML selector button which changes the colour and opacity of all nodes, which are not selected.

The Goal is to highlight the selection by changing the non selected nodes colour and opacity. I do not want completely remove them as done here because some nodes are will be member of more than one context in future.

I thought the swarm intelligence of our community might got an idea. Thanks in advance.

var graph = {
    nodes:[
        {id: "0001", name: "s02vmware", kontext: "Jira", group: 1},
        {id: "0002", name: "v133atlas", kontext: "Jira", group: 2},
        {id: "0003", name: "Linux", kontext: "Jira", group: 2},
        {id: "0004", name: "PostgreSQL", kontext: "Jira", group: 2},
        {id: "0005", name: "OpenSSH", kontext: "Jira", group: 2},
        {id: "0006", name: "Nginx", kontext: "Jira", group: 2},
        {id: "0007", name: "Confluence", kontext: "Jira", group: 3},
        {id: "0008", name: "Tomcat", kontext: "Jira", group: 3},
        {id: "0009", name: "Java", kontext: "Jira", group: 3},

        {id: "0010", name: "Test1", kontext: "Ivis", group: 1},
        {id: "0011", name: "Test2", kontext: "Ivis", group: 2},
        {id: "0012", name: "Test3", kontext: "Ivis", group: 2},
    ],
    links:[
        {source: "0001", target: "0002"},
        {source: "0002", target: "0003"},
        {source: "0004", target: "0003"},
        {source: "0005", target: "0003"},
        {source: "0006", target: "0003"},
        {source: "0007", target: "0003"},
        {source: "0008", target: "0007"},
        {source: "0009", target: "0007"},

        {source: "0010", target: "0012"},
        {source: "0011", target: "0010"},
        {source: "0012", target: "0011"},
    ]
};


var svg = d3.select("svg"),
    width = window.innerWidth,
    height = window.innerHeight;

var color = d3.scaleOrdinal(d3.schemeCategory20);

var simulation = d3.forceSimulation()
 .force("link", d3.forceLink().id(function(d) { return d.id; }).distance(100))
 .force("charge", d3.forceManyBody())
 .force("center", d3.forceCenter(width / 2, height / 2))
 .force("attraceForce",d3.forceManyBody().strength(-80));

var link = svg.append("g")
    .attr("class", "links")
    .selectAll("line")
    .data(graph.links)
    .enter().append("line")
    .attr("stroke-width", 2);

var node = svg.selectAll(".node")
            .data(graph.nodes)
            .enter().append("g")
            .attr("class", "node")
            .on("mouseover", mouseover)
            .on("mouseout", mouseout)
            /*
            .on("click", function(d) {      
                div.transition()        
                    .duration(200)      
                    .style("opacity", 0.9);     
                div .html((d.name) + "<br/>"  + d.close)    
                    .style("left", (d3.event.pageX) + "px")     
                    .style("top", (d3.event.pageY - 28) + "px")*/
            .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));

node.append("circle")
  .attr("r", 15)
  .attr("fill", function(d) { return color(d.group); });

node.append("text")
    .attr("dy", -20)
    .text(function(d) { return d.name; });

simulation
   .nodes(graph.nodes)
   .on("tick", ticked);

simulation.force("link")
   .links(graph.links);

function ticked() {
   link
    .attr("x1", function(d) { return d.source.x; })
    .attr("y1", function(d) { return d.source.y; })
    .attr("x2", function(d) { return d.target.x; })
    .attr("y2", function(d) { return d.target.y; });

    node.attr("transform", function(d) {
        return "translate(" + d.x + "," + d.y + ")";
   });
}

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(d) {
   d.fx = d3.event.x;
   d.fy = d3.event.y;
}

function dragended(d) {
    if (!d3.event.active) simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
}

function mouseover() {
    d3.select(this).select("circle").transition()
        .duration(100)
        .attr("r", 20);
}

function mouseout() {
    d3.select(this).select("circle").transition()
        .duration(500)
        .attr("r", 15);
}
/* todo: add styles */
.vertical-grid{
    stroke:#ccc;
    
  }
  
  .ordinate-text{
    font-size:10px;
    transform:translateY(10px);
  }
  
  div.tooltip {
    position: absolute;
    text-align: center;
    width: auto;
    height: auto;
    padding: 2px;
    font: 12px sans-serif;
    background: lightsteelblue;
    border: 0px;
    border-radius: 8px;
    pointer-events: none;
    opacity:0;
    padding:5px;
  }
  
  .right-align{
    position:absolute;
    right:100px;
    
  }
  .box,
  .label{
      display:inline-block;
  }
  
  .box{
    width:10px;
    height:10px;
    margin:0px 10px;
  }
<!DOCTYPE html>

    <head>

        <meta charset = "utf-8">
        <link rel="stylesheet" type="text/css" href="style.css">
       
        <title>Ideal iDEP on D3.js</title>
        
   </head>


   <body>
      <!-- Kontext Option Button -->
      <select>
        <option value="01">01 - Jira</option>
        <option value="02">02 - Ivis</option>
        <option value="03">Whatever</option>
      </select>
      
      <!-- SVG as a canvas area -->
      <svg width="100%" height="100%"></svg>
      <!--<svg id="canvas" width="100%" height="100%"></svg> -->
      <!-- import d3.js library -->
      <script src="https://d3js.org/d3.v4.js"></script> 
      <!-- where the magic is happening -->    
      <script type="" src="appv4.js"></script>

   </body>

</html>

1 Answers

You'll want the select menu to have values representing each kontext:

  <select>
    <option value="Jira">01 - Jira</option>
    <option value="Ivis">02 - Ivis</option>
    <option value="All" selected>Whatever</option>
  </select>

Then you'll want to listen for this to change. When it changes, you'll rever all nodes back to normal and filter for those that have a kontext that matches the selected one.

Because you have two different child elements, you might want to change color and opacity differentially. Below I have altered both label and circle's opacity first, then selected the circle of each node and changed the fill:

d3.select("select").on("change", function() {
  var value = this.value;
  
 // Reset every node:
  node.style("opacity", 1) // change opacity back for every node
      .select("circle")    // select the circle to alter its color
      .style("fill", function(d) { return color(d.group); });
      
  // If "All" isn't selected, filter for the selected value:
  if(value != "All") {
    node.filter(function(d) { return d.kontext != value; })
        .style("opacity", 0.5) // Change the opacity of text and circle for filtered items
        .select("circle")      // select the circle to alter its color
        .style("fill","#aaa");
  }
})

Taken together we get:

var graph = {
    nodes:[
        {id: "0001", name: "s02vmware", kontext: "Jira", group: 1},
        {id: "0002", name: "v133atlas", kontext: "Jira", group: 2},
        {id: "0003", name: "Linux", kontext: "Jira", group: 2},
        {id: "0004", name: "PostgreSQL", kontext: "Jira", group: 2},
        {id: "0005", name: "OpenSSH", kontext: "Jira", group: 2},
        {id: "0006", name: "Nginx", kontext: "Jira", group: 2},
        {id: "0007", name: "Confluence", kontext: "Jira", group: 3},
        {id: "0008", name: "Tomcat", kontext: "Jira", group: 3},
        {id: "0009", name: "Java", kontext: "Jira", group: 3},

        {id: "0010", name: "Test1", kontext: "Ivis", group: 1},
        {id: "0011", name: "Test2", kontext: "Ivis", group: 2},
        {id: "0012", name: "Test3", kontext: "Ivis", group: 2},
    ],
    links:[
        {source: "0001", target: "0002"},
        {source: "0002", target: "0003"},
        {source: "0004", target: "0003"},
        {source: "0005", target: "0003"},
        {source: "0006", target: "0003"},
        {source: "0007", target: "0003"},
        {source: "0008", target: "0007"},
        {source: "0009", target: "0007"},

        {source: "0010", target: "0012"},
        {source: "0011", target: "0010"},
        {source: "0012", target: "0011"},
    ]
};


var svg = d3.select("svg"),
    width = 500,
    height = 300;

var color = d3.scaleOrdinal(d3.schemeCategory20);

var simulation = d3.forceSimulation()
 .force("link", d3.forceLink().id(function(d) { return d.id; }).distance(100))
 .force("charge", d3.forceManyBody())
 .force("center", d3.forceCenter(width / 2, height / 2))
 .force("attraceForce",d3.forceManyBody().strength(10));

var link = svg.append("g")
    .attr("class", "links")
    .selectAll("line")
    .data(graph.links)
    .enter().append("line")
    .attr("stroke-width", 2);

var node = svg.selectAll(".node")
            .data(graph.nodes)
            .enter().append("g")
            .attr("class", "node")
            .on("mouseover", mouseover)
            .on("mouseout", mouseout)
            /*
            .on("click", function(d) {      
                div.transition()        
                    .duration(200)      
                    .style("opacity", 0.9);     
                div .html((d.name) + "<br/>"  + d.close)    
                    .style("left", (d3.event.pageX) + "px")     
                    .style("top", (d3.event.pageY - 28) + "px")*/
            .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));

node.append("circle")
  .attr("r", 15)
  .attr("fill", function(d) { return color(d.group); });

node.append("text")
    .attr("dy", -20)
    .text(function(d) { return d.name; });

simulation
   .nodes(graph.nodes)
   .on("tick", ticked);

simulation.force("link")
   .links(graph.links);

function ticked() {
   link
    .attr("x1", function(d) { return d.source.x; })
    .attr("y1", function(d) { return d.source.y; })
    .attr("x2", function(d) { return d.target.x; })
    .attr("y2", function(d) { return d.target.y; });

    node.attr("transform", function(d) {
        return "translate(" + d.x + "," + d.y + ")";
   });
}

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(d) {
   d.fx = d3.event.x;
   d.fy = d3.event.y;
}

function dragended(d) {
    if (!d3.event.active) simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
}

function mouseover() {
    d3.select(this).select("circle").transition()
        .duration(100)
        .attr("r", 20);
}

function mouseout() {
    d3.select(this).select("circle").transition()
        .duration(500)
        .attr("r", 15);
}

d3.select("select").on("change", function() {
  var value = this.value;
  
 // Reset every node:
  node.style("opacity", 1) // change opacity back for every node
      .select("circle")    // select the circle to alter its color
      .style("fill", function(d) { return color(d.group); });
      
  // If "All" isn't selected, filter for the selected value:
  if(value != "All") {
    node.filter(function(d) { return d.kontext != value; })
        .style("opacity", 0.5) // Change the opacity of text and circle for filtered items
        .select("circle")      // select the circle to alter its color
        .style("fill","#aaa");
  }
})
/* todo: add styles */
.vertical-grid{
    stroke:#ccc;
    
  }
  
  .ordinate-text{
    font-size:10px;
    transform:translateY(10px);
  }
  
  div.tooltip {
    position: absolute;
    text-align: center;
    width: auto;
    height: auto;
    padding: 2px;
    font: 12px sans-serif;
    background: lightsteelblue;
    border: 0px;
    border-radius: 8px;
    pointer-events: none;
    opacity:0;
    padding:5px;
  }
  
  .right-align{
    position:absolute;
    right:100px;
    
  }
  .box,
  .label{
      display:inline-block;
  }
  
  .box{
    width:10px;
    height:10px;
    margin:0px 10px;
  }
<!DOCTYPE html>

    <head>

        <meta charset = "utf-8">
        <link rel="stylesheet" type="text/css" href="style.css">
       
        <title>Ideal iDEP on D3.js</title>
        
   </head>


   <body>
      <!-- Kontext Option Button -->
      <select>
        <option value="Jira">01 - Jira</option>
        <option value="Ivis">02 - Ivis</option>
        <option value="All" selected>Whatever</option>
      </select>
      
      <!-- SVG as a canvas area -->
      <svg width="500" height="300"></svg>
      <!--<svg id="canvas" width="100%" height="100%"></svg> -->
      <!-- import d3.js library -->
      <script src="https://d3js.org/d3.v4.js"></script> 
      <!-- where the magic is happening -->    
      <script type="" src="appv4.js"></script>

   </body>

</html>

If nodes can be part of more than one context:

{id: "0002", name: "name1", kontext: ["ContextA"], group: 2},
{id: "0003", name: "name2", kontext: ["ContextA","ContextB"] , group: 2},

You'll need to check to see if the selected context is in the kontext array, this will also be easier if all kontext properties are arrays, as opposed to some strings and arrays. If kontext is an array, you'd have something like:

 node.filter(function(d) { return d.kontext.indexOf(value) == -1; })
Related