notebook

都内でWEB系エンジニアやってます。

HTMLの中からSVGの部分を切り出す

テストカバレッジや静的コード解析ツールなどを使う場合、その時その時のスコアは分かりますが「数ヶ月前と比べてどうだったか」というのも知りたいですよね

そうなると継続的に指標を取ってどこかに記録しておく必要があると思います

イメージとしてはJenkinsのCoverage表示のPluginで出てくるグラフです

「ココらへんでテストコードガッツリ書いたので上がってる!」みたいなことがやりたいわけです

例としてカバレッジを上げましたが他にも独自で指標を取って変化を追いたいというモチベーションがずっとありました

なるべく簡単に実現したいですね

そのときに考えていたのが

  • 可視化用のサービスを使う(未調査)

    • そのためだけに使うのはちょっと大げさすぎる気が。。。
  • spreadsheetなどに記録してRedashやdataportalで可視化する

    • Redashはサーバ用意しないといけないのでそこまでするのは。。。
    • dataportalであればサーバも必要ないし良さそうな気がしますがすでにそういう手段はやったことがあるので今回は除外
    • READMEなどから見れたらいいかも
  • SVGを使ってグラフを作ってそれを表示させる

    • データ自体はjsonで持たせれば後でなんとでもできる
      • 先にデータ取得を始めるということも可能
    • 他サービスなどのAPIを使わなくてもなんとかできそう

みたいなことを考えていました

そうしているうちにSVGを使って試したくなってきてしまったので試してみることにしました

方法

最初jsonを読み込んでスクラッチでグラフっぽい感じの推移を表現しようと思ったのですがめちゃくちゃ時間が取られそうに思えたのでやめました

今回はGoogleChartsを使ってjsonからSVGグラフを表示させる部分は楽させてもらいました

流れ

  • 対象指標を定期的に計測し保存
    • git管理
    • jsonを保存
  • GoogleChartsでjsonからデータを読み込むhtmlを用意
  • ローカルでstaticサーバを起動させSVGを生成
  • puppeteerでアクセスしてSVGの箇所を切り出してファイルに保存
  • SVGをREADMEに設置するなりartifactに上げるなりしていつでも見れるようにする

定期的に計測

本来だったら下記のフォーマットになるようスクリプトなり何なりを書く

後の工程でGoogleChartに食わせるのでそれに合わせたjsonの形式にしておく

  • data.json
[
  ["Date", "hoge", "fuga", "piyo"],
  ["2019-06-20",  37.8, 80.8, 41.8],
  ["2019-06-21",  30.9, 69.5, 32.4],
  ["2019-06-22",  25.4,   57, 25.7],
  ["2019-06-23",  11.7, 18.8, 10.5],
  ["2019-06-24",  11.9, 17.6, 10.4],
  ["2019-06-25",   8.8, 13.6,  7.7],
  ["2019-06-26",   7.6, 12.3,  9.6],
  ["2019-06-27",  12.3, 29.2, 10.6],
  ["2019-06-28",  16.9, 42.9, 14.8],
  ["2019-06-29", 12.8, 30.9, 11.6],
  ["2019-06-30",  5.3,  7.9,  4.7]
]

GoogleChartsを使うHTMLを用意し表示させる

とりあえずサンプルをちょっといじって表示できるところまでのものを用意する

arrayToDataTableでjsonのデータをそのまま使うことができます

  • index.html
<html>
  <head>
    <!--Load the AJAX API-->
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">
      document.fonts.ready.then(() => {
        // Load the Visualization API and the corechart package.
        google.charts.load('current', {'packages':['line']});

        // Set a callback to run when the Google Visualization API is loaded.
        google.charts.setOnLoadCallback(drawChart);

        // Callback that creates and populates a data table,
        // instantiates the pie chart, passes in the data and
        // draws it.
      });

    function drawChart() {
      const httpRequest = new XMLHttpRequest();
      httpRequest.open('GET', 'data.json');
      httpRequest.send();
      httpRequest.onreadystatechange = function() {
        if (httpRequest.readyState == 4 && httpRequest.status == 200){

          var httpResult = JSON.parse(httpRequest.responseText);
          var data = google.visualization.arrayToDataTable(httpResult);

          var options = {
            title: 'Company Performance',
            curveType: 'function',
            legend: { position: 'bottom' },
            height: 300
          };

          var chart = new google.charts.Line(document.getElementById('chart_div'));

          chart.draw(data, google.charts.Line.convertOptions(options));
        }
      };
    }
    </script>
  </head>

  <body>
    <div id="chart_div"></div>
  </body>
</html>

staticサーバを起動する

pythonさえが入ってればとくに追加でインストールは不要

python -m http.server 8080
# python2の場合は
# python -m SimpleHTTPServer 8080

8080番にアクセスしてみる

f:id:swfz:20190630230846p:plain

jsonから持ってきたデータをもとにグラフが表示されました

puppeteerでSVGの部分だけ切り出す

まぁあとはよしなにやるだけですね

  • app.js
const puppeteer = require('puppeteer');
const fs = require('fs');

(async() => {
    const browser = await puppeteer.launch({
        args: [
            '--no-sandbox',
            '--disable-setuid-sandbox'
        ]
    });
    const page = await browser.newPage();

    page.on('pageerror', error => {
        console.log('Console Error', error.message)
    });
    page.on('response', response => {
        console.log(response.status(), response.url());
        if (300 > response.status() && 200 <= response.status()) return;
        console.warn('status error', response.status(), response.url());
    });
    await page.goto('http://192.168.30.94:8080/', { waitUntil: 'networkidle0' }); // ローカルサーバのURL
    await page.screenshot({path: 'graph.png'});
    const html = await page.$eval('svg', svg => {
        // SVGを画像として表示させるときに必要なため差し込んでいる
        svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
        return svg.outerHTML;
    });

    fs.writeFileSync('graph.svg', html);
    browser.close();
})();

outerHTMLでSVG要素を含めたHTMLを取得してきています

それをそのままファイルに保存するだけです

余談ですがデバッグ用にpage.onでコンソールに出てくる内容とhttp通信のログを表示するようにするコールバックを入れましたがこれは結構便利でした

実行

node app.js

SVG部分のみ取得することができるようになりました

はてなブログでsvgを上げることができなかったのですが表示的には画像と同じ見た目です

まとめ

ここまでできればあとは用途によっていろいろできるかなと思います

自分の場合はCircleCIで毎日指標を取ってjsonへ追記、SVG取得させていて

そのSVGをREADMEから読むようにしています

なので毎日自動で数値が更新されていくものをREADMEに行けば読むことができます

(まぁREADMEをそんなに見に行くかというと微妙ですが。。。

他にいい方法ありそうだな感はあるもののこれはこれで面白かったです

参考