d3 stacked area graph not working with log scale - javascript

I have been able to put together a stacked area graph.
See this fiddle, which uses dummy data. However, I want to implement the y axis as a logarithmic scale. But when I change the line
var y = d3.scale.linear()
.range([HEIGHT, 0]);
to
var y = d3.scale.log()
.range([HEIGHT, 0]);
my graph stops rendering with errors like
d3.v3.min.js:1 Error: Invalid value for attribute
d="M0,NaNQ400,NaN,500,NaNQ600,NaN,1000,NaNL1000,NaNQ600,NaN,500,NaNQ400,NaN,0,NaNZ".
I am not sure where I am going wrong. Can anyone please help?

Have a look at the documentation on Log Scales:
As log(0) is negative infinity, a log scale must have either an exclusively-positive or exclusively-negative domain; the domain must not include or cross zero.
You need to ensure that your domain does not include the zero value. Setting it to some small value will suffice:
y.domain([1e-6, d3.max(data, function(d) { return d.y0 + d.y; })]);
See the updated JSFiddle for a working example.

Related

Non continuous date domain using scaleTime()

Preface: I've gone through all other stackoverflow questions relating to this problem I could find, which are outdated or recommend using a ordinal scale instead. Including this one: D3 Non-Continuous Dates Domain Gives Gaps on X-Axis
which describes my problem as well.
Goal: I am trying to create a candlestick chart with zoom in/zoom out and panning. Currently the chart is working as intended, minus the x-axis (which contains dates/times of each candlestick).
I want the x axis to function like it does with scaleTime(), where it shows years/months, and when you zoom in it shows the date/time, depending on the time interval. I know timeScale() is continuous, but I need the functionality of the zooming in and out.
Problem: Gaps are included in the x-axis since when I set the domain for the x-scale and x-band, I am using the minimum and maximum dates in the dataset (named 'prices').
Code (d3js v7):
Works but x-scale is not dates, but decimal values from -1 to dates.length:
var xScale = d3.scaleTime()
.domain([-1, dates.length])
.rangeRound([0, w]);
Works x-scale is dates, but includes gaps for weekends and holidays:
var xScale = d3.scaleTime()
.domain([xmin, xmax])
.rangeRound([0, w]);
Doesn't work, x-scale is blank, coordinates are messed up:
var xScale = d3.scaleTime()
.domain(prices.map(r => r.t))
.rangeRound([0, w]);
I calculate the x coordinates for my candle bodies for (map) and [xmin,xmax] as:
.attr('x', (d, i) => xScale(d.t) - xBand.bandwidth())
or for domain([-1, dates.length])
.attr('x', (d, i) => xScale(i) - xBand.bandwidth())
xBand is declared as:
let xBand = d3.scaleBand()
.domain(d3.range(-1, dates.length))
.range([0, w])
.padding(0.2);
Another solution I can think of is to somehow convert the demical values from the domain in the first xScale example to function like the scaleTime() scale using tickFormat(), but I'm not sure how I would go about doing that. I have already converted them from decimal to dates using .tickFormat(prices.map(d => new Date(d.t))); but don't know where to go from there.
Questions: Can you pass an array of values as the domain? Or is it just a min and max value, since domain is defined as boundaries within which your data lies?
Am I passing the array mapping correctly? Is this allowed?
Finally, how can I set my domain to the array of dates I have without including gaps on the chart?

Plotting heatmap with available attributes instead of range D3 React

I'm working on a heatmap which basically plots the graph between taxIDs and KeywordNames from an external JSON. While I'm able to plot the values I see many blank spaces on the graph and clueless how I can plot them with the available data.
Here's the link to codesandbox: https://codesandbox.io/s/40mnzk9xv4
On the X-Axis I'm plotting the TaxIDs which are being calculated within the given range. I did try using the function rangeBands() but I get an error everytime.
Its the similar case with Y-Axis where I'am plotting the keywordIDs which are also being calculated within a range. I'm trying to print all the KeywordNames on Y axis and all taxIDs on the X-Axis and plot their corresponding spectracount on graph.
Please help me where have I gone wrong.
The output I'm looking for is something similar to this: https://bl.ocks.org/Bl3f/cdb5ad854b376765fa99
Thank you.
Some things to help you get you one your way:
First, your scales should use scaleBand(), not scaleLinear(), as they have discrete domains (i.e. categories of something, rather than continuous)
Second, your scale domains is taking every value of taxId and keywordName in your data as a possible value. But some values are repeated more than once. You need to be filtering them so you only have unique values. So your scale code should be:
const xValues = d3.set(data.map(d => d.taxId)).values();
const yValues = d3.set(data.map(d => d.keywordName)).values();
const xScale = d3.scaleBand()
.range([0, width])
.domain(xValues); //X-Axis
const yScale = d3.scaleBand()
.range([0, height])
.domain(yValues); //Y-Axis
Finally, your code that places the heatmap tiles needs to be calling the scale functions so it works out the position of each rect correctly:
chart.selectAll('g')
.data(data).enter().append('g')
.append('rect')
.attr('x', d => { return xScale(d.taxId) })
.attr('y', d => { return yScale(d.keywordName) })
That should get you most of the way there. You'll want to also reduce cellSize and remove the tickFormat calls on the axes as they are trying to convert your text labels to numbers.

How to set domain to avoid overlapping dots and axis in d3 plot?

I'm trying to create a simple scatter plot in d3 (similar to this one from matplotlib):
I use extent() to set the scale's input domain range.
xScale.domain(d3.extent(xvalues));
Using this approach results in some dots overlapping axises in d3 plot:
How to avoid axis overlapping and make a margin similar to the matplotlib's plot?
Input values vary, so simple increment / decrement of the extent() output doesn't look like a general solution.
In general, the best way of handling this is to call the scale's .nice() function, which will round the ends of the domain of the scale to nice values. In your particular case, this doesn't work, as the values are "nice" already.
In this case I would compute the extent of the domain and extend it by a fraction of that. For example:
var padding = (xScale.domain()[1] - xScale.domain()[0]) / 10;
xScale.domain([xScale.domain()[0] - padding, xScale.domain()[1] + padding]).nice();
In your matplotlib image, the dots are not overlapping and the x scale has negative value.
In d3:
var xScale = d3.scale.linear()
.domain([
d3.min(data, function(d) {
return d.val;
})-10, //so the domain is wider enough for the zero value
d3.max(data, function(d) {
return d.val;
}),
])
.range([height , 0])

D3 - how do I remove comma separator from my xAxis?

I have a data set that uses straightforward dates for the x-axis. However, I keep getting a comma separator in the thousands place (which would lend some confusion to my graph's intent.) I can't seem to figure out how to word my .format line properly.
Original code:
var x = d3.scale.linear()
.range([0, width]);
My .formatted scale:
var x = d3.scale.linear()
.range([0, width])
.format(04d);
The second option -- you guessed it -- makes my whole graph disappear. Any takers?
Put the 04d in quotes. It should be a string.
var x = d3.scale.linear()
.range([0, width])
.format("04d");
Read more about formatting in the d3 docs.
By the way, your chart probably disappeared because without the quotes, 04d is a syntax error. Your browser's development tools should show you such an error, so make sure you check them. It's really handy to have them open while developing.

D3.js stacked bar chart

I've spent enough time modifying the stacked bar chart sample that it would be wise to get a second pair of eyes to look at what I'm doing wrong. The modified sample is in a js fiddle.
My main changes are these:
1: On line 10 reversed the array for range
y = d3.scale.linear().range([height, 0])
2: On lines 17 and 22 added axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(format);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
3: Line 62 changed the addition of y0 and y (this is the change that might be creating problem)
.attr("y", function (d) { return y(d.y) - y(d.y0); })
As you can see bars are going way above the specified height.
If someone could tell me what I'm doing wrong I'd be very thankful. I spent too much time looking just by myself. I do think I'm very close as chart is displaying other than the height problem.
There is one more change, which you haven't mentioned: that you the chart containing g is now translated to the top-left point instead of the bottom-left point, as was in the original plot.
Changing the following two lines around the line you mentioned will solve the problem:
.attr("y", function (d) { return y(d.y + d.y0); })
.attr("height", function (d) { return y(d.y0) - y(d.y +d.y0); })
Demo
This is how I generally do these calculations with an inverted scale such as y = d3.scale.linear().range([height, 0]):
Here, the point S(y + y0) will be the value of the point closer to the top, or with the lower y value, and the height of the bar will be = S(y0) - S(y + y0) or = abs(S(y0+y) - S(y0)), whichever you find more palatable.
However, to be honest, there is plenty of trial and error involved before I actually draw this down. Then I do more trial and error to find the solution and convince myself that this drawing helped.
So ... your mileage might vary. :)

Categories