D3: Are "parabolic" scales possible?
I am working on a chart for which each observation has a value between [-100,100] and I want to plot each point of the position on a scale. The challenge is that the vast majority of points have values ββin one region of the scale (the distribution is essentially Gaussian with a mean of 0).
In the past, when I needed to plot something like a Zipf probability density distribution, I used log scales to propagate points in a congested area. Now my situation is similar, except that I have two distributions for which I need to decompose points (positive scale from [0, max] and mirrored negative scale from [0, min]).
I know I can create one scale for positive values ββand one for negative values, but I'm wondering if this layout can be achieved with only one scale. It seems that something like a parabolic scale might help here (if it exists). Is it possible to achieve something similar in D3?
source to share
Before explaining my proposed solution, some considerations regarding the comments in this question: can't use log scale in your situation. It's a simple math principle: Log (0) minus infinity. In fact, this is explicitly stated in the docs :
Since log (0) = -β, the log scale area must be strictly positive or strictly negative; the domain must not include or cross zero.
Having said that, let go of the proposed solution.
You can create your own scale (it's not that hard). However, here I will be using the interpolate function based on this excellent answer (not a duplicate though, because you want the opposite) and this code from Mike Bostock .
Using a linear scale, we set up an interpolator:
var xScale = d3.scaleLinear()
.domain([-100, 100])
.interpolate(easeInterpolate(d3.easeQuadInOut));
Then we use relaxation in the function easeInterpolate
:
function easeInterpolate(ease) {
return function(a, b) {
var i = d3.interpolate(a, b);
return function(t) {
return i(ease(t));
};
};
}
Here I am using d3.easeQuadInOut
which I think works for you, but you can change this for another, or even create your own.
Take a look at this demo. I create 50 circles evenly spaced from -100 to +100 (-100, -96, -92, -88 ... to +100). You can see that they are being pushed away from the center. If you use this scale with your data, you will avoid overflowing data points around zero:
var data = d3.range(51).map(function(d) {
return -100 + (d * 4)
});
var svg = d3.select("body")
.append("svg")
.attr("width", 600)
.attr("height", 100);
var xScale = d3.scaleLinear()
.domain([-100, 100])
.range([20, 580])
.interpolate(easeInterpolate(d3.easeQuadInOut));
svg.append("g")
.attr("transform", "translate(0,70)")
.call(d3.axisBottom(xScale));
svg.selectAll("galileo")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d)
})
.attr("cy", 50)
.attr("r", 4)
.attr("fill", "teal")
function easeInterpolate(ease) {
return function(a, b) {
var i = d3.interpolate(a, b);
return function(t) {
return i(ease(t));
};
};
}
<script src="https://d3js.org/d3.v4.min.js"></script>
If you ask what the last tick is not 80100
. It is just a sign 80
overlapping the mark 100
(the same happens with -80
and -100
).
Also, it's worth noting that there is nothing wrong with using transformed scales, and that even if it deforms or distorts the diagram, it is perfect and does not lead to misinterpretations as long as you communicate the transformation to users .
source to share