1. ホーム
  2. algorithm

[解決済み] グラフのY軸に魅力的なリニアスケールを選択する

2023-04-12 19:45:19

質問

私たちのソフトウェアで、棒グラフ(または折れ線グラフ)を表示するコードを少し書いています。 すべてうまくいっています。 困っているのは、Y 軸のラベル付けです。

呼び出し元は、Y スケールにどの程度細かくラベル付けしてほしいかを私に伝えることができますが、私は、quot; attractive" のような方法で正確にラベル付けすることに行き詰っているようです。 私は、quot;attractive" を説明できませんし、おそらくあなたもそうでしょうが、私たちはそれを見ればわかりますよね?

ということは、もしデータポイントが

   15, 234, 140, 65, 90

そして、ユーザはY軸に10個のラベルを要求し、紙と鉛筆で少し細工をすると出てきます。

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

つまり、(0 を含まない)10 個のラベルがあり、最後のラベルは最高値 (234 < 250) を超えて広がっており、25 個ずつの "nice" 増加しているのです。 もしラベルを8枚要求されたら、30枚の増分が良かったかもしれませんね。

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

9は難しいな。 8か10のどちらかを使って、十分近いということにすればよかったかもしれませんね。 また、いくつかのポイントがマイナスの場合はどうすればいいのでしょうか?

Excelはこの問題にうまく取り組んでいるのがわかりますね。

これを解くための汎用的なアルゴリズム(多少のブルートフォースでも構いません)をご存知の方はいらっしゃいますか? 早くやる必要はないのですが、見栄えは良くしたいです。

どのように解決するのですか?

ずっと前に、私はこれをうまくカバーするグラフモジュールを書きました。灰色の塊を掘り下げると、次のようになります。

  • データの下限と上限を決定します。(下限=上限という特殊なケースに注意!
  • 範囲を必要な目盛りの量に分割します。
  • ティックレンジを適切な量に切り上げます。
  • 下限と上限を適宜調整します。

例を見てみましょう。

15, 234, 140, 65, 90 with 10 ticks

  1. 下限値 = 15
  2. 上限値 = 234
  3. 範囲 = 234-15 = 219
  4. tick range = 21.9です。これは25.0であるべきです
  5. 新しい下限値 = 25 * round(15/25) = 0
  6. 新しい上限値 = 25 * round(1+235/25) = 250

つまり、範囲 = 0,25,50,...,225,250 となります。

以下の手順で、素敵なティックレンジを得ることができます。

  1. 10^x で割って、結果が 0.1 と 1.0 の間になるようにします (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.9 -> 0.9
    • <= 1.0 -> 1.0
  3. を10^x倍してください。
  4. この場合、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つ少なくなります。