patorashのブログ

方向性はまだない

d3.js, c3.jsで描いたチャートを印刷する

d3.jsを皆さまはご存知でしょうか?data-driven-documentの頭文字をとって、d3.js。JavaScriptsvgでチャートを書くためのライブラリです。

d3js.org

そして、d3.jsをベースにチャートを生成しやすくしたライブラリがc3.js。

c3js.org

c3.jsで描いたチャートを印刷したかったのですが、d3.js・c3.js共に印刷とかファイルへの保存とかには対応していないみたいだったので、自分でなんとかしてみました。 元々は、highchartsというライブラリを使っていたのですが、有償ということもあって、別にものに変えたかったというのがありました。こちらは、印刷とかpngやpdfにして保存という機能が元々あって便利です。

www.highcharts.com

方針

チャートだけ印刷したかったので、以下の手順で実現することにしました。

  1. ポップアップウィンドウで別ウィンドウを開く。
  2. そこに親ウィンドウのsvgタグの内容をマルッとコピーして突っ込む。
  3. 印刷を起動する。
  4. 印刷が終わったらポップアップウィンドウを自動で閉じる。

とりあえずは、これでやりたいことは実現できました。

ハマったこと

IE11と旧Edgeの対応具合が悪い

d3.jsでチャートを書くときに、IE11でエラーが起きていました。d3.jsのバージョン5だと、fetchやpromiseを使っているので、それが存在しないということでエラーになっていたので、polyfillを入れました。 また、svgタグの内容をinnerHTMLで取得していたのですが、IE11だとsvgタグ内の内容をinnerHTMLで取得できないという仕様のようでした😨 それもまた、Googleが作っているinnersvg-polyfillというライブラリを入れることで解決しました。

最初はjQueryのouterHTMLを使っていましたが、jQueryに依存した状態になるのも微妙なので、XMLSerializerでNodeを文字列に変換するのがよいという記事を見かけたのでそうすることにしました。

c3.jsのcssを読み込まなければならない

c3.jsはチャートの表示の整形にcssを使っているので、それをポップアップウィンドウ側でも読み込まなければなりません。

IE11がafterprintイベントを拾わない

イベントリスナを定義して、afterprintでポップアップウィンドウを閉じていたのですが、閉じてくれないケースがありました。 かなり悩んだのですが、setTimeoutで閉じるという技を見かけたので、これを採用しました。

上記の問題を解決したコード

CoffeeScriptですが、以下のような感じになりました。

# self.svgには、d3.jsのSelectionオブジェクトが入っている
printWindow = window.open('', 'PrintChart', "width=#{self.svg.attr('width')}, height=#{self.svg.attr('height')}")
printWindow.document.writeln('<head><link rel="stylesheet" href="/css/c3.css" media="all" /></head>')
printWindow.document.writeln("<body class='c3'>#{new XMLSerializer().serializeToString(self.svg.node())}</body>")
printWindow.document.close()
printWindow.print()
setTimeout ->
  printWindow.close()
, 1000

動かしてみる

実際に開いた印刷画面はこちら。(Chromeにて)

f:id:patorash:20200603181202p:plain
c3.jsのチャートをポップアップウィンドウで表示して印刷

とりあえず印刷できるところまではいけたのでOK!!👍

他にもボチボチc3.jsのノウハウが溜まってきたので書いていこうと思います。