d3js attach object to svg - javascript

I am creating a very long div containing hundreds of svg lines created by the following method:
function visualizeit(ORFdata,max) {
var browser = d3.select("#viewer")
.append("svg")
.attr("width", max/10)
.attr("height",'50%');
//Add svg to the svg container
for (orf in ORFdata) {
var line = browser.append("svg:line");
var object = ORFdata[orf]
line.datum(object)
line.attr("id", 'mygroup'+orf)
line.attr("x1", function(d){ return ORFdata[orf]["start"]/10})
line.attr("x2", function(d){ return ORFdata[orf]["stop"]/10})
line.attr("y1", function(d){ if (ORFdata[orf]["strand"] == "+1") {return 50} else {return 10}})
line.attr("y2", function(d){ if (ORFdata[orf]["strand"] == "+1") {return 50} else {return 10}})
line.style("stroke", "rgb(6,120,155)")
line.style("stroke-width", orf)
line.on('mouseover', function(d){console.log(d3.select("#mygroup"+orf).datum())})
}
}
However, when I do a mouseover on no matter what line I only get the data back from the last element. At first I thought it was due to 'mygroup' so I added a counter to it +orf but it somehow still erases my older stored data.
When I look in the created html code a svg seems correct by ID at least.
<line id="mygroup50" x1="103356.7" x2="103231.1" y1="10" y2="10" style="stroke: #06789b; stroke-width: 50px;"></line>
But somewhere the link goes awfully wrong...
How I fixed it so far...
var svgContainer = d3.select("body").append("svg")
.attr("width", max/10)
.attr("height", '50%');
//Add svg to the svg container
var lines = svgContainer.selectAll("line")
.data(ORFdata)
.enter()
.append("line")
.attr("x1", function(d){ return d.start/10})
.attr("y1", function(d){ if (d.strand == "+1") {return 65} else {return 10}})
.attr("x2", function(d){ return d.stop/10})
.attr("y2", function(d){ if (d.strand == "+1") {return 65} else {return 10}})
.attr("stroke-width","25")
.attr("stroke",function(d) {if (d.strand == "+1") {return 'green'} else {return 'red'}})
.on('mouseover', function(d) {console.log(d.start)})
}

You're creating a bunch of closures in a loop. Each of the functions you create have the variable orf in their closure scope but your loop is changing the value of orf. By the time the function runs when the mouse over event fires, orf has its final value so therefore your #mygroup + orf selection will always pick up the last element.
Here's a good page on closures that has a section detailing the pitfalls of closures in a loop: http://conceptf1.blogspot.ca/2013/11/javascript-closures.html.
In D3 you can get around this problem by using data joins instead of an external loop. Here's a good tutorial that should help to understand how this works:
http://bost.ocks.org/mike/join/

You need to create different event handlers for each line object, what I mean is store those line ojects them in an associated array or something. This way you are probably overwriting each time.
If you could provide a jsfiddle or something I would be happy to test this theory out for you...

Related

Only one SVG component rendered when calling a method twice

I use a function to set up event handlers for a click and as it gets fired, the rendition goes as supposed to (fold in on the outer control and fold out on the inner one). Then, clicking it again, the process is retracted. However, the next time I perform the operation, only the outer component changes its size while the inner one does not get affected.
function pieClickOuter(target) {
var pie = d3.layout.pie()
.startAngle(0).endAngle(2 * Math.PI)
.value(function (d) { return d.val; });
var out = d3.svg.arc().innerRadius(90).outerRadius(99);
var org = d3.svg.arc().innerRadius(1).outerRadius(1);
var sub = d3.svg.arc().innerRadius(10).outerRadius(80));
d3.selectAll("#chart .sector path")
.transition().duration(1500).attr("d", out);
var grx = _.chart.selectAll(".subSector")
.data(pie(getData())).enter().append("g")
.attr("class", "subSector")
.on("click", pieClickInner);
grx.append("path")
.attr("d", org).style("fill", function (d) { return colors(d.value); });
grx.selectAll("#chart .subSector path")
.transition().duration(1000).attr("d", sub);
}
function pieClickInner() {
d3.selectAll("#chart .sector path")
.transition().duration(1500)
.attr("d", d3.svg.arc().innerRadius(80).outerRadius(99));
outerPieEvents(d3.selectAll("#chart .sector"));
d3.selectAll("#chart .subSector path")
.transition().duration(1000)
.attr("d", d3.svg.arc().innerRadius(1).outerRadius(1));
}
I cannot for my life see why. According to the console output, all the steps are executed, so it seems that the events are re-set up correctly. Still, the inner component seems to disobey.
See this fiddle
I believe that you want something like this.
I changed only 1 name:
var grx = _.chart.selectAll(".subSector")
to
var grx = _.chart.selectAll(".foo")//or any other name
so, we don't select what already exists.
The problem with this approach is that your SVG will have more and more groups each click. But you can avoid this removing them in your pieClickInner():
d3.selectAll("#chart .subSector path")
.transition().duration(1000)
.attr("d", d3.svg.arc().innerRadius(1).outerRadius(1)).remove();
d3.selectAll("#chart .subSector text").attr("opacity", 0).remove();
d3.selectAll("g.subSector").transition().duration(1000).remove();
I, personally, don't like remove(), i'd simply rebind the data.

Show content from data in tooltip d3

I am following this example to add a tooltip to my circles, displayed on a map.
var tooltip = d3.select("body")
.append("div")
.attr("id", "mytooltip")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.text("a simple tooltip");
Then Ive got this mouseover
// callbackfunction preparing the data
// then
var feature = g.selectAll("circle")
.data(data.features)
.enter()
.append("circle")
//...
feature.on("mouseover",function(d) {
d3.select(this)
.transition()
.ease("elastic")
.duration(500)
.attr('r', function (d){
return (d.features.xy);
})
d3.select("#mytooltip")
.style("visibility", "visible")
.text(function(d) {
console.log(d.features.xy)
return (d.features.xy)
})
That does not display the value of xy.
Output of console.log is:
TypeError: undefined is not an object (evaluating 'd.xy')
The Problem is obviously that with the d3.select("#mytooltip") statement I enter the var tooltip to which my data.features... is not bound to. How do I bind the circles to the mouseover (which are created in var feature = g.selectAll("circle"), after calling d3.select?
The .data function is expecting an array, to be distributed among several elements ("data" is plural). If you want to give a single "piece of data" to a single element (namely, your tooltip), you need the .datum function:
tooltip.datum(myData)
Alternatively, you can do:
tooltip.data([myData])
In your original code, since you don't have the tooltip variable (nor, for that matter, myData), you can insert it in the mouseover event:
(...)
d3.select("#mytooltip")
.datum(d)
.style("visibility", "visible")
(...)
Another option: you can draw the tooltip directly, without binding any data to it:
d3.select("#mytooltip")
.style("visibility", "visible")
.text(d.features.xy);
Here d still refers to the data of the object you are mouseover-ing, so this should work just as well.

D3js - Force Layout. Selecting Specific Node

I am trying to select a specific node and do some animation on it.
This is how I try to select it:
var selectedToEnlarge = node.filter(function (d, i) {
return d.name == selectedVal;
});
This variable actually return an array containing my node. Please see attached image:
The node returned is the one I need.
Now I try to animate it a bit, like this:
selectedToEnlarge.transition().duration(2000)
.attr("stroke-width", 20)
.attr("r", 10)
.transition()
.duration(2000)
.attr('stroke-width', 0.5)
.attr("r", 200)
.ease('sine')
But nothing is happening. I am assuming it is something with selectors.
Any insight is appreaciated :)

D3: Surround mouse point with "Crosshair"

I'm trying to surround the mouse point with a circle, much like a crosshair, and have this circle track the mouses movement. So far the best strategy I have is using D3 enter-update-exit:
Append circle on mouse point underpinned by data.
on mouse move add another circle to data array with data = new mouse point.
if data array exceeds 1, shift() the first value out.
update visualisation.
jsfiddle here - http://jsfiddle.net/hiwilson1/kur2bbv9/1/ - though I think it's largely irrelevent as this strategy is fundamentally flawed. The circle appears as though it's lagging behind the cursor and flickers. A lot. Which I don't want.
Key part of code here:
function onMove() {
var m = d3.mouse(this);
var point = {x: m[0], y: m[1]};
area.push(point)
document.getElementById("svg").onmousedown = function() {
mouseDown++;
addNode(m);
};
document.getElementById("svg").onmouseup = function() {
mouseDown--;
};
if (mouseDown > 0) {
addNode(m);
}
//if theres two circles, remove the first leaving just the second.
if (area.length > 1) {
area.shift();
}
var crosshair = svg.selectAll(".area")
.data([area])
crosshair
.attr("class", "area")
.attr("cx", m[0])
.attr("cy", m[1])
.attr("fill", "none")
.attr("stroke", "grey")
.attr("stroke-width", "3px")
.attr("r", 30)
crosshair.enter()
.append("circle")
.attr("class", "area")
.attr("cx", m[0])
.attr("cy", m[1])
.attr("fill", "none")
.attr("stroke", "grey")
.attr("stroke-width", "3px")
.attr("r", 30)
crosshair.exit().remove()
};
Is there another way of accomplishing this? Happy to accept non D3 strategies.
I couldn't get your JSfiddle to display anything, so I'm not sure if I'm totally missing the point, but could you just use a custom CSS cursor on top of your SVG element? It seems that .cur cursor files have the most wide-spread support. That would be a native alternative for custom hacks (thus giving better performance), and it would also degradate gracefully on un-supported browsers.

How to display names next to bubbles in d3js motion chart

In Mike Bostocks example http://bost.ocks.org/mike/nations/ there is so much data that putting the names of the countries there would make it chaotic, but for a smaller project I would like to display it.
I found this in the source:
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(interpolateData(2004))
.enter().append("circle")
.attr("class", "dot")
.style("fill", function(d) { return color(d); })
.text(function(d) { return d.name; })
.call(position)
.sort(order);
dot.append("title")
.text(function(d) { return d.name; });
But somehow a title never shows up. Does anybody have an idea, how to display the name, next to the bubble?
As the other answer suggests, you need to group your elements together. In addition, you need to append a text element -- the title element only displays as a tooltip in SVG. The code you're looking for would look something like this.
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(interpolateData(2004))
.enter()
.append("g")
.attr("class", "dot")
.call(position)
.sort(order);
dot.append("circle")
.style("fill", function(d) { return color(d); });
dot.append("text")
.attr("y", 10)
.text(function(d) { return d.name; });
In the call to position, you would need to set the transform attribute. You may have to adjust the coordinates of the text element.
Unfortunately grouping the text and circles together will not help in this case. The bubbles are moved by changing their position attributes (cx and cy), but elements do not have x and y positions to move. They can only be moved with a transform-translate. See: https://www.dashingd3js.com/svg-group-element-and-d3js
Your options here are:
1) rewrite the position function to calculate the position difference (change in x and change in y) between the elements current position and its new position and apply that to the . THIS WOULD BE VERY DIFFICULT.
or 2) Write a parallel set of instructions to setup and move the tags. Something like:
var tag = svg.append("g")
.attr("class", "tag")
.selectAll(".tag")
.data(interpolateData(2004))
.enter().append("text")
.attr("class", "tag")
.attr("text-anchor", "left")
.style("fill", function(d) { return color(d); })
.text(function(d) { return d.name; })
.call(tagposition)
.sort(order);
You will need a separate tagposition function since text needs 'x' and 'y' instead of 'cx', 'cy', and 'r' attributes. Don't forget to update the "displayYear" function to change the tag positions as well. You will probably want to offset the text from the bubbles, but making sure the text does not overlap is a much more complicated problem: http://bl.ocks.org/thudfactor/6688739
PS- I called them tags since 'label' already means something in that example.
you have to wrap the circle element and text together , it should look like
<country>
<circle ></circle>
<text></text>
</country>

Categories