Excelなどでグラフを書くときは、目盛りは自動で決めてくれます。

ヤフーファイナンスなどを見ていても、グラフの目盛りはそれなりに見やすく決まります。

どうやって計算しているのだろうと思って調べてみても、

あまり計算方法は示されていません。

 

グラフと目盛りの間隔(新潟大学)

 

こういう記事がありましたので参考にして作ってみました。

 

考え方としては

1.データの最小値min、最大値maxと その範囲(R = max - min) に対してN区分ぐらいの目盛りにしたい。ヤフーファイナンスを見ていると、3~6区分なので、5区分ぐらいとする。

2.スケール(scale ≒ 範囲R / 区分N )の値は、1, 2, 5の3通り(参考サイトの通り)とする。

3.スケールを決めたら、それに合わせて、グラフの最小値、最大値を決める。

です。

 

スケールの決め方も含めて、最大値最小値の決め方を以下に示します。

数値例 max = 2458,  min = 2316 とともに示します。

 

A.範囲の桁を調べて1から10未満の数値にする。

R = max - min = 2458 - 2316 = 142

log10(R) = 2.15,      int(log10(R)) = 2

keta=10^int(log10(max - min)) = 100

R / keta = 142 / 100 = 1.42

 

B. R / keta を基数kisu 1, 2, 5, 10  に切り上げる。さらにその上の基数を選ぶ。

基数で割って整数にしたときに1未満になる時、その基数が切り上げた後の値となる。

int(X/kisu) < 1 なら X = kisu

切上(R/keta) = 2 ==> 5

 

C.桁をかけて元に戻し、それを1/10にしたものをスケールとする。Bで上の基数を選んだうえで10分の1にしているので大雑把に5区分ぐらいになる。

scale = 切上(R/keta) * keta / 10 = 5 * 100 / 10 = 50

 

D.グラフの最大値gmax、最小値gminをスケールの倍数に決める。

gmax = scale * int(max/scale + 1) = 50 * int(2458/50 + 1) = 50 * int(49.16 + 1) = 2500

gmin = scale * int(min/scale) = 50 * int(2316 / 50) = 50 * int(46.32) = 2300

この時、データの値がもともとスケールの倍数であった時は、グラフの端にデータが来るのでそれを避けるために余裕を持たせるなら、たとえば余裕率yoyu = 0.01 を使って

gmax = scale * int(max/scale + 1 + yoyu) =  50 * int(49.16 + 1.01) = 2500

gmin = scale * int(min/scale - yoyu) =  50 * int(46.32 - 0.01) = 2300

この例では変化はありません。

 

以上の計算で、

max = 2458,  min = 2316  から gmax = 2500,  gmin = 2300,  scale = 50

が求まりました。区分は4区分になりました。

 

この方法で30例ほどヤフーファイナンスのグラフの最大最小とスケールを計算してみたところ、すべて一致しました。

 

こころの相談室おうみ / 小倉

 

'VBA

Sub main()

    Dim min as DOuble, max as Double, scale as Double

    min = 2316

    max = 2458

    RoundMinMax(min, max, scale)

    msgbox min & "," & max & "," & scale

End Sub

 

Function RoundMinMax(min As Double, max As Double, sca As Double)
    Dim yoyu As Double
    yoyu = 0.01 '最大最小がスケールの倍数とぴったりにならないよう余裕を持たせる
    sca = getScaleNum(min, max)
    min = sca * Int(min / sca - yoyu) 'スケールの倍数に合わせる
    max = sca * Int(max / sca + 1 + yoyu) 'スケールの倍数に合わせる
End Function

Function getScaleNum(min As Double, max As Double) As Double
    Dim keta As Double, res As Double
    Dim kisu As Variant, i As Long
    '最終的に分割数の半分程度の分割目指す
    Const Ndiv = 10# '分割数
    kisu = Array(1, 2, 5, 10, 20) '基数
    
    res = max - min '間隔。
    keta = 10# ^ Int(Log(res) / Log(10)) 'それの位を調べる。
    res = res / keta '1以上から10未満に変換
    For i = LBound(kisu) To UBound(kisu) '基数を大きくしていき
        If Int(res / kisu(i)) < 1 Then '基数で割ると1未満になる時
            res = kisu(i + 1) * keta / Ndiv 'その次の基数を採用(分割数の半分程度の分割目指すから)
            Exit For
        End If
    Next i
    getScaleNum = res
End Function

 

'結果は同じですが以下のようにした方がいいかと思います。

'分割を先にする。累進度合いを定数にして外に出す。

'分割を5にして、累進をしないというのでもよいと思うので。

Function getScaleNum(min As Double, max As Double) As Double
    Dim keta As Double, res As Double
    Dim kisu As Variant, i As Long
    '最終的に分割数の半分程度の分割目指す
    Const Ndiv = 10# '分割数
    Const NRuisin = 1
    kisu = Array(2, 5, 10, 20, 50) '基数
    
    res = (max - min) / Ndiv '間隔を10分の一にする。
    keta = 10# ^ Int(Log(res) / Log(10)) 'それの位を調べる。
    res = res / keta '1以上から10未満に変換
    For i = LBound(kisu) To UBound(kisu) '基数を大きくしていき
        If Int(res / kisu(i)) < 1 Then '基数で割ると1未満になる時
            res = kisu(i + NRuisin) * keta  'その次の基数を採用(分割数の半分程度の分割目指すから)
            Exit For
        End If
    Next i
    getScaleNum = res
End Function