Using Custom Scale function for ReCharts or d3-scale - d3.js

I want to display Weibull analysis plot using Recharts. For Weibull analysis, formula for Y-Axis Scale is as:
ln(-ln(1-p))
where p=(i-0.3)/(n+0.4) and i is the rank of the observation. This scale is chosen in order to linearize the resulting plot for Weibull data. You can have a look at the Y Axis log scale as shown in the example graph below.
I tried standard scaleLog() but I need to customize the log function.
const y_scale = scaleLog().range([0, 100]);
Also, one of the recharts issue shows an option of function while defining scale, but that also does not seem to work.
https://github.com/recharts/recharts/issues/305
Would be great if someone could help with the way to implement custom scale functions.
Example of Weibull Graph:

Recharts seems to have added exactly what you were searching on the documentation of YAxis
https://recharts.org/en-US/api/YAxis#scale
import { scaleLog } from 'd3-scale';
const scale = scaleLog().base(Math.E);
...
<YAxis scale={scale} />
...

Related

Use dates as numbers, but format them as dates in UI

Let's take this example.
https://d3fc.io/examples/series-canvas-candlestick/
It defines X scale as a time scale.
const xScale = d3
.scaleTime()
.domain(fc.extentDate().accessors([d => d.date])(data));
To prevent multiple tick to date conversion in my zoom handler, I'd like to always work with number, but keep UI showing these numbers as dates. So, I change the code above to linear scale.
const xScale = d3
.scaleLinear()
.domain(fc.extentLinear().accessors([d => d.date.getTime()])(data));
Everything works fine, but now I have an X scale showing numbers instead of dates. The question is, how to add mapper or formatter that would show these numbers as dates in UI? I see some examples of d3.tickFormat, but not sure how to apply it in d3fc.
https://observablehq.com/#d3/axis-ticks
You supply your scales to the D3FC cartesian chart, which then couples these with the axes that it creates for you.
As mentioned in the documentation the chart exposes the properties of the X and Y axes with an x and y prefix. So, if you want to change the tick format for the X axis, which you would typically do via the tickFormat property, you instead do the following:
var chart = fc.chartCartesian(
d3.scaleLinear(),
d3.scaleLinear()
)
.xTickFormat(/* ... formatter goes here */);

Retrieving dc chart axis generators (from DOM)

Say, I have loaded a dc bar chart:
I want to retrieve its axes generators. Below, an ax is shown of length 16:
My first thought would be simply iterating over each j-th ax element sim.barCharts[i].getElementsByClassName('bar')[j] after which I calculate its value by subtraction of its attributes height and y: value = height - y.
However, couldn't this also be solved by using the dc.js api is some way, or even the D3 api, since they seem closely related?
The data is already there; it was generated by dc. It would seem cumbersome to approach this problem from a DOM perspective, without using the facilities of the library who generated the data.
Context
I need to retrieve the axis of a chart in order to programmatically dispatch a brush event on it.
function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
chart.select(".area").attr("d", area);
chart.select(".x.axis").call(xAxis);
}
The snippet above is the event handler of the brush event. As can be seen, the axis generator xAxis is needed to create the brushed chart values.

Multi line ordinal chart in dc.js

I am stuck on a problem here. Could be simple though but i am having a tough time figuring it out. I want to show multiple lines on a dc composite chart.
My data is like this:
{ Name: Mike, mark1: 26.9, mark2: 62.3 },
{ Name: John, mark1: 23.5, mark2: 60.3 },
{ Name: Firen, mark1: 24.3, mark2: 62.5 }
I need the name plotted against X axis and mark1 and mark2 plotted as lines against the Y axis. I found a fiddle here which uses a linear scale to achieve the same result. http://jsfiddle.net/anmolkoul/mzx6mnru/3/
But it uses a linear scale as the base dimension is numerical. My base dimension is a string and hence not working with the same code. I figured it is due to the scale definition that i am using. Here is the fiddle that i need help with: http://jsfiddle.net/anmolkoul/pjLoh1az/1/
I have currently defined my x axis as
.x(d3.scale.ordinal().domain(nameDimension))
.xUnits(dc.units.ordinal)
I think this is where it is going wrong. I have two supplementary questions as well:
Once this is done, how to assign different colors to the lines ( it should reflect in the legend as well)
I was taking a look at the dc.js series chart, what does this line of code do?
runDimension = ndx.dimension(function(d) {return [+d.Expt, +d.Run]; });
Does it pivot the two dimensions? or is it just a quicker way of creating two crossfilter dimensions.
Thank you for the help!`
You can get the ordinal values with :
nameDimension.top(Infinity).map(function(d) {return d.Name}))
which returns ["Mike", "John", "Firen"] , then use it for the ordinal domain.
But it's not necessary, it's calculated automatically :
.x(d3.scale.ordinal())
.xUnits(dc.units.ordinal)
For colors, you can use :
dc.lineChart(lineChart1).group(mark1Group,"Mark 1").colors("#FF0000")
Here is a fiddle with those modifications : http://jsfiddle.net/1exo25u9/

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

I am using NVD3.js multiChart to show multiple lines and 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:
With red lines I marked where the labels should be.
I made jsFiddle: http://jsfiddle.net/n2hfN/
Thanks!
As #Miichi mentioned, this is a bug in nvd3...
I'm surprised that they have a TODO to "figure out why the value appears to be shifted" because it's pretty obvious... The bars use an ordinal scale with .rangeBands() and the line uses a linear scale, and the two scales are never made to relate to one another, except in that they share the same endpoints.
One solution would be to take the ordinal scale from the bars, and simply adjust it by half of the bar width to make the line's x-scale. That would put the line points in the center of the bars. I imagine that something similar is done in the nv.models.linePlusBarChart that #LarsKotthoff mentioned.
Basically, your line's x-scale would look something like this:
var xScaleLine = function(d) {
var offset = xScaleBars.rangeBand() / 2;
return xScaleBars(d) + offset;
};
...where xScaleBars is the x-scale used for the bar portion of the chart.
By combing through the source code for nvd3, it seems that this scale is accessible as chart.bars1.scale().
Maybe someday the authors of nvd3 will decide that their kludge of a library deserves some documentation. For now, I can show you the kind of thing that would solve the problem, by making a custom chart, and showing how the two scales would relate.
First, I'll use your data, but separate the line and bar data into two arrays:
var barData = [
{"x":0,"y":6500},
{"x":1,"y":8600},
{"x":2,"y":17200},
{"x":3,"y":15597},
{"x":4,"y":8600},
{"x":5,"y":814}
];
var lineData = [
{"x":0,"y":2},
{"x":1,"y":2},
{"x":2,"y":4},
{"x":3,"y":6},
{"x":4,"y":2},
{"x":5,"y":5}
];
Then set up the scales for the bars. For the x-scale, I'll use an ordinal scale and rangeRoundBands with the default group spacing for nvd3's multiBar which is 0.1. For the y-scale I'll use a regular linear scale, using .nice() so that the scale doesn't end on an awkward value as it does by default in nvd3. Having some space above the largest value gives you some context, which is "nice" to have when trying to interpret a chart.
var xScaleBars = d3.scale.ordinal()
.domain(d3.range(barData.length))
.rangeRoundBands([0, w], 0.1);
var yScaleBars = d3.scale.linear()
.domain([0, d3.max(barData, function(d) {return d.y;})])
.range([h, 0])
.nice(10);
Now here's the important part. For the line's x-scale, don't make a separate scale, but just make it a function of the bars' x-scale:
var xScaleLine = function(d) {
var offset = xScaleBars.rangeBand() / 2;
return xScaleBars(d) + offset;
};
Here's the complete example as a JSBin. I've tried to document the major sections with comments so it's easy to follow the overall logic of it. If you can figure out from the nvd3 source code exactly what each of the elements of the multiChart are called and how to set the individual scales of the constituent parts, then you might be able to just plug in the new scale.
My feeling on it is that you need to have a pretty good handle on how d3 works to do anything useful with nvd3, and if you want to customize it, you're probably better off just rolling your own chart. That way you have complete knowledge and control of what the element classes and variable names of the parts of your chart are, and can do whatever you want with them. If nvd3 ever gets proper documentation, maybe this will become a simple fix. Good luck, and I hope this at least helps you get started.

d3 autospace overlapping tick labels

Is there a way in d3 to not draw overlapping tick labels? For example, if I have a bar chart, but the bars are only 5 pixels wide and the labels are 10 pixels wide, I end up with a cluttered mess. I'm currently working on an implementation to only draw the labels when they do not overlap. I can't find any existing way to do that, but wasn't sure if anyone else had dealt with this problem.
There is no way of doing this automatically in D3. You can set the number of ticks or the tick values explicitly (see the documentation), but you'll have to figure out the respective numbers/values yourself. Another option would be to rotate the labels such that there is less chance of them overlapping.
Alternatively, like suggested in the other answer, you could try using a force layout to place the labels. To clarify, you would use the force layout on the labels only -- this is completely independent of the type of chart. I have done this in this example, which is slightly more relevant than the one linked in the other answer.
Note that if you go with the force layout solution, you don't have to animate the position of the labels. You could simply compute the force layout until it converges and then plot the labels.
I've had a similar problem with multiple (sub-)axis, where the last tick overlaps my vertical axis in some situations (depending on the screen width), so I've just wrote a little function that compares the position of the end of the text label with the position of the next axis. This code is very specific to my use case, but could adapted easily to your needs:
var $svg = $('#svg');
// get the last tick of each of my sub-axis
$('.tick-axis').find('.tick:last-of-type').each(function() {
// get position of the end of this text field
var endOfTextField = $(this).offset().left + $(this).find('text').width();
// get the next vertical axis
var $nextAxis = $('line[data-axis="' + $(this).closest('.tick-axis').attr('data-axis') + '"]');
// there is no axis on the very right, so just use the svg width
var positionOfAxis = ($nextAxis.length > 0) ? $nextAxis.offset().left : $svg.offset().left + $svg.width();
// hide the ugly ones!
if (endOfTextField > positionOfAxis) {
$(this).attr('class', 'tick hide');
}
});
The ticks with color: aqua are the hidden ones:

Resources