Text labels in vtk.js?

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