Text labels in vtk.js?

I’m using vtk.js 25 and I’d like to place text at specific 3d positions. Are there examples of this available? From other recent questions it seems like the best way to do this is (1) create a vtkPixelSpaceCallbackMapper and an actor, (2) have the mapper’s callback apply the current view transform to the 3d position to get a 2d screen position, (3) create html text directly on the canvas used by the renderer. Is that right?

Yep, that is the correct approach for translating 3D positions into the 2D display space. You could also manually use worldToDisplay, to transform your coordinates, but vtkPixelSpaceCallbackMapper is easier as it updates whenever the camera updates.

1 Like

Thanks… One question: in PixelSpaceCallbackMapper | vtk.js I see:

  • … The callback
  • function will have the following parameters:
  • // An array of 4-component arrays, in the index-order of the datasets points
  • coords: [
  • [screenx, screeny, screenz, zBufValue],
  • ]
  • @param callback
    */
    setCallback(callback: () => any): boolean

Shouldn’t the type of the callback argument have those 4 parameters? I’m new to typescript so I may be missing something…

Here’s my code (works, needs cleanup) in case it’s useful to anybody.

import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor'
import vtkRenderer from "vtk.js/Sources/Rendering/Core/Renderer"
import vtkOpenGLRenderWindow from "vtk.js/Sources/Rendering/OpenGL/RenderWindow"
import vtkPixelSpaceCallbackMapper from "vtk.js/Sources/Rendering/Core/PixelSpaceCallbackMapper"
import vtk from "vtk.js/Sources/vtk"

export class RenderTextData {
  polydata: Object
  labels: string[] = []
  positions: number[] = []

  constructor(pd : Object) {
    this.polydata = pd
  }
}

export function addRenderText(data: RenderTextData, pos: [number, number, number], text: string) {
  data.polydata['getPoints']()['insertNextPoint'](pos[0], pos[1], pos[2])
  data.labels.push(text)
  data.positions.push(pos[0], pos[1], pos[2])
}

export function setupRenderText(renderer: vtkRenderer, openglRenderWindow: vtkOpenGLRenderWindow,
                                textCtx: CanvasRenderingContext2D): RenderTextData {
  const pointPoly = vtk({vtkClass: 'vtkPolyData'}) as Object

  let data = new RenderTextData(pointPoly)

  // (1) create a vtkPixelSpaceCallbackMapper and an actor,
  // (2) have the mapper’s callback apply the current view transform to the 3d position to get a 2d screen position,
  // (3) create html text directly on the canvas used by the renderer.

  let oglCtx = openglRenderWindow.get3DContext({})!

  const psMapper = vtkPixelSpaceCallbackMapper.newInstance();
  psMapper.setInputData(pointPoly);
  // @ts-ignore
  psMapper.setCallback((coords, camera, aspect, depthBuffer) => {
    textCtx.clearRect(0, 0, oglCtx.drawingBufferWidth, oglCtx.drawingBufferHeight);
    coords.forEach((xy, idx) => {
      let label = data.labels[idx]
      textCtx.fillStyle = "white";
      textCtx.font = "12px Arial";
      textCtx.fillText(label, xy[0], oglCtx.drawingBufferHeight - xy[1]);
    })
  })

  const textActor = vtkActor.newInstance();
  textActor.setMapper(psMapper);
  renderer.addActor(textActor);
  renderer.resetCamera();

  return data
}

// example of how to call it
function testSetup() {
  const container: HTMLElement = ... (the div that encloses both opengl canvas and text canvas, with position: relative)
  const { width, height } = container.getBoundingClientRect()
  
  // Set up the text canvas overlay for the labels
  const textCanvas = document.createElement('canvas')
  textCanvas.setAttribute('style', 'position: absolute; top: 0; left: 0');
  textCanvas.setAttribute('width', width.toString());
  textCanvas.setAttribute('height', height.toString());
  container.appendChild(textCanvas)
  const textCtx = textCanvas.getContext('2d')!

  let renderTextData = setupRenderText(renderer, openglRenderWindow, textCtx!
  addRenderText(renderTextData, [0, 0, 0], "the text")
}

1 Like