Source: mag.js

/**
 * mag
 */

(function (root, factory) {
  'use strict'; // eslint-disable-line semi

  var name = 'Mag'
  if (typeof define === 'function' && define.amd) {
    define(['./mag-analytics'], function (MagnificentAnalytics) {
      return (root[name] = factory(MagnificentAnalytics))
    })
  } else if (typeof exports === 'object') {
    module.exports = factory(require('./mag-analytics'))
  } else {
    root[name] = factory(root.MagnificentAnalytics)
  }
}(this, function (MagnificentAnalytics) {
  'use strict'; // eslint-disable-line semi

  /**
   * @typedef {Object} MagModelFocus
   * @property {number} x - X position, from [0,1].
   * @property {number} y - Y position, from [0,1].
   */

  /**
   * @typedef {Object} MagModelLens
   * @property {number} w - Width, from (0,∞).
   * @property {number} h - Height, from (0,∞).
   */

  /**
   * @typedef {Object} MagModel
   * @property {number} zoom - Zoom level, from (0,∞).
   * @property {MagModelFocus} focus - Focus object.
   * @property {MagModelLens} lens - Lens object.
   */

  /**
   * @typedef {Object} MagOptions
   * @property {MagModel} model - A model.
   * @property {number} zoomMin - Minimum zoom level allowed, from (0,∞).
   * @property {number} zoomMax - Maximum zoom level allowed, from (0,∞).
   * @property {boolean} constrainLens - Whether lens position is constrained.
   * @property {boolean} constrainZoomed - Whether zoomed position is constrained.
   */

  /**
   * Mag constructor.
   *
   * @alias module:mag
   *
   * @class
   *
   * @param {MagOptions} options - Options.
   */
  var Mag = function (options) {
    options = options || {}
    options.model = options.model || {}
    options.zoomMin = options.zoomMin || 1
    options.zoomMax = options.zoomMax || 10
    options.constrainLens = options.constrainLens !== false
    options.constrainZoomed = options.constrainZoomed !== false

    this.id = options.id

    this.model = options.model
    this.options = options

    this.fillModel()
  }

  Mag.prototype.fillXY = function (r) {
    r = r || {}
    r.x = r.x || 0
    r.y = r.y || 0
    return r
  }

  Mag.prototype.fillWH = function (r) {
    r = r || {}
    r.w = r.w || 0
    r.h = r.h || 0
    return r
  }

  Mag.prototype.fillModel = function () {
    var model = this.model
    model.mode = model.mode || 'lag'
    model.focus = this.fillXY(model.focus)
    model.lens = this.fillXY(this.fillWH(model.lens))
    model.zoomed = this.fillXY(this.fillWH(model.zoomed))
    model.boundedLens = this.fillXY(this.fillWH(model.boundedLens))
    model.zoom = model.zoom || 1
    model.ratio = model.ratio || 1
  }

  /**
   * Update computed model state, especially lens and zoomed.
   */
  Mag.prototype.compute = function () {
    var lens, focus, zoomed, zoom, dw, dh
    var options = this.options
    var model = this.model
    lens = model.lens
    focus = model.focus
    zoomed = model.zoomed
    zoom = model.zoom

    zoom = this.minMax(zoom, options.zoomMin, options.zoomMax)

    focus.x = this.minMax(focus.x, 0, 1)
    focus.y = this.minMax(focus.y, 0, 1)

    dw = 1 / zoom
    dh = 1 / zoom
    dh = dh / model.ratio

    lens.w = dw
    lens.h = dh

    if (options.constrainLens) {
      lens = this.constrainLensWH(lens)
    }
    lens.x = focus.x - (lens.w / 2)
    lens.y = focus.y - (lens.h / 2)
    if (options.constrainLens) {
      lens = this.constrainLensXY(lens)
    }

    zoomed.w = 1 / dw
    zoomed.h = 1 / dh

    var z = this.constrainZoomed(zoomed, options)
    if (z.w !== zoomed.w) {
      zoom *= z.w / zoomed.w
    }
    zoomed = z

    zoomed.x = 0.5 - focus.x * zoomed.w
    zoomed.y = 0.5 - focus.y * zoomed.h

    // the following is better equation for constrained zoom
    // zoomed.x = focus.x * (1 - zoom)
    // zoomed.y = focus.y * (1 - zoom)

    if (options.constrainZoomed) {
      zoomed.x = this.minMax(zoomed.x, 1 - zoom, 0)
      zoomed.y = this.minMax(zoomed.y, 1 - zoom, 0)
    }

    model.lens = lens
    model.focus = focus
    model.zoomed = zoomed
    model.zoom = zoom
  }

  Mag.prototype.minMax = function (val, min, max) {
    return val < min ? min : (val > max ? max : val)
  }

  Mag.prototype.minMax1 = function (val, min) {
    return this.minMax(val, min, 1)
  }

  Mag.prototype.constrainZoomed = function (r, options) {
    var wm
    var hm
    wm = this.minMax(r.w, options.zoomMin, options.zoomMax)
    if (wm !== r.w) {
      hm *= wm / r.w
      hm = this.minMax(hm, options.zoomMin, options.zoomMax)
    } else {
      hm = this.minMax(r.h, options.zoomMin, options.zoomMax)
      if (hm !== r.h) {
        wm *= hm / r.h
        wm = this.minMax(wm, options.zoomMin, options.zoomMax)
      }
    }
    return {
      w: wm,
      h: hm,
      x: r.x,
      y: r.y
    }
  }

  Mag.prototype.constrainLensWH = function (r) {
    var wm
    var hm
    wm = this.minMax1(r.w, 0.1)
    if (wm !== r.w) {
      hm *= wm / r.w
      hm = this.minMax1(hm, 0.1)
    } else {
      hm = this.minMax1(r.h, 0.1)
      if (hm !== r.h) {
        wm *= hm / r.h
        wm = this.minMax1(wm, 0.1)
      }
    }
    return {
      w: wm,
      h: hm,
      x: r.x,
      y: r.y
    }
  }

  Mag.prototype.constrainLensXY = function (r) {
    return {
      x: this.minMax(r.x, 0, 1 - r.w),
      y: this.minMax(r.y, 0, 1 - r.h),
      w: r.w,
      h: r.h
    }
  }

  Mag.prototype.constrainLens = function (r) {
    var c = this.constrainLensXY(this.constrainLensWH(r))
    if (((c.w + c.x) > 1)) {
      c.x = Math.max(0, 1 - c.w)
    }
    if (((c.h + c.y) > 1)) {
      c.y = Math.max(0, 1 - c.h)
    }
    return c
  }

  Mag.prototype.project = function (frame) {
    var model = this.model
    var lens = model.lens
    return {
      x: lens.x * frame.w,
      y: lens.y * frame.h,
      w: lens.w * frame.w,
      h: lens.h * frame.h
    }
  }

  if (MagnificentAnalytics) {
    MagnificentAnalytics.track('mag.js')
  }

  return Mag
}))