【问题标题】:Optimal algorithm for segmenting set of integers into labels for a chart axis?将整数集分割成图表轴标签的最佳算法?
【发布时间】:2015-03-03 15:47:22
【问题描述】:

假设您从0 to 1,000,000,000 获得值,并且您想要绘制 30 天。所以一个特定的图表可能有一组像:

[ 1, 465, 123, 9, ... ]

而另一个图表可以有一组更大的数字:

[ 761010, 418781, ... ]

是否有一种“最佳算法”可以将这些值分割成“干净”的数字?抱歉措辞,不知道正确的术语,我会尽力解释。

“最优算法”是指最小计算步骤数,因为它创建的标签(比如 y 轴)从人类的角度来看是最简单的。

例如,假设您总是想将 y 轴分成 5 个标签。你可以这样做:

var max = Math.max.apply(Math, values); // 465 (from the first set of values)
var interval = max / 5;
var labels = [ interval * 0, interval * 1, interval * 2, ... ];

但这会创建如下标签:

[ 0, 93, 186, ... ]

这对于人类来说是很复杂的。更好(但仍然不理想)是创建如下标签:

[ 0, 125, 250, 375, 500 ]

但这仍然是具体的。不知何故,它应该找出更好的分割是:

[ 0, 200, 400, 600, 800 ]

这样,它被分成更直观的块。

有解决这个问题的标准方法吗?哪种算法效果最好?

【问题讨论】:

    标签: javascript algorithm plot charts aggregate-functions


    【解决方案1】:

    一些数学

    var getLabelWidth = function(sep, max_value){
    
        var l = (""+max_value).length;
    
        var av = max_value/sep/Math.pow(10,l-2); // get the length max 2 digit
        /// 15.22
    
        var width = (Math.ceil(av)*Math.pow(10,l-2)); // do a ceil on the value retrieved 
        // and apply it to the width of max_value.
        // 16 * 10 000    
        return width;
    }
    console.log(getLabelWidth(2,59));  // 30 :  [0, 30, 60]
    
    console.log(getLabelWidth(2,100)); // 50 :  [0, 50, 100]
    console.log(getLabelWidth(2,968)); // 490 : [0, 490, 980]
    
    console.log(getLabelWidth(3,368)); // 130 : [0, 130, 260, 390]
    console.log(getLabelWidth(3,859)); // 290 : [0, 290, 580, 870]
    console.log(getLabelWidth(3,175)); // 60 :  [0, 60, 120, 180]
    console.log(getLabelWidth(3,580)); // 200 : [0, 200, 400, 600]
    console.log(getLabelWidth(3,74));  // 25 :  [0, 25, 50, 75]
    
    console.log(getLabelWidth(4,1111)); // 300 :[0, 300, 600, 900, 1200]
    console.log(getLabelWidth(4,761010)); // 200 000: [0, 200000, 400000, 600000, 800000]

    我觉得可以改进一点,

    对不起我的英语不好。

    【讨论】:

      【解决方案2】:

      作为参考,这是我最终做的。

      function computeLabels(count, max) {
        var magnitude = orderOfMagnitude(max);
        var multiplier = magnitude * count;
        // 1
        if (multiplier >= max) return buildLabels(count, multiplier);
        // 2
        multiplier *= 2;
        if (multiplier >= max) return buildLabels(count, multiplier);
        // 5
        multiplier *= 5;
        if (multiplier >= max) return buildLabels(count, multiplier);
        // 10, don't think it will ever get here but just in case.
        multiplier *= 10;
        if (multiplier >= max) return buildLabels(count, multiplier);
      }
      
      function buildLabels(count, multiplier) {
        var labels = new Array(count);
        while (count--) labels[count] = formatLabel(count * multiplier);
        return labels;
      }
      
      function formatLabel(value) {
        if (value > 10e5) return (value / 10e5) + 'M'; // millions
        if (value > 10e2) return (value / 10e2) + 'K'; // thousands
        return value; // <= hundreds
      }
      
      function orderOfMagnitude(val) {
        var order = Math.floor(log10(val) + 0.000000001);
        return Math.pow(10, order);
      }
      

      在纸上画出来后,“理想”的标签似乎遵循了一个简单的模式:

      1. 在集合中找到max value
      2. 获取order of magnitude
      3. order of magnitude 乘以number of ticks
      4. 迭代:如果之前的计算大于最大值,则使用它。否则,将值乘以 2 并检查。如果不是,请尝试 5 次。所以模式是 1、2、5。

      这会为您提供如下标签:

      • 10、20(2 个滴答声)
      • 20, 40
      • 50, 100
      • 100, 200
      • 200, 400
      • 500, 1000
      • ...
      • 10、20、30(3 个滴答声)
      • 20、40、60
      • 50, 100, 150(不太喜欢这个,但还不错)
      • 100、200、300
      • 10、20、30、40(4 个滴答声)
      • ...

      它似乎可以改进,无论是在生产质量更好的“人类可读”标签方面,还是在使用更优化的功能方面,但还没有完全看到。这暂时有效。

      很想知道你是否找到更好的方法!

      【讨论】:

        猜你喜欢
        • 2011-06-29
        • 1970-01-01
        • 1970-01-01
        • 2018-03-11
        • 1970-01-01
        • 1970-01-01
        • 2021-03-29
        • 1970-01-01
        • 2012-11-16
        相关资源
        最近更新 更多