canvas.getContext is not a function, when called on canvas

15,317

Solution 1

I think the "canvas" element is treated as unknown "canvas" element of SVG by d3. So the "canvas" element is not mapped to HTMLCanvasElement but SVGUnknownElement in domtree of document, thus getContext() of SVGUnknownElement is undefined.

To solve this problem, you should wrap the "canvas" element by foreignObject element and add xhtml namespace to the "canvas" element.

I'm not good at d3, please try to construct this structure by using d3.

<g class="node" transform="translate(210,330)">
  <foreignObject x="-8" y="-8">
    <canvas id="0_0" xmlns="http://www.w3.org/1999/xhtml"></canvas>
  </foreignObject>
</g>

Or use image element instead of "canvas" element to put image created by (html)canvas element.

SVG structure

<g class="node" transform="translate(210,330)">
  <image x="-8" y="-8" id="0_0"/>
</g>

Javascript code

//create canvas element.
//var canvas = document.getElementById(nodes[i].__data__.name);
var canvas = document.createElement("canvas");
//console.log(canvas, canvas.nodeName);
var ctx = canvas.getContext('2d');

canvas.width = width;
canvas.height = height;

var idata = ctx.createImageData(width, height);
idata.data.set(buffer);
ctx.putImageData(idata, 0, 0);

//set image created by canvas to image element.
var image = document.getElementById(nodes[i].__data__.name);
image.width.baseVal.value = width;
image.height.baseVal.value = height;
image.href.baseVal = canvas.toDataURL();

Solution 2

With D3.js it's crucial to create the canvas element within the proper namespace by using .create('xhtml:canvas') or .append('xhtml:canvas'). The xmlns attribute is nice to have, but will be disregarded by most modern HTML5 browsers.

Here's a full D3.js example:

const svg = d3.select('body').append('svg')
  .style('width', '100%')
  .style('height', '100%');

const group = svg.append('g')
  .attr('class', 'node')
  .attr('transform', 'translate(10,10)');

const foreignObject = group.append('foreignObject')
  .attr('width', 100)
  .attr('height', 100);

const canvas = foreignObject.append('xhtml:canvas')
  .attr('xmlns', 'http://www.w3.org/1999/xhtml');

const context = canvas.node().getContext('2d');

console.log(canvas.node().constructor.name);
// => HTMLCanvasElement

// Draw something
context.fillStyle = 'blue';
context.fillRect(0, 0, 100, 100);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

The concept of namespaces it not easy to grasp and most time you won't need to explicitly set them, since they are inherited from the parent element. Mike Bostock explains the concept quite well in this Github issue.

(Yeah, I open the issue and it didn't know how to use namespaces either.)

Share:
15,317
Admin
Author by

Admin

Updated on June 19, 2022

Comments

  • Admin
    Admin almost 2 years

    I am encountering an error where getContext cannot be called even though the element is a canvas.

    var canvas = document.getElementById(id);
    console.log(canvas, canvas.nodeName);
    var ctx = canvas.getContext('2d');
    

    Thanks!

    Snippet of it working in isolation, but not in the script

    var canvas = document.getElementById( '0_0' );
    document.write(canvas.getContext('2d'));
    <g class="node" transform="translate(210,330)">
      <canvas x="-8" y="-8" id="0_0"></canvas>
    </g>

    <canvas x=​"-8" y=​"-8" id=​"0_0">​ "canvas"
    Uncaught TypeError: canvas.getContext is not a function
      at Array.populateNodes (script.js:95)
      at Array.Co.call (d3.v3.min.js:3)
      at gen_graph (script.js:63)
      at Object.success (main.js:16)
      at i (jquery.min.js:2)
      at Object.fireWith [as resolveWith] (jquery.min.js:2)
      at A (jquery.min.js:4)
      at XMLHttpRequest.<anonymous> (jquery.min.js:4)
    

    The js, using d3:

    Apologies for how bad my JS may or not be, I am quite new to it

  • Admin
    Admin about 7 years
    Hi thanks a lot for your message, the first method yielded the same error. As you mentioned using an image tag instead of a canvas, how would I go about doing that? I am taking in an array of RGB values for an image to then be displayed somehow.
  • defghi1977
    defghi1977 about 7 years
    You can get canvas graphic by calling toDataURL method, and set the url created by this to image element.
  • Admin
    Admin about 7 years
    Thank you so much for your help, I would up-vote you but I don't have a high enough rep.