text: import macro                      from 'vtk.js/Sources/macro';
  import vtk                        from 'vtk.js/Sources/vtk';
  import vtkActor                   from 'vtk.js/Sources/Rendering/Core/Actor';
  import vtkCalculator              from 'vtk.js/Sources/Filters/General/Calculator';
  import vtkConeSource              from 'vtk.js/Sources/Filters/Sources/ConeSource';
  import vtkDataArray               from 'vtk.js/Sources/Common/Core/DataArray';
  import vtkFullScreenRenderWindow  from 'vtk.js/Sources/Rendering/Misc/FullScreenRenderWindow';
  import vtkMapper                  from 'vtk.js/Sources/Rendering/Core/Mapper';
  import { AttributeTypes }         from 'vtk.js/Sources/Common/DataModel/DataSetAttributes/Constants';
  import { FieldDataTypes }         from 'vtk.js/Sources/Common/DataModel/DataSet/Constants';
  
  import controlPanel from './controller.html';
  
  // ----------------------------------------------------------------------------
  // Standard rendering code setup
  // ----------------------------------------------------------------------------
  
  const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({ background: [0, 0, 0] });
  const renderer = fullScreenRenderer.getRenderer();
  const renderWindow = fullScreenRenderer.getRenderWindow();
  
  // ----------------------------------------------------------------------------
  // Example code
  // ----------------------------------------------------------------------------
  
  const pipelineExecution = [0, 0, 0, 0];
  
  function updateExecution(filterIdx) {
    pipelineExecution[filterIdx]++;
  
    pipelineExecution.forEach((value, index) => {
      const el = document.querySelector(`.filter${index}`);
      if (el) {
        el.innerHTML = `Execution #${value}`;
      }
    });
  }
  
  const coneSource = vtkConeSource.newInstance({ height: 1.0 });
  const filterA = vtkCalculator.newInstance();
  const filterB = vtkCalculator.newInstance();
  const filterC = vtkCalculator.newInstance();
  
  filterA.setFormula({
    getArrays: inputDataSets => ({
      input: [],
      output: [
        { location: FieldDataTypes.CELL, name: 'a', dataType: 'Float32Array', attribute: AttributeTypes.SCALARS },
      ],
    }),
    evaluate: (arraysIn, arraysOut) => {
      updateExecution(0);
      const [scalars] = arraysOut.map(d => d.getData());
      for (let i = 0; i < scalars.length; i++) {
        scalars[i] = Math.random();
      }
    },
  });
  
  filterB.setFormula({
    getArrays: inputDataSets => ({
      input: [],
      output: [
        { location: FieldDataTypes.CELL, name: 'b', dataType: 'Float32Array', attribute: AttributeTypes.SCALARS },
      ],
    }),
    evaluate: (arraysIn, arraysOut) => {
      updateExecution(1);
      const [scalars] = arraysOut.map(d => d.getData());
      for (let i = 0; i < scalars.length; i++) {
        scalars[i] = Math.random();
      }
    },
  });
  
  const randFilter = macro.newInstance((publicAPI, model) => {
    macro.obj(publicAPI, model); // make it an object
    macro.algo(publicAPI, model, 1, 1); // mixin algorithm code 1 in, 1 out
    publicAPI.requestData = (inData, outData) => { // implement requestData
      updateExecution(2);
      const newArray = new Float32Array(inData[0].getPoints().getNumberOfPoints());
      for (let i = 0; i < newArray.length; i++) {
        newArray[i] = i % 2 ? 1 : 0;
      }
  
      const da = vtkDataArray.newInstance({ name: 'spike', values: newArray });
      const newDataSet = vtk({ vtkClass: inData[0].getClassName() });
      newDataSet.shallowCopy(inData[0]);
      newDataSet.getPointData().setScalars(da);
      outData[0] = newDataSet;
    };
  })();
  
  filterC.setFormula({
    getArrays: inputDataSets => ({
      input: [],
      output: [
        { location: FieldDataTypes.CELL, name: 'c', dataType: 'Float32Array', attribute: AttributeTypes.SCALARS },
      ],
    }),
    evaluate: (arraysIn, arraysOut) => {
      updateExecution(3);
      const [scalars] = arraysOut.map(d => d.getData());
      for (let i = 0; i < scalars.length; i++) {
        scalars[i] = Math.random();
      }
    },
  });
  
  filterA.setInputConnection(coneSource.getOutputPort());
  filterB.setInputConnection(filterA.getOutputPort());
  randFilter.setInputConnection(filterB.getOutputPort());
  filterC.setInputConnection(randFilter.getOutputPort());
  
  const mapper = vtkMapper.newInstance();
  mapper.setInputConnection(filterC.getOutputPort());
  
  const actor = vtkActor.newInstance();
  actor.setMapper(mapper);
  
  renderer.addActor(actor);
  renderer.resetCamera();
  renderWindow.render();
  
  // -----------------------------------------------------------
  // UI control handling
  // -----------------------------------------------------------
  
  fullScreenRenderer.addController(controlPanel);
  const representationSelector = document.querySelector('.representations');
  const resolutionChange = document.querySelector('.resolution');
  
  representationSelector.addEventListener('change', (e) => {
    const newRepValue = Number(e.target.value);
    actor.getProperty().setRepresentation(newRepValue);
    renderWindow.render();
  });
  
  resolutionChange.addEventListener('input', (e) => {
    const resolution = Number(e.target.value);
    coneSource.setResolution(resolution);
    renderWindow.render();
  });
  
  const buttons = document.querySelectorAll('button');
  for (let i = 0; i < buttons.length; i++) {
    buttons[i].addEventListener('click', (e) => {
      const idx = Number(e.target.name);
      if (!isNaN(idx)) {
        const filter = global.filters[idx];
        filter.modified();
        renderWindow.render();
      } else {
        // Reset
        pipelineExecution.forEach((v, index) => {
          pipelineExecution[index] = 0;
        });
        updateExecution(-1);
      }
    });
  }
  
  // Update UI
  updateExecution(-1);
  
  // -----------------------------------------------------------
  // Make some variables global so that you can inspect and
  // modify objects in your browser's developer console:
  // -----------------------------------------------------------
  
  global.source = coneSource;
  global.filters = [filterA, filterB, randFilter, filterC];
  global.mapper = mapper;
  global.actor = actor;
  global.renderer = renderer;
  global.renderWindow = renderWindow;
  