0

I'm using Node on a server side application, to dynamically produce graphs. I'm using d3.js and rickshaw.js to create SVG versions of the graph, and imagemagick to convert that SVG into a png. I am using JSDOM as my DOM.

I am getting this error: https://github.com/shutterstock/rickshaw/issues/186

The solution given here is to pull in the css, I found an answer on stack overflow on how to do this with JSDOM:

How do you ad stylesheets to JSDOM

So I've followed these instructions, and pulled in a rickshaw.css. When I print the DOM to console, I can see it in the head, in a style element.

var mainCss = fs.readFileSync(path.normalize("rickshaw.css"), 'utf8');
console.log("mainCss",mainCss);
var document = jsdom.jsdom("<!DOCTYPE html><html><meta http-equiv=\"content-type\"content=\"text/html; charset=utf-8\"><head></head><body id=\"abody\"><div id=\"chart_container\"><div id=\"y_axis\"></div><div id=\"chart\"></div></div></body></html>", jsdom.level(3, 'index'), {
    features : {
        FetchExternalResources : ['script', 'css'],
        QuerySelector : true
    }
});     

GLOBAL.window = document.parentWindow;
var head = document.getElementsByTagName('head')[0];
style = document.createElement("style");
style.type = 'text/css';
style.innerHTML = mainCss;
head.appendChild(style);

I'm setting up my graph as so:

var graph = new Rickshaw.Graph( {
element: document.querySelector('#chart'), 
    width: 600, 
    height: 600, 
    series: seriesArr
});

var yAxis = new Rickshaw.Graph.Axis.Y({
    graph: graph
});

var xAxis = new Rickshaw.Graph.Axis.Time({
    graph: graph,
    timeUnit: "hour"
});


yAxis.render();
xAxis.render();

graph.render();

utils.convertSVGtoPNG(document.querySelector('#chart').innerHTML);

Still I am getting a black square, as my output SVG.

Am I missing something? Am I thinking about something wrong? Any help would be greatly appreciated.

Community
  • 1
  • 1
thirdcreed
  • 15
  • 7
  • It looks like [imagemagick doesn't support external CSS](http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=14954). Since the styles in question are fairly straightforward, you should be able to just re-select the relevant elements with d3 and apply the styles. – AmeliaBR Feb 24 '14 at 23:16
  • The styles are being applied in the node JS application, the rendering via image magic doesn't happen until after the svg has been created. ImageMagick is just being used to convert the files from SVG to PNG. do you have any reason to think that style element wouldn't work? I still haven't figured anything out, thanks anyway. – thirdcreed Feb 25 '14 at 02:12
  • I'm sorry I was on my phone, yesterday, and didn't follow the link, just assuming that you had misunderstood me. You did understand me, and your answer was very helpful, so sorry about that. If you answer me in answer form, I would select this answer. – thirdcreed Feb 25 '14 at 15:38

2 Answers2

2

Unfortunately, it looks like imagemagick doesn't support external CSS and other people asking for solutions for similar problems haven't received any alternative suggestions for tools that do. So you're going to have to make sure that the relevant styles are applied inline in order for your SVG to PNG converter to recognize them.

The universal way to do this would be to write a script that traverses the CSS-DOM, grabs each rule, selects all elements that match the rule, and applies the corresponding styles to them as inline styles.

However, that would probably be overkill for your needs. Your specific problem is caused by the default style for <path> elements, which is solid black fill and no stroke. When using grid lines, this means that the axis domain path gets drawn as a solid black rectangle covering the entire plotting area.

The simple solution is therefore to select these paths after drawing the axes, and apply custom styles directly:

d3.selectAll("path.domain")
  .style({ fill:"none",
           stroke:"black",
           stroke-width:1,
        });
Community
  • 1
  • 1
AmeliaBR
  • 27,344
  • 6
  • 86
  • 119
  • 1
    Thanks for the answer Amelia, I did this and got it working in this fiddle: http://jsfiddle.net/thirdcreed/LWR39/3/ However, the bigger problem here is that Rickshaw uses html elements as well as SVG to render axes and such. So even if you get everything styled correctly it can't be rendered. – thirdcreed Feb 25 '14 at 18:26
  • Whoah, yeah, that will throw you off. I knew most of their examples drew legends and axes in separate `
    ` elements, but that you could work around just be setting up a root ``as the chart container with appropriately positioned child `` elements for the different components. However, I didn't realize that they used `
    ` elements for the tick marks themselves -- I assumed they used default d3 axes. That's going to be a heck of a lot of trouble to convert to SVG. Maybe it's time to start looking at other d3-based graphing tools?
    – AmeliaBR Feb 25 '14 at 18:59
  • That's too bad. ATM It looks like I'm going to be doing this In python's matplotlib, but before I do, I will do one last desperate search for a library that will suit my needs here. Thanks for all your help! – thirdcreed Feb 25 '14 at 19:54
  • Depending on what you're doing, d3-based options are [NVD3](http://nvd3.org/) and [D3.chart](http://misoproject.com/d3-chart/); the former has more decoration by default, but lots of the content is interactive so not relevant to your purposes; the latter requires more understanding of the basic d3 library. Another one that came up on a quick search is [dimple.js](http://dimplejs.org/examples_index.html); it looks fairly straightforward to use, though still not as glossy a design as Rickshaw. – AmeliaBR Feb 26 '14 at 00:44
0

If anyone is interested in the d3 selectors to replicate all css, as recommended by Amelia here they are:

    d3.selectAll("path.domain")
  .style({ "fill":"none",
           "stroke":"black",
           "stroke-width":1
        });

d3.selectAll("path.domain")
    .style({
    "fill": "none",
        "stroke": "black",
        "stroke-width": "1"
});

d3.selectAll(".rickshaw_graph .detail")
    .style({
    "pointer-events": "none",
        "position": "absolute",
        "top": "0",
        "z-index": "2",
        "background": "rgba(0, 0, 0, 0.1)",
        "bottom": "0",
        "width": "1px",
        "transition": "opacity 0.25s linear",
        "-moz-transition": "opacity 0.25s linear",
        "-o-transition": "opacity 0.25s linear",
        "-webkit-transition": "opacity 0.25s linear"
});

d3.selectAll(".rickshaw_graph .detail.inactive ")
    .style({
    "opacity": "0"
});

d3.selectAll(".rickshaw_graph .detail .item.active ")
    .style({
    "opacity": "1"
});
d3.selectAll(".rickshaw_graph .detail .x_label ")
    .style({
    "font-family": "Arial, sans-serif",
        "border-radius": "3px",
        "padding": "6px",
        "opacity": "0.5",
        "border": "1px solid #e0e0e0",
        "font-size": "12px",
        "position": "absolute",
        "background": "white",
        "white-space": "nowrap"
});
d3.selectAll(".rickshaw_graph .detail .x_label.left ")
    .style({
    "left": "0"
});
d3.selectAll(".rickshaw_graph .detail .x_label.right ")
    .style({
    "right": "0"
});
d3.selectAll(".rickshaw_graph .detail .item ")
    .style({
    "position": "absolute",
        "z-index": "2",
        "border-radius": "3px",
        "padding": "0.25em",
        "font-size": "12px",
        "font-family": "Arial, sans-serif",
        "opacity": "0",
        "background": "rgba(0, 0, 0, 0.4)",
        "color": "white",
        "border": "1px solid rgba(0, 0, 0, 0.4)",
        "margin-left": "1em",
        "margin-right": "1em",
        "margin-top": "-1em",
        "white-space": "nowrap"
});
d3.selectAll(".rickshaw_graph .detail .item.left ")
    .style({
    "left": "0"
});
d3.selectAll(".rickshaw_graph .detail .item.right ")
    .style({
    "right": "0"
});
d3.selectAll(".rickshaw_graph .detail .item.active ")
    .style({
    "opacity": "1",
        "background": "rgba(0, 0, 0, 0.8)"
});

d3.selectAll(".rickshaw_graph .detail .dot ")
    .style({
    "width": "4px",
        "height": "4px",
        "margin-left": "-2px",
        "margin-top": "-2px",
        "border-radius": "5px",
        "position": "absolute",
        "box-shadow": "0 0 2px rgba(0, 0, 0, 0.6)",
        "background": "white",
        "border-width": "2px",
        "border-style": "solid",
        "display": "none",
        "background-clip": "padding-box"
});
d3.selectAll(".rickshaw_graph .detail .dot.active ")
    .style({
    "display": "block"
});
/* graph */

d3.selectAll(".rickshaw_graph ")
    .style({
    "position": "relative"
});
d3.selectAll(".rickshaw_graph svg ")
    .style({
    "display": "block",
        "overflow": "hidden"
});


d3.selectAll(".rickshaw_graph .x_tick ")
    .style({
    "position": "absolute",
        "top": "0",
        "bottom": "0",
        "width": "0px",
        "border-left": "1px dotted rgba(0, 0, 0, 0.2)",
        "pointer-events": "none"
});
d3.selectAll(".rickshaw_graph .x_tick .title ")
    .style({
    "position": "absolute",
        "font-size": "12px",
        "font-family": "Arial, sans-serif",
        "opacity": "0.5",
        "white-space": "nowrap",
        "margin-left": "3px",
        "bottom": "1px"
});


d3.selectAll(".rickshaw_annotation_timeline ")
    .style({
    "height": "1px",
        "border-top": "1px solid #e0e0e0",
        "margin-top": "10px",
        "position": "relative"
});
d3.selectAll(".rickshaw_annotation_timeline .annotation ")
    .style({
    "position": "absolute",
        "height": "6px",
        "width": "6px",
        "margin-left": "-2px",
        "top": "-3px",
        "border-radius": "5px",
        "background-color": "rgba(0, 0, 0, 0.25)"
});
d3.selectAll(".rickshaw_graph .annotation_line ")
    .style({
    "position": "absolute",
        "top": "0",
        "bottom": "-6px",
        "width": "0px",
        "border-left": "2px solid rgba(0, 0, 0, 0.3)",
        "display": "none"
});
d3.selectAll(".rickshaw_graph .annotation_line.active ")
    .style({
    "display": "block"
});

d3.selectAll(".rickshaw_graph .annotation_range ")
    .style({
    "background": "rgba(0, 0, 0, 0.1)",
    "display": "none",
    "position": "absolute",
    "top": "0",
    "bottom": "-6px"
});
d3.selectAll(".rickshaw_graph .annotation_range.active ")
    .style({
    "display": "block"
});
d3.selectAll(".rickshaw_graph .annotation_range.active.offscreen ")
    .style({
    "display": "none"
});

d3.selectAll(".rickshaw_annotation_timeline .annotation .content ")
    .style({
    "background": "white",
        "color": "black",
        "opacity": "0.9",
        "padding": "5px 5px",
        "box-shadow": "0 0 2px rgba(0, 0, 0, 0.8)",
        "border-radius": "3px",
        "position": "relative",
        "z-index": "20",
        "font-size": "12px",
        "top": "18px",
        "left": "-11px",
        "width": "160px",
        "display": "none",
        "cursor": "pointer"
});



d3.selectAll(".rickshaw_annotation_timeline .annotation.active .content ")
    .style({
    "display": "block"
});
d3.selectAll(".rickshaw_annotation_timeline .annotation:hover .content ")
    .style({
    "display": "block",
        "z-index": "50"
});
d3.selectAll(".rickshaw_graph .y_axis, .rickshaw_graph  .x_axis_d3 ")
    .style({
    "fill": "none"
});
d3.selectAll(".rickshaw_graph .y_ticks .tick, .rickshaw_graph .x_ticks_d3 .tick ")
    .style({
    "stroke": "rgba(0, 0, 0, 0.16)",
        "stroke-width": "2px",
        "shape-rendering": "crisp-edges",
        "pointer-events": "none"
});

d3.selectAll(".rickshaw_graph .y_grid .tick, .rickshaw_graph .x_grid_d3 .tick ")
    .style({
    "z-index": "-1",
        "stroke": "rgba(0, 0, 0, 0.20)",
        "stroke-width": "1px",
        "stroke-dasharray": "1 1"
});
d3.selectAll(".rickshaw_graph .y_grid .tick[data-y-value=\"0\"] ")
    .style({
    "stroke-dasharray": "1 0"
});
d3.selectAll(".rickshaw_graph .y_grid path,.rickshaw_graph .x_grid_d3 path  ")
    .style({
    "fill": "none",
        "stroke": "none"
});
d3.selectAll(".rickshaw_graph .y_ticks path, .rickshaw_graph .x_ticks_d3 path ")
    .style({
    "fill": "none",
        "stroke": "#808080"
});
d3.selectAll(".rickshaw_graph .y_ticks text, .rickshaw_graph .x_ticks_d3 text ")
    .style({
    "opacity": "0.5",
        "font-size": "12px",
        "pointer-events": "none"
});
d3.selectAll(".rickshaw_graph .x_tick.glow .title, .rickshaw_graph .y_ticks.glow text ")
    .style({
    "fill": "black",
        "color": "black",
        "text-shadow":
        "-1px 1px 0 rgba(255, 255, 255, 0.1),1px -1px 0 rgba(255, 255, 255, 0.1),1px 1px 0 rgba(255, 255, 255, 0.1),0px 1px 0 rgba(255, 255, 255, 0.1),0px -1px 0 rgba(255, 255, 255, 0.1),1px 0px 0 rgba(255, 255, 255, 0.1),-1px 0px 0 rgba(255, 255, 255, 0.1),-1px -1px 0 rgba(255, 255, 255, 0.1)"
});

d3.selectAll(".rickshaw_graph .x_tick.inverse .title, .rickshaw_graph .y_ticks.inverse text")
    .style({
    "fill": "white",
        "color": "white",
        "text-shadow": "-1px 1px 0 rgba(0, 0, 0, 0.8),1px -1px 0 rgba(0, 0, 0, 0.8),1px 1px 0 rgba(0, 0, 0, 0.8),0px 1px 0 rgba(0, 0, 0, 0.8),0px -1px 0 rgba(0, 0, 0, 0.8),1px 0px 0 rgba(0, 0, 0, 0.8),-1px 0px 0 rgba(0, 0, 0, 0.8),-1px -1px 0 rgba(0, 0, 0, 0.8)"
});
d3.selectAll(".rickshaw_legend ")
    .style({
    "font-family": "Arial",
        "font-size": "12px",
        "color": "white",
        "background": "#404040",
        "display": "inline-block",
        "padding": "12px 5px",
        "border-radius": "2px",
        "position": "relative"
});

d3.selectAll(".rickshaw_legend .swatch ")
    .style({
    "width": "10px",
        "height": "10px",
        "border": "1px solid rgba(0, 0, 0, 0.2)"
});
d3.selectAll(".rickshaw_legend .line ")
    .style({
    "clear": "both",
        "line-height": "140%",
        "padding-right": "15px"
});
d3.selectAll(".rickshaw_legend .line .swatch ")
    .style({
    "display": "inline-block",
        "margin-right": "3px",
        "border-radius": "2px"
});
d3.selectAll(".rickshaw_legend .label ")
    .style({
    "margin": "0",
        "white-space": "nowrap",
        "display": "inline",
        "font-size": "inherit",
        "background-color": "transparent",
        "color": "inherit",
        "font-weight": "normal",
        "line-height": "normal",
        "padding": "0px",
        "text-shadow": "none"
});

d3.selectAll(".rickshaw_legend .action ")
    .style({
    "margin-right": "0.2em",
        "font-size": "10px",
        "opacity": "0.2",
        "cursor": "pointer"
});
d3.selectAll(".rickshaw_legend .line.disabled ")
    .style({
    "opacity": "0.4"
});
d3.selectAll(".rickshaw_legend ul ")
    .style({
    "list-style-type": "none",
        "margin": "0",
        "padding": "0",
            "cursor": "pointer"
    });
    d3.selectAll(".rickshaw_legend li ")
        .style({
        "padding": "0 0 0 2px",
            "min-width": "80px",
            "white-space": "nowrap"
    });

It isn't formatted well, and I've had to remove any class that had pseudo-selectors.

thirdcreed
  • 15
  • 7