Recharts is inconstantly displaying x-axis ticks, is it possible to to make consistent? - recharts

We've started using recharts as a replacement for a less developed charting library and have run into an issue with axis ticks. Essentially we need to be able to have an arbitrary number of data points on the x Axis, and want to show as many x-axis ticks as possible while not overlapping.
Now Recharts is able to automatically hide and show any number of ticks that will fit on the axis without overlapping. The issue is is that it does it unevenly, and I can't seem to find out a way to make it even. Below is an example of what is happening.
Recharts has decided that Feb 2022 does not fit, and has removed it, but this has left the axis uneven, ideally we would want it to remove every other tick so that there is even spacing.
Is this at all possible without somehow manually measuring and defining ticks?
Here is the x-axis configuration
<XAxis
dataKey="category"
axisLine={false}
tickLine={false}
padding={{ left: 64, right: 64 }}
fontSize="0.75rem"
style={labelStyles}
domain={
data.length
? [data[0].category, data[data.length - 1].category]
: undefined
}
scale="time"
type="number"
tickFormatter={(value: number) =>
DateTime.fromMillis(value * 1000).toFormat(
dateFormat || DEFAULT_DATE_FORMAT
)
}
/>

Related

Is it possible to change the x scale of a rowchart to sqrt?

I want to change the scale of the x axis of a row chart because some values are close to 0 but others are over 100000. I have tried this:
chart.x([d3.scaleSqrt().domain([0, 75000]).clamp(true)])
but it doesn't work.
However, this same method does work with a barchart. Any suggestions of how I can solve this?
EDIT: What I meant by 'it doesn't work' is that it shows the rowchart but the x axis is what comes by default, so there is no rescaling of the axis.
First off, you've got an extra set of brackets - that should be
chart.x(d3.scaleSqrt().domain([0, 75000]).clamp(true))
Since you don't say what didn't work, I will guess that you got a "squished" chart.
Here is what I got when I first tried this:
Unlike other dc.js charts, the row chart will not set the range of the X scale when the scale is passed in by the user.
For other charts, dc.js will initialize the range based on the "effective width" - the width of the chart minus the left and right margins.
We can do the same:
chart.x(d3.scaleSqrt()
.domain([0, 75000])
.range([0,chart.effectiveWidth()])
.clamp(true)])
My example now looks like

Adding specific ticks on D3 axis w/ brush

I need help adding specific ticks to an axis that is also being focused with a brush. I found a similar question/answer for a static axis (without a brush) here:
Adding a specific tick to a D3.js axis
It's along the lines of what I'm trying to do. My chart is more similar to this example though :
http://blockbuilder.org/pergamonster/419aef09c21ffe8dd1dac971016468d6
Say you wanted to have a 'July 4' tick on both the full axis and the focus axis at all times. I've tried the axis.ticks().push(...arrayOfNumbers) from the above question/answer in my code, but nothing happens. If I do axis.tickValues(arrayOfNumbers) it puts ONLY those numbers as ticks, but I would like to have the auto scaling number PLUS my specific numbers on the axis for the focus area.
Any help would be appreciated.
P.S. Though my chart is similar the about blockBuilder example, I'm using a linearScale not a timeScale if that matters. Thanks
draw a second x-axis where you only specify the extra ticks.
svg.append('g')
.attr('class', 'x-axis-extra')
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(xScale).tickValues([2.78,3.14]));
You need a bit of CSS to hide the extra path
.x-axis-extra path { opacity: 0; }

dc.js - Yearly average values for previous years + monthly for current year

I'm trying to find a way in dc-js to draw a chart that would display monthly values for current year but yearly averages for previous years. Is this possible provided that the date axis is dynamic? I was thinking of grouping records by year before passing them to dc.js but the chart will just leave blank gaps on the x axis.
I would like to achieve the similar result to the attached excel screenshot but on a web-based interactive solution.
You will need to use an ordinal domain for this purpose.
If you use a time-based scale, then the bars will always be positioned based on a linear mapping of dates to X coordinates. This explains the gaps you are seeing.
Instead, you can define the ordinal domain explicitly:
var domain = ["2015 Average", "2016 Average", "2017 Average", "201801", "201802", ...];
chart
.x(d3.scaleOrdinal().domain(domain))
.xUnits(dc.units.ordinal)
You'll also need to change your group key function accordingly:
var group = dimension.group(function(d) {
return d.date.getYear() < 2018 ?
d.date.getYear() + " Average" :
d3.timeFormat('%Y%m')(d.date);
});
I'm not sure how you intend to calculate the averages, so I won't get into that.
Unfortunately (or fortunately, depending on your purposes) this will change the selection behavior so that instead of a continuous X brush that you can drag, you'll have to click on individual bars in order to select them. There are some ways around that, but they are kind of tricky, so lmk if this is a problem for you.

NVD3.js multiChart x-axis labels is aligned to multiple lines, but not multiple bars

This question relates to NVD3.js multiChart x-axis labels is aligned to lines, but not bars
I am using NVD3.js multiChart to show multiple lines and multiple bars in the chart. All is working fine, but the x-axis labels is aligned only to the line points, not bars. I want to correctly align labels directly below the bars as it should. But I get this:
As you can see - x-axis (example, 2014-Feb) is not aligned to Bars.
1) How to align x-axis labels to bars and lines at the same time?
2) I need this solution for NVD3.js or how to properly integrate.
I made jsFiddle: http://jsfiddle.net/n2hfN/28/
Thanks!
The problem here is that nv.models.multiChart uses a linear scale for its x-axis, and then when it draws the bars it calls nv.models.multiBar, which uses an ordinal scale with .rangeBands().
You can follow this mess through the source code:
First lets look at multiChart.js
HERE is where it sets the x-scale to be a linear scale.
HERE it calls the nv.models.multiBar model to create the bars.
If we jump over to have a look at multiBar.js
HERE it creates an ordinal scale, and HERE it sets the range of the scale using .rangeBands()
The result is that the ordinal scale used for placing the bars, and the linear scale used for the chart's axis do not align. Here's what the two scales look like on their own if plotted on an axis:
The solution would be to force the chart to render the line graphs and the x-axis in terms of the ordinal scale used by the bars. This would work in your case because the bars and the lines all use the same data for the x-axis. This is very simple to do if you are making your own chart and not relying on nvd3, as I showed in my answer to your previous question HERE. This is extraordinarily complicated to do if you're trying to work within nvd3, and many others have tried and failed to switch out the default scales used by nvd3 charts. Have a look at this issue on the nvd3 github page that has been open since January, 2013 for example.
I've tried a number of approaches myself to reuse the bars' ordinal scale, but with little success. If you want to poke around and try to brute-force it yourself, I can tell you that from my experiments I came closest when using chart.bars1.xScale().copy() to make a copy of the bars' scale, and set its domain and rangeBands. Unfortunately, since the chart's width is computed at render time, and I can't seem to create a hook into the chart.update function, it is impossible to set the rangeBands' extent to the correct values.
In short, if you can't live with the labels being offset, you're probably going to need to code up your own chart without nvd3, or else find a different type of layout for your visualization.
After playing around with the NVD3 v1.7.1 source code with the immensely helpful guidance offered by jshanley's answer, I think I've managed to come up with an answer (perhaps more of a kludge than a good solution).
What I did was to have the x-axis labels align with the bars, and have the line data points align with the bars.
1.1. To align the x-axis label, I shifted the x-axis to the right so that the first label appears underneath the middle of the first bar. I then shifted the last label to the left, so that it appears underneath the middle of the last bar. See code here. The amount to shift by is computed at drawing time using .rangeBand() and saved in a rbcOffset variable (I had to modify multiBar.js for this to work).
1.2. To align the line data points with the bars, a similar shift is also required. Luckily, this part is easy because scatter.js (which is used by line chart) comes with a padData boolean variable that does what we want already. So basically, I just set padData to true and the lines shift to align with the bars, see here.
In order to properly integrate with NVD3 and make everything look good, some additional changes are required. I've forked NVD3 on GitHub so you can see the complete solution there. Of course, contributions are welcome.
I use last solution and it runs. So, you can specify
lines1.padData(true)
in order to align lines too.
Same here, I used the last solution,it worked for me as well. Find the following line in multiChart.js
if(dataLines1.length){
lines1.scatter.padData(true); // add this code to make the line in sync with the bar
d3.transition(lines1Wrap).call(lines1);
}
I encountered the same problem and fixed it with below code:
at lines 7832 and 7878 replace
.attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
with :
var w = (x.rangeBand() / (stacked && !data[j].nonStackable ? 1 : data.length));
var sectionWidth = availableWidth/(bars.enter()[0].length - 1);
if(bars.enter().length == 2)
return 'translate(' + ((i-1)*w + i*w + (i*(sectionWidth - 2*w))) + ',0)';
else
return 'translate(' + ((i-0.5)*w + i*(sectionWidth - w)) + ',0)';
The first case handles multiple bars case while the second one handles single bar case.
lawry's solution works. Also if using interactive guidelines, you need to shift the interactive line to match the new scale. Modify:
if(useInteractiveGuideline){
interactiveLayer
.width(availableWidth)
.height(availableHeight)
.margin({left:margin.left, top:margin.top})
.svgContainer(container)
.xScale(x);
wrap.select(".nv-interactive").call(interactiveLayer);
//ADD THIS LINE
wrap.select(".nv-interactiveGuideLine")
.attr('transform', 'translate(' + rbcOffset +', ' + 0 + ')' +
'scale(' + ((availableWidth - rbcOffset*2)/availableWidth) + ', 1)');
}
in multiChart.js.

Vary font size in tick labels of time axis

I'm plotting time series data based on a regular time axis. For now, I use the default "multi-scale" tick format, so I see dates combined with AM/PM hours on the X axis.
Now: how can I show the dates in a bigger font size, to make them stand out from the hour ticks? All ticks seem to have the same "tick" CSS class, so I can't differentiate between them using CSS...
Any suggestion is appreciated.
D3 doesn't provide anything to distinguish between the different types of ticks generated, but you can check the condition used in generating the labels in setting the styles as well. The code would look something like this.
d3.selectAll(".tick > text")
.style("font-size", function(d) { return d.getHours() == 0 ? 32 : 24; });

Resources