Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating an asinh (inverse hyperbolic sine) scale in d3.js v4

This would be a replacement for a log scale so that it can deal with negative numbers. Haven't seen many examples of custom scales though I've been trying to use d3's logarithmic scale's source as a starting point.

like image 353
user7144613 Avatar asked Nov 11 '16 05:11

user7144613


1 Answers

As far as I know there are no ways to make custom scales in D3 (at least not in the sense you are looking for). All D3 scales scale in two steps:

  1. using the domain, deinterpolate the input given a deinterpolation function
  2. using the range, interpolate the intermediate result from step 1 to obtain the output

I believe your ideal answer would basically answer the question, "how do I set a D3 scale's deinterpolation function to a custom function?", and I don't think this is currently possible.

However, you can set the interpolate function. This example from Mike Bostock shows how to set the interpolation using one of D3's built in ease functions: http://bl.ocks.org/mbostock/56ea94205411ee9e4dbec3742f7ad08c

That example kinda has a "fisheye lens" effect, which is probably the opposite of what you want. You can use the polynomial easing function, d3.easePolyInOut, with an exponent less than one to get something closer to log scaling (see my code snippet). Unfortunately, there is no "logInOut" or "asinhInOut", so if you need a steeper rolloff (than polynomial), then you'll have to write your own easing/interpolation function.

var data = Array.from(Array(21), (_,i)=>{return 10*(i-10)})

var svg = d3.select("svg"),
    margin = {top: 50, right: 20, bottom: 5, left: 20},
    width = svg.attr("width") - margin.left - margin.right,
    height = svg.attr("height") - margin.top - margin.bottom,
    g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var polyexp = 0.25

var x = d3.scaleLinear()
    .domain([-100,100])
    .range([0, width])
    .interpolate(easeInterpolate(d3.easePolyInOut.exponent(polyexp)));

g.append("g")
    .attr("class", "axis axis--x")
    .call(d3.axisBottom(x));

g.selectAll("circle").data(data).enter().append("circle")
  .attr("cx", (d) => x(d))
  .attr("cy", -10)
  .attr("r", 3)
  .attr("fill", "steelblue")

function easeInterpolate(ease) {
  return function(a, b) {
    var i = d3.interpolate(a, b);
    return function(t) {
      return i(ease(t));
    };
  };
}
.axis text {
  font: 10px sans-serif;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.3.0/d3.js"></script>
<svg width="600" height="100"></svg>
like image 132
Steve Ladavich Avatar answered Nov 09 '22 00:11

Steve Ladavich