i've been working with p5.js for a while, but i just picked up d3/p5 to make some voronoi diagrams. I have an existing program where an array of 512 points are mapped to the screen. I want to use those points to make a voronoi diagram.
the only relevant reference i can find is http://codepen.io/sepans/pen/Qbgaby
however, this program operates solely within function setup(), as it is a static program. this is the beginning:
function setup() {
var width = 1500,
height = 1000;
// randomly generate vertices in [[123,57],[43,67], ..] format
var vertices = d3.range(100).map(function(d) {
return [Math.random() * width, Math.random() * height];
});
// using d3.js voronoi layout to calculate voronoi polygons
var voronoi = d3.geom.voronoi()
.clipExtent([
[0, 0],
[width, height]
]);
I unsure how to move these elements into function draw(), so that my continuously dynamic variables can feed into the diagram.
this is probably not 100% clear so if anyone has any advice as to how i can explain my problem more effectively that would be great. Thanks!
The key to this program is this line:
var vertices = d3.range(100).map(function(d) {
return [Math.random() * width, Math.random() * height];
});
Please try to understand what this code is doing. It's filling the vertices array with 100 random points.
You say you want to fill your diagram with points you gather from somewhere else. You need to get rid of this line and populate vertices with your points. The best advice I can give you is to break your problem down into smaller pieces and take those pieces on one at a time.
For example, get this working with just a couple hard-coded points. Then try to add points when you click the mouse. Don't worry about your final end goal yet, just get these simple examples working. Then if you get stuck, you can post a MCVE along with a more specific question. Good luck.
Related
I'm trying to create a chart, where the input is a list of circles (position and radius) (or better ellipses) and the overlaps of the circles become shapes and a mouseover event can be applied. I also wish for the circles to move to the front, and have a mouseover effect, almost exactly like this
http://benfred.github.io/venn.js/examples/intersection_tooltip.html
The size of the overlap does not need to be known.
I've tried using D3.js Venn diagrams by Ben Frederickson. Although I can't understand some of the chart(selection) function, I've made it so that the circles can be inputted, and are drawn fine, including the overlaps, but this still relies on having the 'data' as an input as well and all of the sets (seen in the jsonp file) are still require. I realise that I can just make a script to list all of the possible sets, but this is ideal.
http://www.benfrederickson.com/venn-diagrams-with-d3.js/
I'm struggling to understand how the code creates these overlaps and then assigns them to the set.
Cheers, Ryan
Each intersection area has an SVG path computed for it by the 'venn.intersectionAreaPath' function. It takes a list of circles and returns a path element for the intersection area.
If you already have positions for the circles, you can override the 'layoutFunction' attribute on the venn diagram object like:
var circles = [{'x' : 0, 'y': 100, 'radius' : 80},
{'x' : 0, 'y': 0, 'radius' : 90 },];
var chart = venn.VennDiagram().layoutFunction(function() { return circles; });
d3.select("#venn").datum([{sets: [0]}, {sets:[1]}, {sets:[0,1]}]).call(chart);
This still requires having a list of all possible regions that you wish to draw (like "[{sets: [0]}, {sets:[1]}, {sets:[0,1]}]"), but this way you don't need to specify sizes for the regions.
I would like to draw a d3 voronoi diagram and clip it like d3 hull borders do bound a collection of nodes or any similar clipping.
The red line in this Screenshot shows what i would like to achieve.
How can it be done ?
The d3.geom.hull function will find a polygon that contains all your nodes tightly, with no extra spacing. That of course would cancel out much of the purpose of the Voronoi regions, which are intended to add some active space around the nodes. So what you need to calculate is a polygon that is a certain padding distance larger than the convex hull polygon on all sides.
My recommended algorithm:
Use d3.geom.hull(nodes) to calculate the array of vertices that define the tight boundary of your nodes.
Use those vertices to create a d3 polygon object.
Calculate the center of that polygon with .centroid().
For each vertex in your convex hull, calculate a point that is padding distance farther away from the center of the polygon.
Use this expanded polygon to clip all the polygons in the array returned by Voronoi function.
Sample code:
var hullFunction = d3.geom.hull()
.x(/*x accessor function*/)
.y(/*y accessor function*/);
var tightHull = hullFunction(nodes); //returns an array of vertices
var centerPoint = d3.geom.polygon(tightHullArray).centroid();
var expandedHull = tightHullArray.map( function(vertex) {
//Create a new array of vertices, each of which is the result
//of running this function on the corresponding vertex of the
//original hull.
//Each vertex is of the form [x,y]
var vector = [vertex[0] - centerPoint[0],
vertex[1] - centerPoint[1] ];
//the vector representing the line from center to this point
var vectorLength = Math.sqrt(vector[0]*vector[0]
+ vector[1]*vector[1]);
//Pythagorus' theorem to get the length of the line
var normalizedVector = [vector[0] / vectorLength,
vector[1] / vectorLength];
//the vector scaled down to length 1, but with the same angle
//as the original vector
return [vertex[0] + normalizedVector[0]*padding,
vertex[1] + normalizedVector[1]*padding ];
//use the normalized vector to adjust the vertex point away from
//the center point by a distance of `padding`
});
var clippedVoronoi = voronoiPolygons.map(function(voronoi) {
//voronoiPolygons would be the array returned by the voronoi function
return expandedHull.clip(voronoi);
//I think this is correct; if you get weird results, try
// return voronoi.clip(expandedHull);
});
I recently made an example to illustrate to myself how polygon clipping works:
http://tributary.io/inlet/8263747
You can see the clipping code in the update function, and the rendering code in the process function. drag the points around to see how the clipping will be affected.
A couple things to watch out for:
the order of the points in the "hull" (or clipping path in my example) matters. Your polygon has to be counter-clockwise as well as convex (no caves). If these conditions aren't met there is no error, you just get back an empty array.
the polygon operations manipulate your point arrays in place, if you don't want your geometry itself clipped, but want a copy you need to make a copy first.
Basically, I am attempting to apply the d3 fisheye distortion algorithm to a radial tree. I believe the issues I am encountering revolve around the fact that the coords being fed to the fisheye distortion are the coords computed by the d3.layout.tree. But the actual coords have been adjusted by the g transform. So, the coords resulting from the fisheye distortion need to be adjusted back to the g transform.
For example:
// re-setting the projection according to fisheye coords
diagonal.projection(function(d) { d.fisheye = fisheye(d); return [d.fisheye.y, d.fisheye.x / 180 * Math.PI]; })
I have been attempting this...here is the fiddle.
I am somewhat close...help is appreciated.
Following the direction I'd suggested in the comments, this is the result:
https://jsfiddle.net/xdk5ehcr/
Instead of using rotations and translations to position the nodes, I created two trigonometry-based functions to calculate horizontal and vertical position from the data (x,y) values, which are treated as polar coordinates.
Then I had to set the fisheye function to use my positioning functions as "accessor" functions instead of reading d.x and d.y directly. Unfortunately, the basic plug-in you were using for the fisheye didn't include a way to get and set x/y accessor functions, so I had to modify that too. I was surprised it wasn't already in the code; it's a standard functionality on most d3 layout objects.
(When I get github set up, I will have to make a pull request to add it in. I'll need to figure out how the fisheye scale/zoom function works, though -- I took that out of this example since you weren't using it.)
The positioning functions were as follows:
function getHPosition(d){
//calculate the transformed (Cartesian) position (H, V)
//(without fisheye effect)
//from the polar coordinates (x,y) where
//x is the angle
//y is the distance from (radius,radius)
//See http://www.engineeringtoolbox.com/converting-cartesian-polar-coordinates-d_1347.html
return (d.y)*Math.cos(d.x);
}
function getVPosition(d){
return (d.y)*Math.sin(d.x);
};
The functions are used to set the original position of the nodes and links, and then once the fisheye kicks in it uses these functions internally, returning the results (with distortion if appropriate) as d.fisheye.x and d.fisheye.y.
For example, for links that means the projection setting the d3.svg.diagonal function like this for initialization:
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [getHPosition(d), getVPosition(d)];
});
But like this for update:
diagonal.projection(function(d) {
d.fisheye = fisheye(d);
return [d.fisheye.x, d.fisheye.y];
});
There are a couple other little changes:
I simplified the dimensions of the plotting area a bit.
I added a background rectangle with pointer-events:all; so that the fisheye doesn't turn on and off as the mouse moves between nodes and empty background.
I didn't bother rotating the text (since the node groups are no longer rotating, it doesn't happen by default), but you could easily add in a rotate transformation on the individual text elements.
Finally, and this one stumped me for longer than I'd like to admit, the angles have to be in radians for the Javascript trig functions. Couldn't figure out why my layouts were so ugly, with overlapping lines. I thought it was something to do with switching between d3.svg.diagonal() and d3.svg.diagonal.radial() and spent a lot of time trying to do inverse-trig and all sorts of things...
In the two examples in d3js website:
Easy Version
More complicated
I find some of their code hard to understand. The D3 reference API did not offer in depth explanation. In the easy version, the vertices were generated using Math.random() like so:
var vertices = d3.range(100).map(function(d) {
return [Math.random() * width, Math.random() * height];
});
Width and height are the document size. This ensure all vertices are within the scope of the SVG element ( svg tag has width and height attribute set to these values too).
However in the more complicated version, it uses an algorithm to generate the vertices:
var vertices = d3.range(numVertices).map(function(d) { return {x: d.x, y: d.y}; })
voronoiVertices = vertices.map(function(o){return [o.x, o.y, o]})
path = path.data(d3.geom.voronoi(voronoiVertices))
When examining the variable vertices in console. I found out that it is a two dimensional array. Each sub-array has a length of 3, first and second element are numbers, and the last element is an object which contains a lot more properties like x, y and index and so on.
The resulting Voronoi diagram are very different. Why is that? They are both generated using d3.geom.voronoi() and (from what I can hopefully understand) the only differences lies in the object that is passed into it. In the easy example it was simply a two dimensional array with 2 numbers in each sub-array. I cannot seem to see how the two lines in the complicated example works to create such a complex object and append it to the end of each sub-array.
How is it ensured that the veritices are within the bound of the parent SVG element too?
I am really stuck, thank you so much for any help.
I'm trying to create a visualization with D3 such that nodes are differently sized by a particular attribute and bigger nodes go to the center and smaller nodes go to the outside. I have sizing and clustering and collision detection working, but I can't figure out how to tell the bigger nodes to go to the center.
I've tried messing with the charge, but couldn't convince that to work. I got linkDistance to move the bigger ones to the center, but (a) getting there was VERY jittery and (b) the smaller ones are way outside rather than tightly packed. The linkDistance is still in the code, just commented out.
It's up at http://pokedex.mrh.is/stats/index.html:
The relevant code (I assume) is also below. The nodes are sized per their attr attribute. Oh, and the nodes are Pokémon.
force = d3.layout.force()
// .gravity(0.05)
// .charge(function(d, i) { return d.attr; })
// .linkDistance(function(d) {
// return 50000/Math.pow(d.source.attr+d.target.attr,1);
// })
.nodes(pokemon)
// .links(links)
.size([$(window).width(), $(window).height()]);
The following gave me a less jittery version of what you have now.
force = d3.layout.force()
.gravity(0.1)
.charge(function(d, i) { return -d[selectedAttr]})
.friction(0.9)
.nodes(pokemon)
.size([$(window).width(), $(window).height()]);
To answer your actual question, each node's coordinates are currently being placed in your graph at random. I quote from the D3 documentation:
When nodes are added to the force layout, if they do not have x and y attributes already set, then these attributes are initialized using a uniform random distribution in the range [0, x] and [0, y], respectively.
From my experience, there's no magic force method that gets the nodes you want to the center of the map. The way that I've accomplished your desired result in the past has been by replacing the randomized coordinates of each node with coordinates that place the nodes in a the desired order, expanding from the center of the map.