(vtk.js) Looking for a way to add always-front-facing text that maps to an underlying image

I’m working on an existing website that already has a vtk.js viewer setup. I tried the pixelSpaceCallbackMapper, which seemed to the be the closest to accomplishing my goal, but Spheres w/ Labels example used openGL which was conflicting with my existing setup.

I also tried overlaying a 2D canvas on my existing webGL2 canvas (which needs to remain), and then using the pixelSpaceCallbackMapper, as I’d seen similar overlay examples online, but something was off there as well in terms of the text mapping accurately to the underlying image.

In the end - let’s say for simplicity’s sake that my underlying vtp file is a house. I’d like one piece of text that reads ‘FRONT DOOR’ that always faces forward and follows the front door, regardless of camera rotation. How can this be accomplished?

pixelSpaceCallbackMapper is indeed the way to go. I’m not sure to understand what issue you are running into. Please describe it better. If the text need to disappear when the door became hidden due to the camera orientation you can look at the following example.

Hi Sebastien - thanks for getting back to me on this. The issue in my case isn’t the display order/depth, but rather a conflict with the canvas context(s).

Our existing viewer is in a canvas with a webGL2 context. In order to use textFill, my understanding thus far has been that I need a 2D context. I tried overlaying a 2D canvas on my webGL2 canvas, and it was close, but something beyond my current level of understanding caused the text mapping to be ‘off’ - i.e. the text on one canvas wasn’t following the same path as a given point on the underlying vtp file. So let’s say the user dragged the mouse to rotate the view … the text would move, but in seemingly erratic directions, rather than in lock-step with the underlying data.

This issue may be too specific to my own project, but it’s at least helpful to know you would also use pixelSpaceCallbackMapper for this.

With the pixelSpaceCallbackMapper you can use plain HTML elements in absolute positioning rather than another canvas. But if you have a different pixel ratio you may need to compensate the coordinates to match any device pixel ratio you may use.

1 Like

Thanks so much for the continued help on this. That info got me very close; in fact, I was able to get the text properly mapped using an html file with a simple script to practice on. However, once I tried it in my actual code base, the issue I faced was that the text would appear, and it would move concurrently with the underlying image – But… it seemed to be rotating about a separate, offset axis, instead of being sync’ed up with the axes of the underlying image.

Here’s a snippet from the method to which I’m adding this functionality:

  private addLocalMediumVTPToRenderer (vtpFile) {
    let mapper = this.vtkObj.Rendering.Core.vtkMapper.newInstance()

    let actor = this.vtkObj.Rendering.Core.vtkActor.newInstance()
    this.vtpRendererActors["localMedium"] = actor

    const userParams = this.vtkObj.Common.Core.vtkURLExtract.extractURLParameters()
    const psMapper = this.vtkObj.Rendering.Core.vtkPixelSpaceCallbackMapper.newInstance()
    // This is our underlying vtp file, which is seemingly a 
    // different type of object than the cubeSource or coneSource
    // and is perhaps a cause of the different text behavior?

    psMapper.setCallback((coordsList) => {
      let counter = 0
      // In this case I only wanted a text element for one coordinate,
      // and have arbitrarily used the first coordinate
      coordsList.forEach((xy, idx) => {
        if (counter < 1) {
          const xy = coordsList[coordsList.length-1]

          // Create a text element
          let tag = document.createElement('p')

          // Clear old text element if any
          // (avoid 'streaking' text)
          let textToRemove = document.getElementsByTagName('p')
          if (textToRemove.length > 1) {

          // Style text element
          tag.style.color = 'yellow'
          tag.style.fontSize = '3em'
          tag.style.zIndex = '2'
          tag.style.position = 'absolute'
          tag.style.top = `${canvas.height - xy[1]}px`
          tag.style.left = `${xy[0]}px`
          let text = document.createTextNode('Left')
          let textParentElement = document.getElementById(vtkContainerId).firstChild
          counter = counter + 1
        // }
      // })

    const textActor = this.vtkObj.Rendering.Core.vtkActor.newInstance()

    const apiSpecificRenderWindow = this.vtkRenderer.getRenderWindow().newAPISpecificView(
    const canvas = document.getElementsByTagName('canvas')[0]

    const {width, height} = this.vtkContainer.getBoundingClientRect()
    apiSpecificRenderWindow.setSize(width, height)

    this.vtkRenderer.getRenderer().getActiveCamera().setPosition(0, -1, 0);

Hi @mattbrook ,
possibly you need to divide xy[0] and xy[1] by window.devicePixelRatio. At least for me, this helped fixing problems with offset labels. See also Label widget example offset on windows 10 · Issue #1179 · Kitware/vtk-js · GitHub .

Mathias, thanks so much for this last piece of the puzzle. I was able to correct the mapping of the text with this. And @Sebastien_Jourdain thank you again for all the help.

1 Like