DatacubeView.js

'use strict'

var Datacube = require('./Datacube')
var isNil = require('lodash/isNil')

/**
 * @typedef View
 * @property {HTMLCanvasElement} node
 * @property {Number[][][]} data
 * @property {string} axis
 * @property {Function} [onMouseMove]
 * @property {boolean} [matchCanvasToData=true] sets height and width to the same dims as the data from the axis perspective
 */
/**
 * @memberOf DatacubeView
 * @property {HTMLCanvasElement} node
 * @property {ClientRect} nodeRect
 * @property {Datacube} datacube
 */

/**
 * @class DatacubeView
 * @param {View} view
 */
function DatacubeView (view) {
  this.datacube = new Datacube({ data: view.data })
  this.node = view.node
  this.axis = view.axis
  if (view.onMouseMove) this.onMouseMove = view.onMouseMove
  this.ctx = this.node.getContext('2d')
  this.node.addEventListener('mousemove', this.mousemove.bind(this))
  this.position = Object.assign({}, this.datacube.midpoints)
  if (view.matchCanvasToData || isNil(view.matchCanvasToData)) this.matchCanvasToData()
  this.render()
}

/**
 * @private
 */
DatacubeView.prototype.constrainAxisPosition = function (opts) {
  var axis = opts.axis
  var requested = opts.requested
  if (requested <= 0) return 0
  else if (requested >= this.datacube.dims[axis]) return this.datacube.dims[axis] - 1
  return requested
}

/**
 * Resize datacube canvas node height & width to match data slice dimensions.
 * @returns {undefined}
 */
DatacubeView.prototype.matchCanvasToData = function () {
  if (this.axis === 'x') {
    this.node.height = this.datacube.dims.z
    this.node.width = this.datacube.dims.y
  } else if (this.axis === 'y') {
    this.node.height = this.datacube.dims.z
    this.node.width = this.datacube.dims.x
  } else { // z
    this.node.height = this.datacube.dims.y
    this.node.width = this.datacube.dims.x
  }
}

/**
 * Handles a mousemove event on the canvas node & updates the view's `position`
 * value.
 * @param {Event} evt
 * @returns {undefined}
 */
DatacubeView.prototype.mousemove = function (evt) {
  if (this.axis === 'x') {
    this.position.y = this.constrainAxisPosition({ axis: 'y', requested: evt.offsetX })
    this.position.z = this.constrainAxisPosition({ axis: 'z', requested: evt.offsetY })
  } else if (this.axis === 'y') {
    this.position.x = this.constrainAxisPosition({ axis: 'x', requested: evt.offsetX })
    this.position.z = this.constrainAxisPosition({ axis: 'z', requested: evt.offsetY })
  } else { // z
    this.position.x = this.constrainAxisPosition({ axis: 'x', requested: evt.offsetX })
    this.position.y = this.constrainAxisPosition({ axis: 'y', requested: evt.offsetY })
  }
  console.log(this.position)
  if (this.onMouseMove) this.onMouseMove(this)
}

/**
 * Renders the 2D slice of the data onto the canvas based on
 * the view's current `position`.
 * @returns {undefined}
 */
DatacubeView.prototype.render = function () {
  var slice = this.datacube.querySlice({
    axis: this.axis,
    value: Math.floor(this.position[this.axis])
  })
  var width = slice.length
  var height = slice[0].length
  var image = this.ctx.createImageData(width, height)
  var pixelRgbaIndex = -1
  for (var x = 0; x < slice.length; ++x) {
    for (var y = 0; y < slice[0].length; ++y) {
      var pixelValue = slice[x][y]
      image.data[++pixelRgbaIndex] = 0
      image.data[++pixelRgbaIndex] = 0
      image.data[++pixelRgbaIndex] = 0
      image.data[++pixelRgbaIndex] = pixelValue
    }
  }
  this.ctx.putImageData(image, 0, 0)
}

module.exports = DatacubeView