async function renderLatentSpace(latentSpace) {
  document.getElementById('plot-area').innerText = '';
  const [xAxis, yAxis] = latentSpace;

  // Create the canvases that we will draw to.
  const xPlaceholder = Array(xAxis.shape[0]).fill(0);
  const yPlaceholder = Array(yAxis.shape[0]).fill(0);

  const rows = d3Select('.plot-area').selectAll('div.row').data(xPlaceholder);
  const rEnter = rows.enter().append('div').attr('class', 'row');
  rows.exit().remove();

  const cols = rEnter.selectAll('div.col').data(yPlaceholder);
  cols.enter()
      .append('div')
      .attr('class', 'col')
      .append('canvas')
      .attr('width', 50)
      .attr('height', 50);

  // Generate images and render them to each canvas element
  rows.merge(rEnter).each(async function(rowZ, rowIndex) {
    // Generate a batch of zVectors for each row.
    const zX = xAxis.slice(rowIndex, 1).tile(yAxis.shape);
    const zBatch = zX.stack(yAxis).transpose();
    const batchImageTensor = decodeZ(zBatch);
    const imageTensors = batchImageTensor.unstack();

    tf.dispose([zX, zBatch, batchImageTensor]);

    const cols = d3Select(this).selectAll('.col');
    cols.each(async function(colZ, colIndex) {
      const canvas = d3Select(this).select('canvas').node();
      const imageTensor = imageTensors[colIndex];

      // Render the results to the canvas
      tf.browser.toPixels(imageTensor, canvas).then(() => {
        tf.dispose([imageTensor]);
      });
    });
  });
}
Render the latent space by z vectors through the VAE and rendering
the result.

Handles only 2D latent spaces