Reputation: 729
I am using d3 to create a force nodes model. I meet an error that ID selector can't find node with certain ID. What I want to do is when your mouse moves onto the node a text shows up and when mouse leaves it disappears. But my problem is it won't disappear because the selector can't find it.
I am pretty new to d3 so I copy many codes from many examples to it looks in a mess. I attach the whole code so that you can run it in the browser directly. You can look handleMouseOut
and handleMouseOver
to watch over the main point.
The problem looks like
Uncaught DOMException: Failed to execute 'querySelector' on 'Document': '#t330.17798857827233-303.63309689212775-4' is not a valid selector.
But the text with that ID is already there. Could you help me to fix this?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Force Layout with labels on edges</title>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<style type="text/css">
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
</head>
<body>
<script type="text/javascript">
var w = 1000;
var h = 600;
var linkDistance=200;
radius = 20;
var colors = d3.scale.category10();
var dataset = {
nodes: [
{name: "Adam"},
{name: "Bob"},
{name: "Carrie"},
{name: "Donovan"},
{name: "Edward"},
{name: "Felicity"},
{name: "George"},
{name: "Hannah"},
{name: "Iris"},
{name: "Jerry"}
],
edges: [
{source: 0, target: 1, weight:1},
{source: 0, target: 2, weight:1},
{source: 0, target: 3, weight:1},
{source: 0, target: 4, weight:1},
{source: 1, target: 5, weight:1},
{source: 2, target: 5, weight:1},
{source: 2, target: 5, weight:1},
{source: 3, target: 4, weight:1},
{source: 5, target: 8, weight:1},
{source: 5, target: 9, weight:1},
{source: 6, target: 7, weight:1},
{source: 7, target: 8, weight:1},
{source: 8, target: 9, weight:1}
]
};
var svg = d3.select("body").append("svg").attr({"width":w,"height":h}).call(d3.behavior.zoom().on("zoom", function () {
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")")
})).append("g");;
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w,h])
.linkDistance([linkDistance])
.charge([-500])
.theta(0.1)
.gravity(0.05)
.start();
var circleAttrs = {
r: radius,
};
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.attr("id",function(d,i) {return 'edge'+i})
.style("stroke","#000")
.style("stroke-width", function(d,i) {return d.weight + "px"})
.style("pointer-events", "none");
var nodes = svg.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.attr(circleAttrs) // Get attributes from circleAttrs var
.on("mouseover", handleMouseOver)
.on("mouseout", handleMouseOut)
.on("click", clicked)
.style("fill",function(d,i){return colors(i);})
.call(force.drag)
/* svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr(circleAttrs) // Get attributes from circleAttrs var
.on("mouseover", handleMouseOver)
.on("mouseout", handleMouseOut)
.on("click", clicked);*/
var nodelabels = svg.selectAll(".nodelabel")
.data(dataset.nodes)
.enter()
.append("text")
.attr({"x":function(d){return d.x;},
"y":function(d){return d.y;},
"class":"nodelabel",
"stroke":"black"})
.text(function(d){return d.name;});
var edgepaths = svg.selectAll(".edgepath")
.data(dataset.edges)
.enter()
.append('path')
.attr({'d': function(d) {return 'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y},
'class':'edgepath',
'fill-opacity':0,
'stroke-opacity':0,
'fill':'blue',
'stroke':'red',
'id':function(d,i) {return 'edgepath'+i}})
.style("pointer-events", "none");
var edgelabels = svg.selectAll(".edgelabel")
.data(dataset.edges)
.enter()
.append('text')
.style("pointer-events", "none")
.attr({'class':'edgelabel',
'id':function(d,i){return 'edgelabel'+i},
'dx':80,
'dy':0,
'font-size':10,
'fill':'#aaa'});
edgelabels.append('textPath')
.attr('xlink:href',function(d,i) {return '#edgepath'+i})
.style("pointer-events", "none")
.text(function(d,i){return 'label '+i});
svg.append('defs').append('marker')
.attr({'id':'arrowhead',
'viewBox':'-0 -5 10 10',
'refX':25,
'refY':0,
//'markerUnits':'strokeWidth',
'orient':'auto',
'markerWidth':10,
'markerHeight':10,
'xoverflow':'visible'})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#ccc')
.attr('stroke','#ccc');
force.on("tick", function(){
edges.attr({"x1": function(d){return d.source.x;},
"y1": function(d){return d.source.y;},
"x2": function(d){return d.target.x;},
"y2": function(d){return d.target.y;}
});
nodes.attr({"cx":function(d){return d.x;},
"cy":function(d){return d.y;}
});
nodelabels.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
edgepaths.attr('d', function(d) { var path='M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y;
//console.log(d)
return path});
edgelabels.attr('transform',function(d,i){
if (d.target.x<d.source.x){
bbox = this.getBBox();
rx = bbox.x+bbox.width/2;
ry = bbox.y+bbox.height/2;
return 'rotate(180 '+rx+' '+ry+')';
}
else {
return 'rotate(0)';
}
});
});
var formatTime = function(){
return 'time';
};
// Create Event Handlers for mouse
function handleMouseOver(d, i) { // Add interactivity
div.transition()
.duration(200)
.style("opacity", .9);
div .html(formatTime(d.date) + "<br/>" + d.close)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
// Use D3 to select element, change color and size
d3.select(this).attr({
fill: "orange",
r: radius+10
});
// Specify where to put label of text
svg.append("text").attr({
id: "t" + d.x + "-" + d.y + "-" + i, // Create an id for text so we can select it later for removing on mouseout
x: function() { return d.x - 30; },
y: function() { return d.y - 15; }
})
.text(function() {
console.log(d);
return [d.x, d.y]; // Value of the text
});
}
var clickScale = 2.0; // scale used when circle is clicked
function clicked(d, i) {
if (d3.event.defaultPrevented) {
return; // panning, not clicking
}
node = d3.select(this);
var transform = getTransform(node, clickScale);
svg.transition().duration(1000)
.attr("transform", "translate(" + transform.translate + ")scale(" + transform.scale + ")");
d3.behavior.zoom().scale(transform.scale)
.translate(transform.translate);
scale = transform.scale;
}
function getTransform(node, thisScale) {
bbox = node.node().getBBox();
var bx = bbox.x;
var by = bbox.y;
var bw = bbox.width;
var bh = bbox.height;
var tx = -bx*thisScale + w/2 - bw*thisScale/2;
var ty = -by*thisScale + h/2 - bh*thisScale/2;
return {translate: [tx, ty], scale: thisScale}
}
function handleMouseOut(d, i) {
div.transition()
.duration(500)
.style("opacity", 0);
// Use D3 to select element, change color back to normal
d3.select(this).attr({
fill: "grey",
r: radius
});
// Select text by id and then remove
d3.select("#t" + d.x + "-" + d.y + "-" + i).remove(); // Remove text location
}
</script>
</body>
</html>
Upvotes: 0
Views: 1707
Reputation: 1074385
The error is quite clear, and correct; #t330.17798857827233-303.63309689212775-4
is indeed an invalid selector. The .
introduces a class selector, and class selectors are not allowed to start with unescaped digits.
To get an element by ID, your best bet is getElementById
:
var theElement = document.getElementById("t330.17798857827233-303.63309689212775-4");
Note that there' no leading #
, because getElementById
doesn't use CSS syntax.
If you have to use a CSS selector, you can use an attribute selector:
var theElement = document.querySelector('[id="t330.17798857827233-303.63309689212775-4"]');
That way, the whole value is taken as the ID to find.
Upvotes: 3