【Sass実践勉強会】cssだけで折れ線チャートを描く方法


2021/06/05
蛸壺の技術ブログ|cssだけで折れ線チャートを描く方法

基本的にCSSだけでは折れ線が描けないので、Canvas要素やSVG画像を使って線を作成するしかないのですが、いつ頃からか
linear-gradientを利用して擬似的に斜線を描く『裏ワザ』がネタとしてチラホラ目にするようになりました。

今回はこの裏ワザで折れ線チャートが果たして上手く描けるのかを検証してみたいと思います。


基礎の基礎〜linear-gradientで斜線を1本引く

linear-gradientは通常背景色にグラディエーションを付けるときに利用する属性です。

使い方はグラディエーションを付ける方向を指定し、そこから色の推移を決める位置をパーセンテージなどの値で決めていくことでグラディエーション色を定義できます。

つまりは斜線を
「単色で極端に狭いグラディエーション」と捉え直すことで、直線が引けるのがこのテクニックのキモです。

簡単な例としてCssとHtmlで例を挙げてみますと、

            
            .psudo-line-rt {
    margin: 0;
    padding: 0;
    width: 300px;
    height: 200px;
    background-image: linear-gradient(to right top, transparent 49.5%, black 49.5%, black 50.5%, transparent 50.5%);
}
.psudo-line-lt {
    margin: 0;
    padding: 0;
    width: 300px;
    height: 200px;
    background-image: linear-gradient(to left top, transparent 49.5%, black 49.5%, black 50.5%, transparent 50.5%);
}
        
でスタイルを与えておいて、以下のhtml要素に適用にしたとします。

            
            <div class="psudo-line-rt"></div>
<div class="psudo-line-lt"></div>
        
これを描画すると以下です。

線の太さは対角線の距離の1%で計算できるので、
(3002+2002)(0.5050.495)3.6\sqrt(300^2 + 200^2) * (0.505 - 0.495) \simeq 3.6[px]程度になっています。

つまり、この方法で斜線の太さを制御しようとした場合、htmlブロック要素の縦横サイズと、グラディエーションの区切り値だけで決まるわけです。

ブロック要素のサイズを使った線の引き方の例外として水平方向・垂直方向の直線の描き方は単純に細い四角の塗りつぶすだけになります。

            
            .psudo-line-h {
    margin: 0;
    padding: 0;
    width: 300px;
    height: 3px;
    background: black;
}
.psudo-line-v {
    margin: 0;
    padding: 0;
    width: 3px;
    height: 200px;
    background: black;
}
        
            
            <div class="psudo-line-h"></div>
<div class="psudo-line-v"></div>
        
つまり水平線・垂直線はline-gradientを除外することで対応することができます。

これだけでも十分折れ線になっている気がしますが、もっと汎用性の高い折れ線作図方法をSassを使ってカスタマイズしてみます。


より実用的な折れ線チャートの作成

まず今回の自作CSS折れ線チャートの基本的な概要を示した図が以下です。

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

今回はチャート描画用の要素を横方向に100分割させて短冊状のエリア割を行い、先程説明した斜線を引くブロック要素を描画するデータ点の2点から配置して折れ線とします。

まず結論・結果から言わせていただくと、
linear-gradientによる折れ線チャートは使いものにならない、ということです。

100点のデータに対して、x軸を100分割して、y軸は0から9の整数をランダムにとった関数を折れ線プロットした一例が以下の結果です。

この結果を見ると分かるように、細かいブロック要素の中でlinear-gradiantで擬似的に発生した線がえらく霞んでいます。

こればブロックを細かくすればするほど斜線は潰れて角々しい折れ線になっていって、期待したほど綺麗に表示されないようです。

これならばCanvas要素にお絵描きしたほうがまだ綺麗です。

折角なのでScssコードを付けます

今回のやり方は全然ダメっぽかったのですが、そもそもSassの力添えがなければ生Cssでブロック要素を一つ一つスタイリングしていくことも困難でした。

話のネタついでの紹介までに今回の折れ線描画に使ったScssファイル(style.scss)を以下に示します。

            
            @use 'sass:math';

$raw_ydata: [
    1,4,5,6,7,6,2,3,4,8,
    3,3,2,1,2,7,4,6,3,2,
    4,3,4,5,6,1,2,4,9,6,
    6,5,3,8,6,5,0,6,5,4,
    2,3,5,8,5,6,6,3,2,1,
    6,3,2,8,6,3,2,0,9,4,
    4,3,2,1,2,5,7,6,4,2,
    6,7,9,3,4,1,2,7,4,6,
    0,9,6,4,5,8,4,6,9,2,
    5,2,0,5,4,8,7,2,1,4
];

@mixin generic_line(
    $line_index,
    $chart_width: 600px,
    $chart_height: 200px,
    $rect_width: 6px,
    $line_width: 2px,
    $data
) {
    .line-#{$line_index - 1} {
        position: absolute;
        margin: 0;
        padding: 0;
        left: $rect_width * ($line_index - 1);
        width: $rect_width;
        top: $chart_height * (1 - nth($data, $line_index + 1) / 10);
        $diff: nth($data, $line_index + 1) - nth($data, $line_index);
        $rect_height: $chart_height * abs($diff) / 10;
        height: $rect_height;

        $ratio1: $line_width/$rect_height;
        $ratio2: $rect_height/$chart_height;
        $ratio3: $chart_width/$chart_height;
        $line_ratio: $line_width / 1px * $ratio2 * $ratio1 * 100%;
        $former_pos: 50% - ($line_ratio / 2);
        $latter_pos: 50% + ($line_ratio / 2);

        @if $diff > 0 {
            background-image: linear-gradient(to left top, transparent $former_pos, black $former_pos, black $latter_pos, transparent $latter_pos);
        } @else if $diff < 0 {
            top: $chart_height * (1 - nth($data, $line_index) / 10);
            background-image: linear-gradient(to right top, transparent $former_pos, black $former_pos, black $latter_pos, transparent $latter_pos);
        } @else {
            height: $line_width / $ratio3;
            background: black;
        }
    }
}

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

    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 99 {
        @include generic_line($i, $chart_width, $chart_height, 6px, 6px, $raw_ydata);
    }
}
        
またスタイル付けして使う場合の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-2"></div>
            <!-- サフィックス番号の繰り返しなので中略 -->
            <div class="line-99"></div>
        </div>
    </body>
</html>
        
以上、気になったから検証してみて、駄目そうだったネタでした。

Sassの可能性を広げるために、まだまだCssだけで描くチャートへの挑戦ネタも継続していきたいと思っております。興味がございましたら、また本ブログを覗いてみてください🙇


参考サイト

CSSで斜線を描画する

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

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