upload/download

【D3.js】チャートをSVGやPNGでダウンロードできるようにするまでの長い道のり

結論

先に結論だけ書いておきます。
画像にしてダウンロードさせるのは、サーバーサイドでSVGをラスタに変換する方がずーと楽。

目標

・D3.jsで出力したチャートをSVGやPNGでダウンロードできるようにする。
・ダウロードしたチャートにはCSSの内容も反映されていること。

SVG・PNG共通

チャートに適用されているCSSの内容がダウンロードしたファイルにも反映されるように、すべてのパラメータをSVGの属性(atribute)に変換します。
その際、元のチャートには手をつけたくないので、ダウンロード実行時にいったんチャートエレメントをすべてコピーしgetComputedStyleメソッドを使ってチャートを構成する要素の算出スタイルをatributeに変換します。
そのままでは数が多すぎるので、空のSVGを作成して必要なスタイルを抽出するためのフィルターとして使います。

ぶっちゃけこの辺はSVG Crowbarのコードを丸パクリしてます。

また、svgの中に画像などをimageタグで挿入している場合、data URI スキーム化してsvg内に埋め込む必要があります。

download SVG

SVG要素をXMLSerializerを使ってシリアライズし、さらにblobオブジェクトに変換してダウンロードできる形にしています。
IEの場合はmsSaveBlob関数を使い、それ以外のブラウザの場合はdownload属性を付けたa要素にblobオブジェクトを渡しています。

・問題点
safariでは、a要素のdownload属性が実装されていないため、この方法だとページ遷移が発生してダウンロードされません。(ファイルがブラウザ上で開かれるます)

downloadPNG

SVGとしてダウンロードさせるより一手間かかります。
まず、シリアライズしたSVGをData URI schemeに変換しimgオブジェクトに読み込ませ、それをcanvasにdrawImage使って転写することでラスタライズします。
その後、canvasのtoDataURLメソッドを使って再度Data URI schemeに変換し、画像としてダウンロードさせます。

・問題点 safari
downloadSVGと同じく、ダウンロードされずブラウザ上で開かれてしまう。

・問題点 IE
svgをData URI schemeに変換しimgオブジェクトに読み込ませる際、base64に変換する必要があります。
(他ブラウザではそのまま渡せる)
base64に変換するwindow.btoaは、ユニコードに対応していないため元svgに日本語が含まれているとエラーとなります。従って、別途base64 encoderを実装する必要があります。

さらにimgオブジェクトに読み込ませることに成功したとしても、canvasのdrawImageメソッドを使ってimgオブジェクトを転写する際にSecurity Errorがでます。
これについては回避策が無いため、IEのみ別のアプローチが必要となります。
今回はサードパーティライブラリのcanvg.jsを使いIEの時だけSVGをパースしてcamvasに一から描画するという方法を使いました。
以下はIE対策を施したdownloadPNG

また、imgオブジェクトのonloadイベントが発火しなかったり、データの読み込みが終わる前に発火してしまったりと不安定な部分も。

サンプル

bl.ocks.orgにサンプルを掲載してあります。

Chart Downloader α – bl.ocks.org