Vector Graphics overlay on top of Image or Video element

Hi Guys,

First off, congratulations and thank you for making Observable. Designing algorithms is the fun part of my job and Observable made it even more fun.

I have been trying to add a vector overlay (plain canvas commands or SVG) on top of an Image element or live video. Is there a reasonably easy way to do that? On top of a GL canvas (after webGL rendering) would work too. I searched for examples, but couldn’t find any.

Many Thanks!

2 Likes

Set up a relatively positioned DIV, then position an absolutely positioned image, followed by an absolutely positioned SVG. The overall structure would look like:

	{
	  let container = div
	    .append('div')
	    .style('position', 'relative')
	    .style('width', `${width}px`)
	    .style('height', `${height}px`);
	  let canvas = container
	    .append('canvas')
	    .style('position', 'absolute')
	    .style('top', 0)
	    .style('left', 0)
	    .attr('width', width)
	    .attr('height', height);
	  let svg = container
	    .append('svg')
	    .style('position', 'absolute')
	    .style('top', 0)
	    .style('left', 0)
	    .attr('width', width)
	    .attr('height', height);

		// Draw stuff ...
	}

More generally, you should be familiar with how positioning works in CSS to do this type of thing.

2 Likes

Thank you for the quick answer! Is this using D3? I added

const div = d3.select(DOM.element('div'));

on top to make it work. But I am not familiar with D3. How do I add an HTMLImageElement that I have created in another cell of the notebook?

OK, I figured that I had to get the Node back, add the child HTMLImageElement, then reselect to style with d3. Is this the idiomatic way?

let image = container.node().appendChild(testimage);
d3.select(image)
    .style('position', 'absolute')
    .style('top', 0)
    .style('left', 0)
    .attr('width', imgWidth)
    .attr('height', imgHeight);

I generally find these things easier to discuss if there’s a link shared notebook to reference. I would think, though, that if your container is a d3 object, then you would probably do

let image = container.append(testimage)

Calling append() directly on the d3 object gives the error:

InvalidCharacterError: Failed to execute ‘createElement’ on ‘Document’: The tag name provided (‘[object HTMLImageElement]’) is not a valid name.

Here is the notebook: Image or Video with Graphical Overlay / benoit9 | Observable

Edit: I was wrong, see my next comment.

There’s no need for those positioning shenanigans, because you can embed a video directly into an SVG by wrapping it in a <foreignObject> element:

2 Likes

I really, really should have tested this properly before making the above claim. foreignObject support is a hot mess. Browser support differs wildly, and there are all sorts of stacking issues with animated content.
Don’t use foreignObject for video (text content should be good, though). Follow @mcmcclur’s advice instead and use the established methods of CSS positioning.

Sorry!

2 Likes