【问题标题】:Reasonable optimized chart scaling合理优化图表缩放
【发布时间】:2010-10-11 08:16:26
【问题描述】:

我需要制作一个具有优化 y 轴最大值的图表。

我目前制作图表的方法只是使用所有图表的最大值,然后将其除以 10,并将其用作网格线。不是我写的。

更新说明:这些图表已更改。一旦我修复了代码,我的动态图就开始工作了,这让这个问题变得荒谬(因为示例中不再有任何错误)。我已经用静态图像更新了这些,但有些答案引用了不同的值。记住这一点。 到目前为止,2 月份有 12003 到 14003 个呼入电话。内容丰富,但丑陋。

我想避免使用 y 轴编号的看起来像猴子的图表。

使用 Google 图表 API 会有所帮助,但这仍然不是我想要的。 数字很​​干净,但 y 值的顶部始终与图表上的最大值相同。此图表的范围从 0 到 1357。我需要计算出 1400 的正确值,有问题


我在这里加入rbobby 对“好”数字的定义,因为它解释得很好。

  • “好”数字是指具有 3 个或更少非零数字的数字(例如 1230000)
  • 一个“好”的数字与零数字具有相同或少数几个非零数字(例如,1230 不好,1200 好)
  • 最好的数字是 3 个零的倍数(例如“1,000”、“1,000,000”)
  • 第二好的数字是由 3 个零和 2 个零组成的整数(例如“1,500,000”、“1,200”)

解决方案

我找到了使用 Mark Ransom 想法的修改版本来获得我想要的结果的方法。

拳头,Mark Ransom 的代码在给定刻度数时确定刻度之间的最佳间距。有时,这个数字最终会是图表上最高值的两倍多,具体取决于您想要多少条网格线。

我正在运行带有 5、6、7、8、9 和 10 条网格线(刻度线)的 Mark 代码,以找出其中最低的一条。值为 23 时,图表的高度变为 25,网格线位于 5、10、15、20 和 25 处。值为 26 时,图表的高度为 30,网格线位于 5、10 , 15, 20, 25, 30。网格线之间的间距相同,但数量更多。

以下是复制 Excel 所做的以使图表变得精美的步骤。

  1. 暂时将图表的最高值提高约 5%(这样图表的最高点和图表区域的顶部之间总是有一些空间。我们希望 99.9 向上舍入到 120)
  2. 找到最佳网格线位置 适用于 5、6、7、8、9 和 10 格 行。
  3. 从这些数字中选出最小的。请记住获得该值所需的网格线数。
  4. 现在您有了最佳图表高度。线/条永远不会与图表顶部对接,并且您拥有最佳的刻度数。

PHP:

function roundUp($maxValue){
    $optiMax = $maxValue * 2;
    for ($i = 5; $i <= 10; $i++){
        $tmpMaxValue = bestTick($maxValue,$i);
        if (($optiMax > $tmpMaxValue) and ($tmpMaxValue > ($maxValue + $maxValue * 0.05))){
            $optiMax = $tmpMaxValue;
            $optiTicks = $i;
        }
    }
    return $optiMax;
}
function bestTick($maxValue, $mostTicks){
    $minimum = $maxValue / $mostTicks;
    $magnitude = pow(10,floor(log($minimum) / log(10)));
    $residual = $minimum / $magnitude;
    if ($residual > 5){
        $tick = 10 * $magnitude;
    } elseif ($residual > 2) {
        $tick = 5 * $magnitude;
    } elseif ($residual > 1){
        $tick = 2 * $magnitude;
    } else {
        $tick = $magnitude;
    }
    return ($tick * $mostTicks);
}

Python:

import math

def BestTick(largest, mostticks):
    minimum = largest / mostticks
    magnitude = 10 ** math.floor(math.log(minimum) / math.log(10))
    residual = minimum / magnitude
    if residual > 5:
        tick = 10 * magnitude
    elif residual > 2:
        tick = 5 * magnitude
    elif residual > 1:
        tick = 2 * magnitude
    else:
        tick = magnitude
    return tick

value = int(input(""))
optMax = value * 2
for i in range(5,11):
    maxValue = BestTick(value,i) * i
    print maxValue
    if (optMax > maxValue) and (maxValue > value  + (value*.05)):
        optMax = maxValue
        optTicks = i
print "\nTest Value: " + str(value + (value * .05)) + "\n\nChart Height: " + str(optMax) + " Ticks: " + str(optTicks)

【问题讨论】:

  • 这是一个绝妙的问题。写得好,布局好,显然经过研究,甚至包括插图。为什么没有其他人支持它?
  • 不适用于大数字,间隔变大
  • @Matjaž Jurečič 看看这个stackoverflow.com/a/35254002/2277240 是否适合你。
  • @FGRibreau 听起来工作量很大。我只是要用静态副本替换它。

标签: algorithm math charts


【解决方案1】:

如果你想让1400在顶部,那么将最后两个参数调整为1400而不是1357怎么样:

【讨论】:

  • 我不知道图表的价值会是什么。我无法手动设置图表的所有最大值。
  • 如果您能够将数据插入 Google Charts API,那么您就可以访问最大值。
  • 这种情况下的最大值是 1357。我知道如何将它四舍五入到 1400,但是计算机可能会将它四舍五入到 1360 之类的。
【解决方案2】:

您可以使用 div 和 mod。例如。

假设您希望图表以 20 为增量四舍五入(只是为了使其比典型的“10”值更随意)。

所以我会假设 1、11、18 将四舍五入为 20。但 21、33、38 将四舍五入为 40。

要得出正确的值,请执行以下操作:

Where divisor = your rounding increment.

divisor = 20
multiple = maxValue / divisor;  // Do an integer divide here. 
if (maxValue modulus divisor > 0)
   multiple++;

graphMax = multiple * maxValue;

现在让我们插入实数:

divisor = 20;
multiple = 33 / 20; (integer divide)
so multiple = 1
if (33 modulus 20 > 0)  (it is.. it equals 13) 
   multiple++;

so multiple = 2;
graphMax = multiple (2) * maxValue (20);
graphMax = 40;

【讨论】:

  • 在这种情况下,像 1479 这样的数字不会四舍五入到 1480 吗?它比我目前拥有的要好,但是当数字这么高时,我需要将它四舍五入到 1500。
  • 然后将除数设为 100 这是数学运算:multiple = 1479 / 100 = 14; if (1479 模数 100 > 0) (它确实...它等于 479) 倍数 ++; (15) graphMax = 倍数 (15) * 除数 (100) = 1500
  • 这个公式适用于所有情况..您只需要决定要四舍五入的值..四舍五入20?以 100 为单位。没关系。
  • 问题是,我不知道图表会有多大。有些图表是 100 的,有些是 10,000 的。将 5 舍入到最接近的 1,000 是很愚蠢的。
  • 更好的是,Excel 会自动以我喜欢的方式 调整图表。所以用最简单的话来说,我想复制 excel 所做的任何事情来计算比例。
【解决方案3】:

在过去,我以蛮力的方式做到了这一点。这是一段运行良好的 C++ 代码......但是对于硬编码的下限和上限(0 和 5000):

int PickYUnits()
{
    int MinSize[8] = {20, 20, 20, 20, 20, 20, 20, 20};
    int ItemsPerUnit[8] = {5, 10, 20, 25, 50, 100, 250, 500};
    int ItemLimits[8] = {20, 50, 100, 250, 500, 1000, 2500, 5000};
    int MaxNumUnits = 8;
    double PixelsPerY;
    int PixelsPerAxis;
    int Units;

    //
    // Figure out the max from the dataset
    //  - Min is always 0 for a bar chart
    //
    m_MinY = 0;
    m_MaxY = -9999999;
    m_TotalY = 0;
    for (int j = 0; j < m_DataPoints.GetSize(); j++) {
        if (m_DataPoints[j].m_y > m_MaxY) {
            m_MaxY = m_DataPoints[j].m_y;
        }

        m_TotalY += m_DataPoints[j].m_y;
    }

    //
    // Give some space at the top
    //
    m_MaxY = m_MaxY + 1;


    //
    // Figure out the size of the range
    //
    double yRange = (m_MaxY - m_MinY);

    //
    // Pick the initial size
    //
    Units = MaxNumUnits;
    for (int k = 0; k < MaxNumUnits; k++)
    {
        if (yRange < ItemLimits[k])
        {
            Units = k;
            break;
        }
    }

    //
    // Adjust it upwards based on the space available
    //
    PixelsPerY = m_rcGraph.Height() / yRange;
    PixelsPerAxis = (int)(PixelsPerY * ItemsPerUnit[Units]);

    while (PixelsPerAxis < MinSize[Units]){
        Units += 1;
        PixelsPerAxis = (int)(PixelsPerY * ItemsPerUnit[Units]);
        if (Units == 5)
            break;
    }


    return ItemsPerUnit[Units];
}

但是,您所说的某些内容使我感到不安。要选择好的轴数,“好数”的定义会有所帮助:

  • “好”数字是指具有 3 个或更少非零数字的数字(例如 1230000)
  • 一个“好”的数字与零数字具有相同或少数几个非零数字(例如,1230 不好,1200 好)
  • 最好的数字是 3 个零的倍数(例如“1,000”、“1,000,000”)
  • 第二好的数字是由 3 个零和 2 个零组成的整数(例如“1,500,000”、“1,200”)

不确定上述定义是否“正确”或是否真的有用(但有了定义,设计算法就变得更简单了)。

【讨论】:

  • 我喜欢你对“好”数字的定义。这些规则给了我一些切实的目标。
  • 这类似于从我自己的一个问题演变而来的解决方案。似乎一种主要是蛮力的方法是要走的路:stackoverflow.com/questions/302406/…
【解决方案4】:

您最多可以四舍五入两位有效数字。以下伪代码应该可以工作:

// maxValue is the largest value in your chart
magnitude = floor(log10(maxValue))
base = 10^(magnitude - 1)
chartHeight = ceiling(maxValue / base) * base

例如,如果maxValue 为1357,则幅度为3,基数为100。除以100,向上取整,再乘以100 的结果是向上取整到下一个100 的倍数,即向上取整两个有效数字。在这种情况下,结果为 1400 (1357 ⇒ 13.57 ⇒ 14 ⇒ 1400)。

【讨论】:

  • 1357 舍入到 1360,1401 舍入到 1408。我会继续检查这里是否做错了
  • 我做错了什么。 “^”在 PHP 中不是“强大的”。这是我最近的一次。
  • ^ 在许多语言中是按位异或(我不确定 PHP)。尝试使用 pow() 代替:us.php.net/pow
  • Pow(10,x) 有效。我忘了提到我让它工作了。我的意思是你的整个答案是我迄今为止最接近的答案。
【解决方案5】:

这是来自之前的一个类似问题:

Algorithm for "nice" grid line intervals on a graph

我用一种蛮横的方式做到了这一点 力法。首先,弄清楚 您可以使用的最大刻度线数 融入空间。将总数除以 值的范围由数量 蜱;这是最低 刻度线的间距。现在计算 以 10 为底的对数的底 得到刻度的大小,和 除以这个值。你应该结束 在 1 到 10.只需选择大于或等于该值的整数和 乘以对数 较早计算。这是你的 最终刻度间距。

Python 示例:

import math

def BestTick(largest, mostticks):
    minimum = largest / mostticks
    magnitude = 10 ** math.floor(math.log(minimum) / math.log(10))
    residual = minimum / magnitude
    if residual > 5:
        tick = 10 * magnitude
    elif residual > 2:
        tick = 5 * magnitude
    elif residual > 1:
        tick = 2 * magnitude
    else:
        tick = magnitude
    return tick

【讨论】:

  • 我读过这个问题。我以为它只是处理最大值和最小值之间的刻度线?
  • 一旦有了刻度线之间的间距,将端点四舍五入到刻度线的倍数就很简单了。
  • 如果我不需要优化最大值来获取刻度线,那么使用刻度线将如何帮助我获得优化的最大值?我是否误解了您的刻度线的作用?我的意思不是听起来不欣赏,但你说得非常含糊。
  • 是的,我有时会这样做;) 您不需要优化的最大值来获得优化的刻度大小,而是使用实际的最大值。计算后,优化后的最大值为 ceil(max/tick)*tick。
  • 感谢您从其他线程复制答案 - 我应该自己完成。
【解决方案6】:

稍作改进和测试...(适用于分数单位,而不仅仅是整数)

public void testNumbers() {
        double test = 0.20000;

        double multiple = 1;
        int scale = 0;
        String[] prefix = new String[]{"", "m", "u", "n"};
        while (Math.log10(test) < 0) {
            multiple = multiple * 1000;
            test = test * 1000;
            scale++;
        }

        double tick;
        double minimum = test / 10;
        double magnitude = 100000000;
        while (minimum <= magnitude){
            magnitude = magnitude / 10;
        }

        double residual = test / (magnitude * 10);
        if (residual > 5) {
            tick = 10 * magnitude;
        } else if (residual > 2) {
            tick = 5 * magnitude;
        } else if (residual > 1) {
            tick = 2 * magnitude;
        } else {
            tick = magnitude;
        }

        double curAmt = 0;

        int ticks = (int) Math.ceil(test / tick);

        for (int ix = 0; ix < ticks; ix++) {
            curAmt += tick;
            BigDecimal bigDecimal = new BigDecimal(curAmt);
            bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP);
            System.out.println(bigDecimal.stripTrailingZeros().toPlainString() + prefix[scale] + "s");
        }

        System.out.println("Value = " + test + prefix[scale] + "s");
        System.out.println("Tick = " + tick + prefix[scale] + "s");
        System.out.println("Ticks = " + ticks);
        System.out.println("Scale = " +  multiple + " : " + scale);


    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-09-08
    • 2016-06-03
    • 1970-01-01
    • 2016-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多