🔖

Highcharts 問題集

2021/12/04に公開

ちょっと大げさなタイトルですが、最近のプロジェクトでhighchartsを使っているので、いろんな問題と解決法のまとめです。

ほんの一部の機能しか使っていないと思いますが、それでも結構奥深いものでした。。

コラムグラフのバーを対に表示する

コラムグラフのデフォルトの行為は、データシーリズごとにバーガ作られます。公式の例で言えば、こちらの降水グラフとなります。

例えば、そのデータを対で上下に表示したい、という時に、まずはスタックの設定をノーマルとして、それでマイナスのデータがあれば、対に表示できます。こちらが例です。

マイナスのデータをプラスに表示

上記の変更に一つ問題があります。降水量がマイナスになるわけがないですから、対に表示するとは言え、データそのものが間違っています。ここで解決したいのは:

  • データを対に表示
  • データは全部プラスに表示

ここで使うのがフォーマッターです。y軸の内容だけではんく、tooltipの中のデータもマイナスからプラスに修正します。

y軸に別のデータを追加したい

例えば気温のデータも追加したい、という場合は、y軸を追加します。

ここでyAxisのopposite属性をtrueにすることで左側に置くことができます。データシーリズではデータの表現タイプ(コラムかラインかとか)、対応するy軸をインデックスで調整可能。

難問:y軸の0を真ん中にしたい

これは大変困りました。今のサンプルで見たらわかると思いますが、データの幅が違うので、0が真ん中にきません。

これについていろんな解決方法がありますが、一番簡単なのは、y軸の最大値と最小値を再設定することです。

function alignAxisCenter(chart, name) {
    let axis = chart.yAxis.find(axis => axis.axisTitle.textStr == name);
    if (!axis) {
        return;
    }
    // let { dataMax, dataMin } = axis.getExtremes();
    // let maxVal = Math.max(Math.abs(dataMin), Math.abs(dataMax));
    let maxVal = Math.max(...axis.tickPositions);
    console.log('y axis max value', maxVal)
    // y軸の最大値と最小値を一緒(絶対値)にする
    axis.setExtremes(maxVal * -1, maxVal);
}

下記の通り、大体のケースはこれで対応できますが、一部極端な場合は対応できません。

例えば、グラフの上部にスペースを一定量で増加すると、highchartsが上下のバランス調整し、0が真ん中でなくなります。

難問続:y軸の0を真ん中にしたい

実際のプロジェクトで、上記のケースがありました。グラフ上部にカスタマイズのアイコンとかラベルを追加しているので、結局その関係で前節の方法が無効となります。

その時に最終武器を使います。tickPositionerです。公式の例

(よく分からんが表示されない)

公式の例だけでは我々の目的に達成できません。0を真ん中におきたいということは、目盛(tick)の数が3以上の奇数とのこと。tickPositionerは目盛の配列をリターンするので、目盛の数を決めておくと、絶対そうならなければなりません。

一般化可能な解決策はこれ:

const TICK_PRECISION = 2;
// y軸のデータ最大値をかける倍率、目盛の上限を設定
const AXIS_MAX_EXPAND_RATE = 1.2;
// y軸の分割を奇数で決める
function setAxisTicks(axis, tickCount) {
    // データの最大値を取得し、上下に余裕を取るために一定の乗率で最大値を増加
    let maxDeviation = (Math.max(Math.abs(axis.dataMax), Math.abs(axis.dataMin)) * AXIS_MAX_EXPAND_RATE).toPrecision(TICK_PRECISION);
    // 小数対応のため一回整数に
    let wholeMaxDeviation = maxDeviation * 10 ** TICK_PRECISION;
    // halfCountは0上下のtickの数
    let halfCount = Math.floor(tickCount / 2);
    // 最大値と一番近いhalfCountで割れる値を探し、それをy軸の最大値にする
    while (wholeMaxDeviation % halfCount != 0) {
        wholeMaxDeviation++;
    }
    // halfCount分の一の値を計算
    let baseTick = (wholeMaxDeviation / halfCount) / 10 ** TICK_PRECISION;
    let tickPositions = [];
    for (let i = -halfCount; i <= halfCount; i++) {
        // 小数計算に精度の問題があるので、toFixedで不要な部分を切り捨て
        let tick = parseFloat((baseTick * i).toFixed(TICK_PRECISION));
        tickPositions.push(tick);
    }
    // console.log('tick positions', tickPositions);
    return tickPositions;
}

実際の効果を見ると早いかも:

ちなみに、tickAmountという属性もありますが、あれは数だけを決めることができますが、0がどこに来るかはデータ次第です。また、tickPositionerのコールバックは優先されます。

ただ、tickAmountを上記の例に合わせると、細かい修正ができます。上記の例には、左右のy軸の目盛の数が合いません。それで、右の気温の目盛の線も左とずれています。ここで右の軸にtickAmountを指定すると解決できます。

単純に一方の軸の目盛線を隠したいなら、gridLineWidthを0に変えればいけます。デフォルトは1です。目盛数が同じのときは重なるので0に変えなくても効果は変わりません。

まとめ

グラフを出すにはhighchartsを使うのが結構おすすめです。基本的な設定とかは公式のドキュメントを読むと大体分かるが、少し特別なケースになるとstack overflowとか、公式のフォーラムとかで探さないといけません。それでも解決できない可能性がありますが、という時は自分で作り込みするしかないと。今回はいくつか特別なケースをあげて、解決策をまとめました。

他にも色々と問題と解決策がありますが、今日は一旦これで切り上げます。

Discussion