Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Svg exporting #232

Merged
merged 1 commit into from
Mar 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"react-addons-shallow-compare": "^0.14.7",
"react-addons-update": "^0.14.7",
"react-dom": "^0.14.7",
"save-svg-as-png": "^1.0.2",
"save-svg-as-png": "git://github.com/nsonnad/saveSvgAsPng.git#clean-svg-fonts",
"sizeof": "^1.0.0",
"sugar-date": "^1.5.1"
},
Expand Down
193 changes: 22 additions & 171 deletions src/js/components/ChartExport.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,38 +35,6 @@ var ChartExport = React.createClass({
};
},

createSVGFile: function(chart) {
// Returns a valid svg string representing the chart that can be opened in graphics software

// retrieve the current size
var size = chart.getBoundingClientRect();
var scale_factor = 2;

chart = this._addIDsForIllustrator(chart);

// get a useable SVG string using SVG Crowbar
var svg_content = this.createSVGContent(chart);

// set the width of the svg in pixels for output
var svg_string = svg_content.source[0]
.split('width="100%"').join('width="'+size.width*scale_factor+'"')
.split('height="100%"').join('height="'+size.height*scale_factor+'"');

return "data:text/svg," + svg_string;
},

embedCSS: function(type) {
if (type == "add") {
d3.selectAll("." + this.props.svgWrapperClassName)
.append("style")
.attr("type","text/css")
.html("\n<![CDATA["+this.styleStringify(document.styleSheets)+"]]>\n");
}
else {
d3.selectAll("." + this.props.svgWrapperClassName + " style").remove();
}
},

componentDidMount: function() {
var enableSvgExport;
var chartNode = null;
Expand All @@ -78,17 +46,9 @@ var ChartExport = React.createClass({
.getElementsByClassName(this.props.svgWrapperClassName)[0]
.getElementsByClassName("renderer-svg")[0];

try {
var svgHref = this.createSVGFile(chart);
enableSvgExport = true;
chartNode = chart;
} catch (e) {
enableSvgExport = false;
}

this.setState({
chartNode: chart,
enableSvgExport: enableSvgExport
enableSvgExport: true
});
},

Expand All @@ -100,55 +60,6 @@ var ChartExport = React.createClass({
this.setState({ chartNode: chart });
},

styleStringify: function(styleSheets) {
var sheet, style_strings = [], rules, rule;

for (var i = styleSheets.length - 1; i >= 0; i--) {
sheet = styleSheets[i];
if (sheet) {
if (sheet.rules !== undefined) {
rules = sheet.rules;
}
else {
rules = sheet.cssRules;
}

for (var j = rules.length - 1; j >= 0; j--) {
if (rules[j].style) {
rule = this._cleanRulesForIllustrator(rules[j]);
style_strings.unshift(rule.cssText);
}
}
}
}

return style_strings.join("\n");
},

_cleanRulesForIllustrator: function(rule) {
// Adobe Illustrator freaks out with certain style declarations this strips those out
var styleDec = rule.style;
var pd;
for (var i = styleDec.length - 1; i >= 0; i--) {
orig_prop = styleDec[i];
orig_dec = styleDec[orig_prop];

pd = {
prop: orig_prop,
dec: orig_dec
};

pd = this._cleanRuleOfExtraFonts(pd);

rule.style[pd.prop] = pd.dec;

rule.cssText = rule.cssText.split(orig_prop + ":" + orig_dec + ";")
.join(pd.prop + ":" + pd.dec + ";");
}

return rule;
},

_addIDsForIllustrator: function(node) {
var chart = d3.select(node);
var classAttr = "";
Expand All @@ -169,65 +80,6 @@ var ChartExport = React.createClass({
return chart[0][0];
},

_cleanRuleOfExtraFonts: function(pd) {
// Adobe Illustrator freaks out with fallback fonts, remove fallback fonts
if(pd.prop == "font-family") {
pd.dec = pd.dec.split(",")[0];
}

return {"prop":pd.prop,"dec":pd.dec};
},

createSVGContent: function(svg) {
/*
Copyright (c) 2013 The New York Times

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

//via https://github.com/NYTimes/svg-crowbar

var prefix = {
xmlns: "http://www.w3.org/2000/xmlns/",
xlink: "http://www.w3.org/1999/xlink",
svg: "http://www.w3.org/2000/svg"
};

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">';

svg.setAttribute("version", "1.1");

var defsEl = document.createElement("defs");
svg.insertBefore(defsEl, svg.firstChild); //TODO .insert("defs", ":first-child")

var styleEl = document.createElement("style");
defsEl.appendChild(styleEl);
styleEl.setAttribute("type", "text/css");

// removing attributes so they aren't doubled up
svg.removeAttribute("xmlns");
svg.removeAttribute("xlink");

// These are needed for the svg
if (!svg.hasAttributeNS(prefix.xmlns, "xmlns")) {
svg.setAttributeNS(prefix.xmlns, "xmlns", prefix.svg);
}

if (!svg.hasAttributeNS(prefix.xmlns, "xmlns:xlink")) {
svg.setAttributeNS(prefix.xmlns, "xmlns:xlink", prefix.xlink);
}

//TODO atually get the styles
var styles = "";

var source = (new XMLSerializer()).serializeToString(svg).replace('</style>', '<![CDATA[' + this.styleStringify(document.styleSheets) + ']]></style>');

return {svg: svg, source: [doctype + source]};
},

_makeFilename: function(extension) {
var filename = this.props.data.reduce(function(a, b, i) {
if (a.length === 0) {
Expand All @@ -242,48 +94,47 @@ var ChartExport = React.createClass({
].join(".");
},

createSVGOutput: function(callback) {
// updates the download links with the data-uris and download file names
var filename = this._makeFilename("svg");
//clone the svg so that the image creation and svg creation don't conflict
return {
download: filename,
href: this.createSVGFile(this.state.chartNode)
};
},

downloadPNG: function() {
filename = this._makeFilename("png");
saveSvgAsPng.saveSvgAsPng(this.state.chartNode, filename, { scale: 2.0 });
},

downloadSVG: function() {
var output = this.createSVGOutput();
_autoClickDownload: function(filename, href) {
var a = document.createElement('a');
a.download = output.download;
a.href = output.href;
a.download = filename;
a.href = href;
document.body.appendChild(a);
a.addEventListener("click", function(e) {
a.parentNode.removeChild(a);
});
a.click();
},

downloadSVG: function() {
var filename = this._makeFilename("svg");
var chart = this._addIDsForIllustrator(this.state.chartNode);
var autoClickDownload = this._autoClickDownload;
saveSvgAsPng.svgAsDataUri(chart, {
cleanFontDefs: true,
fontFamilyRemap: {
"Khula-Light": "Khula Light",
"Khula-Regular": "Khula",
}
}, function(uri) {
autoClickDownload(filename, uri);
});
},

downloadJSON: function() {

json_string = JSON.stringify({
chartProps: this.props.model.chartProps,
metadata: this.props.model.metadata
}, null, "\t")

var a = document.createElement('a');
a.download = this._makeFilename(".json")
a.href = "data:text/json;charset=utf-8," + encodeURIComponent(json_string);
document.body.appendChild(a);
a.addEventListener("click", function(e) {
a.parentNode.removeChild(a);
});
a.click();
var filename = this._makeFilename("json");
var href = "data:text/json;charset=utf-8," + encodeURIComponent(json_string);
this._autoClickDownload(filename, href);
},

setAdvancedOptionState: function() {
Expand Down
5 changes: 0 additions & 5 deletions src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,4 @@ document.addEventListener("DOMContentLoaded", function() {
/>,
container );

//append the current timestamp to the end of the stylesheets on load so that the fonts will load
Array.prototype.slice.call(document.querySelectorAll('link[rel="stylesheet"]'))
.forEach(function(el){
el.setAttribute("href",el.getAttribute("href").split("?")[0] + "?t=" + (new Date().getTime()));
});
});
4 changes: 4 additions & 0 deletions src/styl/chart-renderer.styl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ svg

.d4
font-size $em_size
.leftAxis path.domain
display none
.xAxis path.domain
display none
.axis
.tick
font-family $font-sans-light
Expand Down