Storing ellipse path data in D3js - d3.js

I've got an application that uses D3js to display ratios as ellipses on a chart. Right now every time the chart is zoomed in and out of, my code recalculates the path of every ellipse. Right now I'm trying to store the projected path data for each ellipse, then just scale that, but I'm having trouble figuring out how to store data like that for each ellipse. Anyone have an idea of how I'd approach it?
The calculations I want to store are in interpolate
var ellipsePath = d3.svg.line()
.x(function (datum) {
return x(datum[0] + d.x);
})
.y(function (datum) {
return y(datum[1] + d.y);
})
.interpolate(function (points) {
var i = 1, path = [points[0][0], ",", points[0][1]];
while (i + 3 <= points.length) {
cubicBezier(path, points[i++], points[i++], points[i++]);
}
return path.join("");
});

Related

d3 v4 voronoi find nearest neighbours in a scatterplot svg filled with (dots / circles)

I am trying to find the nearest neighbors in a scatterplot using the data attached below, with the help of this snippet -
const voronoiDiagram = d3.voronoi()
.x(d => d.x)
.y(d => d.y)(data);
data.forEach(function(d){
console.log(d, voronoiDiagram.find(d.x, d.y, 50));
});
Now the dataset I am using is the standard iris sepal, petal lengths data in
format -
{"sepalLength":7.7,"sepalWidth":3,"petalLength":"6.1","petalWidth":"2.3","species":"virginica","index":135,"x":374.99999999999994,"y":33.75,"vy":0,"vx":0},
{"sepalLength":6.3,"sepalWidth":3.4,"petalLength":"5.6","petalWidth":"2.4","species":"virginica","index":136,"x":524.9999999999999,"y":191.25,"vy":0,"vx":0},
{"sepalLength":6.4,"sepalWidth":3.1,"petalLength":"5.5","petalWidth":"1.8","species":"virginica","index":137,"x":412.5,"y":179.99999999999994,"vy":0,"vx":0},
{"sepalLength":6,"sepalWidth":3,"petalLength":"4.8","petalWidth":"1.8","species":"virginica","index":138,"x":374.99999999999994,"y":225,"vy":0,"vx":0},
....
So, essentially it is in the form of
{d: {x, y, sepal length, width, petal length, width}.
Now, I am trying to find the nearest neighbors with d3 voronoi from reference.
But, all I get is this in results -
Let point d in my dataset =
{"sepalLength":5.9,"sepalWidth":3,"petalLength":"5.1","petalWidth":"1.8","species":"virginica","index":149,"x":374.99999999999994,"y":236.24999999999997,"vy":0,"vx":0}
Now, the voronoiDiagram.find(d.x, d.y, 50) for this is resulting in -
"[375,236.25]"
That is, the same point with coordinates rounded off instead of another point.
So, how do I exclude current point being scanned in this case from the voronoi diagram.
Also, If I exclude that point & re-calculate everything would this be good from the performance perspective ?
Can anyone help me with finding nearest neighbors from a set of points
with d3 voronoi / quadtrees (I have tried a couple of examples already from Mike Bostock but couldn't get them to work in my case because of some errors,
so will post them if d3 voronoi does not help).
voronoiDiagram.find(y, x, r) will only ever return, at most, once cell. From the API documentation:
Returns the nearest site to point [x, y]. If radius is specified, only sites within radius distance are considered. (link)
I've previously read that as being plural, apparently I've never looked closely (and I think there is a large amount of utility in being able to find all points within a given radius).
What we can do instead is create a function fairly easily that will:
start with voronoiDiagram.find() to find the cell the point falls in
find the neighbors of the found cell
for each neighbor, see if its point is within the specified radius
if a neighbors point is within the specified radius:
add the neighbor to a list of cells with points within the specified radius,
use the neighbor to repeat steps 2 through 4
stop when no more neighbors have been found within the specified radius, (keep a list of already checked cells to ensure none are checked twice).
The snippet below uses the above process (in the function findAll(x,y,r)) to show points within the specified distance as orange, the closest point will be red (I've set the function to differentiate between the two).
var width = 500;
var height = 300;
var data = d3.range(200).map(function(d) {
var x = Math.random()*width;
var y = Math.random()*height;
var index = d;
return {x:x,y:y,index:index}
});
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
var circles = svg.selectAll()
.data(data, function(d,i) { return d.index; });
circles = circles.enter()
.append("circle")
.attr("cx",function(d) { return d.x; })
.attr("cy",function(d) { return d.y; })
.attr("r",3)
.attr("fill","steelblue")
.merge(circles);
var voronoi = d3.voronoi()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.size([width,height])(data);
var results = findAll(width/2,height/2,30);
circles.data(results.nearest,function(d) { return d.index; })
.attr("fill","orange");
circles.data([results.center],function(d) { return d.index; })
.attr("fill","crimson");
var circle = svg.append("circle")
.attr("cx",width/2)
.attr("cy",height/2)
.attr("r",30)
.attr("fill","none")
.attr("stroke","black")
.attr("stroke-width",1);
circle.transition()
.attrTween("r", function() {
var node = this;
return function(t) {
var r = d3.interpolate(30,148)(t);
var results = findAll(width/2,height/2,r);
circles.data(results.nearest,function(d) { return d.index; })
.attr("fill","orange");
return r;
}
})
.duration(2000)
.delay(1000);
function findAll(x,y,r) {
var start = voronoi.find(x,y,r);
if(!start) return {center:[],nearest:[]} ; // no results.
var queue = [start];
var checked = [];
var results = [];
for(i = 0; i < queue.length; i++) {
checked.push(queue[i].index); // don't check cells twice
var edges = voronoi.cells[queue[i].index].halfedges;
// use edges to find neighbors
var neighbors = edges.map(function(e) {
if(voronoi.edges[e].left == queue[i]) return voronoi.edges[e].right;
else return voronoi.edges[e].left;
})
// for each neighbor, see if its point is within the radius:
neighbors.forEach(function(n) {
if (n && checked.indexOf(n.index) == -1) {
var dx = n[0] - x;
var dy = n[1] - y;
var d = Math.sqrt(dx*dx+dy*dy);
if(d>r) checked.push(n.index) // don't check cells twice
else {
queue.push(n); // add to queue
results.push(n); // add to results
}
}
})
}
// center: the point/cell that is closest/overlapping, and within the specified radius, of point x,y
// nearest: all other cells within the specified radius of point x,y
return {center:start,nearest:results};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

how to draw a d3 bubble chart for a local geojson file

I want to plot a d3 bubble chart. By taking the example from d3
This link
i tried to get the bubble chart for my local file i.e myfile.geojson. The code which i tried is in the plunker link. I want to plot a bubble chart based on the value "Profit". Tried everything in the google and youtube but i didnt get the solution to my problem.
Plunker link
I am new to d3. If i do any mistakes in the code please suggest me to make them correct. Thanks In advance.
Your data is way different from flare.json so copying the recurse code will not make your data. Your dataset is very simple it does not need a recursion to flatten the dataset.
function classes(root) {
var classes = [];
function recurse(profit, node) {
if (node.profit) node.profit.forEach(function(child) { recurse(node.profit, child); });
else classes.push({packageName: type, className: node.profit, value: node.profit});
}
recurse(null, root);
return {features: classes};
}
This should have been:
function classes(root) {
var classes = root.features.map(function(f, i) {
//here i is the index
return {
value: f.properties.profit,
className: "Hello" + i,////just giving some dummy values
packageName: i//just giving some dummy values
}
});
return classes;
}
Now pass this data to the bubble layout like this:
var node = svg.selectAll(".node")
.data(bubble.nodes({
children: classes(root)
}).filter(function(d) {
return !d.children;
}))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
EDIT
The bubble chart is based on the profit value:
The radius of the circle depends on the value you give here inside the classes function.
return {
value: f.properties.profit,
className: "Hello" + i,////just giving some dummy values
packageName: i//just giving some dummy values
}
Thus more the value or f.properties.profit the bigger will be the radius.
The radius will be relative to the diameter you setting here:
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
Read Domain range in d3 https://www.dashingd3js.com/d3js-scales
In place of className and packageName what should i give to get the bubble chart based on the profit value.
This i don't know what to answer I think your concept is not clear so is the naive question.
If you see the code packageName defines the color
.style("fill", function(d) {
return color(d.packageName);
});
and className defines the text to be displayed in the bubble
.text(function(d) {
return d.className;
});
Kindly see the code in fiddle its very simple to understand.
Working code here.

D3 Transitionning data with sequences sunburst

Introducing
I'm using Sequences Sunburst of d3.js for visualization of data.
I want to add a transition between two datasets (triggered by a button). I would like each arc to animate to display the new data.
Something like this: (1)Sunburst_Exemple, but without changing the accessor.
Research
In the (1)Sunburst_Example, the value accessor is modified. But I want to change the data, not the function that defines how to reach the data.
So, I searched a way for redefining data into path.
I was inspired by (2)Sunburst_Exemple, using each() method to store old values and attrTween() for transitions. But nothing is changing and I have the following error message:
Maximum call stack size exceeded . Maybe caused by the fact I have a method and I'm not in a global scope.
(2) link : _http://ninjapixel.io/StackOverflow/doughnutTransition.html
Then I have tried (3)Zoomable_Sunburst example, but nothing it happens in my case... .
(3) link : _http://bl.ocks.org/mbostock/4348373
My Example
Here is my example : JSFIDDLE
Problem is :
colors are lost
transition is not happening
I think I don't understand how transitioning is really working, I could have missed something that could help me in this case.
Any help ?
-
Listener of button call click method that redefined nodes and path.
/*CHANGING DATA*/
function click(d){
d3.select("#container").selectAll("path").remove();
var nodes = partition.nodes(d)
.filter(function(d) {
return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
}) ;
var path = vis.data([d]).selectAll("path")
.data(nodes)
.enter().append("svg:path")
.attr("display", function(d) { return d.depth ? null : "none"; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", function(d) { return colors[d.name]; })
.style("opacity", 1)
.on("mouseover", mouseover)
.each(stash)
.transition()
.duration(750)
.attrTween("d", arcTween);
;
// Get total size of the tree = value of root node from partition.
totalSize = path.node().__data__.value;
}
_
// Stash the old values for transition.
function stash(d) {
d.x0 = d.x;
d.dx0 = d.dx;
}
// Interpolate the arcs in data space.
function arcTween(a) {
var i = d3.interpolate({x: a.x0, dx: a.dx0}, a);
return function(t) {
var b = i(t);
a.x0 = b.x;
a.dx0 = b.dx;
return arc(b);
};
}
Data Characteristics :
the root node is the same for the two datasets.
the structure is globally the same, only the values are changing.
Some fields disappear but we can add them with value equal to 0.

Semantic zoom on map with circle showing capital

I wanted to implement semantic zoom on map of d3.js. I have developed a example of map and Major cities of particular country, which is working fine but sometime circle got overlap due to near places in maps so if i implement semantic zoom which will solve my circle overlapping problem.
I don't understand how to transform only graph not circle in my map.
My zooming function code is :
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("circle")
.attr("d", path.projection(projection));
g.selectAll("path")
.attr("d", path.projection(projection));
});
My jsfiddle link
Anybody help me please!
Are you asking how to not scale the circles according to the zoom? The way you have it you are scaling the g element and the circles are in it. The way I'd do it is to "shrink" the circles when zoomed.
// zoom and pan
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("circle")
.attr("r", function(){
var self = d3.select(this);
var r = 8 / d3.event.scale; // set radius according to scale
self.style("stroke-width", r < 4 ? (r < 2 ? 0.5 : 1) : 2); // scale stroke-width
return r;
});
});
Update fiddle.
I know this is an old post and it already has an accepted answer but as the original post suggests, d3's Semantic Zoom is a cleaner way of doing this. I implemented the same thing you did (circles on cities on a map) by following this demo https://bl.ocks.org/mbostock/368095. The only thing I had to change was I had to subtract the initial position of the circles in the transform function in order to correct their initial position.
function transform(t) {
return function(d) {
const point = [d.x, d.y] // this is the initial point
const tPoint = t.apply(point) // after translation
const finalPoint = [tPoint[0] - d.x, tPoint[1] - d.y]// subtract initial x & y to correct
return `translate(${finalPoint})`;
};
}

Normalize the Graph axes in NVD3 Line with focus chart

I am having two lines in th graph like in the picture having different X axis points. I just want to compare the lines. So I need to bring the second line under the first line (Normalize the graph like the second picture)
I tried to use nvd3 domain and range option and some others too. But its not working. Could you guys tell me how to get those normalized graph. I dont worry about the Tick format as far as the lines are comparable.
PICTURE 1 (the graph I am having)
https://www.dropbox.com/s/sycwppnlachju2s/Screenshot%20from%202014-06-19%2014%3A21%3A40.png
PICTURE 2 (Graph I need)
https://www.dropbox.com/s/eoajf0yyk96w6y7/Screenshot%20from%202014-06-19%2014%3A20%3A21.png
nv.addGraph(function() {
var chart = nv.models.lineWithFocusChart();
chart.xAxis
.tickFormat(d3.format(',f'));
chart.yAxis
.tickFormat(d3.format(',.2f'));
chart.y2Axis
.tickFormat(d3.format(',.2f'));
d3.select('#chart svg')
.datum(data)
.transition().duration(500)
.call(chart)
;
nv.utils.windowResize(chart.update);
return chart;
});
This is my data file. data.json
[{"key":"Memory_FREEmyses1","values":[{"x":"1395426430","y":"200028"},
{"x":"1395426431","y":"199904"},{"x":"1395426432","y":"187620"},
{"x":"1395426434","y":"187504"},{"x":"1395426435","y":"187380"},
{"x":"1395426436","y":"187008"},{"x":"1395426437","y":"186760"},
{"x":"1395426438","y":"186512"},{"x":"1395426439","y":"186388"},
{"x":"1395426440","y":"186264"},{"x":"1395426441","y":"181804"},
{"x":"1395426443","y":"181084"},{"x":"1395426444","y":"181084"}]},
{"key":"Memory_FREEmyses2","values":[{"x":"1395426455","y":"178604"},
{"x":"1395426456","y":"178348"},{"x":"1395426457","y":"178356"},
{"x":"1395426458","y":"178232"},{"x":"1395426460","y":"178108"},
{"x":"1395426461","y":"177860"},{"x":"1395426462","y":"177480"},
{"x":"1395426463","y":"176992"},{"x":"1395426464","y":"176868"},
{"x":"1395426465","y":"176620"},{"x":"1395426466","y":"176612"},
{"x":"1395426467","y":"176620"}]}]
Try to map your data like:
data = data.map(function(series) {
var d0 = series.values[0].x;
var dN = series.values[series.values.length -1].x;
series.values = series.values.map(function(d) {
return {
x: 10*(d.x-d0)/(dN-d0),
y: d.y
}
});
return series;
})
See demo.

Resources