D3 Силовой направленный граф - как отфильтровать узлы и связанные ссылки? - PullRequest
0 голосов
/ 04 февраля 2019

Я новичок в d3 (и javascript), и я впервые пишу вопрос о Stackoverflow.Пожалуйста, дайте мне знать, если мой вопрос неясен или неуместен.Буду очень признателен, если вы дадите какой-либо совет или помощь.

У меня возникли проблемы с фильтрацией узлов в ориентированном графе силы.Я искал и использовал код, размещенный в другом вопросе ( D3-форсированный граф - узлы фильтра и связанные ссылки ), чтобы скрыть узлы и связанные с ними ссылки.Однако я не смог скрыть узлы, ссылки на которые невидимы.

Вот что я пытаюсь сделать:

Если все ссылки с этим узлом невидимы, узел также должен быть невидимым.В противном случае, если какая-либо ссылка, связанная с этим узлом, является видимой, узел должен быть видимым.Я нашел этот код на jsfiddle.net (zhanghuancs / cuYu8 /), но я не смог использовать этот код на своем. (Я не могу понять, как связать код jsfiddle здесь.)

Может кто-нибудьПомоги мне?Вот мой код:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <title>Force Layout</title>
  <script type="text/javascript" src="http://code.jquery.com/jquery-2.1.0.js"></script>
  <script type="text/javascript" src="http://code.jquery.com/ui/1.11.0/jquery-ui.min.js"></script>
  <link rel="stylesheet" type="text/css" href="http://code.jquery.com/ui/1.11.0/themes/smoothness/jquery-ui.css">
  <script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
  <script type="text/javascript" src="https://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>

  <style id="compiled-css" type="text/css">
  .node {
      stroke: #fff;
      stroke-width: 0.5px;
  }
  .link {
      stroke: #999;
      stroke-opacity: .1;
  }
  d3-tip {
      line-height: 1;
      color: black;
  }
  div {
    display: inline-block;
  }
  form {
    display: inline-block;
  }
  svg {
    border: solid 1px blue;
  }
  body,
    .container {
        background-color: white;
        margin: 5px;
    }

    .graphContainer {
        text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white;
    }
    #sidebar{
        position: absolute;
        z-index: 2;
        background-color: #FFF;
        padding: 10px;
        margin: 5px;
        border: 1px solid #6895b4;
        min-height: 3px;
        width: 100px;
        top:45px;
        right:220px;
    }

    .item-group {
        margin-bottom: 5px;
    }

    .item-group .item-label {
        width: 90px;
        text-align: right;
        font-family: Arial,sans-serif;
        font-size: 14px;
        font-weight: bold;
        position: relative;
        min-height: 1px;
        margin-top: 5px;
        display: inline;
        padding-right: 5px;
        font-size: .90em;
    }

    .checkbox-interaction-group {
        margin-left: 10px;
        margin-top: 5px;
        clear: both;
    }

    .checkbox-container {
        display: block;
        min-height: 30px;
        vertical-align: middle;
        margin-left: 10px;
    }

    .checkbox-container label{
        display:inline;
        margin-bottom: 0px;
    }
  </style>

</head>
<body>
  <div id="container" class="container">
          <div id="sidebar" style="display: none;">
               <div class="item-group">
                   <label class="item-label">Filter</label>
                   <div id="filterContainer" class="filterContainer checkbox-interaction-group"></div>
               </div>
           </div>
  <div id="graphContainer" class="graphContainer">
  <script type="application/json" id="patent">
    {
    "nodes":[
    {"label": "label1","title":"label1","group": "group1", "type": "label1", "s":1},
    {"label": "label2","title":"label2","group": "group1", "type": "label2","s":1},
    {"label": "label3","title":"label3","group": "group1", "type": "label3","s":1},
    {"id":"5712454", "title": "title1", "group": "group2", "s":0},
    {"id":"5497941", "title": "title2", "group": "group2",  "s":0},
    {"id":"5517952", "title": "title3", "group": "group2",  "s":0},
    {"id":"4854277", "title": "title4", "group": "group2",  "s":0},
    {"id":"9556782", "title": "title5", "group": "group2",  "s":0}
  ],
  "links":[
    {"source": 3, "target": 0, "value": 1},
    {"source": 3, "target": 1, "value": 1},
    {"source": 3, "target": 2, "value": 1},
    {"source": 4, "target": 0, "value": 1},
    {"source": 5, "target": 2, "value": 1},
    {"source": 6, "target": 1, "value": 1},
    {"source": 7, "target": 2, "value": 1},
    {"source": 7, "target": 0, "value": 1},
    {"source": 6, "target": 2, "value": 1},
    {"source": 5, "target": 1, "value": 1}
    ]
    }
  </script>
  <!-- TODO: Missing CoffeeScript 2 -->

  <script type="text/javascript">

//Constants for the SVG
var width = window.innerWidth,
    height = window.innerHeight-47;

//Set up the colour scale
var color = d3.scale.category10();

//Set up the force layout
var force = d3.layout.force()
    .charge(-200)
    .linkDistance(20)
    .size([width, height]);

// Set up zoom behavior
var zoom = d3.behavior.zoom().scaleExtent([0.1,5]).on("zoom",redraw);

//Append a SVG to the body of the html page. Assign this SVG as an object to svg
var svg = d3.select("body")
    .append("svg")
    .attr("width", width-240)
    .attr("height", height)
    .call(zoom)
    .on("dblclick.zoom",null)
    .append('g');

var svg2 = d3.select("body")
    .append("svg")
    .attr("width", 200)
    .attr("height", height);

//Set up tooltip
var tip = d3.tip()
    .attr('class', 'd3-tip')
    .offset([-10, 0])
    .html(function (d) {
    return  d.title + "</span>";
    })
    svg.call(tip);

//Read the data from the mis element
var patent = document.getElementById('patent').innerHTML;
graph = JSON.parse(patent);

//Creates the graph data structure out of the json data
force.nodes(graph.nodes)
    .links(graph.links)
    .start()
    .friction(0.5);

//Create all the line svgs but without locations yet
var link = svg.selectAll(".link")
    .data(graph.links)
    .enter().append("line")
    .attr("class", "link")
    .style("stroke-width", function (d) {return Math.sqrt(d.value);
    });

//Do the same with the circles for the nodes - no
var node = svg.selectAll(".node")
    .data(graph.nodes)
    .enter().append("path")
    .attr("class", "node")
    .attr("d", d3.svg.symbol()
        .type(function(d) { return d3.svg.symbolTypes[d.s]; }))
    .style("fill", function (d) {return color(d.group);})
    .call(force.drag)
    .on('dblclick', connectedNodes)
    .on('mouseover', tip.show)
    .on('mouseout', tip.hide)
    .on("click", function(d){
      if (d3.event.shiftKey) {
        var url = "https://patents.google.com/patent/US"+ d.id
        alert("Redirecting you to " + url)
        window.open(url,"", "width=800,height=800");
       }
    });

var label = svg.selectAll(".mytext")
		.data(graph.nodes)
		.enter()
		.append("text")
    .text(function (d) { return d.label; })
    .style("text-anchor", "middle")
    .style("fill","gray")
    .style("font-family", "Arial")
    .style("font-size", 8);

//Zoom and Pan function
function redraw() {
    svg.attr("transform",
        "translate(" + d3.event.translate + ")"
        + " scale(" + d3.event.scale + ")");
    }

var drag = force.drag()
    .on("dragstart", function(d) {
	  d3.event.sourceEvent.stopPropagation();
    });

//Now we are giving the SVGs co-ordinates - the force layout is generating the co-ordinates which this code is using to update the attributes of the SVG elements
force.on("tick", function () {
    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 + ")";
        });

    label.attr("x", function(d){ return d.x; })
        .attr("y", function (d) {return d.y - 10; });
});

//Highlight function
//Toggle stores whether the highlighting is on
var toggle = 0;
//Create an array logging what is connected to what
var linkedByIndex = {};
for (i = 0; i < graph.nodes.length; i++) {
    linkedByIndex[i + "," + i] = 1;
};
graph.links.forEach(function (d) {
    linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
//This function looks up whether a pair are neighbours
function neighboring(a, b) {
    return linkedByIndex[a.index + "," + b.index];
}
function connectedNodes() {
    if (toggle == 0) {
        //Reduce the opacity of all but the neighbouring nodes
        d = d3.select(this).node().__data__;
        node.style("opacity", function (o) {
            return neighboring(d, o) | neighboring(o, d) ? 1 : 0.1;
        });
        label.style("opacity", function (o) {
            return neighboring(d, o) | neighboring(o, d) ? 1 : 0.1;
        });
        link.style("opacity", function (o) {
            return d.index==o.source.index | d.index==o.target.index ? 1 : 0.1;
        });
        //Reduce the op
        toggle = 1;
    } else {
        //Put them back to opacity=1
        node.style("opacity", 1);
        link.style("opacity", 1);
        label.style("opacity",1);
        toggle = 0;
    }
}

//Search function
var optArray = [];
for (var i = 0; i < graph.nodes.length; i++) {
    optArray.push(graph.nodes[i].title);
}

optArray = optArray.sort();

$(function () {
    $("#search").autocomplete({
        source: optArray
    });
});

window.searchNode = searchNode;

function searchNode() {
    //find the node
    var selectedVal = document.getElementById('search').value;
    if (selectedVal == "none") {
        node.style("stroke", "white").style("stroke-width", "1");
    } else {
        var selected = node.filter(function (d, i) {
            return d.title != selectedVal;
        });
        selected.style("opacity", "0");
        link.style("opacity", "0");
        label.style("opacity", "0");
        d3.selectAll(".node, .link").transition()
            .duration(2000)
            .style("opacity", 1);
        label.transition()
        .duration(2000)
        .style("opacity",1);

        var selectedNode = node
            .filter(function (d, i) { return d.title == selectedVal; })
            .datum();
        var scale = zoom.scale();
        var desiredPosition = { x: (width-240)/2, y: height/2}; // constants, set to svg center point
            zoom.translate([desiredPosition.x - selectedNode.x*scale, desiredPosition.y - selectedNode.y*scale]);
            zoom.event(svg);
          }
}

//Legend
var legend = svg2.selectAll(".legend")
    .data(color.domain())
    .enter().append("g")
    .attr("class", "legend")
    .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

legend.append("rect")
    .attr("x", 5)
    .attr("y",5)
    .attr("width", 18)
    .attr("height", 18)
    .style("fill", color);

legend.append("text")
    .attr("x",  30)
    .attr("y", 13)
    .attr("dy", ".35em")
    .style("text-anchor", "start")
    .text(function(d) { return d });

// call method to create filter
createFilter();
// method to create filter
function createFilter(){
   d3.select(".filterContainer").selectAll("div")
     .data(["label1","label2","label3"])
     .enter()
     .append("div")
     .attr("class", "checkbox-container")
     .append("label")
     .each(function(d) {
        // create checkbox for each data
   d3.select(this).append("input")
     .attr("type", "checkbox")
     .attr("id", function(d) {return "chk_" + d;})
     .attr("checked", true)
     .on("click", function(d, i) {
     // register on click event
     var lVisibility = this.checked? "visible":"hidden";
     filterGraph(d, lVisibility);
     })
     d3.select(this).append("span")
       .text(function(d){return d;});
     });

     $("#sidebar").show();  // show sidebar
}

var hidden_nodes =[];
// Method to filter graph
function filterGraph(aType, aVisibility){
// change the visibility of the node
// if all the links with that node are invisibile, the node should also be invisible
// otherwise if any link related to that node is visibile, the node should be visible
// change the visibility of the connection link
node.style("visibility", function(o) {
var lOriginalVisibility = $(this).css("visibility");
       if (o.type == aType) {
           if (aVisibility == "hidden")
               {
                   hidden_nodes.push(o.title);
               }
           else
               {
                   index = hidden_nodes.indexOf(o.title);
                   if (index > -1)
                   {
                       hidden_nodes.splice(index, 1);
                   }
               }
       }
       return o.type === aType ? aVisibility : lOriginalVisibility;
   });

label.style("visibility", function(o) {
var lOriginalVisibility = $(this).css("visibility");
      if (o.type == aType) {
          if (aVisibility == "hidden")
                  {
                  hidden_nodes.push(o.title);
                  }
          else
              {
                  index = hidden_nodes.indexOf(o.title);
                  if (index > -1)
                  {
                      hidden_nodes.splice(index, 1);
                  }
              }
      }
      return o.type === aType ? aVisibility : lOriginalVisibility;
  });

link.attr("display", function (o) {
////Here the structure of the the link can vary, sometimes it is o["source"]["name"], sometimes it is o["source"]["name"], check it out before you fill in.
var source_name = o["source"]["title"];
var target_name = o["target"]["title"];

var result = hidden_nodes.indexOf(source_name) != -1 || hidden_nodes.indexOf(target_name) != -1 ? "none" : "auto"
return result;
       })
     ;
     }
</script>
</div>
</body>
</html>
...