그래프의 Y 축에 대한 매력적인 선형 스케일 선택


84

소프트웨어에서 막대 (또는 선) 그래프를 표시하는 코드를 작성하고 있습니다. 모든 것이 잘되고 있습니다. 저를 당황하게 만드는 것은 Y 축에 레이블을 지정하는 것입니다.

발신자는 Y 스케일을 얼마나 세밀하게 표시하고 싶은지 말해 줄 수 있지만, "매력적인"방식으로 라벨을 정확히 지정해야하는 것 같습니다. 나는 "매력적"이라고 설명 할 수없고, 아마 당신도 할 수 없을 것입니다.하지만 우리는 그것을 볼 때 그것을 압니다. 그렇죠?

따라서 데이터 포인트가 다음과 같은 경우 :

   15, 234, 140, 65, 90

그리고 사용자는 Y 축에 10 개의 레이블을 요청합니다. 종이와 연필로 약간의 마무리를하면 다음과 같은 결과가 나타납니다.

  0, 25, 50, 75, 100, 125, 150, 175, 200, 225, 250

그래서 거기에는 10 개 (0 제외)가 있고, 마지막 하나는 가장 높은 값 (234 <250)을 넘어서 확장되며 각각 25 씩 "좋은"증분입니다. 8 개의 레이블을 요청했다면 30 씩 증가하면 좋을 것입니다.

  0, 30, 60, 90, 120, 150, 180, 210, 240

9 개는 까다 로웠을 것입니다. 아마 8 또는 10을 사용하고 충분히 가깝게 호출하면 괜찮을 것입니다. 일부 포인트가 부정적 일 때 어떻게해야합니까?

Excel이이 문제를 잘 해결하는 것을 볼 수 있습니다.

누구든지이 문제를 해결하기위한 범용 알고리즘 (무차별 대입도 괜찮음)을 알고 있습니까? 빨리 할 필요는 없지만 멋져 보일 것입니다.


1
Excel에서 Y 축의 최대 값과 최소값을 선택하는 방법에 대한 정보는 다음과 같습니다. support.microsoft.com/kb/214075
Christopher Orr

답변:


103

오래 전에 저는 이것을 잘 다루는 그래프 모듈을 작성했습니다. 회색 덩어리를 파는 것은 다음을 얻습니다.

  • 데이터의 하한과 상한을 결정합니다. (하한 = 상한 인 특수한 경우에주의하십시오!
  • 범위를 필요한 틱 수로 나눕니다.
  • 눈금 범위를 좋은 양으로 반올림하십시오.
  • 그에 따라 하한과 상한을 조정합니다.

예를 들어 보겠습니다.

15, 234, 140, 65, 90 with 10 ticks
  1. 하한 = 15
  2. 상한 = 234
  3. 범위 = 234-15 = 219
  4. 눈금 범위 = 21.9. 25.0이어야합니다.
  5. 새 하한 = 25 * round (15/25) = 0
  6. 새 상한 = 25 * round (1 + 235 / 25) = 250

따라서 범위 = 0,25,50, ..., 225,250

다음 단계를 통해 멋진 눈금 범위를 얻을 수 있습니다.

  1. 결과가 0.1과 1.0 사이가되도록 10 ^ x로 나눕니다 (1을 제외한 0.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
  1. 하한 = 15
  2. 상한 = 234
  3. 범위 = 234-15 = 219
  4. 눈금 범위 = 27.375
    1. 0.27375에 대해 10 ^ 2로 나누면 0.3으로 변환되어 (10 ^ 2 곱하기) 30이됩니다.
  5. 새 하한 = 30 * round (15/30) = 0
  6. 새 상한 = 30 * round (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;

일반적으로 틱 수에는 하단 틱이 포함되므로 실제 y 축 세그먼트는 틱 수보다 하나 적습니다.


1
이것은 거의 맞았습니다. 3 단계, 저는 X를 1로 줄여야했습니다. 219에서 .1-> 1까지의 범위를 얻으려면 10 ^ 2 (100)이 아닌 10 ^ 3 (1000)으로 나누어야합니다. 그렇지 않으면 자리를 잡으십시오.
Clinton Pierce

2
10 ^ x로 나누고 10 ^ x로 곱하는 것을 참조합니다. x는 다음과 같이 찾을 수 있습니다. 'double x = Math.Ceiling (Math.Log10 (tickRange));'
Bryan

1
매우 유용합니다. 이해하지 못했지만- 'new lower bound = 30 * round (15/30) = 0'(내 생각 엔 30이 될 것입니다) 그리고 어떻게 235를 'new upper bound = 30 * round (1 + 235 / 30) = 240 '235는 어디에도 언급되지
Mutant

4
이것은 훌륭한 대답입니다. 대단히 감사합니다.
Joel Anair

4
@JoelAnair 감사합니다 슬픈 하루를 조금 더 밝게 만들었습니다.
Toon Krijthe 2014

22

다음은 내가 사용하는 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);
?>

샘플 데이터의 결과 출력

# ./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
)

내 상사가 이것에 만족할 것입니다-나도 감사합니다 n 감사합니다!
Stephen Hazel

좋은 대답! 그것을 Swift 4 로 변환합니다. stackoverflow.com/a/55151115/2670547
Petr Syrov

@Scott Guthrie : 입력이 정수가 아니고 작은 숫자 (예 : yMin = 0.03 및 yMax = 0.11)가 아니라면 이것은 좋습니다.
Greg

9

이 코드를 사용해보십시오. 몇 가지 차트 시나리오에서 사용했으며 잘 작동합니다. 너무 빠릅니다.

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;
    }
}

6

발신자가 원하는 범위를 알려주지 않는 것 같습니다.

따라서 레이블 수로 잘 나눌 때까지 끝점을 자유롭게 변경할 수 있습니다.

"nice"를 정의합시다. 레이블이 다음과 같이 꺼져 있으면 nice라고 부를 것입니다.

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, ...

데이터 시리즈의 최대 값과 최소값을 찾으십시오. 이 점을 부릅시다 :

min_point and max_point.

이제 필요한 것은 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"

방정식에 맞는 :

(end_label - start_label)/label_offset == label_count

아마도 많은 솔루션이있을 것이므로 하나만 선택하십시오. 대부분의 경우 설정할 수 있습니다.

start_label to 0

그래서 다른 정수를 시도하십시오

end_label

오프셋이 "nice"가 될 때까지


3

나는 아직도 이것과 싸우고있다 :)

원래 Gamecat 답변은 대부분의 경우 작동하는 것 같지만 필요한 틱 수 (동일한 데이터 값 15, 234, 140, 65, 90)로 "3 틱"이라고 연결해보십시오 .... it 틱 범위가 73 인 것 같고, 10 ^ 2로 나눈 후 0.73이되고, 이는 0.75에 매핑되어 '좋은'틱 범위가 75가됩니다.

그런 다음 상한 계산 : 75 * round (1 + 234 / 75) = 300

하한 : 75 * round (15/75) = 0

그러나 분명히 0에서 시작하여 75 단계에서 300의 상한선까지 진행하면 0,75,150,225,300 .... 이것은 의심 할 여지없이 유용하지만 4 틱 (0은 제외)이 아닙니다. 3 틱이 필요합니다.

100 % 작동하지 않는다는 사실이 실망 스러웠습니다. 물론 어딘가에서 내 실수로 귀결 될 수 있습니다!


원래 문제는 Bryan이 제안한 x 도출 방법과 관련이있을 수 있다고 생각했지만 이것은 물론 완벽하게 정확합니다.
StillPondering

3

Toon Krijthe 의 대답은 대부분의 경우 작동합니다. 그러나 때로는 과도한 수의 진드기를 생성합니다. 음수에서도 작동하지 않습니다. 문제에 대한 전반적인 접근 방식은 괜찮지 만이를 처리하는 더 좋은 방법이 있습니다. 사용하려는 알고리즘은 실제로 얻고 자하는 것에 따라 달라집니다. 아래에서는 JS Ploting 라이브러리에서 사용한 코드를 보여 드리겠습니다. 나는 그것을 테스트했으며 항상 작동합니다 (희망적으로;)). 주요 단계는 다음과 같습니다.

  • 전역 극한값 xMin 및 xMax 가져 오기 (알고리즘에서 인쇄하려는 모든 플롯을 포함하지 않음)
  • xMin과 xMax 사이의 범위 계산
  • 범위의 크기 차수 계산
  • 범위를 틱 수에서 1을 뺀 값으로 나누어 틱 크기 계산
  • 이것은 선택 사항입니다. 항상 0 눈금을 인쇄하려면 눈금 크기를 사용하여 양수 및 음수 눈금 수를 계산합니다. 총 틱 수는 합계 + 1 (제로 틱)입니다.
  • 항상 눈금이 0 인 경우에는 필요하지 않습니다. 하한과 상한을 계산하지만 플롯을 중앙에 배치해야합니다.

시작하자. 먼저 기본 계산

    var range = Math.abs(xMax - xMin); //both can be negative
    var rangeOrder = Math.floor(Math.log10(range)) - 1; 
    var power10 = Math.pow(10, rangeOrder);
    var maxRound = (xMax > 0) ? Math.ceil(xMax / power10) : Math.floor(xMax / power10);
    var minRound = (xMin < 0) ? Math.floor(xMin / power10) : Math.ceil(xMin / power10);

내 플롯이 모든 데이터를 포함 할 수 있도록 최소 및 최대 값을 100 % 반올림합니다. 음수인지 아닌지 여부에 관계없이 범위의 log10 바닥에 매우 중요하며 나중에 1을 뺍니다. 그렇지 않으면 알고리즘이 1보다 작은 숫자에 대해 작동하지 않습니다.

    var fullRange = Math.abs(maxRound - minRound);
    var tickSize = Math.ceil(fullRange / (this.XTickCount - 1));

    //You can set nice looking ticks if you want
    //You can find exemplary method below 
    tickSize = this.NiceLookingTick(tickSize);

    //Here you can write a method to determine if you need zero tick
    //You can find exemplary method below
    var isZeroNeeded = this.HasZeroTick(maxRound, minRound, tickSize);

7, 13, 17 등과 같은 진드기를 피하기 위해 "멋진 진드기"를 사용합니다. 여기서 사용하는 방법은 매우 간단합니다. 필요할 때 zeroTick을 사용하는 것도 좋습니다. 이렇게하면 플롯이 훨씬 더 전문적으로 보입니다. 이 답변의 끝에 모든 방법이 있습니다.

이제 상한과 하한을 계산해야합니다. 이것은 제로 틱으로 매우 쉽지만 다른 경우에는 조금 더 노력이 필요합니다. 왜? 왜냐하면 우리는 상한과 하한 내에서 플롯을 멋지게 중앙에 배치하기를 원하기 때문입니다. 내 코드를 살펴보십시오. 일부 변수는이 범위 밖에서 정의되며 일부는 제시된 전체 코드가 유지되는 객체의 속성입니다.

    if (isZeroNeeded) {

        var positiveTicksCount = 0;
        var negativeTickCount = 0;

        if (maxRound != 0) {

            positiveTicksCount = Math.ceil(maxRound / tickSize);
            XUpperBound = tickSize * positiveTicksCount * power10;
        }

        if (minRound != 0) {
            negativeTickCount = Math.floor(minRound / tickSize);
            XLowerBound = tickSize * negativeTickCount * power10;
        }

        XTickRange = tickSize * power10;
        this.XTickCount = positiveTicksCount - negativeTickCount + 1;
    }
    else {
        var delta = (tickSize * (this.XTickCount - 1) - fullRange) / 2.0;

        if (delta % 1 == 0) {
            XUpperBound = maxRound + delta;
            XLowerBound = minRound - delta;
        }
        else {
            XUpperBound =  maxRound + Math.ceil(delta);
            XLowerBound =  minRound - Math.floor(delta);
        }

        XTickRange = tickSize * power10;
        XUpperBound = XUpperBound * power10;
        XLowerBound = XLowerBound * power10;
    }

그리고 여기에 내가 전에 언급 한 방법들이 있습니다. 당신은 혼자서 쓸 수 있지만 당신은 제 것을 사용할 수도 있습니다.

this.NiceLookingTick = function (tickSize) {

    var NiceArray = [1, 2, 2.5, 3, 4, 5, 10];

    var tickOrder = Math.floor(Math.log10(tickSize));
    var power10 = Math.pow(10, tickOrder);
    tickSize = tickSize / power10;

    var niceTick;
    var minDistance = 10;
    var index = 0;

    for (var i = 0; i < NiceArray.length; i++) {
        var dist = Math.abs(NiceArray[i] - tickSize);
        if (dist < minDistance) {
            minDistance = dist;
            index = i;
        }
    }

    return NiceArray[index] * power10;
}

this.HasZeroTick = function (maxRound, minRound, tickSize) {

    if (maxRound * minRound < 0)
    {
        return true;
    }
    else if (Math.abs(maxRound) < tickSize || Math.round(minRound) < tickSize) {

        return true;
    }
    else {

        return false;
    }
}

여기에 포함되지 않은 것이 하나 더 있습니다. 이것은 "멋져 보이는 경계"입니다. 이것은 "멋진 틱"의 숫자와 유사한 숫자 인 하한입니다. 예를 들어, 동일한 틱 크기로 6에서 시작하는 플롯을 갖는 것보다 틱 크기 5로 시작하는 하한을 5에서 시작하는 것이 좋습니다. 그러나 이것은 내 해고 나는 그것을 당신에게 맡깁니다.

도움이되기를 바랍니다. 건배!


2

답변Swift 4 로 변환했습니다.

extension Int {

    static func makeYaxis(yMin: Int, yMax: Int, ticks: Int = 10) -> [Int] {
        var yMin = yMin
        var yMax = yMax
        var ticks = ticks
        // 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.
        var result = [Int]()
        // 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 -= ticks   // some small value
            yMax += ticks   // some small value
        }
        // Determine Range
        let range = yMax - yMin
        // Adjust ticks if needed
        if ticks < 2 { ticks = 2 }
        else if ticks > 2 { ticks -= 2 }

        // Get raw step value
        let tempStep: CGFloat = CGFloat(range) / CGFloat(ticks)
        // Calculate pretty step value
        let mag = floor(log10(tempStep))
        let magPow = pow(10,mag)
        let magMsd = Int(tempStep / magPow + 0.5)
        let stepSize = magMsd * Int(magPow)

        // build Y label array.
        // Lower and upper bounds calculations
        let lb = stepSize * Int(yMin/stepSize)
        let ub = stepSize * Int(ceil(CGFloat(yMax)/CGFloat(stepSize)))
        // Build array
        var val = lb
        while true {
            result.append(val)
            val += stepSize
            if val > ub { break }
        }
        return result
    }

}

예를 들어 yMin = 0.03이고 yMax = 0.11 인 경우 입력이 정수가 아니고 작은 숫자가 아니라면 이것은 좋습니다.
Greg

1

이것은 매력처럼 작동합니다. 10 단계 + 0을 원한다면

//get proper scale for y
$maximoyi_temp= max($institucion); //get max value from data array
 for ($i=10; $i< $maximoyi_temp; $i=($i*10)) {   
    if (($divisor = ($maximoyi_temp / $i)) < 2) break; //get which divisor will give a number between 1-2    
 } 
 $factor_d = $maximoyi_temp / $i;
 $factor_d = ceil($factor_d); //round up number to 2
 $maximoyi = $factor_d * $i; //get new max value for y
 if ( ($maximoyi/ $maximoyi_temp) > 2) $maximoyi = $maximoyi /2; //check if max value is too big, then split by 2

1

ES5 Javascript에서 이것을 필요로하는 사람을 위해 약간 레슬링을했지만 여기에 있습니다.

var min=52;
var max=173;
var actualHeight=500; // 500 pixels high graph

var tickCount =Math.round(actualHeight/100); 
// we want lines about every 100 pixels.

if(tickCount <3) tickCount =3; 
var range=Math.abs(max-min);
var unroundedTickSize = range/(tickCount-1);
var x = Math.ceil(Math.log10(unroundedTickSize)-1);
var pow10x = Math.pow(10, x);
var roundedTickRange = Math.ceil(unroundedTickSize / pow10x) * pow10x;
var min_rounded=roundedTickRange * Math.floor(min/roundedTickRange);
var max_rounded= roundedTickRange * Math.ceil(max/roundedTickRange);
var nr=tickCount;
var str="";
for(var x=min_rounded;x<=max_rounded;x+=roundedTickRange)
{
    str+=x+", ";
}
console.log("nice Y axis "+str);    

Toon Krijtje의 우수한 답변을 기반으로합니다.


1

이 솔루션은 내가 찾은 Java 예제를 기반으로합니다 .

const niceScale = ( minPoint, maxPoint, maxTicks) => {
    const niceNum = ( localRange,  round) => {
        var exponent,fraction,niceFraction;
        exponent = Math.floor(Math.log10(localRange));
        fraction = localRange / Math.pow(10, exponent);
        if (round) {
            if (fraction < 1.5) niceFraction = 1;
            else if (fraction < 3) niceFraction = 2;
            else if (fraction < 7) niceFraction = 5;
            else niceFraction = 10;
        } else {
            if (fraction <= 1) niceFraction = 1;
            else if (fraction <= 2) niceFraction = 2;
            else if (fraction <= 5) niceFraction = 5;
            else niceFraction = 10;
        }
        return niceFraction * Math.pow(10, exponent);
    }
    const result = [];
    const range = niceNum(maxPoint - minPoint, false);
    const stepSize = niceNum(range / (maxTicks - 1), true);
    const lBound = Math.floor(minPoint / stepSize) * stepSize;
    const uBound = Math.ceil(maxPoint / stepSize) * stepSize;
    for(let i=lBound;i<=uBound;i+=stepSize) result.push(i);
    return result;
};
console.log(niceScale(15,234,6));
// > [0, 100, 200, 300]


0

질문과 답변에 감사드립니다. Gamecat, 틱 범위를 반올림해야하는 방법을 어떻게 결정하는지 궁금합니다.

눈금 범위 = 21.9. 25.0이어야합니다.

이를 알고리즘 적으로 수행하려면 위의 알고리즘에 논리를 추가하여 더 큰 숫자에 대해이 스케일을 멋지게 만들어야합니까? 예를 들어 10 틱의 경우 범위가 3346이면 틱 범위는 334.6으로 평가되고 가장 가까운 10으로 반올림하면 350이 더 좋을 때 340이됩니다.

어떻게 생각해?


@Gamecat의 예에서는 334.6 => 0.3346이며 0.4로 가야합니다. 따라서 틱 범위는 실제로 400이 될 것입니다. 꽤 좋은 숫자입니다.
Bryan

0

@Gamecat의 알고리즘을 기반으로 다음과 같은 도우미 클래스를 생성했습니다.

public struct Interval
{
    public readonly double Min, Max, TickRange;

    public static Interval Find(double min, double max, int tickCount, double padding = 0.05)
    {
        double range = max - min;
        max += range*padding;
        min -= range*padding;

        var attempts = new List<Interval>();
        for (int i = tickCount; i > tickCount / 2; --i)
            attempts.Add(new Interval(min, max, i));

        return attempts.MinBy(a => a.Max - a.Min);
    }

    private Interval(double min, double max, int tickCount)
    {
        var candidates = (min <= 0 && max >= 0 && tickCount <= 8) ? new[] {2, 2.5, 3, 4, 5, 7.5, 10} : new[] {2, 2.5, 5, 10};

        double unroundedTickSize = (max - min) / (tickCount - 1);
        double x = Math.Ceiling(Math.Log10(unroundedTickSize) - 1);
        double pow10X = Math.Pow(10, x);
        TickRange = RoundUp(unroundedTickSize/pow10X, candidates) * pow10X;
        Min = TickRange * Math.Floor(min / TickRange);
        Max = TickRange * Math.Ceiling(max / TickRange);
    }

    // 1 < scaled <= 10
    private static double RoundUp(double scaled, IEnumerable<double> candidates)
    {
        return candidates.First(candidate => scaled <= candidate);
    }
}

0

위의 알고리즘은 최소값과 최대 값 사이의 범위가 너무 작은 경우를 고려하지 않습니다. 그리고이 값이 0보다 훨씬 크다면 어떻게 될까요? 그런 다음 0보다 큰 값으로 y 축을 시작할 수 있습니다. 또한 선이 그래프의 위쪽 또는 아래쪽에 완전히 배치되지 않도록하려면 "호흡 할 공기"를 제공해야합니다.

이러한 경우를 다루기 위해 위의 코드를 (PHP에서) 작성했습니다.

function calculateStartingPoint($min, $ticks, $times, $scale) {

    $starting_point = $min - floor((($ticks - $times) * $scale)/2);

    if ($starting_point < 0) {
        $starting_point = 0;
    } else {
        $starting_point = floor($starting_point / $scale) * $scale;
        $starting_point = ceil($starting_point / $scale) * $scale;
        $starting_point = round($starting_point / $scale) * $scale;
    }
    return $starting_point;
}

function calculateYaxis($min, $max, $ticks = 7)
{
    print "Min = " . $min . "\n";
    print "Max = " . $max . "\n";

    $range = $max - $min;
    $step = floor($range/$ticks);
    print "First step is " . $step . "\n";
    $available_steps = array(5, 10, 20, 25, 30, 40, 50, 100, 150, 200, 300, 400, 500);
    $distance = 1000;
    $scale = 0;

    foreach ($available_steps as $i) {
        if (($i - $step < $distance) && ($i - $step > 0)) {
            $distance = $i - $step;
            $scale = $i;
        }
    }

    print "Final scale step is " . $scale . "\n";

    $times = floor($range/$scale);
    print "range/scale = " . $times . "\n";

    print "floor(times/2) = " . floor($times/2) . "\n";

    $starting_point = calculateStartingPoint($min, $ticks, $times, $scale);

    if ($starting_point + ($ticks * $scale) < $max) {
        $ticks += 1;
    }

    print "starting_point = " . $starting_point . "\n";

    // result calculation
    $result = [];
    for ($x = 0; $x <= $ticks; $x++) {
        $result[] = $starting_point + ($x * $scale);
    }
    return $result;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.