block by nstrayer 6c702f7f57c08b01f3edfd7474cf77ba

like svg-crowbar, but for multiple svg elements!

Full Screen
  1. Paste multi-crowbar.js into the browser developer console
  2. Call multiCrowbar function and pass a selector for the container div
multiCrowbar(".my-chart")

if you want to include html labels or captions you can pass another selector as second argument

multiCrowbar(".my-chart", "h2.my-caption")

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Multi-Crowbar</title>
  </head>
  <style>

	.bookmarklet {
	  padding: 3px 8px;
	  font-size: 12px;
	  font-weight: bold;
	  text-decoration: none;
	  border-radius: 1em;
	  background: #2b8cbe;
	  color: white;
	}

	.center {
	padding: 70px 0;
    text-align: center;
	}


	</style>
  <body>
  <p class = "center">
	<a class="bookmarklet" href="javascript:(function(){s=document.createElement('script');s.type='text/javascript';s.src='https://cdn.rawgit.com/nstrayer/6c702f7f57c08b01f3edfd7474cf77ba/raw/ca571c282fb14922916b3281f72bba0442c528de/bookmarklet.js?v='+parseInt(Math.random()*99999999);document.body.appendChild(s);})();">Multi-Crowbar</a>&nbsp; <span>← Drag me to your bookmarks bar.</span>
<p>
  </body>
</html>

bookmarklet.js

var multiCrowbar = (function() {
/*
 * SVG Export
 * converts html labels to svg text nodes
 * will produce incorrect results when used with multi-line html texts
 *
 * Author: Gregor Aisch
 * based on https://github.com/NYTimes/svg-crowbar/blob/gh-pages/svg-crowbar-2.js
 */

window.d3 = null;
var s = document.createElement('script');
s.src = 'https://d3js.org/d3.v3.min.js';
document.getElementsByTagName('head')[0].appendChild(s);
function check() {
    if (    !window.d3) return setTimeout(check, 200);
    console.log('ready...');
    run_multi_crowbar()
}
check();
var doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "https://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';

return function(cont, label_selector) {

    var container = prompt("Enter a holder ID or Class for your vis. \nE.g. '#vizDiv' (Leave blank for the whole page)", "");
    container = container == "" ? "body": container
    
    var parent = d3.select(container),
        parent_n = parent.node();

    var out_w = parent_n.nodeName == 'body' ? parent_n.scrollWidth : parent_n.clientWidth,
        out_h = parent_n.nodeName == 'body' ? parent_n.scrollHeight: parent_n.clientHeight;

    var labels = label_selector ? parent.selectAll(label_selector) : null,
        nodes = parent.selectAll('path, line, rect, circle, text');
        // divs = parent.selectAll('.export-rect,.rect'),
        // circles = parent.selectAll('.circle');


    var svgNodes = parent.selectAll('svg');

    // 1. create a new svg container of the size of the page
    var out = parent.append('svg');

    // empty css declaration
    var emptyCSS = window.getComputedStyle(out.node());

    out.attr({ width: out_w, height: out_h })
        .style({ position: 'absolute', left: 0, top: 0 });

    var offsetTop = parent_n.getBoundingClientRect().top - parent_n.parentNode.getBoundingClientRect().top;

    var out_g = out.append('g').attr('id', 'svg');

    nodes.each(function() {
        var el = this,
            cur = el,
            curCSS,
            bbox,
            transforms = [];
        while (cur) {
            curCSS = getComputedStyle(cur);
            if (cur.nodeName == 'defs') return;
            if (cur.nodeName != 'svg') {
                // check node visibility
                transforms.push(attr(cur, 'transform'));
                cur = cur.parentNode;
            } else {
                bbox = cur.getBoundingClientRect();
                transforms.push('translate('+[bbox.left, bbox.top]+')');
                cur = null;
            }
            if (isHidden(curCSS)) return;
        }
        transforms = _.filter(transforms, _.identity).reverse();
        var cloned = el.cloneNode(true);
        cloned.setAttribute('transform', transforms.join(' '));

        // copy all computed style attributes
        explicitlySetStyle(el, cloned);
        out_g.node().appendChild(cloned);
    });

    if (labels) {
        out_g = out.append('g').attr('id', 'text');

        labels.each(function() {
            // create a text node for each label
            var el = this,
                cur = el,
                bbox = el.getBoundingClientRect(),
                align = 'left',
                content = el.innerText,
                transforms = [];

            var txt = out_g.append('text')
                .text(content)
                .attr({ x: bbox.left });

            copyTextStyles(el, txt.node());

            txt.attr('y', bbox.top)
                .style('dominant-baseline', 'text-before-edge');

            bbox = txt.node().getBoundingClientRect();
            txt.attr('y', bbox.top+bbox.height).style('dominant-baseline', 'text-after-edge');
        });
    }
    

    download(out.node(), 'export');

    out.remove();

    // labels.remove();
    // svgNodes.remove();

    function isHidden(css) {
        return css.display == 'none' ||
            css.visibility == 'hidden' ||
            +css.opacity === 0 ||
                (+css.fillOpacity === 0 || css.fill == 'none') &&
                (css.stroke == 'none' || !css.stroke || +css.strokeOpacity === 0);
    }

    function explicitlySetStyle(element, target) {
        var elCSS = getComputedStyle(element),
            i, len, key, value,
            computedStyleStr = "";
        for (i=0, len=elCSS.length; i<len; i++) {
            key=elCSS[i];
            value=elCSS.getPropertyValue(key);
            if (value!==emptyCSS.getPropertyValue(key)) {
                computedStyleStr+=key+":"+value+";";
            }
        }
        target.setAttribute('style', computedStyleStr);
    }

    function copyTextStyles(element, target) {
        var elCSS = getComputedStyle(element),
            i, len, key, value,
            computedStyleStr = "";
        for (i=0, len=elCSS.length; i<len; i++) {
            key=elCSS[i];
            if (key.substr(0,4) == 'font' || key.substr(0,4) == 'text' || key == 'color') {
                value=elCSS.getPropertyValue(key);
                if (key == 'color') key = 'fill';
                if (value!==emptyCSS.getPropertyValue(key)) {
                    computedStyleStr+=key+":"+value+";";
                }
            }
        }
        target.setAttribute('style', computedStyleStr);
    }

    function download(svg, filename) {
        var source = (new XMLSerializer()).serializeToString(svg);
        var url = window.URL.createObjectURL(new Blob([doctype + source], { "type" : "text\/xml" }));

        var a = document.createElement("a");
        document.body.appendChild(a);
        a.setAttribute("class", "svg-crowbar");
        a.setAttribute("download", filename + ".svg");
        a.setAttribute("href", url);
        a.style.display = "none";
        a.click();

        setTimeout(function() {
            window.URL.revokeObjectURL(url);
        }, 10);
    }

    function attr(n, v) { return n.getAttribute(v); }

};

})();

function run_multi_crowbar(){
    multiCrowbar()
}

multi-crowbar.js

var multiCrowbar = (function() {
    /*
     * SVG Export
     * converts html labels to svg text nodes
     * will produce incorrect results when used with multi-line html texts
     *
     * Author: Gregor Aisch
     * based on https://github.com/NYTimes/svg-crowbar/blob/gh-pages/svg-crowbar-2.js
     */

    window.d3 = null;
    var s = document.createElement('script');
    s.src = 'https://d3js.org/d3.v3.min.js';
    document.getElementsByTagName('head')[0].appendChild(s);
    function check() {
        if (!window.d3) return setTimeout(check, 200);
        console.log('ready...');
        // run('body');
    }
    check();
    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">';

    return function(cont, label_selector) {
        var parent = d3.select(cont),
            parent_n = parent.node();

        var out_w = parent_n.nodeName == 'body' ? parent_n.scrollWidth : parent_n.clientWidth,
            out_h = parent_n.nodeName == 'body' ? parent_n.scrollHeight: parent_n.clientHeight;

        var labels = label_selector ? parent.selectAll(label_selector) : null,
            nodes = parent.selectAll('path, line, rect, circle, text');
            // divs = parent.selectAll('.export-rect,.rect'),
            // circles = parent.selectAll('.circle');


        var svgNodes = parent.selectAll('svg');

        // 1. create a new svg container of the size of the page
        var out = parent.append('svg');

        // empty css declaration
        var emptyCSS = window.getComputedStyle(out.node());

        out.attr({ width: out_w, height: out_h })
            .style({ position: 'absolute', left: 0, top: 0 });

        var offsetTop = parent_n.getBoundingClientRect().top - parent_n.parentNode.getBoundingClientRect().top;

        var out_g = out.append('g').attr('id', 'svg');

        nodes.each(function() {
            var el = this,
                cur = el,
                curCSS,
                bbox,
                transforms = [];
            while (cur) {
                curCSS = getComputedStyle(cur);
                if (cur.nodeName == 'defs') return;
                if (cur.nodeName != 'svg') {
                    // check node visibility
                    transforms.push(attr(cur, 'transform'));
                    cur = cur.parentNode;
                } else {
                    bbox = cur.getBoundingClientRect();
                    transforms.push('translate('+[bbox.left, bbox.top]+')');
                    cur = null;
                }
                if (isHidden(curCSS)) return;
            }
            transforms = _.filter(transforms, _.identity).reverse();
            var cloned = el.cloneNode(true);
            cloned.setAttribute('transform', transforms.join(' '));

            // copy all computed style attributes
            explicitlySetStyle(el, cloned);
            out_g.node().appendChild(cloned);
        });

        if (labels) {
            out_g = out.append('g').attr('id', 'text');

            labels.each(function() {
                // create a text node for each label
                var el = this,
                    cur = el,
                    bbox = el.getBoundingClientRect(),
                    align = 'left',
                    content = el.innerText,
                    transforms = [];

                var txt = out_g.append('text')
                    .text(content)
                    .attr({ x: bbox.left });

                copyTextStyles(el, txt.node());

                txt.attr('y', bbox.top)
                    .style('dominant-baseline', 'text-before-edge');

                bbox = txt.node().getBoundingClientRect();
                txt.attr('y', bbox.top+bbox.height).style('dominant-baseline', 'text-after-edge');
            });
        }
        

        download(out.node(), 'export');

        out.remove();

        // labels.remove();
        // svgNodes.remove();

        function isHidden(css) {
            return css.display == 'none' ||
                css.visibility == 'hidden' ||
                +css.opacity === 0 ||
                    (+css.fillOpacity === 0 || css.fill == 'none') &&
                    (css.stroke == 'none' || !css.stroke || +css.strokeOpacity === 0);
        }

        function explicitlySetStyle(element, target) {
            var elCSS = getComputedStyle(element),
                i, len, key, value,
                computedStyleStr = "";
            for (i=0, len=elCSS.length; i<len; i++) {
                key=elCSS[i];
                value=elCSS.getPropertyValue(key);
                if (value!==emptyCSS.getPropertyValue(key)) {
                    computedStyleStr+=key+":"+value+";";
                }
            }
            target.setAttribute('style', computedStyleStr);
        }

        function copyTextStyles(element, target) {
            var elCSS = getComputedStyle(element),
                i, len, key, value,
                computedStyleStr = "";
            for (i=0, len=elCSS.length; i<len; i++) {
                key=elCSS[i];
                if (key.substr(0,4) == 'font' || key.substr(0,4) == 'text' || key == 'color') {
                    value=elCSS.getPropertyValue(key);
                    if (key == 'color') key = 'fill';
                    if (value!==emptyCSS.getPropertyValue(key)) {
                        computedStyleStr+=key+":"+value+";";
                    }
                }
            }
            target.setAttribute('style', computedStyleStr);
        }

        function download(svg, filename) {
            var source = (new XMLSerializer()).serializeToString(svg);
            var url = window.URL.createObjectURL(new Blob([doctype + source], { "type" : "text\/xml" }));

            var a = document.createElement("a");
            document.body.appendChild(a);
            a.setAttribute("class", "svg-crowbar");
            a.setAttribute("download", filename + ".svg");
            a.setAttribute("href", url);
            a.style.display = "none";
            a.click();

            setTimeout(function() {
                window.URL.revokeObjectURL(url);
            }, 10);
        }


        function attr(n, v) { return n.getAttribute(v); }

    };

})();