Skip to content

Instantly share code, notes, and snippets.

@shimizu
Last active February 24, 2017 22:13
Show Gist options
  • Save shimizu/fda228d7229df38673cc6d5c12776cf4 to your computer and use it in GitHub Desktop.
Save shimizu/fda228d7229df38673cc6d5c12776cf4 to your computer and use it in GitHub Desktop.
SVG download module
license: mit
function createDownloader(){
var doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';
var prefix = {
xmlns: "http://www.w3.org/2000/xmlns/",
xlink: "http://www.w3.org/1999/xlink",
svg: "http://www.w3.org/2000/svg"
}
function exports(_selection) {
var svg = _selection.node()
var w = svg.clientWidth, h = svg.clientHeight
var _emptySvg,_emptySvgDeclarationComputed
var _copyChart
function createEmptySVG() {
_emptySvg = window.document.createElementNS(prefix.svg, 'svg');
window.document.body.appendChild(_emptySvg);
_emptySvgDeclarationComputed = getComputedStyle(_emptySvg);
}
function createCopySVG() {
_copyChart = d3.select("body")
.append("div")
.html(svg.innerHTML)
.node()
}
function traverse(obj){
var tree = [];
tree.push(obj);
visit(obj);
function visit(node) {
if (node && node.hasChildNodes()) {
var child = node.firstChild;
while (child) {
if (child.nodeType === 1 && child.nodeName != 'SCRIPT'){
tree.push(child);
visit(child);
}
child = child.nextSibling;
}
}
}
return tree;
}
function explicitlySetStyle(element) {
var cSSStyleDeclarationComputed = getComputedStyle(element)
var attributes = Object.keys(element.attributes).map(function(i){ return element.attributes[i].name } )
var i, len
var computedStyleStr = ""
for (i=0, len=cSSStyleDeclarationComputed.length; i<len; i++) {
var key=cSSStyleDeclarationComputed[i]
var value=cSSStyleDeclarationComputed.getPropertyValue(key)
if(!attributes.some(function(k){ return k === key}) && value!==_emptySvgDeclarationComputed.getPropertyValue(key)) {
computedStyleStr+=key+":"+value+";"
}
}
element.setAttribute('style', computedStyleStr);
}
function downloadSVG(source) {
var filename = "chart.svg";
var svg = d3.select(source).select("svg")
.attr("xmlns", prefix.svg)
.attr("version", "1.1")
.node()
var blobObject = new Blob([doctype + (new XMLSerializer()).serializeToString(svg)], { "type" : "text\/xml" })
if (navigator.appVersion.toString().indexOf('.NET') > 0){ //IE hack
window.navigator.msSaveBlob(blobObject, filename)
}else {
var url = window.URL.createObjectURL(blobObject)
var a = d3.select("body").append("a")
a.attr("class", "downloadLink")
.attr("download", "chart.svg")
.attr("href", url)
.text("test")
.style("display", "none")
a.node().click()
setTimeout(function() {
window.URL.revokeObjectURL(url)
a.remove()
}, 10);
}
}
function downloadPNG(source) {
var filename = "chart.png";
var svg = d3.select(source).select("svg")
.attr("xmlns", prefix.svg)
.attr("version", "1.1")
.node()
var data_uri = "data:image/svg+xml;utf8," + encodeURIComponent( (new XMLSerializer()).serializeToString(svg) )
var canvas = d3.select("body").append("canvas")
.attr("id", "drawingArea")
.attr("width", w)
.attr("height", h)
.style("display", "none")
var context = canvas.node().getContext("2d")
var download = function() {
if (navigator.appVersion.toString().indexOf('.NET') > 0){
canvg(document.getElementById('drawingArea'), (new XMLSerializer()).serializeToString(svg))
var dataURI2Blob = function(dataURI, dataTYPE) {
var binary = atob(dataURI.split(',')[1]), array = [];
for(var i = 0; i < binary.length; i++) array.push(binary.charCodeAt(i));
return new Blob([new Uint8Array(array)], {type: dataTYPE});
}
var data_uri = canvas.node().toDataURL("image/png")
var blobObject = dataURI2Blob(data_uri, "image/png")
window.navigator.msSaveBlob(blobObject, filename)
}else {
context.drawImage(img, 0, 0)
var url = canvas.node().toDataURL("image/png")
var a = d3.select("body").append("a").attr("id", "downloadLink")
a.attr("class", "downloadLink")
.attr("download", filename)
.attr("href", url)
.text("test")
.style("display", "none")
a.node().click()
setTimeout(function() {
window.URL.revokeObjectURL(url)
canvas.remove()
a.remove()
}, 10);
}
}
var img = new Image();
img.src = data_uri
if (navigator.appVersion.toString().indexOf('.NET') > 0){ //IE hack
d3.select(img).attr("onload", download)
}else{
img.addEventListener('load', download, false)
}
}
/**
* @callback downloadSVG
* @desc svgをダウンロードする
*/
_selection.downloadSVG = function(){
createEmptySVG()
createCopySVG()
var allElements = traverse(_copyChart)
var i = allElements.length;
while (i--){
explicitlySetStyle(allElements[i]);
}
downloadSVG(_copyChart)
d3.select(_copyChart).remove()
d3.select(_emptySvg).remove()
}
/**
* @callback downloadPNG
* @desc pngをダウンロードする
*/
_selection.downloadPNG = function(){
createEmptySVG()
createCopySVG()
var allElements = traverse(_copyChart)
var i = allElements.length;
while (i--){
explicitlySetStyle(allElements[i]);
}
downloadPNG(_copyChart)
d3.select(_copyChart).remove()
d3.select(_emptySvg).remove()
}
}
return exports
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<style>
html, body {
width: 100%;
height: 100%;
padding: 0px;
margin: 0px;
}
#chart {
width: 960px;
height: 400px;
}
.bg {
fill:white;
}
.bar {
fill:skyblue;
}
.axisLayer .axis .domain {
stroke: #333333;
}
.axisLayer .tick line {
stroke: #333333;
stroke-width: 1px;
}
.axisLayer .tick text {
fill: #333333;
font-size: 14px;
letter-spacing: .05em;
}
.axisLayer .label {
font-size: 12px;
font-weight: normal;
letter-spacing: .05em;
}
.backgroundLayer .grid line {
stroke: #cccccc;
stroke-dasharray: 3,3;
}
.backgroundLayer .grid .domain {
stroke: none;
}
</style>
</head>
<body>
<button id="update">updarte</button>
<div id="chart"></div>
<button id="downloadSVG">SVG Download</button>
<button id="downloadPNG">PNG Download</button>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/4.3.0/d3.min.js"></script>
<script src="//bl.ocks.org/shimizu/raw/0b526eab82263c8443108c33e454d221/nChart.js"></script>
<script src="createDownloader.js"></script>
<script>
!(function(){
"use strict"
var data = generateData()
var BarChart = nChart.createVStackBarChart()
.baseMargin({top:20, left:0, bottom:20, right:0})
.plotMargin({top:20, left:100, bottom:20, right:100})
.x(function(d){ return d["年"] })
.s(function(d){ return d["国名"] })
.y(function(d){ return d["値"] })
var Axis = nChart.createAxis()
.yAxisGridVisible(true)
var downloader = createDownloader()
var selector = d3.selectAll("#chart")
.datum(data)
.call(BarChart)
.call(Axis)
.call(downloader)
d3.select("#update").on("click", function(){
selector.update(generateData())
})
d3.select("#downloadSVG").on("click", selector.downloadSVG )
d3.select("#downloadPNG").on("click", selector.downloadPNG )
function generateData(){
return Array.prototype.concat.apply([], ["日本", "アメリカ", "フランス"].map(function(country){
var array = [2001,2002,2003,2004].map(function(year){
var value = ~~(Math.random() * 100)
return {"国名":country, "年":year, "値":value}
})
return array
}))
}
}());
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment