I have been working on a d3 visualization in v4, which is a scatter/bubble plot with data points that have been filled with an image (see JS Bin link).
The problem I have is that a lot of the data points overlap, so I would like to have the overlapping points move until they are next to each other (the exact location of the points isn't too important).
I am new to d3 and have been struggling to get my head around simulation (collision detection, forceCollide etc) and would appreciate some help on how I can achieve this.
My attempts so far result in the initial x and y data points being ignored and the result is one big circle of non-overlapping circles. But what I'm after is the initial x and y values to be preserved and ONLY the overlapping circles be moved (so the outliers should still be outliers).
I've created an example on JS Bin (below) to demonstrate what I have. In particular, the force simulation code (when uncommenting simulation.stop(); ) seems to overwrite the initial x and y values.
I feel like I'm almost there but I'm doing something in the wrong order...
var simulation = d3.forceSimulation(data)
.force('charge', d3.forceManyBody().strength(3))
.force('collision', d3.forceCollide().radius(function(d) { return d.radius + 1 }) )
.on('tick', function() {
svg.selectAll('.node')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
})
simulation.stop();
JS Bin Example
I have a similar chart issue and was able to get the desired behavior by applying 3 forces: d3.forceX, d3.forceY to maintain the plotted points and then d3.forceCollide which treats each circle not as a point but a circle with a radius so they don't overlap.
You had a couple issues. First, utilizing simulation.stop() will stop the simulation from running and applying your force calculations. I believe simulation.stop() is used if you want to manually control the simulation using simulation.tick() see d3 docs for .stop(). Also, I don't think using d3.forceManyBody is needed, but if there are other posters with more experience on that I'd love see a discussion. Lastly, I positioned the simulation to run after your chart was initialized. The graph needed to be initialized, so you could then reference your circle nodes and apply the force layout(or so I believe, like I said, I'm still new to d3).
You can check out an altered JSbin here
This is an older questions with no answers, but I thought I'd chime in if it helps someone else, and maybe stir up discussion on some of the parts I'm not as clear on either. Hopefully that is what you were looking for.
Related
I would like to create a D3 graph like the image in this post.
Force Layout seem to be the best option, but, my nodes has to have different distances, and each node has to have different size.
So, should I insist on Force Layout?
I couldn't find any example similar to my problem, and it's being very hard to understand how to write down some code to implement those different distances and sizes.
The graph I want to make (it's my first question, so I don't have reputation to put an image directly in this post):
https://i.ibb.co/Tk0hHkv/toaskd3.png
Different link distances and different radii can be achieved in d3js.
You can add a radius property to each node, i.e. your nodes should look something like {r:5, id:1, ...}. Then, when its time to create corresponding svg elements, you can do something like
var circles = svg.append("g").selectAll("circle").data(nodes);
circles.enter().append("circle").attr("r", function(d) { return d.r; });
Similarly, you can add a dist property to each of your links. Then add a link force to your simulation like this:
var sim = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links)
.distance(function(l){
return l.dist;
})
.strength(1)
);
For a working example, you can check this codepen I just created. You can always check the official API reference for detailed information.
In my opinion, D3's force layout is a very flexible option for depicting graphs.
I've been working with Mike Bostock's stacked bar chart (here: https://bl.ocks.org/mbostock/4679202).
I've successfully made a number of modifications, but what I'm stuck on is trying to add a y axis with ticks and properly scaled values.
I thought it would simply be done by using this:
var yAxisRight = d3.svg.axis().scale(y2) //define ticks
.orient("right").ticks(5);
However, that results in the values for only ONE set of the stack being used for the entire Y axis. This results in an incorrect scale. The values for the range of all stacks COMBINED needs to be used to determine the range of values I believe.
Is there an easy way to do this that I'm missing? To sum the range of all the columns.
If not, how would I write a function to set the range based on the values in all 4 columns?
Here is a working JSfiddle of what I have now (which is incorrect):
https://jsfiddle.net/1jhm7ths/
If I understood correctly what you tried to achieve, you need to compute your range based on your stacked data and not the original ones. I updated your jsFiddle with the following modification on line 92:
y2.domain([0, d3.max(dataByGroup, function(d) { return d3.sum(d.values, function(v) {return v.value;}); })]); //added
What this does is taking each group, computing the sum of all values, and the taking the max of the sums.
On a side note, I would discourage learning d3 v3 and try to focus on the v4 for longer term support, latest functionalities, modulariy, and a ton of other advantages.
Say I have some data (e.g. book loans and book returns). I want to plot this in a histogram, but I also want it to be 'stacked' in that book loans should be positive (above the x axis) while book returns should be negative (below the x axis). I've not been able to find any documentation on this, and the closest example is this one, but it uses the old v3 of d3 (which has changed a LOT regarding the stack api), and it also doesn't have negative stacks. The key thing here is that originally the book return data point isn't negative (in fact, it can't be, since the histogram api generates the bins), so I can't simply force that one series to appear below the axis in that way.
edit: after struggling with histograms + bins for a while, I finally got an example going at this pen. In my case, I'd like for the 'FixedRemaining' series to appear below the axis while the 'Remaining' series stays above. The data is pretty simple, and the code for drawing the rects is taken mostly from this example. In particular, I suspect this line would have to change:
.attr("y", function(d) { return y(d[1]); })
since it currently just sets the height to be above the axis. I know the domain would also have to be modified to take into account the below-axis part.
Answered by mbostock in this block here: https://bl.ocks.org/mbostock/b5935342c6d21928111928401e2c8608
I need to arrange several plots in a (possibly "incomplete") rectangular array. (By "incomplete" I mean that the last row of the array may have fewer cells than does the first row.)
I implemented an approach for doing this that relies on HTML tables, as shown in this silly example, but it seems to me that this is the kind of thing that should be done entirely within d3.js. Before re-inventing the wheel, is there a built-in way to do this? (I'm hoping to find a way that incorporates refinements, such as axes and tickmarks, and takes care of corner cases, including "incomplete" arrays, as defined above.)
You can do this with the same selections API you used to build the chart itself. So instead of something like d3.select('body').append('svg'), you can do this:
var svg = d3.select("body").selectAll(".pie")
.data(data)
.enter().append("svg")
.attr("class", "pie")
.attr("width", radius * 2)
.attr("height", radius * 2)
Demo using multiple donut charts: http://bl.ocks.org/mbostock/3888852
So here I am with this d3 tree:
http://plnkr.co/edit/HwcZecZtLor51cyNSGSL?p=preview
As you may see, the tree is a bit complex and some of the leave names may be quite long. My main questions are:
Can we change the colour of specific knots (some blue, some red)
and is there any way I can make it so that the text of a leave does not overlap with the link with the previous level? This happens especially when the link is a straight line.
My JS skills are lacking to say the least as these are my baby steps into this world and any help would be greatly appreciated.
Thanks!
The answer to your first question is easy. The colour of a node is set in line 90 in your example --
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
To change the colour, all you need to do is adapt this line. In the function that returns the colour, you have access to the data that you bind to the nodes, so you can use any of the data attributes to decide the colour. Note that only the fill colour is set here and not the outline stroke colour, but you could easily add .style("stroke", ...).
The answer to your second question is much more complex. The functionality you're asking for is not built in to D3, so you would have to do that yourself. Note that this would be quite a complex thing to do because you would have to figure out the positions and bounding boxes of the links and text elements dynamically. Doing this in a general fashion would be a major project.
I suggest that you experiment with the label placements to avoid overlap as much as possible. This will be much easier.