append a circle in middle of all my path (sunburst) - javascript

Hello I do an sunburst or bilevel chart it's middle of a pie & donut chart ^^ When I append all path it works fine:
this.path = this.svg.selectAll("path")
.data(this.partition.nodes(rootData).slice(1))
.enter().append("path")
.attr("d", this.arc)
.style("fill", function(d) { return d.fill; })
.each(function(d){ this._current = thiss.updateArc(d);});
But the probleme is when I'm trying to add a circle in middle-extern of all my path so it didn't work, this code add circle in the middle-middle of all my path fine
var indicator = this.svg.selectAll('circle')
.data(this.partition.nodes(rootData))
.enter().append("circle")
.attr("cx", function(d){return thiss.arc.centroid(d)[0]})
.attr("cx", function(d){return thiss.arc.centroid(d)[1]})
.attr("r", 5).style('fill','#ff0000');
But I need to add this little circle in the midle but on extern border of the path.
I don't know how I can get the right cx and cy attributs, help please ?
This is screenshot of my goal (black points are what I had) and (red points are what I want to do)
http://i.stack.imgur.com/GXPYM.jpg

This is in part a repeat of Lars' equations from the comments, but I thought it was worth recapping all at once because the trig identities for converting from angles to x/y coordinates won't match your trig text book.
Most text books assume that angles start at the right horizontal axis and increase counter-clockwise, and that the vertical axis has larger values higher up on the page.
In SVG, larger y values are lower on the page, and the angles created by the pie chart layout (and the example code the OP is using for the sunburst layout) draw an angle of zero as vertical line to the top of the circle, with angles increasing clockwise.
With that information, you can convert to x and y values with the following trig equations:
g.append("circle") //circles inherit pie chart data from the <g>
.attr("r", 5)
.attr("cx", function(d) {
return Math.sin((d.startAngle + d.endAngle)/2) *radius;
})
.attr("cy", function(d) {
return -Math.cos((d.startAngle + d.endAngle)/2) *radius;
});
Live example: http://fiddle.jshell.net/4x9ap/1/
Again, this simple example uses a pie chart layout, so the data has startAngle and endAngle values, and the radius is constant. For a sunburst diagram made with the partition layout, you would replace (d.startAngle + d.endAngle)/2 with d.x + d.dx/2, and you would replace radius with a function based on d.depth.

As an alternative to trigonometry, you could use transformations to position the circles. If the first step in a transformation is a rotation, and then you apply a translation afterwards, the translation will be applied in the rotated coordinate system.
A little extra complication, though, is that the d3 pie chart gives angles in radians (since that's what the trigonometry functions use), but the rotation needs angles in degrees.
var degreesPerRadian = 180/Math.PI;
g.append("circle") //circles inherit pie chart data from the <g>
.attr("r", 5)
.attr("transform", function(d) {
return "rotate(" + degreesPerRadian*((d.startAngle + d.endAngle)/2)
+ ")" +
//rotate by the average of the start and end angle
//Note that d3 specifies angles in radians, but the rotate
//function needs them in degrees
"translate(0," + -radius + ")";
//then translate "up" the distance of the radius;
//"up" is interpretted according to the rotated coordinates,
//but for zero rotation it will position the dot at the top
//of the circle, which is the zero angle for d3
});
Live example: http://fiddle.jshell.net/4x9ap/
(based on this simple pie chart code )

Related

d3 US Map zoom on load to marked state

US map with d3.v3 using Mike Bostock's example:
I want the map to zoom into the marked locations initially when the page loads but the entire map should be rendered so that a user can zoom out if he wants to.
var w = 300;
var h = 280;
//Define map projection
var projection = d3.geo.albersUsa()
.translate([w/2, h/2])
.scale([300]);
//Define path generator
var path = d3.geo.path()
.projection(projection);
//Create SVG element
var svg = d3.select("#map1").append("svg")
.attr("width", w)
.attr("height", h)
var g = svg.append("g");
var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "1000")
.style('opacity', 0)
.style("font-family", "sans-serif")
.style("background-color", "white")
.style("border-radius", "5px")
.style("padding", "10px")
.style('color', '#000')
.style("font-size", "12px");
//Load in GeoJSON data
d3.json("us-states.json", function(json) {
d3.csv("cities.csv", function(error, data) {
g.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([d.longi, d.lati])[0];
})
.attr("cy", function(d) {
return projection([d.longi, d.lati])[1];
})
.attr("r", 4)
.style("fill", "#4F6D88")
.on("mouseover", function(d){
tooltip.transition().style("opacity", 0.9)
.style('left', (d3.event.pageX) + 'px')
.style('top', (d3.event.pageY) + 'px')
.text(d.city)
})
.on("mousemove", function(event){
tooltip.style("top", (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");
})
.on("mouseout", function(){
tooltip.transition().delay(500).style("opacity", 0);
});
});
//Bind data and create one path per GeoJSON feature
g.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path);
});
var zoom = d3.behavior.zoom()
.scaleExtent([1, 50])
.on("zoom", function() {
var e = d3.event,
tx = Math.min(0, Math.max(e.translate[0], w - w * e.scale)),
ty = Math.min(0, Math.max(e.translate[1], h - h * e.scale));
zoom.translate([tx, ty]);
g.attr("transform", [
"translate(" + [tx, ty] + ")",
"scale(" + e.scale + ")"
].join(" "));
});
svg.call(zoom)
I have the code to zoom in with scroll which i have pasted above but i want it to zoom on load to those specific locations. How i want it to be:
There are two primary ways to zoom a map in d3:
modify the projection which will re-draw the paths, or
modify the drawn paths with scale and transform.
Modifying the projection is easiest in d3v4 with fitSize or fitExtent - though you would need to turn your points into geojson. You can also manually calculate the translate and scale values to update a projection (see this answer by Mike Bostock which explains this common d3v3 approach).
Alternatively, you can modify the drawn paths by calling the zoom function - this question asked yesterday has an excellent example of doing so (in d3v4). Or you can calculate and apply the zoom manually and then update the zoom to indicate the current scale and translate. I'll use the common method of modifying a d3v3 projection mentioned above (with Mike's answer) and apply it to the transform on the paths - rather than modifying the projection. Though it should not be difficult to see how my answer could be changed to modify the projection instead.
First you need to determine the maximum difference between the x and y coordinates of your points. If dealing with two points, this will be fairly easy:
var data = [[-100,45],[-110,45]];
var p1 = projection(data[0]);
var p2 = projection(data[1]);
var dx = Math.abs(p1[0] - p2[0]);
var dy = Math.abs(p1[1] - p2[1]);
I'm assuming a simple data format for the sake of a shorter answer. Also, if dealing with many points, this would be a bit more complex. One potential option would be to place your points in geojson and get the bounding box of the points.
Now we need to find out the centroid of the points - in the case of two points this is just the average of the x and y values:
var x = (p1[0] + p2[0])/2;
var y = (p1[1] + p2[1])/2;
Next we need to calculate a new scale, while also determining if the scale is restricted by the difference in x values of the coordinates or the difference in y values of the coordinates:
var scale = 0.9 / Math.max( dx/w , dy/h );
The 0.9 reduces the scale slightly, it is the same as 0.9 * scale and allows a variable amount of margin. The value returned by dx/w is one over the scale value we need to stretch the difference across the width of the svg container.
(it would probably make more sense written like: var scale = 0.9 * Math.min(w/dx,h/dy); - we want to limit the zoom by the lowest scale value and multiply it by some percentage to give margins. But the other representation is ubiquitous in online examples)
Now we have a scale, we only need to determine a translate. To do so we find out how far we need to re-position the values held in the x and y variables so that those values would be centered:
var translate = [w/2 - scale * x, h/2-scale*y];
Now you can set the initial scale and translate of the map:
g.attr("transform", "translate("+translate+")scale("+scale+")");
But, you probably want to update the zoom parameters on page load to reflect the initial zoom and translate:
zoom.translate(translate);
zoom.scale(scale);
This way when you zoom in or out from the initial view, the change is relative to your initial zoom.
Now all you have to do is include the above code when you add the points. Note that this technique might require some modification if you want to return to the initial position.

Painting different vertical segments under curve using d3

I am trying to create a simple line graph using d3 which segments the curve and paint each segment with a different colour. Currently I am only able to colour the whole area under the curve.
Current:
Attempting to achieve (Pardon me for the terrible colouring. In a rush for time):
This is bits of relevant code. Please help!
var x = d3.scaleLinear();
var y = d3.scaleLinear();
//Set the range of the data
x.domain([0, Math.max(endGrowth, endPlateau) ,maxX]).range([0, width*0.8, width]);
y.domain([0, maxY]).range([height, 0]);
//Define the area under the Graph
var area1 = d3.area()
.x0(function(d){
return x(d.Rank);
})
.y1(function(d){ return y(d.Elements);})
.y0(height);
//Add the colored regions
svg.append("path")
.data([data])
.attr("class", "areaUnderGraph")
.attr("fill", "blue")
.attr("transform", "translate(" + leftMarginLabel + ",0)")
.attr("d", area1);
Right now, the area under the curve is one path, so it can only be colored one color. The simplest way to color different portions under the curve different colors is to split them up in data. It's not clear where data is coming from, but you'd take sub-sections of the array, like
var segments = [data.slice(0, 2), data.slice(2)];
svg.selectAll("path")
.data(segments)
.enter()
.append("path")
.attr("fill", function(d) { /* use d to choose a color */ })
That's the gist: you'd have multiple slices of the data, and instead of one path, you'd create multiple paths that you can color as you wish.

Axis Zooming - translation issue - d3.js version 4 -

short version:
I am using Axis Zooming and normal Zooming. I combine both together and the Zooming works fine. Only problem is, that the translation is not working as I want it. The translation one the axes is not 1 to 1. It depends on the scale factor of the normal zoom, how the translation behaves.
my status:
I have a line graph, which has normal zooming. Additional to that I have Axis-Zooming. So if I am in the Y-axis area, I only want to zoom the Y-axis and only move the Y-axis around. The same for the X-Axis.
For that I used d3.zoom instance and called(zoom) on 3 different rectangles.
is covering the whole graph area
is covering only x-axis
is only covering y-axis
The transform is saved on the elements.
The zoom function applies all 3 different zoom transforms to the scale, when triggered.
Setting everything up:
graph.zoomXY = d3.zoom()
.scaleExtent([-10, 1000]);
graph.overlayX = graph.g//.select(".axis.x")
.append("rect")
.attr("fill", "rgba(255,0,0,0.5)")
.attr("width", graph.rectWidth)
.attr("height", 15)
.attr("y", graph.rectHeight)
.call(graph.zoomXY);
graph.overlayY = graph.g//.select(".axis.y")
.append("rect")
.attr("fill", "rgba(255,0,0,0.5)")
.attr("width", 150)
.attr("height", graph.rectHeight)
.attr("x", -150)
.call(graph.zoomXY);
//append the rectangle to capture zoom
graph.overlayRect = graph.g.append("rect")
.attr("class", "overlay-rect")
.attr("width", graph.rectWidth)
.attr("height", graph.rectHeight)
.style("fill", "none")
.call(graph.zoomXY)
.on("dblclick.zoom", function() {
resetZoom();
} );
Calculating Scale:
function zoomed() {
getZoomedScales();
updateGraph();
}
function getZoomedScales() {
var transformX = d3.zoomTransform(graph.overlayX.node());
var transformY = d3.zoomTransform(graph.overlayY.node());
var transformXY = d3.zoomTransform(graph.overlayRect.node());
graph.yScaleTemp = transformXY.rescaleY(transformY.rescaleY(graph.yScale));
graph.xScaleTemp = transformXY.rescaleX(transformX.rescaleX(graph.xScale));
}
The Zooming is working fine. But the translation on the axes Zoom (graph.overlayY and graph.overlayX) is influenced by the Scaling factor of the zoom applied to graph.overlayRect. If I change the order, the issue will be just flipped. The axes Zoom's scale factor (graph.overlayY and graph.overlayX), messes up the translation of the Zoom on graph.overlayRect.
Open the fiddle and change the Zooming, while over the graph area. Then mousedown and mousemove on one of the axes. Repeat and see how it changes the translation.
Here is a fiddle:
https://jsfiddle.net/9j4kqq1v/

D3js - Add transparent gap between path in pie chart

I have a simple pie chart made with d3js and I would like to add transparent gap between each path.
paths = pieWrap.selectAll("path")
.data(pie(data)).enter()
.append("path")
.style("fill", "rgba(90, 168, 217, 1)")
.style("stroke", "#FFF")
.style("stroke-width", "1")
.style("stroke-opacity", "0")
.attr("d", arc);
Example here : http://jsfiddle.net/x4p0eLmL/2/
Just to know, in my case the background is an image so I can't use its color.
I tried stroke-opacity but it doesn't seem to work.
Is there a proper way to do that with d3js?
Thanks
I have had the same idea as #redress suggested: http://jsfiddle.net/x4p0eLmL/9/. The added part is as follows:
.attr("transform", function(path) {
middleAngle = -Math.PI/2 + (path.startAngle+path.endAngle)/2;
dx = 3 * Math.cos(middleAngle);
dy = 3 * Math.sin(middleAngle);
return "translate("+dx+", "+dy+")";
})
path has the attributes startAngle and endAngle. There is computed middle angle and translated. It is suitable for smaller gaps between the paths. Each path is translated to outer circle with radius+3 in the current example. There is the problem with "wider" gaps where the circle may be "distorted"

What's the simplest way to project a straight line from lat/lon on a D3.js map?

I've added several circles on a D3.js map by plotting them at 0,0 and then using transform to translate the circles to the correct latitude and longitude (contained in the array data):
var projection = d3.geo.mercator();
g.selectAll(".pin")
.data(geo)
.enter().append("circle", ".pin")
.attr("r", 3)
.attr("transform", function(d) {
return "translate(" + projection([d.lng, d.lat]) + ")"
});
The circles work fine, but I also want to add short lines (all between points within the UK) between some of these points, drawing on the same set of coordinates.
I've tried adding lines like below, but don't understand how to add the coordinates:
var line = d3.svg.line();
g.selectAll(".lines")
.data(geo)
.enter().append("line", ".line")
.attr("d", line)
.attr("x1", ?) //Line to go from 51.50, -0.12 to 55.95, -3.18
.attr("y1", ?)
.attr("x2", ?)
.attr("y2", ?);
Is there a way of setting both x1 and y1, and x2 and y2 at the same time so that I could use the projection() function (like I do with the circles)?
Or is there a better way to do this altogether?
Thanks!

Categories

Resources