【Sass実践勉強会】続・cssだけで折れ線チャートを描く方法〜要素の回転と変形を使う


2021/06/06
蛸壺の技術ブログ|続・cssだけで折れ線チャートを描く方法〜要素の回転と変形を使う

前の回の試みで、cssのlinear-gradient属性を使って擬似的な直線を作成する方法からCSS純正折れ線チャートを描いてみましたが、使いものにならないことが分かりました。

そこで今回は前々から検証して見たかった試みの一つである、
「ブロック要素の回転と変形」をシンプルに繋いで折れ線グラフに仕立てられるかを本稿で解説していきます。


ブロック要素の回転と変形の基礎

まずは実線にみたてたブロック要素を折れ線として接続できるようにしたいので、まずは回転と変形の基礎を説明しておきましょう。

回転・transform

cssでの要素の回転はtransformが利用出来ます。

trandformを回転操作として使う基本的な用法として、

            
            transform: rotate(<傾ける角度>);
transform-origin: <左からの位置> <上からの位置>;
        
をセットで使います。

transform-originでブロック要素の相対位置から回転の原点を指定し、その原点に対し回転する角度(時計回りが正方向)を指定して利用します。

例えば以下のブロック要素の回転に関与するcssスタイルを適用すると、

            
            .黒線 {
    width: 200px;
    height: 4px;
    background: black;
}
.緑線 {
    width: 200px;
    height: 4px;
    background: green;
    transform: rotate(30deg);
    transform-origin: 0 50%;
}
.黄線 {
    width: 200px;
    height: 4px;
    background: yellow;
    transform: rotate(-30deg);
    transform-origin: 0 50%;
}
        
以下のような感じに回転することが出来ます。

この回転を折れ線チャートで利用する場合にもっとも重要な点は回転位置を
transform-origin: 0 50%;に指定することです。

合同会社タコスキングダム|蛸壺の技術ブログ

これで出来るだけ折れ線の継ぎ目を目だたないようにできると思います。

長さの修正

現在の点を次のデータ点へ接続する場合に、角度を計算して回しただけでは回転後に距離が足らない・伸び過ぎな状態に陥るので、回転後にブロック要素の幅(width)を再計算・補正しなくてはなりません。

この長さの補正計算はSassで与えるので、具体的な説明は後述します。


Sassの実装

では先ほどのテクニックを元にして本題の折れ線チャートを仕上げていきましょう。

まずはデータとチャート描画に役割分けでscssファイルを分割しておきます。

            
            $ tree
.
├── index.html
├── style.scss
└── _data.scss
        
Sassプロジェクトのファイル分割方法に関しては以下のリンク記事で説明していますので詳しくはそちらの方を確認ください。

では描画するデータ部分
_data.scssを以下のように与えておきます。

            
            $sim_xydata: [
[0,1],
[1,0],
[2,0],
//...中略
[99,7],
];
        
Sass式の2次元配列で(x,y)の折れ線チャート用データセットを記述しています。ここでは単純な例として、100点をサンプリング点としてy軸用のデータを0〜9のランダムな整数にしております。

なおファイルのフルバージョンは
こちらにおいておきます。中身が気になる方はご参考ください。

次にスタイルを割り当てるhtmlファイルも作成しておきます。

            
            <html>
<head><link rel="stylesheet" type="text/css" href="style.css"></head>
<body>
<div class="line-chart">
<div class="line-0"></div>
<div class="line-1"></div>
<!-- 中略 -->
<div class="line-99"></div>
</div>
</body>
</html>
        
以前のこちらの回で説明させていただいたようにラインを描く一定幅の区間を今回も100分割しておきます。

このindex.htmlのフルバージョンも
こちらに置いています。

さて、これでお膳立ては整いましたので、メインのscssファイル(
style.scss)を作成していきましょう。

            
            @use 'sass:math';
@use 'data';

@mixin generic_line(
    $line_index,
    $chart_width,
    $chart_height,
    $rect_width,
    $line_width,
    $data
) {
    .line-#{$line_index - 1} {
        position: absolute;
        box-sizing: border-box;
        margin: 0;
        padding: 0;
        left: $rect_width * ($line_index - 1);
        width: $rect_width;
        height: $line_width;

        top: $chart_height * (1 - nth(nth($data, $line_index), 2) / 10);
        $diff: nth(nth($data, $line_index + 1), 2) - nth(nth($data, $line_index), 2);
        $rect_height: $chart_height * abs($diff) / 10;

        //👇①回転後の補正距離の計算
        width: 1px*math.pow(math.pow($rect_width/1px,2)+math.pow($rect_height/1px,2), 0.5);
        //👇②回転角の計算
        $tilte_angle: math.atan(-1*$chart_height*$diff/10/$rect_width);

        @if $diff > 0 {
            background: blue;
            transform: rotate($tilte_angle);
            transform-origin: 0 50%;
        } @else if $diff < 0 {
            background: red;
            transform: rotate($tilte_angle);
            transform-origin: 0 50%;
        } @else {
            background: black;
        }

        &:before {
            display: block;
            position: absolute;
            border-radius: 50%;
            content: "";
            top: 0;
            left: 0;
            width: $line_width;
            height: $line_width;
            background: yellow;
        }
    }
}

.line-chart {
    $chart_width: 600px;
    $chart_height: 200px;
    $section_num: 99;

    display: block;
    position: relative;
    width: $chart_width;
    height: $chart_height;
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    background-color: darkgray;

    @for $i from 1 through $section_num {
        @include generic_line($i, $chart_width, $chart_height, $chart_width/$section_num, 2px, data.$sim_xydata);
    }
}
        
ここではモジュールの呼び出しやforループ処理などのSassテクニックを色々と利用しています。

それぞれ細かいテクニックを話すとキリがないのですので、ここでは以下の個別のトピックで説明した内容で詳細を確認ください。

ここでは、伸縮(コード内の①)と回転(コード内の②)の再計算の考え方を解説します。

まずは伸縮ですが現在の点
(xi,yi)(x_i,y_i)と次点(xi+1,yi+1)(x_{i+1},y_{i+1})との2乗平均距離diff\ell_\mathrm{diff}を計算させています。

diff=(xi+1xi)2+(yi+1yi)2\ell_\mathrm{diff} = \sqrt{ (x_{i+1} - x_i)^2 + (y_{i+1} - y_i)^2 }Eq. (1)

なお、関数パラメータに単位を含んだままでは計算が通りませんので、
1pxなどの単位元を割ったり掛けたりして計算できるようにしています。

なお、現時点で多次元での距離を返してくれる
math.hypot関数もあるようですが、この程度は関数を自作しても良いように思います。

もう一つ回転角
θrot\theta_\mathrm{rot}の計算は以下のように考えます。

θrot=arctan(αyi+1yixi+1xi)\theta_\mathrm{rot} = \arctan \Bigl( \alpha \frac{y_{i+1} - y_i}{x_{i+1} - x_i} \Bigr)Eq. (2)

ここでは仰角を割り出せるようにアークタンジェント関数を利用します。

ここでの式中の補正比率
α\alphaは、チャートの描画領域とブロック要素の比率にマイナスを掛けたものです。Sassの回転系は通常の回転系とは逆に時計回りを正にとっているので、マイナスを掛けていることに注意してください。

ともあれこれを計算することでデータの座標値から接続された折れ線がスタイルとして生成されます。

以上、説明が長くなりましたが、ここまでで完成した折れ線チャートは以下のようになります。

いい感じのcss純正折れ線チャートに仕上がりました。


まとめ

今回はSassの能力を最大限に活用した例としてcssだけで折れ線チャートを作ってみた話を順を追って説明していきました。

今回のブロック要素を回転・伸縮させてcss純正の折れ線グラフを作るというアイデア自体は全然新しいものではないのですが、cssコードのハードコーディングではあまりに複雑になりすぎるので、到底Sassなどのcss系フレームワークを力添えなしでは作成不可能なものでした。

改めてSassを使ったスタイル表現のポテンシャルを感じていただけたら幸いです。

参考サイト

【初心者必見】要素をくるっと回転!transform:rotate()の全て

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

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