그래프 그릴 때 엑셀이나 Matlab, OPNET 등에서는 적절한 간격과 값이 저절로 부여된다.
그런데 막상 스스로 그리려고 하니 그 값을 어떻게 만들어내는건지 방법을 금방 떠올리기 어려웠다.
뭐지 로그를 어떻게 잘 이용하나? 정도 밖에 딱히 구체적인 생각이 나지 않는다.
일단 창 크기만 가지고 일정한 간격으로 잘라 그리도록 해서 다른 테스트부터 먼저 해보고
적절한 간격을 구하는 방법에 대한 부분은 뒤로 미뤘는데 이제 다시 생각해볼 시간이 됐다.
일단 몇가지 스레드를 참고.
http://stackoverflow.com/questions/237220/tickmark-algorithm-for-a-graph-axis
http://stackoverflow.com/questions/326679/choosing-an-attractive-linear-scale-for-a-graphs-y-axis
http://stackoverflow.com/questions/361681/algorithm-for-nice-grid-line-intervals-on-a-graph
그런데 소숫점일 경우는 어떻게 처리해야할지 생각해보면 보다 효과적인 방법이 있을 것 같아서
더 찾아보니 이게 나왔다. ACM 알고리즘이라 꽤 모범적인 방법일거라고 보고 해석해본다.
http://lists.gnu.org/archive/html/octave-bug-tracker/2011-09/pdfJd5VVqNUGE.pdf
* Readability, Round-off 고려
반올림이라는 말이 적절한지 모르겠는데 확장적인 의미로 쓰이는 것 같다.
읽기 좋은 간격은 1, 2, 5, 10의 n승의 배수인게 좋다. 그리고 최소 최대값도 고려해야 한다.
좋은 간격:
-0.5, 0.0, 0.5, 1.0, ...
1.24, 1.26, 1.28, ...
100.0, 200.0, 300.0, ..., etc
안좋은 간격:
-1.0, 4.0, 9.0, ...
1.2, 1.31, 1.42, ...
0.0. 4.0, 5.0, 12.0, ..., etc
소개된 스케일 함수(scale1, scale2, scale3)를 보면 scale1은 인터벌 개수가 적절하게 바뀌고 scale2는 인터벌 개수가 주어진 대로이다. scale3은 logarithm 형태의 그래프에 사용한다.
scale1(xMin, xMax, n, xMinP, xMaxP, dist)
// xMin: 실제 최소값
// xMax: 실제 최대값
// n: 요청된 그리드 개수
// xMinP: 최소 바운드값
// xMaxP: 최대 바운드값
// dist: 간격
코드가 포트란이라서 적당히 C++ 코드로 변경해 봄.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | double scale1( double fMin, double fMax, int nCount, double & fMinBound, double & fMaxBound) { double fInterval = 0.0f; double fDist = 0.0f; double fCount = ( double )nCount; double fBound = 0.0f; double fMult = 0.0f; int nIntervalLog = 0; int nSelection = 0; int nMult = 0; double fSqrArr[3] = {1.414214f, 3.162278f, 7.071068f}; double fIntArr[4] = {1.0f, 2.0f, 5.0f, 10.0f}; double fLimit = 0.00002f; fInterval = (fMax - fMin) / fCount; nIntervalLog = ( int ) log10 (fInterval); if (fInterval < 1) nIntervalLog--; fBound = fInterval / pow (10.0f, nIntervalLog); for (nSelection = 0; nSelection < 3; nSelection++) { if (fBound < fSqrArr[nSelection]) break ; } fDist = fIntArr[nSelection] * pow (10.0f, nIntervalLog); // get fMinBound fMult = fMin / fDist; nMult = ( int )fMult; if (fMult < 0) nMult = nMult - 1; if ( abs (( double )nMult + 1.0f - fMult) < fLimit) nMult = nMult + 1; fMinBound = fDist * ( double )nMult; // get fMaxBound fMult = fMax / fDist; nMult = ( int )fMult + 1; if (fMult < (-1.0)) nMult = nMult - 1; if ( abs (fMult + 1.0f - ( double )nMult) < fLimit) nMult = nMult - 1; fMaxBound = fDist * ( double )nMult; return fDist; } |
제대로 바꾼건가...대충 값은 맞게 나오는거 같은데?
다만 이 Readability라는게 상당히 애매한 개념이라, 예를 들어 0~899 까지라면
0~1000으로 바뀌어 나오는데 난 0~900가 더 적당하다는 생각이 든다. 간격의 문제일까?
어쨌건 처음에 상당히 골치아픈 문제라고 생각했던 것에 비해
상당히 간단한 코드로 nice한 결과값이 나오긴 한다.
Dist를 구하기 위한 배열을 살짝 조정해주면 Round-off의 정도를 변경할 수도 있을 것 같고.