I'm very much new to d3 and was wondering how to achieve this effect, where multiple lines remain tangent to the perimeter of a circle at all times.
This is what I have thus far: http://jsfiddle.net/tz5KT/181/
function transition() {
svg.selectAll(".lines")
.attr("x2", function (d) {
var tangent = findTangent(d.x, d.y);
return tangent.x;
})
.attr("y2", function (d) {
var tangent = findTangent(d.x, d.y);
return tangent.y;
});
circleX = getRandom(0, width),
circleY = getRandom(0, height);
svg.select(".circle").transition()
.duration(1500)
.attr("cx", circleX)
.attr("cy", circleY)
.each("end", transition);
}
I'm just not sure how to transition the lines from one position to the next, all while keeping them tangent to the circle. Any advice on how to do this? Much appreciated.
I think I got it
http://jsfiddle.net/tz5KT/219/
Check out my use of attrTween, it might be helpful
you need to add the transition to the line points in the transition function, like so: http://jsfiddle.net/tz5KT/177/
here it's a bit delayed tho
This is what you'll want in the transition function:
function transition() {
circleX = getRandom(0, width),
circleY = getRandom(0, height);
svg.select(".circle").transition()
.duration(1500)
.attr("cx", circleX)
.attr("cy", circleY)
.each("end", transition);
svg.selectAll(".lines")
.transition()
.duration(1500)
.attr("x2", function (d) {
var tangent = findTangent(d.x, d.y);
return tangent.x;
})
.attr("y2", function (d) {
var tangent = findTangent(d.x, d.y);
return tangent.y;
});
}
Related
I have modified this sunburst diagram in D3 and would like to add text labels and some other effects. I have tried to adopt every example I could find but without luck. Looks like I'm not quite there yet with D3 :(
For labels, I would like to only use names of top/parent nodes and that they appear outside of the diagram (as per the image below). This doesn't quite work:
var label = svg.datum(root)
.selectAll("text")
.data(partition.nodes(root).slice(0,3)) // just top/parent nodes?
.enter().append("text")
.attr("class", "label")
.attr("x", 0) // middle of arc
.attr("dy", -10) // outside last children arcs
/*
.attr("transform", function(d) {
var angle = (d.x + d.dx / 2) * 180 / Math.PI - 90;
console.log(d, angle);
if (Math.floor(angle) == 119) {
console.log("Flip", d)
return ""
} else {
//return "scale(-1 -1)"
}
})
*/
.append("textPath")
.attr("xlink:href", function(d, i) { return "#path_" + i; })
.text(function(d) { return d.name + " X%"; });
I would also like to modify a whole tree branch on hover so that it 'shifts' outwards. How would I accomplish that?
function mouseover(d) {
d3.select(this) // current element and all its children
.transition()
.duration(250)
.style("fill", function(d) { return color((d.children ? d : d.parent).name); });
// shift arcs outwards
}
function mouseout(d) {
d3.selectAll("path")
.transition()
.duration(250)
.style("fill", "#fff");
// bring arcs back
}
Next, I'd like to add extra lines/ticks on the outside of the diagram that correspond to boundaries of top/parent nodes, highlighting them. Something along these lines:
var ticks = svg.datum(root).selectAll("line")
.data(partition.nodes) // just top/parent nodes?
.enter().append("svg:line")
.style("fill", "none")
.style("stroke", "#f00");
ticks
.transition()
.ease("elastic")
.duration(750)
.attr("x1", function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.attr("y1", function(d) { return Math.max(0, y(d.y + d.dy)); })
.attr("x2", function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.attr("y2", function(d) { return Math.max(0, y(d.y + d.dy) + radius/10); });
Finally, I would like to limit zoom level so the last nodes in the tree do not fire zoom but instead launch a URL (which will be added in JSON file). How would I modify the below?
function click(d) {
node = d;
path.transition()
.duration(750)
.attrTween("d", arcTweenZoom(d));
}
My full pen here.
Any help with this would be much appreciated.
I have this visualization and I'm trying to add fisheye view to the chart. I have tried adding it with the following lines in the plotData function but it doesn't happen:
var fisheye = d3.fisheye.circular()
.radius(120);
svg.on("mousemove", function () {
fisheye.focus(d3.mouse(this));
circle.each(function (d) {
d.fisheye = fisheye(d);
});
});
Any ideas on how to solve this?
Thanks!
First things first, your d3.timer never stops running. This is driving my machine crazy (cpu 100%) and killing the performance of the fishey. I'm really not sure what you are doing there, so ignoring that for a moment.
Your fisheye needs a little massaging. First, it expects your data pixel's positions to be stored in d.x and d.y attributes. You can fudge this in with when drawing your circles:
circle
.attr("cx", function(d, i) { d.x = X(d[0]); return d.x; })
.attr("cy", function(d, i){ d.y = Y(d[1]); return d.y; });
Second, you are plotting your data in multiple steps, so you need to select all the circles for the fisheye. And third, you forgot the code that actually makes the points grow and shrink:
svg.on("mousemove", function () {
fisheye.focus(d3.mouse(this));
// select all the circles
d3.selectAll("circle.data").each(function(d) { d.fisheye = fisheye(d); })
// make them grow and shrink and dance
.attr("cx", function(d) { return d.fisheye.x; })
.attr("cy", function(d) { return d.fisheye.y; })
.attr("r", function(d) { return d.fisheye.z * 4.5; });
});
Updated example.
I am new to programming so apologies if the answer to this is obvious but after hours of searching I can't find out what's wrong.
I simply want to tween an arc in D3.js (in this case change the endAngle to 0). I've been through lots of examples but I must be missing something. I have built a function to change arc colour on clicking which works but it is the second function 'arcTween' to change the arc endAngle of the outermost arcs that doesn't work. Can you help?
Many thanks
Full JS fiddle here: http://jsfiddle.net/vaaa052h/
Extracts below
var chartArea = d3.select("#chart")
.append("svg") // d3 SVG function
.attr("width", 210)
.attr("height", 210);
var arcGroup = chartArea.append("g") // d3 g grouping function
.attr("transform", "translate(" + transX + "," + transY + ")")
.attr("class", "arc");
var arc = d3.svg.arc()
.innerRadius(function (d) {
return radius[level];
})
.outerRadius(function (d) {
return radius[level + 1];
})
.startAngle(function (d) {
return minAngArc;
})
.endAngle(function (d) {
return maxAngArc;
});
//////// chart building ///////////////
arcGroup.append("path")
.attr("d", arc)
.attr("fill", color(0, random, 0, i, j, k))
.attr("opacity", opacity(rating))
.on("click", arcTween());
////// click functions //////////
function arcTween(d) {
d3.select(this).transition().duration(1000)
.attrTween("d", function (d) {
var interpolate = d3.interpolate(d.endAngle, 0);
return function (t) {
d.endAngle = interpolate(t);
return arc(d);
};
});
};
I made a couple of changes in this updated fiddle: http://jsfiddle.net/henbox/a8r326m5/1/
First, when you set up the click handler, avoid calling it on page load by using:
.on("click", arcTween);
instead of
.on("click", arcTween());
as per Lars' explanation here. This will stop you getting "Object [object global] has no method 'getAttribute'" errors in the console
Second, bind some data to the path elements so we can manipulate it later:
arcGroup.append("path")
.datum({endAngle:maxAngArc, startAngle:minAngArc})
....
And thirdly, use this data in the arcTween function. By setting maxAngArc and minAngArc, and then tweening the value of maxAngArc to minAngArc (I've asumed you mean to do this rather than tweening to 0), you should get the behaviour you want. The tween function:
function arcTween(d) {
maxAngArc = d.endAngle;
minAngArc = d.startAngle;
d3.select(this).transition().duration(1000)
.attrTween("d", function (d) {
var interpolate = d3.interpolate(d.endAngle, d.startAngle);
return function (t) {
maxAngArc = interpolate(t);
return arc(d);
};
});
};
I'm trying to implement drill-down capability in zoom function, i.e., I want that my initial plot shows, for example, 50 points, and when the user makes zoom the number of points increases to 500.
My attempt consists in redraw inside the zoom function all the points and remove part of them when the zoom scale is under a threshold. As you can see in this JSFIDDLE, the implementation reproduces the drill-down capability.
However, I suspect that there is a more efficient way to implement the drill-down. Therefore, the question is if I'm in the correct way or there is a standard (more efficient and elegant) way for doing this effect.
My example code:
var width = 300,
height = 300;
var randomX = d3.random.normal(width / 2, 40),
randomY = d3.random.normal(height / 2, 40);
var data = d3.range(500).map(function() {
return [randomX(), randomY()];
});
var svg = d3.select("body").append("svg");
var zoomBehav = d3.behavior.zoom();
svg.attr("height", height)
.attr("width", width)
.call(zoomBehav
.scaleExtent([1, 10])
.on("zoom", zoom));
// Initial plot
d3.select("svg").selectAll("circle")
.data(data, function(d,i) {return i;})
.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d) {return d[0]; })
.attr("cy", function(d) {return d[1]; })
.style("fill", "red");
d3.selectAll("circle")
.filter(function(d, i) {
if (zoomBehav.scale() < 2) { return i > 50; }
})
.remove();
function zoom(){
var selection = d3.select("svg")
.selectAll("circle")
.data(data, function(d,i) { return i; });
selection
.attr("cx", function(d) { return d3.event.translate[0] + d3.event.scale * d[0]; })
.attr("cy", function(d) { return d3.event.translate[1] + d3.event.scale * d[1]; });
selection.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d) { return d3.event.translate[0] + d3.event.scale * d[0]; })
.attr("cy", function(d) { return d3.event.translate[1] + d3.event.scale * d[1]; })
.style("fill", "red");
d3.selectAll("circle")
.filter(function(d, i) {
if (zoomBehav.scale() < 2) { return i > 50; }
})
.remove();
}
If you're interested in dealing with semantic zoom of elements on an XY canvas, then you'll want to look into d3.geom.quadtree:
https://github.com/mbostock/d3/wiki/Quadtree-Geom
You can pass your points to a quadtree and they'll be spatially nested. Then, you can tie the nesting level to the zoom level and have automatic grid clustering. It's rather more involved than would fit into this answer, since you have to come up with mechanisms for representing the clustered points, and you'll also need to get into recursive functions to deal with the hierarchical level of points.
Here's an example using quadtrees for semantic zoom and clustering for mapping:
http://bl.ocks.org/emeeks/066e20c1ce5008f884eb
I am trying to make tooltip like: http://jsfiddle.net/6cJ5c/10/ for my graph and that is the result on my realtime graph: http://jsfiddle.net/QBDGB/52/ I am wondering why there is a gap between the circles and the graph and why at the beginning there is a vertical line of circles? When it starts the circles are close to the curve but suddendly they start to jump up and down !! I want the circles to move smooothly and stick on the surface of the curve. I think the problem is that they are not moving with the "path1" and so it does not recognize the circles and thats why they are moving separetly or maybe the value of tooltipis are different of the value of the curve so they do not overlap!. That is how the data is generated ( value and time) and the tooltip:
var data1 = initialise();
var data1s = data1;
function initialise() {
var arr = [];
for (var i = 0; i < n; i++) {
var obj = {
time: Date.now(),
value: Math.floor(Math.random() * 90)
};
arr.push(obj);
}
return arr;
}
// push a new element on to the given array
function updateData(a) {
var obj = {
time: Date.now(),
value: Math.floor(Math.random() * 90)
};
a.push(obj);
}
var formatTime = d3.time.format("%H:%M:%S");
//tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var blueCircles = svg.selectAll("dot")
.data(data1s)
.enter().append("circle")
.attr("r", 3)
.attr("cx", function(d) { return x(d.time); })
.attr("cy", function(d) { return y(d.value); })
.style("fill", "white")
.style("stroke", "red")
.style("stroke-width", "2px")
.on("mousemove", function(d ,i) {
div.transition()
.duration(650)
.style("opacity", .9);
div.html(formatTime(new Date(d.time)) + "<br/>" + d.value)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d ,i ) {
div.transition()
.duration(650)
.style("opacity", 0);
});
blueCircles.data(data1s)
.transition()
.duration(650)
.attr("cx", function(d) { return x(d.time); })
.attr("cy", function(d) { return y(d.value); });
Please kindly tell me your opinions since I really need it :(
As I said maybe I should add "mouseover and mouse move functions" to the "path" to make it recognize the tooltip. something like following. but I am nor really sure :(
var path1 = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.data([data1])
.attr("class", "line1")
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseout", mouseout);
I think your problem lies in the interpolation of your paths. You set the interpolation between points on your var area to "basis", which I found is a B-spline interpolation. This means the area drawn does not go through the points in your dataset, as shown in this example:
The path your points move over, though, are just straight lines between the points in your dataset. I updated and changed the interpolation from basic to linear, to demonstrate that it will work that way. I also set the ease() for the movement to linear, which makes it less 'jumpy'. http://jsfiddle.net/QBDGB/53/