#SVGを使ってフルスクラッチでグラフを描画してみた

今回はSVGを使って、コーディングで直接グラフを描画する方法をご紹介します。

なぜSVGを使うか

Chart.jsやCanvas.jsなどグラフ描画ライブラリは色々ありますが、ライブラリでは描画の細かい調整に限界があるため、フルスクラッチでグラフを描画することにしました。 フルスクラッチのコーディングで描画する場合、SVGとCanvasの2方式がありますが、今回は後述のとおり画像拡大しても劣化しないベクター画像であるSVGを採用しています。

SVGとは

SVGとはScalable Vector Graphics(スケーラブル・ベクター・グラフィックス)の略で、画像フォーマットの一種です。 一般的なJPEGやPNGがビットマップデータなのに対し、SVGはXMLをベースにした二次元ベクターデータで画像を表現します。 このベクターデータとは「画像を、点の座標とそれを結ぶ線(ベクター、ベクトル)などの数値データをもとにして演算によって再現する方式」であり、拡大・縮小しても画質が損なわれないという利点があります。

SVGの描画要素いろいろ

SVGではXMLの要素として色々な表現をすることができます。それぞれの要素を組み合わせることで、グラフィックを表現していきます。

rect要素(矩形を描画する)

rect要素は長方形など矩形を描画することができます。

 <rect x="100" y="100" width="200" height="200"
        fill="yellow" stroke="navy" stroke-width="10"  />

サンプル四角形

主なコマンド

  • x : 矩形の左辺の X 座標(現在の利用座標系において Y 軸に平行な辺の X 座標の小さい方)。

  • y : 矩形の上辺の Y 座標(現在の利用座標系において X 軸に平行な辺の Y 座標の小さい方)。

  • width :  矩形の幅。

  • height : 矩形の高さ。

  • rx : 矩形の角を丸めるために用いられる楕円の X 軸半径の長さ。

  • ry : 矩形の角を丸めるために用いられる楕円の Y 軸半径の長さ。

参考: https://triple-underscore.github.io/SVG11/shapes.html#RectElement

path要素(外形線を描画する)

path要素は線を使って任意の図形を描画することができます。自由度が高いですが、単純な長方形を描画するならrect要素、円形の場合はcircle要素を使った方が簡単です。

  <path d="M20,50 L60,80 L100,50 L140,20 L180,50" fill="none" stroke="#000" stroke-width="3"/>

サンプルpath

主なコマンド

path要素はすべてのコマンドに対し、その相対版が存在します。(大文字は絶対座標, 小文字は相対座標を意味します)。相対版のコマンドにおけるすべての座標は、命令開始時の現在の点から相対座標になります。

  • M,m : 初期位置,位置のスキップ.
  • L,l : 直線を引く
  • H,h : 水平線を引く
  • V,v : 垂直線を引く
  • C,c,S,s,Q,q,T,t : 曲線を引く.端点と制御点とで曲線を指定する.
  • A,a : 円弧を引く
  • Z,z 直近のM位置まで直線を引いて線を閉じる.なお,座標を重ねただけでは線が閉じられた事にはならない.
  • B,b x軸の方向を設定する(他のコマンドによる描画に影響を及ぼす)
  • R,r Catmull-Romスプライン曲線を引く

参考: https://triple-underscore.github.io/SVG11/paths.html#PathElement

text要素(テキストを描画する)

text要素はその名の通り画像内にテキストを配置することができます。

 <text x="1" y="1" font-family="IPAゴシック" font-size="10">
   testtest
 </text>

参考: https://triple-underscore.github.io/SVG11/text.html#TextElement

上記要素でグラフを作ってみる

では実際にグラフを描いてみます。実は棒グラフの場合、上記の3要素だけで描画することができます。 この例はハードコーディングですが、各要素を動的に生成してあげる処理を自作すれば、自作グラフ描画ライブラリの出来上がりです!

    <svg height="120" version="1.1" width="310" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="font-family:'IPA Pゴシック'; overflow: hidden; position: relative; left: -0.5px;">
      <defs style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></defs>

      <!--  縦軸ラベル、目盛り線 -->
      <path fill="none" stroke="#aaaaaa" d="M31.0,12.0H279.0" stroke-width="0.5" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path>
      <text x="31.0" y="12.0" text-anchor="end" font-family="IPA Pゴシック" font-size="5px" stroke="none" fill="#888888" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); text-anchor: end; font-family: IPA Pゴシック; font-size: 5px; font-weight: normal;" font-weight="normal">
        <tspan dy="3.75" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);">20</tspan>
      </text>
      <path fill="none" stroke="#aaaaaa" d="M31.0,60.0H279.0" stroke-width="0.5" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path>
      <text x="31.0" y="60.0" text-anchor="end" font-family="IPA Pゴシック" font-size="5px" stroke="none" fill="#888888" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); text-anchor: end; font-family: IPA Pゴシック; font-size: 5px; font-weight: normal;" font-weight="normal">
        <tspan dy="3.75" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);">10</tspan>
      </text>
      <path fill="none" stroke="#aaaaaa" d="M31.0,108.0H279.0" stroke-width="0.5" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path>
      <text x="31.0" y="108.0" text-anchor="end" font-family="IPA Pゴシック" font-size="5px" stroke="none" fill="#888888" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); text-anchor: end; font-family: IPA Pゴシック; font-size: 5px; font-weight: normal;" font-weight="normal">
        <tspan dy="3.75" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);">0</tspan>
      </text>

      <!-- 棒グラフ描画 rect要素-->
      <rect x="37.2" y="73.2" width="18.5" height="34.8" rx="0" ry="0" fill="#ee82ee" stroke="none" fill-opacity="1" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); fill-opacity: 1;"></rect>

      <text x="46.5" y="110" text-anchor="middle" font-family="IPA Pゴシック" font-size="5px" stroke="none" fill="#888888" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); text-anchor: middle; font-family: IPA Pゴシック; font-size: 5px; font-weight: normal;" font-weight="normal" transform="matrix(1,0,0,1,0,5.5)">
        5/23
      </text>

      <rect x="68.2" y="53.2" width="18.5" height="54.8" rx="0" ry="0" fill="#ee82ee" stroke="none" fill-opacity="1" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); fill-opacity: 1;"></rect>
      <text x="77.5" y="110" text-anchor="middle" font-family="IPA Pゴシック" font-size="5px" stroke="none" fill="#888888" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); text-anchor: middle; font-family: IPA Pゴシック; font-size: 5px; font-weight: normal;" font-weight="normal" transform="matrix(1,0,0,1,0,5.5)">
         5/24
      </text>

    </svg>

サンプルpath

まとめ

ライブラリを使わず、直接SVGを描画するということでかなり手間がかかると思ったのですが、意外とあっさり書くことができました。 SVGで直接描画するという手段は選択としてありなのではないかと思います。ぜひ参考にしてみてください。