I am drawing circles by setting a fixed x position but a changing y position. The problem is the circles are overlapping since the radius of each circle is different.
Ideally in theory to solve that I would probably want to get the y position of the previous circle and add the radius of the current circle to it to get the y position of the current circle. Correct me if I am thinking it wrong.
Right now I am doing something like this now
var k = 10;
var circleAttributes = circles.attr("cx", '150')
.attr("cy", function (d) {
return (k++) * 10; //this is a very gray area
})
And I am getting an overlap. Ideally I would like to space the circles form each other. Even if the outer edges touch each other I could live with that. How should I approach it?
I am writing a range which i am using to get the radius
var rScale = d3.scale.linear()
.domain([min, max])
.range([10, 150]);
and simply passing that as the radius like this
.attr("r", function(d) { return rScale(d.consumption_gj_);})
This is my fiddle
http://jsfiddle.net/sghoush1/Vn7mf/27/
Did a solution here: http://tributary.io/inlet/6283630
The key was to keep track of the sum of the radius of all previous circles. I did that in a forEach loop:
data.forEach(function(d,i){
d.radius = rScale(d.consumption_gj_);
if (i !== 0){
d.ypos = d.radius*2 + data[i-1].ypos;
}
else {
d.ypos = d.radius*2;
}
})
then, when setting the attributes of the circles you can use your new d.radius and d.ypos
var circleAttributes = circles.attr("cx", '150')
.attr("cy", function (d,i) {
return d.ypos + 5*i;
})
.attr("r", function(d) { return d.radius;})
The Charge Property
The charge in a force layout refers to how nodes in the environment push away from one another or attract one another. Kind of like magnets, nodes have a charge that can be positive (attraction force) or negative (repelling force).
From the Documentation:
If charge is specified, sets the charge strength to the specified value. If charge is not specified, returns the current charge strength, which defaults to -30. If charge is a constant, then all nodes have the same charge. Otherwise, if charge is a function, then the function is evaluated for each node (in order), being passed the node and its index, with the this context as the force layout; the function's return value is then used to set each node's charge. The function is evaluated whenever the layout starts.
A negative value results in node repulsion, while a positive value results in node attraction. For graph layout, negative values should be used; for n-body simulation, positive values can be used. All nodes are assumed to be infinitesimal points with equal charge and mass. Charge forces are implemented efficiently via the Barnes–Hut algorithm, computing a quadtree for each tick. Setting the charge force to zero disables computation of the quadtree, which can noticeably improve performance if you do not need n-body forces.
A good tutorial that will help you see this in action:
http://vallandingham.me/bubble_charts_in_d3.html
Related
I am currently working on a project where I need to keep the nodes of a force simulation away from the svg edges.
My attempt was using a exponential curve to increase the centering force for nodes reaching a certain x coordinate.
I am using this function
function getBorderForce(x, width, middle, steepness = 10) {
return Math.pow(((x-middle)/(width/2)), steepness)
}
If the given x value reaches middle + width / 2 it returns a value of 1. The steepness of the increase can modified by steepness parameter. A higher value means the nodes would get repealed later. I hope it is understandable.
I would then implement this function as follows into a d3.forceSimulation():
const sim = d3.forceSimulation().
.force("forceX", d3.forceX().x(d => d.forceX).strength(0.02))
.force("forceY", d3.forceY().y(d => d.forceY).strength(0.1))
.force("edgeRepeal", d3.forceX().x(_ => center).strength(d =>
getBorderForce(d.x, width, center)
))
.nodes(nodes)
When I use this approach my browser crashes and I get an out of memory error.
It works is if I use the d.forceX instead of the d.x value, but that means that nodes closer to the edges would be pushed in and would not be placed on their desired position d.forceX.
Maybe someone has a better idea or can show the flaw in my code – I am relatively new to d3, so it would be no surprise.
All the Best
I'm looking for a way to plug in groups to my force-directed graph visualization. I've found three related examples so far:
Cola.js which would require adding another library and possibly retro-fitting my code to fit this different library.
This block, which is pretty hard to untangle.
This slide from mbostock's slide deck, which isn't what I want but on the right path...
What I'd like most is a simple way of adding something very close to the structure from the first link, but without too much overhead.
Right now I have a pretty standard setup:
var link = g.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style(...
var node = g.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("id", function(d) { return d.id; })
I was hoping to just grab the d3 code out of cola.js and mess with it, but that library seems fairly complicated so it wouldn't be too easy. I'm hoping it isn't too hard to get something kind of like this in straight d3:
Thanks!
I'm following the title "visualize groups of nodes" more than the suggested picture, but I think it wouldn't be that hard to tweak my answer to show bounding boxes as in the image
There's probably a few d3 only solutions, all of them almost certainly require tweaking the node positions manually to keep nodes grouped properly. The end result won't strictly be typical of a force-layout because links and node positions must be manipulated to show grouping in addition to connectivity - consquently, the end result will be a compromise between each force - node charge, length strength and length, and group.
The easiest way to accomplish your goal may be to:
Weaken link strength when links link different groups
On each tick, calculate each group's centroid
Adjust each node's position to move it closer to the group's centroid
Use a voronoi diagram to show the groupings
For my example here, I'll use Mike's canonical force layout.
Weaken links when links link different groups
Using the linked example, we can dampen the link strength when link target and link source have different groups. The specified strength will likely need to be altered depending on the nature of the force layout - more inter-connected groups will likely need to have weaker intergroup link strength.
To change the link strength depending on if we have an intergroup link or not, we might use:
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }).strength(function(link) {
if (link.source.group == link.source.target) {
return 1; // stronger link for links within a group
}
else {
return 0.1; // weaker links for links across groups
}
}) )
.force("charge", d3.forceManyBody().strength(-20))
.force("center", d3.forceCenter(width / 2, height / 2));
On Each Tick, Calculate Group Centroids
We want to force group nodes together, to do so we need to know the centroid of the group. The data structure of simulation.nodes() isn't the most amenable to calculating centroids, so we need to do a bit of work:
var nodes = this.nodes();
var coords ={};
var groups = [];
// sort the nodes into groups:
node.each(function(d) {
if (groups.indexOf(d.group) == -1 ) {
groups.push(d.group);
coords[d.group] = [];
}
coords[d.group].push({x:d.x,y:d.y});
})
// get the centroid of each group:
var centroids = {};
for (var group in coords) {
var groupNodes = coords[group];
var n = groupNodes.length;
var cx = 0;
var tx = 0;
var cy = 0;
var ty = 0;
groupNodes.forEach(function(d) {
tx += d.x;
ty += d.y;
})
cx = tx/n;
cy = ty/n;
centroids[group] = {x: cx, y: cy}
}
Adjust each node's position to move it closer to its group's centroid:
We don't need to adjust every node - just those that are straying fairly far from their centroids. For those that are sufficiently far we can nudge them closer using a weighted average of the centroid and the node's current position.
I modify the minimum distance used to determine if a node should be adjusted as the visualization cools. For the majority of the time when the visualization is active, when alpha is high, the priority is grouping, so most nodes will be forced towards the grouping centroid. As alpha drops towards zero, nodes should be grouped already, and the need to coerce their position is less important:
// don't modify points close the the group centroid:
var minDistance = 10;
// modify the min distance as the force cools:
if (alpha < 0.1) {
minDistance = 10 + (1000 * (0.1-alpha))
}
// adjust each point if needed towards group centroid:
node.each(function(d) {
var cx = centroids[d.group].x;
var cy = centroids[d.group].y;
var x = d.x;
var y = d.y;
var dx = cx - x;
var dy = cy - y;
var r = Math.sqrt(dx*dx+dy*dy)
if (r>minDistance) {
d.x = x * 0.9 + cx * 0.1;
d.y = y * 0.9 + cy * 0.1;
}
})
Use a Voronoi Diagram
This allows the easiest grouping of nodes - it ensures that there is no overlap between group shells. I haven't built in any verification to ensure that a node or set of node's aren't isolated from the rest of their group - depending on the visualization's complexity you might need this.
My initial thought was using a hidden canvas to calculate if shells overlapped, but with a Voronoi you could probably calculate if each group is consolidated using neighboring cells. In the event of non-consolidated groups you could use a stronger coercion on stray nodes.
To apply the voronoi is fairly straightforward:
// append voronoi
var cells = svg.selectAll()
.data(simulation.nodes())
.enter().append("g")
.attr("fill",function(d) { return color(d.group); })
.attr("class",function(d) { return d.group })
var cell = cells.append("path")
.data(voronoi.polygons(simulation.nodes()))
And update on each tick:
// update voronoi:
cell = cell.data(voronoi.polygons(simulation.nodes())).attr("d", renderCell);
Results
Altogether, this looks like this during the grouping phase:
And as the visualization finally stops:
If the first image is preferable, then remove the part the changes the minDistance as alpha cools down.
Here's a block using the above method.
Further Modification
Rather than using the centroid of each group's nodes, we could use another force diagram to position the ideal centroid of each group. This force diagram would have a node for each group, the strength of links between each group would correspond to te number of links between the nodes of the groups. Using this force diagram, we could coerce the original nodes towards our idealized centroids - the nodes of the second force layout.
This approach may have advantages in certain situations, such as by separating groups by greater amounts. This approach might give you something like:
I've included an example here, but hope that the code is commented sufficiently to understand without a breakdown like the above code.
Block of second example.
The voronoi is easy, but not always the most aesthetic, you could use a clip path to keep clip the polygons to some sort of oval, or use a gradient overlay to fade the polygons out as they reach the edges. One option that is likely possible depending on graph complexity is using a minimum convex polygon instead, though this won't work well with groups with less than three nodes. Bounding box's probably won't work in most instances, unless you really keep the coercion factor high (eg: keep minDistance very low the entire time). The trade off will always be what do you want to show more: connections or grouping.
Let's say I have 16 circles in an 2 x 8 grid:
svg = d3.select(body).append('svg').attr('height,h).attr('width',w);
svg.selectAll('.centroids')
.data(d3.range(0,16))
.enter()
.append('circle')
.attr('class','centroids')
.attr('r','5')
.attr('cx', function(d,i) { return i * 10; })
.attr('cy', function(d,i) {
if (i > 7) return 20;
return 10;
});
Given a random coordinate in that space, how do I determine the nearest .centroid point?
One way in N time is of course to loop through all the points, measuring the hypotenuse to the difference in x and y coordinates, choosing the smallest value.
I'd like to find a better way though. Does anyone know an optimized way?
Optimization will depend on your exact settings:
if you have a few nodes (16 as in your example), in random positions, then your method is probably optimal (just compute the square of the hypotenuse, which gains a few square root operations).
if you have many nodes in random positions, you'll want to start considering quadtrees to manage your nodes. The overhead is not negligible, so don't bother about it until you have hundreds or thousands or nodes. On the plus side, d3has it all coded for you.
for a grid:
var startx=0;
var offsetx=10;
var cols=8;
var starty=10;
var offsety=10;
var rows=2;
var xi=d3.median([0,cols-1, Math.round((x-startx)/stepx)])
var yi=d3.median([0,rows-1, Math.round((y-starty)/stepy)])
var i=xi + yi*cols
this is constant time, adjust the (many) constants according to your dimensions.
A bit of details: (x-startx)/stepx allows to scale the coordinates so that the first dot is at 0, the next at 1, etc. Math.round gives the nearest integer, d3.median pushes the result between 0 and cols-1 (check out each case, by all mean it's nicer than nested ifs).... overall this gives the index of the nearest column, then you do the same for the rows, and there you are!
I'm interested in graphing live-ish data in D3js. Now, when I say "live-ish" I mean that I'll be collecting data every 200ms +/- 10ms, but there may be several minute long periods of inactivity. Fortunately, the input data is time-stamped!
What I have so far: I've followed some line drawing in d3 guides (eg: this) and I have a Y axis with the value range/domain I want. I have an X axis with the range I want and a moving domain as per a standard time-series fixed-width graph. That is, if my graph's x axis domain is (0:15, 0:35) in 5 seconds it will be (0:20, 0:40). This transitions nicely as it's using linear easing.
I have mock-data being output each iteration of the graph tick. My domain is set up as such that new points are just out of the x-axis domain such as to allow the smooth effect as per 1. All in all, it looks great.
So where do I go from here? My desired result: data comes in asynchronously and is placed precisely at its x-axis time-stamped location. If data is up to date, it gets placed juuust outside the x-axis domain and has a smooth transition in. If data doesn't arrive in time, the graph continues without drawing any new points until data is received, at which time it adds each point at its appropriate time-stamp retroactively. If data for the missing period doesn't arrive at all, we just continue with a gap in the graph. I can emulate this by calling...
d3.select(window).on("click", .. )
Effectively, I can click to add random data at the current time-stamp using some anonymous function which allows me to mimic the data / event structure my code should handle.
I think my current confusion is due to how I add data and draw the path from it.
var line = d3.svg.line()
.interpolate("basis-open")
.x(function(d, i) { return x(now - (n-1-i)*duration); })
.y(function(d, i) { return y(d); });
var axis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(x.axis = d3.svg.axis().scale(x).orient("bottom"));
svg.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(data)
.attr("class", "line")
The big question: my y-values correspond to the path.datum(data) (data is just an array of values) appropriately, but when I push to the data array to draw the line, it always places each point graphically at equal distance apart. How do I break out of the mono-variable graph without destroying my time-series smooth scrolling animation? I could record a second array of timestamps alongside my data array, but how to I integrate those into the line? Ideally, I'd have them both be a part of the same array so I could sort by timestamp so when I call data.shift() truly the oldest data is gone. I tried changing the "duration" of the transition function but it made the graph accelerate weirdly and didn't actually break the equidistance of points on x.
How do I set up the y-axis graph to also take into account x-location without breaking my graph?
Alright, so I figured out a pretty straightforward way of doing exactly what I wanted while still using .datum(data) instead of .data(data) (where 'data' is my array). Instead of passing in an array of values to .datum, I pass in an array of objects. Or, what was once data = [value1, value2, ...] is now data = [{gx:timestamp1, gy:value1}, {gx:timestamp2, gy:value2}, ...].
My line x-axis / y-axis functions are now...
.x(function(d, i) { return x(d.gx); })
.y(function(d, i) { return y(d.gy); });
Which ends up being a bit neater than my initial run at it. My transition functions didn't have to change.
The final puzzle piece was what to do about data that comes in time-stamped but out of order. Fortunately, my display domain isn't very large and thus, I don't need to store many values in the array. As such, when new data comes in I simply sort it to make the line not a wobbly-bobbly mess.
data.sort(function(a,b){ return a.gx - b.gx});
And voila! If the size of the array is beyond the boundary, shift off data. This gives us the effect of a sliding timeseries window of the past n seconds where time-stamped data points may be arbitrarily dumped on and displayed properly.
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.