Question

I'm a noob and trying to implement a search method for a diagram.

The diagram is a chord diagram and was mostly adapted from here:

http://bl.ocks.org/mbostock/1044242

enter image description here

And the search function was taken from here:

http://mbostock.github.io/protovis/ex/treemap.html

My problem is that when it reads my file it interprets the text as: [object SVGTextElement] and so the only hit I have for my search is if I search [object SVGTextElement].

This is my entire code:

<html>
  <head>
    <title>I'm Cool</title>
    <link rel="stylesheet" type="text/css" href="ex.css?3.2"/>
    <script type="text/javascript" src="../protovis-r3.2.js"></script>
    <script type="text/javascript" src="bla3.json"></script>
    <style type="text/css">

.node {
  font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
  fill: #bbb;
}

.node:hover {
  fill: #000;
}

.link {
  stroke: steelblue;
  stroke-opacity: 0.4;
  fill: none;
  pointer-events: none;
}

.node:hover,
.node--source,
.node--target {
  font-weight: 700;
}

.node--source {
  fill: #2ca02c;
}

.node--target {
  fill: #d62728;
}


.link--source,
.link--target {
  stroke-opacity: 1;
  stroke-width: 2px;
}

.link--source {
  stroke: #d62728;
}

.link--target {
  stroke: #2ca02c;
}
#fig {
  width: 860px;
}

#footer {
  font: 24pt helvetica neue;
  color: #666;
}

input {
  font: 24pt helvetica neue;
  background: none;
  border: none;
  outline: 0;
}

#title {
  float: right;
  text-align: right;
}
</style>
<body><div id="center"><div id="fig">
    <div id="title"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>

var diameter = 800,
    radius = diameter / 2,
    innerRadius = radius - 160;

var cluster = d3.layout.cluster()
    .size([360, innerRadius])
    .sort(null)
    .value(function(d) { return d.size; });

var bundle = d3.layout.bundle();

var line = d3.svg.line.radial()
    .interpolate("bundle")
    .tension(.85)
    .radius(function(d) { return d.y; })
    .angle(function(d) { return d.x / 180 * Math.PI; });

var svg = d3.select("body").append("svg")
    .attr("width", diameter)
    .attr("height", diameter)
  .append("g")
    .attr("transform", "translate(" + radius + "," + radius + ")");

var link = svg.append("g").selectAll(".link"),
    node = svg.append("g").selectAll(".node");

d3.json("bla3.json", function(error, classes) {
  var nodes = cluster.nodes(packageHierarchy(classes)),
      links = packageImports(nodes);

  link = link
      .data(bundle(links))
    .enter().append("path")
      .each(function(d) { d.source = d[0], d.target = d[d.length - 1]; })
      .attr("class", "link")
      .attr("d", line);

  node = node
      .data(nodes.filter(function(n) { return !n.children; }))
    .enter().append("text")
      .attr("class", "node")
      .attr("dx", function(d) { return d.x < 180 ? 12 : -12; })
      .attr("dy", ".31em")
      .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y +  ")" + (d.x < 180 ? "" : "rotate(180)"); })
      .style("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
      .text(function(d) { return d.key; })
      .on("mouseover", mouseovered)
      .on("mouseout", mouseouted);
});

function mouseovered(d) {
  node
      .each(function(n) { n.target = n.source = false; });

  link
      .classed("link--target", function(l) { if (l.target === d) return l.source.source = true; })
      .classed("link--source", function(l) { if (l.source === d) return l.target.target = true; })
    .filter(function(l) { return l.target === d || l.source === d; })
      .each(function() { this.parentNode.appendChild(this); });

  node
      .classed("node--target", function(n) { return n.target; })
      .classed("node--source", function(n) { return n.source; });
}

function mouseouted(d) {
  link
      .classed("link--target", false)
      .classed("link--source", false);

  node
      .classed("node--target", false)
      .classed("node--source", false);
}

d3.select(self.frameElement).style("height", diameter + "px");

// Lazily construct the package hierarchy from class names.
function packageHierarchy(classes) {
  var map = {};

  function find(name, data) {
    var node = map[name], i;
    if (!node) {
      node = map[name] = data || {name: name, children: []};
      if (name.length) {
        node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
        node.parent.children.push(node);
        node.key = name.substring(i + 1);
      }
    }
    return node;
  }

  classes.forEach(function(d) {
    find(d.name, d);
  });

  return map[""];
}

// Return a list of imports for the given array of nodes.
function packageImports(node) {
  var map = {},
      imports = [];

  // Compute a map from name to node.
  node.forEach(function(d) {
    map[d.name] = d;
  });

  // For each import, construct a link from the source to target node.
  node.forEach(function(d) {
    if (d.imports) d.imports.forEach(function(i) {
      imports.push({source: map[d.name], target: map[i]});
    });
  });

  return imports;
}
function title(d) {
  return d.parentNode ? (title(d.parentNode) + "." + d.nodeName) : d.nodeName;
}

var re = "",
    color = pv.Colors.category19().by(function(d) d.parentNode.nodeName)
    node = pv.dom(bla3).root("bla3.json").node();

var vis = new pv.Panel()
    .width(860)
    .height(568);


cluster.bundle.add(pv.Panel)
    .fillStyle(function(d) color(d).alpha(title(d).match(re) ? 1 : .2))
    .strokeStyle("#fff")
    .lineWidth(1)
    .antialias(false);

cluster.bundle.add(pv.Label)
    .textStyle(function(d) pv.rgb(0, 0, 0, title(d).match(re) ? 1 : .2));

vis.render();

/** Counts the number of matching classes, updating the title element. */
function count() {
  var classes = 0, bytes = 0, total = 0;
  for (var i = 0; i < node.length; i++) {
    var n = node[i];
    if(n.firstChild) continue;
    total += n.nodeValue;
    if (title(n).match(re)) {
      classes++;
      bytes += n.nodeValue;
    }
  }
  var percent = bytes / total * 100;
  document.getElementById("title").innerHTML
      = classes + " classes found "+n;
}

/** Updates the visualization and count when a new query is entered. */
function update(query) {
  if (query != re) {
    re = new RegExp(query, "i");
    count();
    vis.render();
  }
}

count();
</script>
 <div id="footer">
      <label for="search">search: </label>
      <input type="text" id="search" onkeyup="update(this.value)">
    </div>
  </div></div></body>
</html>

The input is bla3.json and looks like this:

[{"name":"A.Patient Intake","imports":["E.Name","C.injury","E.DOB","E.Email","Progress","B.Obtain Brief Medical History","Perform Physical Exam","Perform Subjective Patient Evaluation"]},
{"name":"C.injury","imports":[]},
{"name":"E.Name","imports":[]},
{"name":"E.Email","imports":[]},
...

I didn't put the whole thing but it shouldn't matter...

My purpose is of course to have a search function that I could type, for example, "Patient Intake" and it will highlight that chord (or just the name):

enter image description here

Any ideas of how to go about this?

Was it helpful?

Solution

I would approach this in a completely different way to what you're currently doing. I would filter the data based on the query (not the DOM elements) and then use D3's data matching to determine what to highlight. In code, this would look something like this.

function update(query) {
  if (query != re) {
    re = new RegExp(query, "i");

    var matching = classes.filter(function(d) { return d.name.match(re); });

    d3.selectAll("text.node").data(matching, function(d) { return d.name; })
      // do something with the nodes

    // can be source or target in links, so we use a different method here
    links.filter(function(d) {
        var ret = false;
        matching.forEach(function(e) {
          ret = ret || e.name == d.source.name || e.name == d.target.name;
        });
        return ret;
      })
      // do something with the links
  }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top