d3js 搜索节点及其子节点

d3js search nodes and its child

我的项目是做一个带有文本框的力导向图来搜索任何节点。当用户搜索某些内容时,我想突出显示该节点及其相邻节点,就像您将鼠标悬停在节点上时 here 所做的那样。

我的source code:

var width = 900,
  height = 590;

// var zoom = d3.behavior.zoom().scaleExtent([0.1,5]).on("zoom");
var svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height)
  .append('g');

d3.json("https://raw.githubusercontent.com/khalidal-walid/fyp/master/countries.json", function(data) {

  // Extract the nodes and links from the data.
  var nodes = data["nodes"];
  var links = data["links"];

  //CONNECTIONS
  var hash_lookup = [];
  nodes.forEach(function(d, i) {
    hash_lookup[d.country] = d;
  });
  links.forEach(function(d, i) {
    d.source = hash_lookup[d.source];
    d.target = hash_lookup[d.target];
  });

  // Now we create a force layout object and define its properties.
  // Those include the dimensions of the visualization and the arrays of nodes and links.
  var force = d3.layout.force()
    .size([width, height])
    .nodes(d3.values(nodes))
    .links(links)
    .on('tick', tick)
    .linkDistance(100)
    .gravity(.15)
    .friction(.8)
    .linkStrength(1)
    .charge(-425)
    .chargeDistance(600)
    .start();

  //LINKS
  var link = svg.selectAll('.link')
    .data(links)
    .enter().append('line')
    .attr('class', 'link');

  //NODES
  var node = svg.selectAll('.node')
    .data(force.nodes())
    .enter().append('circle')
    .attr('class', 'node')
    .attr('r', width * 0.01)

  //LABELS
  var text_center = false;
  var nominal_text_size = 12;
  var max_text_size = 22;
  var nominal_base_node_size = 8;
  var max_base_node_size = 36;
  var size = d3.scale.pow().exponent(1)
    .domain([1, 100])
    .range([8, 24]);

  var text = svg.selectAll(".text")
    .data(nodes)
    .enter().append("text")
    .attr("dy", ".35em")
    .style("font-size", nominal_text_size + "px")

  if (text_center)
    text.text(function(d) {
      return d.code;
    })
    .style("text-anchor", "middle");

  else
    text.attr("dx", function(d) {
      return (size(d.size) || nominal_base_node_size);
    })
    .text(function(d) {
      return '\u2002' + d.code;
    });

  //ZOOM AND PAN
  // 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();
  //   });

  //NODES IN SPACE
  function tick(e) {

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

    node.attr('cx', function(d) {
        return d.x;
      })
      .attr('cy', function(d) {
        return d.y;
      })
      .call(force.drag);

    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;
      });

  };

  //AUTOCOMPLETE SEARCH
  var optArray = [];

  for (var i = 0; i < nodes.length - 1; i++) {
    optArray.push(nodes[i].code);
  }

  optArray = optArray.sort();

  window.searchNode = searchNode;

  function searchNode() {
    var selectedVal = document.getElementById('search').value;
    if (selectedVal == 'none') {} else {
      var selected = node.filter(function(d, i) {
        return d.code != selectedVal;
      });
      var selectedText = text.filter(function(d, i) {
        return d.code != selectedVal;
      });
      selected.style("opacity", "0");
      selectedText.style("opacity", "0");
      var link = svg.selectAll(".link")
      link.style("opacity", "0");
      d3.selectAll(".node, .link, .text").transition()
        .duration(3000)
        .style("opacity", '1');

      var selectedNode = node
        .filter(function(d, i) {
          return d.code == selectedVal;
        })
        .datum();

      var scale = zoom.scale();
      var desiredPosition = {
        x: 200,
        y: 200
      }; // constants, set to svg center point
      zoom.translate([desiredPosition.x - selectedNode.x * scale, desiredPosition.y - selectedNode.y * scale]);
      zoom.event(svg);
    }
  }
})
.node {
  stroke: #aaa;
  stroke-width: 2px;
}

.link {
  stroke: #aaa;
  stroke-width: 2px;
}
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js'></script>
<div class="ui-widget">
  <input id="search" , size='54' , autocomplete="off">
  <button type="button" onclick="searchNode()">Search</button>
</div>

我想下面就是你想要的。我首先检查是否有任何节点与搜索词匹配,如果是,我找到所有不涉及匹配节点的链接,并将它们隐藏。同时,我填充 highlightCodes,一个包含匹配代码和所有直接邻居代码的数组。然后我隐藏所有与 highlightCodes 数组不匹配的节点。

var width = 900,
  height = 590;

// var zoom = d3.behavior.zoom().scaleExtent([0.1,5]).on("zoom");
var svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height)
  .append('g');

d3.json("https://raw.githubusercontent.com/khalidal-walid/fyp/master/countries.json", function(data) {

  // Extract the nodes and links from the data.
  var nodes = data["nodes"];
  var links = data["links"];

  //CONNECTIONS
  var hash_lookup = [];
  nodes.forEach(function(d, i) {
    hash_lookup[d.country] = d;
  });
  links.forEach(function(d, i) {
    d.source = hash_lookup[d.source];
    d.target = hash_lookup[d.target];
  });

  // Now we create a force layout object and define its properties.
  // Those include the dimensions of the visualization and the arrays of nodes and links.
  var force = d3.layout.force()
    .size([width, height])
    .nodes(d3.values(nodes))
    .links(links)
    .on('tick', tick)
    .linkDistance(100)
    .gravity(.15)
    .friction(.8)
    .linkStrength(1)
    .charge(-425)
    .chargeDistance(600)
    .start();

  //LINKS
  var link = svg.selectAll('.link')
    .data(links)
    .enter()
    .append('line')
    .attr('class', 'link');

  //NODES
  var node = svg.selectAll('.node')
    .data(force.nodes())
    .enter()
    .append('circle')
    .attr('class', 'node')
    .attr('r', width * 0.01);

  //LABELS
  var text_center = false;
  var nominal_text_size = 12;
  var max_text_size = 22;
  var nominal_base_node_size = 8;
  var max_base_node_size = 36;
  var size = d3.scale.pow()
    .exponent(1)
    .domain([1, 100])
    .range([8, 24]);

  var text = svg.selectAll(".text")
    .data(nodes)
    .enter()
    .append("text")
    .attr("dy", ".35em")
    .style("font-size", nominal_text_size + "px");

  if(text_center) {
    text.text(function(d) {
      return d.code;
    })
      .style("text-anchor", "middle");
  } else {
    text.attr("dx", function(d) {
      return (size(d.size) || nominal_base_node_size);
    })
      .text(function(d) {
        return '\u2002' + d.code;
      });
  }

  //ZOOM AND PAN
  // 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();
  //   });

  //NODES IN SPACE
  function tick(e) {

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

    node.attr('cx', function(d) {
      return d.x;
    })
      .attr('cy', function(d) {
        return d.y;
      })
      .call(force.drag);

    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;
      });

  };

  //AUTOCOMPLETE SEARCH
  window.searchNode = searchNode;

  function resetSelection() {
    node.style('opacity', null);
    link.style('opacity', null);
    text.style('opacity', null);
  }

  function searchNode() {
    var selectedVal = document.getElementById('search').value;
    if(selectedVal == 'none') {
      return resetSelection();
    }

    var matchingNode = nodes.find(function(d) {
      return d.code === selectedVal;
    });

    if(matchingNode === undefined) {
      return resetSelection();
    }
    
    var matchingCode = matchingNode.code;

    var highlightCodes = [];

    link
      .filter(function(d) {
        if(d.source.code === matchingCode ||
            d.target.code === matchingCode) {
          highlightCodes.push(d.source.code, d.target.code);
          return false;
        }
        return true;
      })
      .transition()
      .duration(500)
      .style("opacity", 0.3);

    d3.selectAll(".node, .text")
      .filter(function(d) {
        return highlightCodes.indexOf(d.code) === -1;
      })
      .transition()
      .duration(500)
      .style("opacity", 0.3);
  }
});
.node {
  stroke: #aaa;
  stroke-width: 2px;
}

.link {
  stroke: #aaa;
  stroke-width: 2px;
}
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js'></script>
<div class="ui-widget">
  <input id="search" size='54' autocomplete="off">
  <button type="button" onclick="searchNode()">Search</button>
</div>