Skip to content

Plain JavaScript

The following examples rely on loading the vtk.umd.js bundle from a CDN. Also to mainly focus on the initialization part, we've externalized the JS/WASM code since that part does not change.

Load WASM as module

In this example we pre-load the WASM module and therefore we don't need to provide any URL for loading it when creating the vtk namespace.

html
<html>
    <head>
        <script
            src="/vtk-wasm/wasm32/latest/vtkWebAssembly.mjs"
            type="module"
        ></script>
        <script src="https://unpkg.com/@kitware/trame-vtklocal@1.1.1/dist/umd/vtk.umd.js"></script>
        <script src="example.js"></script>
    </head>
    <body>
        <canvas id="vtk-wasm-window"></canvas>
        <script>
            vtkWASM.createNamespace().then(buildWASMScene);
        </script>
    </body>
</html>
js
async function buildWASMScene(vtk) {
// Make up some data array to generate a mesh (JS-only) 
    function makeQuadMesh(nx, ny) {
        // Create a grid of points on the XY plane from (0, 0) to (nx, ny)
        const pointJSArray = []
        for (let i = 0; i < ny + 1; i++) {
            for (let j = 0; j < nx + 1; j++) {
                const x = (j - 0.5 * nx) / nx;
                const y = (i - 0.5 * ny) / ny;
                pointJSArray.push(x); // x-coordinate
                pointJSArray.push(y); // y-coordinate
                pointJSArray.push(2 * Math.sqrt(x * x + y * y) * Math.sin(x) * Math.cos(y)); // z-coordinate
            }
        }

        const connectivityJSArray = [];
        const offsetsJSArray = [];
        for (let i = 0; i < ny; i++) {
            for (let j = 0; j < nx; j++) {
                offsetsJSArray.push(connectivityJSArray.length);
                connectivityJSArray.push(j + i * (nx + 1));
                connectivityJSArray.push(j + i * (nx + 1) + 1);
                connectivityJSArray.push(j + i * (nx + 1) + nx + 2);
                connectivityJSArray.push(j + i * (nx + 1) + nx + 1);
            }
        }
        offsetsJSArray.push(connectivityJSArray.length);

        return { 
            points: pointJSArray, 
            offsets: offsetsJSArray, 
            connectivity: connectivityJSArray,
        };
    }
    const meshData = makeQuadMesh(50, 50);

    // Working on VTK.wasm
    // => vtkObject creation is directly available on the vtk namespace
    const points = vtk.vtkPoints();
    const polys = vtk.vtkCellArray()
    const connectivity = vtk.vtkTypeInt32Array();
    const offsets = vtk.vtkTypeInt32Array();

    // Ways to bind JS data to VTK.wasm types
    // => method call are async and needs to be awaited
    // => property can be accessed using the dot notation
    await points.data.setArray(new Float32Array(meshData.points));
    await connectivity.setArray(new Int32Array(meshData.connectivity));
    await offsets.setArray(new Int32Array(meshData.offsets));

    // Calling methods with other vtkObject as arguments
    await polys.setData(offsets, connectivity);

    // Using properties to set values as a batch update
    const polyData = vtk.vtkPolyData();
    polyData.set({ points, polys })

    // Getting values from method call (async) or property (sync)
    console.log("NumberOfPoints:", await polyData.getNumberOfPoints());
    console.log("NumberOfCells:", await polyData.getNumberOfCells());
    console.log("PolyDataBounds:", await polyData.getBounds());

    // Create object with properties in constructor
    const mapper = vtk.vtkPolyDataMapper();
    await mapper.setInputData(polyData);
    const actor = vtk.vtkActor({ mapper });

    // Setting a property even across vtkObjects
    // Same as: await (await actor.getProperty()).setEdgeVisibility(true);
    actor.property.edgeVisibility = true;

    // Setup rendering part
    const renderer = vtk.vtkRenderer();
    await renderer.addActor(actor);
    await renderer.resetCamera();

    // Create a RenderWindow and bind it to a canvas in the DOM
    const canvasSelector = "#vtk-wasm-window";
    const renderWindow = vtk.vtkRenderWindow({ canvasSelector });
    await renderWindow.addRenderer(renderer);
    const interactor = vtk.vtkRenderWindowInteractor({ 
        canvasSelector,
        renderWindow,
    });

    // Trigger render and start interactor
    await interactor.render();
    await interactor.start();

    // Observing vtkObject
    const tag = renderWindow.observe("StartEvent", () => {
        console.log("Camera position", renderer.activeCamera.position);
    });
    setTimeout(() => {
        // Remove observer for one specific tag
        renderWindow.unObserve(tag);

        // Remove all observers
        renderWindow.unObserveAll();

        // Print the full state of a vtkObject
        console.log("Camera state:", renderer.activeCamera.state);
    }, 30000);
}

Defer WASM loading

In this example, since we didn't load the WASM module, we need to specify from where it should be loaded.

In this context we provide the URL where the WASM bundle can be found and used from.

html
<html>
    <head>
        <script src="https://unpkg.com/@kitware/trame-vtklocal@1.1.1/dist/umd/vtk.umd.js"></script>
        <script src="example.js"></script>
    </head>
    <body>
        <canvas id="vtk-wasm-window"></canvas>
        <script>
            vtkWASM
                .createNamespace("/vtk-wasm/wasm32/latest")
                .then(buildWASMScene);
        </script>
    </body>
</html>
js
async function buildWASMScene(vtk) {
// Make up some data array to generate a mesh (JS-only) 
    function makeQuadMesh(nx, ny) {
        // Create a grid of points on the XY plane from (0, 0) to (nx, ny)
        const pointJSArray = []
        for (let i = 0; i < ny + 1; i++) {
            for (let j = 0; j < nx + 1; j++) {
                const x = (j - 0.5 * nx) / nx;
                const y = (i - 0.5 * ny) / ny;
                pointJSArray.push(x); // x-coordinate
                pointJSArray.push(y); // y-coordinate
                pointJSArray.push(2 * Math.sqrt(x * x + y * y) * Math.sin(x) * Math.cos(y)); // z-coordinate
            }
        }

        const connectivityJSArray = [];
        const offsetsJSArray = [];
        for (let i = 0; i < ny; i++) {
            for (let j = 0; j < nx; j++) {
                offsetsJSArray.push(connectivityJSArray.length);
                connectivityJSArray.push(j + i * (nx + 1));
                connectivityJSArray.push(j + i * (nx + 1) + 1);
                connectivityJSArray.push(j + i * (nx + 1) + nx + 2);
                connectivityJSArray.push(j + i * (nx + 1) + nx + 1);
            }
        }
        offsetsJSArray.push(connectivityJSArray.length);

        return { 
            points: pointJSArray, 
            offsets: offsetsJSArray, 
            connectivity: connectivityJSArray,
        };
    }
    const meshData = makeQuadMesh(50, 50);

    // Working on VTK.wasm
    // => vtkObject creation is directly available on the vtk namespace
    const points = vtk.vtkPoints();
    const polys = vtk.vtkCellArray()
    const connectivity = vtk.vtkTypeInt32Array();
    const offsets = vtk.vtkTypeInt32Array();

    // Ways to bind JS data to VTK.wasm types
    // => method call are async and needs to be awaited
    // => property can be accessed using the dot notation
    await points.data.setArray(new Float32Array(meshData.points));
    await connectivity.setArray(new Int32Array(meshData.connectivity));
    await offsets.setArray(new Int32Array(meshData.offsets));

    // Calling methods with other vtkObject as arguments
    await polys.setData(offsets, connectivity);

    // Using properties to set values as a batch update
    const polyData = vtk.vtkPolyData();
    polyData.set({ points, polys })

    // Getting values from method call (async) or property (sync)
    console.log("NumberOfPoints:", await polyData.getNumberOfPoints());
    console.log("NumberOfCells:", await polyData.getNumberOfCells());
    console.log("PolyDataBounds:", await polyData.getBounds());

    // Create object with properties in constructor
    const mapper = vtk.vtkPolyDataMapper();
    await mapper.setInputData(polyData);
    const actor = vtk.vtkActor({ mapper });

    // Setting a property even across vtkObjects
    // Same as: await (await actor.getProperty()).setEdgeVisibility(true);
    actor.property.edgeVisibility = true;

    // Setup rendering part
    const renderer = vtk.vtkRenderer();
    await renderer.addActor(actor);
    await renderer.resetCamera();

    // Create a RenderWindow and bind it to a canvas in the DOM
    const canvasSelector = "#vtk-wasm-window";
    const renderWindow = vtk.vtkRenderWindow({ canvasSelector });
    await renderWindow.addRenderer(renderer);
    const interactor = vtk.vtkRenderWindowInteractor({ 
        canvasSelector,
        renderWindow,
    });

    // Trigger render and start interactor
    await interactor.render();
    await interactor.start();

    // Observing vtkObject
    const tag = renderWindow.observe("StartEvent", () => {
        console.log("Camera position", renderer.activeCamera.position);
    });
    setTimeout(() => {
        // Remove observer for one specific tag
        renderWindow.unObserve(tag);

        // Remove all observers
        renderWindow.unObserveAll();

        // Print the full state of a vtkObject
        console.log("Camera state:", renderer.activeCamera.state);
    }, 30000);
}

Defer WASM loading with annotation

In this example we tag the script to autoload WASM and create a global vtk namespace.

html
<html>
    <head>
        <script
            src="https://unpkg.com/@kitware/trame-vtklocal@1.1.1/dist/umd/vtk.umd.js"
            id="vtk-wasm"
            data-url="/vtk-wasm/wasm32/latest"
        ></script>
        <script src="example.js"></script>
    </head>
    <body>
        <canvas id="vtk-wasm-window"></canvas>
        <script>
            vtkReady.then((vtk) => {
                buildWASMScene(vtk); // Also available on window.vtk
            });
        </script>
    </body>
</html>
js
async function buildWASMScene(vtk) {
// Make up some data array to generate a mesh (JS-only) 
    function makeQuadMesh(nx, ny) {
        // Create a grid of points on the XY plane from (0, 0) to (nx, ny)
        const pointJSArray = []
        for (let i = 0; i < ny + 1; i++) {
            for (let j = 0; j < nx + 1; j++) {
                const x = (j - 0.5 * nx) / nx;
                const y = (i - 0.5 * ny) / ny;
                pointJSArray.push(x); // x-coordinate
                pointJSArray.push(y); // y-coordinate
                pointJSArray.push(2 * Math.sqrt(x * x + y * y) * Math.sin(x) * Math.cos(y)); // z-coordinate
            }
        }

        const connectivityJSArray = [];
        const offsetsJSArray = [];
        for (let i = 0; i < ny; i++) {
            for (let j = 0; j < nx; j++) {
                offsetsJSArray.push(connectivityJSArray.length);
                connectivityJSArray.push(j + i * (nx + 1));
                connectivityJSArray.push(j + i * (nx + 1) + 1);
                connectivityJSArray.push(j + i * (nx + 1) + nx + 2);
                connectivityJSArray.push(j + i * (nx + 1) + nx + 1);
            }
        }
        offsetsJSArray.push(connectivityJSArray.length);

        return { 
            points: pointJSArray, 
            offsets: offsetsJSArray, 
            connectivity: connectivityJSArray,
        };
    }
    const meshData = makeQuadMesh(50, 50);

    // Working on VTK.wasm
    // => vtkObject creation is directly available on the vtk namespace
    const points = vtk.vtkPoints();
    const polys = vtk.vtkCellArray()
    const connectivity = vtk.vtkTypeInt32Array();
    const offsets = vtk.vtkTypeInt32Array();

    // Ways to bind JS data to VTK.wasm types
    // => method call are async and needs to be awaited
    // => property can be accessed using the dot notation
    await points.data.setArray(new Float32Array(meshData.points));
    await connectivity.setArray(new Int32Array(meshData.connectivity));
    await offsets.setArray(new Int32Array(meshData.offsets));

    // Calling methods with other vtkObject as arguments
    await polys.setData(offsets, connectivity);

    // Using properties to set values as a batch update
    const polyData = vtk.vtkPolyData();
    polyData.set({ points, polys })

    // Getting values from method call (async) or property (sync)
    console.log("NumberOfPoints:", await polyData.getNumberOfPoints());
    console.log("NumberOfCells:", await polyData.getNumberOfCells());
    console.log("PolyDataBounds:", await polyData.getBounds());

    // Create object with properties in constructor
    const mapper = vtk.vtkPolyDataMapper();
    await mapper.setInputData(polyData);
    const actor = vtk.vtkActor({ mapper });

    // Setting a property even across vtkObjects
    // Same as: await (await actor.getProperty()).setEdgeVisibility(true);
    actor.property.edgeVisibility = true;

    // Setup rendering part
    const renderer = vtk.vtkRenderer();
    await renderer.addActor(actor);
    await renderer.resetCamera();

    // Create a RenderWindow and bind it to a canvas in the DOM
    const canvasSelector = "#vtk-wasm-window";
    const renderWindow = vtk.vtkRenderWindow({ canvasSelector });
    await renderWindow.addRenderer(renderer);
    const interactor = vtk.vtkRenderWindowInteractor({ 
        canvasSelector,
        renderWindow,
    });

    // Trigger render and start interactor
    await interactor.render();
    await interactor.start();

    // Observing vtkObject
    const tag = renderWindow.observe("StartEvent", () => {
        console.log("Camera position", renderer.activeCamera.position);
    });
    setTimeout(() => {
        // Remove observer for one specific tag
        renderWindow.unObserve(tag);

        // Remove all observers
        renderWindow.unObserveAll();

        // Print the full state of a vtkObject
        console.log("Camera state:", renderer.activeCamera.state);
    }, 30000);
}

Configuration options

The method createNamespace(url, config) takes two arguments. The first one is used to specify the base directory where the wasm file from VTK will be find. When the module is loaded, the url parameter could be skipped. For the config it is aimed to tune how you would like your WASM environement to behave. The following sections cover the various options and what it means.

  • { rendering: 'webgl', mode: 'sync' }
    • Using WebGL2 for rendering.
    • Using synchronous method execution.
  • { rendering: 'webgl', mode: 'async' }
    • Using WebGL2 for rendering.
    • Using asynchronous method execution.
    • This require WebAssembly JavaScript Promise Integration (JSPI) support in your browser
  • { rendering: 'webgpu' }
    • Using WebGPU for rendering.
    • WebGPU only works with the asynchronous implementation of method execution.
    • This require WebAssembly JavaScript Promise Integration (JSPI) support in your browser

For the annotation usecase you can add data-config="{'rendering': 'webgpu'}" attribute in your HTML to adjust the config setting.