[英]How to write a d3 positive and negative logarithmic scale
题
我有积极和消极的价值观,我想以“对数”的比例绘制它们。
想象一个具有均匀间隔的刻度的刻度,用于以下值:
-1000, -100, -10, -1, 0, 1, 10, 100, 1000
我希望在那里有0,它被定义为-Inf
by logarithms,这进一步复杂化了。
但是,我不认为这个要求是不合理的。 这似乎是一个合理的规模,任何数据科学家可能想要绘制强烈不同的价值观。
你如何在d3中创建这样的比例和轴?
思考
如果您使用类似这样的技术,可以使用2 d3.scaleLog()
s或3个刻度巧妙地执行此操作。
我希望有可能在适合这种简单的方法d3.scalePow()
与.exponent(0.1)
但除非我有我的日志规则混合起来,你不能得到一个.scaleLog()
出来的.scalePow()
(虽然你可以在一些范围内近似它)。
我们不能像这样拥有真正的对数标度,甚至不能像这样的两个对数标度的组合。 我们需要为零值设置一个截止值,这可能是根据您的数据引入错误的地方。 否则,要使这样的比例函数相当简单,只需为负和正调用不同的比例,同时将零值设置为零。
这种尺度组合可能如下所示:
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插值器: a
, b
是域限制, t
是0和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 = ba; 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; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
修改k值
Ø k
,这是怎么回事约k
。 需要设置零值。 k
也会改变图形的形状。 如果显示规则间隔的蜱,增加k
十倍会使最小幅度滴答(除零之外)的幅度增加十倍。 在我的上面的例子中,将k
乘以10可以推动得分为1,超过零刻度。 将它除以10将为0.1刻度创建空间(当然,这需要修改刻度线生成器以显示刻度)。 k
很难解释所以我希望我在那里做得很好。
我将展示尝试更好地沟通它。 让我们使用上面的方法将最小幅度刻度设置为0.1,我们要修改tick函数和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 = ba; 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; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
如果您的域名为+/- 1000,并且您希望最小幅度刻度为1(不包括零),则需要k
为0.0001或0.1 / 1000。
如果域的正负极限不同,那么我们需要两个k
值,一个用于负截止,一个用于正截止。
最后,
k
设置为零的值,在我的示例中,介于-k和+ k之间的t值设置为相同 - 0.理想情况下,数据集中的数值不会很多,但如果是,则可能得到一行如:
每个输入值都不同,但是有很多零输出值,由于我认为是零的边界而产生视觉伪像。 如果在零度范围内只有一个值,就像我上面的例子(但不是上面的图片),我们得到了更好的:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.