【问题标题】:Calculating the length of a segment in a logarithmic scale以对数刻度计算段的长度
【发布时间】:2018-04-21 16:38:26
【问题描述】:

我想为一系列事件计算一行的长度。

我正在使用以下代码进行此操作。

var maxLineLength = 20;
var lineLen = function(x, max) {
    return maxLineLength * (x / max);
}
var events = [0.1, 1, 5, 20, 50];
var max = Math.max.apply(null, events);
events.map(function (x) {
    console.log(lineLen(x, max));
});

这可行,但我使用线性缩放,而我想使用对数,因为我不希望小事件在大事件出现时变得太小。

我修改了 lineLen 函数,如下所示,但是 - 显然 - 它不适用于等于 1 的事件,因为 1 的日志为零。我想显示事件等于一(适当地缩放)而不是使它们变为零。我还需要正数来保持正数(0.1 变成负数)

我应该如何修改 lineLen 以使用对数刻度?

var maxLineLength = 20;
var lineLen = function(x, max) {
   return maxLineLength * (Math.log(x) / Math.log(max));
}
var events = [0.1, 1, 5, 20, 50];
var max = Math.max.apply(null, events);
events.map(function (x) {
    console.log(lineLen(x, max));
});

【问题讨论】:

  • 这是您唯一需要的应急措施吗?如果等于 1,则保留为 1?
  • 我只需要这个,但也应该缩放一个
  • 我想澄清一下,我不想留下一个...一个,但我也想扩展它

标签: javascript logarithm


【解决方案1】:

您可以使用log(x+1) 而不是log(x),这不会对值进行太多更改,并且对于较小的数字保持比率。

var maxLineLength = 20;
var lineLen = (x, max) => maxLineLength * Math.log(x+1)/Math.log(max+1);
var events = [ 0.1, 1, 5, 20, 50];
var visualizer = function(events){
    var max = Math.max.apply(null, events);
    return events.reduce((y, x) => {
         y.push(lineLen(x, max));
         return y;
    }, []);
};

console.log(visualizer(events));

【讨论】:

  • 使用 log(x+1) 的想法很有趣,但我不完全理解为什么要乘以 (max+min)/(max-min)。对于等于最大值的元素,我还希望 lineLen 等于 maxLineLength。
  • 这只是一个比例因子。您可以选择省略它。但是当最大值和最小值更接近时,它会有所帮助。如果它们相距很远,那么它什么也不做。当值彼此接近时,它会缩放更接近的项之间的差异。试试[1,2,3,4]的示例
  • @cdarwin,根据您的要求编辑了答案。这只是x+1 而不是xmax+1 而不是max
【解决方案2】:

只要您的事件 > 0,您就可以通过缩放事件来避免移位,使最小值大于 1。除了您已经拥有的最大行长度之外,还可以根据最小行长度来计算标量。

// generates an array of line lengths between minLineLength and maxLineLength
// assumes events contains only values > 0 and 0 < minLineLength < maxLineLength
function generateLineLengths(events, minLineLength, maxLineLength) {
  var min = Math.min.apply(null, events);
  var max = Math.max.apply(null, events);

  //calculate scalar that sets line length for the minimum value in events to minLineLength
  var mmr = minLineLength / maxLineLength;
  var scalar = Math.pow(Math.pow(max, mmr) / min, 1 / (1 - mmr));

  function lineLength(x) {
    return maxLineLength * (Math.log(x * scalar) / Math.log(max * scalar));
  }

  return events.map(lineLength)
}

var events = [0.1, 1, 5, 20, 50];

console.log('Between 1 and 20')
generateLineLengths(events, 1, 20).forEach(function(x) {
  console.log(x)
})
// 1
// 8.039722549519123
// 12.960277450480875
// 17.19861274759562
// 20

console.log('Between 5 and 25')
generateLineLengths(events, 5, 25).forEach(function(x) {
  console.log(x)
})
// 5
// 12.410234262651711
// 17.58976573734829
// 22.05117131325855
// 25

【讨论】:

    【解决方案3】:

    较短但不能太短;更长但不要太长。

    实际上我几年前遇到了同样的问题,并为此放弃了(也许?)。 我刚刚在这里和现在阅读了您的问题,我想我刚刚找到了解决方案:shifting

    const log = (base, value) => (Math.log(value) / Math.log(base));
    
    const weights = [0, 0.1, 1, 5, 20, 50, 100];
    const base = Math.E; // Setting
    
    const shifted = weights.map(x => x + base);
    const logged = shifted.map(x => log(base, x));
    const unshifted = logged.map(x => x - 1);
    
    const total = unshifted.reduce((a, b) => a + b, 0);
    const ratio = unshifted.map(x => x / total);
    const percents = ratio.map(x => x * 100);
    
    console.log(percents);
    // [
    //   0,
    //   0.35723375538333857,
    //   3.097582209424984,
    //   10.3192042142806,
    //   20.994247877004888,
    //   29.318026542735115,
    //   35.91370540117108
    // ]
    

    可视化

    对数底越小,越调整; 反之亦然。 其实我也不知道原因。呵呵

    <!DOCTYPE html>
    <html>
    	<head>
    		<meta name="author" content="K.">
    
    		<title>Shorter but not too short; longer but not too long.</title>
    
    		<style>
    			canvas
    			{
    				background-color: whitesmoke;
    			}
    		</style>
    	</head>
    
    	<body>
    		<canvas id="canvas" height="5"></canvas>
    
    		<label>Weights: <input id="weights" type="text" value="[0, 0.1, 100, 1, 5, 20, 2.718, 50]">.</label>
    		<label>Base: <input id="base" type="number" value="2.718281828459045">.</label>
    		<button id="draw" type="button">Draw</button>
    
    		<script>
    			const input = new Proxy({}, {
    				get(_, thing)
    				{
    					return eval(document.getElementById(thing).value);
    				}
    			});
    			const color = ["tomato", "black"];
    			const canvas_element = document.getElementById("canvas");
    			const canvas_context = canvas_element.getContext("2d");
    			canvas_element.width = document.body.clientWidth;
    
    			document.getElementById("draw").addEventListener("click", _ => {
    				const log = (base, value) => (Math.log(value) / Math.log(base));
    
    				const weights = input.weights;
    				const base = input.base;
    
    				const orig_total = weights.reduce((a, b) => a + b, 0);
    				const orig_percents = weights.map(x => x / orig_total * 100);
    
    				const adjusted = weights.map(x => x + base);
    				const logged = adjusted.map(x => log(base, x));
    				const rebased = logged.map(x => x - 1);
    
    				const total = rebased.reduce((a, b) => a + b, 0);
    				const ratio = rebased.map(x => x / total);
    				const percents = ratio.map(x => x * 100);
    				const result = percents.map((percent, index) => `${weights[index]} | ${orig_percents[index]}% --> ${percent}% (${color[index & 1]})`);
    				console.info(result);
    
    				let position = 0;
    				ratio.forEach((rate, index) => {
    					canvas_context.beginPath();
    					canvas_context.moveTo(position, 0);
    					position += (canvas_element.width * rate);
    					canvas_context.lineTo(position, 0);
    					canvas_context.lineWidth = 10;
    					canvas_context.strokeStyle = color[index & 1];
    					canvas_context.stroke();
    				});
    			});
    		</script>
    	</body>
    </html>

    【讨论】:

    • 很遗憾,我不知道如何用数学方式表达这种方法。
    • 我认为需要转变,至少可以避免负面结果。但我不明白为什么你在计算调整的 var 时会移动 Math.E,你能解释一下原因吗?
    • @cdarwin 通过该算法,您指定的对数底数越小,调整得越多(尝试0.00000001 作为底数)。您可以为基数指定任何值。我只是认为Math.E 非常适合示例代码,因为它是 beautiful 常量之一,例如 π。
    【解决方案4】:

    您正在缩放这些数字。您的起始集是域,最终得到的是范围。听起来,这种转变的形状将是对数或遵循幂。

    事实证明,这是许多领域中非常普遍的问题,尤其是数据可视化。这就是为什么 D3.js - 数据可视化工具 - 有 everything you need 可以轻松完成此操作的原因。

     const x = d3.scale.log().range([0, events]);
    

    这是工作的权利。如果您需要绘制一些图表,一切就绪!

    【讨论】:

      【解决方案5】:

      您可以使用Math.pow(x, 0.35) 之类的表达式来代替Math.log(x)。它使所有值保持正数,并为小比率提供您想要的行为。您可以在 (0,1) 范围内尝试不同的指数值,以找到适合您需要的指数值。

      var maxLineLength = 20;
      var exponent = 0.35;
      var lineLen = function(x, max) {
         return maxLineLength * Math.pow(x/max, exponent);
      }
      var events = [0, 0.01, 0.1, 1, 5, 20, 50];
      var max = Math.max.apply(null, events);
      events.map(function (x) {
          console.log(lineLen(x, max));
      });

      【讨论】:

        【解决方案6】:

        您可以减少 maxLineLength 并在计算结束时添加一个。

        对于小于 1 的值,您可以使用一个因子将所有值相对于第一个值进行归一化。起始值始终为 1,或者在对数视图中为零。

        var maxLineLength = 20,
            lineLen = function(max) {
                return function (x) {
                    return (maxLineLength - 1) * Math.log(x) / Math.log(max) + 1;
                };
            },
            events = [0.1, 1, 5, 20, 50],
            normalized = events.map((v, _, a) => v / a[0]),
            max = Math.max.apply(null, normalized),
            result = normalized.map(lineLen(max));
        
        console.log(result);
        console.log(normalized);
        .as-console-wrapper { max-height: 100% !important; top: 0; }

        【讨论】:

        • 这会留下一个......一个,但我记得它也应该缩放到 maxLineLength / Math.log(max) 之类的东西
        • 我还在考虑一个,但是如果你映射一个像 0.1 这样的事件,它会变成一个负数 (-38) ,而我需要它映射到一个小但正数。跨度>
        • 请添加一些示例数据和想要的结果。
        • 我编辑了我的问题,指定正数应该保持正数。也许需要横向翻译
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-01
        • 2010-11-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多