1

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?

VividD
  • 10,456
  • 6
  • 64
  • 111
FairyDuster
  • 145
  • 3
  • 13

1 Answers1

3

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
  }
}
Lars Kotthoff
  • 107,425
  • 16
  • 204
  • 204
  • Thanks! I've been trying to call my count() method from within this function and it doesn't work.. I want to highlight the matching node and count how many hits are there. :) – FairyDuster Apr 08 '14 at 22:46
  • The number of matching classes would be `matching.length`. – Lars Kotthoff Apr 09 '14 at 08:26
  • Thank you so much! I still have the same problem though. If I try to print out `matching` for example, I just get `[object SVGTextElement]` rather than the actual name. Any ideas? – FairyDuster Apr 10 '14 at 22:03
  • Actually never mind, I only want it to highlight the ones that matches. Counting them does work when I use `matching[0].length`. Is there any easy way to highlight the selected ones? – FairyDuster Apr 10 '14 at 22:31
  • You could set a style on everything in `matching`. – Lars Kotthoff Apr 11 '14 at 08:23
  • Any idea why if I do `d3.selectAll(matching[0]).text('bla');` it does change the selcted ones to "bla" but when I do something like `d3.selectAll(matching[0]).style("color",'#DE3378');` it doesn't work? – FairyDuster Apr 11 '14 at 19:22
  • Never mind. Should have used "fill". Thanks for everything!! Now I only need to find a way to make it change back after I delete the query! – FairyDuster Apr 11 '14 at 19:29