d3 x axis too short for scatterplot content - javascript

I'm having trouble with my X axis width on a D3 scatterplot - what's happening is that my x-axis isn't filling the space allotted, that the Data happily fill. JSfiddle here: http://jsfiddle.net/u4cGJ/
I've defined my d3 scale's output range thusly:
.range([padding, svgWidth - padding]);
and the output range of points on the scatterplot perfectly lines up with this, but the x-axis doesn't - it stops short of the range of points displayed, - it's doing exactly what I tell it to do, and yet, as the svg extends farther out, the data fill in that space too, leaving a section of data that are visible, but not being placed in context of an axis.
Thanks for any help you can offer!

Likely the problem is that your defined domain doesn't include the full extent of your data. The scale domain/range are used to map ranges of data values so that if your domain is [0,10] and your range is [0,100], you will get values like range(0) = 0, range(1) = 10, range(2) = 20... range(10) = 100 (depending on the kind of scale used).
Since it's just mapping domain to range, if you give it a value that it can't map, you aren't likely to get a value inside of your range's extent. Eg. if you do range(11) you won't get a value between 0 and 100. Since the range value is what your axis and plot are likely using to build the actual svg, the points that fall outside of your domain are going to end up off your plot (or NaN or something).
Try deriving the domain from the data itself. D3 has min, max, and extent functions to make this easy for you. If you have an array of point objects, you can use the accessor as a function to provide the right value to use in the calculation.
Here's an example:
var data = [{x:1, y:1}, {x:2, y:4}, {x:3, y:2}, {x:4, y:1}, {x:5, y:2}];
var xExtent = d3.extent(data, function(d) { return d.x; });
var yExtent = d3.extent(data, function(d) { return d.y; });
See the jsFiddle: http://jsfiddle.net/reblace/c9qw8/
Looking at your fiddle, it looks like there's an error in the logic where you're using the wrong domain for what's actually being plotted. If you get rid of the default min and max stuff, it seems to correct the problem you're describing:
function makeScales(xAxisRepresents, domainMinOverride, domainMaxOverride) {
var domainMin = d3.min(dataset, function datumToValueTransformer(datum) { return datum[xAxisRepresents]; });
var domainMax = d3.max(dataset, function datumToValueTransformer(datum) { return datum[xAxisRepresents]; });

Related

D3.js Brush & Zoom to Rescale Y-Axis By Zoomed Range's Values

What would be the most elegant way to autoscale Y-axis range/domain according to zoome range's values, when modifying this demo?
https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172
There must be some d3-zoom methods that can accomplish accessing zoomed range's values, but haven't yet figured out which those would be...?
I don't know what's the most elegant way (until someone shows it). However, here is my solution.
First, we filter the data according to the zoom:
var filteredData = data.filter(d=>d.date > x.domain()[0] && d.date < x.domain()[1]);
Then, we change the y domain accordingly:
y.domain([0, d3.max(filteredData, function(d) { return d.price; })]);
Here is the demo, zoom in and out to see the y axis changing:
https://bl.ocks.org/anonymous/raw/9f8c2edcb5950cac61f1bc3873886ec5/
This other version avoids NaN when you zoom in too much:
https://bl.ocks.org/anonymous/raw/56fa7fe37596f753db74fdad58ba6725/
PS: I had to move some functions (due to scope issues) in order to access data inside the function zoomed.

How to make axis "smart" and "flexible" so that the starting/end points on axis are sensible in D3 V4?

I have the following Line Graph (CodePen) and as you can see, the Y-Axis isn't very smart. It begins from 0 when it should begin from somewhere near 2200.
My guess was that this would be to do with scales:
// set the ranges
var x = d3.scalePoint().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
But I tried all sorts to get this to behave the "smart" and "flexible" way and couldn't.
How can I make the axis smarter? So if tomorrow I feed the chart a different set of data that starts at 150, the code is smart enough to draw relevant axis values.
If you don't want the Y axis starting at 0, you shouldn't set the lower value in the domain to zero, as you did:
y.domain([0, d3.max(result, function(d) {
return d.consumption;
})]);
If you want the Y axis to start next to the minimum value, you should use:
y.domain([d3.min(result, function(d) {
return d.consumption; })*0.975,
d3.max(result, function(d) {
return d.consumption; })*1.025
]);
Here, the numbers 0.975 and 1.025 are just values to make the axis going a little bit below and after the minimum and maximum values, respectively. You can change them or remove them.
Here is your updated CodePen: http://codepen.io/anon/pen/EgOBvO?editors=0010
PS: while in a bar chart it's highly recommended to always use a zero baseline for the Y axis, there is no problem using a non-zero baseline for the Y axis in a time series (line chart).

Automatic Date Labeling for NVD3 Graphs

I want the date labels to automatically be calculated, appear, and disappear when I change the focus range so that they don't overlap.
I am using a MultiBar graph with a focus chart with the default ordinal scale for nv.models.multiBar(). When I use .ticks(availableWidth / 100 ) on the xAxis, it seems to generate a tick label for EVERY date, or at least a very large number of them:
On nv.models.lineWithFocusChart(), the labels are automatically reduced to fit in a space. This could be because it uses the scale for nv.models.scatter() which is a d3.scale.linear(), but I'm not sure. I tried creating my own scale with the following:
x = d3.scale.ordinal() //as well as x = d3.scale.linear()
.domain(d3.extent(data.map(function(d) {
return d.values.map(function(d,i) {
var X = getX(d,i);
return X.getTime();
});
})))
.range([0, availableWidth]);
I get the following for an ordinal scale:
and no labels for a linear scale. Will this approach work? If so, what am I doing wrong?
On nv.models.multiBarChart(), there is a .reduceXTicks(BOOLEAN) option but this only applies to multiBarChart and there doesn't seem to be an easy way to add it to nv.models.multiBar(). Can I somehow use this?
If there is anything I haven't tried please let me know. I don't want to calculate the labels myself and specify them using .tickValues()
The solution was in fact to use d3.scale.linear() for the x axis. What I tried above didn't work because I was specifying the whole domain of the context chart, when I needed to specify the min and max of the current selection.
In chart(selection) {...}, I put
x = d3.scale.linear()
.range([0, availableWidth]);
and in onBrush(), I put x.domain([new Date(extent[0]), new Date(extent[1])]);, where extent contains the min and max dates of the selection in milliseconds.

Line not visible in line chart when having same value in the domain() - using D3

I am trying to build a line chart using D3 and I am using this example.
The problem that I am facing is that when I am passing a dataset that contains the same value in the linear.domain() fof the y then the line of the line chart gets drawn on top of the horizontal axis and as a result is not visible. Also no numbers are printed on the y axis.
Here is a JSFiddle with my code.
The metrics variable contains 3 objects, three different timestamps with the same value. On the result panel the line is get drawn on top of x axis. If you change one of the three values from 5 to any other number then the line chart gets drawn as normal.
Any ideas on how can I draw the line in the middle of the y axis when the metrics variable contains values of the same value?
Thanks
Instead of line
y.domain(d3.extent(metrics, function(d) { return d.close; }));
use
y.domain([d3.max(metrics, function(d) { return d.close; }) + 0.01,
d3.min(metrics, function(d) { return d.close; }) - 0.01]);
The thing is, if y scale is just segment of length 0, like [5.0, 5.0] (or any other number other that 5.0), d3 is totally confused, and refuses to display anything on y axis, and places 5.0 right at origin.
With new code: the following is obtained for the same case:
]
Here is also link to jsfiddle.

Brushing on ordinal data does not work

I really like this graph and its functionality and it is perfect for what I want/need. The only thing I need to change is I need it to allow ordinal data on the y-axis and I cannot seem to get that to work (I am a beginner).
When I change the y scale from linear to ordinal:
yscale[k] = d3.scale.linear()
.domain(d3.extent(data, function(d) { return +d[k]; }))
.range([h, 0]));
to
yscale[k] = d3.scale.ordinal().rangePoints([h, 0]),
yscale[k].domain(data.map(function(d) { return d[k]; })))
Brushing still shows up and works by itself but it does not filter leaving the selected lines. No lines show up unless I move it to the very top of the axis then, all or mostly all show up. When I stepped through the code with firebug it looked like it was just not getting the lines that were in the brush area but all(?)... and I can't seem to figure out. :(
If anyone could help out with this (especially all the places I have to change and how), I would love to get this working and learn what I am doing wrong :-\
Brushing an ordinal axis returns the pixels, while brushing a quantitative axis returns the domain.
https://github.com/mbostock/d3/wiki/SVG-Controls#wiki-brush_x
The scale is typically defined as a
quantitative scale, in which case the extent is in data space from the
scale's domain; however, it may instead be defined as an ordinal
scale, where the extent is in pixel space from the scale's range
extent.
My guess is that you need to work backwards and translate the pixels to the domain values. I found this question because I'm trying to do the same thing. If I figure it out, I'll let you know.
EDIT: Here's an awesome example to get you started.
http://philau.willbowman.com/2012/digitalInnovation/DevelopmentReferences/LIBS/d3JS/examples/brush/brush-ordinal.html
function brushmove() {
var s = d3.event.target.extent();
symbol.classed("selected", function(d) { return s[0] <= (d = x(d)) && d <= s[1]; });
}
He grabs the selection extent (in pixels), then selects all of the series elements and determines whether they lie within the extent. You can filter elements based on that, and return data keys or what have you to add to your filters.
There is an example of an ordinal scale with brushing here:
http://bl.ocks.org/chrisbrich/4173587
The basic idea is as #gumballhead suggests, you are responsible for projecting the pixel values back onto the input domain. The relevant snippet from the example is:
brushed = function(){var selected = yScale.domain().filter(function(d){return (brush.extent()[0] <= yScale(d)) && (yScale(d) <= brush.extent()[1])});
d3.select(".selected").text(selected.join(","));}

Categories

Resources