What is the best way to overlay an SVG over a Canvas?

I’m trying to reproduce https://bl.ocks.org/travisdoesmath/c0d08396a0bf8d5de8519ded00bb7866 in a notebook, but I’m struggling to return a cell with the pendulums in an SVG on top of the trails in a canvas element

Nevermind, I got it working!

I have been having the same issue. Could you share your solution here so other people searching and finding this post (as I did) can see how to solve it?

I guess the notebook that @travisdoesmath ended up publishing was this one:

However, it looks like that notebook doesn’t actually have an SVG overlaid on top of the canvas – the pendula are just drawn using canvas commands.

Anyways, here’s a snippet extracted from a draft notebook of mine (using d3) where I did put SVG over a canvas. If you can’t get it work let me know and I’ll try to publish a working notebook example.

{
  const container = d3.select(DOM.element('div'));
  container.style("position", "relative");
  const canvas = container.append('canvas').node();
  const context = canvas.getContext('2d');
  
  canvas.width = 800;
  canvas.height = 600;

  const svg = container.append('svg')
    .attr("width", canvas.width)
    .attr("height", canvas.height)
    .style("position", "absolute")
    .style("top", '0px')
    .style("left", '0px');
  
  // put your canvas commands and SVG stuff here

  return container.node()
}
3 Likes

Another way to do it is to embed the canvas directly into the SVG using foreignObject: https://beta.observablehq.com/@mootari/embed-canvas-into-svg

1 Like

This got me thinking – what would be the best way to save an SVG with an embedded canvas? I experimented with importing the Saving SVG functions into your notebook and they seem to ignore the canvas.

Chrome reports the canvas as being tainted, Firefox exports the plain canvas element. Apparently the case (canvas inside foreignObject) is not well defined in the specs (Chrome bug).

I’m thinking that you’d have to convert the canvas to an image with a data src, ideally replacing the whole foreignObject node.

I’ve added a download feature to my notebook. Also now included is a helper function that converts canvas elements to images with data URLs.

@mbostock How do you feel about a callback parameter in your SVG serialize() helper to handle these edge cases?

2 Likes

Is this a request for a lazy DOM.download, or something else?

Something else, cleaning up the cloned SVG (e.g. replacing canvas with img). In serialize() in Saving SVG / Mike Bostock | Observable you’re already using a treewalker to sanitize URIs in the SVG. I was thinking perhaps a callback(currentNode, svg) could be added to allow custom modifications to the SVG prior to serialization.

Edit: I’m not sure how well TreeWalker handles live modifications to the target DOM though, and the above use case would also require receiving the matching element from the source SVG. If you give the idea a :+1: I’d be happy to look into it and create a merge request.

2 Likes

I’d definitely learn a lot from seeing how you’d do this, so let me put in a :+1:

Hi,

When I use canvas inside an SVG as a foreignObject, I wouldn’t be able to put additional SVG elements on top of that canvas any more, even if they are specified after that canvas.

Any idea how to resolve this? Thanks!

1 Like

an example is here: https://observablehq.com/@tracyhenry/density-contours

1 Like

That seems to be a Chrome bug (which I can replicate), because Firefox displays correctly.

Edit: This should actually be fixed in the most recent version, Chrome 75 (can’t update right now). Bug report: 467484 - chromium - An open-source project to help move the web forward. - Monorail

thanks for the research. But seems like the bug is not fixed (I’m running 75+): https://bugs.chromium.org/p/chromium/issues/detail?id=148499

1 Like

Hi,could you find soultion to this problem?

@vanitagithub If you’re talking about the render bug in Chrome, you can try this workaround:

The current workaround is to apply the CSS properties “position:relative; z-index: -1” to the foreignObject element. This also has the effect that a CSS background on foreignObject no longer renders, which matches current Firefox behavior.

1 Like

no I can’t. This is a bug of chrome. Using firefox is ok.

Actually just tried similar approach and it worked With z index and top , left position as 0 and I am able to overlay as expected thanks for quick reply.

I just discovered another workaround for Chrome: If you call .getImageData() on the canvas context it restores the render order. The area doesn’t matter.

I’ve updated my notebook accordingly:

1 Like