Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SVG to Canvas with d3.js

Has anyone tried using a svg to canvas library when creating d3.js visualizations? I've tried to use canvg.js and d3.js to convert svg to canvas from within an android 2.3 application webview, but when I call:

svg.selectAll(".axis")
    .data(d3.range(angle.domain()[1]))
    .enter().append("g")
    .attr("class", "axis")
    .attr("transform", function(d) { return "rotate(" + angle(d) * 180 / Math.PI + ")"; })
    .call(d3.svg.axis()
        .scale(radius.copy().range([-5, -outerRadius]))
        .ticks(5)
        .orient("left"))
    .append("text")
    .attr("y", 
        function (d) {
            if (window.innerWidth < 455){
                console.log("innerWidth less than 455: ",window.innerWidth);
                return -(window.innerHeight * .33);
            }
            else {
                console.log("innerWidth greater than 455: ",window.innerWidth);
                return -(window.innerHeight * .33);
            }
        })
    .attr("dy", ".71em")
    .attr("text-anchor", "middle")
    .text(function(d, i) { return capitalMeta[i]; })
    .attr("style","font-size:12px;");

I get the error: Uncaught TypeError: Cannot call method setProperty of null http://mbostock.github.com/d3/d3.js?2.5.0:1707

Would some sort of headless browser application, or a server side js parser work? Has anyone encountered this before?

like image 343
user1317804 Avatar asked Jul 19 '12 19:07

user1317804


People also ask

Does D3 use SVG or canvas?

D3 charts are most often rendered using SVG, a retained mode graphics model, which is easy to use, but performance is limited. SVG charts can typically handle around 1,000 datapoints. Since D3 v4 you've also had the option to render charts using canvas, which is an immediate mode graphics model.

Does D3 work with canvas?

js with canvas. There are three common ways D3 users render to canvas. You could use D3. js entirely for its functional purpose – to transform data that you can then position onto your canvas as you see fit.

Does D3 js use SVG?

To create SVG using D3. js, let us follow the steps given below. Step 1 − Create a container to hold the SVG image as given below. Step 2 − Select the SVG container using the select() method and inject the SVG element using the append() method.

Does D3 require jQuery?

d3 does not directly use jQuery, so the jQuery library of functions is not directly accessible in d3 by default.


4 Answers

Here's one way you could write your svg to canvas (and then save the result as a png or whatever):

// Create an export button
d3.select("body")
    .append("button")
    .html("Export")
    .on("click",svgToCanvas);

var w = 100, // or whatever your svg width is
    h = 100;

// Create the export function - this will just export 
// the first svg element it finds
function svgToCanvas(){
    // Select the first svg element
    var svg = d3.select("svg")[0][0],
        img = new Image(),
        serializer = new XMLSerializer(),
        svgStr = serializer.serializeToString(svg);

    img.src = 'data:image/svg+xml;base64,'+window.btoa(svgStr);

    // You could also use the actual string without base64 encoding it:
    //img.src = "data:image/svg+xml;utf8," + svgStr;

    var canvas = document.createElement("canvas");
    document.body.appendChild(canvas);

    canvas.width = w;
    canvas.height = h;
    canvas.getContext("2d").drawImage(img,0,0,w,h);
    // Now save as png or whatever
};
like image 75
ace Avatar answered Oct 05 '22 08:10

ace


The answer by @ace is very good, however it doesn't handle the case of external CSS stylesheets. My example below will automatically style the generated image exactly how the original SVG looks, even if it's pulling styles form separate stylesheets.

// when called, will open a new tab with the SVG
// which can then be right-clicked and 'save as...'
function saveSVG(){

    // get styles from all required stylesheets
    // http://www.coffeegnome.net/converting-svg-to-png-with-canvg/
    var style = "\n";
    var requiredSheets = ['phylogram_d3.css', 'open_sans.css']; // list of required CSS
    for (var i=0; i<document.styleSheets.length; i++) {
        var sheet = document.styleSheets[i];
        if (sheet.href) {
            var sheetName = sheet.href.split('/').pop();
            if (requiredSheets.indexOf(sheetName) != -1) {
                var rules = sheet.rules;
                if (rules) {
                    for (var j=0; j<rules.length; j++) {
                        style += (rules[j].cssText + '\n');
                    }
                }
            }
        }
    }

    var svg = d3.select("svg"),
        img = new Image(),
        serializer = new XMLSerializer(),

    // prepend style to svg
    svg.insert('defs',":first-child")
    d3.select("svg defs")
        .append('style')
        .attr('type','text/css')
        .html(style);


    // generate IMG in new tab
    var svgStr = serializer.serializeToString(svg.node());
    img.src = 'data:image/svg+xml;base64,'+window.btoa(unescape(encodeURIComponent(svgStr)));
    window.open().document.write('<img src="' + img.src + '"/>');
};

And, to be complete, the button that calls the function:

// save button
d3.select('body')
    .append("button")
    .on("click",saveSVG)
    .attr('class', 'btn btn-success')
like image 39
Constantino Avatar answered Oct 05 '22 07:10

Constantino


Have you tried the same code on a browser supporting SVG to see if it's a problem with webview? Then try this example using canvg or this one using DOM serialization. For server-side rendering, you could start with this example for how to render it to canvas server-side using Node.js.

like image 25
Biovisualize Avatar answered Oct 05 '22 06:10

Biovisualize


I've not tried a library, but have rendered a d3-produced SVG to a canvas following this post on MDN.

This code is a quick mish-mash of the MDN and some jQuery, that'll you'll need to tidy up, and it has no error- or platfrom-checking, but it works, and I hope it helps.

$(document.body).append(
    '<canvas id="canvas" width="'+diameter+'" height="'+diameter+'"></canvas>'
);

// https://developer.mozilla.org/en/docs/HTML/Canvas/Drawing_DOM_objects_into_a_canvas
var el = $($('svg')[0]);
var svgMarkup = '<svg xmlns="http://www.w3.org/2000/svg"'
+ ' class="'  + el.attr('class') +'"'
+ ' width="'  + el.attr('width') +'"'
+ ' height="' + el.attr('height') +'"'
+ '>'
+ $('svg')[0].innerHTML.toString()+'</svg>';
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var DOMURL = this.URL || this.webkitURL || this;
var img = new Image();
var svg = new Blob([svgMarkup], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
    ctx.drawImage(img, 0, 0);
    alert('ok');
    DOMURL.revokeObjectURL(url);
};
img.src = url;
like image 21
Lee Goddard Avatar answered Oct 05 '22 08:10

Lee Goddard