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
Related
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.
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.
I'm using DC.JS scatterplots to let users select points of interest. If you use elastic axis you cannot select the highest value point. Look at the DC.JS example (https://dc-js.github.io/dc.js/examples/scatter-brushing.html). You cannot select the highest point in the left or right plot.
In several cases, the highest or lowest point(s) is exactly what people need to be able to select because those are the outliers we care about. If you disable elastic axis and make sure you specify a range that is higher than the max value, you can select the point.
Is there another solution besides setting the axis domain based on current min/max and expanding them little bit? This is sometimes ugly when the minimum=0 and now your domain needs to include some small negative number.
--Nico
Always when I face this issue, I increase the y domain by 5% manually.
For instance:
var balanceDomain = d3.scale.linear().domain([0, s.balanceDimension.top(1)[0].balance + (s.balanceDimension.top(1)[0].balance*0.05)]);
s.amountOverallScore
.width(400)
.height(400)
.x(someDomain)
.y(balanceDomain)
...
Maybe this is not the best solution, but always work for me.
Hope it helps (=.
In my application the values were always positive and I used the following to get correct behavior:
// using reductio on the all_grp to get easy access to filtered min,max,avg,etc.
totalTimeMinValue = all_grp.top(1)[0].value.min;
totalTimeMaxValue = all_grp.top(1)[0].value.max;
// now use it to scale the charts we want
detail1_chart.y(d3.scale.linear().domain([totalTimeMinValue-1, totalTimeMaxValue+1]));
detail3_chart.y(d3.scale.linear().domain([totalTimeMinValue-1, totalTimeMaxValue+1]));
This keeps both charts in sink. An additional benefit was that my rather large dots (symbolsize=15) are no longer being clipped.
Thanks Roger.
I'm trying to reduce the number of points in a DC.js line chart to improve performance. The docs lead me to believe xUnits() is the way to do this:
The coordinate grid chart uses the xUnits function to calculate the number of data projections on x axis such as the number of bars for a bar chart or the number of dots for a line chart.
but xUnits does not even seem to be used:
http://jsfiddle.net/m5tguakf/2/
What am I doing wrong?
The number of points is actually determined by crossfilter - dc.js doesn't do any aggregation on its own, so it has no way to add or reduce the number of points.
That documentation may be misleading - it doesn't alter the shape of the data. xUnits is really just needed for dc.js to know the number of elements it is going to draw. It's used for two purposes:
to determine the width of bars or box-plots
to know whether the x scale is ordinal or quantitative
Could dc.js just count the number of points in the crossfilter group? Perhaps.
Anyway, to get back to your original question: if you want to reduce the number of points drawn, aggregate your data differently in your group. Usually this means creating larger bins which either sum or average the data which fall into that interval.
As a simple example, you can combine every other point in your fiddle by binning by even numbers, like so:
var BINSIZE = 2;
// ...
speedSumGroup = runDimension
.group(function(r) { return Math.floor(r/BINSIZE) * BINSIZE; })
// ...
http://jsfiddle.net/gordonwoodhull/djrhodkj/2/
This causes e.g. both Run 6 and Run 7 to fall in the same bin, because they have the same group key. In a real example, you'd probably want to average them, as shown in the annotated stock example.
I got a data set that where each sample has a size (0-1000) and a value (grade 1-5). I want to visualise the data with circles of different sizes along a line (domain axis), much like:
http://www.nytimes.com/interactive/2013/05/25/sunday-review/corporate-taxes.html?_r=1&
(note that circles even with the same effective taxrate do not overlap)
Example data:
sample 1: size 300 value 3.2
sample 2: size 45 value 3.8
sample 3: size 4400 value 4.0
sample 5: size 233 value 0.2
sample 6: size 4000 value 4.2
How can the data above be visualised using circles on a line (size decides diameter, value decides approximate position on the line) so that circles do not overlap?
I've been looking at D3's packing layout, but from what I can tell it doesn't support this out of the box. Anyone got any ideas on how to approach this?
Oooh, this one was a puzzle...
If you look at the code for the NYTimes graphic, it uses pre-computed coordinates in the data file, so that's not much use.
However, there's an unused variable declaration at the top of the script that hints that the original version used d3.geom.quadtree to lay out the circles. The quadtree isn't actually a layout method; it is used to create a search tree of adjacent nodes, so that when you need to find a node in a given area you don't have to search through the whole set. Example here.
The quadtree can therefore be used to identify which of your datapoints might be overlapping each other on the x-axis. Then you have to figure out how much you need to offset them in order to avoid that overlap. The variable radii complicate both functions...
I've got a test case implemented here:
http://fiddle.jshell.net/6cW9u/5/
The packing algorithm isn't perfect: I always add new circles to the outside of existing circles, without testing whether they could possibly fit closer in, so sometimes you get significant extra whitespace when it is just the far edges of circles bumping into each other. (Run it a few times to get an idea of the possibilities -- note that I've got x-variables distributed as random normal and r-variables distributed as random uniform.) I also got a stack overflow on the recursive methods during one iteration with N=100 -- the random distribution clearly wasn't distributed well enough for the quadtree optimization.
But it's got the basic functionality. Leave a comment here if you can't follow the logic of my code comments.
--ABR
Update
New fiddle here: http://fiddle.jshell.net/6cW9u/8/
After a lot of re-arranging, I got the packing algorithm to search for gaps between existing bubbles. I've got the sort order switched (so that biggest circles get added first) to show off how little circles can get added in the gaps -- although as I mention in the code comments, this reduces the efficiency of the quadtree search.
Also added various decoration and transition so you can clearly see how the circles are being positioned, and set the r-scale to be square root, so the area (not radius) is proportional to the value in the data (which is more realistic, and what the O.P. asked for).
D3's packing layout is not the answer here. It places circles in a spiral fashion around the existing group. Here's me reverse-engineering the algorithm behind packing layout:
I would suggest a force layout-based approach. That way, you can give your nodes force towards a gravitational center, and then let gravity do its thing.
Force layouts (e.g. Clustered Force Layout I) are usually animations, so you'll want to apply a static force layout.
I've wrapped up this approach in an example block, which looks like this: