Creating an asinh (inverse hyperbolic sine) scale

2019-06-15 16:49发布

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.

2条回答
Juvenile、少年°
2楼-- · 2019-06-15 17:10

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>

查看更多
Explosion°爆炸
3楼-- · 2019-06-15 17:11

The best example in the documentation seems to be the interpolate feature of a scale. See https://github.com/d3/d3-scale/blob/master/README.md#continuous-scales

var color = d3.scaleLinear()
    .domain([10, 100])
    .range(["brown", "steelblue"])
    .interpolate(d3.interpolateHcl);

I'm not really a d3 expert so I hope that helps.

查看更多
登录 后发表回答