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



Answers

다음은 내가 사용하고있는 PHP 예제입니다. 이 함수는 전달 된 min 및 max 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
)
Question

나는 우리 소프트웨어에 bar (또는 line) 그래프를 표시하기 위해 약간의 코드를 작성하고있다. 모든 것이 잘되고 있습니다. 저를 곤란하게 만든 것은 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

아홉은 까다 롭습니다. 어쩌면 그냥 8 또는 10 중 하나를 사용하고 충분히 가까이 전화 괜찮을 것입니다. 포인트 중 일부가 부정적 일 때 어떻게해야합니까?

Excel에서이 문제를 해결할 수 있습니다.

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




위의 알고리즘은 min과 max 값 사이의 범위가 너무 작은 경우를 고려하지 않습니다. 그리고이 값이 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;
}



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

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

"좋은"것을 정의합시다. 라벨이 떨어져 있다면 나는 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.

이제 find는 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"가 될 때까지




질문과 답변을 주셔서 감사합니다. 매우 도움이됩니다. Gamecat, 나는 틱 범위를 반올림해야하는 대상을 어떻게 결정하는지 궁금합니다.

틱 범위 = 21.9. 이 값은 25.0이어야합니다.

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

어떻게 생각해?




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



Links