为图形的Y轴选择有吸引力的线性刻度

Cli*_*rce 75 algorithm math graph

我正在编写一些代码来在我们的软件中显示条形图(或线条).一切都很顺利.令我难过的是标记Y轴.

调用者可以告诉我他们想要Y标记的标记有多精细,但我似乎仍然坚持要以"有吸引力"的方式标记它们.我无法描述"有吸引力",也许你也不能,但是当我们看到它时我们就知道了,对吗?

所以如果数据点是:

   15, 234, 140, 65, 90
Run Code Online (Sandbox Code Playgroud)

并且用户在Y轴上要求10个标签,用纸和铅笔进行一点点处理:

  0, 25, 50, 75, 100, 125, 150, 175, 200, 225, 250
Run Code Online (Sandbox Code Playgroud)

那里有10个(不包括0),最后一个延伸超过最高值(234 <250),并且它是一个"好"的增量,每个增加25.如果他们要求8个标签,那么30的增量看起来不错:

  0, 30, 60, 90, 120, 150, 180, 210, 240
Run Code Online (Sandbox Code Playgroud)

九会很棘手.也许只是使用了8或10,并将其称为足够接近就可以了.当一些观点是否定的时候该怎么办?

我可以看到Excel很好地解决了这个问题.

有没有人知道解决这个问题的通用算法(甚至有些蛮力没问题)?我不必快速做,但它应该看起来不错.

Too*_*the 93

很久以前我写了一个图形模块,很好地涵盖了这一点.挖掘灰色质量得到以下内容:

  • 确定数据的下限和上限.(注意下限=上限的特殊情况!
  • 将范围除以所需的刻度数.
  • 将蜱范围缩小到很好的数量.
  • 相应地调整下限和上限.

让我们举个例子:

15, 234, 140, 65, 90 with 10 ticks
Run Code Online (Sandbox Code Playgroud)
  1. 下限= 15
  2. 上限= 234
  3. 范围= 234-15 = 219
  4. 滴答范围= 21.9.这应该是25.0
  5. 新的下限= 25*圆(15/25)= 0
  6. 新上限= 25*圆(1 + 235/25)= 250

所以范围= 0,25,50,...,225,250

您可以通过以下步骤获得良好的滴答范围:

  1. 除以10 ^ x,结果在0.1和1.0之间(包括0.1除1).
  2. 相应翻译:
    • 0.1 - > 0.1
    • <= 0.2 - > 0.2
    • <= 0.25 - > 0.25
    • <= 0.3 - > 0.3
    • <= 0.4 - > 0.4
    • <= 0.5 - > 0.5
    • <= 0.6 - > 0.6
    • <= 0.7 - > 0.7
    • <= 0.75 - > 0.75
    • <= 0.8 - > 0.8
    • <= 0.9 - > 0.9
    • <= 1.0 - > 1.0
  3. 乘以10 ^ x.

在这种情况下,21.9除以10 ^ 2得到0.219.这是<= 0.25所以我们现在有0.25.乘以10 ^ 2得到25.

让我们看一下8个刻度的相同例子:

15, 234, 140, 65, 90 with 8 ticks
Run Code Online (Sandbox Code Playgroud)
  1. 下限= 15
  2. 上限= 234
  3. 范围= 234-15 = 219
  4. 滴答范围= 27.375
    1. 除以10 ^ 2得到0.27375,转换为0.3,得到(乘以10 ^ 2)30.
  5. 新下界= 30*圆(15/30)= 0
  6. 新上限= 30*圆(1 + 235/30)= 240

给出你要求的结果;-).

------由KD添加------

这是在不使用查找表等的情况下实现此算法的代码...:

double range = ...;
int tickCount = ...;
double unroundedTickSize = range/(tickCount-1);
double x = Math.ceil(Math.log10(unroundedTickSize)-1);
double pow10x = Math.pow(10, x);
double roundedTickRange = Math.ceil(unroundedTickSize / pow10x) * pow10x;
return roundedTickRange;
Run Code Online (Sandbox Code Playgroud)

一般来说,刻度数包括底部刻度,因此实际的y轴段比刻度数少一个.

  • 这是一个很好的答案.非常感谢. (3认同)
  • @JoelAnair谢谢你让悲伤的一天变得更加光明. (3认同)
  • 你引用除以10 ^ x并乘以10 ^ x.应该注意的是x可以这样找到:'double x = Math.Ceiling(Math.Log10(tickRange));' (2认同)
  • 很有帮助。虽然不明白 - '新下限 = 30 * 回合(15/30)= 0'(我认为它会来 30)以及你如何在 '新上限 = 30 * 回合(1+235/30)= 中得到 235 240' 235 没有被提及,它应该是 234。 (2认同)

小智 20

这是我正在使用的PHP示例.此函数返回一个漂亮的Y轴值数组,其中包含传入的最小和最大Y值.当然,此例程也可用于X轴值.

它允许您"建议"您可能需要多少个刻度,但例程将返回看起来不错的刻度.我添加了一些示例数据并显示了这些结果.

#!/usr/bin/php -q
<?php

function makeYaxis($yMin, $yMax, $ticks = 10)
{
  // This routine creates the Y axis values for a graph.
  //
  // Calculate Min amd Max graphical labels and graph
  // increments.  The number of ticks defaults to
  // 10 which is the SUGGESTED value.  Any tick value
  // entered is used as a suggested value which is
  // adjusted to be a 'pretty' value.
  //
  // Output will be an array of the Y axis values that
  // encompass the Y values.
  $result = array();
  // If yMin and yMax are identical, then
  // adjust the yMin and yMax values to actually
  // make a graph. Also avoids division by zero errors.
  if($yMin == $yMax)
  {
    $yMin = $yMin - 10;   // some small value
    $yMax = $yMax + 10;   // some small value
  }
  // Determine Range
  $range = $yMax - $yMin;
  // Adjust ticks if needed
  if($ticks < 2)
    $ticks = 2;
  else if($ticks > 2)
    $ticks -= 2;
  // Get raw step value
  $tempStep = $range/$ticks;
  // Calculate pretty step value
  $mag = floor(log10($tempStep));
  $magPow = pow(10,$mag);
  $magMsd = (int)($tempStep/$magPow + 0.5);
  $stepSize = $magMsd*$magPow;

  // build Y label array.
  // Lower and upper bounds calculations
  $lb = $stepSize * floor($yMin/$stepSize);
  $ub = $stepSize * ceil(($yMax/$stepSize));
  // Build array
  $val = $lb;
  while(1)
  {
    $result[] = $val;
    $val += $stepSize;
    if($val > $ub)
      break;
  }
  return $result;
}

// Create some sample data for demonstration purposes
$yMin = 60;
$yMax = 330;
$scale =  makeYaxis($yMin, $yMax);
print_r($scale);

$scale = makeYaxis($yMin, $yMax,5);
print_r($scale);

$yMin = 60847326;
$yMax = 73425330;
$scale =  makeYaxis($yMin, $yMax);
print_r($scale);
?>
Run Code Online (Sandbox Code Playgroud)

样本数据的结果输出

# ./test1.php
Array
(
    [0] => 60
    [1] => 90
    [2] => 120
    [3] => 150
    [4] => 180
    [5] => 210
    [6] => 240
    [7] => 270
    [8] => 300
    [9] => 330
)

Array
(
    [0] => 0
    [1] => 90
    [2] => 180
    [3] => 270
    [4] => 360
)

Array
(
    [0] => 60000000
    [1] => 62000000
    [2] => 64000000
    [3] => 66000000
    [4] => 68000000
    [5] => 70000000
    [6] => 72000000
    [7] => 74000000
)
Run Code Online (Sandbox Code Playgroud)


Dre*_*kes 8

试试这个代码.我已经在一些图表场景中使用它并且效果很好.它也很快.

public static class AxisUtil
{
    public static float CalculateStepSize(float range, float targetSteps)
    {
        // calculate an initial guess at step size
        float tempStep = range/targetSteps;

        // get the magnitude of the step size
        float mag = (float)Math.Floor(Math.Log10(tempStep));
        float magPow = (float)Math.Pow(10, mag);

        // calculate most significant digit of the new step size
        float magMsd = (int)(tempStep/magPow + 0.5);

        // promote the MSD to either 1, 2, or 5
        if (magMsd > 5.0)
            magMsd = 10.0f;
        else if (magMsd > 2.0)
            magMsd = 5.0f;
        else if (magMsd > 1.0)
            magMsd = 2.0f;

        return magMsd*magPow;
    }
}
Run Code Online (Sandbox Code Playgroud)


Pyr*_*cal 6

听起来好像来电者不会告诉你它想要的范围.

所以你可以自由地改变终点,直到你的标签数量很好地整除它.

让我们定义"好".如果标签关闭,我会称之为好:

1. 2^n, for some integer n. eg. ..., .25, .5, 1, 2, 4, 8, 16, ...
2. 10^n, for some integer n. eg. ..., .01, .1, 1, 10, 100
3. n/5 == 0, for some positive integer n, eg, 5, 10, 15, 20, 25, ...
4. n/2 == 0, for some positive integer n, eg, 2, 4, 6, 8, 10, 12, 14, ...
Run Code Online (Sandbox Code Playgroud)

查找数据系列的最大值和最小值.我们称之为以下几点:

min_point and max_point.
Run Code Online (Sandbox Code Playgroud)

现在你需要做的就是找到3个值:

- start_label, where start_label < min_point and start_label is an integer
- end_label, where end_label > max_point and end_label is an integer
- label_offset, where label_offset is "nice"
Run Code Online (Sandbox Code Playgroud)

符合等式:

(end_label - start_label)/label_offset == label_count
Run Code Online (Sandbox Code Playgroud)

可能有很多解决方案,所以选择一个.大多数时候我打赌你可以设置

start_label to 0
Run Code Online (Sandbox Code Playgroud)

所以试试不同的整数

end_label
Run Code Online (Sandbox Code Playgroud)

直到偏移"很好"