ProgramingTip

그래프에서 "좋은"격자 선 간격을위한 알고리즘

bestdevel 2020. 12. 1. 19:13
반응형

그래프에서 "좋은"격자 선 간격을위한 알고리즘


그래프 (차트)에 "멋진"격자 선을 만들려면 합리적으로 스마트 한 알고리즘이 필요합니다.

예를 들어 값이 10, 30, 72 및 60 인 막대 차트를 가정합니다. 다음을 알고 있습니다.

최소값 : 10 최대 값 : 72 범위 : 62

첫 번째 질문은 무엇에서 시작합니까? 이 경우 0은 값이지만 다른 데이터 세트를 유지하지 추측하고 있습니다.

그리드 최소값은 0 또는 범위 내의 데이터 최소값보다 낮은 "좋은"값이어야합니다. 또는 있습니다.

그리드 최대 값은 범위의 최대 값보다 "좋은"값이어야합니다. (예 : 실제 값에 관계없이 백분율을 표시하는 경우 0에서 100까지 가능합니다).

범위의 격자 선 (틱) 수는 값이 "좋음"(예 : 반올림)이고 차트 영역의 사용을위한 지정된 범위 내의 숫자 (예 : 3-8) 또는 지정합니다. 이 예에서 80은 차트 높이 (72/80)의 90 %를 사용하는 것이기 때문에 현명한 최대 값이되고 100은 더 많은 공간을 사용할 수 있습니다.

누구든지 이것에 대한 좋은 알고리즘을 알고 있습니까? 언어는 내가 필요한대로 구현할 것 관련이 없습니다.


CPAN은 여기 에서 구현을 제공합니다 (소스 링크 참조).

그래프 축에 대한 Tickmark 알고리즘 참조

참고로 샘플 데이터 :

  • 메이플 : 최소 = 8, 최대 = 74, 레이블 = 10,20, .., 60,70, Ticks = 10,12,14, .. 70,72
  • MATLAB : 최소 = 10, 최대 = 80, 레이블 = 10,20, .., 60,80

나는없는 무차별 대입 방법으로 받았습니다. 먼저 공간에 맞출 수있는 최대 눈금 수를 알아 내십시오. 값의 총 범위를 틱 수로 나눕니다. 이것은 최소 간격입니다. 이제 틱의 크기를 구하기 위해 밑이 10 인 로그를 계산 하고이 값으로 나눕니다. 1에서 10 사이의 범위가 끝나야합니다. 값보다 크거나 같은 반올림 수를 선택하고 이전에 계산 한 로그를 곱하면됩니다. 이것이 마지막 눈금 간격입니다.

Python의 예 :

import math

def BestTick(largest, mostticks):
    minimum = largest / mostticks
    magnitude = 10 ** math.floor(math.log(minimum, 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

편집 : "좋은"간격의 선택을 자유롭게 사용할 수 있습니다. 실제 틱 수가 최대 값보다 최대 2.5 배 적을 수 있기 때문에 한 댓글 작성자는 선택에 불만족하는 투표합니다. 다음은 좋은 간격에 대한 테이블을 정의하는 약간의 수정입니다. 이 예에서는 틱 수가 최대 값의 3/5 미만이 없음 선택 항목을 확장했습니다.

import bisect

def BestTick2(largest, mostticks):
    minimum = largest / mostticks
    magnitude = 10 ** math.floor(math.log(minimum, 10))
    residual = minimum / magnitude
    # this table must begin with 1 and end with 10
    table = [1, 1.5, 2, 3, 5, 7, 10]
    tick = table[bisect.bisect_right(table, residual)] if residual < 10 else 10
    return tick * magnitude

문제에는 두 가지가 있습니다.

  1. 관련된 규모의 순서를 결정하고
  2. 편리하게 반올림하십시오.

로그를 사용하여 첫 번째 부분을 처리 할 수 ​​있습니다.

range = max - min;  
exponent = int(log(range));       // See comment below.
magnitude = pow(10, exponent);

예를 들어 범위가 50-1200이면 지수는 3이고 크기는 1000입니다.

그런 다음 그리드에서 원하는 세분화 수를 결정하여 두 번째 부분을 처리합니다.

value_per_division = magnitude / subdivisions;

지수가 정수로 잘 렸기 때문에 이렇게 계산입니다. 경계 조건을 더 잘 처리하기 위해 지수 계산을 할 수 있습니다. 예를 들어int()너무 많은 세분화로 끝날 경우를 취하는 대신 반올림하여 사용할 수 있습니다.


다음 알고리즘을 사용합니다. 여기에 게시 된 다른 사람들과 유사하지만 C #의 첫 번째 예제입니다.

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

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

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

        // promote the MSD to either 1, 2, or 5
        if (magMsd > 5)
            magMsd = 10;
        else if (magMsd > 2)
            magMsd = 5;
        else if (magMsd > 1)
            magMsd = 2;

        return magMsd*magPow;
    }
}

다음은 JavaScript의 또 다른 구현입니다.

var calcStepSize = function(range, targetSteps)
{
  // calculate an initial guess at step size
  var tempStep = range / targetSteps;

  // get the magnitude of the step size
  var mag = Math.floor(Math.log(tempStep) / Math.LN10);
  var magPow = Math.pow(10, mag);

  // calculate most significant digit of the new step size
  var magMsd = Math.round(tempStep / magPow + 0.5);

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

  return magMsd * magPow;
};

데이터 세트의 주어진 최소값과 최대 값에 대해 멋진 축 생일과 멋진 틱을 반환하는 목적을 작성했습니다.

- (NSArray*)niceAxis:(double)minValue :(double)maxValue
{
    double min_ = 0, max_ = 0, min = minValue, max = maxValue, power = 0, factor = 0, tickWidth, minAxisValue = 0, maxAxisValue = 0;
    NSArray *factorArray = [NSArray arrayWithObjects:@"0.0f",@"1.2f",@"2.5f",@"5.0f",@"10.0f",nil];
    NSArray *scalarArray = [NSArray arrayWithObjects:@"0.2f",@"0.2f",@"0.5f",@"1.0f",@"2.0f",nil];

    // calculate x-axis nice scale and ticks
    // 1. min_
    if (min == 0) {
        min_ = 0;
    }
    else if (min > 0) {
        min_ = MAX(0, min-(max-min)/100);
    }
    else {
        min_ = min-(max-min)/100;
    }

    // 2. max_
    if (max == 0) {
        if (min == 0) {
            max_ = 1;
        }
        else {
            max_ = 0;
        }
    }
    else if (max < 0) {
        max_ = MIN(0, max+(max-min)/100);
    }
    else {
        max_ = max+(max-min)/100;
    }

    // 3. power
    power = log(max_ - min_) / log(10);

    // 4. factor
    factor = pow(10, power - floor(power));

    // 5. nice ticks
    for (NSInteger i = 0; factor > [[factorArray objectAtIndex:i]doubleValue] ; i++) {
        tickWidth = [[scalarArray objectAtIndex:i]doubleValue] * pow(10, floor(power));
    }

    // 6. min-axisValues
    minAxisValue = tickWidth * floor(min_/tickWidth);

    // 7. min-axisValues
    maxAxisValue = tickWidth * floor((max_/tickWidth)+1);

    // 8. create NSArray to return
    NSArray *niceAxisValues = [NSArray arrayWithObjects:[NSNumber numberWithDouble:minAxisValue], [NSNumber numberWithDouble:maxAxisValue],[NSNumber numberWithDouble:tickWidth], nil];

    return niceAxisValues;
}

다음과 같이 메서드를 호출 할 수 있습니다.

NSArray *niceYAxisValues = [self niceAxis:-maxy :maxy];

축 설정 :

double minYAxisValue = [[niceYAxisValues objectAtIndex:0]doubleValue];
double maxYAxisValue = [[niceYAxisValues objectAtIndex:1]doubleValue];
double ticksYAxis = [[niceYAxisValues objectAtIndex:2]doubleValue];

축 눈금 수를 제한하려는 경우 다음을 수행하십시오.

NSInteger maxNumberOfTicks = 9;
NSInteger numberOfTicks = valueXRange / ticksXAxis;
NSInteger newNumberOfTicks = floor(numberOfTicks / (1 + floor(numberOfTicks/(maxNumberOfTicks+0.5))));
double newTicksXAxis = ticksXAxis * (1 + floor(numberOfTicks/(maxNumberOfTicks+0.5)));

코드의 첫 번째 부분은 여기 에서 계산을 기반으로 멋진 그래프 축 눈금과 엑셀 그래프와 눈금을 계산합니다. 모든 종류의 데이터 세트에서 훌륭하게 작동합니다. 다음은 iPhone 구현의 예입니다.

여기에 이미지 설명 입력


C #의 약간 더 완전한 Util 클래스 인 위의 Mark에서 가져 왔습니다. 또한 첫 번째 및 마지막 틱을 계산합니다.

public  class AxisAssists
{
    public double Tick { get; private set; }

    public AxisAssists(double aTick)
    {
        Tick = aTick;
    }
    public AxisAssists(double range, int mostticks)
    {
        var minimum = range / mostticks;
        var magnitude = Math.Pow(10.0, (Math.Floor(Math.Log(minimum) / Math.Log(10))));
        var residual = minimum / magnitude;
        if (residual > 5)
        {
            Tick = 10 * magnitude;
        }
        else if (residual > 2)
        {
            Tick = 5 * magnitude;
        }
        else if (residual > 1)
        {
            Tick = 2 * magnitude;
        }
        else
        {
            Tick = magnitude;
        }
    }

    public double GetClosestTickBelow(double v)
    {
        return Tick* Math.Floor(v / Tick);
    }
    public double GetClosestTickAbove(double v)
    {
        return Tick * Math.Ceiling(v / Tick);
    }

}
With ability to create an instance ,but if you just want calculate and throw it away:   
double tickX = new AxisAssists(aMaxX - aMinX, 8).Tick;

또 다른 아이디어는 축의 범위가 값의 범위가 눈금을 적절한 위치에 두는 것입니다. 즉, 7에서 22까지 다음을 수행합니다.

[--- | ---- | ---- | -]
       10 15 20

눈금 간격 선택에 관해서는 10 ^ x * i / n 형식의 숫자를 제안합니다. 여기서 i <n 및 0 <n <10입니다.이 목록을 생성하고 정렬하면 가장 큰 숫자를 찾을 수 있습니다. 이진 검색을 사용하여 value_per_division (adam_liss에서와 같이)보다 작습니다.


저는 " 차트 축에서 최적의 스케일링을위한 알고리즘 "의 저자입니다 . 이전에는 trollop.org에서 호스팅되었지만 최근에 도메인 / 블로깅 엔진을 이동했습니다.

관련 질문에 대한답변을 참조하십시오 .


여기에서 이미 사용 가능한 답변에서 많은 영감을 얻었으며 여기에 C로 구현 한 것이 있습니다 ndex. 배열에 확장 성이 내장되어 있습니다.

float findNiceDelta(float maxvalue, int count)
{
    float step = maxvalue/count,
         order = powf(10, floorf(log10(step))),
         delta = (int)(step/order + 0.5);

    static float ndex[] = {1, 1.5, 2, 2.5, 5, 10};
    static int ndexLenght = sizeof(ndex)/sizeof(float);
    for(int i = ndexLenght - 2; i > 0; --i)
        if(delta > ndex[i]) return ndex[i + 1] * order;
    return delta*order;
}

R에서는

tickSize <- function(range,minCount){
    logMaxTick <- log10(range/minCount)
    exponent <- floor(logMaxTick)
    mantissa <- 10^(logMaxTick-exponent)
    af <- c(1,2,5) # allowed factors
    mantissa <- af[findInterval(mantissa,af)]
    return(mantissa*10^exponent)
}

여기서 범위 인수는 도메인의 최대 최소값입니다.


다음은 그리드 간격 (max-min)/gridLinesNumber을 아름다운 값 으로 반올림하기 위해 작성한 javascript 함수 입니다. 어떤 숫자로도 작동합니다. 자세한 내용은 요점참조하여 작동 방식과 호출 방법을 알아보세요.

var ceilAbs = function(num, to, bias) {
  if (to == undefined) to = [-2, -5, -10]
  if (bias == undefined) bias = 0
  var numAbs = Math.abs(num) - bias
  var exp = Math.floor( Math.log10(numAbs) )

    if (typeof to == 'number') {
        return Math.sign(num) * to * Math.ceil(numAbs/to) + bias
    }

  var mults = to.filter(function(value) {return value > 0})
  to = to.filter(function(value) {return value < 0}).map(Math.abs)
  var m = Math.abs(numAbs) * Math.pow(10, -exp)
  var mRounded = Infinity

  for (var i=0; i<mults.length; i++) {
    var candidate = mults[i] * Math.ceil(m / mults[i])
    if (candidate < mRounded)
      mRounded = candidate
  }
  for (var i=0; i<to.length; i++) {
    if (to[i] >= m && to[i] < mRounded)
      mRounded = to[i]
  }
  return Math.sign(num) * mRounded * Math.pow(10, exp) + bias
}

ceilAbs(number, [0.5])다른 번호를 호출 하면 다음과 같이 반올림됩니다.

301573431.1193228 -> 350000000
14127.786597236991 -> 15000
-63105746.17236853 -> -65000000
-718854.2201183736 -> -750000
-700660.340487957 -> -750000
0.055717507097870114 -> 0.06
0.0008068701205775142 -> 0.00085
-8.66660070605576 -> -9
-400.09256079792976 -> -450
0.0011740548815578223 -> 0.0015
-5.3003294346854085e-8 -> -6e-8
-0.00005815960629843176 -> -0.00006
-742465964.5184875 -> -750000000
-81289225.90985894 -> -85000000
0.000901771713513881 -> 0.00095
-652726598.5496342 -> -700000000
-0.6498901364393532 -> -0.65
0.9978325804695487 -> 1
5409.4078950583935 -> 5500
26906671.095639467 -> 30000000

코드를 실험 하기 위해 바이올린확인하십시오 . 대답의 코드, 요점과 바이올린은 대답에 주어진 것을 사용하고 있습니다.


VB.NET 차트에서 눈금을 올바르게 표시하려는 경우 Adam Liss의 예제를 사용했지만 십진수 유형의 변수에서 전달하는 최소 및 최대 눈금 값을 설정할 때 확인하십시오. (단일 또는 이중 유형이 아님) 그렇지 않으면 눈금 표시 값이 소수점 8 자리와 같이 설정됩니다. 예를 들어 최소 Y 축 값을 0.0001로 설정하고 최대 Y 축 값을 0.002로 설정 한 차트가 1 개 있습니다. 이 값을 차트 개체에 싱글로 전달하면 0.00048000001697801, 0.000860000036482233 ...의 눈금 값을 얻습니다.이 값을 십진수로 차트 개체에 전달하면 0.00048, 0.00086 ...의 멋진 눈금 값을 얻습니다. ..


파이썬에서 :

steps = [numpy.round(x) for x in np.linspace(min, max, num=num_of_steps)]

참고 URL : https://stackoverflow.com/questions/361681/algorithm-for-nice-grid-line-intervals-on-a-graph

반응형