specallaw specallaw - 5 months ago 5
Javascript Question

node search bar for D3.js graph using jQuery

I have a web page that displays a force directed graph (with other features) using the D3 javascript library. I am trying to add a node search bar (using jQuery) that highlights the node when I've searched for one of it's attributes (name, clusterID, etc. found in the nodes part of the json file).

So I've got the web page displaying the search bar above the graph correctly, but the search button doesn't correctly highlight a node. I get a Chrome console message every time I type a character in the search bar:

VM84:1 Uncaught SyntaxError: Unexpected token < in JSON at position 0
. The full message is at the bottom of the question. Also, when I click the search button to search for a known name attribute of one of the nodes, nothing happens in the console.

I just can't seem to get the jQuery and D3 codes to work together, in particular the autocomplete feature in the
$()
jQuery function and the
searchNode
function in the graph.js file.

Can anybody help me? Below is the jsFiddle link, the html and javascript files to build the web page, and a screenshot of the Chrome console message. Thanks for any help!

jsFiddle: http://jsfiddle.net/spw1016/mc5Lprcr/

Chrome console full error:
enter image description here

Full error message after typing in the search bar:

Uncaught SyntaxError: Unexpected token < in JSON at position 0 VM89:1

parseJSON @ jquery-1.9.1.min.js:3
transformResult @ jquery.autocomplete.min.js:10
processResponse @ jquery.autocomplete.min.js:21
(anonymous function) @ jquery.autocomplete.min.js:19
c @ jquery-1.9.1.min.js:3
fireWith @ jquery-1.9.1.min.js:3
k @ jquery-1.9.1.min.js:5
r @ jquery-1.9.1.min.js:5


index.html:

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
overflow:hidden;
overflow:hidden;
margin:0;
}

text {
font-family: sans-serif;
pointer-events: none;
}
</style>
<head lang="en">
<title>Graph Visualization</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="js/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="js/jquery.autocomplete.min.js"></script>
<script type="text/javascript" src="js/currency-autocomplete.js"></script>
</head>
<body>
<div class="ui-widget">
<input id="search">
<button type="button" .onclick="searchNode()">Search</button>
</div>
</body>
<script src="graph.js"></script>
</html>


graph.js:

var w = window.innerWidth;
var h = window.innerHeight;

var keyc = true, keys = true, keyt = true, keyr = true, keyx = true, keyd = true, keyl = true, keym = true, keyh = true, key1 = true, key2 = true, key3 = true, key0 = true

var focus_node = null, highlight_node = null;

var text_center = false;
var outline = false;

var min_score = 1;
var max_score = 10;

/*
var color = d3.scale.linear()
.domain(1, 10)
.range(["lime", "yellow", "red"]);
*/

var color = d3.scale.linear()
.domain([min_score, (min_score+max_score)/2, max_score])
.range(["lime", "yellow", "red"]);

var highlight_color = "blue";
var highlight_trans = 0.1;

var size = d3.scale.pow().exponent(1)
.domain([1,100])
.range([8,24]);

var force = d3.layout.force()
.linkDistance(60)
.charge(-300)
.size([w,h]);

var default_node_color = "#ccc";
//var default_node_color = "rgb(3,190,100)";
var default_link_color = "#888";
var nominal_base_node_size = 8;
var nominal_text_size = 10;
var max_text_size = 24;
var nominal_stroke = 1.5;
var max_stroke = 4.5;
var max_base_node_size = 36;
var min_zoom = 0.1;
var max_zoom = 7;
var svg = d3.select("body").append("svg");
var zoom = d3.behavior.zoom().scaleExtent([min_zoom,max_zoom])
var g = svg.append("g");
svg.style("cursor","move");
/*
var request = new XMLHttpRequest();
request.open("GET", "graph.json", false);
request.send(null)
var graph = JSON.parse(request.responseText);
*/

d3.json("graph.json", function(error, graph) {

var linkedByIndex = {};
graph.links.forEach(function(d) {
linkedByIndex[d.source + "," + d.target] = true;
});

function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}

function hasConnections(a) {
for (var property in linkedByIndex) {
s = property.split(",");
if ((s[0] == a.index || s[1] == a.index) && linkedByIndex[property]) return true;
}
return false;
}

force
.nodes(graph.nodes)
.links(graph.links)
.start();

var link = g.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width",nominal_stroke)
.style("stroke", default_link_color)


var node = g.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")

.call(force.drag)


node.on("dblclick.zoom", function(d) { d3.event.stopPropagation();
var dcx = (window.innerWidth/2-d.x*zoom.scale());
var dcy = (window.innerHeight/2-d.y*zoom.scale());
zoom.translate([dcx,dcy]);
g.attr("transform", "translate("+ dcx + "," + dcy + ")scale(" + zoom.scale() + ")");


});


var tocolor = "fill";
var towhite = "stroke";
if (outline) {
tocolor = "stroke"
towhite = "fill"
}

var circle = node.append("path")


.attr("d", d3.svg.symbol()
.size(function(d) { return Math.PI*Math.pow(size(d.size)||nominal_base_node_size,2); })
.type(function(d) { return d.type; }))

.style("fill", function(d) {return color(d.clusterID);})
.style("stroke-width", nominal_stroke)
.style(towhite, "white");


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

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

node.on("mouseover", function(d) {
set_highlight(d);
})
.on("mousedown", function(d) { d3.event.stopPropagation();
focus_node = d;
set_focus(d)
if (highlight_node === null) set_highlight(d)}).on("mouseout", function(d) {
exit_highlight();});

d3.select(window).on("mouseup",
function() {
if (focus_node!==null)
{
focus_node = null;
if (highlight_trans<1)
{

circle.style("opacity", 1);
text.style("opacity", 1);
link.style("opacity", 1);
}
}

if (highlight_node === null) exit_highlight();
});

function exit_highlight()
{
highlight_node = null;
if (focus_node===null)
{
svg.style("cursor","move");
if (highlight_color!="white")
{
circle.style(towhite, "white");
text.style("font-weight", "normal");
link.style("stroke", function(o) {return (isNumber(o.score) && o.score>=0)?color(o.score):default_link_color});
}

}
}

function set_focus(d)
{
if (highlight_trans<1) {
circle.style("opacity", function(o) {
return isConnected(d, o) ? 1 : highlight_trans;
});

text.style("opacity", function(o) {
return isConnected(d, o) ? 1 : highlight_trans;
});

link.style("opacity", function(o) {
return o.source.index == d.index || o.target.index == d.index ? 1 : highlight_trans;
});
}
}


function set_highlight(d)
{
svg.style("cursor","pointer");
if (focus_node!==null) d = focus_node;
highlight_node = d;

if (highlight_color!="white")
{
circle.style(towhite, function(o) {
return isConnected(d, o) ? highlight_color : "white";});
text.style("font-weight", function(o) {
return isConnected(d, o) ? "bold" : "normal";});
link.style("stroke", function(o) {
return o.source.index == d.index || o.target.index == d.index ? highlight_color : ((isNumber(o.score) && o.score>=0)?color(o.score):default_link_color);

});
}
}


zoom.on("zoom", function() {

var stroke = nominal_stroke;
if (nominal_stroke*zoom.scale()>max_stroke) stroke = max_stroke/zoom.scale();
link.style("stroke-width",stroke);
circle.style("stroke-width",stroke);

var base_radius = nominal_base_node_size;
if (nominal_base_node_size*zoom.scale()>max_base_node_size) base_radius = max_base_node_size/zoom.scale();
circle.attr("d", d3.svg.symbol()
.size(function(d) { return Math.PI*Math.pow(size(d.size)*base_radius/nominal_base_node_size||base_radius,2); })
.type(function(d) { return d.type; }))

//circle.attr("r", function(d) { return (size(d.size)*base_radius/nominal_base_node_size||base_radius); })
if (!text_center) text.attr("dx", function(d) { return (size(d.size)*base_radius/nominal_base_node_size||base_radius); });

var text_size = nominal_text_size;
if (nominal_text_size*zoom.scale()>max_text_size) text_size = max_text_size/zoom.scale();
text.style("font-size",text_size + "px");

g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
});

svg.call(zoom);

resize();
//window.focus();
d3.select(window).on("resize", resize).on("keydown", keydown);

force.on("tick", function() {

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

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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});


var optArray = [];
for (var i = 0; i < graph.nodes.length - 1; i++) {
optArray.push(graph.nodes[i].name);
}
optArray = optArray.sort();
$(function () {
$("#search").autocomplete({
source: optArray
});
});
function searchNode() {
//find the node
var selectedVal = document.getElementById('search').value;
//node = svg.selectAll(".node");
if (selectedVal == "none") {
node.style("stroke", "white").style("stroke-width", "1");
} else {
var selected = node.filter(function (d, i) {
return d.name != selectedVal;
});
selected.style("opacity", "0");
//var link = svg.selectAll(".link")
link.style("opacity", "0");
d3.selectAll(".node, .link").transition()
.duration(5000)
.style("opacity", 1);
}
}


function resize() {
var width = window.innerWidth, height = window.innerHeight;
svg.attr("width", width).attr("height", height);

force.size([force.size()[0]+(width-w)/zoom.scale(),force.size()[1]+(height-h)/zoom.scale()]).resume();
w = width;
h = height;
}

function keydown() {
if (d3.event.keyCode==32) { force.stop();}
else if (d3.event.keyCode>=48 && d3.event.keyCode<=90 && !d3.event.ctrlKey && !d3.event.altKey && !d3.event.metaKey)
{
switch (String.fromCharCode(d3.event.keyCode)) {
case "C": keyc = !keyc; break;
case "S": keys = !keys; break;
case "T": keyt = !keyt; break;
case "R": keyr = !keyr; break;
case "X": keyx = !keyx; break;
case "D": keyd = !keyd; break;
case "L": keyl = !keyl; break;
case "M": keym = !keym; break;
case "H": keyh = !keyh; break;
case "1": key1 = !key1; break;
case "2": key2 = !key2; break;
case "3": key3 = !key3; break;
case "0": key0 = !key0; break;
}

link.style("display", function(d) {
var flag = vis_by_type(d.source.type)&&vis_by_type(d.target.type)&&vis_by_node_score(d.source.score)&&vis_by_node_score(d.target.score)&&vis_by_link_score(d.score);
linkedByIndex[d.source.index + "," + d.target.index] = flag;
return flag?"inline":"none";});
node.style("display", function(d) {
return (key0||hasConnections(d))&&vis_by_type(d.type)&&vis_by_node_score(d.score)?"inline":"none";});
text.style("display", function(d) {
return (key0||hasConnections(d))&&vis_by_type(d.type)&&vis_by_node_score(d.score)?"inline":"none";});

if (highlight_node !== null)
{
if ((key0||hasConnections(highlight_node))&&vis_by_type(highlight_node.type)&&vis_by_node_score(highlight_node.score)) {
if (focus_node!==null) set_focus(focus_node);
set_highlight(highlight_node);}
else {exit_highlight();}
}
}
}
});


function vis_by_type(type)
{
switch (type) {
case "circle": return keyc;
case "square": return keys;
case "triangle-up": return keyt;
case "diamond": return keyr;
case "cross": return keyx;
case "triangle-down": return keyd;
default: return true;
}
}
function vis_by_node_score(score)
{
if (isNumber(score))
{
if (score>=0.666) return keyh;
else if (score>=0.333) return keym;
else if (score>=0) return keyl;
}
return true;
}

function vis_by_link_score(score)
{
if (isNumber(score))
{
if (score>=0.666) return key3;
else if (score>=0.333) return key2;
else if (score>=0) return key1;
}
return true;
}

function isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}

Answer

The problem with search was that the function was not getting called as you are appending a dot in front of onclick:

You were doing

<button type="button" .onclick="searchNode()">Search</button>

                      ^
                      |____ this is incorrect no need for a dot here.

here is a screen shot: http://www.clipular.com/c/5115163935506432.png?k=HwZkU1_RriBsPlAfjsZWNzGcBSY

Working code here

Comments