Trying to append an ellipse to svg to cover points - d3.js

I have produced a scatter chart using D3. It works okay. Now I want to cover the points plotted with an ellipse.
I've tried to get the xOval value by looking for the max value in the dataset, same for yOval.
var xOval = d3.scale.linear()
.domain([0,d3.max(dataset, function(d) { return d[0]; })])
var yOval = d3.scale.linear()
.domain([0,d3.max(dataset, function(d) { return d[1]; })])
Further down in the body after the scatter is plotted I have :
svg.append("ellipse")
.attr("cx", function(d) {
return xOval(d[0]);
})
.attr("cy", function(d) {
return yOval(d[1]);
})
.attr("rx", 10)
.attr("ry", 10);
This fails to work.
Any ideas where I'm going wrong?

This is quite difficult to do with an ellipse, as you have to make sure that everything is within the path defined by it. Here's how to do it with a circle, which should get you started.
The easiest way to do this is to use D3's polygon methods for this. In particular, you can get the centroid of the polygon defined by all points as the center of the circle. Then all you have to do is find the distance to the point that is farthest away from that and you have your radius.
The code looks something like this, assuming suitable data structures.
var center = d3.geom.polygon(dataset).centroid(),
r = d3.max(dataset, function(d) {
return Math.sqrt(Math.pow(xScale(d[0]) - xScale(center[0]), 2) + Math.pow(yScale(d[1]) - yScale(center[1]), 2));
}) + rScale;
rScale here is the radius of the circles that represent a point. The radius of the covering circle needs to be extended by that to make sure that not only the center, but the entire data point circle is covered.
Complete example here. Note that this is not optimal in the sense that there would be a smaller circle that covers all data points.

Related

D3js Zoom With Manually Drawn Circle

I am working on a d3 scatter plot where an area of the chart will be circled (a Youden Plot). Based on available samples, I have been able to add zoom to both my data points and my axis. However, I am unable to get the circle to zoom correctly.
I suspect that I need to set up some kind of scale (scaleSqrt, possibly), but I am struggling to find documentation on this that is written at a beginner level.
My current circle code is very straightforward
var circle = drawCircle();
function drawCircle() {
return svg
.append('g')
.attr('class', 'scatter-group')
.append('circle')
.attr("r", 75 )
.attr('cx', 200 + margin.left) //suspect this needs to be related to a scale
.attr('cy', 200 + margin.top) //suspect this needs to be related to
.attr('r', 75)//suspect this needs to be related to a scale
.attr('stroke', 'red')
.attr('stroke-width', 3)
.style('fill', 'none')
}
As is the zoomed function
function zoomed() {
var new_xScale = d3.event.transform.rescaleX(xScale);
var new_yScale = d3.event.transform.rescaleY(yScale);
// update axes
gX.call(xAxis.scale(new_xScale));
gY.call(yAxis.scale(new_yScale));
//redraw data ppints
points.data(data)
.attr('cx', function(d) {return new_xScale(d.x)})
.attr('cy', function(d) {return new_yScale(d.y)});
//redraw circle
}
My work in progress is available in this fiddle . Can someone possible point me in the right direction?
I believe this will get you most of the way there. You need to update your circle attributes in the zoomed function along with the other elements:
function zoomed() {
var new_xScale = d3.event.transform.rescaleX(xScale);
var new_yScale = d3.event.transform.rescaleY(yScale);
// update axes
gX.call(xAxis.scale(new_xScale));
gY.call(yAxis.scale(new_yScale));
//redraw data ppints
points.data(data)
.attr('cx', function(d) {return new_xScale(d.x)})
.attr('cy', function(d) {return new_yScale(d.y)});
// The new part:
// the transform
let trans = d3.event.transform
// the approximate domain value of the circle 'cx' for converting later
let cx_domain = xScale.invert(200 + margin.left)
// the approximate domain value of the circle 'cy' for converting later
let cy_domain = yScale.invert(200 + margin.top)
// the circle
let circ = d3.select('.scatter-group circle')
// the radius
let rad = 75
// reset the circle 'cx' and 'cy' according to the transform
circ
.attr('cx',function(d) { return new_xScale(cx_domain)})
.attr('cy',function(d) { return new_yScale(cy_domain)})
// reset the radius by the scaling factor
.attr('r', function(d) { return rad*trans.k })
}
See this fiddle
You'll notice the circle does not scale or move at quite the same rate as the scatter dots. This is possibly because of the use of the invert function, because the conversion from range to domain and back to range is imperfect. This issue is documented
For a valid value y in the range, continuous(continuous.invert(y)) approximately equals y; similarly, for a valid value x in the domain, continuous.invert(continuous(x)) approximately equals x. The scale and its inverse may not be exact due to the limitations of floating point precision.
Your original idea to assign dynamic values to cx, cy and r will likely compensate for this, because you can then avoid the inversion.

How to limit the text of polygons in Voronoi diagram with D3.js?

I've see the Example of D3.js-Voronoi Tessellation.But I want to put some text in each of polygons instead of a circle,Here is my js code:
var width = 600, height = 400;
var vertices = d3.range(20).map(function(d){
return [Math.random() * width, Math.random() * height]
});
var voronoi = d3.geom.voronoi();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
path = svg.append("g").selectAll("path");
svg.selectAll("info")
.data(vertices.slice(1))
.enter().append("text")
.attr("transform", function(d) {
return "translate(" + d + ")";
})
.text("someText")
.attr("shape-rendering","crispEdges")
.style("text-anchor","middle");
redraw();
function redraw(){
path = path
.data(voronoi(vertices), polygon);
path.exit().remove();
path.enter().append("path")
.attr("class", function(d, i) {return "q" + (i % 9) + "-9";})
.attr("d", polygon);
path.order();
}
function polygon(d){
return "M" + d.join("L") + "Z";
}
I have a JSFiddle for that basic example here:
my voronoi code
now, I want each of the polygons' text in the center of the polygon, and don't cross with the polygon's border. If the polygon have not enough space to contain the all text, just contain the first part of it!
Let me know if there is anything I can do to solve this issue, thank you!
PS:I'm so sorry to my English, yes, it's so poor! :)
Have a look at this example http://bl.ocks.org/mbostock/6909318 , you probably want to place the text at the polygon centroid and not the seed (point) used to determine the voronoi tessellation.
That should fix the majority of your layout issues.
Automatically scaling the text to fit is a little bit harder, if you are willing to scale and rotate the text you can use a technique similar to the following to determine the length of the line at that point:
https://mathoverflow.net/questions/116418/find-longest-segment-through-centroid-of-2d-convex-polygon
Then you need to determine the angle of the line. I have a plugin that should help with that:
http://bl.ocks.org/stephen101/7640188/3ffe0c5dbb040f785b91687640a893bae07e36c3
Lastly you need to scale and rotate the text to fit. To determine the width of the text use getBBox() on the text element:
var text = svg.append("svg:text")
.attr("x", 480)
.attr("y", 250)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("font", "300 128px Helvetica Neue")
.text("Hello, getBBox!");
var bbox = text.node().getBBox();
Then you use the angle you calculated earlier to scale and rotate your text:
text.attr("transform", "rotate(40) scale(7)")
I would love to give a complete example but this is quite a bit of work to get it right.
There are other options to achieve the same effect but none of them are simple (ie you could anneal the layout similar to the way d3 does the Sankey layout)

Can d3 build shapes within a shape?

I have a newby question. Can D3 draw this:
http://www.nytimes.com/interactive/2008/05/03/business/20080403_SPENDING_GRAPHIC.html?_r=0
using the voronoi function within d3? What i am thinking is a svg that behaves like a and binds the voronoi found here http://bl.ocks.org/mbostock/4060366 to a circle. NY Times has accomplished the above visualization using flash.
Any ideas?
I have tried creating a large circle and embeding the smaller circles, but the voronoi does not show up and the points are not confined to the outer circle.
Code generated:
<svg class="PiYG" width="560" height="570">
<circle cx="270" cy="300" r="260" style="stroke: rgb(0, 0, 0);">
<g>
My js code looks something like this:
var width = 560, height = 570;
var svg = d3.select("#VD1").append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "PiYG");
var path = svg.append("circle")
.attr("cx", 270)
.attr("cy", 300)
.attr("r", 260)
.style("stroke", "#000")
.append("g")
.selectAll("path");
var vertices = d3.range(count).map(function(d) {
return [Math.random() * width, Math.random() * height];
});
var voronoi = d3.geom.voronoi()
.clipExtent([[0, 0], [width, height]]);
svg.selectAll("circle")
.data(vertices.slice(2))
.enter().append("circle")
.attr("transform", function(d) { return "translate(" + d + ")"; })
.attr("r", 2);
Thanks so much!
Not really, but not due to any shortcomings of d3, but rather because that's not what a Voronoi function does. A Voronoi function builds lines based on which regions of a graph are closest to a given point. It is not a way of proportionately dividing a circle into smaller segments, with size corresponding to data.
That being said, it is definitely possible to create a circular Voronoi diagram. To do so, you'll have to make a few changes to the example Voronoi diagram code.
First you'll have to make sure that all of the points fit in a circle. In your example, point location is given by d, here:
.attr("transform", function(d) { return "translate(" + d + ")"; })
Either d (your dataset) needs to fit in the circle, or you need to make some transformation of it. For data normalized to (-1,1) in both dimensions, the function
.attr("transform", function(d) { return "translate([" +
d[0]*Math.sqrt(1 - Math.pow(d[1],2)/2)
+ "," +
d[1]*Math.sqrt(1 - Math.pow(d[0],2)/2)
+ "])"; })
will do so. Here, we've created a new array that will be bounded by a circle from the array originally in d.
Next, you would need to clip your Voronoi diagram to be contained within a circle. The fun part is, there isn't a built in 'circle' geometric object to clip with, so you'll need to get creative! Either build a custom way to do this, or let the Voronoi extend beyond your circle and build an SVG to cover it up. Either should work.

D3: How to access an attribute of a previous item

I am using D3 to plot a rectangle for each object in an array, the height of the rectangle being dependant on the 'Size' property of the object. These rectangles are stacked on top of each other. I currently set the y position by summing the 'Size' of each subsequent rect that gets plotted - but this seems wrong - and I was wondering if there was a better way to do this, such as accessing the 'y' attribute of the previous item (and how?) or another way...
This is what the essence of my code looks like. There is a link to the fiddle below.
var cumY = 0;
var blocks1 = sampleSVG.selectAll("rect")
.data(fpp)
.enter()
.append("rect")
.sort(SortBySize)
.style("stroke", "gray")
.style("opacity", blockOpacity)
.style("fill", function (d) {return d.Colour})
.attr("width", 80)
.attr("height", function (d) {return d.Size})
.attr("x", 5)
.attr("y", function (d, i) {
var thisY = cumY;
cumY += d.Size;
// perhaps I could just return something like d.Size + previousItem.GetAttribute("y") ???
return thisY;
});
http://jsfiddle.net/ninjaPixel/bvER3/
This is tricky do! You're right that keeping track of the cumulative height 'seems wrong' - it works now but it isn't very idiomatic d3 and will get pretty messy once you start trying to do something more complicated.
I would try using d3's built in stack-layout which was created solve this problem. You might want to start working off of this example and posting an updated fiddle if you get stuck. Good luck!

D3 log scale displaying wrong numbers

I'm trying to wrap my head around the log scales provided by D3.js. It should be noted that as of yesterday, I had no idea what a logarithmic scale was.
For practice, I made a column chart displaying a dataset with four values: [100, 200, 300, 500]. I used a log scale to determine their height.
var y = d3.scale.log()
.domain([1, 500])
.range([height, 1]);
This scale doesn't work (at least not when applied to the y-axis as well). The bar representing the value 500 does not reach the top of the svg container as it should. If I change the domain to [100, 500] that bar does reach the top but the axis ticks does not correspond to the proper values of the bars. Because 4e+2 is 4*10^2, right?
What am I not getting here? Here is a fiddle.
Your scale already reverses the range to account for the SVG y-coordinates starting at the top of the screen -- ie, you have domain([min, max]) and range([max, min]). This means your calcs for the y position and height should be reversed because your scale already calculated y directly:
bars.append("rect")
.attr("x", function (d, i) { return i * 20 + 20; })
.attr("y", function (d) { return y(d); })
.attr("width", 15)
.attr("height", function (d) { return height - y(d); });
Here's an updated Fiddle: http://jsfiddle.net/findango/VeNYj/2/

Resources