[Sass] sassだけで株価ローソク足チャートを描く方法


2020/05/22

今回は軽量&簡単なローソク足チャートをSassのコーディングだけで描いてみようと言う野心的な試みです。

巷では
D3.jsChart.jsなどの高性能かつ汎用の目的で使える描画用のjsライブラリーが手軽に使えるようになりました。

これまでのわざわざフルスクラッチで...ましてやスタイルファイルだけでグラフを作ろうということ自体、かなりマイノリティーなお話になっています。

とは言え、jsスクリプトを使わず、html内のグラフを純粋なcssのだけで記述できることの最もメリットは、
クライアント側で描画時のJavascriptのレンダリング処理が不要になる、という点が挙げられます。

どうしてもチャート系のjsライブラリを使うと、グラフの処理にリソースが割かれて、挙動が重くなってしまう宿命にあります。

遅延ロードさせることでこれを回避することも可能ですが、今度は遅延ロードをさせるためのライブラリーやスクリプトの実装などに色々と頭を悩まさなくてはいけなくなります。

また、チャート操作用のjsライブラリーは使い方を覚えるまでにかかる学習コストがあり、なかなか思うようなチャートが描けるようになるまで時間もかかります。

このようにチャート系のjsライブラリーの利用には、高性能な分、色々とデメリットの面も考えさせられます。

他方、cssスタイルファイルだけでどうにかなるのなら、描画時間が短縮になり、遅延ロードのような煩雑になる仕組みを考えなくても済みます。

余程の凝りに凝ったアニメーションを行いたい訳でないのなら、cssでも簡単なアニメーションを装飾させることも可能です。

前置きはさておき、簡単なローソク足の株価チャートを具体的なコーディングと共に説明していきます。


描画用のデータ

以前、sassの配列をどう使うべきか?という内容をポストしました。

この考え方にしたがって、とりあえず以下のように株価の日足データの1つだけ与えておきます。

            
            // とある会社のある日の日足データ
$stock_data: [
    // [日付, 始値, 高値, 低値, 終値]
    ['2020-05-20', 310, 321, 299, 314]
];
        
では、このデータからローソク足をひとつ描画できるまでをやってみます。


ローソク足

データ1つで実験

描画はdiv要素をベースに利用します。

幅は
100%で自動計算の値を使いますが、高さはひとまず200pxにしておきます。

            
            <div class="hiashi-chart">
    <div class="hiashi-1"></div>
</div>
        
赤の四角で陽線、青で陰線を与えることにします。

まずは日足のデータを元に、手作業でローソク足を描画して雛形を作成します。

            
            .hiashi-chart {
    width: 100%;
    height: 200px;
    padding: 30px;
    border: 2px solid black;

    .hiashi-1 {
        $rect_width: 20px;
        $rect_hieght: 314px - 310px; // ① 終値 - 始値
        $rect_color: red; // ② 陽線
        $line_width: 1px;
        $line_height: 321px - 299px; // ③ 高値 - 終値
        $line_top_pos: 314px - 321px; // ④ 終値 - 高値

        position: relative;
        width: $rect_width;
        height: $rect_hieght;
        background-color: $rect_color;

        &::after {
            position: absolute;
            left: ($rect_width + $line_width) / 2;
            top: $line_top_pos;
            content: '';
            height: $line_height;
            border-left: $line_width solid $rect_color;
        }
    }
}
        
これを描画すると以下です。

ローソク足一つだと、まだチャートへったくれもないのです。

ここからローソク足チャートを描画するためにコードを拡張していきましょう。

さて、コードの中身でもコメントで書き込んでいるように、データからローソクを描画する際に計算をするポイントが何個かあります。

            
            ①:
    ローソクの高さ = (終値 - 始値)の絶対値
②:
    陽線か陰線か = (終値 - 始値)での正負符号
③:
    ヒゲの高さ = (高値 - 終値)の値
④:
    ローソクのtopからみたヒゲの描画開始位置 = (終値 - 高値)の値。
    今回は終値が基準(=ローソクのtop位置)だったが、
    始値が基準の場合には(始値 - 高値)の値を計算する
        
主に以上のような点を考慮しながらデータをローソク足に読み取っていきます。

@mixin化

同じようなスタイルを使い回す場合には@mixinを作っておくと便利です。

先ほどのソースコードで、
@mixinを使って書き直すと、

            
            @mixin generic_hiashi(
    $hiashi-index,
    $rect_width: 20px,
    $rect_hieght: 0,
    $rect_color: red,
    $line_width: 1px,
    $line_height: 0,
    $line_top_pos: 0
) {
    .hiashi-#{$hiashi-index} {
        position: relative;
        width: $rect_width;
        height: $rect_hieght;
        background-color: $rect_color;
        &::after {
            position: absolute;
            left: ($rect_width + $line_width) / 2;
            top: $line_top_pos;
            content: '';
            height: $line_height;
            border-left: $line_width solid $rect_color;
        }
    }
}

.hiashi-chart {
    width: 100%;
    height: 200px;
    padding: 30px;
    border: 2px solid black;
    @include generic_hiashi(1, 20px, 314px - 310px, red, 1px, 321px - 299px, 314px - 321px);
}
        
とできます。

こちらの方が、スタイルをスッキリした見通しの良い形式で管理できます。

データ読み込み後のパラメータ自動計算

先ほどのミクスインに株価のデータの配列がそのまま引数として渡せるように改良します。

            
            @mixin generic_hiashi(
    $hiashi-index,
    $rect_width: 20px,
    $line_width: 1px,
    $hiashi_data: ['2000-01-01', 0, 0, 0, 0]
) {
    .hiashi-#{$hiashi-index} {
        position: relative;
        width: $rect_width;
        $diff: nth($hiashi_data, 5) - nth($hiashi_data, 2);
        height: abs($diff * 1px);
        $rect_color: red;
        @if $diff < 0 {
            $rect_color: blue;
        }
        background-color: $rect_color;
        &::after {
            position: absolute;
            content: '';
            left: ($rect_width - $line_width) / 2;
            top: (max(nth($hiashi_data, 2), nth($hiashi_data, 5)) - nth($hiashi_data, 3)) * 1px;
            height: (nth($hiashi_data, 3) - nth($hiashi_data, 4)) * 1px;
            border-left: $line_width solid $rect_color;
        }
    }
}

.hiashi-chart {
    width: 100%;
    height: 300px;
    padding: 30px;
    border: 2px solid black;
    @include generic_hiashi(1, 20px, 1px, ['2020-05-20', 310, 321, 299, 314]);
}
        

配列からローソク足の生成

上記までの内容でローソク足一つの描画を試しただけでした。

複数のローソク足をまとめて描画するには、日付で時間軸(x軸)上に、株価でy軸上にチャート要素に対して相対的な位置決めをする必要があります。

x軸の平行移動

日足データは日付を1つ変数として持っています。

ですが、型が
stringなのでそのままではx軸として扱うことができません。

そこで数値型として扱うときに都合の良い
Epoch時間に間接的に変換してあげます。

例えば、
ここのサイトなどで換算していただけるとわかるように、ミリ秒単位なしのEpoch Timeの例を挙げると、

            
            + 2020-01-01 00:00:00(国際標準時) ==> 1577804400
+ 2000-01-01 00:00:00(国際標準時) ==> 946652400
+ 2020-01-01 00:00:00(国際標準時) ==> 1589986800
        
Epoch時間は国際標準時間換算ですので、実際の日本時間はここから+9時間された値となります。

日足の場合には、日付だけが判別できれば良いので、今回は時差を考慮せず国際標準時のまま利用します。

ミリ秒なしのEpoch時間で便利なのが、
1時間=36001日=86400ですのでこれを時間軸に利用します。

また、Epoch時間の換算は自前での換算関数実装がわりと複雑です。

予め日足データにEpoch時間を組み込んでおきます。

とりあえず10日分くらいの描画実験用データを与えておきましょう。

            
            // とある会社のある日の日足データ
$stock_data: [
    // [日付, Epoch時間(GMT), 始値, 高値, 低値, 終値]
    ['2020-05-20', 1589900400, 310, 321, 299, 314],
    ['2020-05-19', 1589814000, 305, 319, 301, 311],
    ['2020-05-18', 1589727600, 304, 325, 302, 312],
    ['2020-05-15', 1589468400, 310, 311, 297, 304],
    ['2020-05-14', 1589382000, 300, 309, 294, 298],
    ['2020-05-13', 1589295600, 299, 305, 292, 295],
    ['2020-05-12', 1589209200, 295, 301, 285, 290],
    ['2020-05-11', 1589122800, 291, 302, 286, 290],
    ['2020-05-08', 1588863600, 284, 300, 276, 280],
    ['2020-05-07', 1588777200, 285, 288, 273, 288],
];
        
とりあえずデータ配列は日付に対して降順で並べています。

このデータを元に時間軸で並行移動できるように再度scssコードにx軸座標を計算して返す補助的な関数を用意します。

            
            @function configure-xaxis($source_array, $base_width: 100%) {
    $first: nth(nth($source_array, 1), 2);
    $last: nth(nth($source_array, length($source_array)), 2);
    @return [$first, $last, $base_width , $base_width * 86400 / ($first - $last)];
}

@function epochtime-to-xpos($epoch_date, $x_pos_unit) {
    $first: nth($x_pos_unit, 1);
    $last: nth($x_pos_unit, 2);
    $x_pos: ($last - $epoch_date) / ($last - $first) * nth($x_pos_unit, 3);
    @return $x_pos;
}
        
深くは解説しませんが、configure-xaxis関数は、元のデータ配列からx軸の描画範囲を読み出して、設定パラメータを配列で吐き出します。

epochtime-to-xpos関数で、描画する先の座標を計算して返すものです。

y軸方向の補正

y軸方向にも、チャートの描画範囲に応じた座標を計算する関数が必要になります。

            
            @function configure-yaxis($min_limit: 0, $max_limit: 100, $base_hight: 100%) {
    @return [$min_limit, $max_limit, $base_hight, $base_hight / ($max_limit - $min_limit)];
}

@function currentprice-to-ypos($current_price, $y_pos_unit) {
    $min: nth($y_pos_unit, 1);
    $max: nth($y_pos_unit, 2);
    $y_pos: ($max - $current_price) / ($max - $min) * nth($y_pos_unit, 3);
    @return $y_pos;
}
        
これも先程のx軸で用いる補助関数と同じような考え方ですが、y軸方向の描画範囲(下限・上限)は手動で値を割り振るようにしています。

ミクスインの修正

それではx軸、y軸の補正関数を組み込んで、再度ミクスインを修正します。

            
            @mixin generic_hiashi(
    $hiashi-index,
    $rect_width: 20px,
    $line_width: 1px,
    $hiashi_data: ['2000-01-01', 946652400, 0, 0, 0, 0],
    $xaxis_config: [100, 0, 100px, 100%], // x軸方向の設定パラメータ
    $yaxis_config: [0, 100, 300px, 1px]  // y軸方向の設定パラメータ
) {
    .hiashi-#{$hiashi-index} {
        position: absolute;
        left: epochtime-to-xpos(nth($hiashi_data, 2), $xaxis_config);
        width: $rect_width;
        top: currentprice-to-ypos(max(nth($hiashi_data, 3), nth($hiashi_data, 6)), $yaxis_config);
        $diff: nth($hiashi_data, 6) - nth($hiashi_data, 3);
        height: abs($diff * nth($yaxis_config, 4));
        $rect_color: red;
        @if $diff < 0 {
            $rect_color: blue;
        }
        background-color: $rect_color;

        &::after {
          position: absolute;
            content: '';
            left: ($rect_width - $line_width) / 2;
            top: (max(nth($hiashi_data, 3), nth($hiashi_data, 6)) - nth($hiashi_data, 4)) * nth($yaxis_config, 4);
            height: (nth($hiashi_data, 4) - nth($hiashi_data, 5)) * nth($yaxis_config, 4);
            border-left: $line_width solid $rect_color;
        }

        &::before {
            position: absolute;
            font-size: 10px;
            content: nth($hiashi_data, 1);
            left: 0;
            top: 10px;
        }
    }
}

.hiashi-chart {
    position: relative;
    $chart_width: 600px;
    $chart_height: 200px;
    width: $chart_width;
    height: $chart_height;
    padding: 10px 40px;
    border: 2px solid black;

    $xaxis_configuration: configure-xaxis($stock_data, $chart_width);
    $yaxis_configuration: configure-yaxis(270, 330, $chart_height);

    $i: 1;
    @each $item in $stock_data {
        @include generic_hiashi($i, 20px, 1px, $item, $xaxis_configuration, $yaxis_configuration);
        $i: $i + 1;
    }
}
        
htmlのコードは以下のように変更です。

            
            <div class="hiashi-chart">
    <div class="hiashi-1"></div>
    <div class="hiashi-2"></div>
    <div class="hiashi-3"></div>
    <div class="hiashi-4"></div>
    <div class="hiashi-5"></div>
    <div class="hiashi-6"></div>
    <div class="hiashi-7"></div>
    <div class="hiashi-8"></div>
    <div class="hiashi-9"></div>
    <div class="hiashi-10"></div>
</div>
        
チャートは以下のようになりました。

実用上はまだまだ機能を盛り込んでいく必要がありますが、最低限のローソク足チャートが描画できました。


まとめ

今回はローソク足チャートで日足の描画するところまでを解説しました。

まだ軸にチックで刻みをいれたり、ラベルをいれたり、マウスでホバー時にイベントを行ったり...

ここからの応用して実用的なチャートにするにはまだまだ工夫が必要になります。

html側も手動でclass名を入力して各要素にスタイリングしています。

css側で
div:nth-child()を利用したら、もうすこし楽にローソク足チャートが出来そうです。

また気が向いたら、もっとローソク足チャートらしいものを作成して、このブログで紹介するかもしれません。

まだまだ改良の余地がありますが、この記事に載せたコードはいくらでも改変していただいて構いません。

お手元でカスタマイズしてオリジナルの株価チャートを自作しても面白いかもしれません。
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。