如何编写d3正负对数刻度

时间:2018-07-29 21:56:43

标签: javascript d3.js

问题

我有正值和负值,我想按“对数”标度绘制它们。

想象一下一个刻度,刻度均匀地分布于以下值:

-1000, -100, -10, -1, 0, 1, 10, 100, 1000

我想在其中输入0,根据对数定义为-Inf,这使情况进一步复杂化。

但是,我认为这个请求是不合理的。对于任何数据科学家来说,似乎都想对高度不同的价值观进行反驳,这似乎是一个明智的规模。

如何在d3中创建这样的比例尺和轴?

想法

如果使用a technique like this one,则可能用2个d3.scaleLog()或3个音阶来巧妙地做到这一点。

我希望可以有一种简便的方法将其与d3.scalePow()配合在.exponent(0.1)中,但是除非我混淆了日志规则,否则无法获得{{1} }排除在.scaleLog()之外(尽管您可以在某些范围内将其近似估算)。

1 个答案:

答案 0 :(得分:5)

我们不能拥有像这样的真实对数刻度,甚至不能像这样具有两个对数刻度的组合。我们需要为零值设置一个截止点,这可能会根据您的数据而引入错误。否则,要使这样的标度函数非常简单,只需将零位零值设置为零,就可以为负号和正号调用不同的标度。

这种比例的组合可能看起来像:

var positive = d3.scaleLog()
  .domain([1e-6,1000])
  .range([height/2,0])

var negative = d3.scaleLog()
  .domain([-1000,-1e-6])
  .range([height,height/2])

var scale = function(x) {
  if (x > 1e-6) return positive(x);
  else if (x < -1e-6) return negative(x);
  else return height/2; // zero value.
}

还有一个例子:

var width = 500;
var height = 300;

var positive = d3.scaleLog()
  .domain([1e-1,1000])
  .range([height/2,0])
  
var negative = d3.scaleLog()
  .domain([-1000,-1e-1])
  .range([height,height/2])
  
var scale = function(x) {
  if (x > 1e-6) return positive(x);
  else if (x < -1e-6) return negative(x);
  else return height/2; // zero value.
}

var line = d3.line()
  .y(function(d) { return scale(d) })
  .x(function(d,i) { return (i); })
  
var svg = d3.select("body")
  .append("svg")
  .attr("width",width)
  .attr("height",height)
  
var data = d3.range(width).map(function(d) {
  return (d - 250) * 4;
})

svg.append("path")
  .attr("d", line(data) );
path {
  fill: none;
  stroke: steelblue;
  stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>


创建单个秤

以上可能是概念证明。

现在更棘手的部分是制作轴。我们可以为上述两个刻度创建轴,并通过某种手动校正将其保留为零。但是,使用上述示例作为示例,使用我们自己的interpolotor创建标尺会更容易。这为我们提供了一个可以为其创建轴的比例尺。我们的插值器可能看起来像:

// Interpolate an output value:
var interpolator = function(a,b) {
  var y0 = a;
  var y1 = b;
  var yd = b-a;
  var k = 0.0001; 

  var positive = d3.scaleLog()
    .domain([k,1])
    .range([(y0 + y1)/2 ,y1])

  var negative = d3.scaleLog()
    .domain([-1,-k])
    .range([y0, (y1 + y0)/2])

  return function(t) { 
    t = (t - 0.5) * 2; // for an easy range of -1 to 1.
    if (t > k) return positive(t);
    if (t < -1 + k) return y0;
    if (t < -k) return negative(t);
    else return (y0 + y1) /2;
  }
}

然后我们可以将其应用于常规的旧d3线性比例尺:

d3.scaleLinear().interpolate(interpolator)...

这会将域中的数字插值到我们指定的范围内。它在很大程度上采用了上述内容并将其用作d3插值器:ab是域限制,t是0到1之间的规范化域,而{{1} }定义零值。有关以下k的更多信息。

为了获得报价,假设一个不错的舍入域只有十个以十为底的舍入数字,我们可以使用:

k

应用这个,我们得到:

// Set the ticks:
var ticks = [0];
scale.domain().forEach(function(d) {
  while (Math.abs(d) >= 1) {
    ticks.push(d); d /= 10;
  }
})
var margin = {left: 40, top: 10, bottom: 10}
var width = 500;
var height = 300;

var svg = d3.select("body")
  .append("svg")
  .attr("width",width+margin.left)
  .attr("height",height+margin.top+margin.bottom)
  .append("g").attr("transform","translate("+[margin.left,margin.top]+")");

var data = d3.range(width).map(function(d) {
  return (d - 250) * 4;
})

// Interpolate an output value:
var interpolator = function(a,b) {
  var y0 = a;
  var y1 = b;
  var yd = b-a;
  var k = 0.0001; 
 
  var positive = d3.scaleLog()
    .domain([k,1])
	.range([(y0 + y1)/2 ,y1])

  var negative = d3.scaleLog()
    .domain([-1,-k])
	.range([y0, (y1 + y0)/2])
	
  return function(t) { 
	t = (t - 0.5) * 2; // for an easy range of -1 to 1.
	if (t > k) return positive(t);
	if (t < -1 + k) return y0;
	if (t < -k) return negative(t);
	else return (y0 + y1) /2;
  }
}

// Create a scale using it:
var scale = d3.scaleLinear()
  .range([height,0])
  .domain([-1000,1000])
  .interpolate(interpolator);
  
// Set the ticks:
var ticks = [0];
scale.domain().forEach(function(d) {
  while (Math.abs(d) >= 1) {
	ticks.push(d); d /= 10;
  }
})

// Apply the scale:
var line = d3.line()
  .y(function(d) { return scale(d) })
  .x(function(d,i) { return (i); })
  
// Draw a line:
svg.append("path")
  .attr("d", line(data) )
  .attr("class","line");

// Add an axis:
var axis = d3.axisLeft()
  .scale(scale)
  .tickValues(ticks)
  
svg.append("g").call(axis);
.line {
  fill: none;
  stroke: steelblue;
  stroke-width: 2px;
}


修改k值

O <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>,关于k的最新消息。需要设置零值。 k也会更改图形的形状。如果显示规则间隔的刻度,将k增加十倍会使最小幅度刻度(不为零)的幅度增加十倍。在上面的例子中,k乘以10会推动刻度线,其幅度比零刻度高1。将其除以十会为0.1个刻度创建空间(当然,需要修改刻度生成器以显示该刻度)。 k很难解释,所以我希望在那里做得很好。

我将演示尝试更好地进行交流。让我们使用上述方法将最小震级刻度设置为0.1,我们将要修改刻度功能和k:

k
var margin = {left: 40, top: 10, bottom: 10}
var width = 500;
var height = 300;

var svg = d3.select("body")
  .append("svg")
  .attr("width",width+margin.left)
  .attr("height",height+margin.top+margin.bottom)
  .append("g").attr("transform","translate("+[margin.left,margin.top]+")");

var data = d3.range(width).map(function(d) {
  return (d - 250) * 4;
})

// Interpolate an output value:
var interpolator = function(a,b) {
  var y0 = a;
  var y1 = b;
  var yd = b-a;
  var k = 0.00001; 
 
  var positive = d3.scaleLog()
    .domain([k,1])
	.range([(y0 + y1)/2 ,y1])

  var negative = d3.scaleLog()
    .domain([-1,-k])
	.range([y0, (y1 + y0)/2])
	
  return function(t) { 
	t = (t - 0.5) * 2; // for an easy range of -1 to 1.
	if (t > k) {return positive(t)};
	if (t < -1 + k) return y0;
	if (t < -k) return negative(t);
	else return (y0 + y1) /2 //yd;
  }
}

// Create a scale using it:
var scale = d3.scaleLinear()
  .range([height,0])
  .domain([-1000,1000])
  .interpolate(interpolator);
  
// Set the ticks:
var ticks = [0];
scale.domain().forEach(function(d) {
  while (Math.abs(d) >= 0.1) {
	ticks.push(d); d /= 10;
  }
})

// Apply the scale:
var line = d3.line()
  .y(function(d) { return scale(d) })
  .x(function(d,i) { return (i); })
  
// Draw a line:
svg.append("path")
  .attr("d", line(data) )
  .attr("class","line");

// Add an axis:
var axis = d3.axisLeft()
  .scale(scale)
  .tickValues(ticks)
  .ticks(10,".1f")
  
svg.append("g").call(axis);
.line {
  fill: none;
  stroke: steelblue;
  stroke-width: 2px;
}

如果您的域为+/- 1000,并且您希望最小幅度刻度为1(不包括零),则您希望<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>为0.0001或0.1 / 1000。

如果域的正极限和负极限不同,那么我们将需要两个k值,一个为负截止值,一个为正值。

最后,

k设置为零的值,在我的示例中,介于-k和+ k之间的t值设置为相同的-0。理想情况下,数据集中不会有很多值,但是如果是这样,您可能会得到诸如以下的行:

enter image description here

每个输入值都不同,但是有许多零输出值,由于我认为是零的边界,产生了视觉伪像。如果在零位范围内只有一个值,如上面的示例(但没有上面的图片),我们会得到更好的结果:

enter image description here