<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
--><!--
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
--><!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
--><!--
@license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
--><!--
@license
Copyright (C) 2016, Iftach Sadeh <iftach.sadeh@desy.de>
The MIT License (MIT)
See the file LICENSE.txt for further details.
--><!--
@license
    paper-dropdown: Wrapper for paper-dropdown-menu
    Copyright (c) 2017 Pushkar Anand

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
--><!--
@license
    paper-dropdown-behavior: Behavior for paper-dropdown
    Copyright (c) 2017 Pushkar Anand

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
--><div hidden="" by-polymer-bundler=""><script>(function(e, a) { for(var i in a) e[i] = a[i]; }(window, /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

  "use strict";


var d3 = __webpack_require__(1);
var sprintf = __webpack_require__(2);
var epiviz = __webpack_require__(3);

module.exports = {
    sprintf: sprintf,
    epiviz : epiviz
};

// window.sprintf = sprintf;
// window.epiviz = epiviz;


/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;!function() {
  var d3 = {
    version: "3.5.17"
  };
  var d3_arraySlice = [].slice, d3_array = function(list) {
    return d3_arraySlice.call(list);
  };
  var d3_document = this.document;
  function d3_documentElement(node) {
    return node && (node.ownerDocument || node.document || node).documentElement;
  }
  function d3_window(node) {
    return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView);
  }
  if (d3_document) {
    try {
      d3_array(d3_document.documentElement.childNodes)[0].nodeType;
    } catch (e) {
      d3_array = function(list) {
        var i = list.length, array = new Array(i);
        while (i--) array[i] = list[i];
        return array;
      };
    }
  }
  if (!Date.now) Date.now = function() {
    return +new Date();
  };
  if (d3_document) {
    try {
      d3_document.createElement("DIV").style.setProperty("opacity", 0, "");
    } catch (error) {
      var d3_element_prototype = this.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = this.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty;
      d3_element_prototype.setAttribute = function(name, value) {
        d3_element_setAttribute.call(this, name, value + "");
      };
      d3_element_prototype.setAttributeNS = function(space, local, value) {
        d3_element_setAttributeNS.call(this, space, local, value + "");
      };
      d3_style_prototype.setProperty = function(name, value, priority) {
        d3_style_setProperty.call(this, name, value + "", priority);
      };
    }
  }
  d3.ascending = d3_ascending;
  function d3_ascending(a, b) {
    return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
  }
  d3.descending = function(a, b) {
    return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
  };
  d3.min = function(array, f) {
    var i = -1, n = array.length, a, b;
    if (arguments.length === 1) {
      while (++i < n) if ((b = array[i]) != null && b >= b) {
        a = b;
        break;
      }
      while (++i < n) if ((b = array[i]) != null && a > b) a = b;
    } else {
      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
        a = b;
        break;
      }
      while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b;
    }
    return a;
  };
  d3.max = function(array, f) {
    var i = -1, n = array.length, a, b;
    if (arguments.length === 1) {
      while (++i < n) if ((b = array[i]) != null && b >= b) {
        a = b;
        break;
      }
      while (++i < n) if ((b = array[i]) != null && b > a) a = b;
    } else {
      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
        a = b;
        break;
      }
      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b;
    }
    return a;
  };
  d3.extent = function(array, f) {
    var i = -1, n = array.length, a, b, c;
    if (arguments.length === 1) {
      while (++i < n) if ((b = array[i]) != null && b >= b) {
        a = c = b;
        break;
      }
      while (++i < n) if ((b = array[i]) != null) {
        if (a > b) a = b;
        if (c < b) c = b;
      }
    } else {
      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
        a = c = b;
        break;
      }
      while (++i < n) if ((b = f.call(array, array[i], i)) != null) {
        if (a > b) a = b;
        if (c < b) c = b;
      }
    }
    return [ a, c ];
  };
  function d3_number(x) {
    return x === null ? NaN : +x;
  }
  function d3_numeric(x) {
    return !isNaN(x);
  }
  d3.sum = function(array, f) {
    var s = 0, n = array.length, a, i = -1;
    if (arguments.length === 1) {
      while (++i < n) if (d3_numeric(a = +array[i])) s += a;
    } else {
      while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a;
    }
    return s;
  };
  d3.mean = function(array, f) {
    var s = 0, n = array.length, a, i = -1, j = n;
    if (arguments.length === 1) {
      while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j;
    } else {
      while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j;
    }
    if (j) return s / j;
  };
  d3.quantile = function(values, p) {
    var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h;
    return e ? v + e * (values[h] - v) : v;
  };
  d3.median = function(array, f) {
    var numbers = [], n = array.length, a, i = -1;
    if (arguments.length === 1) {
      while (++i < n) if (d3_numeric(a = d3_number(array[i]))) numbers.push(a);
    } else {
      while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) numbers.push(a);
    }
    if (numbers.length) return d3.quantile(numbers.sort(d3_ascending), .5);
  };
  d3.variance = function(array, f) {
    var n = array.length, m = 0, a, d, s = 0, i = -1, j = 0;
    if (arguments.length === 1) {
      while (++i < n) {
        if (d3_numeric(a = d3_number(array[i]))) {
          d = a - m;
          m += d / ++j;
          s += d * (a - m);
        }
      }
    } else {
      while (++i < n) {
        if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) {
          d = a - m;
          m += d / ++j;
          s += d * (a - m);
        }
      }
    }
    if (j > 1) return s / (j - 1);
  };
  d3.deviation = function() {
    var v = d3.variance.apply(this, arguments);
    return v ? Math.sqrt(v) : v;
  };
  function d3_bisector(compare) {
    return {
      left: function(a, x, lo, hi) {
        if (arguments.length < 3) lo = 0;
        if (arguments.length < 4) hi = a.length;
        while (lo < hi) {
          var mid = lo + hi >>> 1;
          if (compare(a[mid], x) < 0) lo = mid + 1; else hi = mid;
        }
        return lo;
      },
      right: function(a, x, lo, hi) {
        if (arguments.length < 3) lo = 0;
        if (arguments.length < 4) hi = a.length;
        while (lo < hi) {
          var mid = lo + hi >>> 1;
          if (compare(a[mid], x) > 0) hi = mid; else lo = mid + 1;
        }
        return lo;
      }
    };
  }
  var d3_bisect = d3_bisector(d3_ascending);
  d3.bisectLeft = d3_bisect.left;
  d3.bisect = d3.bisectRight = d3_bisect.right;
  d3.bisector = function(f) {
    return d3_bisector(f.length === 1 ? function(d, x) {
      return d3_ascending(f(d), x);
    } : f);
  };
  d3.shuffle = function(array, i0, i1) {
    if ((m = arguments.length) < 3) {
      i1 = array.length;
      if (m < 2) i0 = 0;
    }
    var m = i1 - i0, t, i;
    while (m) {
      i = Math.random() * m-- | 0;
      t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t;
    }
    return array;
  };
  d3.permute = function(array, indexes) {
    var i = indexes.length, permutes = new Array(i);
    while (i--) permutes[i] = array[indexes[i]];
    return permutes;
  };
  d3.pairs = function(array) {
    var i = 0, n = array.length - 1, p0, p1 = array[0], pairs = new Array(n < 0 ? 0 : n);
    while (i < n) pairs[i] = [ p0 = p1, p1 = array[++i] ];
    return pairs;
  };
  d3.transpose = function(matrix) {
    if (!(n = matrix.length)) return [];
    for (var i = -1, m = d3.min(matrix, d3_transposeLength), transpose = new Array(m); ++i < m; ) {
      for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n; ) {
        row[j] = matrix[j][i];
      }
    }
    return transpose;
  };
  function d3_transposeLength(d) {
    return d.length;
  }
  d3.zip = function() {
    return d3.transpose(arguments);
  };
  d3.keys = function(map) {
    var keys = [];
    for (var key in map) keys.push(key);
    return keys;
  };
  d3.values = function(map) {
    var values = [];
    for (var key in map) values.push(map[key]);
    return values;
  };
  d3.entries = function(map) {
    var entries = [];
    for (var key in map) entries.push({
      key: key,
      value: map[key]
    });
    return entries;
  };
  d3.merge = function(arrays) {
    var n = arrays.length, m, i = -1, j = 0, merged, array;
    while (++i < n) j += arrays[i].length;
    merged = new Array(j);
    while (--n >= 0) {
      array = arrays[n];
      m = array.length;
      while (--m >= 0) {
        merged[--j] = array[m];
      }
    }
    return merged;
  };
  var abs = Math.abs;
  d3.range = function(start, stop, step) {
    if (arguments.length < 3) {
      step = 1;
      if (arguments.length < 2) {
        stop = start;
        start = 0;
      }
    }
    if ((stop - start) / step === Infinity) throw new Error("infinite range");
    var range = [], k = d3_range_integerScale(abs(step)), i = -1, j;
    start *= k, stop *= k, step *= k;
    if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k);
    return range;
  };
  function d3_range_integerScale(x) {
    var k = 1;
    while (x * k % 1) k *= 10;
    return k;
  }
  function d3_class(ctor, properties) {
    for (var key in properties) {
      Object.defineProperty(ctor.prototype, key, {
        value: properties[key],
        enumerable: false
      });
    }
  }
  d3.map = function(object, f) {
    var map = new d3_Map();
    if (object instanceof d3_Map) {
      object.forEach(function(key, value) {
        map.set(key, value);
      });
    } else if (Array.isArray(object)) {
      var i = -1, n = object.length, o;
      if (arguments.length === 1) while (++i < n) map.set(i, object[i]); else while (++i < n) map.set(f.call(object, o = object[i], i), o);
    } else {
      for (var key in object) map.set(key, object[key]);
    }
    return map;
  };
  function d3_Map() {
    this._ = Object.create(null);
  }
  var d3_map_proto = "__proto__", d3_map_zero = "\x00";
  d3_class(d3_Map, {
    has: d3_map_has,
    get: function(key) {
      return this._[d3_map_escape(key)];
    },
    set: function(key, value) {
      return this._[d3_map_escape(key)] = value;
    },
    remove: d3_map_remove,
    keys: d3_map_keys,
    values: function() {
      var values = [];
      for (var key in this._) values.push(this._[key]);
      return values;
    },
    entries: function() {
      var entries = [];
      for (var key in this._) entries.push({
        key: d3_map_unescape(key),
        value: this._[key]
      });
      return entries;
    },
    size: d3_map_size,
    empty: d3_map_empty,
    forEach: function(f) {
      for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]);
    }
  });
  function d3_map_escape(key) {
    return (key += "") === d3_map_proto || key[0] === d3_map_zero ? d3_map_zero + key : key;
  }
  function d3_map_unescape(key) {
    return (key += "")[0] === d3_map_zero ? key.slice(1) : key;
  }
  function d3_map_has(key) {
    return d3_map_escape(key) in this._;
  }
  function d3_map_remove(key) {
    return (key = d3_map_escape(key)) in this._ && delete this._[key];
  }
  function d3_map_keys() {
    var keys = [];
    for (var key in this._) keys.push(d3_map_unescape(key));
    return keys;
  }
  function d3_map_size() {
    var size = 0;
    for (var key in this._) ++size;
    return size;
  }
  function d3_map_empty() {
    for (var key in this._) return false;
    return true;
  }
  d3.nest = function() {
    var nest = {}, keys = [], sortKeys = [], sortValues, rollup;
    function map(mapType, array, depth) {
      if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array;
      var i = -1, n = array.length, key = keys[depth++], keyValue, object, setter, valuesByKey = new d3_Map(), values;
      while (++i < n) {
        if (values = valuesByKey.get(keyValue = key(object = array[i]))) {
          values.push(object);
        } else {
          valuesByKey.set(keyValue, [ object ]);
        }
      }
      if (mapType) {
        object = mapType();
        setter = function(keyValue, values) {
          object.set(keyValue, map(mapType, values, depth));
        };
      } else {
        object = {};
        setter = function(keyValue, values) {
          object[keyValue] = map(mapType, values, depth);
        };
      }
      valuesByKey.forEach(setter);
      return object;
    }
    function entries(map, depth) {
      if (depth >= keys.length) return map;
      var array = [], sortKey = sortKeys[depth++];
      map.forEach(function(key, keyMap) {
        array.push({
          key: key,
          values: entries(keyMap, depth)
        });
      });
      return sortKey ? array.sort(function(a, b) {
        return sortKey(a.key, b.key);
      }) : array;
    }
    nest.map = function(array, mapType) {
      return map(mapType, array, 0);
    };
    nest.entries = function(array) {
      return entries(map(d3.map, array, 0), 0);
    };
    nest.key = function(d) {
      keys.push(d);
      return nest;
    };
    nest.sortKeys = function(order) {
      sortKeys[keys.length - 1] = order;
      return nest;
    };
    nest.sortValues = function(order) {
      sortValues = order;
      return nest;
    };
    nest.rollup = function(f) {
      rollup = f;
      return nest;
    };
    return nest;
  };
  d3.set = function(array) {
    var set = new d3_Set();
    if (array) for (var i = 0, n = array.length; i < n; ++i) set.add(array[i]);
    return set;
  };
  function d3_Set() {
    this._ = Object.create(null);
  }
  d3_class(d3_Set, {
    has: d3_map_has,
    add: function(key) {
      this._[d3_map_escape(key += "")] = true;
      return key;
    },
    remove: d3_map_remove,
    values: d3_map_keys,
    size: d3_map_size,
    empty: d3_map_empty,
    forEach: function(f) {
      for (var key in this._) f.call(this, d3_map_unescape(key));
    }
  });
  d3.behavior = {};
  function d3_identity(d) {
    return d;
  }
  d3.rebind = function(target, source) {
    var i = 1, n = arguments.length, method;
    while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
    return target;
  };
  function d3_rebind(target, source, method) {
    return function() {
      var value = method.apply(source, arguments);
      return value === source ? target : value;
    };
  }
  function d3_vendorSymbol(object, name) {
    if (name in object) return name;
    name = name.charAt(0).toUpperCase() + name.slice(1);
    for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) {
      var prefixName = d3_vendorPrefixes[i] + name;
      if (prefixName in object) return prefixName;
    }
  }
  var d3_vendorPrefixes = [ "webkit", "ms", "moz", "Moz", "o", "O" ];
  function d3_noop() {}
  d3.dispatch = function() {
    var dispatch = new d3_dispatch(), i = -1, n = arguments.length;
    while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
    return dispatch;
  };
  function d3_dispatch() {}
  d3_dispatch.prototype.on = function(type, listener) {
    var i = type.indexOf("."), name = "";
    if (i >= 0) {
      name = type.slice(i + 1);
      type = type.slice(0, i);
    }
    if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener);
    if (arguments.length === 2) {
      if (listener == null) for (type in this) {
        if (this.hasOwnProperty(type)) this[type].on(name, null);
      }
      return this;
    }
  };
  function d3_dispatch_event(dispatch) {
    var listeners = [], listenerByName = new d3_Map();
    function event() {
      var z = listeners, i = -1, n = z.length, l;
      while (++i < n) if (l = z[i].on) l.apply(this, arguments);
      return dispatch;
    }
    event.on = function(name, listener) {
      var l = listenerByName.get(name), i;
      if (arguments.length < 2) return l && l.on;
      if (l) {
        l.on = null;
        listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1));
        listenerByName.remove(name);
      }
      if (listener) listeners.push(listenerByName.set(name, {
        on: listener
      }));
      return dispatch;
    };
    return event;
  }
  d3.event = null;
  function d3_eventPreventDefault() {
    d3.event.preventDefault();
  }
  function d3_eventSource() {
    var e = d3.event, s;
    while (s = e.sourceEvent) e = s;
    return e;
  }
  function d3_eventDispatch(target) {
    var dispatch = new d3_dispatch(), i = 0, n = arguments.length;
    while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
    dispatch.of = function(thiz, argumentz) {
      return function(e1) {
        try {
          var e0 = e1.sourceEvent = d3.event;
          e1.target = target;
          d3.event = e1;
          dispatch[e1.type].apply(thiz, argumentz);
        } finally {
          d3.event = e0;
        }
      };
    };
    return dispatch;
  }
  d3.requote = function(s) {
    return s.replace(d3_requote_re, "\\$&");
  };
  var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
  var d3_subclass = {}.__proto__ ? function(object, prototype) {
    object.__proto__ = prototype;
  } : function(object, prototype) {
    for (var property in prototype) object[property] = prototype[property];
  };
  function d3_selection(groups) {
    d3_subclass(groups, d3_selectionPrototype);
    return groups;
  }
  var d3_select = function(s, n) {
    return n.querySelector(s);
  }, d3_selectAll = function(s, n) {
    return n.querySelectorAll(s);
  }, d3_selectMatches = function(n, s) {
    var d3_selectMatcher = n.matches || n[d3_vendorSymbol(n, "matchesSelector")];
    d3_selectMatches = function(n, s) {
      return d3_selectMatcher.call(n, s);
    };
    return d3_selectMatches(n, s);
  };
  if (typeof Sizzle === "function") {
    d3_select = function(s, n) {
      return Sizzle(s, n)[0] || null;
    };
    d3_selectAll = Sizzle;
    d3_selectMatches = Sizzle.matchesSelector;
  }
  d3.selection = function() {
    return d3.select(d3_document.documentElement);
  };
  var d3_selectionPrototype = d3.selection.prototype = [];
  d3_selectionPrototype.select = function(selector) {
    var subgroups = [], subgroup, subnode, group, node;
    selector = d3_selection_selector(selector);
    for (var j = -1, m = this.length; ++j < m; ) {
      subgroups.push(subgroup = []);
      subgroup.parentNode = (group = this[j]).parentNode;
      for (var i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) {
          subgroup.push(subnode = selector.call(node, node.__data__, i, j));
          if (subnode && "__data__" in node) subnode.__data__ = node.__data__;
        } else {
          subgroup.push(null);
        }
      }
    }
    return d3_selection(subgroups);
  };
  function d3_selection_selector(selector) {
    return typeof selector === "function" ? selector : function() {
      return d3_select(selector, this);
    };
  }
  d3_selectionPrototype.selectAll = function(selector) {
    var subgroups = [], subgroup, node;
    selector = d3_selection_selectorAll(selector);
    for (var j = -1, m = this.length; ++j < m; ) {
      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) {
          subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j)));
          subgroup.parentNode = node;
        }
      }
    }
    return d3_selection(subgroups);
  };
  function d3_selection_selectorAll(selector) {
    return typeof selector === "function" ? selector : function() {
      return d3_selectAll(selector, this);
    };
  }
  var d3_nsXhtml = "http://www.w3.org/1999/xhtml";
  var d3_nsPrefix = {
    svg: "http://www.w3.org/2000/svg",
    xhtml: d3_nsXhtml,
    xlink: "http://www.w3.org/1999/xlink",
    xml: "http://www.w3.org/XML/1998/namespace",
    xmlns: "http://www.w3.org/2000/xmlns/"
  };
  d3.ns = {
    prefix: d3_nsPrefix,
    qualify: function(name) {
      var i = name.indexOf(":"), prefix = name;
      if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1);
      return d3_nsPrefix.hasOwnProperty(prefix) ? {
        space: d3_nsPrefix[prefix],
        local: name
      } : name;
    }
  };
  d3_selectionPrototype.attr = function(name, value) {
    if (arguments.length < 2) {
      if (typeof name === "string") {
        var node = this.node();
        name = d3.ns.qualify(name);
        return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name);
      }
      for (value in name) this.each(d3_selection_attr(value, name[value]));
      return this;
    }
    return this.each(d3_selection_attr(name, value));
  };
  function d3_selection_attr(name, value) {
    name = d3.ns.qualify(name);
    function attrNull() {
      this.removeAttribute(name);
    }
    function attrNullNS() {
      this.removeAttributeNS(name.space, name.local);
    }
    function attrConstant() {
      this.setAttribute(name, value);
    }
    function attrConstantNS() {
      this.setAttributeNS(name.space, name.local, value);
    }
    function attrFunction() {
      var x = value.apply(this, arguments);
      if (x == null) this.removeAttribute(name); else this.setAttribute(name, x);
    }
    function attrFunctionNS() {
      var x = value.apply(this, arguments);
      if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x);
    }
    return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant;
  }
  function d3_collapse(s) {
    return s.trim().replace(/\s+/g, " ");
  }
  d3_selectionPrototype.classed = function(name, value) {
    if (arguments.length < 2) {
      if (typeof name === "string") {
        var node = this.node(), n = (name = d3_selection_classes(name)).length, i = -1;
        if (value = node.classList) {
          while (++i < n) if (!value.contains(name[i])) return false;
        } else {
          value = node.getAttribute("class");
          while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
        }
        return true;
      }
      for (value in name) this.each(d3_selection_classed(value, name[value]));
      return this;
    }
    return this.each(d3_selection_classed(name, value));
  };
  function d3_selection_classedRe(name) {
    return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
  }
  function d3_selection_classes(name) {
    return (name + "").trim().split(/^|\s+/);
  }
  function d3_selection_classed(name, value) {
    name = d3_selection_classes(name).map(d3_selection_classedName);
    var n = name.length;
    function classedConstant() {
      var i = -1;
      while (++i < n) name[i](this, value);
    }
    function classedFunction() {
      var i = -1, x = value.apply(this, arguments);
      while (++i < n) name[i](this, x);
    }
    return typeof value === "function" ? classedFunction : classedConstant;
  }
  function d3_selection_classedName(name) {
    var re = d3_selection_classedRe(name);
    return function(node, value) {
      if (c = node.classList) return value ? c.add(name) : c.remove(name);
      var c = node.getAttribute("class") || "";
      if (value) {
        re.lastIndex = 0;
        if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name));
      } else {
        node.setAttribute("class", d3_collapse(c.replace(re, " ")));
      }
    };
  }
  d3_selectionPrototype.style = function(name, value, priority) {
    var n = arguments.length;
    if (n < 3) {
      if (typeof name !== "string") {
        if (n < 2) value = "";
        for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
        return this;
      }
      if (n < 2) {
        var node = this.node();
        return d3_window(node).getComputedStyle(node, null).getPropertyValue(name);
      }
      priority = "";
    }
    return this.each(d3_selection_style(name, value, priority));
  };
  function d3_selection_style(name, value, priority) {
    function styleNull() {
      this.style.removeProperty(name);
    }
    function styleConstant() {
      this.style.setProperty(name, value, priority);
    }
    function styleFunction() {
      var x = value.apply(this, arguments);
      if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority);
    }
    return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant;
  }
  d3_selectionPrototype.property = function(name, value) {
    if (arguments.length < 2) {
      if (typeof name === "string") return this.node()[name];
      for (value in name) this.each(d3_selection_property(value, name[value]));
      return this;
    }
    return this.each(d3_selection_property(name, value));
  };
  function d3_selection_property(name, value) {
    function propertyNull() {
      delete this[name];
    }
    function propertyConstant() {
      this[name] = value;
    }
    function propertyFunction() {
      var x = value.apply(this, arguments);
      if (x == null) delete this[name]; else this[name] = x;
    }
    return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant;
  }
  d3_selectionPrototype.text = function(value) {
    return arguments.length ? this.each(typeof value === "function" ? function() {
      var v = value.apply(this, arguments);
      this.textContent = v == null ? "" : v;
    } : value == null ? function() {
      this.textContent = "";
    } : function() {
      this.textContent = value;
    }) : this.node().textContent;
  };
  d3_selectionPrototype.html = function(value) {
    return arguments.length ? this.each(typeof value === "function" ? function() {
      var v = value.apply(this, arguments);
      this.innerHTML = v == null ? "" : v;
    } : value == null ? function() {
      this.innerHTML = "";
    } : function() {
      this.innerHTML = value;
    }) : this.node().innerHTML;
  };
  d3_selectionPrototype.append = function(name) {
    name = d3_selection_creator(name);
    return this.select(function() {
      return this.appendChild(name.apply(this, arguments));
    });
  };
  function d3_selection_creator(name) {
    function create() {
      var document = this.ownerDocument, namespace = this.namespaceURI;
      return namespace === d3_nsXhtml && document.documentElement.namespaceURI === d3_nsXhtml ? document.createElement(name) : document.createElementNS(namespace, name);
    }
    function createNS() {
      return this.ownerDocument.createElementNS(name.space, name.local);
    }
    return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? createNS : create;
  }
  d3_selectionPrototype.insert = function(name, before) {
    name = d3_selection_creator(name);
    before = d3_selection_selector(before);
    return this.select(function() {
      return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null);
    });
  };
  d3_selectionPrototype.remove = function() {
    return this.each(d3_selectionRemove);
  };
  function d3_selectionRemove() {
    var parent = this.parentNode;
    if (parent) parent.removeChild(this);
  }
  d3_selectionPrototype.data = function(value, key) {
    var i = -1, n = this.length, group, node;
    if (!arguments.length) {
      value = new Array(n = (group = this[0]).length);
      while (++i < n) {
        if (node = group[i]) {
          value[i] = node.__data__;
        }
      }
      return value;
    }
    function bind(group, groupData) {
      var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData;
      if (key) {
        var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue;
        for (i = -1; ++i < n; ) {
          if (node = group[i]) {
            if (nodeByKeyValue.has(keyValue = key.call(node, node.__data__, i))) {
              exitNodes[i] = node;
            } else {
              nodeByKeyValue.set(keyValue, node);
            }
            keyValues[i] = keyValue;
          }
        }
        for (i = -1; ++i < m; ) {
          if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) {
            enterNodes[i] = d3_selection_dataNode(nodeData);
          } else if (node !== true) {
            updateNodes[i] = node;
            node.__data__ = nodeData;
          }
          nodeByKeyValue.set(keyValue, true);
        }
        for (i = -1; ++i < n; ) {
          if (i in keyValues && nodeByKeyValue.get(keyValues[i]) !== true) {
            exitNodes[i] = group[i];
          }
        }
      } else {
        for (i = -1; ++i < n0; ) {
          node = group[i];
          nodeData = groupData[i];
          if (node) {
            node.__data__ = nodeData;
            updateNodes[i] = node;
          } else {
            enterNodes[i] = d3_selection_dataNode(nodeData);
          }
        }
        for (;i < m; ++i) {
          enterNodes[i] = d3_selection_dataNode(groupData[i]);
        }
        for (;i < n; ++i) {
          exitNodes[i] = group[i];
        }
      }
      enterNodes.update = updateNodes;
      enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode;
      enter.push(enterNodes);
      update.push(updateNodes);
      exit.push(exitNodes);
    }
    var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]);
    if (typeof value === "function") {
      while (++i < n) {
        bind(group = this[i], value.call(group, group.parentNode.__data__, i));
      }
    } else {
      while (++i < n) {
        bind(group = this[i], value);
      }
    }
    update.enter = function() {
      return enter;
    };
    update.exit = function() {
      return exit;
    };
    return update;
  };
  function d3_selection_dataNode(data) {
    return {
      __data__: data
    };
  }
  d3_selectionPrototype.datum = function(value) {
    return arguments.length ? this.property("__data__", value) : this.property("__data__");
  };
  d3_selectionPrototype.filter = function(filter) {
    var subgroups = [], subgroup, group, node;
    if (typeof filter !== "function") filter = d3_selection_filter(filter);
    for (var j = 0, m = this.length; j < m; j++) {
      subgroups.push(subgroup = []);
      subgroup.parentNode = (group = this[j]).parentNode;
      for (var i = 0, n = group.length; i < n; i++) {
        if ((node = group[i]) && filter.call(node, node.__data__, i, j)) {
          subgroup.push(node);
        }
      }
    }
    return d3_selection(subgroups);
  };
  function d3_selection_filter(selector) {
    return function() {
      return d3_selectMatches(this, selector);
    };
  }
  d3_selectionPrototype.order = function() {
    for (var j = -1, m = this.length; ++j < m; ) {
      for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) {
        if (node = group[i]) {
          if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next);
          next = node;
        }
      }
    }
    return this;
  };
  d3_selectionPrototype.sort = function(comparator) {
    comparator = d3_selection_sortComparator.apply(this, arguments);
    for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator);
    return this.order();
  };
  function d3_selection_sortComparator(comparator) {
    if (!arguments.length) comparator = d3_ascending;
    return function(a, b) {
      return a && b ? comparator(a.__data__, b.__data__) : !a - !b;
    };
  }
  d3_selectionPrototype.each = function(callback) {
    return d3_selection_each(this, function(node, i, j) {
      callback.call(node, node.__data__, i, j);
    });
  };
  function d3_selection_each(groups, callback) {
    for (var j = 0, m = groups.length; j < m; j++) {
      for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) {
        if (node = group[i]) callback(node, i, j);
      }
    }
    return groups;
  }
  d3_selectionPrototype.call = function(callback) {
    var args = d3_array(arguments);
    callback.apply(args[0] = this, args);
    return this;
  };
  d3_selectionPrototype.empty = function() {
    return !this.node();
  };
  d3_selectionPrototype.node = function() {
    for (var j = 0, m = this.length; j < m; j++) {
      for (var group = this[j], i = 0, n = group.length; i < n; i++) {
        var node = group[i];
        if (node) return node;
      }
    }
    return null;
  };
  d3_selectionPrototype.size = function() {
    var n = 0;
    d3_selection_each(this, function() {
      ++n;
    });
    return n;
  };
  function d3_selection_enter(selection) {
    d3_subclass(selection, d3_selection_enterPrototype);
    return selection;
  }
  var d3_selection_enterPrototype = [];
  d3.selection.enter = d3_selection_enter;
  d3.selection.enter.prototype = d3_selection_enterPrototype;
  d3_selection_enterPrototype.append = d3_selectionPrototype.append;
  d3_selection_enterPrototype.empty = d3_selectionPrototype.empty;
  d3_selection_enterPrototype.node = d3_selectionPrototype.node;
  d3_selection_enterPrototype.call = d3_selectionPrototype.call;
  d3_selection_enterPrototype.size = d3_selectionPrototype.size;
  d3_selection_enterPrototype.select = function(selector) {
    var subgroups = [], subgroup, subnode, upgroup, group, node;
    for (var j = -1, m = this.length; ++j < m; ) {
      upgroup = (group = this[j]).update;
      subgroups.push(subgroup = []);
      subgroup.parentNode = group.parentNode;
      for (var i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) {
          subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i, j));
          subnode.__data__ = node.__data__;
        } else {
          subgroup.push(null);
        }
      }
    }
    return d3_selection(subgroups);
  };
  d3_selection_enterPrototype.insert = function(name, before) {
    if (arguments.length < 2) before = d3_selection_enterInsertBefore(this);
    return d3_selectionPrototype.insert.call(this, name, before);
  };
  function d3_selection_enterInsertBefore(enter) {
    var i0, j0;
    return function(d, i, j) {
      var group = enter[j].update, n = group.length, node;
      if (j != j0) j0 = j, i0 = 0;
      if (i >= i0) i0 = i + 1;
      while (!(node = group[i0]) && ++i0 < n) ;
      return node;
    };
  }
  d3.select = function(node) {
    var group;
    if (typeof node === "string") {
      group = [ d3_select(node, d3_document) ];
      group.parentNode = d3_document.documentElement;
    } else {
      group = [ node ];
      group.parentNode = d3_documentElement(node);
    }
    return d3_selection([ group ]);
  };
  d3.selectAll = function(nodes) {
    var group;
    if (typeof nodes === "string") {
      group = d3_array(d3_selectAll(nodes, d3_document));
      group.parentNode = d3_document.documentElement;
    } else {
      group = d3_array(nodes);
      group.parentNode = null;
    }
    return d3_selection([ group ]);
  };
  d3_selectionPrototype.on = function(type, listener, capture) {
    var n = arguments.length;
    if (n < 3) {
      if (typeof type !== "string") {
        if (n < 2) listener = false;
        for (capture in type) this.each(d3_selection_on(capture, type[capture], listener));
        return this;
      }
      if (n < 2) return (n = this.node()["__on" + type]) && n._;
      capture = false;
    }
    return this.each(d3_selection_on(type, listener, capture));
  };
  function d3_selection_on(type, listener, capture) {
    var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener;
    if (i > 0) type = type.slice(0, i);
    var filter = d3_selection_onFilters.get(type);
    if (filter) type = filter, wrap = d3_selection_onFilter;
    function onRemove() {
      var l = this[name];
      if (l) {
        this.removeEventListener(type, l, l.$);
        delete this[name];
      }
    }
    function onAdd() {
      var l = wrap(listener, d3_array(arguments));
      onRemove.call(this);
      this.addEventListener(type, this[name] = l, l.$ = capture);
      l._ = listener;
    }
    function removeAll() {
      var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match;
      for (var name in this) {
        if (match = name.match(re)) {
          var l = this[name];
          this.removeEventListener(match[1], l, l.$);
          delete this[name];
        }
      }
    }
    return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll;
  }
  var d3_selection_onFilters = d3.map({
    mouseenter: "mouseover",
    mouseleave: "mouseout"
  });
  if (d3_document) {
    d3_selection_onFilters.forEach(function(k) {
      if ("on" + k in d3_document) d3_selection_onFilters.remove(k);
    });
  }
  function d3_selection_onListener(listener, argumentz) {
    return function(e) {
      var o = d3.event;
      d3.event = e;
      argumentz[0] = this.__data__;
      try {
        listener.apply(this, argumentz);
      } finally {
        d3.event = o;
      }
    };
  }
  function d3_selection_onFilter(listener, argumentz) {
    var l = d3_selection_onListener(listener, argumentz);
    return function(e) {
      var target = this, related = e.relatedTarget;
      if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) {
        l.call(target, e);
      }
    };
  }
  var d3_event_dragSelect, d3_event_dragId = 0;
  function d3_event_dragSuppress(node) {
    var name = ".dragsuppress-" + ++d3_event_dragId, click = "click" + name, w = d3.select(d3_window(node)).on("touchmove" + name, d3_eventPreventDefault).on("dragstart" + name, d3_eventPreventDefault).on("selectstart" + name, d3_eventPreventDefault);
    if (d3_event_dragSelect == null) {
      d3_event_dragSelect = "onselectstart" in node ? false : d3_vendorSymbol(node.style, "userSelect");
    }
    if (d3_event_dragSelect) {
      var style = d3_documentElement(node).style, select = style[d3_event_dragSelect];
      style[d3_event_dragSelect] = "none";
    }
    return function(suppressClick) {
      w.on(name, null);
      if (d3_event_dragSelect) style[d3_event_dragSelect] = select;
      if (suppressClick) {
        var off = function() {
          w.on(click, null);
        };
        w.on(click, function() {
          d3_eventPreventDefault();
          off();
        }, true);
        setTimeout(off, 0);
      }
    };
  }
  d3.mouse = function(container) {
    return d3_mousePoint(container, d3_eventSource());
  };
  var d3_mouse_bug44083 = this.navigator && /WebKit/.test(this.navigator.userAgent) ? -1 : 0;
  function d3_mousePoint(container, e) {
    if (e.changedTouches) e = e.changedTouches[0];
    var svg = container.ownerSVGElement || container;
    if (svg.createSVGPoint) {
      var point = svg.createSVGPoint();
      if (d3_mouse_bug44083 < 0) {
        var window = d3_window(container);
        if (window.scrollX || window.scrollY) {
          svg = d3.select("body").append("svg").style({
            position: "absolute",
            top: 0,
            left: 0,
            margin: 0,
            padding: 0,
            border: "none"
          }, "important");
          var ctm = svg[0][0].getScreenCTM();
          d3_mouse_bug44083 = !(ctm.f || ctm.e);
          svg.remove();
        }
      }
      if (d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY; else point.x = e.clientX, 
      point.y = e.clientY;
      point = point.matrixTransform(container.getScreenCTM().inverse());
      return [ point.x, point.y ];
    }
    var rect = container.getBoundingClientRect();
    return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ];
  }
  d3.touch = function(container, touches, identifier) {
    if (arguments.length < 3) identifier = touches, touches = d3_eventSource().changedTouches;
    if (touches) for (var i = 0, n = touches.length, touch; i < n; ++i) {
      if ((touch = touches[i]).identifier === identifier) {
        return d3_mousePoint(container, touch);
      }
    }
  };
  d3.behavior.drag = function() {
    var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_window, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_identity, "touchmove", "touchend");
    function drag() {
      this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart);
    }
    function dragstart(id, position, subject, move, end) {
      return function() {
        var that = this, target = d3.event.target.correspondingElement || d3.event.target, parent = that.parentNode, dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = d3.select(subject(target)).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(target), position0 = position(parent, dragId);
        if (origin) {
          dragOffset = origin.apply(that, arguments);
          dragOffset = [ dragOffset.x - position0[0], dragOffset.y - position0[1] ];
        } else {
          dragOffset = [ 0, 0 ];
        }
        dispatch({
          type: "dragstart"
        });
        function moved() {
          var position1 = position(parent, dragId), dx, dy;
          if (!position1) return;
          dx = position1[0] - position0[0];
          dy = position1[1] - position0[1];
          dragged |= dx | dy;
          position0 = position1;
          dispatch({
            type: "drag",
            x: position1[0] + dragOffset[0],
            y: position1[1] + dragOffset[1],
            dx: dx,
            dy: dy
          });
        }
        function ended() {
          if (!position(parent, dragId)) return;
          dragSubject.on(move + dragName, null).on(end + dragName, null);
          dragRestore(dragged);
          dispatch({
            type: "dragend"
          });
        }
      };
    }
    drag.origin = function(x) {
      if (!arguments.length) return origin;
      origin = x;
      return drag;
    };
    return d3.rebind(drag, event, "on");
  };
  function d3_behavior_dragTouchId() {
    return d3.event.changedTouches[0].identifier;
  }
  d3.touches = function(container, touches) {
    if (arguments.length < 2) touches = d3_eventSource().touches;
    return touches ? d3_array(touches).map(function(touch) {
      var point = d3_mousePoint(container, touch);
      point.identifier = touch.identifier;
      return point;
    }) : [];
  };
  var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π;
  function d3_sgn(x) {
    return x > 0 ? 1 : x < 0 ? -1 : 0;
  }
  function d3_cross2d(a, b, c) {
    return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
  }
  function d3_acos(x) {
    return x > 1 ? 0 : x < -1 ? π : Math.acos(x);
  }
  function d3_asin(x) {
    return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x);
  }
  function d3_sinh(x) {
    return ((x = Math.exp(x)) - 1 / x) / 2;
  }
  function d3_cosh(x) {
    return ((x = Math.exp(x)) + 1 / x) / 2;
  }
  function d3_tanh(x) {
    return ((x = Math.exp(2 * x)) - 1) / (x + 1);
  }
  function d3_haversin(x) {
    return (x = Math.sin(x / 2)) * x;
  }
  var ρ = Math.SQRT2, ρ2 = 2, ρ4 = 4;
  d3.interpolateZoom = function(p0, p1) {
    var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], ux1 = p1[0], uy1 = p1[1], w1 = p1[2], dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, i, S;
    if (d2 < ε2) {
      S = Math.log(w1 / w0) / ρ;
      i = function(t) {
        return [ ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(ρ * t * S) ];
      };
    } else {
      var d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + ρ4 * d2) / (2 * w0 * ρ2 * d1), b1 = (w1 * w1 - w0 * w0 - ρ4 * d2) / (2 * w1 * ρ2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1);
      S = (r1 - r0) / ρ;
      i = function(t) {
        var s = t * S, coshr0 = d3_cosh(r0), u = w0 / (ρ2 * d1) * (coshr0 * d3_tanh(ρ * s + r0) - d3_sinh(r0));
        return [ ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / d3_cosh(ρ * s + r0) ];
      };
    }
    i.duration = S * 1e3;
    return i;
  };
  d3.behavior.zoom = function() {
    var view = {
      x: 0,
      y: 0,
      k: 1
    }, translate0, center0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, duration = 250, zooming = 0, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1;
    if (!d3_behavior_zoomWheel) {
      d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() {
        return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1);
      }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() {
        return d3.event.wheelDelta;
      }, "mousewheel") : (d3_behavior_zoomDelta = function() {
        return -d3.event.detail;
      }, "MozMousePixelScroll");
    }
    function zoom(g) {
      g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted);
    }
    zoom.event = function(g) {
      g.each(function() {
        var dispatch = event.of(this, arguments), view1 = view;
        if (d3_transitionInheritId) {
          d3.select(this).transition().each("start.zoom", function() {
            view = this.__chart__ || {
              x: 0,
              y: 0,
              k: 1
            };
            zoomstarted(dispatch);
          }).tween("zoom:zoom", function() {
            var dx = size[0], dy = size[1], cx = center0 ? center0[0] : dx / 2, cy = center0 ? center0[1] : dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]);
            return function(t) {
              var l = i(t), k = dx / l[2];
              this.__chart__ = view = {
                x: cx - l[0] * k,
                y: cy - l[1] * k,
                k: k
              };
              zoomed(dispatch);
            };
          }).each("interrupt.zoom", function() {
            zoomended(dispatch);
          }).each("end.zoom", function() {
            zoomended(dispatch);
          });
        } else {
          this.__chart__ = view;
          zoomstarted(dispatch);
          zoomed(dispatch);
          zoomended(dispatch);
        }
      });
    };
    zoom.translate = function(_) {
      if (!arguments.length) return [ view.x, view.y ];
      view = {
        x: +_[0],
        y: +_[1],
        k: view.k
      };
      rescale();
      return zoom;
    };
    zoom.scale = function(_) {
      if (!arguments.length) return view.k;
      view = {
        x: view.x,
        y: view.y,
        k: null
      };
      scaleTo(+_);
      rescale();
      return zoom;
    };
    zoom.scaleExtent = function(_) {
      if (!arguments.length) return scaleExtent;
      scaleExtent = _ == null ? d3_behavior_zoomInfinity : [ +_[0], +_[1] ];
      return zoom;
    };
    zoom.center = function(_) {
      if (!arguments.length) return center;
      center = _ && [ +_[0], +_[1] ];
      return zoom;
    };
    zoom.size = function(_) {
      if (!arguments.length) return size;
      size = _ && [ +_[0], +_[1] ];
      return zoom;
    };
    zoom.duration = function(_) {
      if (!arguments.length) return duration;
      duration = +_;
      return zoom;
    };
    zoom.x = function(z) {
      if (!arguments.length) return x1;
      x1 = z;
      x0 = z.copy();
      view = {
        x: 0,
        y: 0,
        k: 1
      };
      return zoom;
    };
    zoom.y = function(z) {
      if (!arguments.length) return y1;
      y1 = z;
      y0 = z.copy();
      view = {
        x: 0,
        y: 0,
        k: 1
      };
      return zoom;
    };
    function location(p) {
      return [ (p[0] - view.x) / view.k, (p[1] - view.y) / view.k ];
    }
    function point(l) {
      return [ l[0] * view.k + view.x, l[1] * view.k + view.y ];
    }
    function scaleTo(s) {
      view.k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s));
    }
    function translateTo(p, l) {
      l = point(l);
      view.x += p[0] - l[0];
      view.y += p[1] - l[1];
    }
    function zoomTo(that, p, l, k) {
      that.__chart__ = {
        x: view.x,
        y: view.y,
        k: view.k
      };
      scaleTo(Math.pow(2, k));
      translateTo(center0 = p, l);
      that = d3.select(that);
      if (duration > 0) that = that.transition().duration(duration);
      that.call(zoom.event);
    }
    function rescale() {
      if (x1) x1.domain(x0.range().map(function(x) {
        return (x - view.x) / view.k;
      }).map(x0.invert));
      if (y1) y1.domain(y0.range().map(function(y) {
        return (y - view.y) / view.k;
      }).map(y0.invert));
    }
    function zoomstarted(dispatch) {
      if (!zooming++) dispatch({
        type: "zoomstart"
      });
    }
    function zoomed(dispatch) {
      rescale();
      dispatch({
        type: "zoom",
        scale: view.k,
        translate: [ view.x, view.y ]
      });
    }
    function zoomended(dispatch) {
      if (!--zooming) dispatch({
        type: "zoomend"
      }), center0 = null;
    }
    function mousedowned() {
      var that = this, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window(that)).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress(that);
      d3_selection_interrupt.call(that);
      zoomstarted(dispatch);
      function moved() {
        dragged = 1;
        translateTo(d3.mouse(that), location0);
        zoomed(dispatch);
      }
      function ended() {
        subject.on(mousemove, null).on(mouseup, null);
        dragRestore(dragged);
        zoomended(dispatch);
      }
    }
    function touchstarted() {
      var that = this, dispatch = event.of(that, arguments), locations0 = {}, distance0 = 0, scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove" + zoomName, touchend = "touchend" + zoomName, targets = [], subject = d3.select(that), dragRestore = d3_event_dragSuppress(that);
      started();
      zoomstarted(dispatch);
      subject.on(mousedown, null).on(touchstart, started);
      function relocate() {
        var touches = d3.touches(that);
        scale0 = view.k;
        touches.forEach(function(t) {
          if (t.identifier in locations0) locations0[t.identifier] = location(t);
        });
        return touches;
      }
      function started() {
        var target = d3.event.target;
        d3.select(target).on(touchmove, moved).on(touchend, ended);
        targets.push(target);
        var changed = d3.event.changedTouches;
        for (var i = 0, n = changed.length; i < n; ++i) {
          locations0[changed[i].identifier] = null;
        }
        var touches = relocate(), now = Date.now();
        if (touches.length === 1) {
          if (now - touchtime < 500) {
            var p = touches[0];
            zoomTo(that, p, locations0[p.identifier], Math.floor(Math.log(view.k) / Math.LN2) + 1);
            d3_eventPreventDefault();
          }
          touchtime = now;
        } else if (touches.length > 1) {
          var p = touches[0], q = touches[1], dx = p[0] - q[0], dy = p[1] - q[1];
          distance0 = dx * dx + dy * dy;
        }
      }
      function moved() {
        var touches = d3.touches(that), p0, l0, p1, l1;
        d3_selection_interrupt.call(that);
        for (var i = 0, n = touches.length; i < n; ++i, l1 = null) {
          p1 = touches[i];
          if (l1 = locations0[p1.identifier]) {
            if (l0) break;
            p0 = p1, l0 = l1;
          }
        }
        if (l1) {
          var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1, scale1 = distance0 && Math.sqrt(distance1 / distance0);
          p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ];
          l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ];
          scaleTo(scale1 * scale0);
        }
        touchtime = null;
        translateTo(p0, l0);
        zoomed(dispatch);
      }
      function ended() {
        if (d3.event.touches.length) {
          var changed = d3.event.changedTouches;
          for (var i = 0, n = changed.length; i < n; ++i) {
            delete locations0[changed[i].identifier];
          }
          for (var identifier in locations0) {
            return void relocate();
          }
        }
        d3.selectAll(targets).on(zoomName, null);
        subject.on(mousedown, mousedowned).on(touchstart, touchstarted);
        dragRestore();
        zoomended(dispatch);
      }
    }
    function mousewheeled() {
      var dispatch = event.of(this, arguments);
      if (mousewheelTimer) clearTimeout(mousewheelTimer); else d3_selection_interrupt.call(this), 
      translate0 = location(center0 = center || d3.mouse(this)), zoomstarted(dispatch);
      mousewheelTimer = setTimeout(function() {
        mousewheelTimer = null;
        zoomended(dispatch);
      }, 50);
      d3_eventPreventDefault();
      scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * view.k);
      translateTo(center0, translate0);
      zoomed(dispatch);
    }
    function dblclicked() {
      var p = d3.mouse(this), k = Math.log(view.k) / Math.LN2;
      zoomTo(this, p, location(p), d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1);
    }
    return d3.rebind(zoom, event, "on");
  };
  var d3_behavior_zoomInfinity = [ 0, Infinity ], d3_behavior_zoomDelta, d3_behavior_zoomWheel;
  d3.color = d3_color;
  function d3_color() {}
  d3_color.prototype.toString = function() {
    return this.rgb() + "";
  };
  d3.hsl = d3_hsl;
  function d3_hsl(h, s, l) {
    return this instanceof d3_hsl ? void (this.h = +h, this.s = +s, this.l = +l) : arguments.length < 2 ? h instanceof d3_hsl ? new d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : new d3_hsl(h, s, l);
  }
  var d3_hslPrototype = d3_hsl.prototype = new d3_color();
  d3_hslPrototype.brighter = function(k) {
    k = Math.pow(.7, arguments.length ? k : 1);
    return new d3_hsl(this.h, this.s, this.l / k);
  };
  d3_hslPrototype.darker = function(k) {
    k = Math.pow(.7, arguments.length ? k : 1);
    return new d3_hsl(this.h, this.s, k * this.l);
  };
  d3_hslPrototype.rgb = function() {
    return d3_hsl_rgb(this.h, this.s, this.l);
  };
  function d3_hsl_rgb(h, s, l) {
    var m1, m2;
    h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h;
    s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s;
    l = l < 0 ? 0 : l > 1 ? 1 : l;
    m2 = l <= .5 ? l * (1 + s) : l + s - l * s;
    m1 = 2 * l - m2;
    function v(h) {
      if (h > 360) h -= 360; else if (h < 0) h += 360;
      if (h < 60) return m1 + (m2 - m1) * h / 60;
      if (h < 180) return m2;
      if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60;
      return m1;
    }
    function vv(h) {
      return Math.round(v(h) * 255);
    }
    return new d3_rgb(vv(h + 120), vv(h), vv(h - 120));
  }
  d3.hcl = d3_hcl;
  function d3_hcl(h, c, l) {
    return this instanceof d3_hcl ? void (this.h = +h, this.c = +c, this.l = +l) : arguments.length < 2 ? h instanceof d3_hcl ? new d3_hcl(h.h, h.c, h.l) : h instanceof d3_lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : new d3_hcl(h, c, l);
  }
  var d3_hclPrototype = d3_hcl.prototype = new d3_color();
  d3_hclPrototype.brighter = function(k) {
    return new d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)));
  };
  d3_hclPrototype.darker = function(k) {
    return new d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)));
  };
  d3_hclPrototype.rgb = function() {
    return d3_hcl_lab(this.h, this.c, this.l).rgb();
  };
  function d3_hcl_lab(h, c, l) {
    if (isNaN(h)) h = 0;
    if (isNaN(c)) c = 0;
    return new d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c);
  }
  d3.lab = d3_lab;
  function d3_lab(l, a, b) {
    return this instanceof d3_lab ? void (this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.h, l.c, l.l) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b);
  }
  var d3_lab_K = 18;
  var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883;
  var d3_labPrototype = d3_lab.prototype = new d3_color();
  d3_labPrototype.brighter = function(k) {
    return new d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
  };
  d3_labPrototype.darker = function(k) {
    return new d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
  };
  d3_labPrototype.rgb = function() {
    return d3_lab_rgb(this.l, this.a, this.b);
  };
  function d3_lab_rgb(l, a, b) {
    var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200;
    x = d3_lab_xyz(x) * d3_lab_X;
    y = d3_lab_xyz(y) * d3_lab_Y;
    z = d3_lab_xyz(z) * d3_lab_Z;
    return new d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z));
  }
  function d3_lab_hcl(l, a, b) {
    return l > 0 ? new d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : new d3_hcl(NaN, NaN, l);
  }
  function d3_lab_xyz(x) {
    return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
  }
  function d3_xyz_lab(x) {
    return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
  }
  function d3_xyz_rgb(r) {
    return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055));
  }
  d3.rgb = d3_rgb;
  function d3_rgb(r, g, b) {
    return this instanceof d3_rgb ? void (this.r = ~~r, this.g = ~~g, this.b = ~~b) : arguments.length < 2 ? r instanceof d3_rgb ? new d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : new d3_rgb(r, g, b);
  }
  function d3_rgbNumber(value) {
    return new d3_rgb(value >> 16, value >> 8 & 255, value & 255);
  }
  function d3_rgbString(value) {
    return d3_rgbNumber(value) + "";
  }
  var d3_rgbPrototype = d3_rgb.prototype = new d3_color();
  d3_rgbPrototype.brighter = function(k) {
    k = Math.pow(.7, arguments.length ? k : 1);
    var r = this.r, g = this.g, b = this.b, i = 30;
    if (!r && !g && !b) return new d3_rgb(i, i, i);
    if (r && r < i) r = i;
    if (g && g < i) g = i;
    if (b && b < i) b = i;
    return new d3_rgb(Math.min(255, r / k), Math.min(255, g / k), Math.min(255, b / k));
  };
  d3_rgbPrototype.darker = function(k) {
    k = Math.pow(.7, arguments.length ? k : 1);
    return new d3_rgb(k * this.r, k * this.g, k * this.b);
  };
  d3_rgbPrototype.hsl = function() {
    return d3_rgb_hsl(this.r, this.g, this.b);
  };
  d3_rgbPrototype.toString = function() {
    return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b);
  };
  function d3_rgb_hex(v) {
    return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16);
  }
  function d3_rgb_parse(format, rgb, hsl) {
    var r = 0, g = 0, b = 0, m1, m2, color;
    m1 = /([a-z]+)\((.*)\)/.exec(format = format.toLowerCase());
    if (m1) {
      m2 = m1[2].split(",");
      switch (m1[1]) {
       case "hsl":
        {
          return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100);
        }

       case "rgb":
        {
          return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2]));
        }
      }
    }
    if (color = d3_rgb_names.get(format)) {
      return rgb(color.r, color.g, color.b);
    }
    if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) {
      if (format.length === 4) {
        r = (color & 3840) >> 4;
        r = r >> 4 | r;
        g = color & 240;
        g = g >> 4 | g;
        b = color & 15;
        b = b << 4 | b;
      } else if (format.length === 7) {
        r = (color & 16711680) >> 16;
        g = (color & 65280) >> 8;
        b = color & 255;
      }
    }
    return rgb(r, g, b);
  }
  function d3_rgb_hsl(r, g, b) {
    var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2;
    if (d) {
      s = l < .5 ? d / (max + min) : d / (2 - max - min);
      if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4;
      h *= 60;
    } else {
      h = NaN;
      s = l > 0 && l < 1 ? 0 : h;
    }
    return new d3_hsl(h, s, l);
  }
  function d3_rgb_lab(r, g, b) {
    r = d3_rgb_xyz(r);
    g = d3_rgb_xyz(g);
    b = d3_rgb_xyz(b);
    var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z);
    return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z));
  }
  function d3_rgb_xyz(r) {
    return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4);
  }
  function d3_rgb_parseNumber(c) {
    var f = parseFloat(c);
    return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f;
  }
  var d3_rgb_names = d3.map({
    aliceblue: 15792383,
    antiquewhite: 16444375,
    aqua: 65535,
    aquamarine: 8388564,
    azure: 15794175,
    beige: 16119260,
    bisque: 16770244,
    black: 0,
    blanchedalmond: 16772045,
    blue: 255,
    blueviolet: 9055202,
    brown: 10824234,
    burlywood: 14596231,
    cadetblue: 6266528,
    chartreuse: 8388352,
    chocolate: 13789470,
    coral: 16744272,
    cornflowerblue: 6591981,
    cornsilk: 16775388,
    crimson: 14423100,
    cyan: 65535,
    darkblue: 139,
    darkcyan: 35723,
    darkgoldenrod: 12092939,
    darkgray: 11119017,
    darkgreen: 25600,
    darkgrey: 11119017,
    darkkhaki: 12433259,
    darkmagenta: 9109643,
    darkolivegreen: 5597999,
    darkorange: 16747520,
    darkorchid: 10040012,
    darkred: 9109504,
    darksalmon: 15308410,
    darkseagreen: 9419919,
    darkslateblue: 4734347,
    darkslategray: 3100495,
    darkslategrey: 3100495,
    darkturquoise: 52945,
    darkviolet: 9699539,
    deeppink: 16716947,
    deepskyblue: 49151,
    dimgray: 6908265,
    dimgrey: 6908265,
    dodgerblue: 2003199,
    firebrick: 11674146,
    floralwhite: 16775920,
    forestgreen: 2263842,
    fuchsia: 16711935,
    gainsboro: 14474460,
    ghostwhite: 16316671,
    gold: 16766720,
    goldenrod: 14329120,
    gray: 8421504,
    green: 32768,
    greenyellow: 11403055,
    grey: 8421504,
    honeydew: 15794160,
    hotpink: 16738740,
    indianred: 13458524,
    indigo: 4915330,
    ivory: 16777200,
    khaki: 15787660,
    lavender: 15132410,
    lavenderblush: 16773365,
    lawngreen: 8190976,
    lemonchiffon: 16775885,
    lightblue: 11393254,
    lightcoral: 15761536,
    lightcyan: 14745599,
    lightgoldenrodyellow: 16448210,
    lightgray: 13882323,
    lightgreen: 9498256,
    lightgrey: 13882323,
    lightpink: 16758465,
    lightsalmon: 16752762,
    lightseagreen: 2142890,
    lightskyblue: 8900346,
    lightslategray: 7833753,
    lightslategrey: 7833753,
    lightsteelblue: 11584734,
    lightyellow: 16777184,
    lime: 65280,
    limegreen: 3329330,
    linen: 16445670,
    magenta: 16711935,
    maroon: 8388608,
    mediumaquamarine: 6737322,
    mediumblue: 205,
    mediumorchid: 12211667,
    mediumpurple: 9662683,
    mediumseagreen: 3978097,
    mediumslateblue: 8087790,
    mediumspringgreen: 64154,
    mediumturquoise: 4772300,
    mediumvioletred: 13047173,
    midnightblue: 1644912,
    mintcream: 16121850,
    mistyrose: 16770273,
    moccasin: 16770229,
    navajowhite: 16768685,
    navy: 128,
    oldlace: 16643558,
    olive: 8421376,
    olivedrab: 7048739,
    orange: 16753920,
    orangered: 16729344,
    orchid: 14315734,
    palegoldenrod: 15657130,
    palegreen: 10025880,
    paleturquoise: 11529966,
    palevioletred: 14381203,
    papayawhip: 16773077,
    peachpuff: 16767673,
    peru: 13468991,
    pink: 16761035,
    plum: 14524637,
    powderblue: 11591910,
    purple: 8388736,
    rebeccapurple: 6697881,
    red: 16711680,
    rosybrown: 12357519,
    royalblue: 4286945,
    saddlebrown: 9127187,
    salmon: 16416882,
    sandybrown: 16032864,
    seagreen: 3050327,
    seashell: 16774638,
    sienna: 10506797,
    silver: 12632256,
    skyblue: 8900331,
    slateblue: 6970061,
    slategray: 7372944,
    slategrey: 7372944,
    snow: 16775930,
    springgreen: 65407,
    steelblue: 4620980,
    tan: 13808780,
    teal: 32896,
    thistle: 14204888,
    tomato: 16737095,
    turquoise: 4251856,
    violet: 15631086,
    wheat: 16113331,
    white: 16777215,
    whitesmoke: 16119285,
    yellow: 16776960,
    yellowgreen: 10145074
  });
  d3_rgb_names.forEach(function(key, value) {
    d3_rgb_names.set(key, d3_rgbNumber(value));
  });
  function d3_functor(v) {
    return typeof v === "function" ? v : function() {
      return v;
    };
  }
  d3.functor = d3_functor;
  d3.xhr = d3_xhrType(d3_identity);
  function d3_xhrType(response) {
    return function(url, mimeType, callback) {
      if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, 
      mimeType = null;
      return d3_xhr(url, mimeType, response, callback);
    };
  }
  function d3_xhr(url, mimeType, response, callback) {
    var xhr = {}, dispatch = d3.dispatch("beforesend", "progress", "load", "error"), headers = {}, request = new XMLHttpRequest(), responseType = null;
    if (this.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest();
    "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() {
      request.readyState > 3 && respond();
    };
    function respond() {
      var status = request.status, result;
      if (!status && d3_xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) {
        try {
          result = response.call(xhr, request);
        } catch (e) {
          dispatch.error.call(xhr, e);
          return;
        }
        dispatch.load.call(xhr, result);
      } else {
        dispatch.error.call(xhr, request);
      }
    }
    request.onprogress = function(event) {
      var o = d3.event;
      d3.event = event;
      try {
        dispatch.progress.call(xhr, request);
      } finally {
        d3.event = o;
      }
    };
    xhr.header = function(name, value) {
      name = (name + "").toLowerCase();
      if (arguments.length < 2) return headers[name];
      if (value == null) delete headers[name]; else headers[name] = value + "";
      return xhr;
    };
    xhr.mimeType = function(value) {
      if (!arguments.length) return mimeType;
      mimeType = value == null ? null : value + "";
      return xhr;
    };
    xhr.responseType = function(value) {
      if (!arguments.length) return responseType;
      responseType = value;
      return xhr;
    };
    xhr.response = function(value) {
      response = value;
      return xhr;
    };
    [ "get", "post" ].forEach(function(method) {
      xhr[method] = function() {
        return xhr.send.apply(xhr, [ method ].concat(d3_array(arguments)));
      };
    });
    xhr.send = function(method, data, callback) {
      if (arguments.length === 2 && typeof data === "function") callback = data, data = null;
      request.open(method, url, true);
      if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*";
      if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]);
      if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType);
      if (responseType != null) request.responseType = responseType;
      if (callback != null) xhr.on("error", callback).on("load", function(request) {
        callback(null, request);
      });
      dispatch.beforesend.call(xhr, request);
      request.send(data == null ? null : data);
      return xhr;
    };
    xhr.abort = function() {
      request.abort();
      return xhr;
    };
    d3.rebind(xhr, dispatch, "on");
    return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback));
  }
  function d3_xhr_fixCallback(callback) {
    return callback.length === 1 ? function(error, request) {
      callback(error == null ? request : null);
    } : callback;
  }
  function d3_xhrHasResponse(request) {
    var type = request.responseType;
    return type && type !== "text" ? request.response : request.responseText;
  }
  d3.dsv = function(delimiter, mimeType) {
    var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0);
    function dsv(url, row, callback) {
      if (arguments.length < 3) callback = row, row = null;
      var xhr = d3_xhr(url, mimeType, row == null ? response : typedResponse(row), callback);
      xhr.row = function(_) {
        return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row;
      };
      return xhr;
    }
    function response(request) {
      return dsv.parse(request.responseText);
    }
    function typedResponse(f) {
      return function(request) {
        return dsv.parse(request.responseText, f);
      };
    }
    dsv.parse = function(text, f) {
      var o;
      return dsv.parseRows(text, function(row, i) {
        if (o) return o(row, i - 1);
        var a = new Function("d", "return {" + row.map(function(name, i) {
          return JSON.stringify(name) + ": d[" + i + "]";
        }).join(",") + "}");
        o = f ? function(row, i) {
          return f(a(row), i);
        } : a;
      });
    };
    dsv.parseRows = function(text, f) {
      var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol;
      function token() {
        if (I >= N) return EOF;
        if (eol) return eol = false, EOL;
        var j = I;
        if (text.charCodeAt(j) === 34) {
          var i = j;
          while (i++ < N) {
            if (text.charCodeAt(i) === 34) {
              if (text.charCodeAt(i + 1) !== 34) break;
              ++i;
            }
          }
          I = i + 2;
          var c = text.charCodeAt(i + 1);
          if (c === 13) {
            eol = true;
            if (text.charCodeAt(i + 2) === 10) ++I;
          } else if (c === 10) {
            eol = true;
          }
          return text.slice(j + 1, i).replace(/""/g, '"');
        }
        while (I < N) {
          var c = text.charCodeAt(I++), k = 1;
          if (c === 10) eol = true; else if (c === 13) {
            eol = true;
            if (text.charCodeAt(I) === 10) ++I, ++k;
          } else if (c !== delimiterCode) continue;
          return text.slice(j, I - k);
        }
        return text.slice(j);
      }
      while ((t = token()) !== EOF) {
        var a = [];
        while (t !== EOL && t !== EOF) {
          a.push(t);
          t = token();
        }
        if (f && (a = f(a, n++)) == null) continue;
        rows.push(a);
      }
      return rows;
    };
    dsv.format = function(rows) {
      if (Array.isArray(rows[0])) return dsv.formatRows(rows);
      var fieldSet = new d3_Set(), fields = [];
      rows.forEach(function(row) {
        for (var field in row) {
          if (!fieldSet.has(field)) {
            fields.push(fieldSet.add(field));
          }
        }
      });
      return [ fields.map(formatValue).join(delimiter) ].concat(rows.map(function(row) {
        return fields.map(function(field) {
          return formatValue(row[field]);
        }).join(delimiter);
      })).join("\n");
    };
    dsv.formatRows = function(rows) {
      return rows.map(formatRow).join("\n");
    };
    function formatRow(row) {
      return row.map(formatValue).join(delimiter);
    }
    function formatValue(text) {
      return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text;
    }
    return dsv;
  };
  d3.csv = d3.dsv(",", "text/csv");
  d3.tsv = d3.dsv("	", "text/tab-separated-values");
  var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_frame = this[d3_vendorSymbol(this, "requestAnimationFrame")] || function(callback) {
    setTimeout(callback, 17);
  };
  d3.timer = function() {
    d3_timer.apply(this, arguments);
  };
  function d3_timer(callback, delay, then) {
    var n = arguments.length;
    if (n < 2) delay = 0;
    if (n < 3) then = Date.now();
    var time = then + delay, timer = {
      c: callback,
      t: time,
      n: null
    };
    if (d3_timer_queueTail) d3_timer_queueTail.n = timer; else d3_timer_queueHead = timer;
    d3_timer_queueTail = timer;
    if (!d3_timer_interval) {
      d3_timer_timeout = clearTimeout(d3_timer_timeout);
      d3_timer_interval = 1;
      d3_timer_frame(d3_timer_step);
    }
    return timer;
  }
  function d3_timer_step() {
    var now = d3_timer_mark(), delay = d3_timer_sweep() - now;
    if (delay > 24) {
      if (isFinite(delay)) {
        clearTimeout(d3_timer_timeout);
        d3_timer_timeout = setTimeout(d3_timer_step, delay);
      }
      d3_timer_interval = 0;
    } else {
      d3_timer_interval = 1;
      d3_timer_frame(d3_timer_step);
    }
  }
  d3.timer.flush = function() {
    d3_timer_mark();
    d3_timer_sweep();
  };
  function d3_timer_mark() {
    var now = Date.now(), timer = d3_timer_queueHead;
    while (timer) {
      if (now >= timer.t && timer.c(now - timer.t)) timer.c = null;
      timer = timer.n;
    }
    return now;
  }
  function d3_timer_sweep() {
    var t0, t1 = d3_timer_queueHead, time = Infinity;
    while (t1) {
      if (t1.c) {
        if (t1.t < time) time = t1.t;
        t1 = (t0 = t1).n;
      } else {
        t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n;
      }
    }
    d3_timer_queueTail = t0;
    return time;
  }
  function d3_format_precision(x, p) {
    return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1);
  }
  d3.round = function(x, n) {
    return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x);
  };
  var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix);
  d3.formatPrefix = function(value, precision) {
    var i = 0;
    if (value = +value) {
      if (value < 0) value *= -1;
      if (precision) value = d3.round(value, d3_format_precision(value, precision));
      i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
      i = Math.max(-24, Math.min(24, Math.floor((i - 1) / 3) * 3));
    }
    return d3_formatPrefixes[8 + i / 3];
  };
  function d3_formatPrefix(d, i) {
    var k = Math.pow(10, abs(8 - i) * 3);
    return {
      scale: i > 8 ? function(d) {
        return d / k;
      } : function(d) {
        return d * k;
      },
      symbol: d
    };
  }
  function d3_locale_numberFormat(locale) {
    var locale_decimal = locale.decimal, locale_thousands = locale.thousands, locale_grouping = locale.grouping, locale_currency = locale.currency, formatGroup = locale_grouping && locale_thousands ? function(value, width) {
      var i = value.length, t = [], j = 0, g = locale_grouping[0], length = 0;
      while (i > 0 && g > 0) {
        if (length + g + 1 > width) g = Math.max(1, width - length);
        t.push(value.substring(i -= g, i + g));
        if ((length += g + 1) > width) break;
        g = locale_grouping[j = (j + 1) % locale_grouping.length];
      }
      return t.reverse().join(locale_thousands);
    } : d3_identity;
    return function(specifier) {
      var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "-", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, prefix = "", suffix = "", integer = false, exponent = true;
      if (precision) precision = +precision.substring(1);
      if (zfill || fill === "0" && align === "=") {
        zfill = fill = "0";
        align = "=";
      }
      switch (type) {
       case "n":
        comma = true;
        type = "g";
        break;

       case "%":
        scale = 100;
        suffix = "%";
        type = "f";
        break;

       case "p":
        scale = 100;
        suffix = "%";
        type = "r";
        break;

       case "b":
       case "o":
       case "x":
       case "X":
        if (symbol === "#") prefix = "0" + type.toLowerCase();

       case "c":
        exponent = false;

       case "d":
        integer = true;
        precision = 0;
        break;

       case "s":
        scale = -1;
        type = "r";
        break;
      }
      if (symbol === "$") prefix = locale_currency[0], suffix = locale_currency[1];
      if (type == "r" && !precision) type = "g";
      if (precision != null) {
        if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision));
      }
      type = d3_format_types.get(type) || d3_format_typeDefault;
      var zcomma = zfill && comma;
      return function(value) {
        var fullSuffix = suffix;
        if (integer && value % 1) return "";
        var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign;
        if (scale < 0) {
          var unit = d3.formatPrefix(value, precision);
          value = unit.scale(value);
          fullSuffix = unit.symbol + suffix;
        } else {
          value *= scale;
        }
        value = type(value, precision);
        var i = value.lastIndexOf("."), before, after;
        if (i < 0) {
          var j = exponent ? value.lastIndexOf("e") : -1;
          if (j < 0) before = value, after = ""; else before = value.substring(0, j), after = value.substring(j);
        } else {
          before = value.substring(0, i);
          after = locale_decimal + value.substring(i + 1);
        }
        if (!zfill && comma) before = formatGroup(before, Infinity);
        var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : "";
        if (zcomma) before = formatGroup(padding + before, padding.length ? width - after.length : Infinity);
        negative += prefix;
        value = before + after;
        return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + fullSuffix;
      };
    };
  }
  var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;
  var d3_format_types = d3.map({
    b: function(x) {
      return x.toString(2);
    },
    c: function(x) {
      return String.fromCharCode(x);
    },
    o: function(x) {
      return x.toString(8);
    },
    x: function(x) {
      return x.toString(16);
    },
    X: function(x) {
      return x.toString(16).toUpperCase();
    },
    g: function(x, p) {
      return x.toPrecision(p);
    },
    e: function(x, p) {
      return x.toExponential(p);
    },
    f: function(x, p) {
      return x.toFixed(p);
    },
    r: function(x, p) {
      return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p))));
    }
  });
  function d3_format_typeDefault(x) {
    return x + "";
  }
  var d3_time = d3.time = {}, d3_date = Date;
  function d3_date_utc() {
    this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]);
  }
  d3_date_utc.prototype = {
    getDate: function() {
      return this._.getUTCDate();
    },
    getDay: function() {
      return this._.getUTCDay();
    },
    getFullYear: function() {
      return this._.getUTCFullYear();
    },
    getHours: function() {
      return this._.getUTCHours();
    },
    getMilliseconds: function() {
      return this._.getUTCMilliseconds();
    },
    getMinutes: function() {
      return this._.getUTCMinutes();
    },
    getMonth: function() {
      return this._.getUTCMonth();
    },
    getSeconds: function() {
      return this._.getUTCSeconds();
    },
    getTime: function() {
      return this._.getTime();
    },
    getTimezoneOffset: function() {
      return 0;
    },
    valueOf: function() {
      return this._.valueOf();
    },
    setDate: function() {
      d3_time_prototype.setUTCDate.apply(this._, arguments);
    },
    setDay: function() {
      d3_time_prototype.setUTCDay.apply(this._, arguments);
    },
    setFullYear: function() {
      d3_time_prototype.setUTCFullYear.apply(this._, arguments);
    },
    setHours: function() {
      d3_time_prototype.setUTCHours.apply(this._, arguments);
    },
    setMilliseconds: function() {
      d3_time_prototype.setUTCMilliseconds.apply(this._, arguments);
    },
    setMinutes: function() {
      d3_time_prototype.setUTCMinutes.apply(this._, arguments);
    },
    setMonth: function() {
      d3_time_prototype.setUTCMonth.apply(this._, arguments);
    },
    setSeconds: function() {
      d3_time_prototype.setUTCSeconds.apply(this._, arguments);
    },
    setTime: function() {
      d3_time_prototype.setTime.apply(this._, arguments);
    }
  };
  var d3_time_prototype = Date.prototype;
  function d3_time_interval(local, step, number) {
    function round(date) {
      var d0 = local(date), d1 = offset(d0, 1);
      return date - d0 < d1 - date ? d0 : d1;
    }
    function ceil(date) {
      step(date = local(new d3_date(date - 1)), 1);
      return date;
    }
    function offset(date, k) {
      step(date = new d3_date(+date), k);
      return date;
    }
    function range(t0, t1, dt) {
      var time = ceil(t0), times = [];
      if (dt > 1) {
        while (time < t1) {
          if (!(number(time) % dt)) times.push(new Date(+time));
          step(time, 1);
        }
      } else {
        while (time < t1) times.push(new Date(+time)), step(time, 1);
      }
      return times;
    }
    function range_utc(t0, t1, dt) {
      try {
        d3_date = d3_date_utc;
        var utc = new d3_date_utc();
        utc._ = t0;
        return range(utc, t1, dt);
      } finally {
        d3_date = Date;
      }
    }
    local.floor = local;
    local.round = round;
    local.ceil = ceil;
    local.offset = offset;
    local.range = range;
    var utc = local.utc = d3_time_interval_utc(local);
    utc.floor = utc;
    utc.round = d3_time_interval_utc(round);
    utc.ceil = d3_time_interval_utc(ceil);
    utc.offset = d3_time_interval_utc(offset);
    utc.range = range_utc;
    return local;
  }
  function d3_time_interval_utc(method) {
    return function(date, k) {
      try {
        d3_date = d3_date_utc;
        var utc = new d3_date_utc();
        utc._ = date;
        return method(utc, k)._;
      } finally {
        d3_date = Date;
      }
    };
  }
  d3_time.year = d3_time_interval(function(date) {
    date = d3_time.day(date);
    date.setMonth(0, 1);
    return date;
  }, function(date, offset) {
    date.setFullYear(date.getFullYear() + offset);
  }, function(date) {
    return date.getFullYear();
  });
  d3_time.years = d3_time.year.range;
  d3_time.years.utc = d3_time.year.utc.range;
  d3_time.day = d3_time_interval(function(date) {
    var day = new d3_date(2e3, 0);
    day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
    return day;
  }, function(date, offset) {
    date.setDate(date.getDate() + offset);
  }, function(date) {
    return date.getDate() - 1;
  });
  d3_time.days = d3_time.day.range;
  d3_time.days.utc = d3_time.day.utc.range;
  d3_time.dayOfYear = function(date) {
    var year = d3_time.year(date);
    return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5);
  };
  [ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ].forEach(function(day, i) {
    i = 7 - i;
    var interval = d3_time[day] = d3_time_interval(function(date) {
      (date = d3_time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7);
      return date;
    }, function(date, offset) {
      date.setDate(date.getDate() + Math.floor(offset) * 7);
    }, function(date) {
      var day = d3_time.year(date).getDay();
      return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i);
    });
    d3_time[day + "s"] = interval.range;
    d3_time[day + "s"].utc = interval.utc.range;
    d3_time[day + "OfYear"] = function(date) {
      var day = d3_time.year(date).getDay();
      return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7);
    };
  });
  d3_time.week = d3_time.sunday;
  d3_time.weeks = d3_time.sunday.range;
  d3_time.weeks.utc = d3_time.sunday.utc.range;
  d3_time.weekOfYear = d3_time.sundayOfYear;
  function d3_locale_timeFormat(locale) {
    var locale_dateTime = locale.dateTime, locale_date = locale.date, locale_time = locale.time, locale_periods = locale.periods, locale_days = locale.days, locale_shortDays = locale.shortDays, locale_months = locale.months, locale_shortMonths = locale.shortMonths;
    function d3_time_format(template) {
      var n = template.length;
      function format(date) {
        var string = [], i = -1, j = 0, c, p, f;
        while (++i < n) {
          if (template.charCodeAt(i) === 37) {
            string.push(template.slice(j, i));
            if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i);
            if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p);
            string.push(c);
            j = i + 1;
          }
        }
        string.push(template.slice(j, i));
        return string.join("");
      }
      format.parse = function(string) {
        var d = {
          y: 1900,
          m: 0,
          d: 1,
          H: 0,
          M: 0,
          S: 0,
          L: 0,
          Z: null
        }, i = d3_time_parse(d, template, string, 0);
        if (i != string.length) return null;
        if ("p" in d) d.H = d.H % 12 + d.p * 12;
        var localZ = d.Z != null && d3_date !== d3_date_utc, date = new (localZ ? d3_date_utc : d3_date)();
        if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("W" in d || "U" in d) {
          if (!("w" in d)) d.w = "W" in d ? 1 : 0;
          date.setFullYear(d.y, 0, 1);
          date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7);
        } else date.setFullYear(d.y, d.m, d.d);
        date.setHours(d.H + (d.Z / 100 | 0), d.M + d.Z % 100, d.S, d.L);
        return localZ ? date._ : date;
      };
      format.toString = function() {
        return template;
      };
      return format;
    }
    function d3_time_parse(date, template, string, j) {
      var c, p, t, i = 0, n = template.length, m = string.length;
      while (i < n) {
        if (j >= m) return -1;
        c = template.charCodeAt(i++);
        if (c === 37) {
          t = template.charAt(i++);
          p = d3_time_parsers[t in d3_time_formatPads ? template.charAt(i++) : t];
          if (!p || (j = p(date, string, j)) < 0) return -1;
        } else if (c != string.charCodeAt(j++)) {
          return -1;
        }
      }
      return j;
    }
    d3_time_format.utc = function(template) {
      var local = d3_time_format(template);
      function format(date) {
        try {
          d3_date = d3_date_utc;
          var utc = new d3_date();
          utc._ = date;
          return local(utc);
        } finally {
          d3_date = Date;
        }
      }
      format.parse = function(string) {
        try {
          d3_date = d3_date_utc;
          var date = local.parse(string);
          return date && date._;
        } finally {
          d3_date = Date;
        }
      };
      format.toString = local.toString;
      return format;
    };
    d3_time_format.multi = d3_time_format.utc.multi = d3_time_formatMulti;
    var d3_time_periodLookup = d3.map(), d3_time_dayRe = d3_time_formatRe(locale_days), d3_time_dayLookup = d3_time_formatLookup(locale_days), d3_time_dayAbbrevRe = d3_time_formatRe(locale_shortDays), d3_time_dayAbbrevLookup = d3_time_formatLookup(locale_shortDays), d3_time_monthRe = d3_time_formatRe(locale_months), d3_time_monthLookup = d3_time_formatLookup(locale_months), d3_time_monthAbbrevRe = d3_time_formatRe(locale_shortMonths), d3_time_monthAbbrevLookup = d3_time_formatLookup(locale_shortMonths);
    locale_periods.forEach(function(p, i) {
      d3_time_periodLookup.set(p.toLowerCase(), i);
    });
    var d3_time_formats = {
      a: function(d) {
        return locale_shortDays[d.getDay()];
      },
      A: function(d) {
        return locale_days[d.getDay()];
      },
      b: function(d) {
        return locale_shortMonths[d.getMonth()];
      },
      B: function(d) {
        return locale_months[d.getMonth()];
      },
      c: d3_time_format(locale_dateTime),
      d: function(d, p) {
        return d3_time_formatPad(d.getDate(), p, 2);
      },
      e: function(d, p) {
        return d3_time_formatPad(d.getDate(), p, 2);
      },
      H: function(d, p) {
        return d3_time_formatPad(d.getHours(), p, 2);
      },
      I: function(d, p) {
        return d3_time_formatPad(d.getHours() % 12 || 12, p, 2);
      },
      j: function(d, p) {
        return d3_time_formatPad(1 + d3_time.dayOfYear(d), p, 3);
      },
      L: function(d, p) {
        return d3_time_formatPad(d.getMilliseconds(), p, 3);
      },
      m: function(d, p) {
        return d3_time_formatPad(d.getMonth() + 1, p, 2);
      },
      M: function(d, p) {
        return d3_time_formatPad(d.getMinutes(), p, 2);
      },
      p: function(d) {
        return locale_periods[+(d.getHours() >= 12)];
      },
      S: function(d, p) {
        return d3_time_formatPad(d.getSeconds(), p, 2);
      },
      U: function(d, p) {
        return d3_time_formatPad(d3_time.sundayOfYear(d), p, 2);
      },
      w: function(d) {
        return d.getDay();
      },
      W: function(d, p) {
        return d3_time_formatPad(d3_time.mondayOfYear(d), p, 2);
      },
      x: d3_time_format(locale_date),
      X: d3_time_format(locale_time),
      y: function(d, p) {
        return d3_time_formatPad(d.getFullYear() % 100, p, 2);
      },
      Y: function(d, p) {
        return d3_time_formatPad(d.getFullYear() % 1e4, p, 4);
      },
      Z: d3_time_zone,
      "%": function() {
        return "%";
      }
    };
    var d3_time_parsers = {
      a: d3_time_parseWeekdayAbbrev,
      A: d3_time_parseWeekday,
      b: d3_time_parseMonthAbbrev,
      B: d3_time_parseMonth,
      c: d3_time_parseLocaleFull,
      d: d3_time_parseDay,
      e: d3_time_parseDay,
      H: d3_time_parseHour24,
      I: d3_time_parseHour24,
      j: d3_time_parseDayOfYear,
      L: d3_time_parseMilliseconds,
      m: d3_time_parseMonthNumber,
      M: d3_time_parseMinutes,
      p: d3_time_parseAmPm,
      S: d3_time_parseSeconds,
      U: d3_time_parseWeekNumberSunday,
      w: d3_time_parseWeekdayNumber,
      W: d3_time_parseWeekNumberMonday,
      x: d3_time_parseLocaleDate,
      X: d3_time_parseLocaleTime,
      y: d3_time_parseYear,
      Y: d3_time_parseFullYear,
      Z: d3_time_parseZone,
      "%": d3_time_parseLiteralPercent
    };
    function d3_time_parseWeekdayAbbrev(date, string, i) {
      d3_time_dayAbbrevRe.lastIndex = 0;
      var n = d3_time_dayAbbrevRe.exec(string.slice(i));
      return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
    }
    function d3_time_parseWeekday(date, string, i) {
      d3_time_dayRe.lastIndex = 0;
      var n = d3_time_dayRe.exec(string.slice(i));
      return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
    }
    function d3_time_parseMonthAbbrev(date, string, i) {
      d3_time_monthAbbrevRe.lastIndex = 0;
      var n = d3_time_monthAbbrevRe.exec(string.slice(i));
      return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
    }
    function d3_time_parseMonth(date, string, i) {
      d3_time_monthRe.lastIndex = 0;
      var n = d3_time_monthRe.exec(string.slice(i));
      return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
    }
    function d3_time_parseLocaleFull(date, string, i) {
      return d3_time_parse(date, d3_time_formats.c.toString(), string, i);
    }
    function d3_time_parseLocaleDate(date, string, i) {
      return d3_time_parse(date, d3_time_formats.x.toString(), string, i);
    }
    function d3_time_parseLocaleTime(date, string, i) {
      return d3_time_parse(date, d3_time_formats.X.toString(), string, i);
    }
    function d3_time_parseAmPm(date, string, i) {
      var n = d3_time_periodLookup.get(string.slice(i, i += 2).toLowerCase());
      return n == null ? -1 : (date.p = n, i);
    }
    return d3_time_format;
  }
  var d3_time_formatPads = {
    "-": "",
    _: " ",
    "0": "0"
  }, d3_time_numberRe = /^\s*\d+/, d3_time_percentRe = /^%/;
  function d3_time_formatPad(value, fill, width) {
    var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length;
    return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string);
  }
  function d3_time_formatRe(names) {
    return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i");
  }
  function d3_time_formatLookup(names) {
    var map = new d3_Map(), i = -1, n = names.length;
    while (++i < n) map.set(names[i].toLowerCase(), i);
    return map;
  }
  function d3_time_parseWeekdayNumber(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 1));
    return n ? (date.w = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseWeekNumberSunday(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i));
    return n ? (date.U = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseWeekNumberMonday(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i));
    return n ? (date.W = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseFullYear(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 4));
    return n ? (date.y = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseYear(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1;
  }
  function d3_time_parseZone(date, string, i) {
    return /^[+-]\d{4}$/.test(string = string.slice(i, i + 5)) ? (date.Z = -string, 
    i + 5) : -1;
  }
  function d3_time_expandYear(d) {
    return d + (d > 68 ? 1900 : 2e3);
  }
  function d3_time_parseMonthNumber(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.m = n[0] - 1, i + n[0].length) : -1;
  }
  function d3_time_parseDay(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.d = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseDayOfYear(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 3));
    return n ? (date.j = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseHour24(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.H = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseMinutes(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.M = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseSeconds(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.S = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseMilliseconds(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 3));
    return n ? (date.L = +n[0], i + n[0].length) : -1;
  }
  function d3_time_zone(d) {
    var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = abs(z) / 60 | 0, zm = abs(z) % 60;
    return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2);
  }
  function d3_time_parseLiteralPercent(date, string, i) {
    d3_time_percentRe.lastIndex = 0;
    var n = d3_time_percentRe.exec(string.slice(i, i + 1));
    return n ? i + n[0].length : -1;
  }
  function d3_time_formatMulti(formats) {
    var n = formats.length, i = -1;
    while (++i < n) formats[i][0] = this(formats[i][0]);
    return function(date) {
      var i = 0, f = formats[i];
      while (!f[1](date)) f = formats[++i];
      return f[0](date);
    };
  }
  d3.locale = function(locale) {
    return {
      numberFormat: d3_locale_numberFormat(locale),
      timeFormat: d3_locale_timeFormat(locale)
    };
  };
  var d3_locale_enUS = d3.locale({
    decimal: ".",
    thousands: ",",
    grouping: [ 3 ],
    currency: [ "$", "" ],
    dateTime: "%a %b %e %X %Y",
    date: "%m/%d/%Y",
    time: "%H:%M:%S",
    periods: [ "AM", "PM" ],
    days: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
    shortDays: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ],
    months: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ],
    shortMonths: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]
  });
  d3.format = d3_locale_enUS.numberFormat;
  d3.geo = {};
  function d3_adder() {}
  d3_adder.prototype = {
    s: 0,
    t: 0,
    add: function(y) {
      d3_adderSum(y, this.t, d3_adderTemp);
      d3_adderSum(d3_adderTemp.s, this.s, this);
      if (this.s) this.t += d3_adderTemp.t; else this.s = d3_adderTemp.t;
    },
    reset: function() {
      this.s = this.t = 0;
    },
    valueOf: function() {
      return this.s;
    }
  };
  var d3_adderTemp = new d3_adder();
  function d3_adderSum(a, b, o) {
    var x = o.s = a + b, bv = x - a, av = x - bv;
    o.t = a - av + (b - bv);
  }
  d3.geo.stream = function(object, listener) {
    if (object && d3_geo_streamObjectType.hasOwnProperty(object.type)) {
      d3_geo_streamObjectType[object.type](object, listener);
    } else {
      d3_geo_streamGeometry(object, listener);
    }
  };
  function d3_geo_streamGeometry(geometry, listener) {
    if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) {
      d3_geo_streamGeometryType[geometry.type](geometry, listener);
    }
  }
  var d3_geo_streamObjectType = {
    Feature: function(feature, listener) {
      d3_geo_streamGeometry(feature.geometry, listener);
    },
    FeatureCollection: function(object, listener) {
      var features = object.features, i = -1, n = features.length;
      while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener);
    }
  };
  var d3_geo_streamGeometryType = {
    Sphere: function(object, listener) {
      listener.sphere();
    },
    Point: function(object, listener) {
      object = object.coordinates;
      listener.point(object[0], object[1], object[2]);
    },
    MultiPoint: function(object, listener) {
      var coordinates = object.coordinates, i = -1, n = coordinates.length;
      while (++i < n) object = coordinates[i], listener.point(object[0], object[1], object[2]);
    },
    LineString: function(object, listener) {
      d3_geo_streamLine(object.coordinates, listener, 0);
    },
    MultiLineString: function(object, listener) {
      var coordinates = object.coordinates, i = -1, n = coordinates.length;
      while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0);
    },
    Polygon: function(object, listener) {
      d3_geo_streamPolygon(object.coordinates, listener);
    },
    MultiPolygon: function(object, listener) {
      var coordinates = object.coordinates, i = -1, n = coordinates.length;
      while (++i < n) d3_geo_streamPolygon(coordinates[i], listener);
    },
    GeometryCollection: function(object, listener) {
      var geometries = object.geometries, i = -1, n = geometries.length;
      while (++i < n) d3_geo_streamGeometry(geometries[i], listener);
    }
  };
  function d3_geo_streamLine(coordinates, listener, closed) {
    var i = -1, n = coordinates.length - closed, coordinate;
    listener.lineStart();
    while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1], coordinate[2]);
    listener.lineEnd();
  }
  function d3_geo_streamPolygon(coordinates, listener) {
    var i = -1, n = coordinates.length;
    listener.polygonStart();
    while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1);
    listener.polygonEnd();
  }
  d3.geo.area = function(object) {
    d3_geo_areaSum = 0;
    d3.geo.stream(object, d3_geo_area);
    return d3_geo_areaSum;
  };
  var d3_geo_areaSum, d3_geo_areaRingSum = new d3_adder();
  var d3_geo_area = {
    sphere: function() {
      d3_geo_areaSum += 4 * π;
    },
    point: d3_noop,
    lineStart: d3_noop,
    lineEnd: d3_noop,
    polygonStart: function() {
      d3_geo_areaRingSum.reset();
      d3_geo_area.lineStart = d3_geo_areaRingStart;
    },
    polygonEnd: function() {
      var area = 2 * d3_geo_areaRingSum;
      d3_geo_areaSum += area < 0 ? 4 * π + area : area;
      d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop;
    }
  };
  function d3_geo_areaRingStart() {
    var λ00, φ00, λ0, cosφ0, sinφ0;
    d3_geo_area.point = function(λ, φ) {
      d3_geo_area.point = nextPoint;
      λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4), 
      sinφ0 = Math.sin(φ);
    };
    function nextPoint(λ, φ) {
      λ *= d3_radians;
      φ = φ * d3_radians / 2 + π / 4;
      var dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u = cosφ0 * cosφ + k * Math.cos(adλ), v = k * sdλ * Math.sin(adλ);
      d3_geo_areaRingSum.add(Math.atan2(v, u));
      λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ;
    }
    d3_geo_area.lineEnd = function() {
      nextPoint(λ00, φ00);
    };
  }
  function d3_geo_cartesian(spherical) {
    var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ);
    return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ];
  }
  function d3_geo_cartesianDot(a, b) {
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
  }
  function d3_geo_cartesianCross(a, b) {
    return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ];
  }
  function d3_geo_cartesianAdd(a, b) {
    a[0] += b[0];
    a[1] += b[1];
    a[2] += b[2];
  }
  function d3_geo_cartesianScale(vector, k) {
    return [ vector[0] * k, vector[1] * k, vector[2] * k ];
  }
  function d3_geo_cartesianNormalize(d) {
    var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
    d[0] /= l;
    d[1] /= l;
    d[2] /= l;
  }
  function d3_geo_spherical(cartesian) {
    return [ Math.atan2(cartesian[1], cartesian[0]), d3_asin(cartesian[2]) ];
  }
  function d3_geo_sphericalEqual(a, b) {
    return abs(a[0] - b[0]) < ε && abs(a[1] - b[1]) < ε;
  }
  d3.geo.bounds = function() {
    var λ0, φ0, λ1, φ1, λ_, λ__, φ__, p0, dλSum, ranges, range;
    var bound = {
      point: point,
      lineStart: lineStart,
      lineEnd: lineEnd,
      polygonStart: function() {
        bound.point = ringPoint;
        bound.lineStart = ringStart;
        bound.lineEnd = ringEnd;
        dλSum = 0;
        d3_geo_area.polygonStart();
      },
      polygonEnd: function() {
        d3_geo_area.polygonEnd();
        bound.point = point;
        bound.lineStart = lineStart;
        bound.lineEnd = lineEnd;
        if (d3_geo_areaRingSum < 0) λ0 = -(λ1 = 180), φ0 = -(φ1 = 90); else if (dλSum > ε) φ1 = 90; else if (dλSum < -ε) φ0 = -90;
        range[0] = λ0, range[1] = λ1;
      }
    };
    function point(λ, φ) {
      ranges.push(range = [ λ0 = λ, λ1 = λ ]);
      if (φ < φ0) φ0 = φ;
      if (φ > φ1) φ1 = φ;
    }
    function linePoint(λ, φ) {
      var p = d3_geo_cartesian([ λ * d3_radians, φ * d3_radians ]);
      if (p0) {
        var normal = d3_geo_cartesianCross(p0, p), equatorial = [ normal[1], -normal[0], 0 ], inflection = d3_geo_cartesianCross(equatorial, normal);
        d3_geo_cartesianNormalize(inflection);
        inflection = d3_geo_spherical(inflection);
        var dλ = λ - λ_, s = dλ > 0 ? 1 : -1, λi = inflection[0] * d3_degrees * s, antimeridian = abs(dλ) > 180;
        if (antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
          var φi = inflection[1] * d3_degrees;
          if (φi > φ1) φ1 = φi;
        } else if (λi = (λi + 360) % 360 - 180, antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
          var φi = -inflection[1] * d3_degrees;
          if (φi < φ0) φ0 = φi;
        } else {
          if (φ < φ0) φ0 = φ;
          if (φ > φ1) φ1 = φ;
        }
        if (antimeridian) {
          if (λ < λ_) {
            if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
          } else {
            if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
          }
        } else {
          if (λ1 >= λ0) {
            if (λ < λ0) λ0 = λ;
            if (λ > λ1) λ1 = λ;
          } else {
            if (λ > λ_) {
              if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
            } else {
              if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
            }
          }
        }
      } else {
        point(λ, φ);
      }
      p0 = p, λ_ = λ;
    }
    function lineStart() {
      bound.point = linePoint;
    }
    function lineEnd() {
      range[0] = λ0, range[1] = λ1;
      bound.point = point;
      p0 = null;
    }
    function ringPoint(λ, φ) {
      if (p0) {
        var dλ = λ - λ_;
        dλSum += abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ;
      } else λ__ = λ, φ__ = φ;
      d3_geo_area.point(λ, φ);
      linePoint(λ, φ);
    }
    function ringStart() {
      d3_geo_area.lineStart();
    }
    function ringEnd() {
      ringPoint(λ__, φ__);
      d3_geo_area.lineEnd();
      if (abs(dλSum) > ε) λ0 = -(λ1 = 180);
      range[0] = λ0, range[1] = λ1;
      p0 = null;
    }
    function angle(λ0, λ1) {
      return (λ1 -= λ0) < 0 ? λ1 + 360 : λ1;
    }
    function compareRanges(a, b) {
      return a[0] - b[0];
    }
    function withinRange(x, range) {
      return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x;
    }
    return function(feature) {
      φ1 = λ1 = -(λ0 = φ0 = Infinity);
      ranges = [];
      d3.geo.stream(feature, bound);
      var n = ranges.length;
      if (n) {
        ranges.sort(compareRanges);
        for (var i = 1, a = ranges[0], b, merged = [ a ]; i < n; ++i) {
          b = ranges[i];
          if (withinRange(b[0], a) || withinRange(b[1], a)) {
            if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1];
            if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0];
          } else {
            merged.push(a = b);
          }
        }
        var best = -Infinity, dλ;
        for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) {
          b = merged[i];
          if ((dλ = angle(a[1], b[0])) > best) best = dλ, λ0 = b[0], λ1 = a[1];
        }
      }
      ranges = range = null;
      return λ0 === Infinity || φ0 === Infinity ? [ [ NaN, NaN ], [ NaN, NaN ] ] : [ [ λ0, φ0 ], [ λ1, φ1 ] ];
    };
  }();
  d3.geo.centroid = function(object) {
    d3_geo_centroidW0 = d3_geo_centroidW1 = d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
    d3.geo.stream(object, d3_geo_centroid);
    var x = d3_geo_centroidX2, y = d3_geo_centroidY2, z = d3_geo_centroidZ2, m = x * x + y * y + z * z;
    if (m < ε2) {
      x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1;
      if (d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0;
      m = x * x + y * y + z * z;
      if (m < ε2) return [ NaN, NaN ];
    }
    return [ Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees ];
  };
  var d3_geo_centroidW0, d3_geo_centroidW1, d3_geo_centroidX0, d3_geo_centroidY0, d3_geo_centroidZ0, d3_geo_centroidX1, d3_geo_centroidY1, d3_geo_centroidZ1, d3_geo_centroidX2, d3_geo_centroidY2, d3_geo_centroidZ2;
  var d3_geo_centroid = {
    sphere: d3_noop,
    point: d3_geo_centroidPoint,
    lineStart: d3_geo_centroidLineStart,
    lineEnd: d3_geo_centroidLineEnd,
    polygonStart: function() {
      d3_geo_centroid.lineStart = d3_geo_centroidRingStart;
    },
    polygonEnd: function() {
      d3_geo_centroid.lineStart = d3_geo_centroidLineStart;
    }
  };
  function d3_geo_centroidPoint(λ, φ) {
    λ *= d3_radians;
    var cosφ = Math.cos(φ *= d3_radians);
    d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ));
  }
  function d3_geo_centroidPointXYZ(x, y, z) {
    ++d3_geo_centroidW0;
    d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0;
    d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0;
    d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0;
  }
  function d3_geo_centroidLineStart() {
    var x0, y0, z0;
    d3_geo_centroid.point = function(λ, φ) {
      λ *= d3_radians;
      var cosφ = Math.cos(φ *= d3_radians);
      x0 = cosφ * Math.cos(λ);
      y0 = cosφ * Math.sin(λ);
      z0 = Math.sin(φ);
      d3_geo_centroid.point = nextPoint;
      d3_geo_centroidPointXYZ(x0, y0, z0);
    };
    function nextPoint(λ, φ) {
      λ *= d3_radians;
      var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z);
      d3_geo_centroidW1 += w;
      d3_geo_centroidX1 += w * (x0 + (x0 = x));
      d3_geo_centroidY1 += w * (y0 + (y0 = y));
      d3_geo_centroidZ1 += w * (z0 + (z0 = z));
      d3_geo_centroidPointXYZ(x0, y0, z0);
    }
  }
  function d3_geo_centroidLineEnd() {
    d3_geo_centroid.point = d3_geo_centroidPoint;
  }
  function d3_geo_centroidRingStart() {
    var λ00, φ00, x0, y0, z0;
    d3_geo_centroid.point = function(λ, φ) {
      λ00 = λ, φ00 = φ;
      d3_geo_centroid.point = nextPoint;
      λ *= d3_radians;
      var cosφ = Math.cos(φ *= d3_radians);
      x0 = cosφ * Math.cos(λ);
      y0 = cosφ * Math.sin(λ);
      z0 = Math.sin(φ);
      d3_geo_centroidPointXYZ(x0, y0, z0);
    };
    d3_geo_centroid.lineEnd = function() {
      nextPoint(λ00, φ00);
      d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd;
      d3_geo_centroid.point = d3_geo_centroidPoint;
    };
    function nextPoint(λ, φ) {
      λ *= d3_radians;
      var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), cx = y0 * z - z0 * y, cy = z0 * x - x0 * z, cz = x0 * y - y0 * x, m = Math.sqrt(cx * cx + cy * cy + cz * cz), u = x0 * x + y0 * y + z0 * z, v = m && -d3_acos(u) / m, w = Math.atan2(m, u);
      d3_geo_centroidX2 += v * cx;
      d3_geo_centroidY2 += v * cy;
      d3_geo_centroidZ2 += v * cz;
      d3_geo_centroidW1 += w;
      d3_geo_centroidX1 += w * (x0 + (x0 = x));
      d3_geo_centroidY1 += w * (y0 + (y0 = y));
      d3_geo_centroidZ1 += w * (z0 + (z0 = z));
      d3_geo_centroidPointXYZ(x0, y0, z0);
    }
  }
  function d3_geo_compose(a, b) {
    function compose(x, y) {
      return x = a(x, y), b(x[0], x[1]);
    }
    if (a.invert && b.invert) compose.invert = function(x, y) {
      return x = b.invert(x, y), x && a.invert(x[0], x[1]);
    };
    return compose;
  }
  function d3_true() {
    return true;
  }
  function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) {
    var subject = [], clip = [];
    segments.forEach(function(segment) {
      if ((n = segment.length - 1) <= 0) return;
      var n, p0 = segment[0], p1 = segment[n];
      if (d3_geo_sphericalEqual(p0, p1)) {
        listener.lineStart();
        for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]);
        listener.lineEnd();
        return;
      }
      var a = new d3_geo_clipPolygonIntersection(p0, segment, null, true), b = new d3_geo_clipPolygonIntersection(p0, null, a, false);
      a.o = b;
      subject.push(a);
      clip.push(b);
      a = new d3_geo_clipPolygonIntersection(p1, segment, null, false);
      b = new d3_geo_clipPolygonIntersection(p1, null, a, true);
      a.o = b;
      subject.push(a);
      clip.push(b);
    });
    clip.sort(compare);
    d3_geo_clipPolygonLinkCircular(subject);
    d3_geo_clipPolygonLinkCircular(clip);
    if (!subject.length) return;
    for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) {
      clip[i].e = entry = !entry;
    }
    var start = subject[0], points, point;
    while (1) {
      var current = start, isSubject = true;
      while (current.v) if ((current = current.n) === start) return;
      points = current.z;
      listener.lineStart();
      do {
        current.v = current.o.v = true;
        if (current.e) {
          if (isSubject) {
            for (var i = 0, n = points.length; i < n; ++i) listener.point((point = points[i])[0], point[1]);
          } else {
            interpolate(current.x, current.n.x, 1, listener);
          }
          current = current.n;
        } else {
          if (isSubject) {
            points = current.p.z;
            for (var i = points.length - 1; i >= 0; --i) listener.point((point = points[i])[0], point[1]);
          } else {
            interpolate(current.x, current.p.x, -1, listener);
          }
          current = current.p;
        }
        current = current.o;
        points = current.z;
        isSubject = !isSubject;
      } while (!current.v);
      listener.lineEnd();
    }
  }
  function d3_geo_clipPolygonLinkCircular(array) {
    if (!(n = array.length)) return;
    var n, i = 0, a = array[0], b;
    while (++i < n) {
      a.n = b = array[i];
      b.p = a;
      a = b;
    }
    a.n = b = array[0];
    b.p = a;
  }
  function d3_geo_clipPolygonIntersection(point, points, other, entry) {
    this.x = point;
    this.z = points;
    this.o = other;
    this.e = entry;
    this.v = false;
    this.n = this.p = null;
  }
  function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) {
    return function(rotate, listener) {
      var line = clipLine(listener), rotatedClipStart = rotate.invert(clipStart[0], clipStart[1]);
      var clip = {
        point: point,
        lineStart: lineStart,
        lineEnd: lineEnd,
        polygonStart: function() {
          clip.point = pointRing;
          clip.lineStart = ringStart;
          clip.lineEnd = ringEnd;
          segments = [];
          polygon = [];
        },
        polygonEnd: function() {
          clip.point = point;
          clip.lineStart = lineStart;
          clip.lineEnd = lineEnd;
          segments = d3.merge(segments);
          var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon);
          if (segments.length) {
            if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
            d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener);
          } else if (clipStartInside) {
            if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
            listener.lineStart();
            interpolate(null, null, 1, listener);
            listener.lineEnd();
          }
          if (polygonStarted) listener.polygonEnd(), polygonStarted = false;
          segments = polygon = null;
        },
        sphere: function() {
          listener.polygonStart();
          listener.lineStart();
          interpolate(null, null, 1, listener);
          listener.lineEnd();
          listener.polygonEnd();
        }
      };
      function point(λ, φ) {
        var point = rotate(λ, φ);
        if (pointVisible(λ = point[0], φ = point[1])) listener.point(λ, φ);
      }
      function pointLine(λ, φ) {
        var point = rotate(λ, φ);
        line.point(point[0], point[1]);
      }
      function lineStart() {
        clip.point = pointLine;
        line.lineStart();
      }
      function lineEnd() {
        clip.point = point;
        line.lineEnd();
      }
      var segments;
      var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), polygonStarted = false, polygon, ring;
      function pointRing(λ, φ) {
        ring.push([ λ, φ ]);
        var point = rotate(λ, φ);
        ringListener.point(point[0], point[1]);
      }
      function ringStart() {
        ringListener.lineStart();
        ring = [];
      }
      function ringEnd() {
        pointRing(ring[0][0], ring[0][1]);
        ringListener.lineEnd();
        var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length;
        ring.pop();
        polygon.push(ring);
        ring = null;
        if (!n) return;
        if (clean & 1) {
          segment = ringSegments[0];
          var n = segment.length - 1, i = -1, point;
          if (n > 0) {
            if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
            listener.lineStart();
            while (++i < n) listener.point((point = segment[i])[0], point[1]);
            listener.lineEnd();
          }
          return;
        }
        if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
        segments.push(ringSegments.filter(d3_geo_clipSegmentLength1));
      }
      return clip;
    };
  }
  function d3_geo_clipSegmentLength1(segment) {
    return segment.length > 1;
  }
  function d3_geo_clipBufferListener() {
    var lines = [], line;
    return {
      lineStart: function() {
        lines.push(line = []);
      },
      point: function(λ, φ) {
        line.push([ λ, φ ]);
      },
      lineEnd: d3_noop,
      buffer: function() {
        var buffer = lines;
        lines = [];
        line = null;
        return buffer;
      },
      rejoin: function() {
        if (lines.length > 1) lines.push(lines.pop().concat(lines.shift()));
      }
    };
  }
  function d3_geo_clipSort(a, b) {
    return ((a = a.x)[0] < 0 ? a[1] - halfπ - ε : halfπ - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfπ - ε : halfπ - b[1]);
  }
  var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate, [ -π, -π / 2 ]);
  function d3_geo_clipAntimeridianLine(listener) {
    var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean;
    return {
      lineStart: function() {
        listener.lineStart();
        clean = 1;
      },
      point: function(λ1, φ1) {
        var sλ1 = λ1 > 0 ? π : -π, dλ = abs(λ1 - λ0);
        if (abs(dλ - π) < ε) {
          listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? halfπ : -halfπ);
          listener.point(sλ0, φ0);
          listener.lineEnd();
          listener.lineStart();
          listener.point(sλ1, φ0);
          listener.point(λ1, φ0);
          clean = 0;
        } else if (sλ0 !== sλ1 && dλ >= π) {
          if (abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε;
          if (abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε;
          φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1);
          listener.point(sλ0, φ0);
          listener.lineEnd();
          listener.lineStart();
          listener.point(sλ1, φ0);
          clean = 0;
        }
        listener.point(λ0 = λ1, φ0 = φ1);
        sλ0 = sλ1;
      },
      lineEnd: function() {
        listener.lineEnd();
        λ0 = φ0 = NaN;
      },
      clean: function() {
        return 2 - clean;
      }
    };
  }
  function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) {
    var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1);
    return abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2;
  }
  function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) {
    var φ;
    if (from == null) {
      φ = direction * halfπ;
      listener.point(-π, φ);
      listener.point(0, φ);
      listener.point(π, φ);
      listener.point(π, 0);
      listener.point(π, -φ);
      listener.point(0, -φ);
      listener.point(-π, -φ);
      listener.point(-π, 0);
      listener.point(-π, φ);
    } else if (abs(from[0] - to[0]) > ε) {
      var s = from[0] < to[0] ? π : -π;
      φ = direction * s / 2;
      listener.point(-s, φ);
      listener.point(0, φ);
      listener.point(s, φ);
    } else {
      listener.point(to[0], to[1]);
    }
  }
  function d3_geo_pointInPolygon(point, polygon) {
    var meridian = point[0], parallel = point[1], meridianNormal = [ Math.sin(meridian), -Math.cos(meridian), 0 ], polarAngle = 0, winding = 0;
    d3_geo_areaRingSum.reset();
    for (var i = 0, n = polygon.length; i < n; ++i) {
      var ring = polygon[i], m = ring.length;
      if (!m) continue;
      var point0 = ring[0], λ0 = point0[0], φ0 = point0[1] / 2 + π / 4, sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), j = 1;
      while (true) {
        if (j === m) j = 0;
        point = ring[j];
        var λ = point[0], φ = point[1] / 2 + π / 4, sinφ = Math.sin(φ), cosφ = Math.cos(φ), dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, antimeridian = adλ > π, k = sinφ0 * sinφ;
        d3_geo_areaRingSum.add(Math.atan2(k * sdλ * Math.sin(adλ), cosφ0 * cosφ + k * Math.cos(adλ)));
        polarAngle += antimeridian ? dλ + sdλ * τ : dλ;
        if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) {
          var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point));
          d3_geo_cartesianNormalize(arc);
          var intersection = d3_geo_cartesianCross(meridianNormal, arc);
          d3_geo_cartesianNormalize(intersection);
          var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]);
          if (parallel > φarc || parallel === φarc && (arc[0] || arc[1])) {
            winding += antimeridian ^ dλ >= 0 ? 1 : -1;
          }
        }
        if (!j++) break;
        λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point;
      }
    }
    return (polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < -ε) ^ winding & 1;
  }
  function d3_geo_clipCircle(radius) {
    var cr = Math.cos(radius), smallRadius = cr > 0, notHemisphere = abs(cr) > ε, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians);
    return d3_geo_clip(visible, clipLine, interpolate, smallRadius ? [ 0, -radius ] : [ -π, radius - π ]);
    function visible(λ, φ) {
      return Math.cos(λ) * Math.cos(φ) > cr;
    }
    function clipLine(listener) {
      var point0, c0, v0, v00, clean;
      return {
        lineStart: function() {
          v00 = v0 = false;
          clean = 1;
        },
        point: function(λ, φ) {
          var point1 = [ λ, φ ], point2, v = visible(λ, φ), c = smallRadius ? v ? 0 : code(λ, φ) : v ? code(λ + (λ < 0 ? π : -π), φ) : 0;
          if (!point0 && (v00 = v0 = v)) listener.lineStart();
          if (v !== v0) {
            point2 = intersect(point0, point1);
            if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) {
              point1[0] += ε;
              point1[1] += ε;
              v = visible(point1[0], point1[1]);
            }
          }
          if (v !== v0) {
            clean = 0;
            if (v) {
              listener.lineStart();
              point2 = intersect(point1, point0);
              listener.point(point2[0], point2[1]);
            } else {
              point2 = intersect(point0, point1);
              listener.point(point2[0], point2[1]);
              listener.lineEnd();
            }
            point0 = point2;
          } else if (notHemisphere && point0 && smallRadius ^ v) {
            var t;
            if (!(c & c0) && (t = intersect(point1, point0, true))) {
              clean = 0;
              if (smallRadius) {
                listener.lineStart();
                listener.point(t[0][0], t[0][1]);
                listener.point(t[1][0], t[1][1]);
                listener.lineEnd();
              } else {
                listener.point(t[1][0], t[1][1]);
                listener.lineEnd();
                listener.lineStart();
                listener.point(t[0][0], t[0][1]);
              }
            }
          }
          if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) {
            listener.point(point1[0], point1[1]);
          }
          point0 = point1, v0 = v, c0 = c;
        },
        lineEnd: function() {
          if (v0) listener.lineEnd();
          point0 = null;
        },
        clean: function() {
          return clean | (v00 && v0) << 1;
        }
      };
    }
    function intersect(a, b, two) {
      var pa = d3_geo_cartesian(a), pb = d3_geo_cartesian(b);
      var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2;
      if (!determinant) return !two && a;
      var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2);
      d3_geo_cartesianAdd(A, B);
      var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1);
      if (t2 < 0) return;
      var t = Math.sqrt(t2), q = d3_geo_cartesianScale(u, (-w - t) / uu);
      d3_geo_cartesianAdd(q, A);
      q = d3_geo_spherical(q);
      if (!two) return q;
      var λ0 = a[0], λ1 = b[0], φ0 = a[1], φ1 = b[1], z;
      if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z;
      var δλ = λ1 - λ0, polar = abs(δλ - π) < ε, meridian = polar || δλ < ε;
      if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z;
      if (meridian ? polar ? φ0 + φ1 > 0 ^ q[1] < (abs(q[0] - λ0) < ε ? φ0 : φ1) : φ0 <= q[1] && q[1] <= φ1 : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) {
        var q1 = d3_geo_cartesianScale(u, (-w + t) / uu);
        d3_geo_cartesianAdd(q1, A);
        return [ q, d3_geo_spherical(q1) ];
      }
    }
    function code(λ, φ) {
      var r = smallRadius ? radius : π - radius, code = 0;
      if (λ < -r) code |= 1; else if (λ > r) code |= 2;
      if (φ < -r) code |= 4; else if (φ > r) code |= 8;
      return code;
    }
  }
  function d3_geom_clipLine(x0, y0, x1, y1) {
    return function(line) {
      var a = line.a, b = line.b, ax = a.x, ay = a.y, bx = b.x, by = b.y, t0 = 0, t1 = 1, dx = bx - ax, dy = by - ay, r;
      r = x0 - ax;
      if (!dx && r > 0) return;
      r /= dx;
      if (dx < 0) {
        if (r < t0) return;
        if (r < t1) t1 = r;
      } else if (dx > 0) {
        if (r > t1) return;
        if (r > t0) t0 = r;
      }
      r = x1 - ax;
      if (!dx && r < 0) return;
      r /= dx;
      if (dx < 0) {
        if (r > t1) return;
        if (r > t0) t0 = r;
      } else if (dx > 0) {
        if (r < t0) return;
        if (r < t1) t1 = r;
      }
      r = y0 - ay;
      if (!dy && r > 0) return;
      r /= dy;
      if (dy < 0) {
        if (r < t0) return;
        if (r < t1) t1 = r;
      } else if (dy > 0) {
        if (r > t1) return;
        if (r > t0) t0 = r;
      }
      r = y1 - ay;
      if (!dy && r < 0) return;
      r /= dy;
      if (dy < 0) {
        if (r > t1) return;
        if (r > t0) t0 = r;
      } else if (dy > 0) {
        if (r < t0) return;
        if (r < t1) t1 = r;
      }
      if (t0 > 0) line.a = {
        x: ax + t0 * dx,
        y: ay + t0 * dy
      };
      if (t1 < 1) line.b = {
        x: ax + t1 * dx,
        y: ay + t1 * dy
      };
      return line;
    };
  }
  var d3_geo_clipExtentMAX = 1e9;
  d3.geo.clipExtent = function() {
    var x0, y0, x1, y1, stream, clip, clipExtent = {
      stream: function(output) {
        if (stream) stream.valid = false;
        stream = clip(output);
        stream.valid = true;
        return stream;
      },
      extent: function(_) {
        if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ];
        clip = d3_geo_clipExtent(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]);
        if (stream) stream.valid = false, stream = null;
        return clipExtent;
      }
    };
    return clipExtent.extent([ [ 0, 0 ], [ 960, 500 ] ]);
  };
  function d3_geo_clipExtent(x0, y0, x1, y1) {
    return function(listener) {
      var listener_ = listener, bufferListener = d3_geo_clipBufferListener(), clipLine = d3_geom_clipLine(x0, y0, x1, y1), segments, polygon, ring;
      var clip = {
        point: point,
        lineStart: lineStart,
        lineEnd: lineEnd,
        polygonStart: function() {
          listener = bufferListener;
          segments = [];
          polygon = [];
          clean = true;
        },
        polygonEnd: function() {
          listener = listener_;
          segments = d3.merge(segments);
          var clipStartInside = insidePolygon([ x0, y1 ]), inside = clean && clipStartInside, visible = segments.length;
          if (inside || visible) {
            listener.polygonStart();
            if (inside) {
              listener.lineStart();
              interpolate(null, null, 1, listener);
              listener.lineEnd();
            }
            if (visible) {
              d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener);
            }
            listener.polygonEnd();
          }
          segments = polygon = ring = null;
        }
      };
      function insidePolygon(p) {
        var wn = 0, n = polygon.length, y = p[1];
        for (var i = 0; i < n; ++i) {
          for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) {
            b = v[j];
            if (a[1] <= y) {
              if (b[1] > y && d3_cross2d(a, b, p) > 0) ++wn;
            } else {
              if (b[1] <= y && d3_cross2d(a, b, p) < 0) --wn;
            }
            a = b;
          }
        }
        return wn !== 0;
      }
      function interpolate(from, to, direction, listener) {
        var a = 0, a1 = 0;
        if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) {
          do {
            listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0);
          } while ((a = (a + direction + 4) % 4) !== a1);
        } else {
          listener.point(to[0], to[1]);
        }
      }
      function pointVisible(x, y) {
        return x0 <= x && x <= x1 && y0 <= y && y <= y1;
      }
      function point(x, y) {
        if (pointVisible(x, y)) listener.point(x, y);
      }
      var x__, y__, v__, x_, y_, v_, first, clean;
      function lineStart() {
        clip.point = linePoint;
        if (polygon) polygon.push(ring = []);
        first = true;
        v_ = false;
        x_ = y_ = NaN;
      }
      function lineEnd() {
        if (segments) {
          linePoint(x__, y__);
          if (v__ && v_) bufferListener.rejoin();
          segments.push(bufferListener.buffer());
        }
        clip.point = point;
        if (v_) listener.lineEnd();
      }
      function linePoint(x, y) {
        x = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, x));
        y = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, y));
        var v = pointVisible(x, y);
        if (polygon) ring.push([ x, y ]);
        if (first) {
          x__ = x, y__ = y, v__ = v;
          first = false;
          if (v) {
            listener.lineStart();
            listener.point(x, y);
          }
        } else {
          if (v && v_) listener.point(x, y); else {
            var l = {
              a: {
                x: x_,
                y: y_
              },
              b: {
                x: x,
                y: y
              }
            };
            if (clipLine(l)) {
              if (!v_) {
                listener.lineStart();
                listener.point(l.a.x, l.a.y);
              }
              listener.point(l.b.x, l.b.y);
              if (!v) listener.lineEnd();
              clean = false;
            } else if (v) {
              listener.lineStart();
              listener.point(x, y);
              clean = false;
            }
          }
        }
        x_ = x, y_ = y, v_ = v;
      }
      return clip;
    };
    function corner(p, direction) {
      return abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 : abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 : abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2;
    }
    function compare(a, b) {
      return comparePoints(a.x, b.x);
    }
    function comparePoints(a, b) {
      var ca = corner(a, 1), cb = corner(b, 1);
      return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0];
    }
  }
  function d3_geo_conic(projectAt) {
    var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1);
    p.parallels = function(_) {
      if (!arguments.length) return [ φ0 / π * 180, φ1 / π * 180 ];
      return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180);
    };
    return p;
  }
  function d3_geo_conicEqualArea(φ0, φ1) {
    var sinφ0 = Math.sin(φ0), n = (sinφ0 + Math.sin(φ1)) / 2, C = 1 + sinφ0 * (2 * n - sinφ0), ρ0 = Math.sqrt(C) / n;
    function forward(λ, φ) {
      var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n;
      return [ ρ * Math.sin(λ *= n), ρ0 - ρ * Math.cos(λ) ];
    }
    forward.invert = function(x, y) {
      var ρ0_y = ρ0 - y;
      return [ Math.atan2(x, ρ0_y) / n, d3_asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) ];
    };
    return forward;
  }
  (d3.geo.conicEqualArea = function() {
    return d3_geo_conic(d3_geo_conicEqualArea);
  }).raw = d3_geo_conicEqualArea;
  d3.geo.albers = function() {
    return d3.geo.conicEqualArea().rotate([ 96, 0 ]).center([ -.6, 38.7 ]).parallels([ 29.5, 45.5 ]).scale(1070);
  };
  d3.geo.albersUsa = function() {
    var lower48 = d3.geo.albers();
    var alaska = d3.geo.conicEqualArea().rotate([ 154, 0 ]).center([ -2, 58.5 ]).parallels([ 55, 65 ]);
    var hawaii = d3.geo.conicEqualArea().rotate([ 157, 0 ]).center([ -3, 19.9 ]).parallels([ 8, 18 ]);
    var point, pointStream = {
      point: function(x, y) {
        point = [ x, y ];
      }
    }, lower48Point, alaskaPoint, hawaiiPoint;
    function albersUsa(coordinates) {
      var x = coordinates[0], y = coordinates[1];
      point = null;
      (lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y);
      return point;
    }
    albersUsa.invert = function(coordinates) {
      var k = lower48.scale(), t = lower48.translate(), x = (coordinates[0] - t[0]) / k, y = (coordinates[1] - t[1]) / k;
      return (y >= .12 && y < .234 && x >= -.425 && x < -.214 ? alaska : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii : lower48).invert(coordinates);
    };
    albersUsa.stream = function(stream) {
      var lower48Stream = lower48.stream(stream), alaskaStream = alaska.stream(stream), hawaiiStream = hawaii.stream(stream);
      return {
        point: function(x, y) {
          lower48Stream.point(x, y);
          alaskaStream.point(x, y);
          hawaiiStream.point(x, y);
        },
        sphere: function() {
          lower48Stream.sphere();
          alaskaStream.sphere();
          hawaiiStream.sphere();
        },
        lineStart: function() {
          lower48Stream.lineStart();
          alaskaStream.lineStart();
          hawaiiStream.lineStart();
        },
        lineEnd: function() {
          lower48Stream.lineEnd();
          alaskaStream.lineEnd();
          hawaiiStream.lineEnd();
        },
        polygonStart: function() {
          lower48Stream.polygonStart();
          alaskaStream.polygonStart();
          hawaiiStream.polygonStart();
        },
        polygonEnd: function() {
          lower48Stream.polygonEnd();
          alaskaStream.polygonEnd();
          hawaiiStream.polygonEnd();
        }
      };
    };
    albersUsa.precision = function(_) {
      if (!arguments.length) return lower48.precision();
      lower48.precision(_);
      alaska.precision(_);
      hawaii.precision(_);
      return albersUsa;
    };
    albersUsa.scale = function(_) {
      if (!arguments.length) return lower48.scale();
      lower48.scale(_);
      alaska.scale(_ * .35);
      hawaii.scale(_);
      return albersUsa.translate(lower48.translate());
    };
    albersUsa.translate = function(_) {
      if (!arguments.length) return lower48.translate();
      var k = lower48.scale(), x = +_[0], y = +_[1];
      lower48Point = lower48.translate(_).clipExtent([ [ x - .455 * k, y - .238 * k ], [ x + .455 * k, y + .238 * k ] ]).stream(pointStream).point;
      alaskaPoint = alaska.translate([ x - .307 * k, y + .201 * k ]).clipExtent([ [ x - .425 * k + ε, y + .12 * k + ε ], [ x - .214 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point;
      hawaiiPoint = hawaii.translate([ x - .205 * k, y + .212 * k ]).clipExtent([ [ x - .214 * k + ε, y + .166 * k + ε ], [ x - .115 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point;
      return albersUsa;
    };
    return albersUsa.scale(1070);
  };
  var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = {
    point: d3_noop,
    lineStart: d3_noop,
    lineEnd: d3_noop,
    polygonStart: function() {
      d3_geo_pathAreaPolygon = 0;
      d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart;
    },
    polygonEnd: function() {
      d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop;
      d3_geo_pathAreaSum += abs(d3_geo_pathAreaPolygon / 2);
    }
  };
  function d3_geo_pathAreaRingStart() {
    var x00, y00, x0, y0;
    d3_geo_pathArea.point = function(x, y) {
      d3_geo_pathArea.point = nextPoint;
      x00 = x0 = x, y00 = y0 = y;
    };
    function nextPoint(x, y) {
      d3_geo_pathAreaPolygon += y0 * x - x0 * y;
      x0 = x, y0 = y;
    }
    d3_geo_pathArea.lineEnd = function() {
      nextPoint(x00, y00);
    };
  }
  var d3_geo_pathBoundsX0, d3_geo_pathBoundsY0, d3_geo_pathBoundsX1, d3_geo_pathBoundsY1;
  var d3_geo_pathBounds = {
    point: d3_geo_pathBoundsPoint,
    lineStart: d3_noop,
    lineEnd: d3_noop,
    polygonStart: d3_noop,
    polygonEnd: d3_noop
  };
  function d3_geo_pathBoundsPoint(x, y) {
    if (x < d3_geo_pathBoundsX0) d3_geo_pathBoundsX0 = x;
    if (x > d3_geo_pathBoundsX1) d3_geo_pathBoundsX1 = x;
    if (y < d3_geo_pathBoundsY0) d3_geo_pathBoundsY0 = y;
    if (y > d3_geo_pathBoundsY1) d3_geo_pathBoundsY1 = y;
  }
  function d3_geo_pathBuffer() {
    var pointCircle = d3_geo_pathBufferCircle(4.5), buffer = [];
    var stream = {
      point: point,
      lineStart: function() {
        stream.point = pointLineStart;
      },
      lineEnd: lineEnd,
      polygonStart: function() {
        stream.lineEnd = lineEndPolygon;
      },
      polygonEnd: function() {
        stream.lineEnd = lineEnd;
        stream.point = point;
      },
      pointRadius: function(_) {
        pointCircle = d3_geo_pathBufferCircle(_);
        return stream;
      },
      result: function() {
        if (buffer.length) {
          var result = buffer.join("");
          buffer = [];
          return result;
        }
      }
    };
    function point(x, y) {
      buffer.push("M", x, ",", y, pointCircle);
    }
    function pointLineStart(x, y) {
      buffer.push("M", x, ",", y);
      stream.point = pointLine;
    }
    function pointLine(x, y) {
      buffer.push("L", x, ",", y);
    }
    function lineEnd() {
      stream.point = point;
    }
    function lineEndPolygon() {
      buffer.push("Z");
    }
    return stream;
  }
  function d3_geo_pathBufferCircle(radius) {
    return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z";
  }
  var d3_geo_pathCentroid = {
    point: d3_geo_pathCentroidPoint,
    lineStart: d3_geo_pathCentroidLineStart,
    lineEnd: d3_geo_pathCentroidLineEnd,
    polygonStart: function() {
      d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart;
    },
    polygonEnd: function() {
      d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
      d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart;
      d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd;
    }
  };
  function d3_geo_pathCentroidPoint(x, y) {
    d3_geo_centroidX0 += x;
    d3_geo_centroidY0 += y;
    ++d3_geo_centroidZ0;
  }
  function d3_geo_pathCentroidLineStart() {
    var x0, y0;
    d3_geo_pathCentroid.point = function(x, y) {
      d3_geo_pathCentroid.point = nextPoint;
      d3_geo_pathCentroidPoint(x0 = x, y0 = y);
    };
    function nextPoint(x, y) {
      var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
      d3_geo_centroidX1 += z * (x0 + x) / 2;
      d3_geo_centroidY1 += z * (y0 + y) / 2;
      d3_geo_centroidZ1 += z;
      d3_geo_pathCentroidPoint(x0 = x, y0 = y);
    }
  }
  function d3_geo_pathCentroidLineEnd() {
    d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
  }
  function d3_geo_pathCentroidRingStart() {
    var x00, y00, x0, y0;
    d3_geo_pathCentroid.point = function(x, y) {
      d3_geo_pathCentroid.point = nextPoint;
      d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y);
    };
    function nextPoint(x, y) {
      var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
      d3_geo_centroidX1 += z * (x0 + x) / 2;
      d3_geo_centroidY1 += z * (y0 + y) / 2;
      d3_geo_centroidZ1 += z;
      z = y0 * x - x0 * y;
      d3_geo_centroidX2 += z * (x0 + x);
      d3_geo_centroidY2 += z * (y0 + y);
      d3_geo_centroidZ2 += z * 3;
      d3_geo_pathCentroidPoint(x0 = x, y0 = y);
    }
    d3_geo_pathCentroid.lineEnd = function() {
      nextPoint(x00, y00);
    };
  }
  function d3_geo_pathContext(context) {
    var pointRadius = 4.5;
    var stream = {
      point: point,
      lineStart: function() {
        stream.point = pointLineStart;
      },
      lineEnd: lineEnd,
      polygonStart: function() {
        stream.lineEnd = lineEndPolygon;
      },
      polygonEnd: function() {
        stream.lineEnd = lineEnd;
        stream.point = point;
      },
      pointRadius: function(_) {
        pointRadius = _;
        return stream;
      },
      result: d3_noop
    };
    function point(x, y) {
      context.moveTo(x + pointRadius, y);
      context.arc(x, y, pointRadius, 0, τ);
    }
    function pointLineStart(x, y) {
      context.moveTo(x, y);
      stream.point = pointLine;
    }
    function pointLine(x, y) {
      context.lineTo(x, y);
    }
    function lineEnd() {
      stream.point = point;
    }
    function lineEndPolygon() {
      context.closePath();
    }
    return stream;
  }
  function d3_geo_resample(project) {
    var δ2 = .5, cosMinDistance = Math.cos(30 * d3_radians), maxDepth = 16;
    function resample(stream) {
      return (maxDepth ? resampleRecursive : resampleNone)(stream);
    }
    function resampleNone(stream) {
      return d3_geo_transformPoint(stream, function(x, y) {
        x = project(x, y);
        stream.point(x[0], x[1]);
      });
    }
    function resampleRecursive(stream) {
      var λ00, φ00, x00, y00, a00, b00, c00, λ0, x0, y0, a0, b0, c0;
      var resample = {
        point: point,
        lineStart: lineStart,
        lineEnd: lineEnd,
        polygonStart: function() {
          stream.polygonStart();
          resample.lineStart = ringStart;
        },
        polygonEnd: function() {
          stream.polygonEnd();
          resample.lineStart = lineStart;
        }
      };
      function point(x, y) {
        x = project(x, y);
        stream.point(x[0], x[1]);
      }
      function lineStart() {
        x0 = NaN;
        resample.point = linePoint;
        stream.lineStart();
      }
      function linePoint(λ, φ) {
        var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ);
        resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream);
        stream.point(x0, y0);
      }
      function lineEnd() {
        resample.point = point;
        stream.lineEnd();
      }
      function ringStart() {
        lineStart();
        resample.point = ringPoint;
        resample.lineEnd = ringEnd;
      }
      function ringPoint(λ, φ) {
        linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0;
        resample.point = linePoint;
      }
      function ringEnd() {
        resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream);
        resample.lineEnd = lineEnd;
        lineEnd();
      }
      return resample;
    }
    function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) {
      var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy;
      if (d2 > 4 * δ2 && depth--) {
        var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = abs(abs(c) - 1) < ε || abs(λ0 - λ1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2;
        if (dz * dz / d2 > δ2 || abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) {
          resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream);
          stream.point(x2, y2);
          resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream);
        }
      }
    }
    resample.precision = function(_) {
      if (!arguments.length) return Math.sqrt(δ2);
      maxDepth = (δ2 = _ * _) > 0 && 16;
      return resample;
    };
    return resample;
  }
  d3.geo.path = function() {
    var pointRadius = 4.5, projection, context, projectStream, contextStream, cacheStream;
    function path(object) {
      if (object) {
        if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments));
        if (!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream);
        d3.geo.stream(object, cacheStream);
      }
      return contextStream.result();
    }
    path.area = function(object) {
      d3_geo_pathAreaSum = 0;
      d3.geo.stream(object, projectStream(d3_geo_pathArea));
      return d3_geo_pathAreaSum;
    };
    path.centroid = function(object) {
      d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
      d3.geo.stream(object, projectStream(d3_geo_pathCentroid));
      return d3_geo_centroidZ2 ? [ d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2 ] : d3_geo_centroidZ1 ? [ d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1 ] : d3_geo_centroidZ0 ? [ d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0 ] : [ NaN, NaN ];
    };
    path.bounds = function(object) {
      d3_geo_pathBoundsX1 = d3_geo_pathBoundsY1 = -(d3_geo_pathBoundsX0 = d3_geo_pathBoundsY0 = Infinity);
      d3.geo.stream(object, projectStream(d3_geo_pathBounds));
      return [ [ d3_geo_pathBoundsX0, d3_geo_pathBoundsY0 ], [ d3_geo_pathBoundsX1, d3_geo_pathBoundsY1 ] ];
    };
    path.projection = function(_) {
      if (!arguments.length) return projection;
      projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity;
      return reset();
    };
    path.context = function(_) {
      if (!arguments.length) return context;
      contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_);
      if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius);
      return reset();
    };
    path.pointRadius = function(_) {
      if (!arguments.length) return pointRadius;
      pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_);
      return path;
    };
    function reset() {
      cacheStream = null;
      return path;
    }
    return path.projection(d3.geo.albersUsa()).context(null);
  };
  function d3_geo_pathProjectStream(project) {
    var resample = d3_geo_resample(function(x, y) {
      return project([ x * d3_degrees, y * d3_degrees ]);
    });
    return function(stream) {
      return d3_geo_projectionRadians(resample(stream));
    };
  }
  d3.geo.transform = function(methods) {
    return {
      stream: function(stream) {
        var transform = new d3_geo_transform(stream);
        for (var k in methods) transform[k] = methods[k];
        return transform;
      }
    };
  };
  function d3_geo_transform(stream) {
    this.stream = stream;
  }
  d3_geo_transform.prototype = {
    point: function(x, y) {
      this.stream.point(x, y);
    },
    sphere: function() {
      this.stream.sphere();
    },
    lineStart: function() {
      this.stream.lineStart();
    },
    lineEnd: function() {
      this.stream.lineEnd();
    },
    polygonStart: function() {
      this.stream.polygonStart();
    },
    polygonEnd: function() {
      this.stream.polygonEnd();
    }
  };
  function d3_geo_transformPoint(stream, point) {
    return {
      point: point,
      sphere: function() {
        stream.sphere();
      },
      lineStart: function() {
        stream.lineStart();
      },
      lineEnd: function() {
        stream.lineEnd();
      },
      polygonStart: function() {
        stream.polygonStart();
      },
      polygonEnd: function() {
        stream.polygonEnd();
      }
    };
  }
  d3.geo.projection = d3_geo_projection;
  d3.geo.projectionMutator = d3_geo_projectionMutator;
  function d3_geo_projection(project) {
    return d3_geo_projectionMutator(function() {
      return project;
    })();
  }
  function d3_geo_projectionMutator(projectAt) {
    var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) {
      x = project(x, y);
      return [ x[0] * k + δx, δy - x[1] * k ];
    }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, preclip = d3_geo_clipAntimeridian, postclip = d3_identity, clipAngle = null, clipExtent = null, stream;
    function projection(point) {
      point = projectRotate(point[0] * d3_radians, point[1] * d3_radians);
      return [ point[0] * k + δx, δy - point[1] * k ];
    }
    function invert(point) {
      point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k);
      return point && [ point[0] * d3_degrees, point[1] * d3_degrees ];
    }
    projection.stream = function(output) {
      if (stream) stream.valid = false;
      stream = d3_geo_projectionRadians(preclip(rotate, projectResample(postclip(output))));
      stream.valid = true;
      return stream;
    };
    projection.clipAngle = function(_) {
      if (!arguments.length) return clipAngle;
      preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians);
      return invalidate();
    };
    projection.clipExtent = function(_) {
      if (!arguments.length) return clipExtent;
      clipExtent = _;
      postclip = _ ? d3_geo_clipExtent(_[0][0], _[0][1], _[1][0], _[1][1]) : d3_identity;
      return invalidate();
    };
    projection.scale = function(_) {
      if (!arguments.length) return k;
      k = +_;
      return reset();
    };
    projection.translate = function(_) {
      if (!arguments.length) return [ x, y ];
      x = +_[0];
      y = +_[1];
      return reset();
    };
    projection.center = function(_) {
      if (!arguments.length) return [ λ * d3_degrees, φ * d3_degrees ];
      λ = _[0] % 360 * d3_radians;
      φ = _[1] % 360 * d3_radians;
      return reset();
    };
    projection.rotate = function(_) {
      if (!arguments.length) return [ δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees ];
      δλ = _[0] % 360 * d3_radians;
      δφ = _[1] % 360 * d3_radians;
      δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0;
      return reset();
    };
    d3.rebind(projection, projectResample, "precision");
    function reset() {
      projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project);
      var center = project(λ, φ);
      δx = x - center[0] * k;
      δy = y + center[1] * k;
      return invalidate();
    }
    function invalidate() {
      if (stream) stream.valid = false, stream = null;
      return projection;
    }
    return function() {
      project = projectAt.apply(this, arguments);
      projection.invert = project.invert && invert;
      return reset();
    };
  }
  function d3_geo_projectionRadians(stream) {
    return d3_geo_transformPoint(stream, function(x, y) {
      stream.point(x * d3_radians, y * d3_radians);
    });
  }
  function d3_geo_equirectangular(λ, φ) {
    return [ λ, φ ];
  }
  (d3.geo.equirectangular = function() {
    return d3_geo_projection(d3_geo_equirectangular);
  }).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular;
  d3.geo.rotation = function(rotate) {
    rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0);
    function forward(coordinates) {
      coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
      return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
    }
    forward.invert = function(coordinates) {
      coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
      return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
    };
    return forward;
  };
  function d3_geo_identityRotation(λ, φ) {
    return [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ];
  }
  d3_geo_identityRotation.invert = d3_geo_equirectangular;
  function d3_geo_rotation(δλ, δφ, δγ) {
    return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation;
  }
  function d3_geo_forwardRotationλ(δλ) {
    return function(λ, φ) {
      return λ += δλ, [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ];
    };
  }
  function d3_geo_rotationλ(δλ) {
    var rotation = d3_geo_forwardRotationλ(δλ);
    rotation.invert = d3_geo_forwardRotationλ(-δλ);
    return rotation;
  }
  function d3_geo_rotationφγ(δφ, δγ) {
    var cosδφ = Math.cos(δφ), sinδφ = Math.sin(δφ), cosδγ = Math.cos(δγ), sinδγ = Math.sin(δγ);
    function rotation(λ, φ) {
      var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδφ + x * sinδφ;
      return [ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), d3_asin(k * cosδγ + y * sinδγ) ];
    }
    rotation.invert = function(λ, φ) {
      var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδγ - y * sinδγ;
      return [ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), d3_asin(k * cosδφ - x * sinδφ) ];
    };
    return rotation;
  }
  d3.geo.circle = function() {
    var origin = [ 0, 0 ], angle, precision = 6, interpolate;
    function circle() {
      var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = [];
      interpolate(null, null, 1, {
        point: function(x, y) {
          ring.push(x = rotate(x, y));
          x[0] *= d3_degrees, x[1] *= d3_degrees;
        }
      });
      return {
        type: "Polygon",
        coordinates: [ ring ]
      };
    }
    circle.origin = function(x) {
      if (!arguments.length) return origin;
      origin = x;
      return circle;
    };
    circle.angle = function(x) {
      if (!arguments.length) return angle;
      interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians);
      return circle;
    };
    circle.precision = function(_) {
      if (!arguments.length) return precision;
      interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians);
      return circle;
    };
    return circle.angle(90);
  };
  function d3_geo_circleInterpolate(radius, precision) {
    var cr = Math.cos(radius), sr = Math.sin(radius);
    return function(from, to, direction, listener) {
      var step = direction * precision;
      if (from != null) {
        from = d3_geo_circleAngle(cr, from);
        to = d3_geo_circleAngle(cr, to);
        if (direction > 0 ? from < to : from > to) from += direction * τ;
      } else {
        from = radius + direction * τ;
        to = radius - .5 * step;
      }
      for (var point, t = from; direction > 0 ? t > to : t < to; t -= step) {
        listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]);
      }
    };
  }
  function d3_geo_circleAngle(cr, point) {
    var a = d3_geo_cartesian(point);
    a[0] -= cr;
    d3_geo_cartesianNormalize(a);
    var angle = d3_acos(-a[1]);
    return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI);
  }
  d3.geo.distance = function(a, b) {
    var Δλ = (b[0] - a[0]) * d3_radians, φ0 = a[1] * d3_radians, φ1 = b[1] * d3_radians, sinΔλ = Math.sin(Δλ), cosΔλ = Math.cos(Δλ), sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), sinφ1 = Math.sin(φ1), cosφ1 = Math.cos(φ1), t;
    return Math.atan2(Math.sqrt((t = cosφ1 * sinΔλ) * t + (t = cosφ0 * sinφ1 - sinφ0 * cosφ1 * cosΔλ) * t), sinφ0 * sinφ1 + cosφ0 * cosφ1 * cosΔλ);
  };
  d3.geo.graticule = function() {
    var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10, dy = dx, DX = 90, DY = 360, x, y, X, Y, precision = 2.5;
    function graticule() {
      return {
        type: "MultiLineString",
        coordinates: lines()
      };
    }
    function lines() {
      return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) {
        return abs(x % DX) > ε;
      }).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) {
        return abs(y % DY) > ε;
      }).map(y));
    }
    graticule.lines = function() {
      return lines().map(function(coordinates) {
        return {
          type: "LineString",
          coordinates: coordinates
        };
      });
    };
    graticule.outline = function() {
      return {
        type: "Polygon",
        coordinates: [ X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1)) ]
      };
    };
    graticule.extent = function(_) {
      if (!arguments.length) return graticule.minorExtent();
      return graticule.majorExtent(_).minorExtent(_);
    };
    graticule.majorExtent = function(_) {
      if (!arguments.length) return [ [ X0, Y0 ], [ X1, Y1 ] ];
      X0 = +_[0][0], X1 = +_[1][0];
      Y0 = +_[0][1], Y1 = +_[1][1];
      if (X0 > X1) _ = X0, X0 = X1, X1 = _;
      if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _;
      return graticule.precision(precision);
    };
    graticule.minorExtent = function(_) {
      if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ];
      x0 = +_[0][0], x1 = +_[1][0];
      y0 = +_[0][1], y1 = +_[1][1];
      if (x0 > x1) _ = x0, x0 = x1, x1 = _;
      if (y0 > y1) _ = y0, y0 = y1, y1 = _;
      return graticule.precision(precision);
    };
    graticule.step = function(_) {
      if (!arguments.length) return graticule.minorStep();
      return graticule.majorStep(_).minorStep(_);
    };
    graticule.majorStep = function(_) {
      if (!arguments.length) return [ DX, DY ];
      DX = +_[0], DY = +_[1];
      return graticule;
    };
    graticule.minorStep = function(_) {
      if (!arguments.length) return [ dx, dy ];
      dx = +_[0], dy = +_[1];
      return graticule;
    };
    graticule.precision = function(_) {
      if (!arguments.length) return precision;
      precision = +_;
      x = d3_geo_graticuleX(y0, y1, 90);
      y = d3_geo_graticuleY(x0, x1, precision);
      X = d3_geo_graticuleX(Y0, Y1, 90);
      Y = d3_geo_graticuleY(X0, X1, precision);
      return graticule;
    };
    return graticule.majorExtent([ [ -180, -90 + ε ], [ 180, 90 - ε ] ]).minorExtent([ [ -180, -80 - ε ], [ 180, 80 + ε ] ]);
  };
  function d3_geo_graticuleX(y0, y1, dy) {
    var y = d3.range(y0, y1 - ε, dy).concat(y1);
    return function(x) {
      return y.map(function(y) {
        return [ x, y ];
      });
    };
  }
  function d3_geo_graticuleY(x0, x1, dx) {
    var x = d3.range(x0, x1 - ε, dx).concat(x1);
    return function(y) {
      return x.map(function(x) {
        return [ x, y ];
      });
    };
  }
  function d3_source(d) {
    return d.source;
  }
  function d3_target(d) {
    return d.target;
  }
  d3.geo.greatArc = function() {
    var source = d3_source, source_, target = d3_target, target_;
    function greatArc() {
      return {
        type: "LineString",
        coordinates: [ source_ || source.apply(this, arguments), target_ || target.apply(this, arguments) ]
      };
    }
    greatArc.distance = function() {
      return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments));
    };
    greatArc.source = function(_) {
      if (!arguments.length) return source;
      source = _, source_ = typeof _ === "function" ? null : _;
      return greatArc;
    };
    greatArc.target = function(_) {
      if (!arguments.length) return target;
      target = _, target_ = typeof _ === "function" ? null : _;
      return greatArc;
    };
    greatArc.precision = function() {
      return arguments.length ? greatArc : 0;
    };
    return greatArc;
  };
  d3.geo.interpolate = function(source, target) {
    return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians);
  };
  function d3_geo_interpolate(x0, y0, x1, y1) {
    var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))), k = 1 / Math.sin(d);
    var interpolate = d ? function(t) {
      var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1;
      return [ Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees ];
    } : function() {
      return [ x0 * d3_degrees, y0 * d3_degrees ];
    };
    interpolate.distance = d;
    return interpolate;
  }
  d3.geo.length = function(object) {
    d3_geo_lengthSum = 0;
    d3.geo.stream(object, d3_geo_length);
    return d3_geo_lengthSum;
  };
  var d3_geo_lengthSum;
  var d3_geo_length = {
    sphere: d3_noop,
    point: d3_noop,
    lineStart: d3_geo_lengthLineStart,
    lineEnd: d3_noop,
    polygonStart: d3_noop,
    polygonEnd: d3_noop
  };
  function d3_geo_lengthLineStart() {
    var λ0, sinφ0, cosφ0;
    d3_geo_length.point = function(λ, φ) {
      λ0 = λ * d3_radians, sinφ0 = Math.sin(φ *= d3_radians), cosφ0 = Math.cos(φ);
      d3_geo_length.point = nextPoint;
    };
    d3_geo_length.lineEnd = function() {
      d3_geo_length.point = d3_geo_length.lineEnd = d3_noop;
    };
    function nextPoint(λ, φ) {
      var sinφ = Math.sin(φ *= d3_radians), cosφ = Math.cos(φ), t = abs((λ *= d3_radians) - λ0), cosΔλ = Math.cos(t);
      d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosφ * Math.sin(t)) * t + (t = cosφ0 * sinφ - sinφ0 * cosφ * cosΔλ) * t), sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ);
      λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ;
    }
  }
  function d3_geo_azimuthal(scale, angle) {
    function azimuthal(λ, φ) {
      var cosλ = Math.cos(λ), cosφ = Math.cos(φ), k = scale(cosλ * cosφ);
      return [ k * cosφ * Math.sin(λ), k * Math.sin(φ) ];
    }
    azimuthal.invert = function(x, y) {
      var ρ = Math.sqrt(x * x + y * y), c = angle(ρ), sinc = Math.sin(c), cosc = Math.cos(c);
      return [ Math.atan2(x * sinc, ρ * cosc), Math.asin(ρ && y * sinc / ρ) ];
    };
    return azimuthal;
  }
  var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosλcosφ) {
    return Math.sqrt(2 / (1 + cosλcosφ));
  }, function(ρ) {
    return 2 * Math.asin(ρ / 2);
  });
  (d3.geo.azimuthalEqualArea = function() {
    return d3_geo_projection(d3_geo_azimuthalEqualArea);
  }).raw = d3_geo_azimuthalEqualArea;
  var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosλcosφ) {
    var c = Math.acos(cosλcosφ);
    return c && c / Math.sin(c);
  }, d3_identity);
  (d3.geo.azimuthalEquidistant = function() {
    return d3_geo_projection(d3_geo_azimuthalEquidistant);
  }).raw = d3_geo_azimuthalEquidistant;
  function d3_geo_conicConformal(φ0, φ1) {
    var cosφ0 = Math.cos(φ0), t = function(φ) {
      return Math.tan(π / 4 + φ / 2);
    }, n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)), F = cosφ0 * Math.pow(t(φ0), n) / n;
    if (!n) return d3_geo_mercator;
    function forward(λ, φ) {
      if (F > 0) {
        if (φ < -halfπ + ε) φ = -halfπ + ε;
      } else {
        if (φ > halfπ - ε) φ = halfπ - ε;
      }
      var ρ = F / Math.pow(t(φ), n);
      return [ ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ) ];
    }
    forward.invert = function(x, y) {
      var ρ0_y = F - y, ρ = d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y);
      return [ Math.atan2(x, ρ0_y) / n, 2 * Math.atan(Math.pow(F / ρ, 1 / n)) - halfπ ];
    };
    return forward;
  }
  (d3.geo.conicConformal = function() {
    return d3_geo_conic(d3_geo_conicConformal);
  }).raw = d3_geo_conicConformal;
  function d3_geo_conicEquidistant(φ0, φ1) {
    var cosφ0 = Math.cos(φ0), n = φ0 === φ1 ? Math.sin(φ0) : (cosφ0 - Math.cos(φ1)) / (φ1 - φ0), G = cosφ0 / n + φ0;
    if (abs(n) < ε) return d3_geo_equirectangular;
    function forward(λ, φ) {
      var ρ = G - φ;
      return [ ρ * Math.sin(n * λ), G - ρ * Math.cos(n * λ) ];
    }
    forward.invert = function(x, y) {
      var ρ0_y = G - y;
      return [ Math.atan2(x, ρ0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y) ];
    };
    return forward;
  }
  (d3.geo.conicEquidistant = function() {
    return d3_geo_conic(d3_geo_conicEquidistant);
  }).raw = d3_geo_conicEquidistant;
  var d3_geo_gnomonic = d3_geo_azimuthal(function(cosλcosφ) {
    return 1 / cosλcosφ;
  }, Math.atan);
  (d3.geo.gnomonic = function() {
    return d3_geo_projection(d3_geo_gnomonic);
  }).raw = d3_geo_gnomonic;
  function d3_geo_mercator(λ, φ) {
    return [ λ, Math.log(Math.tan(π / 4 + φ / 2)) ];
  }
  d3_geo_mercator.invert = function(x, y) {
    return [ x, 2 * Math.atan(Math.exp(y)) - halfπ ];
  };
  function d3_geo_mercatorProjection(project) {
    var m = d3_geo_projection(project), scale = m.scale, translate = m.translate, clipExtent = m.clipExtent, clipAuto;
    m.scale = function() {
      var v = scale.apply(m, arguments);
      return v === m ? clipAuto ? m.clipExtent(null) : m : v;
    };
    m.translate = function() {
      var v = translate.apply(m, arguments);
      return v === m ? clipAuto ? m.clipExtent(null) : m : v;
    };
    m.clipExtent = function(_) {
      var v = clipExtent.apply(m, arguments);
      if (v === m) {
        if (clipAuto = _ == null) {
          var k = π * scale(), t = translate();
          clipExtent([ [ t[0] - k, t[1] - k ], [ t[0] + k, t[1] + k ] ]);
        }
      } else if (clipAuto) {
        v = null;
      }
      return v;
    };
    return m.clipExtent(null);
  }
  (d3.geo.mercator = function() {
    return d3_geo_mercatorProjection(d3_geo_mercator);
  }).raw = d3_geo_mercator;
  var d3_geo_orthographic = d3_geo_azimuthal(function() {
    return 1;
  }, Math.asin);
  (d3.geo.orthographic = function() {
    return d3_geo_projection(d3_geo_orthographic);
  }).raw = d3_geo_orthographic;
  var d3_geo_stereographic = d3_geo_azimuthal(function(cosλcosφ) {
    return 1 / (1 + cosλcosφ);
  }, function(ρ) {
    return 2 * Math.atan(ρ);
  });
  (d3.geo.stereographic = function() {
    return d3_geo_projection(d3_geo_stereographic);
  }).raw = d3_geo_stereographic;
  function d3_geo_transverseMercator(λ, φ) {
    return [ Math.log(Math.tan(π / 4 + φ / 2)), -λ ];
  }
  d3_geo_transverseMercator.invert = function(x, y) {
    return [ -y, 2 * Math.atan(Math.exp(x)) - halfπ ];
  };
  (d3.geo.transverseMercator = function() {
    var projection = d3_geo_mercatorProjection(d3_geo_transverseMercator), center = projection.center, rotate = projection.rotate;
    projection.center = function(_) {
      return _ ? center([ -_[1], _[0] ]) : (_ = center(), [ _[1], -_[0] ]);
    };
    projection.rotate = function(_) {
      return _ ? rotate([ _[0], _[1], _.length > 2 ? _[2] + 90 : 90 ]) : (_ = rotate(), 
      [ _[0], _[1], _[2] - 90 ]);
    };
    return rotate([ 0, 0, 90 ]);
  }).raw = d3_geo_transverseMercator;
  d3.geom = {};
  function d3_geom_pointX(d) {
    return d[0];
  }
  function d3_geom_pointY(d) {
    return d[1];
  }
  d3.geom.hull = function(vertices) {
    var x = d3_geom_pointX, y = d3_geom_pointY;
    if (arguments.length) return hull(vertices);
    function hull(data) {
      if (data.length < 3) return [];
      var fx = d3_functor(x), fy = d3_functor(y), i, n = data.length, points = [], flippedPoints = [];
      for (i = 0; i < n; i++) {
        points.push([ +fx.call(this, data[i], i), +fy.call(this, data[i], i), i ]);
      }
      points.sort(d3_geom_hullOrder);
      for (i = 0; i < n; i++) flippedPoints.push([ points[i][0], -points[i][1] ]);
      var upper = d3_geom_hullUpper(points), lower = d3_geom_hullUpper(flippedPoints);
      var skipLeft = lower[0] === upper[0], skipRight = lower[lower.length - 1] === upper[upper.length - 1], polygon = [];
      for (i = upper.length - 1; i >= 0; --i) polygon.push(data[points[upper[i]][2]]);
      for (i = +skipLeft; i < lower.length - skipRight; ++i) polygon.push(data[points[lower[i]][2]]);
      return polygon;
    }
    hull.x = function(_) {
      return arguments.length ? (x = _, hull) : x;
    };
    hull.y = function(_) {
      return arguments.length ? (y = _, hull) : y;
    };
    return hull;
  };
  function d3_geom_hullUpper(points) {
    var n = points.length, hull = [ 0, 1 ], hs = 2;
    for (var i = 2; i < n; i++) {
      while (hs > 1 && d3_cross2d(points[hull[hs - 2]], points[hull[hs - 1]], points[i]) <= 0) --hs;
      hull[hs++] = i;
    }
    return hull.slice(0, hs);
  }
  function d3_geom_hullOrder(a, b) {
    return a[0] - b[0] || a[1] - b[1];
  }
  d3.geom.polygon = function(coordinates) {
    d3_subclass(coordinates, d3_geom_polygonPrototype);
    return coordinates;
  };
  var d3_geom_polygonPrototype = d3.geom.polygon.prototype = [];
  d3_geom_polygonPrototype.area = function() {
    var i = -1, n = this.length, a, b = this[n - 1], area = 0;
    while (++i < n) {
      a = b;
      b = this[i];
      area += a[1] * b[0] - a[0] * b[1];
    }
    return area * .5;
  };
  d3_geom_polygonPrototype.centroid = function(k) {
    var i = -1, n = this.length, x = 0, y = 0, a, b = this[n - 1], c;
    if (!arguments.length) k = -1 / (6 * this.area());
    while (++i < n) {
      a = b;
      b = this[i];
      c = a[0] * b[1] - b[0] * a[1];
      x += (a[0] + b[0]) * c;
      y += (a[1] + b[1]) * c;
    }
    return [ x * k, y * k ];
  };
  d3_geom_polygonPrototype.clip = function(subject) {
    var input, closed = d3_geom_polygonClosed(subject), i = -1, n = this.length - d3_geom_polygonClosed(this), j, m, a = this[n - 1], b, c, d;
    while (++i < n) {
      input = subject.slice();
      subject.length = 0;
      b = this[i];
      c = input[(m = input.length - closed) - 1];
      j = -1;
      while (++j < m) {
        d = input[j];
        if (d3_geom_polygonInside(d, a, b)) {
          if (!d3_geom_polygonInside(c, a, b)) {
            subject.push(d3_geom_polygonIntersect(c, d, a, b));
          }
          subject.push(d);
        } else if (d3_geom_polygonInside(c, a, b)) {
          subject.push(d3_geom_polygonIntersect(c, d, a, b));
        }
        c = d;
      }
      if (closed) subject.push(subject[0]);
      a = b;
    }
    return subject;
  };
  function d3_geom_polygonInside(p, a, b) {
    return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
  }
  function d3_geom_polygonIntersect(c, d, a, b) {
    var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
    return [ x1 + ua * x21, y1 + ua * y21 ];
  }
  function d3_geom_polygonClosed(coordinates) {
    var a = coordinates[0], b = coordinates[coordinates.length - 1];
    return !(a[0] - b[0] || a[1] - b[1]);
  }
  var d3_geom_voronoiEdges, d3_geom_voronoiCells, d3_geom_voronoiBeaches, d3_geom_voronoiBeachPool = [], d3_geom_voronoiFirstCircle, d3_geom_voronoiCircles, d3_geom_voronoiCirclePool = [];
  function d3_geom_voronoiBeach() {
    d3_geom_voronoiRedBlackNode(this);
    this.edge = this.site = this.circle = null;
  }
  function d3_geom_voronoiCreateBeach(site) {
    var beach = d3_geom_voronoiBeachPool.pop() || new d3_geom_voronoiBeach();
    beach.site = site;
    return beach;
  }
  function d3_geom_voronoiDetachBeach(beach) {
    d3_geom_voronoiDetachCircle(beach);
    d3_geom_voronoiBeaches.remove(beach);
    d3_geom_voronoiBeachPool.push(beach);
    d3_geom_voronoiRedBlackNode(beach);
  }
  function d3_geom_voronoiRemoveBeach(beach) {
    var circle = beach.circle, x = circle.x, y = circle.cy, vertex = {
      x: x,
      y: y
    }, previous = beach.P, next = beach.N, disappearing = [ beach ];
    d3_geom_voronoiDetachBeach(beach);
    var lArc = previous;
    while (lArc.circle && abs(x - lArc.circle.x) < ε && abs(y - lArc.circle.cy) < ε) {
      previous = lArc.P;
      disappearing.unshift(lArc);
      d3_geom_voronoiDetachBeach(lArc);
      lArc = previous;
    }
    disappearing.unshift(lArc);
    d3_geom_voronoiDetachCircle(lArc);
    var rArc = next;
    while (rArc.circle && abs(x - rArc.circle.x) < ε && abs(y - rArc.circle.cy) < ε) {
      next = rArc.N;
      disappearing.push(rArc);
      d3_geom_voronoiDetachBeach(rArc);
      rArc = next;
    }
    disappearing.push(rArc);
    d3_geom_voronoiDetachCircle(rArc);
    var nArcs = disappearing.length, iArc;
    for (iArc = 1; iArc < nArcs; ++iArc) {
      rArc = disappearing[iArc];
      lArc = disappearing[iArc - 1];
      d3_geom_voronoiSetEdgeEnd(rArc.edge, lArc.site, rArc.site, vertex);
    }
    lArc = disappearing[0];
    rArc = disappearing[nArcs - 1];
    rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, rArc.site, null, vertex);
    d3_geom_voronoiAttachCircle(lArc);
    d3_geom_voronoiAttachCircle(rArc);
  }
  function d3_geom_voronoiAddBeach(site) {
    var x = site.x, directrix = site.y, lArc, rArc, dxl, dxr, node = d3_geom_voronoiBeaches._;
    while (node) {
      dxl = d3_geom_voronoiLeftBreakPoint(node, directrix) - x;
      if (dxl > ε) node = node.L; else {
        dxr = x - d3_geom_voronoiRightBreakPoint(node, directrix);
        if (dxr > ε) {
          if (!node.R) {
            lArc = node;
            break;
          }
          node = node.R;
        } else {
          if (dxl > -ε) {
            lArc = node.P;
            rArc = node;
          } else if (dxr > -ε) {
            lArc = node;
            rArc = node.N;
          } else {
            lArc = rArc = node;
          }
          break;
        }
      }
    }
    var newArc = d3_geom_voronoiCreateBeach(site);
    d3_geom_voronoiBeaches.insert(lArc, newArc);
    if (!lArc && !rArc) return;
    if (lArc === rArc) {
      d3_geom_voronoiDetachCircle(lArc);
      rArc = d3_geom_voronoiCreateBeach(lArc.site);
      d3_geom_voronoiBeaches.insert(newArc, rArc);
      newArc.edge = rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site);
      d3_geom_voronoiAttachCircle(lArc);
      d3_geom_voronoiAttachCircle(rArc);
      return;
    }
    if (!rArc) {
      newArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site);
      return;
    }
    d3_geom_voronoiDetachCircle(lArc);
    d3_geom_voronoiDetachCircle(rArc);
    var lSite = lArc.site, ax = lSite.x, ay = lSite.y, bx = site.x - ax, by = site.y - ay, rSite = rArc.site, cx = rSite.x - ax, cy = rSite.y - ay, d = 2 * (bx * cy - by * cx), hb = bx * bx + by * by, hc = cx * cx + cy * cy, vertex = {
      x: (cy * hb - by * hc) / d + ax,
      y: (bx * hc - cx * hb) / d + ay
    };
    d3_geom_voronoiSetEdgeEnd(rArc.edge, lSite, rSite, vertex);
    newArc.edge = d3_geom_voronoiCreateEdge(lSite, site, null, vertex);
    rArc.edge = d3_geom_voronoiCreateEdge(site, rSite, null, vertex);
    d3_geom_voronoiAttachCircle(lArc);
    d3_geom_voronoiAttachCircle(rArc);
  }
  function d3_geom_voronoiLeftBreakPoint(arc, directrix) {
    var site = arc.site, rfocx = site.x, rfocy = site.y, pby2 = rfocy - directrix;
    if (!pby2) return rfocx;
    var lArc = arc.P;
    if (!lArc) return -Infinity;
    site = lArc.site;
    var lfocx = site.x, lfocy = site.y, plby2 = lfocy - directrix;
    if (!plby2) return lfocx;
    var hl = lfocx - rfocx, aby2 = 1 / pby2 - 1 / plby2, b = hl / plby2;
    if (aby2) return (-b + Math.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx;
    return (rfocx + lfocx) / 2;
  }
  function d3_geom_voronoiRightBreakPoint(arc, directrix) {
    var rArc = arc.N;
    if (rArc) return d3_geom_voronoiLeftBreakPoint(rArc, directrix);
    var site = arc.site;
    return site.y === directrix ? site.x : Infinity;
  }
  function d3_geom_voronoiCell(site) {
    this.site = site;
    this.edges = [];
  }
  d3_geom_voronoiCell.prototype.prepare = function() {
    var halfEdges = this.edges, iHalfEdge = halfEdges.length, edge;
    while (iHalfEdge--) {
      edge = halfEdges[iHalfEdge].edge;
      if (!edge.b || !edge.a) halfEdges.splice(iHalfEdge, 1);
    }
    halfEdges.sort(d3_geom_voronoiHalfEdgeOrder);
    return halfEdges.length;
  };
  function d3_geom_voronoiCloseCells(extent) {
    var x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], x2, y2, x3, y3, cells = d3_geom_voronoiCells, iCell = cells.length, cell, iHalfEdge, halfEdges, nHalfEdges, start, end;
    while (iCell--) {
      cell = cells[iCell];
      if (!cell || !cell.prepare()) continue;
      halfEdges = cell.edges;
      nHalfEdges = halfEdges.length;
      iHalfEdge = 0;
      while (iHalfEdge < nHalfEdges) {
        end = halfEdges[iHalfEdge].end(), x3 = end.x, y3 = end.y;
        start = halfEdges[++iHalfEdge % nHalfEdges].start(), x2 = start.x, y2 = start.y;
        if (abs(x3 - x2) > ε || abs(y3 - y2) > ε) {
          halfEdges.splice(iHalfEdge, 0, new d3_geom_voronoiHalfEdge(d3_geom_voronoiCreateBorderEdge(cell.site, end, abs(x3 - x0) < ε && y1 - y3 > ε ? {
            x: x0,
            y: abs(x2 - x0) < ε ? y2 : y1
          } : abs(y3 - y1) < ε && x1 - x3 > ε ? {
            x: abs(y2 - y1) < ε ? x2 : x1,
            y: y1
          } : abs(x3 - x1) < ε && y3 - y0 > ε ? {
            x: x1,
            y: abs(x2 - x1) < ε ? y2 : y0
          } : abs(y3 - y0) < ε && x3 - x0 > ε ? {
            x: abs(y2 - y0) < ε ? x2 : x0,
            y: y0
          } : null), cell.site, null));
          ++nHalfEdges;
        }
      }
    }
  }
  function d3_geom_voronoiHalfEdgeOrder(a, b) {
    return b.angle - a.angle;
  }
  function d3_geom_voronoiCircle() {
    d3_geom_voronoiRedBlackNode(this);
    this.x = this.y = this.arc = this.site = this.cy = null;
  }
  function d3_geom_voronoiAttachCircle(arc) {
    var lArc = arc.P, rArc = arc.N;
    if (!lArc || !rArc) return;
    var lSite = lArc.site, cSite = arc.site, rSite = rArc.site;
    if (lSite === rSite) return;
    var bx = cSite.x, by = cSite.y, ax = lSite.x - bx, ay = lSite.y - by, cx = rSite.x - bx, cy = rSite.y - by;
    var d = 2 * (ax * cy - ay * cx);
    if (d >= -ε2) return;
    var ha = ax * ax + ay * ay, hc = cx * cx + cy * cy, x = (cy * ha - ay * hc) / d, y = (ax * hc - cx * ha) / d, cy = y + by;
    var circle = d3_geom_voronoiCirclePool.pop() || new d3_geom_voronoiCircle();
    circle.arc = arc;
    circle.site = cSite;
    circle.x = x + bx;
    circle.y = cy + Math.sqrt(x * x + y * y);
    circle.cy = cy;
    arc.circle = circle;
    var before = null, node = d3_geom_voronoiCircles._;
    while (node) {
      if (circle.y < node.y || circle.y === node.y && circle.x <= node.x) {
        if (node.L) node = node.L; else {
          before = node.P;
          break;
        }
      } else {
        if (node.R) node = node.R; else {
          before = node;
          break;
        }
      }
    }
    d3_geom_voronoiCircles.insert(before, circle);
    if (!before) d3_geom_voronoiFirstCircle = circle;
  }
  function d3_geom_voronoiDetachCircle(arc) {
    var circle = arc.circle;
    if (circle) {
      if (!circle.P) d3_geom_voronoiFirstCircle = circle.N;
      d3_geom_voronoiCircles.remove(circle);
      d3_geom_voronoiCirclePool.push(circle);
      d3_geom_voronoiRedBlackNode(circle);
      arc.circle = null;
    }
  }
  function d3_geom_voronoiClipEdges(extent) {
    var edges = d3_geom_voronoiEdges, clip = d3_geom_clipLine(extent[0][0], extent[0][1], extent[1][0], extent[1][1]), i = edges.length, e;
    while (i--) {
      e = edges[i];
      if (!d3_geom_voronoiConnectEdge(e, extent) || !clip(e) || abs(e.a.x - e.b.x) < ε && abs(e.a.y - e.b.y) < ε) {
        e.a = e.b = null;
        edges.splice(i, 1);
      }
    }
  }
  function d3_geom_voronoiConnectEdge(edge, extent) {
    var vb = edge.b;
    if (vb) return true;
    var va = edge.a, x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], lSite = edge.l, rSite = edge.r, lx = lSite.x, ly = lSite.y, rx = rSite.x, ry = rSite.y, fx = (lx + rx) / 2, fy = (ly + ry) / 2, fm, fb;
    if (ry === ly) {
      if (fx < x0 || fx >= x1) return;
      if (lx > rx) {
        if (!va) va = {
          x: fx,
          y: y0
        }; else if (va.y >= y1) return;
        vb = {
          x: fx,
          y: y1
        };
      } else {
        if (!va) va = {
          x: fx,
          y: y1
        }; else if (va.y < y0) return;
        vb = {
          x: fx,
          y: y0
        };
      }
    } else {
      fm = (lx - rx) / (ry - ly);
      fb = fy - fm * fx;
      if (fm < -1 || fm > 1) {
        if (lx > rx) {
          if (!va) va = {
            x: (y0 - fb) / fm,
            y: y0
          }; else if (va.y >= y1) return;
          vb = {
            x: (y1 - fb) / fm,
            y: y1
          };
        } else {
          if (!va) va = {
            x: (y1 - fb) / fm,
            y: y1
          }; else if (va.y < y0) return;
          vb = {
            x: (y0 - fb) / fm,
            y: y0
          };
        }
      } else {
        if (ly < ry) {
          if (!va) va = {
            x: x0,
            y: fm * x0 + fb
          }; else if (va.x >= x1) return;
          vb = {
            x: x1,
            y: fm * x1 + fb
          };
        } else {
          if (!va) va = {
            x: x1,
            y: fm * x1 + fb
          }; else if (va.x < x0) return;
          vb = {
            x: x0,
            y: fm * x0 + fb
          };
        }
      }
    }
    edge.a = va;
    edge.b = vb;
    return true;
  }
  function d3_geom_voronoiEdge(lSite, rSite) {
    this.l = lSite;
    this.r = rSite;
    this.a = this.b = null;
  }
  function d3_geom_voronoiCreateEdge(lSite, rSite, va, vb) {
    var edge = new d3_geom_voronoiEdge(lSite, rSite);
    d3_geom_voronoiEdges.push(edge);
    if (va) d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, va);
    if (vb) d3_geom_voronoiSetEdgeEnd(edge, rSite, lSite, vb);
    d3_geom_voronoiCells[lSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, lSite, rSite));
    d3_geom_voronoiCells[rSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, rSite, lSite));
    return edge;
  }
  function d3_geom_voronoiCreateBorderEdge(lSite, va, vb) {
    var edge = new d3_geom_voronoiEdge(lSite, null);
    edge.a = va;
    edge.b = vb;
    d3_geom_voronoiEdges.push(edge);
    return edge;
  }
  function d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, vertex) {
    if (!edge.a && !edge.b) {
      edge.a = vertex;
      edge.l = lSite;
      edge.r = rSite;
    } else if (edge.l === rSite) {
      edge.b = vertex;
    } else {
      edge.a = vertex;
    }
  }
  function d3_geom_voronoiHalfEdge(edge, lSite, rSite) {
    var va = edge.a, vb = edge.b;
    this.edge = edge;
    this.site = lSite;
    this.angle = rSite ? Math.atan2(rSite.y - lSite.y, rSite.x - lSite.x) : edge.l === lSite ? Math.atan2(vb.x - va.x, va.y - vb.y) : Math.atan2(va.x - vb.x, vb.y - va.y);
  }
  d3_geom_voronoiHalfEdge.prototype = {
    start: function() {
      return this.edge.l === this.site ? this.edge.a : this.edge.b;
    },
    end: function() {
      return this.edge.l === this.site ? this.edge.b : this.edge.a;
    }
  };
  function d3_geom_voronoiRedBlackTree() {
    this._ = null;
  }
  function d3_geom_voronoiRedBlackNode(node) {
    node.U = node.C = node.L = node.R = node.P = node.N = null;
  }
  d3_geom_voronoiRedBlackTree.prototype = {
    insert: function(after, node) {
      var parent, grandpa, uncle;
      if (after) {
        node.P = after;
        node.N = after.N;
        if (after.N) after.N.P = node;
        after.N = node;
        if (after.R) {
          after = after.R;
          while (after.L) after = after.L;
          after.L = node;
        } else {
          after.R = node;
        }
        parent = after;
      } else if (this._) {
        after = d3_geom_voronoiRedBlackFirst(this._);
        node.P = null;
        node.N = after;
        after.P = after.L = node;
        parent = after;
      } else {
        node.P = node.N = null;
        this._ = node;
        parent = null;
      }
      node.L = node.R = null;
      node.U = parent;
      node.C = true;
      after = node;
      while (parent && parent.C) {
        grandpa = parent.U;
        if (parent === grandpa.L) {
          uncle = grandpa.R;
          if (uncle && uncle.C) {
            parent.C = uncle.C = false;
            grandpa.C = true;
            after = grandpa;
          } else {
            if (after === parent.R) {
              d3_geom_voronoiRedBlackRotateLeft(this, parent);
              after = parent;
              parent = after.U;
            }
            parent.C = false;
            grandpa.C = true;
            d3_geom_voronoiRedBlackRotateRight(this, grandpa);
          }
        } else {
          uncle = grandpa.L;
          if (uncle && uncle.C) {
            parent.C = uncle.C = false;
            grandpa.C = true;
            after = grandpa;
          } else {
            if (after === parent.L) {
              d3_geom_voronoiRedBlackRotateRight(this, parent);
              after = parent;
              parent = after.U;
            }
            parent.C = false;
            grandpa.C = true;
            d3_geom_voronoiRedBlackRotateLeft(this, grandpa);
          }
        }
        parent = after.U;
      }
      this._.C = false;
    },
    remove: function(node) {
      if (node.N) node.N.P = node.P;
      if (node.P) node.P.N = node.N;
      node.N = node.P = null;
      var parent = node.U, sibling, left = node.L, right = node.R, next, red;
      if (!left) next = right; else if (!right) next = left; else next = d3_geom_voronoiRedBlackFirst(right);
      if (parent) {
        if (parent.L === node) parent.L = next; else parent.R = next;
      } else {
        this._ = next;
      }
      if (left && right) {
        red = next.C;
        next.C = node.C;
        next.L = left;
        left.U = next;
        if (next !== right) {
          parent = next.U;
          next.U = node.U;
          node = next.R;
          parent.L = node;
          next.R = right;
          right.U = next;
        } else {
          next.U = parent;
          parent = next;
          node = next.R;
        }
      } else {
        red = node.C;
        node = next;
      }
      if (node) node.U = parent;
      if (red) return;
      if (node && node.C) {
        node.C = false;
        return;
      }
      do {
        if (node === this._) break;
        if (node === parent.L) {
          sibling = parent.R;
          if (sibling.C) {
            sibling.C = false;
            parent.C = true;
            d3_geom_voronoiRedBlackRotateLeft(this, parent);
            sibling = parent.R;
          }
          if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) {
            if (!sibling.R || !sibling.R.C) {
              sibling.L.C = false;
              sibling.C = true;
              d3_geom_voronoiRedBlackRotateRight(this, sibling);
              sibling = parent.R;
            }
            sibling.C = parent.C;
            parent.C = sibling.R.C = false;
            d3_geom_voronoiRedBlackRotateLeft(this, parent);
            node = this._;
            break;
          }
        } else {
          sibling = parent.L;
          if (sibling.C) {
            sibling.C = false;
            parent.C = true;
            d3_geom_voronoiRedBlackRotateRight(this, parent);
            sibling = parent.L;
          }
          if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) {
            if (!sibling.L || !sibling.L.C) {
              sibling.R.C = false;
              sibling.C = true;
              d3_geom_voronoiRedBlackRotateLeft(this, sibling);
              sibling = parent.L;
            }
            sibling.C = parent.C;
            parent.C = sibling.L.C = false;
            d3_geom_voronoiRedBlackRotateRight(this, parent);
            node = this._;
            break;
          }
        }
        sibling.C = true;
        node = parent;
        parent = parent.U;
      } while (!node.C);
      if (node) node.C = false;
    }
  };
  function d3_geom_voronoiRedBlackRotateLeft(tree, node) {
    var p = node, q = node.R, parent = p.U;
    if (parent) {
      if (parent.L === p) parent.L = q; else parent.R = q;
    } else {
      tree._ = q;
    }
    q.U = parent;
    p.U = q;
    p.R = q.L;
    if (p.R) p.R.U = p;
    q.L = p;
  }
  function d3_geom_voronoiRedBlackRotateRight(tree, node) {
    var p = node, q = node.L, parent = p.U;
    if (parent) {
      if (parent.L === p) parent.L = q; else parent.R = q;
    } else {
      tree._ = q;
    }
    q.U = parent;
    p.U = q;
    p.L = q.R;
    if (p.L) p.L.U = p;
    q.R = p;
  }
  function d3_geom_voronoiRedBlackFirst(node) {
    while (node.L) node = node.L;
    return node;
  }
  function d3_geom_voronoi(sites, bbox) {
    var site = sites.sort(d3_geom_voronoiVertexOrder).pop(), x0, y0, circle;
    d3_geom_voronoiEdges = [];
    d3_geom_voronoiCells = new Array(sites.length);
    d3_geom_voronoiBeaches = new d3_geom_voronoiRedBlackTree();
    d3_geom_voronoiCircles = new d3_geom_voronoiRedBlackTree();
    while (true) {
      circle = d3_geom_voronoiFirstCircle;
      if (site && (!circle || site.y < circle.y || site.y === circle.y && site.x < circle.x)) {
        if (site.x !== x0 || site.y !== y0) {
          d3_geom_voronoiCells[site.i] = new d3_geom_voronoiCell(site);
          d3_geom_voronoiAddBeach(site);
          x0 = site.x, y0 = site.y;
        }
        site = sites.pop();
      } else if (circle) {
        d3_geom_voronoiRemoveBeach(circle.arc);
      } else {
        break;
      }
    }
    if (bbox) d3_geom_voronoiClipEdges(bbox), d3_geom_voronoiCloseCells(bbox);
    var diagram = {
      cells: d3_geom_voronoiCells,
      edges: d3_geom_voronoiEdges
    };
    d3_geom_voronoiBeaches = d3_geom_voronoiCircles = d3_geom_voronoiEdges = d3_geom_voronoiCells = null;
    return diagram;
  }
  function d3_geom_voronoiVertexOrder(a, b) {
    return b.y - a.y || b.x - a.x;
  }
  d3.geom.voronoi = function(points) {
    var x = d3_geom_pointX, y = d3_geom_pointY, fx = x, fy = y, clipExtent = d3_geom_voronoiClipExtent;
    if (points) return voronoi(points);
    function voronoi(data) {
      var polygons = new Array(data.length), x0 = clipExtent[0][0], y0 = clipExtent[0][1], x1 = clipExtent[1][0], y1 = clipExtent[1][1];
      d3_geom_voronoi(sites(data), clipExtent).cells.forEach(function(cell, i) {
        var edges = cell.edges, site = cell.site, polygon = polygons[i] = edges.length ? edges.map(function(e) {
          var s = e.start();
          return [ s.x, s.y ];
        }) : site.x >= x0 && site.x <= x1 && site.y >= y0 && site.y <= y1 ? [ [ x0, y1 ], [ x1, y1 ], [ x1, y0 ], [ x0, y0 ] ] : [];
        polygon.point = data[i];
      });
      return polygons;
    }
    function sites(data) {
      return data.map(function(d, i) {
        return {
          x: Math.round(fx(d, i) / ε) * ε,
          y: Math.round(fy(d, i) / ε) * ε,
          i: i
        };
      });
    }
    voronoi.links = function(data) {
      return d3_geom_voronoi(sites(data)).edges.filter(function(edge) {
        return edge.l && edge.r;
      }).map(function(edge) {
        return {
          source: data[edge.l.i],
          target: data[edge.r.i]
        };
      });
    };
    voronoi.triangles = function(data) {
      var triangles = [];
      d3_geom_voronoi(sites(data)).cells.forEach(function(cell, i) {
        var site = cell.site, edges = cell.edges.sort(d3_geom_voronoiHalfEdgeOrder), j = -1, m = edges.length, e0, s0, e1 = edges[m - 1].edge, s1 = e1.l === site ? e1.r : e1.l;
        while (++j < m) {
          e0 = e1;
          s0 = s1;
          e1 = edges[j].edge;
          s1 = e1.l === site ? e1.r : e1.l;
          if (i < s0.i && i < s1.i && d3_geom_voronoiTriangleArea(site, s0, s1) < 0) {
            triangles.push([ data[i], data[s0.i], data[s1.i] ]);
          }
        }
      });
      return triangles;
    };
    voronoi.x = function(_) {
      return arguments.length ? (fx = d3_functor(x = _), voronoi) : x;
    };
    voronoi.y = function(_) {
      return arguments.length ? (fy = d3_functor(y = _), voronoi) : y;
    };
    voronoi.clipExtent = function(_) {
      if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent;
      clipExtent = _ == null ? d3_geom_voronoiClipExtent : _;
      return voronoi;
    };
    voronoi.size = function(_) {
      if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent && clipExtent[1];
      return voronoi.clipExtent(_ && [ [ 0, 0 ], _ ]);
    };
    return voronoi;
  };
  var d3_geom_voronoiClipExtent = [ [ -1e6, -1e6 ], [ 1e6, 1e6 ] ];
  function d3_geom_voronoiTriangleArea(a, b, c) {
    return (a.x - c.x) * (b.y - a.y) - (a.x - b.x) * (c.y - a.y);
  }
  d3.geom.delaunay = function(vertices) {
    return d3.geom.voronoi().triangles(vertices);
  };
  d3.geom.quadtree = function(points, x1, y1, x2, y2) {
    var x = d3_geom_pointX, y = d3_geom_pointY, compat;
    if (compat = arguments.length) {
      x = d3_geom_quadtreeCompatX;
      y = d3_geom_quadtreeCompatY;
      if (compat === 3) {
        y2 = y1;
        x2 = x1;
        y1 = x1 = 0;
      }
      return quadtree(points);
    }
    function quadtree(data) {
      var d, fx = d3_functor(x), fy = d3_functor(y), xs, ys, i, n, x1_, y1_, x2_, y2_;
      if (x1 != null) {
        x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2;
      } else {
        x2_ = y2_ = -(x1_ = y1_ = Infinity);
        xs = [], ys = [];
        n = data.length;
        if (compat) for (i = 0; i < n; ++i) {
          d = data[i];
          if (d.x < x1_) x1_ = d.x;
          if (d.y < y1_) y1_ = d.y;
          if (d.x > x2_) x2_ = d.x;
          if (d.y > y2_) y2_ = d.y;
          xs.push(d.x);
          ys.push(d.y);
        } else for (i = 0; i < n; ++i) {
          var x_ = +fx(d = data[i], i), y_ = +fy(d, i);
          if (x_ < x1_) x1_ = x_;
          if (y_ < y1_) y1_ = y_;
          if (x_ > x2_) x2_ = x_;
          if (y_ > y2_) y2_ = y_;
          xs.push(x_);
          ys.push(y_);
        }
      }
      var dx = x2_ - x1_, dy = y2_ - y1_;
      if (dx > dy) y2_ = y1_ + dx; else x2_ = x1_ + dy;
      function insert(n, d, x, y, x1, y1, x2, y2) {
        if (isNaN(x) || isNaN(y)) return;
        if (n.leaf) {
          var nx = n.x, ny = n.y;
          if (nx != null) {
            if (abs(nx - x) + abs(ny - y) < .01) {
              insertChild(n, d, x, y, x1, y1, x2, y2);
            } else {
              var nPoint = n.point;
              n.x = n.y = n.point = null;
              insertChild(n, nPoint, nx, ny, x1, y1, x2, y2);
              insertChild(n, d, x, y, x1, y1, x2, y2);
            }
          } else {
            n.x = x, n.y = y, n.point = d;
          }
        } else {
          insertChild(n, d, x, y, x1, y1, x2, y2);
        }
      }
      function insertChild(n, d, x, y, x1, y1, x2, y2) {
        var xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym, i = below << 1 | right;
        n.leaf = false;
        n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode());
        if (right) x1 = xm; else x2 = xm;
        if (below) y1 = ym; else y2 = ym;
        insert(n, d, x, y, x1, y1, x2, y2);
      }
      var root = d3_geom_quadtreeNode();
      root.add = function(d) {
        insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_);
      };
      root.visit = function(f) {
        d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_);
      };
      root.find = function(point) {
        return d3_geom_quadtreeFind(root, point[0], point[1], x1_, y1_, x2_, y2_);
      };
      i = -1;
      if (x1 == null) {
        while (++i < n) {
          insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_);
        }
        --i;
      } else data.forEach(root.add);
      xs = ys = data = d = null;
      return root;
    }
    quadtree.x = function(_) {
      return arguments.length ? (x = _, quadtree) : x;
    };
    quadtree.y = function(_) {
      return arguments.length ? (y = _, quadtree) : y;
    };
    quadtree.extent = function(_) {
      if (!arguments.length) return x1 == null ? null : [ [ x1, y1 ], [ x2, y2 ] ];
      if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0], 
      y2 = +_[1][1];
      return quadtree;
    };
    quadtree.size = function(_) {
      if (!arguments.length) return x1 == null ? null : [ x2 - x1, y2 - y1 ];
      if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = y1 = 0, x2 = +_[0], y2 = +_[1];
      return quadtree;
    };
    return quadtree;
  };
  function d3_geom_quadtreeCompatX(d) {
    return d.x;
  }
  function d3_geom_quadtreeCompatY(d) {
    return d.y;
  }
  function d3_geom_quadtreeNode() {
    return {
      leaf: true,
      nodes: [],
      point: null,
      x: null,
      y: null
    };
  }
  function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) {
    if (!f(node, x1, y1, x2, y2)) {
      var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes;
      if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy);
      if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy);
      if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2);
      if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2);
    }
  }
  function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) {
    var minDistance2 = Infinity, closestPoint;
    (function find(node, x1, y1, x2, y2) {
      if (x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return;
      if (point = node.point) {
        var point, dx = x - node.x, dy = y - node.y, distance2 = dx * dx + dy * dy;
        if (distance2 < minDistance2) {
          var distance = Math.sqrt(minDistance2 = distance2);
          x0 = x - distance, y0 = y - distance;
          x3 = x + distance, y3 = y + distance;
          closestPoint = point;
        }
      }
      var children = node.nodes, xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym;
      for (var i = below << 1 | right, j = i + 4; i < j; ++i) {
        if (node = children[i & 3]) switch (i & 3) {
         case 0:
          find(node, x1, y1, xm, ym);
          break;

         case 1:
          find(node, xm, y1, x2, ym);
          break;

         case 2:
          find(node, x1, ym, xm, y2);
          break;

         case 3:
          find(node, xm, ym, x2, y2);
          break;
        }
      }
    })(root, x0, y0, x3, y3);
    return closestPoint;
  }
  d3.interpolateRgb = d3_interpolateRgb;
  function d3_interpolateRgb(a, b) {
    a = d3.rgb(a);
    b = d3.rgb(b);
    var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab;
    return function(t) {
      return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t));
    };
  }
  d3.interpolateObject = d3_interpolateObject;
  function d3_interpolateObject(a, b) {
    var i = {}, c = {}, k;
    for (k in a) {
      if (k in b) {
        i[k] = d3_interpolate(a[k], b[k]);
      } else {
        c[k] = a[k];
      }
    }
    for (k in b) {
      if (!(k in a)) {
        c[k] = b[k];
      }
    }
    return function(t) {
      for (k in i) c[k] = i[k](t);
      return c;
    };
  }
  d3.interpolateNumber = d3_interpolateNumber;
  function d3_interpolateNumber(a, b) {
    a = +a, b = +b;
    return function(t) {
      return a * (1 - t) + b * t;
    };
  }
  d3.interpolateString = d3_interpolateString;
  function d3_interpolateString(a, b) {
    var bi = d3_interpolate_numberA.lastIndex = d3_interpolate_numberB.lastIndex = 0, am, bm, bs, i = -1, s = [], q = [];
    a = a + "", b = b + "";
    while ((am = d3_interpolate_numberA.exec(a)) && (bm = d3_interpolate_numberB.exec(b))) {
      if ((bs = bm.index) > bi) {
        bs = b.slice(bi, bs);
        if (s[i]) s[i] += bs; else s[++i] = bs;
      }
      if ((am = am[0]) === (bm = bm[0])) {
        if (s[i]) s[i] += bm; else s[++i] = bm;
      } else {
        s[++i] = null;
        q.push({
          i: i,
          x: d3_interpolateNumber(am, bm)
        });
      }
      bi = d3_interpolate_numberB.lastIndex;
    }
    if (bi < b.length) {
      bs = b.slice(bi);
      if (s[i]) s[i] += bs; else s[++i] = bs;
    }
    return s.length < 2 ? q[0] ? (b = q[0].x, function(t) {
      return b(t) + "";
    }) : function() {
      return b;
    } : (b = q.length, function(t) {
      for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t);
      return s.join("");
    });
  }
  var d3_interpolate_numberA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, d3_interpolate_numberB = new RegExp(d3_interpolate_numberA.source, "g");
  d3.interpolate = d3_interpolate;
  function d3_interpolate(a, b) {
    var i = d3.interpolators.length, f;
    while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ;
    return f;
  }
  d3.interpolators = [ function(a, b) {
    var t = typeof b;
    return (t === "string" ? d3_rgb_names.has(b.toLowerCase()) || /^(#|rgb\(|hsl\()/i.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b);
  } ];
  d3.interpolateArray = d3_interpolateArray;
  function d3_interpolateArray(a, b) {
    var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i;
    for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i]));
    for (;i < na; ++i) c[i] = a[i];
    for (;i < nb; ++i) c[i] = b[i];
    return function(t) {
      for (i = 0; i < n0; ++i) c[i] = x[i](t);
      return c;
    };
  }
  var d3_ease_default = function() {
    return d3_identity;
  };
  var d3_ease = d3.map({
    linear: d3_ease_default,
    poly: d3_ease_poly,
    quad: function() {
      return d3_ease_quad;
    },
    cubic: function() {
      return d3_ease_cubic;
    },
    sin: function() {
      return d3_ease_sin;
    },
    exp: function() {
      return d3_ease_exp;
    },
    circle: function() {
      return d3_ease_circle;
    },
    elastic: d3_ease_elastic,
    back: d3_ease_back,
    bounce: function() {
      return d3_ease_bounce;
    }
  });
  var d3_ease_mode = d3.map({
    "in": d3_identity,
    out: d3_ease_reverse,
    "in-out": d3_ease_reflect,
    "out-in": function(f) {
      return d3_ease_reflect(d3_ease_reverse(f));
    }
  });
  d3.ease = function(name) {
    var i = name.indexOf("-"), t = i >= 0 ? name.slice(0, i) : name, m = i >= 0 ? name.slice(i + 1) : "in";
    t = d3_ease.get(t) || d3_ease_default;
    m = d3_ease_mode.get(m) || d3_identity;
    return d3_ease_clamp(m(t.apply(null, d3_arraySlice.call(arguments, 1))));
  };
  function d3_ease_clamp(f) {
    return function(t) {
      return t <= 0 ? 0 : t >= 1 ? 1 : f(t);
    };
  }
  function d3_ease_reverse(f) {
    return function(t) {
      return 1 - f(1 - t);
    };
  }
  function d3_ease_reflect(f) {
    return function(t) {
      return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t));
    };
  }
  function d3_ease_quad(t) {
    return t * t;
  }
  function d3_ease_cubic(t) {
    return t * t * t;
  }
  function d3_ease_cubicInOut(t) {
    if (t <= 0) return 0;
    if (t >= 1) return 1;
    var t2 = t * t, t3 = t2 * t;
    return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75);
  }
  function d3_ease_poly(e) {
    return function(t) {
      return Math.pow(t, e);
    };
  }
  function d3_ease_sin(t) {
    return 1 - Math.cos(t * halfπ);
  }
  function d3_ease_exp(t) {
    return Math.pow(2, 10 * (t - 1));
  }
  function d3_ease_circle(t) {
    return 1 - Math.sqrt(1 - t * t);
  }
  function d3_ease_elastic(a, p) {
    var s;
    if (arguments.length < 2) p = .45;
    if (arguments.length) s = p / τ * Math.asin(1 / a); else a = 1, s = p / 4;
    return function(t) {
      return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * τ / p);
    };
  }
  function d3_ease_back(s) {
    if (!s) s = 1.70158;
    return function(t) {
      return t * t * ((s + 1) * t - s);
    };
  }
  function d3_ease_bounce(t) {
    return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375;
  }
  d3.interpolateHcl = d3_interpolateHcl;
  function d3_interpolateHcl(a, b) {
    a = d3.hcl(a);
    b = d3.hcl(b);
    var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al;
    if (isNaN(bc)) bc = 0, ac = isNaN(ac) ? b.c : ac;
    if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
    return function(t) {
      return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + "";
    };
  }
  d3.interpolateHsl = d3_interpolateHsl;
  function d3_interpolateHsl(a, b) {
    a = d3.hsl(a);
    b = d3.hsl(b);
    var ah = a.h, as = a.s, al = a.l, bh = b.h - ah, bs = b.s - as, bl = b.l - al;
    if (isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as;
    if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
    return function(t) {
      return d3_hsl_rgb(ah + bh * t, as + bs * t, al + bl * t) + "";
    };
  }
  d3.interpolateLab = d3_interpolateLab;
  function d3_interpolateLab(a, b) {
    a = d3.lab(a);
    b = d3.lab(b);
    var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab;
    return function(t) {
      return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + "";
    };
  }
  d3.interpolateRound = d3_interpolateRound;
  function d3_interpolateRound(a, b) {
    b -= a;
    return function(t) {
      return Math.round(a + b * t);
    };
  }
  d3.transform = function(string) {
    var g = d3_document.createElementNS(d3.ns.prefix.svg, "g");
    return (d3.transform = function(string) {
      if (string != null) {
        g.setAttribute("transform", string);
        var t = g.transform.baseVal.consolidate();
      }
      return new d3_transform(t ? t.matrix : d3_transformIdentity);
    })(string);
  };
  function d3_transform(m) {
    var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0;
    if (r0[0] * r1[1] < r1[0] * r0[1]) {
      r0[0] *= -1;
      r0[1] *= -1;
      kx *= -1;
      kz *= -1;
    }
    this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees;
    this.translate = [ m.e, m.f ];
    this.scale = [ kx, ky ];
    this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0;
  }
  d3_transform.prototype.toString = function() {
    return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")";
  };
  function d3_transformDot(a, b) {
    return a[0] * b[0] + a[1] * b[1];
  }
  function d3_transformNormalize(a) {
    var k = Math.sqrt(d3_transformDot(a, a));
    if (k) {
      a[0] /= k;
      a[1] /= k;
    }
    return k;
  }
  function d3_transformCombine(a, b, k) {
    a[0] += k * b[0];
    a[1] += k * b[1];
    return a;
  }
  var d3_transformIdentity = {
    a: 1,
    b: 0,
    c: 0,
    d: 1,
    e: 0,
    f: 0
  };
  d3.interpolateTransform = d3_interpolateTransform;
  function d3_interpolateTransformPop(s) {
    return s.length ? s.pop() + "," : "";
  }
  function d3_interpolateTranslate(ta, tb, s, q) {
    if (ta[0] !== tb[0] || ta[1] !== tb[1]) {
      var i = s.push("translate(", null, ",", null, ")");
      q.push({
        i: i - 4,
        x: d3_interpolateNumber(ta[0], tb[0])
      }, {
        i: i - 2,
        x: d3_interpolateNumber(ta[1], tb[1])
      });
    } else if (tb[0] || tb[1]) {
      s.push("translate(" + tb + ")");
    }
  }
  function d3_interpolateRotate(ra, rb, s, q) {
    if (ra !== rb) {
      if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360;
      q.push({
        i: s.push(d3_interpolateTransformPop(s) + "rotate(", null, ")") - 2,
        x: d3_interpolateNumber(ra, rb)
      });
    } else if (rb) {
      s.push(d3_interpolateTransformPop(s) + "rotate(" + rb + ")");
    }
  }
  function d3_interpolateSkew(wa, wb, s, q) {
    if (wa !== wb) {
      q.push({
        i: s.push(d3_interpolateTransformPop(s) + "skewX(", null, ")") - 2,
        x: d3_interpolateNumber(wa, wb)
      });
    } else if (wb) {
      s.push(d3_interpolateTransformPop(s) + "skewX(" + wb + ")");
    }
  }
  function d3_interpolateScale(ka, kb, s, q) {
    if (ka[0] !== kb[0] || ka[1] !== kb[1]) {
      var i = s.push(d3_interpolateTransformPop(s) + "scale(", null, ",", null, ")");
      q.push({
        i: i - 4,
        x: d3_interpolateNumber(ka[0], kb[0])
      }, {
        i: i - 2,
        x: d3_interpolateNumber(ka[1], kb[1])
      });
    } else if (kb[0] !== 1 || kb[1] !== 1) {
      s.push(d3_interpolateTransformPop(s) + "scale(" + kb + ")");
    }
  }
  function d3_interpolateTransform(a, b) {
    var s = [], q = [];
    a = d3.transform(a), b = d3.transform(b);
    d3_interpolateTranslate(a.translate, b.translate, s, q);
    d3_interpolateRotate(a.rotate, b.rotate, s, q);
    d3_interpolateSkew(a.skew, b.skew, s, q);
    d3_interpolateScale(a.scale, b.scale, s, q);
    a = b = null;
    return function(t) {
      var i = -1, n = q.length, o;
      while (++i < n) s[(o = q[i]).i] = o.x(t);
      return s.join("");
    };
  }
  function d3_uninterpolateNumber(a, b) {
    b = (b -= a = +a) || 1 / b;
    return function(x) {
      return (x - a) / b;
    };
  }
  function d3_uninterpolateClamp(a, b) {
    b = (b -= a = +a) || 1 / b;
    return function(x) {
      return Math.max(0, Math.min(1, (x - a) / b));
    };
  }
  d3.layout = {};
  d3.layout.bundle = function() {
    return function(links) {
      var paths = [], i = -1, n = links.length;
      while (++i < n) paths.push(d3_layout_bundlePath(links[i]));
      return paths;
    };
  };
  function d3_layout_bundlePath(link) {
    var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ];
    while (start !== lca) {
      start = start.parent;
      points.push(start);
    }
    var k = points.length;
    while (end !== lca) {
      points.splice(k, 0, end);
      end = end.parent;
    }
    return points;
  }
  function d3_layout_bundleAncestors(node) {
    var ancestors = [], parent = node.parent;
    while (parent != null) {
      ancestors.push(node);
      node = parent;
      parent = parent.parent;
    }
    ancestors.push(node);
    return ancestors;
  }
  function d3_layout_bundleLeastCommonAncestor(a, b) {
    if (a === b) return a;
    var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null;
    while (aNode === bNode) {
      sharedNode = aNode;
      aNode = aNodes.pop();
      bNode = bNodes.pop();
    }
    return sharedNode;
  }
  d3.layout.chord = function() {
    var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords;
    function relayout() {
      var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j;
      chords = [];
      groups = [];
      k = 0, i = -1;
      while (++i < n) {
        x = 0, j = -1;
        while (++j < n) {
          x += matrix[i][j];
        }
        groupSums.push(x);
        subgroupIndex.push(d3.range(n));
        k += x;
      }
      if (sortGroups) {
        groupIndex.sort(function(a, b) {
          return sortGroups(groupSums[a], groupSums[b]);
        });
      }
      if (sortSubgroups) {
        subgroupIndex.forEach(function(d, i) {
          d.sort(function(a, b) {
            return sortSubgroups(matrix[i][a], matrix[i][b]);
          });
        });
      }
      k = (τ - padding * n) / k;
      x = 0, i = -1;
      while (++i < n) {
        x0 = x, j = -1;
        while (++j < n) {
          var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k;
          subgroups[di + "-" + dj] = {
            index: di,
            subindex: dj,
            startAngle: a0,
            endAngle: a1,
            value: v
          };
        }
        groups[di] = {
          index: di,
          startAngle: x0,
          endAngle: x,
          value: groupSums[di]
        };
        x += padding;
      }
      i = -1;
      while (++i < n) {
        j = i - 1;
        while (++j < n) {
          var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i];
          if (source.value || target.value) {
            chords.push(source.value < target.value ? {
              source: target,
              target: source
            } : {
              source: source,
              target: target
            });
          }
        }
      }
      if (sortChords) resort();
    }
    function resort() {
      chords.sort(function(a, b) {
        return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2);
      });
    }
    chord.matrix = function(x) {
      if (!arguments.length) return matrix;
      n = (matrix = x) && matrix.length;
      chords = groups = null;
      return chord;
    };
    chord.padding = function(x) {
      if (!arguments.length) return padding;
      padding = x;
      chords = groups = null;
      return chord;
    };
    chord.sortGroups = function(x) {
      if (!arguments.length) return sortGroups;
      sortGroups = x;
      chords = groups = null;
      return chord;
    };
    chord.sortSubgroups = function(x) {
      if (!arguments.length) return sortSubgroups;
      sortSubgroups = x;
      chords = null;
      return chord;
    };
    chord.sortChords = function(x) {
      if (!arguments.length) return sortChords;
      sortChords = x;
      if (chords) resort();
      return chord;
    };
    chord.chords = function() {
      if (!chords) relayout();
      return chords;
    };
    chord.groups = function() {
      if (!groups) relayout();
      return groups;
    };
    return chord;
  };
  d3.layout.force = function() {
    var force = {}, event = d3.dispatch("start", "tick", "end"), timer, size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, chargeDistance2 = d3_layout_forceChargeDistance2, gravity = .1, theta2 = .64, nodes = [], links = [], distances, strengths, charges;
    function repulse(node) {
      return function(quad, x1, _, x2) {
        if (quad.point !== node) {
          var dx = quad.cx - node.x, dy = quad.cy - node.y, dw = x2 - x1, dn = dx * dx + dy * dy;
          if (dw * dw / theta2 < dn) {
            if (dn < chargeDistance2) {
              var k = quad.charge / dn;
              node.px -= dx * k;
              node.py -= dy * k;
            }
            return true;
          }
          if (quad.point && dn && dn < chargeDistance2) {
            var k = quad.pointCharge / dn;
            node.px -= dx * k;
            node.py -= dy * k;
          }
        }
        return !quad.charge;
      };
    }
    force.tick = function() {
      if ((alpha *= .99) < .005) {
        timer = null;
        event.end({
          type: "end",
          alpha: alpha = 0
        });
        return true;
      }
      var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y;
      for (i = 0; i < m; ++i) {
        o = links[i];
        s = o.source;
        t = o.target;
        x = t.x - s.x;
        y = t.y - s.y;
        if (l = x * x + y * y) {
          l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
          x *= l;
          y *= l;
          t.x -= x * (k = s.weight + t.weight ? s.weight / (s.weight + t.weight) : .5);
          t.y -= y * k;
          s.x += x * (k = 1 - k);
          s.y += y * k;
        }
      }
      if (k = alpha * gravity) {
        x = size[0] / 2;
        y = size[1] / 2;
        i = -1;
        if (k) while (++i < n) {
          o = nodes[i];
          o.x += (x - o.x) * k;
          o.y += (y - o.y) * k;
        }
      }
      if (charge) {
        d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges);
        i = -1;
        while (++i < n) {
          if (!(o = nodes[i]).fixed) {
            q.visit(repulse(o));
          }
        }
      }
      i = -1;
      while (++i < n) {
        o = nodes[i];
        if (o.fixed) {
          o.x = o.px;
          o.y = o.py;
        } else {
          o.x -= (o.px - (o.px = o.x)) * friction;
          o.y -= (o.py - (o.py = o.y)) * friction;
        }
      }
      event.tick({
        type: "tick",
        alpha: alpha
      });
    };
    force.nodes = function(x) {
      if (!arguments.length) return nodes;
      nodes = x;
      return force;
    };
    force.links = function(x) {
      if (!arguments.length) return links;
      links = x;
      return force;
    };
    force.size = function(x) {
      if (!arguments.length) return size;
      size = x;
      return force;
    };
    force.linkDistance = function(x) {
      if (!arguments.length) return linkDistance;
      linkDistance = typeof x === "function" ? x : +x;
      return force;
    };
    force.distance = force.linkDistance;
    force.linkStrength = function(x) {
      if (!arguments.length) return linkStrength;
      linkStrength = typeof x === "function" ? x : +x;
      return force;
    };
    force.friction = function(x) {
      if (!arguments.length) return friction;
      friction = +x;
      return force;
    };
    force.charge = function(x) {
      if (!arguments.length) return charge;
      charge = typeof x === "function" ? x : +x;
      return force;
    };
    force.chargeDistance = function(x) {
      if (!arguments.length) return Math.sqrt(chargeDistance2);
      chargeDistance2 = x * x;
      return force;
    };
    force.gravity = function(x) {
      if (!arguments.length) return gravity;
      gravity = +x;
      return force;
    };
    force.theta = function(x) {
      if (!arguments.length) return Math.sqrt(theta2);
      theta2 = x * x;
      return force;
    };
    force.alpha = function(x) {
      if (!arguments.length) return alpha;
      x = +x;
      if (alpha) {
        if (x > 0) {
          alpha = x;
        } else {
          timer.c = null, timer.t = NaN, timer = null;
          event.end({
            type: "end",
            alpha: alpha = 0
          });
        }
      } else if (x > 0) {
        event.start({
          type: "start",
          alpha: alpha = x
        });
        timer = d3_timer(force.tick);
      }
      return force;
    };
    force.start = function() {
      var i, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o;
      for (i = 0; i < n; ++i) {
        (o = nodes[i]).index = i;
        o.weight = 0;
      }
      for (i = 0; i < m; ++i) {
        o = links[i];
        if (typeof o.source == "number") o.source = nodes[o.source];
        if (typeof o.target == "number") o.target = nodes[o.target];
        ++o.source.weight;
        ++o.target.weight;
      }
      for (i = 0; i < n; ++i) {
        o = nodes[i];
        if (isNaN(o.x)) o.x = position("x", w);
        if (isNaN(o.y)) o.y = position("y", h);
        if (isNaN(o.px)) o.px = o.x;
        if (isNaN(o.py)) o.py = o.y;
      }
      distances = [];
      if (typeof linkDistance === "function") for (i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i); else for (i = 0; i < m; ++i) distances[i] = linkDistance;
      strengths = [];
      if (typeof linkStrength === "function") for (i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i); else for (i = 0; i < m; ++i) strengths[i] = linkStrength;
      charges = [];
      if (typeof charge === "function") for (i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i); else for (i = 0; i < n; ++i) charges[i] = charge;
      function position(dimension, size) {
        if (!neighbors) {
          neighbors = new Array(n);
          for (j = 0; j < n; ++j) {
            neighbors[j] = [];
          }
          for (j = 0; j < m; ++j) {
            var o = links[j];
            neighbors[o.source.index].push(o.target);
            neighbors[o.target.index].push(o.source);
          }
        }
        var candidates = neighbors[i], j = -1, l = candidates.length, x;
        while (++j < l) if (!isNaN(x = candidates[j][dimension])) return x;
        return Math.random() * size;
      }
      return force.resume();
    };
    force.resume = function() {
      return force.alpha(.1);
    };
    force.stop = function() {
      return force.alpha(0);
    };
    force.drag = function() {
      if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend);
      if (!arguments.length) return drag;
      this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag);
    };
    function dragmove(d) {
      d.px = d3.event.x, d.py = d3.event.y;
      force.resume();
    }
    return d3.rebind(force, event, "on");
  };
  function d3_layout_forceDragstart(d) {
    d.fixed |= 2;
  }
  function d3_layout_forceDragend(d) {
    d.fixed &= ~6;
  }
  function d3_layout_forceMouseover(d) {
    d.fixed |= 4;
    d.px = d.x, d.py = d.y;
  }
  function d3_layout_forceMouseout(d) {
    d.fixed &= ~4;
  }
  function d3_layout_forceAccumulate(quad, alpha, charges) {
    var cx = 0, cy = 0;
    quad.charge = 0;
    if (!quad.leaf) {
      var nodes = quad.nodes, n = nodes.length, i = -1, c;
      while (++i < n) {
        c = nodes[i];
        if (c == null) continue;
        d3_layout_forceAccumulate(c, alpha, charges);
        quad.charge += c.charge;
        cx += c.charge * c.cx;
        cy += c.charge * c.cy;
      }
    }
    if (quad.point) {
      if (!quad.leaf) {
        quad.point.x += Math.random() - .5;
        quad.point.y += Math.random() - .5;
      }
      var k = alpha * charges[quad.point.index];
      quad.charge += quad.pointCharge = k;
      cx += k * quad.point.x;
      cy += k * quad.point.y;
    }
    quad.cx = cx / quad.charge;
    quad.cy = cy / quad.charge;
  }
  var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1, d3_layout_forceChargeDistance2 = Infinity;
  d3.layout.hierarchy = function() {
    var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue;
    function hierarchy(root) {
      var stack = [ root ], nodes = [], node;
      root.depth = 0;
      while ((node = stack.pop()) != null) {
        nodes.push(node);
        if ((childs = children.call(hierarchy, node, node.depth)) && (n = childs.length)) {
          var n, childs, child;
          while (--n >= 0) {
            stack.push(child = childs[n]);
            child.parent = node;
            child.depth = node.depth + 1;
          }
          if (value) node.value = 0;
          node.children = childs;
        } else {
          if (value) node.value = +value.call(hierarchy, node, node.depth) || 0;
          delete node.children;
        }
      }
      d3_layout_hierarchyVisitAfter(root, function(node) {
        var childs, parent;
        if (sort && (childs = node.children)) childs.sort(sort);
        if (value && (parent = node.parent)) parent.value += node.value;
      });
      return nodes;
    }
    hierarchy.sort = function(x) {
      if (!arguments.length) return sort;
      sort = x;
      return hierarchy;
    };
    hierarchy.children = function(x) {
      if (!arguments.length) return children;
      children = x;
      return hierarchy;
    };
    hierarchy.value = function(x) {
      if (!arguments.length) return value;
      value = x;
      return hierarchy;
    };
    hierarchy.revalue = function(root) {
      if (value) {
        d3_layout_hierarchyVisitBefore(root, function(node) {
          if (node.children) node.value = 0;
        });
        d3_layout_hierarchyVisitAfter(root, function(node) {
          var parent;
          if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0;
          if (parent = node.parent) parent.value += node.value;
        });
      }
      return root;
    };
    return hierarchy;
  };
  function d3_layout_hierarchyRebind(object, hierarchy) {
    d3.rebind(object, hierarchy, "sort", "children", "value");
    object.nodes = object;
    object.links = d3_layout_hierarchyLinks;
    return object;
  }
  function d3_layout_hierarchyVisitBefore(node, callback) {
    var nodes = [ node ];
    while ((node = nodes.pop()) != null) {
      callback(node);
      if ((children = node.children) && (n = children.length)) {
        var n, children;
        while (--n >= 0) nodes.push(children[n]);
      }
    }
  }
  function d3_layout_hierarchyVisitAfter(node, callback) {
    var nodes = [ node ], nodes2 = [];
    while ((node = nodes.pop()) != null) {
      nodes2.push(node);
      if ((children = node.children) && (n = children.length)) {
        var i = -1, n, children;
        while (++i < n) nodes.push(children[i]);
      }
    }
    while ((node = nodes2.pop()) != null) {
      callback(node);
    }
  }
  function d3_layout_hierarchyChildren(d) {
    return d.children;
  }
  function d3_layout_hierarchyValue(d) {
    return d.value;
  }
  function d3_layout_hierarchySort(a, b) {
    return b.value - a.value;
  }
  function d3_layout_hierarchyLinks(nodes) {
    return d3.merge(nodes.map(function(parent) {
      return (parent.children || []).map(function(child) {
        return {
          source: parent,
          target: child
        };
      });
    }));
  }
  d3.layout.partition = function() {
    var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ];
    function position(node, x, dx, dy) {
      var children = node.children;
      node.x = x;
      node.y = node.depth * dy;
      node.dx = dx;
      node.dy = dy;
      if (children && (n = children.length)) {
        var i = -1, n, c, d;
        dx = node.value ? dx / node.value : 0;
        while (++i < n) {
          position(c = children[i], x, d = c.value * dx, dy);
          x += d;
        }
      }
    }
    function depth(node) {
      var children = node.children, d = 0;
      if (children && (n = children.length)) {
        var i = -1, n;
        while (++i < n) d = Math.max(d, depth(children[i]));
      }
      return 1 + d;
    }
    function partition(d, i) {
      var nodes = hierarchy.call(this, d, i);
      position(nodes[0], 0, size[0], size[1] / depth(nodes[0]));
      return nodes;
    }
    partition.size = function(x) {
      if (!arguments.length) return size;
      size = x;
      return partition;
    };
    return d3_layout_hierarchyRebind(partition, hierarchy);
  };
  d3.layout.pie = function() {
    var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = τ, padAngle = 0;
    function pie(data) {
      var n = data.length, values = data.map(function(d, i) {
        return +value.call(pie, d, i);
      }), a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle), da = (typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a, p = Math.min(Math.abs(da) / n, +(typeof padAngle === "function" ? padAngle.apply(this, arguments) : padAngle)), pa = p * (da < 0 ? -1 : 1), sum = d3.sum(values), k = sum ? (da - n * pa) / sum : 0, index = d3.range(n), arcs = [], v;
      if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) {
        return values[j] - values[i];
      } : function(i, j) {
        return sort(data[i], data[j]);
      });
      index.forEach(function(i) {
        arcs[i] = {
          data: data[i],
          value: v = values[i],
          startAngle: a,
          endAngle: a += v * k + pa,
          padAngle: p
        };
      });
      return arcs;
    }
    pie.value = function(_) {
      if (!arguments.length) return value;
      value = _;
      return pie;
    };
    pie.sort = function(_) {
      if (!arguments.length) return sort;
      sort = _;
      return pie;
    };
    pie.startAngle = function(_) {
      if (!arguments.length) return startAngle;
      startAngle = _;
      return pie;
    };
    pie.endAngle = function(_) {
      if (!arguments.length) return endAngle;
      endAngle = _;
      return pie;
    };
    pie.padAngle = function(_) {
      if (!arguments.length) return padAngle;
      padAngle = _;
      return pie;
    };
    return pie;
  };
  var d3_layout_pieSortByValue = {};
  d3.layout.stack = function() {
    var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY;
    function stack(data, index) {
      if (!(n = data.length)) return data;
      var series = data.map(function(d, i) {
        return values.call(stack, d, i);
      });
      var points = series.map(function(d) {
        return d.map(function(v, i) {
          return [ x.call(stack, v, i), y.call(stack, v, i) ];
        });
      });
      var orders = order.call(stack, points, index);
      series = d3.permute(series, orders);
      points = d3.permute(points, orders);
      var offsets = offset.call(stack, points, index);
      var m = series[0].length, n, i, j, o;
      for (j = 0; j < m; ++j) {
        out.call(stack, series[0][j], o = offsets[j], points[0][j][1]);
        for (i = 1; i < n; ++i) {
          out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]);
        }
      }
      return data;
    }
    stack.values = function(x) {
      if (!arguments.length) return values;
      values = x;
      return stack;
    };
    stack.order = function(x) {
      if (!arguments.length) return order;
      order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault;
      return stack;
    };
    stack.offset = function(x) {
      if (!arguments.length) return offset;
      offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero;
      return stack;
    };
    stack.x = function(z) {
      if (!arguments.length) return x;
      x = z;
      return stack;
    };
    stack.y = function(z) {
      if (!arguments.length) return y;
      y = z;
      return stack;
    };
    stack.out = function(z) {
      if (!arguments.length) return out;
      out = z;
      return stack;
    };
    return stack;
  };
  function d3_layout_stackX(d) {
    return d.x;
  }
  function d3_layout_stackY(d) {
    return d.y;
  }
  function d3_layout_stackOut(d, y0, y) {
    d.y0 = y0;
    d.y = y;
  }
  var d3_layout_stackOrders = d3.map({
    "inside-out": function(data) {
      var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) {
        return max[a] - max[b];
      }), top = 0, bottom = 0, tops = [], bottoms = [];
      for (i = 0; i < n; ++i) {
        j = index[i];
        if (top < bottom) {
          top += sums[j];
          tops.push(j);
        } else {
          bottom += sums[j];
          bottoms.push(j);
        }
      }
      return bottoms.reverse().concat(tops);
    },
    reverse: function(data) {
      return d3.range(data.length).reverse();
    },
    "default": d3_layout_stackOrderDefault
  });
  var d3_layout_stackOffsets = d3.map({
    silhouette: function(data) {
      var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = [];
      for (j = 0; j < m; ++j) {
        for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
        if (o > max) max = o;
        sums.push(o);
      }
      for (j = 0; j < m; ++j) {
        y0[j] = (max - sums[j]) / 2;
      }
      return y0;
    },
    wiggle: function(data) {
      var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = [];
      y0[0] = o = o0 = 0;
      for (j = 1; j < m; ++j) {
        for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1];
        for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) {
          for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) {
            s3 += (data[k][j][1] - data[k][j - 1][1]) / dx;
          }
          s2 += s3 * data[i][j][1];
        }
        y0[j] = o -= s1 ? s2 / s1 * dx : 0;
        if (o < o0) o0 = o;
      }
      for (j = 0; j < m; ++j) y0[j] -= o0;
      return y0;
    },
    expand: function(data) {
      var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = [];
      for (j = 0; j < m; ++j) {
        for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
        if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k;
      }
      for (j = 0; j < m; ++j) y0[j] = 0;
      return y0;
    },
    zero: d3_layout_stackOffsetZero
  });
  function d3_layout_stackOrderDefault(data) {
    return d3.range(data.length);
  }
  function d3_layout_stackOffsetZero(data) {
    var j = -1, m = data[0].length, y0 = [];
    while (++j < m) y0[j] = 0;
    return y0;
  }
  function d3_layout_stackMaxIndex(array) {
    var i = 1, j = 0, v = array[0][1], k, n = array.length;
    for (;i < n; ++i) {
      if ((k = array[i][1]) > v) {
        j = i;
        v = k;
      }
    }
    return j;
  }
  function d3_layout_stackReduceSum(d) {
    return d.reduce(d3_layout_stackSum, 0);
  }
  function d3_layout_stackSum(p, d) {
    return p + d[1];
  }
  d3.layout.histogram = function() {
    var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges;
    function histogram(data, i) {
      var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x;
      while (++i < m) {
        bin = bins[i] = [];
        bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]);
        bin.y = 0;
      }
      if (m > 0) {
        i = -1;
        while (++i < n) {
          x = values[i];
          if (x >= range[0] && x <= range[1]) {
            bin = bins[d3.bisect(thresholds, x, 1, m) - 1];
            bin.y += k;
            bin.push(data[i]);
          }
        }
      }
      return bins;
    }
    histogram.value = function(x) {
      if (!arguments.length) return valuer;
      valuer = x;
      return histogram;
    };
    histogram.range = function(x) {
      if (!arguments.length) return ranger;
      ranger = d3_functor(x);
      return histogram;
    };
    histogram.bins = function(x) {
      if (!arguments.length) return binner;
      binner = typeof x === "number" ? function(range) {
        return d3_layout_histogramBinFixed(range, x);
      } : d3_functor(x);
      return histogram;
    };
    histogram.frequency = function(x) {
      if (!arguments.length) return frequency;
      frequency = !!x;
      return histogram;
    };
    return histogram;
  };
  function d3_layout_histogramBinSturges(range, values) {
    return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1));
  }
  function d3_layout_histogramBinFixed(range, n) {
    var x = -1, b = +range[0], m = (range[1] - b) / n, f = [];
    while (++x <= n) f[x] = m * x + b;
    return f;
  }
  function d3_layout_histogramRange(values) {
    return [ d3.min(values), d3.max(values) ];
  }
  d3.layout.pack = function() {
    var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ], radius;
    function pack(d, i) {
      var nodes = hierarchy.call(this, d, i), root = nodes[0], w = size[0], h = size[1], r = radius == null ? Math.sqrt : typeof radius === "function" ? radius : function() {
        return radius;
      };
      root.x = root.y = 0;
      d3_layout_hierarchyVisitAfter(root, function(d) {
        d.r = +r(d.value);
      });
      d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings);
      if (padding) {
        var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2;
        d3_layout_hierarchyVisitAfter(root, function(d) {
          d.r += dr;
        });
        d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings);
        d3_layout_hierarchyVisitAfter(root, function(d) {
          d.r -= dr;
        });
      }
      d3_layout_packTransform(root, w / 2, h / 2, radius ? 1 : 1 / Math.max(2 * root.r / w, 2 * root.r / h));
      return nodes;
    }
    pack.size = function(_) {
      if (!arguments.length) return size;
      size = _;
      return pack;
    };
    pack.radius = function(_) {
      if (!arguments.length) return radius;
      radius = _ == null || typeof _ === "function" ? _ : +_;
      return pack;
    };
    pack.padding = function(_) {
      if (!arguments.length) return padding;
      padding = +_;
      return pack;
    };
    return d3_layout_hierarchyRebind(pack, hierarchy);
  };
  function d3_layout_packSort(a, b) {
    return a.value - b.value;
  }
  function d3_layout_packInsert(a, b) {
    var c = a._pack_next;
    a._pack_next = b;
    b._pack_prev = a;
    b._pack_next = c;
    c._pack_prev = b;
  }
  function d3_layout_packSplice(a, b) {
    a._pack_next = b;
    b._pack_prev = a;
  }
  function d3_layout_packIntersects(a, b) {
    var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r;
    return .999 * dr * dr > dx * dx + dy * dy;
  }
  function d3_layout_packSiblings(node) {
    if (!(nodes = node.children) || !(n = nodes.length)) return;
    var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n;
    function bound(node) {
      xMin = Math.min(node.x - node.r, xMin);
      xMax = Math.max(node.x + node.r, xMax);
      yMin = Math.min(node.y - node.r, yMin);
      yMax = Math.max(node.y + node.r, yMax);
    }
    nodes.forEach(d3_layout_packLink);
    a = nodes[0];
    a.x = -a.r;
    a.y = 0;
    bound(a);
    if (n > 1) {
      b = nodes[1];
      b.x = b.r;
      b.y = 0;
      bound(b);
      if (n > 2) {
        c = nodes[2];
        d3_layout_packPlace(a, b, c);
        bound(c);
        d3_layout_packInsert(a, c);
        a._pack_prev = c;
        d3_layout_packInsert(c, b);
        b = a._pack_next;
        for (i = 3; i < n; i++) {
          d3_layout_packPlace(a, b, c = nodes[i]);
          var isect = 0, s1 = 1, s2 = 1;
          for (j = b._pack_next; j !== b; j = j._pack_next, s1++) {
            if (d3_layout_packIntersects(j, c)) {
              isect = 1;
              break;
            }
          }
          if (isect == 1) {
            for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) {
              if (d3_layout_packIntersects(k, c)) {
                break;
              }
            }
          }
          if (isect) {
            if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b);
            i--;
          } else {
            d3_layout_packInsert(a, c);
            b = c;
            bound(c);
          }
        }
      }
    }
    var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0;
    for (i = 0; i < n; i++) {
      c = nodes[i];
      c.x -= cx;
      c.y -= cy;
      cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y));
    }
    node.r = cr;
    nodes.forEach(d3_layout_packUnlink);
  }
  function d3_layout_packLink(node) {
    node._pack_next = node._pack_prev = node;
  }
  function d3_layout_packUnlink(node) {
    delete node._pack_next;
    delete node._pack_prev;
  }
  function d3_layout_packTransform(node, x, y, k) {
    var children = node.children;
    node.x = x += k * node.x;
    node.y = y += k * node.y;
    node.r *= k;
    if (children) {
      var i = -1, n = children.length;
      while (++i < n) d3_layout_packTransform(children[i], x, y, k);
    }
  }
  function d3_layout_packPlace(a, b, c) {
    var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y;
    if (db && (dx || dy)) {
      var da = b.r + c.r, dc = dx * dx + dy * dy;
      da *= da;
      db *= db;
      var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc);
      c.x = a.x + x * dx + y * dy;
      c.y = a.y + x * dy - y * dx;
    } else {
      c.x = a.x + db;
      c.y = a.y;
    }
  }
  d3.layout.tree = function() {
    var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = null;
    function tree(d, i) {
      var nodes = hierarchy.call(this, d, i), root0 = nodes[0], root1 = wrapTree(root0);
      d3_layout_hierarchyVisitAfter(root1, firstWalk), root1.parent.m = -root1.z;
      d3_layout_hierarchyVisitBefore(root1, secondWalk);
      if (nodeSize) d3_layout_hierarchyVisitBefore(root0, sizeNode); else {
        var left = root0, right = root0, bottom = root0;
        d3_layout_hierarchyVisitBefore(root0, function(node) {
          if (node.x < left.x) left = node;
          if (node.x > right.x) right = node;
          if (node.depth > bottom.depth) bottom = node;
        });
        var tx = separation(left, right) / 2 - left.x, kx = size[0] / (right.x + separation(right, left) / 2 + tx), ky = size[1] / (bottom.depth || 1);
        d3_layout_hierarchyVisitBefore(root0, function(node) {
          node.x = (node.x + tx) * kx;
          node.y = node.depth * ky;
        });
      }
      return nodes;
    }
    function wrapTree(root0) {
      var root1 = {
        A: null,
        children: [ root0 ]
      }, queue = [ root1 ], node1;
      while ((node1 = queue.pop()) != null) {
        for (var children = node1.children, child, i = 0, n = children.length; i < n; ++i) {
          queue.push((children[i] = child = {
            _: children[i],
            parent: node1,
            children: (child = children[i].children) && child.slice() || [],
            A: null,
            a: null,
            z: 0,
            m: 0,
            c: 0,
            s: 0,
            t: null,
            i: i
          }).a = child);
        }
      }
      return root1.children[0];
    }
    function firstWalk(v) {
      var children = v.children, siblings = v.parent.children, w = v.i ? siblings[v.i - 1] : null;
      if (children.length) {
        d3_layout_treeShift(v);
        var midpoint = (children[0].z + children[children.length - 1].z) / 2;
        if (w) {
          v.z = w.z + separation(v._, w._);
          v.m = v.z - midpoint;
        } else {
          v.z = midpoint;
        }
      } else if (w) {
        v.z = w.z + separation(v._, w._);
      }
      v.parent.A = apportion(v, w, v.parent.A || siblings[0]);
    }
    function secondWalk(v) {
      v._.x = v.z + v.parent.m;
      v.m += v.parent.m;
    }
    function apportion(v, w, ancestor) {
      if (w) {
        var vip = v, vop = v, vim = w, vom = vip.parent.children[0], sip = vip.m, sop = vop.m, sim = vim.m, som = vom.m, shift;
        while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) {
          vom = d3_layout_treeLeft(vom);
          vop = d3_layout_treeRight(vop);
          vop.a = v;
          shift = vim.z + sim - vip.z - sip + separation(vim._, vip._);
          if (shift > 0) {
            d3_layout_treeMove(d3_layout_treeAncestor(vim, v, ancestor), v, shift);
            sip += shift;
            sop += shift;
          }
          sim += vim.m;
          sip += vip.m;
          som += vom.m;
          sop += vop.m;
        }
        if (vim && !d3_layout_treeRight(vop)) {
          vop.t = vim;
          vop.m += sim - sop;
        }
        if (vip && !d3_layout_treeLeft(vom)) {
          vom.t = vip;
          vom.m += sip - som;
          ancestor = v;
        }
      }
      return ancestor;
    }
    function sizeNode(node) {
      node.x *= size[0];
      node.y = node.depth * size[1];
    }
    tree.separation = function(x) {
      if (!arguments.length) return separation;
      separation = x;
      return tree;
    };
    tree.size = function(x) {
      if (!arguments.length) return nodeSize ? null : size;
      nodeSize = (size = x) == null ? sizeNode : null;
      return tree;
    };
    tree.nodeSize = function(x) {
      if (!arguments.length) return nodeSize ? size : null;
      nodeSize = (size = x) == null ? null : sizeNode;
      return tree;
    };
    return d3_layout_hierarchyRebind(tree, hierarchy);
  };
  function d3_layout_treeSeparation(a, b) {
    return a.parent == b.parent ? 1 : 2;
  }
  function d3_layout_treeLeft(v) {
    var children = v.children;
    return children.length ? children[0] : v.t;
  }
  function d3_layout_treeRight(v) {
    var children = v.children, n;
    return (n = children.length) ? children[n - 1] : v.t;
  }
  function d3_layout_treeMove(wm, wp, shift) {
    var change = shift / (wp.i - wm.i);
    wp.c -= change;
    wp.s += shift;
    wm.c += change;
    wp.z += shift;
    wp.m += shift;
  }
  function d3_layout_treeShift(v) {
    var shift = 0, change = 0, children = v.children, i = children.length, w;
    while (--i >= 0) {
      w = children[i];
      w.z += shift;
      w.m += shift;
      shift += w.s + (change += w.c);
    }
  }
  function d3_layout_treeAncestor(vim, v, ancestor) {
    return vim.a.parent === v.parent ? vim.a : ancestor;
  }
  d3.layout.cluster = function() {
    var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false;
    function cluster(d, i) {
      var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0;
      d3_layout_hierarchyVisitAfter(root, function(node) {
        var children = node.children;
        if (children && children.length) {
          node.x = d3_layout_clusterX(children);
          node.y = d3_layout_clusterY(children);
        } else {
          node.x = previousNode ? x += separation(node, previousNode) : 0;
          node.y = 0;
          previousNode = node;
        }
      });
      var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2;
      d3_layout_hierarchyVisitAfter(root, nodeSize ? function(node) {
        node.x = (node.x - root.x) * size[0];
        node.y = (root.y - node.y) * size[1];
      } : function(node) {
        node.x = (node.x - x0) / (x1 - x0) * size[0];
        node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1];
      });
      return nodes;
    }
    cluster.separation = function(x) {
      if (!arguments.length) return separation;
      separation = x;
      return cluster;
    };
    cluster.size = function(x) {
      if (!arguments.length) return nodeSize ? null : size;
      nodeSize = (size = x) == null;
      return cluster;
    };
    cluster.nodeSize = function(x) {
      if (!arguments.length) return nodeSize ? size : null;
      nodeSize = (size = x) != null;
      return cluster;
    };
    return d3_layout_hierarchyRebind(cluster, hierarchy);
  };
  function d3_layout_clusterY(children) {
    return 1 + d3.max(children, function(child) {
      return child.y;
    });
  }
  function d3_layout_clusterX(children) {
    return children.reduce(function(x, child) {
      return x + child.x;
    }, 0) / children.length;
  }
  function d3_layout_clusterLeft(node) {
    var children = node.children;
    return children && children.length ? d3_layout_clusterLeft(children[0]) : node;
  }
  function d3_layout_clusterRight(node) {
    var children = node.children, n;
    return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node;
  }
  d3.layout.treemap = function() {
    var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5));
    function scale(children, k) {
      var i = -1, n = children.length, child, area;
      while (++i < n) {
        area = (child = children[i]).value * (k < 0 ? 0 : k);
        child.area = isNaN(area) || area <= 0 ? 0 : area;
      }
    }
    function squarify(node) {
      var children = node.children;
      if (children && children.length) {
        var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n;
        scale(remaining, rect.dx * rect.dy / node.value);
        row.area = 0;
        while ((n = remaining.length) > 0) {
          row.push(child = remaining[n - 1]);
          row.area += child.area;
          if (mode !== "squarify" || (score = worst(row, u)) <= best) {
            remaining.pop();
            best = score;
          } else {
            row.area -= row.pop().area;
            position(row, u, rect, false);
            u = Math.min(rect.dx, rect.dy);
            row.length = row.area = 0;
            best = Infinity;
          }
        }
        if (row.length) {
          position(row, u, rect, true);
          row.length = row.area = 0;
        }
        children.forEach(squarify);
      }
    }
    function stickify(node) {
      var children = node.children;
      if (children && children.length) {
        var rect = pad(node), remaining = children.slice(), child, row = [];
        scale(remaining, rect.dx * rect.dy / node.value);
        row.area = 0;
        while (child = remaining.pop()) {
          row.push(child);
          row.area += child.area;
          if (child.z != null) {
            position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length);
            row.length = row.area = 0;
          }
        }
        children.forEach(stickify);
      }
    }
    function worst(row, u) {
      var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length;
      while (++i < n) {
        if (!(r = row[i].area)) continue;
        if (r < rmin) rmin = r;
        if (r > rmax) rmax = r;
      }
      s *= s;
      u *= u;
      return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity;
    }
    function position(row, u, rect, flush) {
      var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o;
      if (u == rect.dx) {
        if (flush || v > rect.dy) v = rect.dy;
        while (++i < n) {
          o = row[i];
          o.x = x;
          o.y = y;
          o.dy = v;
          x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0);
        }
        o.z = true;
        o.dx += rect.x + rect.dx - x;
        rect.y += v;
        rect.dy -= v;
      } else {
        if (flush || v > rect.dx) v = rect.dx;
        while (++i < n) {
          o = row[i];
          o.x = x;
          o.y = y;
          o.dx = v;
          y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0);
        }
        o.z = false;
        o.dy += rect.y + rect.dy - y;
        rect.x += v;
        rect.dx -= v;
      }
    }
    function treemap(d) {
      var nodes = stickies || hierarchy(d), root = nodes[0];
      root.x = root.y = 0;
      if (root.value) root.dx = size[0], root.dy = size[1]; else root.dx = root.dy = 0;
      if (stickies) hierarchy.revalue(root);
      scale([ root ], root.dx * root.dy / root.value);
      (stickies ? stickify : squarify)(root);
      if (sticky) stickies = nodes;
      return nodes;
    }
    treemap.size = function(x) {
      if (!arguments.length) return size;
      size = x;
      return treemap;
    };
    treemap.padding = function(x) {
      if (!arguments.length) return padding;
      function padFunction(node) {
        var p = x.call(treemap, node, node.depth);
        return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p);
      }
      function padConstant(node) {
        return d3_layout_treemapPad(node, x);
      }
      var type;
      pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], 
      padConstant) : padConstant;
      return treemap;
    };
    treemap.round = function(x) {
      if (!arguments.length) return round != Number;
      round = x ? Math.round : Number;
      return treemap;
    };
    treemap.sticky = function(x) {
      if (!arguments.length) return sticky;
      sticky = x;
      stickies = null;
      return treemap;
    };
    treemap.ratio = function(x) {
      if (!arguments.length) return ratio;
      ratio = x;
      return treemap;
    };
    treemap.mode = function(x) {
      if (!arguments.length) return mode;
      mode = x + "";
      return treemap;
    };
    return d3_layout_hierarchyRebind(treemap, hierarchy);
  };
  function d3_layout_treemapPadNull(node) {
    return {
      x: node.x,
      y: node.y,
      dx: node.dx,
      dy: node.dy
    };
  }
  function d3_layout_treemapPad(node, padding) {
    var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2];
    if (dx < 0) {
      x += dx / 2;
      dx = 0;
    }
    if (dy < 0) {
      y += dy / 2;
      dy = 0;
    }
    return {
      x: x,
      y: y,
      dx: dx,
      dy: dy
    };
  }
  d3.random = {
    normal: function(µ, σ) {
      var n = arguments.length;
      if (n < 2) σ = 1;
      if (n < 1) µ = 0;
      return function() {
        var x, y, r;
        do {
          x = Math.random() * 2 - 1;
          y = Math.random() * 2 - 1;
          r = x * x + y * y;
        } while (!r || r > 1);
        return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r);
      };
    },
    logNormal: function() {
      var random = d3.random.normal.apply(d3, arguments);
      return function() {
        return Math.exp(random());
      };
    },
    bates: function(m) {
      var random = d3.random.irwinHall(m);
      return function() {
        return random() / m;
      };
    },
    irwinHall: function(m) {
      return function() {
        for (var s = 0, j = 0; j < m; j++) s += Math.random();
        return s;
      };
    }
  };
  d3.scale = {};
  function d3_scaleExtent(domain) {
    var start = domain[0], stop = domain[domain.length - 1];
    return start < stop ? [ start, stop ] : [ stop, start ];
  }
  function d3_scaleRange(scale) {
    return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
  }
  function d3_scale_bilinear(domain, range, uninterpolate, interpolate) {
    var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]);
    return function(x) {
      return i(u(x));
    };
  }
  function d3_scale_nice(domain, nice) {
    var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx;
    if (x1 < x0) {
      dx = i0, i0 = i1, i1 = dx;
      dx = x0, x0 = x1, x1 = dx;
    }
    domain[i0] = nice.floor(x0);
    domain[i1] = nice.ceil(x1);
    return domain;
  }
  function d3_scale_niceStep(step) {
    return step ? {
      floor: function(x) {
        return Math.floor(x / step) * step;
      },
      ceil: function(x) {
        return Math.ceil(x / step) * step;
      }
    } : d3_scale_niceIdentity;
  }
  var d3_scale_niceIdentity = {
    floor: d3_identity,
    ceil: d3_identity
  };
  function d3_scale_polylinear(domain, range, uninterpolate, interpolate) {
    var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1;
    if (domain[k] < domain[0]) {
      domain = domain.slice().reverse();
      range = range.slice().reverse();
    }
    while (++j <= k) {
      u.push(uninterpolate(domain[j - 1], domain[j]));
      i.push(interpolate(range[j - 1], range[j]));
    }
    return function(x) {
      var j = d3.bisect(domain, x, 1, k) - 1;
      return i[j](u[j](x));
    };
  }
  d3.scale.linear = function() {
    return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3_interpolate, false);
  };
  function d3_scale_linear(domain, range, interpolate, clamp) {
    var output, input;
    function rescale() {
      var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber;
      output = linear(domain, range, uninterpolate, interpolate);
      input = linear(range, domain, uninterpolate, d3_interpolate);
      return scale;
    }
    function scale(x) {
      return output(x);
    }
    scale.invert = function(y) {
      return input(y);
    };
    scale.domain = function(x) {
      if (!arguments.length) return domain;
      domain = x.map(Number);
      return rescale();
    };
    scale.range = function(x) {
      if (!arguments.length) return range;
      range = x;
      return rescale();
    };
    scale.rangeRound = function(x) {
      return scale.range(x).interpolate(d3_interpolateRound);
    };
    scale.clamp = function(x) {
      if (!arguments.length) return clamp;
      clamp = x;
      return rescale();
    };
    scale.interpolate = function(x) {
      if (!arguments.length) return interpolate;
      interpolate = x;
      return rescale();
    };
    scale.ticks = function(m) {
      return d3_scale_linearTicks(domain, m);
    };
    scale.tickFormat = function(m, format) {
      return d3_scale_linearTickFormat(domain, m, format);
    };
    scale.nice = function(m) {
      d3_scale_linearNice(domain, m);
      return rescale();
    };
    scale.copy = function() {
      return d3_scale_linear(domain, range, interpolate, clamp);
    };
    return rescale();
  }
  function d3_scale_linearRebind(scale, linear) {
    return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp");
  }
  function d3_scale_linearNice(domain, m) {
    d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2]));
    d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2]));
    return domain;
  }
  function d3_scale_linearTickRange(domain, m) {
    if (m == null) m = 10;
    var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step;
    if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2;
    extent[0] = Math.ceil(extent[0] / step) * step;
    extent[1] = Math.floor(extent[1] / step) * step + step * .5;
    extent[2] = step;
    return extent;
  }
  function d3_scale_linearTicks(domain, m) {
    return d3.range.apply(d3, d3_scale_linearTickRange(domain, m));
  }
  function d3_scale_linearTickFormat(domain, m, format) {
    var range = d3_scale_linearTickRange(domain, m);
    if (format) {
      var match = d3_format_re.exec(format);
      match.shift();
      if (match[8] === "s") {
        var prefix = d3.formatPrefix(Math.max(abs(range[0]), abs(range[1])));
        if (!match[7]) match[7] = "." + d3_scale_linearPrecision(prefix.scale(range[2]));
        match[8] = "f";
        format = d3.format(match.join(""));
        return function(d) {
          return format(prefix.scale(d)) + prefix.symbol;
        };
      }
      if (!match[7]) match[7] = "." + d3_scale_linearFormatPrecision(match[8], range);
      format = match.join("");
    } else {
      format = ",." + d3_scale_linearPrecision(range[2]) + "f";
    }
    return d3.format(format);
  }
  var d3_scale_linearFormatSignificant = {
    s: 1,
    g: 1,
    p: 1,
    r: 1,
    e: 1
  };
  function d3_scale_linearPrecision(value) {
    return -Math.floor(Math.log(value) / Math.LN10 + .01);
  }
  function d3_scale_linearFormatPrecision(type, range) {
    var p = d3_scale_linearPrecision(range[2]);
    return type in d3_scale_linearFormatSignificant ? Math.abs(p - d3_scale_linearPrecision(Math.max(abs(range[0]), abs(range[1])))) + +(type !== "e") : p - (type === "%") * 2;
  }
  d3.scale.log = function() {
    return d3_scale_log(d3.scale.linear().domain([ 0, 1 ]), 10, true, [ 1, 10 ]);
  };
  function d3_scale_log(linear, base, positive, domain) {
    function log(x) {
      return (positive ? Math.log(x < 0 ? 0 : x) : -Math.log(x > 0 ? 0 : -x)) / Math.log(base);
    }
    function pow(x) {
      return positive ? Math.pow(base, x) : -Math.pow(base, -x);
    }
    function scale(x) {
      return linear(log(x));
    }
    scale.invert = function(x) {
      return pow(linear.invert(x));
    };
    scale.domain = function(x) {
      if (!arguments.length) return domain;
      positive = x[0] >= 0;
      linear.domain((domain = x.map(Number)).map(log));
      return scale;
    };
    scale.base = function(_) {
      if (!arguments.length) return base;
      base = +_;
      linear.domain(domain.map(log));
      return scale;
    };
    scale.nice = function() {
      var niced = d3_scale_nice(domain.map(log), positive ? Math : d3_scale_logNiceNegative);
      linear.domain(niced);
      domain = niced.map(pow);
      return scale;
    };
    scale.ticks = function() {
      var extent = d3_scaleExtent(domain), ticks = [], u = extent[0], v = extent[1], i = Math.floor(log(u)), j = Math.ceil(log(v)), n = base % 1 ? 2 : base;
      if (isFinite(j - i)) {
        if (positive) {
          for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(pow(i) * k);
          ticks.push(pow(i));
        } else {
          ticks.push(pow(i));
          for (;i++ < j; ) for (var k = n - 1; k > 0; k--) ticks.push(pow(i) * k);
        }
        for (i = 0; ticks[i] < u; i++) {}
        for (j = ticks.length; ticks[j - 1] > v; j--) {}
        ticks = ticks.slice(i, j);
      }
      return ticks;
    };
    scale.tickFormat = function(n, format) {
      if (!arguments.length) return d3_scale_logFormat;
      if (arguments.length < 2) format = d3_scale_logFormat; else if (typeof format !== "function") format = d3.format(format);
      var k = Math.max(1, base * n / scale.ticks().length);
      return function(d) {
        var i = d / pow(Math.round(log(d)));
        if (i * base < base - .5) i *= base;
        return i <= k ? format(d) : "";
      };
    };
    scale.copy = function() {
      return d3_scale_log(linear.copy(), base, positive, domain);
    };
    return d3_scale_linearRebind(scale, linear);
  }
  var d3_scale_logFormat = d3.format(".0e"), d3_scale_logNiceNegative = {
    floor: function(x) {
      return -Math.ceil(-x);
    },
    ceil: function(x) {
      return -Math.floor(-x);
    }
  };
  d3.scale.pow = function() {
    return d3_scale_pow(d3.scale.linear(), 1, [ 0, 1 ]);
  };
  function d3_scale_pow(linear, exponent, domain) {
    var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent);
    function scale(x) {
      return linear(powp(x));
    }
    scale.invert = function(x) {
      return powb(linear.invert(x));
    };
    scale.domain = function(x) {
      if (!arguments.length) return domain;
      linear.domain((domain = x.map(Number)).map(powp));
      return scale;
    };
    scale.ticks = function(m) {
      return d3_scale_linearTicks(domain, m);
    };
    scale.tickFormat = function(m, format) {
      return d3_scale_linearTickFormat(domain, m, format);
    };
    scale.nice = function(m) {
      return scale.domain(d3_scale_linearNice(domain, m));
    };
    scale.exponent = function(x) {
      if (!arguments.length) return exponent;
      powp = d3_scale_powPow(exponent = x);
      powb = d3_scale_powPow(1 / exponent);
      linear.domain(domain.map(powp));
      return scale;
    };
    scale.copy = function() {
      return d3_scale_pow(linear.copy(), exponent, domain);
    };
    return d3_scale_linearRebind(scale, linear);
  }
  function d3_scale_powPow(e) {
    return function(x) {
      return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e);
    };
  }
  d3.scale.sqrt = function() {
    return d3.scale.pow().exponent(.5);
  };
  d3.scale.ordinal = function() {
    return d3_scale_ordinal([], {
      t: "range",
      a: [ [] ]
    });
  };
  function d3_scale_ordinal(domain, ranger) {
    var index, range, rangeBand;
    function scale(x) {
      return range[((index.get(x) || (ranger.t === "range" ? index.set(x, domain.push(x)) : NaN)) - 1) % range.length];
    }
    function steps(start, step) {
      return d3.range(domain.length).map(function(i) {
        return start + step * i;
      });
    }
    scale.domain = function(x) {
      if (!arguments.length) return domain;
      domain = [];
      index = new d3_Map();
      var i = -1, n = x.length, xi;
      while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi));
      return scale[ranger.t].apply(scale, ranger.a);
    };
    scale.range = function(x) {
      if (!arguments.length) return range;
      range = x;
      rangeBand = 0;
      ranger = {
        t: "range",
        a: arguments
      };
      return scale;
    };
    scale.rangePoints = function(x, padding) {
      if (arguments.length < 2) padding = 0;
      var start = x[0], stop = x[1], step = domain.length < 2 ? (start = (start + stop) / 2, 
      0) : (stop - start) / (domain.length - 1 + padding);
      range = steps(start + step * padding / 2, step);
      rangeBand = 0;
      ranger = {
        t: "rangePoints",
        a: arguments
      };
      return scale;
    };
    scale.rangeRoundPoints = function(x, padding) {
      if (arguments.length < 2) padding = 0;
      var start = x[0], stop = x[1], step = domain.length < 2 ? (start = stop = Math.round((start + stop) / 2), 
      0) : (stop - start) / (domain.length - 1 + padding) | 0;
      range = steps(start + Math.round(step * padding / 2 + (stop - start - (domain.length - 1 + padding) * step) / 2), step);
      rangeBand = 0;
      ranger = {
        t: "rangeRoundPoints",
        a: arguments
      };
      return scale;
    };
    scale.rangeBands = function(x, padding, outerPadding) {
      if (arguments.length < 2) padding = 0;
      if (arguments.length < 3) outerPadding = padding;
      var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding);
      range = steps(start + step * outerPadding, step);
      if (reverse) range.reverse();
      rangeBand = step * (1 - padding);
      ranger = {
        t: "rangeBands",
        a: arguments
      };
      return scale;
    };
    scale.rangeRoundBands = function(x, padding, outerPadding) {
      if (arguments.length < 2) padding = 0;
      if (arguments.length < 3) outerPadding = padding;
      var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding));
      range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step);
      if (reverse) range.reverse();
      rangeBand = Math.round(step * (1 - padding));
      ranger = {
        t: "rangeRoundBands",
        a: arguments
      };
      return scale;
    };
    scale.rangeBand = function() {
      return rangeBand;
    };
    scale.rangeExtent = function() {
      return d3_scaleExtent(ranger.a[0]);
    };
    scale.copy = function() {
      return d3_scale_ordinal(domain, ranger);
    };
    return scale.domain(domain);
  }
  d3.scale.category10 = function() {
    return d3.scale.ordinal().range(d3_category10);
  };
  d3.scale.category20 = function() {
    return d3.scale.ordinal().range(d3_category20);
  };
  d3.scale.category20b = function() {
    return d3.scale.ordinal().range(d3_category20b);
  };
  d3.scale.category20c = function() {
    return d3.scale.ordinal().range(d3_category20c);
  };
  var d3_category10 = [ 2062260, 16744206, 2924588, 14034728, 9725885, 9197131, 14907330, 8355711, 12369186, 1556175 ].map(d3_rgbString);
  var d3_category20 = [ 2062260, 11454440, 16744206, 16759672, 2924588, 10018698, 14034728, 16750742, 9725885, 12955861, 9197131, 12885140, 14907330, 16234194, 8355711, 13092807, 12369186, 14408589, 1556175, 10410725 ].map(d3_rgbString);
  var d3_category20b = [ 3750777, 5395619, 7040719, 10264286, 6519097, 9216594, 11915115, 13556636, 9202993, 12426809, 15186514, 15190932, 8666169, 11356490, 14049643, 15177372, 8077683, 10834324, 13528509, 14589654 ].map(d3_rgbString);
  var d3_category20c = [ 3244733, 7057110, 10406625, 13032431, 15095053, 16616764, 16625259, 16634018, 3253076, 7652470, 10607003, 13101504, 7695281, 10394312, 12369372, 14342891, 6513507, 9868950, 12434877, 14277081 ].map(d3_rgbString);
  d3.scale.quantile = function() {
    return d3_scale_quantile([], []);
  };
  function d3_scale_quantile(domain, range) {
    var thresholds;
    function rescale() {
      var k = 0, q = range.length;
      thresholds = [];
      while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q);
      return scale;
    }
    function scale(x) {
      if (!isNaN(x = +x)) return range[d3.bisect(thresholds, x)];
    }
    scale.domain = function(x) {
      if (!arguments.length) return domain;
      domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending);
      return rescale();
    };
    scale.range = function(x) {
      if (!arguments.length) return range;
      range = x;
      return rescale();
    };
    scale.quantiles = function() {
      return thresholds;
    };
    scale.invertExtent = function(y) {
      y = range.indexOf(y);
      return y < 0 ? [ NaN, NaN ] : [ y > 0 ? thresholds[y - 1] : domain[0], y < thresholds.length ? thresholds[y] : domain[domain.length - 1] ];
    };
    scale.copy = function() {
      return d3_scale_quantile(domain, range);
    };
    return rescale();
  }
  d3.scale.quantize = function() {
    return d3_scale_quantize(0, 1, [ 0, 1 ]);
  };
  function d3_scale_quantize(x0, x1, range) {
    var kx, i;
    function scale(x) {
      return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))];
    }
    function rescale() {
      kx = range.length / (x1 - x0);
      i = range.length - 1;
      return scale;
    }
    scale.domain = function(x) {
      if (!arguments.length) return [ x0, x1 ];
      x0 = +x[0];
      x1 = +x[x.length - 1];
      return rescale();
    };
    scale.range = function(x) {
      if (!arguments.length) return range;
      range = x;
      return rescale();
    };
    scale.invertExtent = function(y) {
      y = range.indexOf(y);
      y = y < 0 ? NaN : y / kx + x0;
      return [ y, y + 1 / kx ];
    };
    scale.copy = function() {
      return d3_scale_quantize(x0, x1, range);
    };
    return rescale();
  }
  d3.scale.threshold = function() {
    return d3_scale_threshold([ .5 ], [ 0, 1 ]);
  };
  function d3_scale_threshold(domain, range) {
    function scale(x) {
      if (x <= x) return range[d3.bisect(domain, x)];
    }
    scale.domain = function(_) {
      if (!arguments.length) return domain;
      domain = _;
      return scale;
    };
    scale.range = function(_) {
      if (!arguments.length) return range;
      range = _;
      return scale;
    };
    scale.invertExtent = function(y) {
      y = range.indexOf(y);
      return [ domain[y - 1], domain[y] ];
    };
    scale.copy = function() {
      return d3_scale_threshold(domain, range);
    };
    return scale;
  }
  d3.scale.identity = function() {
    return d3_scale_identity([ 0, 1 ]);
  };
  function d3_scale_identity(domain) {
    function identity(x) {
      return +x;
    }
    identity.invert = identity;
    identity.domain = identity.range = function(x) {
      if (!arguments.length) return domain;
      domain = x.map(identity);
      return identity;
    };
    identity.ticks = function(m) {
      return d3_scale_linearTicks(domain, m);
    };
    identity.tickFormat = function(m, format) {
      return d3_scale_linearTickFormat(domain, m, format);
    };
    identity.copy = function() {
      return d3_scale_identity(domain);
    };
    return identity;
  }
  d3.svg = {};
  function d3_zero() {
    return 0;
  }
  d3.svg.arc = function() {
    var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, cornerRadius = d3_zero, padRadius = d3_svg_arcAuto, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle, padAngle = d3_svg_arcPadAngle;
    function arc() {
      var r0 = Math.max(0, +innerRadius.apply(this, arguments)), r1 = Math.max(0, +outerRadius.apply(this, arguments)), a0 = startAngle.apply(this, arguments) - halfπ, a1 = endAngle.apply(this, arguments) - halfπ, da = Math.abs(a1 - a0), cw = a0 > a1 ? 0 : 1;
      if (r1 < r0) rc = r1, r1 = r0, r0 = rc;
      if (da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, 1 - cw) : "") + "Z";
      var rc, cr, rp, ap, p0 = 0, p1 = 0, x0, y0, x1, y1, x2, y2, x3, y3, path = [];
      if (ap = (+padAngle.apply(this, arguments) || 0) / 2) {
        rp = padRadius === d3_svg_arcAuto ? Math.sqrt(r0 * r0 + r1 * r1) : +padRadius.apply(this, arguments);
        if (!cw) p1 *= -1;
        if (r1) p1 = d3_asin(rp / r1 * Math.sin(ap));
        if (r0) p0 = d3_asin(rp / r0 * Math.sin(ap));
      }
      if (r1) {
        x0 = r1 * Math.cos(a0 + p1);
        y0 = r1 * Math.sin(a0 + p1);
        x1 = r1 * Math.cos(a1 - p1);
        y1 = r1 * Math.sin(a1 - p1);
        var l1 = Math.abs(a1 - a0 - 2 * p1) <= π ? 0 : 1;
        if (p1 && d3_svg_arcSweep(x0, y0, x1, y1) === cw ^ l1) {
          var h1 = (a0 + a1) / 2;
          x0 = r1 * Math.cos(h1);
          y0 = r1 * Math.sin(h1);
          x1 = y1 = null;
        }
      } else {
        x0 = y0 = 0;
      }
      if (r0) {
        x2 = r0 * Math.cos(a1 - p0);
        y2 = r0 * Math.sin(a1 - p0);
        x3 = r0 * Math.cos(a0 + p0);
        y3 = r0 * Math.sin(a0 + p0);
        var l0 = Math.abs(a0 - a1 + 2 * p0) <= π ? 0 : 1;
        if (p0 && d3_svg_arcSweep(x2, y2, x3, y3) === 1 - cw ^ l0) {
          var h0 = (a0 + a1) / 2;
          x2 = r0 * Math.cos(h0);
          y2 = r0 * Math.sin(h0);
          x3 = y3 = null;
        }
      } else {
        x2 = y2 = 0;
      }
      if (da > ε && (rc = Math.min(Math.abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments))) > .001) {
        cr = r0 < r1 ^ cw ? 0 : 1;
        var rc1 = rc, rc0 = rc;
        if (da < π) {
          var oc = x3 == null ? [ x2, y2 ] : x1 == null ? [ x0, y0 ] : d3_geom_polygonIntersect([ x0, y0 ], [ x3, y3 ], [ x1, y1 ], [ x2, y2 ]), ax = x0 - oc[0], ay = y0 - oc[1], bx = x1 - oc[0], by = y1 - oc[1], kc = 1 / Math.sin(Math.acos((ax * bx + ay * by) / (Math.sqrt(ax * ax + ay * ay) * Math.sqrt(bx * bx + by * by))) / 2), lc = Math.sqrt(oc[0] * oc[0] + oc[1] * oc[1]);
          rc0 = Math.min(rc, (r0 - lc) / (kc - 1));
          rc1 = Math.min(rc, (r1 - lc) / (kc + 1));
        }
        if (x1 != null) {
          var t30 = d3_svg_arcCornerTangents(x3 == null ? [ x2, y2 ] : [ x3, y3 ], [ x0, y0 ], r1, rc1, cw), t12 = d3_svg_arcCornerTangents([ x1, y1 ], [ x2, y2 ], r1, rc1, cw);
          if (rc === rc1) {
            path.push("M", t30[0], "A", rc1, ",", rc1, " 0 0,", cr, " ", t30[1], "A", r1, ",", r1, " 0 ", 1 - cw ^ d3_svg_arcSweep(t30[1][0], t30[1][1], t12[1][0], t12[1][1]), ",", cw, " ", t12[1], "A", rc1, ",", rc1, " 0 0,", cr, " ", t12[0]);
          } else {
            path.push("M", t30[0], "A", rc1, ",", rc1, " 0 1,", cr, " ", t12[0]);
          }
        } else {
          path.push("M", x0, ",", y0);
        }
        if (x3 != null) {
          var t03 = d3_svg_arcCornerTangents([ x0, y0 ], [ x3, y3 ], r0, -rc0, cw), t21 = d3_svg_arcCornerTangents([ x2, y2 ], x1 == null ? [ x0, y0 ] : [ x1, y1 ], r0, -rc0, cw);
          if (rc === rc0) {
            path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t21[1], "A", r0, ",", r0, " 0 ", cw ^ d3_svg_arcSweep(t21[1][0], t21[1][1], t03[1][0], t03[1][1]), ",", 1 - cw, " ", t03[1], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]);
          } else {
            path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]);
          }
        } else {
          path.push("L", x2, ",", y2);
        }
      } else {
        path.push("M", x0, ",", y0);
        if (x1 != null) path.push("A", r1, ",", r1, " 0 ", l1, ",", cw, " ", x1, ",", y1);
        path.push("L", x2, ",", y2);
        if (x3 != null) path.push("A", r0, ",", r0, " 0 ", l0, ",", 1 - cw, " ", x3, ",", y3);
      }
      path.push("Z");
      return path.join("");
    }
    function circleSegment(r1, cw) {
      return "M0," + r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + -r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + r1;
    }
    arc.innerRadius = function(v) {
      if (!arguments.length) return innerRadius;
      innerRadius = d3_functor(v);
      return arc;
    };
    arc.outerRadius = function(v) {
      if (!arguments.length) return outerRadius;
      outerRadius = d3_functor(v);
      return arc;
    };
    arc.cornerRadius = function(v) {
      if (!arguments.length) return cornerRadius;
      cornerRadius = d3_functor(v);
      return arc;
    };
    arc.padRadius = function(v) {
      if (!arguments.length) return padRadius;
      padRadius = v == d3_svg_arcAuto ? d3_svg_arcAuto : d3_functor(v);
      return arc;
    };
    arc.startAngle = function(v) {
      if (!arguments.length) return startAngle;
      startAngle = d3_functor(v);
      return arc;
    };
    arc.endAngle = function(v) {
      if (!arguments.length) return endAngle;
      endAngle = d3_functor(v);
      return arc;
    };
    arc.padAngle = function(v) {
      if (!arguments.length) return padAngle;
      padAngle = d3_functor(v);
      return arc;
    };
    arc.centroid = function() {
      var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - halfπ;
      return [ Math.cos(a) * r, Math.sin(a) * r ];
    };
    return arc;
  };
  var d3_svg_arcAuto = "auto";
  function d3_svg_arcInnerRadius(d) {
    return d.innerRadius;
  }
  function d3_svg_arcOuterRadius(d) {
    return d.outerRadius;
  }
  function d3_svg_arcStartAngle(d) {
    return d.startAngle;
  }
  function d3_svg_arcEndAngle(d) {
    return d.endAngle;
  }
  function d3_svg_arcPadAngle(d) {
    return d && d.padAngle;
  }
  function d3_svg_arcSweep(x0, y0, x1, y1) {
    return (x0 - x1) * y0 - (y0 - y1) * x0 > 0 ? 0 : 1;
  }
  function d3_svg_arcCornerTangents(p0, p1, r1, rc, cw) {
    var x01 = p0[0] - p1[0], y01 = p0[1] - p1[1], lo = (cw ? rc : -rc) / Math.sqrt(x01 * x01 + y01 * y01), ox = lo * y01, oy = -lo * x01, x1 = p0[0] + ox, y1 = p0[1] + oy, x2 = p1[0] + ox, y2 = p1[1] + oy, x3 = (x1 + x2) / 2, y3 = (y1 + y2) / 2, dx = x2 - x1, dy = y2 - y1, d2 = dx * dx + dy * dy, r = r1 - rc, D = x1 * y2 - x2 * y1, d = (dy < 0 ? -1 : 1) * Math.sqrt(Math.max(0, r * r * d2 - D * D)), cx0 = (D * dy - dx * d) / d2, cy0 = (-D * dx - dy * d) / d2, cx1 = (D * dy + dx * d) / d2, cy1 = (-D * dx + dy * d) / d2, dx0 = cx0 - x3, dy0 = cy0 - y3, dx1 = cx1 - x3, dy1 = cy1 - y3;
    if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1;
    return [ [ cx0 - ox, cy0 - oy ], [ cx0 * r1 / r, cy0 * r1 / r ] ];
  }
  function d3_svg_line(projection) {
    var x = d3_geom_pointX, y = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7;
    function line(data) {
      var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y);
      function segment() {
        segments.push("M", interpolate(projection(points), tension));
      }
      while (++i < n) {
        if (defined.call(this, d = data[i], i)) {
          points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]);
        } else if (points.length) {
          segment();
          points = [];
        }
      }
      if (points.length) segment();
      return segments.length ? segments.join("") : null;
    }
    line.x = function(_) {
      if (!arguments.length) return x;
      x = _;
      return line;
    };
    line.y = function(_) {
      if (!arguments.length) return y;
      y = _;
      return line;
    };
    line.defined = function(_) {
      if (!arguments.length) return defined;
      defined = _;
      return line;
    };
    line.interpolate = function(_) {
      if (!arguments.length) return interpolateKey;
      if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
      return line;
    };
    line.tension = function(_) {
      if (!arguments.length) return tension;
      tension = _;
      return line;
    };
    return line;
  }
  d3.svg.line = function() {
    return d3_svg_line(d3_identity);
  };
  var d3_svg_lineInterpolators = d3.map({
    linear: d3_svg_lineLinear,
    "linear-closed": d3_svg_lineLinearClosed,
    step: d3_svg_lineStep,
    "step-before": d3_svg_lineStepBefore,
    "step-after": d3_svg_lineStepAfter,
    basis: d3_svg_lineBasis,
    "basis-open": d3_svg_lineBasisOpen,
    "basis-closed": d3_svg_lineBasisClosed,
    bundle: d3_svg_lineBundle,
    cardinal: d3_svg_lineCardinal,
    "cardinal-open": d3_svg_lineCardinalOpen,
    "cardinal-closed": d3_svg_lineCardinalClosed,
    monotone: d3_svg_lineMonotone
  });
  d3_svg_lineInterpolators.forEach(function(key, value) {
    value.key = key;
    value.closed = /-closed$/.test(key);
  });
  function d3_svg_lineLinear(points) {
    return points.length > 1 ? points.join("L") : points + "Z";
  }
  function d3_svg_lineLinearClosed(points) {
    return points.join("L") + "Z";
  }
  function d3_svg_lineStep(points) {
    var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
    while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]);
    if (n > 1) path.push("H", p[0]);
    return path.join("");
  }
  function d3_svg_lineStepBefore(points) {
    var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
    while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]);
    return path.join("");
  }
  function d3_svg_lineStepAfter(points) {
    var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
    while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]);
    return path.join("");
  }
  function d3_svg_lineCardinalOpen(points, tension) {
    return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, -1), d3_svg_lineCardinalTangents(points, tension));
  }
  function d3_svg_lineCardinalClosed(points, tension) {
    return points.length < 3 ? d3_svg_lineLinearClosed(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), 
    points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension));
  }
  function d3_svg_lineCardinal(points, tension) {
    return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension));
  }
  function d3_svg_lineHermite(points, tangents) {
    if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) {
      return d3_svg_lineLinear(points);
    }
    var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1;
    if (quad) {
      path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1];
      p0 = points[1];
      pi = 2;
    }
    if (tangents.length > 1) {
      t = tangents[1];
      p = points[pi];
      pi++;
      path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
      for (var i = 2; i < tangents.length; i++, pi++) {
        p = points[pi];
        t = tangents[i];
        path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
      }
    }
    if (quad) {
      var lp = points[pi];
      path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1];
    }
    return path;
  }
  function d3_svg_lineCardinalTangents(points, tension) {
    var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length;
    while (++i < n) {
      p0 = p1;
      p1 = p2;
      p2 = points[i];
      tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]);
    }
    return tangents;
  }
  function d3_svg_lineBasis(points) {
    if (points.length < 3) return d3_svg_lineLinear(points);
    var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0, "L", d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
    points.push(points[n - 1]);
    while (++i <= n) {
      pi = points[i];
      px.shift();
      px.push(pi[0]);
      py.shift();
      py.push(pi[1]);
      d3_svg_lineBasisBezier(path, px, py);
    }
    points.pop();
    path.push("L", pi);
    return path.join("");
  }
  function d3_svg_lineBasisOpen(points) {
    if (points.length < 4) return d3_svg_lineLinear(points);
    var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ];
    while (++i < 3) {
      pi = points[i];
      px.push(pi[0]);
      py.push(pi[1]);
    }
    path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py));
    --i;
    while (++i < n) {
      pi = points[i];
      px.shift();
      px.push(pi[0]);
      py.shift();
      py.push(pi[1]);
      d3_svg_lineBasisBezier(path, px, py);
    }
    return path.join("");
  }
  function d3_svg_lineBasisClosed(points) {
    var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = [];
    while (++i < 4) {
      pi = points[i % n];
      px.push(pi[0]);
      py.push(pi[1]);
    }
    path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
    --i;
    while (++i < m) {
      pi = points[i % n];
      px.shift();
      px.push(pi[0]);
      py.shift();
      py.push(pi[1]);
      d3_svg_lineBasisBezier(path, px, py);
    }
    return path.join("");
  }
  function d3_svg_lineBundle(points, tension) {
    var n = points.length - 1;
    if (n) {
      var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t;
      while (++i <= n) {
        p = points[i];
        t = i / n;
        p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx);
        p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy);
      }
    }
    return d3_svg_lineBasis(points);
  }
  function d3_svg_lineDot4(a, b) {
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
  }
  var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ];
  function d3_svg_lineBasisBezier(path, x, y) {
    path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y));
  }
  function d3_svg_lineSlope(p0, p1) {
    return (p1[1] - p0[1]) / (p1[0] - p0[0]);
  }
  function d3_svg_lineFiniteDifferences(points) {
    var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1);
    while (++i < j) {
      m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2;
    }
    m[i] = d;
    return m;
  }
  function d3_svg_lineMonotoneTangents(points) {
    var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1;
    while (++i < j) {
      d = d3_svg_lineSlope(points[i], points[i + 1]);
      if (abs(d) < ε) {
        m[i] = m[i + 1] = 0;
      } else {
        a = m[i] / d;
        b = m[i + 1] / d;
        s = a * a + b * b;
        if (s > 9) {
          s = d * 3 / Math.sqrt(s);
          m[i] = s * a;
          m[i + 1] = s * b;
        }
      }
    }
    i = -1;
    while (++i <= j) {
      s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i]));
      tangents.push([ s || 0, m[i] * s || 0 ]);
    }
    return tangents;
  }
  function d3_svg_lineMonotone(points) {
    return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points));
  }
  d3.svg.line.radial = function() {
    var line = d3_svg_line(d3_svg_lineRadial);
    line.radius = line.x, delete line.x;
    line.angle = line.y, delete line.y;
    return line;
  };
  function d3_svg_lineRadial(points) {
    var point, i = -1, n = points.length, r, a;
    while (++i < n) {
      point = points[i];
      r = point[0];
      a = point[1] - halfπ;
      point[0] = r * Math.cos(a);
      point[1] = r * Math.sin(a);
    }
    return points;
  }
  function d3_svg_area(projection) {
    var x0 = d3_geom_pointX, x1 = d3_geom_pointX, y0 = 0, y1 = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7;
    function area(data) {
      var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() {
        return x;
      } : d3_functor(x1), fy1 = y0 === y1 ? function() {
        return y;
      } : d3_functor(y1), x, y;
      function segment() {
        segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z");
      }
      while (++i < n) {
        if (defined.call(this, d = data[i], i)) {
          points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]);
          points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]);
        } else if (points0.length) {
          segment();
          points0 = [];
          points1 = [];
        }
      }
      if (points0.length) segment();
      return segments.length ? segments.join("") : null;
    }
    area.x = function(_) {
      if (!arguments.length) return x1;
      x0 = x1 = _;
      return area;
    };
    area.x0 = function(_) {
      if (!arguments.length) return x0;
      x0 = _;
      return area;
    };
    area.x1 = function(_) {
      if (!arguments.length) return x1;
      x1 = _;
      return area;
    };
    area.y = function(_) {
      if (!arguments.length) return y1;
      y0 = y1 = _;
      return area;
    };
    area.y0 = function(_) {
      if (!arguments.length) return y0;
      y0 = _;
      return area;
    };
    area.y1 = function(_) {
      if (!arguments.length) return y1;
      y1 = _;
      return area;
    };
    area.defined = function(_) {
      if (!arguments.length) return defined;
      defined = _;
      return area;
    };
    area.interpolate = function(_) {
      if (!arguments.length) return interpolateKey;
      if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
      interpolateReverse = interpolate.reverse || interpolate;
      L = interpolate.closed ? "M" : "L";
      return area;
    };
    area.tension = function(_) {
      if (!arguments.length) return tension;
      tension = _;
      return area;
    };
    return area;
  }
  d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter;
  d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore;
  d3.svg.area = function() {
    return d3_svg_area(d3_identity);
  };
  d3.svg.area.radial = function() {
    var area = d3_svg_area(d3_svg_lineRadial);
    area.radius = area.x, delete area.x;
    area.innerRadius = area.x0, delete area.x0;
    area.outerRadius = area.x1, delete area.x1;
    area.angle = area.y, delete area.y;
    area.startAngle = area.y0, delete area.y0;
    area.endAngle = area.y1, delete area.y1;
    return area;
  };
  d3.svg.chord = function() {
    var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle;
    function chord(d, i) {
      var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i);
      return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z";
    }
    function subgroup(self, f, d, i) {
      var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) - halfπ, a1 = endAngle.call(self, subgroup, i) - halfπ;
      return {
        r: r,
        a0: a0,
        a1: a1,
        p0: [ r * Math.cos(a0), r * Math.sin(a0) ],
        p1: [ r * Math.cos(a1), r * Math.sin(a1) ]
      };
    }
    function equals(a, b) {
      return a.a0 == b.a0 && a.a1 == b.a1;
    }
    function arc(r, p, a) {
      return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p;
    }
    function curve(r0, p0, r1, p1) {
      return "Q 0,0 " + p1;
    }
    chord.radius = function(v) {
      if (!arguments.length) return radius;
      radius = d3_functor(v);
      return chord;
    };
    chord.source = function(v) {
      if (!arguments.length) return source;
      source = d3_functor(v);
      return chord;
    };
    chord.target = function(v) {
      if (!arguments.length) return target;
      target = d3_functor(v);
      return chord;
    };
    chord.startAngle = function(v) {
      if (!arguments.length) return startAngle;
      startAngle = d3_functor(v);
      return chord;
    };
    chord.endAngle = function(v) {
      if (!arguments.length) return endAngle;
      endAngle = d3_functor(v);
      return chord;
    };
    return chord;
  };
  function d3_svg_chordRadius(d) {
    return d.radius;
  }
  d3.svg.diagonal = function() {
    var source = d3_source, target = d3_target, projection = d3_svg_diagonalProjection;
    function diagonal(d, i) {
      var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, {
        x: p0.x,
        y: m
      }, {
        x: p3.x,
        y: m
      }, p3 ];
      p = p.map(projection);
      return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3];
    }
    diagonal.source = function(x) {
      if (!arguments.length) return source;
      source = d3_functor(x);
      return diagonal;
    };
    diagonal.target = function(x) {
      if (!arguments.length) return target;
      target = d3_functor(x);
      return diagonal;
    };
    diagonal.projection = function(x) {
      if (!arguments.length) return projection;
      projection = x;
      return diagonal;
    };
    return diagonal;
  };
  function d3_svg_diagonalProjection(d) {
    return [ d.x, d.y ];
  }
  d3.svg.diagonal.radial = function() {
    var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection;
    diagonal.projection = function(x) {
      return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection;
    };
    return diagonal;
  };
  function d3_svg_diagonalRadialProjection(projection) {
    return function() {
      var d = projection.apply(this, arguments), r = d[0], a = d[1] - halfπ;
      return [ r * Math.cos(a), r * Math.sin(a) ];
    };
  }
  d3.svg.symbol = function() {
    var type = d3_svg_symbolType, size = d3_svg_symbolSize;
    function symbol(d, i) {
      return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i));
    }
    symbol.type = function(x) {
      if (!arguments.length) return type;
      type = d3_functor(x);
      return symbol;
    };
    symbol.size = function(x) {
      if (!arguments.length) return size;
      size = d3_functor(x);
      return symbol;
    };
    return symbol;
  };
  function d3_svg_symbolSize() {
    return 64;
  }
  function d3_svg_symbolType() {
    return "circle";
  }
  function d3_svg_symbolCircle(size) {
    var r = Math.sqrt(size / π);
    return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z";
  }
  var d3_svg_symbols = d3.map({
    circle: d3_svg_symbolCircle,
    cross: function(size) {
      var r = Math.sqrt(size / 5) / 2;
      return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z";
    },
    diamond: function(size) {
      var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30;
      return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z";
    },
    square: function(size) {
      var r = Math.sqrt(size) / 2;
      return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z";
    },
    "triangle-down": function(size) {
      var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
      return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z";
    },
    "triangle-up": function(size) {
      var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
      return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z";
    }
  });
  d3.svg.symbolTypes = d3_svg_symbols.keys();
  var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians);
  d3_selectionPrototype.transition = function(name) {
    var id = d3_transitionInheritId || ++d3_transitionId, ns = d3_transitionNamespace(name), subgroups = [], subgroup, node, transition = d3_transitionInherit || {
      time: Date.now(),
      ease: d3_ease_cubicInOut,
      delay: 0,
      duration: 250
    };
    for (var j = -1, m = this.length; ++j < m; ) {
      subgroups.push(subgroup = []);
      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) d3_transitionNode(node, i, ns, id, transition);
        subgroup.push(node);
      }
    }
    return d3_transition(subgroups, ns, id);
  };
  d3_selectionPrototype.interrupt = function(name) {
    return this.each(name == null ? d3_selection_interrupt : d3_selection_interruptNS(d3_transitionNamespace(name)));
  };
  var d3_selection_interrupt = d3_selection_interruptNS(d3_transitionNamespace());
  function d3_selection_interruptNS(ns) {
    return function() {
      var lock, activeId, active;
      if ((lock = this[ns]) && (active = lock[activeId = lock.active])) {
        active.timer.c = null;
        active.timer.t = NaN;
        if (--lock.count) delete lock[activeId]; else delete this[ns];
        lock.active += .5;
        active.event && active.event.interrupt.call(this, this.__data__, active.index);
      }
    };
  }
  function d3_transition(groups, ns, id) {
    d3_subclass(groups, d3_transitionPrototype);
    groups.namespace = ns;
    groups.id = id;
    return groups;
  }
  var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, d3_transitionInherit;
  d3_transitionPrototype.call = d3_selectionPrototype.call;
  d3_transitionPrototype.empty = d3_selectionPrototype.empty;
  d3_transitionPrototype.node = d3_selectionPrototype.node;
  d3_transitionPrototype.size = d3_selectionPrototype.size;
  d3.transition = function(selection, name) {
    return selection && selection.transition ? d3_transitionInheritId ? selection.transition(name) : selection : d3.selection().transition(selection);
  };
  d3.transition.prototype = d3_transitionPrototype;
  d3_transitionPrototype.select = function(selector) {
    var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnode, node;
    selector = d3_selection_selector(selector);
    for (var j = -1, m = this.length; ++j < m; ) {
      subgroups.push(subgroup = []);
      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
        if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) {
          if ("__data__" in node) subnode.__data__ = node.__data__;
          d3_transitionNode(subnode, i, ns, id, node[ns][id]);
          subgroup.push(subnode);
        } else {
          subgroup.push(null);
        }
      }
    }
    return d3_transition(subgroups, ns, id);
  };
  d3_transitionPrototype.selectAll = function(selector) {
    var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnodes, node, subnode, transition;
    selector = d3_selection_selectorAll(selector);
    for (var j = -1, m = this.length; ++j < m; ) {
      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) {
          transition = node[ns][id];
          subnodes = selector.call(node, node.__data__, i, j);
          subgroups.push(subgroup = []);
          for (var k = -1, o = subnodes.length; ++k < o; ) {
            if (subnode = subnodes[k]) d3_transitionNode(subnode, k, ns, id, transition);
            subgroup.push(subnode);
          }
        }
      }
    }
    return d3_transition(subgroups, ns, id);
  };
  d3_transitionPrototype.filter = function(filter) {
    var subgroups = [], subgroup, group, node;
    if (typeof filter !== "function") filter = d3_selection_filter(filter);
    for (var j = 0, m = this.length; j < m; j++) {
      subgroups.push(subgroup = []);
      for (var group = this[j], i = 0, n = group.length; i < n; i++) {
        if ((node = group[i]) && filter.call(node, node.__data__, i, j)) {
          subgroup.push(node);
        }
      }
    }
    return d3_transition(subgroups, this.namespace, this.id);
  };
  d3_transitionPrototype.tween = function(name, tween) {
    var id = this.id, ns = this.namespace;
    if (arguments.length < 2) return this.node()[ns][id].tween.get(name);
    return d3_selection_each(this, tween == null ? function(node) {
      node[ns][id].tween.remove(name);
    } : function(node) {
      node[ns][id].tween.set(name, tween);
    });
  };
  function d3_transition_tween(groups, name, value, tween) {
    var id = groups.id, ns = groups.namespace;
    return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) {
      node[ns][id].tween.set(name, tween(value.call(node, node.__data__, i, j)));
    } : (value = tween(value), function(node) {
      node[ns][id].tween.set(name, value);
    }));
  }
  d3_transitionPrototype.attr = function(nameNS, value) {
    if (arguments.length < 2) {
      for (value in nameNS) this.attr(value, nameNS[value]);
      return this;
    }
    var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS);
    function attrNull() {
      this.removeAttribute(name);
    }
    function attrNullNS() {
      this.removeAttributeNS(name.space, name.local);
    }
    function attrTween(b) {
      return b == null ? attrNull : (b += "", function() {
        var a = this.getAttribute(name), i;
        return a !== b && (i = interpolate(a, b), function(t) {
          this.setAttribute(name, i(t));
        });
      });
    }
    function attrTweenNS(b) {
      return b == null ? attrNullNS : (b += "", function() {
        var a = this.getAttributeNS(name.space, name.local), i;
        return a !== b && (i = interpolate(a, b), function(t) {
          this.setAttributeNS(name.space, name.local, i(t));
        });
      });
    }
    return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween);
  };
  d3_transitionPrototype.attrTween = function(nameNS, tween) {
    var name = d3.ns.qualify(nameNS);
    function attrTween(d, i) {
      var f = tween.call(this, d, i, this.getAttribute(name));
      return f && function(t) {
        this.setAttribute(name, f(t));
      };
    }
    function attrTweenNS(d, i) {
      var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
      return f && function(t) {
        this.setAttributeNS(name.space, name.local, f(t));
      };
    }
    return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween);
  };
  d3_transitionPrototype.style = function(name, value, priority) {
    var n = arguments.length;
    if (n < 3) {
      if (typeof name !== "string") {
        if (n < 2) value = "";
        for (priority in name) this.style(priority, name[priority], value);
        return this;
      }
      priority = "";
    }
    function styleNull() {
      this.style.removeProperty(name);
    }
    function styleString(b) {
      return b == null ? styleNull : (b += "", function() {
        var a = d3_window(this).getComputedStyle(this, null).getPropertyValue(name), i;
        return a !== b && (i = d3_interpolate(a, b), function(t) {
          this.style.setProperty(name, i(t), priority);
        });
      });
    }
    return d3_transition_tween(this, "style." + name, value, styleString);
  };
  d3_transitionPrototype.styleTween = function(name, tween, priority) {
    if (arguments.length < 3) priority = "";
    function styleTween(d, i) {
      var f = tween.call(this, d, i, d3_window(this).getComputedStyle(this, null).getPropertyValue(name));
      return f && function(t) {
        this.style.setProperty(name, f(t), priority);
      };
    }
    return this.tween("style." + name, styleTween);
  };
  d3_transitionPrototype.text = function(value) {
    return d3_transition_tween(this, "text", value, d3_transition_text);
  };
  function d3_transition_text(b) {
    if (b == null) b = "";
    return function() {
      this.textContent = b;
    };
  }
  d3_transitionPrototype.remove = function() {
    var ns = this.namespace;
    return this.each("end.transition", function() {
      var p;
      if (this[ns].count < 2 && (p = this.parentNode)) p.removeChild(this);
    });
  };
  d3_transitionPrototype.ease = function(value) {
    var id = this.id, ns = this.namespace;
    if (arguments.length < 1) return this.node()[ns][id].ease;
    if (typeof value !== "function") value = d3.ease.apply(d3, arguments);
    return d3_selection_each(this, function(node) {
      node[ns][id].ease = value;
    });
  };
  d3_transitionPrototype.delay = function(value) {
    var id = this.id, ns = this.namespace;
    if (arguments.length < 1) return this.node()[ns][id].delay;
    return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
      node[ns][id].delay = +value.call(node, node.__data__, i, j);
    } : (value = +value, function(node) {
      node[ns][id].delay = value;
    }));
  };
  d3_transitionPrototype.duration = function(value) {
    var id = this.id, ns = this.namespace;
    if (arguments.length < 1) return this.node()[ns][id].duration;
    return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
      node[ns][id].duration = Math.max(1, value.call(node, node.__data__, i, j));
    } : (value = Math.max(1, value), function(node) {
      node[ns][id].duration = value;
    }));
  };
  d3_transitionPrototype.each = function(type, listener) {
    var id = this.id, ns = this.namespace;
    if (arguments.length < 2) {
      var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId;
      try {
        d3_transitionInheritId = id;
        d3_selection_each(this, function(node, i, j) {
          d3_transitionInherit = node[ns][id];
          type.call(node, node.__data__, i, j);
        });
      } finally {
        d3_transitionInherit = inherit;
        d3_transitionInheritId = inheritId;
      }
    } else {
      d3_selection_each(this, function(node) {
        var transition = node[ns][id];
        (transition.event || (transition.event = d3.dispatch("start", "end", "interrupt"))).on(type, listener);
      });
    }
    return this;
  };
  d3_transitionPrototype.transition = function() {
    var id0 = this.id, id1 = ++d3_transitionId, ns = this.namespace, subgroups = [], subgroup, group, node, transition;
    for (var j = 0, m = this.length; j < m; j++) {
      subgroups.push(subgroup = []);
      for (var group = this[j], i = 0, n = group.length; i < n; i++) {
        if (node = group[i]) {
          transition = node[ns][id0];
          d3_transitionNode(node, i, ns, id1, {
            time: transition.time,
            ease: transition.ease,
            delay: transition.delay + transition.duration,
            duration: transition.duration
          });
        }
        subgroup.push(node);
      }
    }
    return d3_transition(subgroups, ns, id1);
  };
  function d3_transitionNamespace(name) {
    return name == null ? "__transition__" : "__transition_" + name + "__";
  }
  function d3_transitionNode(node, i, ns, id, inherit) {
    var lock = node[ns] || (node[ns] = {
      active: 0,
      count: 0
    }), transition = lock[id], time, timer, duration, ease, tweens;
    function schedule(elapsed) {
      var delay = transition.delay;
      timer.t = delay + time;
      if (delay <= elapsed) return start(elapsed - delay);
      timer.c = start;
    }
    function start(elapsed) {
      var activeId = lock.active, active = lock[activeId];
      if (active) {
        active.timer.c = null;
        active.timer.t = NaN;
        --lock.count;
        delete lock[activeId];
        active.event && active.event.interrupt.call(node, node.__data__, active.index);
      }
      for (var cancelId in lock) {
        if (+cancelId < id) {
          var cancel = lock[cancelId];
          cancel.timer.c = null;
          cancel.timer.t = NaN;
          --lock.count;
          delete lock[cancelId];
        }
      }
      timer.c = tick;
      d3_timer(function() {
        if (timer.c && tick(elapsed || 1)) {
          timer.c = null;
          timer.t = NaN;
        }
        return 1;
      }, 0, time);
      lock.active = id;
      transition.event && transition.event.start.call(node, node.__data__, i);
      tweens = [];
      transition.tween.forEach(function(key, value) {
        if (value = value.call(node, node.__data__, i)) {
          tweens.push(value);
        }
      });
      ease = transition.ease;
      duration = transition.duration;
    }
    function tick(elapsed) {
      var t = elapsed / duration, e = ease(t), n = tweens.length;
      while (n > 0) {
        tweens[--n].call(node, e);
      }
      if (t >= 1) {
        transition.event && transition.event.end.call(node, node.__data__, i);
        if (--lock.count) delete lock[id]; else delete node[ns];
        return 1;
      }
    }
    if (!transition) {
      time = inherit.time;
      timer = d3_timer(schedule, 0, time);
      transition = lock[id] = {
        tween: new d3_Map(),
        time: time,
        timer: timer,
        delay: inherit.delay,
        duration: inherit.duration,
        ease: inherit.ease,
        index: i
      };
      inherit = null;
      ++lock.count;
    }
  }
  d3.svg.axis = function() {
    var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, innerTickSize = 6, outerTickSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_;
    function axis(g) {
      g.each(function() {
        var g = d3.select(this);
        var scale0 = this.__chart__ || scale, scale1 = this.__chart__ = scale.copy();
        var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick.order()).style("opacity", 1), tickSpacing = Math.max(innerTickSize, 0) + tickPadding, tickTransform;
        var range = d3_scaleRange(scale1), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), 
        d3.transition(path));
        tickEnter.append("line");
        tickEnter.append("text");
        var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"), sign = orient === "top" || orient === "left" ? -1 : 1, x1, x2, y1, y2;
        if (orient === "bottom" || orient === "top") {
          tickTransform = d3_svg_axisX, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2";
          text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle");
          pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize);
        } else {
          tickTransform = d3_svg_axisY, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2";
          text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start");
          pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize);
        }
        lineEnter.attr(y2, sign * innerTickSize);
        textEnter.attr(y1, sign * tickSpacing);
        lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize);
        textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing);
        if (scale1.rangeBand) {
          var x = scale1, dx = x.rangeBand() / 2;
          scale0 = scale1 = function(d) {
            return x(d) + dx;
          };
        } else if (scale0.rangeBand) {
          scale0 = scale1;
        } else {
          tickExit.call(tickTransform, scale1, scale0);
        }
        tickEnter.call(tickTransform, scale0, scale1);
        tickUpdate.call(tickTransform, scale1, scale1);
      });
    }
    axis.scale = function(x) {
      if (!arguments.length) return scale;
      scale = x;
      return axis;
    };
    axis.orient = function(x) {
      if (!arguments.length) return orient;
      orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient;
      return axis;
    };
    axis.ticks = function() {
      if (!arguments.length) return tickArguments_;
      tickArguments_ = d3_array(arguments);
      return axis;
    };
    axis.tickValues = function(x) {
      if (!arguments.length) return tickValues;
      tickValues = x;
      return axis;
    };
    axis.tickFormat = function(x) {
      if (!arguments.length) return tickFormat_;
      tickFormat_ = x;
      return axis;
    };
    axis.tickSize = function(x) {
      var n = arguments.length;
      if (!n) return innerTickSize;
      innerTickSize = +x;
      outerTickSize = +arguments[n - 1];
      return axis;
    };
    axis.innerTickSize = function(x) {
      if (!arguments.length) return innerTickSize;
      innerTickSize = +x;
      return axis;
    };
    axis.outerTickSize = function(x) {
      if (!arguments.length) return outerTickSize;
      outerTickSize = +x;
      return axis;
    };
    axis.tickPadding = function(x) {
      if (!arguments.length) return tickPadding;
      tickPadding = +x;
      return axis;
    };
    axis.tickSubdivide = function() {
      return arguments.length && axis;
    };
    return axis;
  };
  var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = {
    top: 1,
    right: 1,
    bottom: 1,
    left: 1
  };
  function d3_svg_axisX(selection, x0, x1) {
    selection.attr("transform", function(d) {
      var v0 = x0(d);
      return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)";
    });
  }
  function d3_svg_axisY(selection, y0, y1) {
    selection.attr("transform", function(d) {
      var v0 = y0(d);
      return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")";
    });
  }
  d3.svg.brush = function() {
    var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, xExtent = [ 0, 0 ], yExtent = [ 0, 0 ], xExtentDomain, yExtentDomain, xClamp = true, yClamp = true, resizes = d3_svg_brushResizes[0];
    function brush(g) {
      g.each(function() {
        var g = d3.select(this).style("pointer-events", "all").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart);
        var background = g.selectAll(".background").data([ 0 ]);
        background.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair");
        g.selectAll(".extent").data([ 0 ]).enter().append("rect").attr("class", "extent").style("cursor", "move");
        var resize = g.selectAll(".resize").data(resizes, d3_identity);
        resize.exit().remove();
        resize.enter().append("g").attr("class", function(d) {
          return "resize " + d;
        }).style("cursor", function(d) {
          return d3_svg_brushCursor[d];
        }).append("rect").attr("x", function(d) {
          return /[ew]$/.test(d) ? -3 : null;
        }).attr("y", function(d) {
          return /^[ns]/.test(d) ? -3 : null;
        }).attr("width", 6).attr("height", 6).style("visibility", "hidden");
        resize.style("display", brush.empty() ? "none" : null);
        var gUpdate = d3.transition(g), backgroundUpdate = d3.transition(background), range;
        if (x) {
          range = d3_scaleRange(x);
          backgroundUpdate.attr("x", range[0]).attr("width", range[1] - range[0]);
          redrawX(gUpdate);
        }
        if (y) {
          range = d3_scaleRange(y);
          backgroundUpdate.attr("y", range[0]).attr("height", range[1] - range[0]);
          redrawY(gUpdate);
        }
        redraw(gUpdate);
      });
    }
    brush.event = function(g) {
      g.each(function() {
        var event_ = event.of(this, arguments), extent1 = {
          x: xExtent,
          y: yExtent,
          i: xExtentDomain,
          j: yExtentDomain
        }, extent0 = this.__chart__ || extent1;
        this.__chart__ = extent1;
        if (d3_transitionInheritId) {
          d3.select(this).transition().each("start.brush", function() {
            xExtentDomain = extent0.i;
            yExtentDomain = extent0.j;
            xExtent = extent0.x;
            yExtent = extent0.y;
            event_({
              type: "brushstart"
            });
          }).tween("brush:brush", function() {
            var xi = d3_interpolateArray(xExtent, extent1.x), yi = d3_interpolateArray(yExtent, extent1.y);
            xExtentDomain = yExtentDomain = null;
            return function(t) {
              xExtent = extent1.x = xi(t);
              yExtent = extent1.y = yi(t);
              event_({
                type: "brush",
                mode: "resize"
              });
            };
          }).each("end.brush", function() {
            xExtentDomain = extent1.i;
            yExtentDomain = extent1.j;
            event_({
              type: "brush",
              mode: "resize"
            });
            event_({
              type: "brushend"
            });
          });
        } else {
          event_({
            type: "brushstart"
          });
          event_({
            type: "brush",
            mode: "resize"
          });
          event_({
            type: "brushend"
          });
        }
      });
    };
    function redraw(g) {
      g.selectAll(".resize").attr("transform", function(d) {
        return "translate(" + xExtent[+/e$/.test(d)] + "," + yExtent[+/^s/.test(d)] + ")";
      });
    }
    function redrawX(g) {
      g.select(".extent").attr("x", xExtent[0]);
      g.selectAll(".extent,.n>rect,.s>rect").attr("width", xExtent[1] - xExtent[0]);
    }
    function redrawY(g) {
      g.select(".extent").attr("y", yExtent[0]);
      g.selectAll(".extent,.e>rect,.w>rect").attr("height", yExtent[1] - yExtent[0]);
    }
    function brushstart() {
      var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(target), center, origin = d3.mouse(target), offset;
      var w = d3.select(d3_window(target)).on("keydown.brush", keydown).on("keyup.brush", keyup);
      if (d3.event.changedTouches) {
        w.on("touchmove.brush", brushmove).on("touchend.brush", brushend);
      } else {
        w.on("mousemove.brush", brushmove).on("mouseup.brush", brushend);
      }
      g.interrupt().selectAll("*").interrupt();
      if (dragging) {
        origin[0] = xExtent[0] - origin[0];
        origin[1] = yExtent[0] - origin[1];
      } else if (resizing) {
        var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing);
        offset = [ xExtent[1 - ex] - origin[0], yExtent[1 - ey] - origin[1] ];
        origin[0] = xExtent[ex];
        origin[1] = yExtent[ey];
      } else if (d3.event.altKey) center = origin.slice();
      g.style("pointer-events", "none").selectAll(".resize").style("display", null);
      d3.select("body").style("cursor", eventTarget.style("cursor"));
      event_({
        type: "brushstart"
      });
      brushmove();
      function keydown() {
        if (d3.event.keyCode == 32) {
          if (!dragging) {
            center = null;
            origin[0] -= xExtent[1];
            origin[1] -= yExtent[1];
            dragging = 2;
          }
          d3_eventPreventDefault();
        }
      }
      function keyup() {
        if (d3.event.keyCode == 32 && dragging == 2) {
          origin[0] += xExtent[1];
          origin[1] += yExtent[1];
          dragging = 0;
          d3_eventPreventDefault();
        }
      }
      function brushmove() {
        var point = d3.mouse(target), moved = false;
        if (offset) {
          point[0] += offset[0];
          point[1] += offset[1];
        }
        if (!dragging) {
          if (d3.event.altKey) {
            if (!center) center = [ (xExtent[0] + xExtent[1]) / 2, (yExtent[0] + yExtent[1]) / 2 ];
            origin[0] = xExtent[+(point[0] < center[0])];
            origin[1] = yExtent[+(point[1] < center[1])];
          } else center = null;
        }
        if (resizingX && move1(point, x, 0)) {
          redrawX(g);
          moved = true;
        }
        if (resizingY && move1(point, y, 1)) {
          redrawY(g);
          moved = true;
        }
        if (moved) {
          redraw(g);
          event_({
            type: "brush",
            mode: dragging ? "move" : "resize"
          });
        }
      }
      function move1(point, scale, i) {
        var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], extent = i ? yExtent : xExtent, size = extent[1] - extent[0], min, max;
        if (dragging) {
          r0 -= position;
          r1 -= size + position;
        }
        min = (i ? yClamp : xClamp) ? Math.max(r0, Math.min(r1, point[i])) : point[i];
        if (dragging) {
          max = (min += position) + size;
        } else {
          if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min));
          if (position < min) {
            max = min;
            min = position;
          } else {
            max = position;
          }
        }
        if (extent[0] != min || extent[1] != max) {
          if (i) yExtentDomain = null; else xExtentDomain = null;
          extent[0] = min;
          extent[1] = max;
          return true;
        }
      }
      function brushend() {
        brushmove();
        g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null);
        d3.select("body").style("cursor", null);
        w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null);
        dragRestore();
        event_({
          type: "brushend"
        });
      }
    }
    brush.x = function(z) {
      if (!arguments.length) return x;
      x = z;
      resizes = d3_svg_brushResizes[!x << 1 | !y];
      return brush;
    };
    brush.y = function(z) {
      if (!arguments.length) return y;
      y = z;
      resizes = d3_svg_brushResizes[!x << 1 | !y];
      return brush;
    };
    brush.clamp = function(z) {
      if (!arguments.length) return x && y ? [ xClamp, yClamp ] : x ? xClamp : y ? yClamp : null;
      if (x && y) xClamp = !!z[0], yClamp = !!z[1]; else if (x) xClamp = !!z; else if (y) yClamp = !!z;
      return brush;
    };
    brush.extent = function(z) {
      var x0, x1, y0, y1, t;
      if (!arguments.length) {
        if (x) {
          if (xExtentDomain) {
            x0 = xExtentDomain[0], x1 = xExtentDomain[1];
          } else {
            x0 = xExtent[0], x1 = xExtent[1];
            if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1);
            if (x1 < x0) t = x0, x0 = x1, x1 = t;
          }
        }
        if (y) {
          if (yExtentDomain) {
            y0 = yExtentDomain[0], y1 = yExtentDomain[1];
          } else {
            y0 = yExtent[0], y1 = yExtent[1];
            if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1);
            if (y1 < y0) t = y0, y0 = y1, y1 = t;
          }
        }
        return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ];
      }
      if (x) {
        x0 = z[0], x1 = z[1];
        if (y) x0 = x0[0], x1 = x1[0];
        xExtentDomain = [ x0, x1 ];
        if (x.invert) x0 = x(x0), x1 = x(x1);
        if (x1 < x0) t = x0, x0 = x1, x1 = t;
        if (x0 != xExtent[0] || x1 != xExtent[1]) xExtent = [ x0, x1 ];
      }
      if (y) {
        y0 = z[0], y1 = z[1];
        if (x) y0 = y0[1], y1 = y1[1];
        yExtentDomain = [ y0, y1 ];
        if (y.invert) y0 = y(y0), y1 = y(y1);
        if (y1 < y0) t = y0, y0 = y1, y1 = t;
        if (y0 != yExtent[0] || y1 != yExtent[1]) yExtent = [ y0, y1 ];
      }
      return brush;
    };
    brush.clear = function() {
      if (!brush.empty()) {
        xExtent = [ 0, 0 ], yExtent = [ 0, 0 ];
        xExtentDomain = yExtentDomain = null;
      }
      return brush;
    };
    brush.empty = function() {
      return !!x && xExtent[0] == xExtent[1] || !!y && yExtent[0] == yExtent[1];
    };
    return d3.rebind(brush, event, "on");
  };
  var d3_svg_brushCursor = {
    n: "ns-resize",
    e: "ew-resize",
    s: "ns-resize",
    w: "ew-resize",
    nw: "nwse-resize",
    ne: "nesw-resize",
    se: "nwse-resize",
    sw: "nesw-resize"
  };
  var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ];
  var d3_time_format = d3_time.format = d3_locale_enUS.timeFormat;
  var d3_time_formatUtc = d3_time_format.utc;
  var d3_time_formatIso = d3_time_formatUtc("%Y-%m-%dT%H:%M:%S.%LZ");
  d3_time_format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso;
  function d3_time_formatIsoNative(date) {
    return date.toISOString();
  }
  d3_time_formatIsoNative.parse = function(string) {
    var date = new Date(string);
    return isNaN(date) ? null : date;
  };
  d3_time_formatIsoNative.toString = d3_time_formatIso.toString;
  d3_time.second = d3_time_interval(function(date) {
    return new d3_date(Math.floor(date / 1e3) * 1e3);
  }, function(date, offset) {
    date.setTime(date.getTime() + Math.floor(offset) * 1e3);
  }, function(date) {
    return date.getSeconds();
  });
  d3_time.seconds = d3_time.second.range;
  d3_time.seconds.utc = d3_time.second.utc.range;
  d3_time.minute = d3_time_interval(function(date) {
    return new d3_date(Math.floor(date / 6e4) * 6e4);
  }, function(date, offset) {
    date.setTime(date.getTime() + Math.floor(offset) * 6e4);
  }, function(date) {
    return date.getMinutes();
  });
  d3_time.minutes = d3_time.minute.range;
  d3_time.minutes.utc = d3_time.minute.utc.range;
  d3_time.hour = d3_time_interval(function(date) {
    var timezone = date.getTimezoneOffset() / 60;
    return new d3_date((Math.floor(date / 36e5 - timezone) + timezone) * 36e5);
  }, function(date, offset) {
    date.setTime(date.getTime() + Math.floor(offset) * 36e5);
  }, function(date) {
    return date.getHours();
  });
  d3_time.hours = d3_time.hour.range;
  d3_time.hours.utc = d3_time.hour.utc.range;
  d3_time.month = d3_time_interval(function(date) {
    date = d3_time.day(date);
    date.setDate(1);
    return date;
  }, function(date, offset) {
    date.setMonth(date.getMonth() + offset);
  }, function(date) {
    return date.getMonth();
  });
  d3_time.months = d3_time.month.range;
  d3_time.months.utc = d3_time.month.utc.range;
  function d3_time_scale(linear, methods, format) {
    function scale(x) {
      return linear(x);
    }
    scale.invert = function(x) {
      return d3_time_scaleDate(linear.invert(x));
    };
    scale.domain = function(x) {
      if (!arguments.length) return linear.domain().map(d3_time_scaleDate);
      linear.domain(x);
      return scale;
    };
    function tickMethod(extent, count) {
      var span = extent[1] - extent[0], target = span / count, i = d3.bisect(d3_time_scaleSteps, target);
      return i == d3_time_scaleSteps.length ? [ methods.year, d3_scale_linearTickRange(extent.map(function(d) {
        return d / 31536e6;
      }), count)[2] ] : !i ? [ d3_time_scaleMilliseconds, d3_scale_linearTickRange(extent, count)[2] ] : methods[target / d3_time_scaleSteps[i - 1] < d3_time_scaleSteps[i] / target ? i - 1 : i];
    }
    scale.nice = function(interval, skip) {
      var domain = scale.domain(), extent = d3_scaleExtent(domain), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" && tickMethod(extent, interval);
      if (method) interval = method[0], skip = method[1];
      function skipped(date) {
        return !isNaN(date) && !interval.range(date, d3_time_scaleDate(+date + 1), skip).length;
      }
      return scale.domain(d3_scale_nice(domain, skip > 1 ? {
        floor: function(date) {
          while (skipped(date = interval.floor(date))) date = d3_time_scaleDate(date - 1);
          return date;
        },
        ceil: function(date) {
          while (skipped(date = interval.ceil(date))) date = d3_time_scaleDate(+date + 1);
          return date;
        }
      } : interval));
    };
    scale.ticks = function(interval, skip) {
      var extent = d3_scaleExtent(scale.domain()), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" ? tickMethod(extent, interval) : !interval.range && [ {
        range: interval
      }, skip ];
      if (method) interval = method[0], skip = method[1];
      return interval.range(extent[0], d3_time_scaleDate(+extent[1] + 1), skip < 1 ? 1 : skip);
    };
    scale.tickFormat = function() {
      return format;
    };
    scale.copy = function() {
      return d3_time_scale(linear.copy(), methods, format);
    };
    return d3_scale_linearRebind(scale, linear);
  }
  function d3_time_scaleDate(t) {
    return new Date(t);
  }
  var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ];
  var d3_time_scaleLocalMethods = [ [ d3_time.second, 1 ], [ d3_time.second, 5 ], [ d3_time.second, 15 ], [ d3_time.second, 30 ], [ d3_time.minute, 1 ], [ d3_time.minute, 5 ], [ d3_time.minute, 15 ], [ d3_time.minute, 30 ], [ d3_time.hour, 1 ], [ d3_time.hour, 3 ], [ d3_time.hour, 6 ], [ d3_time.hour, 12 ], [ d3_time.day, 1 ], [ d3_time.day, 2 ], [ d3_time.week, 1 ], [ d3_time.month, 1 ], [ d3_time.month, 3 ], [ d3_time.year, 1 ] ];
  var d3_time_scaleLocalFormat = d3_time_format.multi([ [ ".%L", function(d) {
    return d.getMilliseconds();
  } ], [ ":%S", function(d) {
    return d.getSeconds();
  } ], [ "%I:%M", function(d) {
    return d.getMinutes();
  } ], [ "%I %p", function(d) {
    return d.getHours();
  } ], [ "%a %d", function(d) {
    return d.getDay() && d.getDate() != 1;
  } ], [ "%b %d", function(d) {
    return d.getDate() != 1;
  } ], [ "%B", function(d) {
    return d.getMonth();
  } ], [ "%Y", d3_true ] ]);
  var d3_time_scaleMilliseconds = {
    range: function(start, stop, step) {
      return d3.range(Math.ceil(start / step) * step, +stop, step).map(d3_time_scaleDate);
    },
    floor: d3_identity,
    ceil: d3_identity
  };
  d3_time_scaleLocalMethods.year = d3_time.year;
  d3_time.scale = function() {
    return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat);
  };
  var d3_time_scaleUtcMethods = d3_time_scaleLocalMethods.map(function(m) {
    return [ m[0].utc, m[1] ];
  });
  var d3_time_scaleUtcFormat = d3_time_formatUtc.multi([ [ ".%L", function(d) {
    return d.getUTCMilliseconds();
  } ], [ ":%S", function(d) {
    return d.getUTCSeconds();
  } ], [ "%I:%M", function(d) {
    return d.getUTCMinutes();
  } ], [ "%I %p", function(d) {
    return d.getUTCHours();
  } ], [ "%a %d", function(d) {
    return d.getUTCDay() && d.getUTCDate() != 1;
  } ], [ "%b %d", function(d) {
    return d.getUTCDate() != 1;
  } ], [ "%B", function(d) {
    return d.getUTCMonth();
  } ], [ "%Y", d3_true ] ]);
  d3_time_scaleUtcMethods.year = d3_time.year.utc;
  d3_time.scale.utc = function() {
    return d3_time_scale(d3.scale.linear(), d3_time_scaleUtcMethods, d3_time_scaleUtcFormat);
  };
  d3.text = d3_xhrType(function(request) {
    return request.responseText;
  });
  d3.json = function(url, callback) {
    return d3_xhr(url, "application/json", d3_json, callback);
  };
  function d3_json(request) {
    return JSON.parse(request.responseText);
  }
  d3.html = function(url, callback) {
    return d3_xhr(url, "text/html", d3_html, callback);
  };
  function d3_html(request) {
    var range = d3_document.createRange();
    range.selectNode(d3_document.body);
    return range.createContextualFragment(request.responseText);
  }
  d3.xml = d3_xhrType(function(request) {
    return request.responseXML;
  });
  if (true) this.d3 = d3, !(__WEBPACK_AMD_DEFINE_FACTORY__ = (d3),
				__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
				(__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) :
				__WEBPACK_AMD_DEFINE_FACTORY__),
				__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); else if (typeof module === "object" && module.exports) module.exports = d3; else this.d3 = d3;
}();

/***/ }),
/* 2 */
/***/ (function(module, exports) {

/**
sprintf() for JavaScript 0.7-beta1
http://www.diveintojavascript.com/projects/javascript-sprintf

Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of sprintf() for JavaScript nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


Changelog:
2010.11.07 - 0.7-beta1-node
  - converted it to a node.js compatible module

2010.09.06 - 0.7-beta1
  - features: vsprintf, support for named placeholders
  - enhancements: format cache, reduced global namespace pollution

2010.05.22 - 0.6:
 - reverted to 0.4 and fixed the bug regarding the sign of the number 0
 Note:
 Thanks to Raphael Pigulla <raph (at] n3rd [dot) org> (http://www.n3rd.org/)
 who warned me about a bug in 0.5, I discovered that the last update was
 a regress. I appologize for that.

2010.05.09 - 0.5:
 - bug fix: 0 is now preceeded with a + sign
 - bug fix: the sign was not at the right position on padded results (Kamal Abdali)
 - switched from GPL to BSD license

2007.10.21 - 0.4:
 - unit test and patch (David Baird)

2007.09.17 - 0.3:
 - bug fix: no longer throws exception on empty paramenters (Hans Pufal)

2007.09.11 - 0.2:
 - feature: added argument swapping

2007.04.03 - 0.1:
 - initial release
**/

var sprintf = (function() {
	function get_type(variable) {
		return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
	}
	function str_repeat(input, multiplier) {
		for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
		return output.join('');
	}

	var str_format = function() {
		if (!str_format.cache.hasOwnProperty(arguments[0])) {
			str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
		}
		return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
	};

	// convert object to simple one line string without indentation or
	// newlines. Note that this implementation does not print array
	// values to their actual place for sparse arrays. 
	//
	// For example sparse array like this
	//    l = []
	//    l[4] = 1
	// Would be printed as "[1]" instead of "[, , , , 1]"
	// 
	// If argument 'seen' is not null and array the function will check for 
	// circular object references from argument.
	str_format.object_stringify = function(obj, depth, maxdepth, seen) {
		var str = '';
		if (obj != null) {
			switch( typeof(obj) ) {
			case 'function': 
				return '[Function' + (obj.name ? ': '+obj.name : '') + ']';
			    break;
			case 'object':
				if ( obj instanceof Error) { return '[' + obj.toString() + ']' };
				if (depth >= maxdepth) return '[Object]'
				if (seen) {
					// add object to seen list
					seen = seen.slice(0)
					seen.push(obj);
				}
				if (obj.length != null) { //array
					str += '[';
					var arr = []
					for (var i in obj) {
						if (seen && seen.indexOf(obj[i]) >= 0) arr.push('[Circular]');
						else arr.push(str_format.object_stringify(obj[i], depth+1, maxdepth, seen));
					}
					str += arr.join(', ') + ']';
				} else if ('getMonth' in obj) { // date
					return 'Date(' + obj + ')';
				} else { // object
					str += '{';
					var arr = []
					for (var k in obj) { 
						if(obj.hasOwnProperty(k)) {
							if (seen && seen.indexOf(obj[k]) >= 0) arr.push(k + ': [Circular]');
							else arr.push(k +': ' +str_format.object_stringify(obj[k], depth+1, maxdepth, seen)); 
						}
					}
					str += arr.join(', ') + '}';
				}
				return str;
				break;
			case 'string':				
				return '"' + obj + '"';
				break
			}
		}
		return '' + obj;
	}

	str_format.format = function(parse_tree, argv) {
		var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
		for (i = 0; i < tree_length; i++) {
			node_type = get_type(parse_tree[i]);
			if (node_type === 'string') {
				output.push(parse_tree[i]);
			}
			else if (node_type === 'array') {
				match = parse_tree[i]; // convenience purposes only
				if (match[2]) { // keyword argument
					arg = argv[cursor];
					for (k = 0; k < match[2].length; k++) {
						if (!arg.hasOwnProperty(match[2][k])) {
							throw new Error(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
						}
						arg = arg[match[2][k]];
					}
				}
				else if (match[1]) { // positional argument (explicit)
					arg = argv[match[1]];
				}
				else { // positional argument (implicit)
					arg = argv[cursor++];
				}

				if (/[^sO]/.test(match[8]) && (get_type(arg) != 'number')) {
					throw new Error(sprintf('[sprintf] expecting number but found %s "' + arg + '"', get_type(arg)));
				}
				switch (match[8]) {
					case 'b': arg = arg.toString(2); break;
					case 'c': arg = String.fromCharCode(arg); break;
					case 'd': arg = parseInt(arg, 10); break;
					case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
					case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
				    case 'O': arg = str_format.object_stringify(arg, 0, parseInt(match[7]) || 5); break;
					case 'o': arg = arg.toString(8); break;
					case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
					case 'u': arg = Math.abs(arg); break;
					case 'x': arg = arg.toString(16); break;
					case 'X': arg = arg.toString(16).toUpperCase(); break;
				}
				arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
				pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
				pad_length = match[6] - String(arg).length;
				pad = match[6] ? str_repeat(pad_character, pad_length) : '';
				output.push(match[5] ? arg + pad : pad + arg);
			}
		}
		return output.join('');
	};

	str_format.cache = {};

	str_format.parse = function(fmt) {
		var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
		while (_fmt) {
			if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
				parse_tree.push(match[0]);
			}
			else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
				parse_tree.push('%');
			}
			else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosOuxX])/.exec(_fmt)) !== null) {
				if (match[2]) {
					arg_names |= 1;
					var field_list = [], replacement_field = match[2], field_match = [];
					if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
						field_list.push(field_match[1]);
						while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
							if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
								field_list.push(field_match[1]);
							}
							else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
								field_list.push(field_match[1]);
							}
							else {
								throw new Error('[sprintf] ' + replacement_field);
							}
						}
					}
					else {
                        throw new Error('[sprintf] ' + replacement_field);
					}
					match[2] = field_list;
				}
				else {
					arg_names |= 2;
				}
				if (arg_names === 3) {
					throw new Error('[sprintf] mixing positional and named placeholders is not (yet) supported');
				}
				parse_tree.push(match);
			}
			else {
				throw new Error('[sprintf] ' + _fmt);
			}
			_fmt = _fmt.substring(match[0].length);
		}
		return parse_tree;
	};

	return str_format;
})();

var vsprintf = function(fmt, argv) {
	var argvClone = argv.slice();
	argvClone.unshift(fmt);
	return sprintf.apply(null, argvClone);
};

module.exports = sprintf;
sprintf.sprintf = sprintf;
sprintf.vsprintf = vsprintf;


/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


module.exports = __webpack_require__(4).epiviz;



/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {

/* WEBPACK VAR INJECTION */(function(global) {var $jscomp={scope:{}};$jscomp.defineProperty="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(c.get||c.set)throw new TypeError("ES3 does not support getters and setters.");a!=Array.prototype&&a!=Object.prototype&&(a[b]=c.value)};$jscomp.getGlobal=function(a){return"undefined"!=typeof window&&window===a?a:"undefined"!=typeof global&&null!=global?global:a};$jscomp.global=$jscomp.getGlobal(this);$jscomp.SYMBOL_PREFIX="jscomp_symbol_";
$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.symbolCounter_=0;$jscomp.Symbol=function(a){return $jscomp.SYMBOL_PREFIX+(a||"")+$jscomp.symbolCounter_++};
$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var a=$jscomp.global.Symbol.iterator;a||(a=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[a]&&$jscomp.defineProperty(Array.prototype,a,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(a){var b=0;return $jscomp.iteratorPrototype(function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}})};
$jscomp.iteratorPrototype=function(a){$jscomp.initSymbolIterator();a={next:a};a[$jscomp.global.Symbol.iterator]=function(){return this};return a};$jscomp.array=$jscomp.array||{};$jscomp.iteratorFromArray=function(a,b){$jscomp.initSymbolIterator();a instanceof String&&(a+="");var c=0,d={next:function(){if(c<a.length){var e=c++;return{value:b(e,a[e]),done:!1}}d.next=function(){return{done:!0,value:void 0}};return d.next()}};d[Symbol.iterator]=function(){return d};return d};
$jscomp.polyfill=function(a,b,c,d){if(b){c=$jscomp.global;a=a.split(".");for(d=0;d<a.length-1;d++){var e=a[d];e in c||(c[e]={});c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&$jscomp.defineProperty(c,a,{configurable:!0,writable:!0,value:b})}};$jscomp.polyfill("Array.prototype.keys",function(a){return a?a:function(){return $jscomp.iteratorFromArray(this,function(a){return a})}},"es6-impl","es3");
$jscomp.polyfill("Array.prototype.values",function(a){return a?a:function(){return $jscomp.iteratorFromArray(this,function(a,c){return c})}},"es6","es3");$jscomp.checkStringArgs=function(a,b,c){if(null==a)throw new TypeError("The 'this' value for String.prototype."+c+" must not be null or undefined");if(b instanceof RegExp)throw new TypeError("First argument to String.prototype."+c+" must not be a regular expression");return a+""};
$jscomp.polyfill("String.prototype.includes",function(a){return a?a:function(a,c){return-1!==$jscomp.checkStringArgs(this,a,"includes").indexOf(a,c||0)}},"es6-impl","es3");$jscomp.findInternal=function(a,b,c){a instanceof String&&(a=String(a));for(var d=a.length,e=0;e<d;e++){var f=a[e];if(b.call(c,f,e,a))return{i:e,v:f}}return{i:-1,v:void 0}};$jscomp.polyfill("Array.prototype.find",function(a){return a?a:function(a,c){return $jscomp.findInternal(this,a,c).v}},"es6-impl","es3");
$jscomp.polyfill("Math.log2",function(a){return a?a:function(a){return Math.log(a)/Math.LN2}},"es6-impl","es3");$jscomp.polyfill("Array.prototype.fill",function(a){return a?a:function(a,c,d){var b=this.length||0;0>c&&(c=Math.max(0,b+c));if(null==d||d>b)d=b;d=Number(d);0>d&&(d=Math.max(0,b+d));for(c=Number(c||0);c<d;c++)this[c]=a;return this}},"es6-impl","es3");var COMPILED=!0,goog=goog||{};goog.global=this;goog.isDef=function(a){return void 0!==a};
goog.exportPath_=function(a,b,c){a=a.split(".");c=c||goog.global;a[0]in c||!c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)!a.length&&goog.isDef(b)?c[d]=b:c=c[d]?c[d]:c[d]={}};
goog.define=function(a,b){var c=b;COMPILED||(goog.global.CLOSURE_UNCOMPILED_DEFINES&&Object.prototype.hasOwnProperty.call(goog.global.CLOSURE_UNCOMPILED_DEFINES,a)?c=goog.global.CLOSURE_UNCOMPILED_DEFINES[a]:goog.global.CLOSURE_DEFINES&&Object.prototype.hasOwnProperty.call(goog.global.CLOSURE_DEFINES,a)&&(c=goog.global.CLOSURE_DEFINES[a]));goog.exportPath_(a,c)};goog.DEBUG=!0;goog.LOCALE="en";goog.TRUSTED_SITE=!0;goog.STRICT_MODE_COMPATIBLE=!1;goog.DISALLOW_TEST_ONLY_CODE=COMPILED&&!goog.DEBUG;
goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING=!1;goog.provide=function(a){if(goog.isInModuleLoader_())throw Error("goog.provide can not be used within a goog.module.");if(!COMPILED&&goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');goog.constructNamespace_(a)};goog.constructNamespace_=function(a,b){if(!COMPILED){delete goog.implicitNamespaces_[a];for(var c=a;(c=c.substring(0,c.lastIndexOf(".")))&&!goog.getObjectByName(c);)goog.implicitNamespaces_[c]=!0}goog.exportPath_(a,b)};
goog.VALID_MODULE_RE_=/^[a-zA-Z_$][a-zA-Z0-9._$]*$/;
goog.module=function(a){if(!goog.isString(a)||!a||-1==a.search(goog.VALID_MODULE_RE_))throw Error("Invalid module identifier");if(!goog.isInModuleLoader_())throw Error("Module "+a+" has been loaded incorrectly. Note, modules cannot be loaded as normal scripts. They require some kind of pre-processing step. You're likely trying to load a module via a script tag or as a part of a concatenated bundle without rewriting the module. For more info see: https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide.");if(goog.moduleLoaderState_.moduleName)throw Error("goog.module may only be called once per module.");
goog.moduleLoaderState_.moduleName=a;if(!COMPILED){if(goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');delete goog.implicitNamespaces_[a]}};goog.module.get=function(a){return goog.module.getInternal_(a)};goog.module.getInternal_=function(a){if(!COMPILED){if(a in goog.loadedModules_)return goog.loadedModules_[a];if(!goog.implicitNamespaces_[a])return a=goog.getObjectByName(a),null!=a?a:null}return null};goog.moduleLoaderState_=null;
goog.isInModuleLoader_=function(){return null!=goog.moduleLoaderState_};goog.module.declareLegacyNamespace=function(){if(!COMPILED&&!goog.isInModuleLoader_())throw Error("goog.module.declareLegacyNamespace must be called from within a goog.module");if(!COMPILED&&!goog.moduleLoaderState_.moduleName)throw Error("goog.module must be called prior to goog.module.declareLegacyNamespace.");goog.moduleLoaderState_.declareLegacyNamespace=!0};
goog.setTestOnly=function(a){if(goog.DISALLOW_TEST_ONLY_CODE)throw a=a||"",Error("Importing test-only code into non-debug environment"+(a?": "+a:"."));};goog.forwardDeclare=function(a){};COMPILED||(goog.isProvided_=function(a){return a in goog.loadedModules_||!goog.implicitNamespaces_[a]&&goog.isDefAndNotNull(goog.getObjectByName(a))},goog.implicitNamespaces_={"goog.module":!0});
goog.getObjectByName=function(a,b){for(var c=a.split("."),d=b||goog.global,e;e=c.shift();)if(goog.isDefAndNotNull(d[e]))d=d[e];else return null;return d};goog.globalize=function(a,b){var c=b||goog.global,d;for(d in a)c[d]=a[d]};
goog.addDependency=function(a,b,c,d){if(goog.DEPENDENCIES_ENABLED){var e;a=a.replace(/\\/g,"/");var f=goog.dependencies_;d&&"boolean"!==typeof d||(d=d?{module:"goog"}:{});for(var g=0;e=b[g];g++)f.nameToPath[e]=a,f.loadFlags[a]=d;for(d=0;b=c[d];d++)a in f.requires||(f.requires[a]={}),f.requires[a][b]=!0}};goog.ENABLE_DEBUG_LOADER=!0;goog.logToConsole_=function(a){goog.global.console&&goog.global.console.error(a)};
goog.require=function(a){if(!COMPILED){goog.ENABLE_DEBUG_LOADER&&goog.IS_OLD_IE_&&goog.maybeProcessDeferredDep_(a);if(goog.isProvided_(a)){if(goog.isInModuleLoader_())return goog.module.getInternal_(a)}else if(goog.ENABLE_DEBUG_LOADER){var b=goog.getPathFromDeps_(a);if(b)goog.writeScripts_(b);else throw a="goog.require could not find: "+a,goog.logToConsole_(a),Error(a);}return null}};goog.basePath="";goog.nullFunction=function(){};
goog.abstractMethod=function(){throw Error("unimplemented abstract method");};goog.addSingletonGetter=function(a){a.instance_=void 0;a.getInstance=function(){if(a.instance_)return a.instance_;goog.DEBUG&&(goog.instantiatedSingletons_[goog.instantiatedSingletons_.length]=a);return a.instance_=new a}};goog.instantiatedSingletons_=[];goog.LOAD_MODULE_USING_EVAL=!0;goog.SEAL_MODULE_EXPORTS=goog.DEBUG;goog.loadedModules_={};goog.DEPENDENCIES_ENABLED=!COMPILED&&goog.ENABLE_DEBUG_LOADER;goog.TRANSPILE="detect";
goog.TRANSPILER="transpile.js";
goog.DEPENDENCIES_ENABLED&&(goog.dependencies_={loadFlags:{},nameToPath:{},requires:{},visited:{},written:{},deferred:{}},goog.inHtmlDocument_=function(){var a=goog.global.document;return null!=a&&"write"in a},goog.findBasePath_=function(){if(goog.isDef(goog.global.CLOSURE_BASE_PATH))goog.basePath=goog.global.CLOSURE_BASE_PATH;else if(goog.inHtmlDocument_())for(var a=goog.global.document.getElementsByTagName("SCRIPT"),b=a.length-1;0<=b;--b){var c=a[b].src,d=c.lastIndexOf("?"),d=-1==d?c.length:d;if("base.js"==
c.substr(d-7,7)){goog.basePath=c.substr(0,d-7);break}}},goog.importScript_=function(a,b){(goog.global.CLOSURE_IMPORT_SCRIPT||goog.writeScriptTag_)(a,b)&&(goog.dependencies_.written[a]=!0)},goog.IS_OLD_IE_=!(goog.global.atob||!goog.global.document||!goog.global.document.all),goog.importProcessedScript_=function(a,b,c){goog.importScript_("",'goog.retrieveAndExec_("'+a+'", '+b+", "+c+");")},goog.queuedModules_=[],goog.wrapModule_=function(a,b){return goog.LOAD_MODULE_USING_EVAL&&goog.isDef(goog.global.JSON)?
"goog.loadModule("+goog.global.JSON.stringify(b+"\n//# sourceURL="+a+"\n")+");":'goog.loadModule(function(exports) {"use strict";'+b+"\n;return exports});\n//# sourceURL="+a+"\n"},goog.loadQueuedModules_=function(){var a=goog.queuedModules_.length;if(0<a){var b=goog.queuedModules_;goog.queuedModules_=[];for(var c=0;c<a;c++)goog.maybeProcessDeferredPath_(b[c])}},goog.maybeProcessDeferredDep_=function(a){goog.isDeferredModule_(a)&&goog.allDepsAreAvailable_(a)&&(a=goog.getPathFromDeps_(a),goog.maybeProcessDeferredPath_(goog.basePath+
a))},goog.isDeferredModule_=function(a){var b=(a=goog.getPathFromDeps_(a))&&goog.dependencies_.loadFlags[a]||{},c=b.lang||"es3";return a&&("goog"==b.module||goog.needsTranspile_(c))?goog.basePath+a in goog.dependencies_.deferred:!1},goog.allDepsAreAvailable_=function(a){if((a=goog.getPathFromDeps_(a))&&a in goog.dependencies_.requires)for(var b in goog.dependencies_.requires[a])if(!goog.isProvided_(b)&&!goog.isDeferredModule_(b))return!1;return!0},goog.maybeProcessDeferredPath_=function(a){if(a in
goog.dependencies_.deferred){var b=goog.dependencies_.deferred[a];delete goog.dependencies_.deferred[a];goog.globalEval(b)}},goog.loadModuleFromUrl=function(a){goog.retrieveAndExec_(a,!0,!1)},goog.writeScriptSrcNode_=function(a){goog.global.document.write('<script type="text/javascript" src="'+a+'">\x3c\/script>')},goog.appendScriptSrcNode_=function(a){var b=goog.global.document,c=b.createElement("script");c.type="text/javascript";c.src=a;c.defer=!1;c.async=!1;b.head.appendChild(c)},goog.writeScriptTag_=
function(a,b){if(goog.inHtmlDocument_()){var c=goog.global.document;if(!goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING&&"complete"==c.readyState){if(/\bdeps.js$/.test(a))return!1;throw Error('Cannot write "'+a+'" after document load');}if(void 0===b)if(goog.IS_OLD_IE_){var d=" onreadystatechange='goog.onScriptLoad_(this, "+ ++goog.lastNonModuleScriptIndex_+")' ";c.write('<script type="text/javascript" src="'+a+'"'+d+">\x3c\/script>")}else goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING?goog.appendScriptSrcNode_(a):
goog.writeScriptSrcNode_(a);else c.write('<script type="text/javascript">'+goog.protectScriptTag_(b)+"\x3c\/script>");return!0}return!1},goog.protectScriptTag_=function(a){return a.replace(/<\/(SCRIPT)/ig,"\\x3c\\$1")},goog.needsTranspile_=function(a){if("always"==goog.TRANSPILE)return!0;if("never"==goog.TRANSPILE)return!1;goog.requiresTranspilation_||(goog.requiresTranspilation_=goog.createRequiresTranspilation_());if(a in goog.requiresTranspilation_)return goog.requiresTranspilation_[a];throw Error("Unknown language mode: "+
a);},goog.requiresTranspilation_=null,goog.lastNonModuleScriptIndex_=0,goog.onScriptLoad_=function(a,b){"complete"==a.readyState&&goog.lastNonModuleScriptIndex_==b&&goog.loadQueuedModules_();return!0},goog.writeScripts_=function(a){function b(a){if(!(a in e.written||a in e.visited)){e.visited[a]=!0;if(a in e.requires)for(var f in e.requires[a])if(!goog.isProvided_(f))if(f in e.nameToPath)b(e.nameToPath[f]);else throw Error("Undefined nameToPath for "+f);a in d||(d[a]=!0,c.push(a))}}var c=[],d={},
e=goog.dependencies_;b(a);for(a=0;a<c.length;a++){var f=c[a];goog.dependencies_.written[f]=!0}var g=goog.moduleLoaderState_;goog.moduleLoaderState_=null;for(a=0;a<c.length;a++)if(f=c[a]){var h=e.loadFlags[f]||{},m=goog.needsTranspile_(h.lang||"es3");"goog"==h.module||m?goog.importProcessedScript_(goog.basePath+f,"goog"==h.module,m):goog.importScript_(goog.basePath+f)}else throw goog.moduleLoaderState_=g,Error("Undefined script input");goog.moduleLoaderState_=g},goog.getPathFromDeps_=function(a){return a in
goog.dependencies_.nameToPath?goog.dependencies_.nameToPath[a]:null},goog.findBasePath_(),goog.global.CLOSURE_NO_DEPS||goog.importScript_(goog.basePath+"deps.js"));
goog.loadModule=function(a){var b=goog.moduleLoaderState_;try{goog.moduleLoaderState_={moduleName:void 0,declareLegacyNamespace:!1};var c;if(goog.isFunction(a))c=a.call(void 0,{});else if(goog.isString(a))c=goog.loadModuleFromSource_.call(void 0,a);else throw Error("Invalid module definition");var d=goog.moduleLoaderState_.moduleName;if(!goog.isString(d)||!d)throw Error('Invalid module name "'+d+'"');goog.moduleLoaderState_.declareLegacyNamespace?goog.constructNamespace_(d,c):goog.SEAL_MODULE_EXPORTS&&
Object.seal&&goog.isObject(c)&&Object.seal(c);goog.loadedModules_[d]=c}finally{goog.moduleLoaderState_=b}};goog.loadModuleFromSource_=function(a){eval(a);return{}};goog.normalizePath_=function(a){a=a.split("/");for(var b=0;b<a.length;)"."==a[b]?a.splice(b,1):b&&".."==a[b]&&a[b-1]&&".."!=a[b-1]?a.splice(--b,2):b++;return a.join("/")};
goog.loadFileSync_=function(a){if(goog.global.CLOSURE_LOAD_FILE_SYNC)return goog.global.CLOSURE_LOAD_FILE_SYNC(a);try{var b=new goog.global.XMLHttpRequest;b.open("get",a,!1);b.send();return 0==b.status||200==b.status?b.responseText:null}catch(c){return null}};
goog.retrieveAndExec_=function(a,b,c){if(!COMPILED){var d=a;a=goog.normalizePath_(a);var e=goog.global.CLOSURE_IMPORT_SCRIPT||goog.writeScriptTag_,f=goog.loadFileSync_(a);if(null==f)throw Error('Load of "'+a+'" failed');c&&(f=goog.transpile_.call(goog.global,f,a));f=b?goog.wrapModule_(a,f):f+("\n//# sourceURL="+a);goog.IS_OLD_IE_?(goog.dependencies_.deferred[d]=f,goog.queuedModules_.push(d)):e(a,f)}};
goog.transpile_=function(a,b){var c=goog.global.$jscomp;c||(goog.global.$jscomp=c={});var d=c.transpile;if(!d){var e=goog.basePath+goog.TRANSPILER,f=goog.loadFileSync_(e);if(f){eval(f+"\n//# sourceURL="+e);if(goog.global.$gwtExport&&goog.global.$gwtExport.$jscomp&&!goog.global.$gwtExport.$jscomp.transpile)throw Error('The transpiler did not properly export the "transpile" method. $gwtExport: '+JSON.stringify(goog.global.$gwtExport));goog.global.$jscomp.transpile=goog.global.$gwtExport.$jscomp.transpile;
c=goog.global.$jscomp;d=c.transpile}}d||(d=c.transpile=function(a,b){goog.logToConsole_(b+" requires transpilation but no transpiler was found.");return a});return d(a,b)};
goog.typeOf=function(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
else if("function"==b&&"undefined"==typeof a.call)return"object";return b};goog.isNull=function(a){return null===a};goog.isDefAndNotNull=function(a){return null!=a};goog.isArray=function(a){return"array"==goog.typeOf(a)};goog.isArrayLike=function(a){var b=goog.typeOf(a);return"array"==b||"object"==b&&"number"==typeof a.length};goog.isDateLike=function(a){return goog.isObject(a)&&"function"==typeof a.getFullYear};goog.isString=function(a){return"string"==typeof a};
goog.isBoolean=function(a){return"boolean"==typeof a};goog.isNumber=function(a){return"number"==typeof a};goog.isFunction=function(a){return"function"==goog.typeOf(a)};goog.isObject=function(a){var b=typeof a;return"object"==b&&null!=a||"function"==b};goog.getUid=function(a){return a[goog.UID_PROPERTY_]||(a[goog.UID_PROPERTY_]=++goog.uidCounter_)};goog.hasUid=function(a){return!!a[goog.UID_PROPERTY_]};
goog.removeUid=function(a){null!==a&&"removeAttribute"in a&&a.removeAttribute(goog.UID_PROPERTY_);try{delete a[goog.UID_PROPERTY_]}catch(b){}};goog.UID_PROPERTY_="closure_uid_"+(1E9*Math.random()>>>0);goog.uidCounter_=0;goog.getHashCode=goog.getUid;goog.removeHashCode=goog.removeUid;goog.cloneObject=function(a){var b=goog.typeOf(a);if("object"==b||"array"==b){if(a.clone)return a.clone();var b="array"==b?[]:{},c;for(c in a)b[c]=goog.cloneObject(a[c]);return b}return a};
goog.bindNative_=function(a,b,c){return a.call.apply(a.bind,arguments)};goog.bindJs_=function(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var c=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(c,d);return a.apply(b,c)}}return function(){return a.apply(b,arguments)}};
goog.bind=function(a,b,c){Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?goog.bind=goog.bindNative_:goog.bind=goog.bindJs_;return goog.bind.apply(null,arguments)};goog.partial=function(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=c.slice();b.push.apply(b,arguments);return a.apply(this,b)}};goog.mixin=function(a,b){for(var c in b)a[c]=b[c]};goog.now=goog.TRUSTED_SITE&&Date.now||function(){return+new Date};
goog.globalEval=function(a){if(goog.global.execScript)goog.global.execScript(a,"JavaScript");else if(goog.global.eval){if(null==goog.evalWorksForGlobals_)if(goog.global.eval("var _evalTest_ = 1;"),"undefined"!=typeof goog.global._evalTest_){try{delete goog.global._evalTest_}catch(d){}goog.evalWorksForGlobals_=!0}else goog.evalWorksForGlobals_=!1;if(goog.evalWorksForGlobals_)goog.global.eval(a);else{var b=goog.global.document,c=b.createElement("SCRIPT");c.type="text/javascript";c.defer=!1;c.appendChild(b.createTextNode(a));
b.body.appendChild(c);b.body.removeChild(c)}}else throw Error("goog.globalEval not available");};goog.evalWorksForGlobals_=null;
goog.getCssName=function(a,b){if("."==String(a).charAt(0))throw Error('className passed in goog.getCssName must not start with ".". You passed: '+a);var c=function(a){return goog.cssNameMapping_[a]||a},d=function(a){a=a.split("-");for(var b=[],d=0;d<a.length;d++)b.push(c(a[d]));return b.join("-")},d=goog.cssNameMapping_?"BY_WHOLE"==goog.cssNameMappingStyle_?c:d:function(a){return a},d=b?a+"-"+d(b):d(a);return goog.global.CLOSURE_CSS_NAME_MAP_FN?goog.global.CLOSURE_CSS_NAME_MAP_FN(d):d};
goog.setCssNameMapping=function(a,b){goog.cssNameMapping_=a;goog.cssNameMappingStyle_=b};!COMPILED&&goog.global.CLOSURE_CSS_NAME_MAPPING&&(goog.cssNameMapping_=goog.global.CLOSURE_CSS_NAME_MAPPING);goog.getMsg=function(a,b){b&&(a=a.replace(/\{\$([^}]+)}/g,function(a,d){return null!=b&&d in b?b[d]:a}));return a};goog.getMsgWithFallback=function(a,b){return a};goog.exportSymbol=function(a,b,c){goog.exportPath_(a,b,c)};goog.exportProperty=function(a,b,c){a[b]=c};
goog.inherits=function(a,b){function c(){}c.prototype=b.prototype;a.superClass_=b.prototype;a.prototype=new c;a.prototype.constructor=a;a.base=function(a,c,f){for(var d=Array(arguments.length-2),e=2;e<arguments.length;e++)d[e-2]=arguments[e];return b.prototype[c].apply(a,d)}};
goog.base=function(a,b,c){var d=arguments.callee.caller;if(goog.STRICT_MODE_COMPATIBLE||goog.DEBUG&&!d)throw Error("arguments.caller not defined.  goog.base() cannot be used with strict mode code. See http://www.ecma-international.org/ecma-262/5.1/#sec-C");if(d.superClass_){for(var e=Array(arguments.length-1),f=1;f<arguments.length;f++)e[f-1]=arguments[f];return d.superClass_.constructor.apply(a,e)}e=Array(arguments.length-2);for(f=2;f<arguments.length;f++)e[f-2]=arguments[f];for(var f=!1,g=a.constructor;g;g=
g.superClass_&&g.superClass_.constructor)if(g.prototype[b]===d)f=!0;else if(f)return g.prototype[b].apply(a,e);if(a[b]===d)return a.constructor.prototype[b].apply(a,e);throw Error("goog.base called from a method of one name to a method of a different name");};goog.scope=function(a){if(goog.isInModuleLoader_())throw Error("goog.scope is not supported within a goog.module.");a.call(goog.global)};COMPILED||(goog.global.COMPILED=COMPILED);
goog.defineClass=function(a,b){var c=b.constructor,d=b.statics;c&&c!=Object.prototype.constructor||(c=function(){throw Error("cannot instantiate an interface (no constructor defined).");});c=goog.defineClass.createSealingConstructor_(c,a);a&&goog.inherits(c,a);delete b.constructor;delete b.statics;goog.defineClass.applyProperties_(c.prototype,b);null!=d&&(d instanceof Function?d(c):goog.defineClass.applyProperties_(c,d));return c};goog.defineClass.SEAL_CLASS_INSTANCES=goog.DEBUG;
goog.defineClass.createSealingConstructor_=function(a,b){if(!goog.defineClass.SEAL_CLASS_INSTANCES)return a;var c=!goog.defineClass.isUnsealable_(b),d=function(){var b=a.apply(this,arguments)||this;b[goog.UID_PROPERTY_]=b[goog.UID_PROPERTY_];this.constructor===d&&c&&Object.seal instanceof Function&&Object.seal(b);return b};return d};goog.defineClass.isUnsealable_=function(a){return a&&a.prototype&&a.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_]};goog.defineClass.OBJECT_PROTOTYPE_FIELDS_="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" ");
goog.defineClass.applyProperties_=function(a,b){for(var c in b)Object.prototype.hasOwnProperty.call(b,c)&&(a[c]=b[c]);for(var d=0;d<goog.defineClass.OBJECT_PROTOTYPE_FIELDS_.length;d++)c=goog.defineClass.OBJECT_PROTOTYPE_FIELDS_[d],Object.prototype.hasOwnProperty.call(b,c)&&(a[c]=b[c])};goog.tagUnsealableClass=function(a){!COMPILED&&goog.defineClass.SEAL_CLASS_INSTANCES&&(a.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_]=!0)};goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_="goog_defineClass_legacy_unsealable";
goog.createRequiresTranspilation_=function(){function a(a,b){d?c[a]=!0:b()?c[a]=!1:d=c[a]=!0}function b(a){try{return!!eval(a)}catch(f){return!1}}var c={es3:!1},d=!1;a("es5",function(){return b("[1,].length==1")});a("es6",function(){return b('(()=>{"use strict";class X{constructor(){if(new.target!=String)throw 1;this.x=42}}let q=Reflect.construct(X,[],String);if(q.x!=42||!(q instanceof String))throw 1;for(const a of[2,3]){if(a==2)continue;function f(z={a}){let a=0;return z.a}{function f(){return 0;}}return f()==3}})()')});
a("es6-impl",function(){return!0});a("es7",function(){return b("2 ** 2 == 4")});a("es8",function(){return b("async () => 1, true")});return c};var epiviz={deferred:{}};epiviz.deferred.Deferred=function(a){this._deferred=a||$.Deferred()};epiviz.deferred.Deferred.State={PENDING:"pending",RESOLVED:"resolved",REJECTED:"rejected"};epiviz.deferred.Deferred.prototype.then=function(a,b,c){return new epiviz.deferred.Promise(this._deferred.then(a,b,c))};epiviz.deferred.Deferred.prototype.done=function(a,b){return new epiviz.deferred.Deferred(this._deferred.done(a,b))};
epiviz.deferred.Deferred.prototype.fail=function(a,b){return new epiviz.deferred.Deferred(this._deferred.fail(a,b))};epiviz.deferred.Deferred.prototype.always=function(a,b){return new epiviz.deferred.Deferred(this._deferred.always(a,b))};epiviz.deferred.Deferred.prototype.state=function(){return this._deferred.state()};epiviz.deferred.Deferred.prototype.notify=function(a){return new epiviz.deferred.Deferred(this._deferred.notify(a))};
epiviz.deferred.Deferred.prototype.notifyWith=function(a,b){return new epiviz.deferred.Deferred(this._deferred.notifyWith(a,b))};epiviz.deferred.Deferred.prototype.progress=function(a,b){return new epiviz.deferred.Deferred(this._deferred.progress(a,b))};epiviz.deferred.Deferred.prototype.promise=function(a){return new epiviz.deferred.Promise(this._deferred.promise(a))};epiviz.deferred.Deferred.prototype.reject=function(a){return new epiviz.deferred.Deferred(this._deferred.reject(a))};
epiviz.deferred.Deferred.prototype.rejectWith=function(a,b){return new epiviz.deferred.Deferred(this._deferred.rejectWith(a,b))};epiviz.deferred.Deferred.prototype.resolve=function(a){return new epiviz.deferred.Deferred(this._deferred.resolve(a))};epiviz.deferred.Deferred.prototype.resolveWith=function(a,b){return new epiviz.deferred.Deferred(this._deferred.resolveWith(a,b))};epiviz.utils=function(){};epiviz.utils.capitalizeFirstLetter=function(a){return a.charAt(0).toUpperCase()+a.slice(1)};epiviz.utils.stringContains=function(a,b){return-1!=a.indexOf(b)};epiviz.utils.stringStartsWith=function(a,b){return 0==a.indexOf(b)};epiviz.utils.stringEndsWith=function(a,b){return a.lastIndexOf(b)==a.length-b.length};epiviz.utils.fillArray=function(a,b){a=a||0;for(var c=Array(a),d=0;d<a;++d)c[d]=b;return c};
epiviz.utils.indexOf=function(a,b){for(var c=0;c<a.length;++c)if(b(a[c]))return c;return-1};epiviz.utils.arraysEqual=function(a,b){if(a==b)return!0;if(!a||!b||a.length!=b.length||a<b||b<a)return!1;for(var c=0;c<a.length;++c)if(a[c]!=b[c])return!1;return!0};epiviz.utils.elementsEqual=function(a,b){if(a==b)return!0;if(!a||!b||a.length!=b.length)return!1;var c={},d;for(d=0;d<a.length;++d)a[d]in c||(c[a[d]]=0),++c[a[d]];for(d=0;d<b.length;++d){if(!c[b[d]])return!1;--c[b[d]]}return!0};
epiviz.utils.range=function(a,b){b=b||0;a=a||0;for(var c=Array(a),d=0;d<a;++d)c[d]=d+b;return c};epiviz.utils.arrayAppend=function(a,b){a.push.apply(a,b)};epiviz.utils.arrayFlip=function(a){for(var b={},c=0;c<a.length;++c)b[a[c]]=c;return b};epiviz.utils.indexOfMin=function(a,b){for(var c=null,d=null,e=0;e<a.length;++e)for(var f=b?e+1:0;f<a[e].length;++f)if(null==d||a[e][f]<d)d=a[e][f],c=[e,f];return{min:d,index:c}};
epiviz.utils.arrayIntersection=function(a,b){var c={};a.forEach(function(a){c[a]=a});var d=[];b.forEach(function(a){a in c&&d.push(a)});return d};epiviz.utils.asyncFor=function(a,b,c){if(a){var d=function(e){e>=a?c&&c():b(e,function(a){a?c&&c():d(e+1)})};d(0)}else c&&c()};
epiviz.utils.deferredFor=function(a,b){for(var c=new epiviz.deferred.Deferred,d=new epiviz.deferred.Deferred,e=c.promise(),f=0;f<a;++f)(function(c){e=e.then(function(){var e=b(c);c==a-1&&e.then(function(){d.resolve()});return e})})(f);c.resolve();return d};epiviz.utils.mapCopy=function(a){var b={},c;for(c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b};
epiviz.utils.mapEquals=function(a,b){if(a==b)return!0;if(!a||!b)return!1;for(var c in a)if(a.hasOwnProperty(c)&&(!b.hasOwnProperty(c)||a[c]!=b[c]))return!1;for(c in b)if(b.hasOwnProperty(c)&&!a.hasOwnProperty(c))return!1;return!0};epiviz.utils.mapCombine=function(a,b,c){var d={},e;if(b)for(e in b)b.hasOwnProperty(e)&&(d[e]=b[e]);if(a)for(e in a)a.hasOwnProperty(e)&&(c&&d[e]&&$.isArray(d[e])&&a[e]&&$.isArray(a[e])?d[e]=d[e].concat(a[e]):d[e]=a[e]);return d};
epiviz.utils.mapJoin=function(a,b,c){b||""===b||(b=":");c||""===c||(c=",");var d="",e;for(e in a)a.hasOwnProperty(e)&&(d&&(d+=c),d+=e+b+a[e]);return d};epiviz.utils.mapKeyIntersection=function(a,b){var c=[];if(!a||!b)return c;for(var d in a)a.hasOwnProperty(d)&&d in b&&c.push(d);return c};epiviz.utils.forEach=function(a,b){for(var c in a)if(a.hasOwnProperty(c)&&b(a[c],c,a))break};
epiviz.utils.evaluateFullyQualifiedTypeName=function(a){try{for(var b=a.split("."),c=b.pop(),d=window,e=0;e<b.length;++e)d=d[b[e]];var f=d[c];return"function"!==typeof f?(console.error("Unknown type name: "+a),null):f}catch(g){return console.error("Unknown type name: "+a),null}};epiviz.utils.applyConstructor=function(a,b){var c;c=function(){};c.prototype=a.prototype;c=new c;c.constructor=a;a.apply(c,b);return c};epiviz.utils.RAD_TO_DEG=180/Math.PI;epiviz.utils.DEG_TO_RAD=Math.PI/180;
epiviz.utils.getInternetExplorerVersion=function(){var a=-1;if("Microsoft Internet Explorer"==navigator.appName){var b=/MSIE ([0-9]{1,}[.0-9]{0,})/.exec(navigator.userAgent);null!=b&&(a=parseFloat(b[1]))}return a};epiviz.utils.generatePseudoGUID=function(a){for(var b="",c=0;c<a;++c)b+="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"[Math.round(61*Math.random())];return b};epiviz.utils.colorize=function(a,b,c,d,e,f){return d3.scale.linear().domain([a,c,b]).range([d,f,e])};
epiviz.utils.colorizeBinary=function(a,b,c,d){return d3.scale.linear().domain([a,b]).range([c,d])};epiviz.utils.sign=function(a){return 0>a?-1:0==a?0:1};var caja={};epiviz.caja={};caja.initialize=function(){};epiviz.caja.cajole=function(a,b){var c=new epiviz.deferred.Deferred;setTimeout(function(){c.resolve(eval("("+a+")"))},0);return c};epiviz.caja.run=function(a,b){var c=new epiviz.deferred.Deferred;setTimeout(function(){var b=document.getElementsByTagName("head")[0],e=document.createElement("script");e.type="text/javascript";e.src=a;e.onreadystatechange=e.onload=function(){c.resolve()};b.appendChild(e)},0);return c};
epiviz.caja.chain=function(a,b){$.isArray(b)||(b=epiviz.utils.fillArray(a.length,b));return epiviz.utils.deferredFor(a.length,function(c){return epiviz.caja.run(a[c],b[c])})};
epiviz.caja.buildChartMethodContext=function(){return{epiviz:{ui:{charts:epiviz.ui.charts,controls:epiviz.ui.controls},utils:epiviz.utils,plugins:epiviz.plugins,measurements:epiviz.measurements,events:epiviz.events,deferred:epiviz.deferred,datatypes:epiviz.datatypes,data:{DataProvider:epiviz.data.DataProvider,Request:epiviz.data.Request,Response:epiviz.data.Response,WebServerDataProvider:{makeGetRequest:epiviz.data.WebServerDataProvider.makeGetRequest}},Config:epiviz.Config},d3:d3,$:$,sprintf:sprintf,
goog:goog}};epiviz.events={};epiviz.events.EventListener=function(a){this._id=epiviz.events.EventListener._nextId++;this._updateCallback=a};epiviz.events.EventListener._nextId=0;epiviz.events.EventListener.prototype.update=function(a){this._updateCallback(a)};epiviz.events.EventListener.prototype.id=function(){return this._id};epiviz.ui={};epiviz.ui.WebArgsManager=function(a,b){this._locationManager=a;this._workspaceManager=b;this._registerLocationChanged();this._registerActiveWorkspaceChanged()};epiviz.ui.WebArgsManager.WEB_ARGS=epiviz.ui.WebArgsManager.WEB_ARGS||{};
epiviz.ui.WebArgsManager.extractWindowLocationArgs=function(){var a={};(0<window.location.search.length?window.location.search.substr(1):"").split("&").forEach(function(b,c){if(0!=b.trim().length){var d=b.indexOf("[]");if(0!=d){var e,f;f=b.indexOf("=");0>f?(e=0>d?b:b.substr(0,d),f="true"):(e=0>d?b.substr(0,f):b.substr(0,d),f=b.substr(f+1));e=decodeURIComponent(e);f=decodeURIComponent(f);0>d?a[e]=f:(e in a||(a[e]=[]),a[e].push(f))}}});return a};
epiviz.ui.WebArgsManager.prototype._updateUrl=function(){var a="?",b=epiviz.ui.WebArgsManager.WEB_ARGS;!("ws"in b)&&"workspace"in b&&(b.ws=b.workspace,delete b.workspace);for(var c in b)if(b.hasOwnProperty(c))if(Array.isArray(b[c]))for(var d,e=0;e<b[c].length;++e)d=b[c][e],void 0==d&&(d="undefined"),a+=sprintf("%s[]=%s&",c,d);else d=b[c],void 0==d&&(d="undefined"),a+=sprintf("%s=%s&",c,d);b=epiviz.utils.getInternetExplorerVersion();if(0>b||10<=b)switch(window.location.protocol){case "http:":case "https:":history.replaceState(null,
"",a)}};epiviz.ui.WebArgsManager.prototype._registerLocationChanged=function(){var a=this;this._locationManager.onCurrentLocationChanged().addListener(new epiviz.events.EventListener(function(b){epiviz.ui.WebArgsManager.WEB_ARGS.seqName=b.newValue.seqName();epiviz.ui.WebArgsManager.WEB_ARGS.start=b.newValue.start();epiviz.ui.WebArgsManager.WEB_ARGS.end=b.newValue.end();epiviz.ui.WebArgsManager.WEB_ARGS.genome=b.newValue.genome();a._updateUrl()}))};
epiviz.ui.WebArgsManager.prototype._registerActiveWorkspaceChanged=function(){var a=this;this._workspaceManager.onActiveWorkspaceChanged().addListener(new epiviz.events.EventListener(function(b){epiviz.ui.WebArgsManager.WEB_ARGS.ws=b.workspaceId||"";delete epiviz.ui.WebArgsManager.WEB_ARGS.workspace;a._updateUrl()}))};epiviz.events.Event=function(){this._count=0;this._listeners={};this._firing=!1};epiviz.events.Event.prototype.addListener=function(a){this._listeners[a.id()]||++this._count;this._listeners[a.id()]=a};epiviz.events.Event.prototype.removeListener=function(a){this._listeners[a.id()]&&(delete this._listeners[a.id()],--this._count)};
epiviz.events.Event.prototype.notify=function(a){if(!this._firing&&0!=this._count){this._firing=!0;for(var b in this._listeners)this._listeners.hasOwnProperty(b)&&this._listeners[b].update(a);this._firing=!1}};epiviz.events.Event.prototype.isFiring=function(){return this._firing};epiviz.data={};epiviz.data.MessageType={REQUEST:"request",RESPONSE:"response"};epiviz.data.Response=function(a,b){this._id=a;this._data=b};epiviz.data.Response.prototype.id=function(){return this._id};epiviz.data.Response.prototype.data=function(){var a=this._data,b=Object.keys(a);0<b.length&&-1!=b.indexOf("success")&&(b.splice(b.indexOf("success"),1),delete a.success);return a};epiviz.data.Response.prototype.type=function(){return epiviz.data.MessageType.RESPONSE};epiviz.data.Response.prototype.raw=function(){return{requestId:this._id,type:this.type(),data:this._data}};
epiviz.data.Response.fromRawObject=function(a){return new epiviz.data.Response(a.requestId,a.data)};epiviz.data.DataProvider=function(a){this._id=a;this._requestAddSeqInfos=new epiviz.events.Event;this._requestRemoveSeqNames=new epiviz.events.Event;this._requestAddMeasurements=new epiviz.events.Event;this._requestRemoveMeasurements=new epiviz.events.Event;this._requestAddChart=new epiviz.events.Event;this._requestRemoveChart=new epiviz.events.Event;this._requestFlushCache=new epiviz.events.Event;this._requestClearDatasourceGroupCache=new epiviz.events.Event;this._requestNavigate=new epiviz.events.Event;
this._requestRedraw=new epiviz.events.Event;this._requestCurrentLocation=new epiviz.events.Event;this._requestPrintWorkspace=new epiviz.events.Event;this._requestSetChartSettings=new epiviz.events.Event;this._requestGetChartSettings=new epiviz.events.Event;this._requestGetAvailableCharts=new epiviz.events.Event;this._requestLoadWorkspace=new epiviz.events.Event;this._requestUiStatus=new epiviz.events.Event;this._requestLoadMeasurements=new epiviz.events.Event};
epiviz.data.DataProvider.prototype.id=function(){return this._id};epiviz.data.DataProvider.prototype.getData=function(a,b){b(epiviz.data.Response.fromRawObject({requestId:a.id(),data:null}))};epiviz.data.DataProvider.prototype.onRequestAddSeqInfos=function(){return this._requestAddSeqInfos};epiviz.data.DataProvider.prototype.onRequestRemoveSeqNames=function(){return this._requestRemoveSeqNames};epiviz.data.DataProvider.prototype.onRequestAddMeasurements=function(){return this._requestAddMeasurements};
epiviz.data.DataProvider.prototype.onRequestRemoveMeasurements=function(){return this._requestRemoveMeasurements};epiviz.data.DataProvider.prototype.onRequestLoadMeasurements=function(){return this._requestLoadMeasurements};epiviz.data.DataProvider.prototype.onRequestAddChart=function(){return this._requestAddChart};epiviz.data.DataProvider.prototype.onRequestRemoveChart=function(){return this._requestRemoveChart};epiviz.data.DataProvider.prototype.onRequestFlushCache=function(){return this._requestFlushCache};
epiviz.data.DataProvider.prototype.onRequestClearDatasourceGroupCache=function(){return this._requestClearDatasourceGroupCache};epiviz.data.DataProvider.prototype.onRequestNavigate=function(){return this._requestNavigate};epiviz.data.DataProvider.prototype.onRequestRedraw=function(){return this._requestRedraw};epiviz.data.DataProvider.prototype.onRequestCurrentLocation=function(){return this._requestCurrentLocation};epiviz.data.DataProvider.prototype.onRequestPrintWorkspace=function(){return this._requestPrintWorkspace};
epiviz.data.DataProvider.prototype.onRequestGetChartSettings=function(){return this._requestGetChartSettings};epiviz.data.DataProvider.prototype.onRequestSetChartSettings=function(){return this._requestSetChartSettings};epiviz.data.DataProvider.prototype.onRequestGetAvailableCharts=function(){return this._requestGetAvailableCharts};epiviz.data.DataProvider.prototype.onRequestLoadWorkspace=function(){return this._requestLoadWorkspace};epiviz.data.DataProvider.prototype.onRequestUiStatus=function(){return this._requestUiStatus};epiviz.data.Request=function(a,b,c){this._id=a;this._args=b;this._method=c};epiviz.data.Request.Method={GET:"get",POST:"post"};
epiviz.data.Request.Action={GET_ROWS:"getRows",GET_VALUES:"getValues",GET_COMBINED:"getCombined",GET_MEASUREMENTS:"getMeasurements",SEARCH:"search",GET_SEQINFOS:"getSeqInfos",SAVE_WORKSPACE:"saveWorkspace",DELETE_WORKSPACE:"deleteWorkspace",GET_WORKSPACES:"getWorkspaces",GET_HIERARCHY:"getHierarchy",PROPAGATE_HIERARCHY_CHANGES:"propagateHierarchyChanges",GET_PCA:"getPCA",GET_DIVERSITY:"getDiversity",GET_CHART_SETTINGS:"getChartSettings",SET_CHART_SETTINGS:"setChartSettings",GET_AVAILABLE_CHARTS:"getAvailableCharts",
ADD_MEASUREMENTS:"addMeasurements",REMOVE_MEASUREMENTS:"removeMeasurements",LOAD_MEASUREMENTS:"loadMeasurements",ADD_SEQINFOS:"addSeqInfos",REMOVE_SEQNAMES:"removeSeqNames",ADD_CHART:"addChart",REMOVE_CHART:"removeChart",CLEAR_DATASOURCE_GROUP_CACHE:"clearDatasourceGroupCache",FLUSH_CACHE:"flushCache",NAVIGATE:"navigate",REDRAW:"redraw",GET_CURRENT_LOCATION:"getCurrentLocation",WRITE_DEBUG_MSG:"writeMsg",PRINT_WORKSPACE:"printWorkspace",LOAD_WORKSPACE:"loadWorkspace",REGISTER_CHART_TYPES:"registerChartTypes",
UI_STATUS:"uiStatus"};epiviz.data.Request.createRequest=function(a,b){return new epiviz.data.Request(epiviz.data.Request._nextId++,a,b||epiviz.data.Request.Method.GET)};epiviz.data.Request.fromRawObject=function(a){return new epiviz.data.Request(a.requestId,a.data)};epiviz.data.Request._nextId=0;epiviz.data.Request.prototype.id=function(){return this._id};epiviz.data.Request.prototype.type=function(){return epiviz.data.MessageType.REQUEST};epiviz.data.Request.prototype.method=function(){return this._method};
epiviz.data.Request.prototype.joinArgs=function(a,b){a=a||"=";b=b||"&";var c=sprintf("requestId%s%s",a,this._id),d;for(d in this._args)if(this._args.hasOwnProperty(d))if(Array.isArray(this._args[d]))for(var e=0;e<this._args[d].length;++e)c+=sprintf("%s%s[]%s%s",b,d,a,this._args[d][e]);else c+=sprintf("%s%s%s%s",b,d,a,this._args[d]||"");return c};epiviz.data.Request.prototype.isEmpty=function(){for(var a in this._args)if(this._args.hasOwnProperty(a))return!1;return!0};
epiviz.data.Request.prototype.get=function(a){return a in this._args?this._args[a]:null};epiviz.data.Request.prototype.raw=function(){return{requestId:this._id,type:this.type(),data:epiviz.utils.mapCopy(this._args)}};epiviz.data.Request.emptyRequest=function(){return epiviz.data.Request.createRequest({})};
epiviz.data.Request.getRows=function(a,b){return epiviz.data.Request.createRequest({version:epiviz.EpiViz.VERSION,action:epiviz.data.Request.Action.GET_ROWS,datasourceGroup:a.dataprovider(),datasource:a.id(),seqName:b?b.seqName():void 0,start:b?b.start():void 0,end:b?b.end():void 0,genome:b?b.genome():void 0,metadata:a.metadata()})};
epiviz.data.Request.getValues=function(a,b){return epiviz.data.Request.createRequest({version:epiviz.EpiViz.VERSION,action:epiviz.data.Request.Action.GET_VALUES,datasourceGroup:a.dataprovider(),datasource:a.datasource().id(),measurement:a.id(),seqName:b?b.seqName():void 0,start:b?b.start():void 0,end:b?b.end():void 0,genome:b?b.genome():void 0,bins:window.innerWidth})};
epiviz.data.Request.getCombined=function(a,b){var c={},d="",e="",f,g;for(g in a)d=g,a.hasOwnProperty(g)&&(c[g]=function(){var b=[];a[g].foreach(function(a){"genes"==d&&(f=a);e=a.dataprovider();b.push(a.id())});return b}());return"genes"==d?epiviz.data.Request.createRequest({version:epiviz.EpiViz.VERSION,action:epiviz.data.Request.Action.GET_COMBINED,seqName:b?b.seqName():void 0,start:b?b.start():void 0,end:b?b.end():void 0,measurements:c[d],datasourceGroup:e,datasource:d,genome:b?b.genome():void 0,
metadata:f.metadata()}):epiviz.data.Request.createRequest({version:epiviz.EpiViz.VERSION,action:epiviz.data.Request.Action.GET_COMBINED,seqName:b?b.seqName():void 0,start:b?b.start():void 0,end:b?b.end():void 0,measurements:c[d],datasource:d,datasourceGroup:e,genome:b?b.genome():void 0,bins:window.innerWidth})};epiviz.data.Request.getMeasurements=function(a){return epiviz.data.Request.createRequest({version:epiviz.EpiViz.VERSION,action:epiviz.data.Request.Action.GET_MEASUREMENTS,datasourceGroup:a})};
epiviz.data.Request.search=function(a,b){return epiviz.data.Request.createRequest({version:epiviz.EpiViz.VERSION,action:epiviz.data.Request.Action.SEARCH,q:a||"",maxResults:b})};epiviz.data.Request.getSeqInfos=function(a){return epiviz.data.Request.createRequest({version:epiviz.EpiViz.VERSION,action:epiviz.data.Request.Action.GET_SEQINFOS,datasourceGroup:a})};
epiviz.data.Request.saveWorkspace=function(a,b){return epiviz.data.Request.createRequest({version:epiviz.EpiViz.VERSION,action:epiviz.data.Request.Action.SAVE_WORKSPACE,id:a.id(),name:a.name(),content:encodeURIComponent(JSON.stringify(a.raw(b).content))},epiviz.data.Request.Method.POST)};epiviz.data.Request.deleteWorkspace=function(a){return epiviz.data.Request.createRequest({version:epiviz.EpiViz.VERSION,action:epiviz.data.Request.Action.DELETE_WORKSPACE,id:a.id()},epiviz.data.Request.Method.POST)};
epiviz.data.Request.getWorkspaces=function(a,b){return epiviz.data.Request.createRequest({version:epiviz.EpiViz.VERSION,action:epiviz.data.Request.Action.GET_WORKSPACES,q:a||"",ws:b})};epiviz.data.Request.getHierarchy=function(a,b){return epiviz.data.Request.createRequest({version:epiviz.EpiViz.VERSION,action:epiviz.data.Request.Action.GET_HIERARCHY,datasourceGroup:a,nodeId:b})};
epiviz.data.Request.propagateHierarchyChanges=function(a,b,c,d){return epiviz.data.Request.createRequest({version:epiviz.EpiViz.VERSION,action:epiviz.data.Request.Action.PROPAGATE_HIERARCHY_CHANGES,datasourceGroup:a,selection:b,order:c,selectedLevels:d})};
epiviz.data.Request.getPCA=function(a,b){var c={},d;for(d in a)a.hasOwnProperty(d)&&(c[d]=function(){var b=[];a[d].foreach(function(a){b.push(a.id())});return b}());return epiviz.data.Request.createRequest({version:epiviz.EpiViz.VERSION,action:epiviz.data.Request.Action.GET_PCA,measurements:c})};
epiviz.data.Request.getDiversity=function(a,b){var c={},d;for(d in a)a.hasOwnProperty(d)&&(c[d]=function(){var b=[];a[d].foreach(function(a){b.push(a.id())});return b}());return epiviz.data.Request.createRequest({version:epiviz.EpiViz.VERSION,action:epiviz.data.Request.Action.GET_DIVERSITY,measurements:c})};epiviz.measurements={};epiviz.measurements.Measurement=function(a,b,c,d,e,f,g,h,m,l,p,n){var t=epiviz.measurements.Measurement.Type;this._id=a;this._name=b;this._type=c;this._datasource=c==t.RANGE?this:new epiviz.measurements.Measurement(d,d,t.RANGE,d,e,f,null,"Blocks Track",null,null,null,n);this._datasourceGroup=e;this._dataprovider=f;this._formula=g||null;this._defaultChartType=h||null;this._annotation=m||null;this._minValue=l||0===l?l:null;this._maxValue=p||0===p?p:null;this._metadata=n||null};
epiviz.measurements.Measurement.Type={FEATURE:"feature",RANGE:"range",UNORDERED:"unordered"};epiviz.measurements.Measurement.Type.isOrdered=function(a){return a==epiviz.measurements.Measurement.Type.FEATURE||a==epiviz.measurements.Measurement.Type.RANGE};epiviz.measurements.Measurement.Type.hasValues=function(a){return a==epiviz.measurements.Measurement.Type.FEATURE||a==epiviz.measurements.Measurement.Type.UNORDERED};epiviz.measurements.Measurement.prototype.id=function(){return this._id};
epiviz.measurements.Measurement.prototype.name=function(){return this._name};epiviz.measurements.Measurement.prototype.type=function(){return this._type};epiviz.measurements.Measurement.prototype.datasource=function(){return this._datasource};epiviz.measurements.Measurement.prototype.datasourceId=function(){return this._datasource.id()};epiviz.measurements.Measurement.prototype.datasourceGroup=function(){return this._datasourceGroup};epiviz.measurements.Measurement.prototype.dataprovider=function(){return this._dataprovider};
epiviz.measurements.Measurement.prototype.formula=function(){return this._formula};epiviz.measurements.Measurement.prototype.formulaStr=function(){if(!this._formula)return"";var a=this._formula.referredMeasurements,b=this._formula.expression.toString(),c;for(c in a)a.hasOwnProperty(c)&&(b=b.replace(new RegExp("\\{"+c+"\\}","g")," {"+a[c].name()+"} "));return b};
epiviz.measurements.Measurement.prototype.evaluate=function(a){var b={},c;for(c in this._formula.referredMeasurements)if(this._formula.referredMeasurements.hasOwnProperty(c)){var d=this._formula.referredMeasurements[c];b["{"+c+"}"]=d.isComputed()?d.evaluate(a):a.get(d)}return this._formula.expression.evaluate(b)};
epiviz.measurements.Measurement.prototype.evaluateArr=function(a){var b={},c;for(c in this._formula.referredMeasurements)if(this._formula.referredMeasurements.hasOwnProperty(c)){var d=this._formula.referredMeasurements[c];b["{"+c+"}"]=d.isComputed()?d.evaluateArr(a):a.get(d)}return this._formula.expression.evaluateArr(b)};epiviz.measurements.Measurement.prototype.defaultChartType=function(){return this._defaultChartType};epiviz.measurements.Measurement.prototype.annotation=function(){return this._annotation};
epiviz.measurements.Measurement.prototype.minValue=function(){return this._minValue};epiviz.measurements.Measurement.prototype.maxValue=function(){return this._maxValue};epiviz.measurements.Measurement.prototype.metadata=function(){return this._metadata||[]};
epiviz.measurements.Measurement.prototype.componentMeasurements=function(){var a=new epiviz.measurements.MeasurementSet;if(!this._formula)return a.add(this),a;for(var b in this._formula.referredMeasurements)this._formula.referredMeasurements.hasOwnProperty(b)&&a.addAll(this._formula.referredMeasurements[b].componentMeasurements());return a};epiviz.measurements.Measurement.prototype.isComputed=function(){return this._formula?!0:!1};
epiviz.measurements.Measurement.prototype.raw=function(a){if(this._formula){var b=this._formula.referredMeasurements,c={},d;for(d in b)b.hasOwnProperty(d)&&(c[d]=a.get(b[d]))}return{id:this._id,name:this._name,type:this._type,datasourceId:this._datasource._id,datasourceGroup:this._datasourceGroup,dataprovider:this._dataprovider,formula:this._formula?{expression:this._formula.expression.toString(),referredMeasurements:c}:null,defaultChartType:this._defaultChartType,annotation:this._annotation,minValue:this._minValue,
maxValue:this._maxValue,metadata:this._metadata}};epiviz.measurements.Measurement.prototype.toString=function(){return this.name()};
epiviz.measurements.Measurement.fromRawObject=function(a,b){var c=null;if(a.formula){for(var c=epiviz.utils.ExpressionParser.parse(a.formula.expression),d={},e=c.variables(),f=0;f<e.length;++f)if(epiviz.utils.stringStartsWith(e[f],"{")&&epiviz.utils.stringEndsWith(e[f],"}")){var g=parseInt(e[f].substring(1,e[f].length-1));d[g]=b[a.formula.referredMeasurements[g]]}c={expression:c,referredMeasurements:d}}return new epiviz.measurements.Measurement(a.id,a.name,a.type,a.datasourceId,a.datasourceGroup,
a.dataprovider,c,a.defaultChartType,a.annotation,a.minValue,a.maxValue,a.metadata)};epiviz.utils.Iterable=function(){};epiviz.utils.Iterable.prototype.foreach=function(a){};epiviz.utils.Iterator=function(){};epiviz.utils.Iterator.prototype.first=function(){throw Error("unimplemented abstract method");};epiviz.utils.Iterator.prototype.next=function(){throw Error("unimplemented abstract method");};epiviz.measurements.MeasurementSet=function(a){this._measurementTree={};this._size=0;this._order=[];a&&this.addAll(a)};
epiviz.measurements.MeasurementSet.prototype.add=function(a){a.dataprovider()in this._measurementTree||(this._measurementTree[a.dataprovider()]={});a.type()in this._measurementTree[a.dataprovider()]||(this._measurementTree[a.dataprovider()][a.type()]={});a.datasourceGroup()in this._measurementTree[a.dataprovider()][a.type()]||(this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()]={});a.datasource().id()in this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()]||(this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()][a.datasource().id()]=
{});if(a.id()in this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()][a.datasource().id()])return!1;this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()][a.datasource().id()][a.id()]={measurement:a,index:this._order.length};this._order.push({measurement:a,contained:!0});++this._size;return!0};
epiviz.measurements.MeasurementSet.prototype.remove=function(a){if(!(a.dataprovider()in this._measurementTree&&a.type()in this._measurementTree[a.dataprovider()]&&a.datasourceGroup()in this._measurementTree[a.dataprovider()][a.type()]&&a.datasource().id()in this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()]&&a.id()in this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()][a.datasource().id()]))return!1;this._order[this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()][a.datasource().id()][a.id()].index].contained=
!1;delete this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()][a.datasource().id()][a.id()];--this._size;return!0};epiviz.measurements.MeasurementSet.prototype.addAll=function(a){if(!a||!a.size())return!1;var b=!1,c=this;a.foreach(function(a){c.add(a)&&(b=!0);return!1});return b};epiviz.measurements.MeasurementSet.prototype.removeAll=function(a){var b=!1,c=this;a.foreach(function(a){c.remove(a)&&(b=!0);return!1});return b};
epiviz.measurements.MeasurementSet.prototype.foreach=function(a,b){for(var c=this.iterator(),d=c.first(),e=0;null!==d&&(b&&!b(d)||!a(d,e));d=c.next(),++e);};epiviz.measurements.MeasurementSet.prototype.iterator=function(){return new epiviz.measurements.MeasurementSet.Iterator(this)};epiviz.measurements.MeasurementSet.prototype.subset=function(a){var b=new epiviz.measurements.MeasurementSet;this.foreach(function(a){b.add(a)},a);return b};
epiviz.measurements.MeasurementSet.prototype.map=function(a){var b=new epiviz.measurements.MeasurementSet;this.foreach(function(c){b.add(a(c))});return b};epiviz.measurements.MeasurementSet.prototype.size=function(){return this._size};
epiviz.measurements.MeasurementSet.prototype.contains=function(a){return a.dataprovider()in this._measurementTree&&a.type()in this._measurementTree[a.dataprovider()]&&a.datasourceGroup()in this._measurementTree[a.dataprovider()][a.type()]&&a.datasource().id()in this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()]?a.id()in this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()][a.datasource().id()]:!1};epiviz.measurements.MeasurementSet.prototype.first=function(){return this.iterator().first()};
epiviz.measurements.MeasurementSet.prototype.get=function(a){if(a>=this._size||0>a)return null;if(this._size==this._order.length)return this._order[a].measurement;var b=null;this.foreach(function(c,d){return d==a?(b=c,!0):!1});return b};epiviz.measurements.MeasurementSet.prototype.toArray=function(){var a=Array(this._size);this.foreach(function(b,c){a[c]=b});return a};
epiviz.measurements.MeasurementSet.prototype.sorted=function(a){a=this.toArray().sort(a);var b=new epiviz.measurements.MeasurementSet;a.forEach(function(a){b.add(a)});return b};epiviz.measurements.MeasurementSet.prototype.raw=function(){var a=Array(this._size);this.foreach(function(b,c){a[c]=b.raw()});return a};epiviz.measurements.MeasurementSet.prototype.remoteRaw=function(){var a=[];this.foreach(function(b,c){-1==b.dataprovider().indexOf("websocket")&&(a[c]=b.raw())});return a};
epiviz.measurements.MeasurementSet.prototype.split=function(a){var b={};this.foreach(function(c,d){var e=a(c),f=b[e];void 0==f&&(f=new epiviz.measurements.MeasurementSet,b[e]=f);f.add(c)});return b};epiviz.measurements.MeasurementSet.Iterator=function(a){this._parent=a;this._lastIndex=null};
epiviz.measurements.MeasurementSet.Iterator.prototype.first=function(){if(0==this._parent.size())return null;for(var a=0;a<this._parent._order.length;++a)if(this._parent._order[a].contained)return this._lastIndex=a,this._parent._order[a].measurement;throw Error("Inconsistent MeasurementSet with size() > 0 and no first element");};
epiviz.measurements.MeasurementSet.Iterator.prototype.next=function(){if(null===this._lastIndex)throw Error("Iterator.next() called before calling Iterator.first()");for(var a=this._lastIndex+1;a<this._parent._order.length;++a)if(this._parent._order[a].contained)return this._lastIndex=a,this._parent._order[a].measurement;this._lastIndex=this._parent._order.length;return null};epiviz.ui.controls={};epiviz.ui.controls.VisConfigSelection=function(a,b,c,d,e,f,g,h){this.measurements=a;this.datasource=b;this.datasourceGroup=c;this.dataprovider=d;this.annotation=e;this.defaultChartType=f;this.minSelectedMeasurements=g||1;this.customData=h};epiviz.events.EventResult=function(){this.value=this.errorMessage=this.success=null};epiviz.datatypes={};epiviz.datatypes.GenomicRange=function(a,b,c,d){void 0!=c&&0>c&&(c=-c,void 0!=b&&(b-=c));this._seqname=a;this._start=b;this._width=c;this._genome=d};epiviz.datatypes.GenomicRange.fromStartEnd=function(a,b,c,d){return new epiviz.datatypes.GenomicRange(a,b,void 0!=b&&void 0!=c?c-b:void 0,d)};epiviz.datatypes.GenomicRange.prototype.seqName=function(){return this._seqname};epiviz.datatypes.GenomicRange.prototype.genome=function(){return this._genome};
epiviz.datatypes.GenomicRange.prototype.start=function(){return this._start};epiviz.datatypes.GenomicRange.prototype.width=function(){return this._width};epiviz.datatypes.GenomicRange.prototype.end=function(){return void 0!=this._start&&void 0!=this._width?this._start+this._width:void 0};epiviz.datatypes.GenomicRange.prototype.isEmpty=function(){return 0>=this._width};
epiviz.datatypes.GenomicRange.prototype.subtract=function(a){return!a||a.genome()!=this._genome||a.seqName()!=this._seqname||a.isEmpty()||a.start()>=this.end()||this._start>=a.end()?[this]:a.start()<=this._start&&a.end()>=this.end()?[]:a.start()>this._start&&a.end()<this.end()?[epiviz.datatypes.GenomicRange.fromStartEnd(this._seqname,this._start,a.start()),epiviz.datatypes.GenomicRange.fromStartEnd(this._seqname,a.end(),this.end())]:a.start()>this._start?[epiviz.datatypes.GenomicRange.fromStartEnd(this._seqname,
this._start,a.start(),this._genome)]:[epiviz.datatypes.GenomicRange.fromStartEnd(this._seqname,a.end(),this.end(),this._genome)]};epiviz.datatypes.GenomicRange.prototype.equals=function(a){return a?a==this?!0:this._genome==a._genome&&this._seqname==a._seqname&&this._start==a._start&&this._width==a._width:!1};epiviz.datatypes.GenomicRange.prototype.overlapsWith=function(a){return a?this==a?!0:this.genome()!=a.genome()||this.seqName()!=a.seqName()?!1:this.start()<a.end()&&this.end()>a.start():!1};
epiviz.datatypes.GenomicRange.prototype.raw=function(){return{seqName:this._seqname,start:this._start,width:this._width,genome:this._genome}};epiviz.datatypes.GenomicRange.fromRawObject=function(a){return new epiviz.datatypes.GenomicRange(a.seqName,a.start,a.width,a.genome)};epiviz.ui.charts={};epiviz.ui.charts.ColorPalette=function(a,b,c,d){this._colors=a;this._id=c||epiviz.utils.generatePseudoGUID(6);this._name=b||"Custom ("+this._id+")";this._keyIndices=d||{};this._nKeys=0};epiviz.ui.charts.ColorPalette.prototype.id=function(){return this._id};epiviz.ui.charts.ColorPalette.prototype.name=function(){return this._name};epiviz.ui.charts.ColorPalette.prototype.get=function(a){return this._colors[a%this._colors.length]};
epiviz.ui.charts.ColorPalette.prototype.getByKey=function(a){var b=this._keyIndices[a];void 0==b&&(b=this._nKeys,this._keyIndices[a]=this._nKeys,++this._nKeys);return this.get(b)};epiviz.ui.charts.ColorPalette.prototype.keyColorIndex=function(a){a=this._keyIndices[a];return void 0==a?-1:a};epiviz.ui.charts.ColorPalette.prototype.keyIndices=function(){return this._keyIndices};epiviz.ui.charts.ColorPalette.prototype.size=function(){return this._colors.length};
epiviz.ui.charts.ColorPalette.prototype.equals=function(a){return this==a?!0:a?epiviz.utils.arraysEqual(this._colors,a._colors):!1};epiviz.ui.charts.ColorPalette.prototype.copy=function(){return new epiviz.ui.charts.ColorPalette(this._colors.slice(0))};epiviz.ui.charts.ColorPalette.prototype.raw=function(a){return a&&this._id in a.colorPalettesMap?{id:this._id}:{id:this._id,name:this._name,colors:this._colors}};
epiviz.ui.charts.ColorPalette.fromRawObject=function(a,b){if($.isArray(a))return new epiviz.ui.charts.ColorPalette(a);if(b&&a.id in b.colorPalettesMap)return b.colorPalettesMap[a.id];a.colors&&a.colors.length||(a.colors=epiviz.Config.COLORS_BRIGHT);return new epiviz.ui.charts.ColorPalette(a.colors,a.name,a.id)};epiviz.data.WebsocketDataProvider=function(a,b){epiviz.data.DataProvider.call(this,a||epiviz.data.WebsocketDataProvider.DEFAULT_ID);this._websocketHost=b;this._socket=null;this._useUI="true"!=epiviz.ui.WebArgsManager.WEB_ARGS.websocketNoUI;this._debug="true"==epiviz.ui.WebArgsManager.WEB_ARGS.debug;this._callbacks={};this._requestsStack=[];this._initialize()};epiviz.data.WebsocketDataProvider.prototype=epiviz.utils.mapCopy(epiviz.data.DataProvider.prototype);
epiviz.data.WebsocketDataProvider.constructor=epiviz.data.WebsocketDataProvider;epiviz.data.WebsocketDataProvider.DEFAULT_ID="websocket";
epiviz.data.WebsocketDataProvider.prototype._initialize=function(){if(this._websocketHost&&"None"!=this._websocketHost)try{this._socket=new WebSocket(this._websocketHost);this._log("WebSocket - status "+this._socket.readyState);var a=this;this._socket.onopen=function(){a._onSocketOpen()};this._socket.onmessage=function(b){a._onSocketMessage(b)};this._socket.onclose=function(){a._onSocketClose()}}catch(b){this._log(b.toString())}};
epiviz.data.WebsocketDataProvider.prototype._onSocketOpen=function(){for(var a=0;a<this._requestsStack.length;++a)this._socket.send(this._requestsStack[a]);this._requestsStack=[];this._registerAvailableCharts()};epiviz.data.WebsocketDataProvider.prototype._onSocketClose=function(){this._socket=null};epiviz.data.WebsocketDataProvider.prototype._sendMessage=function(a){this.connected()&&this._socket.readyState?this._socket.send(a):this._requestsStack.push(a)};
epiviz.data.WebsocketDataProvider.prototype._onSocketMessage=function(a){this._log("Local Controller Received: "+a.data);var b=JSON.parse(a.data);b.data.dataprovidertype="websocket";if(b.type==epiviz.data.MessageType.RESPONSE)a=epiviz.data.Response.fromRawObject(b),b=this._callbacks[a.id()],delete this._callbacks[a.id()],b(a);else if(b.type==epiviz.data.MessageType.REQUEST)switch(a=epiviz.data.Request.Action,b=epiviz.data.Request.fromRawObject(b),b.get("action")){case a.ADD_MEASUREMENTS:this._addMeasurements(b);
break;case a.REMOVE_MEASUREMENTS:this._removeMeasurements(b);break;case a.ADD_SEQINFOS:this._addSeqInfos(b);break;case a.REMOVE_SEQNAMES:this._removeSeqNames(b);break;case a.ADD_CHART:this._addChart(b);break;case a.REMOVE_CHART:this._removeChart(b);break;case a.CLEAR_DATASOURCE_GROUP_CACHE:this._clearDatasourceGroupCache(b);break;case a.FLUSH_CACHE:this._flushCache(b);break;case a.NAVIGATE:this._navigate(b);break;case a.REDRAW:this._redraw(b);break;case a.GET_CURRENT_LOCATION:this._getCurrentLocation(b);
break;case a.WRITE_DEBUG_MSG:this._writeDebugMsg(b);break;case a.PRINT_WORKSPACE:this._printWorkspace(b);break;case a.SET_CHART_SETTINGS:this._setChartSettings(b);break;case a.GET_CHART_SETTINGS:this._getChartSettings(b);break;case a.GET_AVAILABLE_CHARTS:this._getAvailableCharts(b);break;case a.LOAD_WORKSPACE:this._loadWorkspace(b);break;case a.UI_STATUS:this._uiStatus(b);case a.LOAD_MEASUREMENTS:this._loadMeasurements(b)}};
epiviz.data.WebsocketDataProvider.prototype._log=function(a){this._debug&&console.log(a)};epiviz.data.WebsocketDataProvider.prototype._fireEvent=function(a,b){this._useUI?a.notify(b):b.result.success=!0};epiviz.data.WebsocketDataProvider.prototype.connected=function(){return null!=this._socket};epiviz.data.WebsocketDataProvider.prototype.getData=function(a,b){var c=JSON.stringify(a.raw());this._callbacks[a.id()]=b;this._sendMessage(c)};
epiviz.data.WebsocketDataProvider.prototype._addMeasurements=function(a){for(var b=new epiviz.events.EventResult,c=new epiviz.measurements.MeasurementSet,d=JSON.parse(a.get("measurements")),e=0;e<d.length;++e)c.add(new epiviz.measurements.Measurement(d[e].id,d[e].name,d[e].type,d[e].datasourceId,d[e].datasourceGroup,this.id(),null,d[e].defaultChartType,d[e].annotation,d[e].minValue,d[e].maxValue,d[e].metadata));this._fireEvent(this.onRequestAddMeasurements(),{measurements:c,result:b});a=new epiviz.data.Response(a.id(),
b);this._sendMessage(JSON.stringify(a.raw()))};
epiviz.data.WebsocketDataProvider.prototype._removeMeasurements=function(a){for(var b=new epiviz.events.EventResult,c=new epiviz.measurements.MeasurementSet,d=JSON.parse(a.get("measurements")),e=0;e<d.length;++e)c.add(new epiviz.measurements.Measurement(d[e].id,d[e].name,d[e].type,d[e].datasourceId,d[e].datasourceGroup,this.id(),null,d[e].defaultChartType,d[e].annotation,d[e].minValue,d[e].maxValue,d[e].metadata));this._fireEvent(this.onRequestRemoveMeasurements(),{measurements:c,result:b});a=new epiviz.data.Response(a.id(),
b);this._sendMessage(JSON.stringify(a.raw()))};epiviz.data.WebsocketDataProvider.prototype._addSeqInfos=function(a){var b=new epiviz.events.EventResult,c=JSON.parse(a.get("seqInfos"));this._fireEvent(this.onRequestAddSeqInfos(),{seqInfos:c,result:b});a=new epiviz.data.Response(a.id(),b);this._sendMessage(JSON.stringify(a.raw()))};
epiviz.data.WebsocketDataProvider.prototype._removeSeqNames=function(a){var b=new epiviz.events.EventResult,c=JSON.parse(a.get("seqNames"));this._fireEvent(this.onRequestRemoveSeqNames(),{seqNames:c,result:b});a=new epiviz.data.Response(a.id(),b);this._sendMessage(JSON.stringify(a.raw()))};
epiviz.data.WebsocketDataProvider.prototype._addChart=function(a){var b=new epiviz.events.EventResult,c,d,e;d=this.id();if(void 0!=a.get("measurements")){c=new epiviz.measurements.MeasurementSet;e=JSON.parse(a.get("measurements"));for(var f=0;f<e.length;++f)null!=e[f].dataprovider&&0!=e[f].dataprovider.length&&(d=e[f].dataprovider),c.add(new epiviz.measurements.Measurement(e[f].id,e[f].name,e[f].type,e[f].datasourceId,e[f].datasourceGroup,d,null,e[f].defaultChartType,e[f].annotation,e[f].minValue,
e[f].maxValue,e[f].metadata))}d=a.get("datasource");e=a.get("datasourceGroup")||d;this._fireEvent(this.onRequestAddChart(),{type:a.get("type"),visConfigSelection:new epiviz.ui.controls.VisConfigSelection(c,d,e),result:b});a=new epiviz.data.Response(a.id(),b);this._sendMessage(JSON.stringify(a.raw()))};
epiviz.data.WebsocketDataProvider.prototype._removeChart=function(a){var b=a.get("chartId"),c=new epiviz.events.EventResult;this._fireEvent(this.onRequestRemoveChart(),{id:b,result:c});a=new epiviz.data.Response(a.id(),c);this._sendMessage(JSON.stringify(a.raw()))};
epiviz.data.WebsocketDataProvider.prototype._clearDatasourceGroupCache=function(a){var b=new epiviz.events.EventResult;this._fireEvent(this.onRequestClearDatasourceGroupCache(),{datasourceGroup:a.get("datasourceGroup"),result:b});a=new epiviz.data.Response(a.id(),b);this._sendMessage(JSON.stringify(a.raw()))};
epiviz.data.WebsocketDataProvider.prototype._flushCache=function(a){var b=new epiviz.events.EventResult;this._fireEvent(this.onRequestFlushCache(),{result:b});a=new epiviz.data.Response(a.id(),b);this._sendMessage(JSON.stringify(a.raw()))};
epiviz.data.WebsocketDataProvider.prototype._navigate=function(a){var b=JSON.parse(a.get("range")),c=new epiviz.events.EventResult;this._fireEvent(this.onRequestNavigate(),{range:epiviz.datatypes.GenomicRange.fromStartEnd(b.seqName,b.start,b.end),result:c});a=new epiviz.data.Response(a.id(),c);this._sendMessage(JSON.stringify(a.raw()))};
epiviz.data.WebsocketDataProvider.prototype._redraw=function(a){var b=new epiviz.events.EventResult;this._fireEvent(this.onRequestRedraw(),{result:b});a=new epiviz.data.Response(a.id(),b);this._sendMessage(JSON.stringify(a.raw()))};epiviz.data.WebsocketDataProvider.prototype._getCurrentLocation=function(a){var b=new epiviz.events.EventResult;this._fireEvent(this.onRequestCurrentLocation(),{result:b});a=new epiviz.data.Response(a.id(),b);this._sendMessage(JSON.stringify(a.raw()))};
epiviz.data.WebsocketDataProvider.prototype._writeDebugMsg=function(a){var b=a.get("msg"),c=document.createElement("pre");c.innerHTML=b.replace(/&/g,"&amp;").replace(/\\</g,"&lt;");a=new epiviz.data.Response(a.id(),{msg:"that msg"});document.getElementById("chart-container").appendChild(c);this._sendMessage(JSON.stringify(a.raw()))};
epiviz.data.WebsocketDataProvider.prototype._printWorkspace=function(a){var b=a.get("chartId"),c=a.get("fileName"),d=a.get("fileType"),e=new epiviz.events.EventResult;this._fireEvent(this.onRequestPrintWorkspace(),{chartId:b,fileName:c,fileType:d,result:e});a=new epiviz.data.Response(a.id(),e);this._sendMessage(JSON.stringify(a.raw()))};
epiviz.data.WebsocketDataProvider.prototype._setChartSettings=function(a){var b=a.get("chartId"),c=a.get("settings"),d=a.get("colorMap"),d=new epiviz.ui.charts.ColorPalette(d),e=new epiviz.events.EventResult;this._fireEvent(this.onRequestSetChartSettings(),{chartId:b,settings:c,colorMap:d,result:e});a=new epiviz.data.Response(a.id(),e);this._sendMessage(JSON.stringify(a.raw()))};
epiviz.data.WebsocketDataProvider.prototype._getChartSettings=function(a){var b=a.get("chartId"),c=new epiviz.events.EventResult;this._fireEvent(this.onRequestGetChartSettings(),{chartId:b,result:c});a=new epiviz.data.Response(a.id(),c);this._sendMessage(JSON.stringify(a.raw()))};epiviz.data.WebsocketDataProvider.prototype._getAvailableCharts=function(a){var b=new epiviz.events.EventResult;this._fireEvent(this.onRequestGetChartSettings(),{result:b});a=new epiviz.data.Response(a.id(),b);this._sendMessage(JSON.stringify(a.raw()))};
epiviz.data.WebsocketDataProvider.prototype._registerAvailableCharts=function(){var a=new epiviz.events.EventResult;this._fireEvent(this.onRequestGetChartSettings(),{result:a});request=epiviz.data.Request.createRequest({action:epiviz.data.Request.Action.REGISTER_CHART_TYPES,data:a.value});a=JSON.stringify(request.raw());this._callbacks[request.id()]=function(a){};this._sendMessage(a)};
epiviz.data.WebsocketDataProvider.prototype.updateChartSettings=function(a,b){var c=JSON.stringify(a.raw());this._callbacks[a.id()]=b;this._sendMessage(c)};epiviz.data.WebsocketDataProvider.prototype._loadWorkspace=function(a){var b=a.get("workspaceId"),c=new epiviz.events.EventResult;this._fireEvent(this.onRequestLoadWorkspace(),{workspace:b});a=new epiviz.data.Response(a.id(),c);this._sendMessage(JSON.stringify(a.raw()))};
epiviz.data.WebsocketDataProvider.prototype._uiStatus=function(a){var b=new epiviz.events.EventResult;this._fireEvent(this.onRequestUiStatus(),{result:b});a=new epiviz.data.Response(a.id(),b);this._sendMessage(JSON.stringify(a.raw()))};epiviz.data.WebsocketDataProvider.prototype._loadMeasurements=function(a){var b=new epiviz.events.EventResult;this._fireEvent(this.onRequestLoadMeasurements(),{result:b});a=new epiviz.data.Response(a.id(),b);this._sendMessage(JSON.stringify(a.raw()))};epiviz.data.WebServerDataProvider=function(a,b){epiviz.data.DataProvider.call(this,a||epiviz.Config.DEFAULT_DATA_PROVIDER_ID);this._serverEndpoint=b||epiviz.data.WebServerDataProvider.DEFAULT_SERVER_ENDPOINT};epiviz.data.WebServerDataProvider.prototype=epiviz.utils.mapCopy(epiviz.data.DataProvider.prototype);epiviz.data.WebServerDataProvider.constructor=epiviz.data.WebServerDataProvider;epiviz.data.WebServerDataProvider.DEFAULT_SERVER_ENDPOINT="data/main.php";
epiviz.data.WebServerDataProvider.prototype.getData=function(a,b,c){a.isEmpty()||(a.method()==epiviz.data.Request.Method.GET?(a=sprintf("%s?%s",this._serverEndpoint,a.joinArgs()),epiviz.data.WebServerDataProvider.makeGetRequest(a,function(a){b(epiviz.data.Response.fromRawObject(a))},function(a,b,f){c(a,b,f)})):epiviz.data.WebServerDataProvider.makePostRequest(this._serverEndpoint,a.joinArgs(),function(a){b(epiviz.data.Response.fromRawObject(a))},function(a,b,f){c(a,b,f)}))};
epiviz.data.WebServerDataProvider.makeGetRequest=function(a,b,c){a=$.ajax({type:"get",url:a,dataType:"json",async:!0,cache:!1,processData:!0});a.done(function(a){b(a)});a.fail(function(a,b,f){c(a,b,f)});a.always(function(){})};epiviz.data.WebServerDataProvider.makePostRequest=function(a,b,c,d){a=$.ajax({type:"post",url:a,data:b,dataType:"json",async:!0,cache:!1,processData:!0});a.done(function(a,b,d){c(a)});a.fail(function(a,b,c){console.error("The following error occured: "+b,c);d(a,b,c)});a.always(function(){})};epiviz.data.ShinyDataProvider=function(a){epiviz.data.DataProvider.call(this,a||epiviz.Config.DEFAULT_DATA_PROVIDER_ID);this._shinycallbacks={};this._requestQueue=[];this._registered=this._requestInProgress=!1;this.providerId=Date.now().toString();Shiny.onInputChange("registerProvider",{id:this.providerId,".nounce":Math.random()});Shiny.addCustomMessageHandler(this.providerId+".callback",this.callbackHandler.bind(this));Shiny.addCustomMessageHandler(this.providerId+".registration",this.registrationHandler.bind(this))};
epiviz.data.ShinyDataProvider.prototype._initialize=function(){Shiny.addCustomMessageHandler(this.providerId+".callback",this.callbackHandler.bind(this));Shiny.addCustomMessageHandler(this.providerId+".registration",this.registrationHandler.bind(this))};epiviz.data.ShinyDataProvider.prototype=epiviz.utils.mapCopy(epiviz.data.DataProvider.prototype);epiviz.data.ShinyDataProvider.constructor=epiviz.data.ShinyDataProvider;
epiviz.data.ShinyDataProvider.prototype.getData=function(a,b){if(!a.isEmpty())if(0==Object.keys(this._shinycallbacks).length&&this._registered){var c=Date.now(),d=a.id();this._shinycallbacks[c]=[b,d];d={};d._method=a._args.action;d._reqid=c;d._args=a._args;d[".nounce"]=Math.random();Shiny.onInputChange(this.providerId,d)}else this._requestQueue.push([a,b])};
epiviz.data.ShinyDataProvider.prototype.registrationHandler=function(a){a.success&&(this._registered=!0,0<this._requestQueue.length&&(a=this._requestQueue.shift(),this.getData(a[0],a[1])))};
epiviz.data.ShinyDataProvider.prototype.callbackHandler=function(a){a.data=JSON.parse(a.data);"epivizr"==a.jsonType&&(a.data.values=JSON.parse(a.data.values),a.data.rows=JSON.parse(a.data.rows),null==a.data.rows.values.metadata&&(a.data.rows.values.metadata={}));a=epiviz.data.Response.fromRawObject(a);var b=a.id(),c=this._shinycallbacks[b][0];a._id=this._shinycallbacks[b][1];delete this._shinycallbacks[b];0<this._requestQueue.length&&(b=this._requestQueue.shift(),this.getData(b[0],b[1]));c(a)};epiviz.Config=function(a){this.workspacesDataProvider=this.dataProviders=this.defaultWorkspaceSettings=this.navigationDelay=this.navigationStepRatio=this.zoomoutRatio=this.zoominRatio=this.chartSaverLocation=this.dataServerLocation=null;this.useCache=!0;this.useCookie=null;this.cacheUpdateIntervalMilliseconds=3E4;this.colorPalettesMap=this.colorPalettes=this.clustering=this.chartCustomSettings=this.chartSettings=this.chartTypes=this.maxSearchResults=null;if(a){for(var b in a)a.hasOwnProperty(b)&&
(this[b]=a[b]);if("epivizr_standalone"!=a.configType&&(b=epiviz.ui.WebArgsManager.WEB_ARGS["websocket-host"])&&b.length)for(var c=0;c<b.length;++c)this.dataProviders.push(sprintf("epiviz.data.WebsocketDataProvider,%s,%s",epiviz.data.WebsocketDataProvider.DEFAULT_ID+"-"+c,b[c]))}var d={};this.colorPalettes.forEach(function(a){d[a.id()]=a});this.colorPalettesMap=d;"default"!=a.configType&&(this.useCookie=epiviz.ui.WebArgsManager.WEB_ARGS.useCookie)};epiviz.Config.SETTINGS={};
epiviz.Config.DEFAULT_DATA_PROVIDER_ID="umd";epiviz.Config.DEFAULT_WORKSPACE_NAME="Default Workspace";epiviz.Config.EPIVIZ_V1_COLORS="#025167 #e7003e #ffcd00 #057d9f #970026 #ffe373 #ff8100".split(" ");epiviz.Config.COLORS_BRIGHT="#1859a9 #ed2d2e #008c47 #010101 #f37d22 #662c91 #a11d20 #b33893".split(" ");epiviz.Config.COLORS_LIGHT="#b8d2eb #f2aeac #d8e4aa #cccccc #f2d1b0 #d4b2d3 #ddb8a9 #ebbfd9".split(" ");epiviz.Config.COLORS_MEDIUM="#599ad3 #f1595f #79c36a #727272 #f9a65a #9e66ab #cd7058 #d77fb3".split(" ");
epiviz.Config.COLORS_D3_CAT10="#1f77b4 #ff7f0e #2ca02c #d62728 #9467bd #8c564b #e377c2 #7f7f7f #bcbd22 #17becf".split(" ");epiviz.Config.COLORS_D3_CAT20="#1f77b4 #aec7e8 #ff7f0e #ffbb78 #2ca02c #98df8a #d62728 #ff9896 #9467bd #c5b0d5 #8c564b #c49c94 #e377c2 #f7b6d2 #7f7f7f #c7c7c7 #bcbd22 #dbdb8d #17becf #9edae5".split(" ");epiviz.Config.COLORS_D3_CAT20B="#393b79 #5254a3 #6b6ecf #9c9ede #637939 #8ca252 #b5cf6b #cedb9c #8c6d31 #bd9e39 #e7ba52 #e7cb94 #843c39 #ad494a #d6616b #e7969c #7b4173 #a55194 #ce6dbd #de9ed6".split(" ");
epiviz.Config.COLORS_D3_CAT20C="#3182bd #6baed6 #9ecae1 #c6dbef #e6550d #fd8d3c #fdae6b #fdd0a2 #31a354 #74c476 #a1d99b #c7e9c0 #756bb1 #9e9ac8 #bcbddc #dadaeb #636363 #969696 #bdbdbd #d9d9d9".split(" ");epiviz.Config.VisualizationPropertySettings={WIDTH:"width",HEIGHT:"height",MARGINS:"margins",COLORS:"colors",DECORATIONS:"decorations"};epiviz.data.RequestStack=function(){this._requests=[];this._callbacks={};this._dataMap={}};epiviz.data.RequestStack.prototype.pushRequest=function(a,b){this._requests.push(a);this._callbacks[a.id()]=b};
epiviz.data.RequestStack.prototype.serveData=function(a){if(this._callbacks[a.id()])if(0<this._requests.length&&this._requests[0].id()==a.id()){var b=this._callbacks[a.id()];delete this._callbacks[a.id()];this._requests=this._requests.slice(1);for(b(a.data());0<this._requests.length&&this._requests[0].id()in this._dataMap;)b=this._callbacks[this._requests[0].id()],a=this._dataMap[this._requests[0].id()],delete this._callbacks[this._requests[0].id()],delete this._dataMap[this._requests[0].id()],this._requests=
this._requests.slice(1),b(a)}else this._dataMap[a.id()]=a.data()};epiviz.data.RequestStack.prototype.clear=function(){this._requests=[];this._callbacks={};this._dataMap={}};epiviz.measurements.MeasurementHashtable=function(){this._size=0;this._measurementTree={};this._order=[]};
epiviz.measurements.MeasurementHashtable.prototype.put=function(a,b){if(this.contains(a)){var c=this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()][a.datasource().id()][a.id()];this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()][a.datasource().id()][a.id()]={key:a,value:b,index:c.index};this._order[c.index]={key:a,value:b,contained:!0}}else a.dataprovider()in this._measurementTree||(this._measurementTree[a.dataprovider()]={}),a.type()in this._measurementTree[a.dataprovider()]||
(this._measurementTree[a.dataprovider()][a.type()]={}),a.datasourceGroup()in this._measurementTree[a.dataprovider()][a.type()]||(this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()]={}),a.datasource().id()in this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()]||(this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()][a.datasource().id()]={}),this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()][a.datasource().id()][a.id()]={key:a,
value:b,index:this._order.length},this._order.push({key:a,value:b,contained:!0}),++this._size};epiviz.measurements.MeasurementHashtable.prototype.get=function(a){return this.contains(a)?this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()][a.datasource().id()][a.id()].value:null};
epiviz.measurements.MeasurementHashtable.prototype.remove=function(a){if(!this.contains(a))return!1;var b=this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()][a.datasource().id()][a.id()];delete this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()][a.datasource().id()][a.id()];this._order[b.index].contained=!1;--this._size;return!0};
epiviz.measurements.MeasurementHashtable.prototype.contains=function(a){return a.dataprovider()in this._measurementTree&&a.type()in this._measurementTree[a.dataprovider()]&&a.datasourceGroup()in this._measurementTree[a.dataprovider()][a.type()]&&a.datasource().id()in this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()]?a.id()in this._measurementTree[a.dataprovider()][a.type()][a.datasourceGroup()][a.datasource().id()]:!1};
epiviz.measurements.MeasurementHashtable.prototype.clear=function(){this._size=0;this._measurementTree={};this._order=[]};epiviz.measurements.MeasurementHashtable.prototype.isEmpty=function(){return 0==this._size};epiviz.measurements.MeasurementHashtable.prototype.size=function(){return this._size};epiviz.measurements.MeasurementHashtable.prototype.foreach=function(a,b){for(var c=this.iterator(),d=c.first(),e=0;null!==d&&(b&&!b(d.key)||!a(d.key,d.value,e));d=c.next(),++e);};
epiviz.measurements.MeasurementHashtable.prototype.first=function(){return this.iterator().first()};epiviz.measurements.MeasurementHashtable.prototype.sorted=function(a){var b=new epiviz.measurements.MeasurementHashtable,c=this._order.slice(0);c.sort(function(b,c){return a(b.key,c.key)});c.forEach(function(a){a.contained&&b.put(a.key,a.value)});return b};epiviz.measurements.MeasurementHashtable.prototype.keys=function(){var a=[];this._order.forEach(function(b){b.contained&&a.push(b.key)});return a};
epiviz.measurements.MeasurementHashtable.prototype.iterator=function(){return new epiviz.measurements.MeasurementHashtable.Iterator(this)};epiviz.measurements.MeasurementHashtable.prototype.toString=function(){var a={};this.foreach(function(b,c){for(var d=b.id(),e=2;d in a;)d=b.id()+" ("+e++ +")";a[d]=c});return JSON.stringify(a)};epiviz.measurements.MeasurementHashtable.Iterator=function(a){this._parent=a;this._lastIndex=null};
epiviz.measurements.MeasurementHashtable.Iterator.prototype.first=function(){if(0==this._parent.size())return null;for(var a=0;a<this._parent._order.length;++a)if(this._parent._order[a].contained)return this._lastIndex=a,{key:this._parent._order[a].key,value:this._parent._order[a].value};throw Error("Inconsistent MeasurementHashtable with size() > 0 and no first element");};
epiviz.measurements.MeasurementHashtable.Iterator.prototype.next=function(){if(null===this._lastIndex)throw Error("Iterator.next() called before calling Iterator.first()");for(var a=this._lastIndex+1;a<this._parent._order.length;++a)if(this._parent._order[a].contained)return this._lastIndex=a,{key:this._parent._order[a].key,value:this._parent._order[a].value};this._lastIndex=this._parent._order.length;return null};
epiviz.measurements.MeasurementHashtable.prototype.split=function(a){var b={};this.foreach(function(c,d){var e=a(c),f=b[e];void 0==f&&(f=new epiviz.measurements.MeasurementSet,b[e]=f);f.add(c)});return b};epiviz.datatypes.MeasurementGenomicData=function(){};epiviz.datatypes.MeasurementGenomicData.prototype.get=function(a){throw Error("unimplemented abstract method");};epiviz.datatypes.MeasurementGenomicData.prototype.getRow=function(a){throw Error("unimplemented abstract method");};epiviz.datatypes.MeasurementGenomicData.prototype.measurement=function(){throw Error("unimplemented abstract method");};
epiviz.datatypes.MeasurementGenomicData.prototype.globalStartIndex=function(){throw Error("unimplemented abstract method");};epiviz.datatypes.MeasurementGenomicData.prototype.globalEndIndex=function(){throw Error("unimplemented abstract method");};epiviz.datatypes.MeasurementGenomicData.prototype.size=function(){throw Error("unimplemented abstract method");};epiviz.datatypes.MeasurementGenomicData.prototype.getByGlobalIndex=function(a){throw Error("unimplemented abstract method");};
epiviz.datatypes.MeasurementGenomicData.prototype.getRowByGlobalIndex=function(a){throw Error("unimplemented abstract method");};epiviz.datatypes.MeasurementGenomicData.prototype.binarySearchStarts=function(a){throw Error("unimplemented abstract method");};epiviz.datatypes.MeasurementGenomicDataArrayWrapper=function(a,b,c){this._measurement=a;this._items=b;this._itemsByGlobalIndex=c};epiviz.datatypes.MeasurementGenomicDataArrayWrapper.prototype.get=function(a){return this._items&&0<=a&&a<this._items.length?this._items[a]:null};epiviz.datatypes.MeasurementGenomicDataArrayWrapper.prototype.getRow=function(a){return this._items&&0<=a&&a<this._items.length?this._items[a].rowItem:null};
epiviz.datatypes.MeasurementGenomicDataArrayWrapper.prototype.measurement=function(){return this._measurement};epiviz.datatypes.MeasurementGenomicDataArrayWrapper.prototype.globalStartIndex=function(){return this._items&&this._items.length?this._items[0].globalIndex:null};epiviz.datatypes.MeasurementGenomicDataArrayWrapper.prototype.globalEndIndex=function(){return this._items&&this._items.length?this._items[this._items.length-1].globalIndex+1:null};
epiviz.datatypes.MeasurementGenomicDataArrayWrapper.prototype.size=function(){return this._items?this._items.length:0};epiviz.datatypes.MeasurementGenomicDataArrayWrapper.prototype.getByGlobalIndex=function(a){return this._itemsByGlobalIndex&&a in this._itemsByGlobalIndex?this._itemsByGlobalIndex[a]:null};epiviz.datatypes.MeasurementGenomicDataArrayWrapper.prototype.getRowByGlobalIndex=function(a){return this._itemsByGlobalIndex&&a in this._itemsByGlobalIndex?this._itemsByGlobalIndex[a].rowItem:null};
epiviz.datatypes.MeasurementGenomicDataArrayWrapper.prototype.binarySearchStarts=function(a){if(!this._items||!this._items.length||this._items[0].rowItem.start()>a.end()||this._items[this._items.length-1].rowItem.start()<a.start())return{index:null,length:0};for(var b=0,c=this._items.length-1,d,e=null;b<=c;)d=Math.floor(.5*(b+c)),this._items[d].rowItem.start()==a.start()?(e=d,c=d-1):this._items[d].rowItem.start()<a.start()?b=d+1:c=d-1;null===e&&(e=b);for(var b=0,c=this._items.length-1,f=null;b<=c;)d=
Math.floor(.5*(b+c)),this._items[d].rowItem.start()==a.end()?(f=d,b=d+1):this._items[d].rowItem.start()<a.end()?b=d+1:c=d-1;null===f&&(f=b-1);return{index:e,length:f+1-e}};epiviz.datatypes.GenomicData=function(){};epiviz.datatypes.GenomicData.prototype.ready=function(a){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.prototype.isReady=function(){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.prototype.firstSeries=function(){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.prototype.getSeries=function(a){throw Error("unimplemented abstract method");};
epiviz.datatypes.GenomicData.prototype.get=function(a,b){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.prototype.getRow=function(a,b){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.prototype.measurements=function(){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.prototype.globalStartIndex=function(a){throw Error("unimplemented abstract method");};
epiviz.datatypes.GenomicData.prototype.globalEndIndex=function(a){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.prototype.size=function(a){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.prototype.getByGlobalIndex=function(a,b){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.prototype.getRowByGlobalIndex=function(a,b){throw Error("unimplemented abstract method");};
epiviz.datatypes.GenomicData.prototype.binarySearchStarts=function(a,b){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.prototype.foreach=function(a){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.ValueItem=function(a,b,c,d,e){this.globalIndex=a;this.rowItem=b;this.value=0===c||c?c:null;this.measurement=d;this.valueAnnotation=e};epiviz.datatypes.GenomicData.RowItem=function(){};
epiviz.datatypes.GenomicData.RowItem.prototype.id=function(){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.RowItem.prototype.seqName=function(){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.RowItem.prototype.start=function(){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.RowItem.prototype.end=function(){throw Error("unimplemented abstract method");};
epiviz.datatypes.GenomicData.RowItem.prototype.globalIndex=function(){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.RowItem.prototype.strand=function(){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.RowItem.prototype.metadata=function(a){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicData.RowItem.prototype.rowMetadata=function(){throw Error("unimplemented abstract method");};epiviz.datatypes.MapGenomicData=function(a){this._measurements=(this._map=a)?a.keys():null;this._mapLoaded=new epiviz.deferred.Deferred;this._map&&this._mapLoaded.resolve()};epiviz.datatypes.MapGenomicData.prototype.ready=function(a){this._mapLoaded.done(a)};epiviz.datatypes.MapGenomicData.prototype.isReady=function(){return this._mapLoaded.state()==epiviz.deferred.Deferred.State.RESOLVED};
epiviz.datatypes.MapGenomicData.prototype._setMap=function(a){if(this._map)throw Error("MapGenomicData is immutable");if(this._map=a)this._measurements=a.keys(),this._mapLoaded.resolve()};epiviz.datatypes.MapGenomicData.prototype.firstSeries=function(){return 0==this._map.size()?null:this._map.first().value};epiviz.datatypes.MapGenomicData.prototype.getSeries=function(a){return this._map.get(a)};epiviz.datatypes.MapGenomicData.prototype.get=function(a,b){var c=this._map.get(a);return c?c.get(b):null};
epiviz.datatypes.MapGenomicData.prototype.getRow=function(a,b){var c=this._map.get(a);return c?c.getRow(b):null};epiviz.datatypes.MapGenomicData.prototype.measurements=function(){return this._measurements};epiviz.datatypes.MapGenomicData.prototype.globalStartIndex=function(a){return(a=this._map.get(a))?a.globalStartIndex():null};epiviz.datatypes.MapGenomicData.prototype.globalEndIndex=function(a){return(a=this._map.get(a))?a.globalEndIndex():null};
epiviz.datatypes.MapGenomicData.prototype.size=function(a){return(a=this._map.get(a))?a.size():null};epiviz.datatypes.MapGenomicData.prototype.getByGlobalIndex=function(a,b){var c=this._map.get(a);return c?c.getByGlobalIndex(b):null};epiviz.datatypes.MapGenomicData.prototype.getRowByGlobalIndex=function(a,b){var c=this._map.get(a);return c?c.getRowByGlobalIndex(b):null};
epiviz.datatypes.MapGenomicData.prototype.binarySearchStarts=function(a,b){var c=this._map.get(a);return c?c.binarySearchStarts(b):{index:null,length:0}};epiviz.datatypes.MapGenomicData.prototype.foreach=function(a){this._map.foreach(function(b,c,d){a(b,c,d)})};epiviz.datatypes.PartialSummarizedExperiment=function(){this._rowData=null;this._values=new epiviz.measurements.MeasurementHashtable};epiviz.datatypes.PartialSummarizedExperiment.prototype.ranges=function(){return this.rowData()};epiviz.datatypes.PartialSummarizedExperiment.prototype.rowData=function(){return this._rowData};
epiviz.datatypes.PartialSummarizedExperiment.prototype.addRowData=function(a){a&&(!this._rowData||this._rowData.boundaries().seqName()!=a.boundaries().seqName()||this._rowData.boundaries().start()>a.boundaries().end()||this._rowData.boundaries().end()<a.boundaries().start()||a.measurement().type()==epiviz.measurements.Measurement.Type.UNORDERED?this._rowData=a:this._rowData=this._rowData.merge(a))};
epiviz.datatypes.PartialSummarizedExperiment.prototype.addValues=function(a){if(a){var b=this._values.get(a.measurement());!b||b.boundaries().seqName()!=a.boundaries().seqName()||b.boundaries().start()>a.boundaries().end()||b.boundaries().end()<a.boundaries().start()||a.measurement().type()==epiviz.measurements.Measurement.Type.UNORDERED?this._values.put(a.measurement(),a):this._values.put(a.measurement(),b.merge(a))}};
epiviz.datatypes.PartialSummarizedExperiment.prototype.trim=function(a){var b=new epiviz.datatypes.PartialSummarizedExperiment;this._rowData&&b.addRowData(this._rowData.trim(a));b.rowData()&&this._values.foreach(function(c,d){b.addValues(d.trim(a,b.rowData().globalStartIndex(),b.rowData().size()))});return b};epiviz.datatypes.PartialSummarizedExperiment.prototype.values=function(a){return this._values.get(a)};
epiviz.datatypes.PartialSummarizedExperiment.prototype.calcMinGlobalIndex=function(){var a=this._rowData?this._rowData.globalStartIndex():null;this._values&&this._values.foreach(function(b,c){c&&void 0!=c.globalStartIndex()&&(void 0==a||a>c.globalStartIndex())&&(a=c.globalStartIndex())});return a};
epiviz.datatypes.PartialSummarizedExperiment.prototype.calcMaxGlobalIndex=function(){var a=this._rowData&&void 0!=this._rowData.globalStartIndex()?this._rowData.globalStartIndex()+this._rowData.size():null;this._values&&this._values.foreach(function(b,c){c&&void 0!=c.globalStartIndex()&&(void 0==a||a<c.globalStartIndex()+c.size())&&(a=c.globalStartIndex()+c.size())});return a};
epiviz.datatypes.PartialSummarizedExperiment.prototype.toString=function(){var a="",b=this.calcMinGlobalIndex(),c=this.calcMaxGlobalIndex(),a=a+sprintf("%25s",this._rowData&&this._rowData.measurement()?this._rowData.measurement().name().substr(0,22):"[undefined datasource]"),d,e,f,g,h;this._rowData&&this._rowData.boundaries()?(d=this._rowData.boundaries().seqName(),e=this._rowData.boundaries().start(),f=this._rowData.boundaries().end(),g=void 0!=this._rowData.globalStartIndex()?this._rowData.globalStartIndex():
"*",h=this._rowData.size()):(d=e=f=g="*",h=0);var a=a+sprintf(" [%6s%10s%10s] [globalStartIndex: %10s] [size: %7s]\n",d,e,f,g,h),m=sprintf("%15s%15s%15s%15s%15s","id","idx","chr","start","end");this._values&&this._values.foreach(function(b,c){a+=sprintf("%25s",b.name().substr(0,22));c&&c.boundaries()?(d=c.boundaries().seqName(),e=c.boundaries().start(),f=c.boundaries().end()):d=e=f="*";a+=sprintf(" [%6s%10s%10s] [globalStartIndex: %10s] [size: %7s]\n",d,e,f,void 0!=c.globalStartIndex()?c.globalStartIndex():
"*",c.size());m+=sprintf("%25s",b.name().substr(0,22))});for(var a=a+(m+"\n"),l=b;l<c;++l)this._rowData&&void 0!=this._rowData.globalStartIndex()&&this._rowData.globalStartIndex()<=l&&this._rowData.globalStartIndex()+this._rowData.size()>l?(g=this._rowData.getByGlobalIndex(l),b=g.id(),d=g.seqName(),e=g.start(),f=g.end()):b=d=e=f="*",a+=sprintf("%15s%15s%15s%15s%15s",b,l,d,e,f),this._values&&this._values.foreach(function(b,c){a=c&&void 0!=c.globalStartIndex()&&c.globalStartIndex()<=l&&c.globalStartIndex()+
c.size()>l?a+sprintf("%25s",c.getByGlobalIndex(l)):a+sprintf("%25s","*")}),a+="\n";return a};epiviz.data.Cache=function(a,b){this._config=a;this._dataProviderFactory=b;this._data={};this._measurementRequestStackMap=new epiviz.measurements.MeasurementHashtable;this._measurementPendingRequestsMap=new epiviz.measurements.MeasurementHashtable;this._lastRequest=null;if(0<this._config.cacheUpdateIntervalMilliseconds){var c=this;this._intervalId=window.setTimeout(function(){c._clearUnneededData()},a.cacheUpdateIntervalMilliseconds)}};
epiviz.data.Cache.prototype.getData=function(a,b,c,d){var e=epiviz.measurements.Measurement.Type,f=this;this._lastRequest=epiviz.datatypes.GenomicRange.fromStartEnd(a.seqName(),a.start()-a.width(),a.end()+a.width(),a.genome());0<this._config.cacheUpdateIntervalMilliseconds&&(window.clearInterval(this._intervalId),this._intervalId=window.setTimeout(function(){f._clearUnneededData()},this._config.cacheUpdateIntervalMilliseconds));var g=this._extractComputedMeasurements(b);this._updateComputedMeasurementsData(g);
this._serveAvailableData(a,b,c);g=[new epiviz.datatypes.GenomicRange(a.seqName(),Math.max(a.start()-a.width(),0),a.end()+a.width()-Math.max(a.start()-a.width(),0),a.genome())];this._calcMeasurementNeededRanges(g,b).foreach(function(g,m){var h=f._measurementRequestStackMap.get(g);h||(h=new epiviz.data.RequestStack,f._measurementRequestStackMap.put(g,h));var p;if(0==m.length)p=epiviz.data.Request.emptyRequest(),h.pushRequest(p,function(){f._handleResponse(c,a,b,p,null,g,null)}),h.serveData(new epiviz.data.Response(p.id(),
{}));else for(var n=0;n<m.length;++n){p=g.type()==e.RANGE?epiviz.data.Request.getRows(g,m[n]):epiviz.data.Request.getValues(g,m[n]);(function(d,e){h.pushRequest(e,function(h){f._handleResponse(c,a,b,e,d,g,h)})})(m[n],p);var t=f._measurementPendingRequestsMap.get(g);t||(t={},f._measurementPendingRequestsMap.put(g,t));t[p.id()]=m[n];(f._dataProviderFactory.get(g.dataprovider())||f._dataProviderFactory.get(epiviz.data.EmptyResponseDataProvider.DEFAULT_ID)).getData(p,function(a){h.serveData(a)},function(a,
b,c){d(a,b,c)})}})};
epiviz.data.Cache.prototype._handleResponse=function(a,b,c,d,e,f,g){if(e){if(f.type()==epiviz.measurements.Measurement.Type.RANGE)e=new epiviz.datatypes.GenomicRangeArray(f,e,g.globalStartIndex,g.values,g.useOffset);else{var h=f.datasource();0==Object.keys(g.rows.values.metadata).length&&delete g.rows.values.metadata;var m=new epiviz.datatypes.GenomicRangeArray(h,e,g.rows.globalStartIndex,g.rows.values,g.rows.useOffset);this._mergeData(h,m);h=[];0<Object.keys(g.values.values).length&&Object.keys(g.values.values).includes(f.id())&&
(h=g.values.values[f.id()]);e=new epiviz.datatypes.FeatureValueArray(f,e,g.values.globalStartIndex,h)}this._mergeData(f,e)}e=this._extractComputedMeasurements(c);this._updateComputedMeasurementsData(e);(f=this._measurementPendingRequestsMap.get(f))&&delete f[d.id()];this._serveAvailableData(b,c,a)};
epiviz.data.Cache.prototype._serveAvailableData=function(a,b,c){var d=epiviz.measurements.Measurement.Type,e=this,f=[],g;for(g in b)if(b.hasOwnProperty(g)){var h=b[g],m=!0,l=new epiviz.measurements.MeasurementHashtable;(function(b){h.foreach(function(c){var f=e._data[c.datasourceGroup()];if(!f||!f.rowData()||c.type()==d.FEATURE&&!f.values(c))return m=!1,!0;var g=f.rowData(),f=c.type()==d.FEATURE?f.values(c):null,g=a.subtract(g.boundaries());if(g.length||f&&(g=a.subtract(f.boundaries()),g.length))return m=
!1,!0;b.put(c,new epiviz.datatypes.MeasurementGenomicDataWrapper(c,e._data[c.datasourceGroup()]));return!1})})(l);m&&(c(g,new epiviz.datatypes.MapGenomicData(l)),f.push(g))}for(c=0;c<f.length;++c)delete b[f[c]]};
epiviz.data.Cache.prototype._calcMeasurementNeededRanges=function(a,b){var c=epiviz.measurements.Measurement.Type,d=this,e=new epiviz.measurements.MeasurementHashtable,f;for(f in b)if(b.hasOwnProperty(f)){var g=new epiviz.measurements.MeasurementSet;(function(a){b[f].foreach(function(b){b.componentMeasurements().foreach(function(b){a.add(b)})})})(g);g.foreach(function(b){var f;f=d._data[b.datasourceGroup()];if(!f||b.type()==c.FEATURE&&!f.values(b))f=a.slice(0);else{var g=b.type()==c.FEATURE?f.values(b):
f.rowData();if(g){f=[];for(var g=g.boundaries(),h=0;h<a.length;++h)f=f.concat(a[h].subtract(g))}else f=a.slice(0)}if(g=d._measurementPendingRequestsMap.get(b))for(h=0;h<f.length;++h)for(var n in g)if(g.hasOwnProperty(n)){var t=f[h].subtract(g[n]);Array.prototype.splice.apply(f,[h,1].concat(t));if(0==t.length){--h;break}if(h>=f.length)break}e.put(b,f)})}return e};
epiviz.data.Cache.prototype._extractComputedMeasurements=function(a){var b=new epiviz.measurements.MeasurementSet,c;for(c in a)a.hasOwnProperty(c)&&a[c].foreach(function(a){a.isComputed()&&b.add(a)});return b};epiviz.data.Cache.prototype._mergeData=function(a,b){var c=epiviz.measurements.Measurement.Type,d=this._data[a.datasourceGroup()];d||(d=new epiviz.datatypes.PartialSummarizedExperiment,this._data[a.datasourceGroup()]=d);a.type()==c.RANGE?d.addRowData(b):d.addValues(b)};
epiviz.data.Cache.prototype._clearUnneededData=function(){if(this._lastRequest){console.log(sprintf("Clearing data outside of range [%7s%10s%10s]",this._lastRequest.seqName(),this._lastRequest.start(),this._lastRequest.end()));var a={},b;for(b in this._data)this._data.hasOwnProperty(b)&&(a[b]=this._data[b].trim(this._lastRequest));this._data=a}};
epiviz.data.Cache.prototype._updateComputedMeasurementsData=function(a){var b=this,c=epiviz.datatypes.GenomicRange;a.foreach(function(a){var d=b._data[a.datasourceGroup()];if(!d)return!1;var f=a.componentMeasurements(),g=null,h=null,m=null;f.foreach(function(a){a=d.values(a);if(!a||!a.boundaries())return m=h=g=null,!0;if(null===m)return g=a.globalStartIndex(),h=a.size(),m=a.boundaries(),null===g;if(a.boundaries().seqName()!=m.seqName())return h=0,!0;if(g<a.globalStartIndex()){h-=a.globalStartIndex()-
g;if(0>h)return h=0,!0;g=a.globalStartIndex();var b=a.boundaries().start(),e=m.end();h>a.size()&&(h=a.size(),e=a.boundaries().end());m=c.fromStartEnd(m.seqName(),b,e,m.genome())}else if(b=a.size()-g+a.globalStartIndex(),h>b){h=b;if(0>=h)return h=0,!0;m=c.fromStartEnd(m.seqName(),m.start(),a.boundaries().end(),m.genome())}return 0==h?!0:!1});if(null===m)return!1;var l=d.values(a);if(l&&(null===g||l.globalStartIndex()<g&&l.globalStartIndex()+l.size()>g+h))return!1;var p=new epiviz.measurements.MeasurementHashtable,
n=null;if(l&&l.size())return f.foreach(function(a){var b=d.values(a),c=[];if(null!==g)for(var e=g;e<l.globalStartIndex();++e)c.push(b.getByGlobalIndex(e));p.put(a,c)}),n=new epiviz.datatypes.FeatureValueArray(a,c.fromStartEnd(m.seqName(),m.start(),l.boundaries().start()),g,a.evaluateArr(p),m.genome()),b._mergeData(a,n),p=new epiviz.measurements.MeasurementHashtable,f.foreach(function(a){var b=d.values(a),c=[];if(null!==g)for(var e=l.globalStartIndex()+l.size();e<g+h;++e)c.push(b.getByGlobalIndex(e));
p.put(a,c)}),n=new epiviz.datatypes.FeatureValueArray(a,c.fromStartEnd(m.seqName(),l.boundaries().end(),m.end(),m.genome()),l.globalStartIndex()+l.size(),a.evaluateArr(p)),b._mergeData(a,n),!1;p=new epiviz.measurements.MeasurementHashtable;f.foreach(function(a){var b=d.values(a),c=[];if(null!==g)for(var e=g;e<g+h;++e)c.push(b.getByGlobalIndex(e));p.put(a,c)});n=new epiviz.datatypes.FeatureValueArray(a,m,g,a.evaluateArr(p));b._mergeData(a,n);return!1})};
epiviz.data.Cache.prototype.flush=function(){this._data={};this._measurementRequestStackMap.foreach(function(a,b){b.clear()});this._measurementPendingRequestsMap.clear()};
epiviz.data.Cache.prototype.clearDatasourceGroupCache=function(a){delete this._data[a];this._measurementRequestStackMap.foreach(function(b,c){b.datasourceGroup()==a&&c.clear()});var b=[];this._measurementPendingRequestsMap.foreach(function(c,e){c.datasourceGroup()==a&&b.push(c)});for(var c=0;c<b.length;++c)this._measurementPendingRequestsMap.put(b[c],{})};epiviz.data.EmptyResponseDataProvider=function(){epiviz.data.DataProvider.call(this,epiviz.data.EmptyResponseDataProvider.DEFAULT_ID)};epiviz.data.EmptyResponseDataProvider.prototype=epiviz.utils.mapCopy(epiviz.data.DataProvider.prototype);epiviz.data.EmptyResponseDataProvider.constructor=epiviz.data.EmptyResponseDataProvider;epiviz.data.EmptyResponseDataProvider.DEFAULT_ID="empty";
epiviz.data.EmptyResponseDataProvider.prototype.getData=function(a,b){var c=a.id();switch(a.get("action")){case epiviz.data.Request.Action.GET_ROWS:b(epiviz.data.Response.fromRawObject({data:{values:{id:null,start:[],end:[],strand:[],metadata:{my_metadata:[]}},globalStartIndex:null,useOffset:!1},requestId:c}));break;case epiviz.data.Request.Action.GET_VALUES:b(epiviz.data.Response.fromRawObject({data:{values:[],globalStartIndex:null},requestId:c}));break;case epiviz.data.Request.Action.GET_MEASUREMENTS:b(epiviz.data.Response.fromRawObject({requestId:a.id(),
data:{id:[],name:[],type:[],datasourceId:[],datasourceGroup:[],defaultChartType:[],annotation:[],minValue:[],maxValue:[],metadata:[]}}));break;case epiviz.data.Request.Action.GET_SEQINFOS:b(epiviz.data.Response.fromRawObject({requestId:a.id(),data:[]}));break;case epiviz.data.Request.Action.SEARCH:b(epiviz.data.Response.fromRawObject({requestId:a.id(),data:[]}));break;case epiviz.data.Request.Action.SAVE_WORKSPACE:b(epiviz.data.Response.fromRawObject({requestId:a.id(),data:[]}));break;case epiviz.data.Request.Action.DELETE_WORKSPACE:b(epiviz.data.Response.fromRawObject({requestId:a.id(),
data:[]}));break;case epiviz.data.Request.Action.GET_WORKSPACES:b(epiviz.data.Response.fromRawObject({requestId:a.id(),data:[]}));break;default:epiviz.data.DataProvider.prototype.getData.call(this,a,b)}};epiviz.data.DataProviderFactory=function(a){this._config=a;this._providers={};for(var b=this._size=0;b<this._config.dataProviders.length;++b){a=$.isArray(this._config.dataProviders[b])?this._config.dataProviders[b]:this._config.dataProviders[b].split(",");var c=epiviz.utils.evaluateFullyQualifiedTypeName(a[0]);c&&(a=epiviz.utils.applyConstructor(c,a.slice(1)),this._providers[a.id()]=a,++this._size)}a=new epiviz.data.EmptyResponseDataProvider;this._providers[a.id()]=a;++this._size;a=this._config.workspacesDataProvider.split(",");
b=epiviz.utils.evaluateFullyQualifiedTypeName(a[0]);this._workspacesDataProvider=epiviz.utils.applyConstructor(b,a.slice(1))};epiviz.data.DataProviderFactory.prototype.foreach=function(a){for(var b in this._providers)if(this._providers.hasOwnProperty(b)&&a(this._providers[b]))break};epiviz.data.DataProviderFactory.prototype.isEmpty=function(){return!this._size};epiviz.data.DataProviderFactory.prototype.size=function(){return this._size};
epiviz.data.DataProviderFactory.prototype.get=function(a){return a in this._providers?this._providers[a]:null};epiviz.data.DataProviderFactory.prototype.workspacesDataProvider=function(){return this._workspacesDataProvider};epiviz.datatypes.GenomicArray=function(a,b,c,d){this._measurement=a;this._boundaries=b;this._globalStartIndex=c;this._values=d};epiviz.datatypes.GenomicArray.prototype.boundaries=function(){return this._boundaries};epiviz.datatypes.GenomicArray.prototype.globalStartIndex=function(){return this._globalStartIndex};epiviz.datatypes.GenomicArray.prototype.measurement=function(){return this._measurement};
epiviz.datatypes.GenomicArray.prototype.get=function(a){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicArray.prototype.size=function(){throw Error("unimplemented abstract method");};epiviz.datatypes.GenomicArray.prototype.getByGlobalIndex=function(a){return this.get(a-this._globalStartIndex)};epiviz.datatypes.GenomicArray.prototype.concatValues=function(a,b){throw Error("unimplemented abstract method");};
epiviz.datatypes.GenomicArray.prototype.createNew=function(a,b,c,d){throw Error("unimplemented abstract method");};
epiviz.datatypes.GenomicArray.prototype.merge=function(a){if(!a||void 0==a.boundaries())return this;if(this.boundaries().seqName()!=a.boundaries().seqName()||this.boundaries().start()>a.boundaries().end()||a.boundaries().start()>this.boundaries().end())throw Error("Two genomic arrays can only be merged if they overlap or are in continuation to one another");var b=this.boundaries().start()<a.boundaries().start()?this:a,c=b==this?a:this;if(b.boundaries().end()>=c.boundaries().end())return b;var d=void 0!=
b.globalStartIndex()&&void 0!=c.globalStartIndex()?b.globalStartIndex()+b.size()-c.globalStartIndex():0;a=b.measurement();var e=void 0!=b.globalStartIndex()?b.globalStartIndex():c.globalStartIndex(),f=epiviz.datatypes.GenomicRange.fromStartEnd(b.boundaries().seqName(),b.boundaries().start(),c.boundaries().end(),b.boundaries().genome()),b=b.concatValues(c,d);return this.createNew(a,f,e,b)};epiviz.datatypes.GenomicRangeArray=function(a,b,c,d,e){epiviz.datatypes.GenomicArray.call(this,a,b,c,d);this._id=d.id;this._seqName=d.chr;this._start=d.start;this._end=d.end;this._strand=d.strand||null;this._metadata=d.metadata;this._size=null;if(e)for(a=1;a<this._start.length;++a)this._start[a]+=this._start[a-1],this._end&&(this._end[a]+=this._end[a-1])};epiviz.datatypes.GenomicRangeArray.prototype=epiviz.utils.mapCopy(epiviz.datatypes.GenomicArray.prototype);
epiviz.datatypes.GenomicRangeArray.constructor=epiviz.datatypes.GenomicRangeArray;epiviz.datatypes.GenomicRangeArray.prototype.createNew=function(a,b,c,d){return new epiviz.datatypes.GenomicRangeArray(a,b,c,d)};epiviz.datatypes.GenomicRangeArray.prototype.get=function(a){return 0>a||a>=this.size()?null:new epiviz.datatypes.GenomicRangeArray.RowItemWrapper(this,a)};
epiviz.datatypes.GenomicRangeArray.prototype.size=function(){void 0==this._size&&(this._size=Math.max(this._id?this._id.length:0,this._start?this._start.length:0,this._end?this._end.length:0,this._metadata&&Object.keys(this._metadata).length?Math.max.apply(void 0,$.map(this._metadata,function(a){return a.length})):0));return this._size};
epiviz.datatypes.GenomicRangeArray.prototype.concatValues=function(a,b){var c;if(Array.isArray(this._strand)||Array.isArray(a._strand)||this._strand!=a._strand){c=Array.isArray(this._strand)?this._strand:epiviz.utils.fillArray(this.size(),this._strand);var d=Array.isArray(a._strand)?a._strand:epiviz.utils.fillArray(a.size(),a._strand);c=c.concat(d.slice(b))}else c=this._strand;var d=this._id?this._id.concat(a._id.slice(b)):null,e=this._start.concat(a._start.slice(b)),f=this._end?this._end.concat(a._end.slice(b)):
null,g=this._seqName?this._seqName.concat(a._seqName.slice(b)):null,h={},m;for(m in this._metadata)this._metadata.hasOwnProperty(m)&&(h[m]=this._metadata[m].concat(a._metadata[m].slice(b)));return{id:d,start:e,end:f,strand:c,metadata:h,chr:g}};
epiviz.datatypes.GenomicRangeArray.prototype.trim=function(a){if(void 0==this.globalStartIndex()||!this.size()||!a||!this.boundaries()||this.boundaries().seqName()!=a.seqName())return null;var b=Math.max(this.boundaries().start(),a.start()),c=Math.min(this.boundaries().end(),a.end());if(c<=b)return null;a=epiviz.datatypes.GenomicRange.fromStartEnd(a.seqName(),b,c,a.genome());for(var c=b=-1,d=0;d<this.size();++d)0>b&&this.end(d)>=a.start()&&(b=d),this._start[d]<a.end()&&(c=d+1);if(c<=b)return null;
var e;if(0<=b&&c>=b){d={id:this._id?this._id.slice(b,c):null,start:this._start.slice(b,c),end:this._end?this._end.slice(b,c):null,chr:this._seqName?this._seqName.slice(b,c):null,strand:Array.isArray(this._strand)?this._strand.slice(b,c):this._strand,metadata:{}};for(e in this._metadata)this._metadata.hasOwnProperty(e)&&(d.metadata[e]=this._metadata[e].slice(b,c));e=this.globalStartIndex()+b}else{d={id:this._id?[]:null,start:[],chr:this._seqName?[]:null,end:this._end?[]:null,strand:Array.isArray(this._strand)?
[]:this._strand,metadata:{}};for(e in this._metadata)this._metadata.hasOwnProperty(e)&&(d.metadata[e]=[]);e=null}return new epiviz.datatypes.GenomicRangeArray(this.measurement(),a,e,d)};epiviz.datatypes.GenomicRangeArray.prototype.ranges=function(){return this};epiviz.datatypes.GenomicRangeArray.prototype.foreach=function(a){for(var b=this.size(),c=0;c<b&&!a(this.get(c));++c);};
epiviz.datatypes.GenomicRangeArray.prototype.metadataColumns=function(){return this._metadata?Object.keys(this._metadata):[]};epiviz.datatypes.GenomicRangeArray.prototype.id=function(a){return this._id?this._id[a]:this.globalStartIndex()+a};epiviz.datatypes.GenomicRangeArray.prototype.seqName=function(a){return this._seqName?this._seqName[a]:void 0};epiviz.datatypes.GenomicRangeArray.prototype.start=function(a){return this._start?this._start[a]:void 0};
epiviz.datatypes.GenomicRangeArray.prototype.end=function(a){return this._end?this._end[a]:this.start(a)};epiviz.datatypes.GenomicRangeArray.prototype.strand=function(a){return Array.isArray(this._strand)?this._strand[a]:this._strand};epiviz.datatypes.GenomicRangeArray.prototype.metadata=function(a,b){return this._metadata&&this._metadata[a]?this._metadata[a][b]:null};
epiviz.datatypes.GenomicRangeArray.prototype.rowMetadata=function(a){var b={},c;for(c in this._metadata)this._metadata.hasOwnProperty(c)&&(b[c]=this._metadata[c][a]);return b};
epiviz.datatypes.GenomicRangeArray.prototype.toString=function(){var a,b,c;this.boundaries()?(this.boundaries().genome(),a=this.boundaries().seqName(),b=this.boundaries().start(),c=this.boundaries().end()):a=b=c="*";a=sprintf("%25s",this.measurement().name().substr(0,22))+sprintf(" [%6s%10s%10s]",a,b,c);b=sprintf("%10s:","id");c=sprintf("%10s:","idx");var d=sprintf("%10s:","chr"),e=sprintf("%10s:","start"),f=sprintf("%10s:","end");if(void 0!=this.globalStartIndex())for(var g=this.globalStartIndex();g<
this.globalStartIndex()+this.size();++g){var h=this.getByGlobalIndex(g);b+=sprintf("%10s",h.id());c+=sprintf("%10s",g);d+=sprintf("%10s",h.seqName());e+=sprintf("%10s",h.start());f+=sprintf("%10s",h.end())}return[a,b,c,d,e,f].join("\n")};epiviz.datatypes.GenomicRangeArray.RowItemWrapper=function(a,b){this._index=b;this._parent=a};epiviz.datatypes.GenomicRangeArray.RowItemWrapper.prototype.parent=function(){return this._parent};epiviz.datatypes.GenomicRangeArray.RowItemWrapper.prototype.id=function(){return this._parent.id(this._index)};
epiviz.datatypes.GenomicRangeArray.RowItemWrapper.prototype.seqName=function(){return this._parent.seqName(this._index)};epiviz.datatypes.GenomicRangeArray.RowItemWrapper.prototype.start=function(){return this._parent.start(this._index)};epiviz.datatypes.GenomicRangeArray.RowItemWrapper.prototype.end=function(){return this._parent.end(this._index)};epiviz.datatypes.GenomicRangeArray.RowItemWrapper.prototype.index=function(){return this._index};
epiviz.datatypes.GenomicRangeArray.RowItemWrapper.prototype.globalIndex=function(){return this._index+this._parent.globalStartIndex()};epiviz.datatypes.GenomicRangeArray.RowItemWrapper.prototype.equals=function(a){return a?this==a?!0:a.seqName()==this.seqName()&&a.start()==this.start()&&a.end()==this.end():!1};epiviz.datatypes.GenomicRangeArray.RowItemWrapper.prototype.strand=function(){return this._parent.strand(this._index)};
epiviz.datatypes.GenomicRangeArray.RowItemWrapper.prototype.metadata=function(a){return this._parent.metadata(a,this._index)};epiviz.datatypes.GenomicRangeArray.RowItemWrapper.prototype.rowMetadata=function(){return this._parent.rowMetadata(this._index)};epiviz.datatypes.GenomicRangeArray.RowItemWrapper.prototype.overlapsWith=function(a){return a?this==a?!0:this.seqName()!=a.seqName()?!1:this.start()<a.end()&&this.end()>a.start():!1};epiviz.datatypes.FeatureValueArray=function(a,b,c,d){var e;!d||$.isArray(d)?(e=d,d={values:d}):e=d.values;epiviz.datatypes.GenomicArray.call(this,a,b,c,e);this._valuesAnnotation=d};epiviz.datatypes.FeatureValueArray.prototype=epiviz.utils.mapCopy(epiviz.datatypes.GenomicArray.prototype);epiviz.datatypes.FeatureValueArray.constructor=epiviz.datatypes.FeatureValueArray;epiviz.datatypes.FeatureValueArray.prototype.createNew=function(a,b,c,d){return new epiviz.datatypes.FeatureValueArray(a,b,c,d)};
epiviz.datatypes.FeatureValueArray.prototype.get=function(a){return this._values[a]};epiviz.datatypes.FeatureValueArray.prototype.getAnnotation=function(a){if(void 0==this._valuesAnnotation)return null;var b={},c;for(c in this._valuesAnnotation)this._valuesAnnotation.hasOwnProperty(c)&&(b[c]=this._valuesAnnotation[c][a]);return b};epiviz.datatypes.FeatureValueArray.prototype.size=function(){return this._values?this._values.length:0};
epiviz.datatypes.FeatureValueArray.prototype.concatValues=function(a,b){if(!a||!a.size())return this._valuesAnnotation;this._valuesAnnotation&&this._valuesAnnotation.values||(this._valuesAnnotation={values:[]});var c={},d;for(d in this._valuesAnnotation)this._valuesAnnotation.hasOwnProperty(d)&&a._valuesAnnotation.hasOwnProperty(d)&&(c[d]=this._valuesAnnotation[d].concat(a._valuesAnnotation[d].slice(b)));return c};
epiviz.datatypes.FeatureValueArray.prototype.trim=function(a,b,c){if(void 0==this.globalStartIndex()||!this.size()||void 0==b||!a||!this.boundaries()||this.boundaries().seqName()!=a.seqName())return null;var d=Math.max(this.boundaries().start(),a.start()),e=Math.min(this.boundaries().end(),a.end());if(e<=d)return null;a=epiviz.datatypes.GenomicRange.fromStartEnd(a.seqName(),d,e,a.genome());d=Math.max(b,this.globalStartIndex())-this.globalStartIndex();b=Math.min(b+c,this.globalStartIndex()+this.size())-
this.globalStartIndex();if(b<=d)return null;c={};for(var f in this._valuesAnnotation)this._valuesAnnotation.hasOwnProperty(f)&&(c[f]=this._valuesAnnotation[f].slice(d,b));return new epiviz.datatypes.FeatureValueArray(this.measurement(),a,d+this.globalStartIndex(),c)};
epiviz.datatypes.FeatureValueArray.prototype.toString=function(){var a,b,c;this.boundaries()?(a=this.boundaries().seqName(),b=this.boundaries().start(),c=this.boundaries().end()):a=b=c="*";a=sprintf("%25s",this.measurement().name().substr(0,22))+sprintf(" [%6s%10s%10s]",a,b,c);b=sprintf("%10s:","idx");c=sprintf("%10s:","val");if(void 0!=this.globalStartIndex())for(var d=this.globalStartIndex();d<this.globalStartIndex()+this.size();++d){var e=this.getByGlobalIndex(d);b+=sprintf("%10s",d);c+=sprintf("%10s",
e)}return[a,b,c].join("\n")};epiviz.datatypes.MeasurementGenomicDataWrapper=function(a,b){this._measurement=a;this._container=b;this._globalStartIndex=this._size=null};
epiviz.datatypes.MeasurementGenomicDataWrapper.prototype.get=function(a){var b=this._container.rowData(),c,d=this.globalStartIndex(),e=null,f=null,g=c=null,h=this.size();if(!h||a>=h||0>a)return new epiviz.datatypes.GenomicData.ValueItem(g,e,f,this._measurement,c);if(void 0!=d){if(this._measurement.type()==epiviz.measurements.Measurement.Type.FEATURE||this._measurement.type()==epiviz.measurements.Measurement.Type.UNORDERED)c=this._container.values(this._measurement),e=d-c.globalStartIndex()+a,f=c.get(e),
c=c.getAnnotation(e);e=d-b.globalStartIndex()+a;e=b.get(e);g=d+a}return new epiviz.datatypes.GenomicData.ValueItem(g,e,f,this._measurement,c)};epiviz.datatypes.MeasurementGenomicDataWrapper.prototype.getRow=function(a){var b=this._container.rowData(),c=this.globalStartIndex(),d=null,e=this.size();if(!e||a>=e||0>a)return d;void 0!=c&&(a=c-b.globalStartIndex()+a,d=b.get(a));return d};epiviz.datatypes.MeasurementGenomicDataWrapper.prototype.measurement=function(){return this._measurement};
epiviz.datatypes.MeasurementGenomicDataWrapper.prototype.globalStartIndex=function(){if(null!==this._globalStartIndex)return this._globalStartIndex;var a,b=this._container.rowData().globalStartIndex();if(null===b)return b;if(this._measurement.type()==epiviz.measurements.Measurement.Type.FEATURE||this._measurement.type()==epiviz.measurements.Measurement.Type.UNORDERED){a=this._container.values(this._measurement);if(!a.globalStartIndex())return a.globalStartIndex();b=Math.max(b,a.globalStartIndex())}return this._globalStartIndex=
b};epiviz.datatypes.MeasurementGenomicDataWrapper.prototype.globalEndIndex=function(){var a=this.globalStartIndex();return null==a?null:a+this.size()};
epiviz.datatypes.MeasurementGenomicDataWrapper.prototype.size=function(){if(null!==this._size)return this._size;var a=this.globalStartIndex();if(void 0==a)return 0;var b=this._container.rowData(),c=this._container.values(this._measurement),b=b.size()-a+b.globalStartIndex();if(this._measurement.type()==epiviz.measurements.Measurement.Type.FEATURE||this._measurement.type()==epiviz.measurements.Measurement.Type.UNORDERED)b=Math.min(b,c.size()-a+c.globalStartIndex());return this._size=Math.max(0,b)};
epiviz.datatypes.MeasurementGenomicDataWrapper.prototype.getByGlobalIndex=function(a){var b=this.globalStartIndex();return void 0==b?new epiviz.datatypes.GenomicData.ValueItem(null,null,null,this._measurement,null):this.get(a-b)};epiviz.datatypes.MeasurementGenomicDataWrapper.prototype.getRowByGlobalIndex=function(a){var b=this.globalStartIndex();return void 0==b?null:this.getRow(a-b)};
epiviz.datatypes.MeasurementGenomicDataWrapper.prototype.binarySearchStarts=function(a){var b=this._container.rowData();if(0==this.size()||!b||0==b.size()||b.start(0)>=a.end()||b.start(b.size()-1)<=a.start())return{index:null,length:0};for(var c=0,d=b.size()-1,e,f=null;c<=d;)e=Math.floor(.5*(c+d)),b.start(e)==a.start()?(f=e,d=e-1):b.start(e)<a.start()?c=e+1:d=e-1;null===f&&(f=c);for(var c=0,d=b.size()-1,g=null;c<=d;)e=Math.floor(.5*(c+d)),b.start(e)==a.end()?(g=e,c=e+1):b.start(e)<a.end()?c=e+1:d=
e-1;null===g&&(g=c-1);a=Math.max(f+b.globalStartIndex(),this.globalStartIndex());b=Math.min(g+b.globalStartIndex(),this.globalStartIndex()+this.size()-1);return{index:a-this.globalStartIndex(),length:b-a+1}};epiviz.datatypes.SeqInfo=function(a,b,c,d){this.genome=d;this.seqName=a;this.min=b;this.max=c};epiviz.datatypes.SeqInfo.prototype.raw=function(){return[this.seqName,this.min,this.max,this.genome]};epiviz.datatypes.SeqInfo.fromRawObject=function(a){return new epiviz.datatypes.SeqInfo(a[0],parseFloat(a[1]),parseFloat(a[2]),a[3])};
epiviz.datatypes.SeqInfo.compare=function(a,b){if(a.genome==b.genome){if(a.seqName==b.seqName)return 0;if(void 0==a.seqName)return-1;if(void 0==b.seqName)return 1;var c=a.seqName.replace(/\D/g,""),d=b.seqName.replace(/\D/g,"");return""==c||""==d||!epiviz.utils.stringStartsWith(a.seqName,c)&&!epiviz.utils.stringEndsWith(a.seqName,c)||!epiviz.utils.stringStartsWith(b.seqName,d)&&!epiviz.utils.stringEndsWith(b.seqName,d)?a.seqName<b.seqName?-1:a.seqName>b.seqName?1:0:parseInt(c)-parseInt(d)}c=a.genome.replace(/\D/g,
"");d=b.genome.replace(/\D/g,"");return""==c||""==d||!epiviz.utils.stringStartsWith(a.genome,c)&&!epiviz.utils.stringEndsWith(a.genome,c)||!epiviz.utils.stringStartsWith(b.genome,d)&&!epiviz.utils.stringEndsWith(b.genome,d)?a.genome<b.genome?-1:a.genome>b.genome?1:0:parseInt(c)-parseInt(d)};epiviz.data.DataManager=function(a,b){this._config=a;this._measurements=new epiviz.measurements.MeasurementSet;this._dataProviderFactory=b;this._cache=new epiviz.data.Cache(a,b);this._combinedRequestsStacks={};this._requestAddMeasurements=new epiviz.events.Event;this._requestRemoveMeasurements=new epiviz.events.Event;this._requestAddChart=new epiviz.events.Event;this._requestRemoveChart=new epiviz.events.Event;this._requestPrintWorkspace=new epiviz.events.Event;this._requestLoadWorkspace=new epiviz.events.Event;
this._requestAddSeqInfos=new epiviz.events.Event;this._requestRemoveSeqNames=new epiviz.events.Event;this._requestNavigate=new epiviz.events.Event;this._requestRedraw=new epiviz.events.Event;this._flushCache=new epiviz.events.Event;this._clearDatasourceGroupCache=new epiviz.events.Event;this._requestCurrentLocation=new epiviz.events.Event;this._requestGetChartSettings=new epiviz.events.Event;this._requestSetChartSettings=new epiviz.events.Event;this._requestGetAvailableCharts=new epiviz.events.Event;
this._requestLoadMeasurements=new epiviz.events.Event;this._requestUiStatus=new epiviz.events.Event;this._registerProviderAddMeasurements();this._registerProviderRemoveMeasurements();this._registerProviderAddChart();this._registerProviderRemoveChart();this._registerProviderPrintWorkspace();this._registerProviderLoadWorkspace();this._registerProviderAddSeqInfos();this._registerProviderRemoveSeqNames();this._registerProviderNavigate();this._registerProviderRedraw();this._registerProviderFlushCache();
this._registerProviderClearDatasourceGroupCache();this._registerProviderGetCurrentLocation();this._registerProviderGetChartSettings();this._registerProviderSetChartSettings();this._registerProviderGetAvailableCharts();this._registerProviderUiStatus();this._registerProviderLoadMeasurements()};epiviz.data.DataManager.prototype.onRequestAddMeasurements=function(){return this._requestAddMeasurements};epiviz.data.DataManager.prototype.onRequestRemoveMeasurements=function(){return this._requestRemoveMeasurements};
epiviz.data.DataManager.prototype.onRequestLoadMeasurements=function(){return this._requestLoadMeasurements};epiviz.data.DataManager.prototype.onRequestAddChart=function(){return this._requestAddChart};epiviz.data.DataManager.prototype.onRequestRemoveChart=function(){return this._requestRemoveChart};epiviz.data.DataManager.prototype.onRequestPrintWorkspace=function(){return this._requestPrintWorkspace};epiviz.data.DataManager.prototype.onRequestLoadWorkspace=function(){return this._requestLoadWorkspace};
epiviz.data.DataManager.prototype.onRequestAddSeqInfos=function(){return this._requestAddSeqInfos};epiviz.data.DataManager.prototype.onRequestRemoveSeqNames=function(){return this._requestRemoveSeqNames};epiviz.data.DataManager.prototype.onRequestNavigate=function(){return this._requestNavigate};epiviz.data.DataManager.prototype.onRequestRedraw=function(){return this._requestRedraw};epiviz.data.DataManager.prototype.onClearDatasourceGroupCache=function(){return this._clearDatasourceGroupCache};
epiviz.data.DataManager.prototype.onFlushCache=function(){return this._flushCache};epiviz.data.DataManager.prototype.onRequestCurrentLocation=function(){return this._requestCurrentLocation};epiviz.data.DataManager.prototype.onRequestGetChartSettings=function(){return this._requestGetChartSettings};epiviz.data.DataManager.prototype.onRequestSetChartSettings=function(){return this._requestSetChartSettings};epiviz.data.DataManager.prototype.onRequestGetAvailableCharts=function(){return this._requestGetAvailableCharts};
epiviz.data.DataManager.prototype.onRequestUiStatus=function(){return this._requestUiStatus};
epiviz.data.DataManager.prototype.getSeqInfos=function(a,b){var c=this,d=0,e={},f=[];this._dataProviderFactory.foreach(function(g){g.getData(epiviz.data.Request.getSeqInfos(g.id()),function(b){b=b.data();if(5==epiviz.EpiViz.VERSION){if(b)if(Array.isArray(b))for(h=0;h<b.length;++h)b[h][0]in e||f.push(epiviz.datatypes.SeqInfo.fromRawObject(b[h]));else for(var g=Object.keys(b),h=0;h<g.length;h++)for(var p=0;p<b[g[h]].length;p++)f.push(epiviz.datatypes.SeqInfo.fromRawObject([b[g[h]][p][0],b[g[h]][p][1],
b[g[h]][p][2],g[h]]))}else if(b)if(Array.isArray(b))for(h=0;h<b.length;++h)b[h][0]in e||(f.push(epiviz.datatypes.SeqInfo.fromRawObject(b[h])),e[b[h][0]]=!0);else for(g=Object.keys(b),h=0;h<g.length;h++)g[h]in e||(f.push(epiviz.datatypes.SeqInfo.fromRawObject([g[h],b[g[h]][0],b[g[h]][1]])),e[g[h]]=!0);++d<c._dataProviderFactory.size()||a(f.sort(epiviz.datatypes.SeqInfo.compare))},function(a,c,d){b(a,c,d)})})};
epiviz.data.DataManager.prototype.updateChartSettings=function(a){this._dataProviderFactory.foreach(function(b){if(b.id().includes("websocket-")){var c=null;null!=a.colorMap&&(c=a.colorMap._colors);b.updateChartSettings(epiviz.data.Request.createRequest({action:epiviz.data.Request.Action.SET_CHART_SETTINGS,settings:a.settings,colorMap:c,chartId:a.chartId}),function(){})}})};
epiviz.data.DataManager.prototype.getMeasurements=function(a,b){var c=this,d=new epiviz.measurements.MeasurementSet,e=0;this._dataProviderFactory.foreach(function(f){f.getData(epiviz.data.Request.getMeasurements(f.id()),function(b){if(b=b.data())for(var g=b.id?b.id.length||0:0,m=0;m<g;++m)d.add(new epiviz.measurements.Measurement(b.id[m],b.name[m],$.isArray(b.type)?b.type[m]:b.type,$.isArray(b.datasourceId)?b.datasourceId[m]:b.datasourceId,$.isArray(b.datasourceGroup)?b.datasourceGroup[m]:b.datasourceGroup,
f.id(),null,$.isArray(b.defaultChartType)?b.defaultChartType[m]:b.defaultChartType,b.annotation[m],$.isArray(b.minValue)?b.minValue[m]:b.minValue,$.isArray(b.maxValue)?b.maxValue[m]:b.maxValue,eval(b.metadata[m])));++e<c._dataProviderFactory.size()||a(d)},function(a,c,d){b(a,c,d)})})};epiviz.data.DataManager.prototype.getData=function(a,b,c,d){this._config.useCache&&null!=a&&"all"!=a.seqName()&&null!=a.seqName()?this._cache.getData(a,b,c,d):this._getDataNoCache(a,b,c,d)};
epiviz.data.DataManager.prototype._getDataNoCache=function(a,b,c,d){var e=this,f={},g;for(g in b)if(b.hasOwnProperty(g)){var h=b[g].split(function(a){return a.dataprovider()}),m;for(m in h)if(h.hasOwnProperty(m)){var l=f[m];void 0==l?f[m]=h[m]:l.addAll(h[m])}}var p={};epiviz.utils.forEach(f,function(f,g){var h=f.split(function(a){return a.datasource().id()}),l=epiviz.data.Request.getCombined(h,a),m=e._combinedRequestsStacks[g];void 0==m&&(m=new epiviz.data.RequestStack,e._combinedRequestsStacks[g]=
m);m.pushRequest(l,function(d){var f={};epiviz.utils.forEach(h,function(b,c){var e=b.first().datasource(),g=new epiviz.datatypes.PartialSummarizedExperiment,h=d.rows.globalStartIndex,l=a;if(null==a||"all"==a.seqName()||null==a.seqName())l=new epiviz.datatypes.GenomicRange("all",d.rows.values.start[0],d.rows.values.start.length);isNaN(l._start)&&(l._start=h);isNaN(l._width)&&(l._width=d.rows.end[d.rows.end.length-1]-a._start);e=new epiviz.datatypes.GenomicRangeArray(e,l,h,d.rows.values,d.rows.useOffset);
g.addRowData(e);b.foreach(function(a){a=new epiviz.datatypes.FeatureValueArray(a,l,h,d.values.values[a.id()]);g.addValues(a)});f[c]=g});p[g]=f;e._serveAvailableData(a,b,c,p)});(e._dataProviderFactory.get(g)||e._dataProviderFactory.get(epiviz.data.EmptyResponseDataProvider.DEFAULT_ID)).getData(l,function(a){m.serveData(a)},function(a,b,c){d(a,b,c)})})};
epiviz.data.DataManager.prototype._serveAvailableData=function(a,b,c,d){var e=[];epiviz.utils.forEach(b,function(a,b){var f=!0,g=new epiviz.measurements.MeasurementHashtable;a.foreach(function(a){if(!(a.dataprovider()in d))return f=!1,!0;var b=new epiviz.datatypes.MeasurementGenomicDataWrapper(a,d[a.dataprovider()][a.datasource().id()]);g.put(a,b)});if(f){var l=new epiviz.datatypes.MapGenomicData(g);c(b,l);e.push(b)}});e.forEach(function(a){delete b[a]})};
epiviz.data.DataManager.prototype.getPCA=function(a,b,c){var d=this,e={},f;for(f in b)if(b.hasOwnProperty(f)){var g=b[f].split(function(a){return a.dataprovider()}),h;for(h in g)if(g.hasOwnProperty(h)){var m=e[h];void 0==m?e[h]=g[h]:m.addAll(g[h])}}epiviz.utils.forEach(e,function(b,e){var g=b.split(function(a){return a.datasource().id()}),h=epiviz.data.Request.getPCA(g,a),l=Object.keys(g)[0];(d._dataProviderFactory.get(e)||d._dataProviderFactory.get(epiviz.data.EmptyResponseDataProvider.DEFAULT_ID)).getData(h,
function(a){var b=a.data();"websocket"==a.data().dataprovidertype&&(b=b[l]);c(f,b)})})};
epiviz.data.DataManager.prototype.getDiversity=function(a,b,c){var d=this,e={},f;for(f in b)if(b.hasOwnProperty(f)){var g=b[f].split(function(a){return a.dataprovider()}),h;for(h in g)if(g.hasOwnProperty(h)){var m=e[h];void 0==m?e[h]=g[h]:m.addAll(g[h])}}epiviz.utils.forEach(e,function(b,e){var g=b.split(function(a){return a.datasource().id()}),h=epiviz.data.Request.getDiversity(g,a),l=Object.keys(g)[0];(d._dataProviderFactory.get(e)||d._dataProviderFactory.get(epiviz.data.EmptyResponseDataProvider.DEFAULT_ID)).getData(h,
function(a){var b=a.data();"websocket"==a.data().dataprovidertype&&(b=b[l]);c(f,b)})})};
epiviz.data.DataManager.prototype.getHierarchy=function(a,b){for(var c in a)if(a.hasOwnProperty(c))var d=a[c];var e=d.dataprovider;e||d.measurements.foreach(function(a){return a.dataprovider()?(e=a.dataprovider(),!0):!1});var f=d.datasourceGroup;f||d.measurements.foreach(function(a){return a.datasourceGroup()?(f=a.datasourceGroup(),!0):!1});(this._dataProviderFactory.get(e)||this._dataProviderFactory.get(epiviz.data.EmptyResponseDataProvider.DEFAULT_ID)).getData(epiviz.data.Request.getHierarchy(f,d.customData),
function(a){b(c,a.data())})};
epiviz.data.DataManager.prototype.propagateHierarchyChanges=function(a,b){for(var c in a)if(a.hasOwnProperty(c)){var d=a[c],e=d.dataprovider;e||d.measurements.foreach(function(a){return a.dataprovider()?(e=a.dataprovider(),!0):!1});var f=this._dataProviderFactory.get(e)||this._dataProviderFactory.get(epiviz.data.EmptyResponseDataProvider.DEFAULT_ID);(function(a,c,d){c.getData(epiviz.data.Request.propagateHierarchyChanges(d.datasourceGroup,d.customData.selection,d.customData.order,d.customData.selectedLevels),
function(e){setTimeout(function(){c.onRequestClearDatasourceGroupCache().notify({datasourceGroup:d.datasourceGroup,result:new epiviz.events.EventResult});c.onRequestRedraw().notify({result:new epiviz.events.EventResult});b(a,e.data())},0)})})(c,f,d)}};
epiviz.data.DataManager.prototype.getWorkspaces=function(a,b,c){var d=this._dataProviderFactory.workspacesDataProvider();if(!d)throw Error("Invalid data provider for workspaces (see Config.workspaceDataProvider)");d.getData(epiviz.data.Request.getWorkspaces(b,c),function(b){b=b.data();var c=[];if(b&&b.length)for(var d=0;d<b.length;++d)c.push({id:b[d].id,name:b[d].name,content:JSON.parse(b[d].content)});a(c)})};
epiviz.data.DataManager.prototype.saveWorkspace=function(a,b,c){var d=this._dataProviderFactory.workspacesDataProvider();if(!d)throw Error("Invalid data provider for workspaces (see Config.workspaceDataProvider)");d.getData(epiviz.data.Request.saveWorkspace(a,b),function(a){a=a.data();c(a)})};
epiviz.data.DataManager.prototype.deleteWorkspace=function(a){var b=this._dataProviderFactory.workspacesDataProvider();if(!b)throw Error("Invalid data provider for workspaces (see Config.workspaceDataProvider)");b.getData(epiviz.data.Request.deleteWorkspace(a),function(a){a.data()})};
epiviz.data.DataManager.prototype.search=function(a,b){var c=this,d=this._dataProviderFactory.size(),e=[];this._dataProviderFactory.foreach(function(f){f.getData(epiviz.data.Request.search(b,c._config.maxSearchResults),function(b){(b=b.data())&&epiviz.utils.arrayAppend(e,b);--d;d||a(e)})})};epiviz.data.DataManager.prototype.flushCache=function(){this._cache.flush();this._flushCache.notify()};
epiviz.data.DataManager.prototype.clearDatasourceGroupCache=function(a){this._cache.clearDatasourceGroupCache(a);this._clearDatasourceGroupCache.notify({datasourceGroup:a})};epiviz.data.DataManager.prototype._registerProviderAddMeasurements=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestAddMeasurements().addListener(new epiviz.events.EventListener(function(b){a._requestAddMeasurements.notify(b)}))})};
epiviz.data.DataManager.prototype._registerProviderRemoveMeasurements=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestRemoveMeasurements().addListener(new epiviz.events.EventListener(function(b){a._requestRemoveMeasurements.notify(b)}))})};epiviz.data.DataManager.prototype._registerProviderLoadMeasurements=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestLoadMeasurements().addListener(new epiviz.events.EventListener(function(b){a._requestLoadMeasurements.notify(b)}))})};
epiviz.data.DataManager.prototype._registerProviderAddChart=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestAddChart().addListener(new epiviz.events.EventListener(function(b){a._requestAddChart.notify(b)}))})};epiviz.data.DataManager.prototype._registerProviderRemoveChart=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestRemoveChart().addListener(new epiviz.events.EventListener(function(b){a._requestRemoveChart.notify(b)}))})};
epiviz.data.DataManager.prototype._registerProviderPrintWorkspace=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestPrintWorkspace().addListener(new epiviz.events.EventListener(function(b){a._requestPrintWorkspace.notify(b)}))})};epiviz.data.DataManager.prototype._registerProviderLoadWorkspace=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestLoadWorkspace().addListener(new epiviz.events.EventListener(function(b){a._requestLoadWorkspace.notify(b)}))})};
epiviz.data.DataManager.prototype._registerProviderAddSeqInfos=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestAddSeqInfos().addListener(new epiviz.events.EventListener(function(b){for(var c=[],e=0;e<b.seqInfos.length;++e)c.push(epiviz.datatypes.SeqInfo.fromRawObject(b.seqInfos[e]));a._requestAddSeqInfos.notify({seqInfos:c,result:b.result})}))})};epiviz.data.DataManager.prototype._registerProviderRemoveSeqNames=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestRemoveSeqNames().addListener(new epiviz.events.EventListener(function(b){a._requestRemoveSeqNames.notify(b)}))})};
epiviz.data.DataManager.prototype._registerProviderNavigate=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestNavigate().addListener(new epiviz.events.EventListener(function(b){a._requestNavigate.notify(b)}))})};epiviz.data.DataManager.prototype._registerProviderRedraw=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestRedraw().addListener(new epiviz.events.EventListener(function(b){a._requestRedraw.notify(b)}))})};
epiviz.data.DataManager.prototype._registerProviderClearDatasourceGroupCache=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestClearDatasourceGroupCache().addListener(new epiviz.events.EventListener(function(b){a.clearDatasourceGroupCache(b.datasourceGroup);b.result.success=!0}))})};
epiviz.data.DataManager.prototype._registerProviderFlushCache=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestFlushCache().addListener(new epiviz.events.EventListener(function(b){a.flushCache();b.result.success=!0}))})};epiviz.data.DataManager.prototype._registerProviderGetCurrentLocation=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestCurrentLocation().addListener(new epiviz.events.EventListener(function(b){a._requestCurrentLocation.notify(b)}))})};
epiviz.data.DataManager.prototype._registerProviderSetChartSettings=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestSetChartSettings().addListener(new epiviz.events.EventListener(function(b){a._requestSetChartSettings.notify(b)}))})};epiviz.data.DataManager.prototype._registerProviderGetChartSettings=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestGetChartSettings().addListener(new epiviz.events.EventListener(function(b){a._requestGetChartSettings.notify(b)}))})};
epiviz.data.DataManager.prototype._registerProviderGetAvailableCharts=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestGetChartSettings().addListener(new epiviz.events.EventListener(function(b){a._requestGetAvailableCharts.notify(b)}))})};epiviz.data.DataManager.prototype._registerProviderUiStatus=function(){var a=this;this._dataProviderFactory.foreach(function(b){b.onRequestUiStatus().addListener(new epiviz.events.EventListener(function(b){a._requestUiStatus.notify(b)}))})};epiviz.datatypes.ItemFilteredGenomicData=function(a,b){epiviz.datatypes.MapGenomicData.call(this);this._data=a;this._filter=b;this._deferredInit=null;this._initialize()};epiviz.datatypes.ItemFilteredGenomicData.prototype=epiviz.utils.mapCopy(epiviz.datatypes.MapGenomicData.prototype);epiviz.datatypes.ItemFilteredGenomicData.constructor=epiviz.datatypes.ItemFilteredGenomicData;
epiviz.datatypes.ItemFilteredGenomicData.prototype._initialize=function(){if(this._deferredInit)return this._deferredInit;this._deferredInit=new epiviz.deferred.Deferred;var a=this,b=this._filter,c=this._data;c.ready(function(){b.preMark()(c).done(function(d){var e=new epiviz.measurements.MeasurementHashtable,f=c.measurements();epiviz.utils.deferredFor(f.length,function(a){var g=new epiviz.deferred.Deferred,m=f[a],l=[],p={};epiviz.utils.deferredFor(c.size(m),function(a){var e=new epiviz.deferred.Deferred,
f=c.get(m,a);b.mark()(f,c,d).done(function(a){a&&(l.push(f),p[f.globalIndex]=f);e.resolve()});return e}).done(function(){e.put(m,new epiviz.datatypes.MeasurementGenomicDataArrayWrapper(m,l,p));g.resolve()});return g}).done(function(){a._setMap(e);a._deferredInit.resolve()})})});return this._deferredInit};epiviz.datatypes.RowItemImpl=function(a,b,c,d,e,f,g){this._id=a;this._seqName=b;this._start=c;this._end=d;this._globalIndex=e;this._strand=f;this._rowMetadata=g};epiviz.datatypes.RowItemImpl.prototype.id=function(){return this._id};epiviz.datatypes.RowItemImpl.prototype.seqName=function(){return this._seqName};epiviz.datatypes.RowItemImpl.prototype.start=function(){return this._start};epiviz.datatypes.RowItemImpl.prototype.end=function(){return this._end};
epiviz.datatypes.RowItemImpl.prototype.globalIndex=function(){return this._globalIndex};epiviz.datatypes.RowItemImpl.prototype.strand=function(){return this._strand};epiviz.datatypes.RowItemImpl.prototype.metadata=function(a){a=this._rowMetadata[a];return void 0==a?null:a};epiviz.datatypes.RowItemImpl.prototype.rowMetadata=function(){return this._rowMetadata};epiviz.datatypes.MeasurementAggregatedGenomicData=function(a,b,c){epiviz.datatypes.MapGenomicData.call(this);this._data=a;this._groupByMarker=b;this._aggregator=c;this._deferredInit=null;this._initialize()};epiviz.datatypes.MeasurementAggregatedGenomicData.prototype=epiviz.utils.mapCopy(epiviz.datatypes.MapGenomicData.prototype);epiviz.datatypes.MeasurementAggregatedGenomicData.constructor=epiviz.datatypes.MeasurementAggregatedGenomicData;
epiviz.datatypes.MeasurementAggregatedGenomicData.prototype._initialize=function(){if(this._deferredInit)return this._deferredInit;this._deferredInit=new epiviz.deferred.Deferred;var a=this,b=this._groupByMarker,c=this._data;c.ready(function(){b.preMark()(c).done(function(d){var e=new epiviz.measurements.MeasurementHashtable,f={},g=c.measurements();epiviz.utils.deferredFor(g.length,function(a){var e=new epiviz.deferred.Deferred,h=g[a];b.mark()(h,c,d).done(function(a){a in f||(f[a]=[]);f[a].push(h);
e.resolve()});return e}).done(function(){var b={},d,g;for(d in f)if(f.hasOwnProperty(d)){g=f[d];var p=d+"-group",n=d,t=g[0].type(),v=g[0].datasourceId(),q=g[0].datasourceGroup(),u=g[0].dataprovider(),w=g[0].defaultChartType(),r=epiviz.utils.mapCopy(g[0].annotation()),A=g[0].minValue(),B=g[0].maxValue(),z=g[0].metadata(),x={};z.forEach(function(a){x[a]=a});f[d].forEach(function(a){v!=a.datasourceId()&&(v="*");q!=a.datasourceGroup()&&(q="*");u!=a.dataprovider()&&(u="*");w!=a.defaultChartType()&&(w=
"*");var b=a.annotation();if(r!=b)if(void 0==r)r=epiviz.utils.mapCopy(b);else if(void 0!=b)for(var c in b)b.hasOwnProperty(c)&&(c in r?r[c]!=b[c]&&(r[c]="*"):r[c]=b[c]);A=Math.min(A,a.minValue());B=Math.max(B,a.maxValue());a.metadata().forEach(function(a){a in x||(x[a]=a,z.push(a))})});b[d]=new epiviz.measurements.Measurement(p,n,t,v,q,u,null,w,r,A,B,z)}for(d in f)if(f.hasOwnProperty(d)){p=b[d];n=[];t={};g=f[d];for(var E=Math.min.apply(void 0,g.map(function(a){return c.globalStartIndex(a)})),G=Math.max.apply(void 0,
g.map(function(a){return c.globalEndIndex(a)})),F=E;F<G;++F){var y=g.map(function(a){return c.getByGlobalIndex(a,F)}).filter(function(a){return a});y.length&&(E=y.map(function(a){return a.value}),E=a._aggregator.aggregate(d,g,E),y=y[0].rowItem,y=new epiviz.datatypes.RowItemImpl(y.id(),y.seqName(),y.start(),y.end(),y.globalIndex(),y.strand(),y.rowMetadata()||{}),E=new epiviz.datatypes.GenomicData.ValueItem(F,y,E.value,p,{errMinus:E.errMinus,errPlus:E.errPlus}),n.push(E),t[F]=E)}g=new epiviz.datatypes.MeasurementGenomicDataArrayWrapper(p,
n,t);e.put(p,g)}a._setMap(e);a._deferredInit.resolve()})})});return this._deferredInit};epiviz.datatypes.MeasurementOrderedGenomicData=function(a,b){epiviz.datatypes.MapGenomicData.call(this);this._data=a;this._order=b;this._deferredInit=null;this._initialize()};epiviz.datatypes.MeasurementOrderedGenomicData.prototype=epiviz.utils.mapCopy(epiviz.datatypes.MapGenomicData.prototype);epiviz.datatypes.MeasurementOrderedGenomicData.constructor=epiviz.datatypes.MeasurementOrderedGenomicData;
epiviz.datatypes.MeasurementOrderedGenomicData.prototype._initialize=function(){if(this._deferredInit)return this._deferredInit;this._deferredInit=new epiviz.deferred.Deferred;var a=this,b=this._data,c=this._order;b.ready(function(){c.preMark()(b).done(function(d){var e=new epiviz.measurements.MeasurementHashtable,f=b.measurements(),g=new epiviz.measurements.MeasurementHashtable;epiviz.utils.deferredFor(f.length,function(a){var e=new epiviz.deferred.Deferred,h=f[a];c.mark()(h,b,d).done(function(a){g.put(h,
a);e.resolve()});return e}).done(function(){f.sort(function(a,b){var c=g.get(a),d=g.get(b);return c==d?0:c<d?-1:1});f.forEach(function(a){e.put(a,b.getSeries(a))});a._setMap(e);a._deferredInit.resolve()})})});return this._deferredInit};epiviz.deferred.Promise=function(a){this._promise=a};epiviz.deferred.Promise.prototype.then=function(a,b,c){return new epiviz.deferred.Promise(this._promise.then(a,b,c))};epiviz.deferred.Promise.prototype.done=function(a,b){return new epiviz.deferred.Deferred(this._promise.done(a,b))};epiviz.deferred.Promise.prototype.fail=function(a,b){return new epiviz.deferred.Deferred(this._promise.fail(a,b))};
epiviz.deferred.Promise.prototype.always=function(a,b){return new epiviz.deferred.Deferred(this._promise.always(a,b))};epiviz.deferred.Promise.prototype.state=function(){return this._promise.state()};epiviz.ui.charts.CustomSetting=function(a,b,c,d,e){this.id=a;this.type=b;this.defaultValue=c;this.label=d||a;this.possibleValues=e||null};epiviz.ui.charts.CustomSetting.Type={NUMBER:"number",STRING:"string",ARRAY:"array",BOOLEAN:"boolean",CATEGORICAL:"categorical",MEASUREMENTS_METADATA:"measurementsMetadata",MEASUREMENTS_ANNOTATION:"measurementsAnnotation"};epiviz.ui.charts.CustomSetting.DEFAULT="default";epiviz.ui.charts.VisEventArgs=function(a,b){this.id=a;this.args=b};epiviz.ui.charts.Axis={X:"x",Y:"y"};epiviz.ui.charts.Margins=function(a,b,c,d){this._top=a;this._left=b;this._bottom=c;this._right=d};epiviz.ui.charts.Margins.ZERO_MARGIN=new epiviz.ui.charts.Margins(0,0,0,0);epiviz.ui.charts.Margins.prototype.top=function(){return this._top};epiviz.ui.charts.Margins.prototype.left=function(){return this._left};epiviz.ui.charts.Margins.prototype.bottom=function(){return this._bottom};epiviz.ui.charts.Margins.prototype.right=function(){return this._right};
epiviz.ui.charts.Margins.prototype.sumAxis=function(a){switch(a){case epiviz.ui.charts.Axis.X:return this._left+this._right;case epiviz.ui.charts.Axis.Y:return this._top+this._bottom;default:throw Error("Invalid argument: "+a);}};epiviz.ui.charts.Margins.prototype.raw=function(){return{top:this._top,left:this._left,bottom:this._bottom,right:this._right}};epiviz.ui.charts.Margins.fromRawObject=function(a){return new epiviz.ui.charts.Margins(a.top,a.left,a.bottom,a.right)};
epiviz.ui.charts.Margins.prototype.copy=function(){return new epiviz.ui.charts.Margins(this._top,this._left,this._bottom,this._right)};epiviz.ui.charts.Margins.prototype.equals=function(a){return a?this==a?!0:this._top==a._top&&this._left==a._left&&this._bottom==a._bottom&&this._right==a._right:!1};epiviz.ui.charts.Visualization=function(a,b,c){this._id=a;this._container=b;this._properties=c;this._originalMethods={};this._hasModifiedMethods=!1;this._lastModifiedMethod="draw";var d=this;if(c.modifiedMethods){var e=new epiviz.deferred.Deferred,f=c.modifiedMethods,g=Object.keys(f),h={},m=0,l;for(l in f)f.hasOwnProperty(l)&&"_setModifiedMethods"!=l&&function(a){epiviz.caja.cajole(f[a],epiviz.caja.buildChartMethodContext()).done(function(b){b&&(h[a]=b,m+=1,m>=g.length&&e.resolve())})}(l);e.done(function(){for(var a in h)h.hasOwnProperty(a)&&
(d._originalMethods[a]=d[a],d[a]=h[a],d._lastModifiedMethod=a);d._hasModifiedMethods=!0;d.draw()})}this._customSettingsValues={};for(a=0;a<c.customSettingsDefs.length;++a)switch(b=c.customSettingsDefs[a],l=c.customSettingsValues[b.id],b.type){case epiviz.ui.charts.CustomSetting.Type.BOOLEAN:this._customSettingsValues[b.id]=!1===l||l?l:b.defaultValue;break;case epiviz.ui.charts.CustomSetting.Type.NUMBER:this._customSettingsValues[b.id]=0===l||l?l:b.defaultValue;break;case epiviz.ui.charts.CustomSetting.Type.STRING:this._customSettingsValues[b.id]=
""===l||l?l:b.defaultValue;break;case epiviz.ui.charts.CustomSetting.Type.MEASUREMENTS_METADATA:var p={};c.visConfigSelection.measurements.foreach(function(a){a.metadata().forEach(function(a){p[a]=a})});b.possibleValues=Object.keys(p);b.possibleValues.sort();l=l||b.defaultValue;this._customSettingsValues[b.id]=l in p?l:b.possibleValues.length?b.possibleValues[0]:"";break;case epiviz.ui.charts.CustomSetting.Type.MEASUREMENTS_ANNOTATION:p={name:"name"};c.visConfigSelection.measurements.foreach(function(a){(a=
a.annotation())&&Object.keys(a).forEach(function(a){p[a]=a})});b.possibleValues=Object.keys(p);b.possibleValues.sort();l=l||b.defaultValue;this._customSettingsValues[b.id]=l in p?l:b.possibleValues.length?b.possibleValues[0]:"";break;default:this._customSettingsValues[b.id]=l||b.defaultValue}this._svgId=sprintf("%s-svg",this._id);this._lastRange=this._lastData=this._unalteredData=this._svg=null;this._slide=0;this._zoom=1;this._markers=c.chartMarkers;this._markersMap={};this._markersIndices={};this._markers.forEach(function(a,
b){a&&(d._markersMap[a.id()]=a,d._markersIndices[a.id()]=b)});this._autoPropagateChanges=!0;this._hover=new epiviz.events.Event;this._unhover=new epiviz.events.Event;this._select=new epiviz.events.Event;this._deselect=new epiviz.events.Event;this._save=new epiviz.events.Event;this._remove=new epiviz.events.Event;this._colorsChanged=new epiviz.events.Event;this._methodsModified=new epiviz.events.Event;this._methodsReset=new epiviz.events.Event;this._markersModified=new epiviz.events.Event;this._customSettingsChanged=
new epiviz.events.Event;this._sizeChanged=new epiviz.events.Event;this._marginsChanged=new epiviz.events.Event;this._dataWaitStart=new epiviz.events.Event;this._dataWaitEnd=new epiviz.events.Event};epiviz.ui.charts.Visualization.SVG_MARGIN=20;
epiviz.ui.charts.Visualization.prototype._initialize=function(){"100%"==this._properties.height&&(this._properties.height=this._container.height()-epiviz.ui.charts.Visualization.SVG_MARGIN);"100%"==this._properties.width&&(this._properties.width=this._container.width()-epiviz.ui.charts.Visualization.SVG_MARGIN);var a=this.width(),b=this.height();this._container.addClass("visualization-container");this._container.append(sprintf('<svg id="%s" class="visualization" width="%s" height="%s"><style type="text/css"></style><defs></defs></svg>',
this._svgId,a,b));this._svg=d3.select(this._container.find("#"+this._svgId)[0]);this._widthDif=$(this._container.find("#"+this._svgId)[0]).width()-(this._container.width()-epiviz.ui.charts.Visualization.SVG_MARGIN);this._heightDif=b-(this._container.height()-epiviz.ui.charts.Visualization.SVG_MARGIN);this._properties.width=a;this._properties.height=b;var c=this;this._container.click(function(){c._deselect.notify(new epiviz.ui.charts.VisEventArgs(c._id))})};
epiviz.ui.charts.Visualization.prototype._clearAxes=function(a){a=a||this._svg;a.selectAll(".xAxis").remove();a.selectAll(".yAxis").remove()};
epiviz.ui.charts.Visualization.prototype._drawAxesCanvas=function(a,b,c,d,e,f,g,h,m,l,p,n,t,v){e=e||this._canvas;h=h||this.margins();g=g||this.height();f=f||this.width();var q=e.getContext("2d");q.lineWidth=1;if(a){q.beginPath();q.strokeStyle="black";q.moveTo(h.left(),g-h.bottom());q.lineTo(f-h.right(),g-h.bottom());q.stroke();var u=a.ticks(c);u.forEach(function(b){q.beginPath();q.moveTo(h.left()+a(b),g-h.bottom());q.lineTo(h.left()+a(b),g-h.bottom()+6);q.strokeStyle="#565656";q.stroke();q.moveTo(h.left()+
a(b),g-h.bottom());q.lineTo(h.left()+a(b),h.top());q.strokeStyle="#dcdcdc";q.stroke()});q.beginPath();q.textAlign="center";q.textBaseline="top";u.map(m||(p?function(a){return p[a]||""}:function(a){return d3.format("s")(Math.round(1E3*a)/1E3)})).forEach(function(b,c){q.fillText(b,h.left()+a(u[c]),g-h.bottom()+8)})}q.stroke();if(b){q.beginPath();q.strokeStyle="black";q.moveTo(h.left(),g-h.bottom());q.lineTo(h.left(),h.top());q.stroke();var w=b.ticks(d-1);w.forEach(function(a){q.beginPath();q.moveTo(h.left(),
b(a)+h.top());q.lineTo(h.left()-6,b(a)+h.top());q.strokeStyle="#565656";q.stroke();q.moveTo(h.left(),b(a)+h.top());q.lineTo(f-h.right(),b(a)+h.top());q.strokeStyle="#dcdcdc";q.stroke()});q.stroke();q.beginPath();q.textAlign="right";q.textBaseline="middle";w.map(n?function(a){return n[a]}:function(a){return d3.format("s")(Math.round(1E3*a)/1E3)}).forEach(function(a,c){q.fillText(a,h.left()-8,b(w[c])+h.top())});q.stroke()}};
epiviz.ui.charts.Visualization.prototype._drawAxes=function(a,b,c,d,e,f,g,h,m,l,p,n,t,v){e=e||this._svg;h=h||this.margins();g=g||this.height();f=f||this.width();var q=e.select(".axes"),u=q.select(".xAxis-grid");v=q.select(".yAxis-grid");var w=q.select(".xAxis-line");l=q.select(".yAxis-line");q.empty()&&(q=e.append("g").attr("class","axes"));u.empty()&&(u=q.append("g").attr("class","xAxis xAxis-grid"));v.empty()&&(v=q.append("g").attr("class","yAxis yAxis-grid"));w.empty()&&(w=q.append("g").attr("class",
"xAxis xAxis-line"));l.empty()&&(l=q.append("g").attr("class","yAxis yAxis-line"));a&&(u.attr("transform","translate("+h.left()+", "+h.top()+")").selectAll("line.x").data(a.ticks(c)).enter().append("line").attr("x1",a).attr("x2",a).attr("y1",0).attr("y2",g-h.top()-h.bottom()).style("stroke","#eeeeee").style("shape-rendering","crispEdges"),e=m||(p?function(a){return p[a]}:function(a){return d3.format("s")(Math.round(1E3*a)/1E3)}),c=d3.svg.axis().scale(a).orient("bottom").ticks(c).tickFormat(e),w.attr("transform",
"translate("+h.left()+", "+(g-h.bottom())+")").call(c),p&&(g="rotate(-90)",t&&(g+="translate(0,"+(a(.5)-a(0))+")"),w.selectAll("text").style("text-anchor","end").attr("dx","-.8em").attr("dy","-0.5em").attr("transform",g)));b&&(v.attr("transform","translate("+h.left()+", "+h.top()+")").selectAll("line.y").data(b.ticks(d-1)).enter().append("line").attr("x1",0).attr("x2",f-h.left()-h.right()).attr("y1",b).attr("y2",b).style("stroke","#eeeeee").style("shape-rendering","crispEdges"),a=n?function(a){return n[a]}:
function(a){var b=d3.formatPrefix(a);return"m"==b.scale?a:b.scale(a)+b.symbol},b=d3.svg.axis().ticks(d-1).scale(b).orient("left").tickFormat(a),l.attr("transform","translate("+h.left()+", "+h.top()+")").call(b))};epiviz.ui.charts.Visualization.prototype.getDataMinMax=function(a){var b=1E5,c=-1E5;a.foreach(function(a,e){var d=e._container.values(a)._values,g=Math.min.apply(null,d),d=Math.max.apply(null,d);g<b&&(b=g);d>c&&(c=d)});return[b,c]};
epiviz.ui.charts.Visualization.prototype._drawTitle=function(){var a=this._svg.selectAll(".visualization-title"),b=epiviz.ui.charts.Visualization.CustomSettings;(b=this.customSettingsValues()[b.TITLE])&&""!=b.trim()?(a.empty()&&(a=this._svg.append("text").attr("class","visualization-title").attr("text-anchor","middle")),a.attr("x",.5*this.width()).attr("y",25).text(b)):a.empty()||a.remove()};
epiviz.ui.charts.Visualization.prototype.resize=function(a,b){a&&(this._properties.width=a);b&&(this._properties.height=b);this.draw();this._sizeChanged.notify(new epiviz.ui.charts.VisEventArgs(this._id,{width:this._properties.width,height:this._properties.height}))};epiviz.ui.charts.Visualization.prototype.updateSize=function(){this.resize(this._widthDif+this._container.width()-epiviz.ui.charts.Visualization.SVG_MARGIN,this._heightDif+this._container.height()-epiviz.ui.charts.Visualization.SVG_MARGIN)};
epiviz.ui.charts.Visualization.prototype.draw=function(a,b){void 0!=a&&(this._lastRange=a);void 0!=b&&(this._unalteredData=this._lastData=b,this._dataWaitEnd.notify(new epiviz.ui.charts.VisEventArgs(this._id)));this._svg.attr("width",this.width()).attr("height",this.height());this._drawTitle();return[]};epiviz.ui.charts.Visualization.prototype.container=function(){return this._container};epiviz.ui.charts.Visualization.prototype.id=function(){return this._id};
epiviz.ui.charts.Visualization.prototype.properties=function(){return this._properties};epiviz.ui.charts.Visualization.prototype.height=function(){return this._properties.height};epiviz.ui.charts.Visualization.prototype.width=function(){return this._properties.width};epiviz.ui.charts.Visualization.prototype.margins=function(){return this._properties.margins};epiviz.ui.charts.Visualization.prototype.colors=function(){return this._properties.colors};
epiviz.ui.charts.Visualization.prototype.setColors=function(a){a&&!a.equals(this._properties.colors)&&(this._properties.colors=a,this.draw(),this._colorsChanged.notify(new epiviz.ui.charts.VisEventArgs(this._id,this._properties.colors)))};epiviz.ui.charts.Visualization.prototype.colorLabels=function(){var a=Array(this.measurements().size());this.measurements().foreach(function(b,c){a[c]=b.name()});return a};epiviz.ui.charts.Visualization.prototype.measurements=function(){return this.properties().visConfigSelection.measurements};
epiviz.ui.charts.Visualization.prototype.customSettingsValues=function(){return this._customSettingsValues};
epiviz.ui.charts.Visualization.prototype.setCustomSettingsValues=function(a){if(this._customSettingsValues!=a&&a&&!epiviz.utils.mapEquals(this._customSettingsValues,a)){var b=epiviz.ui.charts.Visualization.CustomSettings,c=a[b.TITLE]||"",d=(this._customSettingsValues[b.TITLE]||"").trim().length,c=c.trim().length;if(!(d*c)&&d+c){var d=20*epiviz.utils.sign(c-d),c=a[b.MARGIN_TOP]||this._properties.margins.top(),e=a[b.MARGIN_LEFT]||this._properties.margins.left(),f=a[b.MARGIN_RIGHT]||this._properties.margins.right(),
g=a[b.MARGIN_BOTTOM]||this._properties.margins.bottom();a[b.MARGIN_TOP]=c+d;a[b.MARGIN_LEFT]=e;a[b.MARGIN_RIGHT]=f;a[b.MARGIN_BOTTOM]=g}d=this._customSettingsValues[epiviz.ui.charts.ChartType.CustomSettings.MEASUREMENT_GROUPS_AGGREGATOR];c=a[epiviz.ui.charts.ChartType.CustomSettings.MEASUREMENT_GROUPS_AGGREGATOR];this._customSettingsValues=a;b.MARGIN_TOP in a&&b.MARGIN_BOTTOM in a&&b.MARGIN_LEFT in a&&b.MARGIN_RIGHT in a&&(this._properties.margins=new epiviz.ui.charts.Margins(a[b.MARGIN_TOP],a[b.MARGIN_LEFT],
a[b.MARGIN_BOTTOM],a[b.MARGIN_RIGHT]),this._marginsChanged.notify(new epiviz.ui.charts.VisEventArgs(this._id,this._properties.margins)));if(d!=c){var h=this;this.transformData(this._lastRange,this._unalteredData).done(function(){h.draw()})}else this.draw();this._customSettingsChanged.notify(new epiviz.ui.charts.VisEventArgs(this._id,a))}};
epiviz.ui.charts.Visualization.prototype.setModifiedMethods=function(a){var b=this,c=!1;if(a){var d=Object.keys(a),e=new epiviz.deferred.Deferred,f=0,g={},h;for(h in a)a.hasOwnProperty(h)&&"_setModifiedMethods"!=h&&this[h].toString()!=a[h]&&(h in this._originalMethods||(this._originalMethods[h]=this[h]),function(b){epiviz.caja.cajole(a[b],epiviz.caja.buildChartMethodContext()).done(function(a){a&&(g[b]=a,c=!0,f+=1,f>=d.length&&e.resolve())})}(h));e.done(function(){if(c){for(var d in g)g.hasOwnProperty(d)&&
(b[d]=g[d],b._lastModifiedMethod=d);b._hasModifiedMethods=!0;b.draw();b._methodsModified.notify(new epiviz.ui.charts.VisEventArgs(b._id,a))}})}};epiviz.ui.charts.Visualization.prototype.hasModifiedMethods=function(){return this._hasModifiedMethods};epiviz.ui.charts.Visualization.prototype.lastModifiedMethod=function(){return this._lastModifiedMethod};
epiviz.ui.charts.Visualization.prototype.resetModifiedMethods=function(){if(this._hasModifiedMethods){for(var a in this._originalMethods)this._originalMethods.hasOwnProperty(a)&&(this[a]=this._originalMethods[a]);this._hasModifiedMethods=!1;this.draw();this._methodsReset.notify(new epiviz.ui.charts.VisEventArgs(this._id))}};
epiviz.ui.charts.Visualization.prototype.putMarker=function(a){if(a){var b;if(a.id()in this._markersMap){b=this._markersIndices[a.id()];var c=this._markers[b];if(c==a||c.type()==a.type()&&c.preMarkStr()==a.preMarkStr()&&c.markStr()==a.markStr())return;this._markers[b]=a}else b=this._markers.length,this._markers.push(a),this._markersIndices[a.id()]=b;this._markersMap[a.id()]=a;var d=this;this.transformData(this._lastRange,this._unalteredData).done(function(){d.draw()});this._markersModified.notify(new epiviz.ui.charts.VisEventArgs(this._id,
this._markers))}};epiviz.ui.charts.Visualization.prototype.removeMarker=function(a){if(a in this._markersMap){this._markers[this._markersIndices[a]]=null;delete this._markersMap[a];delete this._markersIndices[a];var b=this;this.transformData(this._lastRange,this._unalteredData).done(function(){b.draw()});this._markersModified.notify(new epiviz.ui.charts.VisEventArgs(this._id,this._markers))}};
epiviz.ui.charts.Visualization.prototype.getMarker=function(a){return a&&a in this._markersMap?this._markersMap[a]:null};epiviz.ui.charts.Visualization.prototype.displayType=function(){throw Error("unimplemented abstract method");};epiviz.ui.charts.Visualization.prototype.autoPropagateChanges=function(){return this._autoPropagateChanges};epiviz.ui.charts.Visualization.prototype.setAutoPropagateChanges=function(a){this._autoPropagateChanges=a};
epiviz.ui.charts.Visualization.prototype.transformData=function(a,b){var c=this._lastRange;void 0!=a&&(this._lastRange=a);void 0!=b&&(this._unalteredData=this._lastData=b);c&&a&&c.overlapsWith(a)&&c.width()==a.width()&&(this._slide=a.start()-c.start());c&&a&&c.overlapsWith(a)&&c.width()!=a.width()&&(this._zoom=c.width()/a.width());c=new epiviz.deferred.Deferred;c.resolve();return c};epiviz.ui.charts.Visualization.prototype.onHover=function(){return this._hover};
epiviz.ui.charts.Visualization.prototype.onUnhover=function(){return this._unhover};epiviz.ui.charts.Visualization.prototype.onSelect=function(){return this._select};epiviz.ui.charts.Visualization.prototype.onDeselect=function(){return this._deselect};
epiviz.ui.charts.Visualization.prototype.doHover=function(a){if("canvas"==this.chartDrawType)this._canvasHoverObject=a;else{var b=this._container.find(".items"),c=b.find("> .hovered"),d=b.find("> .selected"),e=d.find("> .hovered"),f=function(){if(Array.isArray(a)){for(var b=!1,c=0;c<a.length;c++)a[c].overlapsWith(d3.select(this).data()[0])&&(b=!0);return b}return a.overlapsWith(d3.select(this).data()[0])},b=b.find("> .item").filter(f);c.append(b);b=d.find("> .item").filter(f);e.append(b)}};
epiviz.ui.charts.Visualization.prototype.doUnhover=function(){if("canvas"==this.chartDrawType)this._canvasHoverObject=null,this.hoverCanvas.getContext("2d").clearRect(0,0,this.hoverCanvas.width,this.hoverCanvas.height);else{var a=this._container.find(".items"),b=a.find("> .hovered"),c=a.find("> .selected"),d=c.find("> .hovered");a.prepend(b.children());c.prepend(d.children())}};
epiviz.ui.charts.Visualization.prototype.doSelect=function(a){var b=this._container.find(".items"),c=b.find("> .hovered"),d=b.find("> .selected"),e=d.find("> .hovered"),f=function(){return a.overlapsWith(d3.select(this).data()[0])},b=b.find("> .item").filter(f);d.append(b);b=c.find("> .item").filter(f);e.append(b)};
epiviz.ui.charts.Visualization.prototype.doDeselect=function(){var a=this._container.find(".items"),b=a.find("> .hovered"),c=a.find("> .selected"),d=c.find("> .hovered");a.prepend(c.find("> .item"));b.prepend(d.children())};epiviz.ui.charts.Visualization.prototype.onSave=function(){return this._save};epiviz.ui.charts.Visualization.prototype.onRemove=function(){return this._remove};epiviz.ui.charts.Visualization.prototype.onColorsChanged=function(){return this._colorsChanged};
epiviz.ui.charts.Visualization.prototype.onMethodsModified=function(){return this._methodsModified};epiviz.ui.charts.Visualization.prototype.onMethodsReset=function(){return this._methodsReset};epiviz.ui.charts.Visualization.prototype.onMarkersModified=function(){return this._markersModified};epiviz.ui.charts.Visualization.prototype.onCustomSettingsChanged=function(){return this._customSettingsChanged};epiviz.ui.charts.Visualization.prototype.onSizeChanged=function(){return this._sizeChanged};
epiviz.ui.charts.Visualization.prototype.onMarginsChanged=function(){return this._marginsChanged};epiviz.ui.charts.Visualization.prototype.onDataWaitStart=function(){return this._dataWaitStart};epiviz.ui.charts.Visualization.prototype.onDataWaitEnd=function(){return this._dataWaitEnd};
epiviz.ui.charts.Visualization.CustomSettings={TITLE:"title",MARGIN_LEFT:"marginLeft",MARGIN_RIGHT:"marginRight",MARGIN_TOP:"marginTop",MARGIN_BOTTOM:"marginBottom",X_MIN:"xMin",X_MAX:"xMax",Y_MIN:"yMin",Y_MAX:"yMax",COL_LABEL:"colLabel",ROW_LABEL:"rowLabel"};epiviz.ui.charts.VisualizationType=function(a){var b=epiviz.Config.VisualizationPropertySettings;this._config=a;this._defaultSettings=epiviz.utils.mapCombine(epiviz.utils.mapCombine(a.chartSettings[this.typeName()],a.chartSettings[this.chartDisplayType()],!0),a.chartSettings["default"],!0);this._defaultWidth=this._defaultSettings[b.WIDTH];this._defaultHeight=this._defaultSettings[b.HEIGHT];this._defaultMargins=this._defaultSettings[b.MARGINS];this._defaultColors=a.colorPalettesMap[this._defaultSettings[b.COLORS]];
this._decorations=this._defaultSettings[b.DECORATIONS];this._customSettingsValues=a.chartCustomSettings[this.typeName()]||null};epiviz.ui.charts.VisualizationType.DisplayType={PLOT:"plot",TRACK:"track",DATA_STRUCTURE:"data-structure"};epiviz.ui.charts.VisualizationType.prototype.createNew=function(a,b,c){throw Error("unimplemented abstract method");};epiviz.ui.charts.VisualizationType.prototype.typeName=function(){throw Error("unimplemented abstract method");};
epiviz.ui.charts.VisualizationType.prototype.chartName=function(){throw Error("unimplemented abstract method");};epiviz.ui.charts.VisualizationType.prototype.chartHtmlAttributeName=function(){throw Error("unimplemented abstract method");};epiviz.ui.charts.VisualizationType.prototype.chartDisplayType=function(){throw Error("unimplemented abstract method");};epiviz.ui.charts.VisualizationType.prototype.measurementsFilter=function(){return function(a){return!0}};
epiviz.ui.charts.VisualizationType.prototype.isRestrictedToSameDatasourceGroup=function(){return!1};epiviz.ui.charts.VisualizationType.prototype.isRestrictedToRangeMeasurements=function(){return!1};epiviz.ui.charts.VisualizationType.prototype.isRestrictedToFeatureMeasurements=function(){return!this.isRestrictedToRangeMeasurements()};epiviz.ui.charts.VisualizationType.prototype.minSelectedMeasurements=function(){return 1};epiviz.ui.charts.VisualizationType.prototype.chartContainer=function(){return epiviz.ui.ControlManager.CHART_TYPE_CONTAINERS[this.chartDisplayType()]};
epiviz.ui.charts.VisualizationType.prototype.cssClass=function(){throw Error("unimplemented abstract method");};epiviz.ui.charts.VisualizationType.prototype.defaultWidth=function(){return this._defaultWidth};epiviz.ui.charts.VisualizationType.prototype.defaultHeight=function(){return this._defaultHeight};epiviz.ui.charts.VisualizationType.prototype.defaultMargins=function(){return this._defaultMargins};epiviz.ui.charts.VisualizationType.prototype.defaultColors=function(){return this._defaultColors};
epiviz.ui.charts.VisualizationType.prototype.decorations=function(){return this._decorations};epiviz.ui.charts.VisualizationType.prototype.customSettingsValues=function(){return this._customSettingsValues};
epiviz.ui.charts.VisualizationType.prototype.customSettingsDefs=function(){return[new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.TITLE,epiviz.ui.charts.CustomSetting.Type.STRING,"","Title"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.MARGIN_TOP,epiviz.ui.charts.CustomSetting.Type.NUMBER,this._defaultMargins.top(),"Top margin"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.MARGIN_BOTTOM,epiviz.ui.charts.CustomSetting.Type.NUMBER,
this._defaultMargins.bottom(),"Bottom margin"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.MARGIN_LEFT,epiviz.ui.charts.CustomSetting.Type.NUMBER,this._defaultMargins.left(),"Left margin"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.MARGIN_RIGHT,epiviz.ui.charts.CustomSetting.Type.NUMBER,this._defaultMargins.right(),"Right margin")]};epiviz.ui.charts.markers={};
epiviz.ui.charts.markers.VisualizationMarker=function(a,b,c,d,e){this._type=a;this._id=b||epiviz.utils.generatePseudoGUID(6);this._name=c||"Custom Marker "+this._id;this._preMarkStr=d||"";this._markStr=e||"";var f=new epiviz.deferred.Deferred,g=null;epiviz.caja.cajole(this._preMarkStr).done(function(a){g=a;f.resolve()});this._preMark=function(a){var b=new epiviz.deferred.Deferred;f.done(function(){var c=g(a);b.resolve(c)});return b};var h=new epiviz.deferred.Deferred,m=null;epiviz.caja.cajole(this._markStr).done(function(a){m=
a;h.resolve()});this._mark=function(a,b,c){var d=new epiviz.deferred.Deferred;h.done(function(){var e=m(a,b,c);d.resolve(e)});return d}};epiviz.ui.charts.markers.VisualizationMarker.prototype.type=function(){return this._type};epiviz.ui.charts.markers.VisualizationMarker.prototype.id=function(){return this._id};epiviz.ui.charts.markers.VisualizationMarker.prototype.name=function(){return this._name};epiviz.ui.charts.markers.VisualizationMarker.prototype.preMark=function(){return this._preMark};
epiviz.ui.charts.markers.VisualizationMarker.prototype.mark=function(){return this._mark};epiviz.ui.charts.markers.VisualizationMarker.prototype.preMarkStr=function(){return this._preMarkStr};epiviz.ui.charts.markers.VisualizationMarker.prototype.markStr=function(){return this._markStr};epiviz.ui.charts.markers.VisualizationMarker.Type={FILTER:"filter",COLOR_BY_ROW:"colorByRow",ORDER_BY_MEASUREMENTS:"orderByMeasurements",COLOR_BY_MEASUREMENTS:"colorByMeasurements",GROUP_BY_MEASUREMENTS:"groupByMeasurements"};
epiviz.ui.charts.markers.VisualizationMarker.prototype.raw=function(){return{type:this._type,id:this._id,name:this._name,preMark:this._preMarkStr,mark:this._markStr}};epiviz.ui.charts.markers.VisualizationMarker.fromRawObject=function(a){return new epiviz.ui.charts.markers.VisualizationMarker(a.type,a.id,a.name,a.preMark,a.mark)};epiviz.ui.charts.VisualizationProperties=function(a,b,c,d,e,f,g,h,m){this.width=a;this.height=b;this.margins=c;this.visConfigSelection=d;this.colors=e;this.modifiedMethods=f;this.customSettingsValues=g||{};this.customSettingsDefs=h||[];this.chartMarkers=m||[]};
epiviz.ui.charts.VisualizationProperties.prototype.copy=function(){var a=new epiviz.ui.controls.VisConfigSelection(this.visConfigSelection.measurements?new epiviz.measurements.MeasurementSet(this.visConfigSelection.measurements):void 0,this.visConfigSelection.datasource,this.visConfigSelection.datasourceGroup,this.visConfigSelection.dataprovider,epiviz.utils.mapCopy(this.visConfigSelection.annotation),this.visConfigSelection.defaultChartType,this.visConfigSelection.minSelectedMeasurements);return new epiviz.ui.charts.VisualizationProperties(this.width,
this.height,this.margins?this.margins.copy():this.margins,a,this.colors,this.modifiedMethods?epiviz.utils.mapCopy(this.modifiedMethods):this.modifiedMethods,this.customSettingsValues?epiviz.utils.mapCopy(this.customSettingsValues):this.customSettingsValues,this.customSettingsDefs?this.customSettingsDefs.slice(0):this.customSettingsDefs,this.chartMarkers.slice(0))};epiviz.workspaces={};epiviz.workspaces.Workspace=function(a,b,c){this._id=a;this._name=b;this._range=c.range;this._chartsOrder={};this._chartsById={};for(var d in c.charts)if(c.charts.hasOwnProperty(d))for(this._chartsOrder[d]=[],a=0;a<c.charts[d].length;++a)this._chartsById[c.charts[d][a].id]=c.charts[d][a],this._chartsOrder[d].push(c.charts[d][a].id);this._computedMeasurements=c.computedMeasurements||new epiviz.measurements.MeasurementSet;this._changed=!1;this._contentChanged=new epiviz.events.Event};
epiviz.workspaces.Workspace.DEFAULT_WORKSPACE_NAME=epiviz.Config.DEFAULT_WORKSPACE_NAME;epiviz.workspaces.Workspace.prototype.id=function(){return this._id};epiviz.workspaces.Workspace.prototype.name=function(){return this._name};epiviz.workspaces.Workspace.prototype.range=function(){return this._range};epiviz.workspaces.Workspace.prototype.charts=function(){var a={},b;for(b in this._chartsOrder)if(this._chartsOrder.hasOwnProperty(b)){a[b]=[];for(var c=0;c<this._chartsOrder[b].length;++c)a[b].push(this._chartsById[this._chartsOrder[b][c]])}return a};
epiviz.workspaces.Workspace.prototype.chartsOrder=function(){return this._chartsOrder};epiviz.workspaces.Workspace.prototype.computedMeasurements=function(){return this._computedMeasurements};epiviz.workspaces.Workspace.prototype.chartAdded=function(a,b,c,d){this._chartsById[a]={id:a,type:b,properties:c.copy()};this._chartsOrder=d;this._setChanged()};epiviz.workspaces.Workspace.prototype.chartRemoved=function(a,b){this._chartsById[a]&&(delete this._chartsById[a],this._chartsOrder=b,this._setChanged())};
epiviz.workspaces.Workspace.prototype.chartSizeChanged=function(a,b,c){!this._chartsById[a]||this._chartsById[a].properties.width==b&&this._chartsById[a].properties.height==c||(this._chartsById[a].properties.width=b,this._chartsById[a].properties.height=c,this._setChanged())};epiviz.workspaces.Workspace.prototype.chartMarginsChanged=function(a,b){this._chartsById[a].properties.margins.equals(b)||(this._chartsById[a].properties.margins=b?b.copy():b,this._setChanged())};
epiviz.workspaces.Workspace.prototype.chartColorsChanged=function(a,b){this._chartsById[a].properties.colors.equals(b)||(this._chartsById[a].properties.colors=b,this._setChanged())};epiviz.workspaces.Workspace.prototype.chartMethodsModified=function(a,b){epiviz.utils.mapEquals(this._chartsById[a].properties.modifiedMethods,b)||(this._chartsById[a].properties.modifiedMethods=epiviz.utils.mapCombine(b,this._chartsById[a].properties.modifiedMethods),this._setChanged())};
epiviz.workspaces.Workspace.prototype.chartMethodsReset=function(a){this._chartsById[a].properties.modifiedMethods&&0!=Object.keys(this._chartsById[a].properties.modifiedMethods).length&&(this._chartsById[a].properties.modifiedMethods={},this._setChanged())};epiviz.workspaces.Workspace.prototype.chartMarkersModified=function(a,b){epiviz.utils.arraysEqual(this._chartsById[a].properties.chartMarkers,b)||(this._chartsById[a].properties.chartMarkers=b.filter(function(a){return null!=a}),this._setChanged())};
epiviz.workspaces.Workspace.prototype.chartCustomSettingsChanged=function(a,b){epiviz.utils.mapEquals(this._chartsById[a].properties.customSettingsValues,b)||(this._chartsById[a].properties.customSettingsValues=b?epiviz.utils.mapCopy(b):b,this._setChanged())};epiviz.workspaces.Workspace.prototype.locationChanged=function(a){this._range.equals(a)||(this._range=a,this._setChanged())};
epiviz.workspaces.Workspace.prototype.computedMeasurementsAdded=function(a){var b=this._computedMeasurements.size();this._computedMeasurements.addAll(a);b!=this._computedMeasurements.size()&&this._setChanged()};epiviz.workspaces.Workspace.prototype.computedMeasurementsRemoved=function(a){var b=this._computedMeasurements.size();this._computedMeasurements.removeAll(a);b!=this._computedMeasurements.size()&&this._setChanged()};
epiviz.workspaces.Workspace.prototype.chartsOrderChanged=function(a){this._chartsOrder=a;this._setChanged()};epiviz.workspaces.Workspace.prototype.changed=function(){return this._changed};epiviz.workspaces.Workspace.prototype.resetChanged=function(){this._changed=!1};epiviz.workspaces.Workspace.prototype._setChanged=function(){this._changed=!0;this._contentChanged.notify(this)};
epiviz.workspaces.Workspace.prototype.copy=function(a,b){var c=this.charts();return new epiviz.workspaces.Workspace(b||null,a,{range:this._range,computedMeasurements:new epiviz.measurements.MeasurementSet(this._computedMeasurements),charts:c})};
epiviz.workspaces.Workspace.prototype.raw=function(a){var b=new epiviz.measurements.MeasurementHashtable,c={};this._computedMeasurements.foreach(function(a){var c;a.componentMeasurements().foreach(function(a){var c=b.get(a);null===c&&(c=b.size(),b.put(a,c))});var d=a.formula().referredMeasurements,e;for(e in d)d.hasOwnProperty(e)&&(c=b.get(d[e]),null===c&&(c=b.size(),b.put(d[e],c)));c=b.get(a);null===c&&(c=b.size(),b.put(a,c))});for(var d in this._chartsOrder)if(this._chartsOrder.hasOwnProperty(d)){c[d]=
[];for(var e=0;e<this._chartsOrder[d].length;++e){var f=this._chartsById[this._chartsOrder[d][e]],g=f.properties,h=[];(function(a){g.visConfigSelection.measurements.foreach(function(c){var d=b.get(c);null===d&&(d=b.size(),b.put(c,d));a.push(d)})})(h);c[d].push({id:f.id,type:f.type.typeName(),properties:{width:g.width,height:g.height,margins:g.margins.raw(),visConfigSelection:{measurements:h,datasource:g.visConfigSelection.datasource,datasourceGroup:g.visConfigSelection.datasourceGroup,dataprovider:g.visConfigSelection.dataprovider,
annotation:g.visConfigSelection.annotation,defaultChartType:g.visConfigSelection.defaultChartType,minSelectedMeasurements:g.visConfigSelection.minSelectedMeasurements,customData:g.visConfigSelection.customData},colors:g.colors.raw(a),modifiedMethods:epiviz.utils.mapCopy(g.modifiedMethods),customSettings:g.customSettingsValues||null,chartMarkers:g.chartMarkers.map(function(a){return a.raw()})}})}}var m=Array(b.size());b.foreach(function(a,c){m[c]=a.raw(b)});return{id:this._id,name:this._name,content:{range:this._range.raw(),
measurements:m,charts:c}}};
epiviz.workspaces.Workspace.fromRawObject=function(a,b,c){var d,e=Array(a.content.measurements.length),f=new epiviz.measurements.MeasurementSet;for(d=0;d<a.content.measurements.length;++d)a.content.measurements[d].formula||(e[d]=epiviz.measurements.Measurement.fromRawObject(a.content.measurements[d]));for(d=0;d<a.content.measurements.length;++d)a.content.measurements[d].formula&&(e[d]=epiviz.measurements.Measurement.fromRawObject(a.content.measurements[d],e),f.add(e[d]));var g={},h;for(h in a.content.charts)if(a.content.charts.hasOwnProperty(h))for(g[h]=
[],d=0;d<a.content.charts[h].length;++d){var m=a.content.charts[h][d],l,p=m.properties.visConfigSelection,n=p?p.measurements:m.properties.measurements;if(n){l=new epiviz.measurements.MeasurementSet;for(var t=0;t<n.length;++t)l.add(e[n[t]])}p=p?new epiviz.ui.controls.VisConfigSelection(l,p.datasource,p.datasourceGroup,p.dataprovider,p.annotation,p.defaultChartType,p.minSelectedMeasurements,p.customData):new epiviz.ui.controls.VisConfigSelection(l);(n=b.get(m.type))&&g[h].push({id:m.id,type:n,properties:new epiviz.ui.charts.VisualizationProperties(m.properties.width,
m.properties.height,epiviz.ui.charts.Margins.fromRawObject(m.properties.margins),p,epiviz.ui.charts.ColorPalette.fromRawObject(m.properties.colors,c),m.properties.modifiedMethods,m.properties.customSettings,n.customSettingsDefs(),m.properties.chartMarkers?m.properties.chartMarkers.map(function(a){return epiviz.ui.charts.markers.VisualizationMarker.fromRawObject(a)}):[])})}return new epiviz.workspaces.Workspace(a.id,a.name,{range:epiviz.datatypes.GenomicRange.fromRawObject(a.content.range),computedMeasurements:f,
charts:g})};epiviz.workspaces.Workspace.prototype.onContentChanged=function(){return this._contentChanged};epiviz.ui.controls.Dialog=function(a,b){this._container=$("#dialogs");this._title=a;this._id=epiviz.ui.controls.Dialog.generateId();this._handlers=b;this._container.append(sprintf('<div id="%s" title="%s" style="display: none;"></div>',this._id,this._title));this._dialog=null};epiviz.ui.controls.Dialog._nextIdIndex=0;epiviz.ui.controls.Dialog.generateId=function(){return sprintf("dialog-%s",epiviz.utils.generatePseudoGUID(5))};epiviz.ui.controls.Dialog.prototype.show=function(){};epiviz.ui.controls.MessageDialog=function(a,b,c,d){epiviz.ui.controls.Dialog.call(this,a,b);this._message=c;this._icon=d||epiviz.ui.controls.MessageDialog.Icon.INFO};epiviz.ui.controls.MessageDialog.prototype=epiviz.utils.mapCopy(epiviz.ui.controls.Dialog.prototype);epiviz.ui.controls.MessageDialog.constructor=epiviz.ui.controls.MessageDialog;epiviz.ui.controls.MessageDialog.Icon={INFO:"info",ERROR:"error",QUESTION:"question"};
epiviz.ui.controls.MessageDialog.prototype.show=function(){epiviz.ui.controls.Dialog.prototype.show.call(this);var a=epiviz.ui.controls.MessageDialog.Icon;if(!this._dialog){var b=this;this._dialog=$("#"+this._id);this._dialog.css("display","inline");this._dialog.append(sprintf('<div class="ui-state-%s ui-corner-all" style="margin: 5px; padding: 5px; height: auto;"><div class="ui-icon ui-icon-%s" style="float: left; margin-right: 5px;"></div><div class="dialog-text">%s</div></div>',this._icon==a.ERROR?
"error":"highlight",this._icon==a.ERROR?"alert":"info",this._message));var c={},d;for(d in this._handlers)this._handlers.hasOwnProperty(d)&&function(a){c[a]=function(){b._handlers[a]();$(this).dialog("close")}}(d);this._dialog.dialog({autoOpen:!1,resizable:!1,buttons:c,modal:!0});this._dialog.dialog({close:function(a,c){$(this).remove();b._dialog=null}})}this._dialog.dialog("open");this._dialog.dialog("option","position","center")};epiviz.EpiViz=function(a,b,c,d,e,f,g,h,m,l,p){this._config=a;this._locationManager=b;this._measurementsManager=c;this._controlManager=d;this._dataManager=e;this._chartFactory=f;this._chartManager=g;this._workspaceManager=h;this._userManager=m;this._webArgsManager=l;this._cookieManager=p;this._registerRequestSeqInfos();this._registerRequestMeasurements();this._registerUiAddChart();this._registerUiSaveWorkspace();this._registerUiDeleteActiveWorkspace();this._registerUiRevertActiveWorkspace();this._registerUiLoginLinkClicked();
this._registerUiSearchWorkspaces();this._registerUiActiveWorkspaceChanged();this._registerUiSearch();this._registerChartRequestHierarchy();this._registerChartPropagateHierarchySelection();this._registerChartPropogateIcicleLocationChange();this._registerChartPropagateNavigationChanges();this._registerUiSettingsChanged();this._registerDataAddMeasurements();this._registerDataLoadMeasurements();this._registerDataRemoveMeasurements();this._registerDataAddChart();this._registerDataRemoveChart();this._registerDataAddSeqInfos();
this._registerDataRemoveSeqNames();this._registerDataNavigate();this._registerDataRedraw();this._registerDataGetCurrentLocation();this._registerPrintWorkspace();this._registerLoadWorkspace();this._registerDataSetChartSettings();this._registerDataGetChartSettings();this._registerDataGetAvailableCharts();this._registerDataUiStatus();this._registerRequestWorkspaces();this._registerWorkspacesLoaded();this._registerActiveWorkspaceChanged();this._registerActiveWorkspaceContentChanged();this._registerLocationChanged()};
epiviz.EpiViz.VERSION="5";epiviz.EpiViz.prototype.start=function(){this._cookieManager.initialize();this._controlManager.initialize();this._workspaceManager.initialize();this._measurementsManager.initialize();this._locationManager.initialize()};epiviz.EpiViz.prototype.config=function(){return this._config};
epiviz.EpiViz.prototype._addChart=function(a,b,c,d){c=this._chartManager.addChart(a,b,c,d);var e=this;if("epiviz.plugins.charts.CustomScatterPlot"==a.typeName()){var f=null;a={};a[c]=b.measurements;this._dataManager.getPCA(f,a,function(a,b){e._chartManager.updateCharts(f,b,[a])})}else"epiviz.plugins.charts.DiversityScatterPlot"==a.typeName()?(f=null,a={},a[c]=b.measurements,this._dataManager.getDiversity(f,a,function(a,b){e._chartManager.updateCharts(f,b,[a])})):a.chartDisplayType()==epiviz.ui.charts.VisualizationType.DisplayType.DATA_STRUCTURE?
(a={},a[c]=b,f=this._workspaceManager.activeWorkspace().range(),this._dataManager.getHierarchy(a,function(a,b){e._chartManager.updateCharts(f,b,[a])})):(f=this._workspaceManager.activeWorkspace().range(),this._chartManager.dataWaitStart(c),a={},a[c]=b.measurements,this._dataManager.getData(f,a,function(a,b){e._chartManager.updateCharts(f,b,[a])}));return c};
epiviz.EpiViz.prototype._registerRequestSeqInfos=function(){var a=this;this._locationManager.onRequestSeqInfos().addListener(new epiviz.events.EventListener(function(){a._dataManager.getSeqInfos(function(b){a._locationManager.updateSeqInfos(b)},function(a,c,d){console.log("error")})}))};
epiviz.EpiViz.prototype._registerRequestMeasurements=function(){var a=this;this._measurementsManager.onRequestMeasurements().addListener(new epiviz.events.EventListener(function(){a._dataManager.getMeasurements(function(b){a._measurementsManager.addMeasurements(b)},function(a,c,d){console.log("error")})}))};
epiviz.EpiViz.prototype._registerRequestWorkspaces=function(){var a=this;this._workspaceManager.onRequestWorkspaces().addListener(new epiviz.events.EventListener(function(b){var c=a._cookieManager.getWorkspace(a._chartFactory,a._config);a._dataManager.getWorkspaces(function(d){for(var e=[],f=null,g=null,h=0;h<d.length;++h){var m=epiviz.workspaces.Workspace.fromRawObject(d[h],a._chartFactory,a._config);null===m.id()?f=m:(m.id()==b.activeWorkspaceId&&(c&&c.id()==b.activeWorkspaceId&&(g=m,m=c),f=m),
e.push(m))}a._workspaceManager.updateWorkspaces(e,f,b.activeWorkspaceId,g);c||a._workspaceManager.activeWorkspace().resetChanged()},"",b.activeWorkspaceId)}))};epiviz.EpiViz.prototype._registerUiAddChart=function(){var a=this;this._controlManager.onAddChart().addListener(new epiviz.events.EventListener(function(b){a._addChart(b.type,b.visConfigSelection)}))};
epiviz.EpiViz.prototype._registerUiSaveWorkspace=function(){var a=this;this._controlManager.onSaveWorkspace().addListener(new epiviz.events.EventListener(function(b){var c=a._workspaceManager.getByName(b.name),c=c?a._workspaceManager.activeWorkspace().copy(b.name,c.id()):a._workspaceManager.activeWorkspace().copy(b.name);a._dataManager.saveWorkspace(c,a._config,function(b){c=c.copy(c.name(),b);c.resetChanged();a._workspaceManager.updateWorkspace(c);a._workspaceManager.changeActiveWorkspace(b)})}))};
epiviz.EpiViz.prototype._registerUiDeleteActiveWorkspace=function(){var a=this;this._controlManager.onDeleteActiveWorkspace().addListener(new epiviz.events.EventListener(function(){a._dataManager.deleteWorkspace(a._workspaceManager.activeWorkspace());a._workspaceManager.deleteActiveWorkspace()}))};epiviz.EpiViz.prototype._registerUiRevertActiveWorkspace=function(){var a=this;this._controlManager.onRevertActiveWorkspace().addListener(new epiviz.events.EventListener(function(){a._workspaceManager.revertActiveWorkspace()}))};
epiviz.EpiViz.prototype._registerUiLoginLinkClicked=function(){var a=this;this._controlManager.onLoginLinkClicked().addListener(new epiviz.events.EventListener(function(){a._userManager.toggleLogin()}))};epiviz.EpiViz.prototype._registerUiSearchWorkspaces=function(){var a=this;this._controlManager.onSearchWorkspaces().addListener(new epiviz.events.EventListener(function(b){a._dataManager.getWorkspaces(function(a){b.callback(a)},b.searchTerm,b.searchTerm)}))};
epiviz.EpiViz.prototype._registerUiActiveWorkspaceChanged=function(){var a=this;this._controlManager.onActiveWorkspaceChanged().addListener(new epiviz.events.EventListener(function(b){var c=function(){b.newValue.id&&!a._workspaceManager.get(b.newValue.id)?a._dataManager.getWorkspaces(function(c){for(var d=null,f=0;f<c.length;++f){var g=epiviz.workspaces.Workspace.fromRawObject(c[f],a._chartFactory,a._config);if(null===g.id()){d=g;break}}d&&a._workspaceManager.changeActiveWorkspace(b.newValue.id,d)},
b.newValue.name,b.newValue.id):a._workspaceManager.changeActiveWorkspace(b.newValue.id)};epiviz.workspaces.UserManager.USER_STATUS.loggedIn&&!a._workspaceManager.activeWorkspaceChanging()&&a._workspaceManager.activeWorkspace().changed()?(new epiviz.ui.controls.MessageDialog("Discard workspace changes",{Yes:function(){c()},No:function(){b.cancel()}},"There are unsaved changes in the current workspace. Do you wish to discard them?",epiviz.ui.controls.MessageDialog.Icon.QUESTION)).show():c()}))};
epiviz.EpiViz.prototype._registerUiSearch=function(){var a=this;this._controlManager.onSearch().addListener(new epiviz.events.EventListener(function(b){a._dataManager.search(function(a){b.callback(a)},b.searchTerm)}))};
epiviz.EpiViz.prototype._registerChartRequestHierarchy=function(){var a=this;this._chartManager.onChartRequestHierarchy().addListener(new epiviz.events.EventListener(function(b){var c={};c[b.id]=b.args;a._dataManager.getHierarchy(c,function(b,c){a._chartManager.updateCharts(void 0,c,[b])})}))};
epiviz.EpiViz.prototype._registerChartPropagateHierarchySelection=function(){var a=this;this._chartManager.onChartPropagateHierarchyChanges().addListener(new epiviz.events.EventListener(function(b){var c={};c[b.id]=b.args;a._dataManager.propagateHierarchyChanges(c,function(b,c){a._chartManager.updateCharts(void 0,c,[b])})}))};epiviz.EpiViz.prototype._registerChartPropagateNavigationChanges=function(){var a=this;this._chartManager.onChartPropagateNavigationChanges().addListener(new epiviz.events.EventListener(function(b){a._locationManager.changeCurrentLocation(b.range)}))};
epiviz.EpiViz.prototype._registerUiSettingsChanged=function(){var a=this;this._workspaceManager.onUiChartSettingsChanged().addListener(new epiviz.events.EventListener(function(b){a._dataManager.updateChartSettings(b)}))};
epiviz.EpiViz.prototype._registerDataAddMeasurements=function(){var a=this;this._dataManager.onRequestAddMeasurements().addListener(new epiviz.events.EventListener(function(b){try{a._measurementsManager.addMeasurements(b.measurements),b.result.success=!0}catch(c){b.result.success=!1,b.result.errorMessage=c.toString()}}))};
epiviz.EpiViz.prototype._registerDataRemoveMeasurements=function(){var a=this;this._dataManager.onRequestRemoveMeasurements().addListener(new epiviz.events.EventListener(function(b){try{a._measurementsManager.removeMeasurements(b.measurements),b.result.success=!0}catch(c){b.result.success=!1,b.result.errorMessage=c.toString()}}))};
epiviz.EpiViz.prototype._registerDataLoadMeasurements=function(){var a=this;this._dataManager.onRequestLoadMeasurements().addListener(new epiviz.events.EventListener(function(b){try{var c=a._measurementsManager.getRemoteMeasurements();b.result.success=!0;b.result.measurements=c}catch(d){b.result.success=!1,b.result.errorMessage=d.toString()}}))};
epiviz.EpiViz.prototype._registerDataAddChart=function(){var a=this;this._dataManager.onRequestAddChart().addListener(new epiviz.events.EventListener(function(b){try{var c=a._chartFactory.get(b.type),d=a._addChart(c,b.visConfigSelection);b.result.success=!0;b.result.value={id:d}}catch(e){b.result.success=!1,b.result.errorMessage=e.toString()}}))};
epiviz.EpiViz.prototype._registerDataRemoveChart=function(){var a=this;this._dataManager.onRequestRemoveChart().addListener(new epiviz.events.EventListener(function(b){try{a._chartManager.removeChart(b.id),b.result.success=!0}catch(c){b.result.success=!1,b.errorMessage=c.toString()}}))};
epiviz.EpiViz.prototype._registerPrintWorkspace=function(){this._dataManager.onRequestPrintWorkspace().addListener(new epiviz.events.EventListener(function(a){try{(new epiviz.ui.PrintManager(a.chartId,a.fileName,a.fileType)).print(),a.result.success=!0}catch(b){a.result.success=!1,a.errorMessage=b.toString()}}))};
epiviz.EpiViz.prototype._registerLoadWorkspace=function(){var a=this;this._dataManager.onRequestLoadWorkspace().addListener(new epiviz.events.EventListener(function(b){try{a._workspaceManager._requestWorkspaces.notify({activeWorkspaceId:b.workspace})}catch(c){b.result.success=!1,b.errorMessage=c.toString()}}))};
epiviz.EpiViz.prototype._registerDataSetChartSettings=function(){var a=this;this._dataManager.onRequestSetChartSettings().addListener(new epiviz.events.EventListener(function(b){try{a._chartManager.setChartSettings(b.chartId,b.settings,b.colorMap),b.result.success=!0}catch(c){b.result.success=!1,b.result.errorMessage=c.toString()}}))};
epiviz.EpiViz.prototype._registerDataGetChartSettings=function(){var a=this;this._dataManager.onRequestGetChartSettings().addListener(new epiviz.events.EventListener(function(b){try{a._chartManager.getChartSettings(b.chartId),b.result.success=!0}catch(c){b.result.success=!1,b.result.errorMessage=c.toString()}}))};
epiviz.EpiViz.prototype._registerDataGetAvailableCharts=function(){var a=this;this._dataManager.onRequestGetChartSettings().addListener(new epiviz.events.EventListener(function(b){try{b.result.value=[],a._chartFactory.foreach(function(a,d){b.result.value.push({chartName:a,customSettings:d.customSettingsDefs(),colorMap:d.defaultColors()._colors})}),b.result.success=!0}catch(c){b.result.success=!1,b.result.errorMessage=c.toString()}}))};
epiviz.EpiViz.prototype._registerDataAddSeqInfos=function(){var a=this;this._dataManager.onRequestAddSeqInfos().addListener(new epiviz.events.EventListener(function(b){try{a._locationManager.addSeqInfos(b.seqInfos),b.result.success=!0}catch(c){b.result.success=!1,b.errorMessage=c.toString()}}))};
epiviz.EpiViz.prototype._registerDataRemoveSeqNames=function(){var a=this;this._dataManager.onRequestRemoveSeqNames().addListener(new epiviz.events.EventListener(function(b){try{a._locationManager.removeSeqNames(b.seqNames),b.result.success=!0}catch(c){b.result.success=!1,b.errorMessage=c.toString()}}))};
epiviz.EpiViz.prototype._registerDataNavigate=function(){var a=this;this._dataManager.onRequestNavigate().addListener(new epiviz.events.EventListener(function(b){try{a._locationManager.changeCurrentLocation(b.range),b.result.success=!0}catch(c){b.result.success=!1,b.errorMessage=c.toString()}}))};
epiviz.EpiViz.prototype._registerDataRedraw=function(){var a=this;this._dataManager.onRequestRedraw().addListener(new epiviz.events.EventListener(function(b){try{var c=a._locationManager.currentLocation();a._locationManager.changeCurrentLocation(c);b.result.success=!0;a._chartManager.updateDataStructureCharts()}catch(d){b.result.success=!1,b.errorMessage=d.toString()}}))};
epiviz.EpiViz.prototype._registerDataGetCurrentLocation=function(){var a=this;this._dataManager.onRequestCurrentLocation().addListener(new epiviz.events.EventListener(function(b){try{var c=a._locationManager.currentLocation();b.result.value={seqName:c.seqName(),start:c.start(),end:c.end()};b.result.success=!0}catch(d){b.result.success=!1,b.errorMessage=d.toString()}}))};epiviz.EpiViz.prototype._registerWorkspacesLoaded=function(){this._workspaceManager.onWorkspacesLoaded().addListener(new epiviz.events.EventListener(function(a){}))};
epiviz.EpiViz.prototype._registerActiveWorkspaceChanged=function(){var a=this;this._workspaceManager.onActiveWorkspaceChanged().addListener(new epiviz.events.EventListener(function(b){a._workspaceManager.startChangingActiveWorkspace();a._controlManager.updateSelectedWorkspace({id:b.newValue.id(),name:b.newValue.name()});a._locationManager.changeCurrentLocation(b.newValue.range());a._measurementsManager.removeMeasurements(a._measurementsManager.computedMeasurements());a._measurementsManager.addMeasurements(b.newValue.computedMeasurements());
a._chartManager.clear();b=b.newValue.charts();for(var c in b)if(b.hasOwnProperty(c))for(var d=0;d<b[c].length;++d)a._addChart(b[c][d].type,b[c][d].properties.visConfigSelection,b[c][d].id,b[c][d].properties.copy());a._workspaceManager.endChangingActiveWorkspace()}))};epiviz.EpiViz.prototype._registerActiveWorkspaceContentChanged=function(){var a=this;this._workspaceManager.onActiveWorkspaceContentChanged().addListener(new epiviz.events.EventListener(function(b){a._cookieManager.saveWorkspace(b,a._config)}))};
epiviz.EpiViz.prototype._registerLocationChanged=function(){var a=this;this._locationManager.onCurrentLocationChanged().addListener(new epiviz.events.EventListener(function(b){a._chartManager.dataWaitStart(void 0,function(a){return a.displayType()!=epiviz.ui.charts.VisualizationType.DisplayType.DATA_STRUCTURE});var c=a._chartManager.chartsMeasurements(),d;for(d in c)-1==d.indexOf("pca_scatter")&&-1==d.indexOf("diversity_scatter")||delete c[d];a._dataManager.getData(b.newValue,c,function(c,d){a._chartManager.updateCharts(b.newValue,
d,[c])});a._chartManager.onChartPropogateIcicleLocationChanges().isFiring()||a._chartManager.onChartIcicleLocationChanges().notify()}))};epiviz.EpiViz.prototype._registerChartPropogateIcicleLocationChange=function(){var a=this;a._chartManager.onChartPropogateIcicleLocationChanges().addListener(new epiviz.events.EventListener(function(b){var c=a._locationManager.currentLocation();null!=c&&a._locationManager.changeCurrentLocation(new epiviz.datatypes.GenomicRange(c.seqName(),b.start,b.width))}))};
epiviz.EpiViz.prototype._registerDataUiStatus=function(){var a=this;this._dataManager.onRequestUiStatus().addListener(new epiviz.events.EventListener(function(b){null==a._workspaceManager._activeWorkspace?a._workspaceManager.onActiveWorkspaceChanged().addListener(new epiviz.events.EventListener(function(a){b.result.success=!0})):b.result.success=!0}))};epiviz.localstorage={};epiviz.localstorage.LocalStorageManager=function(a){this._workspace=a;this._availStorage="undefined"===typeof Storage||null==localStorage?!1:!0};epiviz.localstorage.LocalStorageManager.MODE={INCOGNITO_MODE:"incognito_workspace",COOKIE_MODE:"workspace"};epiviz.localstorage.LocalStorageManager.prototype.initialize=function(){};
epiviz.localstorage.LocalStorageManager.prototype.getWorkspace=function(a,b){if(this._availStorage){var c=localStorage.getItem(this._workspace);return c?epiviz.workspaces.Workspace.fromRawObject(JSON.parse(c),a,b):null}};epiviz.localstorage.LocalStorageManager.prototype.saveWorkspace=function(a,b){if(this._availStorage){var c=a.raw(b);localStorage.setItem(this._workspace,JSON.stringify(c))}};epiviz.localstorage.LocalStorageManager.prototype.clearWorkspace=function(){this._availStorage&&localStorage.removeItem(this._workspace)};
epiviz.localstorage.LocalStorageManager.prototype.clearAll=function(){this._availStorage&&localStorage.clear()};epiviz.ui.LocationManager=function(a){this._seqInfos={};this._timeout=this._lastUnfilledRequest=this._currentLocation=null;this._navigationDelay=a.navigationDelay;this._currentLocationChanged=new epiviz.events.Event;this._seqInfosUpdated=new epiviz.events.Event;this._requestSeqInfos=new epiviz.events.Event};epiviz.ui.LocationManager.prototype.initialize=function(){this._requestSeqInfos.notify()};
epiviz.ui.LocationManager.prototype.changeCurrentLocation=function(a){if(!this._currentLocationChanged.isFiring()){this._lastUnfilledRequest=a;null!==this._timeout&&(window.clearTimeout(this._timeout),this._timeout=null);var b=this;a=function(){var a=b._lastUnfilledRequest;null!==a&&b._doChangeCurrentLocation(a)};this._navigationDelay?this._timeout=window.setTimeout(a,this._navigationDelay):a()}};
epiviz.ui.LocationManager.prototype._doChangeCurrentLocation=function(a){var b=this._currentLocation,c=a.genome(),d=a.seqName();if(!(a.seqName()in this._seqInfos)){if(!b)return;d=b.seqName()}var e=void 0,f=void 0;this._seqInfos[d]&&void 0!=this._seqInfos[d].min&&void 0!=this._seqInfos[d].max&&(e=void 0!=a.start()&&a.start()>=this._seqInfos[d].min?a.start():this._seqInfos[d].min,f=void 0!=a.width()?e+a.width():e+9999);void 0!=e&&void 0!=f&&f>this._seqInfos[d].max&&(e=Math.max(this._seqInfos[d].min,
this._seqInfos[d].max-f+e),f=this._seqInfos[d].max);this._lastUnfilledRequest=null;this._currentLocation=epiviz.datatypes.GenomicRange.fromStartEnd(d,e,f,c);this._currentLocationChanged.notify({oldValue:b,newValue:this._currentLocation})};epiviz.ui.LocationManager.prototype.currentLocation=function(){return this._currentLocation};epiviz.ui.LocationManager.prototype.lastUnfilledLocationChangeRequest=function(){return this._lastUnfilledRequest};
epiviz.ui.LocationManager.prototype.updateSeqInfos=function(a){this._seqInfos={};for(var b=0;b<a.length;++b)this._seqInfos[a[b].seqName]=a[b];this._seqInfosUpdated.notify(a);null!==this._lastUnfilledRequest&&(this._lastUnfilledRequest.seqName()in this._seqInfos?this._doChangeCurrentLocation(this._lastUnfilledRequest):0<a.length&&(a=new epiviz.datatypes.GenomicRange(a[0].seqName,this._lastUnfilledRequest.start(),this._lastUnfilledRequest.width(),a[0].genome),this._doChangeCurrentLocation(a)))};
epiviz.ui.LocationManager.prototype.addSeqInfos=function(a){for(var b=0;b<a.length;++b)this._seqInfos[a[b].seqName]||(this._seqInfos[a[b].seqName]=a[b]);a=[];for(var c in this._seqInfos)this._seqInfos.hasOwnProperty(c)&&a.push(this._seqInfos[c]);this._seqInfosUpdated.notify(a)};
epiviz.ui.LocationManager.prototype.removeSeqNames=function(a){for(var b=0;b<a.length;++b)delete this._seqInfos[a[b]];a=[];for(var c in this._seqInfos)this._seqInfos.hasOwnProperty(c)&&a.push(this._seqInfos[c]);this._seqInfosUpdated.notify(a);this._currentLocation.seqName()in this._seqInfos||this.changeCurrentLocation(new epiviz.datatypes.GenomicRange(a[0].seqName,this._currentLocation.start(),this._currentLocation.width(),a[0].genome))};epiviz.ui.LocationManager.prototype.seqInfos=function(){return this._seqInfos};
epiviz.ui.LocationManager.prototype.onCurrentLocationChanged=function(){return this._currentLocationChanged};epiviz.ui.LocationManager.prototype.onSeqInfosUpdated=function(){return this._seqInfosUpdated};epiviz.ui.LocationManager.prototype.onRequestSeqInfos=function(){return this._requestSeqInfos};epiviz.measurements.MeasurementsManager=function(){this._requestMeasurements=new epiviz.events.Event;this._measurementsAdded=new epiviz.events.Event;this._measurementsRemoved=new epiviz.events.Event;this._computedMeasurementsAdded=new epiviz.events.Event;this._computedMeasurementsRemoved=new epiviz.events.Event;this._measurements=new epiviz.measurements.MeasurementSet;this._computedMeasurements=new epiviz.measurements.MeasurementSet};epiviz.measurements.MeasurementsManager.prototype.initialize=function(){this._requestMeasurements.notify()};
epiviz.measurements.MeasurementsManager.prototype.onRequestMeasurements=function(){return this._requestMeasurements};epiviz.measurements.MeasurementsManager.prototype.onMeasurementsAdded=function(){return this._measurementsAdded};epiviz.measurements.MeasurementsManager.prototype.onMeasurementsRemoved=function(){return this._measurementsRemoved};epiviz.measurements.MeasurementsManager.prototype.onComputedMeasurementsAdded=function(){return this._computedMeasurementsAdded};
epiviz.measurements.MeasurementsManager.prototype.onComputedMeasurementsRemoved=function(){return this._computedMeasurementsRemoved};epiviz.measurements.MeasurementsManager.prototype.measurements=function(){return this._measurements};epiviz.measurements.MeasurementsManager.prototype.computedMeasurements=function(){return this._computedMeasurements};
epiviz.measurements.MeasurementsManager.prototype.addMeasurements=function(a){if(a&&a.size()){var b=this;this._measurements.addAll(a);var c=new epiviz.measurements.MeasurementSet;a.foreach(function(a){a.isComputed()&&(c.add(a),b._computedMeasurements.add(a))});this._measurementsAdded.notify(a);0<c.size()&&this._computedMeasurementsAdded.notify(c)}};
epiviz.measurements.MeasurementsManager.prototype.removeMeasurements=function(a){var b=this;this._measurements.removeAll(a);var c=new epiviz.measurements.MeasurementSet;a.foreach(function(a){a.isComputed()&&(c.add(a),b._computedMeasurements.remove(a))});this._measurementsRemoved.notify(a);0<c.size()&&this._computedMeasurementsRemoved.notify(c)};epiviz.measurements.MeasurementsManager.prototype.addMeasurement=function(a){var b=new epiviz.measurements.MeasurementSet;b.add(a);this.addMeasurements(b)};
epiviz.measurements.MeasurementsManager.prototype.removeMeasurement=function(a){var b=new epiviz.measurements.MeasurementSet;b.add(a);this.removeMeasurements(b)};epiviz.measurements.MeasurementsManager.prototype.getRemoteMeasurements=function(){return this._measurements.remoteRaw()};epiviz.ui.charts.markers.MeasurementAggregator=function(a,b){this._id=a;this._aggregator=b};epiviz.ui.charts.markers.MeasurementAggregator.prototype.aggregate=function(a,b,c){return this._aggregator(a,b,c)};epiviz.ui.charts.markers.MeasurementAggregator.prototype.id=function(){return this._id};
epiviz.ui.charts.markers.MeasurementAggregators={"mean-stdev":new epiviz.ui.charts.markers.MeasurementAggregator("mean-stdev",function(a,b,c){if(!c||0==c.length)return null;var d=c.reduce(function(a,b){return a+b})/c.length;a=c.map(function(a){return(a-d)*(a-d)}).reduce(function(a,b){return a+b})/c.length;a=Math.sqrt(a);return{value:d,errMinus:d-a,errPlus:d+a}}),quartiles:new epiviz.ui.charts.markers.MeasurementAggregator("quartiles",function(a,b,c){if(!c||0==c.length)return null;c=c.slice(0).sort(function(a,
b){return a-b});a=c.length;b=Math.floor(.5*a);var d=Math.ceil(.5*a);return{value:.5*(c[Math.floor(.5*(a-1))]+c[b]),errMinus:.5*(c[Math.floor(.5*(b-1))]+c[Math.floor(.5*b)]),errPlus:.5*(c[d+Math.floor(.5*(a-d-1))]+c[d+Math.floor(.5*(a-d))])}}),count:new epiviz.ui.charts.markers.MeasurementAggregator("count",function(a,b,c){return c&&0!=c.length?{value:c.length}:0}),min:new epiviz.ui.charts.markers.MeasurementAggregator("min",function(a,b,c){return c&&0!=c.length?{value:Math.min.apply(void 0,c)}:null}),
max:new epiviz.ui.charts.markers.MeasurementAggregator("max",function(a,b,c){return c&&0!=c.length?{value:Math.max.apply(void 0,c)}:null}),sum:new epiviz.ui.charts.markers.MeasurementAggregator("sum",function(a,b,c){return c&&0!=c.length?{value:c.reduce(function(a,b){return a+b})}:null}),log:new epiviz.ui.charts.markers.MeasurementAggregator("log",function(a,b,c){return c&&0!=c.length?{value:c.reduce(function(a,b){return Math.log2((a+1)/(b+1))})}:null}),negate:new epiviz.ui.charts.markers.MeasurementAggregator("negate",
function(a,b,c){return c&&0!=c.length?{value:c.reduce(function(a,b){return a+b})/c.length,errMinus:c.reduce(function(a,b){return a}),errPlus:c.reduce(function(a,b){return-1*b})}:null})};epiviz.ui.charts.ChartType=function(a){epiviz.ui.charts.VisualizationType.call(this,a)};epiviz.ui.charts.ChartType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.VisualizationType.prototype);epiviz.ui.charts.ChartType.constructor=epiviz.ui.charts.ChartType;epiviz.ui.charts.ChartType.prototype.createNew=function(a,b,c){throw Error("unimplemented abstract method");};
epiviz.ui.charts.ChartType.prototype.customSettingsDefs=function(){var a=epiviz.ui.charts.VisualizationType.prototype.customSettingsDefs.call(this);if(this.isRestrictedToRangeMeasurements())return a;var b=Object.keys(epiviz.ui.charts.markers.MeasurementAggregators);return a.concat([new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.ChartType.CustomSettings.MEASUREMENT_GROUPS_AGGREGATOR,epiviz.ui.charts.CustomSetting.Type.CATEGORICAL,b[0],"Aggregator for measurement groups",b)])};
epiviz.ui.charts.ChartType.CustomSettings={MEASUREMENT_GROUPS_AGGREGATOR:"measurementGroupsAggregator"};epiviz.ui.charts.Chart=function(a,b,c){epiviz.ui.charts.Visualization.call(this,a,b,c);this._nBins=100;this._globalIndexColorLabels=this._measurementColorLabels=this._binSize=null};epiviz.ui.charts.Chart.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.Visualization.prototype);epiviz.ui.charts.Chart.constructor=epiviz.ui.charts.Chart;epiviz.ui.charts.Chart.prototype._initialize=function(){epiviz.ui.charts.Visualization.prototype._initialize.call(this);this._svg.classed("base-chart",!0)};
epiviz.ui.charts.Chart.prototype._addFilters=function(){var a=this._svg.append("defs"),b=a.append("filter").attr("id",this.id()+"-glow");b.append("feGaussianBlur").attr("id","gaussianBlur").attr("stdDeviation","2").attr("result","blurResult");b.append("feComposite").attr("id","composite").attr("in","SourceGraphic").attr("in2","blurResult").attr("operator","over");b=a.append("filter").attr("id",this.id()+"-contour");b.append("feGaussianBlur").attr("in","SourceAlpha").attr("stdDeviation","1").attr("result",
"blur");b.append("feColorMatrix").attr("values","1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 10 -1 ").attr("result","colorMatrix");b.append("feFlood").attr("result","fillColor").attr("flood-color","#800000").attr("in","blur");b.append("feComposite").attr("result","composite").attr("in","fillColor").attr("in2","colorMatrix").attr("operator","atop");b.append("feComposite").attr("in","SourceGraphic").attr("in2","composite").attr("operator","atop");a=a.append("filter").attr("id",this.id()+"-dropshadow").attr("filterUnits",
"userSpaceOnUse").attr("color-interpolation-filters","sRGB");b=a.append("feComponentTransfer").attr("in","SourceAlpha");b.append("feFuncR").attr("type","discrete").attr("tableValues","1");b.append("feFuncG").attr("type","discrete").attr("tableValues",198/255);b.append("feFuncB").attr("type","discrete").attr("tableValues","0");a.append("feGaussianBlur").attr("stdDeviation","2");a.append("feOffset").attr("dx","0").attr("dy","0").attr("result","shadow");a.append("feComposite").attr("in","SourceGraphic").attr("in2",
"shadow").attr("operator","over")};epiviz.ui.charts.Chart.prototype.draw=function(a,b){epiviz.ui.charts.Visualization.prototype.draw.call(this,a,b);a&&(this._binSize=Math.ceil((a.end()-a.start())/this._nBins));return[]};
epiviz.ui.charts.Chart.prototype.transformData=function(a,b){var c=new epiviz.deferred.Deferred,d=this;epiviz.ui.charts.Visualization.prototype.transformData.call(this,a,b).done(function(){d._lastData?d._lastData.ready(function(){var a=!1;d._lastData.measurements().every(function(b){a=b.type()!==epiviz.measurements.Measurement.Type.RANGE;return!a});if(a){var b;d._markers.every(function(a){a&&a.type()==epiviz.ui.charts.markers.VisualizationMarker.Type.GROUP_BY_MEASUREMENTS&&(b=a);return!b});if(b){var g=
epiviz.ui.charts.markers.MeasurementAggregators[d.customSettingsValues()[epiviz.ui.charts.ChartType.CustomSettings.MEASUREMENT_GROUPS_AGGREGATOR]];d._lastData=new epiviz.datatypes.MeasurementAggregatedGenomicData(d._lastData,b,g)}}var h;d._markers.every(function(a){a&&a.type()==epiviz.ui.charts.markers.VisualizationMarker.Type.FILTER&&(h=a);return!h});h&&(d._lastData=new epiviz.datatypes.ItemFilteredGenomicData(d._lastData,h));var m;d._markers.every(function(a){a&&a.type()==epiviz.ui.charts.markers.VisualizationMarker.Type.ORDER_BY_MEASUREMENTS&&
(m=a);return!m});m&&(d._lastData=new epiviz.datatypes.MeasurementOrderedGenomicData(d._lastData,m));d._lastData.ready(function(){var a=d._lastData;if(a.isReady()){var b=new epiviz.deferred.Deferred,e;d._markers.every(function(a){a&&a.type()==epiviz.ui.charts.markers.VisualizationMarker.Type.COLOR_BY_MEASUREMENTS&&(e=a);return!e});d._measurementColorLabels=null;if(e){var f=new epiviz.measurements.MeasurementHashtable;e.preMark()(a).done(function(c){var g=a.measurements();epiviz.utils.deferredFor(g.length,
function(b){var d=new epiviz.deferred.Deferred;e.mark()(g[b],a,c).done(function(a){f.put(g[b],a);d.resolve()});return d}).done(function(){d._measurementColorLabels=f;b.resolve()})})}else b.resolve();var g=new epiviz.deferred.Deferred,h;d._markers.every(function(a){a&&a.type()==epiviz.ui.charts.markers.VisualizationMarker.Type.COLOR_BY_ROW&&(h=a);return!h});d._globalIndexColorLabels=null;if(h){var m={};h.preMark()(a).done(function(b){var c=a.firstSeries();epiviz.utils.deferredFor(c.size(),function(d){var e=
new epiviz.deferred.Deferred;h.mark()(c.getRow(d),a,b).done(function(a){m[d+c.globalStartIndex()]=a;e.resolve()});return e}).done(function(){d._globalIndexColorLabels=m;g.resolve()})})}else g.resolve();b.done(function(){g.state()==epiviz.deferred.Deferred.State.RESOLVED&&(d._dataWaitEnd.notify(new epiviz.ui.charts.VisEventArgs(d.id())),c.resolve())});g.done(function(){b.state()==epiviz.deferred.Deferred.State.RESOLVED&&(d._dataWaitEnd.notify(new epiviz.ui.charts.VisEventArgs(d.id())),c.resolve())})}})}):
c.resolve()});return c};epiviz.ui.charts.Chart.prototype.properties=function(){return epiviz.ui.charts.Visualization.prototype.properties.call(this)};epiviz.ui.charts.VisObject=function(){};epiviz.ui.charts.VisObject.prototype.regionStart=function(){return null};epiviz.ui.charts.VisObject.prototype.regionEnd=function(){return null};epiviz.ui.charts.VisObject.prototype.regionSeqName=function(){return null};epiviz.ui.charts.VisObject.prototype.getMetadata=function(a,b,c){return null};epiviz.ui.charts.VisObject.prototype.getSeqName=function(a,b){return null};epiviz.ui.charts.VisObject.prototype.getStart=function(a,b){return null};
epiviz.ui.charts.VisObject.prototype.getEnd=function(a,b){return null};epiviz.ui.charts.VisObject.prototype.metadataColumns=function(){return null};epiviz.ui.charts.VisObject.prototype.dimensions=function(){return[0,0]};epiviz.ui.charts.VisObject.prototype.metadataLooseCompare=function(){return!1};
epiviz.ui.charts.VisObject.prototype.overlapsWith=function(a){if(!a)return!1;if(this===a)return!0;var b,c,d;if(this.regionSeqName()!=a.regionSeqName())return!1;var e=this.dimensions(),f=a.dimensions();if(!(e[0]&&f[0]||void 0!=this.regionStart()&&void 0!=a.regionStart()&&void 0!=this.regionEnd()&&void 0!=a.regionEnd()))return!1;if(!e[0]){if(!f[0])return this.regionStart()<a.regionEnd()&&this.regionEnd()>a.regionStart();for(c=0;c<f[1];++c)if(b=a.getStart(0,c),d=a.getEnd(0,c),void 0!=b&&void 0!=d&&this.regionStart()<
d&&this.regionEnd()>b)return!0;return!1}var g=epiviz.utils.arrayIntersection(this.metadataColumns(),a.metadataColumns());if(g.length){for(b=0;b<e[1];++b)for(c=0;c<f[1];++c){var h=!0;for(d=0;d<g.length;++d){var m=this.metadataLooseCompare()||a.metadataLooseCompare(),l=this.getMetadata(0,b,g[d]),p=a.getMetadata(0,c,g[d]);if(l!=p){if(!m){h=!1;break}l.length<=p.length?(m=l,l=p):m=p;if(!(new RegExp("^(.+,)?"+m+"(,.+)?$")).test(l)){h=!1;break}}}if(h)return!0}return!1}for(b=0;b<e[1];++b)for(c=0;c<f[1];++c)if(this.getStart(0,
b)<a.getEnd(0,c)&&this.getEnd(0,b)>a.getStart(0,c))return!0;return!1};epiviz.ui.charts.ChartObject=function(a,b,c,d,e,f,g,h,m){epiviz.ui.charts.VisObject.call(this);this.id=a;this.seqName=m;this.start=b;this.end=c;this.values=d;this.seriesIndex=e;this.valueItems=f;this.measurements=g;this.cssClasses=h};epiviz.ui.charts.ChartObject.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.VisObject.prototype);epiviz.ui.charts.ChartObject.constructor=epiviz.ui.charts.ChartObject;epiviz.ui.charts.ChartObject.prototype.regionStart=function(){return this.start};
epiviz.ui.charts.ChartObject.prototype.regionEnd=function(){return this.end};epiviz.ui.charts.ChartObject.prototype.regionSeqName=function(){return this.seqName};epiviz.ui.charts.ChartObject.prototype.getMetadata=function(a,b,c){return this.valueItems?this.valueItems[a][b].rowItem.metadata(c):null};epiviz.ui.charts.ChartObject.prototype.getStart=function(a,b){return this.valueItems[a][b].rowItem.start()};epiviz.ui.charts.ChartObject.prototype.getSeqName=function(a,b){return this.seqName|this.valueItems[a][b].rowItem.seqName()};
epiviz.ui.charts.ChartObject.prototype.getEnd=function(a,b){return this.valueItems[a][b].rowItem.end()};epiviz.ui.charts.ChartObject.prototype.metadataColumns=function(){return this.valueItems&&this.valueItems[0]&&this.valueItems[0][0]?Object.keys(this.valueItems[0][0].rowItem.rowMetadata()):[]};epiviz.ui.charts.ChartObject.prototype.dimensions=function(){var a=[];return this.valueItems?(a.push(this.valueItems.length),this.valueItems.length?a.push(this.valueItems[0].length):a.push(0),a):[0,0]};epiviz.ui.charts.Track=function(a,b,c){epiviz.ui.charts.Chart.call(this,a,b,c);this._highlightGroup=this._background=null;this._propagateNavigationChanges=new epiviz.events.Event};epiviz.ui.charts.Track.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.Chart.prototype);epiviz.ui.charts.Track.constructor=epiviz.ui.charts.Track;
epiviz.ui.charts.Track.prototype._initialize=function(){this._properties.width="100%";epiviz.ui.charts.Chart.prototype._initialize.call(this);var a=this;this._highlightGroup=this._svg.append("g").attr("class","track-highlight");this._background=this._svg.append("rect").attr("class","chart-background").attr("x",0).attr("y",0).attr("width","100%").attr("height","100%").attr("fill","#ffffff").attr("fill-opacity","0");this._background.on("mouseover",function(){a._captureMouseHover()}).on("mousemove",
function(){a._captureMouseHover()}).on("mouseout",function(){a._unhover.notify(new epiviz.ui.charts.VisEventArgs(a.id()))})};epiviz.ui.charts.Track.prototype.draw=function(a,b,c,d){a=epiviz.ui.charts.Chart.prototype.draw.call(this,a,b);this._drawLegend();return a};epiviz.ui.charts.Track.prototype.displayType=function(){return epiviz.ui.charts.VisualizationType.DisplayType.TRACK};
epiviz.ui.charts.Track.prototype.doHover=function(a){epiviz.ui.charts.Chart.prototype.doHover.call(this,a);var b=this;if(void 0!=a.start&&void 0!=a.end&&this._lastRange){this._highlightGroup.selectAll("rect").remove();this._highlightGroup.attr("transform","translate("+this.margins().left()+", 0)");var c=epiviz.ui.charts.Axis,d=d3.scale.linear().domain([this._lastRange.start(),this._lastRange.end()]).range([0,this.width()-this.margins().sumAxis(c.X)]),c=[];if(a.measurements&&a.measurements.length)for(var e=
0;e<a.valueItems[0].length;++e){var f=a.valueItems[0][e].rowItem;c.push({start:f.start(),end:f.end()})}else c.push({start:a.start,end:a.end});if("canvas"==b.chartDrawType){var g=b.hoverCanvas.getContext("2d");g.clearRect(0,0,b.hoverCanvas.width,b.hoverCanvas.height);c.forEach(function(a){g.beginPath();g.fillStyle=b.colors().get(0);g.globalAlpha=.1;var c=d(a.end+1)-d(a.start),e=Math.max(5,c),c=d(a.start)+.5*c-.5*e;g.fillRect(c,0,Math.max(5,d(a.end+1)-d(a.start)),b.height())})}else this._highlightGroup.selectAll("rect").data(c,
function(a){return sprintf("%s-%s",a.start,a.end)}).enter().append("rect").style("fill",this.colors().get(0)).style("fill-opacity","0.1").attr("x",function(a){var b=d(a.end+1)-d(a.start),c=Math.max(5,b);return d(a.start)+.5*b-.5*c}).attr("width",function(a){return Math.max(5,d(a.end+1)-d(a.start))}).attr("y",0).attr("height",this.height())}};epiviz.ui.charts.Track.prototype.doUnhover=function(){epiviz.ui.charts.Chart.prototype.doUnhover.call(this);this._highlightGroup.selectAll("rect").remove()};
epiviz.ui.charts.Track.prototype._captureMouseHover=function(){if(this._lastRange){this._unhover.notify(new epiviz.ui.charts.VisEventArgs(this.id()));var a=d3.scale.linear().domain([0,this.width()]).range([this._lastRange.start(),this._lastRange.end()])(d3.mouse(this._background[0][0])[0])-this._binSize/2,b=a+this._binSize,c=this._lastRange.seqName(),a=new epiviz.ui.charts.ChartObject(sprintf("%s-highlight",this.id()),a,b,null,null,null,null,null,c);this._hover.notify(new epiviz.ui.charts.VisEventArgs(this.id(),
a))}};
epiviz.ui.charts.Track.prototype._drawLegend=function(){var a=this;this._svg.selectAll(".chart-title").remove();this._svg.selectAll(".chart-title-color ").remove();if(this._lastData&&this._lastData.isReady()){var b=this._lastData.measurements();if("canvas"==this.chartDrawType){var c=a.canvas.getContext("2d"),d=0;b.forEach(function(b,e){var f;f=a._measurementColorLabels?a.colors().getByKey(a._measurementColorLabels.get(b)):a.colors().get(e);c.strokeStyle=f;c.fillStyle=f;c.beginPath();c.arc(a.margins().left()+d-
2,-9,4,0,2*Math.PI);c.stroke();c.fill();c.font="9px";c.beginPath();c.textAlign="start";c.fillText(b.name(),a.margins().left()+d+8,-14);f=c.measureText(b.name()).width;d=d+8+f+10})}else if(-1!=a._id.indexOf("stacked"))var e=(a.height()-a.margins().sumAxis(epiviz.ui.charts.Axis.Y))/a.measurements().size(),f=this._svg.selectAll(".chart-title").data(b).enter().append("text").attr("class","chart-title").attr("font-weight","bold").attr("fill",function(b,c){return a._measurementColorLabels?a.colors().getByKey(a._measurementColorLabels.get(b)):
a.colors().get(c)}).attr("x",0).attr("y",function(a,b){return b*e+10}).text(function(a,b){return a.name()}).attr("transform","translate("+a.margins().left()+", "+a.margins().top()+")");else{var f=this._svg.selectAll(".chart-title").data(b).enter().append("text").attr("class","chart-title").attr("font-weight","bold").attr("fill",function(b,c){return a._measurementColorLabels?a.colors().getByKey(a._measurementColorLabels.get(b)):a.colors().get(c)}).attr("y",a.margins().top()-5).text(function(a,b){return a.name()}),
g=0,h=[];this._container.find(" .chart-title").each(function(a){h.push(g);g+=this.getBBox().width+15});f.attr("x",function(b,c){return a.margins().left()+10+h[c]});this._svg.selectAll(".chart-title-color").data(b).enter().append("circle").attr("class","chart-title-color").attr("cx",function(b,c){return a.margins().left()+4+h[c]}).attr("cy",a.margins().top()-9).attr("r",4).style("shape-rendering","auto").style("stroke-width","0").style("fill",function(b,c){return a._measurementColorLabels?a.colors().getByKey(a._measurementColorLabels.get(b)):
a.colors().get(c)})}}};
epiviz.ui.charts.Track.prototype.addCanvasEvents=function(a,b,c,d){var e=this,f=b.getContext("2d");e.hoverCanvasObjects=c;b.addEventListener("click",function(a){b.getBoundingClientRect();e.margins().left();e.margins().right()});b.addEventListener("mousemove",function(a){b.getBoundingClientRect();a=a.offsetX-e.margins().left()-e.margins().right();var f=d.invert(a),g=null;c?c.forEach(function(a){a.regionStart()<=f&&a.regionEnd()>=f&&(g=a)}):g=new epiviz.ui.charts.ChartObject(sprintf("%s-highlight",e.id()),
f,f+e._binSize);g||(g=new epiviz.ui.charts.ChartObject(sprintf("%s-highlight",e.id()),f,f+e._binSize));g&&e._hover.notify(new epiviz.ui.charts.VisEventArgs(e.id(),g))});b.addEventListener("mouseout",function(a){console.log("mouseout");e._canvasHoverObject=null;f.clearRect(0,0,b.width,b.height);e._unhover.notify(new epiviz.ui.charts.VisEventArgs(e.id()))})};epiviz.plugins={};epiviz.plugins.charts={};epiviz.plugins.charts.BlocksTrack=function(a,b,c){epiviz.ui.charts.Track.call(this,a,b,c);this._initialize()};epiviz.plugins.charts.BlocksTrack.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.Track.prototype);epiviz.plugins.charts.BlocksTrack.constructor=epiviz.plugins.charts.BlocksTrack;epiviz.plugins.charts.BlocksTrack.prototype._initialize=function(){epiviz.ui.charts.Track.prototype._initialize.call(this);this._svg.classed("blocks-track",!0)};
epiviz.plugins.charts.BlocksTrack.prototype.draw=function(a,b,c,d){epiviz.ui.charts.Track.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;return b&&a&&b.isReady()?this._drawBlocks(a,b,c||0,d||1):[]};epiviz.plugins.charts.BlocksTrack.prototype.drawCanvas=function(a,b,c,d){epiviz.ui.charts.Track.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;return b&&a&&b.isReady()?this._drawBlocksCanvas(a,b,c||0,d||1):[]};
epiviz.plugins.charts.BlocksTrack.prototype._drawBlocks=function(a,b,c,d){var e=epiviz.ui.charts.Axis,f=a.start(),g=a.end(),h=this.width(),m=this.height(),l=this.margins();this.measurements();var p=this.colors(),n=this.customSettingsValues()[epiviz.plugins.charts.BlocksTrackType.CustomSettings.MIN_BLOCK_DISTANCE],t=this.customSettingsValues()[epiviz.plugins.charts.BlocksTrackType.CustomSettings.BLOCK_COLOR_BY],v=this.customSettingsValues()[epiviz.plugins.charts.BlocksTrackType.CustomSettings.USE_COLOR_BY],
q=this.customSettingsValues()[epiviz.plugins.charts.BlocksTrackType.CustomSettings.BLOCK_SCALE_BY],u=this.customSettingsValues()[epiviz.plugins.charts.BlocksTrackType.CustomSettings.USE_SCALE_BY],w=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN],r=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX],A=d3.scale.linear().domain([f,g]).range([0,h-l.sumAxis(e.X)]),B=c*(h-l.sumAxis(e.X))/(g-f);this._clearAxes();this._drawAxes(A,null,10,5);var z=
this,x=[],E=0;b.foreach(function(b,c,d){d=[];for(var e=0;e<c.size();++e){var f=c.get(e);if(!(f.rowItem.start()>a.end()||f.rowItem.end()<a.start())){var g=sprintf("item data-series-%s",E);if(null!==n&&0<d.length){var h=d[d.length-1],m=A(f.rowItem.start()),l=A(h.end);if(m-l<n){v?h.values==f.rowItem.metadata(t)&&(h.end=Math.max(h.end,f.rowItem.end())):h.end=Math.max(h.end,f.rowItem.end());h.valueItems[0].push(f);h.id=sprintf("b-%s-%s-%s",E,h.start,h.end);continue}}d.push(new epiviz.ui.charts.ChartObject(sprintf("b-%s-%s-%s",
E,f.rowItem.start(),f.rowItem.end()),f.rowItem.start(),f.rowItem.end(),f.rowItem.metadata(t),E,[[f]],[b],g,f.rowItem.seqName()))}}x=x.concat(d);++E});b=this._svg.select(".items");c=b.select(".selected");c=this._svg.select("#clip-"+this.id());b.empty()&&(c.empty()&&this._svg.select("defs").append("clipPath").attr("id","clip-"+this.id()).append("rect").attr("class","clip-path-rect"),b=this._svg.append("g").attr("class","items").attr("id",this.id()+"-gene-content").attr("clip-path","url(#clip-"+this.id()+
")"),c=b.append("g").attr("class","selected"),b.append("g").attr("class","hovered"),c.append("g").attr("class","hovered"));b.attr("transform","translate("+l.left()+", "+l.top()+")");this._svg.select(".clip-path-rect").attr("x",0).attr("y",0).attr("width",h-l.sumAxis(e.X)).attr("height",m-l.sumAxis(e.Y));b.selectAll(".item").remove();h=b.selectAll(".item").data(x,function(a){return a.id});h.enter().insert("rect",":first-child").attr("class",function(a){return a.cssClasses}).style("fill",function(a){return v?
p.getByKey(a.values):p.get(a.seriesIndex)}).attr("x",function(a){return A(a.start)/d+B}).attr("width",function(a){return d*(A(a.end+1)-A(a.start))}).on("mouseout",function(){z._unhover.notify(new epiviz.ui.charts.VisEventArgs(z.id()))}).on("mouseover",function(a){z._hover.notify(new epiviz.ui.charts.VisEventArgs(z.id(),a))}).on("click",function(a){z._deselect.notify(new epiviz.ui.charts.VisEventArgs(z.id()));z._select.notify(new epiviz.ui.charts.VisEventArgs(z.id(),a));d3.event.stopPropagation()});
h.attr("class",function(a){return a.cssClasses}).attr("height",function(a){return u?(a.valueItems[0][0].rowItem.metadata(q)-w)/(r-w)*(m-l.sumAxis(e.Y)):m-l.sumAxis(e.Y)}).attr("y",function(a){return u?(1-(a.valueItems[0][0].rowItem.metadata(q)-w)/(r-w))*(m-l.sumAxis(e.Y)):0}).transition().duration(500).attr("x",function(a){return A(a.start)}).attr("width",function(a){return A(a.end+1)-A(a.start)});h.exit().transition().duration(500).attr("x",function(a){return A(a.start)}).remove();z._drawLegend();
return x};
epiviz.plugins.charts.BlocksTrack.prototype._drawBlocksCanvas=function(a,b,c,d){var e=epiviz.ui.charts.Axis,f=a.start(),g=a.end(),h=this.width(),m=this.height(),l=this.margins();this.measurements();var p=this.colors(),n=this.customSettingsValues()[epiviz.plugins.charts.BlocksTrackType.CustomSettings.MIN_BLOCK_DISTANCE],t=this.customSettingsValues()[epiviz.plugins.charts.BlocksTrackType.CustomSettings.BLOCK_COLOR_BY],v=this.customSettingsValues()[epiviz.plugins.charts.BlocksTrackType.CustomSettings.USE_COLOR_BY],q=
this.customSettingsValues()[epiviz.plugins.charts.BlocksTrackType.CustomSettings.BLOCK_SCALE_BY],u=this.customSettingsValues()[epiviz.plugins.charts.BlocksTrackType.CustomSettings.USE_SCALE_BY],w=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN],r=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX],A=d3.scale.linear().domain([f,g]).range([0,h-l.sumAxis(e.X)]),B=c*(h-l.sumAxis(e.X))/(g-f);this._container.find("svg").remove();this._container.find("#"+
this.id()+"-canvas").remove();c=document.createElement("canvas");this.chartDrawType="canvas";this.canvas=c;c.id=this.id()+"-canvas";this._container.append(c);c.width=this.width();c.height=this.height();c.style="position:absolute;top:0;left:0;width:100%;height:100%";this._container.find("#"+this.id()+"-hoverCanvas").remove();this.hoverCanvas=f=document.createElement("canvas");f.id=this.id()+"-hoverCanvas";this._container.append(f);f.width=this.width();f.height=this.height();f.style="position:absolute;top:0;left:0;width:100%;height:100%;z-index:1";
this._drawAxesCanvas(A,null,10,5,c);var z=[],x=0;b.foreach(function(b,c,d){d=[];for(var e=0;e<c.size();++e){var f=c.get(e);if(!(f.rowItem.start()>a.end()||f.rowItem.end()<a.start())){var g=sprintf("item data-series-%s",x);if(null!==n&&0<d.length){var h=d[d.length-1],m=A(f.rowItem.start()),l=A(h.end);if(m-l<n){v?h.values==f.rowItem.metadata(t)&&(h.end=Math.max(h.end,f.rowItem.end())):h.end=Math.max(h.end,f.rowItem.end());h.valueItems[0].push(f);h.id=sprintf("b-%s-%s-%s",x,h.start,h.end);continue}}d.push(new epiviz.ui.charts.ChartObject(sprintf("b-%s-%s-%s",
x,f.rowItem.start(),f.rowItem.end()),f.rowItem.start(),f.rowItem.end(),f.rowItem.metadata(t),x,[[f]],[b],g,f.rowItem.seqName()))}}z=z.concat(d);++x});var E=c.getContext("2d");E.globalAlpha=.6;E.translate(l.left(),l.top());f.getContext("2d").translate(l.left(),l.top());z.forEach(function(a){E.beginPath();var b=(a.valueItems[0][0].rowItem.metadata(q)-w)/(r-w);cheight=m-l.sumAxis(e.Y);cypos=0;u&&(cheight=b*(m-l.sumAxis(e.Y)),cypos=(1-b)*(m-l.sumAxis(e.Y)));b=v?p.getByKey(a.values):p.get(a.seriesIndex);
E.fillStyle=b;E.strokeStyle="black";E.rect(A(a.start)/d+B,cypos,d*(A(a.end+1)-A(a.start)),cheight);E.fill();E.stroke()});this.addCanvasEvents(c,f,z,A);this._drawLegend();return z};
epiviz.plugins.charts.BlocksTrack.prototype._drawLegend=function(){var a=this;this._svg.selectAll(".chart-title").remove();this._svg.selectAll(".chart-title-color ").remove();if(this._lastData&&this._lastData.isReady()){this.customSettingsValues();var b=this.customSettingsValues()[epiviz.plugins.charts.BlocksTrackType.CustomSettings.USE_COLOR_BY],c=this._lastData.measurements();if("canvas"==this.chartDrawType){var d=a.canvas.getContext("2d"),e=0;c.forEach(function(b,c){var f;f=a._measurementColorLabels?
a.colors().getByKey(a._measurementColorLabels.get(b)):a.colors().get(c);d.strokeStyle=f;d.fillStyle=f;d.beginPath();d.arc(a.margins().left()+e-2,-9,4,0,2*Math.PI);d.stroke();d.fill();d.font="9px";d.beginPath();d.textAlign="start";d.fillText(b.name(),a.margins().left()+e+8,-14);f=d.measureText(b.name()).width;e=e+8+f+10})}else{b&&(c=Object.keys(a.colors()._keyIndices));var f=this._svg.selectAll(".chart-title").data(c).enter().append("text").attr("class","chart-title").attr("font-weight","bold").attr("fill",
function(c,d){return b?a.colors().getByKey(c):a._measurementColorLabels?a.colors().getByKey(a._measurementColorLabels.get(c)):a.colors().get(d)}).attr("y",a.margins().top()-5).text(function(a,c){return b?a:a.name()}),g=0,h=[];this._container.find(" .chart-title").each(function(a){h.push(g);g+=this.getBBox().width+15});f.attr("x",function(b,c){return a.margins().left()+10+h[c]});this._svg.selectAll(".chart-title-color").data(c).enter().append("circle").attr("class","chart-title-color").attr("cx",function(b,
c){return a.margins().left()+4+h[c]}).attr("cy",a.margins().top()-9).attr("r",4).style("shape-rendering","auto").style("stroke-width","0").style("fill",function(c,d){return b?a.colors().getByKey(c):a._measurementColorLabels?a.colors().getByKey(a._measurementColorLabels.get(c)):a.colors().get(d)})}}};epiviz.plugins.charts.BlocksTrack.prototype.setColors=function(a){this.container().find(".items").remove();epiviz.ui.charts.Visualization.prototype.setColors.call(this,a)};epiviz.ui.charts.TrackType=function(a){epiviz.ui.charts.ChartType.call(this,a)};epiviz.ui.charts.TrackType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.ChartType.prototype);epiviz.ui.charts.TrackType.constructor=epiviz.ui.charts.TrackType;epiviz.ui.charts.TrackType.prototype.chartDisplayType=function(){return epiviz.ui.charts.VisualizationType.DisplayType.TRACK};epiviz.ui.charts.TrackType.prototype.cssClass=function(){return"track-container ui-widget-content"};epiviz.plugins.charts.BlocksTrackType=function(a){epiviz.ui.charts.TrackType.call(this,a)};epiviz.plugins.charts.BlocksTrackType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.TrackType.prototype);epiviz.plugins.charts.BlocksTrackType.constructor=epiviz.plugins.charts.BlocksTrackType;epiviz.plugins.charts.BlocksTrackType.prototype.createNew=function(a,b,c){return new epiviz.plugins.charts.BlocksTrack(a,b,c)};epiviz.plugins.charts.BlocksTrackType.prototype.typeName=function(){return"epiviz.plugins.charts.BlocksTrack"};
epiviz.plugins.charts.BlocksTrackType.prototype.chartName=function(){return"Blocks Track"};epiviz.plugins.charts.BlocksTrackType.prototype.chartHtmlAttributeName=function(){return"blocks"};epiviz.plugins.charts.BlocksTrackType.prototype.isRestrictedToRangeMeasurements=function(){return!0};epiviz.plugins.charts.BlocksTrackType.prototype.measurementsFilter=function(){return function(a){return a.type()==epiviz.measurements.Measurement.Type.RANGE}};
epiviz.plugins.charts.BlocksTrackType.prototype.customSettingsDefs=function(){return epiviz.ui.charts.TrackType.prototype.customSettingsDefs.call(this).concat([new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.BlocksTrackType.CustomSettings.MIN_BLOCK_DISTANCE,epiviz.ui.charts.CustomSetting.Type.NUMBER,5,"Minimum block distance"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.BlocksTrackType.CustomSettings.USE_COLOR_BY,epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!1,"Use Block Color by"),
new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.BlocksTrackType.CustomSettings.BLOCK_COLOR_BY,epiviz.ui.charts.CustomSetting.Type.MEASUREMENTS_METADATA,"colLabel","Block color by"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.BlocksTrackType.CustomSettings.USE_SCALE_BY,epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!1,"Use Block scale by"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.BlocksTrackType.CustomSettings.BLOCK_SCALE_BY,epiviz.ui.charts.CustomSetting.Type.MEASUREMENTS_METADATA,
"colLabel","Block scale by"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MIN,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Min Y"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MAX,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Max Y")])};
epiviz.plugins.charts.BlocksTrackType.CustomSettings={MIN_BLOCK_DISTANCE:"minBlockDistance",BLOCK_COLOR_BY:"blockColorBy",USE_COLOR_BY:"useColorBy",BLOCK_SCALE_BY:"blockScaleBy",USE_SCALE_BY:"useScaleBy"};epiviz.plugins.charts.StackedBlocksTrack=function(a,b,c){epiviz.ui.charts.Track.call(this,a,b,c);this._initialize()};epiviz.plugins.charts.StackedBlocksTrack.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.Track.prototype);epiviz.plugins.charts.StackedBlocksTrack.constructor=epiviz.plugins.charts.StackedBlocksTrack;epiviz.plugins.charts.StackedBlocksTrack.prototype._initialize=function(){epiviz.ui.charts.Track.prototype._initialize.call(this);this._svg.classed("blocks-track",!0)};
epiviz.plugins.charts.StackedBlocksTrack.prototype.draw=function(a,b,c,d){epiviz.ui.charts.Track.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;return b&&a&&b.isReady()?this._drawBlocks(a,b,c||0,d||1):[]};epiviz.plugins.charts.StackedBlocksTrack.prototype.drawCanvas=function(a,b,c,d){epiviz.ui.charts.Track.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;return b&&a&&b.isReady()?this._drawBlocksCanvas(a,b,c||0,d||1):[]};
epiviz.plugins.charts.StackedBlocksTrack.prototype._drawBlocks=function(a,b,c,d){var e=epiviz.ui.charts.Axis,f=a.start(),g=a.end(),h=this.width(),m=this.height(),l=this.margins();this.measurements();var p=this.colors(),n=this.customSettingsValues()[epiviz.plugins.charts.StackedBlocksTrackType.CustomSettings.MIN_BLOCK_DISTANCE],t=this.customSettingsValues()[epiviz.plugins.charts.StackedBlocksTrackType.CustomSettings.BLOCK_COLOR_BY],v=this.customSettingsValues()[epiviz.plugins.charts.StackedBlocksTrackType.CustomSettings.USE_COLOR_BY],
q=d3.scale.linear().domain([f,g]).range([0,h-l.sumAxis(e.X)]),u=c*(h-l.sumAxis(e.X))/(g-f);this._clearAxes();this._drawAxes(q,null,10,5);var w=this,r=[],A=0;b.foreach(function(b,c,d){d=[];for(var e=0;e<c.size();++e){var f=c.get(e);if(!(f.rowItem.start()>a.end()||f.rowItem.end()<a.start())){var g=sprintf("item data-series-%s",A);if(null!==n&&0<d.length){var h=d[d.length-1],l=q(f.rowItem.start()),m=q(h.end);if(l-m<n){v?h.values==f.rowItem.metadata(t)&&(h.end=Math.max(h.end,f.rowItem.end())):h.end=Math.max(h.end,
f.rowItem.end());h.valueItems[0].push(f);h.id=sprintf("b-%s-%s-%s",A,h.start,h.end);continue}}d.push(new epiviz.ui.charts.ChartObject(sprintf("b-%s-%s-%s",A,f.rowItem.start(),f.rowItem.end()),f.rowItem.start(),f.rowItem.end(),f.rowItem.metadata(t),A,[[f]],[b],g))}}r=r.concat(d);++A});b=this._svg.select(".items");c=b.select(".selected");c=this._svg.select("#clip-"+this.id());b.empty()&&(c.empty()&&this._svg.select("defs").append("clipPath").attr("id","clip-"+this.id()).append("rect").attr("class",
"clip-path-rect"),b=this._svg.append("g").attr("class","items").attr("id",this.id()+"-gene-content").attr("clip-path","url(#clip-"+this.id()+")"),c=b.append("g").attr("class","selected"),b.append("g").attr("class","hovered"),c.append("g").attr("class","hovered"));b.attr("transform","translate("+l.left()+", "+l.top()+")");this._svg.select(".clip-path-rect").attr("x",0).attr("y",0).attr("width",h-l.sumAxis(e.X)).attr("height",m-l.sumAxis(e.Y));b.selectAll(".item").remove();var h=b.selectAll(".item").data(r,
function(a){return a.id}),B=(m-l.sumAxis(e.Y))/A;h.enter().insert("rect",":first-child").attr("class",function(a){return a.cssClasses}).style("fill",function(a){return v?p.getByKey(a.values):p.get(a.seriesIndex)}).attr("x",function(a){return q(a.start)/d+u}).attr("y",function(a){return a.seriesIndex*B+0}).attr("width",function(a){return d*(q(a.end+1)-q(a.start))}).attr("height",function(a){return B-0}).on("mouseout",function(){w._unhover.notify(new epiviz.ui.charts.VisEventArgs(w.id()))}).on("mouseover",
function(a){w._hover.notify(new epiviz.ui.charts.VisEventArgs(w.id(),a))}).on("click",function(a){w._deselect.notify(new epiviz.ui.charts.VisEventArgs(w.id()));w._select.notify(new epiviz.ui.charts.VisEventArgs(w.id(),a));d3.event.stopPropagation()});h.attr("class",function(a){return a.cssClasses}).transition().duration(500).attr("x",function(a){return q(a.start)}).attr("y",function(a){return a.seriesIndex*B+0}).attr("width",function(a){return q(a.end+1)-q(a.start)}).attr("height",function(a){return B-
0});h.exit().transition().duration(500).attr("x",function(a){return q(a.start)}).attr("y",function(a){return a.seriesIndex*B+0}).attr("width",function(a){return q(a.end+1)-q(a.start)}).attr("height",function(a){return B-0}).remove();return r};
epiviz.plugins.charts.StackedBlocksTrack.prototype._drawBlocksCanvas=function(a,b,c,d){var e=epiviz.ui.charts.Axis,f=a.start(),g=a.end(),h=this.width(),m=this.height(),l=this.margins();this.measurements();var p=this.colors(),n=this.customSettingsValues()[epiviz.plugins.charts.StackedBlocksTrackType.CustomSettings.MIN_BLOCK_DISTANCE],t=this.customSettingsValues()[epiviz.plugins.charts.StackedBlocksTrackType.CustomSettings.BLOCK_COLOR_BY],v=this.customSettingsValues()[epiviz.plugins.charts.StackedBlocksTrackType.CustomSettings.USE_COLOR_BY],
q=d3.scale.linear().domain([f,g]).range([0,h-l.sumAxis(e.X)]),u=c*(h-l.sumAxis(e.X))/(g-f);this._container.find("svg").remove();this._container.find("#"+this.id()+"-canvas").remove();c=document.createElement("canvas");this.chartDrawType="canvas";this.canvas=c;c.id=this.id()+"-canvas";this._container.append(c);c.width=this.width();c.height=this.height();c.style="position:absolute;top:0;left:0;width:100%;height:100%";this._container.find("#"+this.id()+"-hoverCanvas").remove();this.hoverCanvas=f=document.createElement("canvas");
f.id=this.id()+"-hoverCanvas";this._container.append(f);f.width=this.width();f.height=this.height();f.style="position:absolute;top:0;left:0;width:100%;height:100%;z-index:1";this._drawAxesCanvas(q,null,10,5,c);var w=[],r=0;b.foreach(function(b,c,d){d=[];for(var e=0;e<c.size();++e){var f=c.get(e);if(!(f.rowItem.start()>a.end()||f.rowItem.end()<a.start())){var g=sprintf("item data-series-%s",r);if(null!==n&&0<d.length){var h=d[d.length-1],l=q(f.rowItem.start()),m=q(h.end);if(l-m<n){v?h.values==f.rowItem.metadata(t)&&
(h.end=Math.max(h.end,f.rowItem.end())):h.end=Math.max(h.end,f.rowItem.end());h.valueItems[0].push(f);h.id=sprintf("b-%s-%s-%s",r,h.start,h.end);continue}}d.push(new epiviz.ui.charts.ChartObject(sprintf("b-%s-%s-%s",r,f.rowItem.start(),f.rowItem.end()),f.rowItem.start(),f.rowItem.end(),f.rowItem.metadata(t),r,[[f]],[b],g))}}w=w.concat(d);++r});var g=this._svg.select(".items"),A=g.select(".selected"),A=this._svg.select("#clip-"+this.id());g.empty()&&(A.empty()&&this._svg.select("defs").append("clipPath").attr("id",
"clip-"+this.id()).append("rect").attr("class","clip-path-rect"),g=this._svg.append("g").attr("class","items").attr("id",this.id()+"-gene-content").attr("clip-path","url(#clip-"+this.id()+")"),A=g.append("g").attr("class","selected"),g.append("g").attr("class","hovered"),A.append("g").attr("class","hovered"));g.attr("transform","translate("+l.left()+", "+l.top()+")");this._svg.select(".clip-path-rect").attr("x",0).attr("y",0).attr("width",h-l.sumAxis(e.X)).attr("height",m-l.sumAxis(e.Y));g.selectAll(".item").remove();
g.selectAll(".item").data(w,function(a){return a.id});var B=(m-l.sumAxis(e.Y))/r,z=c.getContext("2d");z.globalAlpha=.6;z.translate(l.left(),l.top());f.getContext("2d").translate(l.left(),l.top());w.forEach(function(a){z.beginPath();var c;c=1<b.measurements().length?p.get(a.seriesIndex):v?p.getByKey(a.values):p.get(a.seriesIndex);z.fillStyle=c;z.strokeStyle="black";z.rect(q(a.start)/d+u,a.seriesIndex*B,d*(q(a.end+1)-q(a.start)),B);z.fill();z.stroke()});this.addCanvasEvents(c,f,w,q);this._drawLegend();
return w};epiviz.plugins.charts.StackedBlocksTrack.prototype.setColors=function(a){this.container().find(".items").remove();epiviz.ui.charts.Visualization.prototype.setColors.call(this,a)};epiviz.plugins.charts.StackedBlocksTrackType=function(a){epiviz.ui.charts.TrackType.call(this,a)};epiviz.plugins.charts.StackedBlocksTrackType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.TrackType.prototype);epiviz.plugins.charts.StackedBlocksTrackType.constructor=epiviz.plugins.charts.StackedBlocksTrackType;epiviz.plugins.charts.StackedBlocksTrackType.prototype.createNew=function(a,b,c){return new epiviz.plugins.charts.StackedBlocksTrack(a,b,c)};
epiviz.plugins.charts.StackedBlocksTrackType.prototype.typeName=function(){return"epiviz.plugins.charts.StackedBlocksTrack"};epiviz.plugins.charts.StackedBlocksTrackType.prototype.chartName=function(){return"Stacked Blocks Track"};epiviz.plugins.charts.StackedBlocksTrackType.prototype.chartHtmlAttributeName=function(){return"stacked-blocks"};epiviz.plugins.charts.StackedBlocksTrackType.prototype.isRestrictedToRangeMeasurements=function(){return!0};
epiviz.plugins.charts.StackedBlocksTrackType.prototype.measurementsFilter=function(){return function(a){return a.type()==epiviz.measurements.Measurement.Type.RANGE}};
epiviz.plugins.charts.StackedBlocksTrackType.prototype.customSettingsDefs=function(){return epiviz.ui.charts.TrackType.prototype.customSettingsDefs.call(this).concat([new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.StackedBlocksTrackType.CustomSettings.MIN_BLOCK_DISTANCE,epiviz.ui.charts.CustomSetting.Type.NUMBER,5,"Minimum block distance"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.StackedBlocksTrackType.CustomSettings.USE_COLOR_BY,epiviz.ui.charts.CustomSetting.Type.BOOLEAN,
!1,"Use Block Color by"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.StackedBlocksTrackType.CustomSettings.BLOCK_COLOR_BY,epiviz.ui.charts.CustomSetting.Type.MEASUREMENTS_METADATA,"colLabel","Block color by")])};epiviz.plugins.charts.StackedBlocksTrackType.CustomSettings={MIN_BLOCK_DISTANCE:"minBlockDistance",BLOCK_COLOR_BY:"blockColorBy",USE_COLOR_BY:"useColorBy"};epiviz.plugins.charts.LineTrack=function(a,b,c){epiviz.ui.charts.Track.call(this,a,b,c);this._initialize()};epiviz.plugins.charts.LineTrack.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.Track.prototype);epiviz.plugins.charts.LineTrack.constructor=epiviz.plugins.charts.LineTrack;epiviz.plugins.charts.LineTrack.prototype._initialize=function(){epiviz.ui.charts.Track.prototype._initialize.call(this)};
epiviz.plugins.charts.LineTrack.prototype.drawCanvas=function(a,b,c,d){epiviz.ui.charts.Track.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;c=c||this._slide||0;d=d||this._zoom||1;this._slide=0;this._zoom=1;if(!b||!a)return[];var e=epiviz.ui.charts.CustomSetting,f=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN],g=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX],h=this.getDataMinMax(b);f==e.DEFAULT&&(f=h[0]);g==
e.DEFAULT&&(g=h[1]);null===f&&null===g&&(f=-1,g=1);null===f&&(f=g-1);null===g&&(g=f+1);var m=epiviz.ui.charts.Axis,e=d3.scale.linear().domain([a.start(),a.end()]).range([0,this.width()-this.margins().sumAxis(m.X)]),f=d3.scale.linear().domain([f,g]).range([this.height()-this.margins().sumAxis(m.Y),0]);this._container.find("svg").remove();this._container.find("#"+this.id()+"-canvas").remove();g=document.createElement("canvas");this.chartDrawType="canvas";this.canvas=g;g.id=this.id()+"-canvas";this._container.append(g);
g.width=this.width();g.height=this.height();g.style="position:absolute;top:0;left:0;width:100%;height:100%";this._container.find("#"+this.id()+"-hoverCanvas").remove();this.hoverCanvas=h=document.createElement("canvas");h.id=this.id()+"-hoverCanvas";this._container.append(h);h.width=this.width();h.height=this.height();h.style="position:absolute;top:0;left:0;width:100%;height:100%;z-index:1";this._drawAxesCanvas(e,f,10,5,g);g.getContext("2d").translate(this.margins().left(),this.margins().top());h.getContext("2d").translate(this.margins().left(),
this.margins().top());c=c*(this.width()-this.margins().sumAxis(m.X))/a.width();var l=this._svg.selectAll(".lines");l.empty()&&(l=this._svg.append("g").attr("class","lines").attr("transform","translate("+this.margins().left()+", "+this.margins().top()+")"));b.measurements().forEach(function(a,b){var c=l.selectAll(".line-series-index-"+b),d=l.selectAll(".point-series-index-"+b);c.empty()&&l.append("g").attr("class","line-series-index-"+b);d.empty()&&l.append("g").attr("class","point-series-index-"+
b)});for(m=b.measurements().length;;++m){var p=l.selectAll(".line-series-index-"+m),n=l.selectAll(".point-series-index-"+m);if(p.empty())break;p.remove();n.remove()}this.addCanvasEvents(g,h,null,e);this._drawLegend();return this._drawLinesCanvas(a,b,c,d,e,f,g)};
epiviz.plugins.charts.LineTrack.prototype.draw=function(a,b,c,d,e){epiviz.ui.charts.Track.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;c=c||this._slide||0;d=d||this._zoom||1;this._slide=0;this._zoom=1;if(!b||!a)return[];var f=epiviz.ui.charts.CustomSetting,g=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN],h=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX],m=this.getDataMinMax(b);g==f.DEFAULT&&(g=m[0]);h==f.DEFAULT&&
(h=m[1]);null===g&&null===h&&(g=-1,h=1);null===g&&(g=h-1);null===h&&(h=g+1);m=epiviz.ui.charts.Axis;f=d3.scale.linear().domain([a.start(),a.end()]).range([0,this.width()-this.margins().sumAxis(m.X)]);g=d3.scale.linear().domain([g,h]).range([this.height()-this.margins().sumAxis(m.Y),0]);this._clearAxes();e&&(g=d3.scale.linear().domain([-1*h,h]).range([this.height()-this.margins().sumAxis(m.Y),0]));this._drawAxes(f,g,10,5);c=c*(this.width()-this.margins().sumAxis(m.X))/a.width();var l=this._svg.selectAll(".lines");
l.empty()&&(l=this._svg.append("g").attr("class","lines").attr("transform","translate("+this.margins().left()+", "+this.margins().top()+")"));b.measurements().forEach(function(a,b){var c=l.selectAll(".line-series-index-"+b),d=l.selectAll(".point-series-index-"+b);c.empty()&&l.append("g").attr("class","line-series-index-"+b);d.empty()&&l.append("g").attr("class","point-series-index-"+b)});for(e=b.measurements().length;;++e){h=l.selectAll(".line-series-index-"+e);m=l.selectAll(".point-series-index-"+
e);if(h.empty())break;h.remove();m.remove()}return this._drawLines(a,b,c,d,f,g)};
epiviz.plugins.charts.LineTrack.prototype._drawLines=function(a,b,c,d,e,f){var g=this.colors(),h=parseInt(this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.STEP]),m=this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.SHOW_POINTS],l=this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.SHOW_LINES],p=this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.SHOW_ERROR_BARS],n=this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.POINT_RADIUS],
t=this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.LINE_THICKNESS],v=this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.INTERPOLATION];d=this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.ABS_LINE_VAL];var q=this,u=d3.scale.linear().domain([0,this.width()-this.margins().sumAxis(epiviz.ui.charts.Axis.X)]).range([a.start(),a.end()])(c)-a.start(),w=epiviz.datatypes.GenomicRange.fromStartEnd(a.seqName(),Math.min(a.start(),
a.start()+u),Math.max(a.end(),a.end()+u),a.genome()),r=this._svg.select(".lines"),A=[];b.foreach(function(a,b,d){var u=q._measurementColorLabels?g.getByKey(q._measurementColorLabels.get(a)):g.get(d),B=b.binarySearchStarts(w);if(0!=B.length){var x=Math.ceil(B.index/h)*h;B.length=Math.max(0,B.length-x+B.index);B.index=x;for(var x=epiviz.utils.range(B.length,B.index).filter(function(a){return!h||1>=h||0==(a-B.index)%h}),y=0;y<x.length;++y){var z=b.get(x[y]);A.push(new epiviz.ui.charts.ChartObject(sprintf("line_%s_%s",
d,z.globalIndex),z.rowItem.start(),z.rowItem.end(),[z.value],d,[[z]],[a],sprintf("item data-series-%s",d),z.rowItem.seqName()))}var C=function(a){a=b.get(a);return e(a.rowItem.start())};a=function(a){a=b.get(a);return f(a.value)};l?(y=d3.svg.line().x(C).y(a).interpolate(v),z=r.select(".line-series-index-"+d).selectAll("path").data([x]),z.enter().append("path").attr("d",y).style("shape-rendering","auto").style("stroke-opacity","0.8").on("mouseover",function(){q._captureMouseHover()}).on("mousemove",
function(){q._captureMouseHover()}).on("mouseout",function(){q._unhover.notify(new epiviz.ui.charts.VisEventArgs(q.id()))}),z.attr("d",y).style("stroke",u).style("stroke-width",t).attr("transform","translate("+ +c+")").transition().duration(500).attr("transform","translate(0)")):r.select(".line-series-index-"+d).selectAll("path").remove();r.select(".point-series-index-"+d).selectAll("circle").remove();r.select(".point-series-index-"+d).selectAll(".error-bar").remove();m&&(y=r.select(".point-series-index-"+
d).selectAll("circle").data(x),y.enter().append("circle").attr("class","point-series-index-"+d).attr("r",n).attr("cx",C).attr("cy",a).attr("fill",u).attr("stroke",u).attr("transform","translate("+ +c+")").transition().duration(500).attr("transform","translate(0)"),y.on("mouseover",function(){q._captureMouseHover()}).on("mousemove",function(){q._captureMouseHover()}).on("mouseout",function(){q._unhover.notify(new epiviz.ui.charts.VisEventArgs(q.id()))}),y.exit().transition().duration(500).style("opacity",
0).remove(),p&&(d=r.select(".point-series-index-"+d).selectAll(".error-bar").data(x),d.enter().append("g").attr("class","error-bar").each(function(a){var c;c=b.get(a);c=c.valueAnnotation?c.valueAnnotation.errMinus:null;c=void 0!=c?f(c):null;var d;d=b.get(a);d=d.valueAnnotation?d.valueAnnotation.errPlus:null;d=void 0!=d?f(d):null;null!=c&&null!=d&&(d3.select(this).append("line").attr("x1",C(a)).attr("x2",C(a)).attr("y1",c).attr("y2",d).style("stroke",u).style("shape-rendering","auto"),d3.select(this).append("line").attr("x1",
C(a)-2).attr("x2",C(a)+2).attr("y1",c).attr("y2",c).style("stroke",u).style("shape-rendering","auto"),d3.select(this).append("line").attr("x1",C(a)-2).attr("x2",C(a)+2).attr("y1",d).attr("y2",d).style("stroke",u).style("shape-rendering","auto"))}).attr("transform","translate("+ +c+")").transition().duration(500).attr("transform","translate(0)"),d.on("mouseover",function(){q._captureMouseHover()}).on("mousemove",function(){q._captureMouseHover()}).on("mouseout",function(){q._unhover.notify(new epiviz.ui.charts.VisEventArgs(q.id()))}),
d.exit().transition().duration(500).style("opacity",0).remove()))}});d!=epiviz.ui.charts.CustomSetting.DEFAULT&&(r.selectAll(".abLine").remove(),r.append("svg:line").attr("class","abLine").attr("x1",0).attr("x2",q.width()-q.margins().sumAxis(epiviz.ui.charts.Axis.X)).attr("y1",f(d)).attr("y2",f(d)).style("stroke","black").style("stroke-dasharray","5, 5"));return A};
epiviz.plugins.charts.LineTrack.prototype._drawLinesCanvas=function(a,b,c,d,e,f,g){var h=this.colors(),m=parseInt(this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.STEP]),l=this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.SHOW_POINTS],p=this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.SHOW_LINES],n=this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.SHOW_ERROR_BARS],t=this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.POINT_RADIUS],
v=this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.LINE_THICKNESS],q=this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.INTERPOLATION];d=this.customSettingsValues()[epiviz.plugins.charts.LineTrackType.CustomSettings.ABS_LINE_VAL];var u=this;c=d3.scale.linear().domain([0,this.width()-this.margins().sumAxis(epiviz.ui.charts.Axis.X)]).range([a.start(),a.end()])(c)-a.start();var w=epiviz.datatypes.GenomicRange.fromStartEnd(a.seqName(),Math.min(a.start(),
a.start()+c),Math.max(a.end(),a.end()+c),a.genome()),r=g.getContext("2d"),A=[];b.foreach(function(a,b,c){var d=u._measurementColorLabels?h.getByKey(u._measurementColorLabels.get(a)):h.get(c),g=b.binarySearchStarts(w);if(0!=g.length){var B=Math.ceil(g.index/m)*m;g.length=Math.max(0,g.length-B+g.index);g.index=B;for(var B=epiviz.utils.range(g.length,g.index).filter(function(a){return!m||1>=m||0==(a-g.index)%m}),y=0;y<B.length;++y){var x=b.get(B[y]);A.push(new epiviz.ui.charts.ChartObject(sprintf("line_%s_%s",
c,x.globalIndex),x.rowItem.start(),x.rowItem.end(),[x.value],c,[[x]],[a],sprintf("item data-series-%s",c),x.rowItem.seqName()))}var z=function(a){a=b.get(a);return e(a.rowItem.start())},H=function(a){a=b.get(a);return f(a.value)};p&&(a=d3.svg.line().x(z).y(H).interpolate(q),r.globalAlpha=.8,r.beginPath(),a=new Path2D(a(B)),r.strokeStyle=d,r.lineWidth=v,r.stroke(a),r.beginPath());l&&(B.forEach(function(a){var b=z(a);a=H(a);r.beginPath();r.arc(b,a,t,0,2*Math.PI);r.strokeStyle=d;r.stroke();r.fillStyle=
d;r.fill()}),n&&B.forEach(function(a){var c;c=b.get(a);c=c.valueAnnotation?c.valueAnnotation.errMinus:null;c=void 0!=c?f(c):null;var e;e=b.get(a);e=e.valueAnnotation?e.valueAnnotation.errPlus:null;e=void 0!=e?f(e):null;null!=c&&null!=e&&(r.beginPath(),r.moveTo(z(a),H(a)),r.lineTo(c,e),r.strokeStyle=d,r.stroke(),r.beginPath(),r.globalAlpha=.7,r.moveTo(z(a)-2,H(a)+2),r.lineTo(c,c),r.strokeStyle=d,r.stroke(),r.beginPath(),r.globalAlpha=.7,r.moveTo(z(a)-2,H(a)+2),r.lineTo(e,e),r.strokeStyle=d,r.stroke(),
r.beginPath())}))}});d!=epiviz.ui.charts.CustomSetting.DEFAULT&&(r.beginPath(),r.globalAlpha=.7,r.moveTo(0,f(d)),r.lineTo(u.width()-u.margins().sumAxis(epiviz.ui.charts.Axis.X),f(d)),r.strokeStyle=color,r.stroke());return A};epiviz.plugins.charts.LineTrackType=function(a){epiviz.ui.charts.TrackType.call(this,a)};epiviz.plugins.charts.LineTrackType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.TrackType.prototype);epiviz.plugins.charts.LineTrackType.constructor=epiviz.plugins.charts.LineTrackType;epiviz.plugins.charts.LineTrackType.prototype.createNew=function(a,b,c){return new epiviz.plugins.charts.LineTrack(a,b,c)};epiviz.plugins.charts.LineTrackType.prototype.typeName=function(){return"epiviz.plugins.charts.LineTrack"};
epiviz.plugins.charts.LineTrackType.prototype.chartName=function(){return"Line Track"};epiviz.plugins.charts.LineTrackType.prototype.chartHtmlAttributeName=function(){return"lines"};epiviz.plugins.charts.LineTrackType.prototype.measurementsFilter=function(){return function(a){return a.type()==epiviz.measurements.Measurement.Type.FEATURE}};
epiviz.plugins.charts.LineTrackType.prototype.customSettingsDefs=function(){return epiviz.ui.charts.TrackType.prototype.customSettingsDefs.call(this).concat([new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.LineTrackType.CustomSettings.STEP,epiviz.ui.charts.CustomSetting.Type.NUMBER,50,"Step"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.LineTrackType.CustomSettings.SHOW_POINTS,epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!1,"Show points"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.LineTrackType.CustomSettings.SHOW_LINES,
epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!0,"Show lines"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.LineTrackType.CustomSettings.SHOW_ERROR_BARS,epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!0,"Show error bars"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.LineTrackType.CustomSettings.POINT_RADIUS,epiviz.ui.charts.CustomSetting.Type.NUMBER,1,"Point radius"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.LineTrackType.CustomSettings.LINE_THICKNESS,epiviz.ui.charts.CustomSetting.Type.NUMBER,
1,"Line thickness"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MIN,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Min Y"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MAX,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Max Y"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.LineTrackType.CustomSettings.INTERPOLATION,epiviz.ui.charts.CustomSetting.Type.CATEGORICAL,
"linear","Interpolation","linear step-before step-after basis basis-open basis-closed bundle cardinal cardinal-open monotone".split(" ")),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.LineTrackType.CustomSettings.ABS_LINE_VAL,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Draw abline")])};
epiviz.plugins.charts.LineTrackType.CustomSettings={STEP:"step",SHOW_POINTS:"showPoints",SHOW_ERROR_BARS:"showErrorBars",SHOW_LINES:"showLines",POINT_RADIUS:"pointRadius",LINE_THICKNESS:"lineThickness",INTERPOLATION:"interpolation",ABS_LINE_VAL:"abLine"};epiviz.plugins.charts.StackedLineTrack=function(a,b,c){epiviz.ui.charts.Track.call(this,a,b,c);this._initialize()};epiviz.plugins.charts.StackedLineTrack.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.Track.prototype);epiviz.plugins.charts.StackedLineTrack.constructor=epiviz.plugins.charts.StackedLineTrack;epiviz.plugins.charts.StackedLineTrack.prototype._initialize=function(){epiviz.ui.charts.Track.prototype._initialize.call(this)};
epiviz.plugins.charts.StackedLineTrack.prototype.draw=function(a,b,c,d){epiviz.ui.charts.Track.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;c=c||this._slide;d=d||this._zoom;this._slide=0;this._zoom=1;if(!b||!a)return[];var e=epiviz.ui.charts.Axis;c=(c||0)*(this.width()-this.margins().sumAxis(e.X))/a.width();return this._drawLines(a,b,c,d||1)};
epiviz.plugins.charts.StackedLineTrack.prototype.drawCanvas=function(a,b,c,d){epiviz.ui.charts.Track.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;c=c||this._slide;d=d||this._zoom;this._slide=0;this._zoom=1;if(!b||!a)return[];var e=epiviz.ui.charts.Axis;c=(c||0)*(this.width()-this.margins().sumAxis(e.X))/a.width();return this._drawLinesCanvas(a,b,c,d||1)};
epiviz.plugins.charts.StackedLineTrack.prototype._drawLines=function(a,b,c,d){var e=epiviz.ui.charts.Axis,f=this.colors(),g=this.customSettingsValues()[epiviz.plugins.charts.StackedLineTrackType.CustomSettings.STEP],h=this.customSettingsValues()[epiviz.plugins.charts.StackedLineTrackType.CustomSettings.INTERPOLATION],m=this.customSettingsValues()[epiviz.plugins.charts.StackedLineTrackType.CustomSettings.OFFSET];d=this.customSettingsValues()[epiviz.plugins.charts.StackedLineTrackType.CustomSettings.ABS_LINE_VAL];
var l=this,p=d3.scale.linear().domain([0,this.width()-this.margins().sumAxis(e.X)]).range([a.start(),a.end()])(c)-a.start();epiviz.datatypes.GenomicRange.fromStartEnd(a.seqName(),Math.min(a.start(),a.start()+p),Math.max(a.end(),a.end()+p));var n=[],t=[],v=b.firstSeries().globalStartIndex(),q=b.firstSeries().globalEndIndex();b.foreach(function(a,b){var c=b.globalStartIndex(),d=b.globalEndIndex();c>v&&(v=c);d<q&&(q=d)});var v=Math.ceil(v/g)*g,q=Math.floor(q/g)*g,u;b.foreach(function(a,b,c){for(var d=
epiviz.utils.range((q-v)/g).map(function(a){return a*g+v}).filter(function(a){return b.getByGlobalIndex(a)}),e=0;e<d.length;++e){var f=b.getByGlobalIndex(d[e]);n.push(new epiviz.ui.charts.ChartObject(sprintf("line_%s_%s",c,f.globalIndex),f.rowItem.start(),f.rowItem.end(),[f.value],c,[[f]],[a],sprintf("item data-series-%s",c),f.rowItem.seqName()))}var h=[];d.forEach(function(a){h.push({x:b.getByGlobalIndex(a).rowItem.start(),y:b.getByGlobalIndex(a).value})});t.push(h);u||(u=[],d.forEach(function(a){a=
b.getByGlobalIndex(a);u.push(a.rowItem.metadata("bacteria"))}))});var w=d3.scale.linear().domain([a.start(),a.end()]).range([0,this.width()-this.margins().sumAxis(e.X)]);this._clearAxes();this._drawAxes(w,void 0,10);a=this._svg.select(".lines");a.empty()&&(a=this._svg.append("g").attr("class","lines").attr("transform","translate("+this.margins().left()+", "+this.margins().top()+")"));b=d3.layout.stack().offset(m)(t);var r=d3.scale.linear().domain([Math.min(0,d3.min(b,function(a){return d3.min(a,function(a){return a.y0+
a.y})})),d3.max(b,function(a){return d3.max(a,function(a){return a.y0+a.y})})]).range([this.height()-this.margins().sumAxis(e.Y),0]),e=d3.svg.area().x(function(a){return w(a.x)}).y0(function(a){return r(a.y0)}).y1(function(a){return r(a.y0+a.y)}).interpolate(h),h=a.selectAll("path").data(b);h.enter().append("path").attr("d",e).style("shape-rendering","auto").style("stroke-width","0").style("fill",function(a,b){return f.get(b)}).on("mouseover",function(){l._captureMouseHover()}).on("mousemove",function(){l._captureMouseHover()}).on("mouseout",
function(){l._unhover.notify(new epiviz.ui.charts.VisEventArgs(l.id()))});h.attr("d",e).style("fill",function(a,b){return f.get(b)}).attr("transform","translate("+ +c+")").transition().duration(500).attr("transform","translate(0)");d!=epiviz.ui.charts.CustomSetting.DEFAULT&&(a.selectAll(".abLine").remove(),a.append("svg:line").attr("class","abLine").attr("x1",0).attr("x2",l.width()-l.margins().sumAxis(epiviz.ui.charts.Axis.X)).attr("y1",r(d)).attr("y2",r(d)).style("stroke","black").style("stroke-dasharray",
"5, 5"));return n};
epiviz.plugins.charts.StackedLineTrack.prototype._drawLinesCanvas=function(a,b,c,d){d=epiviz.ui.charts.Axis;var e=this.colors(),f=this.customSettingsValues()[epiviz.plugins.charts.StackedLineTrackType.CustomSettings.STEP],g=this.customSettingsValues()[epiviz.plugins.charts.StackedLineTrackType.CustomSettings.INTERPOLATION],h=this.customSettingsValues()[epiviz.plugins.charts.StackedLineTrackType.CustomSettings.OFFSET],m=this.customSettingsValues()[epiviz.plugins.charts.StackedLineTrackType.CustomSettings.ABS_LINE_VAL];c=
d3.scale.linear().domain([0,this.width()-this.margins().sumAxis(d.X)]).range([a.start(),a.end()])(c)-a.start();epiviz.datatypes.GenomicRange.fromStartEnd(a.seqName(),Math.min(a.start(),a.start()+c),Math.max(a.end(),a.end()+c));var l=[],p=[],n=b.firstSeries().globalStartIndex(),t=b.firstSeries().globalEndIndex();b.foreach(function(a,b){var c=b.globalStartIndex(),d=b.globalEndIndex();c>n&&(n=c);d<t&&(t=d)});var n=Math.ceil(n/f)*f,t=Math.floor(t/f)*f,v;b.foreach(function(a,b,c){for(var d=epiviz.utils.range((t-
n)/f).map(function(a){return a*f+n}).filter(function(a){return b.getByGlobalIndex(a)}),e=0;e<d.length;++e){var g=b.getByGlobalIndex(d[e]);l.push(new epiviz.ui.charts.ChartObject(sprintf("line_%s_%s",c,g.globalIndex),g.rowItem.start(),g.rowItem.end(),[g.value],c,[[g]],[a],sprintf("item data-series-%s",c),g.rowItem.seqName()))}var h=[];d.forEach(function(a){h.push({x:b.getByGlobalIndex(a).rowItem.start(),y:b.getByGlobalIndex(a).value})});p.push(h);v||(v=[],d.forEach(function(a){a=b.getByGlobalIndex(a);
v.push(a.rowItem.metadata("bacteria"))}))});var q=d3.scale.linear().domain([a.start(),a.end()]).range([0,this.width()-this.margins().sumAxis(d.X)]);this._container.find("svg").remove();this._container.find("#"+this.id()+"-canvas").remove();a=document.createElement("canvas");this.chartDrawType="canvas";this.canvas=a;a.id=this.id()+"-canvas";this._container.append(a);a.width=this.width();a.height=this.height();a.style="position:absolute;top:0;left:0;width:100%;height:100%";this._container.find("#"+
this.id()+"-hoverCanvas").remove();this.hoverCanvas=b=document.createElement("canvas");b.id=this.id()+"-hoverCanvas";this._container.append(b);b.width=this.width();b.height=this.height();b.style="position:absolute;top:0;left:0;width:100%;height:100%;z-index:1";this._drawAxesCanvas(q,null,10,0,a);c=this._svg.select(".lines");c.empty()&&(c=this._svg.append("g").attr("class","lines").attr("transform","translate("+this.margins().left()+", "+this.margins().top()+")"));var h=d3.layout.stack().offset(h)(p),
u=d3.scale.linear().domain([Math.min(0,d3.min(h,function(a){return d3.min(a,function(a){return a.y0+a.y})})),d3.max(h,function(a){return d3.max(a,function(a){return a.y0+a.y})})]).range([this.height()-this.margins().sumAxis(d.Y),0]),w=d3.svg.area().x(function(a){return q(a.x)}).y0(function(a){return u(a.y0)}).y1(function(a){return u(a.y0+a.y)}).interpolate(g),r=a.getContext("2d");r.translate(this.margins().left(),this.margins().top());b.getContext("2d").translate(this.margins().left(),this.margins().top());
h.forEach(function(a,b){r.globalAlpha=.8;r.beginPath();var c=new Path2D(w(a));r.strokeStyle=e.get(b);r.lineWidth=1;r.fillStyle=e.get(b);r.fill(c);r.stroke()});m!=epiviz.ui.charts.CustomSetting.DEFAULT&&(r.beginPath(),r.globalAlpha=.7,r.moveTo(0,u(m)),r.lineTo(this.width()-this.margins().sumAxis(epiviz.ui.charts.Axis.X),u(m)),r.strokeStyle=color,r.stroke());this.addCanvasEvents(a,b,null,q);this._drawLegend();return l};epiviz.plugins.charts.StackedLineTrackType=function(a){epiviz.ui.charts.TrackType.call(this,a)};epiviz.plugins.charts.StackedLineTrackType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.TrackType.prototype);epiviz.plugins.charts.StackedLineTrackType.constructor=epiviz.plugins.charts.StackedLineTrackType;epiviz.plugins.charts.StackedLineTrackType.prototype.createNew=function(a,b,c){return new epiviz.plugins.charts.StackedLineTrack(a,b,c)};
epiviz.plugins.charts.StackedLineTrackType.prototype.typeName=function(){return"epiviz.plugins.charts.StackedLineTrack"};epiviz.plugins.charts.StackedLineTrackType.prototype.chartName=function(){return"Stacked Track"};epiviz.plugins.charts.StackedLineTrackType.prototype.chartHtmlAttributeName=function(){return"stacked-lines"};epiviz.plugins.charts.StackedLineTrackType.prototype.measurementsFilter=function(){return function(a){return a.type()==epiviz.measurements.Measurement.Type.FEATURE}};
epiviz.plugins.charts.StackedLineTrackType.prototype.isRestrictedToSameDatasourceGroup=function(){return!0};
epiviz.plugins.charts.StackedLineTrackType.prototype.customSettingsDefs=function(){return epiviz.ui.charts.TrackType.prototype.customSettingsDefs.call(this).concat([new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.StackedLineTrackType.CustomSettings.STEP,epiviz.ui.charts.CustomSetting.Type.NUMBER,1,"Step"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.StackedLineTrackType.CustomSettings.OFFSET,epiviz.ui.charts.CustomSetting.Type.CATEGORICAL,"zero","Offset",["zero","wiggle"]),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.StackedLineTrackType.CustomSettings.INTERPOLATION,
epiviz.ui.charts.CustomSetting.Type.CATEGORICAL,"basis","Interpolation","linear step-before step-after basis basis-open basis-closed bundle cardinal cardinal-open monotone".split(" ")),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.StackedLineTrackType.CustomSettings.ABS_LINE_VAL,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Draw abline")])};
epiviz.plugins.charts.StackedLineTrackType.CustomSettings={STEP:"step",OFFSET:"offset",INTERPOLATION:"interpolation",ABS_LINE_VAL:"abLine"};epiviz.plugins.charts.MultiStackedLineTrack=function(a,b,c){epiviz.ui.charts.Track.call(this,a,b,c);this._initialize()};epiviz.plugins.charts.MultiStackedLineTrack.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.Track.prototype);epiviz.plugins.charts.MultiStackedLineTrack.constructor=epiviz.plugins.charts.MultiStackedLineTrack;epiviz.plugins.charts.MultiStackedLineTrack.prototype._initialize=function(){epiviz.ui.charts.Track.prototype._initialize.call(this)};
epiviz.plugins.charts.MultiStackedLineTrack.prototype.drawCanvas=function(a,b,c,d){epiviz.ui.charts.Track.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;c=c||this._slide||0;d=d||this._zoom||1;this._slide=0;this._zoom=1;if(!b||!a)return[];var e=epiviz.ui.charts.CustomSetting,f=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN],g=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX],h=this.getDataMinMax(b);f==e.DEFAULT&&
(f=h[0]);g==e.DEFAULT&&(g=h[1]);null===f&&null===g&&(f=-1,g=1);null===f&&(f=g-1);null===g&&(g=f+1);var m=epiviz.ui.charts.Axis,e=d3.scale.linear().domain([a.start(),a.end()]).range([0,this.width()-this.margins().sumAxis(m.X)]),f=d3.scale.linear().domain([f,g]).range([this.height()-this.margins().sumAxis(m.Y),0]);this._container.find("svg").remove();this._container.find("#"+this.id()+"-canvas").remove();g=document.createElement("canvas");this.chartDrawType="canvas";this.canvas=g;g.id=this.id()+"-canvas";
this._container.append(g);g.width=this.width();g.height=this.height();g.style="position:absolute;top:0;left:0;width:100%;height:100%";this._container.find("#"+this.id()+"-hoverCanvas").remove();this.hoverCanvas=h=document.createElement("canvas");h.id=this.id()+"-hoverCanvas";this._container.append(h);h.width=this.width();h.height=this.height();h.style="position:absolute;top:0;left:0;width:100%;height:100%;z-index:1";this._drawAxesCanvas(e,null,10,5,g);g.getContext("2d").translate(this.margins().left(),
this.margins().top());h.getContext("2d").translate(this.margins().left(),this.margins().top());c=c*(this.width()-this.margins().sumAxis(m.X))/a.width();var l=this._svg.selectAll(".lines");l.empty()&&(l=this._svg.append("g").attr("class","lines").attr("transform","translate("+this.margins().left()+", "+this.margins().top()+")"));b.measurements().forEach(function(a,b){var c=l.selectAll(".line-series-index-"+b),d=l.selectAll(".point-series-index-"+b);c.empty()&&l.append("g").attr("class","line-series-index-"+
b);d.empty()&&l.append("g").attr("class","point-series-index-"+b)});for(m=b.measurements().length;;++m){var p=l.selectAll(".line-series-index-"+m),n=l.selectAll(".point-series-index-"+m);if(p.empty())break;p.remove();n.remove()}this.addCanvasEvents(g,h,null,e);this._drawLegend();return this._drawLinesCanvas(a,b,c,d,e,f,g)};
epiviz.plugins.charts.MultiStackedLineTrack.prototype.draw=function(a,b,c,d){epiviz.ui.charts.Track.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;c=c||this._slide||0;d=d||this._zoom||1;this._slide=0;this._zoom=1;if(!b||!a)return[];var e=epiviz.ui.charts.CustomSetting,f=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN],g=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX],h=this.getDataMinMax(b);f==e.DEFAULT&&(f=h[0]);
g==e.DEFAULT&&(g=h[1]);null===f&&null===g&&(f=-1,g=1);null===f&&--f;null===g&&(g+=1);var m=epiviz.ui.charts.Axis,e=d3.scale.linear().domain([a.start(),a.end()]).range([0,this.width()-this.margins().sumAxis(m.X)]),h=d3.scale.linear().domain([f,g]).range([this.height()-this.margins().sumAxis(m.Y),0]);this._clearAxes();this._drawAxes(e,null,10,5);c=c*(this.width()-this.margins().sumAxis(m.X))/a.width();var l=this._svg.selectAll(".lines");l.empty()&&(l=this._svg.append("g").attr("class","lines").attr("transform",
"translate("+this.margins().left()+", "+this.margins().top()+")"));b.measurements().forEach(function(a,b){var c=l.selectAll(".line-series-index-"+b),d=l.selectAll(".point-series-index-"+b);c.empty()&&l.append("g").attr("class","line-series-index-"+b);d.empty()&&l.append("g").attr("class","point-series-index-"+b)});for(m=b.measurements().length;;++m){var p=l.selectAll(".line-series-index-"+m),n=l.selectAll(".point-series-index-"+m);if(p.empty())break;p.remove();n.remove()}return this._drawLines(a,
b,c,d,e,h,f,g)};
epiviz.plugins.charts.MultiStackedLineTrack.prototype._drawLines=function(a,b,c,d,e,f,g,h){var m=this.colors(),l=parseInt(this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.STEP]),p=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_POINTS],n=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_LINES],t=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_FILL],v=
this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_ERROR_BARS],q=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.POINT_RADIUS],u=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.LINE_THICKNESS],w=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.INTERPOLATION],r=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.ABS_LINE_VAL],
A=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_Y_AXIS],B=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.AUTO_SCALE],z;B&&(z=this.getDataMinMax(b));var x=this;d=d3.scale.linear().domain([0,this.width()-this.margins().sumAxis(epiviz.ui.charts.Axis.X)]).range([a.start(),a.end()])(c)-a.start();var E=epiviz.datatypes.GenomicRange.fromStartEnd(a.seqName(),Math.min(a.start(),a.start()+d),Math.max(a.end(),a.end()+
d),a.genome()),G=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_TRACKS],F=(x.height()-x.margins().sumAxis(epiviz.ui.charts.Axis.Y))/b.measurements().length;"default"!=G&&(d=G.split(","),F=(x.height()-x.margins().sumAxis(epiviz.ui.charts.Axis.Y))/d.length);var y=0,I=this._svg.select(".lines"),C=[];for(b.foreach(function(b,d,f){f=!1;"default"!=G&&(G.split(",").includes(b.id())||(f=!0));if(!f){var g=x._measurementColorLabels?m.getByKey(x._measurementColorLabels.get(b)):
m.get(y),h=d.binarySearchStarts(E);if(0!=h.length){f=Math.ceil(h.index/l)*l;h.length=Math.max(0,h.length-f+h.index);h.index=f;f=epiviz.utils.range(h.length,h.index).filter(function(a){return!l||1>=l||0==(a-h.index)%l});var L=1E3;smaxY=-1E3;for(var D=0;D<f.length;++D){var J=d.get(f[D]);C.push(new epiviz.ui.charts.ChartObject(sprintf("line_%s_%s",y,J.globalIndex),J.rowItem.start(),J.rowItem.end(),[J.value],y,[[J]],[b],sprintf("item data-series-%s",y),J.rowItem.seqName()));J.value<L&&(L=J.value);J.value>
smaxY&&(smaxY=J.value)}b=epiviz.ui.charts.CustomSetting;D=x.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN];J=x.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX];B?(D==b.DEFAULT&&(D=Math.min(0,Math.floor(z[0]))),J==b.DEFAULT&&(J=Math.ceil(z[1]))):(D=Math.min(0,Math.floor(L)),J=Math.ceil(smaxY));var H=d3.scale.linear().domain([D,J]).range([(y+1)*F,y*F]),P=function(a){a=d.get(a);return e(a.rowItem.start())};b=function(a){a=d.get(a);return H(a.value)};
if(A){var L=x._svg.select(".axes"),S=L.select(".yAxis-grid"),Q=L.select(".yAxis-line");S.empty()&&(S=L.append("g").attr("class","yAxis yAxis-grid"));Q.empty()&&(Q=L.append("g").attr("class","yAxis yAxis-line"));d3.svg.line().x([a.start(),a.start()]).y([(y+1)*F,y*F]);Q.append("line").attr("x1",e(a.start())).attr("y1",H(D)).attr("x2",e(a.start())).attr("y2",H(.8*J)).attr("class","yAxis-line-series-"+y).style("shape-rendering","auto").style("stroke-opacity","0.6").attr("transform","translate("+x.margins().left()+
", "+x.margins().top()+")");Q.append("text").attr("class","yAxis-line-minlabel-series"+y).attr("x",e(a.start())-17).attr("y",H(.8*J)).text(d3.format(".2g")(J)).style("opacity","0.6").attr("transform","translate("+x.margins().left()+", "+x.margins().top()+")");Q.append("text").attr("class","yAxis-line-maxlabel-series"+y).attr("x",e(a.start())-17).attr("y",H(D)).text(d3.format(".2g")(D)).style("opacity","0.6").attr("transform","translate("+x.margins().left()+", "+x.margins().top()+")")}else I.select(".line-series-index-"+
y).selectAll("text").remove(),I.select(".line-series-index-"+y).append("text").attr("class","chart-title").attr("font-weight","bold").attr("fill",function(a,b){return x._measurementColorLabels?x.colors().getByKey(x._measurementColorLabels.get(a)):x.colors().get(y)}).attr("x",0).attr("y",y*F+20).text("max: "+d3.format(".2g")(J));n?(J=d3.svg.line().x(P).y(b).interpolate(w),D=I.select(".line-series-index-"+y).selectAll("path").data([f]),D.enter().append("path").attr("d",J).style("shape-rendering","auto").style("stroke-opacity",
"0.8").on("mouseover",function(){x._captureMouseHover()}).on("mousemove",function(){x._captureMouseHover()}).on("mouseout",function(){x._unhover.notify(new epiviz.ui.charts.VisEventArgs(x.id()))}),D.attr("d",J).style("stroke",g).style("stroke-width",u).attr("transform","translate("+ +c+")").transition().duration(500).attr("transform","translate(0)")):I.select(".line-series-index-"+y).selectAll("path").remove();t&&(J=d3.svg.area().x1(P).y1(b).x0(P).y0(H(0)).interpolate(w),D=I.select(".line-series-index-"+
y).selectAll("path").data([f]),D.enter().append("path").attr("d",J).style("shape-rendering","auto").style("stroke-opacity","0.8").on("mouseover",function(){x._captureMouseHover()}).on("mousemove",function(){x._captureMouseHover()}).on("mouseout",function(){x._unhover.notify(new epiviz.ui.charts.VisEventArgs(x.id()))}),D.attr("d",J).style("fill",g).attr("transform","translate("+ +c+")").transition().duration(500).attr("transform","translate(0)"));I.select(".point-series-index-"+y).selectAll("circle").remove();
I.select(".point-series-index-"+y).selectAll(".error-bar").remove();p&&(D=I.select(".point-series-index-"+y).selectAll("circle").data(f),D.enter().append("circle").attr("class","point-series-index-"+y).attr("r",q).attr("cx",P).attr("cy",b).attr("fill",g).attr("stroke",g).attr("transform","translate("+ +c+")").transition().duration(500).attr("transform","translate(0)"),D.on("mouseover",function(){x._captureMouseHover()}).on("mousemove",function(){x._captureMouseHover()}).on("mouseout",function(){x._unhover.notify(new epiviz.ui.charts.VisEventArgs(x.id()))}),
D.exit().transition().duration(500).style("opacity",0).remove(),v&&(f=I.select(".point-series-index-"+y).selectAll(".error-bar").data(f),f.enter().append("g").attr("class","error-bar").each(function(a){var b;b=d.get(a);b=b.valueAnnotation?b.valueAnnotation.errMinus:null;b=void 0!=b?H(b):null;var c;c=d.get(a);c=c.valueAnnotation?c.valueAnnotation.errPlus:null;c=void 0!=c?H(c):null;null!=b&&null!=c&&(d3.select(this).append("line").attr("x1",P(a)).attr("x2",P(a)).attr("y1",b).attr("y2",c).style("stroke",
g).style("shape-rendering","auto"),d3.select(this).append("line").attr("x1",P(a)-2).attr("x2",P(a)+2).attr("y1",b).attr("y2",b).style("stroke",g).style("shape-rendering","auto"),d3.select(this).append("line").attr("x1",P(a)-2).attr("x2",P(a)+2).attr("y1",c).attr("y2",c).style("stroke",g).style("shape-rendering","auto"))}).attr("transform","translate("+ +c+")").transition().duration(500).attr("transform","translate(0)"),f.on("mouseover",function(){x._captureMouseHover()}).on("mousemove",function(){x._captureMouseHover()}).on("mouseout",
function(){x._unhover.notify(new epiviz.ui.charts.VisEventArgs(x.id()))}),f.exit().transition().duration(500).style("opacity",0).remove()),r!=epiviz.ui.charts.CustomSetting.DEFAULT&&(I.selectAll(".abLine").remove(),I.append("svg:line").attr("class","abLine").attr("x1",0).attr("x2",x.width()-x.margins().sumAxis(epiviz.ui.charts.Axis.X)).attr("y1",H(r)).attr("y2",H(r)).style("stroke","black").style("stroke-dasharray","5, 5")));y++}}});y<b.measurements().length;y++)I.select(".line-series-index-"+y).remove(),
I.select(".point-series-index-"+y).remove();return C};
epiviz.plugins.charts.MultiStackedLineTrack.prototype._drawLinesCanvas=function(a,b,c,d,e,f,g){var h=this.colors(),m=parseInt(this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.STEP]),l=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_POINTS],p=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_LINES],n=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_ERROR_BARS],t=
this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.POINT_RADIUS],v=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.LINE_THICKNESS],q=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.INTERPOLATION];d=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.ABS_LINE_VAL];var u=this;c=d3.scale.linear().domain([0,this.width()-this.margins().sumAxis(epiviz.ui.charts.Axis.X)]).range([a.start(),
a.end()])(c)-a.start();var w=epiviz.datatypes.GenomicRange.fromStartEnd(a.seqName(),Math.min(a.start(),a.start()+c),Math.max(a.end(),a.end()+c),a.genome()),r=g.getContext("2d"),A=[];b.foreach(function(a,b,c){var d=u._measurementColorLabels?h.getByKey(u._measurementColorLabels.get(a)):h.get(c),g=b.binarySearchStarts(w);if(0!=g.length){var B=Math.ceil(g.index/m)*m;g.length=Math.max(0,g.length-B+g.index);g.index=B;for(var B=epiviz.utils.range(g.length,g.index).filter(function(a){return!m||1>=m||0==(a-
g.index)%m}),y=0;y<B.length;++y){var x=b.get(B[y]);A.push(new epiviz.ui.charts.ChartObject(sprintf("line_%s_%s",c,x.globalIndex),x.rowItem.start(),x.rowItem.end(),[x.value],c,[[x]],[a],sprintf("item data-series-%s",c),x.rowItem.seqName()))}var C=function(a){a=b.get(a);return e(a.rowItem.start())},z=function(a){a=b.get(a);return f(a.value)};p&&(a=d3.svg.line().x(C).y(z).interpolate(q),r.globalAlpha=.8,r.beginPath(),a=new Path2D(a(B)),r.strokeStyle=d,r.lineWidth=v,r.stroke(a),r.beginPath());l&&(B.forEach(function(a){var b=
C(a);a=z(a);r.beginPath();r.arc(b,a,t,0,2*Math.PI);r.strokeStyle=d;r.stroke();r.fillStyle=d;r.fill()}),n&&B.forEach(function(a){var c;c=b.get(a);c=c.valueAnnotation?c.valueAnnotation.errMinus:null;c=void 0!=c?f(c):null;var e;e=b.get(a);e=e.valueAnnotation?e.valueAnnotation.errPlus:null;e=void 0!=e?f(e):null;null!=c&&null!=e&&(r.beginPath(),r.moveTo(C(a),z(a)),r.lineTo(c,e),r.strokeStyle=d,r.stroke(),r.beginPath(),r.globalAlpha=.7,r.moveTo(C(a)-2,z(a)+2),r.lineTo(c,c),r.strokeStyle=d,r.stroke(),r.beginPath(),
r.globalAlpha=.7,r.moveTo(C(a)-2,z(a)+2),r.lineTo(e,e),r.strokeStyle=d,r.stroke(),r.beginPath())}))}});d!=epiviz.ui.charts.CustomSetting.DEFAULT&&(r.beginPath(),r.globalAlpha=.7,r.moveTo(0,f(d)),r.lineTo(u.width()-u.margins().sumAxis(epiviz.ui.charts.Axis.X),f(d)),r.strokeStyle=color,r.stroke());return A};
epiviz.plugins.charts.MultiStackedLineTrack.prototype._drawLegend=function(){var a=this;this._svg.selectAll(".chart-title").remove();this._svg.selectAll(".chart-title-color ").remove();if(this._lastData&&this._lastData.isReady()){var b=this._lastData.measurements();if("canvas"==this.chartDrawType){var c=a.canvas.getContext("2d"),d=0;b.forEach(function(b,e){var f;f=a._measurementColorLabels?a.colors().getByKey(a._measurementColorLabels.get(b)):a.colors().get(e);c.strokeStyle=f;c.fillStyle=f;c.beginPath();
c.arc(a.margins().left()+d-2,-9,4,0,2*Math.PI);c.stroke();c.fill();c.font="9px";c.beginPath();c.textAlign="start";c.fillText(b.name(),a.margins().left()+d+8,-14);f=c.measureText(b.name()).width;d=d+8+f+10})}else if(-1!=a._id.indexOf("stacked")){var e=(a.height()-a.margins().sumAxis(epiviz.ui.charts.Axis.Y))/a.measurements().size(),f=b,g=this.customSettingsValues()[epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_TRACKS];if("default"!=g){var h=g.split(","),e=(a.height()-a.margins().sumAxis(epiviz.ui.charts.Axis.Y))/
h.length,f=[];b.forEach(function(a){h.includes(a.id())&&f.push(a)})}g=this._svg.selectAll(".chart-title").data(f).enter().append("text").attr("class","chart-title").attr("font-weight","bold").attr("fill",function(b,c){return a._measurementColorLabels?a.colors().getByKey(a._measurementColorLabels.get(b)):a.colors().get(c)}).attr("x",0).attr("y",function(a,b){return b*e+10}).text(function(a,b){return a.name()}).attr("transform","translate("+a.margins().left()+", "+a.margins().top()+")")}else{var g=
this._svg.selectAll(".chart-title").data(b).enter().append("text").attr("class","chart-title").attr("font-weight","bold").attr("fill",function(b,c){return a._measurementColorLabels?a.colors().getByKey(a._measurementColorLabels.get(b)):a.colors().get(c)}).attr("y",a.margins().top()-5).text(function(a,b){return a.name()}),m=0,l=[];this._container.find(" .chart-title").each(function(a){l.push(m);m+=this.getBBox().width+15});g.attr("x",function(b,c){return a.margins().left()+10+l[c]});this._svg.selectAll(".chart-title-color").data(b).enter().append("circle").attr("class",
"chart-title-color").attr("cx",function(b,c){return a.margins().left()+4+l[c]}).attr("cy",a.margins().top()-9).attr("r",4).style("shape-rendering","auto").style("stroke-width","0").style("fill",function(b,c){return a._measurementColorLabels?a.colors().getByKey(a._measurementColorLabels.get(b)):a.colors().get(c)})}}};epiviz.plugins.charts.MultiStackedLineTrackType=function(a){epiviz.ui.charts.TrackType.call(this,a)};epiviz.plugins.charts.MultiStackedLineTrackType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.TrackType.prototype);epiviz.plugins.charts.MultiStackedLineTrackType.constructor=epiviz.plugins.charts.MultiStackedLineTrackType;epiviz.plugins.charts.MultiStackedLineTrackType.prototype.createNew=function(a,b,c){return new epiviz.plugins.charts.MultiStackedLineTrack(a,b,c)};
epiviz.plugins.charts.MultiStackedLineTrackType.prototype.typeName=function(){return"epiviz.plugins.charts.MultiStackedLineTrackType"};epiviz.plugins.charts.MultiStackedLineTrackType.prototype.chartName=function(){return"Multi Stacked Line Track"};epiviz.plugins.charts.MultiStackedLineTrackType.prototype.chartHtmlAttributeName=function(){return"MULTI-stacked-lines"};epiviz.plugins.charts.MultiStackedLineTrackType.prototype.isRestrictedToSameDatasourceGroup=function(){return!1};
epiviz.plugins.charts.MultiStackedLineTrackType.prototype.measurementsFilter=function(){return function(a){return a.type()==epiviz.measurements.Measurement.Type.FEATURE}};
epiviz.plugins.charts.MultiStackedLineTrackType.prototype.customSettingsDefs=function(){return epiviz.ui.charts.TrackType.prototype.customSettingsDefs.call(this).concat([new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.STEP,epiviz.ui.charts.CustomSetting.Type.NUMBER,50,"Step"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_POINTS,epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!1,"Show points"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_LINES,
epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!0,"Show lines"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_FILL,epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!0,"fill"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_ERROR_BARS,epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!0,"Show error bars"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.POINT_RADIUS,
epiviz.ui.charts.CustomSetting.Type.NUMBER,1,"Point radius"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.LINE_THICKNESS,epiviz.ui.charts.CustomSetting.Type.NUMBER,1,"Line thickness"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MIN,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Min Y"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MAX,
epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Max Y"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.INTERPOLATION,epiviz.ui.charts.CustomSetting.Type.CATEGORICAL,"linear","Interpolation","linear step-before step-after basis basis-open basis-closed bundle cardinal cardinal-open monotone".split(" ")),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.ABS_LINE_VAL,epiviz.ui.charts.CustomSetting.Type.NUMBER,
epiviz.ui.charts.CustomSetting.DEFAULT,"Draw abline"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_TRACKS,epiviz.ui.charts.CustomSetting.Type.STRING,epiviz.ui.charts.CustomSetting.DEFAULT,"Hide/Show Tracks"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.SHOW_Y_AXIS,epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!0,"Show y-axis"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings.AUTO_SCALE,
epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!0,"Fixed y-axis")])};epiviz.plugins.charts.MultiStackedLineTrackType.CustomSettings={STEP:"step",SHOW_POINTS:"showPoints",SHOW_ERROR_BARS:"showErrorBars",SHOW_LINES:"showLines",POINT_RADIUS:"pointRadius",LINE_THICKNESS:"lineThickness",INTERPOLATION:"interpolation",ABS_LINE_VAL:"abLine",SHOW_FILL:"showFill",SHOW_TRACKS:"showTracks",SHOW_Y_AXIS:"showYAxis",AUTO_SCALE:"autoScale"};epiviz.ui.charts.Plot=function(a,b,c){epiviz.ui.charts.Chart.call(this,a,b,c)};epiviz.ui.charts.Plot.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.Chart.prototype);epiviz.ui.charts.Plot.constructor=epiviz.ui.charts.Plot;epiviz.ui.charts.Plot.prototype.displayType=function(){return epiviz.ui.charts.VisualizationType.DisplayType.PLOT};epiviz.plugins.charts.ScatterPlot=function(a,b,c){epiviz.ui.charts.Plot.call(this,a,b,c);this._legend=this._chartContent=null;this._measurementsX=[];this._measurementsY=[];var d=this;this.measurements().foreach(function(a,b){0==b%2?d._measurementsX.push(a):d._measurementsY.push(a)});this._yLabel=this._xLabel="";for(a=0;a<Math.min(this._measurementsX.length,this._measurementsY.length);++a)0<a&&(this._xLabel+=", ",this._yLabel+=", "),this._xLabel+=this._measurementsX[a].name(),this._yLabel+=this._measurementsY[a].name();
this._colorLabels=[];this._initialize()};epiviz.plugins.charts.ScatterPlot.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.Plot.prototype);epiviz.plugins.charts.ScatterPlot.constructor=epiviz.plugins.charts.ScatterPlot;epiviz.plugins.charts.ScatterPlot.prototype._initialize=function(){epiviz.ui.charts.Plot.prototype._initialize.call(this);this._svg.classed("scatter-plot",!0);this._chartContent=this._svg.append("g").attr("class","chart-content");this._legend=this._svg.append("g").attr("class","chart-legend")};
epiviz.plugins.charts.ScatterPlot.prototype.draw=function(a,b){epiviz.ui.charts.Plot.prototype.draw.call(this,a,b);b=this._lastData;a=this._lastRange;return b&&a?this._drawCircles(a,b):[]};epiviz.plugins.charts.ScatterPlot.prototype.drawCanvas=function(a,b){epiviz.ui.charts.Plot.prototype.draw.call(this,a,b);b=this._lastData;a=this._lastRange;return b&&a?this._drawCirclesCanvas(a,b):[]};
epiviz.plugins.charts.ScatterPlot.prototype._drawCirclesCanvas=function(a,b){var c=this,d=epiviz.ui.charts.Axis,e=Math.max(1,this.customSettingsValues()[epiviz.plugins.charts.ScatterPlotType.CustomSettings.CIRCLE_RADIUS_RATIO]*Math.min(this.width(),this.height())),f=Math.max(Math.floor(e),1),g=Math.min(this._measurementsX.length,this._measurementsY.length),h=this.customSettingsValues()[epiviz.plugins.charts.ScatterPlotType.CustomSettings.ABS_LINE_VAL],m=b.firstSeries().globalStartIndex(),l=b.firstSeries().globalEndIndex();
b.foreach(function(a,b){var c=b.globalStartIndex(),d=b.globalEndIndex();c>m&&(m=c);d<l&&(l=d)});var p=l-m,n=this.margins(),t=this.width(),v=this.height(),q=epiviz.ui.charts.CustomSetting,u=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN],w=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX],r=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.X_MIN],A=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.X_MAX];
r==q.DEFAULT&&(r=this._measurementsX[0].minValue());u==q.DEFAULT&&(u=this._measurementsY[0].minValue());A==q.DEFAULT&&(A=this._measurementsX[0].maxValue());w==q.DEFAULT&&(w=this._measurementsY[0].maxValue());var B=epiviz.measurements.Measurement.Type.isOrdered(this._measurementsX[0].type()),q=d3.scale.linear().domain([r,A]).range([0,t-n.sumAxis(d.X)]),z=d3.scale.linear().domain([u,w]).range([v-n.sumAxis(d.Y),0]);this._container.find("svg").remove();this._container.find("#"+this.id()+"-canvas").remove();
var x=document.createElement("canvas");this.chartDrawType="canvas";this.canvas=x;x.id=this.id()+"-canvas";this._container.append(x);x.width=t;x.height=v;this._container.find("#"+this.id()+"-hoverCanvas").remove();var E=document.createElement("canvas");this.hoverCanvas=E;E.id=this.id()+"-hoverCanvas";this._container.append(E);E.width=this.width();E.height=this.height();E.style="position:absolute;top:0;left:0;width:100%;height:100%;z-index:1";this._drawAxesCanvas(q,z,15,15,x);var G=x.getContext("2d");
this.renderQueue=renderQueue(function(a){G.beginPath();G.arc(n.left()+(a.values[0]-r)*(t-n.sumAxis(d.X))/(A-r),v-n.bottom()-(a.values[1]-u)*(v-n.sumAxis(d.Y))/(w-u),e,0,2*Math.PI);a=c.colors().get(a.seriesIndex);G.strokeStyle=a;G.stroke();G.fillStyle=a;G.fill()});var F,y,I=[];for(F=0;F<p;++F){y=F+m;var C=b.getSeries(this._measurementsX[0]).getRowByGlobalIndex(y);if(C&&(!B||void 0==a.start()||void 0==a.end()||C.start()<a.end()&&C.end()>a.start()))for(C=0;C<g;++C)I.push(C*p+F)}C={};g=[];B=1;for(F=0;F<
I.length;++F){y=I[F]%p;var H=y+m;y=Math.floor(I[F]/p);var D=c._measurementsX[y],L=c._measurementsY[y],K=b.getSeries(D).getByGlobalIndex(H),H=b.getSeries(L).getByGlobalIndex(H);if(K&&H){var M=sprintf("item data-series-%s",y),N=q(K.value),O=z(H.value),N=Math.floor(N/f)*f,O=Math.floor(O/f)*f,J=null;C[O]&&C[O][N]?(J=C[O][N],J.id+="_"+K.globalIndex,J.start=Math.min(J.start,K.rowItem.start()),J.end=Math.max(J.end,K.rowItem.end()),J.values[0]=(J.values[0]*J.valueItems[0].length+K.value)/(J.valueItems[0].length+
1),J.values[1]=(J.values[1]*J.valueItems[1].length+H.value)/(J.valueItems[1].length+1),J.valueItems[0].push(K),J.valueItems[1].push(H),J.valueItems[0].length>B&&(B=J.valueItems[0].length)):(J=new epiviz.ui.charts.ChartObject(sprintf("scatter_%s_%s",y,K.globalIndex),K.rowItem.start(),K.rowItem.end(),[K.value,H.value],y,[[K],[H]],[D,L],M,K.rowItem.seqName()),C[O]||(C[O]={}),C[O][N]=J,g.push(J))}}f=this._chartContent.select(".items");f.empty()&&(f=this._chartContent.append("g").attr("class","items"),
p=f.append("g").attr("class","selected"),f.append("g").attr("class","hovered"),p.append("g").attr("class","hovered"));G.globalAlpha=.7;c.renderQueue(g);if(this._globalIndexColorLabels){f={};for(C=m;C<l;++C)f[this._globalIndexColorLabels[C]]=this._globalIndexColorLabels[C];this._colorLabels=Object.keys(f);var R=0;this._colorLabels.forEach(function(a,b){var d=c.colors().getByKey(a);G.strokeStyle=d;G.fillStyle=d;G.beginPath();G.arc(c.margins().left()+R-2,-9,4,0,2*Math.PI);G.stroke();G.fill();G.font=
"9px";G.beginPath();G.textAlign="start";G.fillText(a,c.margins().left()+R+8,c.margins().top()-5);d=G.measureText(a).width;R=R+8+d+10})}else{f=Math.min(this._measurementsX.length,this._measurementsY.length);p=Array(f);for(C=0;C<f;++C)p[C]=sprintf("%s vs %s",this._measurementsX[C].name(),this._measurementsY[C].name());this._colorLabels=p}h!=epiviz.ui.charts.CustomSetting.DEFAULT&&(G.beginPath(),G.globalAlpha=.7,G.moveTo(n.left(),z(h)),G.lineTo(n.left()+(A-r)*(t-n.sumAxis(d.X))/(A-r),z(h)),G.strokeStyle=
black,G.stroke());this.addCanvasEvents(x,E,g,q,z,n,r,A,u,w,e);return g};
epiviz.plugins.charts.ScatterPlot.prototype._drawCircles=function(a,b){var c=this,d=epiviz.ui.charts.Axis,e=Math.max(1,this.customSettingsValues()[epiviz.plugins.charts.ScatterPlotType.CustomSettings.CIRCLE_RADIUS_RATIO]*Math.min(this.width(),this.height())),f=Math.max(Math.floor(e),1),g=Math.min(this._measurementsX.length,this._measurementsY.length),h=this.customSettingsValues()[epiviz.plugins.charts.ScatterPlotType.CustomSettings.ABS_LINE_VAL],m=b.firstSeries().globalStartIndex(),l=b.firstSeries().globalEndIndex();
b.foreach(function(a,b){var c=b.globalStartIndex(),d=b.globalEndIndex();c>m&&(m=c);d<l&&(l=d)});var p=l-m,n=this.margins(),t=this.width(),v=this.height(),q=epiviz.ui.charts.CustomSetting,u=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN],w=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX],r=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.X_MIN],A=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.X_MAX];
r==q.DEFAULT&&(r=this._measurementsX[0].minValue());u==q.DEFAULT&&(u=this._measurementsY[0].minValue());A==q.DEFAULT&&(A=this._measurementsX[0].maxValue());w==q.DEFAULT&&(w=this._measurementsY[0].maxValue());var B=epiviz.measurements.Measurement.Type.isOrdered(this._measurementsX[0].type()),q=d3.scale.linear().domain([r,A]).range([0,t-n.sumAxis(d.X)]),z=d3.scale.linear().domain([u,w]).range([v-n.sumAxis(d.Y),0]);this._clearAxes(this._chartContent);this._drawAxes(q,z,15,15,this._chartContent);var x,
E,G=[];for(x=0;x<p;++x){E=x+m;var F=b.getSeries(this._measurementsX[0]).getRowByGlobalIndex(E);if(F&&(!B||void 0==a.start()||void 0==a.end()||F.start()<a.end()&&F.end()>a.start()))for(F=0;F<g;++F)G.push(F*p+x)}var F={},g=[],y=1;for(x=0;x<G.length;++x){E=G[x]%p;var I=E+m,B=Math.floor(G[x]/p);E=c._measurementsX[B];var C=c._measurementsY[B],H=b.getSeries(E).getByGlobalIndex(I),I=b.getSeries(C).getByGlobalIndex(I);if(H&&I){var D=sprintf("item data-series-%s",B),L=q(H.value),K=z(I.value),L=Math.floor(L/
f)*f,K=Math.floor(K/f)*f,M=null;F[K]&&F[K][L]?(M=F[K][L],M.id+="_"+H.globalIndex,M.start=Math.min(M.start,H.rowItem.start()),M.end=Math.max(M.end,H.rowItem.end()),M.values[0]=(M.values[0]*M.valueItems[0].length+H.value)/(M.valueItems[0].length+1),M.values[1]=(M.values[1]*M.valueItems[1].length+I.value)/(M.valueItems[1].length+1),M.valueItems[0].push(H),M.valueItems[1].push(I),M.valueItems[0].length>y&&(y=M.valueItems[0].length)):(M=new epiviz.ui.charts.ChartObject(sprintf("scatter_%s_%s",B,H.globalIndex),
H.rowItem.start(),H.rowItem.end(),[H.value,I.value],B,[[H],[I]],[E,C],D,H.rowItem.seqName()),F[K]||(F[K]={}),F[K][L]=M,g.push(M))}}f=this._chartContent.select(".items");f.empty()&&(f=this._chartContent.append("g").attr("class","items"),p=f.append("g").attr("class","selected"),f.append("g").attr("class","hovered"),p.append("g").attr("class","hovered"));p=f.selectAll("circle").data(g,function(a){return a.id});p.enter().insert("circle",":first-child").attr("id",function(a){return sprintf("%s-item-%s-%s",
c.id(),a.seriesIndex,a.valueItems[0][0].globalIndex)}).style("opacity",0).style("fill-opacity",0).attr("r",0);p.each(function(a){var b=d3.select(this),e;e=c._globalIndexColorLabels?c.colors().getByKey(c._globalIndexColorLabels[a.valueItems[0][0].globalIndex]):c.colors().get(a.seriesIndex);b.attr("cx",n.left()+(a.values[0]-r)*(t-n.sumAxis(d.X))/(A-r)).attr("cy",v-n.bottom()-(a.values[1]-u)*(v-n.sumAxis(d.Y))/(w-u)).attr("class",a.cssClasses).style("fill",e)});p.transition().duration(1E3).style("fill-opacity",
function(a){return Math.max(.6,a.valueItems[0].length/y)}).style("opacity",null).attr("r",e);p.exit().transition().duration(1E3).style("opacity",0).attr("r",0).remove();p.on("mouseover",function(a){c._hover.notify(new epiviz.ui.charts.VisEventArgs(c.id(),a))}).on("mouseout",function(){c._unhover.notify(new epiviz.ui.charts.VisEventArgs(c.id()))}).on("click",function(a){c._deselect.notify(new epiviz.ui.charts.VisEventArgs(c.id()));c._select.notify(new epiviz.ui.charts.VisEventArgs(c.id(),a));d3.event.stopPropagation()});
if(this._globalIndexColorLabels){e={};for(F=m;F<l;++F)e[this._globalIndexColorLabels[F]]=this._globalIndexColorLabels[F];this._colorLabels=Object.keys(e);this._svg.selectAll(".chart-title").remove();this._svg.selectAll(".chart-title-color ").remove();e=this._svg.selectAll(".chart-title").data(this._colorLabels);e.enter().append("text").attr("class","chart-title").attr("font-weight","bold").attr("y",c.margins().top()-5);e.attr("fill",function(a,b){return c.colors().getByKey(a)}).text(function(a){return a});
var N=0,O=[];this._container.find(" .chart-title").each(function(a){O.push(N);N+=this.getBBox().width+15});e.attr("x",function(a,b){return c.margins().left()+10+O[b]});this._svg.selectAll(".chart-title-color").data(this._colorLabels).enter().append("circle").attr("class","chart-title-color").attr("cx",function(a,b){return c.margins().left()+4+O[b]}).attr("cy",c.margins().top()-9).attr("r",4).style("shape-rendering","auto").style("stroke-width","0").attr("fill",function(a,b){return c.colors().getByKey(a)}).style("stroke-width",
0)}else{e=Math.min(this._measurementsX.length,this._measurementsY.length);p=Array(e);for(F=0;F<e;++F)p[F]=sprintf("%s vs %s",this._measurementsX[F].name(),this._measurementsY[F].name());this._colorLabels=p}h!=epiviz.ui.charts.CustomSetting.DEFAULT&&(f.selectAll(".abLine").remove(),f.append("svg:line").attr("class","abLine").attr("x1",n.left()+(r-r)*(t-n.sumAxis(d.X))/(A-r)).attr("x2",n.left()+(A-r)*(t-n.sumAxis(d.X))/(A-r)).attr("y1",v-n.bottom()-(h-u)*(v-n.sumAxis(d.Y))/(w-u)).attr("y2",v-n.bottom()-
(h-u)*(v-n.sumAxis(d.Y))/(w-u)).style("stroke","black").style("stroke-dasharray","5, 5"));return g};epiviz.plugins.charts.ScatterPlot.prototype.colorLabels=function(){return this._colorLabels};
epiviz.plugins.charts.ScatterPlot.prototype._drawAxes=function(a,b,c,d,e,f,g,h){epiviz.ui.charts.Plot.prototype._drawAxes.call(this,a,b,c,d,e,f,g,h);this._legend.selectAll("text").remove();a=this._measurementsX;var m=this;this._legend.selectAll(".x-measurement").remove();this._legend.selectAll(".x-measurement-color").remove();b=this._legend.selectAll(".x-measurement").data(a).enter().append("text").attr("class","x-measurement").attr("font-weight","bold").attr("fill",function(a,b){return m._globalIndexColorLabels?
"#000000":m.colors().get(b)}).attr("y",this.height()-this.margins().bottom()+35).text(function(a,b){return a.name()});var l=0,p=[];this._container.find(" .x-measurement").each(function(a){p.push(l);l+=this.getBBox().width+15});b.attr("x",function(a,b){return.5*(m.width()-l)+7+p[b]});this._legend.selectAll(".x-measurement-color").data(a).enter().append("circle").attr("class","x-measurement-color").attr("cx",function(a,b){return.5*(m.width()-l)+1+p[b]}).attr("cy",this.height()-this.margins().bottom()+
31).attr("r",4).style("shape-rendering","auto").style("stroke-width","0").style("fill",function(a,b){return m._globalIndexColorLabels?"#ffffff":m.colors().get(b)});a=this._measurementsY;this._legend.selectAll(".y-measurement").remove();this._legend.selectAll(".y-measurement-color").remove();b=this._legend.selectAll(".y-measurement").data(a).enter().append("text").attr("class","y-measurement").attr("font-weight","bold").attr("fill",function(a,b){return m._globalIndexColorLabels?"#000000":m.colors().get(b)}).attr("y",
this.margins().left()-35).attr("transform","rotate(-90)").text(function(a,b){return a.name()});var n=0,t=[];this._container.find(" .y-measurement").each(function(a){t.push(n);n+=this.getBBox().width+15});b.attr("x",function(a,b){return-m.height()+.5*(m.height()-n)+12+m.margins().top()+t[b]});this._legend.selectAll(".y-measurement-color").data(a).enter().append("circle").attr("class","y-measurement-color").attr("cx",function(a,b){return-m.height()+.5*(m.height()-n)+6+m.margins().top()+t[b]}).attr("cy",
this.margins().left()-39).attr("transform","rotate(-90)").attr("r",4).style("shape-rendering","auto").style("stroke-width","0").style("fill",function(a,b){return m._globalIndexColorLabels?"#ffffff":m.colors().get(b)})};
epiviz.plugins.charts.ScatterPlot.prototype._drawAxesCanvas=function(a,b,c,d,e,f,g,h){epiviz.ui.charts.Plot.prototype._drawAxesCanvas.call(this,a,b,c,d,e,f,g,h);var m=this;a=this._measurementsX;var l=e.getContext("2d"),p=0;a.forEach(function(a,b){var c=m._globalIndexColorLabels?"#000000":m.colors().get(b);l.strokeStyle=c;l.fillStyle=c;l.beginPath();l.arc(m.margins().left()+p-2,m.height()-m.margins().bottom()+35,4,0,2*Math.PI);l.stroke();l.fill();l.font="9px";l.beginPath();l.textAlign="start";l.fillText(a.name(),
m.margins().left()+p+8,m.height()-m.margins().bottom()+35);c=l.measureText(a.name()).width;p=p+8+c+10});e=this._measurementsY;p=0;e.forEach(function(a,b){l.save();l.translate(15,m.height()-m.margins().bottom()-p);l.rotate(-Math.PI/2);var c=m._globalIndexColorLabels?"#000000":m.colors().get(b);l.strokeStyle=c;l.fillStyle=c;l.beginPath();l.arc(0,0,4,0,2*Math.PI);l.stroke();l.fill();l.font="9px";l.beginPath();l.textAlign="start";l.fillText(a.name(),10,0);l.restore();c=l.measureText(a.name()).width;p=
p+8+c+10})};
epiviz.plugins.charts.ScatterPlot.prototype.addCanvasEvents=function(a,b,c,d,e,f,g,h,m,l,p){var n=this,t=b.getContext("2d");n.hoverCanvasObjects=c;var v=n.width(),q=n.height(),u=epiviz.ui.charts.Axis;b.addEventListener("click",function(a){});b.addEventListener("mousemove",function(a){b.getBoundingClientRect();var d=a.offsetX;a=a.offsetY;var e=(d-p-f.left())*(h-g)/(v-f.sumAxis(u.X))+g,t=(-a-p+(q-f.bottom()))*(l-m)/(q-f.sumAxis(u.Y))+m,w=(d+p-f.left())*(h-g)/(v-f.sumAxis(u.X))+g,x=(-a+p+(q-f.bottom()))*
(l-m)/(q-f.sumAxis(u.Y))+m,E=null;c&&c.forEach(function(a){Math.floor(a.values[0])<=Math.floor(w)&&Math.floor(a.values[0])>=Math.floor(e)&&Math.floor(a.values[1])<=Math.floor(x)&&Math.floor(a.values[1])>=Math.floor(t)&&(E=a)});E&&n._hover.notify(new epiviz.ui.charts.VisEventArgs(n.id(),E))});b.addEventListener("mouseout",function(a){n._canvasHoverObject=null;t.clearRect(0,0,b.width,b.height);n._unhover.notify(new epiviz.ui.charts.VisEventArgs(n.id()))})};
epiviz.plugins.charts.ScatterPlot.prototype.doHover=function(a){epiviz.ui.charts.Plot.prototype.doHover.call(this,a);var b=this;if("canvas"==this.chartDrawType){var c=this.hoverCanvas.getContext("2d");c.clearRect(0,0,this.hoverCanvas.width,this.hoverCanvas.height);var d=epiviz.ui.charts.CustomSetting,e=Math.max(1,this.customSettingsValues()[epiviz.plugins.charts.ScatterPlotType.CustomSettings.CIRCLE_RADIUS_RATIO]*Math.min(this.width(),this.height())),f=epiviz.ui.charts.Axis,g=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN],
h=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX],m=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.X_MIN],l=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.X_MAX];m==d.DEFAULT&&(m=this._measurementsX[0].minValue());g==d.DEFAULT&&(g=this._measurementsY[0].minValue());l==d.DEFAULT&&(l=this._measurementsX[0].maxValue());h==d.DEFAULT&&(h=this._measurementsY[0].maxValue());this._canvasHoverObject=a;this.hoverCanvasObjects.forEach(function(d){d.overlapsWith(a)&&
(c.beginPath(),c.arc(b.margins().left()+(d.values[0]-m)*(b.width()-b.margins().sumAxis(f.X))/(l-m),b.height()-b.margins().bottom()-(d.values[1]-g)*(b.height()-b.margins().sumAxis(f.Y))/(h-g),e,0,2*Math.PI),b.colors().get(d.seriesIndex),c.strokeStyle="yellow",c.stroke())})}else{var p=this._container.find(".items"),d=p.find("> .hovered"),n=p.find("> .selected"),t=n.find("> .hovered"),v=function(){if(Array.isArray(a)){for(var b=!1,c=0;c<a.length;c++)a[c].overlapsWith(d3.select(this).data()[0])&&(b=!0);
return b}return a.overlapsWith(d3.select(this).data()[0])},p=p.find("> .item").filter(v);d.append(p);p=n.find("> .item").filter(v);t.append(p)}};epiviz.ui.charts.PlotType=function(a){epiviz.ui.charts.ChartType.call(this,a)};epiviz.ui.charts.PlotType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.ChartType.prototype);epiviz.ui.charts.PlotType.constructor=epiviz.ui.charts.PlotType;epiviz.ui.charts.PlotType.prototype.chartDisplayType=function(){return epiviz.ui.charts.VisualizationType.DisplayType.PLOT};epiviz.ui.charts.PlotType.prototype.cssClass=function(){return"plot-container ui-widget-content"};epiviz.plugins.charts.ScatterPlotType=function(a){epiviz.ui.charts.PlotType.call(this,a)};epiviz.plugins.charts.ScatterPlotType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.PlotType.prototype);epiviz.plugins.charts.ScatterPlotType.constructor=epiviz.plugins.charts.ScatterPlotType;epiviz.plugins.charts.ScatterPlotType.prototype.createNew=function(a,b,c){return new epiviz.plugins.charts.ScatterPlot(a,b,c)};epiviz.plugins.charts.ScatterPlotType.prototype.typeName=function(){return"epiviz.plugins.charts.ScatterPlot"};
epiviz.plugins.charts.ScatterPlotType.prototype.chartName=function(){return"Scatter Plot"};epiviz.plugins.charts.ScatterPlotType.prototype.chartHtmlAttributeName=function(){return"scatter"};epiviz.plugins.charts.ScatterPlotType.prototype.measurementsFilter=function(){return function(a){return epiviz.measurements.Measurement.Type.hasValues(a.type())}};epiviz.plugins.charts.ScatterPlotType.prototype.isRestrictedToSameDatasourceGroup=function(){return!0};
epiviz.plugins.charts.ScatterPlotType.prototype.minSelectedMeasurements=function(){return 2};
epiviz.plugins.charts.ScatterPlotType.prototype.customSettingsDefs=function(){return epiviz.ui.charts.PlotType.prototype.customSettingsDefs.call(this).concat([new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.ScatterPlotType.CustomSettings.CIRCLE_RADIUS_RATIO,epiviz.ui.charts.CustomSetting.Type.NUMBER,.015,"Circle radius ratio"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.X_MIN,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,
"Min X"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.X_MAX,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Max X"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MIN,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Min Y"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MAX,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,
"Max Y"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.ScatterPlotType.CustomSettings.ABS_LINE_VAL,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Draw abline")])};epiviz.plugins.charts.ScatterPlotType.CustomSettings={CIRCLE_RADIUS_RATIO:"circleRadiusRatio",ABS_LINE_VAL:"abLine"};epiviz.plugins.charts.GenesTrack=function(a,b,c){epiviz.ui.charts.Track.call(this,a,b,c);this._initialize()};epiviz.plugins.charts.GenesTrack.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.Track.prototype);epiviz.plugins.charts.GenesTrack.constructor=epiviz.plugins.charts.GenesTrack;epiviz.plugins.charts.GenesTrack.prototype._initialize=function(){epiviz.ui.charts.Track.prototype._initialize.call(this);this._svg.classed("genes-track",!0)};
epiviz.plugins.charts.GenesTrack.prototype.draw=function(a,b,c,d){epiviz.ui.charts.Track.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;if(!b||!a)return[];c=c||this._slide;d=d||this._zoom;this._slide=0;this._zoom=1;return this._drawGenes(a,b,c||0,d||1)};
epiviz.plugins.charts.GenesTrack.prototype.drawCanvas=function(a,b,c,d){epiviz.ui.charts.Track.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;if(!b||!a)return[];c=c||this._slide;d=d||this._zoom;this._slide=0;this._zoom=1;return this._drawGenesCanvas(a,b,c||0,d||1)};
epiviz.plugins.charts.GenesTrack.prototype._drawGenes=function(a,b,c,d){var e=epiviz.ui.charts.Axis,f=a.start(),g=a.end(),h=this.width(),m=this.height(),l=this.margins(),p=d3.scale.linear().domain([f,g]).range([0,h-l.sumAxis(e.X)]),n=c*(h-l.sumAxis(e.X))/(g-f);this._clearAxes();this._drawAxes(p,null,10,5);var t=this,v=b.firstSeries();b=epiviz.utils.range(v.size()).map(function(a){a=v.get(a);var b=a.rowItem,c=sprintf("item gene-%s",b.metadata("gene"));return new epiviz.ui.charts.ChartObject(b.metadata("gene"),
b.start(),b.end(),null,0,[[a]],[v.measurement()],c,b.seqName())});d&&(this._svg.select(".items").remove(),this._svg.select("defs").select("#clip-"+this.id()).remove());d=this._svg.select(".items");c=d.select(".selected");d.empty()&&(this._svg.select("defs").append("clipPath").attr("id","clip-"+this.id()).append("rect").attr("x",0).attr("y",0).attr("width",h-l.sumAxis(e.X)).attr("height",m-l.sumAxis(e.Y)),d=this._svg.append("g").attr("class","items").attr("transform","translate("+l.left()+", "+l.top()+
")").attr("id",this.id()+"-gene-content").attr("clip-path","url(#clip-"+this.id()+")"),c=d.append("g").attr("class","selected"),d.append("g").attr("class","hovered"),c.append("g").attr("class","hovered"));var q=d.selectAll(".item").data(b,function(a){return a.id});q.enter().insert("g",":first-child").on("mouseout",function(){t._unhover.notify(new epiviz.ui.charts.VisEventArgs(t.id()))}).on("mouseover",function(a){t._hover.notify(new epiviz.ui.charts.VisEventArgs(t.id(),a))}).on("click",function(a){t._deselect.notify(new epiviz.ui.charts.VisEventArgs(t.id()));
t._select.notify(new epiviz.ui.charts.VisEventArgs(t.id(),a));d3.event.stopPropagation()}).attr("transform","translate("+n+", 0) scale(1, 1)").each(function(a){t._drawGene(this,a,p)});var u=0,w=f,r=g,m=d3.behavior.drag().on("drag",function(a){console.log(d3.event.dx);u+=d3.event.dx;0!=d3.event.dx&&(a=Math.round(Math.abs(p.invert(0)-p.invert(Math.abs(u)))),0<u?(w=f-a,r=g-a):0>u&&(w=f+a,r=g+a),a=d3.scale.linear().domain([w,r]).range([0,h-l.sumAxis(e.X)]),q.each(function(a){d3.select(this).attr("transform",
function(a,b){return"translate("+[u,0]+")"})}),t._clearAxes(),t._drawAxes(a,null,10,5))}).on("dragend",function(b){b=new epiviz.datatypes.GenomicRange(a.seqName(),w,r-w,a.genome());t._propagateNavigationChanges.notify({id:t.id(),range:b})});t._svg.call(m);n&&q.each(function(a){t._translateGene(this,a,n)});q.exit().transition().duration(500).style("opacity",0).remove();return b};
epiviz.plugins.charts.GenesTrack.prototype._drawGenesCanvas=function(a,b,c,d){c=epiviz.ui.charts.Axis;var e=a.start(),f=a.end();a=this.width();var g=this.height(),h=this.margins(),m=d3.scale.linear().domain([e,f]).range([0,a-h.sumAxis(c.X)]);h.sumAxis(c.X);this._container.find("svg").remove();this._container.find("#"+this.id()+"-canvas").remove();var l=document.createElement("canvas");this.chartDrawType="canvas";this.canvas=l;l.id=this.id()+"-canvas";this._container.append(l);l.width=this.width();
l.height=this.height();l.style="position:absolute;top:0;left:0;width:100%;height:100%";this._container.find("#"+this.id()+"-hoverCanvas").remove();this.hoverCanvas=e=document.createElement("canvas");e.id=this.id()+"-hoverCanvas";this._container.append(e);e.width=this.width();e.height=this.height();e.style="position:absolute;top:0;left:0;width:100%;height:100%;z-index:1";this._drawAxesCanvas(m,null,10,5,l);var p=this,n=b.firstSeries();b=epiviz.utils.range(n.size()).map(function(a){a=n.get(a);var b=
a.rowItem,c=sprintf("item gene-%s",b.metadata("gene"));return new epiviz.ui.charts.ChartObject(b.metadata("gene"),b.start(),b.end(),null,0,[[a]],[n.measurement()],c,b.seqName())});d&&(this._svg.select(".items").remove(),this._svg.select("defs").select("#clip-"+this.id()).remove());d=this._svg.select(".items");f=d.select(".selected");d.empty()&&(this._svg.select("defs").append("clipPath").attr("id","clip-"+this.id()).append("rect").attr("x",0).attr("y",0).attr("width",a-h.sumAxis(c.X)).attr("height",
g-h.sumAxis(c.Y)),d=this._svg.append("g").attr("class","items").attr("transform","translate("+h.left()+", "+h.top()+")").attr("id",this.id()+"-gene-content").attr("clip-path","url(#clip-"+this.id()+")"),f=d.append("g").attr("class","selected"),d.append("g").attr("class","hovered"),f.append("g").attr("class","hovered"));l.getContext("2d").translate(h.left(),h.top());e.getContext("2d").translate(h.left(),0);b.forEach(function(a){p._drawGeneCanvas(this,a,m,l)});this.addCanvasEvents(l,e,b,m);this._drawLegend();
return b};epiviz.plugins.charts.GenesTrack.prototype._translateGene=function(a,b,c){a=d3.select(a);b=a.attr("transform");var d=RegExp("translate\\([\\d\\.\\-]+[\\,\\s]+[\\d\\.\\-]+\\)","g"),e=RegExp("[\\d\\.\\-]+","g"),f=b.match(d)[0],e=parseFloat(f.match(e)[0]);b=b.replace(d,"translate("+(e-c)+", 0)");a.transition().duration(500).attr("transform",b)};
epiviz.plugins.charts.GenesTrack.prototype._drawGene=function(a,b,c){var d=epiviz.ui.charts.Axis,e=this,f=b.valueItems[0][0].rowItem,g=c(b.start),h=c(b.end),m="+"==f.strand()?1:-1,l=-m*(this.height()-this.margins().sumAxis(d.Y))*.25,p=f.metadata("exon_starts").split(",").map(function(a){return parseInt(a)}),n=f.metadata("exon_ends").split(",").map(function(a){return parseInt(a)}),t=d3.range(0,p.length),v=.08*this.height(),q=.16*this.height(),u=v*Math.sqrt(3)*.5;a=d3.select(a);a.attr("class",b.cssClasses);
a.append("polygon").attr("class","gene-body").style("fill",this.colors().get(0)).attr("points",function(){var a,b;a=.5*(e.height()-e.margins().sumAxis(d.Y)-v)+l;b=[a,a,a+.5*v,a+v,a+v];a="+"==f.strand()?[g,h,h+u,h,g]:[h,g,g-u,g,h];return sprintf("%s,%s %s,%s %s,%s %s,%s %s,%s",a[0],b[0],a[1],b[1],a[2],b[2],a[3],b[3],a[4],b[4])});a.append("g").attr("class","exons").style("fill",this.colors().get(1)).selectAll("rect").data(t).enter().append("rect").attr("x",function(a){return c(p[a])}).attr("y",.5*(e.height()-
q-e.margins().sumAxis(d.Y))+l).attr("width",function(a){return c(n[a])-c(p[a])}).attr("height",q);a.append("text").attr("class","gene-name").attr("x",g+2).attr("y",.5*(e.height()-e.margins().sumAxis(d.Y))+l-m*(v+2)).style("dominant-baseline","central").text(b.id)};
epiviz.plugins.charts.GenesTrack.prototype._drawGeneCanvas=function(a,b,c,d){var e=epiviz.ui.charts.Axis,f=this;a=b.valueItems[0][0].rowItem;var g=c(b.start);b=c(b.end);var h=-("+"==a.strand()?1:-1)*(this.height()-this.margins().sumAxis(e.Y))*.25,m=a.metadata("exon_starts").split(",").map(function(a){return parseInt(a)}),l=a.metadata("exon_ends").split(",").map(function(a){return parseInt(a)}),p=d3.range(0,m.length),n=.08*this.height(),t=.16*this.height(),v=n*Math.sqrt(3)*.5,q=null,q=.5*(f.height()-
f.margins().sumAxis(e.Y)-n)+h,n=[q,q,q+.5*n,q+n,q+n],q="+"==a.strand()?[g,b,b+v,b,g]:[b,g,g-v,g,b],u=d.getContext("2d");u.beginPath();u.strokeStyle="black";u.moveTo(q[0],n[0]);u.lineTo(q[1],n[1]);u.lineTo(q[2],n[2]);u.lineTo(q[3],n[3]);u.lineTo(q[4],n[4]);u.lineTo(q[0],n[0]);u.fillStyle=this.colors().get(0);u.fill();u.lineWidth=1;u.stroke();u.beginPath();p.forEach(function(a,b){u.fillStyle=f.colors().get(1);u.rect(c(m[b]),.5*(f.height()-t-f.margins().sumAxis(e.Y))+h,c(l[b])-c(m[b]),t);u.fill()})};
epiviz.plugins.charts.GenesTrack.prototype.colorLabels=function(){return["Genes","Exons"]};
epiviz.plugins.charts.GenesTrack.prototype.doHover=function(a){epiviz.ui.charts.Track.prototype.doHover.call(this,a);var b=this;if("canvas"==b.chartDrawType){var c=epiviz.ui.charts.Axis,d=d3.scale.linear().domain([this._lastRange.start(),this._lastRange.end()]).range([0,this.width()-b.margins().sumAxis(c.X)]);b.hoverCanvasObjects&&b.hoverCanvasObjects.forEach(function(e){if(e.overlapsWith(a)){var f=d(e.start),g="+"==e.valueItems[0][0].rowItem.strand()?1:-1,h=-g*(b.height()-b.margins().sumAxis(c.Y))*
.25,m=.08*b.height(),l=b.hoverCanvas.getContext("2d");l.globalAlpha=.8;l.strokeStyle="black";l.fillStyle="black";l.lineWidth=4;l.stroke();l.fillText(e.id,f+2,.5*(b.height()-b.margins().sumAxis(c.Y))+h-g*(m+2))}})}};epiviz.plugins.charts.GenesTrackType=function(a){epiviz.ui.charts.TrackType.call(this,a)};epiviz.plugins.charts.GenesTrackType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.TrackType.prototype);epiviz.plugins.charts.GenesTrackType.constructor=epiviz.plugins.charts.GenesTrackType;epiviz.plugins.charts.GenesTrackType.prototype.createNew=function(a,b,c){return new epiviz.plugins.charts.GenesTrack(a,b,c)};epiviz.plugins.charts.GenesTrackType.prototype.typeName=function(){return"epiviz.plugins.charts.GenesTrack"};
epiviz.plugins.charts.GenesTrackType.prototype.chartName=function(){return"Genes Track"};epiviz.plugins.charts.GenesTrackType.prototype.chartHtmlAttributeName=function(){return"genes"};epiviz.plugins.charts.GenesTrackType.prototype.isRestrictedToRangeMeasurements=function(){return!0};epiviz.plugins.charts.GenesTrackType.prototype.measurementsFilter=function(){return function(a){return a.type()==epiviz.measurements.Measurement.Type.RANGE}};epiviz.ui.charts.transform={};epiviz.ui.charts.transform.clustering={};epiviz.ui.charts.transform.clustering.ClusterSubtree=function(a,b){this._children=a;this._weight=null;this._distance=b;this._sorted=!1};epiviz.ui.charts.transform.clustering.ClusterSubtree.prototype.weight=function(){if(void 0==this._weight){for(var a=0,b=0;b<this._children.length;++b)a+=this._children[b].weight();this._weight=a}return this._weight};epiviz.ui.charts.transform.clustering.ClusterSubtree.prototype.children=function(){return this._children};
epiviz.ui.charts.transform.clustering.ClusterSubtree.prototype.data=function(){for(var a=[],b=0;b<this._children.length;++b)a=a.concat(this._children[b].data());return a};epiviz.ui.charts.transform.clustering.ClusterSubtree.prototype.sort=function(a){if(!this.sorted()&&(this._children.sort(function(a,b){return a.weight()-b.weight()}),a)){for(var b=0;b<this._children.length;++b)this._children[b].sort(a);this._sorted=!0}};
epiviz.ui.charts.transform.clustering.ClusterSubtree.prototype.copy=function(){for(var a=[],b=0;b<this._children.length;++b)a.push(this._children[b].copy());return new epiviz.ui.charts.transform.clustering.ClusterSubtree(a)};epiviz.ui.charts.transform.clustering.ClusterSubtree.prototype.distance=function(){return this._distance};epiviz.ui.charts.transform.clustering.ClusterSubtree.prototype.sorted=function(){return this._sorted};epiviz.ui.charts.transform.clustering.ClusterLeaf=function(a){this._dataIndex=a};epiviz.ui.charts.transform.clustering.ClusterLeaf.prototype.weight=function(){return 1};epiviz.ui.charts.transform.clustering.ClusterLeaf.prototype.children=function(){return[]};epiviz.ui.charts.transform.clustering.ClusterLeaf.prototype.data=function(){return[this._dataIndex]};epiviz.ui.charts.transform.clustering.ClusterLeaf.prototype.sort=function(){};
epiviz.ui.charts.transform.clustering.ClusterLeaf.prototype.copy=function(){return new epiviz.ui.charts.transform.clustering.ClusterLeaf(this._dataIndex)};epiviz.ui.charts.transform.clustering.ClusterLeaf.prototype.distance=function(){return 0};epiviz.ui.charts.transform.clustering.ClusterLeaf.prototype.sorted=function(){return!0};epiviz.ui.charts.transform.clustering.ClusterTree=function(a,b){this._root=a;this._data=b};epiviz.ui.charts.transform.clustering.ClusterTree.prototype.root=function(){return this._root};epiviz.ui.charts.transform.clustering.ClusterTree.prototype.orderedData=function(){this._root.sorted()||this._root.sort(!0);for(var a=this._root.data(),b=[],c=0;c<a.length;++c)b.push(this._data[a[c]]);return b};epiviz.ui.charts.transform.clustering.NoneClustering=function(){};epiviz.ui.charts.transform.clustering.NoneClustering.prototype.cluster=function(a,b,c){b=[];for(c=0;c<a.length;++c)b.push(new epiviz.ui.charts.transform.clustering.ClusterLeaf(c));b=new epiviz.ui.charts.transform.clustering.ClusterSubtree(b,0);return new epiviz.ui.charts.transform.clustering.ClusterTree(b,a)};epiviz.ui.charts.transform.clustering.NoneClustering.prototype.id=function(){return"none"};epiviz.ui.charts.transform.clustering.AgglomerativeClustering=function(){};
epiviz.ui.charts.transform.clustering.AgglomerativeClustering.prototype.cluster=function(a,b,c){var d,e,f=Array(a.length);for(d=0;d<a.length;++d)for(f[d]=Array(a.length),e=d+1;e<a.length;++e)f[d][e]=b.distance(a[d],a[e]);b=[];for(d=0;d<a.length;++d)b.push(new epiviz.ui.charts.transform.clustering.ClusterLeaf(d));for(;1<b.length;){e=epiviz.utils.indexOfMin(f,!0);d=e.index;e=new epiviz.ui.charts.transform.clustering.ClusterSubtree([b[d[0]],b[d[1]]],e.min);if(d[0]<d[1]){var g=d[0];d[0]=d[1];d[1]=g}b.splice(d[0],
1);b.splice(d[1],1);b.push(e);f=c.link(f,d)}return new epiviz.ui.charts.transform.clustering.ClusterTree(b[0],a)};epiviz.ui.charts.transform.clustering.AgglomerativeClustering.prototype.id=function(){return"agglomerative"};epiviz.ui.charts.transform.clustering.HierarchicalClusteringAlgorithm=function(){};epiviz.ui.charts.transform.clustering.HierarchicalClusteringAlgorithm.prototype.cluster=function(a,b,c){};epiviz.ui.charts.transform.clustering.HierarchicalClusteringAlgorithm.prototype.id=function(){};epiviz.ui.charts.transform.clustering.EuclideanMetric=function(){};epiviz.ui.charts.transform.clustering.EuclideanMetric.prototype.distance=function(a,b){if(void 0==a||void 0==b)return null;var c=a.length,d=0,e=0,f=0,g;for(g=0;g<c;++g)if(void 0!=a[g]&&void 0!=b[g]){++d;var h=a[g]-b[g],e=e+h,f=f+h*h}0<d&&(e/=d);return f+(c-d)*e*e};epiviz.ui.charts.transform.clustering.EuclideanMetric.prototype.id=function(){return"euclidean"};epiviz.ui.charts.transform.clustering.CompleteLinkage=function(){};epiviz.ui.charts.transform.clustering.CompleteLinkage.prototype.link=function(a,b){var c=Array(a.length-1);if(b[0]<b[1]){var d=b[0];b[0]=b[1];b[1]=d}for(var e=d=0;d<a.length;++d,++e)if(d==b[0]||d==b[1])--e;else{c[e]=a[d].slice(0);c[e].splice(b[0],1);c[e].splice(b[1],1);var f=[d<b[0]?a[d][b[0]]:a[b[0]][d],d<b[1]?a[d][b[1]]:a[b[1]][d]];c[e].push(Math.max(f[0],f[1]))}c[c.length-1]=Array(c.length);return c};
epiviz.ui.charts.transform.clustering.CompleteLinkage.prototype.id=function(){return"complete"};epiviz.ui.charts.transform.clustering.ClusteringLinkage=function(){};epiviz.ui.charts.transform.clustering.ClusteringLinkage.prototype.link=function(a,b){};epiviz.ui.charts.transform.clustering.ClusteringLinkage.prototype.id=function(){};epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory=function(a){this._config=a;this._algorithms={};this._metrics={};this._linkages={};var b;for(b=0;b<a.clustering.algorithms.length;++b){var c=epiviz.utils.evaluateFullyQualifiedTypeName(a.clustering.algorithms[b]),c=epiviz.utils.applyConstructor(c);this._algorithms[c.id()]=c}for(b=0;b<a.clustering.metrics.length;++b)c=epiviz.utils.evaluateFullyQualifiedTypeName(a.clustering.metrics[b]),c=epiviz.utils.applyConstructor(c),this._metrics[c.id()]=
c;for(b=0;b<a.clustering.linkages.length;++b)c=epiviz.utils.evaluateFullyQualifiedTypeName(a.clustering.linkages[b]),c=epiviz.utils.applyConstructor(c),this._linkages[c.id()]=c};epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory._instance=null;epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory.instance=function(){return epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory._instance};
epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory.initialize=function(a){epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory._instance=new epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory(a)};epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory.prototype.algorithm=function(a){return this._algorithms[a]};epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory.prototype.metric=function(a){return this._metrics[a]};
epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory.prototype.linkage=function(a){return this._linkages[a]};epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory.prototype.algorithms=function(){return Object.keys(this._algorithms)};epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory.prototype.metrics=function(){return Object.keys(this._metrics)};epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory.prototype.linkages=function(){return Object.keys(this._linkages)};epiviz.plugins.charts.HeatmapPlot=function(a,b,c){epiviz.ui.charts.Plot.call(this,a,b,c);this._chartContent=null;this._min=this.measurements().first().minValue();this._max=this.measurements().first().maxValue();this._colorScale=epiviz.utils.colorizeBinary(this._min,this._max,"#ffffff",this.colors().getByKey("Max"));this._colorLabels=[];this._dendrogramRatio=.1;this._initialize()};epiviz.plugins.charts.HeatmapPlot.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.Plot.prototype);
epiviz.plugins.charts.HeatmapPlot.constructor=epiviz.plugins.charts.HeatmapPlot;epiviz.plugins.charts.HeatmapPlot.prototype._initialize=function(){epiviz.ui.charts.Plot.prototype._initialize.call(this);this._svg.classed("heatmap-plot",!0);this._chartContent=this._svg.append("g").attr("class","chart-content")};
epiviz.plugins.charts.HeatmapPlot.prototype.draw=function(a,b){epiviz.ui.charts.Plot.prototype.draw.call(this,a,b);b=this._lastData;a=this._lastRange;if(!b||!a)return[];this.customSettingsValues();var c=this._applyClustering(a,b);return this._drawCells(a,c.data,c.columnOrder)};
epiviz.plugins.charts.HeatmapPlot.prototype.drawCanvas=function(a,b){epiviz.ui.charts.Plot.prototype.draw.call(this,a,b);b=this._lastData;a=this._lastRange;if(!b||!a)return[];this.customSettingsValues();this._container.find("#"+this.id()+"-canvas").remove();var c=document.createElement("canvas");this.chartDrawType="canvas";this.canvas=c;c.id=this.id()+"-canvas";this._container.append(c);c.width=this.width();c.height=this.height();c.style="position:absolute;top:0;left:0;width:100%;height:100%";this._container.find("#"+
this.id()+"-hoverCanvas").remove();var d=document.createElement("canvas");this.hoverCanvas=d;d.id=this.id()+"-hoverCanvas";this._container.append(d);d.width=this.width();d.height=this.height();d.style="position:absolute;top:0;left:0;width:100%;height:100%;z-index:1";c=this._applyClustering(a,b,c);return this._drawCellsCanvas(a,c.data,c.columnOrder)};
epiviz.plugins.charts.HeatmapPlot.prototype._applyClustering=function(a,b,c){var d=epiviz.measurements.Measurement.Type.isOrdered(this.measurements().first().type()),e=this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.CLUSTER],f=this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.SHOW_DENDROGRAM],g=epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory.instance(),h=g.algorithm(this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.CLUSTERING_ALG]),
m=g.metric(this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.CLUSTERING_METRIC]),l=g.linkage(this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.CLUSTERING_LINKAGE]),p=this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.MAX_COLUMNS],n=b.firstSeries().globalStartIndex(),t=b.firstSeries().size()+n;b.foreach(function(a,b){var c=b.globalStartIndex(),d=b.globalEndIndex();c>n&&(n=c);d<t&&(t=d)});var v=t-n,g=e==epiviz.plugins.charts.HeatmapPlotType.Cluster.ROWS||
e==epiviz.plugins.charts.HeatmapPlotType.Cluster.BOTH,q=e==epiviz.plugins.charts.HeatmapPlotType.Cluster.COLS||e==epiviz.plugins.charts.HeatmapPlotType.Cluster.BOTH,e=f*this._dendrogramRatio,p=f&&q&&p>=v,u,w,r,A,B,z,x=this._svg;["dendrogram-horizontal","dendrogram-vertical"].forEach(function(a){x.select("."+a).remove()});var E=b;if(g){u=[];b.foreach(function(b,c){for(var e=[],f=0;f<v;++f){var g=c.getByGlobalIndex(f+n),h=g.rowItem;(!d||void 0==a.start()||void 0==a.end()||h.start()<a.end()&&h.end()>=
a.start())&&e.push(g.value)}u.push(e)});f=h.cluster(u,m,l);w=f.root().data();var G=[];b.foreach(function(a){G.push(a)});r=[];for(A=0;A<w.length;++A)r[A]=G[w[A]];w=new epiviz.measurements.MeasurementHashtable;for(A=0;A<r.length;++A)w.put(r[A],b.getSeries(r[A]));E=new epiviz.datatypes.MapGenomicData(w);e&&(z=this.width()*e,B=this.height()*(1-e*p)-this.margins().sumAxis(epiviz.ui.charts.Axis.Y),r=this.margins().top(),A=this.width()-z-this.margins().right(),c?this._drawDendrogramCanvas(f,r,A,B,z):this._drawDendrogram(f,
r,A,B,z))}w=null;if(q){u=[];b.foreach(function(b,c){for(var e=0,f=0;e<v;++e){var g=c.getByGlobalIndex(e+n),h=g.rowItem;if(!d||void 0==a.start()||void 0==a.end()||h.start()<a.end()&&h.end()>=a.start())u.length<=f&&u.push([]),u[f].push(g.value),++f}});if(0==u.length)return{data:E,columnOrder:[]};f=h.cluster(u,m,l);w=f.root().data();p&&(b=this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.SHOW_COLORS_FOR_ROW_LABELS]?20:0,A=this.margins().left(),r=this.height()*(1-e)-this.margins().bottom(),
z=this.height()*e,B=this.width()*(1-e*g)-this.margins().left()-this.margins().right()-b,c?this._drawDendrogramCanvas(f,r,A,B,z,!0):this._drawDendrogram(f,r,A,B,z,!0))}return{data:E,columnOrder:w}};
epiviz.plugins.charts.HeatmapPlot.prototype._drawCells=function(a,b,c){var d=this,e=epiviz.ui.charts.Axis,f=this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.MAX_COLUMNS],g=b.firstSeries().globalStartIndex(),h=b.firstSeries().size()+g,m=[],l=epiviz.measurements.Measurement.Type.isOrdered(this.measurements().first().type());b.foreach(function(a,b){var c=b.globalStartIndex(),d=b.globalEndIndex();c>g&&(g=c);d<h&&(h=d);m.push(a)});for(var p=h-g,n=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.COL_LABEL],
t=this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.CLUSTER],v=t==epiviz.plugins.charts.HeatmapPlotType.Cluster.ROWS||t==epiviz.plugins.charts.HeatmapPlotType.Cluster.BOTH,t=(t==epiviz.plugins.charts.HeatmapPlotType.Cluster.COLS||t==epiviz.plugins.charts.HeatmapPlotType.Cluster.BOTH)&&f>=p,q=this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.SHOW_DENDROGRAM]*this._dendrogramRatio,u=this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.SHOW_COLORS_FOR_ROW_LABELS]?
20:0,v=this.width()*(1-q*v)-u,t=this.height()*(1-q*t),w=[],r=[],A,q=0;q<p;++q){A=q+g;var B;b.foreach(function(a,b){return B=b.getRowByGlobalIndex(A)});B&&(!l||void 0==a.start()||void 0==a.end()||B.start()<a.end()&&B.end()>=a.start())&&(w.push(A),u=B.metadata(n)||""+B.id(),r.push(u))}if(c)for(a=w,l=r,w=Array(w.length),r=Array(r.length),q=0;q<w.length;++q)w[q]=a[c[q]],r[q]=l[c[q]];var z=[],x={};b.foreach(function(a,b,c){for(var d,e=0,g=f,h=0;h<r.length;++h)if(A=w[h],d=b.getByGlobalIndex(A)){var l;0==
e?(e=sprintf("item data-series-%s",c),l=new epiviz.ui.charts.ChartObject(sprintf("heatmap_%s_%s",c,A),d.rowItem.start(),d.rowItem.end(),[d.value],c,[[d]],[a],e,d.rowItem.seqName()),z.push(l),e=d=Math.ceil((r.length-h)/g),--g):(l=z[z.length-1],l.id+="_"+A,epiviz.measurements.Measurement.Type.isOrdered(b.measurement().type())&&(l.start=Math.min(l.start,d.rowItem.start()),l.end=Math.max(l.end,d.rowItem.end())),l.values[0]=(l.values[0]*l.valueItems[0].length+d.value)/(l.valueItems[0].length+1),l.valueItems[0].push(d));
0==c&&(x[A]=z.length-1);--e}});var E;this._min=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN];this._max=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX];c=epiviz.ui.charts.CustomSetting;this._min==c.DEFAULT&&(this._min=b.measurements()[0].minValue());this._max==c.DEFAULT&&(this._max=b.measurements()[0].maxValue());if(this._globalIndexColorLabels){c={};for(a=g;a<h;++a)c[this._globalIndexColorLabels[a]]=this._globalIndexColorLabels[a];
this._colorLabels=Object.keys(c);E={};this._colorLabels.forEach(function(a,b){var c=d.colors().getByKey(a);E[a]=epiviz.utils.colorizeBinary(d._min,d._max,"#ffffff",c)})}else this._colorLabels=[sprintf("Max",b.firstSeries().measurement().maxValue())],this._colorScale=epiviz.utils.colorizeBinary(this._min,this._max,"#ffffff",this.colors().getByKey("Max"));var G=(c=Math.min(r.length,f))?(v-this.margins().sumAxis(e.X))/c:0,F=(t-this.margins().sumAxis(e.Y))/b.measurements().length;b=this._chartContent.select(".items");
b.empty()&&(b=this._chartContent.append("g").attr("class","items"),e=b.append("g").attr("class","selected"),b.append("g").attr("class","hovered"),e.append("g").attr("class","hovered"));b.attr("transform","translate("+this.margins().left()+", "+this.margins().top()+")");e=b.selectAll("rect").data(z,function(a){return a.id});e.enter().append("rect").attr("id",function(a){return sprintf("%s-item-%s-%s",d.id(),a.seriesIndex,a.valueItems[0][0].globalIndex)}).attr("class",function(a){return a.cssClasses}).style("opacity",
0).style("fill-opacity",0).attr("x",function(a){return G*x[a.valueItems[0][0].globalIndex]}).attr("y",function(a){return F*a.seriesIndex}).attr("width",G).attr("height",F).style("fill",function(a,b){return d._globalIndexColorLabels?E[d._globalIndexColorLabels[a.valueItems[0][0].globalIndex]](a.values[0]):d._colorScale(a.values[0])});e.transition().duration(1E3).style("fill-opacity",null).style("opacity",null).attr("x",function(a){return G*x[a.valueItems[0][0].globalIndex]}).attr("y",function(a){return F*
a.seriesIndex}).attr("width",G).attr("height",F).style("fill",function(a){return d._globalIndexColorLabels?E[d._globalIndexColorLabels[a.valueItems[0][0].globalIndex]](a.values[0]):d._colorScale(a.values[0])});e.exit().transition().duration(1E3).style("opacity",0).remove();e.on("mouseover",function(a){d._hover.notify(new epiviz.ui.charts.VisEventArgs(d.id(),a))}).on("mouseout",function(){d._unhover.notify(new epiviz.ui.charts.VisEventArgs(d.id()))}).on("click",function(a){d._deselect.notify(new epiviz.ui.charts.VisEventArgs(d.id()));
d._select.notify(new epiviz.ui.charts.VisEventArgs(d.id(),a));d3.event.stopPropagation()});this._drawLabels(b,r,w,c,m,G,F,g,v);return z};
epiviz.plugins.charts.HeatmapPlot.prototype._drawCellsCanvas=function(a,b,c){var d=this,e=epiviz.ui.charts.Axis,f=this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.MAX_COLUMNS],g=b.firstSeries().globalStartIndex(),h=b.firstSeries().size()+g,m=[],l=epiviz.measurements.Measurement.Type.isOrdered(this.measurements().first().type());b.foreach(function(a,b){var c=b.globalStartIndex(),d=b.globalEndIndex();c>g&&(g=c);d<h&&(h=d);m.push(a)});for(var p=h-g,n=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.COL_LABEL],
t=this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.CLUSTER],v=t==epiviz.plugins.charts.HeatmapPlotType.Cluster.ROWS||t==epiviz.plugins.charts.HeatmapPlotType.Cluster.BOTH,t=(t==epiviz.plugins.charts.HeatmapPlotType.Cluster.COLS||t==epiviz.plugins.charts.HeatmapPlotType.Cluster.BOTH)&&f>=p,q=this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.SHOW_DENDROGRAM]*this._dendrogramRatio,u=this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.SHOW_COLORS_FOR_ROW_LABELS]?
20:0,v=this.width()*(1-q*v)-u,t=this.height()*(1-q*t),w=[],r=[],A,q=0;q<p;++q){A=q+g;var B;b.foreach(function(a,b){return B=b.getRowByGlobalIndex(A)});B&&(!l||void 0==a.start()||void 0==a.end()||B.start()<a.end()&&B.end()>=a.start())&&(w.push(A),u=B.metadata(n)||""+B.id(),r.push(u))}if(c)for(a=w,l=r,w=Array(w.length),r=Array(r.length),q=0;q<w.length;++q)w[q]=a[c[q]],r[q]=l[c[q]];var z=[],x={};b.foreach(function(a,b,c){for(var d,e=0,g=f,h=0;h<r.length;++h)if(A=w[h],d=b.getByGlobalIndex(A)){var l;0==
e?(e=sprintf("item data-series-%s",c),l=new epiviz.ui.charts.ChartObject(sprintf("heatmap_%s_%s",c,A),d.rowItem.start(),d.rowItem.end(),[d.value],c,[[d]],[a],e,d.rowItem.seqName()),z.push(l),e=d=Math.ceil((r.length-h)/g),--g):(l=z[z.length-1],l.id+="_"+A,epiviz.measurements.Measurement.Type.isOrdered(b.measurement().type())&&(l.start=Math.min(l.start,d.rowItem.start()),l.end=Math.max(l.end,d.rowItem.end())),l.values[0]=(l.values[0]*l.valueItems[0].length+d.value)/(l.valueItems[0].length+1),l.valueItems[0].push(d));
0==c&&(x[A]=z.length-1);--e}});var E;this._min=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN];this._max=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX];c=epiviz.ui.charts.CustomSetting;this._min==c.DEFAULT&&(this._min=b.measurements()[0].minValue());this._max==c.DEFAULT&&(this._max=b.measurements()[0].maxValue());if(this._globalIndexColorLabels){c={};for(a=g;a<h;++a)c[this._globalIndexColorLabels[a]]=this._globalIndexColorLabels[a];
this._colorLabels=Object.keys(c);E={};this._colorLabels.forEach(function(a,b){var c=d.colors().getByKey(a);E[a]=epiviz.utils.colorizeBinary(d._min,d._max,"#ffffff",c)})}else this._colorLabels=[sprintf("Max",b.firstSeries().measurement().maxValue())],this._colorScale=epiviz.utils.colorizeBinary(this._min,this._max,"#ffffff",this.colors().getByKey("Max"));var G=(c=Math.min(r.length,f))?(v-this.margins().sumAxis(e.X))/c:0,F=(t-this.margins().sumAxis(e.Y))/b.measurements().length;b=this._chartContent.select(".items");
b.empty()&&(b=this._chartContent.append("g").attr("class","items"),e=b.append("g").attr("class","selected"),b.append("g").attr("class","hovered"),e.append("g").attr("class","hovered"));b.attr("transform","translate("+this.margins().left()+", "+this.margins().top()+")");b.selectAll("rect").data(z,function(a){return a.id});this._container.find("svg").remove();var e=this.canvas,t=this.hoverCanvas,y=e.getContext("2d");y.globalAlpha=.6;y.translate(this.margins().left(),this.margins().top());t.getContext("2d").translate(this.margins().left(),
this.margins().top());this.renderQueue=renderQueue(function(a){y.beginPath();var b=d._globalIndexColorLabels?E[d._globalIndexColorLabels[a.valueItems[0][0].globalIndex]](a.values[0]):d._colorScale(a.values[0]);y.fillStyle=b;y.strokeStyle="black";y.rect(G*x[a.valueItems[0][0].globalIndex],F*a.seriesIndex,G,F);y.fill();y.stroke()});this.renderQueue(z);this._canvasHoverOptions={colIndex:x,cellWidth:G,cellHeight:F};this.addCanvasEvents(e,t,z,null,G,x);this._drawLabelsCanvas(b,r,w,c,m,G,F,g,v);return z};
epiviz.plugins.charts.HeatmapPlot.prototype._drawDendrogram=function(a,b,c,d,e,f){var g=f?"dendrogram-horizontal":"dendrogram-vertical",h=this._svg.append("g").attr("class",g);f?h.attr("transform","translate("+c+","+b+")scale(-1, 1)rotate(90, 0, 0)"):h.attr("transform","translate("+c+","+b+")");this._drawSubDendrogram(this._svg.select("."+g),a.root(),0,0,e,d,!1)};
epiviz.plugins.charts.HeatmapPlot.prototype._drawDendrogramCanvas=function(a,b,c,d,e,f){var g=this.canvas,h=g.getContext("2d");f?(h.save(),h.translate(c,b),h.scale(-1,1),h.rotate(Math.PI/2)):(h.save(),h.translate(c,b));this._drawSubDendrogramCanvas(g,a.root(),0,0,e,d,!1);h.restore()};
epiviz.plugins.charts.HeatmapPlot.prototype._drawSubDendrogramCanvas=function(a,b,c,d,e,f,g){var h=b.children();if(0==h.length)return c+.5*f;for(var m=a.getContext("2d"),l=d3.scale.linear().domain([0,b.distance()]).range([0,e]),p=0,n,t,v=0;v<h.length;++v){var q=c+p,u=f/b.weight()*h[v].weight(),w=l(h[v].distance()),q=this._drawSubDendrogramCanvas(a,h[v],q,d,w,u,g);m.beginPath();m.moveTo(d+w,q);m.lineTo(d+e,q);m.strokeStyle="#555555";m.stroke();0==v&&g&&(m.font="9px",m.beginPath(),m.textAlign="center",
m.fillText(Globalize.format(b.distance(),"n2"),Math.max(d+10,d+.5*(w+e)),q-10));if(void 0==n||n>q)n=q;if(void 0==t||t<q)t=q;p+=f/b.weight()*h[v].weight()}m.beginPath();m.moveTo(d+e,n);m.lineTo(d+e,t);m.strokeStyle="#555555";m.stroke();return.5*(n+t)};
epiviz.plugins.charts.HeatmapPlot.prototype._drawSubDendrogram=function(a,b,c,d,e,f,g){var h=b.children();if(0==h.length)return c+.5*f;for(var m=d3.scale.linear().domain([0,b.distance()]).range([0,e]),l=0,p,n,t=0;t<h.length;++t){var v=c+l,q=f/b.weight()*h[t].weight(),u=m(h[t].distance()),v=this._drawSubDendrogram(a,h[t],v,d,u,q,g);a.append("line").attr("x1",d+u).attr("x2",d+e).attr("y1",v).attr("y2",v).style("stroke","#555555").style("stroke-width",1).style("shape-rendering","auto");0==t&&g&&a.append("text").attr("class",
"row-text").attr("x",Math.max(d+10,d+.5*(u+e))).attr("y",v-10).style("text-anchor","middle").text(Globalize.format(b.distance(),"n2"));if(void 0==p||p>v)p=v;if(void 0==n||n<v)n=v;l+=f/b.weight()*h[t].weight()}a.append("line").attr("x1",d+e).attr("x2",d+e).attr("y1",p).attr("y2",n).style("stroke","#555555").style("stroke-width",1).style("shape-rendering","auto");return.5*(p+n)};
epiviz.plugins.charts.HeatmapPlot.prototype._drawLabels=function(a,b,c,d,e,f,g,h,m){var l=this,p=function(a){a="name"==v?a.name():(a=a.annotation())&&v in a?a[v]:"<NA>";return u[a]=a},n=a.selectAll(".col-text"),t=0;b.length>d?n.transition().duration(500).style("opacity",0).remove():(n=n.data(b,function(a,b){return a+c[b]}),n.enter().append("text").attr("class","col-text").style("opacity","0").attr("x",0).attr("y",0).attr("transform",function(a,b){return"translate("+(b*f+.5*f)+",-5)rotate(-60)"}).text(function(a){return a}),
n.transition().duration(500).attr("x",0).attr("y",0).attr("transform",function(a,b){return"translate("+(b*f+.5*f)+",-5)rotate(-60)"}).style("opacity",null).attr("fill",function(a,b){var c=b+h;return l._globalIndexColorLabels?l.colors().getByKey(l._globalIndexColorLabels[c]):"#000000"}),n.exit().transition().duration(500).style("opacity",0).remove(),this._container.find(" .col-text").each(function(a){a=this.getBBox().width;t<a&&(t=a)}));var v=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.ROW_LABEL];
b=this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.SHOW_COLORS_FOR_ROW_LABELS];b||(a.selectAll(".row-color-label").remove(),d=a.selectAll(".row-text").data(e,function(a){return a.id()}),d.enter().append("text").attr("class","row-text").attr("x",0).attr("y",0).attr("transform",function(a,b){return"translate(-5,"+(b*g+.5*g)+")rotate(30)"}),d.text(function(a){return"name"==v?a.name():(a=a.annotation())&&v in a?a[v]:"<NA>"}),d.transition().duration(500).attr("x",0).attr("y",
0).attr("transform",function(a,b){return"translate(-5,"+(b*g+.5*g)+")rotate(30)"}),d.exit().remove());var q;if(b){a.selectAll(".row-text").remove();var u={};e.forEach(function(a){a=p(a);u[a]=a});q=Object.keys(u);a=a.selectAll(".row-color-label").data(e,function(a){return a.id()});a.enter().append("rect").attr("class","row-color-label").attr("x",m-l.margins().sumAxis(epiviz.ui.charts.Axis.X)).attr("y",.5*-g).attr("width",20).attr("height",g).attr("transform",function(a,b){return"translate(0,"+(b*g+
.5*g)+")"});a.style("fill",function(a){a=p(a);return l.colors().getByKey(a)});a.transition().duration(500).attr("x",m-l.margins().sumAxis(epiviz.ui.charts.Axis.X)).attr("y",.5*-g).attr("height",g).attr("transform",function(a,b){return"translate(0,"+(b*g+.5*g)+")"});a.exit().remove()}this._svg.selectAll(".chart-title").remove();this._svg.selectAll(".chart-title-color ").remove();m=this._svg.selectAll(".chart-title").data(["Min"].concat(this._colorLabels));m.enter().append("text").attr("class","chart-title").attr("font-weight",
"bold").attr("y",l.margins().top()-5-t);m.attr("fill",function(a,b){return 0==b?"#000000":l.colors().getByKey(a)}).text(function(a){return a});var w=0,r=[];this._container.find(" .chart-title").each(function(a){r.push(w);w+=this.getBBox().width+15});m.attr("x",function(a,b){return l.margins().left()+10+r[b]});this._svg.selectAll(".chart-title-color").data(["Min"].concat(this._colorLabels)).enter().append("circle").attr("class","chart-title-color").attr("cx",function(a,b){return l.margins().left()+
4+r[b]}).attr("cy",l.margins().top()-9-t).attr("r",4).style("shape-rendering","auto").style("stroke-width","0").attr("fill",function(a,b){return 0==b?"#ffffff":l.colors().getByKey(a)}).style("stroke-width",function(a,b){return b?0:1}).style("stroke","#000000");this._svg.selectAll(".row-legend").remove();this._svg.selectAll(".row-legend-color").remove();b&&(m=this._svg.selectAll(".row-legend").data(q),m.enter().append("text").attr("class","row-legend").attr("font-weight","bold").attr("x",-20),m.attr("fill",
function(a){return l.colors().getByKey(a)}).text(function(a){return a}).attr("transform",function(a,b){return"translate("+l.margins().left()+","+l.margins().top()+")"}),m.attr("y",function(a,b){return 10+15*b}),this._svg.selectAll(".row-legend-color").data(q).enter().append("rect").attr("class","chart-title-color").attr("x",-18).attr("y",function(a,b){return 2+15*b}).attr("width",10).attr("height",10).style("shape-rendering","auto").style("stroke-width","0").attr("fill",function(a){return l.colors().getByKey(a)}).style("stroke-width",
0).attr("transform",function(a,b){return"translate("+l.margins().left()+","+l.margins().top()+")"}),this._colorLabels=this._colorLabels.concat(q))};
epiviz.plugins.charts.HeatmapPlot.prototype._drawLabelsCanvas=function(a,b,c,d,e,f,g,h,m){var l=this,p=function(a){a="name"==t?a.name():(a=a.annotation())&&t in a?a[t]:"<NA>";return q[a]=a},n=l.canvas.getContext("2d");b.forEach(function(a,b){var d=a+c[b];n.save();n.translate(b*f+.5*f,-5);n.rotate(-Math.PI/4);var e="#000000";l._globalIndexColorLabels&&(e=b+h,e=l.colors().getByKey(l._globalIndexColorLabels[e]));n.strokeStyle=e;n.fillStyle=e;n.font="9px";n.beginPath();n.textAlign="start";n.fillText(d,
0,0);n.restore()});var t=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.ROW_LABEL];(b=this.customSettingsValues()[epiviz.plugins.charts.HeatmapPlotType.CustomSettings.SHOW_COLORS_FOR_ROW_LABELS])||e.forEach(function(a,b){var c;a.id();c="name"==t?a.name():(c=a.annotation())&&t in c?c[t]:"<NA>";n.save();n.translate(-8,b*g+.5*g);n.rotate(Math.PI/3);n.strokeStyle="black";n.fillStyle="black";n.font="9px";n.beginPath();n.textAlign="right";n.fillText(c,0,0);n.restore()});var v;
if(b){a.selectAll(".row-text").remove();var q={};e.forEach(function(a){a=p(a);q[a]=a});v=Object.keys(q);e.forEach(function(a,b){n.beginPath();var c=p(a),c=l.colors().getByKey(c);n.strokeStyle=c;n.fillStyle=c;n.fillRect(m-l.margins().sumAxis(epiviz.ui.charts.Axis.X),b*g+.5*g-.5*g,20,g)})}var u=0;["Min"].concat(this._colorLabels).forEach(function(a,b){var c=0==b?"#000000":l.colors().getByKey(a);n.strokeStyle=c;n.fillStyle=c;n.beginPath();n.arc(u-2,-l.margins().top()-0,4,0,2*Math.PI);n.fill();n.stroke();
n.font="9px";n.beginPath();n.textAlign="start";n.fillText(a,u+8,-l.margins().top()-0+4);c=n.measureText(a).width;u=u+8+c+10});b&&(n.globalAlpha=1,v.forEach(function(a,b){var c=l.colors().getByKey(a);n.strokeStyle=c;n.fillStyle=c;n.beginPath();n.fillRect(-13,55-l.margins().top()+15*(2+b),10,10);n.font="9px";n.beginPath();n.textAlign="right";n.fillText(a,-18,60-l.margins().top()+15*(2+b));n.measureText(a)}),this._colorLabels=this._colorLabels.concat(v))};
epiviz.plugins.charts.HeatmapPlot.prototype.colorLabels=function(){return this._colorLabels};
epiviz.plugins.charts.HeatmapPlot.prototype.addCanvasEvents=function(a,b,c,d,e,f){var g=this,h=b.getContext("2d");g.hoverCanvasObjects=c;g.width();g.height();b.addEventListener("click",function(a){});b.addEventListener("mousemove",function(a){b.getBoundingClientRect();var d=a.offsetX-g.margins().left(),h=null;c&&c.forEach(function(a){var b=e*f[a.valueItems[0][0].globalIndex];d>=b&&d<=b+e&&(h=a)});h&&g._hover.notify(new epiviz.ui.charts.VisEventArgs(g.id(),h))});b.addEventListener("mouseout",function(a){g._canvasHoverObject=
null;h.clearRect(0,0,b.width,b.height);g._unhover.notify(new epiviz.ui.charts.VisEventArgs(g.id()))})};
epiviz.plugins.charts.HeatmapPlot.prototype.doHover=function(a){epiviz.ui.charts.Plot.prototype.doHover.call(this,a);if("canvas"==this.chartDrawType){var b=this.hoverCanvas.getContext("2d");b.clearRect(0,0,this.hoverCanvas.width,this.hoverCanvas.height);var c=this._canvasHoverOptions.cellWidth,d=this._canvasHoverOptions.colIndex,e=this._canvasHoverOptions.cellHeight;this._canvasHoverObject=a;this.hoverCanvasObjects.forEach(function(f){f.overlapsWith(a)&&(b.strokeStyle="yellow",b.strokeRect(c*d[f.valueItems[0][0].globalIndex],
e*f.seriesIndex,c,e))})}else{var f=this._container.find(".items"),g=f.find("> .hovered"),h=f.find("> .selected"),m=h.find("> .hovered"),l=function(){if(Array.isArray(a)){for(var b=!1,c=0;c<a.length;c++)a[c].overlapsWith(d3.select(this).data()[0])&&(b=!0);return b}return a.overlapsWith(d3.select(this).data()[0])},f=f.find("> .item").filter(l);g.append(f);f=h.find("> .item").filter(l);m.append(f)}};epiviz.plugins.charts.HeatmapPlotType=function(a){epiviz.ui.charts.PlotType.call(this,a)};epiviz.plugins.charts.HeatmapPlotType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.PlotType.prototype);epiviz.plugins.charts.HeatmapPlotType.constructor=epiviz.plugins.charts.HeatmapPlotType;epiviz.plugins.charts.HeatmapPlotType.prototype.createNew=function(a,b,c){return new epiviz.plugins.charts.HeatmapPlot(a,b,c)};epiviz.plugins.charts.HeatmapPlotType.prototype.typeName=function(){return"epiviz.plugins.charts.HeatmapPlot"};
epiviz.plugins.charts.HeatmapPlotType.prototype.chartName=function(){return"Heatmap"};epiviz.plugins.charts.HeatmapPlotType.prototype.chartHtmlAttributeName=function(){return"heatmap"};epiviz.plugins.charts.HeatmapPlotType.prototype.measurementsFilter=function(){return function(a){return epiviz.measurements.Measurement.Type.hasValues(a.type())}};epiviz.plugins.charts.HeatmapPlotType.prototype.isRestrictedToSameDatasourceGroup=function(){return!0};
epiviz.plugins.charts.HeatmapPlotType.prototype.customSettingsDefs=function(){var a=epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory.instance();return epiviz.ui.charts.PlotType.prototype.customSettingsDefs.call(this).concat([new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.COL_LABEL,epiviz.ui.charts.CustomSetting.Type.MEASUREMENTS_METADATA,"colLabel","Columns labels"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.ROW_LABEL,
epiviz.ui.charts.CustomSetting.Type.MEASUREMENTS_ANNOTATION,"name","Row labels"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.HeatmapPlotType.CustomSettings.SHOW_COLORS_FOR_ROW_LABELS,epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!1,"Row labels as colors"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.HeatmapPlotType.CustomSettings.MAX_COLUMNS,epiviz.ui.charts.CustomSetting.Type.NUMBER,40,"Max columns"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MIN,
epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Min Value"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MAX,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Max Value"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.HeatmapPlotType.CustomSettings.CLUSTER,epiviz.ui.charts.CustomSetting.Type.CATEGORICAL,"rows","Cluster",Object.keys(epiviz.plugins.charts.HeatmapPlotType.Cluster).map(function(a){return epiviz.plugins.charts.HeatmapPlotType.Cluster[a]})),
new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.HeatmapPlotType.CustomSettings.CLUSTERING_ALG,epiviz.ui.charts.CustomSetting.Type.CATEGORICAL,a.algorithms()[0],"Clustering Algorithm",a.algorithms()),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.HeatmapPlotType.CustomSettings.CLUSTERING_METRIC,epiviz.ui.charts.CustomSetting.Type.CATEGORICAL,a.metrics()[0],"Clustering Metric",a.metrics()),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.HeatmapPlotType.CustomSettings.CLUSTERING_LINKAGE,
epiviz.ui.charts.CustomSetting.Type.CATEGORICAL,a.linkages()[0],"Clustering Linkage",a.linkages()),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.HeatmapPlotType.CustomSettings.SHOW_DENDROGRAM,epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!0,"Show Dendrogram")])};epiviz.plugins.charts.HeatmapPlotType.Cluster={NONE:"none",ROWS:"rows",COLS:"columns",BOTH:"both"};
epiviz.plugins.charts.HeatmapPlotType.CustomSettings={MAX_COLUMNS:"maxColumns",CLUSTER:"cluster",CLUSTERING_ALG:"clusteringAlg",CLUSTERING_METRIC:"clusteringMetric",CLUSTERING_LINKAGE:"clusteringLinkage",SHOW_DENDROGRAM:"showDendrogram",SHOW_COLORS_FOR_ROW_LABELS:"showColorsForRowLabels"};epiviz.plugins.charts.LinePlot=function(a,b,c){epiviz.ui.charts.Plot.call(this,a,b,c);this._initialize()};epiviz.plugins.charts.LinePlot.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.Plot.prototype);epiviz.plugins.charts.LinePlot.constructor=epiviz.plugins.charts.LinePlot;epiviz.plugins.charts.LinePlot.prototype._initialize=function(){epiviz.ui.charts.Plot.prototype._initialize.call(this);this._svg.classed("line-plot",!0)};
epiviz.plugins.charts.LinePlot.prototype.draw=function(a,b,c,d){epiviz.ui.charts.Plot.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;if(!b||!a)return[];c=epiviz.ui.charts.CustomSetting;d=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN];var e=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX],f=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.ROW_LABEL],g=this.getDataMinMax(b);d==c.DEFAULT&&
(d=g[0]);e==c.DEFAULT&&(e=g[1]);null===d&&null===e&&(d=-1,e=1);null===d&&(d=e-1);null===e&&(e=d+1);g=epiviz.ui.charts.Axis;c=d3.scale.linear().domain([0,b.measurements().length-1]).range([0,this.width()-this.margins().sumAxis(g.X)]);d=d3.scale.linear().domain([d,e]).range([this.height()-this.margins().sumAxis(g.Y),0]);this._clearAxes();this._drawAxes(c,d,b.measurements().length,5,void 0,void 0,void 0,void 0,void 0,void 0,b.measurements().map(function(a){return"name"==f?a.name():(a=a.annotation())&&
f in a?a[f]:"<NA>"}));e=this._svg.selectAll(".lines");e.empty()&&(e=this._svg.append("g").attr("class","lines items"),g=e.append("g").attr("class","selected"),e.append("g").attr("class","hovered"),g.append("g").attr("class","hovered"));e.attr("transform","translate("+this.margins().left()+", "+this.margins().top()+")");return this._drawLines(a,b,c,d)};
epiviz.plugins.charts.LinePlot.prototype.drawCanvas=function(a,b,c,d){epiviz.ui.charts.Plot.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;if(!b||!a)return[];c=epiviz.ui.charts.CustomSetting;var e=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN],f=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX],g=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.ROW_LABEL];e==c.DEFAULT&&(e=null,b.measurements().forEach(function(a){null!==
a&&(null===e||a.minValue()<e)&&(e=a.minValue())}));f==c.DEFAULT&&(f=null,b.measurements().forEach(function(a){null!==a&&(null===f||a.maxValue()>f)&&(f=a.maxValue())}));null===e&&null===f&&(e=-1,f=1);null===e&&(e=f-1);null===f&&(f=e+1);d=epiviz.ui.charts.Axis;c=d3.scale.linear().domain([0,b.measurements().length-1]).range([0,this.width()-this.margins().sumAxis(d.X)]);d=d3.scale.linear().domain([e,f]).range([this.height()-this.margins().sumAxis(d.Y),0]);this._container.find("svg").remove();this._container.find("#"+
this.id()+"-canvas").remove();var h=document.createElement("canvas");this.chartDrawType="canvas";this.canvas=h;h.id=this.id()+"-canvas";this._container.append(h);h.width=this.width();h.height=this.height();h.style="position:absolute;top:0;left:0;width:100%;height:100%";this._container.find("#"+this.id()+"-hoverCanvas").remove();var m=document.createElement("canvas");this.hoverCanvas=m;m.id=this.id()+"-hoverCanvas";this._container.append(m);m.width=this.width();m.height=this.height();m.style="position:absolute;top:0;left:0;width:100%;height:100%;z-index:1";
this._drawAxesCanvas(c,d,b.measurements().length,5,h,void 0,void 0,void 0,void 0,void 0,b.measurements().map(function(a){return"name"==g?a.name():(a=a.annotation())&&g in a?a[g]:"<NA>"}));h.getContext("2d").translate(this.margins().left(),this.margins().top());m.getContext("2d").translate(this.margins().left(),this.margins().top());return this._drawLinesCanvas(a,b,c,d,h,m)};
epiviz.plugins.charts.LinePlot.prototype._drawLines=function(a,b,c,d){var e=this.colors(),f=this.customSettingsValues()[epiviz.plugins.charts.LinePlotType.CustomSettings.SHOW_POINTS],g=this.customSettingsValues()[epiviz.plugins.charts.LinePlotType.CustomSettings.SHOW_LINES],h=this.customSettingsValues()[epiviz.plugins.charts.LinePlotType.CustomSettings.SHOW_ERROR_BARS],m=this.customSettingsValues()[epiviz.plugins.charts.LinePlotType.CustomSettings.POINT_RADIUS],l=this.customSettingsValues()[epiviz.plugins.charts.LinePlotType.CustomSettings.LINE_THICKNESS],
p=this.customSettingsValues()[epiviz.plugins.charts.LinePlotType.CustomSettings.INTERPOLATION],n=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.COL_LABEL],t=this.customSettingsValues()[epiviz.plugins.charts.LinePlotType.CustomSettings.ABS_LINE_VAL],v=this,q=this._svg.select(".lines"),u=b.firstSeries().globalStartIndex(),w=b.firstSeries().globalEndIndex();b.foreach(function(a,b){var c=b.globalStartIndex(),d=b.globalEndIndex();c>u&&(u=c);d<w&&(w=d)});for(var r=w-u,A=epiviz.measurements.Measurement.Type.isOrdered(b.measurements()[0].type()),
B,z,x=0;x<r;++x){var E=x+u,G=b.firstSeries().getRowByGlobalIndex(E);if(!A||void 0==a.start()||void 0==a.end()||G.start()<a.end()&&G.end()>=a.start())void 0==B&&(B=E),z=E+1}u=B;w=z;r=z-B;this.width();var F=d3.svg.line().x(function(a){return c(a.x)}).y(function(a){return d(a.y)}).interpolate(p),y=function(a){return v._globalIndexColorLabels?v._globalIndexColorLabels[a.globalIndex()]:a.metadata(n)},I=function(a){return b.measurements().map(function(c,d){var e=b.getByGlobalIndex(c,a);return{x:d,y:e?e.value:
null,errMinus:e&&e.valueAnnotation?e.valueAnnotation.errMinus:null,errPlus:e&&e.valueAnnotation?e.valueAnnotation.errPlus:null}}).filter(function(a){return null!==a.y})};a=epiviz.utils.range(r,u);var C;g?(C=a.map(function(a){var c=b.firstSeries().getRowByGlobalIndex(a);return new epiviz.ui.charts.ChartObject(sprintf("line-series-%s",a),c.start(),c.end(),I(a),a,b.measurements().map(function(c,d){return[b.getByGlobalIndex(c,a)]}),b.measurements(),"",c.seqName())}),g=q.selectAll(".line-series").data(C,
function(a){return a.id}),g.enter().insert("g",":first-child").attr("class","line-series item").style("opacity","0").on("mouseover",function(a){v._hover.notify(new epiviz.ui.charts.VisEventArgs(v.id(),a))}).on("mouseout",function(){v._unhover.notify(new epiviz.ui.charts.VisEventArgs(v.id()))}).each(function(a){d3.select(this).append("path").attr("class","bg-line").attr("d",F(a.values)).style("shape-rendering","auto").style("stroke-width",10).style("stroke","#dddddd").style("stroke-opacity","0.1");
d3.select(this).append("path").attr("class","main-line").attr("d",F(a.values)).style("shape-rendering","auto")}),g.transition().duration(500).style("opacity","0.7").each(function(a){var c=e.getByKey(y(b.firstSeries().getRowByGlobalIndex(a.seriesIndex)));d3.select(this).selectAll(".bg-line").attr("d",F(a.values));d3.select(this).selectAll(".main-line").attr("d",F(a.values)).style("stroke",c).style("stroke-width",l)}),g.exit().transition().duration(500).style("opacity","0").remove()):q.selectAll(".line-series").remove();
f?(f=q.selectAll(".points").data(C,function(a){return a.id}),f.enter().append("g").attr("class","points").style("opacity","0"),f.each(function(a){d3.select(this).selectAll(".data-point").remove();var f=d3.select(this).selectAll(".data-point").data(a.values);f.enter().append("g").attr("class","data-point").each(function(f){d3.select(this).append("circle").attr("cx",function(a){return c(a.x)}).attr("cy",function(a){return d(a.y)}).attr("r",m).style("stroke-width",2).attr("fill","none").attr("stroke",
e.getByKey(y(b.firstSeries().getRowByGlobalIndex(a.seriesIndex))));d3.select(this).selectAll(".error-bar").remove();h&&void 0!=f.errMinus&&void 0!=f.errPlus&&(d3.select(this).append("line").attr("x1",c(f.x)).attr("x2",c(f.x)).attr("y1",d(f.errMinus)).attr("y2",d(f.errPlus)).style("stroke",e.getByKey(y(b.firstSeries().getRowByGlobalIndex(a.seriesIndex)))).style("stroke-width",2).attr("class","error-bar"),d3.select(this).append("line").attr("x1",c(f.x)-4).attr("x2",c(f.x)+4).attr("y1",d(f.errMinus)).attr("y2",
d(f.errMinus)).style("stroke",e.getByKey(y(b.firstSeries().getRowByGlobalIndex(a.seriesIndex)))).style("stroke-width",2).attr("class","error-bar"),d3.select(this).append("line").attr("x1",c(f.x)-4).attr("x2",c(f.x)+4).attr("y1",d(f.errPlus)).attr("y2",d(f.errPlus)).style("stroke",e.getByKey(y(b.firstSeries().getRowByGlobalIndex(a.seriesIndex)))).style("stroke-width",2).attr("class","error-bar"))});f.exit().remove()}).transition().duration(500).style("opacity","1"),f.exit().transition().duration(500).style("opacity",
"0").remove()):q.selectAll(".points").remove();var H={};a.forEach(function(a){a=y(b.firstSeries().getRowByGlobalIndex(a));H[a]=a});this._svg.selectAll(".chart-title").remove();this._svg.selectAll(".chart-title-color").remove();f=this._svg.selectAll(".chart-title").data(Object.keys(H));f.enter().append("text").attr("class","chart-title").attr("font-weight","bold").attr("y",v.margins().top()-5);f.attr("fill",function(a){return e.getByKey(a)}).text(function(a){return a});var D=0,L=[];this._container.find(" .chart-title").each(function(a){L.push(D);
D+=this.getBBox().width+15});f.attr("x",function(a,b){return v.margins().left()+10+L[b]});this._svg.selectAll(".chart-title-color").data(Object.keys(H)).enter().append("circle").attr("class","chart-title-color").attr("cx",function(a,b){return v.margins().left()+4+L[b]}).attr("cy",v.margins().top()-9).attr("r",4).style("shape-rendering","auto").style("stroke-width","0").style("fill",function(a){return e.getByKey(a)});t!=epiviz.ui.charts.CustomSetting.DEFAULT&&(q.selectAll(".abLine").remove(),q.append("svg:line").attr("class",
"abLine").attr("x1",0).attr("x2",v.width()-v.margins().sumAxis(epiviz.ui.charts.Axis.X)).attr("y1",d(t)).attr("y2",d(t)).style("stroke","black").style("stroke-dasharray","5, 5"));return C};
epiviz.plugins.charts.LinePlot.prototype._drawLinesCanvas=function(a,b,c,d,e,f){var g=this.colors(),h=this.customSettingsValues()[epiviz.plugins.charts.LinePlotType.CustomSettings.SHOW_POINTS],m=this.customSettingsValues()[epiviz.plugins.charts.LinePlotType.CustomSettings.SHOW_LINES],l=this.customSettingsValues()[epiviz.plugins.charts.LinePlotType.CustomSettings.SHOW_ERROR_BARS],p=this.customSettingsValues()[epiviz.plugins.charts.LinePlotType.CustomSettings.POINT_RADIUS],n=this.customSettingsValues()[epiviz.plugins.charts.LinePlotType.CustomSettings.LINE_THICKNESS],
t=this.customSettingsValues()[epiviz.plugins.charts.LinePlotType.CustomSettings.INTERPOLATION],v=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.COL_LABEL],q=this.customSettingsValues()[epiviz.plugins.charts.LinePlotType.CustomSettings.ABS_LINE_VAL],u=this;this._svg.select(".lines");var w=b.firstSeries().globalStartIndex(),r=b.firstSeries().globalEndIndex();b.foreach(function(a,b){var c=b.globalStartIndex(),d=b.globalEndIndex();c>w&&(w=c);d<r&&(r=d)});for(var A=r-w,B=epiviz.measurements.Measurement.Type.isOrdered(b.measurements()[0].type()),
z,x,E=0;E<A;++E){var G=E+w,F=b.firstSeries().getRowByGlobalIndex(G);if(!B||void 0==a.start()||void 0==a.end()||F.start()<a.end()&&F.end()>=a.start())void 0==z&&(z=G),x=G+1}w=z;r=x;A=x-z;this.width();var y=d3.svg.line().x(function(a){return c(a.x)}).y(function(a){return d(a.y)}).interpolate(t),I=function(a){return u._globalIndexColorLabels?u._globalIndexColorLabels[a.globalIndex()]:a.metadata(v)},C=function(a){return b.measurements().map(function(c,d){var e=b.getByGlobalIndex(c,a);return{x:d,y:e?e.value:
null,errMinus:e&&e.valueAnnotation?e.valueAnnotation.errMinus:null,errPlus:e&&e.valueAnnotation?e.valueAnnotation.errPlus:null}}).filter(function(a){return null!==a.y})};a=epiviz.utils.range(A,w);var H,D=e.getContext("2d");m&&(H=a.map(function(a){var c=b.firstSeries().getRowByGlobalIndex(a);return new epiviz.ui.charts.ChartObject(sprintf("line-series-%s",a),c.start(),c.end(),C(a),a,b.measurements().map(function(c,d){return[b.getByGlobalIndex(c,a)]}),b.measurements(),"",c.seqName())}),H.forEach(function(a){D.globalAlpha=
.8;D.beginPath();var c=new Path2D(y(a.values));D.strokeStyle=g.getByKey(I(b.firstSeries().getRowByGlobalIndex(a.seriesIndex)));D.lineWidth=n;D.stroke(c);D.beginPath()}));h&&H.forEach(function(a){a.values.forEach(function(e){var f=c(e.x),h=d(e.y);D.beginPath();D.arc(f,h,p,0,2*Math.PI);h=g.getByKey(I(b.firstSeries().getRowByGlobalIndex(a.seriesIndex)));D.strokeStyle=h;D.lineWidth=2;D.stroke();D.fillStyle=h;D.fill();if(l){var m=d(e.errMinus);e=d(e.errPlus);null!=m&&null!=e&&(D.beginPath(),D.moveTo(f,
m),D.lineTo(f,e),D.strokeStyle=h,D.stroke(),D.beginPath(),D.globalAlpha=.7,D.moveTo(f-4,m),D.lineTo(f+4,m),D.strokeStyle=h,D.stroke(),D.beginPath(),D.globalAlpha=.7,D.moveTo(f-4,e),D.lineTo(f+4,e),D.strokeStyle=h,D.stroke(),D.beginPath())}})});var L={};a.forEach(function(a){a=I(b.firstSeries().getRowByGlobalIndex(a));L[a]=a});var K=0;Object.keys(L).forEach(function(a,b){var c=g.getByKey(a);D.strokeStyle=c;D.fillStyle=c;D.beginPath();D.arc(u.margins().left()+K-2,-9,4,0,2*Math.PI);D.stroke();D.fill();
D.font="9px";D.beginPath();D.textAlign="start";D.fillText(a,u.margins().left()+K+8,-12);c=D.measureText(a).width;K=K+8+c+10});q!=epiviz.ui.charts.CustomSetting.DEFAULT&&(D.beginPath(),D.globalAlpha=.7,D.moveTo(0,d(q)),D.lineTo(u.width()-u.margins().sumAxis(epiviz.ui.charts.Axis.X),d(q)),D.strokeStyle=black,D.stroke());this.addCanvasEvents(e,f,H,c,d,y);return H};
epiviz.plugins.charts.LinePlot.prototype.colorLabels=function(){for(var a=[],b=0;b<this.colors().size()&&20>b;++b)a.push("Color "+(b+1));return a};
epiviz.plugins.charts.LinePlot.prototype.addCanvasEvents=function(a,b,c,d,e,f){var g=this,h=b.getContext("2d");g.hoverCanvasObjects=c;g.width();g.height();b.addEventListener("click",function(a){});b.addEventListener("mousemove",function(a){b.getBoundingClientRect();var d=a.offsetX,e=a.offsetY,m=null;h.clearRect(0,0,b.width,b.height);c&&c.forEach(function(a){var b=new Path2D(f(a.values));h.isPointInPath(b,d,e)&&(m=a)});m&&g._hover.notify(new epiviz.ui.charts.VisEventArgs(g.id(),m))});b.addEventListener("mouseout",
function(a){g._canvasHoverObject=null;h.clearRect(0,0,b.width,b.height);g._unhover.notify(new epiviz.ui.charts.VisEventArgs(g.id()))})};epiviz.plugins.charts.LinePlotType=function(a){epiviz.ui.charts.PlotType.call(this,a)};epiviz.plugins.charts.LinePlotType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.PlotType.prototype);epiviz.plugins.charts.LinePlotType.constructor=epiviz.plugins.charts.LinePlotType;epiviz.plugins.charts.LinePlotType.prototype.createNew=function(a,b,c){return new epiviz.plugins.charts.LinePlot(a,b,c)};epiviz.plugins.charts.LinePlotType.prototype.typeName=function(){return"epiviz.plugins.charts.LinePlot"};
epiviz.plugins.charts.LinePlotType.prototype.chartName=function(){return"Line Plot"};epiviz.plugins.charts.LinePlotType.prototype.chartHtmlAttributeName=function(){return"line-plot"};epiviz.plugins.charts.LinePlotType.prototype.measurementsFilter=function(){return function(a){return a.type()==epiviz.measurements.Measurement.Type.FEATURE}};epiviz.plugins.charts.LinePlotType.prototype.isRestrictedToSameDatasourceGroup=function(){return!0};
epiviz.plugins.charts.LinePlotType.prototype.customSettingsDefs=function(){return epiviz.ui.charts.PlotType.prototype.customSettingsDefs.call(this).concat([new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.COL_LABEL,epiviz.ui.charts.CustomSetting.Type.MEASUREMENTS_METADATA,"colLabel","Columns labels"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.ROW_LABEL,epiviz.ui.charts.CustomSetting.Type.MEASUREMENTS_ANNOTATION,"name","Row labels"),
new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.LinePlotType.CustomSettings.SHOW_POINTS,epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!1,"Show points"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.LinePlotType.CustomSettings.SHOW_LINES,epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!0,"Show lines"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.LinePlotType.CustomSettings.SHOW_ERROR_BARS,epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!0,"Show error bars"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.LinePlotType.CustomSettings.POINT_RADIUS,
epiviz.ui.charts.CustomSetting.Type.NUMBER,4,"Point radius"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.LinePlotType.CustomSettings.LINE_THICKNESS,epiviz.ui.charts.CustomSetting.Type.NUMBER,3,"Line thickness"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MIN,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Min Y"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MAX,epiviz.ui.charts.CustomSetting.Type.NUMBER,
epiviz.ui.charts.CustomSetting.DEFAULT,"Max Y"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.LinePlotType.CustomSettings.INTERPOLATION,epiviz.ui.charts.CustomSetting.Type.CATEGORICAL,"basis","Interpolation","linear step-before step-after basis basis-open basis-closed bundle cardinal cardinal-open monotone".split(" ")),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.LinePlotType.CustomSettings.ABS_LINE_VAL,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,
"Draw abline")])};epiviz.plugins.charts.LinePlotType.CustomSettings={SHOW_POINTS:"showPoints",SHOW_ERROR_BARS:"showErrorBars",SHOW_LINES:"showLines",POINT_RADIUS:"pointRadius",LINE_THICKNESS:"lineThickness",INTERPOLATION:"interpolation",ABS_LINE_VAL:"abLine"};epiviz.plugins.charts.StackedLinePlot=function(a,b,c){epiviz.ui.charts.Plot.call(this,a,b,c);this._initialize()};epiviz.plugins.charts.StackedLinePlot.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.Plot.prototype);epiviz.plugins.charts.StackedLinePlot.constructor=epiviz.plugins.charts.StackedLinePlot;epiviz.plugins.charts.StackedLinePlot.prototype._initialize=function(){epiviz.ui.charts.Plot.prototype._initialize.call(this);this._svg.classed("stacked-line-plot",!0)};
epiviz.plugins.charts.StackedLinePlot.prototype.drawCanvas=function(a,b,c,d){epiviz.ui.charts.Plot.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;if(!b||!a)return[];b=this.customSettingsValues()[epiviz.plugins.charts.StackedLinePlotType.CustomSettings.ROW_GROUP_BY];var e=this,f=function(){for(var a=0;a<e._markers.length;a++)if(e._markers[a]._type==epiviz.ui.charts.markers.VisualizationMarker.Type.GROUP_BY_MEASUREMENTS){var b=e._markers[a].id();delete e._markersMap[b];delete e._markersIndices[b];
delete e._markers[a];e._markers.length--}};a=function(a){if(!a)return null;var b;if(a.id()in e._markersMap){b=e._markersIndices[a.id()];var c=e._markers[b];if(c==a||c.type()==a.type()&&c.preMarkStr()==a.preMarkStr()&&c.markStr()==a.markStr())return null;e._markers[b]=a}else f(),b=e._markers.length,e._markers.push(a),e._markersIndices[a.id()]=b;e._markersMap[a.id()]=a};this.customSettingsValues()[epiviz.plugins.charts.StackedLinePlotType.CustomSettings.USE_GROUP_BY]?(b=new epiviz.ui.charts.markers.VisualizationMarker(epiviz.ui.charts.markers.VisualizationMarker.Type.GROUP_BY_MEASUREMENTS,
null,null,"function(data){return null}","function(m, data, preMarkResult) {return m.annotation()['"+b+"'];}"),a(b)):f();(function(){e.transformData(e._lastRange,e._unalteredData).done(function(){e._drawPlotCanvas(e._lastRange,e._lastData,c,d)});e._markersModified.notify(new epiviz.ui.charts.VisEventArgs(e._id,e._markers))})()};
epiviz.plugins.charts.StackedLinePlot.prototype.draw=function(a,b,c,d){epiviz.ui.charts.Plot.prototype.draw.call(this,a,b,c,d);b=this._lastData;a=this._lastRange;if(!b||!a)return[];b=this.customSettingsValues()[epiviz.plugins.charts.StackedLinePlotType.CustomSettings.ROW_GROUP_BY];var e=this,f=function(){for(var a=0;a<e._markers.length;a++)if(e._markers[a]._type==epiviz.ui.charts.markers.VisualizationMarker.Type.GROUP_BY_MEASUREMENTS){var b=e._markers[a].id();delete e._markersMap[b];delete e._markersIndices[b];
delete e._markers[a];e._markers.length--}};a=function(a){if(!a)return null;var b;if(a.id()in e._markersMap){b=e._markersIndices[a.id()];var c=e._markers[b];if(c==a||c.type()==a.type()&&c.preMarkStr()==a.preMarkStr()&&c.markStr()==a.markStr())return null;e._markers[b]=a}else f(),b=e._markers.length,e._markers.push(a),e._markersIndices[a.id()]=b;e._markersMap[a.id()]=a};this.customSettingsValues()[epiviz.plugins.charts.StackedLinePlotType.CustomSettings.USE_GROUP_BY]?(b=new epiviz.ui.charts.markers.VisualizationMarker(epiviz.ui.charts.markers.VisualizationMarker.Type.GROUP_BY_MEASUREMENTS,
null,null,"function(data){return null}","function(m, data, preMarkResult) {return m.annotation()['"+b+"'];}"),a(b)):f();(function(){e.transformData(e._lastRange,e._unalteredData).done(function(){e._drawPlot(e._lastRange,e._lastData,c,d)});e._markersModified.notify(new epiviz.ui.charts.VisEventArgs(e._id,e._markers))})()};
epiviz.plugins.charts.StackedLinePlot.prototype._drawPlot=function(a,b,c,d){var e=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.ROW_LABEL];c=this.customSettingsValues()[epiviz.plugins.charts.StackedLinePlotType.CustomSettings.INTERPOLATION];d=0==c.indexOf("step")?b.measurements().length:b.measurements().length-1;var f=epiviz.ui.charts.Axis;d=d3.scale.linear().domain([0,d]).range([0,this.width()-this.margins().sumAxis(f.X)]);this._clearAxes();this._drawAxes(d,void 0,b.measurements().length,
5,void 0,void 0,void 0,void 0,void 0,void 0,b.measurements().map(function(a){return"name"==e?a.name():(a=a.annotation())&&e in a?a[e]:"<NA>"}),void 0,0==c.indexOf("step"));c=this._svg.selectAll(".lines");c.empty()&&(c=this._svg.append("g").attr("class","lines items"),f=c.append("g").attr("class","selected"),c.append("g").attr("class","hovered"),f.append("g").attr("class","hovered"));c.attr("transform","translate("+this.margins().left()+", "+this.margins().top()+")");return this._drawLines(a,b,d)};
epiviz.plugins.charts.StackedLinePlot.prototype._drawPlotCanvas=function(a,b,c,d){var e=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.ROW_LABEL];c=this.customSettingsValues()[epiviz.plugins.charts.StackedLinePlotType.CustomSettings.INTERPOLATION];d=0==c.indexOf("step")?b.measurements().length:b.measurements().length-1;var f=epiviz.ui.charts.Axis;d=d3.scale.linear().domain([0,d]).range([0,this.width()-this.margins().sumAxis(f.X)]);this._container.find("svg").remove();this._container.find("#"+
this.id()+"-canvas").remove();f=document.createElement("canvas");this.chartDrawType="canvas";this.canvas=f;f.id=this.id()+"-canvas";this._container.append(f);f.width=this.width();f.height=this.height();f.style="position:absolute;top:0;left:0;width:100%;height:100%";this._container.find("#"+this.id()+"-hoverCanvas").remove();var g=document.createElement("canvas");this.hoverCanvas=g;g.id=this.id()+"-hoverCanvas";this._container.append(g);g.width=this.width();g.height=this.height();g.style="position:absolute;top:0;left:0;width:100%;height:100%;z-index:1";
f.getContext("2d").translate(this.margins().left(),this.margins().top());g.getContext("2d").translate(this.margins().left(),this.margins().top());this._drawAxesCanvas(d,void 0,b.measurements().length,5,f,void 0,void 0,void 0,void 0,void 0,b.measurements().map(function(a){return"name"==e?a.name():(a=a.annotation())&&e in a?a[e]:"<NA>"}),void 0,0==c.indexOf("step"));return this._drawLinesCanvas(a,b,d,f)};
epiviz.plugins.charts.StackedLinePlot.prototype._drawLines=function(a,b,c){var d=epiviz.ui.charts.Axis,e=this.colors(),f=this.customSettingsValues()[epiviz.plugins.charts.StackedLinePlotType.CustomSettings.INTERPOLATION],g=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.COL_LABEL],h=this.customSettingsValues()[epiviz.plugins.charts.StackedLinePlotType.CustomSettings.OFFSET],m=this.customSettingsValues()[epiviz.plugins.charts.StackedLinePlotType.CustomSettings.SCALE_TO_PERCENT],
l=this.customSettingsValues()[epiviz.plugins.charts.StackedLinePlotType.CustomSettings.HOVER_OPACITY],p=this,n=this._svg.select(".lines"),t=b.firstSeries().globalStartIndex(),v=b.firstSeries().globalEndIndex();b.foreach(function(a,b){var c=b.globalStartIndex(),d=b.globalEndIndex();c>t&&(t=c);d<v&&(v=d)});for(var q=v-t,u=epiviz.measurements.Measurement.Type.isOrdered(this.measurements().first().type()),w,r,A=0;A<q;++A){var B=A+t,z=b.firstSeries().getRowByGlobalIndex(B);z&&(!u||void 0==a.start()||void 0==
a.end()||z.start()<a.end()&&z.end()>=a.start())&&(void 0==w&&(w=B),r=B+1)}var t=w,v=r,x=epiviz.utils.range(r-w,t);if(0==x.length)return[];var E=null;m&&(E=new epiviz.measurements.MeasurementHashtable,b.measurements().forEach(function(a){var c=x.filter(function(c){return b.getByGlobalIndex(a,c)}).map(function(c){return b.getByGlobalIndex(a,c).value}).reduce(function(a,b){return a+b});E.put(a,c)}));var G=function(a){return p._globalIndexColorLabels?p._globalIndexColorLabels[a.globalIndex()]:a.metadata(g)},
F=function(a){var c=[];"step-before"==f&&c.push({x:0,y:0});var d=b.measurements(),c=c.concat(d.map(function(d,e){var f=m?E.get(d):1,f=f||1,g=b.getByGlobalIndex(d,a);return{x:c.length+e,y:g?g.value/f:null}}));"step-after"==f&&c.push({x:c.length,y:0});return c.filter(function(a){return null!==a.y})};a=x.filter(function(a){return b.firstSeries().getRowByGlobalIndex(a)}).map(function(a){var c=b.firstSeries().getRowByGlobalIndex(a),d=b.measurements();return new epiviz.ui.charts.ChartObject(sprintf("line-series-%s",
a),c.start(),c.end(),F(a),a,d.map(function(c,d){return[b.getByGlobalIndex(c,a)]}),d,"",c.seqName())});var q=x.map(function(a){return F(a)}),y=d3.layout.stack().offset(h)(q),I=d3.scale.linear().domain([Math.min(0,d3.min(y,function(a){return d3.min(a,function(a){return a.y0+a.y})})),d3.max(y,function(a){return d3.max(a,function(a){return a.y0+a.y})})]).range([this.height()-this.margins().sumAxis(d.Y),0]),C=d3.svg.area().x(function(a){return c(a.x)}).y0(function(a){return I(a.y0)}).y1(function(a){return I(a.y0+
a.y)}).interpolate(f),d=n.selectAll(".line-series").data(a,function(a){return a.seriesIndex});d.enter().insert("path",":first-child").attr("class","line-series item").attr("d",function(a,b){return C(y[b])}).style("opacity","0").style("shape-rendering","auto").style("fill",function(a,c){return e.getByKey(G(b.firstSeries().getRowByGlobalIndex(a.seriesIndex)))}).on("mouseover",function(a,b){p._hover.notify(new epiviz.ui.charts.VisEventArgs(p.id(),a));n.selectAll(".item").style("opacity",1-l);n.selectAll(".hovered .item").style("opacity",
l)}).on("mouseout",function(){p._unhover.notify(new epiviz.ui.charts.VisEventArgs(p.id()))});d.transition().duration(500).style("opacity","0.7").attr("d",function(a,b){return C(y[b])}).style("fill",function(a,c){return e.getByKey(G(b.firstSeries().getRowByGlobalIndex(a.seriesIndex)))});d.exit().transition().duration(500).style("opacity","0").remove();var H={};x.forEach(function(a){b.firstSeries().getByGlobalIndex(a)&&(a=G(b.firstSeries().getRowByGlobalIndex(a)),H[a]=a)});this._svg.selectAll(".chart-title").remove();
this._svg.selectAll(".chart-title-color ").remove();d=this._svg.selectAll(".chart-title").data(Object.keys(H));d.enter().append("text").attr("class","chart-title").attr("font-weight","bold").attr("y",p.margins().top()-5);d.attr("fill",function(a){return e.getByKey(a)}).text(function(a){return a});var D=0,L=[];this._container.find(" .chart-title").each(function(a){L.push(D);D+=this.getBBox().width+15});d.attr("x",function(a,b){return p.margins().left()+10+L[b]});this._svg.selectAll(".chart-title-color").data(Object.keys(H)).enter().append("circle").attr("class",
"chart-title-color").attr("cx",function(a,b){return p.margins().left()+4+L[b]}).attr("cy",p.margins().top()-9).attr("r",4).style("shape-rendering","auto").style("stroke-width","0").attr("fill",function(a){return e.getByKey(a)});return a};
epiviz.plugins.charts.StackedLinePlot.prototype._drawLinesCanvas=function(a,b,c,d){var e=epiviz.ui.charts.Axis,f=this.colors(),g=this.customSettingsValues()[epiviz.plugins.charts.StackedLinePlotType.CustomSettings.INTERPOLATION],h=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.COL_LABEL],m=this.customSettingsValues()[epiviz.plugins.charts.StackedLinePlotType.CustomSettings.OFFSET],l=this.customSettingsValues()[epiviz.plugins.charts.StackedLinePlotType.CustomSettings.SCALE_TO_PERCENT];
this.customSettingsValues();var p=this;this._svg.select(".lines");var n=b.firstSeries().globalStartIndex(),t=b.firstSeries().globalEndIndex();b.foreach(function(a,b){var c=b.globalStartIndex(),d=b.globalEndIndex();c>n&&(n=c);d<t&&(t=d)});for(var v=t-n,q=epiviz.measurements.Measurement.Type.isOrdered(this.measurements().first().type()),u,w,r=0;r<v;++r){var A=r+n,B=b.firstSeries().getRowByGlobalIndex(A);B&&(!q||void 0==a.start()||void 0==a.end()||B.start()<a.end()&&B.end()>=a.start())&&(void 0==u&&
(u=A),w=A+1)}var n=u,t=w,z=epiviz.utils.range(w-u,n);if(0==z.length)return[];var x=null;l&&(x=new epiviz.measurements.MeasurementHashtable,b.measurements().forEach(function(a){var c=z.filter(function(c){return b.getByGlobalIndex(a,c)}).map(function(c){return b.getByGlobalIndex(a,c).value}).reduce(function(a,b){return a+b});x.put(a,c)}));var E=function(a){return p._globalIndexColorLabels?p._globalIndexColorLabels[a.globalIndex()]:a.metadata(h)},G=function(a){var c=[];"step-before"==g&&c.push({x:0,
y:0});var d=b.measurements(),c=c.concat(d.map(function(d,e){var f=l?x.get(d):1,f=f||1,g=b.getByGlobalIndex(d,a);return{x:c.length+e,y:g?g.value/f:null}}));"step-after"==g&&c.push({x:c.length,y:0});return c.filter(function(a){return null!==a.y})};a=z.filter(function(a){return b.firstSeries().getRowByGlobalIndex(a)}).map(function(a){var c=b.firstSeries().getRowByGlobalIndex(a),d=b.measurements();return new epiviz.ui.charts.ChartObject(sprintf("line-series-%s",a),c.start(),c.end(),G(a),a,d.map(function(c,
d){return[b.getByGlobalIndex(c,a)]}),d,"",c.seqName())});var v=z.map(function(a){return G(a)}),F=d3.layout.stack().offset(m)(v),y=d3.scale.linear().domain([Math.min(0,d3.min(F,function(a){return d3.min(a,function(a){return a.y0+a.y})})),d3.max(F,function(a){return d3.max(a,function(a){return a.y0+a.y})})]).range([this.height()-this.margins().sumAxis(e.Y),0]),I=d3.svg.area().x(function(a){return c(a.x)}).y0(function(a){return y(a.y0)}).y1(function(a){return y(a.y0+a.y)}).interpolate(g),C=d.getContext("2d");
a.forEach(function(a,c){C.globalAlpha=.8;C.beginPath();var d=new Path2D(I(F[c]));C.fillStyle=C.strokeStyle=f.getByKey(E(b.firstSeries().getRowByGlobalIndex(a.seriesIndex)));C.lineWidth=2;C.stroke(d);C.fill(d);C.beginPath()});var H={};z.forEach(function(a){a=E(b.firstSeries().getRowByGlobalIndex(a));H[a]=a});var D=0;Object.keys(H).forEach(function(a,b){var c=f.getByKey(a);C.strokeStyle=c;C.fillStyle=c;C.beginPath();C.arc(p.margins().left()+D-2,-9,4,0,2*Math.PI);C.stroke();C.fill();C.font="9px";C.beginPath();
C.textAlign="start";C.fillText(a,p.margins().left()+D+8,-12);c=C.measureText(a).width;D=D+8+c+10});return a};epiviz.plugins.charts.StackedLinePlot.prototype.colorLabels=function(){for(var a=[],b=0;b<this.colors().size()&&20>b;++b)a.push("Color "+(b+1));return a};epiviz.plugins.charts.StackedLinePlotType=function(a){epiviz.ui.charts.PlotType.call(this,a)};epiviz.plugins.charts.StackedLinePlotType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.PlotType.prototype);epiviz.plugins.charts.StackedLinePlotType.constructor=epiviz.plugins.charts.StackedLinePlotType;epiviz.plugins.charts.StackedLinePlotType.prototype.createNew=function(a,b,c){return new epiviz.plugins.charts.StackedLinePlot(a,b,c)};epiviz.plugins.charts.StackedLinePlotType.prototype.typeName=function(){return"epiviz.plugins.charts.StackedLinePlot"};
epiviz.plugins.charts.StackedLinePlotType.prototype.chartName=function(){return"Stacked Plot"};epiviz.plugins.charts.StackedLinePlotType.prototype.chartHtmlAttributeName=function(){return"stacked-line-plot"};epiviz.plugins.charts.StackedLinePlotType.prototype.measurementsFilter=function(){return function(a){return a.type()==epiviz.measurements.Measurement.Type.FEATURE}};epiviz.plugins.charts.StackedLinePlotType.prototype.isRestrictedToSameDatasourceGroup=function(){return!0};
epiviz.plugins.charts.StackedLinePlotType.prototype.customSettingsDefs=function(){return epiviz.ui.charts.PlotType.prototype.customSettingsDefs.call(this).concat([new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.COL_LABEL,epiviz.ui.charts.CustomSetting.Type.MEASUREMENTS_METADATA,"colLabel","Color by"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.ROW_LABEL,epiviz.ui.charts.CustomSetting.Type.MEASUREMENTS_ANNOTATION,"name","Labels"),
new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.StackedLinePlotType.CustomSettings.OFFSET,epiviz.ui.charts.CustomSetting.Type.CATEGORICAL,"zero","Offset",["zero","wiggle"]),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.StackedLinePlotType.CustomSettings.INTERPOLATION,epiviz.ui.charts.CustomSetting.Type.CATEGORICAL,"step-after","Interpolation","linear step-before step-after basis basis-open basis-closed bundle cardinal cardinal-open monotone".split(" ")),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.StackedLinePlotType.CustomSettings.SCALE_TO_PERCENT,
epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!0,"Scale to Percent"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.StackedLinePlotType.CustomSettings.USE_GROUP_BY,epiviz.ui.charts.CustomSetting.Type.BOOLEAN,!1,"Use Group by"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.StackedLinePlotType.CustomSettings.ROW_GROUP_BY,epiviz.ui.charts.CustomSetting.Type.MEASUREMENTS_ANNOTATION,"name","Group By"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.StackedLinePlotType.CustomSettings.HOVER_OPACITY,
epiviz.ui.charts.CustomSetting.Type.NUMBER,.6,"Hover Opacity")])};epiviz.plugins.charts.StackedLinePlotType.CustomSettings={INTERPOLATION:"interpolation",OFFSET:"offset",SCALE_TO_PERCENT:"scaleToPercent",ROW_GROUP_BY:"groupBy",USE_GROUP_BY:"useGroupBy",HOVER_OPACITY:"hoverOpacity"};epiviz.ui.charts.DataStructureVisualizationType=function(a){epiviz.ui.charts.VisualizationType.call(this,a)};epiviz.ui.charts.DataStructureVisualizationType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.VisualizationType.prototype);epiviz.ui.charts.DataStructureVisualizationType.constructor=epiviz.ui.charts.DataStructureVisualizationType;epiviz.ui.charts.DataStructureVisualizationType.prototype.chartDisplayType=function(){return epiviz.ui.charts.VisualizationType.DisplayType.DATA_STRUCTURE};
epiviz.ui.charts.DataStructureVisualizationType.prototype.cssClass=function(){return"data-structure-container ui-widget-content"};epiviz.ui.charts.DataStructureVisualizationType.prototype.isRestrictedToSameDatasourceGroup=function(){return!0};epiviz.ui.charts.DataStructureVisualizationType.prototype.hasMeasurements=function(){return!1};epiviz.ui.charts.DataStructureVisualizationType.prototype.customSettingsDefs=function(){return epiviz.ui.charts.VisualizationType.prototype.customSettingsDefs.call(this)};epiviz.ui.charts.tree={};epiviz.ui.charts.tree.HierarchyVisualizationType=function(a){epiviz.ui.charts.DataStructureVisualizationType.call(this,a)};epiviz.ui.charts.tree.HierarchyVisualizationType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.DataStructureVisualizationType.prototype);epiviz.ui.charts.tree.HierarchyVisualizationType.constructor=epiviz.ui.charts.tree.HierarchyVisualizationType;epiviz.ui.charts.tree.IcicleType=function(a){epiviz.ui.charts.tree.HierarchyVisualizationType.call(this,a)};epiviz.ui.charts.tree.IcicleType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.tree.HierarchyVisualizationType.prototype);epiviz.ui.charts.tree.IcicleType.constructor=epiviz.ui.charts.tree.IcicleType;epiviz.ui.charts.tree.IcicleType.prototype.createNew=function(a,b,c){return new epiviz.ui.charts.tree.Icicle(a,b,c)};epiviz.ui.charts.tree.IcicleType.prototype.typeName=function(){return"epiviz.ui.charts.tree.Icicle"};
epiviz.ui.charts.tree.IcicleType.prototype.chartName=function(){return"Icicle"};epiviz.ui.charts.tree.IcicleType.prototype.chartHtmlAttributeName=function(){return"icicle"};
epiviz.ui.charts.tree.IcicleType.prototype.customSettingsDefs=function(){return epiviz.ui.charts.tree.HierarchyVisualizationType.prototype.customSettingsDefs.call(this).concat([new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.tree.IcicleType.CustomSettings.HOVER_OPACITY,epiviz.ui.charts.CustomSetting.Type.NUMBER,.9,"Hover Opacity"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.tree.IcicleType.CustomSettings.AGG_LEVEL,epiviz.ui.charts.CustomSetting.Type.STRING,"","Agg Level"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.tree.IcicleType.CustomSettings.NODE_SEL,
epiviz.ui.charts.CustomSetting.Type.STRING,"{}","Node Selection")])};epiviz.ui.charts.tree.IcicleType.CustomSettings={HOVER_OPACITY:"hoverOpacity",AGG_LEVEL:"aggLevel",NODE_SEL:"nodeSel"};epiviz.ui.charts.ChartIndexObject=function(a,b,c,d,e,f,g,h){epiviz.ui.charts.VisObject.call(this);this.id=a;this.keys=b;this.keyValues=c;this.values=d;this.seriesIndex=g;this.valueItems=e;this.measurements=f;this.cssClasses=h};epiviz.ui.charts.ChartIndexObject.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.VisObject.prototype);epiviz.ui.charts.ChartIndexObject.constructor=epiviz.ui.charts.ChartIndexObject;
epiviz.ui.charts.ChartIndexObject.prototype.getMetadata=function(a,b,c){return this.valueItems?this.valueItems[a][b][c]:null};epiviz.ui.charts.ChartIndexObject.prototype.metadataColumns=function(){return this.keys};epiviz.ui.charts.ChartIndexObject.prototype.dimensions=function(){return[1,1]};epiviz.plugins.charts.DiversityScatterPlot=function(a,b,c){epiviz.ui.charts.Plot.call(this,a,b,c);this._dispatch=d3.dispatch("hover","click");this._legend=this._chartContent=null;this._measurementsX=[];this._measurementsY=[];var d=this;this.measurements().foreach(function(a,b){0==b%2?d._measurementsX.push(a):d._measurementsY.push(a)});this._yLabel=this._xLabel="";for(a=0;a<Math.min(this._measurementsX.length,this._measurementsY.length);++a)0<a&&(this._xLabel+=", ",this._yLabel+=", "),this._xLabel+=
this._measurementsX[a].name(),this._yLabel+=this._measurementsY[a].name();this._colorLabels=[];this._initialize()};epiviz.plugins.charts.DiversityScatterPlot.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.Plot.prototype);epiviz.plugins.charts.DiversityScatterPlot.constructor=epiviz.plugins.charts.DiversityScatterPlot;
epiviz.plugins.charts.DiversityScatterPlot.prototype._initialize=function(){epiviz.ui.charts.Plot.prototype._initialize.call(this);this._svg.classed("scatter-plot",!0);this._chartContent=this._svg.append("g").attr("class","chart-content");this._legend=this._svg.append("g").attr("class","chart-legend")};
epiviz.plugins.charts.DiversityScatterPlot.prototype.draw=function(){epiviz.ui.charts.Plot.prototype.draw.call(this,void 0,void 0);this.drawScatter(this._lastRange,this._lastData.data,"sample_id",this._xLabel,"alphaDiversity")};
epiviz.plugins.charts.DiversityScatterPlot.prototype.drawScatter=function(a,b,c,d,e){this.xTag=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.ROW_LABEL];a=this._measurementsX[0].annotation();this._measurementsX=[];a=new epiviz.measurements.Measurement(this.xTag,this.xTag,"feature","ihmp","ihmp","ihmp",null,null,a,-1,1,[]);this._measurementsX.push(a);return this._drawCircles(b,this.xTag,e,c)};
epiviz.plugins.charts.DiversityScatterPlot.prototype._drawCircles=function(a,b,c,d){function e(a){a.sort(d3.ascending);var b=d3.quantile(a,.25);a=d3.quantile(a,.75);return[b,a]}var f=this,g=epiviz.ui.charts.Axis,h=Math.max(1,this.customSettingsValues()[epiviz.plugins.charts.DiversityScatterPlotType.CustomSettings.CIRCLE_RADIUS_RATIO]*Math.min(this.width(),this.height())),m=Math.max(Math.floor(h),1),l=this.margins(),p=this.width(),n=this.height(),t=epiviz.ui.charts.CustomSetting,v=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN],
q=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX],u=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.X_MIN],w=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.X_MAX];v==t.DEFAULT&&(v=this._measurementsY[0].minValue());q==t.DEFAULT&&(q=this._measurementsY[0].maxValue());var r=[];a.forEach(function(a){r.push(a[b])});var A=[];r.forEach(function(a){-1==A.indexOf(a)&&A.push(a)});console.log(A);this.xTickValues=A;a.forEach(function(a){var c=
A.indexOf(a[b]);a._xVal=c+1});var B=0,z=[];A.forEach(function(a){z[B]=[];z[B][0]=B;z[B][1]=[];B++});a.forEach(function(a){var d=A.indexOf(a[b]);z[d][1].push(a[c])});u==t.DEFAULT&&(u=0);w==t.DEFAULT&&(w=A.length+1);var t=d3.scale.linear().domain([u,w]).range([0,p-l.sumAxis(g.X)]),x=d3.scale.linear().domain([v,q]).range([n-l.sumAxis(g.Y),0]);this._clearAxes(this._chartContent);xLabelsPadded=[""];A.forEach(function(a){xLabelsPadded.push(a)});console.log(xLabelsPadded);this._drawAxes(t,x,xLabelsPadded.length,
15,this._chartContent,p,n,l,void 0,void 0,xLabelsPadded,void 0,void 0);for(var E={},G=[],F=1,y=0;y<a.length;++y){console.log(a[y]);var I=a[y]._xVal,C=a[y][c];if(I&&C){var H=sprintf("item data-series-%s",0),D=t(I),L=x(C),D=Math.floor(D/m)*m,L=Math.floor(L/m)*m,K=null;E[L]&&E[L][D]?(K=E[L][D],K.id+="_"+a[y][d],K.values[0]=(K.values[0]*K.valueItems[0].length+I)/(K.valueItems[0].length+1),K.values[1]=(K.values[1]*K.valueItems[1].length+C)/(K.valueItems[1].length+1),K.valueItems[0].push(a[y]),K.valueItems[1].push(a[y]),
K.valueItems[0].length>F&&(F=K.valueItems[0].length)):(K=new epiviz.ui.charts.ChartIndexObject(sprintf("scatter_%s_%s_%s_%s",I,C,0,a[y][d]),[d],a[y][d],[I,C],[[a[y]],[a[y]]],["_xVal",c],0,H),E[L]||(E[L]={}),E[L][D]=K,G.push(K))}}console.log("after loop");y=this._chartContent.select(".items");y.empty()&&(y=this._chartContent.append("g").attr("class","items"),a=y.append("g").attr("class","selected"),y.append("g").attr("class","hovered"),a.append("g").attr("class","hovered"));a=y.selectAll("circle").data(G,
function(a){return a.id});a.enter().insert("circle",":first-child").attr("id",function(a){return sprintf("%s-item-%s",f.id(),a.seriesIndex)}).style("opacity",0).style("fill-opacity",0).attr("r",0);a.each(function(a){var b=d3.select(this),c=f.colors().get(a.seriesIndex);b.attr("cx",l.left()+(a.values[0]-u)*(p-l.sumAxis(g.X))/(w-u)).attr("cy",n-l.bottom()-(a.values[1]-v)*(n-l.sumAxis(g.Y))/(q-v)).attr("class",a.cssClasses).style("fill",c)});a.transition().duration(1E3).style("fill-opacity",function(a){return Math.max(.6,
a.valueItems[0].length/F)}).style("opacity",null).attr("r",h);a.exit().transition().duration(1E3).style("opacity",0).attr("r",0).remove();a.on("click",function(a){console.log("click");console.log(a);f._deselect.notify(new epiviz.ui.charts.VisEventArgs(f.id()));f._select.notify(new epiviz.ui.charts.VisEventArgs(f.id(),a));f._dispatch.click(f.id(),null);d3.event.stopPropagation()});if(this._globalIndexColorLabels){h={};for(j=firstGlobalIndex;j<lastGlobalIndex;++j)h[this._globalIndexColorLabels[j]]=
this._globalIndexColorLabels[j];this._colorLabels=Object.keys(h);this._svg.selectAll(".chart-title").remove();this._svg.selectAll(".chart-title-color ").remove();h=this._svg.selectAll(".chart-title").data(this._colorLabels);h.enter().append("text").attr("class","chart-title").attr("font-weight","bold").attr("y",f.margins().top()-5);h.attr("fill",function(a,b){return f.colors().getByKey(a)}).text(function(a){return a});var M=0,N=[];this._container.find(" .chart-title").each(function(a){N.push(M);M+=
this.getBBox().width+15});h.attr("x",function(a,b){return f.margins().left()+10+N[b]});this._svg.selectAll(".chart-title-color").data(this._colorLabels).enter().append("circle").attr("class","chart-title-color").attr("cx",function(a,b){return f.margins().left()+4+N[b]}).attr("cy",f.margins().top()-9).attr("r",4).style("shape-rendering","auto").style("stroke-width","0").attr("fill",function(a,b){return f.colors().getByKey(a)}).style("stroke-width",0)}else{h=Math.min(this._measurementsX.length,this._measurementsY.length);
a=Array(h);for(j=0;j<h;++j)a[j]=sprintf("%s vs %s",this._measurementsX[j].name(),this._measurementsY[j].name());this._colorLabels=a}h=y;console.log(A);h.selectAll(".iqr-range").remove();h.selectAll(".whisker").remove();for(y=0;y<z.length;y++){a=z[y][1];console.log(a);d=[];d=e(a);console.log(d);m=1.5*(d[1]-d[0]);E=0;I=a.length-1;for(j=0;j<a.length;j++)if(a[j]<d[0]-m)E=j;else break;for(k=a.length-1;0<k;k--)if(a[k]>d[1]+m)I=k;else break;h.append("rect").attr("id","0").attr("class","iqr-range").style("opacity",
1).style("fill-opacity",.2).attr("x",l.left()+(.6+z[y][0]-u)*(p-l.sumAxis(g.X))/(w-u)).attr("y",n-l.bottom()-(d[1]-v)*(n-l.sumAxis(g.Y))/(q-v)).attr("width",t(.8)).attr("height",Math.abs(x(d[1])-x(d[0]))).attr("fill","#1E90FF");h.append("line").style("stroke","gray").attr("class","whisker").attr("x1",l.left()+(1+z[y][0]-u)*(p-l.sumAxis(g.X))/(w-u)).attr("y1",n-l.bottom()-(d[1]-v)*(n-l.sumAxis(g.Y))/(q-v)).attr("x2",l.left()+(1+z[y][0]-u)*(p-l.sumAxis(g.X))/(w-u)).attr("y2",n-l.bottom()-(a[I]-v)*(n-
l.sumAxis(g.Y))/(q-v));h.append("line").style("stroke","gray").attr("class","whisker").attr("x1",l.left()+(1+z[y][0]-u)*(p-l.sumAxis(g.X))/(w-u)).attr("y1",n-l.bottom()-(d[0]-v)*(n-l.sumAxis(g.Y))/(q-v)).attr("x2",l.left()+(1+z[y][0]-u)*(p-l.sumAxis(g.X))/(w-u)).attr("y2",n-l.bottom()-(a[E]-v)*(n-l.sumAxis(g.Y))/(q-v));h.append("line").style("stroke","gray").attr("class","whisker").attr("x1",l.left()+(.6+z[y][0]-u)*(p-l.sumAxis(g.X))/(w-u)).attr("y1",n-l.bottom()-(a[I]-v)*(n-l.sumAxis(g.Y))/(q-v)).attr("x2",
l.left()+(1.4+z[y][0]-u)*(p-l.sumAxis(g.X))/(w-u)).attr("y2",n-l.bottom()-(a[I]-v)*(n-l.sumAxis(g.Y))/(q-v));h.append("line").style("stroke","gray").attr("class","whisker").attr("x1",l.left()+(.6+z[y][0]-u)*(p-l.sumAxis(g.X))/(w-u)).attr("y1",n-l.bottom()-(a[E]-v)*(n-l.sumAxis(g.Y))/(q-v)).attr("x2",l.left()+(1.4+z[y][0]-u)*(p-l.sumAxis(g.X))/(w-u)).attr("y2",n-l.bottom()-(a[E]-v)*(n-l.sumAxis(g.Y))/(q-v))}return G};epiviz.plugins.charts.DiversityScatterPlot.prototype.colorLabels=function(){return this._colorLabels};
epiviz.plugins.charts.DiversityScatterPlot.prototype._drawAxesOld=function(a,b,c,d,e,f,g,h){epiviz.ui.charts.Plot.prototype._drawAxes(a,b,c,d,e,f,g,h,void 0,void 0,this.xTickValues,void 0,void 0);this._legend.selectAll("text").remove();a=this._measurementsX;var m=this;this._legend.selectAll(".x-measurement").remove();this._legend.selectAll(".x-measurement-color").remove();b=this._legend.selectAll(".x-measurement").data(a).enter().append("text").attr("class","x-measurement").attr("font-weight","bold").attr("fill",
function(a,b){return m._globalIndexColorLabels?"#000000":m.colors().get(b)}).attr("y",this.height()-this.margins().bottom()+35).text(function(a,b){return a.name()});var l=0,p=[];this._container.find(" .x-measurement").each(function(a){p.push(l);l+=this.getBBox().width+15});b.attr("x",function(a,b){return.5*(m.width()-l)+7+p[b]});this._legend.selectAll(".x-measurement-color").data(a).enter().append("circle").attr("class","x-measurement-color").attr("cx",function(a,b){return.5*(m.width()-l)+1+p[b]}).attr("cy",
this.height()-this.margins().bottom()+31).attr("r",4).style("shape-rendering","auto").style("stroke-width","0").style("fill",function(a,b){return m._globalIndexColorLabels?"#ffffff":m.colors().get(b)});a=["alphaDiversity"];this._legend.selectAll(".y-measurement").remove();this._legend.selectAll(".y-measurement-color").remove();b=this._legend.selectAll(".y-measurement").data(a).enter().append("text").attr("class","y-measurement").attr("font-weight","bold").attr("fill",function(a,b){return m._globalIndexColorLabels?
"#000000":m.colors().get(b)}).attr("y",this.margins().left()-35).attr("transform","rotate(-90)").text(function(a,b){return a});var n=0,t=[];this._container.find(" .y-measurement").each(function(a){t.push(n);n+=this.getBBox().width+15});b.attr("x",function(a,b){return-m.height()+.5*(m.height()-n)+12+m.margins().top()+t[b]});this._legend.selectAll(".y-measurement-color").data(a).enter().append("circle").attr("class","y-measurement-color").attr("cx",function(a,b){return-m.height()+.5*(m.height()-n)+
6+m.margins().top()+t[b]}).attr("cy",this.margins().left()-39).attr("transform","rotate(-90)").attr("r",4).style("shape-rendering","auto").style("stroke-width","0").style("fill",function(a,b){return m._globalIndexColorLabels?"#ffffff":m.colors().get(b)})};epiviz.plugins.charts.DiversityScatterPlot.prototype.transformData=function(a,b){void 0!=a&&(this._lastRange=a);void 0!=b&&(this._unalteredData=this._lastData=b);var c=new epiviz.deferred.Deferred;c.resolve();return c};epiviz.plugins.charts.DiversityScatterPlotType=function(a){epiviz.ui.charts.PlotType.call(this,a)};epiviz.plugins.charts.DiversityScatterPlotType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.PlotType.prototype);epiviz.plugins.charts.DiversityScatterPlotType.constructor=epiviz.plugins.charts.DiversityScatterPlotType;epiviz.plugins.charts.DiversityScatterPlotType.prototype.createNew=function(a,b,c){return new epiviz.plugins.charts.DiversityScatterPlot(a,b,c)};
epiviz.plugins.charts.DiversityScatterPlotType.prototype.typeName=function(){return"epiviz.plugins.charts.DiversityScatterPlot"};epiviz.plugins.charts.DiversityScatterPlotType.prototype.chartName=function(){return"Diversity Scatter Plot"};epiviz.plugins.charts.DiversityScatterPlotType.prototype.chartHtmlAttributeName=function(){return"diversity_scatter"};epiviz.plugins.charts.DiversityScatterPlotType.prototype.measurementsFilter=function(){return function(a){return epiviz.measurements.Measurement.Type.hasValues(a.type())}};
epiviz.plugins.charts.DiversityScatterPlotType.prototype.isRestrictedToSameDatasourceGroup=function(){return!0};epiviz.plugins.charts.DiversityScatterPlotType.prototype.minSelectedMeasurements=function(){return 2};
epiviz.plugins.charts.DiversityScatterPlotType.prototype.customSettingsDefs=function(){return epiviz.ui.charts.PlotType.prototype.customSettingsDefs.call(this).concat([new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.DiversityScatterPlotType.CustomSettings.CIRCLE_RADIUS_RATIO,epiviz.ui.charts.CustomSetting.Type.NUMBER,.015,"Circle radius ratio"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.ROW_LABEL,epiviz.ui.charts.CustomSetting.Type.MEASUREMENTS_ANNOTATION,
"name","Row labels"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.X_MIN,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Min X"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.X_MAX,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Max X"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MIN,epiviz.ui.charts.CustomSetting.Type.NUMBER,
epiviz.ui.charts.CustomSetting.DEFAULT,"Min Y"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MAX,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Max Y")])};epiviz.plugins.charts.DiversityScatterPlotType.CustomSettings={CIRCLE_RADIUS_RATIO:"circleRadiusRatio"};epiviz.plugins.charts.CustomScatterPlot=function(a,b,c){epiviz.ui.charts.Plot.call(this,a,b,c);this._legend=this._chartContent=null;this._measurementsX=[];this._measurementsY=[];var d=this;this.measurements().foreach(function(a,b){0==b%2?d._measurementsX.push(a):d._measurementsY.push(a)});this._yLabel=this._xLabel="";for(a=0;a<Math.min(this._measurementsX.length,this._measurementsY.length);++a)0<a&&(this._xLabel+=", ",this._yLabel+=", "),this._xLabel+=this._measurementsX[a].name(),this._yLabel+=this._measurementsY[a].name();
this._colorLabels=[];this._initialize()};epiviz.plugins.charts.CustomScatterPlot.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.Plot.prototype);epiviz.plugins.charts.CustomScatterPlot.constructor=epiviz.plugins.charts.CustomScatterPlot;
epiviz.plugins.charts.CustomScatterPlot.prototype._initialize=function(){epiviz.ui.charts.Plot.prototype._initialize.call(this);this._svg.classed("scatter-plot",!0);this._chartContent=this._svg.append("g").attr("class","chart-content");this._legend=this._svg.append("g").attr("class","chart-legend")};
epiviz.plugins.charts.CustomScatterPlot.prototype.draw=function(){epiviz.ui.charts.Plot.prototype.draw.call(this,void 0,void 0);this._variance_labels=this._lastData.pca_variance_explained;this.drawScatter(this._lastRange,this._lastData.data,"sample_id","PC1","PC2")};epiviz.plugins.charts.CustomScatterPlot.prototype.drawScatter=function(a,b,c,d,e){return this._drawCircles(b,d,e,c)};
epiviz.plugins.charts.CustomScatterPlot.prototype._drawCircles=function(a,b,c,d){var e=this,f=epiviz.ui.charts.Axis,g=Math.max(1,this.customSettingsValues()[epiviz.plugins.charts.CustomScatterPlotType.CustomSettings.CIRCLE_RADIUS_RATIO]*Math.min(this.width(),this.height())),h=Math.max(Math.floor(g),1),m=this.margins(),l=this.width(),p=this.height(),n=epiviz.ui.charts.CustomSetting,t=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MIN],v=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.Y_MAX],
q=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.X_MIN],u=this.customSettingsValues()[epiviz.ui.charts.Visualization.CustomSettings.X_MAX],w=this.customSettingsValues()[epiviz.plugins.charts.CustomScatterPlotType.CustomSettings.COLOR_BY];q==n.DEFAULT&&(q=this._measurementsX[0].minValue());t==n.DEFAULT&&(t=this._measurementsY[0].minValue());u==n.DEFAULT&&(u=this._measurementsX[0].maxValue());v==n.DEFAULT&&(v=this._measurementsY[0].maxValue());var r=d3.scale.linear().domain([q,
u]).range([0,l-m.sumAxis(f.X)]).nice(),A=d3.scale.linear().domain([t,v]).range([p-m.sumAxis(f.Y),0]).nice();this._clearAxes(this._chartContent);this._drawAxes(r,A,15,15,this._chartContent);for(var B={},n=[],z=1,x=0;x<a.length;++x){var E=a[x][b],G=a[x][c];if(E&&G){var F=sprintf("item data-series-%s",0),y=r(E),I=A(G),y=Math.floor(y/h)*h,I=Math.floor(I/h)*h,C=null;B[I]&&B[I][y]?(C=B[I][y],C.id+="_"+a[x][d],C.values[0]=(C.values[0]*C.valueItems[0].length+E)/(C.valueItems[0].length+1),C.values[1]=(C.values[1]*
C.valueItems[1].length+G)/(C.valueItems[1].length+1),C.valueItems[0].push(a[x]),C.valueItems[1].push(a[x]),C.valueItems[0].length>z&&(z=C.valueItems[0].length)):(C=new epiviz.ui.charts.ChartIndexObject(sprintf("scatter_%s_%s_%s_%s",E,G,0,a[x][d]),[d],a[x][d],[E,G],[[a[x]],[a[x]]],[b,c],0,F),B[I]||(B[I]={}),B[I][y]=C,n.push(C))}}a=this._chartContent.select(".items");a.empty()&&(a=this._chartContent.append("g").attr("class","items"),b=a.append("g").attr("class","selected"),a.append("g").attr("class",
"hovered"),b.append("g").attr("class","hovered"));a=a.selectAll("circle").data(n,function(a){return a.id});a.enter().insert("circle",":first-child").attr("id",function(a){return sprintf("%s-item-%s",e.id(),a.seriesIndex)}).style("opacity",0).style("fill-opacity",0).attr("r",0);a.each(function(a){var b=d3.select(this),c=e.colors().getByKey(a.valueItems[0][0][w]);null!=e._globalIndexColorLabels&&-1==e._globalIndexColorLabels.indexOf(a.valueItems[0][0][w])&&e._globalIndexColorLabels.push(a.valueItems[0][0][w]);
b.attr("cx",m.left()+(a.values[0]-q)*(l-m.sumAxis(f.X))/(u-q)).attr("cy",p-m.bottom()-(a.values[1]-t)*(p-m.sumAxis(f.Y))/(v-t)).attr("class",a.cssClasses).style("fill",c)});a.transition().duration(1E3).style("fill-opacity",function(a){return Math.max(.6,a.valueItems[0].length/z)}).style("opacity",null).attr("r",g);a.exit().transition().duration(1E3).style("opacity",0).attr("r",0).remove();a.on("click",function(a){console.log("click");e._deselect.notify(new epiviz.ui.charts.VisEventArgs(e.id()));e._select.notify(new epiviz.ui.charts.VisEventArgs(e.id(),
a));d3.event.stopPropagation()});if(this._globalIndexColorLabels){g={};for(j=firstGlobalIndex;j<lastGlobalIndex;++j)g[this._globalIndexColorLabels[j]]=this._globalIndexColorLabels[j];this._colorLabels=Object.keys(g);this._svg.selectAll(".chart-title").remove();this._svg.selectAll(".chart-title-color ").remove();g=this._svg.selectAll(".chart-title").data(this._colorLabels);g.enter().append("text").attr("class","chart-title").attr("font-weight","bold").attr("y",e.margins().top()-5);g.attr("fill",function(a,
b){return e.colors().getByKey(a)}).text(function(a){return a});var H=0,D=[];this._container.find(" .chart-title").each(function(a){D.push(H);H+=this.getBBox().width+15});g.attr("x",function(a,b){return e.margins().left()+10+D[b]});this._svg.selectAll(".chart-title-color").data(this._colorLabels).enter().append("circle").attr("class","chart-title-color").attr("cx",function(a,b){return e.margins().left()+4+D[b]}).attr("cy",e.margins().top()-9).attr("r",4).style("shape-rendering","auto").style("stroke-width",
"0").attr("fill",function(a,b){return e.colors().getByKey(a)}).style("stroke-width",0)}else{g=Math.min(this._measurementsX.length,this._measurementsY.length);a=Array(g);for(j=0;j<g;++j)a[j]=sprintf("%s vs %s",this._measurementsX[j].name(),this._measurementsY[j].name());this._colorLabels=a}this._colorLabels=[];Object.keys(e.colors()._keyIndices).forEach(function(a){"undefined"!=a&&"Max"!=a&&e._colorLabels.push(a)});this._svg.selectAll(".chart-title").remove();this._svg.selectAll(".chart-title-color ").remove();
g=this._svg.selectAll(".chart-title").data(this._colorLabels);g.enter().append("text").attr("class","chart-title").attr("font-weight","bold").attr("y",e.margins().top()-5);g.attr("fill",function(a,b){return e.colors().getByKey(a)}).text(function(a){return a});H=0;D=[];this._container.find(" .chart-title").each(function(a){D.push(H);H+=this.getBBox().width+15});g.attr("x",function(a,b){return e.margins().left()+10+D[b]});this._svg.selectAll(".chart-title-color").data(this._colorLabels).enter().append("circle").attr("class",
"chart-title-color").attr("cx",function(a,b){return e.margins().left()+4+D[b]}).attr("cy",e.margins().top()-9).attr("r",4).style("shape-rendering","auto").style("stroke-width","0").attr("fill",function(a,b){return e.colors().getByKey(a)}).style("stroke-width",0);return n};epiviz.plugins.charts.CustomScatterPlot.prototype.colorLabels=function(){return this._colorLabels};
epiviz.plugins.charts.CustomScatterPlot.prototype._drawAxes=function(a,b,c,d,e,f,g,h){epiviz.ui.charts.Plot.prototype._drawAxes.call(this,a,b,c,d,e,f,g,h);var m=this;this._legend.selectAll("text").remove();m=this;this._legend.selectAll(".x-measurement").remove();this._legend.selectAll(".x-measurement-color").remove();a=this._legend.selectAll(".x-measurement").data(["pca1"]).enter().append("text").attr("class","x-measurement").attr("font-weight","bold").attr("fill",function(a,b){return m._globalIndexColorLabels?
"#000000":m.colors().get(b)}).attr("y",this.height()-this.margins().bottom()+35).text(function(a,b){return a+" (% Variance Explained = "+m._variance_labels[0]+")"});var l=0,p=[];this._container.find(" .x-measurement").each(function(a){p.push(l);l+=this.getBBox().width+15});a.attr("x",function(a,b){return.5*(m.width()-l)+7+p[b]});this._legend.selectAll(".y-measurement").remove();this._legend.selectAll(".y-measurement-color").remove();a=this._legend.selectAll(".y-measurement").data(["pca2"]).enter().append("text").attr("class",
"y-measurement").attr("font-weight","bold").attr("fill",function(a,b){return m._globalIndexColorLabels?"#000000":m.colors().get(b)}).attr("y",this.margins().left()-35).attr("transform","rotate(-90)").text(function(a,b){return a+" (% Variance Explained = "+m._variance_labels[1]+")"});var n=0,t=[];this._container.find(" .y-measurement").each(function(a){t.push(n);n+=this.getBBox().width+15});a.attr("x",function(a,b){return-m.height()+.5*(m.height()-n)+12+m.margins().top()+t[b]})};
epiviz.plugins.charts.CustomScatterPlot.prototype.transformData=function(a,b){void 0!=a&&(this._lastRange=a);void 0!=b&&(this._unalteredData=this._lastData=b);var c=new epiviz.deferred.Deferred;c.resolve();return c};epiviz.plugins.charts.CustomScatterPlotType=function(a){epiviz.ui.charts.PlotType.call(this,a)};epiviz.plugins.charts.CustomScatterPlotType.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.PlotType.prototype);epiviz.plugins.charts.CustomScatterPlotType.constructor=epiviz.plugins.charts.CustomScatterPlotType;epiviz.plugins.charts.CustomScatterPlotType.prototype.createNew=function(a,b,c){return new epiviz.plugins.charts.CustomScatterPlot(a,b,c)};
epiviz.plugins.charts.CustomScatterPlotType.prototype.typeName=function(){return"epiviz.plugins.charts.CustomScatterPlot"};epiviz.plugins.charts.CustomScatterPlotType.prototype.chartName=function(){return"PCA Scatter Plot"};epiviz.plugins.charts.CustomScatterPlotType.prototype.chartHtmlAttributeName=function(){return"pca_scatter"};epiviz.plugins.charts.CustomScatterPlotType.prototype.measurementsFilter=function(){return function(a){return epiviz.measurements.Measurement.Type.hasValues(a.type())}};
epiviz.plugins.charts.CustomScatterPlotType.prototype.isRestrictedToSameDatasourceGroup=function(){return!0};epiviz.plugins.charts.CustomScatterPlotType.prototype.minSelectedMeasurements=function(){return 2};
epiviz.plugins.charts.CustomScatterPlotType.prototype.customSettingsDefs=function(){return epiviz.ui.charts.PlotType.prototype.customSettingsDefs.call(this).concat([new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.CustomScatterPlotType.CustomSettings.CIRCLE_RADIUS_RATIO,epiviz.ui.charts.CustomSetting.Type.NUMBER,.015,"Circle radius ratio"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.X_MIN,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,
"Min X"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.X_MAX,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Max X"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MIN,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,"Min Y"),new epiviz.ui.charts.CustomSetting(epiviz.ui.charts.Visualization.CustomSettings.Y_MAX,epiviz.ui.charts.CustomSetting.Type.NUMBER,epiviz.ui.charts.CustomSetting.DEFAULT,
"Max Y"),new epiviz.ui.charts.CustomSetting(epiviz.plugins.charts.CustomScatterPlotType.CustomSettings.COLOR_BY,epiviz.ui.charts.CustomSetting.Type.MEASUREMENTS_ANNOTATION,"name","Color By")])};epiviz.plugins.charts.CustomScatterPlotType.CustomSettings={CIRCLE_RADIUS_RATIO:"circleRadiusRatio",COLOR_BY:"colorBy"};epiviz.ui.charts.ChartFactory=function(a){this._config=a;this._types={};for(var b=this._size=0;b<a.chartTypes.length;++b)this.register(a.chartTypes[b])};epiviz.ui.charts.ChartFactory.prototype.size=function(){return this._size};epiviz.ui.charts.ChartFactory.prototype.registerType=function(a){a.typeName()in this._types||++this._size;this._types[a.typeName()]=a};
epiviz.ui.charts.ChartFactory.prototype.unregisterType=function(a){if(!(a.typeName()in this._types))return!1;--this._size;delete this._types[a.typeName()];return!0};epiviz.ui.charts.ChartFactory.prototype.register=function(a){a=epiviz.utils.evaluateFullyQualifiedTypeName(a);if(!a)return!1;this.registerType(new a(this._config));return!0};epiviz.ui.charts.ChartFactory.prototype.unregister=function(a){return(a=epiviz.utils.evaluateFullyQualifiedTypeName(a))?this.unregisterType(new a(this._config)):!1};
epiviz.ui.charts.ChartFactory.prototype.get=function(a){return a&&a in this._types?this._types[a]:null};epiviz.ui.charts.ChartFactory.prototype.foreach=function(a){for(var b in this._types)if(this._types.hasOwnProperty(b)&&a(b,this._types[b]))break};epiviz.ui.controls.Control=function(a,b,c){this._container=a;this._title=b||"";this._id=c||epiviz.utils.generatePseudoGUID(6)};epiviz.ui.controls.Control.prototype.initialize=function(){};epiviz.ui.controls.Control.prototype.id=function(){return this._id};epiviz.ui.controls.Control.prototype.title=function(){return this._title};epiviz.ui.controls.DataTable=function(a,b,c,d,e,f){epiviz.ui.controls.Control.call(this,a);this._columns=b;this._rows=c;this._rowsArray=[];var g=this;this._rows.foreach(function(a){g._rowsArray.push(a)});this._rowParser=d;this._multiselect=e||!1;this._showColumnSelector=f||!1;this._columnSelector=this._table=null;this._selectedIndices=[];this._selectedIndicesMap={};this._lastSelection=this._deselectList=this._selectList=null};epiviz.ui.controls.DataTable.prototype=epiviz.utils.mapCopy(epiviz.ui.controls.Control.prototype);
epiviz.ui.controls.DataTable.constructor=epiviz.ui.controls.DataTable;epiviz.ui.controls.DataTable.ColumnType={STRING:"string",NUMBER:"number",BOOLEAN:"boolean"};
epiviz.ui.controls.DataTable.prototype.initialize=function(){this._container.append('<div class="epiviz-data-table"><table style="width: 100%!important;"><thead></thead><tbody></tbody><tfoot></tfoot></table></div>');this._table=this._container.find("table");var a=this._table.find("thead"),b=this._table.find("tfoot"),c=this._table.find("tbody"),d=sprintf("<tr><th>%s</th></tr>",this._columns.join("</th><th>"));a.append(d);b.append(d);var e=this;this._rows.foreach(function(a){for(var b="",d=0;d<e._columns.length;++d)b+=
sprintf("<td>%s</td>",e._rowParser(a,e._columns[d]));c.append(sprintf("<tr>%s</tr>",b))});b=Array(this._columns.length);for(a=0;a<this._columns.length;++a)b[a]={type:"text",bRegex:!0,bSmart:!0};this._table.dataTable({bJQueryUI:!0,sDom:'<"H"lfr>Tt<"F"ip>',oTableTools:{sRowSelect:this._multiselect?"multi":"single",aButtons:["select_all"],fnPreRowSelect:function(a,b,c){return e._preRowSelect(this,a,b,c)},fnRowSelected:function(a){return e._select(this,a)},fnRowDeselected:function(a){return e._select(this,
a)}}}).columnFilter({aoColumns:b});b=-1;for(a=0;a<this._columns.length;++a)this._columns[a].isVisible&&++b,this._table.fnSetColumnVis(a,this._columns[a].isVisible),this._columns[a].defaultFilter&&(this._table.fnFilter(this._columns[a].defaultFilter,a,!0,!0),this._table.find("tfoot").find(sprintf("th:eq(%s)",b)).find("input").removeClass("search_init").val(this._columns[a].defaultFilter));this._container.find(".ui-buttonset").first().attr("style","position: absolute!important;");if(this._showColumnSelector){this._container.find(".fg-toolbar").first().append(sprintf('<div style="float: right; margin-right: 5px;"><label>Selected Columns: </label><select class="%s" multiple="multiple" style=""><option value="-1">All</option></select></div>',
"epiviz-data-table-column-selector"));this._columnSelector=this._container.find(".epiviz-data-table-column-selector");for(a=0;a<this._columns.length;++a)b=sprintf('<option value="%s"%s%s>%s</option>',a,this._columns[a].isVisible?' selected="selected"':"",this._columns[a].isFixed?' disabled="disabled"':"",this._columns[a].name),this._columnSelector.append(b);this._columnSelector.dropdownchecklist({width:"80px",firstItemChecksAll:!0,onComplete:function(a){e._selectColumns(a)}})}};
epiviz.ui.controls.DataTable.prototype.selectedIndices=function(){return this._selectedIndices||[]};epiviz.ui.controls.DataTable.prototype.selectedRows=function(){if(!this._selectedIndices)return[];for(var a=Array(this._selectedIndices.length),b=0;b<this._selectedIndices.length;++b)a[b]=this._rowsArray[this._selectedIndices[b]];return a};
epiviz.ui.controls.DataTable.prototype._preRowSelect=function(a,b,c,d){b&&(this._selectList=this._deselectList=null,b.shiftKey&&1==c.length?(this._deselectList=a.fnGetSelected(),this._lastSelection||(this._lastSelection=c[0]),this._selectList=this._getRangeOfRows(this._lastSelection,c[0])):(this._lastSelection=c[0],b.ctrlKey||b.metaKey||(this._deselectList=a.fnGetSelected(),d||(this._selectList=c))));return!0};
epiviz.ui.controls.DataTable.prototype._getRangeOfRows=function(a,b){var c=this._table.fnGetPosition(a),d=this._table.fnGetPosition(b),e=this._table.fnSettings(),c=$.inArray(c,e.aiDisplay),d=$.inArray(d,e.aiDisplay),f=[];if(0<=c&&0<=d)for(var g=Math.min(c,d);g<=Math.max(c,d);++g)f.push(e.aoData[e.aiDisplay[g]].nTr);return 0<f.length?f:null};
epiviz.ui.controls.DataTable.prototype._select=function(a,b){var c;this._deselectList&&(c=this._deselectList,this._deselectList=null,a.fnDeselect(c));this._selectList&&(c=this._selectList,!this._multiselect&&0<c.length&&(c=[c[c.length-1]]),this._selectList=null,a.fnSelect(c));c=a.fnGetSelected();var d=Array(c.length),e={},f;for(f=0;f<c.length;++f)d[f]=this._table.fnGetPosition(c[f]),e[d[f]]=!0;for(f=0;f<this._selectedIndices.length;++f)e[this._selectedIndices[f]]||(delete this._selectedIndicesMap[this._selectedIndices[f]],
this._selectedIndices.splice(f,1),--f);for(f=0;f<d.length;++f)this._selectedIndicesMap[d[f]]||(this._selectedIndicesMap[d[f]]=!0,this._selectedIndices.push(d[f]));return!0};epiviz.ui.controls.DataTable.prototype._selectColumns=function(a){var b={},c;for(c=0;c<a.options.length;++c)a.options[c].selected&&a.options[c].value&&(b[parseInt(a.options[c].value)]=!0);for(c=0;c<this._columns.length;++c)this._table.fnSetColumnVis(c,b[c]||this._columns[c].isFixed)};
epiviz.ui.controls.DataTable.Column=function(a,b,c,d,e,f){this.id=a;this.name=b;this.type=c;this.isVisible=!d;this.isFixed=e||!1;this.defaultFilter=f||""};epiviz.ui.controls.DataTable.Column.prototype.toString=function(){return this.name};epiviz.utils.IterableArray=function(a){this._array=a};epiviz.utils.IterableArray.prototype.foreach=function(a){for(var b=0;b<this._array.length&&!a(this._array[b]);++b);};epiviz.ui.controls.DatasourceGroupWizardStep=function(){this._data=this._dataTable=null};
epiviz.ui.controls.DatasourceGroupWizardStep.prototype.initialize=function(a,b){this._data=b;a.find(".epiviz-data-table").remove();var c=[new epiviz.ui.controls.DataTable.Column("datasourceGroup","Data Source Group",epiviz.ui.controls.DataTable.ColumnType.STRING)],d={};b.measurements.foreach(function(a){if(!b.dataprovider||b.dataprovider==a.dataprovider()){if(b.annotation)for(var c in b.annotation)if(b.annotation.hasOwnProperty(c)&&(!a.annotation()||a.annotation()[c]!=b.annotation[c]))return;d[a.datasourceGroup()]=
!0}});this._dataTable=new epiviz.ui.controls.DataTable(a,c,new epiviz.utils.IterableArray(Object.keys(d)),function(a){return a});this._dataTable.initialize()};
epiviz.ui.controls.DatasourceGroupWizardStep.prototype.next=function(){var a=this._dataTable?this._dataTable.selectedRows():[];if(0==a.length)return{error:"No rows selected"};this._data.datasourceGroup=a[0];return{data:new epiviz.ui.controls.VisConfigSelection(this._data.measurements.subset(function(b){return b.datasourceGroup()==a[0]}),this._data.datasource,this._data.datasourceGroup,this._data.dataprovider,this._data.annotation?epiviz.utils.mapCopy(this._data.annotation):this._data.annotation,this._data.defaultChartType,
this._data.minSelectedMeasurements,this._data.customData)}};epiviz.ui.controls.DatasourceGroupWizardStep.prototype.title=function(){return"Select Datasource Group"};epiviz.ui.controls.MeaurementsWizardStep=function(){this._measurements=this._data=this._dataTable=null};
epiviz.ui.controls.MeaurementsWizardStep.prototype.initialize=function(a,b){this._data=b;a.find(".epiviz-data-table").remove();var c=2,d=epiviz.ui.controls.DataTable.ColumnType,e=[new epiviz.ui.controls.DataTable.Column("id","Id",d.STRING,!0),new epiviz.ui.controls.DataTable.Column("name","Name",d.STRING,!1,!0),new epiviz.ui.controls.DataTable.Column("type","Type",d.STRING,!0),new epiviz.ui.controls.DataTable.Column("datasourceId","Data Source",d.STRING,!0),new epiviz.ui.controls.DataTable.Column("datasourceGroup",
"Data Source Group",d.STRING,!0),new epiviz.ui.controls.DataTable.Column("dataprovider","Data Provider",d.STRING,!0),new epiviz.ui.controls.DataTable.Column("formulaStr","Formula",d.STRING,!0)],f=[],g={};b.measurements.foreach(function(a){a=a.annotation();if(void 0!=a)for(var b in a)!a.hasOwnProperty(b)||b in g||(f.push(b),g[b]=!0)});f.sort();e=e.concat(f.map(function(a){var b=!1;0>=c&&(b=!0);c--;return new epiviz.ui.controls.DataTable.Column("[anno] "+a,a,d.STRING,b)}));this._measurements=b.measurements.subset(function(a){if(b.datasource&&
b.datasource!=a.datasourceId()||b.datasourceGroup&&b.datasourceGroup!=a.datasourceGroup()||b.dataprovider&&b.dataprovider!=a.dataprovider())return!1;if(b.annotation)for(var c in b.annotation)if(b.annotation.hasOwnProperty(c)&&(!a.annotation()||a.annotation()[c]!=b.annotation[c]))return!1;return!0});this._dataTable=new epiviz.ui.controls.DataTable(a,e,this._measurements,function(a,b){var c;if(epiviz.utils.stringStartsWith(b.id,"[anno] "))c="",void 0!=a.annotation()&&(c=a.annotation()[b.name]||"");
else switch(b.id){case "annotation":c=epiviz.utils.mapJoin(a.annotation(),": ","<br />");break;default:c=a[b.id]()}return 0===c||c?c:""},!0,!0);this._dataTable.initialize()};
epiviz.ui.controls.MeaurementsWizardStep.prototype.next=function(){var a=this._dataTable?this._dataTable.selectedRows():[];if(a.length<this._data.minSelectedMeasurements)return{error:"Minimum selected rows required is "+this._data.minSelectedMeasurements};for(var b=new epiviz.measurements.MeasurementSet,c=0;c<a.length;++c)b.add(a[c]);this._data.measurements=b;return{data:this._data}};epiviz.ui.controls.MeaurementsWizardStep.prototype.title=function(){return"Select Measurements"};epiviz.ui.controls.Wizard=function(a,b,c,d,e,f,g){epiviz.ui.controls.Dialog.call(this,a,b);this._steps=c;this._initialData=d;this._width=e;this._height=f;this._showTabs=g||!1;this._tabs=null;this._initialize()};epiviz.ui.controls.Wizard.prototype=epiviz.utils.mapCopy(epiviz.ui.controls.Dialog.prototype);epiviz.ui.controls.Wizard.constructor=epiviz.ui.controls.Wizard;
epiviz.ui.controls.Wizard.prototype._initialize=function(){var a=this;this._dialog=$("#"+this._id);this._dialog.append('<div id="wizardDialog" class="wizard-dialog"><div class="wizard-tabs"><ul class="wizard-tabs-title-list"></ul></div></div>');this._tabs=this._dialog.find(".wizard-tabs");for(var b=this._tabs.find(".wizard-tabs-title-list"),c=0;c<this._steps.length;++c)b.append(sprintf('<li><a href="#%s-tab-%s">%s</a></li>',this._id,c,this._steps[c].title())),this._tabs.append(sprintf('<div id="%s-tab-%s"></div>',
this._id,c));this._showTabs||(b.css("visibility","hidden"),b.css("position","absolute"));this._tabs.tabs({activate:function(b,c){a._tabActivate(c)},disabled:epiviz.utils.range(this._steps.length-1,1)});this._dialog.dialog({autoOpen:!1,resizable:!0,width:this._width||void 0,height:this._height||void 0,buttons:{Back:function(){var b=a._tabs.tabs("option","active");0!=b&&(a._tabs.tabs("option","disabled",epiviz.utils.range(a._steps.length-b,b)),a._tabs.tabs("option","active",b-1))},Next:function(){var b=
a._tabs.tabs("option","active"),c=a._steps[b].next();c.error?(new epiviz.ui.controls.MessageDialog("Error",{Ok:function(){}},c.error,epiviz.ui.controls.MessageDialog.Icon.ERROR)).show():(a._steps[b+1].initialize($(sprintf("#%s-tab-%s",a._id,b+1)),c.data),a._tabs.tabs("option","disabled",epiviz.utils.range(a._steps.length-b-2,b+2)),a._tabs.tabs("option","active",b+1))},Finish:function(){var b=a._steps[a._steps.length-1].next();b.error?(new epiviz.ui.controls.MessageDialog("Error",{Ok:function(){}},
b.error,epiviz.ui.controls.MessageDialog.Icon.ERROR)).show():(a._handlers.finish&&a._handlers.finish(b.data),$(this).dialog("close"))},Cancel:function(){a._handlers.close&&a._handlers.close();$(this).dialog("close")}},modal:!0});1<this._steps.length?(this._dialog.parent().find('button:contains("Finish")').button("disable"),this._dialog.parent().find('button:contains("Next")').button("enable")):(this._dialog.parent().find('button:contains("Finish")').button("enable"),this._dialog.parent().find('button:contains("Next")').button("disable"));
this._steps[0].initialize($(sprintf("#%s-tab-0",a._id)),this._initialData);this._dialog.css("overflow","visible")};
epiviz.ui.controls.Wizard.prototype._tabActivate=function(a){a=this._tabs.tabs("option","active");var b=this._dialog.parent().find('button:contains("Finish")'),c=this._dialog.parent().find('button:contains("Next")');a==this._steps.length-1?(c.button("disable"),b.button("enable")):(c.button("enable"),b.button("disable"));this._tabs.tabs("option","disabled",epiviz.utils.range(this._steps.length-a-1,a+1))};
epiviz.ui.controls.Wizard.prototype.show=function(){var a=this;this._dialog.dialog("open");this._dialog.dialog("option","position","center");this._dialog.dialog({close:function(b,c){$(this).remove();a._dialog=null}})};epiviz.ui.controls.Wizard.Step=function(){};epiviz.ui.controls.Wizard.Step.prototype.initialize=function(a,b){};epiviz.ui.controls.Wizard.Step.prototype.next=function(){};epiviz.ui.controls.Wizard.Step.prototype.title=function(){};epiviz.ui.controls.ComputedMeasurementsDialog=function(a,b,c,d){epiviz.ui.controls.Dialog.call(this,a,b);this._measurements=c;this._chartsMeasurements=d;this._measurementsList=this._maxTextBox=this._minTextBox=this._nameTextBox=this._idTextBox=this._expressionTextBox=null;this._addButtonProperties={text:!1,icons:{primary:"ui-icon ui-icon-plus"}};this._deleteButtonsProperties={text:!1,icons:{primary:"ui-icon ui-icon-trash"}};this._datasourceGroupMeasurements=this._selectedDatasourceGroup=this._tabs=
null;this._addTabs();this._addDialogContents();this._addDatasourceGroupTable(c)};epiviz.ui.controls.ComputedMeasurementsDialog.prototype=epiviz.utils.mapCopy(epiviz.ui.controls.Dialog.prototype);epiviz.ui.controls.ComputedMeasurementsDialog.constructor=epiviz.ui.controls.ComputedMeasurementsDialog;
epiviz.ui.controls.ComputedMeasurementsDialog.prototype.show=function(){epiviz.ui.controls.Dialog.prototype.show.call(this);var a=this;this._dialog&&(this._dialog.dialog("open"),this._dialog.dialog("option","position","center"),this._dialog.dialog({close:function(b,c){$(this).remove();a._dialog=null;a._handlers.close()}}))};
epiviz.ui.controls.ComputedMeasurementsDialog.prototype._addDatasourceGroupTable=function(a){var b=this,c=$(sprintf("#datasource-group-tab-%s",this._id)),d="",e={};a.foreach(function(a){a.type()==epiviz.measurements.Measurement.Type.FEATURE&&(a.datasourceGroup()in e||(e[a.datasourceGroup()]=[]),e[a.datasourceGroup()].push(a))});for(var f in e)e.hasOwnProperty(f)&&(d+=sprintf('<tr><td class="center">%s</td></tr>',f));c.append(sprintf('<table style="border-spacing:0; border-collapse:collapse; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: moz-none; -ms-user-select: none; user-select: none; width: 100%%;" class="computed-measurements-dialog-raw-table">%s</table>',
"<thead><tr><th>Data Source Group</th></tr></thead>"+d+"<tfoot><tr><th>Data Source Group</th></tr></tfoot>"));var g=c.find(".computed-measurements-dialog-raw-table").dataTable({bJQueryUI:!0,sDom:'<"H"lfr>Tt<"F"ip>',oTableTools:{sRowSelect:"single",aButtons:[],fnPreRowSelect:function(a,b,c){return!0},fnRowSelected:function(a){a=g.fnGetData(a[0]);b._selectedDatasourceGroup=a[0];b._datasourceGroupMeasurements=e[b._selectedDatasourceGroup]},fnRowDeselected:function(a){g.fnGetData(a[0])==b._selectedDatasourceGroup&&
(b._selectedDatasourceGroup=null,b._datasourceGroupMeasurements=null)}}});c.find(".DTTT_container").css("position","absolute")};
epiviz.ui.controls.ComputedMeasurementsDialog.prototype._addTabs=function(){var a=this;this._selectDialog().append('<div id="computedMeasurementsDialog" class="computed-measurements-dialog"><div class="computed-measurements-tabs"><ul>'+sprintf('<li><a href="#datasource-group-tab-%s">Data Source Group</a></li>',this._id)+sprintf('<li><a href="#formula-tab-%s">Expression</a></li>',this._id)+"</ul>"+sprintf('<div id="datasource-group-tab-%s"></div>',this._id)+sprintf('<div id="formula-tab-%s"></div>',
this._id)+"</div></div>");this._tabs=this._selectTabs();this._tabs.tabs({activate:function(b,c){a._tabActivate(c)}})};
epiviz.ui.controls.ComputedMeasurementsDialog.prototype._addDialogContents=function(){var a=this;this._selectDialog().dialog({autoOpen:!1,resizable:!0,width:"600",height:"550",buttons:{Back:function(){0!=a._tabs.tabs("option","active")&&a._tabs.tabs("option","active",0)},Next:function(){1!=a._tabs.tabs("option","active")&&a._tabs.tabs("option","active",1)},Add:function(){for(var b=epiviz.utils.ExpressionParser.parse(a._selectExpressionTextBox().val().trim()),c={},d=b.variables(),e=null,f=null,g=[],
h={},m=0;m<d.length;++m){var l=d[m];if(epiviz.utils.stringStartsWith(l,"{")&&epiviz.utils.stringEndsWith(l,"}")){var p=parseInt(l.substring(1,l.length-1)),l=a._datasourceGroupMeasurements[p];c[p]=l;if(null===e||e>l.minValue())e=l.minValue();if(null===f||f<l.maxValue())f=l.maxValue();if(l.metadata())for(p=0;p<l.metadata().length;++p)h[l.metadata()[p]]||(h[l.metadata()[p]]=!0,g.push(l.metadata()[p]))}}d=a._selectMinTextBox().val().trim();h=a._selectMaxTextBox().val().trim();e=d?parseFloat(d):e;f=h?
parseFloat(h):f;d=a._selectIdTextBox().val().trim()||epiviz.utils.generatePseudoGUID(5);b=new epiviz.measurements.Measurement(d,a._selectNameTextBox().val().trim()||"Unnamed ["+d+"]",epiviz.measurements.Measurement.Type.FEATURE,null,a._selectedDatasourceGroup,null,{referredMeasurements:c,expression:b},"any",null,e,f,g);c=a._datasourceGroupMeasurements.length;$(sprintf("#computed-measurement-measurements-%s",a._id)).append(sprintf('<div style="min-height: 30px; padding: 2px;"><div style="margin: 6px; float: left;">%1$s {<b>%2$s</b>}</div><div style="float: right;"><button id="delete-button-%2$s-%3$s" data-measurement="%2$s">Delete</button><button style="" id="measurement-button-%2$s-%3$s" data-measurement="%2$s">Insert %2$s</button></div></div>',
b.name(),c,a._id));$("#measurement-button-"+c+"-"+a._id).button(a._addButtonProperties).click(function(){a._addButtonClick($(this))});$("#delete-button-"+c+"-"+a._id).button(a._deleteButtonsProperties).click(function(){a._deleteButtonClick($(this))});a._datasourceGroupMeasurements.push(b);a._handlers.add(b)},Close:function(){a._handlers.close();$(this).dialog("close")}},modal:!0})};
epiviz.ui.controls.ComputedMeasurementsDialog.prototype._addButtonClick=function(a){a=a.data("measurement");var b=this._selectExpressionTextBox();b.val(b.val().trim()+" {"+a+"}")};
epiviz.ui.controls.ComputedMeasurementsDialog.prototype._deleteButtonClick=function(a){for(var b=a.data("measurement"),c=this._datasourceGroupMeasurements[b],d=0;d<this._datasourceGroupMeasurements.length;++d){var e=this._datasourceGroupMeasurements[d];if(null!=e&&e!==c&&e.isComputed()&&e.componentMeasurements().contains(c)){a=new epiviz.ui.controls.MessageDialog("Measurement cannot be deleted",{Ok:function(){}},"There are other measurements that depend on the one selected. Please delete those before deleting this.",
epiviz.ui.controls.MessageDialog.Icon.ERROR);a.show();return}}for(var f in this._chartsMeasurements)if(this._chartsMeasurements.hasOwnProperty(f)&&this._chartsMeasurements[f].contains(c)){a=new epiviz.ui.controls.MessageDialog("Measurement cannot be deleted",{Ok:function(){}},"There are charts using the selected measurement. Remove them from the workspace and then try again.",epiviz.ui.controls.MessageDialog.Icon.ERROR);a.show();return}this._datasourceGroupMeasurements[b]=null;a.parent().parent().remove();
this._handlers.remove(c)};
epiviz.ui.controls.ComputedMeasurementsDialog.prototype._tabActivate=function(a){if(0!=this._selectTabs().tabs("option","active")&&(a.newPanel.empty(),this._measurementsList=this._maxTextBox=this._minTextBox=this._expressionTextBox=this._nameTextBox=this._idTextBox=null,this._selectedDatasourceGroup)){a.newPanel.append(sprintf('<label for="computed-measurement-key-%1$s"><b>Id:</b></label> <input id="computed-measurement-key-%1$s" class="ui-widget-content ui-corner-all" style="padding: 2px;" type="text"/>&nbsp;<label for="computed-measurement-name-%1$s"><b>Name:</b></label> <input id="computed-measurement-name-%1$s" class="ui-widget-content ui-corner-all" style="padding: 2px;" type="text"/><br /><br /><div id="computed-measurement-measurements-%1$s" style="overflow: auto; max-height: 200px; border-style: solid; border-width: 1px; border-color: #999999;"></div><br/><label for="computed-measurement-min-%1$s"><b>Min:</b></label> <input id="computed-measurement-min-%1$s" class="ui-widget-content ui-corner-all" style="padding: 2px;" type="text"/>&nbsp;<label for="computed-measurement-max-%1$s"><b>Max:</b></label> <input id="computed-measurement-max-%1$s" class="ui-widget-content ui-corner-all" style="padding: 2px;" type="text"/><br/><div style="overflow: hidden; padding: 10px; padding-right: 20px; margin: 0px;"><textarea id="computed-measurement-expr-%1$s" class="ui-widget-content ui-corner-all" style="width: 100%%; height: 55px; padding: 5px; margin: 0; resize: none;"></textarea></div>',this._id));
a=this._selectMeasurementsList();for(var b="",c=0;c<this._datasourceGroupMeasurements.length;++c){var d=this._datasourceGroupMeasurements[c],e="";d.isComputed()&&(e='<button id="delete-button-%2$s-%3$s" data-measurement="%2$s">Delete %2$s</button>');b+=sprintf('<div style="min-height: 30px; padding: 2px;"><div style="margin: 6px; float: left;">%1$s {<b>%2$s</b>}</div><div style="float: right;">'+e+'<button style="" id="measurement-button-%2$s-%3$s" data-measurement="%2$s">Insert %2$s</button></div></div>',
d.name(),c,this._id)}a.append(b);for(var f=this,c=0;c<this._datasourceGroupMeasurements.length;++c)d=this._datasourceGroupMeasurements[c],d.isComputed()&&$("#delete-button-"+c+"-"+this._id).button(this._deleteButtonsProperties).click(function(){f._deleteButtonClick($(this))}),$("#measurement-button-"+c+"-"+this._id).button(this._addButtonProperties).click(function(){f._addButtonClick($(this))});this._selectIdTextBox().watermark("[auto]");this._selectNameTextBox().watermark("[auto]");this._selectMinTextBox().watermark("[auto]");
this._selectMaxTextBox().watermark("[auto]");this._selectExpressionTextBox().watermark("[expression; for example: {0} - {1}]")}};epiviz.ui.controls.ComputedMeasurementsDialog.prototype._selectDialog=function(){this._dialog||(this._dialog=$("#"+this._id));return this._dialog};epiviz.ui.controls.ComputedMeasurementsDialog.prototype._selectExpressionTextBox=function(){this._expressionTextBox||(this._expressionTextBox=$("#computed-measurement-expr-"+this._id));return this._expressionTextBox};
epiviz.ui.controls.ComputedMeasurementsDialog.prototype._selectIdTextBox=function(){this._idTextBox||(this._idTextBox=$("#computed-measurement-key-"+this._id));return this._idTextBox};epiviz.ui.controls.ComputedMeasurementsDialog.prototype._selectNameTextBox=function(){this._nameTextBox||(this._nameTextBox=$("#computed-measurement-name-"+this._id));return this._nameTextBox};
epiviz.ui.controls.ComputedMeasurementsDialog.prototype._selectMinTextBox=function(){this._minTextBox||(this._minTextBox=$("#computed-measurement-min-"+this._id));return this._minTextBox};epiviz.ui.controls.ComputedMeasurementsDialog.prototype._selectMaxTextBox=function(){this._maxTextBox||(this._maxTextBox=$("#computed-measurement-max-"+this._id));return this._maxTextBox};
epiviz.ui.controls.ComputedMeasurementsDialog.prototype._selectTabs=function(){this._tabs||(this._tabs=this._dialog.find(".computed-measurements-tabs"));return this._tabs};epiviz.ui.controls.ComputedMeasurementsDialog.prototype._selectMeasurementsList=function(){this._measurementsList||(this._measurementsList=$("#computed-measurement-measurements-"+this._id));return this._measurementsList};epiviz.ui.tutorials=function(){this._tutorialList=[{name:"Epiviz Overview",id:"tut_epiviz_overview",tutorial:[{target:"body",content:"<p class='intro-header'>Welcome to Epiviz Genomic Browser!<br></p><p class='intro-text'>This tutorial will walk you through the functionality available in Epiviz.</p>",position:"center"},{target:"#intro-navigation",content:"<p class='intro-text'>The navigation section of Epiviz lets you select a chromosome and explore the genome. Options are available to move left/right and zoom in/out.</p><p class='intro-text'>The settings icon allows you to control the navigation parameters.</p>",
position:"right",buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:"#search-box",content:"<p class='intro-header'>Use the search input to look for a specific gene or target.</p><p class='intro-text'>This will navigate Epiviz to the selected gene location and update the workspace with the new data.</p>",position:"right",buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:"#vis-menu-button",content:'<p class="intro-text">Choose from a list of available data sources, measurements or chart types to add visualizations to the Epiviz Workspace.</p>',
position:"right",buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:"#intro-workspace",content:'<p class="intro-header">managing workspaces.</p><p class="intro-text">If you are logged in, you will be able to save your Epiviz analysis and workspaces.You will also be able to retrieve them at a later time from your account.</p>',position:"right",buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:"#login-link",content:'<p class="intro-text">Please login to save and manage Epiviz workspaces.</p>',
position:"left",buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:"body",content:'<p class=\'intro-header\'>Thank you for using Epiviz!</p><p class="intro-text">If you would like to give us some feedback or stay informed with updates, Please visit the <a target="_blank" href="http://epiviz.github.io/">Epiviz webpage</a>.</p>',position:"center"}]},{name:"Data Visualization and Controls",id:"tut_data_controls",tutorial:[{target:"body",content:"<p class='intro-header'>Welcome to Epiviz Genomic Browser!<br><br>Data visualization tutorial<br></p><p class='intro-text'>This tutorial will help create/add new data visualizations to the Epiviz workspace and controls available for each visualization.</p>",
position:"center"},{target:"#vis-menu-button",content:'<p class="intro-text">The Data Visualizations button helps users add new charts to the workspace.</p><p>Users have the option to choose data sources and measurements to add to the workspace.</p>',position:"right",onHide:function(a,b,c,d){$("#vis-menu-button").button().trigger("click")},showOverlay:function(){},buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:"#vis-menu",content:'<p class="intro-text">Choose the type of chart to add to your workspace. We choose scatter plot to continue with the tutorial</p>',
position:"right",onShow:function(a,b,c){},onHide:function(a,b,c,d){$("#plot-menu-add-scatter").trigger("click")},showOverlay:function(){},buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:function(){var a=$("#wizardDialog").parent().attr("id");return $("#"+a).parent()},content:'<p class="intro-text">This window lets you choose form a list of data sources and the measurements available from each data source to add to your Epiviz workspace</p><p>We selected the first data source in the table or choose a data source from the list.</p>',
showOverlay:function(){},onShow:function(a,b,c){$("#wizardDialog table tbody tr:first").trigger("click")},onHide:function(a,b,c,d){$('.ui-button:contains("Next")').trigger("click")},position:"right",buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:function(){var a=$("#wizardDialog").parent().attr("id");return $("#"+a).parent()},content:'<p class="intro-text">After choosing a data source, the next tab lists all the measurements (or features) available from this data source. If you have any computed measurements for this data source, they will be added to this list.</p><p>To add a plot to the workspace, pick a few measurements and select finish on this window. </p>',
showOverlay:function(){},position:"right",onShow:function(a,b,c){},onHide:function(a,b,c,d){a=$("#wizardDialog").parent().attr("id");$("#"+a).dialog("close")},buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:"#feature-view",content:'<p class="intro-text">Visualizations are added to the workspace based on the type of chart. </p><p>Brushing is implemented on all the plots. When you hover over a data point, it highlight that region in the gene on all the visualizations.</p>',position:{top:"24em",
left:"14em"},showOverlay:function(){},onShow:function(a,b,c){$('button:contains("Remove"):first').css("display","block")},buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:function(){return $('button:contains("Remove"):first')},content:'<p class="intro-text">Removes the plot from the workspace</p>',position:"left",showOverlay:function(){},className:"anno-width-175",onShow:function(a,b,c){$('button:contains("Save"):eq(1)').css("display","block")},onHide:function(a,b,c,d){$(b).css("display",
"none")},buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:function(){return $('button:contains("Save"):eq(1)')},content:'<p class="intro-text">Save a plot to your local machine (image, pdf)</p>',position:"left",showOverlay:function(){},className:"anno-width-175",onShow:function(a,b,c){$('button:contains("Custom settings"):first').css("display","inline-block")},onHide:function(a,b,c,d){$(b).css("display","none")},buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:function(){return $('button:contains("Custom settings"):first')},
content:'<p class="intro-text">Change chart display properties and aggregation methods for grouping.</p>',position:"left",showOverlay:function(){},className:"anno-width-175",onShow:function(a,b,c){$('button:contains("Code"):first').css("display","inline-block")},onHide:function(a,b,c,d){$(b).css("display","none")},buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:function(){return $('button:contains("Code"):first')},content:'<p class="intro-text">Edit code to redraw the chart on the workspace.</p>',
position:"left",showOverlay:function(){},className:"anno-width-175",onShow:function(a,b,c){$('button:contains("Colors"):first').css("display","inline-block")},onHide:function(a,b,c,d){$(b).css("display","none")},buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:function(){return $('button:contains("Colors"):first')},content:'<p class="intro-text">Choose colors for data points on the plot</p>',position:"left",showOverlay:function(){},className:"anno-width-175",onShow:function(a,b,c){$('label:contains("Toggle tooltip"):first').css("display",
"block")},onHide:function(a,b,c,d){$(b).css("display","none")},buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:function(){return $('label:contains("Toggle tooltip"):first')},content:'<p class="intro-text">Toggle tooltips for data points</p>',position:"right",showOverlay:function(){},className:"anno-width-175",onHide:function(a,b,c,d){$(b).css("display","none")},buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:"body",content:'<p class=\'intro-header\'>Thank you for using Epiviz!</p><p class="intro-text">If you would like to give us some feedback or stay informed with updates, Please visit the <a target="_blank" href="http://epiviz.github.io/">Epiviz webpage</a>.</p>',
position:"center"}]},{name:"Computed Measurements",id:"tut_comp_measurements",tutorial:[{target:"body",content:"<p class='intro-header'>Welcome to Epiviz Genomic Browser!<br>Compute Measurements Tutorial<br></p><p class='intro-text'>This tutorial will help you create new measurements (derived from existing measurements) and generate plots to add to the workspace.</p>",position:"center"},{target:"#computed-measurements-button",content:"<p class='intro-text'>The computed measurements button helps users add new measurements to data sources</p>",
position:"right",onShow:function(a,b,c){$("#computed-measurements-button").button().trigger("click")},buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:"#computedMeasurementsDialog",content:"<p class='intro-text'>This tab lets you choose a data source where you will create a new measurement.</p><p>We choose the first data source in the list or choose any data source.</p>",position:{top:"20em",left:"1em"},showOverlay:function(){},onShow:function(a,b,c){$("#computedMeasurementsDialog table tbody tr td:first").trigger("click")},
buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:"#computedMeasurementsDialog",content:"<p class='intro-text'>The measurements tab lists all available measurements from the selected data source (including previously created computed measurements).</p><p>Use the buttons next to each measurement to add to the expression window</p>",position:{top:"20em",left:"1em"},showOverlay:function(){},onShow:function(a,b,c){$('.ui-button:contains("Next")').trigger("click")},buttons:[AnnoButton.BackButton,
AnnoButton.NextButton]},{target:"#computedMeasurementsDialog",content:"<p class='intro-text'> After choosing measurements, use mathematical operators to evaluate the expression.</p><p><a target='_blank' href='https://silentmatt.com/javascript-expression-evaluator/'>supported operators</a> </p>",position:{top:"33em",left:"1em"},showOverlay:function(){},buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:"#computedMeasurementsDialog",content:"<p class='intro-text'>After adding a computed measurement, use the data visualization button to plot the measurement to your workspace.</p><p>To learn how to add new plots to the workspace, please use the Epiviz data visualization tutorial.</p>",
position:{top:"10em",left:"1em"},showOverlay:function(){},onHide:function(a,b,c,d){a=$("#computedMeasurementsDialog").parent().attr("id");$("#"+a).dialog("close")},buttons:[AnnoButton.BackButton,AnnoButton.NextButton]},{target:"body",content:'<p class=\'intro-header\'>Thank you for using Epiviz!</p><p class="intro-text">If you would like to give us some feedback or stay informed with updates, Please visit the <a target="_blank" href="http://epiviz.github.io/">Epiviz webpage</a>.</p>',position:"center"}]}]};epiviz.ui.PrintManager=function(a,b,c,d){this._containerId=a?a:"pagemain";this._fName=b?b:"epiviz_"+Math.floor($.now()/1E3);this._fType=c?c:"pdf";this._workspaceId=d};
epiviz.ui.PrintManager.prototype.print=function(){function a(a){$(a).find(".domain").each(function(){$(this).css({fill:"none","stroke-width":"1px",stroke:"#000000","shape-rendering":"crispEdges"})});$(a).find(".gene-name").each(function(){$(this).remove()});epiviz.utils.stringContains($(a).attr("id"),"track-lines")&&$(a).find(".lines path").each(function(){$(this).css({fill:"none"})});$(a).find("text").each(function(){$(this).css({"font-size":"11px"})});$(a).find(".chart-legend").each(function(){$(this).css({border:"none",
background:"transparent"})})}var b=this,c=$("#"+b._containerId),d=c.find("svg");d.each(function(){var b,c;b=document.createElement("canvas");b.className="tempCanvas";a(this);c=(new XMLSerializer).serializeToString(this);c=c.replace(/xmlns=\"http:\/\/www\.w3\.org\/2000\/svg\"/,"");canvg(b,c,{useCORS:!0,renderCallback:function(){$(b).insertAfter(this);$(this).hide()}})});html2canvas(c,{timeout:0,width:c[0].scrollWidth+200,height:c[0].scrollHeight+200,useCORS:!0}).then(function(a){var e=a.getContext("2d");
e.mozImageSmoothingEnabled=!1;e.imageSmoothingEnabled=!1;var e=b._fName+"."+b._fType,g=b._fType,h=a.toDataURL("image/png");"pdf"==g?(a=new jsPDF("l","px",[.6*(1400<c[0].scrollWidth?c[0].scrollWidth:1400),.65*(1E3<c[0].scrollHeight?c[0].scrollHeight:1E3)]),g="http://epiviz.cbcb.umd.edu/4/?ws="+b._workspaceId,a.addImage("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAAAyCAYAAABIxaeCAAASqElEQVR4nO2de5QU1Z3Hv/Wurn5M9wxvGBwGBUVF4hBFgwkaZOPukRBdGBaDkpUdY7LGaDxCEjUxehSMxizHF8QHMasug9mYaKJBRKMm7LKALwQxMCAPcXAe/Ziuqdet2j+qi+npqemZ7q7u6eH055w6Z7rurXtv99S37r2/3+/eouoa1++nYb3GmOoWXj62JV417TiGIf6WZpGtOfMCwgUu0rmqGSbFTTZpZiRtkpE662c5ImsmzXXQpn4QwE7a1LYInbs2x0fNjg1luw89s2goq69QplC1SzZYJz5ZxGIs/QPaNLbQlvEnoXPXX+KjZmtD2L6s+FuaeTJh7nxCc4tNmr+M0IKUy/WMqRmAuYkzkk/y0T0vxEfNJsVqqwesyPg74pJnJYDVpWlOhWwU+sDtLcwMGFProE3tGdoyH1S50IGCavKQquj7I5TApBsNWmgiND/KizIZUz3AEfk+vvPDJ8vkYVQPYCH6F6EbFWGWCYUKk86WSGi+WmcDN6hc4GNY5Klg8sCpBdVWOAGOyPd0VZ1xQGWDt3klSgAgtDBJ4SKPJmsa9opax1COLyMA1gLYD2AVBi/KcmUu7O9iAeiA/bCpMADs4LLRLCgsk6UJSwQjtkbs+uTOWHh6V3Gb1htBjzeqXOAXOiON6y8PYyqttGnsBqwDAN3JmIpMGJ8AIGxS9DhCcWeBYuqy1UMYXx1hfBs4I/ltXo9+O+kb/7HX3yULTbBFebIQAdCMnoeL8/lSAJtL3JZVsEcfO1L1d5a4/pwYpDBtCMXxhK26xQhNvTIgH1reJU3cUqyGOQTkwyNUvvoRlQv1edIyphZlTeUlg/H9WVTb3k6KYw+StDGADn+f8vzKsTEKXzOf0OwCAHMBmnOrV2f9F5s0926wa9/Pdv/hxz+vXbKh2PPPZgzcm2xGzw29DmV+cwFogHuP34DSC7Mpre4mlPmQP+scMysWMUU9tlqQj9wRC083PG4XAEBSWy9TufCThBbG9Jw1TcFIvgqLPEbtf+Fl5fRlar7lhzreGZesOr0JFHN9tmGxpLa+xcX3L42NvPCTfOvKQgTAq7BvGDdaYIuwrG+kfojAHsZmivOk7zELNv6c8Y37r9TZqikmRTWAYubojFSTSwEM6d4qah2NSd/4wwW1JA1/SzNLJsy9W+GqbgXFUPZZUxOMrv9kiLJaFkZ5OryU1FYfYXxNKhtYCdBj3PJwRI5yRvIaWRj5Bw+rHkiU18EW5XBmLmxRNMAWw3UANg5pi0qAF1bZEx8OP9tIn7bwsRmEYv/FpPnFhBYmDKYQxtRaBa3jSlkc89eCWgMg2LV/lOwb9zxhfBfZZ0xIavvvGC16ayJ42r5Cy8+GqHUEFL56JSxyCyhG6JPBIpZfPX7vR//9/Ts8Gtr2N3xtgf1Ub/GgjgpDgKfCTMff0kyTCV+do7P+fyc0Px+gmexFmaqoRb+r8NVP5NsYv3JshsJX/57QwsRUme/4ldabk+LYN/ItMx9EreNUnfU/TGhhnlu6pLS+TLdubew6ZUGigGpWwO5JMmkBMBPlP3+skIWiuUuS9YtMha/ZQmjxCn/3p1M5I/kEYOpZihIUPvy4YMTvOvxsI5VrQ0St/QpFGPE2oYWJsIgiGImbAXpmqUUJAApfve/gf139NcbUrgPMPuKTxdGX6eMv+VtV27ZJeVYRQe+AAYdODAOLYYXik9WP6ZD0Tdivs/7lweQn0xhTa4btk3ItTmVDt9U3PvVk6OgmV2tnJoefbaQ4o+s2hQs/TyjOz5jadlHvbFDZ4IMAzMF+Ea+pXbLBIjS/jiHqOQzp3pqZrrKhs7qqp28V9c7+5ofZyBa5MxTDVyeQwfE3ph/7U2kV/2MJGZQwHRL+SfsIzTeKWvtFjKl80F8+nZGWdY+Z/UJV27asIXKho5u4Uxeu/bXOBu4CRYEx1XsJzV+g8CN259KuYkIY3wHxkxe/zBH5XmQ8KAgtjtbZwOui1v7VHIqMoMd0n45jfS0lK2A7/ZthD6vrXfLUp9KaU3nd2p6NV9Fb6K9mpG9Pnd+eY7n9EYHdTgu9RyWrUueaB7g288E02KMD7qOgvMhJmA4KX/NX/7E3GwQj9kNYRHHLozPSP3ZFzn45FN8Tckuv6tjp7x79pd+rXHgpgChHlAWEFn4EoCiul0JI1i8ydEb6kai1L2BMLZ6eRiguqHOhP4lax2B7lIVw7y1L6Q6phy2EXCOLnKik7XAXcT4437sB/Vunc6EJdjs7UdoHXQT27znXi8LyEiYAxMfP01W2apWoR7/ImOp7bnkI4/ty0l+3JSAfHpF+PhT/qEauOv01nfVfxpjqXsFInKczkpduiKKg8DUvBmK7ZwlGYm/6eUJxvMJVPScYsW8Oohi3f1wnSudCaIAtrEJE4EUZDhvRM3z3Yrjs9OibkftcvRMAleexI1WGF79J/sJ0UPiaXYHOD84XjPgatykhYXwN3eLINyWldRwA+Ls/HZ0M1L2hM9L5HJHflLo/vVBlg38vtB2lIhaZsUdo3Xq+YMT/2CuBYhiVrVov6NFrByjC7R+Xz02UDw2wh5JuveQ6AIvQ94ZbCfeHhuOD9aLndMovVJhz0dOeUvpKG9Lq9SRwomBhAkCsZqaqsqEbRS26GBbpE0NLaPEMlY+8LSmtkylLNwBYgpF4TorunpfwT+rwog2lJD5+Xow99Mp8zkjck/EwYlQ2+CtBj17fz6URuN/IO1zOFYO16CvKHQAmo3/H/2rYgnXzqzqxr4Xi1OusqMkX59oWlE6Y9eiJB94Ij/6XngjTQeGrN0jq57MYU+kTukZofpLKh9/Suaox/rbtX9jXvPyqWM3MvMPphppk/SJTZ4M/9iut19rrOlNQDKVy4YcFPXqDy2X9zeccw0ShRzbjwwr07a03w/aZDsYSvBnu4nRiTwthB3puaC+EWcq5ZTNsce6A/XDzBE+FCQCyOOZDqfvYLMZUt2WmEVoYazDSm1r49Ia8Y3TLjKQ49klJPnw5YyrpIwVK5UJrBD36w4zsXhlM8iFTtJ2we8JccCKSMofdXlgj04ez+fxOjtEnvaxi44RTtsD+LT2bjnguTABI+Cd9JiU/uZgj8kuZafYaz9BrotZ+STHqHgoSgcmvBGIffYUzEp/1nKWhcuF7BD16fz4BFx7jZgleifxupBb0tSAXOgRFqkynPfmU5VyzGaXxBTsWWOcB52mdRREmACSCU2Qpse8bjKU/k5lGaD5guxjav16s+ktNLDJjp9T+7gWCHt+Tfl7lwj+oa1z/XCi+x4ehi33NtAQX6kpwu9YrCy2Q+9C4Hj3fsRS9ZRN6RgkrUQQbQdGECQCx8HSDfv/RazgjsT4zjVCcoHOh34pa29XFbEMx8XcfCXFGcpNgJDZw761hYqMvOih8vm22oEffSs9HGF9jt/+UvwhGvO8CUZvJyN9Mn3705wt1m1sWQif63oxe+O/SjUC5lJdu9Cn2/HIuehazryxWfUUVJgDo53yP6GzwXwUj9lhmGqE4RuEi6zkjcXOx2+E1ghGbqAij3tZZ/6Um6LFS7WwRAOLj5nawhzfNE7WOXk9unZG+qLKh1wGz1aU4T3xfWcics3nxhM8sw4stUNKHobkMZ50etti9pWOBBYq8Rrbowkxh7Wtu+o5gxH/ZJ4ViKJ0NPiAYiYe499YMsIKlPBC1tvMMRvpfQvNnc0byZSn64ddi1ecmnfRk/SLl789fv1hSjz+Y4U6pYUzdLUzRk2iRLGSKxgsjReaw3CvDltMD5RJJVQrfZfo2KZvhoQXWjZy2FimE2iUbLBW4SdCjXSoXui3zmaCywe9yZy2vFbWOqxS+uqT7CeWCYMSvUfgRjwLwCUbiN0Lr1mtj4+f1WXVTu2SDKQM3S8pnB2Vh5C9AMamHDhVwKXYh8jfGDMRQWoLzYR16gvybMPBQ0RFwusulGDTDHtk4OyC48sz/3AUAeGvyXdnKWgbglLTPd6bOrXdOlKrHPIHKhW8XtegNgNlnobHOSPN1NrBVUluHeje+PviVYyJH5LUqG1oPwCepnz+wr3n5NXEXUaYji2PWSFrbQlhEBgBC8xSAZEa2CIbf6o1MwXtl2OpEzxx4oN8kgtIYfdam6nHcIoUwA8BTAH6aOuoAhAE8mJ6p5MIEAIWvfkjUOhYzlt4nAJ7Q/FkqF/k/Ueu4bCja5kZAPjRT4cI7dEZqYiydiFrHDbIw8pbB+mJlYfTvBCNxMSxyHLD363XJNty2qsycF3tpcXZENtelnnRKEbC+IlWPV26RdAFGAdwE4EbY4jzBkAgTABR+xPOi2nYJY2qfZaYRmg8rfPVLHJHvr2rf3neLjxIRTHzMiXrnnd2+sVsJ45vGmFpcVI5frvDVD+ValsqFtzGmNosxtY8IzdcCZuZw3Vm5MRyoR1/BeDmMHGxgu5O2EcWZBixEzy4T16Hw7zgndTg4NpfvZ2YcMmECQFIcu1XQY+dxRH7XJZnWGekHXZGztola25mlbpuktn5F9te9o3CROwjFsYypfOxTW2clfeNfzrdMwvgOCFrHhRyRtzBEddvt3VmwXO64tdHroeRAge3py8SKMYytR2+3iBd1PJX290EA/wG7Bw1nZhxSYQKALIw8LMpHvsQReb1bOqHF6Qo/YidnJFdXRd93M5x4ir/76BSOyL+VhdGvE5o/EwAEI/HHQPzj87t8tXsGun4gZHFMp5TY9w+gmF8zlr7XJcsquO8FVC64xcZuhveGF2do2l9UUbrv0uutMJ2VMxF45xZZBns+6XAnbEEuc8s85MIE7CghnZG+JWrt/8ZYerdLFl5n/bd2haZ+JBiJb/pbmj23Jvu7j0zjjMTTSd/YD3VGugIAxVi6IWptK/c1L788Fp4e9aquWHi6QWj+Zto07odF3LbiXAHv1jt6SbofL51i+PPSBZdNmMWYWzqB6V65RTKNO+/CtsDW9XdBWQjTQeFrHpfkIzMZU9nplk5oYbzKBn+j1C3YK+jR71RF3xcLqc/f0syLWvuVnJHclPSN26WzwaUAzQIAY6oHReX4xQo/YnWxAu51xve4/Z4U8x2XZGcxcjOK4+d0RDZYd8pCuO9csBHF27w53QiUbhhrSrWjGEYfxwKb1S2SI5nGnZsGuqCshAkACf+k3VLy0CxBj97NWLrrW7cIzderXPjhrtDUY5J6/Olg1/75/pbmQYm06vO/jRW19kbBiD+t1C1oVfia53XWfylAOxtLgyPyE/6ulnOSvvFve/fN3FGEke8B9Lnofw6zED375ji7iRe6zCq97P2p8lfAXaQrUunp7yBx8HSpkwvOayAy90lKD1j30ujj/LbOKhovCKO3ceeN1JGVkgUY5EIiOEUHcLu/++gGjY+s1RnpQrd8hBbCsjBqKYRRSxlpgiYYsd0APqBM8ilgRQHLsihWMhgpDJhTAHpqbMT5k0C5BxgxpnZA0KPfk4VRL+mhMwbV1lz2D514VdY1xYsw8EuF0o0ubvnyfQ3fXPTsmD5YSvVyno2wf5eFsL9bsQLWnS1Fvd5CNNO4863BXFSWwnRI+sbvOvxs4+zT/vmRxTrrX0VocWJ/eQkt8IQWZsB24OYEY2px2jJ+7mvf+UB81Gy3OW6pWAe7F1iF8g442Ai7pyzFdijrYAuzAfZv4sy7vd6lwFka1wLvfLJ16G3cWQ/bGjsgZTeUzaR2yQZL4WueC3TumiIYsRsY0n3Uq7I5In8uGImfSfKROp2R7h5iUTo40SWTUbwwPSC/DcB2wG6bp4uCB1GnM4dtRs+oweu5pSPGehSwi8RVs263rpp1u/PwSHePOMEEg6Lshelg7ytU9RC961f1wa79S0WtYyusPF4fYhEiau2vBRN7rybvPlKrssGfJAL15bjzubMguRo9m2I5hxftdSJZ0st2Y0cqbWbqGIoXAi1CbwPTanhvCd4Mbx+Ec9A3mGDQlv1+310yHAjIh6Z2i2O+Dor6JwAXEopzHZozptZGm9prNMw/892fvZIITjnmVRs8nGMWi3rYBp50Toa3iJUlThA7gHfQM606COAL6CvMOQBeT/t8YqeLsp5jDkSXNHEvgPsA3Bc89KLPHHnuNItiphFWCsMyaZPmj8Ii77c0L99Xu2SDCQBq0HX/6QoVvGQZets67kQOvSUwzIWZTmLi5d3oZ+nPcB4VVBiW/CTtbyeYICdOGmFWqFAmLEDviJ6D6C3UdOoyPp/Ixxb6Hr8KFSr04pyMzwtSx2D4qfPHsLHKVqgwTOiz2Xk+VIayFSp4y3rYkT6D2Zo1jN5GojecPyrCrFDBe36JnkXQ2ZiD3u6Si50/KkPZChXKkIowK1QoQyrCrFChDKEs66R46VaFCmXBW5PPziX7HJyMIXkVKgxzouhn0fT/A3tQ05HQIpHYAAAAAElFTkSuQmCC",
"PNG",20,20,100,21),a.setFontSize(10),a.text(150,25,"Chromosome: Location"),a.setFontSize(14),a.text(150,40,$("#chromosome-selector").val()+" : "+$("#text-location").val()),a.setTextColor(0,0,0),a.setFontSize(10),null!=b._workspaceId&&(a.text(350,25,"Workspace ID"),a.setTextColor(0,0,255),a.setFontSize(16),a.text(350,45,g)),a.setFontSize(14),a.text(350,40,$("#save-workspace-text").val()),a.addImage(h,"PNG",15,55),a.save(e)):(navigator.msSaveBlob?(h=a.msToBlob(),a=new Blob([h],{type:"image/png"}),
navigator.msSaveBlob(a,e)):(a=new Blob([h],{type:"image/png"}),g=document.createElement("a"),void 0!==g.download?(URL.createObjectURL(a),g.setAttribute("href",h),g.setAttribute("download",e),g.style="visibility:hidden",g.setAttribute("target","_blank"),document.body.appendChild(g),g.click(),document.body.removeChild(g)):(e=h.replace("image/png","image/octet-stream"),window.open(e))),c.find(".tempCanvas").remove(),d.each(function(){$(this).show()}))})};epiviz.ui.ControlManager=function(a,b,c,d,e){this._config=a;this._chartFactory=b;this._chartManager=c;this._measurementsManager=d;this._locationManager=e;this._addChart=new epiviz.events.Event;this._activeWorkspaceChanged=new epiviz.events.Event;this._saveWorkspace=new epiviz.events.Event;this._deleteActiveWorkspace=new epiviz.events.Event;this._revertActiveWorkspace=new epiviz.events.Event;this._loginLinkClicked=new epiviz.events.Event;this._searchWorkspaces=new epiviz.events.Event;this._search=
new epiviz.events.Event;this._activeWorkspaceInfo=null;this._stepRatio=a.navigationStepRatio;this._zoominRatio=a.zoominRatio;this._zoomoutRatio=a.zoomoutRatio};epiviz.ui.ControlManager.CHART_TYPE_CONTAINERS={plot:"feature-view",track:"location-view","data-structure":"data-structure-view"};epiviz.ui.ControlManager.DISPLAY_TYPE_LABELS={plot:"Feature",track:"Location","data-structure":"Data Structure"};
epiviz.ui.ControlManager.prototype.initialize=function(){this._initializeChromosomeSelector();this._initializeLocationTextbox();this._initializeNavigationButtons();this._initializeZoomButtons();this._initializeLocationSettingsDialog();this._initializeChartMenus();this._initializeComputedMeasurementsMenu();this._initializeHelpButton();this._initializeSearchBox();this._initializeWorkspaceSaving();this._initializeTutorials();this._initializeScreenshotMenu();this._initializeLoginLink();this._initializeLayout();
this._checkBrowserCompatibility();this._registerLocationChanged();this._registerSeqInfosUpdated()};epiviz.ui.ControlManager.prototype.onAddChart=function(){return this._addChart};epiviz.ui.ControlManager.prototype.onActiveWorkspaceChanged=function(){return this._activeWorkspaceChanged};epiviz.ui.ControlManager.prototype.onSaveWorkspace=function(){return this._saveWorkspace};epiviz.ui.ControlManager.prototype.onDeleteActiveWorkspace=function(){return this._deleteActiveWorkspace};
epiviz.ui.ControlManager.prototype.onRevertActiveWorkspace=function(){return this._revertActiveWorkspace};epiviz.ui.ControlManager.prototype.onLoginLinkClicked=function(){return this._loginLinkClicked};epiviz.ui.ControlManager.prototype.onSearchWorkspaces=function(){return this._searchWorkspaces};epiviz.ui.ControlManager.prototype.onSearch=function(){return this._search};
epiviz.ui.ControlManager.prototype._updateSeqNames=function(a){var b=$("#chromosome-selector");b.empty();for(var c=0;c<a.length;++c){var d=sprintf('<option value="%s"%s>%s</option>',a[c].seqName,this._locationManager.currentLocation()&&a[c].seqName==this._locationManager.currentLocation().seqName()?'selected="selected"':"",a[c].seqName);b.append(d)}b.selectmenu()};
epiviz.ui.ControlManager.prototype._updateSelectedLocation=function(a){if(a){this._locationManager.changeCurrentLocation(a);a=this._locationManager.currentLocation();$("#text-location").val(Globalize.format(a.start(),"n0")+" - "+Globalize.format(a.end(),"n0"));var b=$("#chromosome-selector");b.val(a.seqName());b.selectmenu()}};
epiviz.ui.ControlManager.prototype.updateSelectedWorkspace=function(a){var b=this,c=$("#save-workspace-text"),d=this._activeWorkspaceInfo;c.val(a.name);this._activeWorkspaceInfo=a;this._activeWorkspaceChanged.notify({oldValue:d,newValue:a,cancel:function(){c.val(d.name);b._activeWorkspaceInfo=d}})};
epiviz.ui.ControlManager.prototype._initializeChromosomeSelector=function(){var a=$("#chromosome-selector");a.selectmenu({style:"popup",width:"90",maxHeight:"100",menuWidth:"90"});var b=this;a.change(function(){var a=b._locationManager.lastUnfilledLocationChangeRequest()||b._locationManager.currentLocation(),d=$(this).val();b._updateSelectedLocation(new epiviz.datatypes.GenomicRange(d,a.start(),a.width(),a.genome()))})};
epiviz.ui.ControlManager.prototype._initializeLocationTextbox=function(){var a=this;$("#text-location").keypress(function(b){if(13!=b.which)return!0;try{var c=$(this).val().split("-"),d=Globalize.parseInt(c[0]),e=Globalize.parseInt(c[1]),f=a._locationManager.lastUnfilledLocationChangeRequest()||a._locationManager.currentLocation();a._updateSelectedLocation(epiviz.datatypes.GenomicRange.fromStartEnd(f.seqName(),d,e,f.genome()));return!0}catch(g){return!1}})};
epiviz.ui.ControlManager.prototype._initializeNavigationButtons=function(){var a=this;$("#moveright").button({icons:{primary:"ui-icon ui-icon-seek-next"},text:!1}).click(function(){var b=a._locationManager.lastUnfilledLocationChangeRequest()||a._locationManager.currentLocation(),c=b.start()+Math.round(b.width()*a._stepRatio);a._updateSelectedLocation(new epiviz.datatypes.GenomicRange(b.seqName(),c,b.width(),b.genome()))});$("#moveleft").button({icons:{primary:"ui-icon ui-icon-seek-prev"},text:!1}).click(function(){var b=
a._locationManager.lastUnfilledLocationChangeRequest()||a._locationManager.currentLocation(),c=b.start()-Math.round(b.width()*a._stepRatio);a._updateSelectedLocation(new epiviz.datatypes.GenomicRange(b.seqName(),c,b.width(),b.genome()))})};
epiviz.ui.ControlManager.prototype._initializeZoomButtons=function(){var a=this,b=$("#zoomin");b.button({icons:{primary:"ui-icon ui-icon-zoomin"},text:!1});var c=$("#zoomout");c.button({icons:{primary:"ui-icon ui-icon-zoomout"},text:!1});var d=function(b){var c=a._locationManager.lastUnfilledLocationChangeRequest()||a._locationManager.currentLocation(),d=c.start()+.5*c.width();b=Math.round(c.width()*b);d=Math.round(d-.5*b);a._updateSelectedLocation(new epiviz.datatypes.GenomicRange(c.seqName(),d,
b,c.genome()))};b.click(function(){d(a._zoominRatio)});c.click(function(){d(a._zoomoutRatio)})};
epiviz.ui.ControlManager.prototype._initializeLocationSettingsDialog=function(){var a=this;$("#location-settings").button({text:!1,icons:{primary:"ui-icon ui-icon-gear"}}).click(function(){$("#location-settings-dialog").dialog("open")});$("#location-settings-dialog").dialog({autoOpen:!1,resizable:!1,width:"300",buttons:{Ok:function(){a._zoominRatio=$("#zoomin-ratio-text").val();a._zoomoutRatio=$("#zoomout-ratio-text").val();a._stepRatio=$("#navigation-step-ratio-text").val();$(this).dialog("close")},
Cancel:function(){$("#zoomin-ratio-text").val(Globalize.format(a._zoominRatio,"n3"));$("#zoomout-ratio-text").val(Globalize.format(a._zoomoutRatio,"n3"));$("#navigation-step-ratio-text").val(Globalize.format(a._stepRatio,"n6"));$(this).dialog("close")}},modal:!0});$("#zoomout-ratio-text").spinner({min:1.001,max:1E3,step:.001,start:1.2,numberFormat:"n3"}).val(a._zoomoutRatio);$("#zoomin-ratio-text").spinner({min:.001,max:.999,step:.01,start:.8,numberFormat:"n3"}).val(a._zoominRatio);$("#navigation-step-ratio-text").spinner({min:1E-6,
max:1,step:1E-6,start:.2,numberFormat:"n6"}).val(a._stepRatio)};
epiviz.ui.ControlManager.prototype._initializeChartMenus=function(){var a=this,b=$("#vis-menu");$("#vis-menu-button").button({text:!1,icons:{primary:"ui-icon ui-icon-scatterplot",secondary:"ui-icon-triangle-1-s"}}).click(function(){var a=b.is(":visible");$(".dropdown-menu").find(">:first-child").hide();a?b.hide():b.show().position({my:"left top",at:"left bottom",of:this});return!1});var c={},d=epiviz.ui.ControlManager.DISPLAY_TYPE_LABELS;this._chartFactory.foreach(function(a,b){b.chartDisplayType()in
c||(c[b.chartDisplayType()]=[]);c[b.chartDisplayType()].push(b)});for(var e in c)c.hasOwnProperty(e)&&($(sprintf('<li class="ui-widget-header">%s</li>',d[e])).appendTo(b),c[e].forEach(function(c,d){var e=sprintf("%s-menu-add-%s",c.chartDisplayType(),c.chartHtmlAttributeName());b.append(sprintf('<li><a href="javascript:void(0)" id="%s">Add New %s</a></li>',e,c.chartName()));$("#"+e).click(function(){var d=[];c.isRestrictedToSameDatasourceGroup()&&d.push(new epiviz.ui.controls.DatasourceGroupWizardStep);
c.chartDisplayType()!=epiviz.ui.charts.VisualizationType.DisplayType.DATA_STRUCTURE&&d.push(new epiviz.ui.controls.MeaurementsWizardStep);if(d.length){var e=a._measurementsManager.measurements().subset(c.measurementsFilter());e.addAll(a._measurementsManager.measurements().map(function(a){return a.datasource()}).subset(c.measurementsFilter()));(new epiviz.ui.controls.Wizard(sprintf("Add new %s",c.chartName()),{finish:function(b){a._addChart.notify({type:c,visConfigSelection:b})}},d,new epiviz.ui.controls.VisConfigSelection(e,
void 0,void 0,void 0,void 0,c.chartName(),c.minSelectedMeasurements()),"750",void 0,c.isRestrictedToSameDatasourceGroup())).show();b.hide()}else a._addChart.notify({type:c,visConfigSelection:new epiviz.ui.controls.VisConfigSelection(a._measurementsManager.measurements().subset(c.measurementsFilter()))})})}));b.hide().menu()};
epiviz.ui.ControlManager.prototype._initializeComputedMeasurementsMenu=function(){var a=this;$("#computed-measurements-button").button({text:!1,icons:{primary:"ui-icon ui-icon-calculator"}}).click(function(){(new epiviz.ui.controls.ComputedMeasurementsDialog("Computed Measurements",{add:function(b){a._measurementsManager.addMeasurement(b)},remove:function(b){a._measurementsManager.removeMeasurement(b)},close:function(){}},a._measurementsManager.measurements(),a._chartManager.chartsMeasurements())).show()})};
epiviz.ui.ControlManager.prototype._initializeHelpButton=function(){$("#help-button").button({text:!1,icons:{primary:"ui-icon ui-icon-help"}}).click(function(){window.open("http://epiviz.github.io/","_blank").focus()})};
epiviz.ui.ControlManager.prototype._initializeTutorials=function(){var a=$("#help-tutorials"),b=new epiviz.ui.tutorials,c='<div class="dropdown-menu"><ul id="tutorial-list"><li class="ui-widget-header">Tutorials</li>';0<b._tutorialList.length?b._tutorialList.forEach(function(a){c+='<li><a href="javascript:void(0);" id="'+a.id+'">'+a.name+"</a></li>"}):c+="<li>No Tutorials available</li>";c+="</ul></div>";$(sprintf(c)).insertAfter(a);var d=$("#tutorial-list");d.hide().menu();a.button({icons:{primary:"ui-icon ui-icon-info",
secondary:"ui-icon-triangle-1-s"},text:!1}).click(function(){d.is(":visible")?d.hide():d.show().position({my:"left top",at:"left bottom",of:this});return!1});0<b._tutorialList.length&&b._tutorialList.forEach(function(a){$("#"+a.id).click(function(){(new Anno(a.tutorial)).show();d.hide()})})};
epiviz.ui.ControlManager.prototype._initializeScreenshotMenu=function(){var a=this,b=$("#save-page");b.button({icons:{primary:"ui-icon ui-icon-print"},text:!1}).click(function(){var c=$("#save-workspace-text").val();a._saveWorkspace.notify({name:c,id:c==a._activeWorkspaceInfo.name?a._activeWorkspaceInfo.id:null});b.append(sprintf('<div id="loading" title="printing workspace"><p>Save/Print the existing EpiViz workspace.</p><div style="position:absolute; right:15px;"><select class="screenshot-file-format"><option value="pdf" selected="selected">PDF</option><option value="png" >PNG</option></select></div></div>'));
b.find("#loading").dialog({resizable:!1,modal:!0,title:"Print workspace as image",buttons:{Print:function(){$(this).dialog("close");var b=$(".screenshot-file-format option:selected").val(),c=Math.floor($.now()/1E3);(new epiviz.ui.PrintManager("pagemain","epiviz_"+c,b,a._activeWorkspaceInfo.id)).print();$(this).dialog("destroy").remove()},cancel:function(){$(this).dialog("destroy").remove()}}}).show()})};
epiviz.ui.ControlManager.prototype._initializeSearchBox=function(){var a=this,b=$("#search-box");b.watermark("Find Gene/Probe");b.autocomplete({source:function(b,d){a._search.notify({searchTerm:b.term,callback:function(a){for(var b=[],c=0;c<a.length;++c)b.push({value:a[c].probe||a[c].gene,label:a[c].probe||a[c].gene,html:a[c].probe?sprintf("<b>%s</b>, %s, [%s: %s - %s]",a[c].probe,a[c].gene,a[c].seqName,Globalize.format(a[c].start,"n0"),Globalize.format(a[c].end,"n0")):sprintf("<b>%s</b>, [%s: %s - %s]",
a[c].gene,a[c].seqName,Globalize.format(a[c].start,"n0"),Globalize.format(a[c].end,"n0")),range:epiviz.datatypes.GenomicRange.fromStartEnd(a[c].seqName,a[c].start,a[c].end)});d(b)}})},minLength:1,select:function(b,d){a._locationManager.lastUnfilledLocationChangeRequest()||a._locationManager.currentLocation();var c=d.item.range.seqName(),f=Math.round(d.item.range.start()-11*d.item.range.width()),g=22*d.item.range.width();a._updateSelectedLocation(new epiviz.datatypes.GenomicRange(c,f,g))},focus:function(a){a.preventDefault()},
open:function(){},close:function(){}}).data("autocomplete")._renderItem=function(a,b){return $("<li></li>").data("item.autocomplete",b).append(sprintf("<a>%s</a>",b.html)).appendTo(a)}};
epiviz.ui.ControlManager.prototype._initializeWorkspaceSaving=function(){var a=this,b=$("#save-workspace-text"),c=$("#save-workspace-button"),d=$("#revert-workspace-button"),e=$("#delete-workspace-button");c.button({text:!1,icons:{primary:"ui-icon-disk"}}).click(function(){var c=null;try{var d=b.val();/[a-zA-Z0-9_\s]+/g.exec(d)==d?epiviz.workspaces.UserManager.USER_STATUS.loggedIn?a._saveWorkspace.notify({name:d,id:d==a._activeWorkspaceInfo.name?a._activeWorkspaceInfo.id:null}):(c=new epiviz.ui.controls.MessageDialog("User not logged in",
{Yes:function(){a._loginLinkClicked.notify()},No:function(){}},"You need to log in in order to save the workspace. Do you wish to log in now?",epiviz.ui.controls.MessageDialog.Icon.QUESTION),c.show()):(c=new epiviz.ui.controls.MessageDialog("Invalid workspace name",{Ok:function(){$(this).remove()}},"Invalid workspace name: "+d,epiviz.ui.controls.MessageDialog.Icon.ERROR),c.show())}catch(h){c=new epiviz.ui.controls.MessageDialog("Error",{ok:function(){$(this).remove()}},"An error occurred while trying to save workspace: "+
h.message,epiviz.ui.controls.MessageDialog.Icon.ERROR),c.show()}});e.button({text:!1,icons:{primary:"ui-icon-trash"}}).click(function(b){epiviz.workspaces.UserManager.USER_STATUS.loggedIn&&(new epiviz.ui.controls.MessageDialog("Delete active workspace",{Yes:function(){a._deleteActiveWorkspace.notify()},No:function(){}},"Are you sure you want to delete the active workspace?",epiviz.ui.controls.MessageDialog.Icon.QUESTION)).show()});d.button({text:!1,icons:{primary:"ui-icon-arrowreturnthick-1-w"}}).click(function(b){(new epiviz.ui.controls.MessageDialog("Delete active workspace",
{Yes:function(){a._revertActiveWorkspace.notify()},No:function(){}},"Are you sure you want to revert the changes on the active workspace?",epiviz.ui.controls.MessageDialog.Icon.QUESTION)).show()});b.watermark("Save Workspace Name");b.autocomplete({source:function(b,c){a._searchWorkspaces.notify({searchTerm:b.term,callback:function(a){for(var b=[],d=0;d<a.length;++d)b.push({value:a[d].id,label:a[d].name,html:sprintf("<b>%s</b> %s",a[d].name,a[d].id||"")});c(b)}})},minLength:0,select:function(c,d){c.preventDefault();
a.updateSelectedWorkspace({id:d.item.value||b.val(),name:d.item.label})},focus:function(a){a.preventDefault()},open:function(){},close:function(){}}).data("autocomplete")._renderItem=function(a,b){return $("<li></li>").data("item.autocomplete",b).append(sprintf("<a>%s</a>",b.html)).appendTo(a)};b.click(function(){b.autocomplete("search","")})};epiviz.ui.ControlManager.prototype._initializeLoginLink=function(){var a=this;$("#login-link").live({click:function(){a._loginLinkClicked.notify()}})};
epiviz.ui.ControlManager.prototype._initializeLayout=function(){$("body").layout({applyDefaultStyles:!0,east__size:390,east__minSize:390,east__initHidden:!0,north__resizable:!1,north__initHidden:!1,south__initHidden:!0,east__initClosed:!0})};
epiviz.ui.ControlManager.prototype._checkBrowserCompatibility=function(){0<epiviz.utils.getInternetExplorerVersion()&&(new epiviz.ui.controls.MessageDialog("Browser compatibility warning",{Ok:function(){}},"EpiViz works best on Google Chrome, Apple Safari or Mozilla Firefox. Please open it using one of those browsers.",epiviz.ui.controls.MessageDialog.Icon.ERROR)).show()};epiviz.ui.ControlManager.prototype._registerLocationChanged=function(){var a=this;this._locationManager.onCurrentLocationChanged().addListener(new epiviz.events.EventListener(function(b){a._updateSelectedLocation(b.newValue)}))};
epiviz.ui.ControlManager.prototype._registerSeqInfosUpdated=function(){var a=this;this._locationManager.onSeqInfosUpdated().addListener(new epiviz.events.EventListener(function(b){a._updateSeqNames(b)}))};epiviz.ui.controls.SaveSvgAsImageDialog=function(a,b,c){epiviz.ui.controls.Dialog.call(this,"Save Chart SVG as Image",a);this._dialog=$("#"+this._id);this._dialog.append('<div class="save-svg-dialog"><label class="dialog-label">Choose file format:</label><br/><br/><div style="position:absolute; right:15px;"><select class="svg-file-format"><option value="pdf" selected="selected">PDF</option><option value="ps" >PS</option><option value="png" >PNG</option><option value="svg">SVG</option><option value="eps">EPS</option></select></div>'+
sprintf('<form name="%s-svg-save-form" method="POST">',this._id)+'<div><input type="hidden" name="svg" /><input type="hidden" name="format" /><br/><br/></div></form></div>');this._chartId=b;this._chartSaverLocation=c;var d=this;this._dialog.dialog({autoOpen:!1,resizable:!1,width:"200",buttons:{Ok:function(){var a=$("#"+d._chartId).find("svg").clone();a.attr("xmlns","http://www.w3.org/2000/svg");a.attr("version","1.1");var b=d._dialog.find(".svg-file-format"),c=document.forms[sprintf("%s-svg-save-form",
d._id)];c.action=d._chartSaverLocation;c.svg.value=$("<div>").append(a).html();c.format.value=b.val();c.submit();d._handlers.ok();$(this).dialog("close")},Cancel:function(){d._handlers.cancel();$(this).dialog("close")}},modal:!0})};epiviz.ui.controls.SaveSvgAsImageDialog.prototype=epiviz.utils.mapCopy(epiviz.ui.controls.Dialog.prototype);epiviz.ui.controls.SaveSvgAsImageDialog.constructor=epiviz.ui.controls.SaveSvgAsImageDialog;
epiviz.ui.controls.SaveSvgAsImageDialog.prototype.show=function(){epiviz.ui.controls.Dialog.prototype.show.call(this);var a=this;this._dialog&&(this._dialog.dialog("open"),this._dialog.dialog("option","position","center"),this._dialog.dialog({close:function(b,c){$(this).remove();a._dialog=null}}))};epiviz.ui.charts.decoration={};epiviz.ui.charts.decoration.VisualizationDecoration=function(a,b,c){this._visualization=a;this._otherDecoration=b;this._config=c};epiviz.ui.charts.decoration.VisualizationDecoration.prototype.decorate=function(){this._otherDecoration&&this._otherDecoration.decorate()};epiviz.ui.charts.decoration.VisualizationDecoration.prototype.visualization=function(){return this._visualization};epiviz.ui.charts.decoration.VisualizationDecoration.prototype.otherDecoration=function(){return this._otherDecoration};
epiviz.ui.charts.decoration.VisualizationDecoration.prototype.config=function(){return this._config};epiviz.ui.charts.decoration.ChartOptionButton=function(a,b,c){epiviz.ui.charts.decoration.VisualizationDecoration.call(this,a,b,c);this.isChartOptionButton=!0};epiviz.ui.charts.decoration.ChartOptionButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.VisualizationDecoration.prototype);epiviz.ui.charts.decoration.ChartOptionButton.constructor=epiviz.ui.charts.decoration.ChartOptionButton;
epiviz.ui.charts.decoration.ChartOptionButton.prototype.decorate=function(){epiviz.ui.charts.decoration.VisualizationDecoration.prototype.decorate.call(this);if(this.isChartOptionButton){for(var a=0,b=this.otherDecoration();b;b=b.otherDecoration())b.isChartOptionButton&&++a;var c=$(sprintf('<button style="position: absolute; top: 5px; right: %spx">%s</button>',5+30*a,this._text())).appendTo(this.visualization().container()).button(this._renderOptions()).click(this._click());this.visualization().container().mousemove(function(){c.show()}).mouseleave(function(){c.hide()})}};
epiviz.ui.charts.decoration.ChartOptionButton.prototype._click=function(){return function(){}};epiviz.ui.charts.decoration.ChartOptionButton.prototype._renderOptions=function(){return{}};epiviz.ui.charts.decoration.ChartOptionButton.prototype._text=function(){return""};epiviz.ui.charts.decoration.RemoveChartButton=function(a,b){epiviz.ui.charts.decoration.ChartOptionButton.call(this,a,b)};epiviz.ui.charts.decoration.RemoveChartButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.ChartOptionButton.prototype);epiviz.ui.charts.decoration.RemoveChartButton.constructor=epiviz.ui.charts.decoration.RemoveChartButton;epiviz.ui.charts.decoration.RemoveChartButton.prototype._click=function(){var a=this;return function(){a.visualization().onRemove().notify(new epiviz.ui.charts.VisEventArgs(a.visualization().id()))}};
epiviz.ui.charts.decoration.RemoveChartButton.prototype._renderOptions=function(){return{icons:{primary:"ui-icon ui-icon-cancel"},text:!1}};epiviz.ui.charts.decoration.RemoveChartButton.prototype._text=function(){return"Remove"};epiviz.ui.charts.decoration.SaveChartButton=function(a,b){epiviz.ui.charts.decoration.ChartOptionButton.call(this,a,b)};epiviz.ui.charts.decoration.SaveChartButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.ChartOptionButton.prototype);epiviz.ui.charts.decoration.SaveChartButton.constructor=epiviz.ui.charts.decoration.SaveChartButton;epiviz.ui.charts.decoration.SaveChartButton.prototype._click=function(){var a=this;return function(){a.visualization().onSave().notify(new epiviz.ui.charts.VisEventArgs(a.visualization().id()))}};
epiviz.ui.charts.decoration.SaveChartButton.prototype._renderOptions=function(){return{icons:{primary:"ui-icon ui-icon-disk"},text:!1}};epiviz.ui.charts.decoration.SaveChartButton.prototype._text=function(){return"Save"};epiviz.ui.controls.CustomSettingsDialog=function(a,b,c,d){epiviz.ui.controls.Dialog.call(this,a,b);this._customSettingsDefs=c;this._customSettingsValues=epiviz.utils.mapCopy(d)};epiviz.ui.controls.CustomSettingsDialog.prototype=epiviz.utils.mapCopy(epiviz.ui.controls.Dialog.prototype);epiviz.ui.controls.CustomSettingsDialog.constructor=epiviz.ui.controls.CustomSettingsDialog;
epiviz.ui.controls.CustomSettingsDialog.prototype.show=function(){epiviz.ui.controls.Dialog.prototype.show.call(this);var a=epiviz.ui.charts.CustomSetting.Type;if(!this._dialog){var b=this;this._dialog=$("#"+this._id);this._dialog.css("display","inline");var c,d,e,f,g="";for(c=0;c<this._customSettingsDefs.length;++c){d=sprintf("%s-%s",this._id,this._customSettingsDefs[c].id);var h=sprintf('<tr><td><label for="%s">%s</label></td><td style="text-align: right;">%%s</td></tr>',d,this._customSettingsDefs[c].label);
e=null;f=this._customSettingsValues[this._customSettingsDefs[c].id];switch(this._customSettingsDefs[c].type){case a.BOOLEAN:h=sprintf(h,sprintf('<div id="%1$s"><label for="%1$s-true">On</label><input type="radio" id="%1$s-true" name="%1$s" %2$s /><label for="%1$s-false">Off</label><input type="radio" id="%1$s-false" name="%1$s" %3$s /></div>',d,f?'checked="checked"':"",f?"":'checked="checked"'));break;case a.ARRAY:h=sprintf(h,sprintf('<input id="%s" value="%s" class="ui-widget-content ui-corner-all" style="text-align: right; padding: 5px;" />',
d,f.join(",")));break;case a.NUMBER:case a.STRING:h=sprintf(h,sprintf('<input id="%s" value="%s" class="ui-widget-content ui-corner-all" style="text-align: right; padding: 5px;" />',d,f));break;case a.CATEGORICAL:case a.MEASUREMENTS_METADATA:case a.MEASUREMENTS_ANNOTATION:var m="",l=this._customSettingsDefs[c];if(l.possibleValues)for(var p=0;p<l.possibleValues.length;++p)m+=sprintf('<option value="%1$s"%2$s>%1$s</option>',l.possibleValues[p],l.possibleValues[p]==f?'selected="selected"':"");f=sprintf('<select id="%s">%s</select>',
d,m);h=sprintf(h,f)}g+=h}g=sprintf('<div style="margin: 5px; padding: 5px; height: auto;"><table style="width: 100%%;">%s</table></div>',g);this._dialog.append(g);for(c=0;c<this._customSettingsDefs.length;++c)switch(d=sprintf("%s-%s",this._id,this._customSettingsDefs[c].id),e=$("#"+d),f=this._customSettingsValues[this._customSettingsDefs[c].id],this._customSettingsDefs[c].type){case a.BOOLEAN:e.buttonset();break;case a.NUMBER:case a.ARRAY:case a.STRING:e.watermark(this._customSettingsDefs[c].label);
break;case a.CATEGORICAL:case a.MEASUREMENTS_METADATA:case a.MEASUREMENTS_ANNOTATION:e.selectmenu()}this._dialog.dialog({autoOpen:!1,resizable:!1,buttons:{Ok:function(){for(var c=0;c<b._customSettingsDefs.length;++c){d=sprintf("%s-%s",b._id,b._customSettingsDefs[c].id);e=$("#"+d);var f=null;if(e.val()==epiviz.ui.charts.CustomSetting.DEFAULT)f=b._customSettingsDefs[c].defaultValue;else{var g=null;try{switch(b._customSettingsDefs[c].type){case a.BOOLEAN:var h=$("#"+d+" :radio:checked").attr("id"),f=
"true"==h.substr(h.lastIndexOf("-")+1);break;case a.NUMBER:f=e.val()==epiviz.ui.charts.CustomSetting.DEFAULT?b._customSettingsDefs[c].defaultValue:parseFloat(e.val());if(isNaN(f)){g=new epiviz.ui.controls.MessageDialog("Invalid property value",{Ok:function(){}},sprintf('Invalid value for setting "%s" (%s)',b._customSettingsDefs[c].label,b._customSettingsDefs[c].id),epiviz.ui.controls.MessageDialog.Icon.ERROR);g.show();return}break;case a.ARRAY:f=e.val().split(/[\s,]+/g);break;case a.STRING:case a.CATEGORICAL:case a.MEASUREMENTS_METADATA:case a.MEASUREMENTS_ANNOTATION:f=
e.val()}}catch(u){g=new epiviz.ui.controls.MessageDialog("Invalid property value",{Ok:function(){}},sprintf('Invalid value for setting "%s" (%s)',b._customSettingsDefs[c].label,b._customSettingsDefs[c].id),epiviz.ui.controls.MessageDialog.Icon.ERROR);g.show();return}}b._customSettingsValues[b._customSettingsDefs[c].id]=f}b._handlers.ok(b._customSettingsValues);$(this).dialog("close")},Cancel:function(){b._handlers.cancel();$(this).dialog("close")}},modal:!0});this._dialog.dialog({close:function(a,
c){$(this).remove();b._dialog=null}})}this._dialog.dialog("open");this._dialog.dialog("option","position","center")};epiviz.ui.charts.decoration.CustomSettingsButton=function(a,b){epiviz.ui.charts.decoration.ChartOptionButton.call(this,a,b)};epiviz.ui.charts.decoration.CustomSettingsButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.ChartOptionButton.prototype);epiviz.ui.charts.decoration.CustomSettingsButton.constructor=epiviz.ui.charts.decoration.CustomSettingsButton;
epiviz.ui.charts.decoration.CustomSettingsButton.prototype._click=function(){var a=this;return function(){(new epiviz.ui.controls.CustomSettingsDialog("Edit custom settings",{ok:function(b){a.visualization().setCustomSettingsValues(b)},cancel:function(){}},a.visualization().properties().customSettingsDefs,a.visualization().customSettingsValues())).show()}};epiviz.ui.charts.decoration.CustomSettingsButton.prototype._renderOptions=function(){return{icons:{primary:"ui-icon ui-icon-gear"},text:!1}};
epiviz.ui.charts.decoration.CustomSettingsButton.prototype._text=function(){return"Custom settings"};epiviz.ui.controls.CodeDialog=function(a,b,c){epiviz.ui.controls.Dialog.call(this,a,b);this._controlCreators=c;this._dialog=$("#"+this._id);this._controls=[];var d=this;this._dialog.append('<div class="code-tabs"><ul></ul></div>');var e=this._dialog.find(".code-tabs"),f=e.find("ul");this._controlCreators.forEach(function(a,b){var c=d._id+"-code-tab-"+b;e.append(sprintf('<div id="%s"></div>',c));var g=e.find("#"+c),g=a(g);f.append(sprintf('<li><a href="#%s">%s</a></li>',c,g.title()));d._controls.push(g)});
e.tabs({activate:function(a,b){d._tabActivate(e)}});this._dialog.dialog({autoOpen:!1,resizable:!1,width:"800",buttons:{Save:function(){var a=[];d._controls.forEach(function(b){b.save();a.push(b.result())});d._handlers.save(a);$(this).dialog("close")},Cancel:function(){d._controls.forEach(function(a){a.revert()});d._handlers.cancel();$(this).dialog("close")}},modal:!0});this._dialog.dialog("option","position","center")};epiviz.ui.controls.CodeDialog.prototype=epiviz.utils.mapCopy(epiviz.ui.controls.Dialog.prototype);
epiviz.ui.controls.CodeDialog.constructor=epiviz.ui.controls.CodeDialog;epiviz.ui.controls.CodeDialog.prototype.show=function(){epiviz.ui.controls.Dialog.prototype.show.call(this);this._dialog.dialog("open");this._controls[0].initialize();this._dialog.dialog("option","position","center")};epiviz.ui.controls.CodeDialog.prototype._tabActivate=function(a){a=a.tabs("option","active");this._controls[a].initialize();this._dialog.dialog("option","position","center")};epiviz.ui.charts.decoration.CodeButton=function(a,b,c){epiviz.ui.charts.decoration.ChartOptionButton.call(this,a,b,c);this.isCodeButton=!0;this._controlCreators=[];a=!0;var d;for(b=this.otherDecoration();b;b=b.otherDecoration())b.isCodeButton&&(a=!1,d=b);d&&d._addControlCreator(this._controlCreator(),this._saveHandler(),this._cancelHandler());(this.isChartOptionButton=a)&&this._addControlCreator(this._controlCreator(),this._saveHandler(),this._cancelHandler())};
epiviz.ui.charts.decoration.CodeButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.ChartOptionButton.prototype);epiviz.ui.charts.decoration.CodeButton.constructor=epiviz.ui.charts.decoration.CodeButton;
epiviz.ui.charts.decoration.CodeButton.prototype._click=function(){var a=this;return function(){(new epiviz.ui.controls.CodeDialog("Chart Code",{save:function(b){b.forEach(function(b,d){a._controlCreators[d].save(b)})},cancel:function(){a._controlCreators.forEach(function(a){a.cancel()})}},a._controlCreators.map(function(a){return a.creator}))).show()}};epiviz.ui.charts.decoration.CodeButton.prototype._renderOptions=function(){return{icons:{primary:"ui-icon ui-icon-pencil"},text:!1}};
epiviz.ui.charts.decoration.CodeButton.prototype._text=function(){return"Code"};epiviz.ui.charts.decoration.CodeButton.prototype._addControlCreator=function(a,b,c){this._controlCreators.push({creator:a,save:b,cancel:c})};epiviz.ui.charts.decoration.CodeButton.prototype._controlCreator=function(){return null};epiviz.ui.charts.decoration.CodeButton.prototype._saveHandler=function(){return null};epiviz.ui.charts.decoration.CodeButton.prototype._cancelHandler=function(){return null};epiviz.ui.controls.CodeControl=function(a,b,c,d){epiviz.ui.controls.Control.call(this,a,b,c);this._targetObj=d;this._text="// TODO: Your code here\n"};epiviz.ui.controls.CodeControl.prototype=epiviz.utils.mapCopy(epiviz.ui.controls.Control.prototype);epiviz.ui.controls.CodeControl.constructor=epiviz.ui.controls.CodeControl;epiviz.ui.controls.CodeControl.prototype.initialize=function(){};epiviz.ui.controls.CodeControl.prototype.save=function(){};epiviz.ui.controls.CodeControl.prototype.revert=function(){};
epiviz.ui.controls.CodeControl.prototype.text=function(){return this._text};epiviz.ui.controls.CodeControl.prototype.result=function(){return null};epiviz.ui.controls.EditCodeControl=function(a,b,c,d,e,f){epiviz.ui.controls.CodeControl.call(this,a,b,c,d);this._defaultMethod=e;this._editor=null;this._methodsCode={};this._selectedMethod=null;this._hasModifiedMethods=f||!1};epiviz.ui.controls.EditCodeControl.prototype=epiviz.utils.mapCopy(epiviz.ui.controls.CodeControl.prototype);epiviz.ui.controls.EditCodeControl.constructor=epiviz.ui.controls.EditCodeControl;
epiviz.ui.controls.EditCodeControl.prototype.initialize=function(){if(!this._editor){this._container.append('<div style="float: left; margin-right: 5px;"><select class="obj-methods"></select></div>'+sprintf('<div id="%1$s"><label for="%1$s-true">On</label><input type="radio" id="%1$s-true" name="%1$s" %2$s /><label for="%1$s-false">Off</label><input type="radio" id="%1$s-false" name="%1$s" %3$s /></div>',this.id()+"-switch",this._hasModifiedMethods?'checked="checked"':"",this._hasModifiedMethods?
"":'checked="checked"')+'<br /><div style="overflow-y: scroll; max-height: 500px;"><textarea autofocus="autofocus" class="code-edit"></textarea></div>');this._methodsSelector=this._container.find(".obj-methods");var a=this._container.find("#"+this.id()+"-switch");a.buttonset();var b=this,c=function(a){a=$("#"+b.id()+"-switch :radio:checked").attr("id");a="true"==a.substr(a.lastIndexOf("-")+1);b._editor&&b._editor.setOption("disableInput",!a);b._hasModifiedMethods=a};a.find("#"+this.id()+"-switch-true").on("change",
c);a.find("#"+this.id()+"-switch-false").on("change",c);var d=this._container.find(".code-edit"),a=[],c=this._targetObj,e;for(e in c)$.isFunction(c[e])&&a.push(e);a.sort();for(var f=0;f<a.length;++f){e=a[f];var g=0==f&&!this._defaultMethod||this._defaultMethod==e;this._methodsSelector.append(sprintf('<option value="%s"%s>%s</option>',e,g?' selected="selected"':"",e));g&&(this._text=c[e].toString(),this._selectedMethod=e)}this._methodsSelector.change(function(){b._methodsCode[b._selectedMethod]=b._editor.getValue();
var a=$(this).val(),c=b._methodsCode[a];c||(c=b._targetObj[a].toString(),b._methodsCode[a]=c);b._text=c;b._editor?b._editor.getDoc().setValue(b._text):d.val(b._text);b._selectedMethod=a});d.val(this._text);this._methodsSelector.selectmenu({style:"popup",width:"150",maxHeight:"150",menuWidth:"150"});this._editor=CodeMirror.fromTextArea(this._container.find(".code-edit")[0],{lineNumbers:!0,matchBrackets:!0,continueComments:"Enter",extraKeys:{"Ctrl-Q":"toggleComment"},autofocus:!0});this._editor.setOption("disableInput",
!this._hasModifiedMethods)}};epiviz.ui.controls.EditCodeControl.prototype.save=function(){this._methodsCode[this._selectedMethod]=this._editor.getValue();this._text=this._editor.getValue()};epiviz.ui.controls.EditCodeControl.prototype.revert=function(){this._editor&&this._editor.setOption("value",this._text)};
epiviz.ui.controls.EditCodeControl.prototype.modifiedMethods=function(){var a={},b;for(b in this._methodsCode)this._methodsCode.hasOwnProperty(b)&&this._methodsCode[b]!=this._targetObj[b].toString()&&(a[b]=this._methodsCode[b]);return a};epiviz.ui.controls.EditCodeControl.prototype.result=function(){return{hasModifiedMethods:this._hasModifiedMethods,modifiedMethods:this._hasModifiedMethods?this.modifiedMethods():{}}};epiviz.ui.charts.decoration.EditCodeButton=function(a,b,c){epiviz.ui.charts.decoration.CodeButton.call(this,a,b,c)};epiviz.ui.charts.decoration.EditCodeButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.CodeButton.prototype);epiviz.ui.charts.decoration.EditCodeButton.constructor=epiviz.ui.charts.decoration.EditCodeButton;
epiviz.ui.charts.decoration.EditCodeButton.prototype._controlCreator=function(){var a=this;return function(b){return new epiviz.ui.controls.EditCodeControl(b,"Edit Code",null,a.visualization(),a.visualization().lastModifiedMethod(),a.visualization().hasModifiedMethods())}};epiviz.ui.charts.decoration.EditCodeButton.prototype._saveHandler=function(){var a=this;return function(b){b.hasModifiedMethods?a.visualization().setModifiedMethods(b.modifiedMethods):a.visualization().resetModifiedMethods()}};
epiviz.ui.charts.decoration.EditCodeButton.prototype._cancelHandler=function(){return function(){}};epiviz.ui.controls.ColorPickerDialog=function(a,b,c,d){epiviz.ui.controls.Dialog.call(this,"Pick Colors",a);this._dialog=$("#"+this._id);this._dialog.append('<div class="color-picker-form" action="" style="width: 420px;"><div class="chart-picker" style="float: right;"></div></div>');var e=this._dialog.find(".color-picker-form");a="";for(var f=0;f<b.length;++f){var g=sprintf("color-%s",f);a+=sprintf('<tr><td><label>%s:&nbsp;</label></td><td><input type="text" name="%s" class="colorwell %s" value="%s" /></td></tr>',
b[f],g,g,0<=d.keyColorIndex(b[f])?d.getByKey(b[f]):d.get(f))}e.append(sprintf('<table class="color-picker-table">%s</table>',a));var h=$.farbtastic(sprintf("#%s .chart-picker",this._id)),m=$(sprintf("#%s .chart-picker",this._id)).css("opacity",.25),l;$(sprintf("#%s .colorwell",this._id)).each(function(){h.linkTo(this);$(this).css("opacity",.75)}).focus(function(){l&&$(l).css("opacity",.75).removeClass("colorwell-selected");h.linkTo(this);m.css("opacity",1);$(l=this).css("opacity",1).addClass("colorwell-selected")});
e.append('<select class="palettes-selector"></select>');var p=e.find(".palettes-selector"),n={};c&&c.forEach(function(a){p.append(sprintf('<option value="%s"%s>%s</option>',a.id(),a.id()==d.id()?' selected="selected"':"",a.name()));n[a.id()]=a});d.id()in n||(p.prepend(sprintf('<option value="%s" selected="selected">%s</option>',d.id(),d.name())),n[d.id()]=d);p.selectmenu({style:"popup",width:"200",maxHeight:"150",menuWidth:"200"});var t=function(){for(var a=e.find(".colorwell"),b=0;b<a.length;++b)h.linkTo($(a[b])),
h.setColor(d.get(b));l&&h.linkTo(l)};p.change(function(){d=n[$(this).val()];t()});var v=this;this._dialog.dialog({autoOpen:!1,resizable:!1,width:"440",buttons:{Ok:function(){var a=e.find(".colorwell"),c=!1,f=[],g;for(g=0;g<d.size();++g)f.push(d.get(g));for(g=0;g<a.length;++g){var h=a[g].value,l=d.keyColorIndex(b[g]);0>l&&(l=g);h!=f[l]&&(c=!0,f[l]=h)}c&&(d=new epiviz.ui.charts.ColorPalette(f,void 0,void 0,d.keyIndices()));v._handlers.ok(d);$(this).dialog("close")},Cancel:function(){v._handlers.cancel();
$(this).dialog("close")},Reset:function(){t();v._handlers.reset()}},modal:!0})};epiviz.ui.controls.ColorPickerDialog.prototype=epiviz.utils.mapCopy(epiviz.ui.controls.Dialog.prototype);epiviz.ui.controls.ColorPickerDialog.constructor=epiviz.ui.controls.ColorPickerDialog;
epiviz.ui.controls.ColorPickerDialog.prototype.show=function(){epiviz.ui.controls.Dialog.prototype.show.call(this);var a=this;this._dialog&&(this._dialog.dialog("open"),this._dialog.dialog("option","position","center"),this._dialog.dialog({close:function(b,c){$(this).remove();a._dialog=null}}))};epiviz.ui.charts.decoration.ChartColorsButton=function(a,b,c){epiviz.ui.charts.decoration.ChartOptionButton.call(this,a,b,c)};epiviz.ui.charts.decoration.ChartColorsButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.ChartOptionButton.prototype);epiviz.ui.charts.decoration.ChartColorsButton.constructor=epiviz.ui.charts.decoration.ChartColorsButton;
epiviz.ui.charts.decoration.ChartColorsButton.prototype._click=function(){var a=this;return function(){var b=a.visualization().colorLabels();(new epiviz.ui.controls.ColorPickerDialog({ok:function(b){a.visualization().setColors(b)},cancel:function(){},reset:function(){}},b,a.config().colorPalettes,a.visualization().colors())).show()}};epiviz.ui.charts.decoration.ChartColorsButton.prototype._renderOptions=function(){return{icons:{primary:"ui-icon ui-icon-colorpicker"},text:!1}};
epiviz.ui.charts.decoration.ChartColorsButton.prototype._text=function(){return"Colors"};epiviz.ui.charts.decoration.ChartLoaderAnimation=function(a,b){epiviz.ui.charts.decoration.VisualizationDecoration.call(this,a,b);this._loaderTimeout=0;this._animationShowing=!1};epiviz.ui.charts.decoration.ChartLoaderAnimation.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.VisualizationDecoration.prototype);epiviz.ui.charts.decoration.ChartLoaderAnimation.constructor=epiviz.ui.charts.decoration.ChartLoaderAnimation;
epiviz.ui.charts.decoration.ChartLoaderAnimation.prototype.decorate=function(){epiviz.ui.charts.decoration.VisualizationDecoration.prototype.decorate.call(this);var a=this;this.visualization().onDataWaitStart().addListener(new epiviz.events.EventListener(function(){a._addLoaderAnimation()}));this.visualization().onDataWaitEnd().addListener(new epiviz.events.EventListener(function(){a._removeLoaderAnimation()}));this.visualization().onSizeChanged().addListener(new epiviz.events.EventListener(function(){a._animationShowing&&
a._addLoaderAnimation()}))};
epiviz.ui.charts.decoration.ChartLoaderAnimation.prototype._addLoaderAnimation=function(){this._loaderTimeout&&clearTimeout(this._loaderTimeout);var a=function(){b._animationShowing=!0;var a=b.visualization(),d=a.container();d.find(".chart-loader").remove();d.append(sprintf('<div class="loader-icon %s" style="top: %spx; left: %spx;"></div>',"chart-loader",Math.floor(.5*a.height()),Math.floor(.5*a.width())));d.find(".chart-loader").activity({segments:8,steps:5,opacity:.3,width:4,space:0,length:10,
color:"#0b0b0b",speed:1})},b=this;this._animationShowing?a():this._loaderTimeout=setTimeout(a,500)};epiviz.ui.charts.decoration.ChartLoaderAnimation.prototype._removeLoaderAnimation=function(){this._loaderTimeout&&clearTimeout(this._loaderTimeout);this._animationShowing=!1;this.visualization().container().find(".chart-loader").remove()};epiviz.ui.charts.decoration.ChartResize=function(a,b){epiviz.ui.charts.decoration.VisualizationDecoration.call(this,a,b)};epiviz.ui.charts.decoration.ChartResize.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.VisualizationDecoration.prototype);epiviz.ui.charts.decoration.ChartResize.constructor=epiviz.ui.charts.decoration.ChartResize;
epiviz.ui.charts.decoration.ChartResize.prototype.decorate=function(){epiviz.ui.charts.decoration.VisualizationDecoration.prototype.decorate.call(this);var a=this;this.visualization().container().resizable({stop:function(b,c){a.visualization().updateSize()}})};epiviz.ui.charts.decoration.ToggleTooltipButton=function(a,b){epiviz.ui.charts.decoration.VisualizationDecoration.call(this,a,b);this.isChartOptionButton=!0;this._checked=!1};epiviz.ui.charts.decoration.ToggleTooltipButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.VisualizationDecoration.prototype);epiviz.ui.charts.decoration.ToggleTooltipButton.constructor=epiviz.ui.charts.decoration.ToggleTooltipButton;
epiviz.ui.charts.decoration.ToggleTooltipButton.prototype.decorate=function(){epiviz.ui.charts.decoration.VisualizationDecoration.prototype.decorate.call(this);for(var a=0,b=this.otherDecoration();b;b=b.otherDecoration())b.isChartOptionButton&&++a;var c=this,b=sprintf("%s-tooltip-button",this.visualization().id());this.visualization().container().append(sprintf('<div id="%1$s-container" style="position: absolute; top: 5px; right: %2$spx"><input type="checkbox" id="%1$s" %3$s /><label for="%1$s" >Toggle tooltip</label></div>',
b,5+30*a,this._checked?'checked="checked"':""));var d=$("#"+b),e=$("#"+b+"-container");d.button({text:!1,icons:{primary:"ui-icon-comment"}}).click(function(){c._checked=d.is(":checked")});this.visualization().container().mousemove(function(){e.show()}).mouseleave(function(){e.hide()})};epiviz.ui.charts.decoration.ToggleTooltipButton.prototype.checked=function(){return this._checked};epiviz.ui.charts.decoration.ChartTooltip=function(a,b){epiviz.ui.charts.decoration.VisualizationDecoration.call(this,a,b)};epiviz.ui.charts.decoration.ChartTooltip.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.VisualizationDecoration.prototype);epiviz.ui.charts.decoration.ChartTooltip.constructor=epiviz.ui.charts.decoration.ChartTooltip;
epiviz.ui.charts.decoration.ChartTooltip.prototype.decorate=function(){epiviz.ui.charts.decoration.VisualizationDecoration.prototype.decorate.call(this);for(var a=void 0,b=this.otherDecoration();b;b=b.otherDecoration())if(b.constructor==epiviz.ui.charts.decoration.ToggleTooltipButton){a=b;break}var c=this;this.visualization().container().tooltip({items:".item",content:function(){if(!a.checked())return!1;var b=d3.select(this).data()[0];return b.valueItems[0].length>b.measurements.length+b.measurements[0].metadata().length?
c._horizontalContent(b):c._verticalContent(b)},track:!0,show:!1})};
epiviz.ui.charts.decoration.ChartTooltip.prototype._horizontalContent=function(a){for(var b=a.measurements[0].metadata(),c=sprintf("%s%s%s",void 0!=a.start&&void 0!=a.end?"<th><b>Start</b></th><th><b>End</b></th>":"",b?"<th><b>"+b.join("</b></th><th><b>")+"</b></th>":"",a.values?"<th><b>"+a.measurements.join("</b></th><th><b>")+"</b></th>":""),d="",e=0;e<a.valueItems[0].length&&10>e;++e){var f="",g=a.valueItems[0][e].rowItem,h=Globalize.format(g.start(),"n0"),m=Globalize.format(g.end(),"n0");void 0!=
h&&void 0!=m&&(f+=sprintf("<td>%s</td><td>%s</td>",h,m));g=g.rowMetadata();if(b&&g)for(h=0;h<b.length;++h)m=g[b[h]]||"",f+=sprintf("<td>%s</td>",15>=m.length?m:m.substr(0,15)+"...");if(a.values)for(g=0;g<a.measurements.length;++g)f+=sprintf("<td>%s</td>",Globalize.format(a.valueItems[g][e].value,"n3"));d+=sprintf("<tr>%s</tr>",f)}e<a.valueItems[0].length&&(d+=sprintf('<tr><td colspan="%s" style="text-align: center;">...</td></tr>',2+(b?b.length:0)+(a.values?a.measurements.length:0)));return sprintf('<table class="tooltip-table"><thead><tr>%s</tr></thead><tbody>%s</tbody></table>',
c,d)};
epiviz.ui.charts.decoration.ChartTooltip.prototype._verticalContent=function(a){var b=[],c=[0,0];if(void 0!=a.start&&void 0!=a.end){var d=["Start"],e=["End"];a.valueItems[0].every(function(a,b){d.push(Globalize.format(a.rowItem.start(),"n0"));e.push(Globalize.format(a.rowItem.end(),"n0"));return 5>b});b.push(d);b.push(e);c=[0,2]}var f=a.measurements[0].metadata(),g=[c[1],c[1]+f.length];f.forEach(function(c){var d=[c];a.valueItems[0].every(function(a,b){var e=a.rowItem.metadata(c)||"[NA]";15<e.length&&
(e=e.substr(0,15)+"...");d.push(e);return 5>b});b.push(d)});f=[g[1],g[1]];a.values&&(f=[g[1],g[1]+a.measurements.length],a.measurements.forEach(function(c,d){var e=[c.name()];a.valueItems[d].every(function(a,b){e.push(Globalize.format(a.value,"n3"));return 5>b});b.push(e)}));var h=f[1];10<h&&(c[1]=1,g[1]=Math.min(g[1],g[0]+4),h=c[1]-c[0]+g[1]-g[0]+f[1]-f[0],10<h&&(f[1]-=h-10));var h="",m;for(m=c[0];m<c[1];++m)h+="<tr><td><b>"+b[m][0]+"</b></td><td>"+b[m].slice(1).join("</td><td>")+"</td></tr>";for(m=
g[0];m<g[1];++m)h+="<tr><td><b>"+b[m][0]+"</b></td><td>"+b[m].slice(1).join("</td><td>")+"</td></tr>";for(m=f[0];m<f[1];++m)h+="<tr><td><b>"+b[m][0]+"</b></td><td>"+b[m].slice(1).join("</td><td>")+"</td></tr>";return'<table class="tooltip-table"><tbody>'+h+"</tbody></table>"};epiviz.ui.controls.MarkerCodeControl=function(a,b,c,d,e,f,g){epiviz.ui.controls.CodeControl.call(this,a,b,c,d);this._markEditor=this._editor=null;this._editorText=e;this._markText=f;this._enabled=g||!1};epiviz.ui.controls.MarkerCodeControl.prototype=epiviz.utils.mapCopy(epiviz.ui.controls.CodeControl.prototype);epiviz.ui.controls.MarkerCodeControl.constructor=epiviz.ui.controls.MarkerCodeControl;
epiviz.ui.controls.MarkerCodeControl.prototype.initialize=function(){if(!this._editor){this._container.append(sprintf('<div id="%1$s"><label for="%1$s-true">On</label><input type="radio" id="%1$s-true" name="%1$s" %2$s /><label for="%1$s-false">Off</label><input type="radio" id="%1$s-false" name="%1$s" %3$s /></div>',this.id()+"-switch",this._enabled?'checked="checked"':"",this._enabled?"":'checked="checked"')+'<br /><div><label><b>Pre-mark Method</b></label></div><br /><div style="overflow-y: scroll; max-height: 250px;"><textarea autofocus="autofocus" class="pre-filter-code"></textarea></div><br/><div><label><b>Mark Method</b></label></div><br/><div style="overflow-y: scroll; max-height: 250px;"><textarea autofocus="autofocus" class="filter-code"></textarea></div>');
var a=this._container.find("#"+this.id()+"-switch");a.buttonset();var b=this,c=function(a){a=$("#"+b.id()+"-switch :radio:checked").attr("id");a="true"==a.substr(a.lastIndexOf("-")+1);b._editor&&b._editor.setOption("disableInput",!a);b._markEditor&&b._markEditor.setOption("disableInput",!a);b._enabled=a};a.find("#"+this.id()+"-switch-true").on("change",c);a.find("#"+this.id()+"-switch-false").on("change",c);a=this._container.find(".pre-filter-code");a.val(this._editorText);c=this._container.find(".filter-code");
c.val(this._markText);this._editor=CodeMirror.fromTextArea(a[0],{lineNumbers:!0,matchBrackets:!0,continueComments:"Enter",extraKeys:{"Ctrl-Q":"toggleComment"},autofocus:!0});this._editor.setOption("disableInput",!this._enabled);this._markEditor=CodeMirror.fromTextArea(c[0],{lineNumbers:!0,matchBrackets:!0,continueComments:"Enter",extraKeys:{"Ctrl-Q":"toggleComment"},autofocus:!0});this._markEditor.setOption("disableInput",!this._enabled)}};
epiviz.ui.controls.MarkerCodeControl.prototype.save=function(){this._editor&&(this._editorText=this._editor.getValue(),this._markText=this._markEditor.getValue())};epiviz.ui.controls.MarkerCodeControl.prototype.revert=function(){this._editor&&this._editor.setOption("value",this._editorText);this._markEditor&&this._markEditor.setOption("value",this._markText)};
epiviz.ui.controls.MarkerCodeControl.prototype.result=function(){return{enabled:this._enabled,preMark:this._enabled?this._editorText:null,mark:this._enabled?this._markText:null}};epiviz.ui.charts.decoration.MarkerCodeButton=function(a,b,c){epiviz.ui.charts.decoration.CodeButton.call(this,a,b,c)};epiviz.ui.charts.decoration.MarkerCodeButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.CodeButton.prototype);epiviz.ui.charts.decoration.MarkerCodeButton.constructor=epiviz.ui.charts.decoration.MarkerCodeButton;
epiviz.ui.charts.decoration.MarkerCodeButton.prototype._controlCreator=function(){var a=this;return function(b){var c=a.visualization().getMarker(a.markerId()),d,e;c&&(d=c.preMarkStr(),e=c.markStr());d=d||a.preMarkTemplate();e=e||a.markTemplate();return new epiviz.ui.controls.MarkerCodeControl(b,a.markerLabel(),null,a.visualization(),d,e,void 0!=c)}};
epiviz.ui.charts.decoration.MarkerCodeButton.prototype._saveHandler=function(){var a=this;return function(b){b.enabled?a.visualization().putMarker(a.createMarker(b.preMark,b.mark)):a.visualization().removeMarker(a.markerId())}};epiviz.ui.charts.decoration.MarkerCodeButton.prototype._cancelHandler=function(){return function(){}};
epiviz.ui.charts.decoration.MarkerCodeButton.prototype.createMarker=function(a,b){return new epiviz.ui.charts.markers.VisualizationMarker(this.markerType(),this.markerId(),this.markerLabel(),a,b)};epiviz.ui.charts.decoration.MarkerCodeButton.prototype.markerType=function(){throw Error("unimplemented abstract method");};epiviz.ui.charts.decoration.MarkerCodeButton.prototype.markerLabel=function(){throw Error("unimplemented abstract method");};
epiviz.ui.charts.decoration.MarkerCodeButton.prototype.markerId=function(){throw Error("unimplemented abstract method");};epiviz.ui.charts.decoration.MarkerCodeButton.prototype.preMarkTemplate=function(){throw Error("unimplemented abstract method");};epiviz.ui.charts.decoration.MarkerCodeButton.prototype.markTemplate=function(){throw Error("unimplemented abstract method");};epiviz.ui.charts.decoration.ChartFilterCodeButton=function(a,b,c){epiviz.ui.charts.decoration.MarkerCodeButton.call(this,a,b,c)};epiviz.ui.charts.decoration.ChartFilterCodeButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.MarkerCodeButton.prototype);epiviz.ui.charts.decoration.ChartFilterCodeButton.constructor=epiviz.ui.charts.decoration.ChartFilterCodeButton;epiviz.ui.charts.decoration.ChartFilterCodeButton.prototype.markerType=function(){return epiviz.ui.charts.markers.VisualizationMarker.Type.FILTER};
epiviz.ui.charts.decoration.ChartFilterCodeButton.prototype.markerLabel=function(){return"User Filter"};epiviz.ui.charts.decoration.ChartFilterCodeButton.prototype.markerId=function(){return"user-filter"};epiviz.ui.charts.decoration.ChartFilterCodeButton.prototype.preMarkTemplate=function(){return"/**\n * This method is called once before every draw, for all data available to the visualization,\n * for initialization. Its result can be used inside the filter method.\n * @param {epiviz.datatypes.GenomicData} [data]\n * @returns {InitialVars}\n * @template InitialVars\n */\nfunction(data) {\n  // TODO: Your code here\n  return null;\n}\n"};
epiviz.ui.charts.decoration.ChartFilterCodeButton.prototype.markTemplate=function(){return"/**\n * This method is called for every data object. If it returns false, the object will not be drawn.\n * @param {epiviz.datatypes.GenomicData.ValueItem} [item]\n * @param {epiviz.datatypes.GenomicData} [data]\n * @param {InitialVars} [preMarkResult]\n * @returns {boolean}\n * @template InitialVars\n */\nfunction(item, data, preMarkResult) {\n  // TODO: Your code here\n  return true;\n}\n"};epiviz.ui.charts.tree.decoration={};epiviz.ui.charts.tree.decoration.TogglePropagateSelectionButton=function(a,b){epiviz.ui.charts.decoration.VisualizationDecoration.call(this,a,b);this._checked=this.isChartOptionButton=!0};epiviz.ui.charts.tree.decoration.TogglePropagateSelectionButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.VisualizationDecoration.prototype);epiviz.ui.charts.tree.decoration.TogglePropagateSelectionButton.constructor=epiviz.ui.charts.tree.decoration.TogglePropagateSelectionButton;
epiviz.ui.charts.tree.decoration.TogglePropagateSelectionButton.prototype.decorate=function(){epiviz.ui.charts.decoration.VisualizationDecoration.prototype.decorate.call(this);for(var a=0,b=this.otherDecoration();b;b=b.otherDecoration())b.isChartOptionButton&&++a;var c=this,b=sprintf("%s-propagate-selection-button",this.visualization().id());this.visualization().container().append(sprintf('<div id="%1$s-container" style="position: absolute; top: 5px; right: %2$spx"><input type="checkbox" id="%1$s" %3$s /><label for="%1$s" >Toggle propagate selection</label></div>',
b,5+30*a,this._checked?'checked="checked"':""));var d=$("#"+b),e=$("#"+b+"-container");d.button({text:!1,icons:{primary:"ui-icon ui-icon-refresh"}}).click(function(){c._checked=d.is(":checked");c.visualization().setAutoPropagateChanges(c._checked)});this.visualization().container().mousemove(function(){e.show()}).mouseleave(function(){e.hide()})};epiviz.ui.charts.tree.decoration.TogglePropagateSelectionButton.prototype.checked=function(){return this._checked};epiviz.ui.charts.decoration.HierarchyFilterCodeButton=function(a,b,c){epiviz.ui.charts.decoration.MarkerCodeButton.call(this,a,b,c)};epiviz.ui.charts.decoration.HierarchyFilterCodeButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.MarkerCodeButton.prototype);epiviz.ui.charts.decoration.HierarchyFilterCodeButton.constructor=epiviz.ui.charts.decoration.HierarchyFilterCodeButton;epiviz.ui.charts.decoration.HierarchyFilterCodeButton.prototype.markerType=function(){return epiviz.ui.charts.markers.VisualizationMarker.Type.FILTER};
epiviz.ui.charts.decoration.HierarchyFilterCodeButton.prototype.markerLabel=function(){return"User Filter"};epiviz.ui.charts.decoration.HierarchyFilterCodeButton.prototype.markerId=function(){return"user-filter"};epiviz.ui.charts.decoration.HierarchyFilterCodeButton.prototype.preMarkTemplate=function(){return"/**\n * This method is called once before every draw, for all data available to the visualization,\n * for initialization. Its result can be used inside the filter method.\n * @param {epiviz.ui.charts.tree.Node} [root]\n * @returns {InitialVars}\n * @template InitialVars\n */\nfunction(root) {\n  // TODO: Your code here\n  return null;\n}\n"};
epiviz.ui.charts.decoration.HierarchyFilterCodeButton.prototype.markTemplate=function(){return"/**\n * This method is called for every data object. If it returns false, the object will not be drawn.\n * @param {epiviz.ui.charts.tree.Node} [node]\n * @param {epiviz.ui.charts.tree.Node} [root]\n * @param {InitialVars} [preMarkResult]\n * @returns {boolean}\n * @template InitialVars\n */\nfunction(node, root, preMarkResult) {\n  // TODO: Your code here\n  return true;\n}\n"};epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton=function(a,b,c){epiviz.ui.charts.decoration.MarkerCodeButton.call(this,a,b,c)};epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.MarkerCodeButton.prototype);epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton.constructor=epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton;
epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton.prototype.markerType=function(){return epiviz.ui.charts.markers.VisualizationMarker.Type.GROUP_BY_MEASUREMENTS};epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton.prototype.markerLabel=function(){return"Group by"};epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton.prototype.markerId=function(){return"group-by-measurements"};
epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton.prototype.preMarkTemplate=function(){return"/**\n * This method is called once before every draw, for all data available to the visualization,\n * for initialization. Its result can be used inside the filter method.\n * @param {epiviz.datatypes.GenomicData} [data]\n * @returns {InitialVars}\n * @template InitialVars\n */\nfunction(data) {\n  // TODO: Your code here\n  return null;\n}\n"};
epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton.prototype.markTemplate=function(){return"/**\n * @param {epiviz.measurements.Measurement} m\n * @param {epiviz.datatypes.GenomicData} [data]\n * @param {InitialVars} [preMarkResult]\n * @returns {string}\n * @template InitialVars\n */\nfunction(m, data, preMarkResult) {\n  // TODO: Your code here\n  return 0;\n}\n"};epiviz.ui.charts.decoration.ChartColorByMeasurementsCodeButton=function(a,b,c){epiviz.ui.charts.decoration.MarkerCodeButton.call(this,a,b,c)};epiviz.ui.charts.decoration.ChartColorByMeasurementsCodeButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.MarkerCodeButton.prototype);epiviz.ui.charts.decoration.ChartColorByMeasurementsCodeButton.constructor=epiviz.ui.charts.decoration.ChartColorByMeasurementsCodeButton;
epiviz.ui.charts.decoration.ChartColorByMeasurementsCodeButton.prototype.markerType=function(){return epiviz.ui.charts.markers.VisualizationMarker.Type.COLOR_BY_MEASUREMENTS};epiviz.ui.charts.decoration.ChartColorByMeasurementsCodeButton.prototype.markerLabel=function(){return"Color by Measurements"};epiviz.ui.charts.decoration.ChartColorByMeasurementsCodeButton.prototype.markerId=function(){return"color-by-measurements"};
epiviz.ui.charts.decoration.ChartColorByMeasurementsCodeButton.prototype.preMarkTemplate=function(){return"/**\n * This method is called once before every draw, for all data available to the visualization,\n * for initialization. Its result can be used inside the filter method.\n * @param {epiviz.datatypes.GenomicData} [data]\n * @returns {InitialVars}\n * @template InitialVars\n */\nfunction(data) {\n  // TODO: Your code here\n  return null;\n}\n"};
epiviz.ui.charts.decoration.ChartColorByMeasurementsCodeButton.prototype.markTemplate=function(){return"/**\n * @param {epiviz.measurements.Measurement} m\n * @param {epiviz.datatypes.GenomicData} [data]\n * @param {InitialVars} [preMarkResult]\n * @returns {string|number}\n * @template InitialVars\n */\nfunction(m, data, preMarkResult) {\n  // TODO: Your code here\n  return 0;\n}\n"};epiviz.ui.charts.decoration.ChartOrderByMeasurementsCodeButton=function(a,b,c){epiviz.ui.charts.decoration.MarkerCodeButton.call(this,a,b,c)};epiviz.ui.charts.decoration.ChartOrderByMeasurementsCodeButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.MarkerCodeButton.prototype);epiviz.ui.charts.decoration.ChartOrderByMeasurementsCodeButton.constructor=epiviz.ui.charts.decoration.ChartOrderByMeasurementsCodeButton;
epiviz.ui.charts.decoration.ChartOrderByMeasurementsCodeButton.prototype.markerType=function(){return epiviz.ui.charts.markers.VisualizationMarker.Type.ORDER_BY_MEASUREMENTS};epiviz.ui.charts.decoration.ChartOrderByMeasurementsCodeButton.prototype.markerLabel=function(){return"Order By"};epiviz.ui.charts.decoration.ChartOrderByMeasurementsCodeButton.prototype.markerId=function(){return"order-by-measurements"};
epiviz.ui.charts.decoration.ChartOrderByMeasurementsCodeButton.prototype.preMarkTemplate=function(){return"/**\n * This method is called once before every draw, for all data available to the visualization,\n * for initialization. Its result can be used inside the filter method.\n * @param {epiviz.datatypes.GenomicData} [data]\n * @returns {InitialVars}\n * @template InitialVars\n */\nfunction(data) {\n  // TODO: Your code here\n  return null;\n}\n"};
epiviz.ui.charts.decoration.ChartOrderByMeasurementsCodeButton.prototype.markTemplate=function(){return"/**\n * @param {epiviz.measurements.Measurement} m\n * @param {epiviz.datatypes.GenomicData} [data]\n * @param {InitialVars} [preMarkResult]\n * @returns {string|number}\n * @template InitialVars\n */\nfunction(m, data, preMarkResult) {\n  // TODO: Your code here\n  return 0;\n}\n"};epiviz.ui.charts.decoration.ChartColorByRowCodeButton=function(a,b,c){epiviz.ui.charts.decoration.MarkerCodeButton.call(this,a,b,c)};epiviz.ui.charts.decoration.ChartColorByRowCodeButton.prototype=epiviz.utils.mapCopy(epiviz.ui.charts.decoration.MarkerCodeButton.prototype);epiviz.ui.charts.decoration.ChartColorByRowCodeButton.constructor=epiviz.ui.charts.decoration.ChartColorByRowCodeButton;epiviz.ui.charts.decoration.ChartColorByRowCodeButton.prototype.markerType=function(){return epiviz.ui.charts.markers.VisualizationMarker.Type.COLOR_BY_ROW};
epiviz.ui.charts.decoration.ChartColorByRowCodeButton.prototype.markerLabel=function(){return"Color By"};epiviz.ui.charts.decoration.ChartColorByRowCodeButton.prototype.markerId=function(){return"color-by"};epiviz.ui.charts.decoration.ChartColorByRowCodeButton.prototype.preMarkTemplate=function(){return"/**\n * This method is called once before every draw, for all data available to the visualization,\n * for initialization. Its result can be used inside the filter method.\n * @param {epiviz.datatypes.GenomicData} [data]\n * @returns {InitialVars}\n * @template InitialVars\n */\nfunction(data) {\n  // TODO: Your code here\n  return null;\n}\n"};
epiviz.ui.charts.decoration.ChartColorByRowCodeButton.prototype.markTemplate=function(){return"/**\n * This method is called for every data object. If it returns false, the object will not be drawn.\n * @param {epiviz.datatypes.GenomicData.RowItem} [row]\n * @param {epiviz.datatypes.GenomicData} [data]\n * @param {InitialVars} [preMarkResult]\n * @returns {string}\n * @template InitialVars\n */\nfunction(row, data, preMarkResult) {\n  // TODO: Your code here\n  return row.metadata('colLabel');\n}\n"};epiviz.ui.charts.ChartManager=function(a){this._config=a;this._charts={};this._chartsOrder={};this._resizeInterval=null;this._chartAdded=new epiviz.events.Event;this._chartRemoved=new epiviz.events.Event;this._chartsOrderChanged=new epiviz.events.Event;this._chartsCleared=new epiviz.events.Event;this._chartColorsChanged=new epiviz.events.Event;this._chartMethodsModified=new epiviz.events.Event;this._chartMethodsReset=new epiviz.events.Event;this._chartMarkersModified=new epiviz.events.Event;this._chartCustomSettingsChanged=
new epiviz.events.Event;this._chartSizeChanged=new epiviz.events.Event;this._chartMarginsChanged=new epiviz.events.Event;this._chartRequestHierarchy=new epiviz.events.Event;this._chartPropagateHierarchyChanges=new epiviz.events.Event;this._chartPropogateIcicleLocationChanges=new epiviz.events.Event;this._chartIcicleLocationChanges=new epiviz.events.Event;this._chartPropagateNavigationChanges=new epiviz.events.Event;this._registerWindowResize()};
epiviz.ui.charts.ChartManager.prototype.addChart=function(a,b,c,d){c=c||sprintf("%s-%s-%s",a.chartDisplayType(),a.chartHtmlAttributeName(),epiviz.utils.generatePseudoGUID(5));var e=a.cssClass(),f=$("#"+a.chartContainer()),g=f.find(".accordion"),h=g.find(".vis-container");0==g.length&&(g=$('<div class="accordion"></div>').appendTo(f),f=a.chartDisplayType(),g.append(sprintf('<h3><a href="#"><b><span style="color: #025167">Views by %s</span></b></a></h3>',epiviz.ui.ControlManager.DISPLAY_TYPE_LABELS[f])),
h=$('<div class="vis-container"></div>').appendTo(g),g.multiAccordion(),g.multiAccordion("option","active","all"));h.append(sprintf('<div id="%s" class="%s"></div>',c,e));g=h.find("#"+c);f=[];if(null!=a._defaultSettings.chartMarkers||void 0!=a._defaultSettings.chartMarkers)for(e=0;e<a._defaultSettings.chartMarkers.length;e++)h=a._defaultSettings.chartMarkers[e],f.push(new epiviz.ui.charts.markers.VisualizationMarker(h.type,h.id,h.name,h.preMark,h.mark));d=d||new epiviz.ui.charts.VisualizationProperties(a.defaultWidth(),
a.defaultHeight(),a.defaultMargins(),b,a.defaultColors(),null,a.customSettingsValues(),a.customSettingsDefs(),f);b=a.createNew(c,g,d);this._charts[c]=b;this._registerChartHover(b);this._registerChartUnhover(b);this._registerChartSelect(b);this._registerChartDeselect(b);this._registerChartColorsChanged(b);this._registerChartMethodsModified(b);this._registerChartMethodsReset(b);this._registerChartMarkersModified(b);this._registerChartCustomSettingsChanged(b);this._registerChartSizeChanged(b);this._registerChartMarginsChanged(b);
this._registerChartRemove(b);this._registerChartSave(b);this._registerChartRequestHierarchy(b);this._registerChartPropagateHierarchyChanges(b);this._registerChartPropogateIcicleLocationChanges(b);this._registerChartIcicleLocationChanges(b);this._registerChartPropagateNavigationChanges(b);if(a.decorations()){g=void 0;for(e=0;e<a.decorations().length;++e)(f=epiviz.utils.evaluateFullyQualifiedTypeName(a.decorations()[e]))&&(g=epiviz.utils.applyConstructor(f,[b,g,this._config]));g&&g.decorate()}a.chartDisplayType()in
this._chartsOrder||(this._chartsOrder[a.chartDisplayType()]=[]);this._chartsOrder[a.chartDisplayType()].push(c);this._chartAdded.notify(new epiviz.ui.charts.VisEventArgs(c,{type:a,properties:d,chartsOrder:this._chartsOrder}));return c};
epiviz.ui.charts.ChartManager.prototype.removeChart=function(a){$("#"+a).remove();var b=this._charts[a];delete this._charts[a];this._chartsOrder[b.displayType()].splice(this._chartsOrder[b.displayType()].indexOf(a),1);b=$("#"+epiviz.ui.ControlManager.CHART_TYPE_CONTAINERS[b.displayType()]);0==b.find(".accordion").find(".vis-container").children().length&&b.empty();this._chartRemoved.notify(new epiviz.ui.charts.VisEventArgs(a,this._chartsOrder))};
epiviz.ui.charts.ChartManager.prototype.chartsMeasurements=function(){var a={},b;for(b in this._charts)this._charts.hasOwnProperty(b)&&this._charts[b].displayType()!=epiviz.ui.charts.VisualizationType.DisplayType.DATA_STRUCTURE&&(a[b]=this._charts[b].measurements());return a};
epiviz.ui.charts.ChartManager.prototype.updateCharts=function(a,b,c){c=c||Object.keys(this._charts);for(var d=0;d<c.length;++d)if(this._charts.hasOwnProperty(c[d])){var e=this._charts[c[d]];e&&function(c){c.transformData(a,b).done(function(){0==c.draw().length&&(c._svg.select(".no-data-text").remove(),c._svg.append("text").attr("font-weight","bold").attr("font-style","italic").attr("fill","#C0C0C0").attr("transform","translate("+c.width()/2+","+c.height()/2+")").attr("class","no-data-text").text(function(a){return"No data in this region"}))})}(e)}};
epiviz.ui.charts.ChartManager.prototype.updateDataStructureCharts=function(){for(var a=Object.keys(this._charts),b=0;b<a.length;++b)if(this._charts.hasOwnProperty(a[b])){var c=this._charts[a[b]];c&&c.displayType()==epiviz.ui.charts.VisualizationType.DisplayType.DATA_STRUCTURE&&function(a){setTimeout(function(){a.fireRequestHierarchy()},0)}(c)}};
epiviz.ui.charts.ChartManager.prototype.clear=function(){this._charts={};this._chartsOrder={};var a=epiviz.ui.ControlManager.CHART_TYPE_CONTAINERS,b;for(b in a)a.hasOwnProperty(b)&&$("#"+a[b]).empty();this._chartsCleared.notify()};epiviz.ui.charts.ChartManager.prototype.dataWaitStart=function(a,b){if(a&&this._charts[a])this._charts[a].onDataWaitStart().notify(new epiviz.ui.charts.VisEventArgs(a));else for(var c in this._charts)this._charts.hasOwnProperty(c)&&b&&b[this._charts[c]]&&this._charts[c].onDataWaitStart().notify(new epiviz.ui.charts.VisEventArgs(c))};
epiviz.ui.charts.ChartManager.prototype.onChartAdded=function(){return this._chartAdded};epiviz.ui.charts.ChartManager.prototype.onChartRemoved=function(){return this._chartRemoved};epiviz.ui.charts.ChartManager.prototype.onChartsOrderChanged=function(){return this._chartsOrderChanged};epiviz.ui.charts.ChartManager.prototype.onChartsCleared=function(){return this._chartsCleared};epiviz.ui.charts.ChartManager.prototype.onChartColorsChanged=function(){return this._chartColorsChanged};
epiviz.ui.charts.ChartManager.prototype.onChartMethodsModified=function(){return this._chartMethodsModified};epiviz.ui.charts.ChartManager.prototype.onChartMethodsReset=function(){return this._chartMethodsReset};epiviz.ui.charts.ChartManager.prototype.onChartMarkersModified=function(){return this._chartMarkersModified};epiviz.ui.charts.ChartManager.prototype.onChartCustomSettingsChanged=function(){return this._chartCustomSettingsChanged};
epiviz.ui.charts.ChartManager.prototype.onChartSizeChanged=function(){return this._chartSizeChanged};epiviz.ui.charts.ChartManager.prototype.onChartMarginsChanged=function(){return this._chartMarginsChanged};epiviz.ui.charts.ChartManager.prototype.onChartRequestHierarchy=function(){return this._chartRequestHierarchy};epiviz.ui.charts.ChartManager.prototype.onChartPropagateHierarchyChanges=function(){return this._chartPropagateHierarchyChanges};
epiviz.ui.charts.ChartManager.prototype.onChartPropogateIcicleLocationChanges=function(){return this._chartPropogateIcicleLocationChanges};epiviz.ui.charts.ChartManager.prototype.onChartIcicleLocationChanges=function(){return this._chartIcicleLocationChanges};epiviz.ui.charts.ChartManager.prototype.onChartPropagateNavigationChanges=function(){return this._chartPropagateNavigationChanges};
epiviz.ui.charts.ChartManager.prototype._registerWindowResize=function(){var a=this;$(window).resize(function(){null!==a._resizeInterval&&window.clearTimeout(a._resizeInterval);a._resizeInterval=window.setTimeout(function(){for(var b in a._charts)a._charts.hasOwnProperty(b)&&a._charts[b].updateSize();a._resizeInterval=null},500)})};
epiviz.ui.charts.ChartManager.prototype._registerChartHover=function(a){var b=this;a.onHover().addListener(new epiviz.events.EventListener(function(a){for(var c in b._charts)b._charts.hasOwnProperty(c)&&b._charts[c].doHover(a.args)}))};epiviz.ui.charts.ChartManager.prototype._registerChartUnhover=function(a){var b=this;a.onUnhover().addListener(new epiviz.events.EventListener(function(){for(var a in b._charts)b._charts.hasOwnProperty(a)&&b._charts[a].doUnhover()}))};
epiviz.ui.charts.ChartManager.prototype._registerChartSelect=function(a){var b=this;a.onSelect().addListener(new epiviz.events.EventListener(function(a){a=a.args;for(var c in b._charts)b._charts.hasOwnProperty(c)&&b._charts[c].doSelect(a)}))};epiviz.ui.charts.ChartManager.prototype._registerChartDeselect=function(a){var b=this;a.onDeselect().addListener(new epiviz.events.EventListener(function(){for(var a in b._charts)b._charts.hasOwnProperty(a)&&b._charts[a].doDeselect()}))};
epiviz.ui.charts.ChartManager.prototype._registerChartRemove=function(a){var b=this;a.onRemove().addListener(new epiviz.events.EventListener(function(a){b.removeChart(a.id)}))};
epiviz.ui.charts.ChartManager.prototype._registerChartSave=function(a){var b=this;"default"!=b._config.configType?a.onSave().addListener(new epiviz.events.EventListener(function(a){(new epiviz.ui.PrintManager(a.id,"epiviz_"+Math.floor($.now()/1E3),"pdf")).print()})):a.onSave().addListener(new epiviz.events.EventListener(function(a){(new epiviz.ui.controls.SaveSvgAsImageDialog({ok:function(){},cancel:function(){}},a.id,b._config.dataServerLocation+b._config.chartSaverLocation)).show()}))};
epiviz.ui.charts.ChartManager.prototype._registerChartColorsChanged=function(a){var b=this;a.onColorsChanged().addListener(new epiviz.events.EventListener(function(a){b._chartColorsChanged.notify(a)}))};epiviz.ui.charts.ChartManager.prototype._registerChartMethodsModified=function(a){var b=this;a.onMethodsModified().addListener(new epiviz.events.EventListener(function(a){b._chartMethodsModified.notify(a)}))};
epiviz.ui.charts.ChartManager.prototype._registerChartMethodsReset=function(a){var b=this;a.onMethodsReset().addListener(new epiviz.events.EventListener(function(a){b._chartMethodsReset.notify(a)}))};epiviz.ui.charts.ChartManager.prototype._registerChartMarkersModified=function(a){var b=this;a.onMarkersModified().addListener(new epiviz.events.EventListener(function(a){b._chartMarkersModified.notify(a)}))};
epiviz.ui.charts.ChartManager.prototype._registerChartCustomSettingsChanged=function(a){var b=this;a.onCustomSettingsChanged().addListener(new epiviz.events.EventListener(function(a){b._chartCustomSettingsChanged.notify(a)}))};epiviz.ui.charts.ChartManager.prototype._registerChartSizeChanged=function(a){var b=this;a.onSizeChanged().addListener(new epiviz.events.EventListener(function(a){b._chartSizeChanged.notify(a)}))};
epiviz.ui.charts.ChartManager.prototype._registerChartMarginsChanged=function(a){var b=this;a.onMarginsChanged().addListener(new epiviz.events.EventListener(function(a){b._chartMarginsChanged.notify(a)}))};epiviz.ui.charts.ChartManager.prototype._registerChartRequestHierarchy=function(a){var b=this;a.displayType()==epiviz.ui.charts.VisualizationType.DisplayType.DATA_STRUCTURE&&a.onRequestHierarchy().addListener(new epiviz.events.EventListener(function(a){b._chartRequestHierarchy.notify(a)}))};
epiviz.ui.charts.ChartManager.prototype._registerChartPropagateHierarchyChanges=function(a){var b=this;a.displayType()==epiviz.ui.charts.VisualizationType.DisplayType.DATA_STRUCTURE&&a.onPropagateHierarchyChanges().addListener(new epiviz.events.EventListener(function(a){b._chartPropagateHierarchyChanges.notify(a)}))};
epiviz.ui.charts.ChartManager.prototype._registerChartPropogateIcicleLocationChanges=function(a){var b=this;a.displayType()==epiviz.ui.charts.VisualizationType.DisplayType.DATA_STRUCTURE&&a.onPropagateIcicleLocationChanges().addListener(new epiviz.events.EventListener(function(a){b._chartPropogateIcicleLocationChanges.notify(a)}))};
epiviz.ui.charts.ChartManager.prototype._registerChartIcicleLocationChanges=function(a){this.onChartIcicleLocationChanges().addListener(new epiviz.events.EventListener(function(b){a.displayType()==epiviz.ui.charts.VisualizationType.DisplayType.DATA_STRUCTURE&&(a._updateLocation(b.args.start,b.args.width),a._drawAxes(a._lastRoot))}))};
epiviz.ui.charts.ChartManager.prototype._registerChartPropagateNavigationChanges=function(a){var b=this;a.displayType()==epiviz.ui.charts.VisualizationType.DisplayType.TRACK&&a._propagateNavigationChanges.addListener(new epiviz.events.EventListener(function(a){b._chartPropagateNavigationChanges.notify(a)}))};epiviz.ui.charts.ChartManager.prototype.getChartSettings=function(a){a=this._charts[a];var b={};b.settings=a.customSettingsValues();b.colorMap=a.properties().colors;return b};
epiviz.ui.charts.ChartManager.prototype.setChartSettings=function(a,b,c){a=this._charts[a];if(null!=b){var d=a.customSettingsValues();Object.keys(b).forEach(function(a){d[a]=b[a]});a.setCustomSettingsValues(d)}null!=c&&a.setColors(c);a.draw()};epiviz.workspaces.WorkspaceManager=function(a,b,c,d,e){this._config=a;this._locationManager=b;this._measurementsManager=c;this._chartManager=d;this._chartFactory=e;this._workspacesByName=this._workspaces=this._unchangedActiveWorkspace=this._activeWorkspace=null;this._workspacesLoaded=new epiviz.events.Event;this._activeWorkspaceChanged=new epiviz.events.Event;this._activeWorkspaceChanging=!1;this._requestWorkspaces=new epiviz.events.Event;this._activeWorkspaceContentChanged=new epiviz.events.Event;
this._uiChartSettingsChanged=new epiviz.events.Event;var f=this;this._activeWorkspaceContentChangedListener=new epiviz.events.EventListener(function(a){f._activeWorkspaceContentChanged.notify(a)});this._registerLocationChanged();this._registerComputedMeasurementAdded();this._registerComputedMeasurementRemoved();this._registerChartAdded();this._registerChartRemoved();this._registerChartsOrderChanged();this._registerChartColorsChanged();this._registerChartMethodsModified();this._registerChartMethodsReset();
this._registerChartMarkersModified();this._registerChartSizeChanged();this._registerChartMarginsChanged();this._registerChartCustomSettingsChanged()};epiviz.workspaces.WorkspaceManager.prototype.onUiChartSettingsChanged=function(){return this._uiChartSettingsChanged};epiviz.workspaces.WorkspaceManager.prototype.activeWorkspace=function(){return this._activeWorkspace||null};epiviz.workspaces.WorkspaceManager.prototype.get=function(a){return a&&this._workspaces?this._workspaces[a]||null:null};
epiviz.workspaces.WorkspaceManager.prototype.getByName=function(a){return a&&this._workspacesByName?this._workspacesByName[a]||null:null};epiviz.workspaces.WorkspaceManager.prototype.initialize=function(){this._requestWorkspaces.notify({activeWorkspaceId:epiviz.ui.WebArgsManager.WEB_ARGS.ws||epiviz.ui.WebArgsManager.WEB_ARGS.workspace||null})};
epiviz.workspaces.WorkspaceManager.prototype.updateWorkspaces=function(a,b,c,d){if(a){this._workspaces={};this._workspacesByName={};for(var e=0;e<a.length;++e)null!==a[e].id()&&(this._workspaces[a[e].id()]=a[e],this._workspacesByName[a[e].name()]=a[e])}b||(b=a&&a.length?a[0]:epiviz.workspaces.Workspace.fromRawObject(this._config.defaultWorkspaceSettings,this._chartFactory,this._config));e=this._activeWorkspace;this._activeWorkspace=b;this._unchangedActiveWorkspace=d?d:b?b.copy(b.name(),b.id()):null;
e&&e.onContentChanged().removeListener(this._activeWorkspaceContentChangedListener);this._activeWorkspace&&this._activeWorkspace.onContentChanged().addListener(this._activeWorkspaceContentChangedListener);b=epiviz.ui.WebArgsManager.WEB_ARGS;d=void 0!=b.genome?b.genome:this._activeWorkspace.range().genome();var f=void 0!=b.seqName?b.seqName:this._activeWorkspace.range().seqName(),g=null,h=null;"undefined"!=b.start&&(g=parseInt(b.start)||this._activeWorkspace.range().start());"undefined"!=b.end&&(h=
parseInt(b.end)||this._activeWorkspace.range().end());this._activeWorkspace.locationChanged(epiviz.datatypes.GenomicRange.fromStartEnd(f,g,h,d));this._workspacesLoaded.notify({activeWorkspace:this._activeWorkspace,workspaces:a});this._activeWorkspaceChanged.notify({oldValue:e,newValue:this._activeWorkspace,workspaceId:this._activeWorkspace.id()||c})};epiviz.workspaces.WorkspaceManager.prototype.updateWorkspace=function(a){this._workspaces[a.id()]=a;this._workspacesByName[a.name()]=a};
epiviz.workspaces.WorkspaceManager.prototype.deleteActiveWorkspace=function(){var a=this._activeWorkspace;if(a&&a.id()){delete this._workspaces[a.id()];delete this._workspacesByName[a.name()];var b=null,c;for(c in this._workspaces)if(this._workspaces.hasOwnProperty(c)){b=this._workspaces[c];break}b||(b=epiviz.workspaces.Workspace.fromRawObject(this._config.defaultWorkspaceSettings,this._chartFactory,this._config));this._unchangedActiveWorkspace=(this._activeWorkspace=b)?b.copy(b.name(),b.id()):null;
b=a.range().genome();c=a.range().seqName();var d=a.range().start(),e=a.range().end();this._activeWorkspace.locationChanged(epiviz.datatypes.GenomicRange.fromStartEnd(c,d,e,b));this._activeWorkspaceChanged.notify({oldValue:a,newValue:this._activeWorkspace,workspaceId:this._activeWorkspace.id()})}};
epiviz.workspaces.WorkspaceManager.prototype.revertActiveWorkspace=function(){if(this._unchangedActiveWorkspace){var a=this._activeWorkspace,b=a.range().genome(),c=a.range().seqName(),d=a.range().start(),a=a.range().end();this._activeWorkspace=this._unchangedActiveWorkspace.copy(this._unchangedActiveWorkspace.name(),this._unchangedActiveWorkspace.id());this._activeWorkspace.locationChanged(epiviz.datatypes.GenomicRange.fromStartEnd(c,d,a,b));this._activeWorkspaceChanged.notify({oldValue:null,newValue:this._activeWorkspace,
workspaceId:this._activeWorkspace.id()})}};epiviz.workspaces.WorkspaceManager.prototype.onWorkspacesLoaded=function(){return this._workspacesLoaded};epiviz.workspaces.WorkspaceManager.prototype.onActiveWorkspaceChanged=function(){return this._activeWorkspaceChanged};epiviz.workspaces.WorkspaceManager.prototype.startChangingActiveWorkspace=function(){this._activeWorkspaceChanging=!0};epiviz.workspaces.WorkspaceManager.prototype.endChangingActiveWorkspace=function(){this._activeWorkspaceChanging=!1};
epiviz.workspaces.WorkspaceManager.prototype.activeWorkspaceChanging=function(){return this._activeWorkspaceChanging};epiviz.workspaces.WorkspaceManager.prototype.onRequestWorkspaces=function(){return this._requestWorkspaces};epiviz.workspaces.WorkspaceManager.prototype.onActiveWorkspaceContentChanged=function(){return this._activeWorkspaceContentChanged};
epiviz.workspaces.WorkspaceManager.prototype.changeActiveWorkspace=function(a,b){if((b=b||this._workspaces[a])&&b!==this._activeWorkspace){var c=this._activeWorkspace;this._unchangedActiveWorkspace=(this._activeWorkspace=b)?this._activeWorkspace.copy(this._activeWorkspace.name(),this._activeWorkspace.id()):null;this._activeWorkspaceChanged.notify({oldValue:c,newValue:this._activeWorkspace,workspaceId:a})}};
epiviz.workspaces.WorkspaceManager.prototype._registerLocationChanged=function(){var a=this;this._locationManager.onCurrentLocationChanged().addListener(new epiviz.events.EventListener(function(b){a._activeWorkspaceChanging||a._activeWorkspace&&a._activeWorkspace.locationChanged(b.newValue)}))};
epiviz.workspaces.WorkspaceManager.prototype._registerChartAdded=function(){var a=this;this._chartManager.onChartAdded().addListener(new epiviz.events.EventListener(function(b){a._activeWorkspaceChanging||a._activeWorkspace&&a._activeWorkspace.chartAdded(b.id,b.args.type,b.args.properties,b.args.chartsOrder)}))};
epiviz.workspaces.WorkspaceManager.prototype._registerChartRemoved=function(){var a=this;this._chartManager.onChartRemoved().addListener(new epiviz.events.EventListener(function(b){a._activeWorkspaceChanging||a._activeWorkspace&&a._activeWorkspace.chartRemoved(b.id,b.args)}))};
epiviz.workspaces.WorkspaceManager.prototype._registerChartsOrderChanged=function(){var a=this;this._chartManager.onChartsOrderChanged().addListener(new epiviz.events.EventListener(function(b){a._activeWorkspaceChanging||a._activeWorkspace&&a._activeWorkspace.chartsOrderChanged(b)}))};
epiviz.workspaces.WorkspaceManager.prototype._registerChartColorsChanged=function(){var a=this;this._chartManager.onChartColorsChanged().addListener(new epiviz.events.EventListener(function(b){!a._activeWorkspaceChanging&&a._activeWorkspace&&(a._activeWorkspace.chartColorsChanged(b.id,b.args),a.onUiChartSettingsChanged().notify({chartId:b.id,colorMap:b.args}))}))};
epiviz.workspaces.WorkspaceManager.prototype._registerChartMethodsModified=function(){var a=this;this._chartManager.onChartMethodsModified().addListener(new epiviz.events.EventListener(function(b){a._activeWorkspaceChanging||a._activeWorkspace&&a._activeWorkspace.chartMethodsModified(b.id,b.args)}))};
epiviz.workspaces.WorkspaceManager.prototype._registerChartMethodsReset=function(){var a=this;this._chartManager.onChartMethodsReset().addListener(new epiviz.events.EventListener(function(b){a._activeWorkspaceChanging||a._activeWorkspace&&a._activeWorkspace.chartMethodsReset(b.id)}))};
epiviz.workspaces.WorkspaceManager.prototype._registerChartMarkersModified=function(){var a=this;this._chartManager.onChartMarkersModified().addListener(new epiviz.events.EventListener(function(b){a._activeWorkspaceChanging||a._activeWorkspace&&a._activeWorkspace.chartMarkersModified(b.id,b.args)}))};
epiviz.workspaces.WorkspaceManager.prototype._registerChartCustomSettingsChanged=function(){var a=this;this._chartManager.onChartCustomSettingsChanged().addListener(new epiviz.events.EventListener(function(b){!a._activeWorkspaceChanging&&a._activeWorkspace&&(a._activeWorkspace.chartCustomSettingsChanged(b.id,b.args),a.onUiChartSettingsChanged().notify({chartId:b.id,settings:b.args}))}))};
epiviz.workspaces.WorkspaceManager.prototype._registerChartSizeChanged=function(){var a=this;this._chartManager.onChartSizeChanged().addListener(new epiviz.events.EventListener(function(b){a._activeWorkspaceChanging||a._activeWorkspace&&a._activeWorkspace.chartSizeChanged(b.id,b.args.width,b.args.height)}))};
epiviz.workspaces.WorkspaceManager.prototype._registerChartMarginsChanged=function(){var a=this;this._chartManager.onChartMarginsChanged().addListener(new epiviz.events.EventListener(function(b){a._activeWorkspaceChanging||a._activeWorkspace&&a._activeWorkspace.chartMarginsChanged(b.id,b.args)}))};
epiviz.workspaces.WorkspaceManager.prototype._registerComputedMeasurementAdded=function(){var a=this;this._measurementsManager.onComputedMeasurementsAdded().addListener(new epiviz.events.EventListener(function(b){a._activeWorkspaceChanging||a._activeWorkspace&&a._activeWorkspace.computedMeasurementsAdded(b)}))};
epiviz.workspaces.WorkspaceManager.prototype._registerComputedMeasurementRemoved=function(){var a=this;this._measurementsManager.onComputedMeasurementsRemoved().addListener(new epiviz.events.EventListener(function(b){a._activeWorkspaceChanging||a._activeWorkspace&&a._activeWorkspace.computedMeasurementsRemoved(b)}))};epiviz.workspaces.UserManager=function(a){this._config=a};epiviz.workspaces.UserManager.USER_STATUS=epiviz.workspaces.UserManager.USER_STATUS||{loggedIn:!1,userData:null,oauthProvider:null};epiviz.workspaces.UserManager.prototype.toggleLogin=function(){epiviz.workspaces.UserManager.USER_STATUS.loggedIn?this._logout():this._login()};
epiviz.workspaces.UserManager.prototype._login=function(){var a=window.location.toString();0<a.length&&(a=encodeURIComponent(a));window.location=this._config.dataServerLocation+"login.php?location="+a};epiviz.workspaces.UserManager.prototype._logout=function(){var a=window.location.toString();0<a.length&&(a=encodeURIComponent(a));window.location=this._config.dataServerLocation+"logout.php?logout&location="+a};epiviz.main=function(){var a=new epiviz.Config(epiviz.Config.SETTINGS),b=new epiviz.ui.LocationManager(a),c=new epiviz.measurements.MeasurementsManager,d=new epiviz.ui.charts.ChartFactory(a),e=new epiviz.ui.charts.ChartManager(a),f=new epiviz.ui.ControlManager(a,d,e,c,b),g=new epiviz.data.DataProviderFactory(a),g=new epiviz.data.DataManager(a,g),h;"false"==a.useCookie?(h=new epiviz.localstorage.LocalStorageManager(epiviz.localstorage.LocalStorageManager.MODE.INCOGNITO_MODE),h.clearWorkspace(),a.defaultWorkspaceSettings.content.charts=
null):h=new epiviz.localstorage.LocalStorageManager(epiviz.localstorage.LocalStorageManager.MODE.COOKIE_MODE);var m=new epiviz.workspaces.WorkspaceManager(a,b,c,e,d),l=new epiviz.workspaces.UserManager(a),p=new epiviz.ui.WebArgsManager(b,m),b=new epiviz.EpiViz(a,b,c,f,g,d,e,m,l,p,h);epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory.initialize(a);b.start()};goog.exportSymbol("epiviz",epiviz);

/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5)))

/***/ }),
/* 5 */
/***/ (function(module, exports) {

var g;

// This works in non-strict mode
g = (function() {
	return this;
})();

try {
	// This works if eval is allowed (see CSP)
	g = g || Function("return this")() || (1,eval)("this");
} catch(e) {
	// This works if the window reference is available
	if(typeof window === "object")
		g = window;
}

// g can still be undefined, but nothing to do about it...
// We return undefined, instead of nothing here, so it's
// easier to handle this case. if(!global) { ...}

module.exports = g;


/***/ })
/******/ ])));</script><script>
(function() {
  'use strict';

  const userPolymer = window.Polymer;

  /**
   * @namespace Polymer
   * @summary Polymer is a lightweight library built on top of the web
   *   standards-based Web Components API's, and makes it easy to build your
   *   own custom HTML elements.
   * @param {!PolymerInit} info Prototype for the custom element. It must contain
   *   an `is` property to specify the element name. Other properties populate
   *   the element prototype. The `properties`, `observers`, `hostAttributes`,
   *   and `listeners` properties are processed to create element features.
   * @return {!Object} Returns a custom element class for the given provided
   *   prototype `info` object. The name of the element if given by `info.is`.
   */
  window.Polymer = function(info) {
    return window.Polymer._polymerFn(info);
  };

  // support user settings on the Polymer object
  if (userPolymer) {
    Object.assign(Polymer, userPolymer);
  }

  // To be plugged by legacy implementation if loaded
  /* eslint-disable valid-jsdoc */
  /**
   * @param {!PolymerInit} info Prototype for the custom element. It must contain
   *   an `is` property to specify the element name. Other properties populate
   *   the element prototype. The `properties`, `observers`, `hostAttributes`,
   *   and `listeners` properties are processed to create element features.
   * @return {!Object} Returns a custom element class for the given provided
   *   prototype `info` object. The name of the element if given by `info.is`.
   */
  window.Polymer._polymerFn = function(info) { // eslint-disable-line no-unused-vars
    throw new Error('Load polymer.html to use the Polymer() function.');
  };
  /* eslint-enable */

  window.Polymer.version = '2.8.0';

  /* eslint-disable no-unused-vars */
  /*
  When using Closure Compiler, JSCompiler_renameProperty(property, object) is replaced by the munged name for object[property]
  We cannot alias this function, so we have to use a small shim that has the same behavior when not compiling.
  */
  window.JSCompiler_renameProperty = function(prop, obj) {
    return prop;
  };
  /* eslint-enable */

})();
</script>
<script>

  (function() {
    'use strict';

    let CSS_URL_RX = /(url\()([^)]*)(\))/g;
    let ABS_URL = /(^\/)|(^#)|(^[\w-\d]*:)/;
    let workingURL;
    let resolveDoc;
    /**
     * Resolves the given URL against the provided `baseUri'.
     * 
     * Note that this function performs no resolution for URLs that start
     * with `/` (absolute URLs) or `#` (hash identifiers).  For general purpose
     * URL resolution, use `window.URL`.
     *
     * @memberof Polymer.ResolveUrl
     * @param {string} url Input URL to resolve
     * @param {?string=} baseURI Base URI to resolve the URL against
     * @return {string} resolved URL
     */
    function resolveUrl(url, baseURI) {
      if (url && ABS_URL.test(url)) {
        return url;
      }
      // Lazy feature detection.
      if (workingURL === undefined) {
        workingURL = false;
        try {
          const u = new URL('b', 'http://a');
          u.pathname = 'c%20d';
          workingURL = (u.href === 'http://a/c%20d');
        } catch (e) {
          // silently fail
        }
      }
      if (!baseURI) {
        baseURI = document.baseURI || window.location.href;
      }
      if (workingURL) {
        return (new URL(url, baseURI)).href;
      }
      // Fallback to creating an anchor into a disconnected document.
      if (!resolveDoc) {
        resolveDoc = document.implementation.createHTMLDocument('temp');
        resolveDoc.base = resolveDoc.createElement('base');
        resolveDoc.head.appendChild(resolveDoc.base);
        resolveDoc.anchor = resolveDoc.createElement('a');
        resolveDoc.body.appendChild(resolveDoc.anchor);
      }
      resolveDoc.base.href = baseURI;
      resolveDoc.anchor.href = url;
      return resolveDoc.anchor.href || url;

    }

    /**
     * Resolves any relative URL's in the given CSS text against the provided
     * `ownerDocument`'s `baseURI`.
     *
     * @memberof Polymer.ResolveUrl
     * @param {string} cssText CSS text to process
     * @param {string} baseURI Base URI to resolve the URL against
     * @return {string} Processed CSS text with resolved URL's
     */
    function resolveCss(cssText, baseURI) {
      return cssText.replace(CSS_URL_RX, function(m, pre, url, post) {
        return pre + '\'' +
          resolveUrl(url.replace(/["']/g, ''), baseURI) +
          '\'' + post;
      });
    }

    /**
     * Returns a path from a given `url`. The path includes the trailing
     * `/` from the url.
     *
     * @memberof Polymer.ResolveUrl
     * @param {string} url Input URL to transform
     * @return {string} resolved path
     */
    function pathFromUrl(url) {
      return url.substring(0, url.lastIndexOf('/') + 1);
    }

    /**
     * Module with utilities for resolving relative URL's.
     *
     * @namespace
     * @memberof Polymer
     * @summary Module with utilities for resolving relative URL's.
     */
    Polymer.ResolveUrl = {
      resolveCss: resolveCss,
      resolveUrl: resolveUrl,
      pathFromUrl: pathFromUrl
    };

  })();

</script>
<script>
/** @suppress {deprecated} */
(function() {
  'use strict';

  /**
   * Sets the global, legacy settings.
   *
   * @deprecated
   * @namespace
   * @memberof Polymer
   */
  Polymer.Settings = Polymer.Settings || {};

  Polymer.Settings.useShadow = !(window.ShadyDOM);
  Polymer.Settings.useNativeCSSProperties =
    Boolean(!window.ShadyCSS || window.ShadyCSS.nativeCss);
  Polymer.Settings.useNativeCustomElements =
    !(window.customElements.polyfillWrapFlushCallback);


  /**
   * Globally settable property that is automatically assigned to
   * `Polymer.ElementMixin` instances, useful for binding in templates to
   * make URL's relative to an application's root.  Defaults to the main
   * document URL, but can be overridden by users.  It may be useful to set
   * `Polymer.rootPath` to provide a stable application mount path when
   * using client side routing.
   *
   * @memberof Polymer
   */
  Polymer.rootPath = Polymer.rootPath ||
    Polymer.ResolveUrl.pathFromUrl(document.baseURI || window.location.href);

  /**
   * Sets the global rootPath property used by `Polymer.ElementMixin` and
   * available via `Polymer.rootPath`.
   *
   * @memberof Polymer
   * @param {string} path The new root path
   * @return {void}
   */
  Polymer.setRootPath = function(path) {
    Polymer.rootPath = path;
  };

  /**
   * A global callback used to sanitize any value before inserting it into the DOM. The callback signature is:
   *
   *     Polymer = {
   *       sanitizeDOMValue: function(value, name, type, node) { ... }
   *     }
   *
   * Where:
   *
   * `value` is the value to sanitize.
   * `name` is the name of an attribute or property (for example, href).
   * `type` indicates where the value is being inserted: one of property, attribute, or text.
   * `node` is the node where the value is being inserted.
   *
   * @type {(function(*,string,string,Node):*)|undefined}
   * @memberof Polymer
   */
  Polymer.sanitizeDOMValue = Polymer.sanitizeDOMValue || null;

  /**
   * Sets the global sanitizeDOMValue available via `Polymer.sanitizeDOMValue`.
   *
   * @memberof Polymer
   * @param {(function(*,string,string,Node):*)|undefined} newSanitizeDOMValue the global sanitizeDOMValue callback
   * @return {void}
   */
  Polymer.setSanitizeDOMValue = function(newSanitizeDOMValue) {
    Polymer.sanitizeDOMValue = newSanitizeDOMValue;
  };

  /**
   * Globally settable property to make Polymer Gestures use passive TouchEvent listeners when recognizing gestures.
   * When set to `true`, gestures made from touch will not be able to prevent scrolling, allowing for smoother
   * scrolling performance.
   * Defaults to `false` for backwards compatibility.
   *
   * @memberof Polymer
   */
  Polymer.passiveTouchGestures = Polymer.passiveTouchGestures || false;

  /**
   * Sets `passiveTouchGestures` globally for all elements using Polymer Gestures.
   *
   * @memberof Polymer
   * @param {boolean} usePassive enable or disable passive touch gestures globally
   * @return {void}
   */
  Polymer.setPassiveTouchGestures = function(usePassive) {
    Polymer.passiveTouchGestures = usePassive;
  };

  Polymer.legacyOptimizations = Polymer.legacyOptimizations ||
      window.PolymerSettings && window.PolymerSettings.legacyOptimizations || false;

  /**
   * Sets `legacyOptimizations` globally for all elements. Enables
   * optimizations when only legacy Polymer() style elements are used.
   *
   * @memberof Polymer
   * @param {boolean} useLegacyOptimizations enable or disable legacy optimizations globally.
   * @return {void}
   */
  Polymer.setLegacyOptimizations = function(useLegacyOptimizations) {
    Polymer.legacyOptimizations = useLegacyOptimizations;
  };

  Polymer.cancelSyntheticClickEvents = typeof Polymer.cancelSyntheticClickEvents === 'boolean'
    ? Polymer.cancelSyntheticClickEvents : true;

  /**
  * Sets `setCancelSyntheticEvents` globally for all elements to cancel synthetic click events
  * fired by older mobile browsers. Modern browsers no longer fire synthetic click events, and
  * the cancellation behavior can interfere when programmatically clicking on elements.
  *
  * @param {boolean} useCancelSyntheticClickEvents enable or disable cancelling synthetic
  * events
  * @return {void}
  */
  Polymer.setCancelSyntheticClickEvents = function(useCancelSyntheticClickEvents) {
    Polymer.cancelSyntheticClickEvents = useCancelSyntheticClickEvents;
  };
})();
</script>
<script>

(function() {

  'use strict';

  // unique global id for deduping mixins.
  let dedupeId = 0;

  /**
   * @constructor
   * @extends {Function}
   * @private
   */
  function MixinFunction(){}
  /** @type {(WeakMap | undefined)} */
  MixinFunction.prototype.__mixinApplications;
  /** @type {(Object | undefined)} */
  MixinFunction.prototype.__mixinSet;

  /* eslint-disable valid-jsdoc */
  /**
   * Wraps an ES6 class expression mixin such that the mixin is only applied
   * if it has not already been applied its base argument. Also memoizes mixin
   * applications.
   *
   * @memberof Polymer
   * @template T
   * @param {T} mixin ES6 class expression mixin to wrap
   * @return {T}
   * @suppress {invalidCasts}
   */
  Polymer.dedupingMixin = function(mixin) {
    let mixinApplications = /** @type {!MixinFunction} */(mixin).__mixinApplications;
    if (!mixinApplications) {
      mixinApplications = new WeakMap();
      /** @type {!MixinFunction} */(mixin).__mixinApplications = mixinApplications;
    }
    // maintain a unique id for each mixin
    let mixinDedupeId = dedupeId++;
    function dedupingMixin(base) {
      let baseSet = /** @type {!MixinFunction} */(base).__mixinSet;
      if (baseSet && baseSet[mixinDedupeId]) {
        return base;
      }
      let map = mixinApplications;
      let extended = map.get(base);
      if (!extended) {
        extended = /** @type {!Function} */(mixin)(base);
        map.set(base, extended);
      }
      // copy inherited mixin set from the extended class, or the base class
      // NOTE: we avoid use of Set here because some browser (IE11)
      // cannot extend a base Set via the constructor.
      let mixinSet = Object.create(/** @type {!MixinFunction} */(extended).__mixinSet || baseSet || null);
      mixinSet[mixinDedupeId] = true;
      /** @type {!MixinFunction} */(extended).__mixinSet = mixinSet;
      return extended;
    }

    return /** @type {T} */ (dedupingMixin);
  };
  /* eslint-enable valid-jsdoc */

})();

</script>
<script>
(function() {
  'use strict';

  const MODULE_STYLE_LINK_SELECTOR = 'link[rel=import][type~=css]';
  const INCLUDE_ATTR = 'include';
  const SHADY_UNSCOPED_ATTR = 'shady-unscoped';

  function importModule(moduleId) {
    const /** Polymer.DomModule */ PolymerDomModule = customElements.get('dom-module');
    if (!PolymerDomModule) {
      return null;
    }
    return PolymerDomModule.import(moduleId);
  }

  function styleForImport(importDoc) {
    // NOTE: polyfill affordance.
    // under the HTMLImports polyfill, there will be no 'body',
    // but the import pseudo-doc can be used directly.
    let container = importDoc.body ? importDoc.body : importDoc;
    const importCss = Polymer.ResolveUrl.resolveCss(container.textContent,
      importDoc.baseURI);
    const style = document.createElement('style');
    style.textContent = importCss;
    return style;
  }

  /** @typedef {{assetpath: string}} */
  let templateWithAssetPath; // eslint-disable-line no-unused-vars

  /**
   * Module with utilities for collection CSS text from `<templates>`, external
   * stylesheets, and `dom-module`s.
   *
   * @namespace
   * @memberof Polymer
   * @summary Module with utilities for collection CSS text from various sources.
   */
  const StyleGather = {

    /**
     * Returns a list of <style> elements in a space-separated list of `dom-module`s.
     *
     * @memberof Polymer.StyleGather
     * @param {string} moduleIds List of dom-module id's within which to
     * search for css.
     * @return {!Array<!HTMLStyleElement>} Array of contained <style> elements
     * @this {StyleGather}
     */
     stylesFromModules(moduleIds) {
      const modules = moduleIds.trim().split(/\s+/);
      const styles = [];
      for (let i=0; i < modules.length; i++) {
        styles.push(...this.stylesFromModule(modules[i]));
      }
      return styles;
    },

    /**
     * Returns a list of <style> elements in a given `dom-module`.
     * Styles in a `dom-module` can come either from `<style>`s within the
     * first `<template>`, or else from one or more
     * `<link rel="import" type="css">` links outside the template.
     *
     * @memberof Polymer.StyleGather
     * @param {string} moduleId dom-module id to gather styles from
     * @return {!Array<!HTMLStyleElement>} Array of contained styles.
     * @this {StyleGather}
     */
    stylesFromModule(moduleId) {
      const m = importModule(moduleId);

      if (!m) {
        console.warn('Could not find style data in module named', moduleId);
        return [];
      }

      if (m._styles === undefined) {
        const styles = [];
        // module imports: <link rel="import" type="css">
        styles.push(...this._stylesFromModuleImports(m));
        // include css from the first template in the module
        const template = m.querySelector('template');
        if (template) {
          styles.push(...this.stylesFromTemplate(template,
            /** @type {templateWithAssetPath} */(m).assetpath));
        }

        m._styles = styles;
      }

      return m._styles;
    },

    /**
     * Returns the `<style>` elements within a given template.
     *
     * @memberof Polymer.StyleGather
     * @param {!HTMLTemplateElement} template Template to gather styles from
     * @param {string} baseURI baseURI for style content
     * @return {!Array<!HTMLStyleElement>} Array of styles
     * @this {StyleGather}
     */
    stylesFromTemplate(template, baseURI) {
      if (!template._styles) {
        const styles = [];
        // if element is a template, get content from its .content
        const e$ = template.content.querySelectorAll('style');
        for (let i=0; i < e$.length; i++) {
          let e = e$[i];
          // support style sharing by allowing styles to "include"
          // other dom-modules that contain styling
          let include = e.getAttribute(INCLUDE_ATTR);
          if (include) {
            styles.push(...this.stylesFromModules(include).filter(function(item, index, self) {
              return self.indexOf(item) === index;
            }));
          }
          if (baseURI) {
            e.textContent = Polymer.ResolveUrl.resolveCss(e.textContent, baseURI);
          }
          styles.push(e);
        }
        template._styles = styles;
      }
      return template._styles;
    },

    /**
     * Returns a list of <style> elements  from stylesheets loaded via `<link rel="import" type="css">` links within the specified `dom-module`.
     *
     * @memberof Polymer.StyleGather
     * @param {string} moduleId Id of `dom-module` to gather CSS from
     * @return {!Array<!HTMLStyleElement>} Array of contained styles.
     * @this {StyleGather}
     */
     stylesFromModuleImports(moduleId) {
      let m = importModule(moduleId);
      return m ? this._stylesFromModuleImports(m) : [];
    },

    /**
     * @memberof Polymer.StyleGather
     * @this {StyleGather}
     * @param {!HTMLElement} module dom-module element that could contain `<link rel="import" type="css">` styles
     * @return {!Array<!HTMLStyleElement>} Array of contained styles
     */
    _stylesFromModuleImports(module) {
      const styles = [];
      const p$ = module.querySelectorAll(MODULE_STYLE_LINK_SELECTOR);
      for (let i=0; i < p$.length; i++) {
        let p = p$[i];
        if (p.import) {
          const importDoc = p.import;
          const unscoped = p.hasAttribute(SHADY_UNSCOPED_ATTR);
          if (unscoped && !importDoc._unscopedStyle) {
            const style = styleForImport(importDoc);
            style.setAttribute(SHADY_UNSCOPED_ATTR, '');
            importDoc._unscopedStyle = style;
          } else if (!importDoc._style) {
            importDoc._style = styleForImport(importDoc);
          }
          styles.push(unscoped ? importDoc._unscopedStyle : importDoc._style);
        }
      }
      return styles;
    },

    /**
     *
     * Returns CSS text of styles in a space-separated list of `dom-module`s.
     * Note: This method is deprecated, use `stylesFromModules` instead.
     *
     * @deprecated
     * @memberof Polymer.StyleGather
     * @param {string} moduleIds List of dom-module id's within which to
     * search for css.
     * @return {string} Concatenated CSS content from specified `dom-module`s
     * @this {StyleGather}
     */
     cssFromModules(moduleIds) {
      let modules = moduleIds.trim().split(/\s+/);
      let cssText = '';
      for (let i=0; i < modules.length; i++) {
        cssText += this.cssFromModule(modules[i]);
      }
      return cssText;
    },

    /**
     * Returns CSS text of styles in a given `dom-module`.  CSS in a `dom-module`
     * can come either from `<style>`s within the first `<template>`, or else
     * from one or more `<link rel="import" type="css">` links outside the
     * template.
     *
     * Any `<styles>` processed are removed from their original location.
     * Note: This method is deprecated, use `styleFromModule` instead.
     *
     * @deprecated
     * @memberof Polymer.StyleGather
     * @param {string} moduleId dom-module id to gather styles from
     * @return {string} Concatenated CSS content from specified `dom-module`
     * @this {StyleGather}
     */
    cssFromModule(moduleId) {
      let m = importModule(moduleId);
      if (m && m._cssText === undefined) {
        // module imports: <link rel="import" type="css">
        let cssText = this._cssFromModuleImports(m);
        // include css from the first template in the module
        let t = m.querySelector('template');
        if (t) {
          cssText += this.cssFromTemplate(t,
            /** @type {templateWithAssetPath} */(m).assetpath);
        }
        m._cssText = cssText || null;
      }
      if (!m) {
        console.warn('Could not find style data in module named', moduleId);
      }
      return m && m._cssText || '';
    },

    /**
     * Returns CSS text of `<styles>` within a given template.
     *
     * Any `<styles>` processed are removed from their original location.
     * Note: This method is deprecated, use `styleFromTemplate` instead.
     *
     * @deprecated
     * @memberof Polymer.StyleGather
     * @param {!HTMLTemplateElement} template Template to gather styles from
     * @param {string} baseURI Base URI to resolve the URL against
     * @return {string} Concatenated CSS content from specified template
     * @this {StyleGather}
     */
    cssFromTemplate(template, baseURI) {
      let cssText = '';
      const e$ = this.stylesFromTemplate(template, baseURI);
      // if element is a template, get content from its .content
      for (let i=0; i < e$.length; i++) {
        let e = e$[i];
        if (e.parentNode) {
          e.parentNode.removeChild(e);
        }
        cssText += e.textContent;
      }
      return cssText;
    },

    /**
     * Returns CSS text from stylesheets loaded via `<link rel="import" type="css">`
     * links within the specified `dom-module`.
     *
     * Note: This method is deprecated, use `stylesFromModuleImports` instead.
     *
     * @deprecated
     *
     * @memberof Polymer.StyleGather
     * @param {string} moduleId Id of `dom-module` to gather CSS from
     * @return {string} Concatenated CSS content from links in specified `dom-module`
     * @this {StyleGather}
     */
    cssFromModuleImports(moduleId) {
      let m = importModule(moduleId);
      return m ? this._cssFromModuleImports(m) : '';
    },

    /**
     * @deprecated
     * @memberof Polymer.StyleGather
     * @this {StyleGather}
     * @param {!HTMLElement} module dom-module element that could contain `<link rel="import" type="css">` styles
     * @return {string} Concatenated CSS content from links in the dom-module
     */
     _cssFromModuleImports(module) {
      let cssText = '';
      let styles = this._stylesFromModuleImports(module);
      for (let i=0; i < styles.length; i++) {
        cssText += styles[i].textContent;
      }
      return cssText;
    }
  };

  Polymer.StyleGather = StyleGather;
})();
</script>
<script>
(function() {
  'use strict';

  let modules = {};
  let lcModules = {};
  function setModule(id, module) {
    // store id separate from lowercased id so that
    // in all cases mixedCase id will stored distinctly
    // and lowercase version is a fallback
    modules[id] = lcModules[id.toLowerCase()] = module;
  }
  function findModule(id) {
    return modules[id] || lcModules[id.toLowerCase()];
  }

  function styleOutsideTemplateCheck(inst) {
    if (inst.querySelector('style')) {
      console.warn('dom-module %s has style outside template', inst.id);
    }
  }

  /**
   * The `dom-module` element registers the dom it contains to the name given
   * by the module's id attribute. It provides a unified database of dom
   * accessible via its static `import` API.
   *
   * A key use case of `dom-module` is for providing custom element `<template>`s
   * via HTML imports that are parsed by the native HTML parser, that can be
   * relocated during a bundling pass and still looked up by `id`.
   *
   * Example:
   *
   *     <dom-module id="foo">
   *       <img src="stuff.png">
   *     </dom-module>
   *
   * Then in code in some other location that cannot access the dom-module above
   *
   *     let img = customElements.get('dom-module').import('foo', 'img');
   *
   * @customElement
   * @extends HTMLElement
   * @memberof Polymer
   * @summary Custom element that provides a registry of relocatable DOM content
   *   by `id` that is agnostic to bundling.
   * @unrestricted
   */
  class DomModule extends HTMLElement {

    static get observedAttributes() { return ['id']; }

    /**
     * Retrieves the element specified by the css `selector` in the module
     * registered by `id`. For example, this.import('foo', 'img');
     * @param {string} id The id of the dom-module in which to search.
     * @param {string=} selector The css selector by which to find the element.
     * @return {Element} Returns the element which matches `selector` in the
     * module registered at the specified `id`.
     */
    static import(id, selector) {
      if (id) {
        let m = findModule(id);
        if (m && selector) {
          return m.querySelector(selector);
        }
        return m;
      }
      return null;
    }

    /* eslint-disable no-unused-vars */
    /**
     * @param {string} name Name of attribute.
     * @param {?string} old Old value of attribute.
     * @param {?string} value Current value of attribute.
     * @param {?string} namespace Attribute namespace.
     * @return {void}
     */
    attributeChangedCallback(name, old, value, namespace) {
      if (old !== value) {
        this.register();
      }
    }
    /* eslint-enable no-unused-args */

    /**
     * The absolute URL of the original location of this `dom-module`.
     *
     * This value will differ from this element's `ownerDocument` in the
     * following ways:
     * - Takes into account any `assetpath` attribute added during bundling
     *   to indicate the original location relative to the bundled location
     * - Uses the HTMLImports polyfill's `importForElement` API to ensure
     *   the path is relative to the import document's location since
     *   `ownerDocument` is not currently polyfilled
     */
    get assetpath() {
      // Don't override existing assetpath.
      if (!this.__assetpath) {
        // note: assetpath set via an attribute must be relative to this
        // element's location; accomodate polyfilled HTMLImports
        const owner = window.HTMLImports && HTMLImports.importForElement ?
          HTMLImports.importForElement(this) || document : this.ownerDocument;
        const url = Polymer.ResolveUrl.resolveUrl(
          this.getAttribute('assetpath') || '', owner.baseURI);
        this.__assetpath = Polymer.ResolveUrl.pathFromUrl(url);
      }
      return this.__assetpath;
    }

    /**
     * Registers the dom-module at a given id. This method should only be called
     * when a dom-module is imperatively created. For
     * example, `document.createElement('dom-module').register('foo')`.
     * @param {string=} id The id at which to register the dom-module.
     * @return {void}
     */
    register(id) {
      id = id || this.id;
      if (id) {
        // Under strictTemplatePolicy, reject and null out any re-registered
        // dom-module since it is ambiguous whether first-in or last-in is trusted 
        if (Polymer.strictTemplatePolicy && findModule(id) !== undefined) {
          setModule(id, null);
          throw new Error(`strictTemplatePolicy: dom-module ${id} re-registered`);
        }
        this.id = id;
        setModule(id, this);
        styleOutsideTemplateCheck(this);
      }
    }
  }

  DomModule.prototype['modules'] = modules;

  customElements.define('dom-module', DomModule);

  /** @const */
  Polymer.DomModule = DomModule;

})();
</script>
<script>
(function() {
  'use strict';

  /**
   * Module with utilities for manipulating structured data path strings.
   *
   * @namespace
   * @memberof Polymer
   * @summary Module with utilities for manipulating structured data path strings.
   */
  const Path = {

    /**
     * Returns true if the given string is a structured data path (has dots).
     *
     * Example:
     *
     * ```
     * Polymer.Path.isPath('foo.bar.baz') // true
     * Polymer.Path.isPath('foo')         // false
     * ```
     *
     * @memberof Polymer.Path
     * @param {string} path Path string
     * @return {boolean} True if the string contained one or more dots
     */
    isPath: function(path) {
      return path.indexOf('.') >= 0;
    },

    /**
     * Returns the root property name for the given path.
     *
     * Example:
     *
     * ```
     * Polymer.Path.root('foo.bar.baz') // 'foo'
     * Polymer.Path.root('foo')         // 'foo'
     * ```
     *
     * @memberof Polymer.Path
     * @param {string} path Path string
     * @return {string} Root property name
     */
    root: function(path) {
      let dotIndex = path.indexOf('.');
      if (dotIndex === -1) {
        return path;
      }
      return path.slice(0, dotIndex);
    },

    /**
     * Given `base` is `foo.bar`, `foo` is an ancestor, `foo.bar` is not
     * Returns true if the given path is an ancestor of the base path.
     *
     * Example:
     *
     * ```
     * Polymer.Path.isAncestor('foo.bar', 'foo')         // true
     * Polymer.Path.isAncestor('foo.bar', 'foo.bar')     // false
     * Polymer.Path.isAncestor('foo.bar', 'foo.bar.baz') // false
     * ```
     *
     * @memberof Polymer.Path
     * @param {string} base Path string to test against.
     * @param {string} path Path string to test.
     * @return {boolean} True if `path` is an ancestor of `base`.
     */
    isAncestor: function(base, path) {
      //     base.startsWith(path + '.');
      return base.indexOf(path + '.') === 0;
    },

    /**
     * Given `base` is `foo.bar`, `foo.bar.baz` is an descendant
     *
     * Example:
     *
     * ```
     * Polymer.Path.isDescendant('foo.bar', 'foo.bar.baz') // true
     * Polymer.Path.isDescendant('foo.bar', 'foo.bar')     // false
     * Polymer.Path.isDescendant('foo.bar', 'foo')         // false
     * ```
     *
     * @memberof Polymer.Path
     * @param {string} base Path string to test against.
     * @param {string} path Path string to test.
     * @return {boolean} True if `path` is a descendant of `base`.
     */
    isDescendant: function(base, path) {
      //     path.startsWith(base + '.');
      return path.indexOf(base + '.') === 0;
    },

    /**
     * Replaces a previous base path with a new base path, preserving the
     * remainder of the path.
     *
     * User must ensure `path` has a prefix of `base`.
     *
     * Example:
     *
     * ```
     * Polymer.Path.translate('foo.bar', 'zot', 'foo.bar.baz') // 'zot.baz'
     * ```
     *
     * @memberof Polymer.Path
     * @param {string} base Current base string to remove
     * @param {string} newBase New base string to replace with
     * @param {string} path Path to translate
     * @return {string} Translated string
     */
    translate: function(base, newBase, path) {
      return newBase + path.slice(base.length);
    },

    /**
     * @param {string} base Path string to test against
     * @param {string} path Path string to test
     * @return {boolean} True if `path` is equal to `base`
     * @this {Path}
     */
    matches: function(base, path) {
      return (base === path) ||
             this.isAncestor(base, path) ||
             this.isDescendant(base, path);
    },

    /**
     * Converts array-based paths to flattened path.  String-based paths
     * are returned as-is.
     *
     * Example:
     *
     * ```
     * Polymer.Path.normalize(['foo.bar', 0, 'baz'])  // 'foo.bar.0.baz'
     * Polymer.Path.normalize('foo.bar.0.baz')        // 'foo.bar.0.baz'
     * ```
     *
     * @memberof Polymer.Path
     * @param {string | !Array<string|number>} path Input path
     * @return {string} Flattened path
     */
    normalize: function(path) {
      if (Array.isArray(path)) {
        let parts = [];
        for (let i=0; i<path.length; i++) {
          let args = path[i].toString().split('.');
          for (let j=0; j<args.length; j++) {
            parts.push(args[j]);
          }
        }
        return parts.join('.');
      } else {
        return path;
      }
    },

    /**
     * Splits a path into an array of property names. Accepts either arrays
     * of path parts or strings.
     *
     * Example:
     *
     * ```
     * Polymer.Path.split(['foo.bar', 0, 'baz'])  // ['foo', 'bar', '0', 'baz']
     * Polymer.Path.split('foo.bar.0.baz')        // ['foo', 'bar', '0', 'baz']
     * ```
     *
     * @memberof Polymer.Path
     * @param {string | !Array<string|number>} path Input path
     * @return {!Array<string>} Array of path parts
     * @this {Path}
     * @suppress {checkTypes}
     */
    split: function(path) {
      if (Array.isArray(path)) {
        return this.normalize(path).split('.');
      }
      return path.toString().split('.');
    },

    /**
     * Reads a value from a path.  If any sub-property in the path is `undefined`,
     * this method returns `undefined` (will never throw.
     *
     * @memberof Polymer.Path
     * @param {Object} root Object from which to dereference path from
     * @param {string | !Array<string|number>} path Path to read
     * @param {Object=} info If an object is provided to `info`, the normalized
     *  (flattened) path will be set to `info.path`.
     * @return {*} Value at path, or `undefined` if the path could not be
     *  fully dereferenced.
     * @this {Path}
     */
    get: function(root, path, info) {
      let prop = root;
      let parts = this.split(path);
      // Loop over path parts[0..n-1] and dereference
      for (let i=0; i<parts.length; i++) {
        if (!prop) {
          return;
        }
        let part = parts[i];
        prop = prop[part];
      }
      if (info) {
        info.path = parts.join('.');
      }
      return prop;
    },

    /**
     * Sets a value to a path.  If any sub-property in the path is `undefined`,
     * this method will no-op.
     *
     * @memberof Polymer.Path
     * @param {Object} root Object from which to dereference path from
     * @param {string | !Array<string|number>} path Path to set
     * @param {*} value Value to set to path
     * @return {string | undefined} The normalized version of the input path
     * @this {Path}
     */
    set: function(root, path, value) {
      let prop = root;
      let parts = this.split(path);
      let last = parts[parts.length-1];
      if (parts.length > 1) {
        // Loop over path parts[0..n-2] and dereference
        for (let i=0; i<parts.length-1; i++) {
          let part = parts[i];
          prop = prop[part];
          if (!prop) {
            return;
          }
        }
        // Set value to object at end of path
        prop[last] = value;
      } else {
        // Simple property set
        prop[path] = value;
      }
      return parts.join('.');
    }

  };

  /**
   * Returns true if the given string is a structured data path (has dots).
   *
   * This function is deprecated.  Use `Polymer.Path.isPath` instead.
   *
   * Example:
   *
   * ```
   * Polymer.Path.isDeep('foo.bar.baz') // true
   * Polymer.Path.isDeep('foo')         // false
   * ```
   *
   * @deprecated
   * @memberof Polymer.Path
   * @param {string} path Path string
   * @return {boolean} True if the string contained one or more dots
   */
  Path.isDeep = Path.isPath;

  Polymer.Path = Path;

})();
</script>
<script>
(function() {
  'use strict';

  const caseMap = {};
  const DASH_TO_CAMEL = /-[a-z]/g;
  const CAMEL_TO_DASH = /([A-Z])/g;

  /**
   * Module with utilities for converting between "dash-case" and "camelCase"
   * identifiers.
   *
   * @namespace
   * @memberof Polymer
   * @summary Module that provides utilities for converting between "dash-case"
   *   and "camelCase".
   */
  const CaseMap = {

    /**
     * Converts "dash-case" identifier (e.g. `foo-bar-baz`) to "camelCase"
     * (e.g. `fooBarBaz`).
     *
     * @memberof Polymer.CaseMap
     * @param {string} dash Dash-case identifier
     * @return {string} Camel-case representation of the identifier
     */
    dashToCamelCase(dash) {
      return caseMap[dash] || (
        caseMap[dash] = dash.indexOf('-') < 0 ? dash : dash.replace(DASH_TO_CAMEL,
          (m) => m[1].toUpperCase()
        )
      );
    },

    /**
     * Converts "camelCase" identifier (e.g. `fooBarBaz`) to "dash-case"
     * (e.g. `foo-bar-baz`).
     *
     * @memberof Polymer.CaseMap
     * @param {string} camel Camel-case identifier
     * @return {string} Dash-case representation of the identifier
     */
    camelToDashCase(camel) {
      return caseMap[camel] || (
        caseMap[camel] = camel.replace(CAMEL_TO_DASH, '-$1').toLowerCase()
      );
    }

  };

  Polymer.CaseMap = CaseMap;
})();
</script>
<script>
(function() {

  'use strict';

  // Microtask implemented using Mutation Observer
  let microtaskCurrHandle = 0;
  let microtaskLastHandle = 0;
  let microtaskCallbacks = [];
  let microtaskNodeContent = 0;
  let microtaskNode = document.createTextNode('');
  new window.MutationObserver(microtaskFlush).observe(microtaskNode, {characterData: true});

  function microtaskFlush() {
    const len = microtaskCallbacks.length;
    for (let i = 0; i < len; i++) {
      let cb = microtaskCallbacks[i];
      if (cb) {
        try {
          cb();
        } catch (e) {
          setTimeout(() => { throw e; });
        }
      }
    }
    microtaskCallbacks.splice(0, len);
    microtaskLastHandle += len;
  }

  /**
   * Module that provides a number of strategies for enqueuing asynchronous
   * tasks.  Each sub-module provides a standard `run(fn)` interface that returns a
   * handle, and a `cancel(handle)` interface for canceling async tasks before
   * they run.
   *
   * @namespace
   * @memberof Polymer
   * @summary Module that provides a number of strategies for enqueuing asynchronous
   * tasks.
   */
  Polymer.Async = {

    /**
     * Async interface wrapper around `setTimeout`.
     *
     * @namespace
     * @memberof Polymer.Async
     * @summary Async interface wrapper around `setTimeout`.
     */
    timeOut: {
      /**
       * Returns a sub-module with the async interface providing the provided
       * delay.
       *
       * @memberof Polymer.Async.timeOut
       * @param {number=} delay Time to wait before calling callbacks in ms
       * @return {!AsyncInterface} An async timeout interface
       */
      after(delay) {
        return {
          run(fn) { return window.setTimeout(fn, delay); },
          cancel(handle) {
            window.clearTimeout(handle);
          }
        };
      },
      /**
       * Enqueues a function called in the next task.
       *
       * @memberof Polymer.Async.timeOut
       * @param {!Function} fn Callback to run
       * @param {number=} delay Delay in milliseconds
       * @return {number} Handle used for canceling task
       */
      run(fn, delay) {
        return window.setTimeout(fn, delay);
      },
      /**
       * Cancels a previously enqueued `timeOut` callback.
       *
       * @memberof Polymer.Async.timeOut
       * @param {number} handle Handle returned from `run` of callback to cancel
       * @return {void}
       */
      cancel(handle) {
        window.clearTimeout(handle);
      }
    },

    /**
     * Async interface wrapper around `requestAnimationFrame`.
     *
     * @namespace
     * @memberof Polymer.Async
     * @summary Async interface wrapper around `requestAnimationFrame`.
     */
    animationFrame: {
      /**
       * Enqueues a function called at `requestAnimationFrame` timing.
       *
       * @memberof Polymer.Async.animationFrame
       * @param {function(number):void} fn Callback to run
       * @return {number} Handle used for canceling task
       */
      run(fn) {
        return window.requestAnimationFrame(fn);
      },
      /**
       * Cancels a previously enqueued `animationFrame` callback.
       *
       * @memberof Polymer.Async.animationFrame
       * @param {number} handle Handle returned from `run` of callback to cancel
       * @return {void}
       */
      cancel(handle) {
        window.cancelAnimationFrame(handle);
      }
    },

    /**
     * Async interface wrapper around `requestIdleCallback`.  Falls back to
     * `setTimeout` on browsers that do not support `requestIdleCallback`.
     *
     * @namespace
     * @memberof Polymer.Async
     * @summary Async interface wrapper around `requestIdleCallback`.
     */
    idlePeriod: {
      /**
       * Enqueues a function called at `requestIdleCallback` timing.
       *
       * @memberof Polymer.Async.idlePeriod
       * @param {function(!IdleDeadline):void} fn Callback to run
       * @return {number} Handle used for canceling task
       */
      run(fn) {
        return window.requestIdleCallback ?
          window.requestIdleCallback(fn) :
          window.setTimeout(fn, 16);
      },
      /**
       * Cancels a previously enqueued `idlePeriod` callback.
       *
       * @memberof Polymer.Async.idlePeriod
       * @param {number} handle Handle returned from `run` of callback to cancel
       * @return {void}
       */
      cancel(handle) {
        window.cancelIdleCallback ?
          window.cancelIdleCallback(handle) :
          window.clearTimeout(handle);
      }
    },

    /**
     * Async interface for enqueuing callbacks that run at microtask timing.
     *
     * Note that microtask timing is achieved via a single `MutationObserver`,
     * and thus callbacks enqueued with this API will all run in a single
     * batch, and not interleaved with other microtasks such as promises.
     * Promises are avoided as an implementation choice for the time being
     * due to Safari bugs that cause Promises to lack microtask guarantees.
     *
     * @namespace
     * @memberof Polymer.Async
     * @summary Async interface for enqueuing callbacks that run at microtask
     *   timing.
     */
    microTask: {

      /**
       * Enqueues a function called at microtask timing.
       *
       * @memberof Polymer.Async.microTask
       * @param {!Function=} callback Callback to run
       * @return {number} Handle used for canceling task
       */
      run(callback) {
        microtaskNode.textContent = microtaskNodeContent++;
        microtaskCallbacks.push(callback);
        return microtaskCurrHandle++;
      },

      /**
       * Cancels a previously enqueued `microTask` callback.
       *
       * @memberof Polymer.Async.microTask
       * @param {number} handle Handle returned from `run` of callback to cancel
       * @return {void}
       */
      cancel(handle) {
        const idx = handle - microtaskLastHandle;
        if (idx >= 0) {
          if (!microtaskCallbacks[idx]) {
            throw new Error('invalid async handle: ' + handle);
          }
          microtaskCallbacks[idx] = null;
        }
      }

    }
  };

})();
</script>
<script>
  (function () {

    'use strict';

    /** @const {!AsyncInterface} */
    const microtask = Polymer.Async.microTask;

    /**
     * Element class mixin that provides basic meta-programming for creating one
     * or more property accessors (getter/setter pair) that enqueue an async
     * (batched) `_propertiesChanged` callback.
     *
     * For basic usage of this mixin, call `MyClass.createProperties(props)`
     * once at class definition time to create property accessors for properties
     * named in props, implement `_propertiesChanged` to react as desired to
     * property changes, and implement `static get observedAttributes()` and
     * include lowercase versions of any property names that should be set from
     * attributes. Last, call `this._enableProperties()` in the element's
     * `connectedCallback` to enable the accessors.
     *
     * @mixinFunction
     * @polymer
     * @memberof Polymer
     * @summary Element class mixin for reacting to property changes from
     *   generated property accessors.
     */
    Polymer.PropertiesChanged = Polymer.dedupingMixin(superClass => {

      /**
       * @polymer
       * @mixinClass
       * @extends {superClass}
       * @implements {Polymer_PropertiesChanged}
       * @unrestricted
       */
      class PropertiesChanged extends superClass {

        /**
         * Creates property accessors for the given property names.
         * @param {!Object} props Object whose keys are names of accessors.
         * @return {void}
         * @protected
         */
        static createProperties(props) {
          const proto = this.prototype;
          for (let prop in props) {
            // don't stomp an existing accessor
            if (!(prop in proto)) {
              proto._createPropertyAccessor(prop);
            }
          }
        }

        /**
         * Returns an attribute name that corresponds to the given property.
         * The attribute name is the lowercased property name. Override to
         * customize this mapping.
         * @param {string} property Property to convert
         * @return {string} Attribute name corresponding to the given property.
         *
         * @protected
         */
        static attributeNameForProperty(property) {
          return property.toLowerCase();
        }

        /**
         * Override point to provide a type to which to deserialize a value to
         * a given property.
         * @param {string} name Name of property
         *
         * @protected
         */
        static typeForProperty(name) { } //eslint-disable-line no-unused-vars

        /**
         * Creates a setter/getter pair for the named property with its own
         * local storage.  The getter returns the value in the local storage,
         * and the setter calls `_setProperty`, which updates the local storage
         * for the property and enqueues a `_propertiesChanged` callback.
         *
         * This method may be called on a prototype or an instance.  Calling
         * this method may overwrite a property value that already exists on
         * the prototype/instance by creating the accessor.
         *
         * @param {string} property Name of the property
         * @param {boolean=} readOnly When true, no setter is created; the
         *   protected `_setProperty` function must be used to set the property
         * @return {void}
         * @protected
         */
        _createPropertyAccessor(property, readOnly) {
          this._addPropertyToAttributeMap(property);
          if (!this.hasOwnProperty('__dataHasAccessor')) {
            this.__dataHasAccessor = Object.assign({}, this.__dataHasAccessor);
          }
          if (!this.__dataHasAccessor[property]) {
            this.__dataHasAccessor[property] = true;
            this._definePropertyAccessor(property, readOnly);
          }
        }

        /**
         * Adds the given `property` to a map matching attribute names
         * to property names, using `attributeNameForProperty`. This map is
         * used when deserializing attribute values to properties.
         *
         * @param {string} property Name of the property
         */
        _addPropertyToAttributeMap(property) {
          if (!this.hasOwnProperty('__dataAttributes')) {
            this.__dataAttributes = Object.assign({}, this.__dataAttributes);
          }
          if (!this.__dataAttributes[property]) {
            const attr = this.constructor.attributeNameForProperty(property);
            this.__dataAttributes[attr] = property;
          }
        }

        /**
         * Defines a property accessor for the given property.
         * @param {string} property Name of the property
         * @param {boolean=} readOnly When true, no setter is created
         * @return {void}
         */
         _definePropertyAccessor(property, readOnly) {
          Object.defineProperty(this, property, {
            /* eslint-disable valid-jsdoc */
            /** @this {PropertiesChanged} */
            get() {
              return this._getProperty(property);
            },
            /** @this {PropertiesChanged} */
            set: readOnly ? function () {} : function (value) {
              this._setProperty(property, value);
            }
            /* eslint-enable */
          });
        }

        constructor() {
          super();
          this.__dataEnabled = false;
          this.__dataReady = false;
          this.__dataInvalid = false;
          this.__data = {};
          this.__dataPending = null;
          this.__dataOld = null;
          this.__dataInstanceProps = null;
          this.__serializing = false;
          this._initializeProperties();
        }

        /**
         * Lifecycle callback called when properties are enabled via
         * `_enableProperties`.
         *
         * Users may override this function to implement behavior that is
         * dependent on the element having its property data initialized, e.g.
         * from defaults (initialized from `constructor`, `_initializeProperties`),
         * `attributeChangedCallback`, or values propagated from host e.g. via
         * bindings.  `super.ready()` must be called to ensure the data system
         * becomes enabled.
         *
         * @return {void}
         * @public
         */
        ready() {
          this.__dataReady = true;
          this._flushProperties();
        }

        /**
         * Initializes the local storage for property accessors.
         *
         * Provided as an override point for performing any setup work prior
         * to initializing the property accessor system.
         *
         * @return {void}
         * @protected
         */
        _initializeProperties() {
          // Capture instance properties; these will be set into accessors
          // during first flush. Don't set them here, since we want
          // these to overwrite defaults/constructor assignments
          for (let p in this.__dataHasAccessor) {
            if (this.hasOwnProperty(p)) {
              this.__dataInstanceProps = this.__dataInstanceProps || {};
              this.__dataInstanceProps[p] = this[p];
              delete this[p];
            }
          }
        }

        /**
         * Called at ready time with bag of instance properties that overwrote
         * accessors when the element upgraded.
         *
         * The default implementation sets these properties back into the
         * setter at ready time.  This method is provided as an override
         * point for customizing or providing more efficient initialization.
         *
         * @param {Object} props Bag of property values that were overwritten
         *   when creating property accessors.
         * @return {void}
         * @protected
         */
        _initializeInstanceProperties(props) {
          Object.assign(this, props);
        }

        /**
         * Updates the local storage for a property (via `_setPendingProperty`)
         * and enqueues a `_proeprtiesChanged` callback.
         *
         * @param {string} property Name of the property
         * @param {*} value Value to set
         * @return {void}
         * @protected
         */
        _setProperty(property, value) {
          if (this._setPendingProperty(property, value)) {
            this._invalidateProperties();
          }
        }

        /**
         * Returns the value for the given property.
         * @param {string} property Name of property
         * @return {*} Value for the given property
         * @protected
         */
        _getProperty(property) {
          return this.__data[property];
        }

        /* eslint-disable no-unused-vars */
        /**
         * Updates the local storage for a property, records the previous value,
         * and adds it to the set of "pending changes" that will be passed to the
         * `_propertiesChanged` callback.  This method does not enqueue the
         * `_propertiesChanged` callback.
         *
         * @param {string} property Name of the property
         * @param {*} value Value to set
         * @param {boolean=} ext Not used here; affordance for closure
         * @return {boolean} Returns true if the property changed
         * @protected
         */
        _setPendingProperty(property, value, ext) {
          let old = this.__data[property];
          let changed = this._shouldPropertyChange(property, value, old);
          if (changed) {
            if (!this.__dataPending) {
              this.__dataPending = {};
              this.__dataOld = {};
            }
            // Ensure old is captured from the last turn
            if (this.__dataOld && !(property in this.__dataOld)) {
              this.__dataOld[property] = old;
            }
            this.__data[property] = value;
            this.__dataPending[property] = value;
          }
          return changed;
        }
        /* eslint-enable */

        /**
         * Marks the properties as invalid, and enqueues an async
         * `_propertiesChanged` callback.
         *
         * @return {void}
         * @protected
         */
        _invalidateProperties() {
          if (!this.__dataInvalid && this.__dataReady) {
            this.__dataInvalid = true;
            microtask.run(() => {
              if (this.__dataInvalid) {
                this.__dataInvalid = false;
                this._flushProperties();
              }
            });
          }
        }

        /**
         * Call to enable property accessor processing. Before this method is
         * called accessor values will be set but side effects are
         * queued. When called, any pending side effects occur immediately.
         * For elements, generally `connectedCallback` is a normal spot to do so.
         * It is safe to call this method multiple times as it only turns on
         * property accessors once.
         *
         * @return {void}
         * @protected
         */
        _enableProperties() {
          if (!this.__dataEnabled) {
            this.__dataEnabled = true;
            if (this.__dataInstanceProps) {
              this._initializeInstanceProperties(this.__dataInstanceProps);
              this.__dataInstanceProps = null;
            }
            this.ready();
          }
        }

        /**
         * Calls the `_propertiesChanged` callback with the current set of
         * pending changes (and old values recorded when pending changes were
         * set), and resets the pending set of changes. Generally, this method
         * should not be called in user code.
         *
         * @return {void}
         * @protected
         */
        _flushProperties() {
          const props = this.__data;
          const changedProps = this.__dataPending;
          const old = this.__dataOld;
          if (this._shouldPropertiesChange(props, changedProps, old)) {
            this.__dataPending = null;
            this.__dataOld = null;
            this._propertiesChanged(props, changedProps, old);
          }
        }

        /**
         * Called in `_flushProperties` to determine if `_propertiesChanged`
         * should be called. The default implementation returns true if
         * properties are pending. Override to customize when
         * `_propertiesChanged` is called.
         * @param {!Object} currentProps Bag of all current accessor values
         * @param {!Object} changedProps Bag of properties changed since the last
         *   call to `_propertiesChanged`
         * @param {!Object} oldProps Bag of previous values for each property
         *   in `changedProps`
         * @return {boolean} true if changedProps is truthy
         */
        _shouldPropertiesChange(currentProps, changedProps, oldProps) { // eslint-disable-line no-unused-vars
          return Boolean(changedProps);
        }

        /**
         * Callback called when any properties with accessors created via
         * `_createPropertyAccessor` have been set.
         *
         * @param {!Object} currentProps Bag of all current accessor values
         * @param {!Object} changedProps Bag of properties changed since the last
         *   call to `_propertiesChanged`
         * @param {!Object} oldProps Bag of previous values for each property
         *   in `changedProps`
         * @return {void}
         * @protected
         */
        _propertiesChanged(currentProps, changedProps, oldProps) { // eslint-disable-line no-unused-vars
        }

        /**
         * Method called to determine whether a property value should be
         * considered as a change and cause the `_propertiesChanged` callback
         * to be enqueued.
         *
         * The default implementation returns `true` if a strict equality
         * check fails. The method always returns false for `NaN`.
         *
         * Override this method to e.g. provide stricter checking for
         * Objects/Arrays when using immutable patterns.
         *
         * @param {string} property Property name
         * @param {*} value New property value
         * @param {*} old Previous property value
         * @return {boolean} Whether the property should be considered a change
         *   and enqueue a `_proeprtiesChanged` callback
         * @protected
         */
        _shouldPropertyChange(property, value, old) {
          return (
            // Strict equality check
            (old !== value &&
              // This ensures (old==NaN, value==NaN) always returns false
              (old === old || value === value))
          );
        }

        /**
         * Implements native Custom Elements `attributeChangedCallback` to
         * set an attribute value to a property via `_attributeToProperty`.
         *
         * @param {string} name Name of attribute that changed
         * @param {?string} old Old attribute value
         * @param {?string} value New attribute value
         * @param {?string} namespace Attribute namespace.
         * @return {void}
         * @suppress {missingProperties} Super may or may not implement the callback
         */
        attributeChangedCallback(name, old, value, namespace) {
          if (old !== value) {
            this._attributeToProperty(name, value);
          }
          if (super.attributeChangedCallback) {
            super.attributeChangedCallback(name, old, value, namespace);
          }
        }

        /**
         * Deserializes an attribute to its associated property.
         *
         * This method calls the `_deserializeValue` method to convert the string to
         * a typed value.
         *
         * @param {string} attribute Name of attribute to deserialize.
         * @param {?string} value of the attribute.
         * @param {*=} type type to deserialize to, defaults to the value
         * returned from `typeForProperty`
         * @return {void}
         */
        _attributeToProperty(attribute, value, type) {
          if (!this.__serializing) {
            const map = this.__dataAttributes;
            const property = map && map[attribute] || attribute;
            this[property] = this._deserializeValue(value, type ||
              this.constructor.typeForProperty(property));
          }
        }

        /**
         * Serializes a property to its associated attribute.
         *
         * @suppress {invalidCasts} Closure can't figure out `this` is an element.
         *
         * @param {string} property Property name to reflect.
         * @param {string=} attribute Attribute name to reflect to.
         * @param {*=} value Property value to refect.
         * @return {void}
         */
        _propertyToAttribute(property, attribute, value) {
          this.__serializing = true;
          value = (arguments.length < 3) ? this[property] : value;
          this._valueToNodeAttribute(/** @type {!HTMLElement} */(this), value,
            attribute || this.constructor.attributeNameForProperty(property));
          this.__serializing = false;
        }

        /**
         * Sets a typed value to an HTML attribute on a node.
         *
         * This method calls the `_serializeValue` method to convert the typed
         * value to a string.  If the `_serializeValue` method returns `undefined`,
         * the attribute will be removed (this is the default for boolean
         * type `false`).
         *
         * @param {Element} node Element to set attribute to.
         * @param {*} value Value to serialize.
         * @param {string} attribute Attribute name to serialize to.
         * @return {void}
         */
        _valueToNodeAttribute(node, value, attribute) {
          const str = this._serializeValue(value);
          if (str === undefined) {
            node.removeAttribute(attribute);
          } else {
            node.setAttribute(attribute, str);
          }
        }

        /**
         * Converts a typed JavaScript value to a string.
         *
         * This method is called when setting JS property values to
         * HTML attributes.  Users may override this method to provide
         * serialization for custom types.
         *
         * @param {*} value Property value to serialize.
         * @return {string | undefined} String serialized from the provided
         * property  value.
         */
        _serializeValue(value) {
          switch (typeof value) {
            case 'boolean':
              return value ? '' : undefined;
            default:
              return value != null ? value.toString() : undefined;
          }
        }

        /**
         * Converts a string to a typed JavaScript value.
         *
         * This method is called when reading HTML attribute values to
         * JS properties.  Users may override this method to provide
         * deserialization for custom `type`s. Types for `Boolean`, `String`,
         * and `Number` convert attributes to the expected types.
         *
         * @param {?string} value Value to deserialize.
         * @param {*=} type Type to deserialize the string to.
         * @return {*} Typed value deserialized from the provided string.
         */
        _deserializeValue(value, type) {
          switch (type) {
            case Boolean:
              return (value !== null);
            case Number:
              return Number(value);
            default:
              return value;
          }
        }

      }

      return PropertiesChanged;
    });


  })();

</script>
<script>
(function() {

  'use strict';

  let caseMap = Polymer.CaseMap;

  // Save map of native properties; this forms a blacklist or properties
  // that won't have their values "saved" by `saveAccessorValue`, since
  // reading from an HTMLElement accessor from the context of a prototype throws
  const nativeProperties = {};
  let proto = HTMLElement.prototype;
  while (proto) {
    let props = Object.getOwnPropertyNames(proto);
    for (let i=0; i<props.length; i++) {
      nativeProperties[props[i]] = true;
    }
    proto = Object.getPrototypeOf(proto);
  }

  /**
   * Used to save the value of a property that will be overridden with
   * an accessor. If the `model` is a prototype, the values will be saved
   * in `__dataProto`, and it's up to the user (or downstream mixin) to
   * decide how/when to set these values back into the accessors.
   * If `model` is already an instance (it has a `__data` property), then
   * the value will be set as a pending property, meaning the user should
   * call `_invalidateProperties` or `_flushProperties` to take effect
   *
   * @param {Object} model Prototype or instance
   * @param {string} property Name of property
   * @return {void}
   * @private
   */
  function saveAccessorValue(model, property) {
    // Don't read/store value for any native properties since they could throw
    if (!nativeProperties[property]) {
      let value = model[property];
      if (value !== undefined) {
        if (model.__data) {
          // Adding accessor to instance; update the property
          // It is the user's responsibility to call _flushProperties
          model._setPendingProperty(property, value);
        } else {
          // Adding accessor to proto; save proto's value for instance-time use
          if (!model.__dataProto) {
            model.__dataProto = {};
          } else if (!model.hasOwnProperty(JSCompiler_renameProperty('__dataProto', model))) {
            model.__dataProto = Object.create(model.__dataProto);
          }
          model.__dataProto[property] = value;
        }
      }
    }
  }

  /**
   * Element class mixin that provides basic meta-programming for creating one
   * or more property accessors (getter/setter pair) that enqueue an async
   * (batched) `_propertiesChanged` callback.
   *
   * For basic usage of this mixin:
   * 
   * -   Declare attributes to observe via the standard `static get observedAttributes()`. Use
   *     `dash-case` attribute names to represent `camelCase` property names. 
   * -   Implement the `_propertiesChanged` callback on the class.
   * -   Call `MyClass.createPropertiesForAttributes()` **once** on the class to generate 
   *     property accessors for each observed attribute. This must be called before the first 
   *     instance is created, for example, by calling it before calling `customElements.define`.
   *     It can also be called lazily from the element's `constructor`, as long as it's guarded so
   *     that the call is only made once, when the first instance is created.
   * -   Call `this._enableProperties()` in the element's `connectedCallback` to enable 
   *     the accessors.
   *
   * Any `observedAttributes` will automatically be
   * deserialized via `attributeChangedCallback` and set to the associated
   * property using `dash-case`-to-`camelCase` convention.
   *
   * @mixinFunction
   * @polymer
   * @appliesMixin Polymer.PropertiesChanged
   * @memberof Polymer
   * @summary Element class mixin for reacting to property changes from
   *   generated property accessors.
   */
  Polymer.PropertyAccessors = Polymer.dedupingMixin(superClass => {

    /**
     * @constructor
     * @extends {superClass}
     * @implements {Polymer_PropertiesChanged}
     * @unrestricted
     * @private
     */
     const base = Polymer.PropertiesChanged(superClass);

    /**
     * @polymer
     * @mixinClass
     * @implements {Polymer_PropertyAccessors}
     * @extends {base}
     * @unrestricted
     */
    class PropertyAccessors extends base {

      /**
       * Generates property accessors for all attributes in the standard
       * static `observedAttributes` array.
       *
       * Attribute names are mapped to property names using the `dash-case` to
       * `camelCase` convention
       *
       * @return {void}
       */
      static createPropertiesForAttributes() {
        let a$ = this.observedAttributes;
        for (let i=0; i < a$.length; i++) {
          this.prototype._createPropertyAccessor(caseMap.dashToCamelCase(a$[i]));
        }
      }

      /**
       * Returns an attribute name that corresponds to the given property.
       * By default, converts camel to dash case, e.g. `fooBar` to `foo-bar`.
       * @param {string} property Property to convert
       * @return {string} Attribute name corresponding to the given property.
       *
       * @protected
       */
      static attributeNameForProperty(property) {
        return caseMap.camelToDashCase(property);
      }

      /**
       * Overrides PropertiesChanged implementation to initialize values for
       * accessors created for values that already existed on the element
       * prototype.
       *
       * @return {void}
       * @protected
       */
      _initializeProperties() {
        if (this.__dataProto) {
          this._initializeProtoProperties(this.__dataProto);
          this.__dataProto = null;
        }
        super._initializeProperties();
      }

      /**
       * Called at instance time with bag of properties that were overwritten
       * by accessors on the prototype when accessors were created.
       *
       * The default implementation sets these properties back into the
       * setter at instance time.  This method is provided as an override
       * point for customizing or providing more efficient initialization.
       *
       * @param {Object} props Bag of property values that were overwritten
       *   when creating property accessors.
       * @return {void}
       * @protected
       */
      _initializeProtoProperties(props) {
        for (let p in props) {
          this._setProperty(p, props[p]);
        }
      }

      /**
       * Ensures the element has the given attribute. If it does not,
       * assigns the given value to the attribute.
       *
       * @suppress {invalidCasts} Closure can't figure out `this` is infact an element
       *
       * @param {string} attribute Name of attribute to ensure is set.
       * @param {string} value of the attribute.
       * @return {void}
       */
      _ensureAttribute(attribute, value) {
        const el = /** @type {!HTMLElement} */(this);
        if (!el.hasAttribute(attribute)) {
          this._valueToNodeAttribute(el, value, attribute);
        }
      }

      /**
       * Overrides PropertiesChanged implemention to serialize objects as JSON.
       *
       * @param {*} value Property value to serialize.
       * @return {string | undefined} String serialized from the provided property value.
       */
      _serializeValue(value) {
        /* eslint-disable no-fallthrough */
        switch (typeof value) {
          case 'object':
            if (value instanceof Date) {
              return value.toString();
            } else if (value) {
              try {
                return JSON.stringify(value);
              } catch(x) {
                return '';
              }
            }

          default:
            return super._serializeValue(value);
        }
      }

      /**
       * Converts a string to a typed JavaScript value.
       *
       * This method is called by Polymer when reading HTML attribute values to
       * JS properties.  Users may override this method on Polymer element
       * prototypes to provide deserialization for custom `type`s.  Note,
       * the `type` argument is the value of the `type` field provided in the
       * `properties` configuration object for a given property, and is
       * by convention the constructor for the type to deserialize.
       *
       *
       * @param {?string} value Attribute value to deserialize.
       * @param {*=} type Type to deserialize the string to.
       * @return {*} Typed value deserialized from the provided string.
       */
      _deserializeValue(value, type) {
        /**
         * @type {*}
         */
        let outValue;
        switch (type) {
          case Object:
            try {
              outValue = JSON.parse(/** @type {string} */(value));
            } catch(x) {
              // allow non-JSON literals like Strings and Numbers
              outValue = value;
            }
            break;
          case Array:
            try {
              outValue = JSON.parse(/** @type {string} */(value));
            } catch(x) {
              outValue = null;
              console.warn(`Polymer::Attributes: couldn't decode Array as JSON: ${value}`);
            }
            break;
          case Date:
            outValue = isNaN(value) ? String(value) : Number(value);
            outValue = new Date(outValue);
            break;
          default:
            outValue = super._deserializeValue(value, type);
            break;
        }
        return outValue;
      }
      /* eslint-enable no-fallthrough */

      /**
       * Overrides PropertiesChanged implementation to save existing prototype
       * property value so that it can be reset.
       * @param {string} property Name of the property
       * @param {boolean=} readOnly When true, no setter is created
       *
       * When calling on a prototype, any overwritten values are saved in
       * `__dataProto`, and it is up to the subclasser to decide how/when
       * to set those properties back into the accessor.  When calling on an
       * instance, the overwritten value is set via `_setPendingProperty`,
       * and the user should call `_invalidateProperties` or `_flushProperties`
       * for the values to take effect.
       * @protected
       * @return {void}
       */
      _definePropertyAccessor(property, readOnly) {
        saveAccessorValue(this, property);
        super._definePropertyAccessor(property, readOnly);
      }

      /**
       * Returns true if this library created an accessor for the given property.
       *
       * @param {string} property Property name
       * @return {boolean} True if an accessor was created
       */
      _hasAccessor(property) {
        return this.__dataHasAccessor && this.__dataHasAccessor[property];
      }

      /**
       * Returns true if the specified property has a pending change.
       *
       * @param {string} prop Property name
       * @return {boolean} True if property has a pending change
       * @protected
       */
      _isPropertyPending(prop) {
        return Boolean(this.__dataPending && (prop in this.__dataPending));
      }

    }

    return PropertyAccessors;

  });

})();
</script>
<script>
(function() {

  'use strict';

  const walker = document.createTreeWalker(document, NodeFilter.SHOW_ALL,
      null, false);

  // 1.x backwards-compatible auto-wrapper for template type extensions
  // This is a clear layering violation and gives favored-nation status to
  // dom-if and dom-repeat templates.  This is a conceit we're choosing to keep
  // a.) to ease 1.x backwards-compatibility due to loss of `is`, and
  // b.) to maintain if/repeat capability in parser-constrained elements
  //     (e.g. table, select) in lieu of native CE type extensions without
  //     massive new invention in this space (e.g. directive system)
  const templateExtensions = {
    'dom-if': true,
    'dom-repeat': true
  };
  function wrapTemplateExtension(node) {
    let is = node.getAttribute('is');
    if (is && templateExtensions[is]) {
      let t = node;
      t.removeAttribute('is');
      node = t.ownerDocument.createElement(is);
      t.parentNode.replaceChild(node, t);
      node.appendChild(t);
      while(t.attributes.length) {
        node.setAttribute(t.attributes[0].name, t.attributes[0].value);
        t.removeAttribute(t.attributes[0].name);
      }
    }
    return node;
  }

  function findTemplateNode(root, nodeInfo) {
    // recursively ascend tree until we hit root
    let parent = nodeInfo.parentInfo && findTemplateNode(root, nodeInfo.parentInfo);
    // unwind the stack, returning the indexed node at each level
    if (parent) {
      // note: marginally faster than indexing via childNodes
      // (http://jsperf.com/childnodes-lookup)
      walker.currentNode = parent;
      for (let n=walker.firstChild(), i=0; n; n=walker.nextSibling()) {
        if (nodeInfo.parentIndex === i++) {
          return n;
        }
      }
    } else {
      return root;
    }
  }

  // construct `$` map (from id annotations)
  function applyIdToMap(inst, map, node, nodeInfo) {
    if (nodeInfo.id) {
      map[nodeInfo.id] = node;
    }
  }

  // install event listeners (from event annotations)
  function applyEventListener(inst, node, nodeInfo) {
    if (nodeInfo.events && nodeInfo.events.length) {
      for (let j=0, e$=nodeInfo.events, e; (j<e$.length) && (e=e$[j]); j++) {
        inst._addMethodEventListenerToNode(node, e.name, e.value, inst);
      }
    }
  }

  // push configuration references at configure time
  function applyTemplateContent(inst, node, nodeInfo) {
    if (nodeInfo.templateInfo) {
      node._templateInfo = nodeInfo.templateInfo;
    }
  }

  function createNodeEventHandler(context, eventName, methodName) {
    // Instances can optionally have a _methodHost which allows redirecting where
    // to find methods. Currently used by `templatize`.
    context = context._methodHost || context;
    let handler = function(e) {
      if (context[methodName]) {
        context[methodName](e, e.detail);
      } else {
        console.warn('listener method `' + methodName + '` not defined');
      }
    };
    return handler;
  }

  /**
   * Element mixin that provides basic template parsing and stamping, including
   * the following template-related features for stamped templates:
   *
   * - Declarative event listeners (`on-eventname="listener"`)
   * - Map of node id's to stamped node instances (`this.$.id`)
   * - Nested template content caching/removal and re-installation (performance
   *   optimization)
   *
   * @mixinFunction
   * @polymer
   * @memberof Polymer
   * @summary Element class mixin that provides basic template parsing and stamping
   */
  Polymer.TemplateStamp = Polymer.dedupingMixin(superClass => {

    /**
     * @polymer
     * @mixinClass
     * @implements {Polymer_TemplateStamp}
     */
    class TemplateStamp extends superClass {

      /**
       * Scans a template to produce template metadata.
       *
       * Template-specific metadata are stored in the object returned, and node-
       * specific metadata are stored in objects in its flattened `nodeInfoList`
       * array.  Only nodes in the template that were parsed as nodes of
       * interest contain an object in `nodeInfoList`.  Each `nodeInfo` object
       * contains an `index` (`childNodes` index in parent) and optionally
       * `parent`, which points to node info of its parent (including its index).
       *
       * The template metadata object returned from this method has the following
       * structure (many fields optional):
       *
       * ```js
       *   {
       *     // Flattened list of node metadata (for nodes that generated metadata)
       *     nodeInfoList: [
       *       {
       *         // `id` attribute for any nodes with id's for generating `$` map
       *         id: {string},
       *         // `on-event="handler"` metadata
       *         events: [
       *           {
       *             name: {string},   // event name
       *             value: {string},  // handler method name
       *           }, ...
       *         ],
       *         // Notes when the template contained a `<slot>` for shady DOM
       *         // optimization purposes
       *         hasInsertionPoint: {boolean},
       *         // For nested `<template>`` nodes, nested template metadata
       *         templateInfo: {object}, // nested template metadata
       *         // Metadata to allow efficient retrieval of instanced node
       *         // corresponding to this metadata
       *         parentInfo: {number},   // reference to parent nodeInfo>
       *         parentIndex: {number},  // index in parent's `childNodes` collection
       *         infoIndex: {number},    // index of this `nodeInfo` in `templateInfo.nodeInfoList`
       *       },
       *       ...
       *     ],
       *     // When true, the template had the `strip-whitespace` attribute
       *     // or was nested in a template with that setting
       *     stripWhitespace: {boolean},
       *     // For nested templates, nested template content is moved into
       *     // a document fragment stored here; this is an optimization to
       *     // avoid the cost of nested template cloning
       *     content: {DocumentFragment}
       *   }
       * ```
       *
       * This method kicks off a recursive treewalk as follows:
       *
       * ```
       *    _parseTemplate <---------------------+
       *      _parseTemplateContent              |
       *        _parseTemplateNode  <------------|--+
       *          _parseTemplateNestedTemplate --+  |
       *          _parseTemplateChildNodes ---------+
       *          _parseTemplateNodeAttributes
       *            _parseTemplateNodeAttribute
       *
       * ```
       *
       * These methods may be overridden to add custom metadata about templates
       * to either `templateInfo` or `nodeInfo`.
       *
       * Note that this method may be destructive to the template, in that
       * e.g. event annotations may be removed after being noted in the
       * template metadata.
       *
       * @param {!HTMLTemplateElement} template Template to parse
       * @param {TemplateInfo=} outerTemplateInfo Template metadata from the outer
       *   template, for parsing nested templates
       * @return {!TemplateInfo} Parsed template metadata
       */
      static _parseTemplate(template, outerTemplateInfo) {
        // since a template may be re-used, memo-ize metadata
        if (!template._templateInfo) {
          let templateInfo = template._templateInfo = {};
          templateInfo.nodeInfoList = [];
          templateInfo.stripWhiteSpace =
            (outerTemplateInfo && outerTemplateInfo.stripWhiteSpace) ||
            template.hasAttribute('strip-whitespace');
          this._parseTemplateContent(template, templateInfo, {parent: null});
        }
        return template._templateInfo;
      }

      static _parseTemplateContent(template, templateInfo, nodeInfo) {
        return this._parseTemplateNode(template.content, templateInfo, nodeInfo);
      }

      /**
       * Parses template node and adds template and node metadata based on
       * the current node, and its `childNodes` and `attributes`.
       *
       * This method may be overridden to add custom node or template specific
       * metadata based on this node.
       *
       * @param {Node} node Node to parse
       * @param {!TemplateInfo} templateInfo Template metadata for current template
       * @param {!NodeInfo} nodeInfo Node metadata for current template.
       * @return {boolean} `true` if the visited node added node-specific
       *   metadata to `nodeInfo`
       */
      static _parseTemplateNode(node, templateInfo, nodeInfo) {
        let noted;
        let element = /** @type {Element} */(node);
        if (element.localName == 'template' && !element.hasAttribute('preserve-content')) {
          noted = this._parseTemplateNestedTemplate(element, templateInfo, nodeInfo) || noted;
        } else if (element.localName === 'slot') {
          // For ShadyDom optimization, indicating there is an insertion point
          templateInfo.hasInsertionPoint = true;
        }
        walker.currentNode = element;
        if (walker.firstChild()) {
          noted = this._parseTemplateChildNodes(element, templateInfo, nodeInfo) || noted;
        }
        if (element.hasAttributes && element.hasAttributes()) {
          noted = this._parseTemplateNodeAttributes(element, templateInfo, nodeInfo) || noted;
        }
        return noted;
      }

      /**
       * Parses template child nodes for the given root node.
       *
       * This method also wraps whitelisted legacy template extensions
       * (`is="dom-if"` and `is="dom-repeat"`) with their equivalent element
       * wrappers, collapses text nodes, and strips whitespace from the template
       * if the `templateInfo.stripWhitespace` setting was provided.
       *
       * @param {Node} root Root node whose `childNodes` will be parsed
       * @param {!TemplateInfo} templateInfo Template metadata for current template
       * @param {!NodeInfo} nodeInfo Node metadata for current template.
       * @return {void}
       */
      static _parseTemplateChildNodes(root, templateInfo, nodeInfo) {
        if (root.localName === 'script' || root.localName === 'style') {
          return;
        }
        walker.currentNode = root;
        for (let node=walker.firstChild(), parentIndex=0, next; node; node=next) {
          // Wrap templates
          if (node.localName == 'template') {
            node = wrapTemplateExtension(node);
          }
          // collapse adjacent textNodes: fixes an IE issue that can cause
          // text nodes to be inexplicably split =(
          // note that root.normalize() should work but does not so we do this
          // manually.
          walker.currentNode = node;
          next = walker.nextSibling();
          if (node.nodeType === Node.TEXT_NODE) {
            let /** Node */ n = next;
            while (n && (n.nodeType === Node.TEXT_NODE)) {
              node.textContent += n.textContent;
              next = walker.nextSibling();
              root.removeChild(n);
              n = next;
            }
            // optionally strip whitespace
            if (templateInfo.stripWhiteSpace && !node.textContent.trim()) {
              root.removeChild(node);
              continue;
            }
          }
          let childInfo = { parentIndex, parentInfo: nodeInfo };
          if (this._parseTemplateNode(node, templateInfo, childInfo)) {
            childInfo.infoIndex = templateInfo.nodeInfoList.push(/** @type {!NodeInfo} */(childInfo)) - 1;
          }
          // Increment if not removed
          walker.currentNode = node;
          if (walker.parentNode()) {
            parentIndex++;
          }
        }
      }

      /**
       * Parses template content for the given nested `<template>`.
       *
       * Nested template info is stored as `templateInfo` in the current node's
       * `nodeInfo`. `template.content` is removed and stored in `templateInfo`.
       * It will then be the responsibility of the host to set it back to the
       * template and for users stamping nested templates to use the
       * `_contentForTemplate` method to retrieve the content for this template
       * (an optimization to avoid the cost of cloning nested template content).
       *
       * @param {HTMLTemplateElement} node Node to parse (a <template>)
       * @param {TemplateInfo} outerTemplateInfo Template metadata for current template
       *   that includes the template `node`
       * @param {!NodeInfo} nodeInfo Node metadata for current template.
       * @return {boolean} `true` if the visited node added node-specific
       *   metadata to `nodeInfo`
       */
      static _parseTemplateNestedTemplate(node, outerTemplateInfo, nodeInfo) {
        let templateInfo = this._parseTemplate(node, outerTemplateInfo);
        let content = templateInfo.content =
          node.content.ownerDocument.createDocumentFragment();
        content.appendChild(node.content);
        nodeInfo.templateInfo = templateInfo;
        return true;
      }

      /**
       * Parses template node attributes and adds node metadata to `nodeInfo`
       * for nodes of interest.
       *
       * @param {Element} node Node to parse
       * @param {TemplateInfo} templateInfo Template metadata for current template
       * @param {NodeInfo} nodeInfo Node metadata for current template.
       * @return {boolean} `true` if the visited node added node-specific
       *   metadata to `nodeInfo`
       */
      static _parseTemplateNodeAttributes(node, templateInfo, nodeInfo) {
        // Make copy of original attribute list, since the order may change
        // as attributes are added and removed
        let noted = false;
        let attrs = Array.from(node.attributes);
        for (let i=attrs.length-1, a; (a=attrs[i]); i--) {
          noted = this._parseTemplateNodeAttribute(node, templateInfo, nodeInfo, a.name, a.value) || noted;
        }
        return noted;
      }

      /**
       * Parses a single template node attribute and adds node metadata to
       * `nodeInfo` for attributes of interest.
       *
       * This implementation adds metadata for `on-event="handler"` attributes
       * and `id` attributes.
       *
       * @param {Element} node Node to parse
       * @param {!TemplateInfo} templateInfo Template metadata for current template
       * @param {!NodeInfo} nodeInfo Node metadata for current template.
       * @param {string} name Attribute name
       * @param {string} value Attribute value
       * @return {boolean} `true` if the visited node added node-specific
       *   metadata to `nodeInfo`
       */
      static _parseTemplateNodeAttribute(node, templateInfo, nodeInfo, name, value) {
        // events (on-*)
        if (name.slice(0, 3) === 'on-') {
          node.removeAttribute(name);
          nodeInfo.events = nodeInfo.events || [];
          nodeInfo.events.push({
            name: name.slice(3),
            value
          });
          return true;
        }
        // static id
        else if (name === 'id') {
          nodeInfo.id = value;
          return true;
        }
        return false;
      }

      /**
       * Returns the `content` document fragment for a given template.
       *
       * For nested templates, Polymer performs an optimization to cache nested
       * template content to avoid the cost of cloning deeply nested templates.
       * This method retrieves the cached content for a given template.
       *
       * @param {HTMLTemplateElement} template Template to retrieve `content` for
       * @return {DocumentFragment} Content fragment
       */
      static _contentForTemplate(template) {
        let templateInfo = /** @type {HTMLTemplateElementWithInfo} */ (template)._templateInfo;
        return (templateInfo && templateInfo.content) || template.content;
      }

      /**
       * Clones the provided template content and returns a document fragment
       * containing the cloned dom.
       *
       * The template is parsed (once and memoized) using this library's
       * template parsing features, and provides the following value-added
       * features:
       * * Adds declarative event listeners for `on-event="handler"` attributes
       * * Generates an "id map" for all nodes with id's under `$` on returned
       *   document fragment
       * * Passes template info including `content` back to templates as
       *   `_templateInfo` (a performance optimization to avoid deep template
       *   cloning)
       *
       * Note that the memoized template parsing process is destructive to the
       * template: attributes for bindings and declarative event listeners are
       * removed after being noted in notes, and any nested `<template>.content`
       * is removed and stored in notes as well.
       *
       * @param {!HTMLTemplateElement} template Template to stamp
       * @return {!StampedTemplate} Cloned template content
       */
      _stampTemplate(template) {
        // Polyfill support: bootstrap the template if it has not already been
        if (template && !template.content &&
            window.HTMLTemplateElement && HTMLTemplateElement.decorate) {
          HTMLTemplateElement.decorate(template);
        }
        let templateInfo = this.constructor._parseTemplate(template);
        let nodeInfo = templateInfo.nodeInfoList;
        let content = templateInfo.content || template.content;
        let dom = /** @type {DocumentFragment} */ (document.importNode(content, true));
        // NOTE: ShadyDom optimization indicating there is an insertion point
        dom.__noInsertionPoint = !templateInfo.hasInsertionPoint;
        let nodes = dom.nodeList = new Array(nodeInfo.length);
        dom.$ = {};
        for (let i=0, l=nodeInfo.length, info; (i<l) && (info=nodeInfo[i]); i++) {
          let node = nodes[i] = findTemplateNode(dom, info);
          applyIdToMap(this, dom.$, node, info);
          applyTemplateContent(this, node, info);
          applyEventListener(this, node, info);
        }
        dom = /** @type {!StampedTemplate} */(dom); // eslint-disable-line no-self-assign
        return dom;
      }

      /**
       * Adds an event listener by method name for the event provided.
       *
       * This method generates a handler function that looks up the method
       * name at handling time.
       *
       * @param {!Node} node Node to add listener on
       * @param {string} eventName Name of event
       * @param {string} methodName Name of method
       * @param {*=} context Context the method will be called on (defaults
       *   to `node`)
       * @return {Function} Generated handler function
       */
      _addMethodEventListenerToNode(node, eventName, methodName, context) {
        context = context || node;
        let handler = createNodeEventHandler(context, eventName, methodName);
        this._addEventListenerToNode(node, eventName, handler);
        return handler;
      }

      /**
       * Override point for adding custom or simulated event handling.
       *
       * @param {!Node} node Node to add event listener to
       * @param {string} eventName Name of event
       * @param {function(!Event):void} handler Listener function to add
       * @return {void}
       */
      _addEventListenerToNode(node, eventName, handler) {
        node.addEventListener(eventName, handler);
      }

      /**
       * Override point for adding custom or simulated event handling.
       *
       * @param {!Node} node Node to remove event listener from
       * @param {string} eventName Name of event
       * @param {function(!Event):void} handler Listener function to remove
       * @return {void}
       */
      _removeEventListenerFromNode(node, eventName, handler) {
        node.removeEventListener(eventName, handler);
      }

    }

    return TemplateStamp;

  });

})();
</script>
<script>
(function() {

  'use strict';

  /** @const {Object} */
  const CaseMap = Polymer.CaseMap;

  // Monotonically increasing unique ID used for de-duping effects triggered
  // from multiple properties in the same turn
  let dedupeId = 0;

  /**
   * Property effect types; effects are stored on the prototype using these keys
   * @enum {string}
   */
  const TYPES = {
    COMPUTE: '__computeEffects',
    REFLECT: '__reflectEffects',
    NOTIFY: '__notifyEffects',
    PROPAGATE: '__propagateEffects',
    OBSERVE: '__observeEffects',
    READ_ONLY: '__readOnly'
  };

  /** @const {RegExp} */
  const capitalAttributeRegex = /[A-Z]/;

  /**
   * @typedef {{
   * name: (string | undefined),
   * structured: (boolean | undefined),
   * wildcard: (boolean | undefined)
   * }}
   */
  let DataTrigger; //eslint-disable-line no-unused-vars

  /**
   * @typedef {{
   * info: ?,
   * trigger: (!DataTrigger | undefined),
   * fn: (!Function | undefined)
   * }}
   */
  let DataEffect; //eslint-disable-line no-unused-vars

  let PropertyEffectsType; //eslint-disable-line no-unused-vars

  /**
   * Ensures that the model has an own-property map of effects for the given type.
   * The model may be a prototype or an instance.
   *
   * Property effects are stored as arrays of effects by property in a map,
   * by named type on the model. e.g.
   *
   *   __computeEffects: {
   *     foo: [ ... ],
   *     bar: [ ... ]
   *   }
   *
   * If the model does not yet have an effect map for the type, one is created
   * and returned.  If it does, but it is not an own property (i.e. the
   * prototype had effects), the the map is deeply cloned and the copy is
   * set on the model and returned, ready for new effects to be added.
   *
   * @param {Object} model Prototype or instance
   * @param {string} type Property effect type
   * @return {Object} The own-property map of effects for the given type
   * @private
   */
  function ensureOwnEffectMap(model, type) {
    let effects = model[type];
    if (!effects) {
      effects = model[type] = {};
    } else if (!model.hasOwnProperty(type)) {
      effects = model[type] = Object.create(model[type]);
      for (let p in effects) {
        let protoFx = effects[p];
        let instFx = effects[p] = Array(protoFx.length);
        for (let i=0; i<protoFx.length; i++) {
          instFx[i] = protoFx[i];
        }
      }
    }
    return effects;
  }

  // -- effects ----------------------------------------------

  /**
   * Runs all effects of a given type for the given set of property changes
   * on an instance.
   *
   * @param {!PropertyEffectsType} inst The instance with effects to run
   * @param {Object} effects Object map of property-to-Array of effects
   * @param {Object} props Bag of current property changes
   * @param {Object=} oldProps Bag of previous values for changed properties
   * @param {boolean=} hasPaths True with `props` contains one or more paths
   * @param {*=} extraArgs Additional metadata to pass to effect function
   * @return {boolean} True if an effect ran for this property
   * @private
   */
  function runEffects(inst, effects, props, oldProps, hasPaths, extraArgs) {
    if (effects) {
      let ran = false;
      let id = dedupeId++;
      for (let prop in props) {
        if (runEffectsForProperty(inst, effects, id, prop, props, oldProps, hasPaths, extraArgs)) {
          ran = true;
        }
      }
      return ran;
    }
    return false;
  }

  /**
   * Runs a list of effects for a given property.
   *
   * @param {!PropertyEffectsType} inst The instance with effects to run
   * @param {Object} effects Object map of property-to-Array of effects
   * @param {number} dedupeId Counter used for de-duping effects
   * @param {string} prop Name of changed property
   * @param {*} props Changed properties
   * @param {*} oldProps Old properties
   * @param {boolean=} hasPaths True with `props` contains one or more paths
   * @param {*=} extraArgs Additional metadata to pass to effect function
   * @return {boolean} True if an effect ran for this property
   * @private
   */
  function runEffectsForProperty(inst, effects, dedupeId, prop, props, oldProps, hasPaths, extraArgs) {
    let ran = false;
    let rootProperty = hasPaths ? Polymer.Path.root(prop) : prop;
    let fxs = effects[rootProperty];
    if (fxs) {
      for (let i=0, l=fxs.length, fx; (i<l) && (fx=fxs[i]); i++) {
        if ((!fx.info || fx.info.lastRun !== dedupeId) &&
            (!hasPaths || pathMatchesTrigger(prop, fx.trigger))) {
          if (fx.info) {
            fx.info.lastRun = dedupeId;
          }
          fx.fn(inst, prop, props, oldProps, fx.info, hasPaths, extraArgs);
          ran = true;
        }
      }
    }
    return ran;
  }

  /**
   * Determines whether a property/path that has changed matches the trigger
   * criteria for an effect.  A trigger is a descriptor with the following
   * structure, which matches the descriptors returned from `parseArg`.
   * e.g. for `foo.bar.*`:
   * ```
   * trigger: {
   *   name: 'a.b',
   *   structured: true,
   *   wildcard: true
   * }
   * ```
   * If no trigger is given, the path is deemed to match.
   *
   * @param {string} path Path or property that changed
   * @param {DataTrigger} trigger Descriptor
   * @return {boolean} Whether the path matched the trigger
   */
  function pathMatchesTrigger(path, trigger) {
    if (trigger) {
      let triggerPath = trigger.name;
      return (triggerPath == path) ||
        (trigger.structured && Polymer.Path.isAncestor(triggerPath, path)) ||
        (trigger.wildcard && Polymer.Path.isDescendant(triggerPath, path));
    } else {
      return true;
    }
  }

  /**
   * Implements the "observer" effect.
   *
   * Calls the method with `info.methodName` on the instance, passing the
   * new and old values.
   *
   * @param {!PropertyEffectsType} inst The instance the effect will be run on
   * @param {string} property Name of property
   * @param {Object} props Bag of current property changes
   * @param {Object} oldProps Bag of previous values for changed properties
   * @param {?} info Effect metadata
   * @return {void}
   * @private
   */
  function runObserverEffect(inst, property, props, oldProps, info) {
    let fn = typeof info.method === "string" ? inst[info.method] : info.method;
    let changedProp = info.property;
    if (fn) {
      fn.call(inst, inst.__data[changedProp], oldProps[changedProp]);
    } else if (!info.dynamicFn) {
      console.warn('observer method `' + info.method + '` not defined');
    }
  }

  /**
   * Runs "notify" effects for a set of changed properties.
   *
   * This method differs from the generic `runEffects` method in that it
   * will dispatch path notification events in the case that the property
   * changed was a path and the root property for that path didn't have a
   * "notify" effect.  This is to maintain 1.0 behavior that did not require
   * `notify: true` to ensure object sub-property notifications were
   * sent.
   *
   * @param {!PropertyEffectsType} inst The instance with effects to run
   * @param {Object} notifyProps Bag of properties to notify
   * @param {Object} props Bag of current property changes
   * @param {Object} oldProps Bag of previous values for changed properties
   * @param {boolean} hasPaths True with `props` contains one or more paths
   * @return {void}
   * @private
   */
  function runNotifyEffects(inst, notifyProps, props, oldProps, hasPaths) {
    // Notify
    let fxs = inst[TYPES.NOTIFY];
    let notified;
    let id = dedupeId++;
    // Try normal notify effects; if none, fall back to try path notification
    for (let prop in notifyProps) {
      if (notifyProps[prop]) {
        if (fxs && runEffectsForProperty(inst, fxs, id, prop, props, oldProps, hasPaths)) {
          notified = true;
        } else if (hasPaths && notifyPath(inst, prop, props)) {
          notified = true;
        }
      }
    }
    // Flush host if we actually notified and host was batching
    // And the host has already initialized clients; this prevents
    // an issue with a host observing data changes before clients are ready.
    let host;
    if (notified && (host = inst.__dataHost) && host._invalidateProperties) {
      host._invalidateProperties();
    }
  }

  /**
   * Dispatches {property}-changed events with path information in the detail
   * object to indicate a sub-path of the property was changed.
   *
   * @param {!PropertyEffectsType} inst The element from which to fire the event
   * @param {string} path The path that was changed
   * @param {Object} props Bag of current property changes
   * @return {boolean} Returns true if the path was notified
   * @private
   */
  function notifyPath(inst, path, props) {
    let rootProperty = Polymer.Path.root(path);
    if (rootProperty !== path) {
      let eventName = Polymer.CaseMap.camelToDashCase(rootProperty) + '-changed';
      dispatchNotifyEvent(inst, eventName, props[path], path);
      return true;
    }
    return false;
  }

  /**
   * Dispatches {property}-changed events to indicate a property (or path)
   * changed.
   *
   * @param {!PropertyEffectsType} inst The element from which to fire the event
   * @param {string} eventName The name of the event to send ('{property}-changed')
   * @param {*} value The value of the changed property
   * @param {string | null | undefined} path If a sub-path of this property changed, the path
   *   that changed (optional).
   * @return {void}
   * @private
   * @suppress {invalidCasts}
   */
  function dispatchNotifyEvent(inst, eventName, value, path) {
    let detail = {
      value: value,
      queueProperty: true
    };
    if (path) {
      detail.path = path;
    }
    /** @type {!HTMLElement} */(inst).dispatchEvent(new CustomEvent(eventName, { detail }));
  }

  /**
   * Implements the "notify" effect.
   *
   * Dispatches a non-bubbling event named `info.eventName` on the instance
   * with a detail object containing the new `value`.
   *
   * @param {!PropertyEffectsType} inst The instance the effect will be run on
   * @param {string} property Name of property
   * @param {Object} props Bag of current property changes
   * @param {Object} oldProps Bag of previous values for changed properties
   * @param {?} info Effect metadata
   * @param {boolean} hasPaths True with `props` contains one or more paths
   * @return {void}
   * @private
   */
  function runNotifyEffect(inst, property, props, oldProps, info, hasPaths) {
    let rootProperty = hasPaths ? Polymer.Path.root(property) : property;
    let path = rootProperty != property ? property : null;
    let value = path ? Polymer.Path.get(inst, path) : inst.__data[property];
    if (path && value === undefined) {
      value = props[property];  // specifically for .splices
    }
    dispatchNotifyEvent(inst, info.eventName, value, path);
  }

  /**
   * Handler function for 2-way notification events. Receives context
   * information captured in the `addNotifyListener` closure from the
   * `__notifyListeners` metadata.
   *
   * Sets the value of the notified property to the host property or path.  If
   * the event contained path information, translate that path to the host
   * scope's name for that path first.
   *
   * @param {CustomEvent} event Notification event (e.g. '<property>-changed')
   * @param {!PropertyEffectsType} inst Host element instance handling the notification event
   * @param {string} fromProp Child element property that was bound
   * @param {string} toPath Host property/path that was bound
   * @param {boolean} negate Whether the binding was negated
   * @return {void}
   * @private
   */
  function handleNotification(event, inst, fromProp, toPath, negate) {
    let value;
    let detail = /** @type {Object} */(event.detail);
    let fromPath = detail && detail.path;
    if (fromPath) {
      toPath = Polymer.Path.translate(fromProp, toPath, fromPath);
      value = detail && detail.value;
    } else {
      value = event.currentTarget[fromProp];
    }
    value = negate ? !value : value;
    if (!inst[TYPES.READ_ONLY] || !inst[TYPES.READ_ONLY][toPath]) {
      if (inst._setPendingPropertyOrPath(toPath, value, true, Boolean(fromPath))
        && (!detail || !detail.queueProperty)) {
        inst._invalidateProperties();
      }
    }
  }

  /**
   * Implements the "reflect" effect.
   *
   * Sets the attribute named `info.attrName` to the given property value.
   *
   * @param {!PropertyEffectsType} inst The instance the effect will be run on
   * @param {string} property Name of property
   * @param {Object} props Bag of current property changes
   * @param {Object} oldProps Bag of previous values for changed properties
   * @param {?} info Effect metadata
   * @return {void}
   * @private
   */
  function runReflectEffect(inst, property, props, oldProps, info) {
    let value = inst.__data[property];
    if (Polymer.sanitizeDOMValue) {
      value = Polymer.sanitizeDOMValue(value, info.attrName, 'attribute', /** @type {Node} */(inst));
    }
    inst._propertyToAttribute(property, info.attrName, value);
  }

  /**
   * Runs "computed" effects for a set of changed properties.
   *
   * This method differs from the generic `runEffects` method in that it
   * continues to run computed effects based on the output of each pass until
   * there are no more newly computed properties.  This ensures that all
   * properties that will be computed by the initial set of changes are
   * computed before other effects (binding propagation, observers, and notify)
   * run.
   *
   * @param {!PropertyEffectsType} inst The instance the effect will be run on
   * @param {!Object} changedProps Bag of changed properties
   * @param {!Object} oldProps Bag of previous values for changed properties
   * @param {boolean} hasPaths True with `props` contains one or more paths
   * @return {void}
   * @private
   */
  function runComputedEffects(inst, changedProps, oldProps, hasPaths) {
    let computeEffects = inst[TYPES.COMPUTE];
    if (computeEffects) {
      let inputProps = changedProps;
      while (runEffects(inst, computeEffects, inputProps, oldProps, hasPaths)) {
        Object.assign(oldProps, inst.__dataOld);
        Object.assign(changedProps, inst.__dataPending);
        inputProps = inst.__dataPending;
        inst.__dataPending = null;
      }
    }
  }

  /**
   * Implements the "computed property" effect by running the method with the
   * values of the arguments specified in the `info` object and setting the
   * return value to the computed property specified.
   *
   * @param {!PropertyEffectsType} inst The instance the effect will be run on
   * @param {string} property Name of property
   * @param {Object} props Bag of current property changes
   * @param {Object} oldProps Bag of previous values for changed properties
   * @param {?} info Effect metadata
   * @return {void}
   * @private
   */
  function runComputedEffect(inst, property, props, oldProps, info) {
    let result = runMethodEffect(inst, property, props, oldProps, info);
    let computedProp = info.methodInfo;
    if (inst.__dataHasAccessor && inst.__dataHasAccessor[computedProp]) {
      inst._setPendingProperty(computedProp, result, true);
    } else {
      inst[computedProp] = result;
    }
  }

  /**
   * Computes path changes based on path links set up using the `linkPaths`
   * API.
   *
   * @param {!PropertyEffectsType} inst The instance whose props are changing
   * @param {string | !Array<(string|number)>} path Path that has changed
   * @param {*} value Value of changed path
   * @return {void}
   * @private
   */
  function computeLinkedPaths(inst, path, value) {
    let links = inst.__dataLinkedPaths;
    if (links) {
      let link;
      for (let a in links) {
        let b = links[a];
        if (Polymer.Path.isDescendant(a, path)) {
          link = Polymer.Path.translate(a, b, path);
          inst._setPendingPropertyOrPath(link, value, true, true);
        } else if (Polymer.Path.isDescendant(b, path)) {
          link = Polymer.Path.translate(b, a, path);
          inst._setPendingPropertyOrPath(link, value, true, true);
        }
      }
    }
  }

  // -- bindings ----------------------------------------------

  /**
   * Adds binding metadata to the current `nodeInfo`, and binding effects
   * for all part dependencies to `templateInfo`.
   *
   * @param {Function} constructor Class that `_parseTemplate` is currently
   *   running on
   * @param {TemplateInfo} templateInfo Template metadata for current template
   * @param {NodeInfo} nodeInfo Node metadata for current template node
   * @param {string} kind Binding kind, either 'property', 'attribute', or 'text'
   * @param {string} target Target property name
   * @param {!Array<!BindingPart>} parts Array of binding part metadata
   * @param {string=} literal Literal text surrounding binding parts (specified
   *   only for 'property' bindings, since these must be initialized as part
   *   of boot-up)
   * @return {void}
   * @private
   */
  function addBinding(constructor, templateInfo, nodeInfo, kind, target, parts, literal) {
    // Create binding metadata and add to nodeInfo
    nodeInfo.bindings = nodeInfo.bindings || [];
    let /** Binding */ binding = { kind, target, parts, literal, isCompound: (parts.length !== 1) };
    nodeInfo.bindings.push(binding);
    // Add listener info to binding metadata
    if (shouldAddListener(binding)) {
      let {event, negate} = binding.parts[0];
      binding.listenerEvent = event || (CaseMap.camelToDashCase(target) + '-changed');
      binding.listenerNegate = negate;
    }
    // Add "propagate" property effects to templateInfo
    let index = templateInfo.nodeInfoList.length;
    for (let i=0; i<binding.parts.length; i++) {
      let part = binding.parts[i];
      part.compoundIndex = i;
      addEffectForBindingPart(constructor, templateInfo, binding, part, index);
    }
  }

  /**
   * Adds property effects to the given `templateInfo` for the given binding
   * part.
   *
   * @param {Function} constructor Class that `_parseTemplate` is currently
   *   running on
   * @param {TemplateInfo} templateInfo Template metadata for current template
   * @param {!Binding} binding Binding metadata
   * @param {!BindingPart} part Binding part metadata
   * @param {number} index Index into `nodeInfoList` for this node
   * @return {void}
   */
  function addEffectForBindingPart(constructor, templateInfo, binding, part, index) {
    if (!part.literal) {
      if (binding.kind === 'attribute' && binding.target[0] === '-') {
        console.warn('Cannot set attribute ' + binding.target +
          ' because "-" is not a valid attribute starting character');
      } else {
        let dependencies = part.dependencies;
        let info = { index, binding, part, evaluator: constructor };
        for (let j=0; j<dependencies.length; j++) {
          let trigger = dependencies[j];
          if (typeof trigger == 'string') {
            trigger = parseArg(trigger);
            trigger.wildcard = true;
          }
          constructor._addTemplatePropertyEffect(templateInfo, trigger.rootProperty, {
            fn: runBindingEffect,
            info, trigger
          });
        }
      }
    }
  }

  /**
   * Implements the "binding" (property/path binding) effect.
   *
   * Note that binding syntax is overridable via `_parseBindings` and
   * `_evaluateBinding`.  This method will call `_evaluateBinding` for any
   * non-literal parts returned from `_parseBindings`.  However,
   * there is no support for _path_ bindings via custom binding parts,
   * as this is specific to Polymer's path binding syntax.
   *
   * @param {!PropertyEffectsType} inst The instance the effect will be run on
   * @param {string} path Name of property
   * @param {Object} props Bag of current property changes
   * @param {Object} oldProps Bag of previous values for changed properties
   * @param {?} info Effect metadata
   * @param {boolean} hasPaths True with `props` contains one or more paths
   * @param {Array} nodeList List of nodes associated with `nodeInfoList` template
   *   metadata
   * @return {void}
   * @private
   */
  function runBindingEffect(inst, path, props, oldProps, info, hasPaths, nodeList) {
    let node = nodeList[info.index];
    let binding = info.binding;
    let part = info.part;
    // Subpath notification: transform path and set to client
    // e.g.: foo="{{obj.sub}}", path: 'obj.sub.prop', set 'foo.prop'=obj.sub.prop
    if (hasPaths && part.source && (path.length > part.source.length) &&
        (binding.kind == 'property') && !binding.isCompound &&
        node.__isPropertyEffectsClient &&
        node.__dataHasAccessor && node.__dataHasAccessor[binding.target]) {
      let value = props[path];
      path = Polymer.Path.translate(part.source, binding.target, path);
      if (node._setPendingPropertyOrPath(path, value, false, true)) {
        inst._enqueueClient(node);
      }
    } else {
      let value = info.evaluator._evaluateBinding(inst, part, path, props, oldProps, hasPaths);
      // Propagate value to child
      applyBindingValue(inst, node, binding, part, value);
    }
  }

  /**
   * Sets the value for an "binding" (binding) effect to a node,
   * either as a property or attribute.
   *
   * @param {!PropertyEffectsType} inst The instance owning the binding effect
   * @param {Node} node Target node for binding
   * @param {!Binding} binding Binding metadata
   * @param {!BindingPart} part Binding part metadata
   * @param {*} value Value to set
   * @return {void}
   * @private
   */
  function applyBindingValue(inst, node, binding, part, value) {
    value = computeBindingValue(node, value, binding, part);
    if (Polymer.sanitizeDOMValue) {
      value = Polymer.sanitizeDOMValue(value, binding.target, binding.kind, node);
    }
    if (binding.kind == 'attribute') {
      // Attribute binding
      inst._valueToNodeAttribute(/** @type {Element} */(node), value, binding.target);
    } else {
      // Property binding
      let prop = binding.target;
      if (node.__isPropertyEffectsClient &&
          node.__dataHasAccessor && node.__dataHasAccessor[prop]) {
        if (!node[TYPES.READ_ONLY] || !node[TYPES.READ_ONLY][prop]) {
          if (node._setPendingProperty(prop, value)) {
            inst._enqueueClient(node);
          }
        }
      } else  {
        inst._setUnmanagedPropertyToNode(node, prop, value);
      }
    }
  }

  /**
   * Transforms an "binding" effect value based on compound & negation
   * effect metadata, as well as handling for special-case properties
   *
   * @param {Node} node Node the value will be set to
   * @param {*} value Value to set
   * @param {!Binding} binding Binding metadata
   * @param {!BindingPart} part Binding part metadata
   * @return {*} Transformed value to set
   * @private
   */
  function computeBindingValue(node, value, binding, part) {
    if (binding.isCompound) {
      let storage = node.__dataCompoundStorage[binding.target];
      storage[part.compoundIndex] = value;
      value = storage.join('');
    }
    if (binding.kind !== 'attribute') {
      // Some browsers serialize `undefined` to `"undefined"`
      if (binding.target === 'textContent' ||
          (binding.target === 'value' &&
            (node.localName === 'input' || node.localName === 'textarea'))) {
        value = value == undefined ? '' : value;
      }
    }
    return value;
  }

  /**
   * Returns true if a binding's metadata meets all the requirements to allow
   * 2-way binding, and therefore a `<property>-changed` event listener should be
   * added:
   * - used curly braces
   * - is a property (not attribute) binding
   * - is not a textContent binding
   * - is not compound
   *
   * @param {!Binding} binding Binding metadata
   * @return {boolean} True if 2-way listener should be added
   * @private
   */
  function shouldAddListener(binding) {
    return Boolean(binding.target) &&
           binding.kind != 'attribute' &&
           binding.kind != 'text' &&
           !binding.isCompound &&
           binding.parts[0].mode === '{';
  }

  /**
   * Setup compound binding storage structures, notify listeners, and dataHost
   * references onto the bound nodeList.
   *
   * @param {!PropertyEffectsType} inst Instance that bas been previously bound
   * @param {TemplateInfo} templateInfo Template metadata
   * @return {void}
   * @private
   */
  function setupBindings(inst, templateInfo) {
    // Setup compound storage, dataHost, and notify listeners
    let {nodeList, nodeInfoList} = templateInfo;
    if (nodeInfoList.length) {
      for (let i=0; i < nodeInfoList.length; i++) {
        let info = nodeInfoList[i];
        let node = nodeList[i];
        let bindings = info.bindings;
        if (bindings) {
          for (let i=0; i<bindings.length; i++) {
            let binding = bindings[i];
            setupCompoundStorage(node, binding);
            addNotifyListener(node, inst, binding);
          }
        }
        node.__dataHost = inst;
      }
    }
  }

  /**
   * Initializes `__dataCompoundStorage` local storage on a bound node with
   * initial literal data for compound bindings, and sets the joined
   * literal parts to the bound property.
   *
   * When changes to compound parts occur, they are first set into the compound
   * storage array for that property, and then the array is joined to result in
   * the final value set to the property/attribute.
   *
   * @param {Node} node Bound node to initialize
   * @param {Binding} binding Binding metadata
   * @return {void}
   * @private
   */
  function setupCompoundStorage(node, binding) {
    if (binding.isCompound) {
      // Create compound storage map
      let storage = node.__dataCompoundStorage ||
        (node.__dataCompoundStorage = {});
      let parts = binding.parts;
      // Copy literals from parts into storage for this binding
      let literals = new Array(parts.length);
      for (let j=0; j<parts.length; j++) {
        literals[j] = parts[j].literal;
      }
      let target = binding.target;
      storage[target] = literals;
      // Configure properties with their literal parts
      if (binding.literal && binding.kind == 'property') {
        node[target] = binding.literal;
      }
    }
  }

  /**
   * Adds a 2-way binding notification event listener to the node specified
   *
   * @param {Object} node Child element to add listener to
   * @param {!PropertyEffectsType} inst Host element instance to handle notification event
   * @param {Binding} binding Binding metadata
   * @return {void}
   * @private
   */
  function addNotifyListener(node, inst, binding) {
    if (binding.listenerEvent) {
      let part = binding.parts[0];
      node.addEventListener(binding.listenerEvent, function(e) {
        handleNotification(e, inst, binding.target, part.source, part.negate);
      });
    }
  }

  // -- for method-based effects (complexObserver & computed) --------------

  /**
   * Adds property effects for each argument in the method signature (and
   * optionally, for the method name if `dynamic` is true) that calls the
   * provided effect function.
   *
   * @param {Element | Object} model Prototype or instance
   * @param {!MethodSignature} sig Method signature metadata
   * @param {string} type Type of property effect to add
   * @param {Function} effectFn Function to run when arguments change
   * @param {*=} methodInfo Effect-specific information to be included in
   *   method effect metadata
   * @param {boolean|Object=} dynamicFn Boolean or object map indicating whether
   *   method names should be included as a dependency to the effect. Note,
   *   defaults to true if the signature is static (sig.static is true).
   * @return {void}
   * @private
   */
  function createMethodEffect(model, sig, type, effectFn, methodInfo, dynamicFn) {
    dynamicFn = sig.static || (dynamicFn &&
      (typeof dynamicFn !== 'object' || dynamicFn[sig.methodName]));
    let info = {
      methodName: sig.methodName,
      args: sig.args,
      methodInfo,
      dynamicFn
    };
    for (let i=0, arg; (i<sig.args.length) && (arg=sig.args[i]); i++) {
      if (!arg.literal) {
        model._addPropertyEffect(arg.rootProperty, type, {
          fn: effectFn, info: info, trigger: arg
        });
      }
    }
    if (dynamicFn) {
      model._addPropertyEffect(sig.methodName, type, {
        fn: effectFn, info: info
      });
    }
  }

  /**
   * Calls a method with arguments marshaled from properties on the instance
   * based on the method signature contained in the effect metadata.
   *
   * Multi-property observers, computed properties, and inline computing
   * functions call this function to invoke the method, then use the return
   * value accordingly.
   *
   * @param {!PropertyEffectsType} inst The instance the effect will be run on
   * @param {string} property Name of property
   * @param {Object} props Bag of current property changes
   * @param {Object} oldProps Bag of previous values for changed properties
   * @param {?} info Effect metadata
   * @return {*} Returns the return value from the method invocation
   * @private
   */
  function runMethodEffect(inst, property, props, oldProps, info) {
    // Instances can optionally have a _methodHost which allows redirecting where
    // to find methods. Currently used by `templatize`.
    let context = inst._methodHost || inst;
    let fn = context[info.methodName];
    if (fn) {
      let args = inst._marshalArgs(info.args, property, props);
      return fn.apply(context, args);
    } else if (!info.dynamicFn) {
      console.warn('method `' + info.methodName + '` not defined');
    }
  }

  const emptyArray = [];

  // Regular expressions used for binding
  const IDENT  = '(?:' + '[a-zA-Z_$][\\w.:$\\-*]*' + ')';
  const NUMBER = '(?:' + '[-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?' + ')';
  const SQUOTE_STRING = '(?:' + '\'(?:[^\'\\\\]|\\\\.)*\'' + ')';
  const DQUOTE_STRING = '(?:' + '"(?:[^"\\\\]|\\\\.)*"' + ')';
  const STRING = '(?:' + SQUOTE_STRING + '|' + DQUOTE_STRING + ')';
  const ARGUMENT = '(?:(' + IDENT + '|' + NUMBER + '|' +  STRING + ')\\s*' + ')';
  const ARGUMENTS = '(?:' + ARGUMENT + '(?:,\\s*' + ARGUMENT + ')*' + ')';
  const ARGUMENT_LIST = '(?:' + '\\(\\s*' +
                                '(?:' + ARGUMENTS + '?' + ')' +
                              '\\)\\s*' + ')';
  const BINDING = '(' + IDENT + '\\s*' + ARGUMENT_LIST + '?' + ')'; // Group 3
  const OPEN_BRACKET = '(\\[\\[|{{)' + '\\s*';
  const CLOSE_BRACKET = '(?:]]|}})';
  const NEGATE = '(?:(!)\\s*)?'; // Group 2
  const EXPRESSION = OPEN_BRACKET + NEGATE + BINDING + CLOSE_BRACKET;
  const bindingRegex = new RegExp(EXPRESSION, "g");

  /**
   * Create a string from binding parts of all the literal parts
   *
   * @param {!Array<BindingPart>} parts All parts to stringify
   * @return {string} String made from the literal parts
   */
  function literalFromParts(parts) {
    let s = '';
    for (let i=0; i<parts.length; i++) {
      let literal = parts[i].literal;
      s += literal || '';
    }
    return s;
  }

  /**
   * Parses an expression string for a method signature, and returns a metadata
   * describing the method in terms of `methodName`, `static` (whether all the
   * arguments are literals), and an array of `args`
   *
   * @param {string} expression The expression to parse
   * @return {?MethodSignature} The method metadata object if a method expression was
   *   found, otherwise `undefined`
   * @private
   */
  function parseMethod(expression) {
    // tries to match valid javascript property names
    let m = expression.match(/([^\s]+?)\(([\s\S]*)\)/);
    if (m) {
      let methodName = m[1];
      let sig = { methodName, static: true, args: emptyArray };
      if (m[2].trim()) {
        // replace escaped commas with comma entity, split on un-escaped commas
        let args = m[2].replace(/\\,/g, '&comma;').split(',');
        return parseArgs(args, sig);
      } else {
        return sig;
      }
    }
    return null;
  }

  /**
   * Parses an array of arguments and sets the `args` property of the supplied
   * signature metadata object. Sets the `static` property to false if any
   * argument is a non-literal.
   *
   * @param {!Array<string>} argList Array of argument names
   * @param {!MethodSignature} sig Method signature metadata object
   * @return {!MethodSignature} The updated signature metadata object
   * @private
   */
  function parseArgs(argList, sig) {
    sig.args = argList.map(function(rawArg) {
      let arg = parseArg(rawArg);
      if (!arg.literal) {
        sig.static = false;
      }
      return arg;
    }, this);
    return sig;
  }

  /**
   * Parses an individual argument, and returns an argument metadata object
   * with the following fields:
   *
   *   {
   *     value: 'prop',        // property/path or literal value
   *     literal: false,       // whether argument is a literal
   *     structured: false,    // whether the property is a path
   *     rootProperty: 'prop', // the root property of the path
   *     wildcard: false       // whether the argument was a wildcard '.*' path
   *   }
   *
   * @param {string} rawArg The string value of the argument
   * @return {!MethodArg} Argument metadata object
   * @private
   */
  function parseArg(rawArg) {
    // clean up whitespace
    let arg = rawArg.trim()
      // replace comma entity with comma
      .replace(/&comma;/g, ',')
      // repair extra escape sequences; note only commas strictly need
      // escaping, but we allow any other char to be escaped since its
      // likely users will do this
      .replace(/\\(.)/g, '\$1')
      ;
    // basic argument descriptor
    let a = {
      name: arg,
      value: '',
      literal: false
    };
    // detect literal value (must be String or Number)
    let fc = arg[0];
    if (fc === '-') {
      fc = arg[1];
    }
    if (fc >= '0' && fc <= '9') {
      fc = '#';
    }
    switch(fc) {
      case "'":
      case '"':
        a.value = arg.slice(1, -1);
        a.literal = true;
        break;
      case '#':
        a.value = Number(arg);
        a.literal = true;
        break;
    }
    // if not literal, look for structured path
    if (!a.literal) {
      a.rootProperty = Polymer.Path.root(arg);
      // detect structured path (has dots)
      a.structured = Polymer.Path.isPath(arg);
      if (a.structured) {
        a.wildcard = (arg.slice(-2) == '.*');
        if (a.wildcard) {
          a.name = arg.slice(0, -2);
        }
      }
    }
    return a;
  }

  // data api

  /**
   * Sends array splice notifications (`.splices` and `.length`)
   *
   * Note: this implementation only accepts normalized paths
   *
   * @param {!PropertyEffectsType} inst Instance to send notifications to
   * @param {Array} array The array the mutations occurred on
   * @param {string} path The path to the array that was mutated
   * @param {Array} splices Array of splice records
   * @return {void}
   * @private
   */
  function notifySplices(inst, array, path, splices) {
    let splicesPath = path + '.splices';
    inst.notifyPath(splicesPath, { indexSplices: splices });
    inst.notifyPath(path + '.length', array.length);
    // Null here to allow potentially large splice records to be GC'ed.
    inst.__data[splicesPath] = {indexSplices: null};
  }

  /**
   * Creates a splice record and sends an array splice notification for
   * the described mutation
   *
   * Note: this implementation only accepts normalized paths
   *
   * @param {!PropertyEffectsType} inst Instance to send notifications to
   * @param {Array} array The array the mutations occurred on
   * @param {string} path The path to the array that was mutated
   * @param {number} index Index at which the array mutation occurred
   * @param {number} addedCount Number of added items
   * @param {Array} removed Array of removed items
   * @return {void}
   * @private
   */
  function notifySplice(inst, array, path, index, addedCount, removed) {
    notifySplices(inst, array, path, [{
      index: index,
      addedCount: addedCount,
      removed: removed,
      object: array,
      type: 'splice'
    }]);
  }

  /**
   * Returns an upper-cased version of the string.
   *
   * @param {string} name String to uppercase
   * @return {string} Uppercased string
   * @private
   */
  function upper(name) {
    return name[0].toUpperCase() + name.substring(1);
  }

  /**
   * Element class mixin that provides meta-programming for Polymer's template
   * binding and data observation (collectively, "property effects") system.
   *
   * This mixin uses provides the following key static methods for adding
   * property effects to an element class:
   * - `addPropertyEffect`
   * - `createPropertyObserver`
   * - `createMethodObserver`
   * - `createNotifyingProperty`
   * - `createReadOnlyProperty`
   * - `createReflectedProperty`
   * - `createComputedProperty`
   * - `bindTemplate`
   *
   * Each method creates one or more property accessors, along with metadata
   * used by this mixin's implementation of `_propertiesChanged` to perform
   * the property effects.
   *
   * Underscored versions of the above methods also exist on the element
   * prototype for adding property effects on instances at runtime.
   *
   * Note that this mixin overrides several `PropertyAccessors` methods, in
   * many cases to maintain guarantees provided by the Polymer 1.x features;
   * notably it changes property accessors to be synchronous by default
   * whereas the default when using `PropertyAccessors` standalone is to be
   * async by default.
   *
   * @mixinFunction
   * @polymer
   * @appliesMixin Polymer.TemplateStamp
   * @appliesMixin Polymer.PropertyAccessors
   * @memberof Polymer
   * @summary Element class mixin that provides meta-programming for Polymer's
   * template binding and data observation system.
   */
  Polymer.PropertyEffects = Polymer.dedupingMixin(superClass => {

    /**
     * @constructor
     * @extends {superClass}
     * @implements {Polymer_PropertyAccessors}
     * @implements {Polymer_TemplateStamp}
     * @unrestricted
     * @private
     */
    const propertyEffectsBase = Polymer.TemplateStamp(Polymer.PropertyAccessors(superClass));

    /**
     * @polymer
     * @mixinClass
     * @implements {Polymer_PropertyEffects}
     * @extends {propertyEffectsBase}
     * @unrestricted
     */
    class PropertyEffects extends propertyEffectsBase {

      constructor() {
        super();
        /** @type {boolean} */
        // Used to identify users of this mixin, ala instanceof
        this.__isPropertyEffectsClient = true;
        /** @type {number} */
        // NOTE: used to track re-entrant calls to `_flushProperties`
        // path changes dirty check against `__dataTemp` only during one "turn"
        // and are cleared when `__dataCounter` returns to 0.
        this.__dataCounter = 0;
        /** @type {boolean} */
        this.__dataClientsReady;
        /** @type {Array} */
        this.__dataPendingClients;
        /** @type {Object} */
        this.__dataToNotify;
        /** @type {Object} */
        this.__dataLinkedPaths;
        /** @type {boolean} */
        this.__dataHasPaths;
        /** @type {Object} */
        this.__dataCompoundStorage;
        /** @type {Polymer_PropertyEffects} */
        this.__dataHost;
        /** @type {!Object} */
        this.__dataTemp;
        /** @type {boolean} */
        this.__dataClientsInitialized;
        /** @type {!Object} */
        this.__data;
        /** @type {!Object} */
        this.__dataPending;
        /** @type {!Object} */
        this.__dataOld;
        /** @type {Object} */
        this.__computeEffects;
        /** @type {Object} */
        this.__reflectEffects;
        /** @type {Object} */
        this.__notifyEffects;
        /** @type {Object} */
        this.__propagateEffects;
        /** @type {Object} */
        this.__observeEffects;
        /** @type {Object} */
        this.__readOnly;
        /** @type {!TemplateInfo} */
        this.__templateInfo;
      }

      get PROPERTY_EFFECT_TYPES() {
        return TYPES;
      }

      /**
       * @return {void}
       */
      _initializeProperties() {
        super._initializeProperties();
        hostStack.registerHost(this);
        this.__dataClientsReady = false;
        this.__dataPendingClients = null;
        this.__dataToNotify = null;
        this.__dataLinkedPaths = null;
        this.__dataHasPaths = false;
        // May be set on instance prior to upgrade
        this.__dataCompoundStorage = this.__dataCompoundStorage || null;
        this.__dataHost = this.__dataHost || null;
        this.__dataTemp = {};
        this.__dataClientsInitialized = false;
      }

      /**
       * Overrides `Polymer.PropertyAccessors` implementation to provide a
       * more efficient implementation of initializing properties from
       * the prototype on the instance.
       *
       * @override
       * @param {Object} props Properties to initialize on the prototype
       * @return {void}
       */
      _initializeProtoProperties(props) {
        this.__data = Object.create(props);
        this.__dataPending = Object.create(props);
        this.__dataOld = {};
      }

      /**
       * Overrides `Polymer.PropertyAccessors` implementation to avoid setting
       * `_setProperty`'s `shouldNotify: true`.
       *
       * @override
       * @param {Object} props Properties to initialize on the instance
       * @return {void}
       */
      _initializeInstanceProperties(props) {
        let readOnly = this[TYPES.READ_ONLY];
        for (let prop in props) {
          if (!readOnly || !readOnly[prop]) {
            this.__dataPending = this.__dataPending || {};
            this.__dataOld = this.__dataOld || {};
            this.__data[prop] = this.__dataPending[prop] = props[prop];
          }
        }
      }

      // Prototype setup ----------------------------------------

      /**
       * Equivalent to static `addPropertyEffect` API but can be called on
       * an instance to add effects at runtime.  See that method for
       * full API docs.
       *
       * @param {string} property Property that should trigger the effect
       * @param {string} type Effect type, from this.PROPERTY_EFFECT_TYPES
       * @param {Object=} effect Effect metadata object
       * @return {void}
       * @protected
       */
      _addPropertyEffect(property, type, effect) {
        this._createPropertyAccessor(property, type == TYPES.READ_ONLY);
        // effects are accumulated into arrays per property based on type
        let effects = ensureOwnEffectMap(this, type)[property];
        if (!effects) {
          effects = this[type][property] = [];
        }
        effects.push(effect);
      }

      /**
       * Removes the given property effect.
       *
       * @param {string} property Property the effect was associated with
       * @param {string} type Effect type, from this.PROPERTY_EFFECT_TYPES
       * @param {Object=} effect Effect metadata object to remove
       * @return {void}
       */
      _removePropertyEffect(property, type, effect) {
        let effects = ensureOwnEffectMap(this, type)[property];
        let idx = effects.indexOf(effect);
        if (idx >= 0) {
          effects.splice(idx, 1);
        }
      }

      /**
       * Returns whether the current prototype/instance has a property effect
       * of a certain type.
       *
       * @param {string} property Property name
       * @param {string=} type Effect type, from this.PROPERTY_EFFECT_TYPES
       * @return {boolean} True if the prototype/instance has an effect of this type
       * @protected
       */
      _hasPropertyEffect(property, type) {
        let effects = this[type];
        return Boolean(effects && effects[property]);
      }

      /**
       * Returns whether the current prototype/instance has a "read only"
       * accessor for the given property.
       *
       * @param {string} property Property name
       * @return {boolean} True if the prototype/instance has an effect of this type
       * @protected
       */
      _hasReadOnlyEffect(property) {
        return this._hasPropertyEffect(property, TYPES.READ_ONLY);
      }

      /**
       * Returns whether the current prototype/instance has a "notify"
       * property effect for the given property.
       *
       * @param {string} property Property name
       * @return {boolean} True if the prototype/instance has an effect of this type
       * @protected
       */
      _hasNotifyEffect(property) {
        return this._hasPropertyEffect(property, TYPES.NOTIFY);
      }

      /**
       * Returns whether the current prototype/instance has a "reflect to attribute"
       * property effect for the given property.
       *
       * @param {string} property Property name
       * @return {boolean} True if the prototype/instance has an effect of this type
       * @protected
       */
      _hasReflectEffect(property) {
        return this._hasPropertyEffect(property, TYPES.REFLECT);
      }

      /**
       * Returns whether the current prototype/instance has a "computed"
       * property effect for the given property.
       *
       * @param {string} property Property name
       * @return {boolean} True if the prototype/instance has an effect of this type
       * @protected
       */
      _hasComputedEffect(property) {
        return this._hasPropertyEffect(property, TYPES.COMPUTE);
      }

      // Runtime ----------------------------------------

      /**
       * Sets a pending property or path.  If the root property of the path in
       * question had no accessor, the path is set, otherwise it is enqueued
       * via `_setPendingProperty`.
       *
       * This function isolates relatively expensive functionality necessary
       * for the public API (`set`, `setProperties`, `notifyPath`, and property
       * change listeners via {{...}} bindings), such that it is only done
       * when paths enter the system, and not at every propagation step.  It
       * also sets a `__dataHasPaths` flag on the instance which is used to
       * fast-path slower path-matching code in the property effects host paths.
       *
       * `path` can be a path string or array of path parts as accepted by the
       * public API.
       *
       * @param {string | !Array<number|string>} path Path to set
       * @param {*} value Value to set
       * @param {boolean=} shouldNotify Set to true if this change should
       *  cause a property notification event dispatch
       * @param {boolean=} isPathNotification If the path being set is a path
       *   notification of an already changed value, as opposed to a request
       *   to set and notify the change.  In the latter `false` case, a dirty
       *   check is performed and then the value is set to the path before
       *   enqueuing the pending property change.
       * @return {boolean} Returns true if the property/path was enqueued in
       *   the pending changes bag.
       * @protected
       */
      _setPendingPropertyOrPath(path, value, shouldNotify, isPathNotification) {
        if (isPathNotification ||
            Polymer.Path.root(Array.isArray(path) ? path[0] : path) !== path) {
          // Dirty check changes being set to a path against the actual object,
          // since this is the entry point for paths into the system; from here
          // the only dirty checks are against the `__dataTemp` cache to prevent
          // duplicate work in the same turn only. Note, if this was a notification
          // of a change already set to a path (isPathNotification: true),
          // we always let the change through and skip the `set` since it was
          // already dirty checked at the point of entry and the underlying
          // object has already been updated
          if (!isPathNotification) {
            let old = Polymer.Path.get(this, path);
            path = /** @type {string} */ (Polymer.Path.set(this, path, value));
            // Use property-accessor's simpler dirty check
            if (!path || !super._shouldPropertyChange(path, value, old)) {
              return false;
            }
          }
          this.__dataHasPaths = true;
          if (this._setPendingProperty(/**@type{string}*/(path), value, shouldNotify)) {
            computeLinkedPaths(this, path, value);
            return true;
          }
        } else {
          if (this.__dataHasAccessor && this.__dataHasAccessor[path]) {
            return this._setPendingProperty(/**@type{string}*/(path), value, shouldNotify);
          } else {
            this[path] = value;
          }
        }
        return false;
      }

      /**
       * Applies a value to a non-Polymer element/node's property.
       *
       * The implementation makes a best-effort at binding interop:
       * Some native element properties have side-effects when
       * re-setting the same value (e.g. setting `<input>.value` resets the
       * cursor position), so we do a dirty-check before setting the value.
       * However, for better interop with non-Polymer custom elements that
       * accept objects, we explicitly re-set object changes coming from the
       * Polymer world (which may include deep object changes without the
       * top reference changing), erring on the side of providing more
       * information.
       *
       * Users may override this method to provide alternate approaches.
       *
       * @param {!Node} node The node to set a property on
       * @param {string} prop The property to set
       * @param {*} value The value to set
       * @return {void}
       * @protected
       */
      _setUnmanagedPropertyToNode(node, prop, value) {
        // It is a judgment call that resetting primitives is
        // "bad" and resettings objects is also "good"; alternatively we could
        // implement a whitelist of tag & property values that should never
        // be reset (e.g. <input>.value && <select>.value)
        if (value !== node[prop] || typeof value == 'object') {
          node[prop] = value;
        }
      }

      /**
       * Overrides the `PropertiesChanged` implementation to introduce special
       * dirty check logic depending on the property & value being set:
       *
       * 1. Any value set to a path (e.g. 'obj.prop': 42 or 'obj.prop': {...})
       *    Stored in `__dataTemp`, dirty checked against `__dataTemp`
       * 2. Object set to simple property (e.g. 'prop': {...})
       *    Stored in `__dataTemp` and `__data`, dirty checked against
       *    `__dataTemp` by default implementation of `_shouldPropertyChange`
       * 3. Primitive value set to simple property (e.g. 'prop': 42)
       *    Stored in `__data`, dirty checked against `__data`
       *
       * The dirty-check is important to prevent cycles due to two-way
       * notification, but paths and objects are only dirty checked against any
       * previous value set during this turn via a "temporary cache" that is
       * cleared when the last `_propertiesChanged` exits. This is so:
       * a. any cached array paths (e.g. 'array.3.prop') may be invalidated
       *    due to array mutations like shift/unshift/splice; this is fine
       *    since path changes are dirty-checked at user entry points like `set`
       * b. dirty-checking for objects only lasts one turn to allow the user
       *    to mutate the object in-place and re-set it with the same identity
       *    and have all sub-properties re-propagated in a subsequent turn.
       *
       * The temp cache is not necessarily sufficient to prevent invalid array
       * paths, since a splice can happen during the same turn (with pathological
       * user code); we could introduce a "fixup" for temporarily cached array
       * paths if needed: https://github.com/Polymer/polymer/issues/4227
       *
       * @override
       * @param {string} property Name of the property
       * @param {*} value Value to set
       * @param {boolean=} shouldNotify True if property should fire notification
       *   event (applies only for `notify: true` properties)
       * @return {boolean} Returns true if the property changed
       */
      _setPendingProperty(property, value, shouldNotify) {
        let isPath = this.__dataHasPaths && Polymer.Path.isPath(property);
        let prevProps = isPath ? this.__dataTemp : this.__data;
        if (this._shouldPropertyChange(property, value, prevProps[property])) {
          if (!this.__dataPending) {
            this.__dataPending = {};
            this.__dataOld = {};
          }
          // Ensure old is captured from the last turn
          if (!(property in this.__dataOld)) {
            this.__dataOld[property] = this.__data[property];
          }
          // Paths are stored in temporary cache (cleared at end of turn),
          // which is used for dirty-checking, all others stored in __data
          if (isPath) {
            this.__dataTemp[property] = value;
          } else {
            this.__data[property] = value;
          }
          // All changes go into pending property bag, passed to _propertiesChanged
          this.__dataPending[property] = value;
          // Track properties that should notify separately
          if (isPath || (this[TYPES.NOTIFY] && this[TYPES.NOTIFY][property])) {
            this.__dataToNotify = this.__dataToNotify || {};
            this.__dataToNotify[property] = shouldNotify;
          }
          return true;
        }
        return false;
      }

      /**
       * Overrides base implementation to ensure all accessors set `shouldNotify`
       * to true, for per-property notification tracking.
       *
       * @override
       * @param {string} property Name of the property
       * @param {*} value Value to set
       * @return {void}
       */
      _setProperty(property, value) {
        if (this._setPendingProperty(property, value, true)) {
          this._invalidateProperties();
        }
      }

      /**
       * Overrides `PropertyAccessor`'s default async queuing of
       * `_propertiesChanged`: if `__dataReady` is false (has not yet been
       * manually flushed), the function no-ops; otherwise flushes
       * `_propertiesChanged` synchronously.
       *
       * @override
       * @return {void}
       */
      _invalidateProperties() {
        if (this.__dataReady) {
          this._flushProperties();
        }
      }

      /**
       * Enqueues the given client on a list of pending clients, whose
       * pending property changes can later be flushed via a call to
       * `_flushClients`.
       *
       * @param {Object} client PropertyEffects client to enqueue
       * @return {void}
       * @protected
       */
      _enqueueClient(client) {
        this.__dataPendingClients = this.__dataPendingClients || [];
        if (client !== this) {
          this.__dataPendingClients.push(client);
        }
      }

      /**
       * Overrides superclass implementation.
       *
       * @return {void}
       * @protected
       */
      _flushProperties() {
        this.__dataCounter++;
        super._flushProperties();
        this.__dataCounter--;
      }

      /**
       * Flushes any clients previously enqueued via `_enqueueClient`, causing
       * their `_flushProperties` method to run.
       *
       * @return {void}
       * @protected
       */
      _flushClients() {
        if (!this.__dataClientsReady) {
          this.__dataClientsReady = true;
          this._readyClients();
          // Override point where accessors are turned on; importantly,
          // this is after clients have fully readied, providing a guarantee
          // that any property effects occur only after all clients are ready.
          this.__dataReady = true;
        } else {
          this.__enableOrFlushClients();
        }
      }

      // NOTE: We ensure clients either enable or flush as appropriate. This
      // handles two corner cases:
      // (1) clients flush properly when connected/enabled before the host
      // enables; e.g.
      //   (a) Templatize stamps with no properties and does not flush and
      //   (b) the instance is inserted into dom and
      //   (c) then the instance flushes.
      // (2) clients enable properly when not connected/enabled when the host
      // flushes; e.g.
      //   (a) a template is runtime stamped and not yet connected/enabled
      //   (b) a host sets a property, causing stamped dom to flush
      //   (c) the stamped dom enables.
      __enableOrFlushClients() {
        let clients = this.__dataPendingClients;
        if (clients) {
          this.__dataPendingClients = null;
          for (let i=0; i < clients.length; i++) {
            let client = clients[i];
            if (!client.__dataEnabled) {
              client._enableProperties();
            } else if (client.__dataPending) {
              client._flushProperties();
            }
          }
        }
      }

      /**
       * Perform any initial setup on client dom. Called before the first
       * `_flushProperties` call on client dom and before any element
       * observers are called.
       *
       * @return {void}
       * @protected
       */
      _readyClients() {
        this.__enableOrFlushClients();
      }

      /**
       * Sets a bag of property changes to this instance, and
       * synchronously processes all effects of the properties as a batch.
       *
       * Property names must be simple properties, not paths.  Batched
       * path propagation is not supported.
       *
       * @param {Object} props Bag of one or more key-value pairs whose key is
       *   a property and value is the new value to set for that property.
       * @param {boolean=} setReadOnly When true, any private values set in
       *   `props` will be set. By default, `setProperties` will not set
       *   `readOnly: true` root properties.
       * @return {void}
       * @public
       */
      setProperties(props, setReadOnly) {
        for (let path in props) {
          if (setReadOnly || !this[TYPES.READ_ONLY] || !this[TYPES.READ_ONLY][path]) {
            //TODO(kschaaf): explicitly disallow paths in setProperty?
            // wildcard observers currently only pass the first changed path
            // in the `info` object, and you could do some odd things batching
            // paths, e.g. {'foo.bar': {...}, 'foo': null}
            this._setPendingPropertyOrPath(path, props[path], true);
          }
        }
        this._invalidateProperties();
      }

      /**
       * Overrides `PropertyAccessors` so that property accessor
       * side effects are not enabled until after client dom is fully ready.
       * Also calls `_flushClients` callback to ensure client dom is enabled
       * that was not enabled as a result of flushing properties.
       *
       * @override
       * @return {void}
       */
      ready() {
        // It is important that `super.ready()` is not called here as it
        // immediately turns on accessors. Instead, we wait until `readyClients`
        // to enable accessors to provide a guarantee that clients are ready
        // before processing any accessors side effects.
        this._flushProperties();
        // If no data was pending, `_flushProperties` will not `flushClients`
        // so ensure this is done.
        if (!this.__dataClientsReady) {
          this._flushClients();
        }
        // Before ready, client notifications do not trigger _flushProperties.
        // Therefore a flush is necessary here if data has been set.
        if (this.__dataPending) {
          this._flushProperties();
        }
      }

      /**
       * Implements `PropertyAccessors`'s properties changed callback.
       *
       * Runs each class of effects for the batch of changed properties in
       * a specific order (compute, propagate, reflect, observe, notify).
       *
       * @param {!Object} currentProps Bag of all current accessor values
       * @param {!Object} changedProps Bag of properties changed since the last
       *   call to `_propertiesChanged`
       * @param {!Object} oldProps Bag of previous values for each property
       *   in `changedProps`
       * @return {void}
       */
      _propertiesChanged(currentProps, changedProps, oldProps) {
        // ----------------------------
        // let c = Object.getOwnPropertyNames(changedProps || {});
        // window.debug && console.group(this.localName + '#' + this.id + ': ' + c);
        // if (window.debug) { debugger; }
        // ----------------------------
        let hasPaths = this.__dataHasPaths;
        this.__dataHasPaths = false;
        // Compute properties
        runComputedEffects(this, changedProps, oldProps, hasPaths);
        // Clear notify properties prior to possible reentry (propagate, observe),
        // but after computing effects have a chance to add to them
        let notifyProps = this.__dataToNotify;
        this.__dataToNotify = null;
        // Propagate properties to clients
        this._propagatePropertyChanges(changedProps, oldProps, hasPaths);
        // Flush clients
        this._flushClients();
        // Reflect properties
        runEffects(this, this[TYPES.REFLECT], changedProps, oldProps, hasPaths);
        // Observe properties
        runEffects(this, this[TYPES.OBSERVE], changedProps, oldProps, hasPaths);
        // Notify properties to host
        if (notifyProps) {
          runNotifyEffects(this, notifyProps, changedProps, oldProps, hasPaths);
        }
        // Clear temporary cache at end of turn
        if (this.__dataCounter == 1) {
          this.__dataTemp = {};
        }
        // ----------------------------
        // window.debug && console.groupEnd(this.localName + '#' + this.id + ': ' + c);
        // ----------------------------
      }

      /**
       * Called to propagate any property changes to stamped template nodes
       * managed by this element.
       *
       * @param {Object} changedProps Bag of changed properties
       * @param {Object} oldProps Bag of previous values for changed properties
       * @param {boolean} hasPaths True with `props` contains one or more paths
       * @return {void}
       * @protected
       */
      _propagatePropertyChanges(changedProps, oldProps, hasPaths) {
        if (this[TYPES.PROPAGATE]) {
          runEffects(this, this[TYPES.PROPAGATE], changedProps, oldProps, hasPaths);
        }
        let templateInfo = this.__templateInfo;
        while (templateInfo) {
          runEffects(this, templateInfo.propertyEffects, changedProps, oldProps,
            hasPaths, templateInfo.nodeList);
          templateInfo = templateInfo.nextTemplateInfo;
        }
      }

      /**
       * Aliases one data path as another, such that path notifications from one
       * are routed to the other.
       *
       * @param {string | !Array<string|number>} to Target path to link.
       * @param {string | !Array<string|number>} from Source path to link.
       * @return {void}
       * @public
       */
      linkPaths(to, from) {
        to = Polymer.Path.normalize(to);
        from = Polymer.Path.normalize(from);
        this.__dataLinkedPaths = this.__dataLinkedPaths || {};
        this.__dataLinkedPaths[to] = from;
      }

      /**
       * Removes a data path alias previously established with `_linkPaths`.
       *
       * Note, the path to unlink should be the target (`to`) used when
       * linking the paths.
       *
       * @param {string | !Array<string|number>} path Target path to unlink.
       * @return {void}
       * @public
       */
      unlinkPaths(path) {
        path = Polymer.Path.normalize(path);
        if (this.__dataLinkedPaths) {
          delete this.__dataLinkedPaths[path];
        }
      }

      /**
       * Notify that an array has changed.
       *
       * Example:
       *
       *     this.items = [ {name: 'Jim'}, {name: 'Todd'}, {name: 'Bill'} ];
       *     ...
       *     this.items.splice(1, 1, {name: 'Sam'});
       *     this.items.push({name: 'Bob'});
       *     this.notifySplices('items', [
       *       { index: 1, removed: [{name: 'Todd'}], addedCount: 1, object: this.items, type: 'splice' },
       *       { index: 3, removed: [], addedCount: 1, object: this.items, type: 'splice'}
       *     ]);
       *
       * @param {string} path Path that should be notified.
       * @param {Array} splices Array of splice records indicating ordered
       *   changes that occurred to the array. Each record should have the
       *   following fields:
       *    * index: index at which the change occurred
       *    * removed: array of items that were removed from this index
       *    * addedCount: number of new items added at this index
       *    * object: a reference to the array in question
       *    * type: the string literal 'splice'
       *
       *   Note that splice records _must_ be normalized such that they are
       *   reported in index order (raw results from `Object.observe` are not
       *   ordered and must be normalized/merged before notifying).
       * @return {void}
       * @public
      */
      notifySplices(path, splices) {
        let info = {path: ''};
        let array = /** @type {Array} */(Polymer.Path.get(this, path, info));
        notifySplices(this, array, info.path, splices);
      }

      /**
       * Convenience method for reading a value from a path.
       *
       * Note, if any part in the path is undefined, this method returns
       * `undefined` (this method does not throw when dereferencing undefined
       * paths).
       *
       * @param {(string|!Array<(string|number)>)} path Path to the value
       *   to read.  The path may be specified as a string (e.g. `foo.bar.baz`)
       *   or an array of path parts (e.g. `['foo.bar', 'baz']`).  Note that
       *   bracketed expressions are not supported; string-based path parts
       *   *must* be separated by dots.  Note that when dereferencing array
       *   indices, the index may be used as a dotted part directly
       *   (e.g. `users.12.name` or `['users', 12, 'name']`).
       * @param {Object=} root Root object from which the path is evaluated.
       * @return {*} Value at the path, or `undefined` if any part of the path
       *   is undefined.
       * @public
       */
      get(path, root) {
        return Polymer.Path.get(root || this, path);
      }

      /**
       * Convenience method for setting a value to a path and notifying any
       * elements bound to the same path.
       *
       * Note, if any part in the path except for the last is undefined,
       * this method does nothing (this method does not throw when
       * dereferencing undefined paths).
       *
       * @param {(string|!Array<(string|number)>)} path Path to the value
       *   to write.  The path may be specified as a string (e.g. `'foo.bar.baz'`)
       *   or an array of path parts (e.g. `['foo.bar', 'baz']`).  Note that
       *   bracketed expressions are not supported; string-based path parts
       *   *must* be separated by dots.  Note that when dereferencing array
       *   indices, the index may be used as a dotted part directly
       *   (e.g. `'users.12.name'` or `['users', 12, 'name']`).
       * @param {*} value Value to set at the specified path.
       * @param {Object=} root Root object from which the path is evaluated.
       *   When specified, no notification will occur.
       * @return {void}
       * @public
      */
      set(path, value, root) {
        if (root) {
          Polymer.Path.set(root, path, value);
        } else {
          if (!this[TYPES.READ_ONLY] || !this[TYPES.READ_ONLY][/** @type {string} */(path)]) {
            if (this._setPendingPropertyOrPath(path, value, true)) {
              this._invalidateProperties();
            }
          }
        }
      }

      /**
       * Adds items onto the end of the array at the path specified.
       *
       * The arguments after `path` and return value match that of
       * `Array.prototype.push`.
       *
       * This method notifies other paths to the same array that a
       * splice occurred to the array.
       *
       * @param {string | !Array<string|number>} path Path to array.
       * @param {...*} items Items to push onto array
       * @return {number} New length of the array.
       * @public
       */
      push(path, ...items) {
        let info = {path: ''};
        let array = /** @type {Array}*/(Polymer.Path.get(this, path, info));
        let len = array.length;
        let ret = array.push(...items);
        if (items.length) {
          notifySplice(this, array, info.path, len, items.length, []);
        }
        return ret;
      }

      /**
       * Removes an item from the end of array at the path specified.
       *
       * The arguments after `path` and return value match that of
       * `Array.prototype.pop`.
       *
       * This method notifies other paths to the same array that a
       * splice occurred to the array.
       *
       * @param {string | !Array<string|number>} path Path to array.
       * @return {*} Item that was removed.
       * @public
       */
      pop(path) {
        let info = {path: ''};
        let array = /** @type {Array} */(Polymer.Path.get(this, path, info));
        let hadLength = Boolean(array.length);
        let ret = array.pop();
        if (hadLength) {
          notifySplice(this, array, info.path, array.length, 0, [ret]);
        }
        return ret;
      }

      /**
       * Starting from the start index specified, removes 0 or more items
       * from the array and inserts 0 or more new items in their place.
       *
       * The arguments after `path` and return value match that of
       * `Array.prototype.splice`.
       *
       * This method notifies other paths to the same array that a
       * splice occurred to the array.
       *
       * @param {string | !Array<string|number>} path Path to array.
       * @param {number} start Index from which to start removing/inserting.
       * @param {number} deleteCount Number of items to remove.
       * @param {...*} items Items to insert into array.
       * @return {Array} Array of removed items.
       * @public
       */
      splice(path, start, deleteCount, ...items) {
        let info = {path : ''};
        let array = /** @type {Array} */(Polymer.Path.get(this, path, info));
        // Normalize fancy native splice handling of crazy start values
        if (start < 0) {
          start = array.length - Math.floor(-start);
        } else if (start) {
          start = Math.floor(start);
        }
        // array.splice does different things based on the number of arguments
        // you pass in. Therefore, array.splice(0) and array.splice(0, undefined)
        // do different things. In the former, the whole array is cleared. In the
        // latter, no items are removed.
        // This means that we need to detect whether 1. one of the arguments
        // is actually passed in and then 2. determine how many arguments
        // we should pass on to the native array.splice
        //
        let ret;
        // Omit any additional arguments if they were not passed in
        if (arguments.length === 2) {
          ret = array.splice(start);
        // Either start was undefined and the others were defined, but in this
        // case we can safely pass on all arguments
        //
        // Note: this includes the case where none of the arguments were passed in,
        // e.g. this.splice('array'). However, if both start and deleteCount
        // are undefined, array.splice will not modify the array (as expected)
        } else {
          ret = array.splice(start, deleteCount, ...items);
        }
        // At the end, check whether any items were passed in (e.g. insertions)
        // or if the return array contains items (e.g. deletions).
        // Only notify if items were added or deleted.
        if (items.length || ret.length) {
          notifySplice(this, array, info.path, start, items.length, ret);
        }
        return ret;
      }

      /**
       * Removes an item from the beginning of array at the path specified.
       *
       * The arguments after `path` and return value match that of
       * `Array.prototype.pop`.
       *
       * This method notifies other paths to the same array that a
       * splice occurred to the array.
       *
       * @param {string | !Array<string|number>} path Path to array.
       * @return {*} Item that was removed.
       * @public
       */
      shift(path) {
        let info = {path: ''};
        let array = /** @type {Array} */(Polymer.Path.get(this, path, info));
        let hadLength = Boolean(array.length);
        let ret = array.shift();
        if (hadLength) {
          notifySplice(this, array, info.path, 0, 0, [ret]);
        }
        return ret;
      }

      /**
       * Adds items onto the beginning of the array at the path specified.
       *
       * The arguments after `path` and return value match that of
       * `Array.prototype.push`.
       *
       * This method notifies other paths to the same array that a
       * splice occurred to the array.
       *
       * @param {string | !Array<string|number>} path Path to array.
       * @param {...*} items Items to insert info array
       * @return {number} New length of the array.
       * @public
       */
      unshift(path, ...items) {
        let info = {path: ''};
        let array = /** @type {Array} */(Polymer.Path.get(this, path, info));
        let ret = array.unshift(...items);
        if (items.length) {
          notifySplice(this, array, info.path, 0, items.length, []);
        }
        return ret;
      }

      /**
       * Notify that a path has changed.
       *
       * Example:
       *
       *     this.item.user.name = 'Bob';
       *     this.notifyPath('item.user.name');
       *
       * @param {string} path Path that should be notified.
       * @param {*=} value Value at the path (optional).
       * @return {void}
       * @public
      */
      notifyPath(path, value) {
        /** @type {string} */
        let propPath;
        if (arguments.length == 1) {
          // Get value if not supplied
          let info = {path: ''};
          value = Polymer.Path.get(this, path, info);
          propPath = info.path;
        } else if (Array.isArray(path)) {
          // Normalize path if needed
          propPath = Polymer.Path.normalize(path);
        } else {
          propPath = /** @type{string} */(path);
        }
        if (this._setPendingPropertyOrPath(propPath, value, true, true)) {
          this._invalidateProperties();
        }
      }

      /**
       * Equivalent to static `createReadOnlyProperty` API but can be called on
       * an instance to add effects at runtime.  See that method for
       * full API docs.
       *
       * @param {string} property Property name
       * @param {boolean=} protectedSetter Creates a custom protected setter
       *   when `true`.
       * @return {void}
       * @protected
       */
      _createReadOnlyProperty(property, protectedSetter) {
        this._addPropertyEffect(property, TYPES.READ_ONLY);
        if (protectedSetter) {
          this['_set' + upper(property)] = /** @this {PropertyEffects} */function(value) {
            this._setProperty(property, value);
          };
        }
      }

      /**
       * Equivalent to static `createPropertyObserver` API but can be called on
       * an instance to add effects at runtime.  See that method for
       * full API docs.
       *
       * @param {string} property Property name
       * @param {string|function(*,*)} method Function or name of observer method to call
       * @param {boolean=} dynamicFn Whether the method name should be included as
       *   a dependency to the effect.
       * @return {void}
       * @protected
       */
      _createPropertyObserver(property, method, dynamicFn) {
        let info = { property, method, dynamicFn: Boolean(dynamicFn) };
        this._addPropertyEffect(property, TYPES.OBSERVE, {
          fn: runObserverEffect, info, trigger: {name: property}
        });
        if (dynamicFn) {
          this._addPropertyEffect(/** @type {string} */(method), TYPES.OBSERVE, {
            fn: runObserverEffect, info, trigger: {name: method}
          });
        }
      }

      /**
       * Equivalent to static `createMethodObserver` API but can be called on
       * an instance to add effects at runtime.  See that method for
       * full API docs.
       *
       * @param {string} expression Method expression
       * @param {boolean|Object=} dynamicFn Boolean or object map indicating
       *   whether method names should be included as a dependency to the effect.
       * @return {void}
       * @protected
       */
      _createMethodObserver(expression, dynamicFn) {
        let sig = parseMethod(expression);
        if (!sig) {
          throw new Error("Malformed observer expression '" + expression + "'");
        }
        createMethodEffect(this, sig, TYPES.OBSERVE, runMethodEffect, null, dynamicFn);
      }

      /**
       * Equivalent to static `createNotifyingProperty` API but can be called on
       * an instance to add effects at runtime.  See that method for
       * full API docs.
       *
       * @param {string} property Property name
       * @return {void}
       * @protected
       */
      _createNotifyingProperty(property) {
        this._addPropertyEffect(property, TYPES.NOTIFY, {
          fn: runNotifyEffect,
          info: {
            eventName: CaseMap.camelToDashCase(property) + '-changed',
            property: property
          }
        });
      }

      /**
       * Equivalent to static `createReflectedProperty` API but can be called on
       * an instance to add effects at runtime.  See that method for
       * full API docs.
       *
       * @param {string} property Property name
       * @return {void}
       * @protected
       */
      _createReflectedProperty(property) {
        let attr = this.constructor.attributeNameForProperty(property);
        if (attr[0] === '-') {
          console.warn('Property ' + property + ' cannot be reflected to attribute ' +
            attr + ' because "-" is not a valid starting attribute name. Use a lowercase first letter for the property instead.');
        } else {
          this._addPropertyEffect(property, TYPES.REFLECT, {
            fn: runReflectEffect,
            info: {
              attrName: attr
            }
          });
        }
      }

      /**
       * Equivalent to static `createComputedProperty` API but can be called on
       * an instance to add effects at runtime.  See that method for
       * full API docs.
       *
       * @param {string} property Name of computed property to set
       * @param {string} expression Method expression
       * @param {boolean|Object=} dynamicFn Boolean or object map indicating
       *   whether method names should be included as a dependency to the effect.
       * @return {void}
       * @protected
       */
      _createComputedProperty(property, expression, dynamicFn) {
        let sig = parseMethod(expression);
        if (!sig) {
          throw new Error("Malformed computed expression '" + expression + "'");
        }
        createMethodEffect(this, sig, TYPES.COMPUTE, runComputedEffect, property, dynamicFn);
      }

      /**
       * Gather the argument values for a method specified in the provided array
       * of argument metadata.
       *
       * The `path` and `value` arguments are used to fill in wildcard descriptor
       * when the method is being called as a result of a path notification.
       *
       * @param {!Array<!MethodArg>} args Array of argument metadata
       * @param {string} path Property/path name that triggered the method effect
       * @param {Object} props Bag of current property changes
       * @return {Array<*>} Array of argument values
       * @private
       */
      _marshalArgs(args, path, props) {
        const data = this.__data;
        let values = [];
        for (let i=0, l=args.length; i<l; i++) {
          let arg = args[i];
          let name = arg.name;
          let v;
          if (arg.literal) {
            v = arg.value;
          } else {
            if (arg.structured) {
              v = Polymer.Path.get(data, name);
              // when data is not stored e.g. `splices`
              if (v === undefined) {
                v = props[name];
              }
            } else {
              v = data[name];
            }
          }
          if (arg.wildcard) {
            // Only send the actual path changed info if the change that
            // caused the observer to run matched the wildcard
            let baseChanged = (name.indexOf(path + '.') === 0);
            let matches = (path.indexOf(name) === 0 && !baseChanged);
            values[i] = {
              path: matches ? path : name,
              value: matches ? props[path] : v,
              base: v
            };
          } else {
            values[i] = v;
          }
        }
        return values;
      }

      // -- static class methods ------------

      /**
       * Ensures an accessor exists for the specified property, and adds
       * to a list of "property effects" that will run when the accessor for
       * the specified property is set.  Effects are grouped by "type", which
       * roughly corresponds to a phase in effect processing.  The effect
       * metadata should be in the following form:
       *
       *     {
       *       fn: effectFunction, // Reference to function to call to perform effect
       *       info: { ... }       // Effect metadata passed to function
       *       trigger: {          // Optional triggering metadata; if not provided
       *         name: string      // the property is treated as a wildcard
       *         structured: boolean
       *         wildcard: boolean
       *       }
       *     }
       *
       * Effects are called from `_propertiesChanged` in the following order by
       * type:
       *
       * 1. COMPUTE
       * 2. PROPAGATE
       * 3. REFLECT
       * 4. OBSERVE
       * 5. NOTIFY
       *
       * Effect functions are called with the following signature:
       *
       *     effectFunction(inst, path, props, oldProps, info, hasPaths)
       *
       * @param {string} property Property that should trigger the effect
       * @param {string} type Effect type, from this.PROPERTY_EFFECT_TYPES
       * @param {Object=} effect Effect metadata object
       * @return {void}
       * @protected
       */
      static addPropertyEffect(property, type, effect) {
        this.prototype._addPropertyEffect(property, type, effect);
      }

      /**
       * Creates a single-property observer for the given property.
       *
       * @param {string} property Property name
       * @param {string|function(*,*)} method Function or name of observer method to call
       * @param {boolean=} dynamicFn Whether the method name should be included as
       *   a dependency to the effect.
       * @return {void}
       * @protected
       */
      static createPropertyObserver(property, method, dynamicFn) {
        this.prototype._createPropertyObserver(property, method, dynamicFn);
      }

      /**
       * Creates a multi-property "method observer" based on the provided
       * expression, which should be a string in the form of a normal JavaScript
       * function signature: `'methodName(arg1, [..., argn])'`.  Each argument
       * should correspond to a property or path in the context of this
       * prototype (or instance), or may be a literal string or number.
       *
       * @param {string} expression Method expression
       * @param {boolean|Object=} dynamicFn Boolean or object map indicating
       * @return {void}
       *   whether method names should be included as a dependency to the effect.
       * @protected
       */
      static createMethodObserver(expression, dynamicFn) {
        this.prototype._createMethodObserver(expression, dynamicFn);
      }

      /**
       * Causes the setter for the given property to dispatch `<property>-changed`
       * events to notify of changes to the property.
       *
       * @param {string} property Property name
       * @return {void}
       * @protected
       */
      static createNotifyingProperty(property) {
        this.prototype._createNotifyingProperty(property);
      }

      /**
       * Creates a read-only accessor for the given property.
       *
       * To set the property, use the protected `_setProperty` API.
       * To create a custom protected setter (e.g. `_setMyProp()` for
       * property `myProp`), pass `true` for `protectedSetter`.
       *
       * Note, if the property will have other property effects, this method
       * should be called first, before adding other effects.
       *
       * @param {string} property Property name
       * @param {boolean=} protectedSetter Creates a custom protected setter
       *   when `true`.
       * @return {void}
       * @protected
       */
      static createReadOnlyProperty(property, protectedSetter) {
        this.prototype._createReadOnlyProperty(property, protectedSetter);
      }

      /**
       * Causes the setter for the given property to reflect the property value
       * to a (dash-cased) attribute of the same name.
       *
       * @param {string} property Property name
       * @return {void}
       * @protected
       */
      static createReflectedProperty(property) {
        this.prototype._createReflectedProperty(property);
      }

      /**
       * Creates a computed property whose value is set to the result of the
       * method described by the given `expression` each time one or more
       * arguments to the method changes.  The expression should be a string
       * in the form of a normal JavaScript function signature:
       * `'methodName(arg1, [..., argn])'`
       *
       * @param {string} property Name of computed property to set
       * @param {string} expression Method expression
       * @param {boolean|Object=} dynamicFn Boolean or object map indicating whether
       *   method names should be included as a dependency to the effect.
       * @return {void}
       * @protected
       */
      static createComputedProperty(property, expression, dynamicFn) {
        this.prototype._createComputedProperty(property, expression, dynamicFn);
      }

      /**
       * Parses the provided template to ensure binding effects are created
       * for them, and then ensures property accessors are created for any
       * dependent properties in the template.  Binding effects for bound
       * templates are stored in a linked list on the instance so that
       * templates can be efficiently stamped and unstamped.
       *
       * @param {!HTMLTemplateElement} template Template containing binding
       *   bindings
       * @return {!TemplateInfo} Template metadata object
       * @protected
       */
      static bindTemplate(template) {
        return this.prototype._bindTemplate(template);
      }

      // -- binding ----------------------------------------------

      /**
       * Equivalent to static `bindTemplate` API but can be called on
       * an instance to add effects at runtime.  See that method for
       * full API docs.
       *
       * This method may be called on the prototype (for prototypical template
       * binding, to avoid creating accessors every instance) once per prototype,
       * and will be called with `runtimeBinding: true` by `_stampTemplate` to
       * create and link an instance of the template metadata associated with a
       * particular stamping.
       *
       * @param {!HTMLTemplateElement} template Template containing binding
       *   bindings
       * @param {boolean=} instanceBinding When false (default), performs
       *   "prototypical" binding of the template and overwrites any previously
       *   bound template for the class. When true (as passed from
       *   `_stampTemplate`), the template info is instanced and linked into
       *   the list of bound templates.
       * @return {!TemplateInfo} Template metadata object; for `runtimeBinding`,
       *   this is an instance of the prototypical template info
       * @protected
       */
      _bindTemplate(template, instanceBinding) {
        let templateInfo = this.constructor._parseTemplate(template);
        let wasPreBound = this.__templateInfo == templateInfo;
        // Optimization: since this is called twice for proto-bound templates,
        // don't attempt to recreate accessors if this template was pre-bound
        if (!wasPreBound) {
          for (let prop in templateInfo.propertyEffects) {
            this._createPropertyAccessor(prop);
          }
        }
        if (instanceBinding) {
          // For instance-time binding, create instance of template metadata
          // and link into list of templates if necessary
          templateInfo = /** @type {!TemplateInfo} */(Object.create(templateInfo));
          templateInfo.wasPreBound = wasPreBound;
          if (!wasPreBound && this.__templateInfo) {
            let last = this.__templateInfoLast || this.__templateInfo;
            this.__templateInfoLast = last.nextTemplateInfo = templateInfo;
            templateInfo.previousTemplateInfo = last;
            return templateInfo;
          }
        }
        return this.__templateInfo = templateInfo;
      }

      /**
       * Adds a property effect to the given template metadata, which is run
       * at the "propagate" stage of `_propertiesChanged` when the template
       * has been bound to the element via `_bindTemplate`.
       *
       * The `effect` object should match the format in `_addPropertyEffect`.
       *
       * @param {Object} templateInfo Template metadata to add effect to
       * @param {string} prop Property that should trigger the effect
       * @param {Object=} effect Effect metadata object
       * @return {void}
       * @protected
       */
      static _addTemplatePropertyEffect(templateInfo, prop, effect) {
        let hostProps = templateInfo.hostProps = templateInfo.hostProps || {};
        hostProps[prop] = true;
        let effects = templateInfo.propertyEffects = templateInfo.propertyEffects || {};
        let propEffects = effects[prop] = effects[prop] || [];
        propEffects.push(effect);
      }

      /**
       * Stamps the provided template and performs instance-time setup for
       * Polymer template features, including data bindings, declarative event
       * listeners, and the `this.$` map of `id`'s to nodes.  A document fragment
       * is returned containing the stamped DOM, ready for insertion into the
       * DOM.
       *
       * This method may be called more than once; however note that due to
       * `shadycss` polyfill limitations, only styles from templates prepared
       * using `ShadyCSS.prepareTemplate` will be correctly polyfilled (scoped
       * to the shadow root and support CSS custom properties), and note that
       * `ShadyCSS.prepareTemplate` may only be called once per element. As such,
       * any styles required by in runtime-stamped templates must be included
       * in the main element template.
       *
       * @param {!HTMLTemplateElement} template Template to stamp
       * @return {!StampedTemplate} Cloned template content
       * @override
       * @protected
       */
      _stampTemplate(template) {
        // Ensures that created dom is `_enqueueClient`'d to this element so
        // that it can be flushed on next call to `_flushProperties`
        hostStack.beginHosting(this);
        let dom = super._stampTemplate(template);
        hostStack.endHosting(this);
        let templateInfo = /** @type {!TemplateInfo} */(this._bindTemplate(template, true));
        // Add template-instance-specific data to instanced templateInfo
        templateInfo.nodeList = dom.nodeList;
        // Capture child nodes to allow unstamping of non-prototypical templates
        if (!templateInfo.wasPreBound) {
          let nodes = templateInfo.childNodes = [];
          for (let n=dom.firstChild; n; n=n.nextSibling) {
            nodes.push(n);
          }
        }
        dom.templateInfo = templateInfo;
        // Setup compound storage, 2-way listeners, and dataHost for bindings
        setupBindings(this, templateInfo);
        // Flush properties into template nodes if already booted
        if (this.__dataReady) {
          runEffects(this, templateInfo.propertyEffects, this.__data, null,
            false, templateInfo.nodeList);
        }
        return dom;
      }

      /**
       * Removes and unbinds the nodes previously contained in the provided
       * DocumentFragment returned from `_stampTemplate`.
       *
       * @param {!StampedTemplate} dom DocumentFragment previously returned
       *   from `_stampTemplate` associated with the nodes to be removed
       * @return {void}
       * @protected
       */
      _removeBoundDom(dom) {
        // Unlink template info
        let templateInfo = dom.templateInfo;
        if (templateInfo.previousTemplateInfo) {
          templateInfo.previousTemplateInfo.nextTemplateInfo =
            templateInfo.nextTemplateInfo;
        }
        if (templateInfo.nextTemplateInfo) {
          templateInfo.nextTemplateInfo.previousTemplateInfo =
            templateInfo.previousTemplateInfo;
        }
        if (this.__templateInfoLast == templateInfo) {
          this.__templateInfoLast = templateInfo.previousTemplateInfo;
        }
        templateInfo.previousTemplateInfo = templateInfo.nextTemplateInfo = null;
        // Remove stamped nodes
        let nodes = templateInfo.childNodes;
        for (let i=0; i<nodes.length; i++) {
          let node = nodes[i];
          node.parentNode.removeChild(node);
        }
      }

      /**
       * Overrides default `TemplateStamp` implementation to add support for
       * parsing bindings from `TextNode`'s' `textContent`.  A `bindings`
       * array is added to `nodeInfo` and populated with binding metadata
       * with information capturing the binding target, and a `parts` array
       * with one or more metadata objects capturing the source(s) of the
       * binding.
       *
       * @override
       * @param {Node} node Node to parse
       * @param {TemplateInfo} templateInfo Template metadata for current template
       * @param {NodeInfo} nodeInfo Node metadata for current template node
       * @return {boolean} `true` if the visited node added node-specific
       *   metadata to `nodeInfo`
       * @protected
       * @suppress {missingProperties} Interfaces in closure do not inherit statics, but classes do
       */
      static _parseTemplateNode(node, templateInfo, nodeInfo) {
        let noted = super._parseTemplateNode(node, templateInfo, nodeInfo);
        if (node.nodeType === Node.TEXT_NODE) {
          let parts = this._parseBindings(node.textContent, templateInfo);
          if (parts) {
            // Initialize the textContent with any literal parts
            // NOTE: default to a space here so the textNode remains; some browsers
            // (IE) omit an empty textNode following cloneNode/importNode.
            node.textContent = literalFromParts(parts) || ' ';
            addBinding(this, templateInfo, nodeInfo, 'text', 'textContent', parts);
            noted = true;
          }
        }
        return noted;
      }

      /**
       * Overrides default `TemplateStamp` implementation to add support for
       * parsing bindings from attributes.  A `bindings`
       * array is added to `nodeInfo` and populated with binding metadata
       * with information capturing the binding target, and a `parts` array
       * with one or more metadata objects capturing the source(s) of the
       * binding.
       *
       * @override
       * @param {Element} node Node to parse
       * @param {TemplateInfo} templateInfo Template metadata for current template
       * @param {NodeInfo} nodeInfo Node metadata for current template node
       * @param {string} name Attribute name
       * @param {string} value Attribute value
       * @return {boolean} `true` if the visited node added node-specific
       *   metadata to `nodeInfo`
       * @protected
       * @suppress {missingProperties} Interfaces in closure do not inherit statics, but classes do
       */
      static _parseTemplateNodeAttribute(node, templateInfo, nodeInfo, name, value) {
        let parts = this._parseBindings(value, templateInfo);
        if (parts) {
          // Attribute or property
          let origName = name;
          let kind = 'property';
          // The only way we see a capital letter here is if the attr has
          // a capital letter in it per spec. In this case, to make sure
          // this binding works, we go ahead and make the binding to the attribute.
          if (capitalAttributeRegex.test(name)) {
            kind = 'attribute';
          } else if (name[name.length-1] == '$') {
            name = name.slice(0, -1);
            kind = 'attribute';
          }
          // Initialize attribute bindings with any literal parts
          let literal = literalFromParts(parts);
          if (literal && kind == 'attribute') {
            // Ensure a ShadyCSS template scoped style is not removed
            // when a class$ binding's initial literal value is set.
            if (name == 'class' && node.hasAttribute('class')) {
              literal += ' ' + node.getAttribute(name);
            }
            node.setAttribute(name, literal);
          }
          // Clear attribute before removing, since IE won't allow removing
          // `value` attribute if it previously had a value (can't
          // unconditionally set '' before removing since attributes with `$`
          // can't be set using setAttribute)
          if (node.localName === 'input' && origName === 'value') {
            node.setAttribute(origName, '');
          }
          // Remove annotation
          node.removeAttribute(origName);
          // Case hackery: attributes are lower-case, but bind targets
          // (properties) are case sensitive. Gambit is to map dash-case to
          // camel-case: `foo-bar` becomes `fooBar`.
          // Attribute bindings are excepted.
          if (kind === 'property') {
            name = Polymer.CaseMap.dashToCamelCase(name);
          }
          addBinding(this, templateInfo, nodeInfo, kind, name, parts, literal);
          return true;
        } else {
          return super._parseTemplateNodeAttribute(node, templateInfo, nodeInfo, name, value);
        }
      }

      /**
       * Overrides default `TemplateStamp` implementation to add support for
       * binding the properties that a nested template depends on to the template
       * as `_host_<property>`.
       *
       * @override
       * @param {Node} node Node to parse
       * @param {TemplateInfo} templateInfo Template metadata for current template
       * @param {NodeInfo} nodeInfo Node metadata for current template node
       * @return {boolean} `true` if the visited node added node-specific
       *   metadata to `nodeInfo`
       * @protected
       * @suppress {missingProperties} Interfaces in closure do not inherit statics, but classes do
       */
      static _parseTemplateNestedTemplate(node, templateInfo, nodeInfo) {
        let noted = super._parseTemplateNestedTemplate(node, templateInfo, nodeInfo);
        // Merge host props into outer template and add bindings
        let hostProps = nodeInfo.templateInfo.hostProps;
        let mode = '{';
        for (let source in hostProps) {
          let parts = [{ mode, source, dependencies: [source] }];
          addBinding(this, templateInfo, nodeInfo, 'property', '_host_' + source, parts);
        }
        return noted;
      }

      /**
       * Called to parse text in a template (either attribute values or
       * textContent) into binding metadata.
       *
       * Any overrides of this method should return an array of binding part
       * metadata  representing one or more bindings found in the provided text
       * and any "literal" text in between.  Any non-literal parts will be passed
       * to `_evaluateBinding` when any dependencies change.  The only required
       * fields of each "part" in the returned array are as follows:
       *
       * - `dependencies` - Array containing trigger metadata for each property
       *   that should trigger the binding to update
       * - `literal` - String containing text if the part represents a literal;
       *   in this case no `dependencies` are needed
       *
       * Additional metadata for use by `_evaluateBinding` may be provided in
       * each part object as needed.
       *
       * The default implementation handles the following types of bindings
       * (one or more may be intermixed with literal strings):
       * - Property binding: `[[prop]]`
       * - Path binding: `[[object.prop]]`
       * - Negated property or path bindings: `[[!prop]]` or `[[!object.prop]]`
       * - Two-way property or path bindings (supports negation):
       *   `{{prop}}`, `{{object.prop}}`, `{{!prop}}` or `{{!object.prop}}`
       * - Inline computed method (supports negation):
       *   `[[compute(a, 'literal', b)]]`, `[[!compute(a, 'literal', b)]]`
       *
       * The default implementation uses a regular expression for best
       * performance. However, the regular expression uses a white-list of
       * allowed characters in a data-binding, which causes problems for
       * data-bindings that do use characters not in this white-list.
       *
       * Instead of updating the white-list with all allowed characters,
       * there is a StrictBindingParser (see lib/mixins/strict-binding-parser)
       * that uses a state machine instead. This state machine is able to handle
       * all characters. However, it is slightly less performant, therefore we
       * extracted it into a separate optional mixin.
       *
       * @param {string} text Text to parse from attribute or textContent
       * @param {Object} templateInfo Current template metadata
       * @return {Array<!BindingPart>} Array of binding part metadata
       * @protected
       */
      static _parseBindings(text, templateInfo) {
        let parts = [];
        let lastIndex = 0;
        let m;
        // Example: "literal1{{prop}}literal2[[!compute(foo,bar)]]final"
        // Regex matches:
        //        Iteration 1:  Iteration 2:
        // m[1]: '{{'          '[['
        // m[2]: ''            '!'
        // m[3]: 'prop'        'compute(foo,bar)'
        while ((m = bindingRegex.exec(text)) !== null) {
          // Add literal part
          if (m.index > lastIndex) {
            parts.push({literal: text.slice(lastIndex, m.index)});
          }
          // Add binding part
          let mode = m[1][0];
          let negate = Boolean(m[2]);
          let source = m[3].trim();
          let customEvent = false, notifyEvent = '', colon = -1;
          if (mode == '{' && (colon = source.indexOf('::')) > 0) {
            notifyEvent = source.substring(colon + 2);
            source = source.substring(0, colon);
            customEvent = true;
          }
          let signature = parseMethod(source);
          let dependencies = [];
          if (signature) {
            // Inline computed function
            let {args, methodName} = signature;
            for (let i=0; i<args.length; i++) {
              let arg = args[i];
              if (!arg.literal) {
                dependencies.push(arg);
              }
            }
            let dynamicFns = templateInfo.dynamicFns;
            if (dynamicFns && dynamicFns[methodName] || signature.static) {
              dependencies.push(methodName);
              signature.dynamicFn = true;
            }
          } else {
            // Property or path
            dependencies.push(source);
          }
          parts.push({
            source, mode, negate, customEvent, signature, dependencies,
            event: notifyEvent
          });
          lastIndex = bindingRegex.lastIndex;
        }
        // Add a final literal part
        if (lastIndex && lastIndex < text.length) {
          let literal = text.substring(lastIndex);
          if (literal) {
            parts.push({
              literal: literal
            });
          }
        }
        if (parts.length) {
          return parts;
        } else {
          return null;
        }
      }

      /**
       * Called to evaluate a previously parsed binding part based on a set of
       * one or more changed dependencies.
       *
       * @param {this} inst Element that should be used as scope for
       *   binding dependencies
       * @param {BindingPart} part Binding part metadata
       * @param {string} path Property/path that triggered this effect
       * @param {Object} props Bag of current property changes
       * @param {Object} oldProps Bag of previous values for changed properties
       * @param {boolean} hasPaths True with `props` contains one or more paths
       * @return {*} Value the binding part evaluated to
       * @protected
       */
      static _evaluateBinding(inst, part, path, props, oldProps, hasPaths) {
        let value;
        if (part.signature) {
          value = runMethodEffect(inst, path, props, oldProps, part.signature);
        } else if (path != part.source) {
          value = Polymer.Path.get(inst, part.source);
        } else {
          if (hasPaths && Polymer.Path.isPath(path)) {
            value = Polymer.Path.get(inst, path);
          } else {
            value = inst.__data[path];
          }
        }
        if (part.negate) {
          value = !value;
        }
        return value;
      }

    }

    // make a typing for closure :P
    PropertyEffectsType = PropertyEffects;

    return PropertyEffects;
  });

  /**
   * Helper api for enqueuing client dom created by a host element.
   *
   * By default elements are flushed via `_flushProperties` when
   * `connectedCallback` is called. Elements attach their client dom to
   * themselves at `ready` time which results from this first flush.
   * This provides an ordering guarantee that the client dom an element
   * creates is flushed before the element itself (i.e. client `ready`
   * fires before host `ready`).
   *
   * However, if `_flushProperties` is called *before* an element is connected,
   * as for example `Templatize` does, this ordering guarantee cannot be
   * satisfied because no elements are connected. (Note: Bound elements that
   * receive data do become enqueued clients and are properly ordered but
   * unbound elements are not.)
   *
   * To maintain the desired "client before host" ordering guarantee for this
   * case we rely on the "host stack. Client nodes registers themselves with
   * the creating host element when created. This ensures that all client dom
   * is readied in the proper order, maintaining the desired guarantee.
   *
   * @private
   */
  let hostStack = {

    stack: [],

    /**
     * @param {*} inst Instance to add to hostStack
     * @return {void}
     * @this {hostStack}
     */
    registerHost(inst) {
      if (this.stack.length) {
        let host = this.stack[this.stack.length-1];
        host._enqueueClient(inst);
      }
    },

    /**
     * @param {*} inst Instance to begin hosting
     * @return {void}
     * @this {hostStack}
     */
    beginHosting(inst) {
      this.stack.push(inst);
    },

    /**
     * @param {*} inst Instance to end hosting
     * @return {void}
     * @this {hostStack}
     */
    endHosting(inst) {
      let stackLen = this.stack.length;
      if (stackLen && this.stack[stackLen-1] == inst) {
        this.stack.pop();
      }
    }

  };

})();
</script>
<script>
(function() {
  'use strict';

  /**
   * Provides basic tracking of element definitions (registrations) and
   * instance counts.
   *
   * @namespace
   * @summary Provides basic tracking of element definitions (registrations) and
   * instance counts.
   */
  Polymer.telemetry = {
    /**
     * Total number of Polymer element instances created.
     * @type {number}
     */
    instanceCount: 0,
    /**
     * Array of Polymer element classes that have been finalized.
     * @type {Array<Polymer.Element>}
     */
    registrations: [],
    /**
     * @param {!PolymerElementConstructor} prototype Element prototype to log
     * @this {this}
     * @private
     */
    _regLog: function(prototype) {
      console.log('[' + prototype.is + ']: registered');
    },
    /**
     * Registers a class prototype for telemetry purposes.
     * @param {HTMLElement} prototype Element prototype to register
     * @this {this}
     * @protected
     */
    register: function(prototype) {
      this.registrations.push(prototype);
      Polymer.log && this._regLog(prototype);
    },
    /**
     * Logs all elements registered with an `is` to the console.
     * @public
     * @this {this}
     */
    dumpRegistrations: function() {
      this.registrations.forEach(this._regLog);
    }
  };

})();
</script>
<script>
(function() {
  'use strict';

  /**
   * Creates a copy of `props` with each property normalized such that
   * upgraded it is an object with at least a type property { type: Type}.
   *
   * @param {Object} props Properties to normalize
   * @return {Object} Copy of input `props` with normalized properties that
   * are in the form {type: Type}
   * @private
   */
  function normalizeProperties(props) {
    const output = {};
    for (let p in props) {
      const o = props[p];
      output[p] = (typeof o === 'function') ? {type: o} : o;
    }
    return output;
  }

  /**
   * Mixin that provides a minimal starting point to using the PropertiesChanged
   * mixin by providing a mechanism to declare properties in a static
   * getter (e.g. static get properties() { return { foo: String } }). Changes
   * are reported via the `_propertiesChanged` method.
   *
   * This mixin provides no specific support for rendering. Users are expected
   * to create a ShadowRoot and put content into it and update it in whatever
   * way makes sense. This can be done in reaction to properties changing by
   * implementing `_propertiesChanged`.
   *
   * @mixinFunction
   * @polymer
   * @appliesMixin Polymer.PropertiesChanged
   * @memberof Polymer
   * @summary Mixin that provides a minimal starting point for using
   * the PropertiesChanged mixin by providing a declarative `properties` object.
   */
   Polymer.PropertiesMixin = Polymer.dedupingMixin(superClass => {

    /**
     * @constructor
     * @extends {superClass}
     * @implements {Polymer_PropertiesChanged}
     * @private
     */
    const base = Polymer.PropertiesChanged(superClass);

    /**
     * Returns the super class constructor for the given class, if it is an
     * instance of the PropertiesMixin.
     *
     * @param {!PropertiesMixinConstructor} constructor PropertiesMixin constructor
     * @return {PropertiesMixinConstructor} Super class constructor
     */
    function superPropertiesClass(constructor) {
      const superCtor = Object.getPrototypeOf(constructor);

      // Note, the `PropertiesMixin` class below only refers to the class
      // generated by this call to the mixin; the instanceof test only works
      // because the mixin is deduped and guaranteed only to apply once, hence
      // all constructors in a proto chain will see the same `PropertiesMixin`
      return (superCtor.prototype instanceof PropertiesMixin) ?
        /** @type {PropertiesMixinConstructor} */ (superCtor) : null;
    }

    /**
     * Returns a memoized version of the `properties` object for the
     * given class. Properties not in object format are converted to at
     * least {type}.
     *
     * @param {PropertiesMixinConstructor} constructor PropertiesMixin constructor
     * @return {Object} Memoized properties object
     */
    function ownProperties(constructor) {
      if (!constructor.hasOwnProperty(JSCompiler_renameProperty('__ownProperties', constructor))) {
        let props = null;

        if (constructor.hasOwnProperty(JSCompiler_renameProperty('properties', constructor))) {
          const properties = constructor.properties;
          
          if (properties) {
            props = normalizeProperties(properties);
          }
        }

        constructor.__ownProperties = props;
      }
      return constructor.__ownProperties;
    }

    /**
     * @polymer
     * @mixinClass
     * @extends {base}
     * @implements {Polymer_PropertiesMixin}
     * @unrestricted
     */
    class PropertiesMixin extends base {

      /**
       * Implements standard custom elements getter to observes the attributes
       * listed in `properties`.
       * @suppress {missingProperties} Interfaces in closure do not inherit statics, but classes do
       */
      static get observedAttributes() {
        if (!this.hasOwnProperty('__observedAttributes')) {
          Polymer.telemetry.register(this.prototype);
          const props = this._properties;
          this.__observedAttributes = props ? Object.keys(props).map(p => this.attributeNameForProperty(p)) : [];
        }
        return this.__observedAttributes;
      }

      /**
       * Finalizes an element definition, including ensuring any super classes
       * are also finalized. This includes ensuring property
       * accessors exist on the element prototype. This method calls
       * `_finalizeClass` to finalize each constructor in the prototype chain.
       * @return {void}
       */
      static finalize() {
        if (!this.hasOwnProperty(JSCompiler_renameProperty('__finalized', this))) {
          const superCtor = superPropertiesClass(/** @type {PropertiesMixinConstructor} */(this));
          if (superCtor) {
            superCtor.finalize();
          }
          this.__finalized = true;
          this._finalizeClass();
        }
      }

      /**
       * Finalize an element class. This includes ensuring property
       * accessors exist on the element prototype. This method is called by
       * `finalize` and finalizes the class constructor.
       *
       * @protected
       */
      static _finalizeClass() {
        const props = ownProperties(/** @type {PropertiesMixinConstructor} */(this));
        if (props) {
          this.createProperties(props);
        }
      }

      /**
       * Returns a memoized version of all properties, including those inherited
       * from super classes. Properties not in object format are converted to
       * at least {type}.
       *
       * @return {Object} Object containing properties for this class
       * @protected
       */
      static get _properties() {
        if (!this.hasOwnProperty(
          JSCompiler_renameProperty('__properties', this))) {
          const superCtor = superPropertiesClass(/** @type {PropertiesMixinConstructor} */(this));
          this.__properties = Object.assign({},
            superCtor && superCtor._properties,
            ownProperties(/** @type {PropertiesMixinConstructor} */(this)));
        }
        return this.__properties;
      }

      /**
       * Overrides `PropertiesChanged` method to return type specified in the
       * static `properties` object for the given property.
       * @param {string} name Name of property
       * @return {*} Type to which to deserialize attribute
       *
       * @protected
       */
      static typeForProperty(name) {
        const info = this._properties[name];
        return info && info.type;
      }

      /**
       * Overrides `PropertiesChanged` method and adds a call to
       * `finalize` which lazily configures the element's property accessors.
       * @override
       * @return {void}
       */
      _initializeProperties() {
        Polymer.telemetry.instanceCount++;
        this.constructor.finalize();
        super._initializeProperties();
      }

      /**
       * Called when the element is added to a document.
       * Calls `_enableProperties` to turn on property system from
       * `PropertiesChanged`.
       * @suppress {missingProperties} Super may or may not implement the callback
       * @return {void}
       */
      connectedCallback() {
        if (super.connectedCallback) {
          super.connectedCallback();
        }
        this._enableProperties();
      }

      /**
       * Called when the element is removed from a document
       * @suppress {missingProperties} Super may or may not implement the callback
       * @return {void}
       */
      disconnectedCallback() {
        if (super.disconnectedCallback) {
          super.disconnectedCallback();
        }
      }

    }

    return PropertiesMixin;

  });

})();

</script>
<script>
(function() {
  'use strict';

  const builtCSS = window.ShadyCSS && window.ShadyCSS['cssBuild'];

  /**
   * Element class mixin that provides the core API for Polymer's meta-programming
   * features including template stamping, data-binding, attribute deserialization,
   * and property change observation.
   *
   * Subclassers may provide the following static getters to return metadata
   * used to configure Polymer's features for the class:
   *
   * - `static get is()`: When the template is provided via a `dom-module`,
   *   users should return the `dom-module` id from a static `is` getter.  If
   *   no template is needed or the template is provided directly via the
   *   `template` getter, there is no need to define `is` for the element.
   *
   * - `static get template()`: Users may provide the template directly (as
   *   opposed to via `dom-module`) by implementing a static `template` getter.
   *   The getter may return an `HTMLTemplateElement` or a string, which will
   *   automatically be parsed into a template.
   *
   * - `static get properties()`: Should return an object describing
   *   property-related metadata used by Polymer features (key: property name
   *   value: object containing property metadata). Valid keys in per-property
   *   metadata include:
   *   - `type` (String|Number|Object|Array|...): Used by
   *     `attributeChangedCallback` to determine how string-based attributes
   *     are deserialized to JavaScript property values.
   *   - `notify` (boolean): Causes a change in the property to fire a
   *     non-bubbling event called `<property>-changed`. Elements that have
   *     enabled two-way binding to the property use this event to observe changes.
   *   - `readOnly` (boolean): Creates a getter for the property, but no setter.
   *     To set a read-only property, use the private setter method
   *     `_setProperty(property, value)`.
   *   - `observer` (string): Observer method name that will be called when
   *     the property changes. The arguments of the method are
   *     `(value, previousValue)`.
   *   - `computed` (string): String describing method and dependent properties
   *     for computing the value of this property (e.g. `'computeFoo(bar, zot)'`).
   *     Computed properties are read-only by default and can only be changed
   *     via the return value of the computing method.
   *
   * - `static get observers()`: Array of strings describing multi-property
   *   observer methods and their dependent properties (e.g.
   *   `'observeABC(a, b, c)'`).
   *
   * The base class provides default implementations for the following standard
   * custom element lifecycle callbacks; users may override these, but should
   * call the super method to ensure
   * - `constructor`: Run when the element is created or upgraded
   * - `connectedCallback`: Run each time the element is connected to the
   *   document
   * - `disconnectedCallback`: Run each time the element is disconnected from
   *   the document
   * - `attributeChangedCallback`: Run each time an attribute in
   *   `observedAttributes` is set or removed (note: this element's default
   *   `observedAttributes` implementation will automatically return an array
   *   of dash-cased attributes based on `properties`)
   *
   * @mixinFunction
   * @polymer
   * @appliesMixin Polymer.PropertyEffects
   * @appliesMixin Polymer.PropertiesMixin
   * @memberof Polymer
   * @property rootPath {string} Set to the value of `Polymer.rootPath`,
   *   which defaults to the main document path
   * @property importPath {string} Set to the value of the class's static
   *   `importPath` property, which defaults to the path of this element's
   *   `dom-module` (when `is` is used), but can be overridden for other
   *   import strategies.
   * @summary Element class mixin that provides the core API for Polymer's
   * meta-programming features.
   */
  Polymer.ElementMixin = Polymer.dedupingMixin(base => {

    /**
     * @constructor
     * @extends {base}
     * @implements {Polymer_PropertyEffects}
     * @implements {Polymer_PropertiesMixin}
     * @private
     */
    const polymerElementBase = Polymer.PropertiesMixin(Polymer.PropertyEffects(base));

    /**
     * Returns a list of properties with default values.
     * This list is created as an optimization since it is a subset of
     * the list returned from `_properties`.
     * This list is used in `_initializeProperties` to set property defaults.
     *
     * @param {PolymerElementConstructor} constructor Element class
     * @return {PolymerElementProperties} Flattened properties for this class
     *   that have default values
     * @private
     */
    function propertyDefaults(constructor) {
      if (!constructor.hasOwnProperty(
        JSCompiler_renameProperty('__propertyDefaults', constructor))) {
        constructor.__propertyDefaults = null;
        let props = constructor._properties;
        for (let p in props) {
          let info = props[p];
          if ('value' in info) {
            constructor.__propertyDefaults = constructor.__propertyDefaults || {};
            constructor.__propertyDefaults[p] = info;
          }
        }
      }
      return constructor.__propertyDefaults;
    }

    /**
     * Returns a memoized version of the `observers` array.
     * @param {PolymerElementConstructor} constructor Element class
     * @return {Array} Array containing own observers for the given class
     * @protected
     */
    function ownObservers(constructor) {
      if (!constructor.hasOwnProperty(
        JSCompiler_renameProperty('__ownObservers', constructor))) {
          constructor.__ownObservers =
          constructor.hasOwnProperty(JSCompiler_renameProperty('observers', constructor)) ?
          /** @type {PolymerElementConstructor} */ (constructor).observers : null;
      }
      return constructor.__ownObservers;
    }

    /**
     * Creates effects for a property.
     *
     * Note, once a property has been set to
     * `readOnly`, `computed`, `reflectToAttribute`, or `notify`
     * these values may not be changed. For example, a subclass cannot
     * alter these settings. However, additional `observers` may be added
     * by subclasses.
     *
     * The info object should contain property metadata as follows:
     *
     * * `type`: {function} type to which an attribute matching the property
     * is deserialized. Note the property is camel-cased from a dash-cased
     * attribute. For example, 'foo-bar' attribute is deserialized to a
     * property named 'fooBar'.
     *
     * * `readOnly`: {boolean} creates a readOnly property and
     * makes a private setter for the private of the form '_setFoo' for a
     * property 'foo',
     *
     * * `computed`: {string} creates a computed property. A computed property
     * is also automatically set to `readOnly: true`. The value is calculated
     * by running a method and arguments parsed from the given string. For
     * example 'compute(foo)' will compute a given property when the
     * 'foo' property changes by executing the 'compute' method. This method
     * must return the computed value.
     *
     * * `reflectToAttribute`: {boolean} If true, the property value is reflected
     * to an attribute of the same name. Note, the attribute is dash-cased
     * so a property named 'fooBar' is reflected as 'foo-bar'.
     *
     * * `notify`: {boolean} sends a non-bubbling notification event when
     * the property changes. For example, a property named 'foo' sends an
     * event named 'foo-changed' with `event.detail` set to the value of
     * the property.
     *
     * * observer: {string} name of a method that runs when the property
     * changes. The arguments of the method are (value, previousValue).
     *
     * Note: Users may want control over modifying property
     * effects via subclassing. For example, a user might want to make a
     * reflectToAttribute property not do so in a subclass. We've chosen to
     * disable this because it leads to additional complication.
     * For example, a readOnly effect generates a special setter. If a subclass
     * disables the effect, the setter would fail unexpectedly.
     * Based on feedback, we may want to try to make effects more malleable
     * and/or provide an advanced api for manipulating them.
     * Also consider adding warnings when an effect cannot be changed.
     *
     * @param {!PolymerElement} proto Element class prototype to add accessors
     *   and effects to
     * @param {string} name Name of the property.
     * @param {Object} info Info object from which to create property effects.
     * Supported keys:
     * @param {Object} allProps Flattened map of all properties defined in this
     *   element (including inherited properties)
     * @return {void}
     * @private
     */
    function createPropertyFromConfig(proto, name, info, allProps) {
      // computed forces readOnly...
      if (info.computed) {
        info.readOnly = true;
      }
      // Note, since all computed properties are readOnly, this prevents
      // adding additional computed property effects (which leads to a confusing
      // setup where multiple triggers for setting a property)
      // While we do have `hasComputedEffect` this is set on the property's
      // dependencies rather than itself.
      if (info.computed && !proto._hasReadOnlyEffect(name)) {
        proto._createComputedProperty(name, info.computed, allProps);
      }
      if (info.readOnly && !proto._hasReadOnlyEffect(name)) {
        proto._createReadOnlyProperty(name, !info.computed);
      }
      if (info.reflectToAttribute && !proto._hasReflectEffect(name)) {
        proto._createReflectedProperty(name);
      }
      if (info.notify && !proto._hasNotifyEffect(name)) {
        proto._createNotifyingProperty(name);
      }
      // always add observer
      if (info.observer) {
        proto._createPropertyObserver(name, info.observer, allProps[info.observer]);
      }
      // always create the mapping from attribute back to property for deserialization.
      proto._addPropertyToAttributeMap(name);
    }

    /**
     * Process all style elements in the element template. Styles with the
     * `include` attribute are processed such that any styles in
     * the associated "style modules" are included in the element template.
     * @param {PolymerElementConstructor} klass Element class
     * @param {!HTMLTemplateElement} template Template to process
     * @param {string} is Name of element
     * @param {string} baseURI Base URI for element
     * @private
     */
    function processElementStyles(klass, template, is, baseURI) {
      if (!builtCSS) {
        const templateStyles = template.content.querySelectorAll('style');
        const stylesWithImports = Polymer.StyleGather.stylesFromTemplate(template);
        // insert styles from <link rel="import" type="css"> at the top of the template
        const linkedStyles = Polymer.StyleGather.stylesFromModuleImports(is);
        const firstTemplateChild = template.content.firstElementChild;
        for (let idx = 0; idx < linkedStyles.length; idx++) {
          let s = linkedStyles[idx];
          s.textContent = klass._processStyleText(s.textContent, baseURI);
          template.content.insertBefore(s, firstTemplateChild);
        }
        // keep track of the last "concrete" style in the template we have encountered
        let templateStyleIndex = 0;
        // ensure all gathered styles are actually in this template.
        for (let i = 0; i < stylesWithImports.length; i++) {
          let s = stylesWithImports[i];
          let templateStyle = templateStyles[templateStyleIndex];
          // if the style is not in this template, it's been "included" and
          // we put a clone of it in the template before the style that included it
          if (templateStyle !== s) {
            s = s.cloneNode(true);
            templateStyle.parentNode.insertBefore(s, templateStyle);
          } else {
            templateStyleIndex++;
          }
          s.textContent = klass._processStyleText(s.textContent, baseURI);
        }
      }
      if (window.ShadyCSS) {
        window.ShadyCSS.prepareTemplate(template, is);
      }
    }

    /**
     * Look up template from dom-module for element
     *
     * @param {!string} is Element name to look up
     * @return {!HTMLTemplateElement} Template found in dom module, or
     *   undefined if not found
     * @protected
     */
    function getTemplateFromDomModule(is) {
      let template = null;
      if (is && Polymer.DomModule) {
        template = Polymer.DomModule.import(is, 'template');
        // Under strictTemplatePolicy, require any element with an `is`
        // specified to have a dom-module
        if (Polymer.strictTemplatePolicy && !template) {
          throw new Error(`strictTemplatePolicy: expecting dom-module or null template for ${is}`);
        }
      }
      return template;
    }

  /**
     * @polymer
     * @mixinClass
     * @unrestricted
     * @implements {Polymer_ElementMixin}
     */
    class PolymerElement extends polymerElementBase {

      /**
       * Override of PropertiesMixin _finalizeClass to create observers and
       * find the template.
       * @return {void}
       * @protected
       * @override
       * @suppress {missingProperties} Interfaces in closure do not inherit statics, but classes do
       */
      static _finalizeClass() {
        super._finalizeClass();
        const observers = ownObservers(this);
        if (observers) {
          this.createObservers(observers, this._properties);
        }
        this._prepareTemplate();
      }

      static _prepareTemplate() {
        // note: create "working" template that is finalized at instance time
        let template = /** @type {PolymerElementConstructor} */ (this).template;
        if (template) {
          if (typeof template === 'string') {
            let t = document.createElement('template');
            t.innerHTML = template;
            template = t;
          } else if (!Polymer.legacyOptimizations) {
             template = template.cloneNode(true);
          }
        }

        this.prototype._template = template;
      }

      /**
       * Override of PropertiesChanged createProperties to create accessors
       * and property effects for all of the properties.
       * @return {void}
       * @protected
       * @override
       */
      static createProperties(props) {
        for (let p in props) {
          createPropertyFromConfig(this.prototype, p, props[p], props);
        }
      }

      /**
       * Creates observers for the given `observers` array.
       * Leverages `PropertyEffects` to create observers.
       * @param {Object} observers Array of observer descriptors for
       *   this class
       * @param {Object} dynamicFns Object containing keys for any properties
       *   that are functions and should trigger the effect when the function
       *   reference is changed
       * @return {void}
       * @protected
       */
      static createObservers(observers, dynamicFns) {
        const proto = this.prototype;
        for (let i=0; i < observers.length; i++) {
          proto._createMethodObserver(observers[i], dynamicFns);
        }
      }

      /**
       * Returns the template that will be stamped into this element's shadow root.
       *
       * If a `static get is()` getter is defined, the default implementation
       * will return the first `<template>` in a `dom-module` whose `id`
       * matches this element's `is`.
       *
       * Users may override this getter to return an arbitrary template
       * (in which case the `is` getter is unnecessary). The template returned
       * may be either an `HTMLTemplateElement` or a string that will be
       * automatically parsed into a template.
       *
       * Note that when subclassing, if the super class overrode the default
       * implementation and the subclass would like to provide an alternate
       * template via a `dom-module`, it should override this getter and
       * return `Polymer.DomModule.import(this.is, 'template')`.
       *
       * If a subclass would like to modify the super class template, it should
       * clone it rather than modify it in place.  If the getter does expensive
       * work such as cloning/modifying a template, it should memoize the
       * template for maximum performance:
       *
       *   let memoizedTemplate;
       *   class MySubClass extends MySuperClass {
       *     static get template() {
       *       if (!memoizedTemplate) {
       *         memoizedTemplate = MySuperClass.template.cloneNode(true);
       *         let subContent = document.createElement('div');
       *         subContent.textContent = 'This came from MySubClass';
       *         memoizedTemplate.content.appendChild(subContent);
       *       }
       *       return memoizedTemplate;
       *     }
       *   }
       *
       * @return {HTMLTemplateElement|string} Template to be stamped
       */
      static get template() {
        // Explanation of template-related properties:
        // - constructor.template (this getter): the template for the class.
        //     This can come from the prototype (for legacy elements), from a
        //     dom-module, or from the super class's template (or can be overridden
        //     altogether by the user)
        // - constructor._template: memoized version of constructor.template
        // - prototype._template: working template for the element, which will be
        //     parsed and modified in place. It is a cloned version of
        //     constructor.template, saved in _finalizeClass(). Note that before
        //     this getter is called, for legacy elements this could be from a
        //     _template field on the info object passed to Polymer(), a behavior,
        //     or set in registered(); once the static getter runs, a clone of it
        //     will overwrite it on the prototype as the working template.
        if (!this.hasOwnProperty(JSCompiler_renameProperty('_template', this))) {
          this._template =
            // If user has put template on prototype (e.g. in legacy via registered
            // callback or info object), prefer that first
            this.prototype.hasOwnProperty(JSCompiler_renameProperty('_template', this.prototype)) ?
            this.prototype._template :
            // Look in dom-module associated with this element's is
            (getTemplateFromDomModule(/** @type {PolymerElementConstructor}*/ (this).is) ||
            // Next look for superclass template (call the super impl this
            // way so that `this` points to the superclass)
            Object.getPrototypeOf(/** @type {PolymerElementConstructor}*/ (this).prototype).constructor.template);
        }
        return this._template;
      }

      /**
       * Set the template.
       *
       * @param {HTMLTemplateElement|string} value Template to set.
       */
      static set template(value) {
        this._template = value;
      }

      /**
       * Path matching the url from which the element was imported.
       *
       * This path is used to resolve url's in template style cssText.
       * The `importPath` property is also set on element instances and can be
       * used to create bindings relative to the import path.
       *
       * For elements defined in ES modules, users should implement
       * `static get importMeta() { return import.meta; }`, and the default
       * implementation of `importPath` will  return `import.meta.url`'s path.
       * For elements defined in HTML imports, this getter will return the path
       * to the document containing a `dom-module` element matching this
       * element's static `is` property.
       *
       * Note, this path should contain a trailing `/`.
       *
       * @return {string} The import path for this element class
       * @suppress {missingProperties}
       */
      static get importPath() {
        if (!this.hasOwnProperty(JSCompiler_renameProperty('_importPath', this))) {
          const meta = this.importMeta;
          if (meta) {
            this._importPath = Polymer.ResolveUrl.pathFromUrl(meta.url);
          } else {
            const module = Polymer.DomModule && Polymer.DomModule.import(/** @type {PolymerElementConstructor} */ (this).is);
            this._importPath = (module && module.assetpath) ||
              Object.getPrototypeOf(/** @type {PolymerElementConstructor}*/ (this).prototype).constructor.importPath;
          }
        }
        return this._importPath;
      }

      constructor() {
        super();
        /** @type {HTMLTemplateElement} */
        this._template;
        /** @type {string} */
        this._importPath;
        /** @type {string} */
        this.rootPath;
        /** @type {string} */
        this.importPath;
        /** @type {StampedTemplate | HTMLElement | ShadowRoot} */
        this.root;
        /** @type {!Object<string, !Element>} */
        this.$;
      }

      /**
       * Overrides the default `Polymer.PropertyAccessors` to ensure class
       * metaprogramming related to property accessors and effects has
       * completed (calls `finalize`).
       *
       * It also initializes any property defaults provided via `value` in
       * `properties` metadata.
       *
       * @return {void}
       * @override
       * @suppress {invalidCasts}
       */
      _initializeProperties() {
        this.constructor.finalize();
        // note: finalize template when we have access to `localName` to
        // avoid dependence on `is` for polyfilling styling.
        this.constructor._finalizeTemplate(/** @type {!HTMLElement} */(this).localName);
        super._initializeProperties();
        // set path defaults
        this.rootPath = Polymer.rootPath;
        this.importPath = this.constructor.importPath;
        // apply property defaults...
        let p$ = propertyDefaults(this.constructor);
        if (!p$) {
          return;
        }
        for (let p in p$) {
          let info = p$[p];
          // Don't set default value if there is already an own property, which
          // happens when a `properties` property with default but no effects had
          // a property set (e.g. bound) by its host before upgrade
          if (!this.hasOwnProperty(p)) {
            let value = typeof info.value == 'function' ?
              info.value.call(this) :
              info.value;
            // Set via `_setProperty` if there is an accessor, to enable
            // initializing readOnly property defaults
            if (this._hasAccessor(p)) {
              this._setPendingProperty(p, value, true);
            } else {
              this[p] = value;
            }
          }
        }
      }

      /**
       * Gather style text for a style element in the template.
       *
       * @param {string} cssText Text containing styling to process
       * @param {string} baseURI Base URI to rebase CSS paths against
       * @return {string} The processed CSS text
       * @protected
       */
      static _processStyleText(cssText, baseURI) {
        return Polymer.ResolveUrl.resolveCss(cssText, baseURI);
      }

      /**
      * Configures an element `proto` to function with a given `template`.
      * The element name `is` and extends `ext` must be specified for ShadyCSS
      * style scoping.
      *
      * @param {string} is Tag name (or type extension name) for this element
      * @return {void}
      * @protected
      */
      static _finalizeTemplate(is) {
        /** @const {HTMLTemplateElement} */
        const template = this.prototype._template;
        if (template && !template.__polymerFinalized) {
          template.__polymerFinalized = true;
          const importPath = this.importPath;
          const baseURI = importPath ? Polymer.ResolveUrl.resolveUrl(importPath) : '';
          // e.g. support `include="module-name"`, and ShadyCSS
          processElementStyles(this, template, is, baseURI);
          this.prototype._bindTemplate(template);
        }
      }

      /**
       * Provides a default implementation of the standard Custom Elements
       * `connectedCallback`.
       *
       * The default implementation enables the property effects system and
       * flushes any pending properties, and updates shimmed CSS properties
       * when using the ShadyCSS scoping/custom properties polyfill.
       *
       * @suppress {missingProperties, invalidCasts} Super may or may not implement the callback
       * @return {void}
       */
      connectedCallback() {
        if (window.ShadyCSS && this._template) {
          window.ShadyCSS.styleElement(/** @type {!HTMLElement} */(this));
        }
        super.connectedCallback();
      }

      /**
       * Stamps the element template.
       *
       * @return {void}
       * @override
       */
      ready() {
        if (this._template) {
          this.root = this._stampTemplate(this._template);
          this.$ = this.root.$;
        }
        super.ready();
      }

      /**
       * Implements `PropertyEffects`'s `_readyClients` call. Attaches
       * element dom by calling `_attachDom` with the dom stamped from the
       * element's template via `_stampTemplate`. Note that this allows
       * client dom to be attached to the element prior to any observers
       * running.
       *
       * @return {void}
       * @override
       */
      _readyClients() {
        if (this._template) {
          this.root = this._attachDom(/** @type {StampedTemplate} */(this.root));
        }
        // The super._readyClients here sets the clients initialized flag.
        // We must wait to do this until after client dom is created/attached
        // so that this flag can be checked to prevent notifications fired
        // during this process from being handled before clients are ready.
        super._readyClients();
      }


      /**
       * Attaches an element's stamped dom to itself. By default,
       * this method creates a `shadowRoot` and adds the dom to it.
       * However, this method may be overridden to allow an element
       * to put its dom in another location.
       *
       * @throws {Error}
       * @suppress {missingReturn}
       * @param {StampedTemplate} dom to attach to the element.
       * @return {ShadowRoot} node to which the dom has been attached.
       */
      _attachDom(dom) {
        if (this.attachShadow) {
          if (dom) {
            if (!this.shadowRoot) {
              this.attachShadow({mode: 'open'});
            }
            this.shadowRoot.appendChild(dom);
            return this.shadowRoot;
          }
          return null;
        } else {
          throw new Error('ShadowDOM not available. ' +
            // TODO(sorvell): move to compile-time conditional when supported
          'Polymer.Element can create dom as children instead of in ' +
          'ShadowDOM by setting `this.root = this;\` before \`ready\`.');
        }
      }

      /**
       * When using the ShadyCSS scoping and custom property shim, causes all
       * shimmed styles in this element (and its subtree) to be updated
       * based on current custom property values.
       *
       * The optional parameter overrides inline custom property styles with an
       * object of properties where the keys are CSS properties, and the values
       * are strings.
       *
       * Example: `this.updateStyles({'--color': 'blue'})`
       *
       * These properties are retained unless a value of `null` is set.
       *
       * Note: This function does not support updating CSS mixins.
       * You can not dynamically change the value of an `@apply`.
       *
       * @param {Object=} properties Bag of custom property key/values to
       *   apply to this element.
       * @return {void}
       * @suppress {invalidCasts}
       */
      updateStyles(properties) {
        if (window.ShadyCSS) {
          window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */(this), properties);
        }
      }

      /**
       * Rewrites a given URL relative to a base URL. The base URL defaults to
       * the original location of the document containing the `dom-module` for
       * this element. This method will return the same URL before and after
       * bundling.
       *
       * Note that this function performs no resolution for URLs that start
       * with `/` (absolute URLs) or `#` (hash identifiers).  For general purpose
       * URL resolution, use `window.URL`.
       *
       * @param {string} url URL to resolve.
       * @param {string=} base Optional base URL to resolve against, defaults
       * to the element's `importPath`
       * @return {string} Rewritten URL relative to base
       */
      resolveUrl(url, base) {
        if (!base && this.importPath) {
          base = Polymer.ResolveUrl.resolveUrl(this.importPath);
        }
        return Polymer.ResolveUrl.resolveUrl(url, base);
      }

      /**
       * Overrides `PropertyAccessors` to add map of dynamic functions on
       * template info, for consumption by `PropertyEffects` template binding
       * code. This map determines which method templates should have accessors
       * created for them.
       *
       * @override
       * @suppress {missingProperties} Interfaces in closure do not inherit statics, but classes do
       */
      static _parseTemplateContent(template, templateInfo, nodeInfo) {
        templateInfo.dynamicFns = templateInfo.dynamicFns || this._properties;
        return super._parseTemplateContent(template, templateInfo, nodeInfo);
      }

    }

    return PolymerElement;
  });

  /**
   * When using the ShadyCSS scoping and custom property shim, causes all
   * shimmed `styles` (via `custom-style`) in the document (and its subtree)
   * to be updated based on current custom property values.
   *
   * The optional parameter overrides inline custom property styles with an
   * object of properties where the keys are CSS properties, and the values
   * are strings.
   *
   * Example: `Polymer.updateStyles({'--color': 'blue'})`
   *
   * These properties are retained unless a value of `null` is set.
   *
   * @param {Object=} props Bag of custom property key/values to
   *   apply to the document.
   * @return {void}
   */
  Polymer.updateStyles = function(props) {
    if (window.ShadyCSS) {
      window.ShadyCSS.styleDocument(props);
    }
  };

})();
</script>
<script>
  (function() {
    'use strict';

    /**
     * Class representing a static string value which can be used to filter
     * strings by asseting that they have been created via this class. The
     * `value` property returns the string passed to the constructor.
     */
    class LiteralString {
      constructor(string) {
        /** @type {string} */
        this.value = string.toString();
      }
      /**
       * @return {string} LiteralString string value
       */
      toString() {
        return this.value;
      }
    }

    /**
     * @param {*} value Object to stringify into HTML
     * @return {string} HTML stringified form of `obj`
     */
    function literalValue(value) {
      if (value instanceof LiteralString) {
        return /** @type {!LiteralString} */(value).value;
      } else {
        throw new Error(`non-literal value passed to Polymer.htmlLiteral: ${value}`);
      }
    }

    /**
     * @param {*} value Object to stringify into HTML
     * @return {string} HTML stringified form of `obj`
     */
    function htmlValue(value) {
      if (value instanceof HTMLTemplateElement) {
        return /** @type {!HTMLTemplateElement } */(value).innerHTML;
      } else if (value instanceof LiteralString) {
        return literalValue(value);
      } else {
        throw new Error(`non-template value passed to Polymer.html: ${value}`);
      }
    }

    /**
     * A template literal tag that creates an HTML <template> element from the
     * contents of the string.
     *
     * This allows you to write a Polymer Template in JavaScript.
     *
     * Templates can be composed by interpolating `HTMLTemplateElement`s in
     * expressions in the JavaScript template literal. The nested template's
     * `innerHTML` is included in the containing template.  The only other
     * values allowed in expressions are those returned from `Polymer.htmlLiteral`
     * which ensures only literal values from JS source ever reach the HTML, to
     * guard against XSS risks.
     *
     * All other values are disallowed in expressions to help prevent XSS
     * attacks; however, `Polymer.htmlLiteral` can be used to compose static
     * string values into templates. This is useful to compose strings into
     * places that do not accept html, like the css text of a `style`
     * element.
     *
     * Example:
     *
     *     static get template() {
     *       return Polymer.html`
     *         <style>:host{ content:"..." }</style>
     *         <div class="shadowed">${this.partialTemplate}</div>
     *         ${super.template}
     *       `;
     *     }
     *     static get partialTemplate() { return Polymer.html`<span>Partial!</span>`; }
     *
     * @memberof Polymer
     * @param {!ITemplateArray} strings Constant parts of tagged template literal
     * @param {...*} values Variable parts of tagged template literal
     * @return {!HTMLTemplateElement} Constructed HTMLTemplateElement
     */
    Polymer.html = function html(strings, ...values) {
      const template = /** @type {!HTMLTemplateElement} */(document.createElement('template'));
      template.innerHTML = values.reduce((acc, v, idx) =>
          acc + htmlValue(v) + strings[idx + 1], strings[0]);
      return template;
    };

    /**
     * An html literal tag that can be used with `Polymer.html` to compose.
     * a literal string.
     *
     * Example:
     *
     *     static get template() {
     *       return Polymer.html`
     *         <style>
     *           :host { display: block; }
     *           ${styleTemplate}
     *         </style>
     *         <div class="shadowed">${staticValue}</div>
     *         ${super.template}
     *       `;
     *     }
     *     static get styleTemplate() { return Polymer.htmlLiteral`.shadowed { background: gray; }`; }
     *
     * @memberof Polymer
     * @param {!ITemplateArray} strings Constant parts of tagged template literal
     * @param {...*} values Variable parts of tagged template literal
     * @return {!LiteralString} Constructed literal string
     */
    Polymer.htmlLiteral = function(strings, ...values) {
      return new LiteralString(values.reduce((acc, v, idx) =>
          acc + literalValue(v) + strings[idx + 1], strings[0]));
    };
  })();
</script>
<script>
(function() {
  'use strict';

  /**
   * Base class that provides the core API for Polymer's meta-programming
   * features including template stamping, data-binding, attribute deserialization,
   * and property change observation.
   *
   * @customElement
   * @memberof Polymer
   * @constructor
   * @implements {Polymer_ElementMixin}
   * @extends {HTMLElement}
   * @appliesMixin Polymer.ElementMixin
   * @summary Custom element base class that provides the core API for Polymer's
   *   key meta-programming features including template stamping, data-binding,
   *   attribute deserialization, and property change observation
   */
  Polymer.Element = Polymer.ElementMixin(HTMLElement);

  // NOTE: this is here for modulizer to export `html` for the module version of this file
  Polymer.html = Polymer.html;
})();
</script>
<dom-module id="epiviz-data-source" assetpath="bower_components/epiviz-data-source/">
  <template>
    <style>
      /* local DOM styles go here */
    </style>
  </template>
  <script>

    // Extend Polymer.Element base class
    class EpivizDataSource extends Polymer.Element {

      static get is() { return 'epiviz-data-source'; }

      static get properties() {
        return {

          /**
           * Epiviz Proivder Type.
           * 
           * Can be one of the possible values -
           * `epiviz.data.WebServerDataProvider` | `epiviz.data.WebSocketDataProvider`
           *
           * @type {string}
           */
          providerType: {
            type: String,
            notify: true
          },

          /**
           * Unique ID assigned to this datasource
           *
           * @type {string}
           */
          providerId: {
            type: String,
            notify: true
          },

          /**
           * APi endpoint/URL location
           *
           * @type {string}
           */
          providerUrl: {
            type: String,
            notify: true
            // readOnly: true
          },

          measurements: {
            type: Object,
            notify: true
          },

          measurementSet: {
            type: Object,
            notify: true
          },

          seqInfos: {
            type: Object,
            notify: true
          },

          // not necessary just for testing
          config: {
            type: Object,
            notify: true
          },

          dataManager: {
            type: Object,
            notify: true
          }
        }
      }

      // static get observers() {
      //   return [
      //     /* observer array just like 1.x */
      //   ]
      // }

      constructor() {
        super();
      }

      connectedCallback() {
        super.connectedCallback();
      }

      disconnectedCallback() {
        super.connectedCallback();
      }

      ready() {
        super.ready();
        var settingsMap = {

          configType: 'default',

          // Navigation settings
          zoominRatio: 0.8,
          zoomoutRatio: 1.2,
          navigationStepRatio: 0.2,
          navigationDelay: 0,

          // Data retrieval

          dataServerLocation: '', //TODO: Fill in (in site-settings.js)
          chartSaverLocation: 'src/chart_saving/save_svg.php',
          dataProviders: [], //TODO: Fill in (in site-settings.js)

          // This is the data provider that handles workspaces; it can be different from the one getting all the other data:
          workspacesDataProvider: sprintf('epiviz.data.EmptyResponseDataProvider', 'empty', ''), //TODO: Fill in (in site-settings.js)

          // For datasources with hierarchies, the cache must be disabled (Epiviz will crash otherwise)
          useCache: true,

          // Every n milliseconds, the cache will free up any data associated with parts of the genome not recently visited
          cacheUpdateIntervalMilliseconds: 30000,

          // For genes search box:
          maxSearchResults: 12,

          colorPalettes: [
            new epiviz.ui.charts.ColorPalette(
              ['#025167', '#e7003e', '#ffcd00', '#057d9f', '#970026', '#ffe373', '#ff8100'],
              'Epiviz v1.0 Colors', 'epiviz-v1'),
            new epiviz.ui.charts.ColorPalette(
              ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
              'Epiviz v2.0 Bright', 'epiviz-v2-bright'),
            new epiviz.ui.charts.ColorPalette(
              ['#b8d2eb', '#f2aeac', '#d8e4aa', '#cccccc', '#f2d1b0', '#d4b2d3', '#ddb8a9', '#ebbfd9'],
              'Epiviz v2.0 Light', 'epiviz-v2-light'),
            new epiviz.ui.charts.ColorPalette(
              ['#599ad3', '#f1595f', '#79c36a', '#727272', '#f9a65a', '#9e66ab', '#cd7058', '#d77fb3'],
              'Epiviz v2.0 Medium', 'epiviz-v2-medium'),
            new epiviz.ui.charts.ColorPalette(
              ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"],
              'D3 Category 10', 'd3-category10'),
            new epiviz.ui.charts.ColorPalette(
              ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"],
              'D3 Category 20', 'd3-category20'),
            new epiviz.ui.charts.ColorPalette(
              ["#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6"],
              'D3 Category 20b', 'd3-category20b'),
            new epiviz.ui.charts.ColorPalette(
              ["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9"],
              'D3 Category 20c', 'd3-category20c'),
            new epiviz.ui.charts.ColorPalette(
              ['#f9a65a', '#599ad3', '#79c36a', '#f1595f', '#727272', '#cd7058', '#d77fb3'],
              'Genes Default', 'genes-default'),
            new epiviz.ui.charts.ColorPalette(
              ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
              'Heatmap Default', 'heatmap-default')
          ],

          chartSettings: {
            default: {
              colors: 'd3-category10',
              decorations: [
                'epiviz.ui.charts.decoration.RemoveChartButton',
                'epiviz.ui.charts.decoration.SaveChartButton',
                'epiviz.ui.charts.decoration.CustomSettingsButton',
                'epiviz.ui.charts.decoration.EditCodeButton',

                'epiviz.ui.charts.decoration.ChartColorsButton',
                'epiviz.ui.charts.decoration.ChartLoaderAnimation',
                'epiviz.ui.charts.decoration.ChartResize'
              ]
            },

            plot: {
              width: 400,
              height: 400,
              margins: new epiviz.ui.charts.Margins(15, 30, 30, 15),
              decorations: [
                'epiviz.ui.charts.decoration.ToggleTooltipButton',

                'epiviz.ui.charts.decoration.ChartTooltip',
                'epiviz.ui.charts.decoration.ChartFilterCodeButton'
              ]
            },

            track: {
              width: '100%',
              height: 90,
              margins: new epiviz.ui.charts.Margins(25, 20, 23, 10),
              decorations: [
                'epiviz.ui.charts.decoration.ToggleTooltipButton',

                'epiviz.ui.charts.decoration.ChartTooltip',
                'epiviz.ui.charts.decoration.ChartFilterCodeButton'
              ]
            },

            'data-structure': {
              width: 800,
              height: 300,
              margins: new epiviz.ui.charts.Margins(20, 10, 10, 10),
              colors: 'd3-category20',
              decorations: [
                'epiviz.ui.charts.tree.decoration.TogglePropagateSelectionButton',
                'epiviz.ui.charts.decoration.HierarchyFilterCodeButton'
              ]
            },

            'epiviz.plugins.charts.GenesTrack': {
              height: 120,
              colors: 'genes-default'
            },

            'epiviz.plugins.charts.LineTrack': {
              colors: 'epiviz-v2-bright',
              decorations: [
                'epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton',
                'epiviz.ui.charts.decoration.ChartColorByMeasurementsCodeButton'
              ]
            },

            'epiviz.plugins.charts.StackedLineTrack': {
              height: 300
            },

            'epiviz.plugins.charts.ScatterPlot': {
              margins: new epiviz.ui.charts.Margins(15, 50, 50, 15),
              decorations: [
                'epiviz.ui.charts.decoration.ChartColorByRowCodeButton'
              ]
            },

            'epiviz.plugins.charts.HeatmapPlot': {
              width: 800,
              height: 400,
              margins: new epiviz.ui.charts.Margins(80, 120, 40, 40),
              decorations: [
                'epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton',
                'epiviz.ui.charts.decoration.ChartOrderByMeasurementsCodeButton',
                'epiviz.ui.charts.decoration.ChartColorByRowCodeButton'
              ],
              colors: 'heatmap-default'
            },

            'epiviz.plugins.charts.LinePlot': {
              width: 800,
              height: 400,
              margins: new epiviz.ui.charts.Margins(30, 30, 50, 15),
              decorations: [
                'epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton',
                'epiviz.ui.charts.decoration.ChartColorByRowCodeButton',
                'epiviz.ui.charts.decoration.ChartOrderByMeasurementsCodeButton'
              ],
              colors: 'd3-category20b'
            },

            'epiviz.plugins.charts.StackedLinePlot': {
              width: 800,
              height: 400,
              margins: new epiviz.ui.charts.Margins(30, 30, 50, 15),
              decorations: [
                'epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton',
                'epiviz.ui.charts.decoration.ChartColorByRowCodeButton',
                'epiviz.ui.charts.decoration.ChartOrderByMeasurementsCodeButton'
              ],
              colors: 'd3-category20b'
            }
          },

          chartCustomSettings: {
            'epiviz.plugins.charts.BlocksTrack': {
              minBlockDistance: 3,
              useColorBy: false,
              blockColorBy: 'label'
            },
            'epiviz.plugins.charts.GenesTrack': {

            },
            'epiviz.plugins.charts.LineTrack': {
              step: 1,
              showPoints: false,
              showLines: true,
              pointRadius: 1,
              lineThickness: 2
            },
            'epiviz.plugins.charts.ScatterPlot': {
              circleRadiusRatio: 0.01
            },
            'epiviz.plugins.charts.HeatmapPlot': {
              colLabel: 'label',
              maxColumns: 120,
              clusteringAlg: 'agglomerative'
            },
            'epiviz.plugins.charts.StackedLinePlot': {
              colLabel: 'label'
            }
          },
        };

        epiviz.Config.SETTINGS.dataProviders = [[this.providerType, this.providerId, this.providerUrl]];

        var config = new epiviz.Config(settingsMap);

        config.dataProviders = [];
        config.dataProviders.push([this.providerType, this.providerId, this.providerUrl]);

        /** @type {epiviz.data.DataProviderFactory} */
        var dataProviderFactory = new epiviz.data.DataProviderFactory(config);

        /** @type {epiviz.data.DataManager} */
        var dataManager = new epiviz.data.DataManager(config, dataProviderFactory);

        this.config = config;
        this.dataProviderFactory = dataProviderFactory;
        this.dataManager = dataManager;

        var self = this;

        this.dataManager.getMeasurements(function (result) {
          self.measurements = result.raw();
          self.measurementSet = result;
        });

        this.dataManager.getSeqInfos(function (result) {
          self.seqInfos = result;
        });
      }

    }

    customElements.define(EpivizDataSource.is, EpivizDataSource);
  </script>
</dom-module><script>(function(){/*

Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
'use strict';var l=!(window.ShadyDOM&&window.ShadyDOM.inUse),p;function r(a){p=a&&a.shimcssproperties?!1:l||!(navigator.userAgent.match(/AppleWebKit\/601|Edge\/15/)||!window.CSS||!CSS.supports||!CSS.supports("box-shadow","0 0 0 var(--foo)"))}var t;window.ShadyCSS&&void 0!==window.ShadyCSS.cssBuild&&(t=window.ShadyCSS.cssBuild);var aa=!(!window.ShadyCSS||!window.ShadyCSS.disableRuntime);
window.ShadyCSS&&void 0!==window.ShadyCSS.nativeCss?p=window.ShadyCSS.nativeCss:window.ShadyCSS?(r(window.ShadyCSS),window.ShadyCSS=void 0):r(window.WebComponents&&window.WebComponents.flags);var u=p,v=t;function w(){this.end=this.start=0;this.rules=this.parent=this.previous=null;this.cssText=this.parsedCssText="";this.atRule=!1;this.type=0;this.parsedSelector=this.selector=this.keyframesName=""}
function x(a){a=a.replace(ba,"").replace(ca,"");var b=y,c=a,e=new w;e.start=0;e.end=c.length;for(var d=e,f=0,g=c.length;f<g;f++)if("{"===c[f]){d.rules||(d.rules=[]);var h=d,k=h.rules[h.rules.length-1]||null;d=new w;d.start=f+1;d.parent=h;d.previous=k;h.rules.push(d)}else"}"===c[f]&&(d.end=f+1,d=d.parent||e);return b(e,a)}
function y(a,b){var c=b.substring(a.start,a.end-1);a.parsedCssText=a.cssText=c.trim();a.parent&&(c=b.substring(a.previous?a.previous.end:a.parent.start,a.start-1),c=da(c),c=c.replace(z," "),c=c.substring(c.lastIndexOf(";")+1),c=a.parsedSelector=a.selector=c.trim(),a.atRule=0===c.indexOf("@"),a.atRule?0===c.indexOf("@media")?a.type=A:c.match(ea)&&(a.type=B,a.keyframesName=a.selector.split(z).pop()):a.type=0===c.indexOf("--")?C:D);if(c=a.rules)for(var e=0,d=c.length,f=void 0;e<d&&(f=c[e]);e++)y(f,b);
return a}function da(a){return a.replace(/\\([0-9a-f]{1,6})\s/gi,function(a,c){a=c;for(c=6-a.length;c--;)a="0"+a;return"\\"+a})}
function E(a,b,c){c=void 0===c?"":c;var e="";if(a.cssText||a.rules){var d=a.rules,f;if(f=d)f=d[0],f=!(f&&f.selector&&0===f.selector.indexOf("--"));if(f){f=0;for(var g=d.length,h=void 0;f<g&&(h=d[f]);f++)e=E(h,b,e)}else b?b=a.cssText:(b=a.cssText,b=b.replace(fa,"").replace(ha,""),b=b.replace(ia,"").replace(ja,"")),(e=b.trim())&&(e="  "+e+"\n")}e&&(a.selector&&(c+=a.selector+" {\n"),c+=e,a.selector&&(c+="}\n\n"));return c}
var D=1,B=7,A=4,C=1E3,ba=/\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,ca=/@import[^;]*;/gim,fa=/(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?(?:[;\n]|$)/gim,ha=/(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?{[^}]*?}(?:[;\n]|$)?/gim,ia=/@apply\s*\(?[^);]*\)?\s*(?:[;\n]|$)?/gim,ja=/[^;:]*?:[^;]*?var\([^;]*\)(?:[;\n]|$)?/gim,ea=/^@[^\s]*keyframes/,z=/\s+/g;var G=/(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};{])+)|\{([^}]*)\}(?:(?=[;\s}])|$))/gi,H=/(?:^|\W+)@apply\s*\(?([^);\n]*)\)?/gi,ka=/@media\s(.*)/;var I=new Set;function J(a){if(!a)return"";"string"===typeof a&&(a=x(a));return E(a,u)}function K(a){!a.__cssRules&&a.textContent&&(a.__cssRules=x(a.textContent));return a.__cssRules||null}function L(a,b,c,e){if(a){var d=!1,f=a.type;if(e&&f===A){var g=a.selector.match(ka);g&&(window.matchMedia(g[1]).matches||(d=!0))}f===D?b(a):c&&f===B?c(a):f===C&&(d=!0);if((a=a.rules)&&!d)for(d=0,f=a.length,g=void 0;d<f&&(g=a[d]);d++)L(g,b,c,e)}}
function M(a,b){var c=a.indexOf("var(");if(-1===c)return b(a,"","","");a:{var e=0;var d=c+3;for(var f=a.length;d<f;d++)if("("===a[d])e++;else if(")"===a[d]&&0===--e)break a;d=-1}e=a.substring(c+4,d);c=a.substring(0,c);a=M(a.substring(d+1),b);d=e.indexOf(",");return-1===d?b(c,e.trim(),"",a):b(c,e.substring(0,d).trim(),e.substring(d+1).trim(),a)}
function N(a){if(void 0!==v)return v;if(void 0===a.__cssBuild){var b=a.getAttribute("css-build");if(b)a.__cssBuild=b;else{a:{b="template"===a.localName?a.content.firstChild:a.firstChild;if(b instanceof Comment&&(b=b.textContent.trim().split(":"),"css-build"===b[0])){b=b[1];break a}b=""}if(""!==b){var c="template"===a.localName?a.content.firstChild:a.firstChild;c.parentNode.removeChild(c)}a.__cssBuild=b}}return a.__cssBuild||""};var la=/;\s*/m,ma=/^\s*(initial)|(inherit)\s*$/,O=/\s*!important/;function P(){this.a={}}P.prototype.set=function(a,b){a=a.trim();this.a[a]={h:b,i:{}}};P.prototype.get=function(a){a=a.trim();return this.a[a]||null};var Q=null;function R(){this.b=this.c=null;this.a=new P}R.prototype.o=function(a){a=H.test(a)||G.test(a);H.lastIndex=0;G.lastIndex=0;return a};
R.prototype.m=function(a,b){if(void 0===a._gatheredStyle){var c=[];for(var e=a.content.querySelectorAll("style"),d=0;d<e.length;d++){var f=e[d];if(f.hasAttribute("shady-unscoped")){if(!l){var g=f.textContent;I.has(g)||(I.add(g),g=f.cloneNode(!0),document.head.appendChild(g));f.parentNode.removeChild(f)}}else c.push(f.textContent),f.parentNode.removeChild(f)}(c=c.join("").trim())?(e=document.createElement("style"),e.textContent=c,a.content.insertBefore(e,a.content.firstChild),c=e):c=null;a._gatheredStyle=
c}return(a=a._gatheredStyle)?this.j(a,b):null};R.prototype.j=function(a,b){b=void 0===b?"":b;var c=K(a);this.l(c,b);a.textContent=J(c);return c};R.prototype.f=function(a){var b=this,c=K(a);L(c,function(a){":root"===a.selector&&(a.selector="html");b.g(a)});a.textContent=J(c);return c};R.prototype.l=function(a,b){var c=this;this.c=b;L(a,function(a){c.g(a)});this.c=null};R.prototype.g=function(a){a.cssText=na(this,a.parsedCssText,a);":root"===a.selector&&(a.selector=":host > *")};
function na(a,b,c){b=b.replace(G,function(b,d,f,g){return oa(a,b,d,f,g,c)});return S(a,b,c)}function pa(a,b){for(var c=b;c.parent;)c=c.parent;var e={},d=!1;L(c,function(c){(d=d||c===b)||c.selector===b.selector&&Object.assign(e,T(a,c.parsedCssText))});return e}
function S(a,b,c){for(var e;e=H.exec(b);){var d=e[0],f=e[1];e=e.index;var g=b.slice(0,e+d.indexOf("@apply"));b=b.slice(e+d.length);var h=c?pa(a,c):{};Object.assign(h,T(a,g));d=void 0;var k=a;f=f.replace(la,"");var n=[];var m=k.a.get(f);m||(k.a.set(f,{}),m=k.a.get(f));if(m){k.c&&(m.i[k.c]=!0);var q=m.h;for(d in q)k=h&&h[d],m=[d,": var(",f,"_-_",d],k&&m.push(",",k.replace(O,"")),m.push(")"),O.test(q[d])&&m.push(" !important"),n.push(m.join(""))}d=n.join("; ");b=g+d+b;H.lastIndex=e+d.length}return b}
function T(a,b,c){c=void 0===c?!1:c;b=b.split(";");for(var e,d,f={},g=0,h;g<b.length;g++)if(e=b[g])if(h=e.split(":"),1<h.length){e=h[0].trim();d=h.slice(1).join(":");if(c){var k=a;h=e;var n=ma.exec(d);n&&(n[1]?(k.b||(k.b=document.createElement("meta"),k.b.setAttribute("apply-shim-measure",""),k.b.style.all="initial",document.head.appendChild(k.b)),h=window.getComputedStyle(k.b).getPropertyValue(h)):h="apply-shim-inherit",d=h)}f[e]=d}return f}function qa(a,b){if(Q)for(var c in b.i)c!==a.c&&Q(c)}
function oa(a,b,c,e,d,f){e&&M(e,function(b,c){c&&a.a.get(c)&&(d="@apply "+c+";")});if(!d)return b;var g=S(a,""+d,f);f=b.slice(0,b.indexOf("--"));var h=g=T(a,g,!0),k=a.a.get(c),n=k&&k.h;n?h=Object.assign(Object.create(n),g):a.a.set(c,h);var m=[],q,Z=!1;for(q in h){var F=g[q];void 0===F&&(F="initial");!n||q in n||(Z=!0);m.push(c+"_-_"+q+": "+F)}Z&&qa(a,k);k&&(k.h=h);e&&(f=b+";"+f);return f+m.join("; ")+";"}R.prototype.detectMixin=R.prototype.o;R.prototype.transformStyle=R.prototype.j;
R.prototype.transformCustomStyle=R.prototype.f;R.prototype.transformRules=R.prototype.l;R.prototype.transformRule=R.prototype.g;R.prototype.transformTemplate=R.prototype.m;R.prototype._separator="_-_";Object.defineProperty(R.prototype,"invalidCallback",{get:function(){return Q},set:function(a){Q=a}});var U={};var ra=Promise.resolve();function sa(a){if(a=U[a])a._applyShimCurrentVersion=a._applyShimCurrentVersion||0,a._applyShimValidatingVersion=a._applyShimValidatingVersion||0,a._applyShimNextVersion=(a._applyShimNextVersion||0)+1}function ta(a){return a._applyShimCurrentVersion===a._applyShimNextVersion}function ua(a){a._applyShimValidatingVersion=a._applyShimNextVersion;a._validating||(a._validating=!0,ra.then(function(){a._applyShimCurrentVersion=a._applyShimNextVersion;a._validating=!1}))};var V=new R;function W(){this.a=null;V.invalidCallback=sa}function X(a){!a.a&&window.ShadyCSS.CustomStyleInterface&&(a.a=window.ShadyCSS.CustomStyleInterface,a.a.transformCallback=function(a){V.f(a)},a.a.validateCallback=function(){requestAnimationFrame(function(){a.a.enqueued&&a.flushCustomStyles()})})}W.prototype.prepareTemplate=function(a,b){X(this);""===N(a)&&(U[b]=a,b=V.m(a,b),a._styleAst=b)};
W.prototype.flushCustomStyles=function(){X(this);if(this.a){var a=this.a.processStyles();if(this.a.enqueued){for(var b=0;b<a.length;b++){var c=this.a.getStyleForCustomStyle(a[b]);c&&V.f(c)}this.a.enqueued=!1}}};
W.prototype.styleSubtree=function(a,b){X(this);if(b)for(var c in b)null===c?a.style.removeProperty(c):a.style.setProperty(c,b[c]);if(a.shadowRoot)for(this.styleElement(a),a=a.shadowRoot.children||a.shadowRoot.childNodes,b=0;b<a.length;b++)this.styleSubtree(a[b]);else for(a=a.children||a.childNodes,b=0;b<a.length;b++)this.styleSubtree(a[b])};
W.prototype.styleElement=function(a){X(this);var b=a.localName,c;b?-1<b.indexOf("-")?c=b:c=a.getAttribute&&a.getAttribute("is")||"":c=a.is;b=U[c];if(!(b&&""!==N(b)||!b||ta(b))){if(ta(b)||b._applyShimValidatingVersion!==b._applyShimNextVersion)this.prepareTemplate(b,c),ua(b);if(a=a.shadowRoot)if(a=a.querySelector("style"))a.__cssRules=b._styleAst,a.textContent=J(b._styleAst)}};W.prototype.styleDocument=function(a){X(this);this.styleSubtree(document.body,a)};
if(!window.ShadyCSS||!window.ShadyCSS.ScopingShim){var Y=new W,va=window.ShadyCSS&&window.ShadyCSS.CustomStyleInterface;window.ShadyCSS={prepareTemplate:function(a,b){Y.flushCustomStyles();Y.prepareTemplate(a,b)},prepareTemplateStyles:function(a,b,c){window.ShadyCSS.prepareTemplate(a,b,c)},prepareTemplateDom:function(){},styleSubtree:function(a,b){Y.flushCustomStyles();Y.styleSubtree(a,b)},styleElement:function(a){Y.flushCustomStyles();Y.styleElement(a)},styleDocument:function(a){Y.flushCustomStyles();
Y.styleDocument(a)},getComputedStyleValue:function(a,b){return(a=window.getComputedStyle(a).getPropertyValue(b))?a.trim():""},flushCustomStyles:function(){Y.flushCustomStyles()},nativeCss:u,nativeShadow:l,cssBuild:v,disableRuntime:aa};va&&(window.ShadyCSS.CustomStyleInterface=va)}window.ShadyCSS.ApplyShim=V;}).call(this);

//# sourceMappingURL=apply-shim.min.js.map
</script>
<script>
(function() {
  'use strict';

  /**
   * @summary Collapse multiple callbacks into one invocation after a timer.
   * @memberof Polymer
   */
  class Debouncer {
    constructor() {
      this._asyncModule = null;
      this._callback = null;
      this._timer = null;
    }
    /**
     * Sets the scheduler; that is, a module with the Async interface,
     * a callback and optional arguments to be passed to the run function
     * from the async module.
     *
     * @param {!AsyncInterface} asyncModule Object with Async interface.
     * @param {function()} callback Callback to run.
     * @return {void}
     */
    setConfig(asyncModule, callback) {
      this._asyncModule = asyncModule;
      this._callback = callback;
      this._timer = this._asyncModule.run(() => {
        this._timer = null;
        debouncerQueue.delete(this);
        this._callback();
      });
    }
    /**
     * Cancels an active debouncer and returns a reference to itself.
     *
     * @return {void}
     */
    cancel() {
      if (this.isActive()) {
        this._cancelAsync();
        // Canceling a debouncer removes its spot from the flush queue,
        // so if a debouncer is manually canceled and re-debounced, it
        // will reset its flush order (this is a very minor difference from 1.x)
        // Re-debouncing via the `debounce` API retains the 1.x FIFO flush order
        debouncerQueue.delete(this);
      }
    }
    /**
     * Cancels a debouncer's async callback.
     *
     * @return {void}
     */
    _cancelAsync() {
      if (this.isActive()) {
        this._asyncModule.cancel(/** @type {number} */(this._timer));
        this._timer = null;
      }
    }
    /**
     * Flushes an active debouncer and returns a reference to itself.
     *
     * @return {void}
     */
    flush() {
      if (this.isActive()) {
        this.cancel();
        this._callback();
      }
    }
    /**
     * Returns true if the debouncer is active.
     *
     * @return {boolean} True if active.
     */
    isActive() {
      return this._timer != null;
    }
    /**
     * Creates a debouncer if no debouncer is passed as a parameter
     * or it cancels an active debouncer otherwise. The following
     * example shows how a debouncer can be called multiple times within a
     * microtask and "debounced" such that the provided callback function is
     * called once. Add this method to a custom element:
     *
     * _debounceWork() {
     *   this._debounceJob = Polymer.Debouncer.debounce(this._debounceJob,
     *       Polymer.Async.microTask, () => {
     *     this._doWork();
     *   });
     * }
     *
     * If the `_debounceWork` method is called multiple times within the same
     * microtask, the `_doWork` function will be called only once at the next
     * microtask checkpoint.
     *
     * Note: In testing it is often convenient to avoid asynchrony. To accomplish
     * this with a debouncer, you can use `Polymer.enqueueDebouncer` and
     * `Polymer.flush`. For example, extend the above example by adding
     * `Polymer.enqueueDebouncer(this._debounceJob)` at the end of the
     * `_debounceWork` method. Then in a test, call `Polymer.flush` to ensure
     * the debouncer has completed.
     *
     * @param {Debouncer?} debouncer Debouncer object.
     * @param {!AsyncInterface} asyncModule Object with Async interface
     * @param {function()} callback Callback to run.
     * @return {!Debouncer} Returns a debouncer object.
     */
    static debounce(debouncer, asyncModule, callback) {
      if (debouncer instanceof Debouncer) {
        // Cancel the async callback, but leave in debouncerQueue if it was
        // enqueued, to maintain 1.x flush order
        debouncer._cancelAsync();
      } else {
        debouncer = new Debouncer();
      }
      debouncer.setConfig(asyncModule, callback);
      return debouncer;
    }
  }

  /** @const */
  Polymer.Debouncer = Debouncer;

  let debouncerQueue = new Set();
  
  /**
   * Adds a `Debouncer` to a list of globally flushable tasks.
   *
   * @memberof Polymer
   * @param {!Debouncer} debouncer Debouncer to enqueue
   * @return {void}
   */
  Polymer.enqueueDebouncer = function(debouncer) {
    debouncerQueue.add(debouncer);
  };
  
  Polymer.flushDebouncers = function() {
    const didFlush = Boolean(debouncerQueue.size);
    // If new debouncers are added while flushing, Set.forEach will ensure
    // newly added ones are also flushed
    debouncerQueue.forEach(debouncer => {
      try {
        debouncer.flush();
      } catch(e) {
        setTimeout(() => {
          throw e;
        });
      }
    });
    return didFlush;
  };

})();
</script>
<script>
(function() {

  'use strict';

  // detect native touch action support
  let HAS_NATIVE_TA = typeof document.head.style.touchAction === 'string';
  let GESTURE_KEY = '__polymerGestures';
  let HANDLED_OBJ = '__polymerGesturesHandled';
  let TOUCH_ACTION = '__polymerGesturesTouchAction';
  // radius for tap and track
  let TAP_DISTANCE = 25;
  let TRACK_DISTANCE = 5;
  // number of last N track positions to keep
  let TRACK_LENGTH = 2;

  // Disabling "mouse" handlers for 2500ms is enough
  let MOUSE_TIMEOUT = 2500;
  let MOUSE_EVENTS = ['mousedown', 'mousemove', 'mouseup', 'click'];
  // an array of bitmask values for mapping MouseEvent.which to MouseEvent.buttons
  let MOUSE_WHICH_TO_BUTTONS = [0, 1, 4, 2];
  let MOUSE_HAS_BUTTONS = (function() {
    try {
      return new MouseEvent('test', {buttons: 1}).buttons === 1;
    } catch (e) {
      return false;
    }
  })();

  /**
   * @param {string} name Possible mouse event name
   * @return {boolean} true if mouse event, false if not
   */
  function isMouseEvent(name) {
    return MOUSE_EVENTS.indexOf(name) > -1;
  }

  /* eslint no-empty: ["error", { "allowEmptyCatch": true }] */
  // check for passive event listeners
  let SUPPORTS_PASSIVE = false;
  (function() {
    try {
      let opts = Object.defineProperty({}, 'passive', {get() {SUPPORTS_PASSIVE = true;}});
      window.addEventListener('test', null, opts);
      window.removeEventListener('test', null, opts);
    } catch(e) {}
  })();

  /**
   * Generate settings for event listeners, dependant on `Polymer.passiveTouchGestures`
   *
   * @param {string} eventName Event name to determine if `{passive}` option is needed
   * @return {{passive: boolean} | undefined} Options to use for addEventListener and removeEventListener
   */
  function PASSIVE_TOUCH(eventName) {
    if (isMouseEvent(eventName) || eventName === 'touchend') {
      return;
    }
    if (HAS_NATIVE_TA && SUPPORTS_PASSIVE && Polymer.passiveTouchGestures) {
      return {passive: true};
    } else {
      return;
    }
  }

  // Check for touch-only devices
  let IS_TOUCH_ONLY = navigator.userAgent.match(/iP(?:[oa]d|hone)|Android/);

  let GestureRecognizer = function(){}; // eslint-disable-line no-unused-vars
  /** @type {function(): void} */
  GestureRecognizer.prototype.reset;
  /** @type {function(MouseEvent): void | undefined} */
  GestureRecognizer.prototype.mousedown;
  /** @type {(function(MouseEvent): void | undefined)} */
  GestureRecognizer.prototype.mousemove;
  /** @type {(function(MouseEvent): void | undefined)} */
  GestureRecognizer.prototype.mouseup;
  /** @type {(function(TouchEvent): void | undefined)} */
  GestureRecognizer.prototype.touchstart;
  /** @type {(function(TouchEvent): void | undefined)} */
  GestureRecognizer.prototype.touchmove;
  /** @type {(function(TouchEvent): void | undefined)} */
  GestureRecognizer.prototype.touchend;
  /** @type {(function(MouseEvent): void | undefined)} */
  GestureRecognizer.prototype.click;

  // keep track of any labels hit by the mouseCanceller
  /** @type {!Array<!HTMLLabelElement>} */
  const clickedLabels = [];

  /** @type {!Object<boolean>} */
  const labellable = {
    'button': true,
    'input': true,
    'keygen': true,
    'meter': true,
    'output': true,
    'textarea': true,
    'progress': true,
    'select': true
  };

  // Defined at https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#enabling-and-disabling-form-controls:-the-disabled-attribute
  /** @type {!Object<boolean>} */
  const canBeDisabled = {
    'button': true,
    'command': true,
    'fieldset': true,
    'input': true,
    'keygen': true,
    'optgroup': true,
    'option': true,
    'select': true,
    'textarea': true
  };

  /**
   * @param {HTMLElement} el Element to check labelling status
   * @return {boolean} element can have labels
   */
  function canBeLabelled(el) {
    return labellable[el.localName] || false;
  }

  /**
   * @param {HTMLElement} el Element that may be labelled.
   * @return {!Array<!HTMLLabelElement>} Relevant label for `el`
   */
  function matchingLabels(el) {
    let labels = Array.from(/** @type {HTMLInputElement} */(el).labels || []);
    // IE doesn't have `labels` and Safari doesn't populate `labels`
    // if element is in a shadowroot.
    // In this instance, finding the non-ancestor labels is enough,
    // as the mouseCancellor code will handle ancstor labels
    if (!labels.length) {
      labels = [];
      let root = el.getRootNode();
      // if there is an id on `el`, check for all labels with a matching `for` attribute
      if (el.id) {
        let matching = root.querySelectorAll(`label[for = ${el.id}]`);
        for (let i = 0; i < matching.length; i++) {
          labels.push(/** @type {!HTMLLabelElement} */(matching[i]));
        }
      }
    }
    return labels;
  }

  // touch will make synthetic mouse events
  // `preventDefault` on touchend will cancel them,
  // but this breaks `<input>` focus and link clicks
  // disable mouse handlers for MOUSE_TIMEOUT ms after
  // a touchend to ignore synthetic mouse events
  let mouseCanceller = function(mouseEvent) {
    // Check for sourceCapabilities, used to distinguish synthetic events
    // if mouseEvent did not come from a device that fires touch events,
    // it was made by a real mouse and should be counted
    // http://wicg.github.io/InputDeviceCapabilities/#dom-inputdevicecapabilities-firestouchevents
    let sc = mouseEvent.sourceCapabilities;
    if (sc && !sc.firesTouchEvents) {
      return;
    }
    // skip synthetic mouse events
    mouseEvent[HANDLED_OBJ] = {skip: true};
    // disable "ghost clicks"
    if (mouseEvent.type === 'click') {
      let clickFromLabel = false;
      let path = mouseEvent.composedPath && mouseEvent.composedPath();
      if (path) {
        for (let i = 0; i < path.length; i++) {
          if (path[i].nodeType === Node.ELEMENT_NODE) {
            if (path[i].localName === 'label') {
              clickedLabels.push(path[i]);
            } else if (canBeLabelled(path[i])) {
              let ownerLabels = matchingLabels(path[i]);
              // check if one of the clicked labels is labelling this element
              for (let j = 0; j < ownerLabels.length; j++) {
                clickFromLabel = clickFromLabel || clickedLabels.indexOf(ownerLabels[j]) > -1;
              }
            }
          }
          if (path[i] === POINTERSTATE.mouse.target) {
            return;
          }
        }
      }
      // if one of the clicked labels was labelling the target element,
      // this is not a ghost click
      if (clickFromLabel) {
        return;
      }
      mouseEvent.preventDefault();
      mouseEvent.stopPropagation();
    }
  };

  /**
   * @param {boolean=} setup True to add, false to remove.
   * @return {void}
   */
  function setupTeardownMouseCanceller(setup) {
    let events = IS_TOUCH_ONLY ? ['click'] : MOUSE_EVENTS;
    for (let i = 0, en; i < events.length; i++) {
      en = events[i];
      if (setup) {
        // reset clickLabels array
        clickedLabels.length = 0;
        document.addEventListener(en, mouseCanceller, true);
      } else {
        document.removeEventListener(en, mouseCanceller, true);
      }
    }
  }

  function ignoreMouse(e) {
    if (!Polymer.cancelSyntheticClickEvents) {
      return;
    }
    if (!POINTERSTATE.mouse.mouseIgnoreJob) {
      setupTeardownMouseCanceller(true);
    }
    let unset = function() {
      setupTeardownMouseCanceller();
      POINTERSTATE.mouse.target = null;
      POINTERSTATE.mouse.mouseIgnoreJob = null;
    };
    POINTERSTATE.mouse.target = e.composedPath()[0];
    POINTERSTATE.mouse.mouseIgnoreJob = Polymer.Debouncer.debounce(
          POINTERSTATE.mouse.mouseIgnoreJob
        , Polymer.Async.timeOut.after(MOUSE_TIMEOUT)
        , unset);
  }

  /**
   * @param {MouseEvent} ev event to test for left mouse button down
   * @return {boolean} has left mouse button down
   */
  function hasLeftMouseButton(ev) {
    let type = ev.type;
    // exit early if the event is not a mouse event
    if (!isMouseEvent(type)) {
      return false;
    }
    // ev.button is not reliable for mousemove (0 is overloaded as both left button and no buttons)
    // instead we use ev.buttons (bitmask of buttons) or fall back to ev.which (deprecated, 0 for no buttons, 1 for left button)
    if (type === 'mousemove') {
      // allow undefined for testing events
      let buttons = ev.buttons === undefined ? 1 : ev.buttons;
      if ((ev instanceof window.MouseEvent) && !MOUSE_HAS_BUTTONS) {
        buttons = MOUSE_WHICH_TO_BUTTONS[ev.which] || 0;
      }
      // buttons is a bitmask, check that the left button bit is set (1)
      return Boolean(buttons & 1);
    } else {
      // allow undefined for testing events
      let button = ev.button === undefined ? 0 : ev.button;
      // ev.button is 0 in mousedown/mouseup/click for left button activation
      return button === 0;
    }
  }

  function isSyntheticClick(ev) {
    if (ev.type === 'click') {
      // ev.detail is 0 for HTMLElement.click in most browsers
      if (ev.detail === 0) {
        return true;
      }
      // in the worst case, check that the x/y position of the click is within
      // the bounding box of the target of the event
      // Thanks IE 10 >:(
      let t = Gestures._findOriginalTarget(ev);
      // make sure the target of the event is an element so we can use getBoundingClientRect,
      // if not, just assume it is a synthetic click
      if (!t.nodeType || /** @type {Element} */(t).nodeType !== Node.ELEMENT_NODE) {
        return true;
      }
      let bcr = /** @type {Element} */(t).getBoundingClientRect();
      // use page x/y to account for scrolling
      let x = ev.pageX, y = ev.pageY;
      // ev is a synthetic click if the position is outside the bounding box of the target
      return !((x >= bcr.left && x <= bcr.right) && (y >= bcr.top && y <= bcr.bottom));
    }
    return false;
  }

  let POINTERSTATE = {
    mouse: {
      target: null,
      mouseIgnoreJob: null
    },
    touch: {
      x: 0,
      y: 0,
      id: -1,
      scrollDecided: false
    }
  };

  function firstTouchAction(ev) {
    let ta = 'auto';
    let path = ev.composedPath && ev.composedPath();
    if (path) {
      for (let i = 0, n; i < path.length; i++) {
        n = path[i];
        if (n[TOUCH_ACTION]) {
          ta = n[TOUCH_ACTION];
          break;
        }
      }
    }
    return ta;
  }

  function trackDocument(stateObj, movefn, upfn) {
    stateObj.movefn = movefn;
    stateObj.upfn = upfn;
    document.addEventListener('mousemove', movefn);
    document.addEventListener('mouseup', upfn);
  }

  function untrackDocument(stateObj) {
    document.removeEventListener('mousemove', stateObj.movefn);
    document.removeEventListener('mouseup', stateObj.upfn);
    stateObj.movefn = null;
    stateObj.upfn = null;
  }

  if (Polymer.cancelSyntheticClickEvents) {
    // use a document-wide touchend listener to start the ghost-click prevention mechanism
    // Use passive event listeners, if supported, to not affect scrolling performance
    document.addEventListener('touchend', ignoreMouse, SUPPORTS_PASSIVE ? {passive: true} : false);
  }

  /**
   * Module for adding listeners to a node for the following normalized
   * cross-platform "gesture" events:
   * - `down` - mouse or touch went down
   * - `up` - mouse or touch went up
   * - `tap` - mouse click or finger tap
   * - `track` - mouse drag or touch move
   *
   * @namespace
   * @memberof Polymer
   * @summary Module for adding cross-platform gesture event listeners.
   */
  const Gestures = {
    gestures: {},
    recognizers: [],

    /**
     * Finds the element rendered on the screen at the provided coordinates.
     *
     * Similar to `document.elementFromPoint`, but pierces through
     * shadow roots.
     *
     * @memberof Polymer.Gestures
     * @param {number} x Horizontal pixel coordinate
     * @param {number} y Vertical pixel coordinate
     * @return {Element} Returns the deepest shadowRoot inclusive element
     * found at the screen position given.
     */
    deepTargetFind: function(x, y) {
      let node = document.elementFromPoint(x, y);
      let next = node;
      // this code path is only taken when native ShadowDOM is used
      // if there is a shadowroot, it may have a node at x/y
      // if there is not a shadowroot, exit the loop
      while (next && next.shadowRoot && !window.ShadyDOM) {
        // if there is a node at x/y in the shadowroot, look deeper
        let oldNext = next;
        next = next.shadowRoot.elementFromPoint(x, y);
        // on Safari, elementFromPoint may return the shadowRoot host
        if (oldNext === next) {
          break;
        }
        if (next) {
          node = next;
        }
      }
      return node;
    },
    /**
     * a cheaper check than ev.composedPath()[0];
     *
     * @private
     * @param {Event} ev Event.
     * @return {EventTarget} Returns the event target.
     */
    _findOriginalTarget: function(ev) {
      // shadowdom
      if (ev.composedPath) {
        const targets = /** @type {!Array<!EventTarget>} */(ev.composedPath());
        // It shouldn't be, but sometimes targets is empty (window on Safari).
        return targets.length > 0 ? targets[0] : ev.target;
      }
      // shadydom
      return ev.target;
    },

    /**
     * @private
     * @param {Event} ev Event.
     * @return {void}
     */
    _handleNative: function(ev) {
      let handled;
      let type = ev.type;
      let node = ev.currentTarget;
      let gobj = node[GESTURE_KEY];
      if (!gobj) {
        return;
      }
      let gs = gobj[type];
      if (!gs) {
        return;
      }
      if (!ev[HANDLED_OBJ]) {
        ev[HANDLED_OBJ] = {};
        if (type.slice(0, 5) === 'touch') {
          ev = /** @type {TouchEvent} */(ev); // eslint-disable-line no-self-assign
          let t = ev.changedTouches[0];
          if (type === 'touchstart') {
            // only handle the first finger
            if (ev.touches.length === 1) {
              POINTERSTATE.touch.id = t.identifier;
            }
          }
          if (POINTERSTATE.touch.id !== t.identifier) {
            return;
          }
          if (!HAS_NATIVE_TA) {
            if (type === 'touchstart' || type === 'touchmove') {
              Gestures._handleTouchAction(ev);
            }
          }
        }
      }
      handled = ev[HANDLED_OBJ];
      // used to ignore synthetic mouse events
      if (handled.skip) {
        return;
      }
      // reset recognizer state
      for (let i = 0, r; i < Gestures.recognizers.length; i++) {
        r = Gestures.recognizers[i];
        if (gs[r.name] && !handled[r.name]) {
          if (r.flow && r.flow.start.indexOf(ev.type) > -1 && r.reset) {
            r.reset();
          }
        }
      }
      // enforce gesture recognizer order
      for (let i = 0, r; i < Gestures.recognizers.length; i++) {
        r = Gestures.recognizers[i];
        if (gs[r.name] && !handled[r.name]) {
          handled[r.name] = true;
          r[type](ev);
        }
      }
    },

    /**
     * @private
     * @param {TouchEvent} ev Event.
     * @return {void}
     */
    _handleTouchAction: function(ev) {
      let t = ev.changedTouches[0];
      let type = ev.type;
      if (type === 'touchstart') {
        POINTERSTATE.touch.x = t.clientX;
        POINTERSTATE.touch.y = t.clientY;
        POINTERSTATE.touch.scrollDecided = false;
      } else if (type === 'touchmove') {
        if (POINTERSTATE.touch.scrollDecided) {
          return;
        }
        POINTERSTATE.touch.scrollDecided = true;
        let ta = firstTouchAction(ev);
        let prevent = false;
        let dx = Math.abs(POINTERSTATE.touch.x - t.clientX);
        let dy = Math.abs(POINTERSTATE.touch.y - t.clientY);
        if (!ev.cancelable) {
          // scrolling is happening
        } else if (ta === 'none') {
          prevent = true;
        } else if (ta === 'pan-x') {
          prevent = dy > dx;
        } else if (ta === 'pan-y') {
          prevent = dx > dy;
        }
        if (prevent) {
          ev.preventDefault();
        } else {
          Gestures.prevent('track');
        }
      }
    },

    /**
     * Adds an event listener to a node for the given gesture type.
     *
     * @memberof Polymer.Gestures
     * @param {!Node} node Node to add listener on
     * @param {string} evType Gesture type: `down`, `up`, `track`, or `tap`
     * @param {!function(!Event):void} handler Event listener function to call
     * @return {boolean} Returns true if a gesture event listener was added.
     * @this {Gestures}
     */
    addListener: function(node, evType, handler) {
      if (this.gestures[evType]) {
        this._add(node, evType, handler);
        return true;
      }
      return false;
    },

    /**
     * Removes an event listener from a node for the given gesture type.
     *
     * @memberof Polymer.Gestures
     * @param {!Node} node Node to remove listener from
     * @param {string} evType Gesture type: `down`, `up`, `track`, or `tap`
     * @param {!function(!Event):void} handler Event listener function previously passed to
     *  `addListener`.
     * @return {boolean} Returns true if a gesture event listener was removed.
     * @this {Gestures}
     */
    removeListener: function(node, evType, handler) {
      if (this.gestures[evType]) {
        this._remove(node, evType, handler);
        return true;
      }
      return false;
    },

    /**
     * automate the event listeners for the native events
     *
     * @private
     * @param {!HTMLElement} node Node on which to add the event.
     * @param {string} evType Event type to add.
     * @param {function(!Event)} handler Event handler function.
     * @return {void}
     * @this {Gestures}
     */
    _add: function(node, evType, handler) {
      let recognizer = this.gestures[evType];
      let deps = recognizer.deps;
      let name = recognizer.name;
      let gobj = node[GESTURE_KEY];
      if (!gobj) {
        node[GESTURE_KEY] = gobj = {};
      }
      for (let i = 0, dep, gd; i < deps.length; i++) {
        dep = deps[i];
        // don't add mouse handlers on iOS because they cause gray selection overlays
        if (IS_TOUCH_ONLY && isMouseEvent(dep) && dep !== 'click') {
          continue;
        }
        gd = gobj[dep];
        if (!gd) {
          gobj[dep] = gd = {_count: 0};
        }
        if (gd._count === 0) {
          node.addEventListener(dep, this._handleNative, PASSIVE_TOUCH(dep));
        }
        gd[name] = (gd[name] || 0) + 1;
        gd._count = (gd._count || 0) + 1;
      }
      node.addEventListener(evType, handler);
      if (recognizer.touchAction) {
        this.setTouchAction(node, recognizer.touchAction);
      }
    },

    /**
     * automate event listener removal for native events
     *
     * @private
     * @param {!HTMLElement} node Node on which to remove the event.
     * @param {string} evType Event type to remove.
     * @param {function(Event?)} handler Event handler function.
     * @return {void}
     * @this {Gestures}
     */
    _remove: function(node, evType, handler) {
      let recognizer = this.gestures[evType];
      let deps = recognizer.deps;
      let name = recognizer.name;
      let gobj = node[GESTURE_KEY];
      if (gobj) {
        for (let i = 0, dep, gd; i < deps.length; i++) {
          dep = deps[i];
          gd = gobj[dep];
          if (gd && gd[name]) {
            gd[name] = (gd[name] || 1) - 1;
            gd._count = (gd._count || 1) - 1;
            if (gd._count === 0) {
              node.removeEventListener(dep, this._handleNative, PASSIVE_TOUCH(dep));
            }
          }
        }
      }
      node.removeEventListener(evType, handler);
    },

    /**
     * Registers a new gesture event recognizer for adding new custom
     * gesture event types.
     *
     * @memberof Polymer.Gestures
     * @param {!GestureRecognizer} recog Gesture recognizer descriptor
     * @return {void}
     * @this {Gestures}
     */
    register: function(recog) {
      this.recognizers.push(recog);
      for (let i = 0; i < recog.emits.length; i++) {
        this.gestures[recog.emits[i]] = recog;
      }
    },

    /**
     * @private
     * @param {string} evName Event name.
     * @return {Object} Returns the gesture for the given event name.
     * @this {Gestures}
     */
    _findRecognizerByEvent: function(evName) {
      for (let i = 0, r; i < this.recognizers.length; i++) {
        r = this.recognizers[i];
        for (let j = 0, n; j < r.emits.length; j++) {
          n = r.emits[j];
          if (n === evName) {
            return r;
          }
        }
      }
      return null;
    },

    /**
     * Sets scrolling direction on node.
     *
     * This value is checked on first move, thus it should be called prior to
     * adding event listeners.
     *
     * @memberof Polymer.Gestures
     * @param {!Element} node Node to set touch action setting on
     * @param {string} value Touch action value
     * @return {void}
     */
    setTouchAction: function(node, value) {
      if (HAS_NATIVE_TA) {
        // NOTE: add touchAction async so that events can be added in
        // custom element constructors. Otherwise we run afoul of custom
        // elements restriction against settings attributes (style) in the
        // constructor.
        Polymer.Async.microTask.run(() => {
          node.style.touchAction = value;
        });
      }
      node[TOUCH_ACTION] = value;
    },

    /**
     * Dispatches an event on the `target` element of `type` with the given
     * `detail`.
     * @private
     * @param {!EventTarget} target The element on which to fire an event.
     * @param {string} type The type of event to fire.
     * @param {!Object=} detail The detail object to populate on the event.
     * @return {void}
     */
    _fire: function(target, type, detail) {
      let ev = new Event(type, { bubbles: true, cancelable: true, composed: true });
      ev.detail = detail;
      target.dispatchEvent(ev);
      // forward `preventDefault` in a clean way
      if (ev.defaultPrevented) {
        let preventer = detail.preventer || detail.sourceEvent;
        if (preventer && preventer.preventDefault) {
          preventer.preventDefault();
        }
      }
    },

    /**
     * Prevents the dispatch and default action of the given event name.
     *
     * @memberof Polymer.Gestures
     * @param {string} evName Event name.
     * @return {void}
     * @this {Gestures}
     */
    prevent: function(evName) {
      let recognizer = this._findRecognizerByEvent(evName);
      if (recognizer.info) {
        recognizer.info.prevent = true;
      }
    },

    /**
     * Reset the 2500ms timeout on processing mouse input after detecting touch input.
     *
     * Touch inputs create synthesized mouse inputs anywhere from 0 to 2000ms after the touch.
     * This method should only be called during testing with simulated touch inputs.
     * Calling this method in production may cause duplicate taps or other Gestures.
     *
     * @memberof Polymer.Gestures
     * @return {void}
     */
    resetMouseCanceller: function() {
      if (POINTERSTATE.mouse.mouseIgnoreJob) {
        POINTERSTATE.mouse.mouseIgnoreJob.flush();
      }
    }
  };

  /* eslint-disable valid-jsdoc */

  Gestures.register({
    name: 'downup',
    deps: ['mousedown', 'touchstart', 'touchend'],
    flow: {
      start: ['mousedown', 'touchstart'],
      end: ['mouseup', 'touchend']
    },
    emits: ['down', 'up'],

    info: {
      movefn: null,
      upfn: null
    },

    /**
     * @this {GestureRecognizer}
     * @return {void}
     */
    reset: function() {
      untrackDocument(this.info);
    },

    /**
     * @this {GestureRecognizer}
     * @param {MouseEvent} e
     * @return {void}
     */
    mousedown: function(e) {
      if (!hasLeftMouseButton(e)) {
        return;
      }
      let t = Gestures._findOriginalTarget(e);
      let self = this;
      let movefn = function movefn(e) {
        if (!hasLeftMouseButton(e)) {
          self._fire('up', t, e);
          untrackDocument(self.info);
        }
      };
      let upfn = function upfn(e) {
        if (hasLeftMouseButton(e)) {
          self._fire('up', t, e);
        }
        untrackDocument(self.info);
      };
      trackDocument(this.info, movefn, upfn);
      this._fire('down', t, e);
    },
    /**
     * @this {GestureRecognizer}
     * @param {TouchEvent} e
     * @return {void}
     */
    touchstart: function(e) {
      this._fire('down', Gestures._findOriginalTarget(e), e.changedTouches[0], e);
    },
    /**
     * @this {GestureRecognizer}
     * @param {TouchEvent} e
     * @return {void}
     */
    touchend: function(e) {
      this._fire('up', Gestures._findOriginalTarget(e), e.changedTouches[0], e);
    },
    /**
     * @param {string} type
     * @param {!EventTarget} target
     * @param {Event} event
     * @param {Function} preventer
     * @return {void}
     */
    _fire: function(type, target, event, preventer) {
      Gestures._fire(target, type, {
        x: event.clientX,
        y: event.clientY,
        sourceEvent: event,
        preventer: preventer,
        prevent: function(e) {
          return Gestures.prevent(e);
        }
      });
    }
  });

  Gestures.register({
    name: 'track',
    touchAction: 'none',
    deps: ['mousedown', 'touchstart', 'touchmove', 'touchend'],
    flow: {
      start: ['mousedown', 'touchstart'],
      end: ['mouseup', 'touchend']
    },
    emits: ['track'],

    info: {
      x: 0,
      y: 0,
      state: 'start',
      started: false,
      moves: [],
      /** @this {GestureRecognizer} */
      addMove: function(move) {
        if (this.moves.length > TRACK_LENGTH) {
          this.moves.shift();
        }
        this.moves.push(move);
      },
      movefn: null,
      upfn: null,
      prevent: false
    },

    /**
     * @this {GestureRecognizer}
     * @return {void}
     */
    reset: function() {
      this.info.state = 'start';
      this.info.started = false;
      this.info.moves = [];
      this.info.x = 0;
      this.info.y = 0;
      this.info.prevent = false;
      untrackDocument(this.info);
    },

    /**
     * @this {GestureRecognizer}
     * @param {number} x
     * @param {number} y
     * @return {boolean}
     */
    hasMovedEnough: function(x, y) {
      if (this.info.prevent) {
        return false;
      }
      if (this.info.started) {
        return true;
      }
      let dx = Math.abs(this.info.x - x);
      let dy = Math.abs(this.info.y - y);
      return (dx >= TRACK_DISTANCE || dy >= TRACK_DISTANCE);
    },
    /**
     * @this {GestureRecognizer}
     * @param {MouseEvent} e
     * @return {void}
     */
    mousedown: function(e) {
      if (!hasLeftMouseButton(e)) {
        return;
      }
      let t = Gestures._findOriginalTarget(e);
      let self = this;
      let movefn = function movefn(e) {
        let x = e.clientX, y = e.clientY;
        if (self.hasMovedEnough(x, y)) {
          // first move is 'start', subsequent moves are 'move', mouseup is 'end'
          self.info.state = self.info.started ? (e.type === 'mouseup' ? 'end' : 'track') : 'start';
          if (self.info.state === 'start') {
            // if and only if tracking, always prevent tap
            Gestures.prevent('tap');
          }
          self.info.addMove({x: x, y: y});
          if (!hasLeftMouseButton(e)) {
            // always _fire "end"
            self.info.state = 'end';
            untrackDocument(self.info);
          }
          self._fire(t, e);
          self.info.started = true;
        }
      };
      let upfn = function upfn(e) {
        if (self.info.started) {
          movefn(e);
        }

        // remove the temporary listeners
        untrackDocument(self.info);
      };
      // add temporary document listeners as mouse retargets
      trackDocument(this.info, movefn, upfn);
      this.info.x = e.clientX;
      this.info.y = e.clientY;
    },
    /**
     * @this {GestureRecognizer}
     * @param {TouchEvent} e
     * @return {void}
     */
    touchstart: function(e) {
      let ct = e.changedTouches[0];
      this.info.x = ct.clientX;
      this.info.y = ct.clientY;
    },
    /**
     * @this {GestureRecognizer}
     * @param {TouchEvent} e
     * @return {void}
     */
    touchmove: function(e) {
      let t = Gestures._findOriginalTarget(e);
      let ct = e.changedTouches[0];
      let x = ct.clientX, y = ct.clientY;
      if (this.hasMovedEnough(x, y)) {
        if (this.info.state === 'start') {
          // if and only if tracking, always prevent tap
          Gestures.prevent('tap');
        }
        this.info.addMove({x: x, y: y});
        this._fire(t, ct);
        this.info.state = 'track';
        this.info.started = true;
      }
    },
    /**
     * @this {GestureRecognizer}
     * @param {TouchEvent} e
     * @return {void}
     */
    touchend: function(e) {
      let t = Gestures._findOriginalTarget(e);
      let ct = e.changedTouches[0];
      // only trackend if track was started and not aborted
      if (this.info.started) {
        // reset started state on up
        this.info.state = 'end';
        this.info.addMove({x: ct.clientX, y: ct.clientY});
        this._fire(t, ct, e);
      }
    },

    /**
     * @this {GestureRecognizer}
     * @param {!EventTarget} target
     * @param {Touch} touch
     * @return {void}
     */
    _fire: function(target, touch) {
      let secondlast = this.info.moves[this.info.moves.length - 2];
      let lastmove = this.info.moves[this.info.moves.length - 1];
      let dx = lastmove.x - this.info.x;
      let dy = lastmove.y - this.info.y;
      let ddx, ddy = 0;
      if (secondlast) {
        ddx = lastmove.x - secondlast.x;
        ddy = lastmove.y - secondlast.y;
      }
      Gestures._fire(target, 'track', {
        state: this.info.state,
        x: touch.clientX,
        y: touch.clientY,
        dx: dx,
        dy: dy,
        ddx: ddx,
        ddy: ddy,
        sourceEvent: touch,
        hover: function() {
          return Gestures.deepTargetFind(touch.clientX, touch.clientY);
        }
      });
    }

  });

  Gestures.register({
    name: 'tap',
    deps: ['mousedown', 'click', 'touchstart', 'touchend'],
    flow: {
      start: ['mousedown', 'touchstart'],
      end: ['click', 'touchend']
    },
    emits: ['tap'],
    info: {
      x: NaN,
      y: NaN,
      prevent: false
    },
    /**
     * @this {GestureRecognizer}
     * @return {void}
     */
    reset: function() {
      this.info.x = NaN;
      this.info.y = NaN;
      this.info.prevent = false;
    },
    /**
     * @this {GestureRecognizer}
     * @param {MouseEvent} e
     * @return {void}
     */
    save: function(e) {
      this.info.x = e.clientX;
      this.info.y = e.clientY;
    },
    /**
     * @this {GestureRecognizer}
     * @param {MouseEvent} e
     * @return {void}
     */
    mousedown: function(e) {
      if (hasLeftMouseButton(e)) {
        this.save(e);
      }
    },
    /**
     * @this {GestureRecognizer}
     * @param {MouseEvent} e
     * @return {void}
     */
    click: function(e) {
      if (hasLeftMouseButton(e)) {
        this.forward(e);
      }
    },
    /**
     * @this {GestureRecognizer}
     * @param {TouchEvent} e
     * @return {void}
     */
    touchstart: function(e) {
      this.save(e.changedTouches[0], e);
    },
    /**
     * @this {GestureRecognizer}
     * @param {TouchEvent} e
     * @return {void}
     */
    touchend: function(e) {
      this.forward(e.changedTouches[0], e);
    },
    /**
     * @this {GestureRecognizer}
     * @param {Event | Touch} e
     * @param {Event=} preventer
     * @return {void}
     */
    forward: function(e, preventer) {
      let dx = Math.abs(e.clientX - this.info.x);
      let dy = Math.abs(e.clientY - this.info.y);
      // find original target from `preventer` for TouchEvents, or `e` for MouseEvents
      let t = Gestures._findOriginalTarget(/** @type {Event} */(preventer || e));
      if (!t || (canBeDisabled[/** @type {!HTMLElement} */(t).localName] && t.hasAttribute('disabled'))) {
        return;
      }
      // dx,dy can be NaN if `click` has been simulated and there was no `down` for `start`
      if (isNaN(dx) || isNaN(dy) || (dx <= TAP_DISTANCE && dy <= TAP_DISTANCE) || isSyntheticClick(e)) {
        // prevent taps from being generated if an event has canceled them
        if (!this.info.prevent) {
          Gestures._fire(t, 'tap', {
            x: e.clientX,
            y: e.clientY,
            sourceEvent: e,
            preventer: preventer
          });
        }
      }
    }
  });

  /* eslint-enable valid-jsdoc */

  /** @deprecated */
  Gestures.findOriginalTarget = Gestures._findOriginalTarget;

  /** @deprecated */
  Gestures.add = Gestures.addListener;

  /** @deprecated */
  Gestures.remove = Gestures.removeListener;

  Polymer.Gestures = Gestures;

})();
</script>
<script>
(function() {

  'use strict';

  /**
   * @const {Polymer.Gestures}
   */
  const gestures = Polymer.Gestures;

  /**
   * Element class mixin that provides API for adding Polymer's cross-platform
   * gesture events to nodes.
   *
   * The API is designed to be compatible with override points implemented
   * in `Polymer.TemplateStamp` such that declarative event listeners in
   * templates will support gesture events when this mixin is applied along with
   * `Polymer.TemplateStamp`.
   *
   * @mixinFunction
   * @polymer
   * @memberof Polymer
   * @summary Element class mixin that provides API for adding Polymer's cross-platform
   * gesture events to nodes
   */
  Polymer.GestureEventListeners = Polymer.dedupingMixin(superClass => {

    /**
     * @polymer
     * @mixinClass
     * @implements {Polymer_GestureEventListeners}
     */
    class GestureEventListeners extends superClass {

      /**
       * Add the event listener to the node if it is a gestures event.
       *
       * @param {!Node} node Node to add event listener to
       * @param {string} eventName Name of event
       * @param {function(!Event):void} handler Listener function to add
       * @return {void}
       */
      _addEventListenerToNode(node, eventName, handler) {
        if (!gestures.addListener(node, eventName, handler)) {
          super._addEventListenerToNode(node, eventName, handler);
        }
      }

      /**
       * Remove the event listener to the node if it is a gestures event.
       *
       * @param {!Node} node Node to remove event listener from
       * @param {string} eventName Name of event
       * @param {function(!Event):void} handler Listener function to remove
       * @return {void}
       */
      _removeEventListenerFromNode(node, eventName, handler) {
        if (!gestures.removeListener(node, eventName, handler)) {
          super._removeEventListenerFromNode(node, eventName, handler);
        }
      }

    }

    return GestureEventListeners;

  });

})();
</script>
<script>
  (function() {
    'use strict';

    const HOST_DIR = /:host\(:dir\((ltr|rtl)\)\)/g;
    const HOST_DIR_REPLACMENT = ':host([dir="$1"])';

    const EL_DIR = /([\s\w-#\.\[\]\*]*):dir\((ltr|rtl)\)/g;
    const EL_DIR_REPLACMENT = ':host([dir="$2"]) $1';

    const DIR_CHECK = /:dir\((?:ltr|rtl)\)/;
    
    const SHIM_SHADOW = Boolean(window['ShadyDOM'] && window['ShadyDOM']['inUse']);

    /**
     * @type {!Array<!Polymer_DirMixin>}
     */
    const DIR_INSTANCES = [];

    /** @type {MutationObserver} */
    let observer = null;

    let DOCUMENT_DIR = '';

    function getRTL() {
      DOCUMENT_DIR = document.documentElement.getAttribute('dir');
    }

    /**
     * @param {!Polymer_DirMixin} instance Instance to set RTL status on
     */
    function setRTL(instance) {
      if (!instance.__autoDirOptOut) {
        const el = /** @type {!HTMLElement} */(instance);
        el.setAttribute('dir', DOCUMENT_DIR);
      }
    }

    function updateDirection() {
      getRTL();
      DOCUMENT_DIR = document.documentElement.getAttribute('dir');
      for (let i = 0; i < DIR_INSTANCES.length; i++) {
        setRTL(DIR_INSTANCES[i]);
      }
    }

    function takeRecords() {
      if (observer && observer.takeRecords().length) {
        updateDirection();
      }
    }

    /**
     * Element class mixin that allows elements to use the `:dir` CSS Selector to have
     * text direction specific styling.
     *
     * With this mixin, any stylesheet provided in the template will transform `:dir` into
     * `:host([dir])` and sync direction with the page via the element's `dir` attribute.
     *
     * Elements can opt out of the global page text direction by setting the `dir` attribute
     * directly in `ready()` or in HTML.
     *
     * Caveats:
     * - Applications must set `<html dir="ltr">` or `<html dir="rtl">` to sync direction
     * - Automatic left-to-right or right-to-left styling is sync'd with the `<html>` element only.
     * - Changing `dir` at runtime is supported.
     * - Opting out of the global direction styling is permanent
     *
     * @mixinFunction
     * @polymer
     * @appliesMixin Polymer.PropertyAccessors
     * @memberof Polymer
     */
    Polymer.DirMixin = Polymer.dedupingMixin((base) => {

      if (!SHIM_SHADOW) {
        if (!observer) {
          getRTL();
          observer = new MutationObserver(updateDirection);
          observer.observe(document.documentElement, {attributes: true, attributeFilter: ['dir']});
        }
      }

      /**
       * @constructor
       * @extends {base}
       * @implements {Polymer_PropertyAccessors}
       * @private
       */
      const elementBase = Polymer.PropertyAccessors(base);

      /**
       * @polymer
       * @mixinClass
       * @implements {Polymer_DirMixin}
       */
      class Dir extends elementBase {

        /**
         * @override
         * @suppress {missingProperties} Interfaces in closure do not inherit statics, but classes do
         */
        static _processStyleText(cssText, baseURI) {
          cssText = super._processStyleText(cssText, baseURI);
          if (!SHIM_SHADOW && DIR_CHECK.test(cssText)) {
            cssText = this._replaceDirInCssText(cssText);
            this.__activateDir = true;
          }
          return cssText;
        }

        /**
         * Replace `:dir` in the given CSS text
         *
         * @param {string} text CSS text to replace DIR
         * @return {string} Modified CSS
         */
        static _replaceDirInCssText(text) {
          let replacedText = text;
          replacedText = replacedText.replace(HOST_DIR, HOST_DIR_REPLACMENT);
          replacedText = replacedText.replace(EL_DIR, EL_DIR_REPLACMENT);
          return replacedText;
        }

        constructor() {
          super();
          /** @type {boolean} */
          this.__autoDirOptOut = false;
        }

        /**
         * @suppress {invalidCasts} Closure doesn't understand that `this` is an HTMLElement
         * @return {void}
         */
        ready() {
          super.ready();
          this.__autoDirOptOut = /** @type {!HTMLElement} */(this).hasAttribute('dir');
        }

        /**
         * @suppress {missingProperties} If it exists on elementBase, it can be super'd
         * @return {void}
         */
        connectedCallback() {
          if (elementBase.prototype.connectedCallback) {
            super.connectedCallback();
          }
          if (this.constructor.__activateDir) {
            takeRecords();
            DIR_INSTANCES.push(this);
            setRTL(this);
          }
        }

        /**
         * @suppress {missingProperties} If it exists on elementBase, it can be super'd
         * @return {void}
         */
        disconnectedCallback() {
          if (elementBase.prototype.disconnectedCallback) {
            super.disconnectedCallback();
          }
          if (this.constructor.__activateDir) {
            const idx = DIR_INSTANCES.indexOf(this);
            if (idx > -1) {
              DIR_INSTANCES.splice(idx, 1);
            }
          }
        }
      }

      Dir.__activateDir = false;

      return Dir;
    });
  })();
</script>
<script>

(function() {

  'use strict';

  // run a callback when HTMLImports are ready or immediately if
  // this api is not available.
  function whenImportsReady(cb) {
    if (window.HTMLImports) {
      HTMLImports.whenReady(cb);
    } else {
      cb();
    }
  }

  /**
   * Convenience method for importing an HTML document imperatively.
   *
   * This method creates a new `<link rel="import">` element with
   * the provided URL and appends it to the document to start loading.
   * In the `onload` callback, the `import` property of the `link`
   * element will contain the imported document contents.
   *
   * @memberof Polymer
   * @param {string} href URL to document to load.
   * @param {?function(!Event):void=} onload Callback to notify when an import successfully
   *   loaded.
   * @param {?function(!ErrorEvent):void=} onerror Callback to notify when an import
   *   unsuccessfully loaded.
   * @param {boolean=} optAsync True if the import should be loaded `async`.
   *   Defaults to `false`.
   * @return {!HTMLLinkElement} The link element for the URL to be loaded.
   */
  Polymer.importHref = function(href, onload, onerror, optAsync) {
    let link = /** @type {HTMLLinkElement} */
      (document.head.querySelector('link[href="' + href + '"][import-href]'));
    if (!link) {
      link = /** @type {HTMLLinkElement} */ (document.createElement('link'));
      link.rel = 'import';
      link.href = href;
      link.setAttribute('import-href', '');
    }
    // always ensure link has `async` attribute if user specified one,
    // even if it was previously not async. This is considered less confusing.
    if (optAsync) {
      link.setAttribute('async', '');
    }
    // NOTE: the link may now be in 3 states: (1) pending insertion,
    // (2) inflight, (3) already loaded. In each case, we need to add
    // event listeners to process callbacks.
    let cleanup = function() {
      link.removeEventListener('load', loadListener);
      link.removeEventListener('error', errorListener);
    };
    let loadListener = function(event) {
      cleanup();
      // In case of a successful load, cache the load event on the link so
      // that it can be used to short-circuit this method in the future when
      // it is called with the same href param.
      link.__dynamicImportLoaded = true;
      if (onload) {
        whenImportsReady(() => {
          onload(event);
        });
      }
    };
    let errorListener = function(event) {
      cleanup();
      // In case of an error, remove the link from the document so that it
      // will be automatically created again the next time `importHref` is
      // called.
      if (link.parentNode) {
        link.parentNode.removeChild(link);
      }
      if (onerror) {
        whenImportsReady(() => {
          onerror(event);
        });
      }
    };
    link.addEventListener('load', loadListener);
    link.addEventListener('error', errorListener);
    if (link.parentNode == null) {
      document.head.appendChild(link);
    // if the link already loaded, dispatch a fake load event
    // so that listeners are called and get a proper event argument.
    } else if (link.__dynamicImportLoaded) {
      link.dispatchEvent(new Event('load'));
    }
    return link;
  };

})();
</script>
<script>
(function() {

  'use strict';

  let scheduled = false;
  let beforeRenderQueue = [];
  let afterRenderQueue = [];

  function schedule() {
    scheduled = true;
    // before next render
    requestAnimationFrame(function() {
      scheduled = false;
      flushQueue(beforeRenderQueue);
      // after the render
      setTimeout(function() {
        runQueue(afterRenderQueue);
      });
    });
  }

  function flushQueue(queue) {
    while (queue.length) {
      callMethod(queue.shift());
    }
  }

  function runQueue(queue) {
    for (let i=0, l=queue.length; i < l; i++) {
      callMethod(queue.shift());
    }
  }

  function callMethod(info) {
    const context = info[0];
    const callback = info[1];
    const args = info[2];
    try {
      callback.apply(context, args);
    } catch(e) {
      setTimeout(() => {
        throw e;
      });
    }
  }

  function flush() {
    while (beforeRenderQueue.length || afterRenderQueue.length) {
      flushQueue(beforeRenderQueue);
      flushQueue(afterRenderQueue);
    }
    scheduled = false;
  }

  /**
   * Module for scheduling flushable pre-render and post-render tasks.
   *
   * @namespace
   * @memberof Polymer
   * @summary Module for scheduling flushable pre-render and post-render tasks.
   */
  Polymer.RenderStatus = {

    /**
     * Enqueues a callback which will be run before the next render, at
     * `requestAnimationFrame` timing.
     *
     * This method is useful for enqueuing work that requires DOM measurement,
     * since measurement may not be reliable in custom element callbacks before
     * the first render, as well as for batching measurement tasks in general.
     *
     * Tasks in this queue may be flushed by calling `Polymer.RenderStatus.flush()`.
     *
     * @memberof Polymer.RenderStatus
     * @param {*} context Context object the callback function will be bound to
     * @param {function(...*):void} callback Callback function
     * @param {!Array=} args An array of arguments to call the callback function with
     * @return {void}
     */
    beforeNextRender: function(context, callback, args) {
      if (!scheduled) {
        schedule();
      }
      beforeRenderQueue.push([context, callback, args]);
    },

    /**
     * Enqueues a callback which will be run after the next render, equivalent
     * to one task (`setTimeout`) after the next `requestAnimationFrame`.
     *
     * This method is useful for tuning the first-render performance of an
     * element or application by deferring non-critical work until after the
     * first paint.  Typical non-render-critical work may include adding UI
     * event listeners and aria attributes.
     *
     * @memberof Polymer.RenderStatus
     * @param {*} context Context object the callback function will be bound to
     * @param {function(...*):void} callback Callback function
     * @param {!Array=} args An array of arguments to call the callback function with
     * @return {void}
     */
    afterNextRender: function(context, callback, args) {
      if (!scheduled) {
        schedule();
      }
      afterRenderQueue.push([context, callback, args]);
    },

    /**
     * Flushes all `beforeNextRender` tasks, followed by all `afterNextRender`
     * tasks.
     *
     * @memberof Polymer.RenderStatus
     * @return {void}
     */
    flush: flush

  };

})();
</script>
<script>
(function() {
  'use strict';

  // unresolved

  function resolve() {
    document.body.removeAttribute('unresolved');
  }

  if (window.WebComponents) {
    window.addEventListener('WebComponentsReady', resolve);
  } else {
    if (document.readyState === 'interactive' || document.readyState === 'complete') {
      resolve();
    } else {
      window.addEventListener('DOMContentLoaded', resolve);
    }
  }

})();
</script>
<script>
(function() {

  'use strict';

  function newSplice(index, removed, addedCount) {
    return {
      index: index,
      removed: removed,
      addedCount: addedCount
    };
  }

  const EDIT_LEAVE = 0;
  const EDIT_UPDATE = 1;
  const EDIT_ADD = 2;
  const EDIT_DELETE = 3;

  // Note: This function is *based* on the computation of the Levenshtein
  // "edit" distance. The one change is that "updates" are treated as two
  // edits - not one. With Array splices, an update is really a delete
  // followed by an add. By retaining this, we optimize for "keeping" the
  // maximum array items in the original array. For example:
  //
  //   'xxxx123' -> '123yyyy'
  //
  // With 1-edit updates, the shortest path would be just to update all seven
  // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
  // leaves the substring '123' intact.
  function calcEditDistances(current, currentStart, currentEnd,
                              old, oldStart, oldEnd) {
    // "Deletion" columns
    let rowCount = oldEnd - oldStart + 1;
    let columnCount = currentEnd - currentStart + 1;
    let distances = new Array(rowCount);

    // "Addition" rows. Initialize null column.
    for (let i = 0; i < rowCount; i++) {
      distances[i] = new Array(columnCount);
      distances[i][0] = i;
    }

    // Initialize null row
    for (let j = 0; j < columnCount; j++)
      distances[0][j] = j;

    for (let i = 1; i < rowCount; i++) {
      for (let j = 1; j < columnCount; j++) {
        if (equals(current[currentStart + j - 1], old[oldStart + i - 1]))
          distances[i][j] = distances[i - 1][j - 1];
        else {
          let north = distances[i - 1][j] + 1;
          let west = distances[i][j - 1] + 1;
          distances[i][j] = north < west ? north : west;
        }
      }
    }

    return distances;
  }

  // This starts at the final weight, and walks "backward" by finding
  // the minimum previous weight recursively until the origin of the weight
  // matrix.
  function spliceOperationsFromEditDistances(distances) {
    let i = distances.length - 1;
    let j = distances[0].length - 1;
    let current = distances[i][j];
    let edits = [];
    while (i > 0 || j > 0) {
      if (i == 0) {
        edits.push(EDIT_ADD);
        j--;
        continue;
      }
      if (j == 0) {
        edits.push(EDIT_DELETE);
        i--;
        continue;
      }
      let northWest = distances[i - 1][j - 1];
      let west = distances[i - 1][j];
      let north = distances[i][j - 1];

      let min;
      if (west < north)
        min = west < northWest ? west : northWest;
      else
        min = north < northWest ? north : northWest;

      if (min == northWest) {
        if (northWest == current) {
          edits.push(EDIT_LEAVE);
        } else {
          edits.push(EDIT_UPDATE);
          current = northWest;
        }
        i--;
        j--;
      } else if (min == west) {
        edits.push(EDIT_DELETE);
        i--;
        current = west;
      } else {
        edits.push(EDIT_ADD);
        j--;
        current = north;
      }
    }

    edits.reverse();
    return edits;
  }

  /**
   * Splice Projection functions:
   *
   * A splice map is a representation of how a previous array of items
   * was transformed into a new array of items. Conceptually it is a list of
   * tuples of
   *
   *   <index, removed, addedCount>
   *
   * which are kept in ascending index order of. The tuple represents that at
   * the |index|, |removed| sequence of items were removed, and counting forward
   * from |index|, |addedCount| items were added.
   */

  /**
   * Lacking individual splice mutation information, the minimal set of
   * splices can be synthesized given the previous state and final state of an
   * array. The basic approach is to calculate the edit distance matrix and
   * choose the shortest path through it.
   *
   * Complexity: O(l * p)
   *   l: The length of the current array
   *   p: The length of the old array
   *
   * @param {!Array} current The current "changed" array for which to
   * calculate splices.
   * @param {number} currentStart Starting index in the `current` array for
   * which splices are calculated.
   * @param {number} currentEnd Ending index in the `current` array for
   * which splices are calculated.
   * @param {!Array} old The original "unchanged" array to compare `current`
   * against to determine splices.
   * @param {number} oldStart Starting index in the `old` array for
   * which splices are calculated.
   * @param {number} oldEnd Ending index in the `old` array for
   * which splices are calculated.
   * @return {!Array} Returns an array of splice record objects. Each of these
   * contains: `index` the location where the splice occurred; `removed`
   * the array of removed items from this location; `addedCount` the number
   * of items added at this location.
   */
  function calcSplices(current, currentStart, currentEnd,
                        old, oldStart, oldEnd) {
    let prefixCount = 0;
    let suffixCount = 0;
    let splice;

    let minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
    if (currentStart == 0 && oldStart == 0)
      prefixCount = sharedPrefix(current, old, minLength);

    if (currentEnd == current.length && oldEnd == old.length)
      suffixCount = sharedSuffix(current, old, minLength - prefixCount);

    currentStart += prefixCount;
    oldStart += prefixCount;
    currentEnd -= suffixCount;
    oldEnd -= suffixCount;

    if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0)
      return [];

    if (currentStart == currentEnd) {
      splice = newSplice(currentStart, [], 0);
      while (oldStart < oldEnd)
        splice.removed.push(old[oldStart++]);

      return [ splice ];
    } else if (oldStart == oldEnd)
      return [ newSplice(currentStart, [], currentEnd - currentStart) ];

    let ops = spliceOperationsFromEditDistances(
        calcEditDistances(current, currentStart, currentEnd,
                               old, oldStart, oldEnd));

    splice = undefined;
    let splices = [];
    let index = currentStart;
    let oldIndex = oldStart;
    for (let i = 0; i < ops.length; i++) {
      switch(ops[i]) {
        case EDIT_LEAVE:
          if (splice) {
            splices.push(splice);
            splice = undefined;
          }

          index++;
          oldIndex++;
          break;
        case EDIT_UPDATE:
          if (!splice)
            splice = newSplice(index, [], 0);

          splice.addedCount++;
          index++;

          splice.removed.push(old[oldIndex]);
          oldIndex++;
          break;
        case EDIT_ADD:
          if (!splice)
            splice = newSplice(index, [], 0);

          splice.addedCount++;
          index++;
          break;
        case EDIT_DELETE:
          if (!splice)
            splice = newSplice(index, [], 0);

          splice.removed.push(old[oldIndex]);
          oldIndex++;
          break;
      }
    }

    if (splice) {
      splices.push(splice);
    }
    return splices;
  }

  function sharedPrefix(current, old, searchLength) {
    for (let i = 0; i < searchLength; i++)
      if (!equals(current[i], old[i]))
        return i;
    return searchLength;
  }

  function sharedSuffix(current, old, searchLength) {
    let index1 = current.length;
    let index2 = old.length;
    let count = 0;
    while (count < searchLength && equals(current[--index1], old[--index2]))
      count++;

    return count;
  }

  function calculateSplices(current, previous) {
    return calcSplices(current, 0, current.length, previous, 0,
                            previous.length);
  }

  function equals(currentValue, previousValue) {
    return currentValue === previousValue;
  }

  /**
   * @namespace
   * @memberof Polymer
   * @summary Module that provides utilities for diffing arrays.
   */
  Polymer.ArraySplice = {
    /**
     * Returns an array of splice records indicating the minimum edits required
     * to transform the `previous` array into the `current` array.
     *
     * Splice records are ordered by index and contain the following fields:
     * - `index`: index where edit started
     * - `removed`: array of removed items from this index
     * - `addedCount`: number of items added at this index
     *
     * This function is based on the Levenshtein "minimum edit distance"
     * algorithm. Note that updates are treated as removal followed by addition.
     *
     * The worst-case time complexity of this algorithm is `O(l * p)`
     *   l: The length of the current array
     *   p: The length of the previous array
     *
     * However, the worst-case complexity is reduced by an `O(n)` optimization
     * to detect any shared prefix & suffix between the two arrays and only
     * perform the more expensive minimum edit distance calculation over the
     * non-shared portions of the arrays.
     *
     * @function
     * @memberof Polymer.ArraySplice
     * @param {!Array} current The "changed" array for which splices will be
     * calculated.
     * @param {!Array} previous The "unchanged" original array to compare
     * `current` against to determine the splices.
     * @return {!Array} Returns an array of splice record objects. Each of these
     * contains: `index` the location where the splice occurred; `removed`
     * the array of removed items from this location; `addedCount` the number
     * of items added at this location.
     */
    calculateSplices
  };

})();
</script>
<script>
(function() {
  'use strict';

  /**
   * Returns true if `node` is a slot element
   * @param {Node} node Node to test.
   * @return {boolean} Returns true if the given `node` is a slot
   * @private
   */
  function isSlot(node) {
    return (node.localName === 'slot');
  }

  /**
   * Class that listens for changes (additions or removals) to
   * "flattened nodes" on a given `node`. The list of flattened nodes consists
   * of a node's children and, for any children that are `<slot>` elements,
   * the expanded flattened list of `assignedNodes`.
   * For example, if the observed node has children `<a></a><slot></slot><b></b>`
   * and the `<slot>` has one `<div>` assigned to it, then the flattened
   * nodes list is `<a></a><div></div><b></b>`. If the `<slot>` has other
   * `<slot>` elements assigned to it, these are flattened as well.
   *
   * The provided `callback` is called whenever any change to this list
   * of flattened nodes occurs, where an addition or removal of a node is
   * considered a change. The `callback` is called with one argument, an object
   * containing an array of any `addedNodes` and `removedNodes`.
   *
   * Note: the callback is called asynchronous to any changes
   * at a microtask checkpoint. This is because observation is performed using
   * `MutationObserver` and the `<slot>` element's `slotchange` event which
   * are asynchronous.
   *
   * An example:
   * ```js
   * class TestSelfObserve extends Polymer.Element {
   *   static get is() { return 'test-self-observe';}
   *   connectedCallback() {
   *     super.connectedCallback();
   *     this._observer = new Polymer.FlattenedNodesObserver(this, (info) => {
   *       this.info = info;
   *     });
   *   }
   *   disconnectedCallback() {
   *     super.disconnectedCallback();
   *     this._observer.disconnect();
   *   }
   * }
   * customElements.define(TestSelfObserve.is, TestSelfObserve);
   * ```
   *
   * @memberof Polymer
   * @summary Class that listens for changes (additions or removals) to
   * "flattened nodes" on a given `node`.
   */
  class FlattenedNodesObserver {

    /**
     * Returns the list of flattened nodes for the given `node`.
     * This list consists of a node's children and, for any children
     * that are `<slot>` elements, the expanded flattened list of `assignedNodes`.
     * For example, if the observed node has children `<a></a><slot></slot><b></b>`
     * and the `<slot>` has one `<div>` assigned to it, then the flattened
     * nodes list is `<a></a><div></div><b></b>`. If the `<slot>` has other
     * `<slot>` elements assigned to it, these are flattened as well.
     *
     * @param {HTMLElement|HTMLSlotElement} node The node for which to return the list of flattened nodes.
     * @return {Array} The list of flattened nodes for the given `node`.
    */
    static getFlattenedNodes(node) {
      if (isSlot(node)) {
        node = /** @type {HTMLSlotElement} */(node); // eslint-disable-line no-self-assign
        return node.assignedNodes({flatten: true});
      } else {
        return Array.from(node.childNodes).map((node) => {
          if (isSlot(node)) {
            node = /** @type {HTMLSlotElement} */(node); // eslint-disable-line no-self-assign
            return node.assignedNodes({flatten: true});
          } else {
            return [node];
          }
        }).reduce((a, b) => a.concat(b), []);
      }
    }

    /**
     * @param {Element} target Node on which to listen for changes.
     * @param {?function(!Element, { target: !Element, addedNodes: !Array<!Element>, removedNodes: !Array<!Element> }):void} callback Function called when there are additions
     * or removals from the target's list of flattened nodes.
    */
    constructor(target, callback) {
      /**
       * @type {MutationObserver}
       * @private
       */
      this._shadyChildrenObserver = null;
      /**
       * @type {MutationObserver}
       * @private
       */
      this._nativeChildrenObserver = null;
      this._connected = false;
      /**
       * @type {Element}
       * @private
       */
      this._target = target;
      this.callback = callback;
      this._effectiveNodes = [];
      this._observer = null;
      this._scheduled = false;
      /**
       * @type {function()}
       * @private
       */
      this._boundSchedule = () => {
        this._schedule();
      };
      this.connect();
      this._schedule();
    }

    /**
     * Activates an observer. This method is automatically called when
     * a `FlattenedNodesObserver` is created. It should only be called to
     * re-activate an observer that has been deactivated via the `disconnect` method.
     *
     * @return {void}
     */
    connect() {
      if (isSlot(this._target)) {
        this._listenSlots([this._target]);
      } else if (this._target.children) {
        this._listenSlots(this._target.children);
        if (window.ShadyDOM) {
          this._shadyChildrenObserver =
            ShadyDOM.observeChildren(this._target, (mutations) => {
              this._processMutations(mutations);
            });
        } else {
          this._nativeChildrenObserver =
            new MutationObserver((mutations) => {
              this._processMutations(mutations);
            });
          this._nativeChildrenObserver.observe(this._target, {childList: true});
        }
      }
      this._connected = true;
    }

    /**
     * Deactivates the flattened nodes observer. After calling this method
     * the observer callback will not be called when changes to flattened nodes
     * occur. The `connect` method may be subsequently called to reactivate
     * the observer.
     *
     * @return {void}
     */
    disconnect() {
      if (isSlot(this._target)) {
        this._unlistenSlots([this._target]);
      } else if (this._target.children) {
        this._unlistenSlots(this._target.children);
        if (window.ShadyDOM && this._shadyChildrenObserver) {
          ShadyDOM.unobserveChildren(this._shadyChildrenObserver);
          this._shadyChildrenObserver = null;
        } else if (this._nativeChildrenObserver) {
          this._nativeChildrenObserver.disconnect();
          this._nativeChildrenObserver = null;
        }
      }
      this._connected = false;
    }

    /**
     * @return {void}
     * @private
     */
    _schedule() {
      if (!this._scheduled) {
        this._scheduled = true;
        Polymer.Async.microTask.run(() => this.flush());
      }
    }

    /**
     * @param {Array<MutationRecord>} mutations Mutations signaled by the mutation observer
     * @return {void}
     * @private
     */
    _processMutations(mutations) {
      this._processSlotMutations(mutations);
      this.flush();
    }

    /**
     * @param {Array<MutationRecord>} mutations Mutations signaled by the mutation observer
     * @return {void}
     * @private
     */
    _processSlotMutations(mutations) {
      if (mutations) {
        for (let i=0; i < mutations.length; i++) {
          let mutation = mutations[i];
          if (mutation.addedNodes) {
            this._listenSlots(mutation.addedNodes);
          }
          if (mutation.removedNodes) {
            this._unlistenSlots(mutation.removedNodes);
          }
        }
      }
    }

    /**
     * Flushes the observer causing any pending changes to be immediately
     * delivered the observer callback. By default these changes are delivered
     * asynchronously at the next microtask checkpoint.
     *
     * @return {boolean} Returns true if any pending changes caused the observer
     * callback to run.
     */
    flush() {
      if (!this._connected) {
        return false;
      }
      if (window.ShadyDOM) {
        ShadyDOM.flush();
      }
      if (this._nativeChildrenObserver) {
        this._processSlotMutations(this._nativeChildrenObserver.takeRecords());
      } else if (this._shadyChildrenObserver) {
        this._processSlotMutations(this._shadyChildrenObserver.takeRecords());
      }
      this._scheduled = false;
      let info = {
        target: this._target,
        addedNodes: [],
        removedNodes: []
      };
      let newNodes = this.constructor.getFlattenedNodes(this._target);
      let splices = Polymer.ArraySplice.calculateSplices(newNodes,
        this._effectiveNodes);
      // process removals
      for (let i=0, s; (i<splices.length) && (s=splices[i]); i++) {
        for (let j=0, n; (j < s.removed.length) && (n=s.removed[j]); j++) {
          info.removedNodes.push(n);
        }
      }
      // process adds
      for (let i=0, s; (i<splices.length) && (s=splices[i]); i++) {
        for (let j=s.index; j < s.index + s.addedCount; j++) {
          info.addedNodes.push(newNodes[j]);
        }
      }
      // update cache
      this._effectiveNodes = newNodes;
      let didFlush = false;
      if (info.addedNodes.length || info.removedNodes.length) {
        didFlush = true;
        this.callback.call(this._target, info);
      }
      return didFlush;
    }

    /**
     * @param {!Array<Element|Node>|!NodeList<Node>} nodeList Nodes that could change
     * @return {void}
     * @private
     */
    _listenSlots(nodeList) {
      for (let i=0; i < nodeList.length; i++) {
        let n = nodeList[i];
        if (isSlot(n)) {
          n.addEventListener('slotchange', this._boundSchedule);
        }
      }
    }

    /**
     * @param {!Array<Element|Node>|!NodeList<Node>} nodeList Nodes that could change
     * @return {void}
     * @private
     */
    _unlistenSlots(nodeList) {
      for (let i=0; i < nodeList.length; i++) {
        let n = nodeList[i];
        if (isSlot(n)) {
          n.removeEventListener('slotchange', this._boundSchedule);
        }
      }
    }

  }

  Polymer.FlattenedNodesObserver = FlattenedNodesObserver;

})();
</script>
<script>
(function() {
  'use strict';

  /**
   * Forces several classes of asynchronously queued tasks to flush:
   * - Debouncers added via `enqueueDebouncer`
   * - ShadyDOM distribution
   *
   * @memberof Polymer
   * @return {void}
   */
  Polymer.flush = function() {
    let shadyDOM, debouncers;
    do {
      shadyDOM = window.ShadyDOM && ShadyDOM.flush();
      if (window.ShadyCSS && window.ShadyCSS.ScopingShim) {
        window.ShadyCSS.ScopingShim.flush();
      }
      debouncers = Polymer.flushDebouncers();
    } while (shadyDOM || debouncers);
  };

})();
</script>
<script>
(function() {
  'use strict';

  const p = Element.prototype;
  /**
   * @const {function(this:Node, string): boolean}
   */
  const normalizedMatchesSelector = p.matches || p.matchesSelector ||
    p.mozMatchesSelector || p.msMatchesSelector ||
    p.oMatchesSelector || p.webkitMatchesSelector;

  /**
   * Cross-platform `element.matches` shim.
   *
   * @function matchesSelector
   * @memberof Polymer.dom
   * @param {!Node} node Node to check selector against
   * @param {string} selector Selector to match
   * @return {boolean} True if node matched selector
   */
  const matchesSelector = function(node, selector) {
    return normalizedMatchesSelector.call(node, selector);
  };

  /**
   * Node API wrapper class returned from `Polymer.dom.(target)` when
   * `target` is a `Node`.
   *
   * @memberof Polymer
   */
  class DomApi {

    /**
     * @param {Node} node Node for which to create a Polymer.dom helper object.
     */
    constructor(node) {
      this.node = node;
    }

    /**
     * Returns an instance of `Polymer.FlattenedNodesObserver` that
     * listens for node changes on this element.
     *
     * @param {function(!Element, { target: !Element, addedNodes: !Array<!Element>, removedNodes: !Array<!Element> }):void} callback Called when direct or distributed children
     *   of this element changes
     * @return {!Polymer.FlattenedNodesObserver} Observer instance
     */
    observeNodes(callback) {
      return new Polymer.FlattenedNodesObserver(this.node, callback);
    }

    /**
     * Disconnects an observer previously created via `observeNodes`
     *
     * @param {!Polymer.FlattenedNodesObserver} observerHandle Observer instance
     *   to disconnect.
     * @return {void}
     */
    unobserveNodes(observerHandle) {
      observerHandle.disconnect();
    }

    /**
     * Provided as a backwards-compatible API only.  This method does nothing.
     * @return {void}
     */
    notifyObserver() {}

    /**
     * Returns true if the provided node is contained with this element's
     * light-DOM children or shadow root, including any nested shadow roots
     * of children therein.
     *
     * @param {Node} node Node to test
     * @return {boolean} Returns true if the given `node` is contained within
     *   this element's light or shadow DOM.
     */
    deepContains(node) {
      if (this.node.contains(node)) {
        return true;
      }
      let n = node;
      let doc = node.ownerDocument;
      // walk from node to `this` or `document`
      while (n && n !== doc && n !== this.node) {
        // use logical parentnode, or native ShadowRoot host
        n = n.parentNode || n.host;
      }
      return n === this.node;
    }

    /**
     * Returns the root node of this node.  Equivalent to `getRoodNode()`.
     *
     * @return {Node} Top most element in the dom tree in which the node
     * exists. If the node is connected to a document this is either a
     * shadowRoot or the document; otherwise, it may be the node
     * itself or a node or document fragment containing it.
     */
    getOwnerRoot() {
      return this.node.getRootNode();
    }

    /**
     * For slot elements, returns the nodes assigned to the slot; otherwise
     * an empty array. It is equivalent to `<slot>.addignedNodes({flatten:true})`.
     *
     * @return {!Array<!Node>} Array of assigned nodes
     */
    getDistributedNodes() {
      return (this.node.localName === 'slot') ?
        this.node.assignedNodes({flatten: true}) :
        [];
    }

    /**
     * Returns an array of all slots this element was distributed to.
     *
     * @return {!Array<!HTMLSlotElement>} Description
     */
    getDestinationInsertionPoints() {
      let ip$ = [];
      let n = this.node.assignedSlot;
      while (n) {
        ip$.push(n);
        n = n.assignedSlot;
      }
      return ip$;
    }

    /**
     * Calls `importNode` on the `ownerDocument` for this node.
     *
     * @param {!Node} node Node to import
     * @param {boolean} deep True if the node should be cloned deeply during
     *   import
     * @return {Node} Clone of given node imported to this owner document
     */
    importNode(node, deep) {
      let doc = this.node instanceof Document ? this.node :
        this.node.ownerDocument;
      return doc.importNode(node, deep);
    }

    /**
     * @return {!Array<!Node>} Returns a flattened list of all child nodes and
     * nodes assigned to child slots.
     */
    getEffectiveChildNodes() {
      return Polymer.FlattenedNodesObserver.getFlattenedNodes(this.node);
    }

    /**
     * Returns a filtered list of flattened child elements for this element based
     * on the given selector.
     *
     * @param {string} selector Selector to filter nodes against
     * @return {!Array<!HTMLElement>} List of flattened child elements
     */
    queryDistributedElements(selector) {
      let c$ = this.getEffectiveChildNodes();
      let list = [];
      for (let i=0, l=c$.length, c; (i<l) && (c=c$[i]); i++) {
        if ((c.nodeType === Node.ELEMENT_NODE) &&
            matchesSelector(c, selector)) {
          list.push(c);
        }
      }
      return list;
    }

    /**
     * For shadow roots, returns the currently focused element within this
     * shadow root.
     *
     * @return {Node|undefined} Currently focused element
     */
    get activeElement() {
      let node = this.node;
      return node._activeElement !== undefined ? node._activeElement : node.activeElement;
    }
  }

  function forwardMethods(proto, methods) {
    for (let i=0; i < methods.length; i++) {
      let method = methods[i];
      /* eslint-disable valid-jsdoc */
      proto[method] = /** @this {DomApi} */ function() {
        return this.node[method].apply(this.node, arguments);
      };
      /* eslint-enable */
    }
  }

  function forwardReadOnlyProperties(proto, properties) {
    for (let i=0; i < properties.length; i++) {
      let name = properties[i];
      Object.defineProperty(proto, name, {
        get: function() {
          const domApi = /** @type {DomApi} */(this);
          return domApi.node[name];
        },
        configurable: true
      });
    }
  }

  function forwardProperties(proto, properties) {
    for (let i=0; i < properties.length; i++) {
      let name = properties[i];
      Object.defineProperty(proto, name, {
        get: function() {
          const domApi = /** @type {DomApi} */(this);
          return domApi.node[name];
        },
        set: function(value) {
          /** @type {DomApi} */ (this).node[name] = value;
        },
        configurable: true
      });
    }
  }

  forwardMethods(DomApi.prototype, [
    'cloneNode', 'appendChild', 'insertBefore', 'removeChild',
    'replaceChild', 'setAttribute', 'removeAttribute',
    'querySelector', 'querySelectorAll'
  ]);

  forwardReadOnlyProperties(DomApi.prototype, [
    'parentNode', 'firstChild', 'lastChild',
    'nextSibling', 'previousSibling', 'firstElementChild',
    'lastElementChild', 'nextElementSibling', 'previousElementSibling',
    'childNodes', 'children', 'classList'
  ]);

  forwardProperties(DomApi.prototype, [
    'textContent', 'innerHTML'
  ]);


  /**
   * Event API wrapper class returned from `Polymer.dom.(target)` when
   * `target` is an `Event`.
   */
  class EventApi {
    constructor(event) {
      this.event = event;
    }

    /**
     * Returns the first node on the `composedPath` of this event.
     *
     * @return {!EventTarget} The node this event was dispatched to
     */
    get rootTarget() {
      return this.event.composedPath()[0];
    }

    /**
     * Returns the local (re-targeted) target for this event.
     *
     * @return {!EventTarget} The local (re-targeted) target for this event.
     */
    get localTarget() {
      return this.event.target;
    }

    /**
     * Returns the `composedPath` for this event.
     * @return {!Array<!EventTarget>} The nodes this event propagated through
     */
    get path() {
      return this.event.composedPath();
    }
  }

  Polymer.DomApi = DomApi;

  /**
   * @function
   * @param {boolean=} deep
   * @return {!Node}
   */
  Polymer.DomApi.prototype.cloneNode;
  /**
   * @function
   * @param {!Node} node
   * @return {!Node}
   */
  Polymer.DomApi.prototype.appendChild;
  /**
   * @function
   * @param {!Node} newChild
   * @param {Node} refChild
   * @return {!Node}
   */
  Polymer.DomApi.prototype.insertBefore;
  /**
   * @function
   * @param {!Node} node
   * @return {!Node}
   */
  Polymer.DomApi.prototype.removeChild;
  /**
   * @function
   * @param {!Node} oldChild
   * @param {!Node} newChild
   * @return {!Node}
   */
  Polymer.DomApi.prototype.replaceChild;
  /**
   * @function
   * @param {string} name
   * @param {string} value
   * @return {void}
   */
  Polymer.DomApi.prototype.setAttribute;
  /**
   * @function
   * @param {string} name
   * @return {void}
   */
  Polymer.DomApi.prototype.removeAttribute;
  /**
   * @function
   * @param {string} selector
   * @return {?Element}
   */
  Polymer.DomApi.prototype.querySelector;
  /**
   * @function
   * @param {string} selector
   * @return {!NodeList<!Element>}
   */
  Polymer.DomApi.prototype.querySelectorAll;

  /**
   * Legacy DOM and Event manipulation API wrapper factory used to abstract
   * differences between native Shadow DOM and "Shady DOM" when polyfilling on
   * older browsers.
   *
   * Note that in Polymer 2.x use of `Polymer.dom` is no longer required and
   * in the majority of cases simply facades directly to the standard native
   * API.
   *
   * @namespace
   * @summary Legacy DOM and Event manipulation API wrapper factory used to
   * abstract differences between native Shadow DOM and "Shady DOM."
   * @memberof Polymer
   * @param {(Node|Event)=} obj Node or event to operate on
   * @return {!DomApi|!EventApi} Wrapper providing either node API or event API
   */
  Polymer.dom = function(obj) {
    obj = obj || document;
    if (!obj.__domApi) {
      let helper;
      if (obj instanceof Event) {
        helper = new EventApi(obj);
      } else {
        helper = new DomApi(obj);
      }
      obj.__domApi = helper;
    }
    return obj.__domApi;
  };

  Polymer.dom.matchesSelector = matchesSelector;

  /**
   * Forces several classes of asynchronously queued tasks to flush:
   * - Debouncers added via `Polymer.enqueueDebouncer`
   * - ShadyDOM distribution
   *
   * This method facades to `Polymer.flush`.
   *
   * @memberof Polymer.dom
   */
  Polymer.dom.flush = Polymer.flush;

  /**
   * Adds a `Polymer.Debouncer` to a list of globally flushable tasks.
   *
   * This method facades to `Polymer.enqueueDebouncer`.
   *
   * @memberof Polymer.dom
   * @param {!Polymer.Debouncer} debouncer Debouncer to enqueue
   */
  Polymer.dom.addDebouncer = Polymer.enqueueDebouncer;
})();
</script>
<script>
(function() {

  'use strict';

  let styleInterface = window.ShadyCSS;

  /**
   * Element class mixin that provides Polymer's "legacy" API intended to be
   * backward-compatible to the greatest extent possible with the API
   * found on the Polymer 1.x `Polymer.Base` prototype applied to all elements
   * defined using the `Polymer({...})` function.
   *
   * @mixinFunction
   * @polymer
   * @appliesMixin Polymer.ElementMixin
   * @appliesMixin Polymer.GestureEventListeners
   * @property isAttached {boolean} Set to `true` in this element's
   *   `connectedCallback` and `false` in `disconnectedCallback`
   * @memberof Polymer
   * @summary Element class mixin that provides Polymer's "legacy" API
   */
  Polymer.LegacyElementMixin = Polymer.dedupingMixin((base) => {

    /**
     * @constructor
     * @extends {base}
     * @implements {Polymer_ElementMixin}
     * @implements {Polymer_GestureEventListeners}
     * @implements {Polymer_DirMixin}
     * @private
     */
    const legacyElementBase = Polymer.DirMixin(Polymer.GestureEventListeners(Polymer.ElementMixin(base)));

    /**
     * Map of simple names to touch action names
     * @dict
     */
    const DIRECTION_MAP = {
      'x': 'pan-x',
      'y': 'pan-y',
      'none': 'none',
      'all': 'auto'
    };

    /**
     * @polymer
     * @mixinClass
     * @extends {legacyElementBase}
     * @implements {Polymer_LegacyElementMixin}
     * @unrestricted
     */
    class LegacyElement extends legacyElementBase {

      constructor() {
        super();
        /** @type {boolean} */
        this.isAttached;
        /** @type {WeakMap<!Element, !Object<string, !Function>>} */
        this.__boundListeners;
        /** @type {Object<string, Function>} */
        this._debouncers;
      }

      /**
       * Forwards `importMeta` from the prototype (i.e. from the info object
       * passed to `Polymer({...})`) to the static API.
       *
       * @return {!Object} The `import.meta` object set on the prototype
       * @suppress {missingProperties} `this` is always in the instance in
       *  closure for some reason even in a static method, rather than the class
       */
      static get importMeta() {
        return this.prototype.importMeta;
      }

      /**
       * Legacy callback called during the `constructor`, for overriding
       * by the user.
       * @return {void}
       */
      created() {}

      /**
       * Provides an implementation of `connectedCallback`
       * which adds Polymer legacy API's `attached` method.
       * @return {void}
       * @override
       */
      connectedCallback() {
        super.connectedCallback();
        this.isAttached = true;
        this.attached();
      }

      /**
       * Legacy callback called during `connectedCallback`, for overriding
       * by the user.
       * @return {void}
       */
      attached() {}

      /**
       * Provides an implementation of `disconnectedCallback`
       * which adds Polymer legacy API's `detached` method.
       * @return {void}
       * @override
       */
      disconnectedCallback() {
        super.disconnectedCallback();
        this.isAttached = false;
        this.detached();
      }

      /**
       * Legacy callback called during `disconnectedCallback`, for overriding
       * by the user.
       * @return {void}
       */
      detached() {}

      /**
       * Provides an override implementation of `attributeChangedCallback`
       * which adds the Polymer legacy API's `attributeChanged` method.
       * @param {string} name Name of attribute.
       * @param {?string} old Old value of attribute.
       * @param {?string} value Current value of attribute.
       * @param {?string} namespace Attribute namespace.
       * @return {void}
       * @override
       */
      attributeChangedCallback(name, old, value, namespace) {
        if (old !== value) {
          super.attributeChangedCallback(name, old, value, namespace);
          this.attributeChanged(name, old, value);
        }
      }

      /**
       * Legacy callback called during `attributeChangedChallback`, for overriding
       * by the user.
       * @param {string} name Name of attribute.
       * @param {?string} old Old value of attribute.
       * @param {?string} value Current value of attribute.
       * @return {void}
       */
      attributeChanged(name, old, value) {} // eslint-disable-line no-unused-vars

      /**
       * Overrides the default `Polymer.PropertyEffects` implementation to
       * add support for class initialization via the `_registered` callback.
       * This is called only when the first instance of the element is created.
       *
       * @return {void}
       * @override
       * @suppress {invalidCasts}
       */
      _initializeProperties() {
        let proto = Object.getPrototypeOf(this);
        if (!proto.hasOwnProperty('__hasRegisterFinished')) {
          this._registered();
          // backstop in case the `_registered` implementation does not set this
          proto.__hasRegisterFinished = true;
        }
        super._initializeProperties();
        this.root = /** @type {HTMLElement} */(this);
        this.created();
        // Ensure listeners are applied immediately so that they are
        // added before declarative event listeners. This allows an element to
        // decorate itself via an event prior to any declarative listeners
        // seeing the event. Note, this ensures compatibility with 1.x ordering.
        this._applyListeners();
      }

      /**
       * Called automatically when an element is initializing.
       * Users may override this method to perform class registration time
       * work. The implementation should ensure the work is performed
       * only once for the class.
       * @protected
       * @return {void}
       */
      _registered() {}

      /**
       * Overrides the default `Polymer.PropertyEffects` implementation to
       * add support for installing `hostAttributes` and `listeners`.
       *
       * @return {void}
       * @override
       */
      ready() {
        this._ensureAttributes();
        super.ready();
      }

      /**
       * Ensures an element has required attributes. Called when the element
       * is being readied via `ready`. Users should override to set the
       * element's required attributes. The implementation should be sure
       * to check and not override existing attributes added by
       * the user of the element. Typically, setting attributes should be left
       * to the element user and not done here; reasonable exceptions include
       * setting aria roles and focusability.
       * @protected
       * @return {void}
       */
      _ensureAttributes() {}

      /**
       * Adds element event listeners. Called when the element
       * is being readied via `ready`. Users should override to
       * add any required element event listeners.
       * In performance critical elements, the work done here should be kept
       * to a minimum since it is done before the element is rendered. In
       * these elements, consider adding listeners asynchronously so as not to
       * block render.
       * @protected
       * @return {void}
       */
      _applyListeners() {}

      /**
       * Converts a typed JavaScript value to a string.
       *
       * Note this method is provided as backward-compatible legacy API
       * only.  It is not directly called by any Polymer features. To customize
       * how properties are serialized to attributes for attribute bindings and
       * `reflectToAttribute: true` properties as well as this method, override
       * the `_serializeValue` method provided by `Polymer.PropertyAccessors`.
       *
       * @param {*} value Value to deserialize
       * @return {string | undefined} Serialized value
       */
      serialize(value) {
        return this._serializeValue(value);
      }

      /**
       * Converts a string to a typed JavaScript value.
       *
       * Note this method is provided as backward-compatible legacy API
       * only.  It is not directly called by any Polymer features.  To customize
       * how attributes are deserialized to properties for in
       * `attributeChangedCallback`, override `_deserializeValue` method
       * provided by `Polymer.PropertyAccessors`.
       *
       * @param {string} value String to deserialize
       * @param {*} type Type to deserialize the string to
       * @return {*} Returns the deserialized value in the `type` given.
       */
      deserialize(value, type) {
        return this._deserializeValue(value, type);
      }

      /**
       * Serializes a property to its associated attribute.
       *
       * Note this method is provided as backward-compatible legacy API
       * only.  It is not directly called by any Polymer features.
       *
       * @param {string} property Property name to reflect.
       * @param {string=} attribute Attribute name to reflect.
       * @param {*=} value Property value to reflect.
       * @return {void}
       */
      reflectPropertyToAttribute(property, attribute, value) {
        this._propertyToAttribute(property, attribute, value);
      }

      /**
       * Sets a typed value to an HTML attribute on a node.
       *
       * Note this method is provided as backward-compatible legacy API
       * only.  It is not directly called by any Polymer features.
       *
       * @param {*} value Value to serialize.
       * @param {string} attribute Attribute name to serialize to.
       * @param {Element} node Element to set attribute to.
       * @return {void}
       */
      serializeValueToAttribute(value, attribute, node) {
        this._valueToNodeAttribute(/** @type {Element} */ (node || this), value, attribute);
      }

      /**
       * Copies own properties (including accessor descriptors) from a source
       * object to a target object.
       *
       * @param {Object} prototype Target object to copy properties to.
       * @param {Object} api Source object to copy properties from.
       * @return {Object} prototype object that was passed as first argument.
       */
      extend(prototype, api) {
        if (!(prototype && api)) {
          return prototype || api;
        }
        let n$ = Object.getOwnPropertyNames(api);
        for (let i=0, n; (i<n$.length) && (n=n$[i]); i++) {
          let pd = Object.getOwnPropertyDescriptor(api, n);
          if (pd) {
            Object.defineProperty(prototype, n, pd);
          }
        }
        return prototype;
      }

      /**
       * Copies props from a source object to a target object.
       *
       * Note, this method uses a simple `for...in` strategy for enumerating
       * properties.  To ensure only `ownProperties` are copied from source
       * to target and that accessor implementations are copied, use `extend`.
       *
       * @param {!Object} target Target object to copy properties to.
       * @param {!Object} source Source object to copy properties from.
       * @return {!Object} Target object that was passed as first argument.
       */
      mixin(target, source) {
        for (let i in source) {
          target[i] = source[i];
        }
        return target;
      }

      /**
       * Sets the prototype of an object.
       *
       * Note this method is provided as backward-compatible legacy API
       * only.  It is not directly called by any Polymer features.
       * @param {Object} object The object on which to set the prototype.
       * @param {Object} prototype The prototype that will be set on the given
       * `object`.
       * @return {Object} Returns the given `object` with its prototype set
       * to the given `prototype` object.
       */
      chainObject(object, prototype) {
        if (object && prototype && object !== prototype) {
          object.__proto__ = prototype;
        }
        return object;
      }

      /* **** Begin Template **** */

      /**
       * Calls `importNode` on the `content` of the `template` specified and
       * returns a document fragment containing the imported content.
       *
       * @param {HTMLTemplateElement} template HTML template element to instance.
       * @return {!DocumentFragment} Document fragment containing the imported
       *   template content.
      */
      instanceTemplate(template) {
        let content = this.constructor._contentForTemplate(template);
        let dom = /** @type {!DocumentFragment} */
          (document.importNode(content, true));
        return dom;
      }

      /* **** Begin Events **** */



      /**
       * Dispatches a custom event with an optional detail value.
       *
       * @param {string} type Name of event type.
       * @param {*=} detail Detail value containing event-specific
       *   payload.
       * @param {{ bubbles: (boolean|undefined), cancelable: (boolean|undefined), composed: (boolean|undefined) }=}
       *  options Object specifying options.  These may include:
       *  `bubbles` (boolean, defaults to `true`),
       *  `cancelable` (boolean, defaults to false), and
       *  `node` on which to fire the event (HTMLElement, defaults to `this`).
       * @return {!Event} The new event that was fired.
       */
      fire(type, detail, options) {
        options = options || {};
        detail = (detail === null || detail === undefined) ? {} : detail;
        let event = new Event(type, {
          bubbles: options.bubbles === undefined ? true : options.bubbles,
          cancelable: Boolean(options.cancelable),
          composed: options.composed === undefined ? true: options.composed
        });
        event.detail = detail;
        let node = options.node || this;
        node.dispatchEvent(event);
        return event;
      }

      /**
       * Convenience method to add an event listener on a given element,
       * late bound to a named method on this element.
       *
       * @param {Element} node Element to add event listener to.
       * @param {string} eventName Name of event to listen for.
       * @param {string} methodName Name of handler method on `this` to call.
       * @return {void}
       */
      listen(node, eventName, methodName) {
        node = /** @type {!Element} */ (node || this);
        let hbl = this.__boundListeners ||
          (this.__boundListeners = new WeakMap());
        let bl = hbl.get(node);
        if (!bl) {
          bl = {};
          hbl.set(node, bl);
        }
        let key = eventName + methodName;
        if (!bl[key]) {
          bl[key] = this._addMethodEventListenerToNode(
            node, eventName, methodName, this);
        }
      }

      /**
       * Convenience method to remove an event listener from a given element,
       * late bound to a named method on this element.
       *
       * @param {Element} node Element to remove event listener from.
       * @param {string} eventName Name of event to stop listening to.
       * @param {string} methodName Name of handler method on `this` to not call
       anymore.
       * @return {void}
       */
      unlisten(node, eventName, methodName) {
        node = /** @type {!Element} */ (node || this);
        let bl = this.__boundListeners && this.__boundListeners.get(node);
        let key = eventName + methodName;
        let handler = bl && bl[key];
        if (handler) {
          this._removeEventListenerFromNode(node, eventName, handler);
          bl[key] = null;
        }
      }

      /**
       * Override scrolling behavior to all direction, one direction, or none.
       *
       * Valid scroll directions:
       *   - 'all': scroll in any direction
       *   - 'x': scroll only in the 'x' direction
       *   - 'y': scroll only in the 'y' direction
       *   - 'none': disable scrolling for this node
       *
       * @param {string=} direction Direction to allow scrolling
       * Defaults to `all`.
       * @param {Element=} node Element to apply scroll direction setting.
       * Defaults to `this`.
       * @return {void}
       */
      setScrollDirection(direction, node) {
        Polymer.Gestures.setTouchAction(/** @type {Element} */ (node || this), DIRECTION_MAP[direction] || 'auto');
      }
      /* **** End Events **** */

      /**
       * Convenience method to run `querySelector` on this local DOM scope.
       *
       * This function calls `Polymer.dom(this.root).querySelector(slctr)`.
       *
       * @param {string} slctr Selector to run on this local DOM scope
       * @return {Element} Element found by the selector, or null if not found.
       */
      $$(slctr) {
        return this.root.querySelector(slctr);
      }

      /**
       * Return the element whose local dom within which this element
       * is contained. This is a shorthand for
       * `this.getRootNode().host`.
       * @this {Element}
       */
      get domHost() {
        let root = this.getRootNode();
        return (root instanceof DocumentFragment) ? /** @type {ShadowRoot} */ (root).host : root;
      }

      /**
       * Force this element to distribute its children to its local dom.
       * This should not be necessary as of Polymer 2.0.2 and is provided only
       * for backwards compatibility.
       * @return {void}
       */
      distributeContent() {
        if (window.ShadyDOM && this.shadowRoot) {
          ShadyDOM.flush();
        }
      }

      /**
       * Returns a list of nodes that are the effective childNodes. The effective
       * childNodes list is the same as the element's childNodes except that
       * any `<content>` elements are replaced with the list of nodes distributed
       * to the `<content>`, the result of its `getDistributedNodes` method.
       * @return {!Array<!Node>} List of effective child nodes.
       * @suppress {invalidCasts} LegacyElementMixin must be applied to an HTMLElement
       */
      getEffectiveChildNodes() {
        const thisEl = /** @type {Element} */ (this);
        const domApi = /** @type {Polymer.DomApi} */(Polymer.dom(thisEl));
        return domApi.getEffectiveChildNodes();
      }

      /**
       * Returns a list of nodes distributed within this element that match
       * `selector`. These can be dom children or elements distributed to
       * children that are insertion points.
       * @param {string} selector Selector to run.
       * @return {!Array<!Node>} List of distributed elements that match selector.
       * @suppress {invalidCasts} LegacyElementMixin must be applied to an HTMLElement
       */
      queryDistributedElements(selector) {
        const thisEl = /** @type {Element} */ (this);
        const domApi = /** @type {Polymer.DomApi} */(Polymer.dom(thisEl));
        return domApi.queryDistributedElements(selector);
      }

      /**
       * Returns a list of elements that are the effective children. The effective
       * children list is the same as the element's children except that
       * any `<content>` elements are replaced with the list of elements
       * distributed to the `<content>`.
       *
       * @return {!Array<!Node>} List of effective children.
       */
      getEffectiveChildren() {
        let list = this.getEffectiveChildNodes();
        return list.filter(function(/** @type {!Node} */ n) {
          return (n.nodeType === Node.ELEMENT_NODE);
        });
      }

      /**
       * Returns a string of text content that is the concatenation of the
       * text content's of the element's effective childNodes (the elements
       * returned by <a href="#getEffectiveChildNodes>getEffectiveChildNodes</a>.
       *
       * @return {string} List of effective children.
       */
      getEffectiveTextContent() {
        let cn = this.getEffectiveChildNodes();
        let tc = [];
        for (let i=0, c; (c = cn[i]); i++) {
          if (c.nodeType !== Node.COMMENT_NODE) {
            tc.push(c.textContent);
          }
        }
        return tc.join('');
      }

      /**
       * Returns the first effective childNode within this element that
       * match `selector`. These can be dom child nodes or elements distributed
       * to children that are insertion points.
       * @param {string} selector Selector to run.
       * @return {Node} First effective child node that matches selector.
       */
      queryEffectiveChildren(selector) {
        let e$ = this.queryDistributedElements(selector);
        return e$ && e$[0];
      }

      /**
       * Returns a list of effective childNodes within this element that
       * match `selector`. These can be dom child nodes or elements distributed
       * to children that are insertion points.
       * @param {string} selector Selector to run.
       * @return {!Array<!Node>} List of effective child nodes that match selector.
       */
      queryAllEffectiveChildren(selector) {
        return this.queryDistributedElements(selector);
      }

      /**
       * Returns a list of nodes distributed to this element's `<slot>`.
       *
       * If this element contains more than one `<slot>` in its local DOM,
       * an optional selector may be passed to choose the desired content.
       *
       * @param {string=} slctr CSS selector to choose the desired
       *   `<slot>`.  Defaults to `content`.
       * @return {!Array<!Node>} List of distributed nodes for the `<slot>`.
       */
      getContentChildNodes(slctr) {
        let content = this.root.querySelector(slctr || 'slot');
        return content ? /** @type {Polymer.DomApi} */(Polymer.dom(content)).getDistributedNodes() : [];
      }

      /**
       * Returns a list of element children distributed to this element's
       * `<slot>`.
       *
       * If this element contains more than one `<slot>` in its
       * local DOM, an optional selector may be passed to choose the desired
       * content.  This method differs from `getContentChildNodes` in that only
       * elements are returned.
       *
       * @param {string=} slctr CSS selector to choose the desired
       *   `<content>`.  Defaults to `content`.
       * @return {!Array<!HTMLElement>} List of distributed nodes for the
       *   `<slot>`.
       * @suppress {invalidCasts}
       */
      getContentChildren(slctr) {
        let children = /** @type {!Array<!HTMLElement>} */(this.getContentChildNodes(slctr).filter(function(n) {
          return (n.nodeType === Node.ELEMENT_NODE);
        }));
        return children;
      }

      /**
       * Checks whether an element is in this element's light DOM tree.
       *
       * @param {?Node} node The element to be checked.
       * @return {boolean} true if node is in this element's light DOM tree.
       * @suppress {invalidCasts} LegacyElementMixin must be applied to an HTMLElement
       */
      isLightDescendant(node) {
        const thisNode = /** @type {Node} */ (this);
        return thisNode !== node && thisNode.contains(node) &&
          thisNode.getRootNode() === node.getRootNode();
      }

      /**
       * Checks whether an element is in this element's local DOM tree.
       *
       * @param {!Element} node The element to be checked.
       * @return {boolean} true if node is in this element's local DOM tree.
       */
      isLocalDescendant(node) {
        return this.root === node.getRootNode();
      }

      /**
       * No-op for backwards compatibility. This should now be handled by
       * ShadyCss library.
       * @param  {*} container Unused
       * @param  {*} shouldObserve Unused
       * @return {void}
       */
      scopeSubtree(container, shouldObserve) { // eslint-disable-line no-unused-vars
      }

      /**
       * Returns the computed style value for the given property.
       * @param {string} property The css property name.
       * @return {string} Returns the computed css property value for the given
       * `property`.
       * @suppress {invalidCasts} LegacyElementMixin must be applied to an HTMLElement
       */
      getComputedStyleValue(property) {
        return styleInterface.getComputedStyleValue(/** @type {!Element} */(this), property);
      }

      // debounce

      /**
       * Call `debounce` to collapse multiple requests for a named task into
       * one invocation which is made after the wait time has elapsed with
       * no new request.  If no wait time is given, the callback will be called
       * at microtask timing (guaranteed before paint).
       *
       *     debouncedClickAction(e) {
       *       // will not call `processClick` more than once per 100ms
       *       this.debounce('click', function() {
       *        this.processClick();
       *       } 100);
       *     }
       *
       * @param {string} jobName String to identify the debounce job.
       * @param {function():void} callback Function that is called (with `this`
       *   context) when the wait time elapses.
       * @param {number} wait Optional wait time in milliseconds (ms) after the
       *   last signal that must elapse before invoking `callback`
       * @return {!Object} Returns a debouncer object on which exists the
       * following methods: `isActive()` returns true if the debouncer is
       * active; `cancel()` cancels the debouncer if it is active;
       * `flush()` immediately invokes the debounced callback if the debouncer
       * is active.
       */
      debounce(jobName, callback, wait) {
        this._debouncers = this._debouncers || {};
        return this._debouncers[jobName] = Polymer.Debouncer.debounce(
              this._debouncers[jobName]
            , wait > 0 ? Polymer.Async.timeOut.after(wait) : Polymer.Async.microTask
            , callback.bind(this));
      }

      /**
       * Returns whether a named debouncer is active.
       *
       * @param {string} jobName The name of the debouncer started with `debounce`
       * @return {boolean} Whether the debouncer is active (has not yet fired).
       */
      isDebouncerActive(jobName) {
        this._debouncers = this._debouncers || {};
        let debouncer = this._debouncers[jobName];
        return !!(debouncer && debouncer.isActive());
      }

      /**
       * Immediately calls the debouncer `callback` and inactivates it.
       *
       * @param {string} jobName The name of the debouncer started with `debounce`
       * @return {void}
       */
      flushDebouncer(jobName) {
        this._debouncers = this._debouncers || {};
        let debouncer = this._debouncers[jobName];
        if (debouncer) {
          debouncer.flush();
        }
      }

      /**
       * Cancels an active debouncer.  The `callback` will not be called.
       *
       * @param {string} jobName The name of the debouncer started with `debounce`
       * @return {void}
       */
      cancelDebouncer(jobName) {
        this._debouncers = this._debouncers || {};
        let debouncer = this._debouncers[jobName];
        if (debouncer) {
          debouncer.cancel();
        }
      }

      /**
       * Runs a callback function asynchronously.
       *
       * By default (if no waitTime is specified), async callbacks are run at
       * microtask timing, which will occur before paint.
       *
       * @param {!Function} callback The callback function to run, bound to `this`.
       * @param {number=} waitTime Time to wait before calling the
       *   `callback`.  If unspecified or 0, the callback will be run at microtask
       *   timing (before paint).
       * @return {number} Handle that may be used to cancel the async job.
       */
      async(callback, waitTime) {
        return waitTime > 0 ? Polymer.Async.timeOut.run(callback.bind(this), waitTime) :
            ~Polymer.Async.microTask.run(callback.bind(this));
      }

      /**
       * Cancels an async operation started with `async`.
       *
       * @param {number} handle Handle returned from original `async` call to
       *   cancel.
       * @return {void}
       */
      cancelAsync(handle) {
        handle < 0 ? Polymer.Async.microTask.cancel(~handle) :
            Polymer.Async.timeOut.cancel(handle);
      }

      // other

      /**
       * Convenience method for creating an element and configuring it.
       *
       * @param {string} tag HTML element tag to create.
       * @param {Object=} props Object of properties to configure on the
       *    instance.
       * @return {!Element} Newly created and configured element.
       */
      create(tag, props) {
        let elt = document.createElement(tag);
        if (props) {
          if (elt.setProperties) {
            elt.setProperties(props);
          } else {
            for (let n in props) {
              elt[n] = props[n];
            }
          }
        }
        return elt;
      }

      /**
       * Convenience method for importing an HTML document imperatively.
       *
       * This method creates a new `<link rel="import">` element with
       * the provided URL and appends it to the document to start loading.
       * In the `onload` callback, the `import` property of the `link`
       * element will contain the imported document contents.
       *
       * @param {string} href URL to document to load.
       * @param {?function(!Event):void=} onload Callback to notify when an import successfully
       *   loaded.
       * @param {?function(!ErrorEvent):void=} onerror Callback to notify when an import
       *   unsuccessfully loaded.
       * @param {boolean=} optAsync True if the import should be loaded `async`.
       *   Defaults to `false`.
       * @return {!HTMLLinkElement} The link element for the URL to be loaded.
       */
      importHref(href, onload, onerror, optAsync) { // eslint-disable-line no-unused-vars
        let loadFn = onload ? onload.bind(this) : null;
        let errorFn = onerror ? onerror.bind(this) : null;
        return Polymer.importHref(href, loadFn, errorFn, optAsync);
      }

      /**
       * Polyfill for Element.prototype.matches, which is sometimes still
       * prefixed.
       *
       * @param {string} selector Selector to test.
       * @param {!Element=} node Element to test the selector against.
       * @return {boolean} Whether the element matches the selector.
       */
      elementMatches(selector, node) {
        return Polymer.dom.matchesSelector(/** @type {!Element} */ (node || this), selector);
      }

      /**
       * Toggles an HTML attribute on or off.
       *
       * @param {string} name HTML attribute name
       * @param {boolean=} bool Boolean to force the attribute on or off.
       *    When unspecified, the state of the attribute will be reversed.
       * @param {Element=} node Node to target.  Defaults to `this`.
       * @return {void}
       */
      toggleAttribute(name, bool, node) {
        node = /** @type {Element} */ (node || this);
        if (arguments.length == 1) {
          bool = !node.hasAttribute(name);
        }
        if (bool) {
          node.setAttribute(name, '');
        } else {
          node.removeAttribute(name);
        }
      }


      /**
       * Toggles a CSS class on or off.
       *
       * @param {string} name CSS class name
       * @param {boolean=} bool Boolean to force the class on or off.
       *    When unspecified, the state of the class will be reversed.
       * @param {Element=} node Node to target.  Defaults to `this`.
       * @return {void}
       */
      toggleClass(name, bool, node) {
        node = /** @type {Element} */ (node || this);
        if (arguments.length == 1) {
          bool = !node.classList.contains(name);
        }
        if (bool) {
          node.classList.add(name);
        } else {
          node.classList.remove(name);
        }
      }

      /**
       * Cross-platform helper for setting an element's CSS `transform` property.
       *
       * @param {string} transformText Transform setting.
       * @param {Element=} node Element to apply the transform to.
       * Defaults to `this`
       * @return {void}
       */
      transform(transformText, node) {
        node = /** @type {Element} */ (node || this);
        node.style.webkitTransform = transformText;
        node.style.transform = transformText;
      }

      /**
       * Cross-platform helper for setting an element's CSS `translate3d`
       * property.
       *
       * @param {number} x X offset.
       * @param {number} y Y offset.
       * @param {number} z Z offset.
       * @param {Element=} node Element to apply the transform to.
       * Defaults to `this`.
       * @return {void}
       */
      translate3d(x, y, z, node) {
        node = /** @type {Element} */ (node || this);
        this.transform('translate3d(' + x + ',' + y + ',' + z + ')', node);
      }

      /**
       * Removes an item from an array, if it exists.
       *
       * If the array is specified by path, a change notification is
       * generated, so that observers, data bindings and computed
       * properties watching that path can update.
       *
       * If the array is passed directly, **no change
       * notification is generated**.
       *
       * @param {string | !Array<number|string>} arrayOrPath Path to array from which to remove the item
       *   (or the array itself).
       * @param {*} item Item to remove.
       * @return {Array} Array containing item removed.
       */
      arrayDelete(arrayOrPath, item) {
        let index;
        if (Array.isArray(arrayOrPath)) {
          index = arrayOrPath.indexOf(item);
          if (index >= 0) {
            return arrayOrPath.splice(index, 1);
          }
        } else {
          let arr = Polymer.Path.get(this, arrayOrPath);
          index = arr.indexOf(item);
          if (index >= 0) {
            return this.splice(arrayOrPath, index, 1);
          }
        }
        return null;
      }

      // logging

      /**
       * Facades `console.log`/`warn`/`error` as override point.
       *
       * @param {string} level One of 'log', 'warn', 'error'
       * @param {Array} args Array of strings or objects to log
       * @return {void}
       */
      _logger(level, args) {
        // accept ['foo', 'bar'] and [['foo', 'bar']]
        if (Array.isArray(args) && args.length === 1 && Array.isArray(args[0])) {
          args = args[0];
        }
        switch(level) {
          case 'log':
          case 'warn':
          case 'error':
            console[level](...args);
        }
      }

      /**
       * Facades `console.log` as an override point.
       *
       * @param {...*} args Array of strings or objects to log
       * @return {void}
       */
      _log(...args) {
        this._logger('log', args);
      }

      /**
       * Facades `console.warn` as an override point.
       *
       * @param {...*} args Array of strings or objects to log
       * @return {void}
       */
      _warn(...args) {
        this._logger('warn', args);
      }

      /**
       * Facades `console.error` as an override point.
       *
       * @param {...*} args Array of strings or objects to log
       * @return {void}
       */
      _error(...args) {
        this._logger('error', args);
      }

      /**
       * Formats a message using the element type an a method name.
       *
       * @param {string} methodName Method name to associate with message
       * @param {...*} args Array of strings or objects to log
       * @return {Array} Array with formatting information for `console`
       *   logging.
       */
      _logf(methodName, ...args) {
        return ['[%s::%s]', this.is, methodName, ...args];
      }

    }

    LegacyElement.prototype.is = '';

    return LegacyElement;

  });

})();
</script>
<script>

  (function() {

    'use strict';

    const lifecycleProps = {
      attached: true,
      detached: true,
      ready: true,
      created: true,
      beforeRegister: true,
      registered: true,
      attributeChanged: true,
      listeners: true,
      hostAttributes: true
    };

    const excludeOnInfo = {
      attached: true,
      detached: true,
      ready: true,
      created: true,
      beforeRegister: true,
      registered: true,
      attributeChanged: true,
      behaviors: true,
      _noAccessors: true
    };

    const excludeOnBehaviors = Object.assign({
      listeners: true,
      hostAttributes: true,
      properties: true,
      observers: true,
    }, excludeOnInfo);

    function copyProperties(source, target, excludeProps) {
      const noAccessors = source._noAccessors;
      const propertyNames = Object.getOwnPropertyNames(source);
      for (let i = 0; i < propertyNames.length; i++) {
        let p = propertyNames[i];
        if (p in excludeProps) {
          continue;
        }
        if (noAccessors) {
          target[p] = source[p];
        } else {
          let pd = Object.getOwnPropertyDescriptor(source, p);
          if (pd) {
            // ensure property is configurable so that a later behavior can
            // re-configure it.
            pd.configurable = true;
            Object.defineProperty(target, p, pd);
          }
        }
      }
    }

    /**
     * Applies a "legacy" behavior or array of behaviors to the provided class.
     *
     * Note: this method will automatically also apply the `Polymer.LegacyElementMixin`
     * to ensure that any legacy behaviors can rely on legacy Polymer API on
     * the underlying element.
     *
     * @template T
     * @param {!Object|!Array<!Object>} behaviors Behavior object or array of behaviors.
     * @param {function(new:T)} klass Element class.
     * @return {function(new:T)} Returns a new Element class extended by the
     * passed in `behaviors` and also by `Polymer.LegacyElementMixin`.
     * @memberof Polymer
     * @suppress {invalidCasts, checkTypes}
     */
    function mixinBehaviors(behaviors, klass) {
      return GenerateClassFromInfo({}, Polymer.LegacyElementMixin(klass), behaviors);
    }

    // NOTE:
    // 1.x
    // Behaviors were mixed in *in reverse order* and de-duped on the fly.
    // The rule was that behavior properties were copied onto the element
    // prototype if and only if the property did not already exist.
    // Given: Polymer{ behaviors: [A, B, C, A, B]}, property copy order was:
    // (1), B, (2), A, (3) C. This means prototype properties win over
    // B properties win over A win over C. This mirrors what would happen
    // with inheritance if element extended B extended A extended C.
    //
    // Again given, Polymer{ behaviors: [A, B, C, A, B]}, the resulting
    // `behaviors` array was [C, A, B].
    // Behavior lifecycle methods were called in behavior array order
    // followed by the element, e.g. (1) C.created, (2) A.created,
    // (3) B.created, (4) element.created. There was no support for
    // super, and "super-behavior" methods were callable only by name).
    //
    // 2.x
    // Behaviors are made into proper mixins which live in the
    // element's prototype chain. Behaviors are placed in the element prototype
    // eldest to youngest and de-duped youngest to oldest:
    // So, first [A, B, C, A, B] becomes [C, A, B] then,
    // the element prototype becomes (oldest) (1) Polymer.Element, (2) class(C),
    // (3) class(A), (4) class(B), (5) class(Polymer({...})).
    // Result:
    // This means element properties win over B properties win over A win
    // over C. (same as 1.x)
    // If lifecycle is called (super then me), order is
    // (1) C.created, (2) A.created, (3) B.created, (4) element.created
    // (again same as 1.x)
    function applyBehaviors(proto, behaviors, lifecycle) {
      for (let i=0; i<behaviors.length; i++) {
        applyInfo(proto, behaviors[i], lifecycle, excludeOnBehaviors);
      }
    }

    function applyInfo(proto, info, lifecycle, excludeProps) {
      copyProperties(info, proto, excludeProps);
      for (let p in lifecycleProps) {
        if (info[p]) {
          lifecycle[p] = lifecycle[p] || [];
          lifecycle[p].push(info[p]);
        }
      }
    }

    /**
     * @param {Array} behaviors List of behaviors to flatten.
     * @param {Array=} list Target list to flatten behaviors into.
     * @param {Array=} exclude List of behaviors to exclude from the list.
     * @return {!Array} Returns the list of flattened behaviors.
     */
    function flattenBehaviors(behaviors, list, exclude) {
      list = list || [];
      for (let i=behaviors.length-1; i >= 0; i--) {
        let b = behaviors[i];
        if (b) {
          if (Array.isArray(b)) {
            flattenBehaviors(b, list);
          } else {
            // dedup
            if (list.indexOf(b) < 0 && (!exclude || exclude.indexOf(b) < 0)) {
              list.unshift(b);
            }
          }
        } else {
          console.warn('behavior is null, check for missing or 404 import');
        }
      }
      return list;
    }

    /* Note about construction and extension of legacy classes.
      [Changed in Q4 2018 to optimize performance.]

      When calling `Polymer` or `mixinBehaviors`, the generated class below is
      made. The list of behaviors was previously made into one generated class per
      behavior, but this is no longer the case as behaviors are now called
      manually. Note, there may *still* be multiple generated classes in the
      element's prototype chain if extension is used with `mixinBehaviors`.

      The generated class is directly tied to the info object and behaviors
      used to create it. That list of behaviors is filtered so it's only the
      behaviors not active on the superclass. In order to call through to the
      entire list of lifecycle methods, it's important to call `super`.

      The element's `properties` and `observers` are controlled via the finalization
      mechanism provided by `PropertiesMixin`. `Properties` and `observers` are
      collected by manually traversing the prototype chain and merging.

      To limit changes, the `_registered` method is called via `_initializeProperties`
      and not `_finalizeClass`.
    */
    /**
     * @param {!PolymerInit} info Polymer info object
     * @param {function(new:HTMLElement)} Base base class to extend with info object
     * @param {Object} behaviors behaviors to copy into the element
     * @return {function(new:HTMLElement)} Generated class
     * @suppress {checkTypes}
     * @private
     */
    function GenerateClassFromInfo(info, Base, behaviors) {

      // manages behavior and lifecycle processing (filled in after class definition)
      let behaviorList;
      const lifecycle = {};

      /** @private */
      class PolymerGenerated extends Base {

        // explicitly not calling super._finalizeClass
        static _finalizeClass() {
          // if calling via a subclass that hasn't been generated, pass through to super
          if (!this.hasOwnProperty(window.JSCompiler_renameProperty('generatedFrom', this))) {
            super._finalizeClass();
          } else {
            // interleave properties and observers per behavior and `info`
            if (behaviorList) {
              for (let i=0, b; i < behaviorList.length; i++) {
                b = behaviorList[i];
                if (b.properties) {
                  this.createProperties(b.properties);
                }
                if (b.observers) {
                  this.createObservers(b.observers, b.properties);
                }
              }
            }
            if (info.properties) {
              this.createProperties(info.properties);
            }
            if (info.observers) {
              this.createObservers(info.observers, info.properties);
            }
            // make sure to prepare the element template
            this._prepareTemplate();
          }
        }

        static get properties() {
          const properties = {};
          if (behaviorList) {
            for (let i=0; i < behaviorList.length; i++) {
              Object.assign(properties, behaviorList[i].properties);
            }
          }
          Object.assign(properties, info.properties);
          return properties;
        }

        static get observers() {
          let observers = [];
          if (behaviorList) {
            for (let i=0, b; i < behaviorList.length; i++) {
              b = behaviorList[i];
              if (b.observers) {
                observers = observers.concat(b.observers);
              }
            }
          }
          if (info.observers) {
            observers = observers.concat(info.observers);
          }
          return observers;
        }

        /**
         * @return {void}
         */
        created() {
          super.created();
          const list = lifecycle.created;
          if (list) {
            for (let i=0; i < list.length; i++) {
              list[i].call(this);
            }
          }
        }

        /**
         * @return {void}
         */
        _registered() {
          /* NOTE: `beforeRegister` is called here for bc, but the behavior
            is different than in 1.x. In 1.0, the method was called *after*
            mixing prototypes together but *before* processing of meta-objects.
            However, dynamic effects can still be set here and can be done either
            in `beforeRegister` or `registered`. It is no longer possible to set
            `is` in `beforeRegister` as you could in 1.x.
          */
          // only proceed if the generated class' prototype has not been registered.
          const generatedProto = PolymerGenerated.prototype;
          if (!generatedProto.hasOwnProperty('__hasRegisterFinished')) {
            generatedProto.__hasRegisterFinished = true;
            // ensure superclass is registered first.
            super._registered();
            // copy properties onto the generated class lazily if we're optimizing,
            if (Polymer.legacyOptimizations) {
              copyPropertiesToProto(generatedProto);
            }
            // make sure legacy lifecycle is called on the *element*'s prototype
            // and not the generated class prototype; if the element has been
            // extended, these are *not* the same.
            const proto = Object.getPrototypeOf(this);
            let list = lifecycle.beforeRegister;
            if (list) {
              for (let i=0; i < list.length; i++) {
                list[i].call(proto);
              }
            }
            list = lifecycle.registered;
            if (list) {
              for (let i=0; i < list.length; i++) {
                list[i].call(proto);
              }
            }
          }
        }

        /**
         * @return {void}
         */
        _applyListeners() {
          super._applyListeners();
          const list = lifecycle.listeners;
          if (list) {
            for (let i=0; i < list.length; i++) {
              const listeners = list[i];
              if (listeners) {
                for (let l in listeners) {
                  this._addMethodEventListenerToNode(this, l, listeners[l]);
                }
              }
            }
          }
        }

        // note: exception to "super then me" rule;
        // do work before calling super so that super attributes
        // only apply if not already set.
        /**
         * @return {void}
         */
        _ensureAttributes() {
          const list = lifecycle.hostAttributes;
          if (list) {
            for (let i=list.length-1; i >= 0; i--) {
              const hostAttributes = list[i];
              for (let a in hostAttributes) {
                  this._ensureAttribute(a, hostAttributes[a]);
                }
            }
          }
          super._ensureAttributes();
        }

        /**
         * @return {void}
         */
        ready() {
          super.ready();
          let list = lifecycle.ready;
          if (list) {
            for (let i=0; i < list.length; i++) {
              list[i].call(this);
            }
          }
        }

        /**
         * @return {void}
         */
        attached() {
          super.attached();
          let list = lifecycle.attached;
          if (list) {
            for (let i=0; i < list.length; i++) {
              list[i].call(this);
            }
          }
        }

        /**
         * @return {void}
         */
        detached() {
          super.detached();
          let list = lifecycle.detached;
          if (list) {
            for (let i=0; i < list.length; i++) {
              list[i].call(this);
            }
          }
        }

        /**
         * Implements native Custom Elements `attributeChangedCallback` to
         * set an attribute value to a property via `_attributeToProperty`.
         *
         * @param {string} name Name of attribute that changed
         * @param {?string} old Old attribute value
         * @param {?string} value New attribute value
         * @return {void}
         */
        attributeChanged(name, old, value) {
          super.attributeChanged();
          let list = lifecycle.attributeChanged;
          if (list) {
            for (let i=0; i < list.length; i++) {
              list[i].call(this, name, old, value);
            }
          }
        }
      }

      // apply behaviors, note actual copying is done lazily at first instance creation
      if (behaviors) {
        // NOTE: ensure the behavior is extending a class with
        // legacy element api. This is necessary since behaviors expect to be able
        // to access 1.x legacy api.
        if (!Array.isArray(behaviors)) {
          behaviors = [behaviors];
        }
        let superBehaviors = Base.prototype.behaviors;
        // get flattened, deduped list of behaviors *not* already on super class
        behaviorList = flattenBehaviors(behaviors, null, superBehaviors);
        PolymerGenerated.prototype.behaviors = superBehaviors ?
          superBehaviors.concat(behaviors) : behaviorList;
      }

      const copyPropertiesToProto = (proto) => {
        if (behaviorList) {
          applyBehaviors(proto, behaviorList, lifecycle);
        }
        applyInfo(proto, info, lifecycle, excludeOnInfo);
      };

      // copy properties if we're not optimizing
      if (!Polymer.legacyOptimizations) {
        copyPropertiesToProto(PolymerGenerated.prototype);
      }

      PolymerGenerated.generatedFrom = info;

      return PolymerGenerated;
    }

    /**
     * Generates a class that extends `Polymer.LegacyElement` based on the
     * provided info object.  Metadata objects on the `info` object
     * (`properties`, `observers`, `listeners`, `behaviors`, `is`) are used
     * for Polymer's meta-programming systems, and any functions are copied
     * to the generated class.
     *
     * Valid "metadata" values are as follows:
     *
     * `is`: String providing the tag name to register the element under. In
     * addition, if a `dom-module` with the same id exists, the first template
     * in that `dom-module` will be stamped into the shadow root of this element,
     * with support for declarative event listeners (`on-...`), Polymer data
     * bindings (`[[...]]` and `{{...}}`), and id-based node finding into
     * `this.$`.
     *
     * `properties`: Object describing property-related metadata used by Polymer
     * features (key: property names, value: object containing property metadata).
     * Valid keys in per-property metadata include:
     * - `type` (String|Number|Object|Array|...): Used by
     *   `attributeChangedCallback` to determine how string-based attributes
     *   are deserialized to JavaScript property values.
     * - `notify` (boolean): Causes a change in the property to fire a
     *   non-bubbling event called `<property>-changed`. Elements that have
     *   enabled two-way binding to the property use this event to observe changes.
     * - `readOnly` (boolean): Creates a getter for the property, but no setter.
     *   To set a read-only property, use the private setter method
     *   `_setProperty(property, value)`.
     * - `observer` (string): Observer method name that will be called when
     *   the property changes. The arguments of the method are
     *   `(value, previousValue)`.
     * - `computed` (string): String describing method and dependent properties
     *   for computing the value of this property (e.g. `'computeFoo(bar, zot)'`).
     *   Computed properties are read-only by default and can only be changed
     *   via the return value of the computing method.
     *
     * `observers`: Array of strings describing multi-property observer methods
     *  and their dependent properties (e.g. `'observeABC(a, b, c)'`).
     *
     * `listeners`: Object describing event listeners to be added to each
     *  instance of this element (key: event name, value: method name).
     *
     * `behaviors`: Array of additional `info` objects containing metadata
     * and callbacks in the same format as the `info` object here which are
     * merged into this element.
     *
     * `hostAttributes`: Object listing attributes to be applied to the host
     *  once created (key: attribute name, value: attribute value).  Values
     *  are serialized based on the type of the value.  Host attributes should
     *  generally be limited to attributes such as `tabIndex` and `aria-...`.
     *  Attributes in `hostAttributes` are only applied if a user-supplied
     *  attribute is not already present (attributes in markup override
     *  `hostAttributes`).
     *
     * In addition, the following Polymer-specific callbacks may be provided:
     * - `registered`: called after first instance of this element,
     * - `created`: called during `constructor`
     * - `attached`: called during `connectedCallback`
     * - `detached`: called during `disconnectedCallback`
     * - `ready`: called before first `attached`, after all properties of
     *   this element have been propagated to its template and all observers
     *   have run
     *
     * @param {!PolymerInit} info Object containing Polymer metadata and functions
     *   to become class methods.
     * @template T
     * @param {function(T):T} mixin Optional mixin to apply to legacy base class
     *   before extending with Polymer metaprogramming.
     * @return {function(new:HTMLElement)} Generated class
     * @memberof Polymer
     */
    Polymer.Class = function(info, mixin) {
      if (!info) {
        console.warn('Polymer.Class requires `info` argument');
      }
      let klass = mixin ? mixin(Polymer.LegacyElementMixin(HTMLElement)) :
          Polymer.LegacyElementMixin(HTMLElement);
      klass = GenerateClassFromInfo(info, klass, info.behaviors);
      if (info._enableDisableUpgrade) {
        klass = Polymer.DisableUpgradeMixin(klass);
      }
      // decorate klass with registration info
      klass.is = klass.prototype.is = info.is;
      return klass;
    };

    Polymer.mixinBehaviors = mixinBehaviors;

  })();

</script>
<script>

  (function() {
    'use strict';

    /**
     * Legacy class factory and registration helper for defining Polymer
     * elements.
     *
     * This method is equivalent to
     * `customElements.define(info.is, Polymer.Class(info));`
     *
     * See `Polymer.Class` for details on valid legacy metadata format for `info`.
     *
     * @global
     * @override
     * @function Polymer
     * @param {!PolymerInit} info Object containing Polymer metadata and functions
     *   to become class methods.
     * @return {function(new: HTMLElement)} Generated class
     * @suppress {duplicate, invalidCasts, checkTypes}
     */
    window.Polymer._polymerFn = function(info) {
      // if input is a `class` (aka a function with a prototype), use the prototype
      // remember that the `constructor` will never be called
      let klass;
      if (typeof info === 'function') {
        klass = info;
      } else {
        klass = Polymer.Class(info);
      }
      customElements.define(klass.is, /** @type {!HTMLElement} */(klass));
      return klass;
    };

  })();

</script>
<script>
(function() {
  'use strict';

  // Common implementation for mixin & behavior
  function mutablePropertyChange(inst, property, value, old, mutableData) {
    let isObject;
    if (mutableData) {
      isObject = (typeof value === 'object' && value !== null);
      // Pull `old` for Objects from temp cache, but treat `null` as a primitive
      if (isObject) {
        old = inst.__dataTemp[property];
      }
    }
    // Strict equality check, but return false for NaN===NaN
    let shouldChange = (old !== value && (old === old || value === value));
    // Objects are stored in temporary cache (cleared at end of
    // turn), which is used for dirty-checking
    if (isObject && shouldChange) {
      inst.__dataTemp[property] = value;
    }
    return shouldChange;
  }

  /**
   * Element class mixin to skip strict dirty-checking for objects and arrays
   * (always consider them to be "dirty"), for use on elements utilizing
   * `Polymer.PropertyEffects`
   *
   * By default, `Polymer.PropertyEffects` performs strict dirty checking on
   * objects, which means that any deep modifications to an object or array will
   * not be propagated unless "immutable" data patterns are used (i.e. all object
   * references from the root to the mutation were changed).
   *
   * Polymer also provides a proprietary data mutation and path notification API
   * (e.g. `notifyPath`, `set`, and array mutation API's) that allow efficient
   * mutation and notification of deep changes in an object graph to all elements
   * bound to the same object graph.
   *
   * In cases where neither immutable patterns nor the data mutation API can be
   * used, applying this mixin will cause Polymer to skip dirty checking for
   * objects and arrays (always consider them to be "dirty").  This allows a
   * user to make a deep modification to a bound object graph, and then either
   * simply re-set the object (e.g. `this.items = this.items`) or call `notifyPath`
   * (e.g. `this.notifyPath('items')`) to update the tree.  Note that all
   * elements that wish to be updated based on deep mutations must apply this
   * mixin or otherwise skip strict dirty checking for objects/arrays.
   * Specifically, any elements in the binding tree between the source of a
   * mutation and the consumption of it must apply this mixin or enable the
   * `Polymer.OptionalMutableData` mixin.
   *
   * In order to make the dirty check strategy configurable, see
   * `Polymer.OptionalMutableData`.
   *
   * Note, the performance characteristics of propagating large object graphs
   * will be worse as opposed to using strict dirty checking with immutable
   * patterns or Polymer's path notification API.
   *
   * @mixinFunction
   * @polymer
   * @memberof Polymer
   * @summary Element class mixin to skip strict dirty-checking for objects
   *   and arrays
   */
  Polymer.MutableData = Polymer.dedupingMixin(superClass => {

    /**
     * @polymer
     * @mixinClass
     * @implements {Polymer_MutableData}
     */
    class MutableData extends superClass {
      /**
       * Overrides `Polymer.PropertyEffects` to provide option for skipping
       * strict equality checking for Objects and Arrays.
       *
       * This method pulls the value to dirty check against from the `__dataTemp`
       * cache (rather than the normal `__data` cache) for Objects.  Since the temp
       * cache is cleared at the end of a turn, this implementation allows
       * side-effects of deep object changes to be processed by re-setting the
       * same object (using the temp cache as an in-turn backstop to prevent
       * cycles due to 2-way notification).
       *
       * @param {string} property Property name
       * @param {*} value New property value
       * @param {*} old Previous property value
       * @return {boolean} Whether the property should be considered a change
       * @protected
       */
      _shouldPropertyChange(property, value, old) {
        return mutablePropertyChange(this, property, value, old, true);
      }

    }

    return MutableData;

  });


  /**
   * Element class mixin to add the optional ability to skip strict
   * dirty-checking for objects and arrays (always consider them to be
   * "dirty") by setting a `mutable-data` attribute on an element instance.
   *
   * By default, `Polymer.PropertyEffects` performs strict dirty checking on
   * objects, which means that any deep modifications to an object or array will
   * not be propagated unless "immutable" data patterns are used (i.e. all object
   * references from the root to the mutation were changed).
   *
   * Polymer also provides a proprietary data mutation and path notification API
   * (e.g. `notifyPath`, `set`, and array mutation API's) that allow efficient
   * mutation and notification of deep changes in an object graph to all elements
   * bound to the same object graph.
   *
   * In cases where neither immutable patterns nor the data mutation API can be
   * used, applying this mixin will allow Polymer to skip dirty checking for
   * objects and arrays (always consider them to be "dirty").  This allows a
   * user to make a deep modification to a bound object graph, and then either
   * simply re-set the object (e.g. `this.items = this.items`) or call `notifyPath`
   * (e.g. `this.notifyPath('items')`) to update the tree.  Note that all
   * elements that wish to be updated based on deep mutations must apply this
   * mixin or otherwise skip strict dirty checking for objects/arrays.
   * Specifically, any elements in the binding tree between the source of a
   * mutation and the consumption of it must enable this mixin or apply the
   * `Polymer.MutableData` mixin.
   *
   * While this mixin adds the ability to forgo Object/Array dirty checking,
   * the `mutableData` flag defaults to false and must be set on the instance.
   *
   * Note, the performance characteristics of propagating large object graphs
   * will be worse by relying on `mutableData: true` as opposed to using
   * strict dirty checking with immutable patterns or Polymer's path notification
   * API.
   *
   * @mixinFunction
   * @polymer
   * @memberof Polymer
   * @summary Element class mixin to optionally skip strict dirty-checking
   *   for objects and arrays
   */
  Polymer.OptionalMutableData = Polymer.dedupingMixin(superClass => {

    /**
     * @mixinClass
     * @polymer
     * @implements {Polymer_OptionalMutableData}
     */
    class OptionalMutableData extends superClass {

      static get properties() {
        return {
          /**
           * Instance-level flag for configuring the dirty-checking strategy
           * for this element.  When true, Objects and Arrays will skip dirty
           * checking, otherwise strict equality checking will be used.
           */
          mutableData: Boolean
        };
      }

      /**
       * Overrides `Polymer.PropertyEffects` to provide option for skipping
       * strict equality checking for Objects and Arrays.
       *
       * When `this.mutableData` is true on this instance, this method
       * pulls the value to dirty check against from the `__dataTemp` cache
       * (rather than the normal `__data` cache) for Objects.  Since the temp
       * cache is cleared at the end of a turn, this implementation allows
       * side-effects of deep object changes to be processed by re-setting the
       * same object (using the temp cache as an in-turn backstop to prevent
       * cycles due to 2-way notification).
       *
       * @param {string} property Property name
       * @param {*} value New property value
       * @param {*} old Previous property value
       * @return {boolean} Whether the property should be considered a change
       * @protected
       */
      _shouldPropertyChange(property, value, old) {
        return mutablePropertyChange(this, property, value, old, this.mutableData);
      }
    }

    return OptionalMutableData;

  });

  // Export for use by legacy behavior
  Polymer.MutableData._mutablePropertyChange = mutablePropertyChange;

})();
</script>
<script>
  (function() {
    'use strict';

    // Base class for HTMLTemplateElement extension that has property effects
    // machinery for propagating host properties to children. This is an ES5
    // class only because Babel (incorrectly) requires super() in the class
    // constructor even though no `this` is used and it returns an instance.
    let newInstance = null;

    /**
     * @constructor
     * @extends {HTMLTemplateElement}
     * @private
     */
    function HTMLTemplateElementExtension() { return newInstance; }
    HTMLTemplateElementExtension.prototype = Object.create(HTMLTemplateElement.prototype, {
      constructor: {
        value: HTMLTemplateElementExtension,
        writable: true
      }
    });

    /**
     * @constructor
     * @implements {Polymer_PropertyEffects}
     * @extends {HTMLTemplateElementExtension}
     * @private
     */
    const DataTemplate = Polymer.PropertyEffects(HTMLTemplateElementExtension);

    /**
     * @constructor
     * @implements {Polymer_MutableData}
     * @extends {DataTemplate}
     * @private
     */
    const MutableDataTemplate = Polymer.MutableData(DataTemplate);

    // Applies a DataTemplate subclass to a <template> instance
    function upgradeTemplate(template, constructor) {
      newInstance = template;
      Object.setPrototypeOf(template, constructor.prototype);
      new constructor();
      newInstance = null;
    }

    /**
     * Base class for TemplateInstance.
     * @constructor
     * @implements {Polymer_PropertyEffects}
     * @private
     */
    const base = Polymer.PropertyEffects(class {});

    /**
     * @polymer
     * @customElement
     * @appliesMixin Polymer.PropertyEffects
     * @unrestricted
     */
    class TemplateInstanceBase extends base {
      constructor(props) {
        super();
        this._configureProperties(props);
        this.root = this._stampTemplate(this.__dataHost);
        // Save list of stamped children
        let children = this.children = [];
        for (let n = this.root.firstChild; n; n=n.nextSibling) {
          children.push(n);
          n.__templatizeInstance = this;
        }
        if (this.__templatizeOwner &&
          this.__templatizeOwner.__hideTemplateChildren__) {
          this._showHideChildren(true);
        }
        // Flush props only when props are passed if instance props exist
        // or when there isn't instance props.
        let options = this.__templatizeOptions;
        if ((props && options.instanceProps) || !options.instanceProps) {
          this._enableProperties();
        }
      }
      /**
       * Configure the given `props` by calling `_setPendingProperty`. Also
       * sets any properties stored in `__hostProps`.
       * @private
       * @param {Object} props Object of property name-value pairs to set.
       * @return {void}
       */
      _configureProperties(props) {
        let options = this.__templatizeOptions;
        if (options.forwardHostProp) {
          for (let hprop in this.__hostProps) {
            this._setPendingProperty(hprop, this.__dataHost['_host_' + hprop]);
          }
        }
        // Any instance props passed in the constructor will overwrite host props;
        // normally this would be a user error but we don't specifically filter them
        for (let iprop in props) {
          this._setPendingProperty(iprop, props[iprop]);
        }
      }
      /**
       * Forwards a host property to this instance.  This method should be
       * called on instances from the `options.forwardHostProp` callback
       * to propagate changes of host properties to each instance.
       *
       * Note this method enqueues the change, which are flushed as a batch.
       *
       * @param {string} prop Property or path name
       * @param {*} value Value of the property to forward
       * @return {void}
       */
      forwardHostProp(prop, value) {
        if (this._setPendingPropertyOrPath(prop, value, false, true)) {
          this.__dataHost._enqueueClient(this);
        }
      }

      /**
       * Override point for adding custom or simulated event handling.
       *
       * @param {!Node} node Node to add event listener to
       * @param {string} eventName Name of event
       * @param {function(!Event):void} handler Listener function to add
       * @return {void}
       */
      _addEventListenerToNode(node, eventName, handler) {
        if (this._methodHost && this.__templatizeOptions.parentModel) {
          // If this instance should be considered a parent model, decorate
          // events this template instance as `model`
          this._methodHost._addEventListenerToNode(node, eventName, (e) => {
            e.model = this;
            handler(e);
          });
        } else {
          // Otherwise delegate to the template's host (which could be)
          // another template instance
          let templateHost = this.__dataHost.__dataHost;
          if (templateHost) {
            templateHost._addEventListenerToNode(node, eventName, handler);
          }
        }
      }
      /**
       * Shows or hides the template instance top level child elements. For
       * text nodes, `textContent` is removed while "hidden" and replaced when
       * "shown."
       * @param {boolean} hide Set to true to hide the children;
       * set to false to show them.
       * @return {void}
       * @protected
       */
      _showHideChildren(hide) {
        let c = this.children;
        for (let i=0; i<c.length; i++) {
          let n = c[i];
          // Ignore non-changes
          if (Boolean(hide) != Boolean(n.__hideTemplateChildren__)) {
            if (n.nodeType === Node.TEXT_NODE) {
              if (hide) {
                n.__polymerTextContent__ = n.textContent;
                n.textContent = '';
              } else {
                n.textContent = n.__polymerTextContent__;
              }
            // remove and replace slot
            } else if (n.localName === 'slot') {
              if (hide) {
                n.__polymerReplaced__ = document.createComment('hidden-slot');
                n.parentNode.replaceChild(n.__polymerReplaced__, n);
              } else {
                const replace = n.__polymerReplaced__;
                if (replace) {
                  replace.parentNode.replaceChild(n, replace);
                }
              }
            }

            else if (n.style) {
              if (hide) {
                n.__polymerDisplay__ = n.style.display;
                n.style.display = 'none';
              } else {
                n.style.display = n.__polymerDisplay__;
              }
            }
          }
          n.__hideTemplateChildren__ = hide;
          if (n._showHideChildren) {
            n._showHideChildren(hide);
          }
        }
      }
      /**
       * Overrides default property-effects implementation to intercept
       * textContent bindings while children are "hidden" and cache in
       * private storage for later retrieval.
       *
       * @param {!Node} node The node to set a property on
       * @param {string} prop The property to set
       * @param {*} value The value to set
       * @return {void}
       * @protected
       */
      _setUnmanagedPropertyToNode(node, prop, value) {
        if (node.__hideTemplateChildren__ &&
            node.nodeType == Node.TEXT_NODE && prop == 'textContent') {
          node.__polymerTextContent__ = value;
        } else {
          super._setUnmanagedPropertyToNode(node, prop, value);
        }
      }
      /**
       * Find the parent model of this template instance.  The parent model
       * is either another templatize instance that had option `parentModel: true`,
       * or else the host element.
       *
       * @return {!Polymer_PropertyEffects} The parent model of this instance
       */
      get parentModel() {
        let model = this.__parentModel;
        if (!model) {
          let options;
          model = this;
          do {
            // A template instance's `__dataHost` is a <template>
            // `model.__dataHost.__dataHost` is the template's host
            model = model.__dataHost.__dataHost;
          } while ((options = model.__templatizeOptions) && !options.parentModel);
          this.__parentModel = model;
        }
        return model;
      }

      /**
       * Stub of HTMLElement's `dispatchEvent`, so that effects that may
       * dispatch events safely no-op.
       *
       * @param {Event} event Event to dispatch
       * @return {boolean} Always true.
       */
       dispatchEvent(event) { // eslint-disable-line no-unused-vars
         return true;
      }
    }

    /** @type {!DataTemplate} */
    TemplateInstanceBase.prototype.__dataHost;
    /** @type {!TemplatizeOptions} */
    TemplateInstanceBase.prototype.__templatizeOptions;
    /** @type {!Polymer_PropertyEffects} */
    TemplateInstanceBase.prototype._methodHost;
    /** @type {!Object} */
    TemplateInstanceBase.prototype.__templatizeOwner;
    /** @type {!Object} */
    TemplateInstanceBase.prototype.__hostProps;

    /**
     * @constructor
     * @extends {TemplateInstanceBase}
     * @implements {Polymer_MutableData}
     * @private
     */
    const MutableTemplateInstanceBase = Polymer.MutableData(TemplateInstanceBase);

    function findMethodHost(template) {
      // Technically this should be the owner of the outermost template.
      // In shadow dom, this is always getRootNode().host, but we can
      // approximate this via cooperation with our dataHost always setting
      // `_methodHost` as long as there were bindings (or id's) on this
      // instance causing it to get a dataHost.
      let templateHost = template.__dataHost;
      return templateHost && templateHost._methodHost || templateHost;
    }

    /* eslint-disable valid-jsdoc */
    /**
     * @suppress {missingProperties} class.prototype is not defined for some reason
     */
    function createTemplatizerClass(template, templateInfo, options) {
      // Anonymous class created by the templatize
      let base = options.mutableData ?
        MutableTemplateInstanceBase : TemplateInstanceBase;
      // Affordance for global mixins onto TemplatizeInstance
      if (Polymer.Templatize.mixin) {
        base = Polymer.Templatize.mixin(base);
      }
      /**
       * @constructor
       * @extends {base}
       * @private
       */
      let klass = class extends base { };
      klass.prototype.__templatizeOptions = options;
      klass.prototype._bindTemplate(template);
      addNotifyEffects(klass, template, templateInfo, options);
      return klass;
    }

    /**
     * @suppress {missingProperties} class.prototype is not defined for some reason
     */
    function addPropagateEffects(template, templateInfo, options) {
      let userForwardHostProp = options.forwardHostProp;
      if (userForwardHostProp) {
        // Provide data API and property effects on memoized template class
        let klass = templateInfo.templatizeTemplateClass;
        if (!klass) {
          let base = options.mutableData ? MutableDataTemplate : DataTemplate;
          /** @private */
          klass = templateInfo.templatizeTemplateClass =
            class TemplatizedTemplate extends base {};
          // Add template - >instances effects
          // and host <- template effects
          let hostProps = templateInfo.hostProps;
          for (let prop in hostProps) {
            klass.prototype._addPropertyEffect('_host_' + prop,
              klass.prototype.PROPERTY_EFFECT_TYPES.PROPAGATE,
              {fn: createForwardHostPropEffect(prop, userForwardHostProp)});
            klass.prototype._createNotifyingProperty('_host_' + prop);
          }
        }
        upgradeTemplate(template, klass);
        // Mix any pre-bound data into __data; no need to flush this to
        // instances since they pull from the template at instance-time
        if (template.__dataProto) {
          // Note, generally `__dataProto` could be chained, but it's guaranteed
          // to not be since this is a vanilla template we just added effects to
          Object.assign(template.__data, template.__dataProto);
        }
        // Clear any pending data for performance
        template.__dataTemp = {};
        template.__dataPending = null;
        template.__dataOld = null;
        template._enableProperties();
      }
    }
    /* eslint-enable valid-jsdoc */

    function createForwardHostPropEffect(hostProp, userForwardHostProp) {
      return function forwardHostProp(template, prop, props) {
        userForwardHostProp.call(template.__templatizeOwner,
          prop.substring('_host_'.length), props[prop]);
      };
    }

    function addNotifyEffects(klass, template, templateInfo, options) {
      let hostProps = templateInfo.hostProps || {};
      for (let iprop in options.instanceProps) {
        delete hostProps[iprop];
        let userNotifyInstanceProp = options.notifyInstanceProp;
        if (userNotifyInstanceProp) {
          klass.prototype._addPropertyEffect(iprop,
            klass.prototype.PROPERTY_EFFECT_TYPES.NOTIFY,
            {fn: createNotifyInstancePropEffect(iprop, userNotifyInstanceProp)});
        }
      }
      if (options.forwardHostProp && template.__dataHost) {
        for (let hprop in hostProps) {
          klass.prototype._addPropertyEffect(hprop,
            klass.prototype.PROPERTY_EFFECT_TYPES.NOTIFY,
            {fn: createNotifyHostPropEffect()});
        }
      }
    }

    function createNotifyInstancePropEffect(instProp, userNotifyInstanceProp) {
      return function notifyInstanceProp(inst, prop, props) {
        userNotifyInstanceProp.call(inst.__templatizeOwner,
          inst, prop, props[prop]);
      };
    }

    function createNotifyHostPropEffect() {
      return function notifyHostProp(inst, prop, props) {
        inst.__dataHost._setPendingPropertyOrPath('_host_' + prop, props[prop], true, true);
      };
    }

    /**
     * Module for preparing and stamping instances of templates that utilize
     * Polymer's data-binding and declarative event listener features.
     *
     * Example:
     *
     *     // Get a template from somewhere, e.g. light DOM
     *     let template = this.querySelector('template');
     *     // Prepare the template
     *     let TemplateClass = Polymer.Templatize.templatize(template);
     *     // Instance the template with an initial data model
     *     let instance = new TemplateClass({myProp: 'initial'});
     *     // Insert the instance's DOM somewhere, e.g. element's shadow DOM
     *     this.shadowRoot.appendChild(instance.root);
     *     // Changing a property on the instance will propagate to bindings
     *     // in the template
     *     instance.myProp = 'new value';
     *
     * The `options` dictionary passed to `templatize` allows for customizing
     * features of the generated template class, including how outer-scope host
     * properties should be forwarded into template instances, how any instance
     * properties added into the template's scope should be notified out to
     * the host, and whether the instance should be decorated as a "parent model"
     * of any event handlers.
     *
     *     // Customize property forwarding and event model decoration
     *     let TemplateClass = Polymer.Templatize.templatize(template, this, {
     *       parentModel: true,
     *       forwardHostProp(property, value) {...},
     *       instanceProps: {...},
     *       notifyInstanceProp(instance, property, value) {...},
     *     });
     *
     * @namespace
     * @memberof Polymer
     * @summary Module for preparing and stamping instances of templates
     *   utilizing Polymer templating features.
     */
    Polymer.Templatize = {

      /**
       * Returns an anonymous `Polymer.PropertyEffects` class bound to the
       * `<template>` provided.  Instancing the class will result in the
       * template being stamped into a document fragment stored as the instance's
       * `root` property, after which it can be appended to the DOM.
       *
       * Templates may utilize all Polymer data-binding features as well as
       * declarative event listeners.  Event listeners and inline computing
       * functions in the template will be called on the host of the template.
       *
       * The constructor returned takes a single argument dictionary of initial
       * property values to propagate into template bindings.  Additionally
       * host properties can be forwarded in, and instance properties can be
       * notified out by providing optional callbacks in the `options` dictionary.
       *
       * Valid configuration in `options` are as follows:
       *
       * - `forwardHostProp(property, value)`: Called when a property referenced
       *   in the template changed on the template's host. As this library does
       *   not retain references to templates instanced by the user, it is the
       *   templatize owner's responsibility to forward host property changes into
       *   user-stamped instances.  The `instance.forwardHostProp(property, value)`
       *    method on the generated class should be called to forward host
       *   properties into the template to prevent unnecessary property-changed
       *   notifications. Any properties referenced in the template that are not
       *   defined in `instanceProps` will be notified up to the template's host
       *   automatically.
       * - `instanceProps`: Dictionary of property names that will be added
       *   to the instance by the templatize owner.  These properties shadow any
       *   host properties, and changes within the template to these properties
       *   will result in `notifyInstanceProp` being called.
       * - `mutableData`: When `true`, the generated class will skip strict
       *   dirty-checking for objects and arrays (always consider them to be
       *   "dirty").
       * - `notifyInstanceProp(instance, property, value)`: Called when
       *   an instance property changes.  Users may choose to call `notifyPath`
       *   on e.g. the owner to notify the change.
       * - `parentModel`: When `true`, events handled by declarative event listeners
       *   (`on-event="handler"`) will be decorated with a `model` property pointing
       *   to the template instance that stamped it.  It will also be returned
       *   from `instance.parentModel` in cases where template instance nesting
       *   causes an inner model to shadow an outer model.
       *
       * All callbacks are called bound to the `owner`. Any context
       * needed for the callbacks (such as references to `instances` stamped)
       * should be stored on the `owner` such that they can be retrieved via
       * `this`.
       *
       * When `options.forwardHostProp` is declared as an option, any properties
       * referenced in the template will be automatically forwarded from the host of
       * the `<template>` to instances, with the exception of any properties listed in
       * the `options.instanceProps` object.  `instanceProps` are assumed to be
       * managed by the owner of the instances, either passed into the constructor
       * or set after the fact.  Note, any properties passed into the constructor will
       * always be set to the instance (regardless of whether they would normally
       * be forwarded from the host).
       *
       * Note that `templatize()` can be run only once for a given `<template>`.
       * Further calls will result in an error. Also, there is a special
       * behavior if the template was duplicated through a mechanism such as
       * `<dom-repeat>` or `<test-fixture>`. In this case, all calls to
       * `templatize()` return the same class for all duplicates of a template.
       * The class returned from `templatize()` is generated only once using
       * the `options` from the first call. This means that any `options`
       * provided to subsequent calls will be ignored. Therefore, it is very
       * important not to close over any variables inside the callbacks. Also,
       * arrow functions must be avoided because they bind the outer `this`.
       * Inside the callbacks, any contextual information can be accessed
       * through `this`, which points to the `owner`.
       *
       * @memberof Polymer.Templatize
       * @param {!HTMLTemplateElement} template Template to templatize
       * @param {Polymer_PropertyEffects=} owner Owner of the template instances;
       *   any optional callbacks will be bound to this owner.
       * @param {Object=} options Options dictionary (see summary for details)
       * @return {function(new:TemplateInstanceBase)} Generated class bound to the template
       *   provided
       * @suppress {invalidCasts}
       */
      templatize(template, owner, options) {
        // Under strictTemplatePolicy, the templatized element must be owned
        // by a (trusted) Polymer element, indicated by existence of _methodHost;
        // e.g. for dom-if & dom-repeat in main document, _methodHost is null
        if (Polymer.strictTemplatePolicy && !findMethodHost(template)) {
          throw new Error('strictTemplatePolicy: template owner not trusted');
        }
        options = /** @type {!TemplatizeOptions} */(options || {});
        if (template.__templatizeOwner) {
          throw new Error('A <template> can only be templatized once');
        }
        template.__templatizeOwner = owner;
        const ctor = owner ? owner.constructor : TemplateInstanceBase;
        let templateInfo = ctor._parseTemplate(template);
        // Get memoized base class for the prototypical template, which
        // includes property effects for binding template & forwarding
        let baseClass = templateInfo.templatizeInstanceClass;
        if (!baseClass) {
          baseClass = createTemplatizerClass(template, templateInfo, options);
          templateInfo.templatizeInstanceClass = baseClass;
        }
        // Host property forwarding must be installed onto template instance
        addPropagateEffects(template, templateInfo, options);
        // Subclass base class and add reference for this specific template
        /** @private */
        let klass = class TemplateInstance extends baseClass {};
        klass.prototype._methodHost = findMethodHost(template);
        klass.prototype.__dataHost = template;
        klass.prototype.__templatizeOwner = owner;
        klass.prototype.__hostProps = templateInfo.hostProps;
        klass = /** @type {function(new:TemplateInstanceBase)} */(klass); //eslint-disable-line no-self-assign
        return klass;
      },

      /**
       * Returns the template "model" associated with a given element, which
       * serves as the binding scope for the template instance the element is
       * contained in. A template model is an instance of
       * `TemplateInstanceBase`, and should be used to manipulate data
       * associated with this template instance.
       *
       * Example:
       *
       *   let model = modelForElement(el);
       *   if (model.index < 10) {
       *     model.set('item.checked', true);
       *   }
       *
       * @memberof Polymer.Templatize
       * @param {HTMLTemplateElement} template The model will be returned for
       *   elements stamped from this template
       * @param {Node=} node Node for which to return a template model.
       * @return {TemplateInstanceBase} Template instance representing the
       *   binding scope for the element
       */
      modelForElement(template, node) {
        let model;
        while (node) {
          // An element with a __templatizeInstance marks the top boundary
          // of a scope; walk up until we find one, and then ensure that
          // its __dataHost matches `this`, meaning this dom-repeat stamped it
          if ((model = node.__templatizeInstance)) {
            // Found an element stamped by another template; keep walking up
            // from its __dataHost
            if (model.__dataHost != template) {
              node = model.__dataHost;
            } else {
              return model;
            }
          } else {
            // Still in a template scope, keep going up until
            // a __templatizeInstance is found
            node = node.parentNode;
          }
        }
        return null;
      }
    };

    Polymer.TemplateInstanceBase = TemplateInstanceBase;

  })();

</script>
<script>
  (function() {
    'use strict';

    let TemplateInstanceBase = Polymer.TemplateInstanceBase; // eslint-disable-line

    /**
     * @typedef {{
     *   _templatizerTemplate: HTMLTemplateElement,
     *   _parentModel: boolean,
     *   _instanceProps: Object,
     *   _forwardHostPropV2: Function,
     *   _notifyInstancePropV2: Function,
     *   ctor: TemplateInstanceBase
     * }}
     */
    let TemplatizerUser; // eslint-disable-line

    /**
     * The `Polymer.Templatizer` behavior adds methods to generate instances of
     * templates that are each managed by an anonymous `Polymer.PropertyEffects`
     * instance where data-bindings in the stamped template content are bound to
     * accessors on itself.
     *
     * This behavior is provided in Polymer 2.x as a hybrid-element convenience
     * only.  For non-hybrid usage, the `Polymer.Templatize` library
     * should be used instead.
     *
     * Example:
     *
     *     // Get a template from somewhere, e.g. light DOM
     *     let template = this.querySelector('template');
     *     // Prepare the template
     *     this.templatize(template);
     *     // Instance the template with an initial data model
     *     let instance = this.stamp({myProp: 'initial'});
     *     // Insert the instance's DOM somewhere, e.g. light DOM
     *     Polymer.dom(this).appendChild(instance.root);
     *     // Changing a property on the instance will propagate to bindings
     *     // in the template
     *     instance.myProp = 'new value';
     *
     * Users of `Templatizer` may need to implement the following abstract
     * API's to determine how properties and paths from the host should be
     * forwarded into to instances:
     *
     *     _forwardHostPropV2: function(prop, value)
     *
     * Likewise, users may implement these additional abstract API's to determine
     * how instance-specific properties that change on the instance should be
     * forwarded out to the host, if necessary.
     *
     *     _notifyInstancePropV2: function(inst, prop, value)
     *
     * In order to determine which properties are instance-specific and require
     * custom notification via `_notifyInstanceProp`, define an `_instanceProps`
     * object containing keys for each instance prop, for example:
     *
     *     _instanceProps: {
     *       item: true,
     *       index: true
     *     }
     *
     * Any properties used in the template that are not defined in _instanceProp
     * will be forwarded out to the Templatize `owner` automatically.
     *
     * Users may also implement the following abstract function to show or
     * hide any DOM generated using `stamp`:
     *
     *     _showHideChildren: function(shouldHide)
     *
     * Note that some callbacks are suffixed with `V2` in the Polymer 2.x behavior
     * as the implementations will need to differ from the callbacks required
     * by the 1.x Templatizer API due to changes in the `TemplateInstance` API
     * between versions 1.x and 2.x.
     *
     * @polymerBehavior
     */
    Polymer.Templatizer = {

      /**
       * Generates an anonymous `TemplateInstance` class (stored as `this.ctor`)
       * for the provided template.  This method should be called once per
       * template to prepare an element for stamping the template, followed
       * by `stamp` to create new instances of the template.
       *
       * @param {!HTMLTemplateElement} template Template to prepare
       * @param {boolean=} mutableData When `true`, the generated class will skip
       *   strict dirty-checking for objects and arrays (always consider them to
       *   be "dirty"). Defaults to false.
       * @return {void}
       * @this {TemplatizerUser}
       */
      templatize(template, mutableData) {
        this._templatizerTemplate = template;
        this.ctor = Polymer.Templatize.templatize(template, this, {
          mutableData: Boolean(mutableData),
          parentModel: this._parentModel,
          instanceProps: this._instanceProps,
          forwardHostProp: this._forwardHostPropV2,
          notifyInstanceProp: this._notifyInstancePropV2
        });
      },

      /**
       * Creates an instance of the template prepared by `templatize`.  The object
       * returned is an instance of the anonymous class generated by `templatize`
       * whose `root` property is a document fragment containing newly cloned
       * template content, and which has property accessors corresponding to
       * properties referenced in template bindings.
       *
       * @param {Object=} model Object containing initial property values to
       *   populate into the template bindings.
       * @return {TemplateInstanceBase} Returns the created instance of
       * the template prepared by `templatize`.
       * @this {TemplatizerUser}
       */
      stamp(model) {
        return new this.ctor(model);
      },

      /**
       * Returns the template "model" (`TemplateInstance`) associated with
       * a given element, which serves as the binding scope for the template
       * instance the element is contained in.  A template model should be used
       * to manipulate data associated with this template instance.
       *
       * @param {HTMLElement} el Element for which to return a template model.
       * @return {TemplateInstanceBase} Model representing the binding scope for
       *   the element.
       * @this {TemplatizerUser}
       */
      modelForElement(el) {
        return Polymer.Templatize.modelForElement(this._templatizerTemplate, el);
      }
    };

  })();
</script>
<script>

  (function() {
    'use strict';

    /**
     * @constructor
     * @extends {HTMLElement}
     * @implements {Polymer_PropertyEffects}
     * @implements {Polymer_OptionalMutableData}
     * @implements {Polymer_GestureEventListeners}
     * @private
     */
    const domBindBase =
      Polymer.GestureEventListeners(
        Polymer.OptionalMutableData(
          Polymer.PropertyEffects(HTMLElement)));

    /**
     * Custom element to allow using Polymer's template features (data binding,
     * declarative event listeners, etc.) in the main document without defining
     * a new custom element.
     *
     * `<template>` tags utilizing bindings may be wrapped with the `<dom-bind>`
     * element, which will immediately stamp the wrapped template into the main
     * document and bind elements to the `dom-bind` element itself as the
     * binding scope.
     *
     * @polymer
     * @customElement
     * @appliesMixin Polymer.PropertyEffects
     * @appliesMixin Polymer.OptionalMutableData
     * @appliesMixin Polymer.GestureEventListeners
     * @extends {domBindBase}
     * @memberof Polymer
     * @summary Custom element to allow using Polymer's template features (data
     *   binding, declarative event listeners, etc.) in the main document.
     */
    class DomBind extends domBindBase {

      static get observedAttributes() { return ['mutable-data']; }

      constructor() {
        super();
        if (Polymer.strictTemplatePolicy) {
          throw new Error(`strictTemplatePolicy: dom-bind not allowed`);
        }
        this.root = null;
        this.$ = null;
        this.__children = null;
      }

      /** @return {void} */
      attributeChangedCallback() {
        // assumes only one observed attribute
        this.mutableData = true;
      }

      /** @return {void} */
      connectedCallback() {
        this.style.display = 'none';
        this.render();
      }

      /** @return {void} */
      disconnectedCallback() {
        this.__removeChildren();
      }

      __insertChildren() {
        this.parentNode.insertBefore(this.root, this);
      }

      __removeChildren() {
        if (this.__children) {
          for (let i=0; i<this.__children.length; i++) {
            this.root.appendChild(this.__children[i]);
          }
        }
      }

      /**
       * Forces the element to render its content. This is typically only
       * necessary to call if HTMLImports with the async attribute are used.
       * @return {void}
       */
      render() {
        let template;
        if (!this.__children) {
          template = /** @type {HTMLTemplateElement} */(template || this.querySelector('template'));
          if (!template) {
            // Wait until childList changes and template should be there by then
            let observer = new MutationObserver(() => {
              template = /** @type {HTMLTemplateElement} */(this.querySelector('template'));
              if (template) {
                observer.disconnect();
                this.render();
              } else {
                throw new Error('dom-bind requires a <template> child');
              }
            });
            observer.observe(this, {childList: true});
            return;
          }
          this.root = this._stampTemplate(template);
          this.$ = this.root.$;
          this.__children = [];
          for (let n=this.root.firstChild; n; n=n.nextSibling) {
            this.__children[this.__children.length] = n;
          }
          this._enableProperties();
        }
        this.__insertChildren();
        this.dispatchEvent(new CustomEvent('dom-change', {
          bubbles: true,
          composed: true
        }));
      }

    }

    customElements.define('dom-bind', DomBind);

    /** @const */
    Polymer.DomBind = DomBind;

  })();

</script>
<script>
(function() {
  'use strict';

  let TemplateInstanceBase = Polymer.TemplateInstanceBase; // eslint-disable-line

  /**
   * @constructor
   * @implements {Polymer_OptionalMutableData}
   * @extends {Polymer.Element}
   * @private
   */
  const domRepeatBase = Polymer.OptionalMutableData(Polymer.Element);

  /**
   * The `<dom-repeat>` element will automatically stamp and binds one instance
   * of template content to each object in a user-provided array.
   * `dom-repeat` accepts an `items` property, and one instance of the template
   * is stamped for each item into the DOM at the location of the `dom-repeat`
   * element.  The `item` property will be set on each instance's binding
   * scope, thus templates should bind to sub-properties of `item`.
   *
   * Example:
   *
   * ```html
   * <dom-module id="employee-list">
   *
   *   <template>
   *
   *     <div> Employee list: </div>
   *     <dom-repeat items="{{employees}}">
   *       <template>
   *         <div>First name: <span>{{item.first}}</span></div>
   *         <div>Last name: <span>{{item.last}}</span></div>
   *       </template>
   *     </dom-repeat>
   *
   *   </template>
   *
   * </dom-module>
   * ```
   *
   * With the following custom element definition:
   *
   * ```js
   * class EmployeeList extends Polymer.Element {
   *   static get is() { return 'employee-list'; }
   *   static get properties() {
   *     return {
   *       employees: {
   *         value() {
   *           return [
   *             {first: 'Bob', last: 'Smith'},
   *             {first: 'Sally', last: 'Johnson'},
   *             ...
   *           ];
   *         }
   *       }
   *     };
   *   }
   * }
   * ```
   *
   * Notifications for changes to items sub-properties will be forwarded to template
   * instances, which will update via the normal structured data notification system.
   *
   * Mutations to the `items` array itself should be made using the Array
   * mutation API's on `Polymer.Base` (`push`, `pop`, `splice`, `shift`,
   * `unshift`), and template instances will be kept in sync with the data in the
   * array.
   *
   * Events caught by event handlers within the `dom-repeat` template will be
   * decorated with a `model` property, which represents the binding scope for
   * each template instance.  The model is an instance of Polymer.Base, and should
   * be used to manipulate data on the instance, for example
   * `event.model.set('item.checked', true);`.
   *
   * Alternatively, the model for a template instance for an element stamped by
   * a `dom-repeat` can be obtained using the `modelForElement` API on the
   * `dom-repeat` that stamped it, for example
   * `this.$.domRepeat.modelForElement(event.target).set('item.checked', true);`.
   * This may be useful for manipulating instance data of event targets obtained
   * by event handlers on parents of the `dom-repeat` (event delegation).
   *
   * A view-specific filter/sort may be applied to each `dom-repeat` by supplying a
   * `filter` and/or `sort` property.  This may be a string that names a function on
   * the host, or a function may be assigned to the property directly.  The functions
   * should implemented following the standard `Array` filter/sort API.
   *
   * In order to re-run the filter or sort functions based on changes to sub-fields
   * of `items`, the `observe` property may be set as a space-separated list of
   * `item` sub-fields that should cause a re-filter/sort when modified.  If
   * the filter or sort function depends on properties not contained in `items`,
   * the user should observe changes to those properties and call `render` to update
   * the view based on the dependency change.
   *
   * For example, for an `dom-repeat` with a filter of the following:
   *
   * ```js
   * isEngineer(item) {
   *   return item.type == 'engineer' || item.manager.type == 'engineer';
   * }
   * ```
   *
   * Then the `observe` property should be configured as follows:
   *
   * ```html
   * <dom-repeat items="{{employees}}" filter="isEngineer" observe="type manager.type">
   * ```
   *
   * @customElement
   * @polymer
   * @memberof Polymer
   * @extends {domRepeatBase}
   * @appliesMixin Polymer.OptionalMutableData
   * @summary Custom element for stamping instance of a template bound to
   *   items in an array.
   */
  class DomRepeat extends domRepeatBase {

    // Not needed to find template; can be removed once the analyzer
    // can find the tag name from customElements.define call
    static get is() { return 'dom-repeat'; }

    static get template() { return null; }

    static get properties() {

      /**
       * Fired whenever DOM is added or removed by this template (by
       * default, rendering occurs lazily).  To force immediate rendering, call
       * `render`.
       *
       * @event dom-change
       */
      return {

        /**
         * An array containing items determining how many instances of the template
         * to stamp and that that each template instance should bind to.
         */
        items: {
          type: Array
        },

        /**
         * The name of the variable to add to the binding scope for the array
         * element associated with a given template instance.
         */
        as: {
          type: String,
          value: 'item'
        },

        /**
         * The name of the variable to add to the binding scope with the index
         * of the instance in the sorted and filtered list of rendered items.
         * Note, for the index in the `this.items` array, use the value of the
         * `itemsIndexAs` property.
         */
        indexAs: {
          type: String,
          value: 'index'
        },

        /**
         * The name of the variable to add to the binding scope with the index
         * of the instance in the `this.items` array. Note, for the index of
         * this instance in the sorted and filtered list of rendered items,
         * use the value of the `indexAs` property.
         */
        itemsIndexAs: {
          type: String,
          value: 'itemsIndex'
        },

        /**
         * A function that should determine the sort order of the items.  This
         * property should either be provided as a string, indicating a method
         * name on the element's host, or else be an actual function.  The
         * function should match the sort function passed to `Array.sort`.
         * Using a sort function has no effect on the underlying `items` array.
         */
        sort: {
          type: Function,
          observer: '__sortChanged'
        },

        /**
         * A function that can be used to filter items out of the view.  This
         * property should either be provided as a string, indicating a method
         * name on the element's host, or else be an actual function.  The
         * function should match the sort function passed to `Array.filter`.
         * Using a filter function has no effect on the underlying `items` array.
         */
        filter: {
          type: Function,
          observer: '__filterChanged'
        },

        /**
         * When using a `filter` or `sort` function, the `observe` property
         * should be set to a space-separated list of the names of item
         * sub-fields that should trigger a re-sort or re-filter when changed.
         * These should generally be fields of `item` that the sort or filter
         * function depends on.
         */
        observe: {
          type: String,
          observer: '__observeChanged'
        },

        /**
         * When using a `filter` or `sort` function, the `delay` property
         * determines a debounce time in ms after a change to observed item
         * properties that must pass before the filter or sort is re-run.
         * This is useful in rate-limiting shuffling of the view when
         * item changes may be frequent.
         */
        delay: Number,

        /**
         * Count of currently rendered items after `filter` (if any) has been applied.
         * If "chunking mode" is enabled, `renderedItemCount` is updated each time a
         * set of template instances is rendered.
         *
         */
        renderedItemCount: {
          type: Number,
          notify: true,
          readOnly: true
        },

        /**
         * Defines an initial count of template instances to render after setting
         * the `items` array, before the next paint, and puts the `dom-repeat`
         * into "chunking mode".  The remaining items will be created and rendered
         * incrementally at each animation frame therof until all instances have
         * been rendered.
         */
        initialCount: {
          type: Number,
          observer: '__initializeChunking'
        },

        /**
         * When `initialCount` is used, this property defines a frame rate (in
         * fps) to target by throttling the number of instances rendered each
         * frame to not exceed the budget for the target frame rate.  The
         * framerate is effectively the number of `requestAnimationFrame`s that
         * it tries to allow to actually fire in a given second. It does this
         * by measuring the time between `rAF`s and continuously adjusting the
         * number of items created each `rAF` to maintain the target framerate.
         * Setting this to a higher number allows lower latency and higher
         * throughput for event handlers and other tasks, but results in a
         * longer time for the remaining items to complete rendering.
         */
        targetFramerate: {
          type: Number,
          value: 20
        },

        _targetFrameTime: {
          type: Number,
          computed: '__computeFrameTime(targetFramerate)'
        }

      };

    }

    static get observers() {
      return [ '__itemsChanged(items.*)' ];
    }

    constructor() {
      super();
      this.__instances = [];
      this.__limit = Infinity;
      this.__pool = [];
      this.__renderDebouncer = null;
      this.__itemsIdxToInstIdx = {};
      this.__chunkCount = null;
      this.__lastChunkTime = null;
      this.__sortFn = null;
      this.__filterFn = null;
      this.__observePaths = null;
      this.__ctor = null;
      this.__isDetached = true;
      this.template = null;
    }

    /**
     * @return {void}
     */
    disconnectedCallback() {
      super.disconnectedCallback();
      this.__isDetached = true;
      for (let i=0; i<this.__instances.length; i++) {
        this.__detachInstance(i);
      }
    }

    /**
     * @return {void}
     */
    connectedCallback() {
      super.connectedCallback();
      this.style.display = 'none';
      // only perform attachment if the element was previously detached.
      if (this.__isDetached) {
        this.__isDetached = false;
        let parent = this.parentNode;
        for (let i=0; i<this.__instances.length; i++) {
          this.__attachInstance(i, parent);
        }
      }
    }

    __ensureTemplatized() {
      // Templatizing (generating the instance constructor) needs to wait
      // until ready, since won't have its template content handed back to
      // it until then
      if (!this.__ctor) {
        let template = this.template = /** @type {HTMLTemplateElement} */(this.querySelector('template'));
        if (!template) {
          // // Wait until childList changes and template should be there by then
          let observer = new MutationObserver(() => {
            if (this.querySelector('template')) {
              observer.disconnect();
              this.__render();
            } else {
              throw new Error('dom-repeat requires a <template> child');
            }
          });
          observer.observe(this, {childList: true});
          return false;
        }
        // Template instance props that should be excluded from forwarding
        let instanceProps = {};
        instanceProps[this.as] = true;
        instanceProps[this.indexAs] = true;
        instanceProps[this.itemsIndexAs] = true;
        this.__ctor = Polymer.Templatize.templatize(template, this, {
          mutableData: this.mutableData,
          parentModel: true,
          instanceProps: instanceProps,
          /**
           * @this {this}
           * @param {string} prop Property to set
           * @param {*} value Value to set property to
           */
          forwardHostProp: function(prop, value) {
            let i$ = this.__instances;
            for (let i=0, inst; (i<i$.length) && (inst=i$[i]); i++) {
              inst.forwardHostProp(prop, value);
            }
          },
          /**
           * @this {this}
           * @param {Object} inst Instance to notify
           * @param {string} prop Property to notify
           * @param {*} value Value to notify
           */
          notifyInstanceProp: function(inst, prop, value) {
            if (Polymer.Path.matches(this.as, prop)) {
              let idx = inst[this.itemsIndexAs];
              if (prop == this.as) {
                this.items[idx] = value;
              }
              let path = Polymer.Path.translate(this.as, `${JSCompiler_renameProperty('items', this)}.${idx}`, prop);
              this.notifyPath(path, value);
            }
          }
        });
      }
      return true;
    }

    __getMethodHost() {
      // Technically this should be the owner of the outermost template.
      // In shadow dom, this is always getRootNode().host, but we can
      // approximate this via cooperation with our dataHost always setting
      // `_methodHost` as long as there were bindings (or id's) on this
      // instance causing it to get a dataHost.
      return this.__dataHost._methodHost || this.__dataHost;
    }

    __functionFromPropertyValue(functionOrMethodName) {
      if (typeof functionOrMethodName === 'string') {
        let methodName = functionOrMethodName;
        let obj = this.__getMethodHost();
        return function() { return obj[methodName].apply(obj, arguments); };
      }

      return functionOrMethodName;
    }

    __sortChanged(sort) {
      this.__sortFn = this.__functionFromPropertyValue(sort);
      if (this.items) { this.__debounceRender(this.__render); }
    }

    __filterChanged(filter) {
      this.__filterFn = this.__functionFromPropertyValue(filter);
      if (this.items) { this.__debounceRender(this.__render); }
    }

    __computeFrameTime(rate) {
      return Math.ceil(1000/rate);
    }

    __initializeChunking() {
      if (this.initialCount) {
        this.__limit = this.initialCount;
        this.__chunkCount = this.initialCount;
        this.__lastChunkTime = performance.now();
      }
    }

    __tryRenderChunk() {
      // Debounced so that multiple calls through `_render` between animation
      // frames only queue one new rAF (e.g. array mutation & chunked render)
      if (this.items && this.__limit < this.items.length) {
        this.__debounceRender(this.__requestRenderChunk);
      }
    }

    __requestRenderChunk() {
      requestAnimationFrame(()=>this.__renderChunk());
    }

    __renderChunk() {
      // Simple auto chunkSize throttling algorithm based on feedback loop:
      // measure actual time between frames and scale chunk count by ratio
      // of target/actual frame time
      let currChunkTime = performance.now();
      let ratio = this._targetFrameTime / (currChunkTime - this.__lastChunkTime);
      this.__chunkCount = Math.round(this.__chunkCount * ratio) || 1;
      this.__limit += this.__chunkCount;
      this.__lastChunkTime = currChunkTime;
      this.__debounceRender(this.__render);
    }

    __observeChanged() {
      this.__observePaths = this.observe &&
        this.observe.replace('.*', '.').split(' ');
    }

    __itemsChanged(change) {
      if (this.items && !Array.isArray(this.items)) {
        console.warn('dom-repeat expected array for `items`, found', this.items);
      }
      // If path was to an item (e.g. 'items.3' or 'items.3.foo'), forward the
      // path to that instance synchronously (returns false for non-item paths)
      if (!this.__handleItemPath(change.path, change.value)) {
        // Otherwise, the array was reset ('items') or spliced ('items.splices'),
        // so queue a full refresh
        this.__initializeChunking();
        this.__debounceRender(this.__render);
      }
    }

    __handleObservedPaths(path) {
      // Handle cases where path changes should cause a re-sort/filter
      if (this.__sortFn || this.__filterFn) {
        if (!path) {
          // Always re-render if the item itself changed
          this.__debounceRender(this.__render, this.delay);
        } else if (this.__observePaths) {
          // Otherwise, re-render if the path changed matches an observed path
          let paths = this.__observePaths;
          for (let i=0; i<paths.length; i++) {
            if (path.indexOf(paths[i]) === 0) {
              this.__debounceRender(this.__render, this.delay);
            }
          }
        }
      }
    }

    /**
     * @param {function(this:DomRepeat)} fn Function to debounce.
     * @param {number=} delay Delay in ms to debounce by.
     */
    __debounceRender(fn, delay = 0) {
      this.__renderDebouncer = Polymer.Debouncer.debounce(
            this.__renderDebouncer
          , delay > 0 ? Polymer.Async.timeOut.after(delay) : Polymer.Async.microTask
          , fn.bind(this));
      Polymer.enqueueDebouncer(this.__renderDebouncer);
    }

    /**
     * Forces the element to render its content. Normally rendering is
     * asynchronous to a provoking change. This is done for efficiency so
     * that multiple changes trigger only a single render. The render method
     * should be called if, for example, template rendering is required to
     * validate application state.
     * @return {void}
     */
    render() {
      // Queue this repeater, then flush all in order
      this.__debounceRender(this.__render);
      Polymer.flush();
    }

    __render() {
      if (!this.__ensureTemplatized()) {
        // No template found yet
        return;
      }
      this.__applyFullRefresh();
      // Reset the pool
      // TODO(kschaaf): Reuse pool across turns and nested templates
      // Now that objects/arrays are re-evaluated when set, we can safely
      // reuse pooled instances across turns, however we still need to decide
      // semantics regarding how long to hold, how many to hold, etc.
      this.__pool.length = 0;
      // Set rendered item count
      this._setRenderedItemCount(this.__instances.length);
      // Notify users
      this.dispatchEvent(new CustomEvent('dom-change', {
        bubbles: true,
        composed: true
      }));
      // Check to see if we need to render more items
      this.__tryRenderChunk();
    }

    __applyFullRefresh() {
      let items = this.items || [];
      let isntIdxToItemsIdx = new Array(items.length);
      for (let i=0; i<items.length; i++) {
        isntIdxToItemsIdx[i] = i;
      }
      // Apply user filter
      if (this.__filterFn) {
        isntIdxToItemsIdx = isntIdxToItemsIdx.filter((i, idx, array) =>
          this.__filterFn(items[i], idx, array));
      }
      // Apply user sort
      if (this.__sortFn) {
        isntIdxToItemsIdx.sort((a, b) => this.__sortFn(items[a], items[b]));
      }
      // items->inst map kept for item path forwarding
      const itemsIdxToInstIdx = this.__itemsIdxToInstIdx = {};
      let instIdx = 0;
      // Generate instances and assign items
      const limit = Math.min(isntIdxToItemsIdx.length, this.__limit);
      for (; instIdx<limit; instIdx++) {
        let inst = this.__instances[instIdx];
        let itemIdx = isntIdxToItemsIdx[instIdx];
        let item = items[itemIdx];
        itemsIdxToInstIdx[itemIdx] = instIdx;
        if (inst) {
          inst._setPendingProperty(this.as, item);
          inst._setPendingProperty(this.indexAs, instIdx);
          inst._setPendingProperty(this.itemsIndexAs, itemIdx);
          inst._flushProperties();
        } else {
          this.__insertInstance(item, instIdx, itemIdx);
        }
      }
      // Remove any extra instances from previous state
      for (let i=this.__instances.length-1; i>=instIdx; i--) {
        this.__detachAndRemoveInstance(i);
      }
    }

    __detachInstance(idx) {
      let inst = this.__instances[idx];
      for (let i=0; i<inst.children.length; i++) {
        let el = inst.children[i];
        inst.root.appendChild(el);
      }
      return inst;
    }

    __attachInstance(idx, parent) {
      let inst = this.__instances[idx];
      parent.insertBefore(inst.root, this);
    }

    __detachAndRemoveInstance(idx) {
      let inst = this.__detachInstance(idx);
      if (inst) {
        this.__pool.push(inst);
      }
      this.__instances.splice(idx, 1);
    }

    __stampInstance(item, instIdx, itemIdx) {
      let model = {};
      model[this.as] = item;
      model[this.indexAs] = instIdx;
      model[this.itemsIndexAs] = itemIdx;
      return new this.__ctor(model);
    }

    __insertInstance(item, instIdx, itemIdx) {
      let inst = this.__pool.pop();
      if (inst) {
        // TODO(kschaaf): If the pool is shared across turns, hostProps
        // need to be re-set to reused instances in addition to item
        inst._setPendingProperty(this.as, item);
        inst._setPendingProperty(this.indexAs, instIdx);
        inst._setPendingProperty(this.itemsIndexAs, itemIdx);
        inst._flushProperties();
      } else {
        inst = this.__stampInstance(item, instIdx, itemIdx);
      }
      let beforeRow = this.__instances[instIdx + 1];
      let beforeNode = beforeRow ? beforeRow.children[0] : this;
      this.parentNode.insertBefore(inst.root, beforeNode);
      this.__instances[instIdx] = inst;
      return inst;
    }

    // Implements extension point from Templatize mixin
    /**
     * Shows or hides the template instance top level child elements. For
     * text nodes, `textContent` is removed while "hidden" and replaced when
     * "shown."
     * @param {boolean} hidden Set to true to hide the children;
     * set to false to show them.
     * @return {void}
     * @protected
     */
    _showHideChildren(hidden) {
      for (let i=0; i<this.__instances.length; i++) {
        this.__instances[i]._showHideChildren(hidden);
      }
    }

    // Called as a side effect of a host items.<key>.<path> path change,
    // responsible for notifying item.<path> changes to inst for key
    __handleItemPath(path, value) {
      let itemsPath = path.slice(6); // 'items.'.length == 6
      let dot = itemsPath.indexOf('.');
      let itemsIdx = dot < 0 ? itemsPath : itemsPath.substring(0, dot);
      // If path was index into array...
      if (itemsIdx == parseInt(itemsIdx, 10)) {
        let itemSubPath = dot < 0 ? '' : itemsPath.substring(dot+1);
        // If the path is observed, it will trigger a full refresh
        this.__handleObservedPaths(itemSubPath);
        // Note, even if a rull refresh is triggered, always do the path
        // notification because unless mutableData is used for dom-repeat
        // and all elements in the instance subtree, a full refresh may
        // not trigger the proper update.
        let instIdx = this.__itemsIdxToInstIdx[itemsIdx];
        let inst = this.__instances[instIdx];
        if (inst) {
          let itemPath = this.as + (itemSubPath ? '.' + itemSubPath : '');
          // This is effectively `notifyPath`, but avoids some of the overhead
          // of the public API
          inst._setPendingPropertyOrPath(itemPath, value, false, true);
          inst._flushProperties();
        }
        return true;
      }
    }

    /**
     * Returns the item associated with a given element stamped by
     * this `dom-repeat`.
     *
     * Note, to modify sub-properties of the item,
     * `modelForElement(el).set('item.<sub-prop>', value)`
     * should be used.
     *
     * @param {!HTMLElement} el Element for which to return the item.
     * @return {*} Item associated with the element.
     */
    itemForElement(el) {
      let instance = this.modelForElement(el);
      return instance && instance[this.as];
    }

    /**
     * Returns the inst index for a given element stamped by this `dom-repeat`.
     * If `sort` is provided, the index will reflect the sorted order (rather
     * than the original array order).
     *
     * @param {!HTMLElement} el Element for which to return the index.
     * @return {?number} Row index associated with the element (note this may
     *   not correspond to the array index if a user `sort` is applied).
     */
    indexForElement(el) {
      let instance = this.modelForElement(el);
      return instance && instance[this.indexAs];
    }

    /**
     * Returns the template "model" associated with a given element, which
     * serves as the binding scope for the template instance the element is
     * contained in. A template model is an instance of `Polymer.Base`, and
     * should be used to manipulate data associated with this template instance.
     *
     * Example:
     *
     *   let model = modelForElement(el);
     *   if (model.index < 10) {
     *     model.set('item.checked', true);
     *   }
     *
     * @param {!HTMLElement} el Element for which to return a template model.
     * @return {TemplateInstanceBase} Model representing the binding scope for
     *   the element.
     */
    modelForElement(el) {
      return Polymer.Templatize.modelForElement(this.template, el);
    }

  }

  customElements.define(DomRepeat.is, DomRepeat);

  /** @const */
  Polymer.DomRepeat = DomRepeat;

})();

</script>
<script>

(function() {
  'use strict';

  /**
   * The `<dom-if>` element will stamp a light-dom `<template>` child when
   * the `if` property becomes truthy, and the template can use Polymer
   * data-binding and declarative event features when used in the context of
   * a Polymer element's template.
   *
   * When `if` becomes falsy, the stamped content is hidden but not
   * removed from dom. When `if` subsequently becomes truthy again, the content
   * is simply re-shown. This approach is used due to its favorable performance
   * characteristics: the expense of creating template content is paid only
   * once and lazily.
   *
   * Set the `restamp` property to true to force the stamped content to be
   * created / destroyed when the `if` condition changes.
   *
   * @customElement
   * @polymer
   * @extends Polymer.Element
   * @memberof Polymer
   * @summary Custom element that conditionally stamps and hides or removes
   *   template content based on a boolean flag.
   */
  class DomIf extends Polymer.Element {

    // Not needed to find template; can be removed once the analyzer
    // can find the tag name from customElements.define call
    static get is() { return 'dom-if'; }

    static get template() { return null; }

    static get properties() {

      return {

        /**
         * Fired whenever DOM is added or removed/hidden by this template (by
         * default, rendering occurs lazily).  To force immediate rendering, call
         * `render`.
         *
         * @event dom-change
         */

        /**
         * A boolean indicating whether this template should stamp.
         */
        if: {
          type: Boolean,
          observer: '__debounceRender'
        },

        /**
         * When true, elements will be removed from DOM and discarded when `if`
         * becomes false and re-created and added back to the DOM when `if`
         * becomes true.  By default, stamped elements will be hidden but left
         * in the DOM when `if` becomes false, which is generally results
         * in better performance.
         */
        restamp: {
          type: Boolean,
          observer: '__debounceRender'
        }

      };

    }

    constructor() {
      super();
      this.__renderDebouncer = null;
      this.__invalidProps = null;
      this.__instance = null;
      this._lastIf = false;
      this.__ctor = null;
    }

    __debounceRender() {
      // Render is async for 2 reasons:
      // 1. To eliminate dom creation trashing if user code thrashes `if` in the
      //    same turn. This was more common in 1.x where a compound computed
      //    property could result in the result changing multiple times, but is
      //    mitigated to a large extent by batched property processing in 2.x.
      // 2. To avoid double object propagation when a bag including values bound
      //    to the `if` property as well as one or more hostProps could enqueue
      //    the <dom-if> to flush before the <template>'s host property
      //    forwarding. In that scenario creating an instance would result in
      //    the host props being set once, and then the enqueued changes on the
      //    template would set properties a second time, potentially causing an
      //    object to be set to an instance more than once.  Creating the
      //    instance async from flushing data ensures this doesn't happen. If
      //    we wanted a sync option in the future, simply having <dom-if> flush
      //    (or clear) its template's pending host properties before creating
      //    the instance would also avoid the problem.
      this.__renderDebouncer = Polymer.Debouncer.debounce(
            this.__renderDebouncer
          , Polymer.Async.microTask
          , () => this.__render());
      Polymer.enqueueDebouncer(this.__renderDebouncer);
    }

    /**
     * @return {void}
     */
    disconnectedCallback() {
      super.disconnectedCallback();
      if (!this.parentNode ||
          (this.parentNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE &&
           !this.parentNode.host)) {
        this.__teardownInstance();
      }
    }

    /**
     * @return {void}
     */
    connectedCallback() {
      super.connectedCallback();
      this.style.display = 'none';
      if (this.if) {
        this.__debounceRender();
      }
    }

    /**
     * Forces the element to render its content. Normally rendering is
     * asynchronous to a provoking change. This is done for efficiency so
     * that multiple changes trigger only a single render. The render method
     * should be called if, for example, template rendering is required to
     * validate application state.
     * @return {void}
     */
    render() {
      Polymer.flush();
    }

    __render() {
      if (this.if) {
        if (!this.__ensureInstance()) {
          // No template found yet
          return;
        }
        this._showHideChildren();
      } else if (this.restamp) {
        this.__teardownInstance();
      }
      if (!this.restamp && this.__instance) {
        this._showHideChildren();
      }
      if (this.if != this._lastIf) {
        this.dispatchEvent(new CustomEvent('dom-change', {
          bubbles: true,
          composed: true
        }));
        this._lastIf = this.if;
      }
    }

    __ensureInstance() {
      let parentNode = this.parentNode;
      // Guard against element being detached while render was queued
      if (parentNode) {
        if (!this.__ctor) {
          let template = /** @type {HTMLTemplateElement} */(this.querySelector('template'));
          if (!template) {
            // Wait until childList changes and template should be there by then
            let observer = new MutationObserver(() => {
              if (this.querySelector('template')) {
                observer.disconnect();
                this.__render();
              } else {
                throw new Error('dom-if requires a <template> child');
              }
            });
            observer.observe(this, {childList: true});
            return false;
          }
          this.__ctor = Polymer.Templatize.templatize(template, this, {
            // dom-if templatizer instances require `mutable: true`, as
            // `__syncHostProperties` relies on that behavior to sync objects
            mutableData: true,
            /**
             * @param {string} prop Property to forward
             * @param {*} value Value of property
             * @this {this}
             */
            forwardHostProp: function(prop, value) {
              if (this.__instance) {
                if (this.if) {
                  this.__instance.forwardHostProp(prop, value);
                } else {
                  // If we have an instance but are squelching host property
                  // forwarding due to if being false, note the invalidated
                  // properties so `__syncHostProperties` can sync them the next
                  // time `if` becomes true
                  this.__invalidProps = this.__invalidProps || Object.create(null);
                  this.__invalidProps[Polymer.Path.root(prop)] = true;
                }
              }
            }
          });
        }
        if (!this.__instance) {
          this.__instance = new this.__ctor();
          parentNode.insertBefore(this.__instance.root, this);
        } else {
          this.__syncHostProperties();
          let c$ = this.__instance.children;
          if (c$ && c$.length) {
            // Detect case where dom-if was re-attached in new position
            let lastChild = this.previousSibling;
            if (lastChild !== c$[c$.length-1]) {
              for (let i=0, n; (i<c$.length) && (n=c$[i]); i++) {
                parentNode.insertBefore(n, this);
              }
            }
          }
        }
      }
      return true;
    }

    __syncHostProperties() {
      let props = this.__invalidProps;
      if (props) {
        for (let prop in props) {
          this.__instance._setPendingProperty(prop, this.__dataHost[prop]);
        }
        this.__invalidProps = null;
        this.__instance._flushProperties();
      }
    }

    __teardownInstance() {
      if (this.__instance) {
        let c$ = this.__instance.children;
        if (c$ && c$.length) {
          // use first child parent, for case when dom-if may have been detached
          let parent = c$[0].parentNode;
          // Instance children may be disconnected from parents when dom-if
          // detaches if a tree was innerHTML'ed
          if (parent) {
            for (let i=0, n; (i<c$.length) && (n=c$[i]); i++) {
              parent.removeChild(n);
            }
          }
        }
        this.__instance = null;
        this.__invalidProps = null;
      }
    }

    /**
     * Shows or hides the template instance top level child elements. For
     * text nodes, `textContent` is removed while "hidden" and replaced when
     * "shown."
     * @return {void}
     * @protected
     */
    _showHideChildren() {
      let hidden = this.__hideTemplateChildren__ || !this.if;
      if (this.__instance) {
        this.__instance._showHideChildren(hidden);
      }
    }

  }

  customElements.define(DomIf.is, DomIf);

  /** @const */
  Polymer.DomIf = DomIf;

})();
</script>
<script>
(function() {
  'use strict';

  /**
   * Element mixin for recording dynamic associations between item paths in a
   * master `items` array and a `selected` array such that path changes to the
   * master array (at the host) element or elsewhere via data-binding) are
   * correctly propagated to items in the selected array and vice-versa.
   *
   * The `items` property accepts an array of user data, and via the
   * `select(item)` and `deselect(item)` API, updates the `selected` property
   * which may be bound to other parts of the application, and any changes to
   * sub-fields of `selected` item(s) will be kept in sync with items in the
   * `items` array.  When `multi` is false, `selected` is a property
   * representing the last selected item.  When `multi` is true, `selected`
   * is an array of multiply selected items.
   *
   * @polymer
   * @mixinFunction
   * @appliesMixin Polymer.ElementMixin
   * @memberof Polymer
   * @summary Element mixin for recording dynamic associations between item paths in a
   * master `items` array and a `selected` array
   */
  let ArraySelectorMixin = Polymer.dedupingMixin(superClass => {

    /**
     * @constructor
     * @extends {superClass}
     * @implements {Polymer_ElementMixin}
     * @private
     */
    let elementBase = Polymer.ElementMixin(superClass);

    /**
     * @polymer
     * @mixinClass
     * @implements {Polymer_ArraySelectorMixin}
     * @unrestricted
     */
    class ArraySelectorMixin extends elementBase {

      static get properties() {

        return {

          /**
           * An array containing items from which selection will be made.
           */
          items: {
            type: Array,
          },

          /**
           * When `true`, multiple items may be selected at once (in this case,
           * `selected` is an array of currently selected items).  When `false`,
           * only one item may be selected at a time.
           */
          multi: {
            type: Boolean,
            value: false,
          },

          /**
           * When `multi` is true, this is an array that contains any selected.
           * When `multi` is false, this is the currently selected item, or `null`
           * if no item is selected.
           * @type {?(Object|Array<!Object>)}
           */
          selected: {
            type: Object,
            notify: true
          },

          /**
           * When `multi` is false, this is the currently selected item, or `null`
           * if no item is selected.
           * @type {?Object}
           */
          selectedItem: {
            type: Object,
            notify: true
          },

          /**
           * When `true`, calling `select` on an item that is already selected
           * will deselect the item.
           */
          toggle: {
            type: Boolean,
            value: false
          }

        };
      }

      static get observers() {
        return ['__updateSelection(multi, items.*)'];
      }

      constructor() {
        super();
        this.__lastItems = null;
        this.__lastMulti = null;
        this.__selectedMap = null;
      }

      __updateSelection(multi, itemsInfo) {
        let path = itemsInfo.path;
        if (path == JSCompiler_renameProperty('items', this)) {
          // Case 1 - items array changed, so diff against previous array and
          // deselect any removed items and adjust selected indices
          let newItems = itemsInfo.base || [];
          let lastItems = this.__lastItems;
          let lastMulti = this.__lastMulti;
          if (multi !== lastMulti) {
            this.clearSelection();
          }
          if (lastItems) {
            let splices = Polymer.ArraySplice.calculateSplices(newItems, lastItems);
            this.__applySplices(splices);
          }
          this.__lastItems = newItems;
          this.__lastMulti = multi;
        } else if (itemsInfo.path == `${JSCompiler_renameProperty('items', this)}.splices`) {
          // Case 2 - got specific splice information describing the array mutation:
          // deselect any removed items and adjust selected indices
          this.__applySplices(itemsInfo.value.indexSplices);
        } else {
          // Case 3 - an array element was changed, so deselect the previous
          // item for that index if it was previously selected
          let part = path.slice(`${JSCompiler_renameProperty('items', this)}.`.length);
          let idx = parseInt(part, 10);
          if ((part.indexOf('.') < 0) && part == idx) {
            this.__deselectChangedIdx(idx);
          }
        }
      }

      __applySplices(splices) {
        let selected = this.__selectedMap;
        // Adjust selected indices and mark removals
        for (let i=0; i<splices.length; i++) {
          let s = splices[i];
          selected.forEach((idx, item) => {
            if (idx < s.index) {
              // no change
            } else if (idx >= s.index + s.removed.length) {
              // adjust index
              selected.set(item, idx + s.addedCount - s.removed.length);
            } else {
              // remove index
              selected.set(item, -1);
            }
          });
          for (let j=0; j<s.addedCount; j++) {
            let idx = s.index + j;
            if (selected.has(this.items[idx])) {
              selected.set(this.items[idx], idx);
            }
          }
        }
        // Update linked paths
        this.__updateLinks();
        // Remove selected items that were removed from the items array
        let sidx = 0;
        selected.forEach((idx, item) => {
          if (idx < 0) {
            if (this.multi) {
              this.splice(JSCompiler_renameProperty('selected', this), sidx, 1);
            } else {
              this.selected = this.selectedItem = null;
            }
            selected.delete(item);
          } else {
            sidx++;
          }
        });
      }

      __updateLinks() {
        this.__dataLinkedPaths = {};
        if (this.multi) {
          let sidx = 0;
          this.__selectedMap.forEach(idx => {
            if (idx >= 0) {
              this.linkPaths(`${JSCompiler_renameProperty('items', this)}.${idx}`, `${JSCompiler_renameProperty('selected', this)}.${sidx++}`);
            }
          });
        } else {
          this.__selectedMap.forEach(idx => {
            this.linkPaths(JSCompiler_renameProperty('selected', this), `${JSCompiler_renameProperty('items', this)}.${idx}`);
            this.linkPaths(JSCompiler_renameProperty('selectedItem', this), `${JSCompiler_renameProperty('items', this)}.${idx}`);
          });
        }
      }

      /**
       * Clears the selection state.
       * @return {void}
       */
      clearSelection() {
        // Unbind previous selection
        this.__dataLinkedPaths = {};
        // The selected map stores 3 pieces of information:
        // key: items array object
        // value: items array index
        // order: selected array index
        this.__selectedMap = new Map();
        // Initialize selection
        this.selected = this.multi ? [] : null;
        this.selectedItem = null;
      }

      /**
       * Returns whether the item is currently selected.
       *
       * @param {*} item Item from `items` array to test
       * @return {boolean} Whether the item is selected
       */
      isSelected(item) {
        return this.__selectedMap.has(item);
      }

      /**
       * Returns whether the item is currently selected.
       *
       * @param {number} idx Index from `items` array to test
       * @return {boolean} Whether the item is selected
       */
      isIndexSelected(idx) {
        return this.isSelected(this.items[idx]);
      }

      __deselectChangedIdx(idx) {
        let sidx = this.__selectedIndexForItemIndex(idx);
        if (sidx >= 0) {
          let i = 0;
          this.__selectedMap.forEach((idx, item) => {
            if (sidx == i++) {
              this.deselect(item);
            }
          });
        }
      }

      __selectedIndexForItemIndex(idx) {
        let selected = this.__dataLinkedPaths[`${JSCompiler_renameProperty('items', this)}.${idx}`];
        if (selected) {
          return parseInt(selected.slice(`${JSCompiler_renameProperty('selected', this)}.`.length), 10);
        }
      }

      /**
       * Deselects the given item if it is already selected.
       *
       * @param {*} item Item from `items` array to deselect
       * @return {void}
       */
      deselect(item) {
        let idx = this.__selectedMap.get(item);
        if (idx >= 0) {
          this.__selectedMap.delete(item);
          let sidx;
          if (this.multi) {
            sidx = this.__selectedIndexForItemIndex(idx);
          }
          this.__updateLinks();
          if (this.multi) {
            this.splice(JSCompiler_renameProperty('selected', this), sidx, 1);
          } else {
            this.selected = this.selectedItem = null;
          }
        }
      }

      /**
       * Deselects the given index if it is already selected.
       *
       * @param {number} idx Index from `items` array to deselect
       * @return {void}
       */
      deselectIndex(idx) {
        this.deselect(this.items[idx]);
      }

      /**
       * Selects the given item.  When `toggle` is true, this will automatically
       * deselect the item if already selected.
       *
       * @param {*} item Item from `items` array to select
       * @return {void}
       */
      select(item) {
        this.selectIndex(this.items.indexOf(item));
      }

      /**
       * Selects the given index.  When `toggle` is true, this will automatically
       * deselect the item if already selected.
       *
       * @param {number} idx Index from `items` array to select
       * @return {void}
       */
      selectIndex(idx) {
        let item = this.items[idx];
        if (!this.isSelected(item)) {
          if (!this.multi) {
            this.__selectedMap.clear();
          }
          this.__selectedMap.set(item, idx);
          this.__updateLinks();
          if (this.multi) {
            this.push(JSCompiler_renameProperty('selected', this), item);
          } else {
            this.selected = this.selectedItem = item;
          }
        } else if (this.toggle) {
          this.deselectIndex(idx);
        }
      }

    }

    return ArraySelectorMixin;

  });

  // export mixin
  Polymer.ArraySelectorMixin = ArraySelectorMixin;

  /**
   * @constructor
   * @extends {Polymer.Element}
   * @implements {Polymer_ArraySelectorMixin}
   * @private
   */
  let baseArraySelector = ArraySelectorMixin(Polymer.Element);

  /**
   * Element implementing the `Polymer.ArraySelector` mixin, which records
   * dynamic associations between item paths in a master `items` array and a
   * `selected` array such that path changes to the master array (at the host)
   * element or elsewhere via data-binding) are correctly propagated to items
   * in the selected array and vice-versa.
   *
   * The `items` property accepts an array of user data, and via the
   * `select(item)` and `deselect(item)` API, updates the `selected` property
   * which may be bound to other parts of the application, and any changes to
   * sub-fields of `selected` item(s) will be kept in sync with items in the
   * `items` array.  When `multi` is false, `selected` is a property
   * representing the last selected item.  When `multi` is true, `selected`
   * is an array of multiply selected items.
   *
   * Example:
   *
   * ```html
   * <dom-module id="employee-list">
   *
   *   <template>
   *
   *     <div> Employee list: </div>
   *     <dom-repeat id="employeeList" items="{{employees}}">
   *       <template>
   *         <div>First name: <span>{{item.first}}</span></div>
   *           <div>Last name: <span>{{item.last}}</span></div>
   *           <button on-click="toggleSelection">Select</button>
   *       </template>
   *     </dom-repeat>
   *
   *     <array-selector id="selector" items="{{employees}}" selected="{{selected}}" multi toggle></array-selector>
   *
   *     <div> Selected employees: </div>
   *     <dom-repeat items="{{selected}}">
   *       <template>
   *         <div>First name: <span>{{item.first}}</span></div>
   *         <div>Last name: <span>{{item.last}}</span></div>
   *       </template>
   *     </dom-repeat>
   *
   *   </template>
   *
   * </dom-module>
   * ```
   *
   * ```js
   *class EmployeeList extends Polymer.Element {
   *  static get is() { return 'employee-list'; }
   *  static get properties() {
   *    return {
   *      employees: {
   *        value() {
   *          return [
   *            {first: 'Bob', last: 'Smith'},
   *            {first: 'Sally', last: 'Johnson'},
   *            ...
   *          ];
   *        }
   *      }
   *    };
   *  }
   *  toggleSelection(e) {
   *    let item = this.$.employeeList.itemForElement(e.target);
   *    this.$.selector.select(item);
   *  }
   *}
   * ```
   *
   * @polymer
   * @customElement
   * @extends {baseArraySelector}
   * @appliesMixin Polymer.ArraySelectorMixin
   * @memberof Polymer
   * @summary Custom element that links paths between an input `items` array and
   *   an output `selected` item or array based on calls to its selection API.
   */
  class ArraySelector extends baseArraySelector {
    // Not needed to find template; can be removed once the analyzer
    // can find the tag name from customElements.define call
    static get is() { return 'array-selector'; }
  }
  customElements.define(ArraySelector.is, ArraySelector);

  /** @const */
  Polymer.ArraySelector = ArraySelector;

})();

</script>
<script>(function(){/*

Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
'use strict';var c=null,f=window.HTMLImports&&window.HTMLImports.whenReady||null,g;function h(a){requestAnimationFrame(function(){f?f(a):(c||(c=new Promise(function(a){g=a}),"complete"===document.readyState?g():document.addEventListener("readystatechange",function(){"complete"===document.readyState&&g()})),c.then(function(){a&&a()}))})};var k=null,l=null;function m(){this.customStyles=[];this.enqueued=!1;h(function(){window.ShadyCSS.flushCustomStyles&&window.ShadyCSS.flushCustomStyles()})}function n(a){!a.enqueued&&l&&(a.enqueued=!0,h(l))}m.prototype.c=function(a){a.__seenByShadyCSS||(a.__seenByShadyCSS=!0,this.customStyles.push(a),n(this))};m.prototype.b=function(a){if(a.__shadyCSSCachedStyle)return a.__shadyCSSCachedStyle;var b;a.getStyle?b=a.getStyle():b=a;return b};
m.prototype.a=function(){for(var a=this.customStyles,b=0;b<a.length;b++){var d=a[b];if(!d.__shadyCSSCachedStyle){var e=this.b(d);e&&(e=e.__appliedElement||e,k&&k(e),d.__shadyCSSCachedStyle=e)}}return a};m.prototype.addCustomStyle=m.prototype.c;m.prototype.getStyleForCustomStyle=m.prototype.b;m.prototype.processStyles=m.prototype.a;
Object.defineProperties(m.prototype,{transformCallback:{get:function(){return k},set:function(a){k=a}},validateCallback:{get:function(){return l},set:function(a){var b=!1;l||(b=!0);l=a;b&&n(this)}}});function p(a,b){for(var d in b)null===d?a.style.removeProperty(d):a.style.setProperty(d,b[d])};var q=!(window.ShadyDOM&&window.ShadyDOM.inUse),r;function t(a){r=a&&a.shimcssproperties?!1:q||!(navigator.userAgent.match(/AppleWebKit\/601|Edge\/15/)||!window.CSS||!CSS.supports||!CSS.supports("box-shadow","0 0 0 var(--foo)"))}var u;window.ShadyCSS&&void 0!==window.ShadyCSS.cssBuild&&(u=window.ShadyCSS.cssBuild);var v=!(!window.ShadyCSS||!window.ShadyCSS.disableRuntime);
window.ShadyCSS&&void 0!==window.ShadyCSS.nativeCss?r=window.ShadyCSS.nativeCss:window.ShadyCSS?(t(window.ShadyCSS),window.ShadyCSS=void 0):t(window.WebComponents&&window.WebComponents.flags);var w=r,x=u;var y=new m;window.ShadyCSS||(window.ShadyCSS={prepareTemplate:function(){},prepareTemplateDom:function(){},prepareTemplateStyles:function(){},styleSubtree:function(a,b){y.a();p(a,b)},styleElement:function(){y.a()},styleDocument:function(a){y.a();p(document.body,a)},getComputedStyleValue:function(a,b){return(a=window.getComputedStyle(a).getPropertyValue(b))?a.trim():""},flushCustomStyles:function(){},nativeCss:w,nativeShadow:q,cssBuild:x,disableRuntime:v});window.ShadyCSS.CustomStyleInterface=y;}).call(this);

//# sourceMappingURL=custom-style-interface.min.js.map
</script>
<script>
(function() {
  'use strict';

  const attr = 'include';

  const CustomStyleInterface = window.ShadyCSS.CustomStyleInterface;

  /**
   * Custom element for defining styles in the main document that can take
   * advantage of [shady DOM](https://github.com/webcomponents/shadycss) shims
   * for style encapsulation, custom properties, and custom mixins.
   *
   * - Document styles defined in a `<custom-style>` are shimmed to ensure they
   *   do not leak into local DOM when running on browsers without native
   *   Shadow DOM.
   * - Custom properties can be defined in a `<custom-style>`. Use the `html` selector
   *   to define custom properties that apply to all custom elements.
   * - Custom mixins can be defined in a `<custom-style>`, if you import the optional
   *   [apply shim](https://github.com/webcomponents/shadycss#about-applyshim)
   *   (`shadycss/apply-shim.html`).
   *
   * To use:
   *
   * - Import `custom-style.html`.
   * - Place a `<custom-style>` element in the main document, wrapping an inline `<style>` tag that
   *   contains the CSS rules you want to shim.
   *
   * For example:
   *
   * ```html
   * <!-- import apply shim--only required if using mixins -->
   * <link rel="import" href="bower_components/shadycss/apply-shim.html">
   * <!-- import custom-style element -->
   * <link rel="import" href="bower_components/polymer/lib/elements/custom-style.html">
   *
   * <custom-style>
   *   <style>
   *     html {
   *       --custom-color: blue;
   *       --custom-mixin: {
   *         font-weight: bold;
   *         color: red;
   *       };
   *     }
   *   </style>
   * </custom-style>
   * ```
   *
   * @customElement
   * @extends HTMLElement
   * @memberof Polymer
   * @summary Custom element for defining styles in the main document that can
   *   take advantage of Polymer's style scoping and custom properties shims.
   */
  class CustomStyle extends HTMLElement {
    constructor() {
      super();
      this._style = null;
      CustomStyleInterface.addCustomStyle(this);
    }
    /**
     * Returns the light-DOM `<style>` child this element wraps.  Upon first
     * call any style modules referenced via the `include` attribute will be
     * concatenated to this element's `<style>`.
     *
     * @return {HTMLStyleElement} This element's light-DOM `<style>`
     */
    'getStyle'() {
      if (this._style) {
        return this._style;
      }
      const style = /** @type {HTMLStyleElement} */(this.querySelector('style'));
      if (!style) {
        return null;
      }
      this._style = style;
      const include = style.getAttribute(attr);
      if (include) {
        style.removeAttribute(attr);
        style.textContent = Polymer.StyleGather.cssFromModules(include) + style.textContent;
      }
      /*
      HTML Imports styling the main document are deprecated in Chrome
      https://crbug.com/523952

      If this element is not in the main document, then it must be in an HTML Import document.
      In that case, move the custom style to the main document.

      The ordering of `<custom-style>` should stay the same as when loaded by HTML Imports, but there may be odd
      cases of ordering w.r.t the main document styles.
      */
      if (this.ownerDocument !== window.document) {
        window.document.head.appendChild(this);
      }
      return this._style;
    }
  }

  window.customElements.define('custom-style', CustomStyle);

  /** @const */
  Polymer.CustomStyle = CustomStyle;
})();
</script>
<script>
(function() {
  'use strict';

  let mutablePropertyChange;
  /** @suppress {missingProperties} */
  (() => {
    mutablePropertyChange = Polymer.MutableData._mutablePropertyChange;
  })();

  /**
   * Legacy element behavior to skip strict dirty-checking for objects and arrays,
   * (always consider them to be "dirty") for use on legacy API Polymer elements.
   *
   * By default, `Polymer.PropertyEffects` performs strict dirty checking on
   * objects, which means that any deep modifications to an object or array will
   * not be propagated unless "immutable" data patterns are used (i.e. all object
   * references from the root to the mutation were changed).
   *
   * Polymer also provides a proprietary data mutation and path notification API
   * (e.g. `notifyPath`, `set`, and array mutation API's) that allow efficient
   * mutation and notification of deep changes in an object graph to all elements
   * bound to the same object graph.
   *
   * In cases where neither immutable patterns nor the data mutation API can be
   * used, applying this mixin will cause Polymer to skip dirty checking for
   * objects and arrays (always consider them to be "dirty").  This allows a
   * user to make a deep modification to a bound object graph, and then either
   * simply re-set the object (e.g. `this.items = this.items`) or call `notifyPath`
   * (e.g. `this.notifyPath('items')`) to update the tree.  Note that all
   * elements that wish to be updated based on deep mutations must apply this
   * mixin or otherwise skip strict dirty checking for objects/arrays.
   * Specifically, any elements in the binding tree between the source of a
   * mutation and the consumption of it must apply this behavior or enable the
   * `Polymer.OptionalMutableDataBehavior`.
   *
   * In order to make the dirty check strategy configurable, see
   * `Polymer.OptionalMutableDataBehavior`.
   *
   * Note, the performance characteristics of propagating large object graphs
   * will be worse as opposed to using strict dirty checking with immutable
   * patterns or Polymer's path notification API.
   *
   * @polymerBehavior
   * @memberof Polymer
   * @summary Behavior to skip strict dirty-checking for objects and
   *   arrays
   */
  Polymer.MutableDataBehavior = {

    /**
     * Overrides `Polymer.PropertyEffects` to provide option for skipping
     * strict equality checking for Objects and Arrays.
     *
     * This method pulls the value to dirty check against from the `__dataTemp`
     * cache (rather than the normal `__data` cache) for Objects.  Since the temp
     * cache is cleared at the end of a turn, this implementation allows
     * side-effects of deep object changes to be processed by re-setting the
     * same object (using the temp cache as an in-turn backstop to prevent
     * cycles due to 2-way notification).
     *
     * @param {string} property Property name
     * @param {*} value New property value
     * @param {*} old Previous property value
     * @return {boolean} Whether the property should be considered a change
     * @protected
     */
    _shouldPropertyChange(property, value, old) {
      return mutablePropertyChange(this, property, value, old, true);
    }
  };

  /**
   * Legacy element behavior to add the optional ability to skip strict
   * dirty-checking for objects and arrays (always consider them to be
   * "dirty") by setting a `mutable-data` attribute on an element instance.
   *
   * By default, `Polymer.PropertyEffects` performs strict dirty checking on
   * objects, which means that any deep modifications to an object or array will
   * not be propagated unless "immutable" data patterns are used (i.e. all object
   * references from the root to the mutation were changed).
   *
   * Polymer also provides a proprietary data mutation and path notification API
   * (e.g. `notifyPath`, `set`, and array mutation API's) that allow efficient
   * mutation and notification of deep changes in an object graph to all elements
   * bound to the same object graph.
   *
   * In cases where neither immutable patterns nor the data mutation API can be
   * used, applying this mixin will allow Polymer to skip dirty checking for
   * objects and arrays (always consider them to be "dirty").  This allows a
   * user to make a deep modification to a bound object graph, and then either
   * simply re-set the object (e.g. `this.items = this.items`) or call `notifyPath`
   * (e.g. `this.notifyPath('items')`) to update the tree.  Note that all
   * elements that wish to be updated based on deep mutations must apply this
   * mixin or otherwise skip strict dirty checking for objects/arrays.
   * Specifically, any elements in the binding tree between the source of a
   * mutation and the consumption of it must enable this behavior or apply the
   * `Polymer.OptionalMutableDataBehavior`.
   *
   * While this behavior adds the ability to forgo Object/Array dirty checking,
   * the `mutableData` flag defaults to false and must be set on the instance.
   *
   * Note, the performance characteristics of propagating large object graphs
   * will be worse by relying on `mutableData: true` as opposed to using
   * strict dirty checking with immutable patterns or Polymer's path notification
   * API.
   *
   * @polymerBehavior
   * @memberof Polymer
   * @summary Behavior to optionally skip strict dirty-checking for objects and
   *   arrays
   */
  Polymer.OptionalMutableDataBehavior = {

    properties: {
      /**
       * Instance-level flag for configuring the dirty-checking strategy
       * for this element.  When true, Objects and Arrays will skip dirty
       * checking, otherwise strict equality checking will be used.
       */
      mutableData: Boolean
    },

    /**
     * Overrides `Polymer.PropertyEffects` to skip strict equality checking
     * for Objects and Arrays.
     *
     * Pulls the value to dirty check against from the `__dataTemp` cache
     * (rather than the normal `__data` cache) for Objects.  Since the temp
     * cache is cleared at the end of a turn, this implementation allows
     * side-effects of deep object changes to be processed by re-setting the
     * same object (using the temp cache as an in-turn backstop to prevent
     * cycles due to 2-way notification).
     *
     * @param {string} property Property name
     * @param {*} value New property value
     * @param {*} old Previous property value
     * @return {boolean} Whether the property should be considered a change
     * @this {this}
     * @protected
     */
    _shouldPropertyChange(property, value, old) {
      return mutablePropertyChange(this, property, value, old, this.mutableData);
    }
  };

})();

</script>
<script>
  // bc
  Polymer.Base = Polymer.LegacyElementMixin(HTMLElement).prototype;

  // NOTE: this is here for modulizer to export `html` for the module version of this file
  Polymer.html = Polymer.html;
</script>
<script>
  (function() {

  // Contains all connected resizables that do not have a parent.
  var ORPHANS = new Set();

  /**
   * `IronResizableBehavior` is a behavior that can be used in Polymer elements to
   * coordinate the flow of resize events between "resizers" (elements that
   *control the size or hidden state of their children) and "resizables" (elements
   *that need to be notified when they are resized or un-hidden by their parents
   *in order to take action on their new measurements).
   *
   * Elements that perform measurement should add the `IronResizableBehavior`
   *behavior to their element definition and listen for the `iron-resize` event on
   *themselves. This event will be fired when they become showing after having
   *been hidden, when they are resized explicitly by another resizable, or when
   *the window has been resized.
   *
   * Note, the `iron-resize` event is non-bubbling.
   *
   * @polymerBehavior Polymer.IronResizableBehavior
   * @demo demo/index.html
   **/
  Polymer.IronResizableBehavior = {
    properties: {
      /**
       * The closest ancestor element that implements `IronResizableBehavior`.
       */
      _parentResizable: {type: Object, observer: '_parentResizableChanged'},

      /**
       * True if this element is currently notifying its descendant elements of
       * resize.
       */
      _notifyingDescendant: {type: Boolean, value: false}
    },

    listeners: {
      'iron-request-resize-notifications': '_onIronRequestResizeNotifications'
    },

    created: function() {
      // We don't really need property effects on these, and also we want them
      // to be created before the `_parentResizable` observer fires:
      this._interestedResizables = [];
      this._boundNotifyResize = this.notifyResize.bind(this);
    },

    attached: function() {
      this._requestResizeNotifications();
    },

    detached: function() {
      if (this._parentResizable) {
        this._parentResizable.stopResizeNotificationsFor(this);
      } else {
        ORPHANS.delete(this);
        window.removeEventListener('resize', this._boundNotifyResize);
      }

      this._parentResizable = null;
    },

    /**
     * Can be called to manually notify a resizable and its descendant
     * resizables of a resize change.
     */
    notifyResize: function() {
      if (!this.isAttached) {
        return;
      }

      this._interestedResizables.forEach(function(resizable) {
        if (this.resizerShouldNotify(resizable)) {
          this._notifyDescendant(resizable);
        }
      }, this);

      this._fireResize();
    },

    /**
     * Used to assign the closest resizable ancestor to this resizable
     * if the ancestor detects a request for notifications.
     */
    assignParentResizable: function(parentResizable) {
      if (this._parentResizable) {
        this._parentResizable.stopResizeNotificationsFor(this);
      }

      this._parentResizable = parentResizable;

      if (parentResizable &&
          parentResizable._interestedResizables.indexOf(this) === -1) {
        parentResizable._interestedResizables.push(this);
        parentResizable.listen(this, 'iron-resize', '_onDescendantIronResize');
      }
    },

    /**
     * Used to remove a resizable descendant from the list of descendants
     * that should be notified of a resize change.
     */
    stopResizeNotificationsFor: function(target) {
      var index = this._interestedResizables.indexOf(target);

      if (index > -1) {
        this._interestedResizables.splice(index, 1);
        this.unlisten(target, 'iron-resize', '_onDescendantIronResize');
      }
    },

    /**
     * This method can be overridden to filter nested elements that should or
     * should not be notified by the current element. Return true if an element
     * should be notified, or false if it should not be notified.
     *
     * @param {HTMLElement} element A candidate descendant element that
     * implements `IronResizableBehavior`.
     * @return {boolean} True if the `element` should be notified of resize.
     */
    resizerShouldNotify: function(element) {
      return true;
    },

    _onDescendantIronResize: function(event) {
      if (this._notifyingDescendant) {
        event.stopPropagation();
        return;
      }

      // NOTE(cdata): In ShadowDOM, event retargeting makes echoing of the
      // otherwise non-bubbling event "just work." We do it manually here for
      // the case where Polymer is not using shadow roots for whatever reason:
      if (!Polymer.Settings.useShadow) {
        this._fireResize();
      }
    },

    _fireResize: function() {
      this.fire('iron-resize', null, {node: this, bubbles: false});
    },

    _onIronRequestResizeNotifications: function(event) {
      var target = /** @type {!EventTarget} */ (Polymer.dom(event).rootTarget);
      if (target === this) {
        return;
      }

      target.assignParentResizable(this);
      this._notifyDescendant(target);

      event.stopPropagation();
    },

    _parentResizableChanged: function(parentResizable) {
      if (parentResizable) {
        window.removeEventListener('resize', this._boundNotifyResize);
      }
    },

    _notifyDescendant: function(descendant) {
      // NOTE(cdata): In IE10, attached is fired on children first, so it's
      // important not to notify them if the parent is not attached yet (or
      // else they will get redundantly notified when the parent attaches).
      if (!this.isAttached) {
        return;
      }

      this._notifyingDescendant = true;
      descendant.notifyResize();
      this._notifyingDescendant = false;
    },

    _requestResizeNotifications: function() {
      if (!this.isAttached)
        return;

      // NOTE(valdrin) In CustomElements v1 with native HTMLImports, the order
      // of imports affects the order of `attached` callbacks (see
      // webcomponents/custom-elements#15). This might cause a child to notify
      // parents too early (as the parent still has to be upgraded), resulting in
      // a parent not able to keep track of the `_interestedResizables`. To solve
      // this, we wait for the document to be done loading before firing the
      // event.
      if (document.readyState === 'loading') {
        var _requestResizeNotifications =
            this._requestResizeNotifications.bind(this);
        document.addEventListener(
            'readystatechange', function readystatechanged() {
              document.removeEventListener('readystatechange', readystatechanged);
              _requestResizeNotifications();
            });
      } else {
        this._findParent();

        if (!this._parentResizable) {
          // If this resizable is an orphan, tell other orphans to try to find
          // their parent again, in case it's this resizable.
          ORPHANS.forEach(function(orphan) {
            if (orphan !== this) {
              orphan._findParent();
            }
          }, this);

          window.addEventListener('resize', this._boundNotifyResize);
          this.notifyResize();
        } else {
          // If this resizable has a parent, tell other child resizables of
          // that parent to try finding their parent again, in case it's this
          // resizable.
          this._parentResizable._interestedResizables
              .forEach(function(resizable) {
                if (resizable !== this) {
                  resizable._findParent();
                }
              }, this);
        }
      }
    },

    _findParent: function() {
      this.assignParentResizable(null);
      this.fire(
          'iron-request-resize-notifications',
          null,
          {node: this, bubbles: true, cancelable: true});

      if (!this._parentResizable) {
        ORPHANS.add(this);
      } else {
        ORPHANS.delete(this);
      }
    }
  };
  })();
</script>
<custom-style>
  <style is="custom-style">
    html {

      /* Material Design color palette for Google products */

      --google-red-100: #f4c7c3;
      --google-red-300: #e67c73;
      --google-red-500: #db4437;
      --google-red-700: #c53929;

      --google-blue-100: #c6dafc;
      --google-blue-300: #7baaf7;
      --google-blue-500: #4285f4;
      --google-blue-700: #3367d6;

      --google-green-100: #b7e1cd;
      --google-green-300: #57bb8a;
      --google-green-500: #0f9d58;
      --google-green-700: #0b8043;

      --google-yellow-100: #fce8b2;
      --google-yellow-300: #f7cb4d;
      --google-yellow-500: #f4b400;
      --google-yellow-700: #f09300;

      --google-grey-100: #f5f5f5;
      --google-grey-300: #e0e0e0;
      --google-grey-500: #9e9e9e;
      --google-grey-700: #616161;

      /* Material Design color palette from online spec document */

      --paper-red-50: #ffebee;
      --paper-red-100: #ffcdd2;
      --paper-red-200: #ef9a9a;
      --paper-red-300: #e57373;
      --paper-red-400: #ef5350;
      --paper-red-500: #f44336;
      --paper-red-600: #e53935;
      --paper-red-700: #d32f2f;
      --paper-red-800: #c62828;
      --paper-red-900: #b71c1c;
      --paper-red-a100: #ff8a80;
      --paper-red-a200: #ff5252;
      --paper-red-a400: #ff1744;
      --paper-red-a700: #d50000;

      --paper-pink-50: #fce4ec;
      --paper-pink-100: #f8bbd0;
      --paper-pink-200: #f48fb1;
      --paper-pink-300: #f06292;
      --paper-pink-400: #ec407a;
      --paper-pink-500: #e91e63;
      --paper-pink-600: #d81b60;
      --paper-pink-700: #c2185b;
      --paper-pink-800: #ad1457;
      --paper-pink-900: #880e4f;
      --paper-pink-a100: #ff80ab;
      --paper-pink-a200: #ff4081;
      --paper-pink-a400: #f50057;
      --paper-pink-a700: #c51162;

      --paper-purple-50: #f3e5f5;
      --paper-purple-100: #e1bee7;
      --paper-purple-200: #ce93d8;
      --paper-purple-300: #ba68c8;
      --paper-purple-400: #ab47bc;
      --paper-purple-500: #9c27b0;
      --paper-purple-600: #8e24aa;
      --paper-purple-700: #7b1fa2;
      --paper-purple-800: #6a1b9a;
      --paper-purple-900: #4a148c;
      --paper-purple-a100: #ea80fc;
      --paper-purple-a200: #e040fb;
      --paper-purple-a400: #d500f9;
      --paper-purple-a700: #aa00ff;

      --paper-deep-purple-50: #ede7f6;
      --paper-deep-purple-100: #d1c4e9;
      --paper-deep-purple-200: #b39ddb;
      --paper-deep-purple-300: #9575cd;
      --paper-deep-purple-400: #7e57c2;
      --paper-deep-purple-500: #673ab7;
      --paper-deep-purple-600: #5e35b1;
      --paper-deep-purple-700: #512da8;
      --paper-deep-purple-800: #4527a0;
      --paper-deep-purple-900: #311b92;
      --paper-deep-purple-a100: #b388ff;
      --paper-deep-purple-a200: #7c4dff;
      --paper-deep-purple-a400: #651fff;
      --paper-deep-purple-a700: #6200ea;

      --paper-indigo-50: #e8eaf6;
      --paper-indigo-100: #c5cae9;
      --paper-indigo-200: #9fa8da;
      --paper-indigo-300: #7986cb;
      --paper-indigo-400: #5c6bc0;
      --paper-indigo-500: #3f51b5;
      --paper-indigo-600: #3949ab;
      --paper-indigo-700: #303f9f;
      --paper-indigo-800: #283593;
      --paper-indigo-900: #1a237e;
      --paper-indigo-a100: #8c9eff;
      --paper-indigo-a200: #536dfe;
      --paper-indigo-a400: #3d5afe;
      --paper-indigo-a700: #304ffe;

      --paper-blue-50: #e3f2fd;
      --paper-blue-100: #bbdefb;
      --paper-blue-200: #90caf9;
      --paper-blue-300: #64b5f6;
      --paper-blue-400: #42a5f5;
      --paper-blue-500: #2196f3;
      --paper-blue-600: #1e88e5;
      --paper-blue-700: #1976d2;
      --paper-blue-800: #1565c0;
      --paper-blue-900: #0d47a1;
      --paper-blue-a100: #82b1ff;
      --paper-blue-a200: #448aff;
      --paper-blue-a400: #2979ff;
      --paper-blue-a700: #2962ff;

      --paper-light-blue-50: #e1f5fe;
      --paper-light-blue-100: #b3e5fc;
      --paper-light-blue-200: #81d4fa;
      --paper-light-blue-300: #4fc3f7;
      --paper-light-blue-400: #29b6f6;
      --paper-light-blue-500: #03a9f4;
      --paper-light-blue-600: #039be5;
      --paper-light-blue-700: #0288d1;
      --paper-light-blue-800: #0277bd;
      --paper-light-blue-900: #01579b;
      --paper-light-blue-a100: #80d8ff;
      --paper-light-blue-a200: #40c4ff;
      --paper-light-blue-a400: #00b0ff;
      --paper-light-blue-a700: #0091ea;

      --paper-cyan-50: #e0f7fa;
      --paper-cyan-100: #b2ebf2;
      --paper-cyan-200: #80deea;
      --paper-cyan-300: #4dd0e1;
      --paper-cyan-400: #26c6da;
      --paper-cyan-500: #00bcd4;
      --paper-cyan-600: #00acc1;
      --paper-cyan-700: #0097a7;
      --paper-cyan-800: #00838f;
      --paper-cyan-900: #006064;
      --paper-cyan-a100: #84ffff;
      --paper-cyan-a200: #18ffff;
      --paper-cyan-a400: #00e5ff;
      --paper-cyan-a700: #00b8d4;

      --paper-teal-50: #e0f2f1;
      --paper-teal-100: #b2dfdb;
      --paper-teal-200: #80cbc4;
      --paper-teal-300: #4db6ac;
      --paper-teal-400: #26a69a;
      --paper-teal-500: #009688;
      --paper-teal-600: #00897b;
      --paper-teal-700: #00796b;
      --paper-teal-800: #00695c;
      --paper-teal-900: #004d40;
      --paper-teal-a100: #a7ffeb;
      --paper-teal-a200: #64ffda;
      --paper-teal-a400: #1de9b6;
      --paper-teal-a700: #00bfa5;

      --paper-green-50: #e8f5e9;
      --paper-green-100: #c8e6c9;
      --paper-green-200: #a5d6a7;
      --paper-green-300: #81c784;
      --paper-green-400: #66bb6a;
      --paper-green-500: #4caf50;
      --paper-green-600: #43a047;
      --paper-green-700: #388e3c;
      --paper-green-800: #2e7d32;
      --paper-green-900: #1b5e20;
      --paper-green-a100: #b9f6ca;
      --paper-green-a200: #69f0ae;
      --paper-green-a400: #00e676;
      --paper-green-a700: #00c853;

      --paper-light-green-50: #f1f8e9;
      --paper-light-green-100: #dcedc8;
      --paper-light-green-200: #c5e1a5;
      --paper-light-green-300: #aed581;
      --paper-light-green-400: #9ccc65;
      --paper-light-green-500: #8bc34a;
      --paper-light-green-600: #7cb342;
      --paper-light-green-700: #689f38;
      --paper-light-green-800: #558b2f;
      --paper-light-green-900: #33691e;
      --paper-light-green-a100: #ccff90;
      --paper-light-green-a200: #b2ff59;
      --paper-light-green-a400: #76ff03;
      --paper-light-green-a700: #64dd17;

      --paper-lime-50: #f9fbe7;
      --paper-lime-100: #f0f4c3;
      --paper-lime-200: #e6ee9c;
      --paper-lime-300: #dce775;
      --paper-lime-400: #d4e157;
      --paper-lime-500: #cddc39;
      --paper-lime-600: #c0ca33;
      --paper-lime-700: #afb42b;
      --paper-lime-800: #9e9d24;
      --paper-lime-900: #827717;
      --paper-lime-a100: #f4ff81;
      --paper-lime-a200: #eeff41;
      --paper-lime-a400: #c6ff00;
      --paper-lime-a700: #aeea00;

      --paper-yellow-50: #fffde7;
      --paper-yellow-100: #fff9c4;
      --paper-yellow-200: #fff59d;
      --paper-yellow-300: #fff176;
      --paper-yellow-400: #ffee58;
      --paper-yellow-500: #ffeb3b;
      --paper-yellow-600: #fdd835;
      --paper-yellow-700: #fbc02d;
      --paper-yellow-800: #f9a825;
      --paper-yellow-900: #f57f17;
      --paper-yellow-a100: #ffff8d;
      --paper-yellow-a200: #ffff00;
      --paper-yellow-a400: #ffea00;
      --paper-yellow-a700: #ffd600;

      --paper-amber-50: #fff8e1;
      --paper-amber-100: #ffecb3;
      --paper-amber-200: #ffe082;
      --paper-amber-300: #ffd54f;
      --paper-amber-400: #ffca28;
      --paper-amber-500: #ffc107;
      --paper-amber-600: #ffb300;
      --paper-amber-700: #ffa000;
      --paper-amber-800: #ff8f00;
      --paper-amber-900: #ff6f00;
      --paper-amber-a100: #ffe57f;
      --paper-amber-a200: #ffd740;
      --paper-amber-a400: #ffc400;
      --paper-amber-a700: #ffab00;

      --paper-orange-50: #fff3e0;
      --paper-orange-100: #ffe0b2;
      --paper-orange-200: #ffcc80;
      --paper-orange-300: #ffb74d;
      --paper-orange-400: #ffa726;
      --paper-orange-500: #ff9800;
      --paper-orange-600: #fb8c00;
      --paper-orange-700: #f57c00;
      --paper-orange-800: #ef6c00;
      --paper-orange-900: #e65100;
      --paper-orange-a100: #ffd180;
      --paper-orange-a200: #ffab40;
      --paper-orange-a400: #ff9100;
      --paper-orange-a700: #ff6500;

      --paper-deep-orange-50: #fbe9e7;
      --paper-deep-orange-100: #ffccbc;
      --paper-deep-orange-200: #ffab91;
      --paper-deep-orange-300: #ff8a65;
      --paper-deep-orange-400: #ff7043;
      --paper-deep-orange-500: #ff5722;
      --paper-deep-orange-600: #f4511e;
      --paper-deep-orange-700: #e64a19;
      --paper-deep-orange-800: #d84315;
      --paper-deep-orange-900: #bf360c;
      --paper-deep-orange-a100: #ff9e80;
      --paper-deep-orange-a200: #ff6e40;
      --paper-deep-orange-a400: #ff3d00;
      --paper-deep-orange-a700: #dd2c00;

      --paper-brown-50: #efebe9;
      --paper-brown-100: #d7ccc8;
      --paper-brown-200: #bcaaa4;
      --paper-brown-300: #a1887f;
      --paper-brown-400: #8d6e63;
      --paper-brown-500: #795548;
      --paper-brown-600: #6d4c41;
      --paper-brown-700: #5d4037;
      --paper-brown-800: #4e342e;
      --paper-brown-900: #3e2723;

      --paper-grey-50: #fafafa;
      --paper-grey-100: #f5f5f5;
      --paper-grey-200: #eeeeee;
      --paper-grey-300: #e0e0e0;
      --paper-grey-400: #bdbdbd;
      --paper-grey-500: #9e9e9e;
      --paper-grey-600: #757575;
      --paper-grey-700: #616161;
      --paper-grey-800: #424242;
      --paper-grey-900: #212121;

      --paper-blue-grey-50: #eceff1;
      --paper-blue-grey-100: #cfd8dc;
      --paper-blue-grey-200: #b0bec5;
      --paper-blue-grey-300: #90a4ae;
      --paper-blue-grey-400: #78909c;
      --paper-blue-grey-500: #607d8b;
      --paper-blue-grey-600: #546e7a;
      --paper-blue-grey-700: #455a64;
      --paper-blue-grey-800: #37474f;
      --paper-blue-grey-900: #263238;

      /* opacity for dark text on a light background */
      --dark-divider-opacity: 0.12;
      --dark-disabled-opacity: 0.38; /* or hint text or icon */
      --dark-secondary-opacity: 0.54;
      --dark-primary-opacity: 0.87;

      /* opacity for light text on a dark background */
      --light-divider-opacity: 0.12;
      --light-disabled-opacity: 0.3; /* or hint text or icon */
      --light-secondary-opacity: 0.7;
      --light-primary-opacity: 1.0;

    }

  </style>
</custom-style>
<script>

  /** @polymerBehavior */
  Polymer.PaperSpinnerBehavior = {

    properties: {
      /**
       * Displays the spinner.
       */
      active: {
        type: Boolean,
        value: false,
        reflectToAttribute: true,
        observer: '__activeChanged'
      },

      /**
       * Alternative text content for accessibility support.
       * If alt is present, it will add an aria-label whose content matches alt when active.
       * If alt is not present, it will default to 'loading' as the alt value.
       */
      alt: {
        type: String,
        value: 'loading',
        observer: '__altChanged'
      },

      __coolingDown: {
        type: Boolean,
        value: false
      }
    },

    __computeContainerClasses: function(active, coolingDown) {
      return [
        active || coolingDown ? 'active' : '',
        coolingDown ? 'cooldown' : ''
      ].join(' ');
    },

    __activeChanged: function(active, old) {
      this.__setAriaHidden(!active);
      this.__coolingDown = !active && old;
    },

    __altChanged: function(alt) {
      // user-provided `aria-label` takes precedence over prototype default
      if (alt === 'loading') {
        this.alt = this.getAttribute('aria-label') || alt;
      } else {
        this.__setAriaHidden(alt==='');
        this.setAttribute('aria-label', alt);
      }
    },

    __setAriaHidden: function(hidden) {
      var attr = 'aria-hidden';
      if (hidden) {
        this.setAttribute(attr, 'true');
      } else {
        this.removeAttribute(attr);
      }
    },

    __reset: function() {
      this.active = false;
      this.__coolingDown = false;
    }
  };
</script>
<dom-module id="paper-spinner-styles" assetpath="bower_components/paper-spinner/">
  <template>
    <style>
      /*
      /**************************/
      /* STYLES FOR THE SPINNER */
      /**************************/

      /*
       * Constants:
       *      ARCSIZE     = 270 degrees (amount of circle the arc takes up)
       *      ARCTIME     = 1333ms (time it takes to expand and contract arc)
       *      ARCSTARTROT = 216 degrees (how much the start location of the arc
       *                                should rotate each time, 216 gives us a
       *                                5 pointed star shape (it's 360/5 * 3).
       *                                For a 7 pointed star, we might do
       *                                360/7 * 3 = 154.286)
       *      SHRINK_TIME = 400ms
       */

      :host {
        display: inline-block;
        position: relative;
        width: 28px;
        height: 28px;

        /* 360 * ARCTIME / (ARCSTARTROT + (360-ARCSIZE)) */
        --paper-spinner-container-rotation-duration: 1568ms;

        /* ARCTIME */
        --paper-spinner-expand-contract-duration: 1333ms;

        /* 4 * ARCTIME */
        --paper-spinner-full-cycle-duration: 5332ms;

        /* SHRINK_TIME */
        --paper-spinner-cooldown-duration: 400ms;
      }

      #spinnerContainer {
        width: 100%;
        height: 100%;

        /* The spinner does not have any contents that would have to be
         * flipped if the direction changes. Always use ltr so that the
         * style works out correctly in both cases. */
        direction: ltr;
      }

      #spinnerContainer.active {
        -webkit-animation: container-rotate var(--paper-spinner-container-rotation-duration) linear infinite;
        animation: container-rotate var(--paper-spinner-container-rotation-duration) linear infinite;
      }

      @-webkit-keyframes container-rotate {
        to { -webkit-transform: rotate(360deg) }
      }

      @keyframes container-rotate {
        to { transform: rotate(360deg) }
      }

      .spinner-layer {
        position: absolute;
        width: 100%;
        height: 100%;
        opacity: 0;
        white-space: nowrap;
        color: var(--paper-spinner-color, var(--google-blue-500));
      }

      .layer-1 {
        color: var(--paper-spinner-layer-1-color, var(--google-blue-500));
      }

      .layer-2 {
        color: var(--paper-spinner-layer-2-color, var(--google-red-500));
      }

      .layer-3 {
        color: var(--paper-spinner-layer-3-color, var(--google-yellow-500));
      }

      .layer-4 {
        color: var(--paper-spinner-layer-4-color, var(--google-green-500));
      }

      /**
       * IMPORTANT NOTE ABOUT CSS ANIMATION PROPERTIES (keanulee):
       *
       * iOS Safari (tested on iOS 8.1) does not handle animation-delay very well - it doesn't
       * guarantee that the animation will start _exactly_ after that value. So we avoid using
       * animation-delay and instead set custom keyframes for each color (as layer-2undant as it
       * seems).
       */
      .active .spinner-layer {
        -webkit-animation-name: fill-unfill-rotate;
        -webkit-animation-duration: var(--paper-spinner-full-cycle-duration);
        -webkit-animation-timing-function: cubic-bezier(0.4, 0.0, 0.2, 1);
        -webkit-animation-iteration-count: infinite;
        animation-name: fill-unfill-rotate;
        animation-duration: var(--paper-spinner-full-cycle-duration);
        animation-timing-function: cubic-bezier(0.4, 0.0, 0.2, 1);
        animation-iteration-count: infinite;
        opacity: 1;
      }

      .active .spinner-layer.layer-1 {
        -webkit-animation-name: fill-unfill-rotate, layer-1-fade-in-out;
        animation-name: fill-unfill-rotate, layer-1-fade-in-out;
      }

      .active .spinner-layer.layer-2 {
        -webkit-animation-name: fill-unfill-rotate, layer-2-fade-in-out;
        animation-name: fill-unfill-rotate, layer-2-fade-in-out;
      }

      .active .spinner-layer.layer-3 {
        -webkit-animation-name: fill-unfill-rotate, layer-3-fade-in-out;
        animation-name: fill-unfill-rotate, layer-3-fade-in-out;
      }

      .active .spinner-layer.layer-4 {
        -webkit-animation-name: fill-unfill-rotate, layer-4-fade-in-out;
        animation-name: fill-unfill-rotate, layer-4-fade-in-out;
      }

      @-webkit-keyframes fill-unfill-rotate {
        12.5% { -webkit-transform: rotate(135deg) } /* 0.5 * ARCSIZE */
        25%   { -webkit-transform: rotate(270deg) } /* 1   * ARCSIZE */
        37.5% { -webkit-transform: rotate(405deg) } /* 1.5 * ARCSIZE */
        50%   { -webkit-transform: rotate(540deg) } /* 2   * ARCSIZE */
        62.5% { -webkit-transform: rotate(675deg) } /* 2.5 * ARCSIZE */
        75%   { -webkit-transform: rotate(810deg) } /* 3   * ARCSIZE */
        87.5% { -webkit-transform: rotate(945deg) } /* 3.5 * ARCSIZE */
        to    { -webkit-transform: rotate(1080deg) } /* 4   * ARCSIZE */
      }

      @keyframes fill-unfill-rotate {
        12.5% { transform: rotate(135deg) } /* 0.5 * ARCSIZE */
        25%   { transform: rotate(270deg) } /* 1   * ARCSIZE */
        37.5% { transform: rotate(405deg) } /* 1.5 * ARCSIZE */
        50%   { transform: rotate(540deg) } /* 2   * ARCSIZE */
        62.5% { transform: rotate(675deg) } /* 2.5 * ARCSIZE */
        75%   { transform: rotate(810deg) } /* 3   * ARCSIZE */
        87.5% { transform: rotate(945deg) } /* 3.5 * ARCSIZE */
        to    { transform: rotate(1080deg) } /* 4   * ARCSIZE */
      }

      @-webkit-keyframes layer-1-fade-in-out {
        0% { opacity: 1 }
        25% { opacity: 1 }
        26% { opacity: 0 }
        89% { opacity: 0 }
        90% { opacity: 1 }
        to { opacity: 1 }
      }

      @keyframes layer-1-fade-in-out {
        0% { opacity: 1 }
        25% { opacity: 1 }
        26% { opacity: 0 }
        89% { opacity: 0 }
        90% { opacity: 1 }
        to { opacity: 1 }
      }

      @-webkit-keyframes layer-2-fade-in-out {
        0% { opacity: 0 }
        15% { opacity: 0 }
        25% { opacity: 1 }
        50% { opacity: 1 }
        51% { opacity: 0 }
        to { opacity: 0 }
      }

      @keyframes layer-2-fade-in-out {
        0% { opacity: 0 }
        15% { opacity: 0 }
        25% { opacity: 1 }
        50% { opacity: 1 }
        51% { opacity: 0 }
        to { opacity: 0 }
      }

      @-webkit-keyframes layer-3-fade-in-out {
        0% { opacity: 0 }
        40% { opacity: 0 }
        50% { opacity: 1 }
        75% { opacity: 1 }
        76% { opacity: 0 }
        to { opacity: 0 }
      }

      @keyframes layer-3-fade-in-out {
        0% { opacity: 0 }
        40% { opacity: 0 }
        50% { opacity: 1 }
        75% { opacity: 1 }
        76% { opacity: 0 }
        to { opacity: 0 }
      }

      @-webkit-keyframes layer-4-fade-in-out {
        0% { opacity: 0 }
        65% { opacity: 0 }
        75% { opacity: 1 }
        90% { opacity: 1 }
        to { opacity: 0 }
      }

      @keyframes layer-4-fade-in-out {
        0% { opacity: 0 }
        65% { opacity: 0 }
        75% { opacity: 1 }
        90% { opacity: 1 }
        to { opacity: 0 }
      }

      .circle-clipper {
        display: inline-block;
        position: relative;
        width: 50%;
        height: 100%;
        overflow: hidden;
      }

      /**
       * Patch the gap that appear between the two adjacent div.circle-clipper while the
       * spinner is rotating (appears on Chrome 50, Safari 9.1.1, and Edge).
       */
      .spinner-layer::after {
        left: 45%;
        width: 10%;
        border-top-style: solid;
      }

      .spinner-layer::after,
      .circle-clipper::after {
        content: '';
        box-sizing: border-box;
        position: absolute;
        top: 0;
        border-width: var(--paper-spinner-stroke-width, 3px);
        border-radius: 50%;
      }

      .circle-clipper::after {
        bottom: 0;
        width: 200%;
        border-style: solid;
        border-bottom-color: transparent !important;
      }

      .circle-clipper.left::after {
        left: 0;
        border-right-color: transparent !important;
        -webkit-transform: rotate(129deg);
        transform: rotate(129deg);
      }

      .circle-clipper.right::after {
        left: -100%;
        border-left-color: transparent !important;
        -webkit-transform: rotate(-129deg);
        transform: rotate(-129deg);
      }

      .active .gap-patch::after,
      .active .circle-clipper::after {
        -webkit-animation-duration: var(--paper-spinner-expand-contract-duration);
        -webkit-animation-timing-function: cubic-bezier(0.4, 0.0, 0.2, 1);
        -webkit-animation-iteration-count: infinite;
        animation-duration: var(--paper-spinner-expand-contract-duration);
        animation-timing-function: cubic-bezier(0.4, 0.0, 0.2, 1);
        animation-iteration-count: infinite;
      }

      .active .circle-clipper.left::after {
        -webkit-animation-name: left-spin;
        animation-name: left-spin;
      }

      .active .circle-clipper.right::after {
        -webkit-animation-name: right-spin;
        animation-name: right-spin;
      }

      @-webkit-keyframes left-spin {
        0% { -webkit-transform: rotate(130deg) }
        50% { -webkit-transform: rotate(-5deg) }
        to { -webkit-transform: rotate(130deg) }
      }

      @keyframes left-spin {
        0% { transform: rotate(130deg) }
        50% { transform: rotate(-5deg) }
        to { transform: rotate(130deg) }
      }

      @-webkit-keyframes right-spin {
        0% { -webkit-transform: rotate(-130deg) }
        50% { -webkit-transform: rotate(5deg) }
        to { -webkit-transform: rotate(-130deg) }
      }

      @keyframes right-spin {
        0% { transform: rotate(-130deg) }
        50% { transform: rotate(5deg) }
        to { transform: rotate(-130deg) }
      }

      #spinnerContainer.cooldown {
        -webkit-animation: container-rotate var(--paper-spinner-container-rotation-duration) linear infinite, fade-out var(--paper-spinner-cooldown-duration) cubic-bezier(0.4, 0.0, 0.2, 1);
        animation: container-rotate var(--paper-spinner-container-rotation-duration) linear infinite, fade-out var(--paper-spinner-cooldown-duration) cubic-bezier(0.4, 0.0, 0.2, 1);
      }

      @-webkit-keyframes fade-out {
        0% { opacity: 1 }
        to { opacity: 0 }
      }

      @keyframes fade-out {
        0% { opacity: 1 }
        to { opacity: 0 }
      }
    </style>
  </template>
</dom-module>
<dom-module id="paper-spinner" assetpath="bower_components/paper-spinner/">
  <template strip-whitespace="">
    <style include="paper-spinner-styles"></style>

    <div id="spinnerContainer" class-name="[[__computeContainerClasses(active, __coolingDown)]]" on-animationend="__reset" on-webkit-animation-end="__reset">
      <div class="spinner-layer layer-1">
        <div class="circle-clipper left"></div>
        <div class="circle-clipper right"></div>
      </div>

      <div class="spinner-layer layer-2">
        <div class="circle-clipper left"></div>
        <div class="circle-clipper right"></div>
      </div>

      <div class="spinner-layer layer-3">
        <div class="circle-clipper left"></div>
        <div class="circle-clipper right"></div>
      </div>

      <div class="spinner-layer layer-4">
        <div class="circle-clipper left"></div>
        <div class="circle-clipper right"></div>
      </div>
    </div>
  </template>

  <script>
    Polymer({
      is: 'paper-spinner',

      behaviors: [
        Polymer.PaperSpinnerBehavior
      ]
    });
  </script>
</dom-module>
<dom-module id="paper-spinner-lite" assetpath="bower_components/paper-spinner/">
  <template strip-whitespace="">
    <style include="paper-spinner-styles"></style>

    <div id="spinnerContainer" class-name="[[__computeContainerClasses(active, __coolingDown)]]" on-animationend="__reset" on-webkit-animation-end="__reset">
      <div class="spinner-layer">
        <div class="circle-clipper left"></div>
        <div class="circle-clipper right"></div>
      </div>
    </div>
  </template>

  <script>
    Polymer({
      is: 'paper-spinner-lite',

      behaviors: [
        Polymer.PaperSpinnerBehavior
      ]
    });
  </script>
</dom-module>
<dom-module id="shared-settings">
    <template>
        <style>
            :host {
                margin: 2px;
            }

            #chartSettingsContainer {
                position: absolute;
                top: 0;
                right: 0;
                border: 1px solid #000;
                border-radius: 2px;
                margin: 1px;
                z-index: 3;
            }

            #chartFixedAxisIcon {
                float: right;
                width: 24px;
                height: 24px;
                padding: 1px;
                padding-top: 5px;
            }

            .dragHandle {
                z-index: 2;
            }

            #chartSettingsIcon,
            #chartColorsIcon,
            #chartRemoveIcon,
            #chartExpandIcon,
            #chartContractIcon {
                float: right;
                width: 24px;
                height: 24px;
                padding: 1px;
            }

            #chartSettingsLeftContainer {
                position: absolute;
                top: 0;
                left: 0;
                /* border: 1px solid #000;
                border-radius: 2px; */
                margin: 1px;
                z-index: 3;
            }

            #chartShowTracksIcon {
                float: left;
                height: 24px;
                padding: 1px;
            }

            paper-spinner-lite {
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translateX(-50%) translateY(-50%);
                height: 75px;
                width: 75px;
                z-index: -1;
            }
        </style>
    </template>
</dom-module>

<script>
    /**
     * `ChartBehavior` is an interface to all epiviz-charts. It implements common methods and properties
     * on all plots and tracks. All charts inherit this behavior.
     *
     * @polymerBehavior
    **/

    EpivizChartBehavior = function (superClass) {
        return class extends superClass {
            constructor() {
                super();
                //this.addEventListener('iron-resize', e => this._onResize(e));
                this.addEventListener('transitionend', e => {
                    this._debouncer = Polymer.Debouncer.debounce(this._debouncer, Polymer.Async.timeOut.after(50), () => this._onResize(e));
                });
            }

            static get properties() {
                return {
                    /**
                    * Measurements for the chart. (x and y axis measurements)
                    * Charts automatically sets measurements from `dimS`.
                    *
                    * Note: Either `measurements` or `dimS` have to be defined on the chart.
                    * 
                    * @type {Array<Object>}
                    */
                    measurements: {
                        type: Array,
                        notify: true,
                        observer: '_measurementsChanged'
                    },

                    /**
                     * Dimensions for a chart. (x and y axis measurements).
                     * if the parent container (navigation or environment) defines measurements, those can be
                     * used as measurements for charts.
                     *
                     * Note: Either `measurements` or `dimS` have to be defined on the chart.
                     * 
                     * @type {Array<string>}
                     */
                    dimS: {
                        type: Array,
                        notify: true
                    },

                    /**
                     * Dimensions for a chart. (x and y axis measurements).
                     * if the parent container (navigation or environment) defines measurements, those can be
                     * used as measurements for charts.
                     *
                     * Note: Either `measurements` or `dimS` have to be defined on the chart.
                     * 
                     * @type {Array<string>}
                     */
                    canvas: {
                        type: Boolean,
                        notify: true,
                        value: false
                    },

                    /**
                    * Parent Container if chart is part of Nav or Env.
                    *
                    * @type {Object}
                    */
                    _parentContainer: {
                        type: Object,
                        notify: true,
                        observer: "_parentContainerChanged"
                    },

                    /**
                     * Whether to use a default data provider.
                     * Note: only for testing & development.
                     *
                     * @type {boolean}
                     */
                    useDefaultDataProvider: {
                        type: Boolean,
                        notify: true
                    },

                    /**
                    * Chart data object.
                    *
                    * @type {Object<epiviz.datatypes.MapGenomicData>}
                    */
                    data: {
                        type: Object,
                        notify: true,
                        observer: '_dataChanged'
                    },

                    /**
                     * Chart json data object.
                     *
                     * @type {Object}
                     */
                    jsonData: {
                        type: Object,
                        notify: true,
                        observer: '_jsonDataChanged'
                    },

                    /**
                     * Unique plot-id. an id will be assigned if not set
                     *
                     * @type {string}
                     */
                    plotId: {
                        type: String,
                        reflectToAttribute: true,
                        notify: true
                    },

                    /**
                    * Measurements for the chart. Charts automatically sets measurements from `dimS`.
                    *
                    * @type {Array<Object>}
                    */
                    measurements: {
                        type: Array,
                        notify: true,
                        observer: '_measurementsChanged'
                    },

                    /**
                    * Computed Range from `<chr>`, `<start>` & `<end>`. 
                    *
                    * @type {Object<epiviz.datatypes.GenomicRange>}
                    */
                    range: {
                        type: Object,
                        notify: true,
                        observer: '_rangeChanged'
                    },

                    /**
                    * Chart Settings. 
                    *
                    * @type {Array.<epiviz.ui.charts.CustomSetting>}
                    */
                    chartSettings: {
                        type: Object,
                        notify: true,
                        observer: '_chartSettingsChanged'
                    },

                    /**
                     * Chart title.
                     *
                     */
                    title: {
                        type: String,
                        notify: true,
                        observer: '_chartTitleChanged'
                    },

                    /**
                     * Color palette to use.
                     *
                     * @type {Array<epiviz.ui.charts.ColorPalette>}
                     */
                    chartColors: {
                        type: Array,
                        notify: true,
                        observer: '_chartColorsChanged'
                    },

                    /**
                     * Set chart width.
                     *
                     */
                    chartWidth: {
                        type: Number,
                        notify: true,
                        observer: '_chartWidthChanged'
                    },


                    /**
                     * Set chart width.
                     *
                     */
                    fixedAxis: {
                        type: Boolean,
                        notify: true,
                        observer: '_chartFixedAxisChanged'
                    },

                    /**
                     * if host is dragged. 
                     * used to remove events when a chart dragging is in progress.
                     *
                     * @type {boolean}
                     */
                    hostDragging: {
                        type: Boolean,
                        value: false
                    }
                };
            }

            static get observers() {
                return ['_dimChanged(dimX.*, dimY.*, dimS.*)'];
            }

            /**
             * Resizes the chart.
             * Automatically triggered when the browser window or the css for the element changes.
             */
            _onResize(e) {
                if (this.chart != null) {
                    var parent = this._parentContainer;
                    var width = Math.max(this.chartType.defaultWidth() / 2, this.offsetWidth - epiviz.ui.charts.Visualization.SVG_MARGIN) - 15;
                    var height = Math.max(this.chartType.defaultHeight(), this.offsetHeight - epiviz.ui.charts.Visualization.SVG_MARGIN) - 15;
                    if (parent) {
                        var prop = this._getStyle(this, "grid-column-start");
                        var columnSpan = this._getColumnSpan(prop);
                        if (columnSpan == 1) {
                            width = parent.offsetWidth / 6;
                        }
                    }
                    this.chart._properties.width = width;
                    this.chart._properties.height = height;
                    this._dataChanged();
                    if (e) {
                        e.stopPropagation();
                    }
                }
            }

            /**
             * Callback when a parent container is assigned to charts. 
             * Initialize chart brushing and other interaction events.
             * 
             * @return {string}
             */
            _parentContainerChanged() {
                var self = this;

                var parent = self._parentContainer;

                if (self.nodeName == "EPIVIZ-NAVIGATION") {

                    parent.addEventListener('hoverAllCharts', function (e) {
                        if (self.collapsed) {
                            if (Array.isArray(e.detail.data)) {
                                for (var rIndex = 0; rIndex < e.detail.data.length; rIndex++) {
                                    if (self.shadowRoot.querySelector("#navTrack").chartObject.overlapsWith(e.detail.data[rIndex])) {
                                        self.shadowRoot.querySelector("#navTrack").hover();
                                        break;
                                    }
                                }
                            }
                            else {
                                if (self.shadowRoot.querySelector("#navTrack").chartObject.overlapsWith(e.detail.data)) {
                                    self.shadowRoot.querySelector("#navTrack").hover();
                                }
                            }
                        }
                        else {
                            let navChildren =
                                Polymer.FlattenedNodesObserver.getFlattenedNodes(self).filter(n => n.nodeType === Node.ELEMENT_NODE);
                            var numChildren = navChildren.length;
                            for (var index = 0; index < numChildren; index++) {
                                var currentChild = navChildren[index];
                                currentChild.hover(e.detail.data);
                            }
                        }
                    }.bind(self));

                    parent.addEventListener('unHoverAllCharts', function (e) {
                        if (self.collapsed) {
                            self.shadowRoot.querySelector("#navTrack").unHover();
                        }
                        else {
                            let navChildren =
                                Polymer.FlattenedNodesObserver.getFlattenedNodes(self).filter(n => n.nodeType === Node.ELEMENT_NODE);
                            var numChildren = navChildren.length;
                            for (var index = 0; index < numChildren; index++) {
                                var currentChild = navChildren[index];
                                currentChild.unHover();
                            }
                        }
                    }.bind(self));

                    parent.addEventListener('selectAllCharts', function (e) {
                        if (!self.collapsed) {
                            if (Array.isArray(e.detail.data)) {
                                for (var rIndex = 0; rIndex < e.detail.data.length; rIndex++) {
                                    if (self.shadowRoot.querySelector("#navTrack").chartObject.overlapsWith(e.detail.data[rIndex])) {
                                        self.shadowRoot.querySelector("#navTrack").hover();
                                        break;
                                    }
                                }
                            }
                            else {
                                if (self.shadowRoot.querySelector("#navTrack").chartObject.overlapsWith(e.detail.data)) {
                                    self.shadowRoot.querySelector("#navTrack").hover();
                                }
                            }
                        }
                        else {
                            let navChildren =
                                Polymer.FlattenedNodesObserver.getFlattenedNodes(self).filter(n => n.nodeType === Node.ELEMENT_NODE);
                            var numChildren = navChildren.length;
                            for (var index = 0; index < numChildren; index++) {
                                var currentChild = navChildren[index];
                                currentChild.select(e.detail.data);
                            }
                        }
                    }.bind(self));

                    parent.addEventListener('unSelectAllCharts', function (e) {
                        if (!self.collapsed) {
                            self.shadowRoot.querySelector("#navTrack").unHover();
                        }
                        else {
                            let navChildren =
                                Polymer.FlattenedNodesObserver.getFlattenedNodes(self).filter(n => n.nodeType === Node.ELEMENT_NODE);
                            var numChildren = navChildren.length;
                            for (var index = 0; index < numChildren; index++) {
                                var currentChild = navChildren[index];
                                currentChild.deSelect();
                            }
                        }
                    }.bind(self));
                }
                else {
                    parent.addEventListener('hoverAllCharts', function (e) {
                        if (!self.hostDragging) {
                            self.hover(e.detail.data);
                        }
                    }.bind(self));

                    parent.addEventListener('unHoverAllCharts', function (e) {
                        if (!self.hostDragging) {
                            self.unHover();
                        }
                    }.bind(self));

                    parent.addEventListener('selectAllCharts', function (e) {
                        if (!self.hostDragging) {
                            self.select(e.detail.data);
                        }
                    }.bind(self));

                    parent.addEventListener('unSelectAllCharts', function (e) {
                        if (!self.hostDragging) {
                            self.deSelect();
                        }
                    }.bind(self));
                }
            }

            /**
             * Helper Function to generate a unique plot-id
             * 
             * @return {string}
             */
            _generatePlotId() {
                var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
                var result = '';
                var size = 7;

                for (var i = 0; i < size; ++i) {
                    result += chars[Math.round(Math.random() * (chars.length - 1))];
                }
                return this.nodeName.toLowerCase() + "_" + result;
            }

            /**
             * MeasurementChange/ChartDimension event handler.
             * Listens to when a chart dimensions(axes) are changed and redraws the chart.
             *
             * @fires dimChanged
             */
            _dimChanged() {
                /**
                 * fires event when a chart dimensions are changed.
                 *
                 * @event dimChanged
                 * @type {string}
                 * @property {string} id - plot-id of the chart element.
                 */
                // $('#' + this.plotId).empty();
                this.dispatchEvent(new CustomEvent('dimChanged',
                    {
                        detail: {
                            id: this.plotId
                        }
                    }
                )
                );
            }

            /**
             * chartSettings change event handler.
             * Listens to when a chart settings are changed and redraws the chart.
             */
            _chartSettingsChanged() {
                var self = this;
                if (self.chartSettings && self.chart) {
                    self.chart.setCustomSettingsValues(self.chartSettings);
                }
            }

            /**
             * chartTitle change event handler.
             * Listens to changes to title attribute to chart component
             */
            _chartTitleChanged() {
                var self = this;
                if (self.title && self.chartSettings) {
                    self.chartSettings.title = self.title;
                }
            }

            /**
             * fixedAxis change event handler.
             * Listens to changes to title attribute to chart component
             * only for stacked-line-tracks
             */
            _chartFixedAxisChanged() {
                var self = this;
                if (self.chartSettings) {
                    self.chartSettings["autoScale"] = self.fixedAxis;
                }
            }

            /**
             * chartColors change event handler.
             * Listens to when a chart colors are changed and redraws the chart.
             */
            _chartColorsChanged() {
                var self = this;
                if (self.chartColors && self.chart) {
                    self.chartColorPalette = new epiviz.ui.charts.ColorPalette(self.chartColors);
                    self.chart.setColors(self.chartColorPalette);
                    self._dataChanged();
                }
            }

            _chartwidthChanged() {
                var self = this;
                if (self.chartWidth) {
                    self._setColumnSpan(self.chartWidth);
                }
            }

            /**
            * ChartLocation/RangeChange event handler.
            */
            _rangeChanged() { }

            /**
            * MeasurementChange/ChartDimension event handler.
            * Listens to when a chart dimensions(axes) are changed and updates chart
            */
            _measurementsChanged() {

                var self = this;

                if (this.measurements != null && self.config != null) {

                    $(this.shadowRoot.querySelector('#' + self.plotId)).empty();
                    var mSet = new epiviz.measurements.MeasurementSet();

                    for (var i = 0; i < self.measurements.length; i++) {

                        var measurement = self.measurements[i];
                        mSet.add(new epiviz.measurements.Measurement(
                            measurement.id,
                            measurement.name,
                            measurement.type,
                            measurement.datasourceId,
                            measurement.datasourceGroup,
                            measurement.dataprovider,
                            measurement.formula,
                            measurement.defaultChartType,
                            measurement.annotation,
                            measurement.minValue,
                            measurement.maxValue,
                            measurement.metadata
                        ));
                    }

                    self.visConfigSelection = new epiviz.ui.controls.VisConfigSelection(mSet);
                    // self.chartType = new epiviz.plugins.charts.LineTrackType(self.config);
                    self.chartType = self._createChart();

                    var width = self.offsetWidth - 40;
                    if (width < self.chartType.defaultWidth() / 2) { width = self.chartType.defaultWidth() / 2; }

                    if (self.chartType) {
                        self.chartProperties = new epiviz.ui.charts.VisualizationProperties(
                            width - 15,
                            self.chartType.defaultHeight() - 15,
                            self.chartType.defaultMargins(),
                            self.visConfigSelection,
                            self.chartType.defaultColors(),
                            null,
                            self.chartType.customSettingsValues(),
                            self.chartType.customSettingsDefs(), 
                            []
                            // null
                        );

                        self.chart = self.chartType.createNew(self.plotId,
                            $(this.shadowRoot.querySelector('#' + self.plotId)),
                            self.chartProperties);

                        if (self.chartSettings != null || self.chartSettings != undefined) {
                            self.chart.setCustomSettingsValues(self.chartSettings);
                        }
                        else {
                            self.chartSettings = self.chart._customSettingsValues;
                        }

                        if (self.chartColors != null || self.chartColors != undefined) {
                            self.chartColorPalette = new epiviz.ui.charts.ColorPalette(self.chartColors);
                            self.chart.setColors(self.chartColorPalette);
                        }
                        else {
                            self.chartColorPalette = self.chart.colors();
                            self.chartColors = self.chartColorPalette._colors;
                        }

                        if (self.title) {
                            self._chartTitleChanged();
                        }
                        
                        // $(self).css('height', self.chartType.defaultHeight() + epiviz.ui.charts.Visualization.SVG_MARGIN);

                        self._initializeSettingsContainer();
                        self._initializeSettingsDialog();
                        self._initializeColorsDialog();
                        self._initializeRemoveDialog();
                        if (self.nodeName == "EPIVIZ-LINE-TRACK") {
                            self._initializeLogRatioDialog();
                            self._initializeNegationDialog();
                            self._initializeAverageDialog();
                        }

                        if (self.nodeName == "EPIVIZ-MULTISTACKED-LINE-TRACK") {
                            self._initializeShowTracksDialog();
                            self._chartFixedAxisChanged();
                            self._initializeFixedAxisDialog();
                        }
                        self._initializeResizeButtons();
                        self._initializeGrid();

                        // Listen to hover event on the chart
                        /**
                        * fires event when a chart data object is hovered.
                        *
                        * @event hover
                        * @type {object}
                        * @property {object} data - data object currently hovered.
                        */
                        self.chart.onHover().addListener(new epiviz.events.EventListener(
                            function (e) {
                                var id = e.id;
                                var data = e.args;
                                self.hover(data);
                                self.dispatchEvent(new CustomEvent('hover',
                                    {
                                        detail: {
                                            id: id,
                                            data: data
                                        },
                                        bubbles: true
                                    }
                                )
                                );
                            }
                        ));

                        /**
                        * fires event when a chart data object is unhovered.
                        *
                        * @event unHover
                        * @type {object}
                        * @property {object} data - data object currently hovered.
                        */
                        self.chart.onUnhover().addListener(new epiviz.events.EventListener(
                            function (e) {
                                var id = e.id;
                                var data = e.args;
                                self.unHover();
                                self.dispatchEvent(new CustomEvent('unHover',
                                    {
                                        detail: {
                                            id: id
                                        },
                                        bubbles: true
                                    }
                                )
                                );
                            }
                        ));

                        /**
                        * fires event when a chart data object is selected.
                        *
                        * @event select
                        * @type {object}
                        * @property {object} data - data object currently selected.
                        */
                        self.chart.onSelect().addListener(new epiviz.events.EventListener(
                            function (e) {
                                var id = e.id;
                                var data = e.args;
                                self.select(data);
                                self.dispatchEvent(new CustomEvent('select',
                                    {
                                        detail: {
                                            id: id,
                                            data: data
                                        },
                                        bubbles: true
                                    }
                                )
                                );
                            }
                        ));


                        /**
                        * fires event when a chart data object is unSelected.
                        *
                        * @event deSelect
                        * @type {object}
                        */
                        self.chart.onDeselect().addListener(new epiviz.events.EventListener(
                            function (e) {
                                var id = e.id;
                                self.deSelect();
                                self.dispatchEvent(new CustomEvent('deSelect',
                                    {
                                        detail: {
                                            id: id
                                        },
                                        bubbles: true
                                    }
                                )
                                );
                            }
                        ));
                    }
                }
            }

            /**
             * ChartData change event handler.
             * Listens to when chart data is updated and redraws the chart.
             */
            _dataChanged() {
                if (this.data != null && this.chart != null) {
                    this._draw();
                }
            }

            get_json_data() {
                var self = this;
                var jData = null;
                if (self.jsonData) { return self.jsonData; }

                if (self.nodeName == "EPIVIZ-GENES-TRACK") {

                    jData = {
                        rows: {
                            start: [],
                            end: [],
                            chr: [],
                            id: null,
                            strand: [],
                            metadata: {
                                exon_ends: [],
                                gene: [],
                                exon_starts: []
                            }
                        }
                    }

                    var series = self.data.firstSeries();
                    var indices = epiviz.utils.range(series.size());
                    var dataItems = indices
                        .map(function (i) {
                            var cell = series.get(i);
                            var item = cell.rowItem;

                            jData.rows.start.push(item.start());
                            jData.rows.end.push(item.end());
                            jData.rows.chr.push(item.seqName());
                            jData.rows.strand.push(item.strand());
                            jData.rows.metadata.gene.push(item.metadata("gene"));
                            jData.rows.exon_ends.gene.push(item.metadata("exon_ends"));
                            jData.rows.exon_starts.gene.push(item.metadata("exon_starts"));
                        });
                }

                if (self.data) {
                    jData = {
                        rows: {
                            start: [],
                            end: [],
                            chr: [],
                            id: null,
                            strand: [],
                            metadata: {
                                exon_ends: [],
                                gene: [],
                                exon_starts: []
                            }
                        },
                        cols: {}
                    }

                    var counter = 0;
                    self.data.foreach(function (measurement, series, seriesIndex) {
                        if (counter == 0) {
                            var rowData = series._container.rowData(measurement);
                            jData.rows.start = rowData._start;
                            jData.rows.end = rowData._end;
                            jData.rows.metadata = rowData._metadata;
                            jData.rows.id = rowData._id;
                            jData.rows.chr = rowData._seqName;
                            counter++;
                        }

                        var featureValues = series._container.values(measurement);
                        jData.cols[measurement.id()] = featureValues._values;
                    });
                }

                return jData;
            }

            /**
            * JSONChartData change event handler.
            * Listens to when chart json data is updated and redraws the chart.
            */
            _jsonDataChanged() {
                var genomic_data;
                if (Array.isArray(this.jsonData)) {
                    genomic_data = this._parseArrayData(this.jsonData);
                }
                else {
                    genomic_data = this._parseData(this.jsonData);
                }
                this.data = genomic_data;
            }

            _parseArrayData(json) {
                var self = this;

                var m = [];
                var mSet = new epiviz.measurements.MeasurementSet();
                var datasource = "epiviz";
                var rowLength = 0;
                var hasfData = true;
                var measurementType = 'feature';
                var useOffset = false;

                if (self.nodeName === "EPIVIZ-JSON-GENES-TRACK") {
                    measurementType = 'range';
                }

                // if data format is a Array of JSON Objects (long format)
                if (Array.isArray(json)) {
                    if (self.key && self.key.length > 0) {
                        json.forEach(function (d) {
                            var keyFields = [];
                            self.key.forEach(function (k) {
                                keyFields.push(d[k]);
                            });
                            d.key = keyFields.join("-");
                        });
                    }

                    var rowKeys = Object.keys(json[0]);
                    var colAnnotation = null;
                    var colsCollection = {}, cols = {};
                    var rowLength = json.length;

                    rowKeys.forEach(function (key) {
                        colsCollection[key] = json.map(function (r) { return r[key]; });
                    });

                    var rowMetadata = rowKeys.slice(0);
                    var rows = Object.assign({}, colsCollection);

                    self.dimS.forEach(function (dim) {
                        var index = rowMetadata.indexOf(dim);
                        rowMetadata.splice(index, 1);
                        delete rows[dim];
                        cols[dim] = colsCollection[dim];
                    });

                    self.dimS.forEach(function (dim) {
                        var colValues = cols[dim];
                        m.push({
                            'id': dim,
                            'name': dim,
                            'type': measurementType,
                            'datasourceId': datasource,
                            'datasourceGroup': dim,
                            'dataprovider': datasource,
                            'formula': null,
                            'defaultChartType': self.chartName,
                            'annotation': colAnnotation,
                            'minValue': Math.min.apply(Math, colValues),
                            'maxValue': Math.max.apply(Math, colValues),
                            'metadata': rowMetadata
                        });

                        mSet.add(new epiviz.measurements.Measurement(
                            dim, dim, measurementType, datasource, dim, datasource, null, self.chartName, colAnnotation, null, null, rowMetadata
                        ));
                    });

                    self.measurements = m;

                    /**
                    *  Must have fields
                    *  jsondata.cols
                    *  jsondata.rows
                    *  jsondata.rows.metadata (label)
                    */
                    var jsonData = {};
                    jsonData.cols = cols;
                    jsonData.globalStartIndex = 1;
                    var rowObj = self._generateRowIndexes(rowLength);
                    rowObj.metadata = rows;
                    jsonData.rows = rowObj;
                }
                else {
                    //if data format is more like a dataframe/data table representation
                    var rowMetadata = Object.keys(json.rows);
                    var rowLength = 0;
                    useOffset = json.rows.useOffset;

                    if (json.cols == null) { hasfData = false; json.cols = {}; }

                    if (json.cols && Object.keys(json.cols) > 0) {
                        if (Object.keys(json.cols[Object.keys(json.cols)[0]]).indexOf("values") != -1) {
                            rowLength = json.cols[Object.keys(json.cols)[0]].values.length;
                        }
                        else {
                            rowLength = json.cols[Object.keys(json.cols)[0]].length;
                        }
                    }
                    else if (json.rows) {
                        if (Object.keys(json.rows).indexOf("values") != -1) {
                            rowLength = json.rows.values[Object.keys(json.rows.values)[0]].length;
                        }
                        else {
                            rowLength = json.rows[Object.keys(json.rows)[0]].length;
                        }
                    };

                    if (self.key && self.key.length > 0) {
                        var keyArray = [];
                        for (var ikey = 0; ikey < rowLength; ikey++) {
                            var keyFields = [];
                            self.key.forEach(function (k) {
                                keyFields.push(json.rows[k][ikey]);
                            });
                            keyArray.push(keyFields.join("-"));
                        }
                        json.rows.key = keyArray;
                    }

                    if (self.measurements) {
                        self.measurements.forEach(function (mea) {
                            mSet.add(new epiviz.measurements.Measurement(
                                mea.id, mea.name, mea.type, mea.datasourceId, mea.datasourceGroup, mea.dataprovider,
                                mea.formula, mea.defaultChartType, mea.annotation, mea.minValue, mea.maxValue, mea.metadata
                            ));
                        });
                        m = self.measurements;
                    }
                    else if (self.dimS) {
                        self.dimS.forEach(function (dim) {

                            var colValues = [0, 5];
                            if (json.cols) {
                                colValues = json.cols[dim] || json.cols[dim].values;
                            }
                            // var colValues = json.cols[dim];
                            var rowLength = colValues.length;

                            m.push({
                                'id': dim,
                                'name': dim,
                                'type': measurementType,
                                'datasourceId': datasource,
                                'datasourceGroup': dim,
                                'dataprovider': datasource,
                                'formula': null,
                                'defaultChartType': self.chartName,
                                'annotation': undefined,
                                'minValue': Math.min.apply(Math, colValues),
                                'maxValue': Math.max.apply(Math, colValues),
                                'metadata': rowMetadata
                            });

                            if (!hasfData) {
                                json.cols[dim] = { "values": [] }
                            }

                            mSet.add(new epiviz.measurements.Measurement(
                                dim, dim, measurementType, datasource, dim, datasource, null, self.chartName, undefined, null, null, rowMetadata
                            ));
                        });
                    }

                    self._chartMeasurements = m;
                    var jsonData = {};
                    jsonData.cols = {};
                    if (json.cols) {
                        var colKeys = Object.keys(json.cols);

                        colKeys.forEach(function (ck) {
                            if (Array.isArray(json.cols[ck])) {
                                jsonData.cols[ck] = json.cols[ck];
                            }
                            else {
                                jsonData.cols[ck] = json.cols[ck]["values"];
                            }
                        });
                    }

                    var rowObj = {};
                    var rowKeys = Object.keys(json.rows);

                    if (rowKeys.indexOf("values") != -1) {
                        rowObj = json.rows.values;
                    }
                    else if ((rowKeys.indexOf("start") == -1) || (rowKeys.indexOf("index") == -1) || (rowKeys.rows.indexOf("end") == -1)) {
                        rowObj = self._generateRowIndexes(rowLength);
                        rowObj.metadata = json.rows;
                    }
                    else {
                        rowObj = json.rows;
                    }

                    jsonData.globalStartIndex = rowObj.start[0];
                    jsonData.rows = rowObj;
                }

                var chartData = new epiviz.measurements.MeasurementHashtable();
                var sumExp = new epiviz.datatypes.PartialSummarizedExperiment();
                var globalStartIndex = jsonData.globalStartIndex;

                var chr = datasource;

                if (jsonData.rows.chr) {
                    chr = jsonData.rows.chr[0];
                }
                var range = new epiviz.datatypes.GenomicRange(chr, globalStartIndex, jsonData.rows.end[rowLength - 1] - globalStartIndex);
                self.range = range;

                var rowDataObj = new epiviz.datatypes.GenomicRangeArray(chr, range, globalStartIndex, jsonData.rows, useOffset);
                sumExp.addRowData(rowDataObj);

                if (hasfData) {
                    mSet.foreach(function (m) {
                        var valueData = new epiviz.datatypes.FeatureValueArray(m, range, globalStartIndex, jsonData.cols[m.id()]);
                        sumExp.addValues(valueData);
                    });
                }

                mSet.foreach(function (m) {
                    var msData = new epiviz.datatypes.MeasurementGenomicDataWrapper(m, sumExp);
                    chartData.put(m, msData);
                });

                var genomicData = new epiviz.datatypes.MapGenomicData(chartData);
                return genomicData;
            }

            /**
            * Helper function to create row-indexes for data items.
            */
            _generateRowIndexes(length) {
                var rowObj = {};
                rowObj.start = [];
                rowObj.index = [];
                rowObj.id = null;
                rowObj.strand = null;
                rowObj.chr = [];
                rowObj.end = [];
                for (var i = 0; i < length; i++) {
                    rowObj.start.push(i + 1);
                    rowObj.index.push(i + 1);
                    // rowObj.id.push(i+1);
                    rowObj.end.push(i + 2);
                    rowObj.chr.push("epiviz");
                }
                return rowObj;
            }

            /**
             *  json format {cols: {"aff1": [], "affy2": []}, rows: {id: [], chr: [], start: [], end: [], strand: null, metadata: {probe: []}}} 
             * 
             * 
             */
            _parseData(json) {
                var self = this;
                var chr = json.rows.chr[0];
                var start = json.rows.start[0];
                var end = 0
                var measurements = [];
                json.rows.end.forEach(function (k) { end = end + k; });
                var mSet = new epiviz.measurements.MeasurementSet();

                if (self.nodeName == "EPIVIZ-GENES-TRACK") {
                    var key = "genes";

                    measurements.push({
                        "id": key,
                        "name": key,
                        "type": "range",
                        "datasourceId": key,
                        "datasourceGroup": key,
                        "dataprovider": "static",
                        "formula": null,
                        "defaultChartType": "Genes Track",
                        "annotation": null,
                        "minValue": null,
                        "maxValue": null,
                        "metadata": ["probe"]
                    });

                    var mea = new epiviz.measurements.Measurement(
                        key, key, "range", key,
                        key, "static", null, "Genes Track",
                        null, null, null, []
                    )

                    mSet.add(mea);

                    self.measurements = measurements;

                    var chartData = new epiviz.measurements.MeasurementHashtable();
                    var range = new epiviz.datatypes.GenomicRange(chr, start, end - start);
                    var sumExp = new epiviz.datatypes.PartialSummarizedExperiment();
                    var datasource = "static";

                    var rowDataObj = new epiviz.datatypes.GenomicRangeArray(datasource, range, start, json.rows, true);

                    sumExp.addRowData(rowDataObj);
                    mSet.foreach(function (m) {
                        var msData = new epiviz.datatypes.MeasurementGenomicDataWrapper(m, sumExp);
                        chartData.put(m, msData);
                    });

                    var genomicData = new epiviz.datatypes.MapGenomicData(chartData);

                    self.range = range;
                    return genomicData;
                }


                if (self.dimS) {
                    self.dimS.forEach(function (key) {

                        measurements.push({
                            "id": key,
                            "name": key,
                            "type": "feature",
                            "datasourceId": key,
                            "datasourceGroup": key,
                            "dataprovider": "static",
                            "formula": null,
                            "defaultChartType": null,
                            "annotation": null,
                            "minValue": -3,
                            "maxValue": 10,
                            "metadata": ["probe"]
                        });

                        mSet.add(new epiviz.measurements.Measurement(
                            key, key, "feature", key,
                            key, "static", null, null,
                            null, -1, 10, ["probe"]
                        ));
                    });

                    var range_mea = new epiviz.measurements.Measurement(
                        key, key, "range", key,
                        key, "static", null, null,
                        null, -1, 10, []
                    )
                }

                self.measurements = measurements;

                var chartData = new epiviz.measurements.MeasurementHashtable();
                var range = new epiviz.datatypes.GenomicRange(chr, start, end - start);
                var sumExp = new epiviz.datatypes.PartialSummarizedExperiment();
                // var datasource = "static";

                var rowDataObj = new epiviz.datatypes.GenomicRangeArray(range_mea, range, start, json.rows, true);

                sumExp.addRowData(rowDataObj);

                mSet.foreach(function (m) {
                    var valueData = new epiviz.datatypes.FeatureValueArray(m, range, start, json.cols[m.id()]);
                    sumExp.addValues(valueData);
                });


                mSet.foreach(function (m) {
                    var msData = new epiviz.datatypes.MeasurementGenomicDataWrapper(m, sumExp);
                    chartData.put(m, msData);
                });

                var genomicData = new epiviz.datatypes.MapGenomicData(chartData);
                self.range = range;
                return genomicData;
            }

            /**
            * Hover event handler.
            *
            * @param {object} data data object currently hovered.
            */
            hover(data) {
                if (!this.hostDragging && this.hostDragging !== undefined && this.chart) {
                    this.chart.doHover(data);
                }
            }

            /**
            * unHover event handler.
            */
            unHover() {
                if (!this.hostDragging && this.hostDragging !== undefined && this.chart) {
                    this.chart.doUnhover();
                }
            }

            /**
             * select event handler.
             *
             * @param {object} data data object currently hovered.
             */
            select(data) {
                this.chart.doSelect(data);
            }

            /**
            * deSelect event handler.
            */
            deSelect() {
                this.chart.doDeselect();
            }

            /**
             * Intializes chart container element for settings and colors elements.
             */
            _initializeSettingsContainer() {
                var chartContainer = this.shadowRoot.querySelector('#' + this.plotId);
                var settingsContainer = this.shadowRoot.querySelector('#' + this.plotId + '-chartSettingsContainer');

                if (settingsContainer == null) {
                    var iconElem = document.createElement('div');
                    iconElem.hidden = true;
                    iconElem.id = "chartSettingsContainer";
                    // iconElem.addEventListener("click", this._showColorsDialog.bind(this));
                    // Polymer.dom(chartContainer).appendChild(iconElem);
                    chartContainer.appendChild(iconElem);
                }

                var settingsLeftContainer = this.shadowRoot.querySelector('#' + this.plotId + '-chartSettingsLeftContainer');

                if (settingsLeftContainer == null) {
                    var iconElem = document.createElement('div');
                    iconElem.hidden = true;
                    iconElem.id = "chartSettingsLeftContainer";
                    // iconElem.addEventListener("click", this._showColorsDialog.bind(this));
                    // Polymer.dom(chartContainer).appendChild(iconElem);
                    chartContainer.appendChild(iconElem);
                }
            }

            /**
            * Handles when mouse-overed on a chart to show settings and colors.
            */
            hostHovered() {
                var currColorsIcon = this.shadowRoot.querySelector('#chartSettingsContainer');
                if (currColorsIcon) {
                    currColorsIcon.hidden = false;
                }

                var currColorsLeftIcon = this.shadowRoot.querySelector('#chartSettingsLeftContainer');
                if (currColorsLeftIcon) {
                    currColorsLeftIcon.hidden = false;
                }
            }

            /**
             * Handles when mouse-overed on a chart to hide settings and colors.
             */
            hostUnhovered() {
                var currColorsIcon = this.shadowRoot.querySelector('#chartSettingsContainer');
                if (currColorsIcon) {
                    currColorsIcon.setAttribute("hidden", true);
                }

                var currColorsLeftIcon = this.shadowRoot.querySelector('#chartSettingsLeftContainer');
                if (currColorsLeftIcon) {
                    currColorsLeftIcon.setAttribute("hidden", true);
                }
            }
        }
    }
</script><script>
    /**
     * `ChartDataBehavior` object manages data requests. 
     * `<epiviz-environment>` & `<epiviz-navigation>` inherit this behavior to get/update chart data.
     *
     * @polymerBehavior
    **/
    EpivizChartDataBehavior = function (superClass) {
        return class extends superClass {
            constructor() {
                super();
                var self = this;

                var settingsMap = {
                    configType: 'default',
                    dataProviders: [],  
                    workspacesDataProvider: sprintf(
                        "epiviz.data.EmptyResponseDataProvider",
                        "empty",
                        ""
                    ),
                    useCache: true,
                    colorPalettes: [],
                    maxSearchResults: 12
                }

                // var dataManagerElem = document.querySelector("epiviz-app").shadowRoot.querySelectorAll('epiviz-data-source');
                // var dataManagerElem = document.querySelectorAll('epiviz-data-source');
                var dataManagerElem = document.querySelector("#" + self.id).parentNode.querySelectorAll('epiviz-data-source');
                if (dataManagerElem && dataManagerElem.length > 0) {

                    dataManagerElem.forEach(function(ds) {
                        settingsMap.dataProviders.push([ds.providerType, ds.providerId, ds.providerUrl]);
                    });

                    var config = new epiviz.Config(settingsMap);
                    var dataProviderFactory = new epiviz.data.DataProviderFactory(config);
                    this.dataManager = new epiviz.data.DataManager(config, dataProviderFactory);

                    this.dataManager.getMeasurements(function (result) {
                        var measurements = result.raw();
                        self.measurementsSet = result;

                        if (measurements) {
                            var mArray = measurements;
                            var measurementsObj = {}
                            mArray.forEach(function(m) {
                                measurementsObj[m.id] = m
                            });
                        
                            self.measurementSet = measurementsObj;
                            self.measurementAll = result;
                        }

                    }, function (jqXHR, textStatus, errorThrown) {
                        var toast = document.createElement("paper-toast");
                        toast.setAttribute("text", textStatus + " - " + errorThrown);
                        // toast.setAttribute("openned", true);
                        this.shadowRoot.appendChild(toast);
                        toast.show();
                    });
                }
            }

            static get properties() {
                return {
                    /**
                    * Whether to apply a data transformation function
                    *
                    * @type {boolean}
                    */
                    transformData: {
                        type: Boolean,
                        notify: true,
                        value: false
                    },

                    /**
                    * Data Transformation function
                    *
                    * @type {string}
                    */
                    transformDataFunc: {
                        type: String,
                        notify: true
                    }
                };
            }

            static get observers() {
                return ['_barChanged(bar.*)'];
            }

            /**
             *  Formats an epiviz data object into json.
             *  output data format
             *  {
             *      columns: Collection <Object> {measurement-id: <Epiviz measurement>}
             *      values : Array  [measurement-id: Val <Integer>, row: rowInfo]
             *  }
             *
             * @param {Object} data data object `epiviz.datatypes.*`
             *
             * @return {Object} transformed data object
             */
            epivizToJSON(data) {
                if (!this.transformData) {
                    return data;
                }

                var fromDataMeasurements = [];
                var range = this.range;

                var firstGlobalIndex = data.firstSeries().globalStartIndex();
                var lastGlobalIndex = data.firstSeries().globalEndIndex();

                data.foreach(function (measurement, series) {
                    fromDataMeasurements.push(measurement);
                    var firstIndex = series.globalStartIndex();
                    var lastIndex = series.globalEndIndex();
                    if (firstIndex > firstGlobalIndex) { firstGlobalIndex = firstIndex; }
                    if (lastIndex < lastGlobalIndex) { lastGlobalIndex = lastIndex; }
                });

                var nItems = lastGlobalIndex - firstGlobalIndex;

                var i, index;
                var grid = {};
                grid.measurements = fromDataMeasurements;
                grid.columns = {};

                grid.measurements.forEach(function (m) {
                    grid.columns[m.datasourceId() + "-" + m.id()] = m;
                });

                grid.values = [];
                var nSeries = fromDataMeasurements.length;
                for (i = 0; i < nItems; ++i) {
                    index = i + firstGlobalIndex;
                    var valArray = {};

                    var row = {};
                    var rowData = data.getSeries(fromDataMeasurements[0]).getRowByGlobalIndex(index);
                    row.start = rowData.start();
                    row.end = rowData.end();
                    row.metadata = rowData.rowMetadata();
                    row.strand = rowData.strand();
                    row.index = rowData.index();
                    row.globalIndex = rowData.globalIndex();
                    row.id = rowData.id();
                    row.seqName = rowData.seqName();
                    valArray.row = row;

                    grid.measurements.forEach(function (m) {
                        var cellX = data.getSeries(m).getByGlobalIndex(index);
                        valArray[m.datasourceId() + "-" + m.id()] = cellX.value;
                    });

                    grid.values.push(valArray);
                }
                return grid;
            }

            /**
             * Handles search requests for gene names /probeid
             * 
             * @param {string} gene keyword to search for genes
             * @param {HTMLElement} currentChild child HTML element where request was created
             * @param {HTMLElement} pDom parent HTML element of the child 
             * @callback cb
             * @param {cb} cb Callback that handles the response
             */
            _getElementSearch(gene, currentChild, pDom, cb) {

                if (this.dataManager) {
                    this.dataManager.search(function (data) {
                        if (pDom) {
                            data.forEach(function (item) {
                                item.value = item.gene + " - " + item.chr + " : " + item.start + " - " + item.end;
                            });
                            pDom.shadowRoot.querySelector("#geneSearch").items = data;
                        }
                        else {
                            cb(data);
                        }
                    }, gene);
                }
            }

            /**
             * Handles gene in range requests.
             * Given a genomic region, get the gene close to the center.
             * 
             * @param {HTMLElement} currentChild child HTML element where request was created
             * @param {HTMLElement} pDom parent HTML element of the child 
             */
            _getGenesInRange(currentChild, pDom) {
                var self = this;

                var mSet = new epiviz.measurements.MeasurementSet();
                var m = new epiviz.measurements.Measurement(
                    "genes",
                    "Genes",
                    "range",
                    "genes",
                    "genes",
                    "umd",
                    null,
                    "Genes Track",
                    null,
                    null,
                    null,
                    ["gene", "exon_starts", "exon_ends"]
                )
                mSet.add(m);

                var chartMeasMap = {};
                chartMeasMap[currentChild.plotId] = mSet;

                if (this.dataManager) {
                    this.dataManager.getData(currentChild.range, chartMeasMap, function (id, data) {
                        // Get gene (based on size)
                        // var index = data.globalStartIndex(m) + Math.round((data.size(m) - 1) / 2);

                        var nItems = data.firstSeries().globalEndIndex() - data.firstSeries().globalStartIndex();

                        if (nItems == 0) {
                            var toast = document.createElement("paper-toast");
                            toast.setAttribute("text", currentChild.nodeName.toLowerCase() + ": query return empty - choose a different genomic region");
                            // toast.setAttribute("openned", true);
                            this.shadowRoot.appendChild(toast);
                            toast.show();
                        }

                        // Get gene (based on position)
                        var searchIndex = data.binarySearchStarts(m, new epiviz.datatypes.GenomicRange(pDom.range.seqName(), (pDom.range.start() + pDom.range.end()) / 2, 1000000));
                        var index = data.globalStartIndex(m) + searchIndex.index;

                        if (data.getRowByGlobalIndex(m, index)) {
                            var gene = data.getRowByGlobalIndex(m, index).rowMetadata().gene;
                            pDom.geneInRange = gene;
                        }
                    },
                        function (jqXHR, textStatus, errorThrown) {
                            var toast = document.createElement("paper-toast");
                            toast.setAttribute("text", textStatus + " - " + errorThrown);
                            // toast.setAttribute("openned", true);
                            this.shadowRoot.appendChild(toast);
                            toast.show();
                        });
                }
            }

            /**
            * Handles SeqInfo requests
            * 
            * @param {HTMLElement} currentChild child HTML element where request was created
            * @param {HTMLElement} pDom parent HTML element of the child 
            */
            _getElementSeqInfo(currentChild, pDom) {
                var self = this;

                if (this.dataManager) {
                    this.dataManager.getSeqInfos(function (data) {
                        // data = [{seqName: "chrI" , min: 1, max: 230218}, {seqName: "chrII" , min: 1, max: 813184}, {seqName: "chrIII" , min: 1, max: 316620}, {seqName: "chrIV" , min: 1, max: 1531933}, {seqName: "chrIX" , min: 1, max: 439888}, {seqName: "chrM" , min: 1, max: 85779}, {seqName: "chrV" , min: 1, max: 576874}, {seqName: "chrVI" , min: 1, max: 270161}, {seqName: "chrVII" , min: 1, max: 1090940}, {seqName: "chrX" , min: 1, max: 745751}, {seqName: "chrXI" , min: 1, max: 666816}, {seqName: "chrXII" , min: 1, max: 1078177}, {seqName: "chrXIII" , min: 1, max: 924431}, {seqName: "chrXIV" , min: 1, max: 784333}, {seqName: "chrXV" , min: 1, max: 1091291}, {seqName: "chrXVI" , min: 1, max: 948066}]


                        var seqinfo = {};
                        data.forEach(function (s) {
                            seqinfo[s.seqName] = s;
                        });
                        currentChild.seqInfo = seqinfo;

                        if (!(currentChild.start && currentChild.end)) {
                            if (currentChild.chr && currentChild.seqInfo) {
                                currentChild.start = 0;
                                currentChild.end = currentChild.seqInfo[currentChild.chr].max;
                            }
                        }
                    },
                        function (jqXHR, textStatus, errorThrown) {
                            var toast = document.createElement("paper-toast");
                            toast.setAttribute("text", textStatus + " - " + errorThrown);
                            // toast.setAttribute("openned", true);
                            this.shadowRoot.appendChild(toast);
                            toast.show();
                        });
                }
            }

            /**
            * Handles data requests
            * 
            * @param {HTMLElement} currentChild child HTML element where request was created
            * @param {HTMLElement} pDom parent HTML element of the child 
            * @param {Array<string>} measurements measurements to request data 
            */
            _getElementData(currentChild, pDom, measurements) {
                var self = this;

                if (self.dataManager != null) {
                    var dims = currentChild.dimS || currentChild.getAttribute("dim-s");

                    if (typeof (dims) == "string") {
                        dims = JSON.parse(dims);
                    }

                    // if (!measurements) {
                    //     var mArray = document.querySelector("epiviz-data-source").measurements;
                    //     measurements = {}
                    //     mArray.forEach(function(m) {
                    //         measurements[m.id] = m
                    //     });

                    //     self.measurements = measurements;
                    // }

                    // var id = self._generateChartId();
                    var mSet = new epiviz.measurements.MeasurementSet();
                    var tMeasurements = [];

                    if (dims != null && dims.length > 0) {
                        for (var idim = 0; idim < dims.length; idim++) {
                            var mtdx = measurements[dims[idim]];
                            mSet.add(new epiviz.measurements.Measurement(
                                mtdx.id,
                                mtdx.name,
                                mtdx.type,
                                mtdx.datasourceId,
                                mtdx.datasourceGroup,
                                mtdx.dataprovider,
                                mtdx.formula,
                                mtdx.defaultChartType,
                                mtdx.annotation,
                                mtdx.minValue,
                                mtdx.maxValue,
                                mtdx.metadata
                            ));

                            tMeasurements.push(mtdx);
                        }
                    }
                    else if (currentChild.measurements != null && currentChild.measurements.length > 0) {
                        for (var idim = 0; idim < currentChild.measurements.length; idim++) {

                            var mtdx = currentChild.measurements[idim];

                            mSet.add(new epiviz.measurements.Measurement(
                                mtdx.id,
                                mtdx.name,
                                mtdx.type,
                                mtdx.datasourceId,
                                mtdx.datasourceGroup,
                                mtdx.dataprovider,
                                mtdx.formula,
                                mtdx.defaultChartType,
                                mtdx.annotation,
                                mtdx.minValue,
                                mtdx.maxValue,
                                mtdx.metadata
                            ));

                            tMeasurements.push(mtdx);
                        }
                    }

                    var id = currentChild.plotId;
                    if (tMeasurements.length != 0) {
                        currentChild.range = currentChild.range || self.range || pDom.range;
                        if (!currentChild.measurements) {
                            currentChild.measurements = tMeasurements;
                        }
                        var chartMeasMap = {};
                        chartMeasMap[id] = mSet;

                        self.dataManager.getData(currentChild.range, chartMeasMap, function (id, data) {

                            var nItems = data.firstSeries().globalEndIndex() - data.firstSeries().globalStartIndex();

                            if (nItems == 0) {
                                var toast = document.createElement("paper-toast");
                                toast.setAttribute("text", currentChild.nodeName.toLowerCase() + ": query return empty - choose a different genomic region");
                                // toast.setAttribute("openned", true);
                                this.shadowRoot.appendChild(toast);
                                toast.show();
                            }

                            var dataChartElem = pDom.querySelector('[plot-id$="' + id + '"]') || Polymer.dom(pDom).querySelector('[plot-id$="' + id + '"]');
                            if (dataChartElem) {
                                dataChartElem.data = pDom.transformFunc(data);
                            }
                            dataChartElem._onResize();
                        }, function (jqXHR, textStatus, errorThrown) {
                            var toast = document.createElement("paper-toast");
                            toast.setAttribute("text", textStatus + " - " + errorThrown);
                            this.shadowRoot.appendChild(toast);
                            toast.show();
                        });

                        // if (self._zoomIn != 0 && self._zoomIn % 2 == 0) {
                        //     self._reupdateDataSet(mSet);
                        //     // delete self.dataManager._cache._data[mtdx.datasourceGroup];
                        // }
                    }
                }
            }

            // _reupdateDataSet(mlist) {
            //     var self = this;
            //     console.log("updating data in the background")
            //     var mset = new epiviz.measurements.MeasurementSet();

            //     mlist.foreach(function(m) {
            //         // if(m.datasourceId == "computed") {
            //         mset.add(m);
            //         delete self.dataManager._cache._data[m.datasourceGroup()];
            //         // }                    
            //     });

            //     var chartMeasMap = {};
            //     chartMeasMap["update"] = mset;

            //     self.dataManager.getData(self.range, chartMeasMap, function (id, data) {
            //             console.log("got data, do not have to assign to chart")
            //             console.log(data);
            //         }, function (jqXHR, textStatus, errorThrown) {
            //             console.log("error")
            //     });
            // }

            _reupdateAllData() {
                var self = this;
                console.log("updating data in the background")

                var mlist = new epiviz.measurements.MeasurementSet();
                for (var key in self.dataManager._cache._data) {
                    
                    var tdata = self.dataManager._cache._data[key];

                    if(tdata._values._size > 0) {
                        mlist.add(tdata._values._order[0].key);
                        delete self.dataManager._cache._data[tdata._values._order[0].key._datasourceGroup];
                    }
                    
                    // if(tdata._rowData._measurement._id == "computed") {
                    //     tdata._rowData._measurement._type = "feature";
                    //     mlist.add(tdata._rowData._measurement);
                    //     delete self.dataManager._cache._data[tdata._rowData._measurement._datasourceGroup];
                    // }
                }

                var chartMeasMap = {};
                chartMeasMap[self.plotId] = mlist;

                self.dataManager.getData(self.range, chartMeasMap, function (id, data) {
                        console.log("got data, do not have to assign to chart")
                        console.log(data);
                    }, function (jqXHR, textStatus, errorThrown) {
                        console.log("error")
                });
            }

            /**
             * Handles data requests when dimensions are changed/updated.
             * 
             * @param {string} cId plot-id of the chart to update.
             */
            _updateChart(cId) {

                if (cId == undefined) { return; }
                var self = this;
                var currentChild = Polymer.dom(self).querySelector('[plot-id$="' + cId + '"]');
                var dims = currentChild.dimS || currentChild.getAttribute("dim-s");
                $('#' + cId).empty();

                if (typeof (dims) == "string") {
                    dims = JSON.parse(dims);
                }

                var mSet = new epiviz.measurements.MeasurementSet();
                var tMeasurements = [];

                if (dims != null && dims.length > 0) {
                    for (var idim = 0; idim < dims.length; idim++) {
                        var mtdx = self.measurements[dims[idim]];
                        mSet.add(new epiviz.measurements.Measurement(
                            mtdx.id,
                            mtdx.name,
                            mtdx.type,
                            mtdx.datasourceId,
                            mtdx.datasourceGroup,
                            mtdx.dataprovider,
                            mtdx.formula,
                            mtdx.defaultChartType,
                            mtdx.annotation,
                            mtdx.minValue,
                            mtdx.maxValue,
                            mtdx.metadata
                        ));
                        tMeasurements.push(mtdx);
                    }
                }
                else if (currentChild.measurements != null && currentChild.measurements.length > 0) {
                    for (var idim = 0; idim < currentChild.measurements.length; idim++) {
                        var mtdx = currentChild.measurements[idim];
                        mSet.add(new epiviz.measurements.Measurement(
                            mtdx.id,
                            mtdx.name,
                            mtdx.type,
                            mtdx.datasourceId,
                            mtdx.datasourceGroup,
                            mtdx.dataprovider,
                            mtdx.formula,
                            mtdx.defaultChartType,
                            mtdx.annotation,
                            mtdx.minValue,
                            mtdx.maxValue,
                            mtdx.metadata
                        ));

                        tMeasurements.push(mtdx);
                    }
                }

                if (tMeasurements.length != 0) {
                    currentChild.range = currentChild.range || self.range;
                    currentChild.measurements = tMeasurements;
                    var chartMeasMap = {};
                    chartMeasMap[cId] = mSet;

                    if (self.dataManager) {

                        self.dataManager.getData(currentChild.range, chartMeasMap, function (id, data) {
                            var nItems = data.firstSeries().globalEndIndex() - data.firstSeries().globalStartIndex();

                            if (nItems == 0) {
                                var toast = document.createElement("paper-toast");
                                toast.setAttribute("text", currentChild.nodeName.toLowerCase() + ": Query return empty - choose a different genomic region");
                                this.shadowRoot.appendChild(toast);
                                toast.show();
                            }

                            var dataChartElem = Polymer.dom(self).querySelector('[plot-id$="' + id + '"]');
                            if (dataChartElem) {
                                dataChartElem.data = self.transformFunc(data);
                            }
                        }, function (jqXHR, textStatus, errorThrown) {
                            var toast = document.createElement("paper-toast");
                            toast.setAttribute("text", textStatus + " - " + errorThrown);
                            this.shadowRoot.appendChild(toast);
                            toast.show();
                        });
                    }
                }
            }
        }
    }
</script><script>
  (function() {

  /**
   * @constructor
   * @param {{
   *   type: (string|null|undefined),
   *   key: (string|null|undefined),
   *   value: *,
   * }=} options
   */
  Polymer.IronMeta = function(options) {
    Polymer.IronMeta[' '](options);

    this.type = (options && options.type) || 'default';
    this.key = options && options.key;
    if (options && 'value' in options) {
      this.value = options.value;
    }
  };

  // This function is used to convince Closure not to remove constructor calls
  // for instances that are not held anywhere. For example, when
  // `new Polymer.IronMeta({...})` is used only for the side effect of adding
  // a value.
  Polymer.IronMeta[' '] = function() {};

  Polymer.IronMeta.types = {};

  Polymer.IronMeta.prototype = {
    get value() {
      var type = this.type;
      var key = this.key;

      if (type && key) {
        return Polymer.IronMeta.types[type] && Polymer.IronMeta.types[type][key];
      }
    },

    set value(value) {
      var type = this.type;
      var key = this.key;

      if (type && key) {
        type = Polymer.IronMeta.types[type] = Polymer.IronMeta.types[type] || {};
        if (value == null) {
          delete type[key];
        } else {
          type[key] = value;
        }
      }
    },

    get list() {
      var type = this.type;

      if (type) {
        var items = Polymer.IronMeta.types[this.type];
        if (!items) {
          return [];
        }

        return Object.keys(items).map(function(key) {
          return metaDatas[this.type][key];
        }, this);
      }
    },

    byKey: function(key) {
      this.key = key;
      return this.value;
    }
  };

  var metaDatas = Polymer.IronMeta.types;

  Polymer({

    is: 'iron-meta',

    properties: {

      /**
       * The type of meta-data.  All meta-data of the same type is stored
       * together.
       * @type {string}
       */
      type: {
        type: String,
        value: 'default',
      },

      /**
       * The key used to store `value` under the `type` namespace.
       * @type {?string}
       */
      key: {
        type: String,
      },

      /**
       * The meta-data to store or retrieve.
       * @type {*}
       */
      value: {
        type: String,
        notify: true,
      },

      /**
       * If true, `value` is set to the iron-meta instance itself.
       */
      self: {type: Boolean, observer: '_selfChanged'},

      __meta: {type: Boolean, computed: '__computeMeta(type, key, value)'}
    },

    hostAttributes: {hidden: true},

    __computeMeta: function(type, key, value) {
      var meta = new Polymer.IronMeta({type: type, key: key});

      if (value !== undefined && value !== meta.value) {
        meta.value = value;
      } else if (this.value !== meta.value) {
        this.value = meta.value;
      }

      return meta;
    },

    get list() {
      return this.__meta && this.__meta.list;
    },

    _selfChanged: function(self) {
      if (self) {
        this.value = this;
      }
    },

    /**
     * Retrieves meta data value by key.
     *
     * @method byKey
     * @param {string} key The key of the meta-data to be returned.
     * @return {*}
     */
    byKey: function(key) {
      return new Polymer.IronMeta({type: this.type, key: key}).value;
    }
  });
  })();
</script>
<script>
  // This is left only for backward compatibility with projects
  // that incorrectly relied on unscoped global [hidden] rules;
  // removing would be a breaking change, but new projects
  // should never rely on this.
  (function() {
    var style = document.createElement('style');
    style.textContent = '[hidden] { display: none !important; }';
    document.head.appendChild(style);
  })();
</script>
<custom-style>
  <style is="custom-style">
    [hidden] {
      display: none !important;
    }
  </style>
</custom-style>

<custom-style>
  <style is="custom-style">
    html {

      --layout: {
        display: -ms-flexbox;
        display: -webkit-flex;
        display: flex;
      };

      --layout-inline: {
        display: -ms-inline-flexbox;
        display: -webkit-inline-flex;
        display: inline-flex;
      };

      --layout-horizontal: {
        @apply --layout;

        -ms-flex-direction: row;
        -webkit-flex-direction: row;
        flex-direction: row;
      };

      --layout-horizontal-reverse: {
        @apply --layout;

        -ms-flex-direction: row-reverse;
        -webkit-flex-direction: row-reverse;
        flex-direction: row-reverse;
      };

      --layout-vertical: {
        @apply --layout;

        -ms-flex-direction: column;
        -webkit-flex-direction: column;
        flex-direction: column;
      };

      --layout-vertical-reverse: {
        @apply --layout;

        -ms-flex-direction: column-reverse;
        -webkit-flex-direction: column-reverse;
        flex-direction: column-reverse;
      };

      --layout-wrap: {
        -ms-flex-wrap: wrap;
        -webkit-flex-wrap: wrap;
        flex-wrap: wrap;
      };

      --layout-wrap-reverse: {
        -ms-flex-wrap: wrap-reverse;
        -webkit-flex-wrap: wrap-reverse;
        flex-wrap: wrap-reverse;
      };

      --layout-flex-auto: {
        -ms-flex: 1 1 auto;
        -webkit-flex: 1 1 auto;
        flex: 1 1 auto;
      };

      --layout-flex-none: {
        -ms-flex: none;
        -webkit-flex: none;
        flex: none;
      };

      --layout-flex: {
        -ms-flex: 1 1 0.000000001px;
        -webkit-flex: 1;
        flex: 1;
        -webkit-flex-basis: 0.000000001px;
        flex-basis: 0.000000001px;
      };

      --layout-flex-2: {
        -ms-flex: 2;
        -webkit-flex: 2;
        flex: 2;
      };

      --layout-flex-3: {
        -ms-flex: 3;
        -webkit-flex: 3;
        flex: 3;
      };

      --layout-flex-4: {
        -ms-flex: 4;
        -webkit-flex: 4;
        flex: 4;
      };

      --layout-flex-5: {
        -ms-flex: 5;
        -webkit-flex: 5;
        flex: 5;
      };

      --layout-flex-6: {
        -ms-flex: 6;
        -webkit-flex: 6;
        flex: 6;
      };

      --layout-flex-7: {
        -ms-flex: 7;
        -webkit-flex: 7;
        flex: 7;
      };

      --layout-flex-8: {
        -ms-flex: 8;
        -webkit-flex: 8;
        flex: 8;
      };

      --layout-flex-9: {
        -ms-flex: 9;
        -webkit-flex: 9;
        flex: 9;
      };

      --layout-flex-10: {
        -ms-flex: 10;
        -webkit-flex: 10;
        flex: 10;
      };

      --layout-flex-11: {
        -ms-flex: 11;
        -webkit-flex: 11;
        flex: 11;
      };

      --layout-flex-12: {
        -ms-flex: 12;
        -webkit-flex: 12;
        flex: 12;
      };

      /* alignment in cross axis */

      --layout-start: {
        -ms-flex-align: start;
        -webkit-align-items: flex-start;
        align-items: flex-start;
      };

      --layout-center: {
        -ms-flex-align: center;
        -webkit-align-items: center;
        align-items: center;
      };

      --layout-end: {
        -ms-flex-align: end;
        -webkit-align-items: flex-end;
        align-items: flex-end;
      };

      --layout-baseline: {
        -ms-flex-align: baseline;
        -webkit-align-items: baseline;
        align-items: baseline;
      };

      /* alignment in main axis */

      --layout-start-justified: {
        -ms-flex-pack: start;
        -webkit-justify-content: flex-start;
        justify-content: flex-start;
      };

      --layout-center-justified: {
        -ms-flex-pack: center;
        -webkit-justify-content: center;
        justify-content: center;
      };

      --layout-end-justified: {
        -ms-flex-pack: end;
        -webkit-justify-content: flex-end;
        justify-content: flex-end;
      };

      --layout-around-justified: {
        -ms-flex-pack: distribute;
        -webkit-justify-content: space-around;
        justify-content: space-around;
      };

      --layout-justified: {
        -ms-flex-pack: justify;
        -webkit-justify-content: space-between;
        justify-content: space-between;
      };

      --layout-center-center: {
        @apply --layout-center;
        @apply --layout-center-justified;
      };

      /* self alignment */

      --layout-self-start: {
        -ms-align-self: flex-start;
        -webkit-align-self: flex-start;
        align-self: flex-start;
      };

      --layout-self-center: {
        -ms-align-self: center;
        -webkit-align-self: center;
        align-self: center;
      };

      --layout-self-end: {
        -ms-align-self: flex-end;
        -webkit-align-self: flex-end;
        align-self: flex-end;
      };

      --layout-self-stretch: {
        -ms-align-self: stretch;
        -webkit-align-self: stretch;
        align-self: stretch;
      };

      --layout-self-baseline: {
        -ms-align-self: baseline;
        -webkit-align-self: baseline;
        align-self: baseline;
      };

      /* multi-line alignment in main axis */

      --layout-start-aligned: {
        -ms-flex-line-pack: start;  /* IE10 */
        -ms-align-content: flex-start;
        -webkit-align-content: flex-start;
        align-content: flex-start;
      };

      --layout-end-aligned: {
        -ms-flex-line-pack: end;  /* IE10 */
        -ms-align-content: flex-end;
        -webkit-align-content: flex-end;
        align-content: flex-end;
      };

      --layout-center-aligned: {
        -ms-flex-line-pack: center;  /* IE10 */
        -ms-align-content: center;
        -webkit-align-content: center;
        align-content: center;
      };

      --layout-between-aligned: {
        -ms-flex-line-pack: justify;  /* IE10 */
        -ms-align-content: space-between;
        -webkit-align-content: space-between;
        align-content: space-between;
      };

      --layout-around-aligned: {
        -ms-flex-line-pack: distribute;  /* IE10 */
        -ms-align-content: space-around;
        -webkit-align-content: space-around;
        align-content: space-around;
      };

      /*******************************
                Other Layout
      *******************************/

      --layout-block: {
        display: block;
      };

      --layout-invisible: {
        visibility: hidden !important;
      };

      --layout-relative: {
        position: relative;
      };

      --layout-fit: {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
      };

      --layout-scroll: {
        -webkit-overflow-scrolling: touch;
        overflow: auto;
      };

      --layout-fullbleed: {
        margin: 0;
        height: 100vh;
      };

      /* fixed position */

      --layout-fixed-top: {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
      };

      --layout-fixed-right: {
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
      };

      --layout-fixed-bottom: {
        position: fixed;
        right: 0;
        bottom: 0;
        left: 0;
      };

      --layout-fixed-left: {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
      };

    }
  </style>
</custom-style>
<dom-module id="iron-icon" assetpath="bower_components/iron-icon/">
  <template>
    <style>
      :host {
        @apply --layout-inline;
        @apply --layout-center-center;
        position: relative;

        vertical-align: middle;

        fill: var(--iron-icon-fill-color, currentcolor);
        stroke: var(--iron-icon-stroke-color, none);

        width: var(--iron-icon-width, 24px);
        height: var(--iron-icon-height, 24px);
        @apply --iron-icon;
      }

      :host([hidden]) {
        display: none;
      }
    </style>
  </template>

  <script>

    Polymer({

      is: 'iron-icon',

      properties: {

        /**
         * The name of the icon to use. The name should be of the form:
         * `iconset_name:icon_name`.
         */
        icon: {
          type: String
        },

        /**
         * The name of the theme to used, if one is specified by the
         * iconset.
         */
        theme: {
          type: String
        },

        /**
         * If using iron-icon without an iconset, you can set the src to be
         * the URL of an individual icon image file. Note that this will take
         * precedence over a given icon attribute.
         */
        src: {
          type: String
        },

        /**
         * @type {!Polymer.IronMeta}
         */
        _meta: {
          value: Polymer.Base.create('iron-meta', {type: 'iconset'})
        }

      },

      observers: [
        '_updateIcon(_meta, isAttached)',
        '_updateIcon(theme, isAttached)',
        '_srcChanged(src, isAttached)',
        '_iconChanged(icon, isAttached)'
      ],

      _DEFAULT_ICONSET: 'icons',

      _iconChanged: function(icon) {
        var parts = (icon || '').split(':');
        this._iconName = parts.pop();
        this._iconsetName = parts.pop() || this._DEFAULT_ICONSET;
        this._updateIcon();
      },

      _srcChanged: function(src) {
        this._updateIcon();
      },

      _usesIconset: function() {
        return this.icon || !this.src;
      },

      /** @suppress {visibility} */
      _updateIcon: function() {
        if (this._usesIconset()) {
          if (this._img && this._img.parentNode) {
            Polymer.dom(this.root).removeChild(this._img);
          }
          if (this._iconName === "") {
            if (this._iconset) {
              this._iconset.removeIcon(this);
            }
          } else if (this._iconsetName && this._meta) {
            this._iconset = /** @type {?Polymer.Iconset} */ (
              this._meta.byKey(this._iconsetName));
            if (this._iconset) {
              this._iconset.applyIcon(this, this._iconName, this.theme);
              this.unlisten(window, 'iron-iconset-added', '_updateIcon');
            } else {
              this.listen(window, 'iron-iconset-added', '_updateIcon');
            }
          }
        } else {
          if (this._iconset) {
            this._iconset.removeIcon(this);
          }
          if (!this._img) {
            this._img = document.createElement('img');
            this._img.style.width = '100%';
            this._img.style.height = '100%';
            this._img.draggable = false;
          }
          this._img.src = this.src;
          Polymer.dom(this.root).appendChild(this._img);
        }
      }

    });

  </script>

</dom-module>
<script>
  (function() {
  'use strict';

  /**
   * Chrome uses an older version of DOM Level 3 Keyboard Events
   *
   * Most keys are labeled as text, but some are Unicode codepoints.
   * Values taken from:
   * http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html#KeySet-Set
   */
  var KEY_IDENTIFIER = {
    'U+0008': 'backspace',
    'U+0009': 'tab',
    'U+001B': 'esc',
    'U+0020': 'space',
    'U+007F': 'del'
  };

  /**
   * Special table for KeyboardEvent.keyCode.
   * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even better
   * than that.
   *
   * Values from:
   * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode#Value_of_keyCode
   */
  var KEY_CODE = {
    8: 'backspace',
    9: 'tab',
    13: 'enter',
    27: 'esc',
    33: 'pageup',
    34: 'pagedown',
    35: 'end',
    36: 'home',
    32: 'space',
    37: 'left',
    38: 'up',
    39: 'right',
    40: 'down',
    46: 'del',
    106: '*'
  };

  /**
   * MODIFIER_KEYS maps the short name for modifier keys used in a key
   * combo string to the property name that references those same keys
   * in a KeyboardEvent instance.
   */
  var MODIFIER_KEYS = {
    'shift': 'shiftKey',
    'ctrl': 'ctrlKey',
    'alt': 'altKey',
    'meta': 'metaKey'
  };

  /**
   * KeyboardEvent.key is mostly represented by printable character made by
   * the keyboard, with unprintable keys labeled nicely.
   *
   * However, on OS X, Alt+char can make a Unicode character that follows an
   * Apple-specific mapping. In this case, we fall back to .keyCode.
   */
  var KEY_CHAR = /[a-z0-9*]/;

  /**
   * Matches a keyIdentifier string.
   */
  var IDENT_CHAR = /U\+/;

  /**
   * Matches arrow keys in Gecko 27.0+
   */
  var ARROW_KEY = /^arrow/;

  /**
   * Matches space keys everywhere (notably including IE10's exceptional name
   * `spacebar`).
   */
  var SPACE_KEY = /^space(bar)?/;

  /**
   * Matches ESC key.
   *
   * Value from: http://w3c.github.io/uievents-key/#key-Escape
   */
  var ESC_KEY = /^escape$/;

  /**
   * Transforms the key.
   * @param {string} key The KeyBoardEvent.key
   * @param {Boolean} [noSpecialChars] Limits the transformation to
   * alpha-numeric characters.
   */
  function transformKey(key, noSpecialChars) {
    var validKey = '';
    if (key) {
      var lKey = key.toLowerCase();
      if (lKey === ' ' || SPACE_KEY.test(lKey)) {
        validKey = 'space';
      } else if (ESC_KEY.test(lKey)) {
        validKey = 'esc';
      } else if (lKey.length == 1) {
        if (!noSpecialChars || KEY_CHAR.test(lKey)) {
          validKey = lKey;
        }
      } else if (ARROW_KEY.test(lKey)) {
        validKey = lKey.replace('arrow', '');
      } else if (lKey == 'multiply') {
        // numpad '*' can map to Multiply on IE/Windows
        validKey = '*';
      } else {
        validKey = lKey;
      }
    }
    return validKey;
  }

  function transformKeyIdentifier(keyIdent) {
    var validKey = '';
    if (keyIdent) {
      if (keyIdent in KEY_IDENTIFIER) {
        validKey = KEY_IDENTIFIER[keyIdent];
      } else if (IDENT_CHAR.test(keyIdent)) {
        keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16);
        validKey = String.fromCharCode(keyIdent).toLowerCase();
      } else {
        validKey = keyIdent.toLowerCase();
      }
    }
    return validKey;
  }

  function transformKeyCode(keyCode) {
    var validKey = '';
    if (Number(keyCode)) {
      if (keyCode >= 65 && keyCode <= 90) {
        // ascii a-z
        // lowercase is 32 offset from uppercase
        validKey = String.fromCharCode(32 + keyCode);
      } else if (keyCode >= 112 && keyCode <= 123) {
        // function keys f1-f12
        validKey = 'f' + (keyCode - 112 + 1);
      } else if (keyCode >= 48 && keyCode <= 57) {
        // top 0-9 keys
        validKey = String(keyCode - 48);
      } else if (keyCode >= 96 && keyCode <= 105) {
        // num pad 0-9
        validKey = String(keyCode - 96);
      } else {
        validKey = KEY_CODE[keyCode];
      }
    }
    return validKey;
  }

  /**
   * Calculates the normalized key for a KeyboardEvent.
   * @param {KeyboardEvent} keyEvent
   * @param {Boolean} [noSpecialChars] Set to true to limit keyEvent.key
   * transformation to alpha-numeric chars. This is useful with key
   * combinations like shift + 2, which on FF for MacOS produces
   * keyEvent.key = @
   * To get 2 returned, set noSpecialChars = true
   * To get @ returned, set noSpecialChars = false
   */
  function normalizedKeyForEvent(keyEvent, noSpecialChars) {
    // Fall back from .key, to .detail.key for artifical keyboard events,
    // and then to deprecated .keyIdentifier and .keyCode.
    if (keyEvent.key) {
      return transformKey(keyEvent.key, noSpecialChars);
    }
    if (keyEvent.detail && keyEvent.detail.key) {
      return transformKey(keyEvent.detail.key, noSpecialChars);
    }
    return transformKeyIdentifier(keyEvent.keyIdentifier) ||
        transformKeyCode(keyEvent.keyCode) || '';
  }

  function keyComboMatchesEvent(keyCombo, event) {
    // For combos with modifiers we support only alpha-numeric keys
    var keyEvent = normalizedKeyForEvent(event, keyCombo.hasModifiers);
    return keyEvent === keyCombo.key &&
        (!keyCombo.hasModifiers ||
         (!!event.shiftKey === !!keyCombo.shiftKey &&
          !!event.ctrlKey === !!keyCombo.ctrlKey &&
          !!event.altKey === !!keyCombo.altKey &&
          !!event.metaKey === !!keyCombo.metaKey));
  }

  function parseKeyComboString(keyComboString) {
    if (keyComboString.length === 1) {
      return {combo: keyComboString, key: keyComboString, event: 'keydown'};
    }
    return keyComboString.split('+')
        .reduce(function(parsedKeyCombo, keyComboPart) {
          var eventParts = keyComboPart.split(':');
          var keyName = eventParts[0];
          var event = eventParts[1];

          if (keyName in MODIFIER_KEYS) {
            parsedKeyCombo[MODIFIER_KEYS[keyName]] = true;
            parsedKeyCombo.hasModifiers = true;
          } else {
            parsedKeyCombo.key = keyName;
            parsedKeyCombo.event = event || 'keydown';
          }

          return parsedKeyCombo;
        }, {combo: keyComboString.split(':').shift()});
  }

  function parseEventString(eventString) {
    return eventString.trim().split(' ').map(function(keyComboString) {
      return parseKeyComboString(keyComboString);
    });
  }

  /**
   * `Polymer.IronA11yKeysBehavior` provides a normalized interface for processing
   * keyboard commands that pertain to [WAI-ARIA best
   * practices](http://www.w3.org/TR/wai-aria-practices/#kbd_general_binding). The
   * element takes care of browser differences with respect to Keyboard events and
   * uses an expressive syntax to filter key presses.
   *
   * Use the `keyBindings` prototype property to express what combination of keys
   * will trigger the callback. A key binding has the format
   * `"KEY+MODIFIER:EVENT": "callback"` (`"KEY": "callback"` or
   * `"KEY:EVENT": "callback"` are valid as well). Some examples:
   *
   *      keyBindings: {
   *        'space': '_onKeydown', // same as 'space:keydown'
   *        'shift+tab': '_onKeydown',
   *        'enter:keypress': '_onKeypress',
   *        'esc:keyup': '_onKeyup'
   *      }
   *
   * The callback will receive with an event containing the following information
   * in `event.detail`:
   *
   *      _onKeydown: function(event) {
   *        console.log(event.detail.combo); // KEY+MODIFIER, e.g. "shift+tab"
   *        console.log(event.detail.key); // KEY only, e.g. "tab"
   *        console.log(event.detail.event); // EVENT, e.g. "keydown"
   *        console.log(event.detail.keyboardEvent); // the original KeyboardEvent
   *      }
   *
   * Use the `keyEventTarget` attribute to set up event handlers on a specific
   * node.
   *
   * See the [demo source
   * code](https://github.com/PolymerElements/iron-a11y-keys-behavior/blob/master/demo/x-key-aware.html)
   * for an example.
   *
   * @demo demo/index.html
   * @polymerBehavior
   */
  Polymer.IronA11yKeysBehavior = {
    properties: {
      /**
       * The EventTarget that will be firing relevant KeyboardEvents. Set it to
       * `null` to disable the listeners.
       * @type {?EventTarget}
       */
      keyEventTarget: {
        type: Object,
        value: function() {
          return this;
        }
      },

      /**
       * If true, this property will cause the implementing element to
       * automatically stop propagation on any handled KeyboardEvents.
       */
      stopKeyboardEventPropagation: {type: Boolean, value: false},

      _boundKeyHandlers: {
        type: Array,
        value: function() {
          return [];
        }
      },

      // We use this due to a limitation in IE10 where instances will have
      // own properties of everything on the "prototype".
      _imperativeKeyBindings: {
        type: Object,
        value: function() {
          return {};
        }
      }
    },

    observers: ['_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)'],


    /**
     * To be used to express what combination of keys  will trigger the relative
     * callback. e.g. `keyBindings: { 'esc': '_onEscPressed'}`
     * @type {!Object}
     */
    keyBindings: {},

    registered: function() {
      this._prepKeyBindings();
    },

    attached: function() {
      this._listenKeyEventListeners();
    },

    detached: function() {
      this._unlistenKeyEventListeners();
    },

    /**
     * Can be used to imperatively add a key binding to the implementing
     * element. This is the imperative equivalent of declaring a keybinding
     * in the `keyBindings` prototype property.
     *
     * @param {string} eventString
     * @param {string} handlerName
     */
    addOwnKeyBinding: function(eventString, handlerName) {
      this._imperativeKeyBindings[eventString] = handlerName;
      this._prepKeyBindings();
      this._resetKeyEventListeners();
    },

    /**
     * When called, will remove all imperatively-added key bindings.
     */
    removeOwnKeyBindings: function() {
      this._imperativeKeyBindings = {};
      this._prepKeyBindings();
      this._resetKeyEventListeners();
    },

    /**
     * Returns true if a keyboard event matches `eventString`.
     *
     * @param {KeyboardEvent} event
     * @param {string} eventString
     * @return {boolean}
     */
    keyboardEventMatchesKeys: function(event, eventString) {
      var keyCombos = parseEventString(eventString);
      for (var i = 0; i < keyCombos.length; ++i) {
        if (keyComboMatchesEvent(keyCombos[i], event)) {
          return true;
        }
      }
      return false;
    },

    _collectKeyBindings: function() {
      var keyBindings = this.behaviors.map(function(behavior) {
        return behavior.keyBindings;
      });

      if (keyBindings.indexOf(this.keyBindings) === -1) {
        keyBindings.push(this.keyBindings);
      }

      return keyBindings;
    },

    _prepKeyBindings: function() {
      this._keyBindings = {};

      this._collectKeyBindings().forEach(function(keyBindings) {
        for (var eventString in keyBindings) {
          this._addKeyBinding(eventString, keyBindings[eventString]);
        }
      }, this);

      for (var eventString in this._imperativeKeyBindings) {
        this._addKeyBinding(
            eventString, this._imperativeKeyBindings[eventString]);
      }

      // Give precedence to combos with modifiers to be checked first.
      for (var eventName in this._keyBindings) {
        this._keyBindings[eventName].sort(function(kb1, kb2) {
          var b1 = kb1[0].hasModifiers;
          var b2 = kb2[0].hasModifiers;
          return (b1 === b2) ? 0 : b1 ? -1 : 1;
        })
      }
    },

    _addKeyBinding: function(eventString, handlerName) {
      parseEventString(eventString).forEach(function(keyCombo) {
        this._keyBindings[keyCombo.event] =
            this._keyBindings[keyCombo.event] || [];

        this._keyBindings[keyCombo.event].push([keyCombo, handlerName]);
      }, this);
    },

    _resetKeyEventListeners: function() {
      this._unlistenKeyEventListeners();

      if (this.isAttached) {
        this._listenKeyEventListeners();
      }
    },

    _listenKeyEventListeners: function() {
      if (!this.keyEventTarget) {
        return;
      }
      Object.keys(this._keyBindings).forEach(function(eventName) {
        var keyBindings = this._keyBindings[eventName];
        var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings);

        this._boundKeyHandlers.push(
            [this.keyEventTarget, eventName, boundKeyHandler]);

        this.keyEventTarget.addEventListener(eventName, boundKeyHandler);
      }, this);
    },

    _unlistenKeyEventListeners: function() {
      var keyHandlerTuple;
      var keyEventTarget;
      var eventName;
      var boundKeyHandler;

      while (this._boundKeyHandlers.length) {
        // My kingdom for block-scope binding and destructuring assignment..
        keyHandlerTuple = this._boundKeyHandlers.pop();
        keyEventTarget = keyHandlerTuple[0];
        eventName = keyHandlerTuple[1];
        boundKeyHandler = keyHandlerTuple[2];

        keyEventTarget.removeEventListener(eventName, boundKeyHandler);
      }
    },

    _onKeyBindingEvent: function(keyBindings, event) {
      if (this.stopKeyboardEventPropagation) {
        event.stopPropagation();
      }

      // if event has been already prevented, don't do anything
      if (event.defaultPrevented) {
        return;
      }

      for (var i = 0; i < keyBindings.length; i++) {
        var keyCombo = keyBindings[i][0];
        var handlerName = keyBindings[i][1];
        if (keyComboMatchesEvent(keyCombo, event)) {
          this._triggerKeyHandler(keyCombo, handlerName, event);
          // exit the loop if eventDefault was prevented
          if (event.defaultPrevented) {
            return;
          }
        }
      }
    },

    _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) {
      var detail = Object.create(keyCombo);
      detail.keyboardEvent = keyboardEvent;
      var event =
          new CustomEvent(keyCombo.event, {detail: detail, cancelable: true});
      this[handlerName].call(this, event);
      if (event.defaultPrevented) {
        keyboardEvent.preventDefault();
      }
    }
  };
  })();
</script>
<script>

  /**
   * @demo demo/index.html
   * @polymerBehavior
   */
  Polymer.IronControlState = {

    properties: {

      /**
       * If true, the element currently has focus.
       */
      focused: {
        type: Boolean,
        value: false,
        notify: true,
        readOnly: true,
        reflectToAttribute: true
      },

      /**
       * If true, the user cannot interact with this element.
       */
      disabled: {
        type: Boolean,
        value: false,
        notify: true,
        observer: '_disabledChanged',
        reflectToAttribute: true
      },

      /**
       * Value of the `tabindex` attribute before `disabled` was activated.
       * `null` means the attribute was not present.
       * @type {?string|undefined}
       */
      _oldTabIndex: {
        type: String
      },

      _boundFocusBlurHandler: {
        type: Function,
        value: function() {
          return this._focusBlurHandler.bind(this);
        }
      },

      __handleEventRetargeting: {
        type: Boolean,
        value: function() {
          return !this.shadowRoot && !Polymer.Element;
        }
      }
    },

    observers: [
      '_changedControlState(focused, disabled)'
    ],

    /**
     * @return {void}
     */
    ready: function() {
      this.addEventListener('focus', this._boundFocusBlurHandler, true);
      this.addEventListener('blur', this._boundFocusBlurHandler, true);
    },

    _focusBlurHandler: function(event) {
      // In Polymer 2.0, the library takes care of retargeting events.
      if (Polymer.Element) {
        this._setFocused(event.type === 'focus');
        return;
      }

      // NOTE(cdata):  if we are in ShadowDOM land, `event.target` will
      // eventually become `this` due to retargeting; if we are not in
      // ShadowDOM land, `event.target` will eventually become `this` due
      // to the second conditional which fires a synthetic event (that is also
      // handled). In either case, we can disregard `event.path`.
      if (event.target === this) {
        this._setFocused(event.type === 'focus');
      } else if (this.__handleEventRetargeting) {
        var target = /** @type {Node} */(Polymer.dom(event).localTarget);
        if (!this.isLightDescendant(target)) {
          this.fire(event.type, {sourceEvent: event}, {
            node: this,
            bubbles: event.bubbles,
            cancelable: event.cancelable
          });
        }
      }
    },

    _disabledChanged: function(disabled, old) {
      this.setAttribute('aria-disabled', disabled ? 'true' : 'false');
      this.style.pointerEvents = disabled ? 'none' : '';
      if (disabled) {
        // Read the `tabindex` attribute instead of the `tabIndex` property.
        // The property returns `-1` if there is no `tabindex` attribute.
        // This distinction is important when restoring the value because
        // leaving `-1` hides shadow root children from the tab order.
        this._oldTabIndex = this.getAttribute('tabindex');
        this._setFocused(false);
        this.tabIndex = -1;
        this.blur();
      } else if (this._oldTabIndex !== undefined) {
        if (this._oldTabIndex === null) {
          this.removeAttribute('tabindex');
        } else {
          this.setAttribute('tabindex', this._oldTabIndex);
        }
      }
    },

    _changedControlState: function() {
      // _controlStateChanged is abstract, follow-on behaviors may implement it
      if (this._controlStateChanged) {
        this._controlStateChanged();
      }
    }

  };

</script>
<script>

  /**
   * @demo demo/index.html
   * @polymerBehavior Polymer.IronButtonState
   */
  Polymer.IronButtonStateImpl = {

    properties: {

      /**
       * If true, the user is currently holding down the button.
       */
      pressed: {
        type: Boolean,
        readOnly: true,
        value: false,
        reflectToAttribute: true,
        observer: '_pressedChanged'
      },

      /**
       * If true, the button toggles the active state with each tap or press
       * of the spacebar.
       */
      toggles: {
        type: Boolean,
        value: false,
        reflectToAttribute: true
      },

      /**
       * If true, the button is a toggle and is currently in the active state.
       */
      active: {
        type: Boolean,
        value: false,
        notify: true,
        reflectToAttribute: true
      },

      /**
       * True if the element is currently being pressed by a "pointer," which
       * is loosely defined as mouse or touch input (but specifically excluding
       * keyboard input).
       */
      pointerDown: {
        type: Boolean,
        readOnly: true,
        value: false
      },

      /**
       * True if the input device that caused the element to receive focus
       * was a keyboard.
       */
      receivedFocusFromKeyboard: {
        type: Boolean,
        readOnly: true
      },

      /**
       * The aria attribute to be set if the button is a toggle and in the
       * active state.
       */
      ariaActiveAttribute: {
        type: String,
        value: 'aria-pressed',
        observer: '_ariaActiveAttributeChanged'
      }
    },

    listeners: {
      down: '_downHandler',
      up: '_upHandler',
      tap: '_tapHandler'
    },

    observers: [
      '_focusChanged(focused)',
      '_activeChanged(active, ariaActiveAttribute)'
    ],

    /**
     * @type {!Object}
     */
    keyBindings: {
      'enter:keydown': '_asyncClick',
      'space:keydown': '_spaceKeyDownHandler',
      'space:keyup': '_spaceKeyUpHandler',
    },

    _mouseEventRe: /^mouse/,

    _tapHandler: function() {
      if (this.toggles) {
       // a tap is needed to toggle the active state
        this._userActivate(!this.active);
      } else {
        this.active = false;
      }
    },

    _focusChanged: function(focused) {
      this._detectKeyboardFocus(focused);

      if (!focused) {
        this._setPressed(false);
      }
    },

    _detectKeyboardFocus: function(focused) {
      this._setReceivedFocusFromKeyboard(!this.pointerDown && focused);
    },

    // to emulate native checkbox, (de-)activations from a user interaction fire
    // 'change' events
    _userActivate: function(active) {
      if (this.active !== active) {
        this.active = active;
        this.fire('change');
      }
    },

    _downHandler: function(event) {
      this._setPointerDown(true);
      this._setPressed(true);
      this._setReceivedFocusFromKeyboard(false);
    },

    _upHandler: function() {
      this._setPointerDown(false);
      this._setPressed(false);
    },

    /**
     * @param {!KeyboardEvent} event .
     */
    _spaceKeyDownHandler: function(event) {
      var keyboardEvent = event.detail.keyboardEvent;
      var target = Polymer.dom(keyboardEvent).localTarget;

      // Ignore the event if this is coming from a focused light child, since that
      // element will deal with it.
      if (this.isLightDescendant(/** @type {Node} */(target)))
        return;

      keyboardEvent.preventDefault();
      keyboardEvent.stopImmediatePropagation();
      this._setPressed(true);
    },

    /**
     * @param {!KeyboardEvent} event .
     */
    _spaceKeyUpHandler: function(event) {
      var keyboardEvent = event.detail.keyboardEvent;
      var target = Polymer.dom(keyboardEvent).localTarget;

      // Ignore the event if this is coming from a focused light child, since that
      // element will deal with it.
      if (this.isLightDescendant(/** @type {Node} */(target)))
        return;

      if (this.pressed) {
        this._asyncClick();
      }
      this._setPressed(false);
    },

    // trigger click asynchronously, the asynchrony is useful to allow one
    // event handler to unwind before triggering another event
    _asyncClick: function() {
      this.async(function() {
        this.click();
      }, 1);
    },

    // any of these changes are considered a change to button state

    _pressedChanged: function(pressed) {
      this._changedButtonState();
    },

    _ariaActiveAttributeChanged: function(value, oldValue) {
      if (oldValue && oldValue != value && this.hasAttribute(oldValue)) {
        this.removeAttribute(oldValue);
      }
    },

    _activeChanged: function(active, ariaActiveAttribute) {
      if (this.toggles) {
        this.setAttribute(this.ariaActiveAttribute,
                          active ? 'true' : 'false');
      } else {
        this.removeAttribute(this.ariaActiveAttribute);
      }
      this._changedButtonState();
    },

    _controlStateChanged: function() {
      if (this.disabled) {
        this._setPressed(false);
      } else {
        this._changedButtonState();
      }
    },

    // provide hook for follow-on behaviors to react to button-state

    _changedButtonState: function() {
      if (this._buttonStateChanged) {
        this._buttonStateChanged(); // abstract
      }
    }

  };

  /** @polymerBehavior */
  Polymer.IronButtonState = [
    Polymer.IronA11yKeysBehavior,
    Polymer.IronButtonStateImpl
  ];

</script>
<dom-module id="paper-ripple" assetpath="bower_components/paper-ripple/">

  <template>
    <style>
      :host {
        display: block;
        position: absolute;
        border-radius: inherit;
        overflow: hidden;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;

        /* See PolymerElements/paper-behaviors/issues/34. On non-Chrome browsers,
         * creating a node (with a position:absolute) in the middle of an event
         * handler "interrupts" that event handler (which happens when the
         * ripple is created on demand) */
        pointer-events: none;
      }

      :host([animating]) {
        /* This resolves a rendering issue in Chrome (as of 40) where the
           ripple is not properly clipped by its parent (which may have
           rounded corners). See: http://jsbin.com/temexa/4

           Note: We only apply this style conditionally. Otherwise, the browser
           will create a new compositing layer for every ripple element on the
           page, and that would be bad. */
        -webkit-transform: translate(0, 0);
        transform: translate3d(0, 0, 0);
      }

      #background,
      #waves,
      .wave-container,
      .wave {
        pointer-events: none;
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
      }

      #background,
      .wave {
        opacity: 0;
      }

      #waves,
      .wave {
        overflow: hidden;
      }

      .wave-container,
      .wave {
        border-radius: 50%;
      }

      :host(.circle) #background,
      :host(.circle) #waves {
        border-radius: 50%;
      }

      :host(.circle) .wave-container {
        overflow: hidden;
      }
    </style>

    <div id="background"></div>
    <div id="waves"></div>
  </template>
</dom-module>
<script>
  (function() {
  'use strict';

  var Utility = {
    distance: function(x1, y1, x2, y2) {
      var xDelta = (x1 - x2);
      var yDelta = (y1 - y2);

      return Math.sqrt(xDelta * xDelta + yDelta * yDelta);
    },

    now: window.performance && window.performance.now ?
        window.performance.now.bind(window.performance) :
        Date.now
  };

  /**
   * @param {HTMLElement} element
   * @constructor
   */
  function ElementMetrics(element) {
    this.element = element;
    this.width = this.boundingRect.width;
    this.height = this.boundingRect.height;

    this.size = Math.max(this.width, this.height);
  }

  ElementMetrics.prototype = {
    get boundingRect() {
      return this.element.getBoundingClientRect();
    },

    furthestCornerDistanceFrom: function(x, y) {
      var topLeft = Utility.distance(x, y, 0, 0);
      var topRight = Utility.distance(x, y, this.width, 0);
      var bottomLeft = Utility.distance(x, y, 0, this.height);
      var bottomRight = Utility.distance(x, y, this.width, this.height);

      return Math.max(topLeft, topRight, bottomLeft, bottomRight);
    }
  };

  /**
   * @param {HTMLElement} element
   * @constructor
   */
  function Ripple(element) {
    this.element = element;
    this.color = window.getComputedStyle(element).color;

    this.wave = document.createElement('div');
    this.waveContainer = document.createElement('div');
    this.wave.style.backgroundColor = this.color;
    this.wave.classList.add('wave');
    this.waveContainer.classList.add('wave-container');
    Polymer.dom(this.waveContainer).appendChild(this.wave);

    this.resetInteractionState();
  }

  Ripple.MAX_RADIUS = 300;

  Ripple.prototype = {
    get recenters() {
      return this.element.recenters;
    },

    get center() {
      return this.element.center;
    },

    get mouseDownElapsed() {
      var elapsed;

      if (!this.mouseDownStart) {
        return 0;
      }

      elapsed = Utility.now() - this.mouseDownStart;

      if (this.mouseUpStart) {
        elapsed -= this.mouseUpElapsed;
      }

      return elapsed;
    },

    get mouseUpElapsed() {
      return this.mouseUpStart ? Utility.now() - this.mouseUpStart : 0;
    },

    get mouseDownElapsedSeconds() {
      return this.mouseDownElapsed / 1000;
    },

    get mouseUpElapsedSeconds() {
      return this.mouseUpElapsed / 1000;
    },

    get mouseInteractionSeconds() {
      return this.mouseDownElapsedSeconds + this.mouseUpElapsedSeconds;
    },

    get initialOpacity() {
      return this.element.initialOpacity;
    },

    get opacityDecayVelocity() {
      return this.element.opacityDecayVelocity;
    },

    get radius() {
      var width2 = this.containerMetrics.width * this.containerMetrics.width;
      var height2 = this.containerMetrics.height * this.containerMetrics.height;
      var waveRadius =
          Math.min(Math.sqrt(width2 + height2), Ripple.MAX_RADIUS) * 1.1 + 5;

      var duration = 1.1 - 0.2 * (waveRadius / Ripple.MAX_RADIUS);
      var timeNow = this.mouseInteractionSeconds / duration;
      var size = waveRadius * (1 - Math.pow(80, -timeNow));

      return Math.abs(size);
    },

    get opacity() {
      if (!this.mouseUpStart) {
        return this.initialOpacity;
      }

      return Math.max(
          0,
          this.initialOpacity -
              this.mouseUpElapsedSeconds * this.opacityDecayVelocity);
    },

    get outerOpacity() {
      // Linear increase in background opacity, capped at the opacity
      // of the wavefront (waveOpacity).
      var outerOpacity = this.mouseUpElapsedSeconds * 0.3;
      var waveOpacity = this.opacity;

      return Math.max(0, Math.min(outerOpacity, waveOpacity));
    },

    get isOpacityFullyDecayed() {
      return this.opacity < 0.01 &&
          this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
    },

    get isRestingAtMaxRadius() {
      return this.opacity >= this.initialOpacity &&
          this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
    },

    get isAnimationComplete() {
      return this.mouseUpStart ? this.isOpacityFullyDecayed :
                                 this.isRestingAtMaxRadius;
    },

    get translationFraction() {
      return Math.min(
          1, this.radius / this.containerMetrics.size * 2 / Math.sqrt(2));
    },

    get xNow() {
      if (this.xEnd) {
        return this.xStart + this.translationFraction * (this.xEnd - this.xStart);
      }

      return this.xStart;
    },

    get yNow() {
      if (this.yEnd) {
        return this.yStart + this.translationFraction * (this.yEnd - this.yStart);
      }

      return this.yStart;
    },

    get isMouseDown() {
      return this.mouseDownStart && !this.mouseUpStart;
    },

    resetInteractionState: function() {
      this.maxRadius = 0;
      this.mouseDownStart = 0;
      this.mouseUpStart = 0;

      this.xStart = 0;
      this.yStart = 0;
      this.xEnd = 0;
      this.yEnd = 0;
      this.slideDistance = 0;

      this.containerMetrics = new ElementMetrics(this.element);
    },

    draw: function() {
      var scale;
      var dx;
      var dy;

      this.wave.style.opacity = this.opacity;

      scale = this.radius / (this.containerMetrics.size / 2);
      dx = this.xNow - (this.containerMetrics.width / 2);
      dy = this.yNow - (this.containerMetrics.height / 2);


      // 2d transform for safari because of border-radius and overflow:hidden
      // clipping bug. https://bugs.webkit.org/show_bug.cgi?id=98538
      this.waveContainer.style.webkitTransform =
          'translate(' + dx + 'px, ' + dy + 'px)';
      this.waveContainer.style.transform =
          'translate3d(' + dx + 'px, ' + dy + 'px, 0)';
      this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')';
      this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)';
    },

    /** @param {Event=} event */
    downAction: function(event) {
      var xCenter = this.containerMetrics.width / 2;
      var yCenter = this.containerMetrics.height / 2;

      this.resetInteractionState();
      this.mouseDownStart = Utility.now();

      if (this.center) {
        this.xStart = xCenter;
        this.yStart = yCenter;
        this.slideDistance =
            Utility.distance(this.xStart, this.yStart, this.xEnd, this.yEnd);
      } else {
        this.xStart = event ?
            event.detail.x - this.containerMetrics.boundingRect.left :
            this.containerMetrics.width / 2;
        this.yStart = event ?
            event.detail.y - this.containerMetrics.boundingRect.top :
            this.containerMetrics.height / 2;
      }

      if (this.recenters) {
        this.xEnd = xCenter;
        this.yEnd = yCenter;
        this.slideDistance =
            Utility.distance(this.xStart, this.yStart, this.xEnd, this.yEnd);
      }

      this.maxRadius = this.containerMetrics.furthestCornerDistanceFrom(
          this.xStart, this.yStart);

      this.waveContainer.style.top =
          (this.containerMetrics.height - this.containerMetrics.size) / 2 + 'px';
      this.waveContainer.style.left =
          (this.containerMetrics.width - this.containerMetrics.size) / 2 + 'px';

      this.waveContainer.style.width = this.containerMetrics.size + 'px';
      this.waveContainer.style.height = this.containerMetrics.size + 'px';
    },

    /** @param {Event=} event */
    upAction: function(event) {
      if (!this.isMouseDown) {
        return;
      }

      this.mouseUpStart = Utility.now();
    },

    remove: function() {
      Polymer.dom(this.waveContainer.parentNode).removeChild(this.waveContainer);
    }
  };

  Polymer({
    is: 'paper-ripple',

    behaviors: [Polymer.IronA11yKeysBehavior],

    properties: {
      /**
       * The initial opacity set on the wave.
       *
       * @attribute initialOpacity
       * @type number
       * @default 0.25
       */
      initialOpacity: {type: Number, value: 0.25},

      /**
       * How fast (opacity per second) the wave fades out.
       *
       * @attribute opacityDecayVelocity
       * @type number
       * @default 0.8
       */
      opacityDecayVelocity: {type: Number, value: 0.8},

      /**
       * If true, ripples will exhibit a gravitational pull towards
       * the center of their container as they fade away.
       *
       * @attribute recenters
       * @type boolean
       * @default false
       */
      recenters: {type: Boolean, value: false},

      /**
       * If true, ripples will center inside its container
       *
       * @attribute recenters
       * @type boolean
       * @default false
       */
      center: {type: Boolean, value: false},

      /**
       * A list of the visual ripples.
       *
       * @attribute ripples
       * @type Array
       * @default []
       */
      ripples: {
        type: Array,
        value: function() {
          return [];
        }
      },

      /**
       * True when there are visible ripples animating within the
       * element.
       */
      animating:
          {type: Boolean, readOnly: true, reflectToAttribute: true, value: false},

      /**
       * If true, the ripple will remain in the "down" state until `holdDown`
       * is set to false again.
       */
      holdDown: {type: Boolean, value: false, observer: '_holdDownChanged'},

      /**
       * If true, the ripple will not generate a ripple effect
       * via pointer interaction.
       * Calling ripple's imperative api like `simulatedRipple` will
       * still generate the ripple effect.
       */
      noink: {type: Boolean, value: false},

      _animating: {type: Boolean},

      _boundAnimate: {
        type: Function,
        value: function() {
          return this.animate.bind(this);
        }
      }
    },

    get target() {
      return this.keyEventTarget;
    },

    /**
     * @type {!Object}
     */
    keyBindings: {
      'enter:keydown': '_onEnterKeydown',
      'space:keydown': '_onSpaceKeydown',
      'space:keyup': '_onSpaceKeyup'
    },

    attached: function() {
      // Set up a11yKeysBehavior to listen to key events on the target,
      // so that space and enter activate the ripple even if the target doesn't
      // handle key events. The key handlers deal with `noink` themselves.
      if (this.parentNode.nodeType == 11) {  // DOCUMENT_FRAGMENT_NODE
        this.keyEventTarget = Polymer.dom(this).getOwnerRoot().host;
      } else {
        this.keyEventTarget = this.parentNode;
      }
      var keyEventTarget = /** @type {!EventTarget} */ (this.keyEventTarget);
      this.listen(keyEventTarget, 'up', 'uiUpAction');
      this.listen(keyEventTarget, 'down', 'uiDownAction');
    },

    detached: function() {
      this.unlisten(this.keyEventTarget, 'up', 'uiUpAction');
      this.unlisten(this.keyEventTarget, 'down', 'uiDownAction');
      this.keyEventTarget = null;
    },

    get shouldKeepAnimating() {
      for (var index = 0; index < this.ripples.length; ++index) {
        if (!this.ripples[index].isAnimationComplete) {
          return true;
        }
      }

      return false;
    },

    simulatedRipple: function() {
      this.downAction(null);

      // Please see polymer/polymer#1305
      this.async(function() {
        this.upAction();
      }, 1);
    },

    /**
     * Provokes a ripple down effect via a UI event,
     * respecting the `noink` property.
     * @param {Event=} event
     */
    uiDownAction: function(event) {
      if (!this.noink) {
        this.downAction(event);
      }
    },

    /**
     * Provokes a ripple down effect via a UI event,
     * *not* respecting the `noink` property.
     * @param {Event=} event
     */
    downAction: function(event) {
      if (this.holdDown && this.ripples.length > 0) {
        return;
      }

      var ripple = this.addRipple();

      ripple.downAction(event);

      if (!this._animating) {
        this._animating = true;
        this.animate();
      }
    },

    /**
     * Provokes a ripple up effect via a UI event,
     * respecting the `noink` property.
     * @param {Event=} event
     */
    uiUpAction: function(event) {
      if (!this.noink) {
        this.upAction(event);
      }
    },

    /**
     * Provokes a ripple up effect via a UI event,
     * *not* respecting the `noink` property.
     * @param {Event=} event
     */
    upAction: function(event) {
      if (this.holdDown) {
        return;
      }

      this.ripples.forEach(function(ripple) {
        ripple.upAction(event);
      });

      this._animating = true;
      this.animate();
    },

    onAnimationComplete: function() {
      this._animating = false;
      this.$.background.style.backgroundColor = null;
      this.fire('transitionend');
    },

    addRipple: function() {
      var ripple = new Ripple(this);

      Polymer.dom(this.$.waves).appendChild(ripple.waveContainer);
      this.$.background.style.backgroundColor = ripple.color;
      this.ripples.push(ripple);

      this._setAnimating(true);

      return ripple;
    },

    removeRipple: function(ripple) {
      var rippleIndex = this.ripples.indexOf(ripple);

      if (rippleIndex < 0) {
        return;
      }

      this.ripples.splice(rippleIndex, 1);

      ripple.remove();

      if (!this.ripples.length) {
        this._setAnimating(false);
      }
    },

    /**
     * This conflicts with Element#antimate().
     * https://developer.mozilla.org/en-US/docs/Web/API/Element/animate
     * @suppress {checkTypes}
     */
    animate: function() {
      if (!this._animating) {
        return;
      }
      var index;
      var ripple;

      for (index = 0; index < this.ripples.length; ++index) {
        ripple = this.ripples[index];

        ripple.draw();

        this.$.background.style.opacity = ripple.outerOpacity;

        if (ripple.isOpacityFullyDecayed && !ripple.isRestingAtMaxRadius) {
          this.removeRipple(ripple);
        }
      }

      if (!this.shouldKeepAnimating && this.ripples.length === 0) {
        this.onAnimationComplete();
      } else {
        window.requestAnimationFrame(this._boundAnimate);
      }
    },

    _onEnterKeydown: function() {
      this.uiDownAction();
      this.async(this.uiUpAction, 1);
    },

    _onSpaceKeydown: function() {
      this.uiDownAction();
    },

    _onSpaceKeyup: function() {
      this.uiUpAction();
    },

    // note: holdDown does not respect noink since it can be a focus based
    // effect.
    _holdDownChanged: function(newVal, oldVal) {
      if (oldVal === undefined) {
        return;
      }
      if (newVal) {
        this.downAction();
      } else {
        this.upAction();
      }
    }

    /**
    Fired when the animation finishes.
    This is useful if you want to wait until
    the ripple animation finishes to perform some action.

    @event transitionend
    @param {{node: Object}} detail Contains the animated node.
    */
  });
  })();
</script>
<script>
  /**
   * `Polymer.PaperRippleBehavior` dynamically implements a ripple
   * when the element has focus via pointer or keyboard.
   *
   * NOTE: This behavior is intended to be used in conjunction with and after
   * `Polymer.IronButtonState` and `Polymer.IronControlState`.
   *
   * @polymerBehavior Polymer.PaperRippleBehavior
   */
  Polymer.PaperRippleBehavior = {
    properties: {
      /**
       * If true, the element will not produce a ripple effect when interacted
       * with via the pointer.
       */
      noink: {type: Boolean, observer: '_noinkChanged'},

      /**
       * @type {Element|undefined}
       */
      _rippleContainer: {
        type: Object,
      }
    },

    /**
     * Ensures a `<paper-ripple>` element is available when the element is
     * focused.
     */
    _buttonStateChanged: function() {
      if (this.focused) {
        this.ensureRipple();
      }
    },

    /**
     * In addition to the functionality provided in `IronButtonState`, ensures
     * a ripple effect is created when the element is in a `pressed` state.
     */
    _downHandler: function(event) {
      Polymer.IronButtonStateImpl._downHandler.call(this, event);
      if (this.pressed) {
        this.ensureRipple(event);
      }
    },

    /**
     * Ensures this element contains a ripple effect. For startup efficiency
     * the ripple effect is dynamically on demand when needed.
     * @param {!Event=} optTriggeringEvent (optional) event that triggered the
     * ripple.
     */
    ensureRipple: function(optTriggeringEvent) {
      if (!this.hasRipple()) {
        this._ripple = this._createRipple();
        this._ripple.noink = this.noink;
        var rippleContainer = this._rippleContainer || this.root;
        if (rippleContainer) {
          Polymer.dom(rippleContainer).appendChild(this._ripple);
        }
        if (optTriggeringEvent) {
          // Check if the event happened inside of the ripple container
          // Fall back to host instead of the root because distributed text
          // nodes are not valid event targets
          var domContainer = Polymer.dom(this._rippleContainer || this);
          var target = Polymer.dom(optTriggeringEvent).rootTarget;
          if (domContainer.deepContains(/** @type {Node} */ (target))) {
            this._ripple.uiDownAction(optTriggeringEvent);
          }
        }
      }
    },

    /**
     * Returns the `<paper-ripple>` element used by this element to create
     * ripple effects. The element's ripple is created on demand, when
     * necessary, and calling this method will force the
     * ripple to be created.
     */
    getRipple: function() {
      this.ensureRipple();
      return this._ripple;
    },

    /**
     * Returns true if this element currently contains a ripple effect.
     * @return {boolean}
     */
    hasRipple: function() {
      return Boolean(this._ripple);
    },

    /**
     * Create the element's ripple effect via creating a `<paper-ripple>`.
     * Override this method to customize the ripple element.
     * @return {!PaperRippleElement} Returns a `<paper-ripple>` element.
     */
    _createRipple: function() {
      var element = /** @type {!PaperRippleElement} */ (
          document.createElement('paper-ripple'));
      return element;
    },

    _noinkChanged: function(noink) {
      if (this.hasRipple()) {
        this._ripple.noink = noink;
      }
    }
  };
</script>
<script>
  /**
   * `Polymer.PaperInkyFocusBehavior` implements a ripple when the element has
   * keyboard focus.
   *
   * @polymerBehavior Polymer.PaperInkyFocusBehavior
   */
  Polymer.PaperInkyFocusBehaviorImpl = {
    observers: ['_focusedChanged(receivedFocusFromKeyboard)'],

    _focusedChanged: function(receivedFocusFromKeyboard) {
      if (receivedFocusFromKeyboard) {
        this.ensureRipple();
      }
      if (this.hasRipple()) {
        this._ripple.holdDown = receivedFocusFromKeyboard;
      }
    },

    _createRipple: function() {
      var ripple = Polymer.PaperRippleBehavior._createRipple();
      ripple.id = 'ink';
      ripple.setAttribute('center', '');
      ripple.classList.add('circle');
      return ripple;
    }
  };

  /** @polymerBehavior Polymer.PaperInkyFocusBehavior */
  Polymer.PaperInkyFocusBehavior = [
    Polymer.IronButtonState,
    Polymer.IronControlState,
    Polymer.PaperRippleBehavior,
    Polymer.PaperInkyFocusBehaviorImpl
  ];
</script>
<custom-style>
  <style is="custom-style">
    html {
      /*
       * You can use these generic variables in your elements for easy theming.
       * For example, if all your elements use `--primary-text-color` as its main
       * color, then switching from a light to a dark theme is just a matter of
       * changing the value of `--primary-text-color` in your application.
       */
      --primary-text-color: var(--light-theme-text-color);
      --primary-background-color: var(--light-theme-background-color);
      --secondary-text-color: var(--light-theme-secondary-color);
      --disabled-text-color: var(--light-theme-disabled-color);
      --divider-color: var(--light-theme-divider-color);
      --error-color: var(--paper-deep-orange-a700);

      /*
       * Primary and accent colors. Also see color.html for more colors.
       */
      --primary-color: var(--paper-indigo-500);
      --light-primary-color: var(--paper-indigo-100);
      --dark-primary-color: var(--paper-indigo-700);

      --accent-color: var(--paper-pink-a200);
      --light-accent-color: var(--paper-pink-a100);
      --dark-accent-color: var(--paper-pink-a400);


      /*
       * Material Design Light background theme
       */
      --light-theme-background-color: #ffffff;
      --light-theme-base-color: #000000;
      --light-theme-text-color: var(--paper-grey-900);
      --light-theme-secondary-color: #737373;  /* for secondary text and icons */
      --light-theme-disabled-color: #9b9b9b;  /* disabled/hint text */
      --light-theme-divider-color: #dbdbdb;

      /*
       * Material Design Dark background theme
       */
      --dark-theme-background-color: var(--paper-grey-900);
      --dark-theme-base-color: #ffffff;
      --dark-theme-text-color: #ffffff;
      --dark-theme-secondary-color: #bcbcbc;  /* for secondary text and icons */
      --dark-theme-disabled-color: #646464;  /* disabled/hint text */
      --dark-theme-divider-color: #3c3c3c;

      /*
       * Deprecated values because of their confusing names.
       */
      --text-primary-color: var(--dark-theme-text-color);
      --default-primary-color: var(--primary-color);
    }
  </style>
</custom-style>
<dom-module id="paper-icon-button" assetpath="bower_components/paper-icon-button/">
  <template strip-whitespace="">
    <style>
      :host {
        display: inline-block;
        position: relative;
        padding: 8px;
        outline: none;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        cursor: pointer;
        z-index: 0;
        line-height: 1;

        width: 40px;
        height: 40px;

        /* NOTE: Both values are needed, since some phones require the value to be `transparent`. */
        -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
        -webkit-tap-highlight-color: transparent;

        /* Because of polymer/2558, this style has lower specificity than * */
        box-sizing: border-box !important;

        @apply --paper-icon-button;
      }

      :host #ink {
        color: var(--paper-icon-button-ink-color, var(--primary-text-color));
        opacity: 0.6;
      }

      :host([disabled]) {
        color: var(--paper-icon-button-disabled-text, var(--disabled-text-color));
        pointer-events: none;
        cursor: auto;

        @apply --paper-icon-button-disabled;
      }

      :host([hidden]) {
        display: none !important;
      }

      :host(:hover) {
        @apply --paper-icon-button-hover;
      }

      iron-icon {
        --iron-icon-width: 100%;
        --iron-icon-height: 100%;
      }
    </style>

    <iron-icon id="icon" src="[[src]]" icon="[[icon]]" alt$="[[alt]]"></iron-icon>
  </template>

  <script>
    Polymer({
      is: 'paper-icon-button',

      hostAttributes: {role: 'button', tabindex: '0'},

      behaviors: [Polymer.PaperInkyFocusBehavior],

      properties: {
        /**
         * The URL of an image for the icon. If the src property is specified,
         * the icon property should not be.
         */
        src: {type: String},

        /**
         * Specifies the icon name or index in the set of icons available in
         * the icon's icon set. If the icon property is specified,
         * the src property should not be.
         */
        icon: {type: String},

        /**
         * Specifies the alternate text for the button, for accessibility.
         */
        alt: {type: String, observer: '_altChanged'}
      },

      _altChanged: function(newValue, oldValue) {
        var label = this.getAttribute('aria-label');

        // Don't stomp over a user-set aria-label.
        if (!label || oldValue == label) {
          this.setAttribute('aria-label', newValue);
        }
      }
    });
  </script>
</dom-module>
<script>

  /**
   * `Polymer.NeonAnimatableBehavior` is implemented by elements containing animations for use with
   * elements implementing `Polymer.NeonAnimationRunnerBehavior`.
   * @polymerBehavior
   */
  Polymer.NeonAnimatableBehavior = {

    properties: {

      /**
       * Animation configuration. See README for more info.
       */
      animationConfig: {
        type: Object
      },

      /**
       * Convenience property for setting an 'entry' animation. Do not set `animationConfig.entry`
       * manually if using this. The animated node is set to `this` if using this property.
       */
      entryAnimation: {
        observer: '_entryAnimationChanged',
        type: String
      },

      /**
       * Convenience property for setting an 'exit' animation. Do not set `animationConfig.exit`
       * manually if using this. The animated node is set to `this` if using this property.
       */
      exitAnimation: {
        observer: '_exitAnimationChanged',
        type: String
      }

    },

    _entryAnimationChanged: function() {
      this.animationConfig = this.animationConfig || {};
      this.animationConfig['entry'] = [{
        name: this.entryAnimation,
        node: this
      }];
    },

    _exitAnimationChanged: function() {
      this.animationConfig = this.animationConfig || {};
      this.animationConfig['exit'] = [{
        name: this.exitAnimation,
        node: this
      }];
    },

    _copyProperties: function(config1, config2) {
      // shallowly copy properties from config2 to config1
      for (var property in config2) {
        config1[property] = config2[property];
      }
    },

    _cloneConfig: function(config) {
      var clone = {
        isClone: true
      };
      this._copyProperties(clone, config);
      return clone;
    },

    _getAnimationConfigRecursive: function(type, map, allConfigs) {
      if (!this.animationConfig) {
        return;
      }

      if(this.animationConfig.value && typeof this.animationConfig.value === 'function') {
      	this._warn(this._logf('playAnimation', "Please put 'animationConfig' inside of your components 'properties' object instead of outside of it."));
      	return;
      }

      // type is optional
      var thisConfig;
      if (type) {
        thisConfig = this.animationConfig[type];
      } else {
        thisConfig = this.animationConfig;
      }

      if (!Array.isArray(thisConfig)) {
        thisConfig = [thisConfig];
      }

      // iterate animations and recurse to process configurations from child nodes
      if (thisConfig) {
        for (var config, index = 0; config = thisConfig[index]; index++) {
          if (config.animatable) {
            config.animatable._getAnimationConfigRecursive(config.type || type, map, allConfigs);
          } else {
            if (config.id) {
              var cachedConfig = map[config.id];
              if (cachedConfig) {
                // merge configurations with the same id, making a clone lazily
                if (!cachedConfig.isClone) {
                  map[config.id] = this._cloneConfig(cachedConfig);
                  cachedConfig = map[config.id];
                }
                this._copyProperties(cachedConfig, config);
              } else {
                // put any configs with an id into a map
                map[config.id] = config;
              }
            } else {
              allConfigs.push(config);
            }
          }
        }
      }
    },

    /**
     * An element implementing `Polymer.NeonAnimationRunnerBehavior` calls this method to configure
     * an animation with an optional type. Elements implementing `Polymer.NeonAnimatableBehavior`
     * should define the property `animationConfig`, which is either a configuration object
     * or a map of animation type to array of configuration objects.
     */
    getAnimationConfig: function(type) {
      var map = {};
      var allConfigs = [];
      this._getAnimationConfigRecursive(type, map, allConfigs);
      // append the configurations saved in the map to the array
      for (var key in map) {
        allConfigs.push(map[key]);
      }
      return allConfigs;
    }

  };

</script>
<script>

  /**
   * `Polymer.NeonAnimationRunnerBehavior` adds a method to run animations.
   *
   * @polymerBehavior Polymer.NeonAnimationRunnerBehavior
   */
  Polymer.NeonAnimationRunnerBehaviorImpl = {

    _configureAnimations: function(configs) {
      var results = [];
      var resultsToPlay = [];

      if (configs.length > 0) {
        for (var config, index = 0; config = configs[index]; index++) {
          var neonAnimation = document.createElement(config.name);
          // is this element actually a neon animation?
          if (neonAnimation.isNeonAnimation) {
            var result = null;
            // Closure compiler does not work well with a try / catch here. .configure needs to be
            // explicitly defined
            if (!neonAnimation.configure) {
              /**
               * @param {Object} config
               * @return {AnimationEffectReadOnly}
               */
              neonAnimation.configure = function(config) {
                return null;
              }
            }

            result = neonAnimation.configure(config);
            resultsToPlay.push({ result: result, config: config });
          } else {
            console.warn(this.is + ':', config.name, 'not found!');
          }
        }
      }

      for (var i = 0; i < resultsToPlay.length; i++) {
        var result = resultsToPlay[i].result;
        var config = resultsToPlay[i].config;
        // configuration or play could fail if polyfills aren't loaded
        try {
          // Check if we have an Effect rather than an Animation
          if (typeof result.cancel != 'function') {
            result = document.timeline.play(result);
          }
        } catch (e) {
          result = null;
          console.warn('Couldnt play', '(', config.name, ').', e);
        }

        if (result) {
          results.push({
            neonAnimation: neonAnimation,
            config: config,
            animation: result,
          });
        }
      }

      return results;
    },

    _shouldComplete: function(activeEntries) {
      var finished = true;
      for (var i = 0; i < activeEntries.length; i++) {
        if (activeEntries[i].animation.playState != 'finished') {
          finished = false;
          break;
        }
      }
      return finished;
    },

    _complete: function(activeEntries) {
      for (var i = 0; i < activeEntries.length; i++) {
        activeEntries[i].neonAnimation.complete(activeEntries[i].config);
      }
      for (var i = 0; i < activeEntries.length; i++) {
        activeEntries[i].animation.cancel();
      }
    },

    /**
     * Plays an animation with an optional `type`.
     * @param {string=} type
     * @param {!Object=} cookie
     */
    playAnimation: function(type, cookie) {
      var configs = this.getAnimationConfig(type);
      if (!configs) {
        return;
      }
      this._active = this._active || {};
      if (this._active[type]) {
        this._complete(this._active[type]);
        delete this._active[type];
      }

      var activeEntries = this._configureAnimations(configs);

      if (activeEntries.length == 0) {
        this.fire('neon-animation-finish', cookie, {bubbles: false});
        return;
      }

      this._active[type] = activeEntries;

      for (var i = 0; i < activeEntries.length; i++) {
        activeEntries[i].animation.onfinish = function() {
          if (this._shouldComplete(activeEntries)) {
            this._complete(activeEntries);
            delete this._active[type];
            this.fire('neon-animation-finish', cookie, {bubbles: false});
          }
        }.bind(this);
      }
    },

    /**
     * Cancels the currently running animations.
     */
    cancelAnimation: function() {
      for (var k in this._active) {
        var entries = this._active[k]

        for (var j in entries) {
          entries[j].animation.cancel();
        }
      }

      this._active = {};
    }
  };

  /** @polymerBehavior Polymer.NeonAnimationRunnerBehavior */
  Polymer.NeonAnimationRunnerBehavior = [
    Polymer.NeonAnimatableBehavior,
    Polymer.NeonAnimationRunnerBehaviorImpl
  ];
</script>
<script>
  /**
  `Polymer.IronFitBehavior` fits an element in another element using `max-height`
  and `max-width`, and optionally centers it in the window or another element.

  The element will only be sized and/or positioned if it has not already been
  sized and/or positioned by CSS.

  CSS properties               | Action
  -----------------------------|-------------------------------------------
  `position` set               | Element is not centered horizontally or
  vertically `top` or `bottom` set        | Element is not vertically centered
  `left` or `right` set        | Element is not horizontally centered
  `max-height` set             | Element respects `max-height`
  `max-width` set              | Element respects `max-width`

  `Polymer.IronFitBehavior` can position an element into another element using
  `verticalAlign` and `horizontalAlign`. This will override the element's css
  position.

        <div class="container">
          <iron-fit-impl vertical-align="top" horizontal-align="auto">
            Positioned into the container
          </iron-fit-impl>
        </div>

  Use `noOverlap` to position the element around another element without
  overlapping it.

        <div class="container">
          <iron-fit-impl no-overlap vertical-align="auto" horizontal-align="auto">
            Positioned around the container
          </iron-fit-impl>
        </div>

  Use `horizontalOffset, verticalOffset` to offset the element from its
  `positionTarget`; `Polymer.IronFitBehavior` will collapse these in order to keep
  the element within `fitInto` boundaries, while preserving the element's CSS
  margin values.

        <div class="container">
          <iron-fit-impl vertical-align="top" vertical-offset="20">
            With vertical offset
          </iron-fit-impl>
        </div>


  @demo demo/index.html
  @polymerBehavior
  */
  Polymer.IronFitBehavior = {

    properties: {

      /**
       * The element that will receive a `max-height`/`width`. By default it is
       * the same as `this`, but it can be set to a child element. This is useful,
       * for example, for implementing a scrolling region inside the element.
       * @type {!Element}
       */
      sizingTarget: {
        type: Object,
        value: function() {
          return this;
        }
      },

      /**
       * The element to fit `this` into.
       */
      fitInto: {type: Object, value: window},

      /**
       * Will position the element around the positionTarget without overlapping
       * it.
       */
      noOverlap: {type: Boolean},

      /**
       * The element that should be used to position the element. If not set, it
       * will default to the parent node.
       * @type {!Element}
       */
      positionTarget: {type: Element},

      /**
       * The orientation against which to align the element horizontally
       * relative to the `positionTarget`. Possible values are "left", "right",
       * "center", "auto".
       */
      horizontalAlign: {type: String},

      /**
       * The orientation against which to align the element vertically
       * relative to the `positionTarget`. Possible values are "top", "bottom",
       * "middle", "auto".
       */
      verticalAlign: {type: String},

      /**
       * If true, it will use `horizontalAlign` and `verticalAlign` values as
       * preferred alignment and if there's not enough space, it will pick the
       * values which minimize the cropping.
       */
      dynamicAlign: {type: Boolean},

      /**
       * A pixel value that will be added to the position calculated for the
       * given `horizontalAlign`, in the direction of alignment. You can think
       * of it as increasing or decreasing the distance to the side of the
       * screen given by `horizontalAlign`.
       *
       * If `horizontalAlign` is "left" or "center", this offset will increase or
       * decrease the distance to the left side of the screen: a negative offset
       * will move the dropdown to the left; a positive one, to the right.
       *
       * Conversely if `horizontalAlign` is "right", this offset will increase
       * or decrease the distance to the right side of the screen: a negative
       * offset will move the dropdown to the right; a positive one, to the left.
       */
      horizontalOffset: {type: Number, value: 0, notify: true},

      /**
       * A pixel value that will be added to the position calculated for the
       * given `verticalAlign`, in the direction of alignment. You can think
       * of it as increasing or decreasing the distance to the side of the
       * screen given by `verticalAlign`.
       *
       * If `verticalAlign` is "top" or "middle", this offset will increase or
       * decrease the distance to the top side of the screen: a negative offset
       * will move the dropdown upwards; a positive one, downwards.
       *
       * Conversely if `verticalAlign` is "bottom", this offset will increase
       * or decrease the distance to the bottom side of the screen: a negative
       * offset will move the dropdown downwards; a positive one, upwards.
       */
      verticalOffset: {type: Number, value: 0, notify: true},

      /**
       * Set to true to auto-fit on attach.
       */
      autoFitOnAttach: {type: Boolean, value: false},

      /** @type {?Object} */
      _fitInfo: {type: Object}
    },

    get _fitWidth() {
      var fitWidth;
      if (this.fitInto === window) {
        fitWidth = this.fitInto.innerWidth;
      } else {
        fitWidth = this.fitInto.getBoundingClientRect().width;
      }
      return fitWidth;
    },

    get _fitHeight() {
      var fitHeight;
      if (this.fitInto === window) {
        fitHeight = this.fitInto.innerHeight;
      } else {
        fitHeight = this.fitInto.getBoundingClientRect().height;
      }
      return fitHeight;
    },

    get _fitLeft() {
      var fitLeft;
      if (this.fitInto === window) {
        fitLeft = 0;
      } else {
        fitLeft = this.fitInto.getBoundingClientRect().left;
      }
      return fitLeft;
    },

    get _fitTop() {
      var fitTop;
      if (this.fitInto === window) {
        fitTop = 0;
      } else {
        fitTop = this.fitInto.getBoundingClientRect().top;
      }
      return fitTop;
    },

    /**
     * The element that should be used to position the element,
     * if no position target is configured.
     */
    get _defaultPositionTarget() {
      var parent = Polymer.dom(this).parentNode;

      if (parent && parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
        parent = parent.host;
      }

      return parent;
    },

    /**
     * The horizontal align value, accounting for the RTL/LTR text direction.
     */
    get _localeHorizontalAlign() {
      if (this._isRTL) {
        // In RTL, "left" becomes "right".
        if (this.horizontalAlign === 'right') {
          return 'left';
        }
        if (this.horizontalAlign === 'left') {
          return 'right';
        }
      }
      return this.horizontalAlign;
    },

    /**
     * True if the element should be positioned instead of centered.
     * @private
     */
    get __shouldPosition() {
      return (this.horizontalAlign || this.verticalAlign) && this.positionTarget;
    },

    attached: function() {
      // Memoize this to avoid expensive calculations & relayouts.
      // Make sure we do it only once
      if (typeof this._isRTL === 'undefined') {
        this._isRTL = window.getComputedStyle(this).direction == 'rtl';
      }
      this.positionTarget = this.positionTarget || this._defaultPositionTarget;
      if (this.autoFitOnAttach) {
        if (window.getComputedStyle(this).display === 'none') {
          setTimeout(function() {
            this.fit();
          }.bind(this));
        } else {
          // NOTE: shadydom applies distribution asynchronously
          // for performance reasons webcomponents/shadydom#120
          // Flush to get correct layout info.
          window.ShadyDOM && ShadyDOM.flush();
          this.fit();
        }
      }
    },

    detached: function() {
      if (this.__deferredFit) {
        clearTimeout(this.__deferredFit);
        this.__deferredFit = null;
      }
    },

    /**
     * Positions and fits the element into the `fitInto` element.
     */
    fit: function() {
      this.position();
      this.constrain();
      this.center();
    },

    /**
     * Memoize information needed to position and size the target element.
     * @suppress {deprecated}
     */
    _discoverInfo: function() {
      if (this._fitInfo) {
        return;
      }
      var target = window.getComputedStyle(this);
      var sizer = window.getComputedStyle(this.sizingTarget);

      this._fitInfo = {
        inlineStyle: {
          top: this.style.top || '',
          left: this.style.left || '',
          position: this.style.position || ''
        },
        sizerInlineStyle: {
          maxWidth: this.sizingTarget.style.maxWidth || '',
          maxHeight: this.sizingTarget.style.maxHeight || '',
          boxSizing: this.sizingTarget.style.boxSizing || ''
        },
        positionedBy: {
          vertically: target.top !== 'auto' ?
              'top' :
              (target.bottom !== 'auto' ? 'bottom' : null),
          horizontally: target.left !== 'auto' ?
              'left' :
              (target.right !== 'auto' ? 'right' : null)
        },
        sizedBy: {
          height: sizer.maxHeight !== 'none',
          width: sizer.maxWidth !== 'none',
          minWidth: parseInt(sizer.minWidth, 10) || 0,
          minHeight: parseInt(sizer.minHeight, 10) || 0
        },
        margin: {
          top: parseInt(target.marginTop, 10) || 0,
          right: parseInt(target.marginRight, 10) || 0,
          bottom: parseInt(target.marginBottom, 10) || 0,
          left: parseInt(target.marginLeft, 10) || 0
        }
      };
    },

    /**
     * Resets the target element's position and size constraints, and clear
     * the memoized data.
     */
    resetFit: function() {
      var info = this._fitInfo || {};
      for (var property in info.sizerInlineStyle) {
        this.sizingTarget.style[property] = info.sizerInlineStyle[property];
      }
      for (var property in info.inlineStyle) {
        this.style[property] = info.inlineStyle[property];
      }

      this._fitInfo = null;
    },

    /**
     * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after
     * the element or the `fitInto` element has been resized, or if any of the
     * positioning properties (e.g. `horizontalAlign, verticalAlign`) is updated.
     * It preserves the scroll position of the sizingTarget.
     */
    refit: function() {
      var scrollLeft = this.sizingTarget.scrollLeft;
      var scrollTop = this.sizingTarget.scrollTop;
      this.resetFit();
      this.fit();
      this.sizingTarget.scrollLeft = scrollLeft;
      this.sizingTarget.scrollTop = scrollTop;
    },

    /**
     * Positions the element according to `horizontalAlign, verticalAlign`.
     */
    position: function() {
      if (!this.__shouldPosition) {
        // needs to be centered, and it is done after constrain.
        return;
      }
      this._discoverInfo();

      this.style.position = 'fixed';
      // Need border-box for margin/padding.
      this.sizingTarget.style.boxSizing = 'border-box';
      // Set to 0, 0 in order to discover any offset caused by parent stacking
      // contexts.
      this.style.left = '0px';
      this.style.top = '0px';

      var rect = this.getBoundingClientRect();
      var positionRect = this.__getNormalizedRect(this.positionTarget);
      var fitRect = this.__getNormalizedRect(this.fitInto);

      var margin = this._fitInfo.margin;

      // Consider the margin as part of the size for position calculations.
      var size = {
        width: rect.width + margin.left + margin.right,
        height: rect.height + margin.top + margin.bottom
      };

      var position = this.__getPosition(
          this._localeHorizontalAlign,
          this.verticalAlign,
          size,
          rect,
          positionRect,
          fitRect);

      var left = position.left + margin.left;
      var top = position.top + margin.top;

      // We first limit right/bottom within fitInto respecting the margin,
      // then use those values to limit top/left.
      var right = Math.min(fitRect.right - margin.right, left + rect.width);
      var bottom = Math.min(fitRect.bottom - margin.bottom, top + rect.height);

      // Keep left/top within fitInto respecting the margin.
      left = Math.max(
          fitRect.left + margin.left,
          Math.min(left, right - this._fitInfo.sizedBy.minWidth));
      top = Math.max(
          fitRect.top + margin.top,
          Math.min(top, bottom - this._fitInfo.sizedBy.minHeight));

      // Use right/bottom to set maxWidth/maxHeight, and respect
      // minWidth/minHeight.
      this.sizingTarget.style.maxWidth =
          Math.max(right - left, this._fitInfo.sizedBy.minWidth) + 'px';
      this.sizingTarget.style.maxHeight =
          Math.max(bottom - top, this._fitInfo.sizedBy.minHeight) + 'px';

      // Remove the offset caused by any stacking context.
      this.style.left = (left - rect.left) + 'px';
      this.style.top = (top - rect.top) + 'px';
    },

    /**
     * Constrains the size of the element to `fitInto` by setting `max-height`
     * and/or `max-width`.
     */
    constrain: function() {
      if (this.__shouldPosition) {
        return;
      }
      this._discoverInfo();

      var info = this._fitInfo;
      // position at (0px, 0px) if not already positioned, so we can measure the
      // natural size.
      if (!info.positionedBy.vertically) {
        this.style.position = 'fixed';
        this.style.top = '0px';
      }
      if (!info.positionedBy.horizontally) {
        this.style.position = 'fixed';
        this.style.left = '0px';
      }

      // need border-box for margin/padding
      this.sizingTarget.style.boxSizing = 'border-box';
      // constrain the width and height if not already set
      var rect = this.getBoundingClientRect();
      if (!info.sizedBy.height) {
        this.__sizeDimension(
            rect, info.positionedBy.vertically, 'top', 'bottom', 'Height');
      }
      if (!info.sizedBy.width) {
        this.__sizeDimension(
            rect, info.positionedBy.horizontally, 'left', 'right', 'Width');
      }
    },

    /**
     * @protected
     * @deprecated
     */
    _sizeDimension: function(rect, positionedBy, start, end, extent) {
      this.__sizeDimension(rect, positionedBy, start, end, extent);
    },

    /**
     * @private
     */
    __sizeDimension: function(rect, positionedBy, start, end, extent) {
      var info = this._fitInfo;
      var fitRect = this.__getNormalizedRect(this.fitInto);
      var max = extent === 'Width' ? fitRect.width : fitRect.height;
      var flip = (positionedBy === end);
      var offset = flip ? max - rect[end] : rect[start];
      var margin = info.margin[flip ? start : end];
      var offsetExtent = 'offset' + extent;
      var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent];
      this.sizingTarget.style['max' + extent] =
          (max - margin - offset - sizingOffset) + 'px';
    },

    /**
     * Centers horizontally and vertically if not already positioned. This also
     * sets `position:fixed`.
     */
    center: function() {
      if (this.__shouldPosition) {
        return;
      }
      this._discoverInfo();

      var positionedBy = this._fitInfo.positionedBy;
      if (positionedBy.vertically && positionedBy.horizontally) {
        // Already positioned.
        return;
      }
      // Need position:fixed to center
      this.style.position = 'fixed';
      // Take into account the offset caused by parents that create stacking
      // contexts (e.g. with transform: translate3d). Translate to 0,0 and
      // measure the bounding rect.
      if (!positionedBy.vertically) {
        this.style.top = '0px';
      }
      if (!positionedBy.horizontally) {
        this.style.left = '0px';
      }
      // It will take in consideration margins and transforms
      var rect = this.getBoundingClientRect();
      var fitRect = this.__getNormalizedRect(this.fitInto);
      if (!positionedBy.vertically) {
        var top = fitRect.top - rect.top + (fitRect.height - rect.height) / 2;
        this.style.top = top + 'px';
      }
      if (!positionedBy.horizontally) {
        var left = fitRect.left - rect.left + (fitRect.width - rect.width) / 2;
        this.style.left = left + 'px';
      }
    },

    __getNormalizedRect: function(target) {
      if (target === document.documentElement || target === window) {
        return {
          top: 0,
          left: 0,
          width: window.innerWidth,
          height: window.innerHeight,
          right: window.innerWidth,
          bottom: window.innerHeight
        };
      }
      return target.getBoundingClientRect();
    },

    __getOffscreenArea: function(position, size, fitRect) {
      var verticalCrop = Math.min(0, position.top) +
          Math.min(0, fitRect.bottom - (position.top + size.height));
      var horizontalCrop = Math.min(0, position.left) +
          Math.min(0, fitRect.right - (position.left + size.width));
      return Math.abs(verticalCrop) * size.width +
          Math.abs(horizontalCrop) * size.height;
    },


    __getPosition: function(
        hAlign, vAlign, size, sizeNoMargins, positionRect, fitRect) {
      // All the possible configurations.
      // Ordered as top-left, top-right, bottom-left, bottom-right.
      var positions = [
        {
          verticalAlign: 'top',
          horizontalAlign: 'left',
          top: positionRect.top + this.verticalOffset,
          left: positionRect.left + this.horizontalOffset
        },
        {
          verticalAlign: 'top',
          horizontalAlign: 'right',
          top: positionRect.top + this.verticalOffset,
          left: positionRect.right - size.width - this.horizontalOffset
        },
        {
          verticalAlign: 'bottom',
          horizontalAlign: 'left',
          top: positionRect.bottom - size.height - this.verticalOffset,
          left: positionRect.left + this.horizontalOffset
        },
        {
          verticalAlign: 'bottom',
          horizontalAlign: 'right',
          top: positionRect.bottom - size.height - this.verticalOffset,
          left: positionRect.right - size.width - this.horizontalOffset
        }
      ];

      if (this.noOverlap) {
        // Duplicate.
        for (var i = 0, l = positions.length; i < l; i++) {
          var copy = {};
          for (var key in positions[i]) {
            copy[key] = positions[i][key];
          }
          positions.push(copy);
        }
        // Horizontal overlap only.
        positions[0].top = positions[1].top += positionRect.height;
        positions[2].top = positions[3].top -= positionRect.height;
        // Vertical overlap only.
        positions[4].left = positions[6].left += positionRect.width;
        positions[5].left = positions[7].left -= positionRect.width;
      }

      // Consider auto as null for coding convenience.
      vAlign = vAlign === 'auto' ? null : vAlign;
      hAlign = hAlign === 'auto' ? null : hAlign;

      if (!hAlign || hAlign === 'center') {
        positions.push({
          verticalAlign: 'top',
          horizontalAlign: 'center',
          top: positionRect.top + this.verticalOffset +
              (this.noOverlap ? positionRect.height : 0),
          left: positionRect.left - sizeNoMargins.width / 2 +
              positionRect.width / 2 + this.horizontalOffset
        });
        positions.push({
          verticalAlign: 'bottom',
          horizontalAlign: 'center',
          top: positionRect.bottom - size.height - this.verticalOffset -
              (this.noOverlap ? positionRect.height : 0),
          left: positionRect.left - sizeNoMargins.width / 2 +
              positionRect.width / 2 + this.horizontalOffset
        });
      }

      if (!vAlign || vAlign === 'middle') {
        positions.push({
          verticalAlign: 'middle',
          horizontalAlign: 'left',
          top: positionRect.top - sizeNoMargins.height / 2 +
              positionRect.height / 2 + this.verticalOffset,
          left: positionRect.left + this.horizontalOffset +
              (this.noOverlap ? positionRect.width : 0)
        });
        positions.push({
          verticalAlign: 'middle',
          horizontalAlign: 'right',
          top: positionRect.top - sizeNoMargins.height / 2 +
              positionRect.height / 2 + this.verticalOffset,
          left: positionRect.right - size.width - this.horizontalOffset -
              (this.noOverlap ? positionRect.width : 0)
        });
      }

      if (vAlign === 'middle' && hAlign === 'center') {
        positions.push({
          verticalAlign: 'middle',
          horizontalAlign: 'center',
          top: positionRect.top - sizeNoMargins.height / 2 +
              positionRect.height / 2 + this.verticalOffset,
          left: positionRect.left - sizeNoMargins.width / 2 +
              positionRect.width / 2 + this.horizontalOffset
        });
      }

      var position;
      for (var i = 0; i < positions.length; i++) {
        var candidate = positions[i];
        var vAlignOk = candidate.verticalAlign === vAlign;
        var hAlignOk = candidate.horizontalAlign === hAlign;

        // If both vAlign and hAlign are defined, return exact match.
        // For dynamicAlign and noOverlap we'll have more than one candidate, so
        // we'll have to check the offscreenArea to make the best choice.
        if (!this.dynamicAlign && !this.noOverlap && vAlignOk && hAlignOk) {
          position = candidate;
          break;
        }

        // Align is ok if alignment preferences are respected. If no preferences,
        // it is considered ok.
        var alignOk = (!vAlign || vAlignOk) && (!hAlign || hAlignOk);

        // Filter out elements that don't match the alignment (if defined).
        // With dynamicAlign, we need to consider all the positions to find the
        // one that minimizes the cropped area.
        if (!this.dynamicAlign && !alignOk) {
          continue;
        }

        candidate.offscreenArea =
            this.__getOffscreenArea(candidate, size, fitRect);
        // If not cropped and respects the align requirements, keep it.
        // This allows to prefer positions overlapping horizontally over the
        // ones overlapping vertically.
        if (candidate.offscreenArea === 0 && alignOk) {
          position = candidate;
          break;
        }
        position = position || candidate;
        var diff = candidate.offscreenArea - position.offscreenArea;
        // Check which crops less. If it crops equally, check if at least one
        // align setting is ok.
        if (diff < 0 || (diff === 0 && (vAlignOk || hAlignOk))) {
          position = candidate;
        }
      }

      return position;
    }

  };
</script>
<dom-module id="iron-overlay-backdrop" assetpath="bower_components/iron-overlay-behavior/">

  <template>
    <style>
      :host {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: var(--iron-overlay-backdrop-background-color, #000);
        opacity: 0;
        transition: opacity 0.2s;
        pointer-events: none;
        @apply --iron-overlay-backdrop;
      }

      :host(.opened) {
        opacity: var(--iron-overlay-backdrop-opacity, 0.6);
        pointer-events: auto;
        @apply --iron-overlay-backdrop-opened;
      }
    </style>

    <slot></slot>
  </template>

</dom-module>

<script>
  Polymer({

    is: 'iron-overlay-backdrop',

    properties: {

      /**
       * Returns true if the backdrop is opened.
       */
      opened: {
        reflectToAttribute: true,
        type: Boolean,
        value: false,
        observer: '_openedChanged'
      }

    },

    listeners: {'transitionend': '_onTransitionend'},

    created: function() {
      // Used to cancel previous requestAnimationFrame calls when opened changes.
      this.__openedRaf = null;
    },

    attached: function() {
      this.opened && this._openedChanged(this.opened);
    },

    /**
     * Appends the backdrop to document body if needed.
     */
    prepare: function() {
      if (this.opened && !this.parentNode) {
        Polymer.dom(document.body).appendChild(this);
      }
    },

    /**
     * Shows the backdrop.
     */
    open: function() {
      this.opened = true;
    },

    /**
     * Hides the backdrop.
     */
    close: function() {
      this.opened = false;
    },

    /**
     * Removes the backdrop from document body if needed.
     */
    complete: function() {
      if (!this.opened && this.parentNode === document.body) {
        Polymer.dom(this.parentNode).removeChild(this);
      }
    },

    _onTransitionend: function(event) {
      if (event && event.target === this) {
        this.complete();
      }
    },

    /**
     * @param {boolean} opened
     * @private
     */
    _openedChanged: function(opened) {
      if (opened) {
        // Auto-attach.
        this.prepare();
      } else {
        // Animation might be disabled via the mixin or opacity custom property.
        // If it is disabled in other ways, it's up to the user to call complete.
        var cs = window.getComputedStyle(this);
        if (cs.transitionDuration === '0s' || cs.opacity == 0) {
          this.complete();
        }
      }

      if (!this.isAttached) {
        return;
      }

      // Always cancel previous requestAnimationFrame.
      if (this.__openedRaf) {
        window.cancelAnimationFrame(this.__openedRaf);
        this.__openedRaf = null;
      }
      // Force relayout to ensure proper transitions.
      this.scrollTop = this.scrollTop;
      this.__openedRaf = window.requestAnimationFrame(function() {
        this.__openedRaf = null;
        this.toggleClass('opened', this.opened);
      }.bind(this));
    }
  });
</script>
<script>
  /**
   * @struct
   * @constructor
   * @private
   */
  Polymer.IronOverlayManagerClass = function() {
    /**
     * Used to keep track of the opened overlays.
     * @private {!Array<!Element>}
     */
    this._overlays = [];

    /**
     * iframes have a default z-index of 100,
     * so this default should be at least that.
     * @private {number}
     */
    this._minimumZ = 101;

    /**
     * Memoized backdrop element.
     * @private {Element|null}
     */
    this._backdropElement = null;

    // Enable document-wide tap recognizer.
    // NOTE: Use useCapture=true to avoid accidentally prevention of the closing
    // of an overlay via event.stopPropagation(). The only way to prevent
    // closing of an overlay should be through its APIs.
    // NOTE: enable tap on <html> to workaround Polymer/polymer#4459
    // Pass no-op function because MSEdge 15 doesn't handle null as 2nd argument
    // https://github.com/Microsoft/ChakraCore/issues/3863
    Polymer.Gestures.add(document.documentElement, 'tap', function() {});
    document.addEventListener('tap', this._onCaptureClick.bind(this), true);
    document.addEventListener('focus', this._onCaptureFocus.bind(this), true);
    document.addEventListener('keydown', this._onCaptureKeyDown.bind(this), true);
  };

  Polymer.IronOverlayManagerClass.prototype = {

    constructor: Polymer.IronOverlayManagerClass,

    /**
     * The shared backdrop element.
     * @return {!Element} backdropElement
     */
    get backdropElement() {
      if (!this._backdropElement) {
        this._backdropElement = document.createElement('iron-overlay-backdrop');
      }
      return this._backdropElement;
    },

    /**
     * The deepest active element.
     * @return {!Element} activeElement the active element
     */
    get deepActiveElement() {
      var active = document.activeElement;
      // document.activeElement can be null
      // https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement
      // In IE 11, it can also be an object when operating in iframes.
      // In these cases, default it to document.body.
      if (!active || active instanceof Element === false) {
        active = document.body;
      }
      while (active.root && Polymer.dom(active.root).activeElement) {
        active = Polymer.dom(active.root).activeElement;
      }
      return active;
    },

    /**
     * Brings the overlay at the specified index to the front.
     * @param {number} i
     * @private
     */
    _bringOverlayAtIndexToFront: function(i) {
      var overlay = this._overlays[i];
      if (!overlay) {
        return;
      }
      var lastI = this._overlays.length - 1;
      var currentOverlay = this._overlays[lastI];
      // Ensure always-on-top overlay stays on top.
      if (currentOverlay &&
          this._shouldBeBehindOverlay(overlay, currentOverlay)) {
        lastI--;
      }
      // If already the top element, return.
      if (i >= lastI) {
        return;
      }
      // Update z-index to be on top.
      var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ);
      if (this._getZ(overlay) <= minimumZ) {
        this._applyOverlayZ(overlay, minimumZ);
      }

      // Shift other overlays behind the new on top.
      while (i < lastI) {
        this._overlays[i] = this._overlays[i + 1];
        i++;
      }
      this._overlays[lastI] = overlay;
    },

    /**
     * Adds the overlay and updates its z-index if it's opened, or removes it if
     * it's closed. Also updates the backdrop z-index.
     * @param {!Element} overlay
     */
    addOrRemoveOverlay: function(overlay) {
      if (overlay.opened) {
        this.addOverlay(overlay);
      } else {
        this.removeOverlay(overlay);
      }
    },

    /**
     * Tracks overlays for z-index and focus management.
     * Ensures the last added overlay with always-on-top remains on top.
     * @param {!Element} overlay
     */
    addOverlay: function(overlay) {
      var i = this._overlays.indexOf(overlay);
      if (i >= 0) {
        this._bringOverlayAtIndexToFront(i);
        this.trackBackdrop();
        return;
      }
      var insertionIndex = this._overlays.length;
      var currentOverlay = this._overlays[insertionIndex - 1];
      var minimumZ = Math.max(this._getZ(currentOverlay), this._minimumZ);
      var newZ = this._getZ(overlay);

      // Ensure always-on-top overlay stays on top.
      if (currentOverlay &&
          this._shouldBeBehindOverlay(overlay, currentOverlay)) {
        // This bumps the z-index of +2.
        this._applyOverlayZ(currentOverlay, minimumZ);
        insertionIndex--;
        // Update minimumZ to match previous overlay's z-index.
        var previousOverlay = this._overlays[insertionIndex - 1];
        minimumZ = Math.max(this._getZ(previousOverlay), this._minimumZ);
      }

      // Update z-index and insert overlay.
      if (newZ <= minimumZ) {
        this._applyOverlayZ(overlay, minimumZ);
      }
      this._overlays.splice(insertionIndex, 0, overlay);

      this.trackBackdrop();
    },

    /**
     * @param {!Element} overlay
     */
    removeOverlay: function(overlay) {
      var i = this._overlays.indexOf(overlay);
      if (i === -1) {
        return;
      }
      this._overlays.splice(i, 1);

      this.trackBackdrop();
    },

    /**
     * Returns the current overlay.
     * @return {!Element|undefined}
     */
    currentOverlay: function() {
      var i = this._overlays.length - 1;
      return this._overlays[i];
    },

    /**
     * Returns the current overlay z-index.
     * @return {number}
     */
    currentOverlayZ: function() {
      return this._getZ(this.currentOverlay());
    },

    /**
     * Ensures that the minimum z-index of new overlays is at least `minimumZ`.
     * This does not effect the z-index of any existing overlays.
     * @param {number} minimumZ
     */
    ensureMinimumZ: function(minimumZ) {
      this._minimumZ = Math.max(this._minimumZ, minimumZ);
    },

    focusOverlay: function() {
      var current = /** @type {?} */ (this.currentOverlay());
      if (current) {
        current._applyFocus();
      }
    },

    /**
     * Updates the backdrop z-index.
     */
    trackBackdrop: function() {
      var overlay = this._overlayWithBackdrop();
      // Avoid creating the backdrop if there is no overlay with backdrop.
      if (!overlay && !this._backdropElement) {
        return;
      }
      this.backdropElement.style.zIndex = this._getZ(overlay) - 1;
      this.backdropElement.opened = !!overlay;
      // Property observers are not fired until element is attached
      // in Polymer 2.x, so we ensure element is attached if needed.
      // https://github.com/Polymer/polymer/issues/4526
      this.backdropElement.prepare();
    },

    /**
     * @return {!Array<!Element>}
     */
    getBackdrops: function() {
      var backdrops = [];
      for (var i = 0; i < this._overlays.length; i++) {
        if (this._overlays[i].withBackdrop) {
          backdrops.push(this._overlays[i]);
        }
      }
      return backdrops;
    },

    /**
     * Returns the z-index for the backdrop.
     * @return {number}
     */
    backdropZ: function() {
      return this._getZ(this._overlayWithBackdrop()) - 1;
    },

    /**
     * Returns the top opened overlay that has a backdrop.
     * @return {!Element|undefined}
     * @private
     */
    _overlayWithBackdrop: function() {
      for (var i = this._overlays.length - 1; i >= 0; i--) {
        if (this._overlays[i].withBackdrop) {
          return this._overlays[i];
        }
      }
    },

    /**
     * Calculates the minimum z-index for the overlay.
     * @param {Element=} overlay
     * @private
     */
    _getZ: function(overlay) {
      var z = this._minimumZ;
      if (overlay) {
        var z1 = Number(
            overlay.style.zIndex || window.getComputedStyle(overlay).zIndex);
        // Check if is a number
        // Number.isNaN not supported in IE 10+
        if (z1 === z1) {
          z = z1;
        }
      }
      return z;
    },

    /**
     * @param {!Element} element
     * @param {number|string} z
     * @private
     */
    _setZ: function(element, z) {
      element.style.zIndex = z;
    },

    /**
     * @param {!Element} overlay
     * @param {number} aboveZ
     * @private
     */
    _applyOverlayZ: function(overlay, aboveZ) {
      this._setZ(overlay, aboveZ + 2);
    },

    /**
     * Returns the deepest overlay in the path.
     * @param {!Array<!Element>=} path
     * @return {!Element|undefined}
     * @suppress {missingProperties}
     * @private
     */
    _overlayInPath: function(path) {
      path = path || [];
      for (var i = 0; i < path.length; i++) {
        if (path[i]._manager === this) {
          return path[i];
        }
      }
    },

    /**
     * Ensures the click event is delegated to the right overlay.
     * @param {!Event} event
     * @private
     */
    _onCaptureClick: function(event) {
      var i = this._overlays.length - 1;
      if (i === -1)
        return;
      var path = /** @type {!Array<!EventTarget>} */ (Polymer.dom(event).path);
      var overlay;
      // Check if clicked outside of overlay.
      while ((overlay = /** @type {?} */ (this._overlays[i])) &&
             this._overlayInPath(path) !== overlay) {
        overlay._onCaptureClick(event);
        if (overlay.allowClickThrough) {
          i--;
        } else {
          break;
        }
      }
    },

    /**
     * Ensures the focus event is delegated to the right overlay.
     * @param {!Event} event
     * @private
     */
    _onCaptureFocus: function(event) {
      var overlay = /** @type {?} */ (this.currentOverlay());
      if (overlay) {
        overlay._onCaptureFocus(event);
      }
    },

    /**
     * Ensures TAB and ESC keyboard events are delegated to the right overlay.
     * @param {!Event} event
     * @private
     */
    _onCaptureKeyDown: function(event) {
      var overlay = /** @type {?} */ (this.currentOverlay());
      if (overlay) {
        if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'esc')) {
          overlay._onCaptureEsc(event);
        } else if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(
                       event, 'tab')) {
          overlay._onCaptureTab(event);
        }
      }
    },

    /**
     * Returns if the overlay1 should be behind overlay2.
     * @param {!Element} overlay1
     * @param {!Element} overlay2
     * @return {boolean}
     * @suppress {missingProperties}
     * @private
     */
    _shouldBeBehindOverlay: function(overlay1, overlay2) {
      return !overlay1.alwaysOnTop && overlay2.alwaysOnTop;
    }
  };

  Polymer.IronOverlayManager = new Polymer.IronOverlayManagerClass();
</script>
<script>
  (function() {
  'use strict';
  /**
   * Used to calculate the scroll direction during touch events.
   * @type {!Object}
   */
  var lastTouchPosition = {pageX: 0, pageY: 0};
  /**
   * Used to avoid computing event.path and filter scrollable nodes (better perf).
   * @type {?EventTarget}
   */
  var lastRootTarget = null;
  /**
   * @type {!Array<!Node>}
   */
  var lastScrollableNodes = [];
  /**
   * @type {!Array<string>}
   */
  var scrollEvents = [
    // Modern `wheel` event for mouse wheel scrolling:
    'wheel',
    // Older, non-standard `mousewheel` event for some FF:
    'mousewheel',
    // IE:
    'DOMMouseScroll',
    // Touch enabled devices
    'touchstart',
    'touchmove'
  ];

  /**
   * The IronScrollManager is intended to provide a central source
   * of authority and control over which elements in a document are currently
   * allowed to scroll.
   *
   * @namespace
   * @memberof Polymer
   */
  Polymer.IronScrollManager = {

    /**
     * The current element that defines the DOM boundaries of the
     * scroll lock. This is always the most recently locking element.
     *
     * @return {!Node|undefined}
     */
    get currentLockingElement() {
      return this._lockingElements[this._lockingElements.length - 1];
    },

    /**
     * Returns true if the provided element is "scroll locked", which is to
     * say that it cannot be scrolled via pointer or keyboard interactions.
     *
     * @memberof Polymer.IronScrollManager
     * @param {!HTMLElement} element An HTML element instance which may or may
     * not be scroll locked.
     */
    elementIsScrollLocked: function(element) {
      var currentLockingElement = this.currentLockingElement;

      if (currentLockingElement === undefined)
        return false;

      var scrollLocked;

      if (this._hasCachedLockedElement(element)) {
        return true;
      }

      if (this._hasCachedUnlockedElement(element)) {
        return false;
      }

      scrollLocked = !!currentLockingElement &&
          currentLockingElement !== element &&
          !this._composedTreeContains(currentLockingElement, element);

      if (scrollLocked) {
        this._lockedElementCache.push(element);
      } else {
        this._unlockedElementCache.push(element);
      }

      return scrollLocked;
    },

    /**
     * Push an element onto the current scroll lock stack. The most recently
     * pushed element and its children will be considered scrollable. All
     * other elements will not be scrollable.
     *
     * Scroll locking is implemented as a stack so that cases such as
     * dropdowns within dropdowns are handled well.
     *
     * @memberof Polymer.IronScrollManager
     * @param {!HTMLElement} element The element that should lock scroll.
     */
    pushScrollLock: function(element) {
      // Prevent pushing the same element twice
      if (this._lockingElements.indexOf(element) >= 0) {
        return;
      }

      if (this._lockingElements.length === 0) {
        this._lockScrollInteractions();
      }

      this._lockingElements.push(element);

      this._lockedElementCache = [];
      this._unlockedElementCache = [];
    },

    /**
     * Remove an element from the scroll lock stack. The element being
     * removed does not need to be the most recently pushed element. However,
     * the scroll lock constraints only change when the most recently pushed
     * element is removed.
     *
     * @memberof Polymer.IronScrollManager
     * @param {!HTMLElement} element The element to remove from the scroll
     * lock stack.
     */
    removeScrollLock: function(element) {
      var index = this._lockingElements.indexOf(element);

      if (index === -1) {
        return;
      }

      this._lockingElements.splice(index, 1);

      this._lockedElementCache = [];
      this._unlockedElementCache = [];

      if (this._lockingElements.length === 0) {
        this._unlockScrollInteractions();
      }
    },

    _lockingElements: [],

    _lockedElementCache: null,

    _unlockedElementCache: null,

    _hasCachedLockedElement: function(element) {
      return this._lockedElementCache.indexOf(element) > -1;
    },

    _hasCachedUnlockedElement: function(element) {
      return this._unlockedElementCache.indexOf(element) > -1;
    },

    _composedTreeContains: function(element, child) {
      // NOTE(cdata): This method iterates over content elements and their
      // corresponding distributed nodes to implement a contains-like method
      // that pierces through the composed tree of the ShadowDOM. Results of
      // this operation are cached (elsewhere) on a per-scroll-lock basis, to
      // guard against potentially expensive lookups happening repeatedly as
      // a user scrolls / touchmoves.
      var contentElements;
      var distributedNodes;
      var contentIndex;
      var nodeIndex;

      if (element.contains(child)) {
        return true;
      }

      contentElements = Polymer.dom(element).querySelectorAll('content,slot');

      for (contentIndex = 0; contentIndex < contentElements.length;
           ++contentIndex) {
        distributedNodes =
            Polymer.dom(contentElements[contentIndex]).getDistributedNodes();

        for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex) {
          // Polymer 2.x returns slot.assignedNodes which can contain text nodes.
          if (distributedNodes[nodeIndex].nodeType !== Node.ELEMENT_NODE)
            continue;

          if (this._composedTreeContains(distributedNodes[nodeIndex], child)) {
            return true;
          }
        }
      }

      return false;
    },

    _scrollInteractionHandler: function(event) {
      // Avoid canceling an event with cancelable=false, e.g. scrolling is in
      // progress and cannot be interrupted.
      if (event.cancelable && this._shouldPreventScrolling(event)) {
        event.preventDefault();
      }
      // If event has targetTouches (touch event), update last touch position.
      if (event.targetTouches) {
        var touch = event.targetTouches[0];
        lastTouchPosition.pageX = touch.pageX;
        lastTouchPosition.pageY = touch.pageY;
      }
    },

    /**
     * @private
     */
    _boundScrollHandler: undefined,

    _lockScrollInteractions: function() {
      this._boundScrollHandler =
          this._boundScrollHandler || this._scrollInteractionHandler.bind(this);
      for (var i = 0, l = scrollEvents.length; i < l; i++) {
        // NOTE: browsers that don't support objects as third arg will
        // interpret it as boolean, hence useCapture = true in this case.
        document.addEventListener(
            scrollEvents[i],
            this._boundScrollHandler,
            {capture: true, passive: false});
      }
    },

    _unlockScrollInteractions: function() {
      for (var i = 0, l = scrollEvents.length; i < l; i++) {
        // NOTE: browsers that don't support objects as third arg will
        // interpret it as boolean, hence useCapture = true in this case.
        document.removeEventListener(
            scrollEvents[i],
            this._boundScrollHandler,
            {capture: true, passive: false});
      }
    },

    /**
     * Returns true if the event causes scroll outside the current locking
     * element, e.g. pointer/keyboard interactions, or scroll "leaking"
     * outside the locking element when it is already at its scroll boundaries.
     * @param {!Event} event
     * @return {boolean}
     * @private
     */
    _shouldPreventScrolling: function(event) {
      // Update if root target changed. For touch events, ensure we don't
      // update during touchmove.
      var target = Polymer.dom(event).rootTarget;
      if (event.type !== 'touchmove' && lastRootTarget !== target) {
        lastRootTarget = target;
        lastScrollableNodes = this._getScrollableNodes(Polymer.dom(event).path);
      }

      // Prevent event if no scrollable nodes.
      if (!lastScrollableNodes.length) {
        return true;
      }
      // Don't prevent touchstart event inside the locking element when it has
      // scrollable nodes.
      if (event.type === 'touchstart') {
        return false;
      }
      // Get deltaX/Y.
      var info = this._getScrollInfo(event);
      // Prevent if there is no child that can scroll.
      return !this._getScrollingNode(
          lastScrollableNodes, info.deltaX, info.deltaY);
    },

    /**
     * Returns an array of scrollable nodes up to the current locking element,
     * which is included too if scrollable.
     * @param {!Array<!Node>} nodes
     * @return {!Array<!Node>} scrollables
     * @private
     */
    _getScrollableNodes: function(nodes) {
      var scrollables = [];
      var lockingIndex = nodes.indexOf(this.currentLockingElement);
      // Loop from root target to locking element (included).
      for (var i = 0; i <= lockingIndex; i++) {
        // Skip non-Element nodes.
        if (nodes[i].nodeType !== Node.ELEMENT_NODE) {
          continue;
        }
        var node = /** @type {!Element} */ (nodes[i]);
        // Check inline style before checking computed style.
        var style = node.style;
        if (style.overflow !== 'scroll' && style.overflow !== 'auto') {
          style = window.getComputedStyle(node);
        }
        if (style.overflow === 'scroll' || style.overflow === 'auto') {
          scrollables.push(node);
        }
      }
      return scrollables;
    },

    /**
     * Returns the node that is scrolling. If there is no scrolling,
     * returns undefined.
     * @param {!Array<!Node>} nodes
     * @param {number} deltaX Scroll delta on the x-axis
     * @param {number} deltaY Scroll delta on the y-axis
     * @return {!Node|undefined}
     * @private
     */
    _getScrollingNode: function(nodes, deltaX, deltaY) {
      // No scroll.
      if (!deltaX && !deltaY) {
        return;
      }
      // Check only one axis according to where there is more scroll.
      // Prefer vertical to horizontal.
      var verticalScroll = Math.abs(deltaY) >= Math.abs(deltaX);
      for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        var canScroll = false;
        if (verticalScroll) {
          // delta < 0 is scroll up, delta > 0 is scroll down.
          canScroll = deltaY < 0 ?
              node.scrollTop > 0 :
              node.scrollTop < node.scrollHeight - node.clientHeight;
        } else {
          // delta < 0 is scroll left, delta > 0 is scroll right.
          canScroll = deltaX < 0 ?
              node.scrollLeft > 0 :
              node.scrollLeft < node.scrollWidth - node.clientWidth;
        }
        if (canScroll) {
          return node;
        }
      }
    },

    /**
     * Returns scroll `deltaX` and `deltaY`.
     * @param {!Event} event The scroll event
     * @return {{deltaX: number, deltaY: number}} Object containing the
     * x-axis scroll delta (positive: scroll right, negative: scroll left,
     * 0: no scroll), and the y-axis scroll delta (positive: scroll down,
     * negative: scroll up, 0: no scroll).
     * @private
     */
    _getScrollInfo: function(event) {
      var info = {deltaX: event.deltaX, deltaY: event.deltaY};
      // Already available.
      if ('deltaX' in event) {
        // do nothing, values are already good.
      }
      // Safari has scroll info in `wheelDeltaX/Y`.
      else if ('wheelDeltaX' in event && 'wheelDeltaY' in event) {
        info.deltaX = -event.wheelDeltaX;
        info.deltaY = -event.wheelDeltaY;
      }
      // IE10 has only vertical scroll info in `wheelDelta`.
      else if ('wheelDelta' in event) {
        info.deltaX = 0;
        info.deltaY = -event.wheelDelta;
      }
      // Firefox has scroll info in `detail` and `axis`.
      else if ('axis' in event) {
        info.deltaX = event.axis === 1 ? event.detail : 0;
        info.deltaY = event.axis === 2 ? event.detail : 0;
      }
      // On mobile devices, calculate scroll direction.
      else if (event.targetTouches) {
        var touch = event.targetTouches[0];
        // Touch moves from right to left => scrolling goes right.
        info.deltaX = lastTouchPosition.pageX - touch.pageX;
        // Touch moves from down to up => scrolling goes down.
        info.deltaY = lastTouchPosition.pageY - touch.pageY;
      }
      return info;
    }
  };
  })();
</script>

<script>
  (function() {
  'use strict';

  var p = Element.prototype;
  var matches = p.matches || p.matchesSelector || p.mozMatchesSelector ||
      p.msMatchesSelector || p.oMatchesSelector || p.webkitMatchesSelector;

  Polymer.IronFocusablesHelper = {

    /**
     * Returns a sorted array of tabbable nodes, including the root node.
     * It searches the tabbable nodes in the light and shadow dom of the chidren,
     * sorting the result by tabindex.
     * @param {!Node} node
     * @return {!Array<!HTMLElement>}
     */
    getTabbableNodes: function(node) {
      var result = [];
      // If there is at least one element with tabindex > 0, we need to sort
      // the final array by tabindex.
      var needsSortByTabIndex = this._collectTabbableNodes(node, result);
      if (needsSortByTabIndex) {
        return this._sortByTabIndex(result);
      }
      return result;
    },

    /**
     * Returns if a element is focusable.
     * @param {!HTMLElement} element
     * @return {boolean}
     */
    isFocusable: function(element) {
      // From http://stackoverflow.com/a/1600194/4228703:
      // There isn't a definite list, it's up to the browser. The only
      // standard we have is DOM Level 2 HTML
      // https://www.w3.org/TR/DOM-Level-2-HTML/html.html, according to which the
      // only elements that have a focus() method are HTMLInputElement,
      // HTMLSelectElement, HTMLTextAreaElement and HTMLAnchorElement. This
      // notably omits HTMLButtonElement and HTMLAreaElement. Referring to these
      // tests with tabbables in different browsers
      // http://allyjs.io/data-tables/focusable.html

      // Elements that cannot be focused if they have [disabled] attribute.
      if (matches.call(element, 'input, select, textarea, button, object')) {
        return matches.call(element, ':not([disabled])');
      }
      // Elements that can be focused even if they have [disabled] attribute.
      return matches.call(
          element, 'a[href], area[href], iframe, [tabindex], [contentEditable]');
    },

    /**
     * Returns if a element is tabbable. To be tabbable, a element must be
     * focusable, visible, and with a tabindex !== -1.
     * @param {!HTMLElement} element
     * @return {boolean}
     */
    isTabbable: function(element) {
      return this.isFocusable(element) &&
          matches.call(element, ':not([tabindex="-1"])') &&
          this._isVisible(element);
    },

    /**
     * Returns the normalized element tabindex. If not focusable, returns -1.
     * It checks for the attribute "tabindex" instead of the element property
     * `tabIndex` since browsers assign different values to it.
     * e.g. in Firefox `<div contenteditable>` has `tabIndex = -1`
     * @param {!HTMLElement} element
     * @return {!number}
     * @private
     */
    _normalizedTabIndex: function(element) {
      if (this.isFocusable(element)) {
        var tabIndex = element.getAttribute('tabindex') || 0;
        return Number(tabIndex);
      }
      return -1;
    },

    /**
     * Searches for nodes that are tabbable and adds them to the `result` array.
     * Returns if the `result` array needs to be sorted by tabindex.
     * @param {!Node} node The starting point for the search; added to `result`
     * if tabbable.
     * @param {!Array<!HTMLElement>} result
     * @return {boolean}
     * @private
     */
    _collectTabbableNodes: function(node, result) {
      // If not an element or not visible, no need to explore children.
      if (node.nodeType !== Node.ELEMENT_NODE || !this._isVisible(node)) {
        return false;
      }
      var element = /** @type {!HTMLElement} */ (node);
      var tabIndex = this._normalizedTabIndex(element);
      var needsSort = tabIndex > 0;
      if (tabIndex >= 0) {
        result.push(element);
      }

      // In ShadowDOM v1, tab order is affected by the order of distrubution.
      // E.g. getTabbableNodes(#root) in ShadowDOM v1 should return [#A, #B];
      // in ShadowDOM v0 tab order is not affected by the distrubution order,
      // in fact getTabbableNodes(#root) returns [#B, #A].
      //  <div id="root">
      //   <!-- shadow -->
      //     <slot name="a">
      //     <slot name="b">
      //   <!-- /shadow -->
      //   <input id="A" slot="a">
      //   <input id="B" slot="b" tabindex="1">
      //  </div>
      // TODO(valdrin) support ShadowDOM v1 when upgrading to Polymer v2.0.
      var children;
      if (element.localName === 'content' || element.localName === 'slot') {
        children = Polymer.dom(element).getDistributedNodes();
      } else {
        // Use shadow root if possible, will check for distributed nodes.
        children = Polymer.dom(element.root || element).children;
      }
      for (var i = 0; i < children.length; i++) {
        // Ensure method is always invoked to collect tabbable children.
        needsSort = this._collectTabbableNodes(children[i], result) || needsSort;
      }
      return needsSort;
    },

    /**
     * Returns false if the element has `visibility: hidden` or `display: none`
     * @param {!HTMLElement} element
     * @return {boolean}
     * @private
     */
    _isVisible: function(element) {
      // Check inline style first to save a re-flow. If looks good, check also
      // computed style.
      var style = element.style;
      if (style.visibility !== 'hidden' && style.display !== 'none') {
        style = window.getComputedStyle(element);
        return (style.visibility !== 'hidden' && style.display !== 'none');
      }
      return false;
    },

    /**
     * Sorts an array of tabbable elements by tabindex. Returns a new array.
     * @param {!Array<!HTMLElement>} tabbables
     * @return {!Array<!HTMLElement>}
     * @private
     */
    _sortByTabIndex: function(tabbables) {
      // Implement a merge sort as Array.prototype.sort does a non-stable sort
      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
      var len = tabbables.length;
      if (len < 2) {
        return tabbables;
      }
      var pivot = Math.ceil(len / 2);
      var left = this._sortByTabIndex(tabbables.slice(0, pivot));
      var right = this._sortByTabIndex(tabbables.slice(pivot));
      return this._mergeSortByTabIndex(left, right);
    },

    /**
     * Merge sort iterator, merges the two arrays into one, sorted by tab index.
     * @param {!Array<!HTMLElement>} left
     * @param {!Array<!HTMLElement>} right
     * @return {!Array<!HTMLElement>}
     * @private
     */
    _mergeSortByTabIndex: function(left, right) {
      var result = [];
      while ((left.length > 0) && (right.length > 0)) {
        if (this._hasLowerTabOrder(left[0], right[0])) {
          result.push(right.shift());
        } else {
          result.push(left.shift());
        }
      }

      return result.concat(left, right);
    },

    /**
     * Returns if element `a` has lower tab order compared to element `b`
     * (both elements are assumed to be focusable and tabbable).
     * Elements with tabindex = 0 have lower tab order compared to elements
     * with tabindex > 0.
     * If both have same tabindex, it returns false.
     * @param {!HTMLElement} a
     * @param {!HTMLElement} b
     * @return {boolean}
     * @private
     */
    _hasLowerTabOrder: function(a, b) {
      // Normalize tabIndexes
      // e.g. in Firefox `<div contenteditable>` has `tabIndex = -1`
      var ati = Math.max(a.tabIndex, 0);
      var bti = Math.max(b.tabIndex, 0);
      return (ati === 0 || bti === 0) ? bti > ati : ati > bti;
    }
  };
  })();
</script>
<script>
  (function() {
  'use strict';

  /** @polymerBehavior */
  Polymer.IronOverlayBehaviorImpl = {

    properties: {

      /**
       * True if the overlay is currently displayed.
       */
      opened:
          {observer: '_openedChanged', type: Boolean, value: false, notify: true},

      /**
       * True if the overlay was canceled when it was last closed.
       */
      canceled: {
        observer: '_canceledChanged',
        readOnly: true,
        type: Boolean,
        value: false
      },

      /**
       * Set to true to display a backdrop behind the overlay. It traps the focus
       * within the light DOM of the overlay.
       */
      withBackdrop: {observer: '_withBackdropChanged', type: Boolean},

      /**
       * Set to true to disable auto-focusing the overlay or child nodes with
       * the `autofocus` attribute` when the overlay is opened.
       */
      noAutoFocus: {type: Boolean, value: false},

      /**
       * Set to true to disable canceling the overlay with the ESC key.
       */
      noCancelOnEscKey: {type: Boolean, value: false},

      /**
       * Set to true to disable canceling the overlay by clicking outside it.
       */
      noCancelOnOutsideClick: {type: Boolean, value: false},

      /**
       * Contains the reason(s) this overlay was last closed (see
       * `iron-overlay-closed`). `IronOverlayBehavior` provides the `canceled`
       * reason; implementers of the behavior can provide other reasons in
       * addition to `canceled`.
       */
      closingReason: {
        // was a getter before, but needs to be a property so other
        // behaviors can override this.
        type: Object
      },

      /**
       * Set to true to enable restoring of focus when overlay is closed.
       */
      restoreFocusOnClose: {type: Boolean, value: false},

      /**
       * Set to true to allow clicks to go through overlays.
       * When the user clicks outside this overlay, the click may
       * close the overlay below.
       */
      allowClickThrough: {type: Boolean},

      /**
       * Set to true to keep overlay always on top.
       */
      alwaysOnTop: {type: Boolean},

      /**
       * Determines which action to perform when scroll outside an opened overlay
       * happens. Possible values: lock - blocks scrolling from happening, refit -
       * computes the new position on the overlay cancel - causes the overlay to
       * close
       */
      scrollAction: {type: String},

      /**
       * Shortcut to access to the overlay manager.
       * @private
       * @type {!Polymer.IronOverlayManagerClass}
       */
      _manager: {type: Object, value: Polymer.IronOverlayManager},

      /**
       * The node being focused.
       * @type {?Node}
       */
      _focusedChild: {type: Object}

    },

    listeners: {'iron-resize': '_onIronResize'},

    observers: ['__updateScrollObservers(isAttached, opened, scrollAction)'],

    /**
     * The backdrop element.
     * @return {!Element}
     */
    get backdropElement() {
      return this._manager.backdropElement;
    },

    /**
     * Returns the node to give focus to.
     * @return {!Node}
     */
    get _focusNode() {
      return this._focusedChild ||
          Polymer.dom(this).querySelector('[autofocus]') || this;
    },

    /**
     * Array of nodes that can receive focus (overlay included), ordered by
     * `tabindex`. This is used to retrieve which is the first and last focusable
     * nodes in order to wrap the focus for overlays `with-backdrop`.
     *
     * If you know what is your content (specifically the first and last focusable
     * children), you can override this method to return only `[firstFocusable,
     * lastFocusable];`
     * @return {!Array<!Node>}
     * @protected
     */
    get _focusableNodes() {
      return Polymer.IronFocusablesHelper.getTabbableNodes(this);
    },

    /**
     * @return {void}
     */
    ready: function() {
      // Used to skip calls to notifyResize and refit while the overlay is
      // animating.
      this.__isAnimating = false;
      // with-backdrop needs tabindex to be set in order to trap the focus.
      // If it is not set, IronOverlayBehavior will set it, and remove it if
      // with-backdrop = false.
      this.__shouldRemoveTabIndex = false;
      // Used for wrapping the focus on TAB / Shift+TAB.
      this.__firstFocusableNode = this.__lastFocusableNode = null;
      // Used by to keep track of the RAF callbacks.
      this.__rafs = {};
      // Focused node before overlay gets opened. Can be restored on close.
      this.__restoreFocusNode = null;
      // Scroll info to be restored.
      this.__scrollTop = this.__scrollLeft = null;
      this.__onCaptureScroll = this.__onCaptureScroll.bind(this);
      // Root nodes hosting the overlay, used to listen for scroll events on them.
      this.__rootNodes = null;
      this._ensureSetup();
    },

    attached: function() {
      // Call _openedChanged here so that position can be computed correctly.
      if (this.opened) {
        this._openedChanged(this.opened);
      }
      this._observer = Polymer.dom(this).observeNodes(this._onNodesChange);
    },

    detached: function() {
      Polymer.dom(this).unobserveNodes(this._observer);
      this._observer = null;
      for (var cb in this.__rafs) {
        if (this.__rafs[cb] !== null) {
          cancelAnimationFrame(this.__rafs[cb]);
        }
      }
      this.__rafs = {};
      this._manager.removeOverlay(this);

      // We got detached while animating, ensure we show/hide the overlay
      // and fire iron-overlay-opened/closed event!
      if (this.__isAnimating) {
        if (this.opened) {
          this._finishRenderOpened();
        } else {
          // Restore the focus if necessary.
          this._applyFocus();
          this._finishRenderClosed();
        }
      }
    },

    /**
     * Toggle the opened state of the overlay.
     */
    toggle: function() {
      this._setCanceled(false);
      this.opened = !this.opened;
    },

    /**
     * Open the overlay.
     */
    open: function() {
      this._setCanceled(false);
      this.opened = true;
    },

    /**
     * Close the overlay.
     */
    close: function() {
      this._setCanceled(false);
      this.opened = false;
    },

    /**
     * Cancels the overlay.
     * @param {Event=} event The original event
     */
    cancel: function(event) {
      var cancelEvent =
          this.fire('iron-overlay-canceled', event, {cancelable: true});
      if (cancelEvent.defaultPrevented) {
        return;
      }

      this._setCanceled(true);
      this.opened = false;
    },

    /**
     * Invalidates the cached tabbable nodes. To be called when any of the
     * focusable content changes (e.g. a button is disabled).
     */
    invalidateTabbables: function() {
      this.__firstFocusableNode = this.__lastFocusableNode = null;
    },

    _ensureSetup: function() {
      if (this._overlaySetup) {
        return;
      }
      this._overlaySetup = true;
      this.style.outline = 'none';
      this.style.display = 'none';
    },

    /**
     * Called when `opened` changes.
     * @param {boolean=} opened
     * @protected
     */
    _openedChanged: function(opened) {
      if (opened) {
        this.removeAttribute('aria-hidden');
      } else {
        this.setAttribute('aria-hidden', 'true');
      }

      // Defer any animation-related code on attached
      // (_openedChanged gets called again on attached).
      if (!this.isAttached) {
        return;
      }

      this.__isAnimating = true;

      // Deraf for non-blocking rendering.
      this.__deraf('__openedChanged', this.__openedChanged);
    },

    _canceledChanged: function() {
      this.closingReason = this.closingReason || {};
      this.closingReason.canceled = this.canceled;
    },

    _withBackdropChanged: function() {
      // If tabindex is already set, no need to override it.
      if (this.withBackdrop && !this.hasAttribute('tabindex')) {
        this.setAttribute('tabindex', '-1');
        this.__shouldRemoveTabIndex = true;
      } else if (this.__shouldRemoveTabIndex) {
        this.removeAttribute('tabindex');
        this.__shouldRemoveTabIndex = false;
      }
      if (this.opened && this.isAttached) {
        this._manager.trackBackdrop();
      }
    },

    /**
     * tasks which must occur before opening; e.g. making the element visible.
     * @protected
     */
    _prepareRenderOpened: function() {
      // Store focused node.
      this.__restoreFocusNode = this._manager.deepActiveElement;

      // Needed to calculate the size of the overlay so that transitions on its
      // size will have the correct starting points.
      this._preparePositioning();
      this.refit();
      this._finishPositioning();

      // Safari will apply the focus to the autofocus element when displayed
      // for the first time, so we make sure to return the focus where it was.
      if (this.noAutoFocus && document.activeElement === this._focusNode) {
        this._focusNode.blur();
        this.__restoreFocusNode.focus();
      }
    },

    /**
     * Tasks which cause the overlay to actually open; typically play an
     * animation.
     * @protected
     */
    _renderOpened: function() {
      this._finishRenderOpened();
    },

    /**
     * Tasks which cause the overlay to actually close; typically play an
     * animation.
     * @protected
     */
    _renderClosed: function() {
      this._finishRenderClosed();
    },

    /**
     * Tasks to be performed at the end of open action. Will fire
     * `iron-overlay-opened`.
     * @protected
     */
    _finishRenderOpened: function() {
      this.notifyResize();
      this.__isAnimating = false;

      this.fire('iron-overlay-opened');
    },

    /**
     * Tasks to be performed at the end of close action. Will fire
     * `iron-overlay-closed`.
     * @protected
     */
    _finishRenderClosed: function() {
      // Hide the overlay.
      this.style.display = 'none';
      // Reset z-index only at the end of the animation.
      this.style.zIndex = '';
      this.notifyResize();
      this.__isAnimating = false;
      this.fire('iron-overlay-closed', this.closingReason);
    },

    _preparePositioning: function() {
      this.style.transition = this.style.webkitTransition = 'none';
      this.style.transform = this.style.webkitTransform = 'none';
      this.style.display = '';
    },

    _finishPositioning: function() {
      // First, make it invisible & reactivate animations.
      this.style.display = 'none';
      // Force reflow before re-enabling animations so that they don't start.
      // Set scrollTop to itself so that Closure Compiler doesn't remove this.
      this.scrollTop = this.scrollTop;
      this.style.transition = this.style.webkitTransition = '';
      this.style.transform = this.style.webkitTransform = '';
      // Now that animations are enabled, make it visible again
      this.style.display = '';
      // Force reflow, so that following animations are properly started.
      // Set scrollTop to itself so that Closure Compiler doesn't remove this.
      this.scrollTop = this.scrollTop;
    },

    /**
     * Applies focus according to the opened state.
     * @protected
     */
    _applyFocus: function() {
      if (this.opened) {
        if (!this.noAutoFocus) {
          this._focusNode.focus();
        }
      } else {
        // Restore focus.
        if (this.restoreFocusOnClose && this.__restoreFocusNode) {
          // If the activeElement is `<body>` or inside the overlay,
          // we are allowed to restore the focus. In all the other
          // cases focus might have been moved elsewhere by another
          // component or by an user interaction (e.g. click on a
          // button outside the overlay).
          var activeElement = this._manager.deepActiveElement;
          if (activeElement === document.body ||
              Polymer.dom(this).deepContains(activeElement)) {
            this.__restoreFocusNode.focus();
          }
        }
        this.__restoreFocusNode = null;
        this._focusNode.blur();
        this._focusedChild = null;
      }
    },

    /**
     * Cancels (closes) the overlay. Call when click happens outside the overlay.
     * @param {!Event} event
     * @protected
     */
    _onCaptureClick: function(event) {
      if (!this.noCancelOnOutsideClick) {
        this.cancel(event);
      }
    },

    /**
     * Keeps track of the focused child. If withBackdrop, traps focus within
     * overlay.
     * @param {!Event} event
     * @protected
     */
    _onCaptureFocus: function(event) {
      if (!this.withBackdrop) {
        return;
      }
      var path = Polymer.dom(event).path;
      if (path.indexOf(this) === -1) {
        event.stopPropagation();
        this._applyFocus();
      } else {
        this._focusedChild = path[0];
      }
    },

    /**
     * Handles the ESC key event and cancels (closes) the overlay.
     * @param {!Event} event
     * @protected
     */
    _onCaptureEsc: function(event) {
      if (!this.noCancelOnEscKey) {
        this.cancel(event);
      }
    },

    /**
     * Handles TAB key events to track focus changes.
     * Will wrap focus for overlays withBackdrop.
     * @param {!Event} event
     * @protected
     */
    _onCaptureTab: function(event) {
      if (!this.withBackdrop) {
        return;
      }
      this.__ensureFirstLastFocusables();
      // TAB wraps from last to first focusable.
      // Shift + TAB wraps from first to last focusable.
      var shift = event.shiftKey;
      var nodeToCheck =
          shift ? this.__firstFocusableNode : this.__lastFocusableNode;
      var nodeToSet =
          shift ? this.__lastFocusableNode : this.__firstFocusableNode;
      var shouldWrap = false;
      if (nodeToCheck === nodeToSet) {
        // If nodeToCheck is the same as nodeToSet, it means we have an overlay
        // with 0 or 1 focusables; in either case we still need to trap the
        // focus within the overlay.
        shouldWrap = true;
      } else {
        // In dom=shadow, the manager will receive focus changes on the main
        // root but not the ones within other shadow roots, so we can't rely on
        // _focusedChild, but we should check the deepest active element.
        var focusedNode = this._manager.deepActiveElement;
        // If the active element is not the nodeToCheck but the overlay itself,
        // it means the focus is about to go outside the overlay, hence we
        // should prevent that (e.g. user opens the overlay and hit Shift+TAB).
        shouldWrap = (focusedNode === nodeToCheck || focusedNode === this);
      }

      if (shouldWrap) {
        // When the overlay contains the last focusable element of the document
        // and it's already focused, pressing TAB would move the focus outside
        // the document (e.g. to the browser search bar). Similarly, when the
        // overlay contains the first focusable element of the document and it's
        // already focused, pressing Shift+TAB would move the focus outside the
        // document (e.g. to the browser search bar).
        // In both cases, we would not receive a focus event, but only a blur.
        // In order to achieve focus wrapping, we prevent this TAB event and
        // force the focus. This will also prevent the focus to temporarily move
        // outside the overlay, which might cause scrolling.
        event.preventDefault();
        this._focusedChild = nodeToSet;
        this._applyFocus();
      }
    },

    /**
     * Refits if the overlay is opened and not animating.
     * @protected
     */
    _onIronResize: function() {
      if (this.opened && !this.__isAnimating) {
        this.__deraf('refit', this.refit);
      }
    },

    /**
     * Will call notifyResize if overlay is opened.
     * Can be overridden in order to avoid multiple observers on the same node.
     * @protected
     */
    _onNodesChange: function() {
      if (this.opened && !this.__isAnimating) {
        // It might have added focusable nodes, so invalidate cached values.
        this.invalidateTabbables();
        this.notifyResize();
      }
    },

    /**
     * Will set first and last focusable nodes if any of them is not set.
     * @private
     */
    __ensureFirstLastFocusables: function() {
      if (!this.__firstFocusableNode || !this.__lastFocusableNode) {
        var focusableNodes = this._focusableNodes;
        this.__firstFocusableNode = focusableNodes[0];
        this.__lastFocusableNode = focusableNodes[focusableNodes.length - 1];
      }
    },

    /**
     * Tasks executed when opened changes: prepare for the opening, move the
     * focus, update the manager, render opened/closed.
     * @private
     */
    __openedChanged: function() {
      if (this.opened) {
        // Make overlay visible, then add it to the manager.
        this._prepareRenderOpened();
        this._manager.addOverlay(this);
        // Move the focus to the child node with [autofocus].
        this._applyFocus();

        this._renderOpened();
      } else {
        // Remove overlay, then restore the focus before actually closing.
        this._manager.removeOverlay(this);
        this._applyFocus();

        this._renderClosed();
      }
    },

    /**
     * Debounces the execution of a callback to the next animation frame.
     * @param {!string} jobname
     * @param {!Function} callback Always bound to `this`
     * @private
     */
    __deraf: function(jobname, callback) {
      var rafs = this.__rafs;
      if (rafs[jobname] !== null) {
        cancelAnimationFrame(rafs[jobname]);
      }
      rafs[jobname] = requestAnimationFrame(function nextAnimationFrame() {
        rafs[jobname] = null;
        callback.call(this);
      }.bind(this));
    },

    /**
     * @param {boolean} isAttached
     * @param {boolean} opened
     * @param {string=} scrollAction
     * @private
     */
    __updateScrollObservers: function(isAttached, opened, scrollAction) {
      if (!isAttached || !opened || !this.__isValidScrollAction(scrollAction)) {
        Polymer.IronScrollManager.removeScrollLock(this);
        this.__removeScrollListeners();
      } else {
        if (scrollAction === 'lock') {
          this.__saveScrollPosition();
          Polymer.IronScrollManager.pushScrollLock(this);
        }
        this.__addScrollListeners();
      }
    },

    /**
     * @private
     */
    __addScrollListeners: function() {
      if (!this.__rootNodes) {
        this.__rootNodes = [];
        // Listen for scroll events in all shadowRoots hosting this overlay only
        // when in native ShadowDOM.
        if (Polymer.Settings.useShadow) {
          var node = this;
          while (node) {
            if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && node.host) {
              this.__rootNodes.push(node);
            }
            node = node.host || node.assignedSlot || node.parentNode;
          }
        }
        this.__rootNodes.push(document);
      }
      this.__rootNodes.forEach(function(el) {
        el.addEventListener('scroll', this.__onCaptureScroll, {
          capture: true,
          passive: true,
        });
      }, this);
    },

    /**
     * @private
     */
    __removeScrollListeners: function() {
      if (this.__rootNodes) {
        this.__rootNodes.forEach(function(el) {
          el.removeEventListener('scroll', this.__onCaptureScroll, {
            capture: true,
            passive: true,
          });
        }, this);
      }
      if (!this.isAttached) {
        this.__rootNodes = null;
      }
    },

    /**
     * @param {string=} scrollAction
     * @return {boolean}
     * @private
     */
    __isValidScrollAction: function(scrollAction) {
      return scrollAction === 'lock' || scrollAction === 'refit' ||
          scrollAction === 'cancel';
    },

    /**
     * @private
     */
    __onCaptureScroll: function(event) {
      if (this.__isAnimating) {
        return;
      }
      // Check if scroll outside the overlay.
      if (Polymer.dom(event).path.indexOf(this) >= 0) {
        return;
      }
      switch (this.scrollAction) {
        case 'lock':
          // NOTE: scrolling might happen if a scroll event is not cancellable, or
          // if user pressed keys that cause scrolling (they're not prevented in
          // order not to break a11y features like navigate with arrow keys).
          this.__restoreScrollPosition();
          break;
        case 'refit':
          this.__deraf('refit', this.refit);
          break;
        case 'cancel':
          this.cancel(event);
          break;
      }
    },

    /**
     * Memoizes the scroll position of the outside scrolling element.
     * @private
     */
    __saveScrollPosition: function() {
      if (document.scrollingElement) {
        this.__scrollTop = document.scrollingElement.scrollTop;
        this.__scrollLeft = document.scrollingElement.scrollLeft;
      } else {
        // Since we don't know if is the body or html, get max.
        this.__scrollTop =
            Math.max(document.documentElement.scrollTop, document.body.scrollTop);
        this.__scrollLeft = Math.max(
            document.documentElement.scrollLeft, document.body.scrollLeft);
      }
    },

    /**
     * Resets the scroll position of the outside scrolling element.
     * @private
     */
    __restoreScrollPosition: function() {
      if (document.scrollingElement) {
        document.scrollingElement.scrollTop = this.__scrollTop;
        document.scrollingElement.scrollLeft = this.__scrollLeft;
      } else {
        // Since we don't know if is the body or html, set both.
        document.documentElement.scrollTop = document.body.scrollTop =
            this.__scrollTop;
        document.documentElement.scrollLeft = document.body.scrollLeft =
            this.__scrollLeft;
      }
    },

  };

  /**
  Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or
  shown, and displays on top of other content. It includes an optional backdrop,
  and can be used to implement a variety of UI controls including dialogs and drop
  downs. Multiple overlays may be displayed at once.

  See the [demo source
  code](https://github.com/PolymerElements/iron-overlay-behavior/blob/master/demo/simple-overlay.html)
  for an example.

  ### Closing and canceling

  An overlay may be hidden by closing or canceling. The difference between close
  and cancel is user intent. Closing generally implies that the user acknowledged
  the content on the overlay. By default, it will cancel whenever the user taps
  outside it or presses the escape key. This behavior is configurable with the
  `no-cancel-on-esc-key` and the `no-cancel-on-outside-click` properties.
  `close()` should be called explicitly by the implementer when the user interacts
  with a control in the overlay element. When the dialog is canceled, the overlay
  fires an 'iron-overlay-canceled' event. Call `preventDefault` on this event to
  prevent the overlay from closing.

  ### Positioning

  By default the element is sized and positioned to fit and centered inside the
  window. You can position and size it manually using CSS. See
  `Polymer.IronFitBehavior`.

  ### Backdrop

  Set the `with-backdrop` attribute to display a backdrop behind the overlay. The
  backdrop is appended to `<body>` and is of type `<iron-overlay-backdrop>`. See
  its doc page for styling options.

  In addition, `with-backdrop` will wrap the focus within the content in the light
  DOM. Override the [`_focusableNodes`
  getter](#Polymer.IronOverlayBehavior:property-_focusableNodes) to achieve a
  different behavior.

  ### Limitations

  The element is styled to appear on top of other content by setting its `z-index`
  property. You must ensure no element has a stacking context with a higher
  `z-index` than its parent stacking context. You should place this element as a
  child of `<body>` whenever possible.

  @demo demo/index.html
  @polymerBehavior
  */
  Polymer.IronOverlayBehavior = [
    Polymer.IronFitBehavior,
    Polymer.IronResizableBehavior,
    Polymer.IronOverlayBehaviorImpl
  ];

  /**
   * Fired after the overlay opens.
   * @event iron-overlay-opened
   */

  /**
   * Fired when the overlay is canceled, but before it is closed.
   * @event iron-overlay-canceled
   * @param {Event} event The closing of the overlay can be prevented
   * by calling `event.preventDefault()`. The `event.detail` is the original event
   * that originated the canceling (e.g. ESC keyboard event or click event outside
   * the overlay).
   */

  /**
   * Fired after the overlay closes.
   * @event iron-overlay-closed
   * @param {Event} event The `event.detail` is the `closingReason` property
   * (contains `canceled`, whether the overlay was canceled).
   */
  })();
</script>
<script>
  (function() {
  'use strict';

  /**
  Use `Polymer.PaperDialogBehavior` and `paper-dialog-shared-styles.html` to
  implement a Material Design dialog.

  For example, if `<paper-dialog-impl>` implements this behavior:

      <paper-dialog-impl>
          <h2>Header</h2>
          <div>Dialog body</div>
          <div class="buttons">
              <paper-button dialog-dismiss>Cancel</paper-button>
              <paper-button dialog-confirm>Accept</paper-button>
          </div>
      </paper-dialog-impl>

  `paper-dialog-shared-styles.html` provide styles for a header, content area, and
  an action area for buttons. Use the `<h2>` tag for the header and the `buttons`
  class for the action area. You can use the `paper-dialog-scrollable` element (in
  its own repository) if you need a scrolling content area.

  Use the `dialog-dismiss` and `dialog-confirm` attributes on interactive controls
  to close the dialog. If the user dismisses the dialog with `dialog-confirm`, the
  `closingReason` will update to include `confirmed: true`.

  ### Accessibility

  This element has `role="dialog"` by default. Depending on the context, it may be
  more appropriate to override this attribute with `role="alertdialog"`.

  If `modal` is set, the element will prevent the focus from exiting the element.
  It will also ensure that focus remains in the dialog.

  @hero hero.svg
  @demo demo/index.html
  @polymerBehavior Polymer.PaperDialogBehavior
  */
  Polymer.PaperDialogBehaviorImpl = {

    hostAttributes: {'role': 'dialog', 'tabindex': '-1'},

    properties: {

      /**
       * If `modal` is true, this implies `no-cancel-on-outside-click`,
       * `no-cancel-on-esc-key` and `with-backdrop`.
       */
      modal: {type: Boolean, value: false},

      __readied: {type: Boolean, value: false}

    },

    observers: ['_modalChanged(modal, __readied)'],

    listeners: {'tap': '_onDialogClick'},

    /**
     * @return {void}
     */
    ready: function() {
      // Only now these properties can be read.
      this.__prevNoCancelOnOutsideClick = this.noCancelOnOutsideClick;
      this.__prevNoCancelOnEscKey = this.noCancelOnEscKey;
      this.__prevWithBackdrop = this.withBackdrop;
      this.__readied = true;
    },

    _modalChanged: function(modal, readied) {
      // modal implies noCancelOnOutsideClick, noCancelOnEscKey and withBackdrop.
      // We need to wait for the element to be ready before we can read the
      // properties values.
      if (!readied) {
        return;
      }

      if (modal) {
        this.__prevNoCancelOnOutsideClick = this.noCancelOnOutsideClick;
        this.__prevNoCancelOnEscKey = this.noCancelOnEscKey;
        this.__prevWithBackdrop = this.withBackdrop;
        this.noCancelOnOutsideClick = true;
        this.noCancelOnEscKey = true;
        this.withBackdrop = true;
      } else {
        // If the value was changed to false, let it false.
        this.noCancelOnOutsideClick =
            this.noCancelOnOutsideClick && this.__prevNoCancelOnOutsideClick;
        this.noCancelOnEscKey =
            this.noCancelOnEscKey && this.__prevNoCancelOnEscKey;
        this.withBackdrop = this.withBackdrop && this.__prevWithBackdrop;
      }
    },

    _updateClosingReasonConfirmed: function(confirmed) {
      this.closingReason = this.closingReason || {};
      this.closingReason.confirmed = confirmed;
    },

    /**
     * Will dismiss the dialog if user clicked on an element with dialog-dismiss
     * or dialog-confirm attribute.
     */
    _onDialogClick: function(event) {
      // Search for the element with dialog-confirm or dialog-dismiss,
      // from the root target until this (excluded).
      var path = Polymer.dom(event).path;
      for (var i = 0, l = path.indexOf(this); i < l; i++) {
        var target = path[i];
        if (target.hasAttribute &&
            (target.hasAttribute('dialog-dismiss') ||
             target.hasAttribute('dialog-confirm'))) {
          this._updateClosingReasonConfirmed(
              target.hasAttribute('dialog-confirm'));
          this.close();
          event.stopPropagation();
          break;
        }
      }
    }

  };

  /** @polymerBehavior */
  Polymer.PaperDialogBehavior =
      [Polymer.IronOverlayBehavior, Polymer.PaperDialogBehaviorImpl];
  })();
</script>
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Roboto+Mono:400,700|Roboto:400,300,300italic,400italic,500,500italic,700,700italic" crossorigin="anonymous">
<custom-style>
  <style is="custom-style">
    html {

      /* Shared Styles */
      --paper-font-common-base: {
        font-family: 'Roboto', 'Noto', sans-serif;
        -webkit-font-smoothing: antialiased;
      };

      --paper-font-common-code: {
        font-family: 'Roboto Mono', 'Consolas', 'Menlo', monospace;
        -webkit-font-smoothing: antialiased;
      };

      --paper-font-common-expensive-kerning: {
        text-rendering: optimizeLegibility;
      };

      --paper-font-common-nowrap: {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      };

      /* Material Font Styles */

      --paper-font-display4: {
        @apply --paper-font-common-base;
        @apply --paper-font-common-nowrap;

        font-size: 112px;
        font-weight: 300;
        letter-spacing: -.044em;
        line-height: 120px;
      };

      --paper-font-display3: {
        @apply --paper-font-common-base;
        @apply --paper-font-common-nowrap;

        font-size: 56px;
        font-weight: 400;
        letter-spacing: -.026em;
        line-height: 60px;
      };

      --paper-font-display2: {
        @apply --paper-font-common-base;

        font-size: 45px;
        font-weight: 400;
        letter-spacing: -.018em;
        line-height: 48px;
      };

      --paper-font-display1: {
        @apply --paper-font-common-base;

        font-size: 34px;
        font-weight: 400;
        letter-spacing: -.01em;
        line-height: 40px;
      };

      --paper-font-headline: {
        @apply --paper-font-common-base;

        font-size: 24px;
        font-weight: 400;
        letter-spacing: -.012em;
        line-height: 32px;
      };

      --paper-font-title: {
        @apply --paper-font-common-base;
        @apply --paper-font-common-nowrap;

        font-size: 20px;
        font-weight: 500;
        line-height: 28px;
      };

      --paper-font-subhead: {
        @apply --paper-font-common-base;

        font-size: 16px;
        font-weight: 400;
        line-height: 24px;
      };

      --paper-font-body2: {
        @apply --paper-font-common-base;

        font-size: 14px;
        font-weight: 500;
        line-height: 24px;
      };

      --paper-font-body1: {
        @apply --paper-font-common-base;

        font-size: 14px;
        font-weight: 400;
        line-height: 20px;
      };

      --paper-font-caption: {
        @apply --paper-font-common-base;
        @apply --paper-font-common-nowrap;

        font-size: 12px;
        font-weight: 400;
        letter-spacing: 0.011em;
        line-height: 20px;
      };

      --paper-font-menu: {
        @apply --paper-font-common-base;
        @apply --paper-font-common-nowrap;

        font-size: 13px;
        font-weight: 500;
        line-height: 24px;
      };

      --paper-font-button: {
        @apply --paper-font-common-base;
        @apply --paper-font-common-nowrap;

        font-size: 14px;
        font-weight: 500;
        letter-spacing: 0.018em;
        line-height: 24px;
        text-transform: uppercase;
      };

      --paper-font-code2: {
        @apply --paper-font-common-code;

        font-size: 14px;
        font-weight: 700;
        line-height: 20px;
      };

      --paper-font-code1: {
        @apply --paper-font-common-code;

        font-size: 14px;
        font-weight: 500;
        line-height: 20px;
      };

    }

  </style>
</custom-style>
<custom-style>
  <style is="custom-style">
    html {

      --shadow-transition: {
        transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1);
      };

      --shadow-none: {
        box-shadow: none;
      };

      /* from http://codepen.io/shyndman/pen/c5394ddf2e8b2a5c9185904b57421cdb */

      --shadow-elevation-2dp: {
        box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14),
                    0 1px 5px 0 rgba(0, 0, 0, 0.12),
                    0 3px 1px -2px rgba(0, 0, 0, 0.2);
      };

      --shadow-elevation-3dp: {
        box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.14),
                    0 1px 8px 0 rgba(0, 0, 0, 0.12),
                    0 3px 3px -2px rgba(0, 0, 0, 0.4);
      };

      --shadow-elevation-4dp: {
        box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14),
                    0 1px 10px 0 rgba(0, 0, 0, 0.12),
                    0 2px 4px -1px rgba(0, 0, 0, 0.4);
      };

      --shadow-elevation-6dp: {
        box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14),
                    0 1px 18px 0 rgba(0, 0, 0, 0.12),
                    0 3px 5px -1px rgba(0, 0, 0, 0.4);
      };

      --shadow-elevation-8dp: {
        box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14),
                    0 3px 14px 2px rgba(0, 0, 0, 0.12),
                    0 5px 5px -3px rgba(0, 0, 0, 0.4);
      };

      --shadow-elevation-12dp: {
        box-shadow: 0 12px 16px 1px rgba(0, 0, 0, 0.14),
                    0 4px 22px 3px rgba(0, 0, 0, 0.12),
                    0 6px 7px -4px rgba(0, 0, 0, 0.4);
      };

      --shadow-elevation-16dp: {
        box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14),
                    0  6px 30px 5px rgba(0, 0, 0, 0.12),
                    0  8px 10px -5px rgba(0, 0, 0, 0.4);
      };

      --shadow-elevation-24dp: {
        box-shadow: 0 24px 38px 3px rgba(0, 0, 0, 0.14),
                    0 9px 46px 8px rgba(0, 0, 0, 0.12),
                    0 11px 15px -7px rgba(0, 0, 0, 0.4);
      };
    }
  </style>
</custom-style>
<dom-module id="paper-dialog-shared-styles" assetpath="bower_components/paper-dialog-behavior/">
  <template>
    <style>
      :host {
        display: block;
        margin: 24px 40px;

        background: var(--paper-dialog-background-color, var(--primary-background-color));
        color: var(--paper-dialog-color, var(--primary-text-color));

        @apply --paper-font-body1;
        @apply --shadow-elevation-16dp;
        @apply --paper-dialog;
      }

      :host > ::slotted(*) {
        margin-top: 20px;
        padding: 0 24px;
      }

      :host > ::slotted(.no-padding) {
        padding: 0;
      }

      
      :host > ::slotted(*:first-child) {
        margin-top: 24px;
      }

      :host > ::slotted(*:last-child) {
        margin-bottom: 24px;
      }

      /* In 1.x, this selector was `:host > ::content h2`. In 2.x <slot> allows
      to select direct children only, which increases the weight of this
      selector, so we have to re-define first-child/last-child margins below. */
      :host > ::slotted(h2) {
        position: relative;
        margin: 0;

        @apply --paper-font-title;
        @apply --paper-dialog-title;
      }

      /* Apply mixin again, in case it sets margin-top. */
      :host > ::slotted(h2:first-child) {
        margin-top: 24px;
        @apply --paper-dialog-title;
      }

      /* Apply mixin again, in case it sets margin-bottom. */
      :host > ::slotted(h2:last-child) {
        margin-bottom: 24px;
        @apply --paper-dialog-title;
      }

      :host > ::slotted(.paper-dialog-buttons),
      :host > ::slotted(.buttons) {
        position: relative;
        padding: 8px 8px 8px 24px;
        margin: 0;

        color: var(--paper-dialog-button-color, var(--primary-color));

        @apply --layout-horizontal;
        @apply --layout-end-justified;
      }
    </style>
  </template>
</dom-module>
<dom-module id="paper-dialog" assetpath="bower_components/paper-dialog/">
  <template>
    <style include="paper-dialog-shared-styles"></style>
    <slot></slot>
  </template>
</dom-module>

<script>
Polymer({
  is: 'paper-dialog',

  behaviors: [
    Polymer.PaperDialogBehavior,
    Polymer.NeonAnimationRunnerBehavior
  ],

  listeners: {
    'neon-animation-finish': '_onNeonAnimationFinish'
  },

  _renderOpened: function() {
    this.cancelAnimation();
    this.playAnimation('entry');
  },

  _renderClosed: function() {
    this.cancelAnimation();
    this.playAnimation('exit');
  },

  _onNeonAnimationFinish: function() {
    if (this.opened) {
      this._finishRenderOpened();
    } else {
      this._finishRenderClosed();
    }
  }
});
</script>
<dom-module id="paper-dialog-scrollable" assetpath="bower_components/paper-dialog-scrollable/">

  <template>
    <style>

      :host {
        display: block;
        @apply --layout-relative;
      }

      :host(.is-scrolled:not(:first-child))::before {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        height: 1px;
        background: var(--divider-color);
      }

      :host(.can-scroll:not(.scrolled-to-bottom):not(:last-child))::after {
        content: '';
        position: absolute;
        bottom: 0;
        left: 0;
        right: 0;
        height: 1px;
        background: var(--divider-color);
      }

      .scrollable {
        padding: 0 24px;

        @apply --layout-scroll;
        @apply --paper-dialog-scrollable;
      }

      .fit {
        @apply --layout-fit;
      }
    </style>

    <div id="scrollable" class="scrollable" on-scroll="updateScrollState">
      <slot></slot>
    </div>
  </template>

</dom-module>

<script>
  Polymer({

    is: 'paper-dialog-scrollable',

    properties: {

      /**
       * The dialog element that implements `Polymer.PaperDialogBehavior`
       * containing this element.
       * @type {?Node}
       */
      dialogElement: {type: Object}

    },

    /**
     * Returns the scrolling element.
     */
    get scrollTarget() {
      return this.$.scrollable;
    },

    ready: function() {
      this._ensureTarget();
      this.classList.add('no-padding');
    },

    attached: function() {
      this._ensureTarget();
      requestAnimationFrame(this.updateScrollState.bind(this));
    },

    updateScrollState: function() {
      this.toggleClass('is-scrolled', this.scrollTarget.scrollTop > 0);
      this.toggleClass(
          'can-scroll',
          this.scrollTarget.offsetHeight < this.scrollTarget.scrollHeight);
      this.toggleClass(
          'scrolled-to-bottom',
          this.scrollTarget.scrollTop + this.scrollTarget.offsetHeight >=
              this.scrollTarget.scrollHeight);
    },

    _ensureTarget: function() {
      // Read parentElement instead of parentNode in order to skip shadowRoots.
      this.dialogElement = this.dialogElement || this.parentElement;
      // Check if dialog implements paper-dialog-behavior. If not, fit
      // scrollTarget to host.
      if (this.dialogElement && this.dialogElement.behaviors &&
          this.dialogElement.behaviors.indexOf(Polymer.PaperDialogBehaviorImpl) >=
              0) {
        this.dialogElement.sizingTarget = this.scrollTarget;
        this.scrollTarget.classList.remove('fit');
      } else if (this.dialogElement) {
        this.scrollTarget.classList.add('fit');
      }
    }

  });
</script>
<script>
  (function() {
  'use strict';
  /**
   * IronDropdownScrollManager is deprecated, use IronScrollManager instead.
   */
  Polymer.IronDropdownScrollManager = Polymer.IronScrollManager;
  })();
</script><dom-module id="iron-dropdown" assetpath="bower_components/iron-dropdown/">
  <template>
    <style>
      :host {
        position: fixed;
      }

      #contentWrapper ::slotted(*) {
        overflow: auto;
      }

      #contentWrapper.animating ::slotted(*) {
        overflow: hidden;
        pointer-events: none;
      }
    </style>

    <div id="contentWrapper">
      <slot id="content" name="dropdown-content"></slot>
    </div>
  </template>

  <script>
    (function() {
    'use strict';

    Polymer({
      is: 'iron-dropdown',

      behaviors: [
        Polymer.IronControlState,
        Polymer.IronA11yKeysBehavior,
        Polymer.IronOverlayBehavior,
        Polymer.NeonAnimationRunnerBehavior
      ],

      properties: {
        /**
         * The orientation against which to align the dropdown content
         * horizontally relative to the dropdown trigger.
         * Overridden from `Polymer.IronFitBehavior`.
         */
        horizontalAlign: {type: String, value: 'left', reflectToAttribute: true},

        /**
         * The orientation against which to align the dropdown content
         * vertically relative to the dropdown trigger.
         * Overridden from `Polymer.IronFitBehavior`.
         */
        verticalAlign: {type: String, value: 'top', reflectToAttribute: true},

        /**
         * An animation config. If provided, this will be used to animate the
         * opening of the dropdown. Pass an Array for multiple animations.
         * See `neon-animation` documentation for more animation configuration
         * details.
         */
        openAnimationConfig: {type: Object},

        /**
         * An animation config. If provided, this will be used to animate the
         * closing of the dropdown. Pass an Array for multiple animations.
         * See `neon-animation` documentation for more animation configuration
         * details.
         */
        closeAnimationConfig: {type: Object},

        /**
         * If provided, this will be the element that will be focused when
         * the dropdown opens.
         */
        focusTarget: {type: Object},

        /**
         * Set to true to disable animations when opening and closing the
         * dropdown.
         */
        noAnimations: {type: Boolean, value: false},

        /**
         * By default, the dropdown will constrain scrolling on the page
         * to itself when opened.
         * Set to true in order to prevent scroll from being constrained
         * to the dropdown when it opens.
         * This property is a shortcut to set `scrollAction` to lock or refit.
         * Prefer directly setting the `scrollAction` property.
         */
        allowOutsideScroll:
            {type: Boolean, value: false, observer: '_allowOutsideScrollChanged'}
      },

      listeners: {'neon-animation-finish': '_onNeonAnimationFinish'},

      observers: [
        '_updateOverlayPosition(positionTarget, verticalAlign, horizontalAlign, verticalOffset, horizontalOffset)'
      ],

      /**
       * The element that is contained by the dropdown, if any.
       */
      get containedElement() {
        // Polymer 2.x returns slot.assignedNodes which can contain text nodes.
        var nodes = Polymer.dom(this.$.content).getDistributedNodes();
        for (var i = 0, l = nodes.length; i < l; i++) {
          if (nodes[i].nodeType === Node.ELEMENT_NODE) {
            return nodes[i];
          }
        }
      },

      ready: function() {
        // Ensure scrollAction is set.
        if (!this.scrollAction) {
          this.scrollAction = this.allowOutsideScroll ? 'refit' : 'lock';
        }
        this._readied = true;
      },

      attached: function() {
        if (!this.sizingTarget || this.sizingTarget === this) {
          this.sizingTarget = this.containedElement || this;
        }
      },

      detached: function() {
        this.cancelAnimation();
      },

      /**
       * Called when the value of `opened` changes.
       * Overridden from `IronOverlayBehavior`
       */
      _openedChanged: function() {
        if (this.opened && this.disabled) {
          this.cancel();
        } else {
          this.cancelAnimation();
          this._updateAnimationConfig();
          Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments);
        }
      },

      /**
       * Overridden from `IronOverlayBehavior`.
       */
      _renderOpened: function() {
        if (!this.noAnimations && this.animationConfig.open) {
          this.$.contentWrapper.classList.add('animating');
          this.playAnimation('open');
        } else {
          Polymer.IronOverlayBehaviorImpl._renderOpened.apply(this, arguments);
        }
      },

      /**
       * Overridden from `IronOverlayBehavior`.
       */
      _renderClosed: function() {
        if (!this.noAnimations && this.animationConfig.close) {
          this.$.contentWrapper.classList.add('animating');
          this.playAnimation('close');
        } else {
          Polymer.IronOverlayBehaviorImpl._renderClosed.apply(this, arguments);
        }
      },

      /**
       * Called when animation finishes on the dropdown (when opening or
       * closing). Responsible for "completing" the process of opening or
       * closing the dropdown by positioning it or setting its display to
       * none.
       */
      _onNeonAnimationFinish: function() {
        this.$.contentWrapper.classList.remove('animating');
        if (this.opened) {
          this._finishRenderOpened();
        } else {
          this._finishRenderClosed();
        }
      },

      /**
       * Constructs the final animation config from different properties used
       * to configure specific parts of the opening and closing animations.
       */
      _updateAnimationConfig: function() {
        // Update the animation node to be the containedElement.
        var animationNode = this.containedElement;
        var animations = [].concat(this.openAnimationConfig || [])
                             .concat(this.closeAnimationConfig || []);
        for (var i = 0; i < animations.length; i++) {
          animations[i].node = animationNode;
        }
        this.animationConfig = {
          open: this.openAnimationConfig,
          close: this.closeAnimationConfig
        };
      },

      /**
       * Updates the overlay position based on configured horizontal
       * and vertical alignment.
       */
      _updateOverlayPosition: function() {
        if (this.isAttached) {
          // This triggers iron-resize, and iron-overlay-behavior will call refit if
          // needed.
          this.notifyResize();
        }
      },

      /**
       * Sets scrollAction according to the value of allowOutsideScroll.
       * Prefer setting directly scrollAction.
       */
      _allowOutsideScrollChanged: function(allowOutsideScroll) {
        // Wait until initial values are all set.
        if (!this._readied) {
          return;
        }
        if (!allowOutsideScroll) {
          this.scrollAction = 'lock';
        } else if (!this.scrollAction || this.scrollAction === 'lock') {
          this.scrollAction = 'refit';
        }
      },

      /**
       * Apply focus to focusTarget or containedElement
       */
      _applyFocus: function() {
        var focusTarget = this.focusTarget || this.containedElement;
        if (focusTarget && this.opened && !this.noAutoFocus) {
          focusTarget.focus();
        } else {
          Polymer.IronOverlayBehaviorImpl._applyFocus.apply(this, arguments);
        }
      }
    });
    })();
  </script>
</dom-module>
<script>

  /**
   * Use `Polymer.NeonAnimationBehavior` to implement an animation.
   * @polymerBehavior
   */
  Polymer.NeonAnimationBehavior = {

    properties: {

      /**
       * Defines the animation timing.
       */
      animationTiming: {
        type: Object,
        value: function() {
          return {
            duration: 500,
            easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
            fill: 'both'
          }
        }
      }

    },

    /**
     * Can be used to determine that elements implement this behavior.
     */
    isNeonAnimation: true,

    /**
     * Do any animation configuration here.
     */
    // configure: function(config) {
    // },

    created: function() {
      if (!document.body.animate) {
        console.warn('No web animations detected. This element will not' +
            ' function without a web animations polyfill.');
      }
    },

    /**
     * Returns the animation timing by mixing in properties from `config` to the defaults defined
     * by the animation.
     */
    timingFromConfig: function(config) {
      if (config.timing) {
        for (var property in config.timing) {
          this.animationTiming[property] = config.timing[property];
        }
      }
      return this.animationTiming;
    },

    /**
     * Sets `transform` and `transformOrigin` properties along with the prefixed versions.
     */
    setPrefixedProperty: function(node, property, value) {
      var map = {
        'transform': ['webkitTransform'],
        'transformOrigin': ['mozTransformOrigin', 'webkitTransformOrigin']
      };
      var prefixes = map[property];
      for (var prefix, index = 0; prefix = prefixes[index]; index++) {
        node.style[prefix] = value;
      }
      node.style[property] = value;
    },

    /**
     * Called when the animation finishes.
     */
    complete: function(config) {}

  };

</script>
<script>

  Polymer({

    is: 'fade-in-animation',

    behaviors: [
      Polymer.NeonAnimationBehavior
    ],

    configure: function(config) {
      var node = config.node;
      this._effect = new KeyframeEffect(node, [
        {'opacity': '0'},
        {'opacity': '1'}
      ], this.timingFromConfig(config));
      return this._effect;
    }

  });

</script>
<script>

  Polymer({

    is: 'fade-out-animation',

    behaviors: [
      Polymer.NeonAnimationBehavior
    ],

    configure: function(config) {
      var node = config.node;
      this._effect = new KeyframeEffect(node, [
        {'opacity': '1'},
        {'opacity': '0'}
      ], this.timingFromConfig(config));
      return this._effect;
    }

  });

</script>
<script>
  Polymer({
    is: 'paper-menu-grow-height-animation',

    behaviors: [Polymer.NeonAnimationBehavior],

    configure: function(config) {
      var node = config.node;
      var rect = node.getBoundingClientRect();
      var height = rect.height;

      this._effect = new KeyframeEffect(
          node,
          [{height: (height / 2) + 'px'}, {height: height + 'px'}],
          this.timingFromConfig(config));

      return this._effect;
    }
  });

  Polymer({
    is: 'paper-menu-grow-width-animation',

    behaviors: [Polymer.NeonAnimationBehavior],

    configure: function(config) {
      var node = config.node;
      var rect = node.getBoundingClientRect();
      var width = rect.width;

      this._effect = new KeyframeEffect(
          node,
          [{width: (width / 2) + 'px'}, {width: width + 'px'}],
          this.timingFromConfig(config));

      return this._effect;
    }
  });

  Polymer({
    is: 'paper-menu-shrink-width-animation',

    behaviors: [Polymer.NeonAnimationBehavior],

    configure: function(config) {
      var node = config.node;
      var rect = node.getBoundingClientRect();
      var width = rect.width;

      this._effect = new KeyframeEffect(
          node,
          [{width: width + 'px'}, {width: width - (width / 20) + 'px'}],
          this.timingFromConfig(config));

      return this._effect;
    }
  });

  Polymer({
    is: 'paper-menu-shrink-height-animation',

    behaviors: [Polymer.NeonAnimationBehavior],

    configure: function(config) {
      var node = config.node;
      var rect = node.getBoundingClientRect();
      var height = rect.height;

      this.setPrefixedProperty(node, 'transformOrigin', '0 0');

      this._effect = new KeyframeEffect(
          node,
          [
            {height: height + 'px', transform: 'translateY(0)'},
            {height: height / 2 + 'px', transform: 'translateY(-20px)'}
          ],
          this.timingFromConfig(config));

      return this._effect;
    }
  });
</script>


<dom-module id="paper-menu-button" assetpath="bower_components/paper-menu-button/">
  <template>
    <style>
      :host {
        display: inline-block;
        position: relative;
        padding: 8px;
        outline: none;

        @apply --paper-menu-button;
      }

      :host([disabled]) {
        cursor: auto;
        color: var(--disabled-text-color);

        @apply --paper-menu-button-disabled;
      }

      iron-dropdown {
        @apply --paper-menu-button-dropdown;
      }

      .dropdown-content {
        @apply --shadow-elevation-2dp;

        position: relative;
        border-radius: 2px;
        background-color: var(--paper-menu-button-dropdown-background, var(--primary-background-color));

        @apply --paper-menu-button-content;
      }

      :host([vertical-align="top"]) .dropdown-content {
        margin-bottom: 20px;
        margin-top: -10px;
        top: 10px;
      }

      :host([vertical-align="bottom"]) .dropdown-content {
        bottom: 10px;
        margin-bottom: -10px;
        margin-top: 20px;
      }

      #trigger {
        cursor: pointer;
      }
    </style>

    <div id="trigger" on-tap="toggle">
      <slot name="dropdown-trigger"></slot>
    </div>

    <iron-dropdown id="dropdown" opened="{{opened}}" horizontal-align="[[horizontalAlign]]" vertical-align="[[verticalAlign]]" dynamic-align="[[dynamicAlign]]" horizontal-offset="[[horizontalOffset]]" vertical-offset="[[verticalOffset]]" no-overlap="[[noOverlap]]" open-animation-config="[[openAnimationConfig]]" close-animation-config="[[closeAnimationConfig]]" no-animations="[[noAnimations]]" focus-target="[[_dropdownContent]]" allow-outside-scroll="[[allowOutsideScroll]]" restore-focus-on-close="[[restoreFocusOnClose]]" on-iron-overlay-canceled="__onIronOverlayCanceled">
      <div slot="dropdown-content" class="dropdown-content">
        <slot id="content" name="dropdown-content"></slot>
      </div>
    </iron-dropdown>
  </template>

  <script>
    (function() {
    'use strict';

    var config = {
      ANIMATION_CUBIC_BEZIER: 'cubic-bezier(.3,.95,.5,1)',
      MAX_ANIMATION_TIME_MS: 400
    };

    Polymer.PaperMenuButton = Polymer({
      is: 'paper-menu-button',

      /**
       * Fired when the dropdown opens.
       *
       * @event paper-dropdown-open
       */

      /**
       * Fired when the dropdown closes.
       *
       * @event paper-dropdown-close
       */

      behaviors: [Polymer.IronA11yKeysBehavior, Polymer.IronControlState],

      properties: {
        /**
         * True if the content is currently displayed.
         */
        opened:
            {type: Boolean, value: false, notify: true, observer: '_openedChanged'},

        /**
         * The orientation against which to align the menu dropdown
         * horizontally relative to the dropdown trigger.
         */
        horizontalAlign: {type: String, value: 'left', reflectToAttribute: true},

        /**
         * The orientation against which to align the menu dropdown
         * vertically relative to the dropdown trigger.
         */
        verticalAlign: {type: String, value: 'top', reflectToAttribute: true},

        /**
         * If true, the `horizontalAlign` and `verticalAlign` properties will
         * be considered preferences instead of strict requirements when
         * positioning the dropdown and may be changed if doing so reduces
         * the area of the dropdown falling outside of `fitInto`.
         */
        dynamicAlign: {type: Boolean},

        /**
         * A pixel value that will be added to the position calculated for the
         * given `horizontalAlign`. Use a negative value to offset to the
         * left, or a positive value to offset to the right.
         */
        horizontalOffset: {type: Number, value: 0, notify: true},

        /**
         * A pixel value that will be added to the position calculated for the
         * given `verticalAlign`. Use a negative value to offset towards the
         * top, or a positive value to offset towards the bottom.
         */
        verticalOffset: {type: Number, value: 0, notify: true},

        /**
         * If true, the dropdown will be positioned so that it doesn't overlap
         * the button.
         */
        noOverlap: {type: Boolean},

        /**
         * Set to true to disable animations when opening and closing the
         * dropdown.
         */
        noAnimations: {type: Boolean, value: false},

        /**
         * Set to true to disable automatically closing the dropdown after
         * a selection has been made.
         */
        ignoreSelect: {type: Boolean, value: false},

        /**
         * Set to true to enable automatically closing the dropdown after an
         * item has been activated, even if the selection did not change.
         */
        closeOnActivate: {type: Boolean, value: false},

        /**
         * An animation config. If provided, this will be used to animate the
         * opening of the dropdown.
         */
        openAnimationConfig: {
          type: Object,
          value: function() {
            return [
              {name: 'fade-in-animation', timing: {delay: 100, duration: 200}},
              {
                name: 'paper-menu-grow-width-animation',
                timing: {
                  delay: 100,
                  duration: 150,
                  easing: config.ANIMATION_CUBIC_BEZIER
                }
              },
              {
                name: 'paper-menu-grow-height-animation',
                timing: {
                  delay: 100,
                  duration: 275,
                  easing: config.ANIMATION_CUBIC_BEZIER
                }
              }
            ];
          }
        },

        /**
         * An animation config. If provided, this will be used to animate the
         * closing of the dropdown.
         */
        closeAnimationConfig: {
          type: Object,
          value: function() {
            return [
              {name: 'fade-out-animation', timing: {duration: 150}},
              {
                name: 'paper-menu-shrink-width-animation',
                timing: {
                  delay: 100,
                  duration: 50,
                  easing: config.ANIMATION_CUBIC_BEZIER
                }
              },
              {
                name: 'paper-menu-shrink-height-animation',
                timing: {duration: 200, easing: 'ease-in'}
              }
            ];
          }
        },

        /**
         * By default, the dropdown will constrain scrolling on the page
         * to itself when opened.
         * Set to true in order to prevent scroll from being constrained
         * to the dropdown when it opens.
         */
        allowOutsideScroll: {type: Boolean, value: false},

        /**
         * Whether focus should be restored to the button when the menu closes.
         */
        restoreFocusOnClose: {type: Boolean, value: true},

        /**
         * This is the element intended to be bound as the focus target
         * for the `iron-dropdown` contained by `paper-menu-button`.
         */
        _dropdownContent: {type: Object}
      },

      hostAttributes: {role: 'group', 'aria-haspopup': 'true'},

      listeners:
          {'iron-activate': '_onIronActivate', 'iron-select': '_onIronSelect'},

      /**
       * The content element that is contained by the menu button, if any.
       */
      get contentElement() {
        // Polymer 2.x returns slot.assignedNodes which can contain text nodes.
        var nodes = Polymer.dom(this.$.content).getDistributedNodes();
        for (var i = 0, l = nodes.length; i < l; i++) {
          if (nodes[i].nodeType === Node.ELEMENT_NODE) {
            return nodes[i];
          }
        }
      },

      /**
       * Toggles the drowpdown content between opened and closed.
       */
      toggle: function() {
        if (this.opened) {
          this.close();
        } else {
          this.open();
        }
      },

      /**
       * Make the dropdown content appear as an overlay positioned relative
       * to the dropdown trigger.
       */
      open: function() {
        if (this.disabled) {
          return;
        }

        this.$.dropdown.open();
      },

      /**
       * Hide the dropdown content.
       */
      close: function() {
        this.$.dropdown.close();
      },

      /**
       * When an `iron-select` event is received, the dropdown should
       * automatically close on the assumption that a value has been chosen.
       *
       * @param {CustomEvent} event A CustomEvent instance with type
       * set to `"iron-select"`.
       */
      _onIronSelect: function(event) {
        if (!this.ignoreSelect) {
          this.close();
        }
      },

      /**
       * Closes the dropdown when an `iron-activate` event is received if
       * `closeOnActivate` is true.
       *
       * @param {CustomEvent} event A CustomEvent of type 'iron-activate'.
       */
      _onIronActivate: function(event) {
        if (this.closeOnActivate) {
          this.close();
        }
      },

      /**
       * When the dropdown opens, the `paper-menu-button` fires `paper-open`.
       * When the dropdown closes, the `paper-menu-button` fires `paper-close`.
       *
       * @param {boolean} opened True if the dropdown is opened, otherwise false.
       * @param {boolean} oldOpened The previous value of `opened`.
       */
      _openedChanged: function(opened, oldOpened) {
        if (opened) {
          // TODO(cdata): Update this when we can measure changes in distributed
          // children in an idiomatic way.
          // We poke this property in case the element has changed. This will
          // cause the focus target for the `iron-dropdown` to be updated as
          // necessary:
          this._dropdownContent = this.contentElement;
          this.fire('paper-dropdown-open');
        } else if (oldOpened != null) {
          this.fire('paper-dropdown-close');
        }
      },

      /**
       * If the dropdown is open when disabled becomes true, close the
       * dropdown.
       *
       * @param {boolean} disabled True if disabled, otherwise false.
       */
      _disabledChanged: function(disabled) {
        Polymer.IronControlState._disabledChanged.apply(this, arguments);
        if (disabled && this.opened) {
          this.close();
        }
      },

      __onIronOverlayCanceled: function(event) {
        var uiEvent = event.detail;
        var trigger = this.$.trigger;
        var path = Polymer.dom(uiEvent).path;

        if (path.indexOf(trigger) > -1) {
          event.preventDefault();
        }
      }
    });

    Object.keys(config).forEach(function(key) {
      Polymer.PaperMenuButton[key] = config[key];
    });
    })();
  </script>
</dom-module>
<script>

  /**
   * @param {!Function} selectCallback
   * @constructor
   * @suppress {missingProvide}
   */
  Polymer.IronSelection = function(selectCallback) {
    this.selection = [];
    this.selectCallback = selectCallback;
  };

  Polymer.IronSelection.prototype = {

    /**
     * Retrieves the selected item(s).
     *
     * @method get
     * @returns Returns the selected item(s). If the multi property is true,
     * `get` will return an array, otherwise it will return
     * the selected item or undefined if there is no selection.
     */
    get: function() {
      return this.multi ? this.selection.slice() : this.selection[0];
    },

    /**
     * Clears all the selection except the ones indicated.
     *
     * @method clear
     * @param {Array} excludes items to be excluded.
     */
    clear: function(excludes) {
      this.selection.slice().forEach(function(item) {
        if (!excludes || excludes.indexOf(item) < 0) {
          this.setItemSelected(item, false);
        }
      }, this);
    },

    /**
     * Indicates if a given item is selected.
     *
     * @method isSelected
     * @param {*} item The item whose selection state should be checked.
     * @returns Returns true if `item` is selected.
     */
    isSelected: function(item) {
      return this.selection.indexOf(item) >= 0;
    },

    /**
     * Sets the selection state for a given item to either selected or deselected.
     *
     * @method setItemSelected
     * @param {*} item The item to select.
     * @param {boolean} isSelected True for selected, false for deselected.
     */
    setItemSelected: function(item, isSelected) {
      if (item != null) {
        if (isSelected !== this.isSelected(item)) {
          // proceed to update selection only if requested state differs from current
          if (isSelected) {
            this.selection.push(item);
          } else {
            var i = this.selection.indexOf(item);
            if (i >= 0) {
              this.selection.splice(i, 1);
            }
          }
          if (this.selectCallback) {
            this.selectCallback(item, isSelected);
          }
        }
      }
    },

    /**
     * Sets the selection state for a given item. If the `multi` property
     * is true, then the selected state of `item` will be toggled; otherwise
     * the `item` will be selected.
     *
     * @method select
     * @param {*} item The item to select.
     */
    select: function(item) {
      if (this.multi) {
        this.toggle(item);
      } else if (this.get() !== item) {
        this.setItemSelected(this.get(), false);
        this.setItemSelected(item, true);
      }
    },

    /**
     * Toggles the selection state for `item`.
     *
     * @method toggle
     * @param {*} item The item to toggle.
     */
    toggle: function(item) {
      this.setItemSelected(item, !this.isSelected(item));
    }

  };

</script>
<script>
  /**
   * @polymerBehavior Polymer.IronSelectableBehavior
   */
  Polymer.IronSelectableBehavior = {

      /**
       * Fired when iron-selector is activated (selected or deselected).
       * It is fired before the selected items are changed.
       * Cancel the event to abort selection.
       *
       * @event iron-activate
       */

      /**
       * Fired when an item is selected
       *
       * @event iron-select
       */

      /**
       * Fired when an item is deselected
       *
       * @event iron-deselect
       */

      /**
       * Fired when the list of selectable items changes (e.g., items are
       * added or removed). The detail of the event is a mutation record that
       * describes what changed.
       *
       * @event iron-items-changed
       */

    properties: {

      /**
       * If you want to use an attribute value or property of an element for
       * `selected` instead of the index, set this to the name of the attribute
       * or property. Hyphenated values are converted to camel case when used to
       * look up the property of a selectable element. Camel cased values are
       * *not* converted to hyphenated values for attribute lookup. It's
       * recommended that you provide the hyphenated form of the name so that
       * selection works in both cases. (Use `attr-or-property-name` instead of
       * `attrOrPropertyName`.)
       */
      attrForSelected: {
        type: String,
        value: null
      },

      /**
       * Gets or sets the selected element. The default is to use the index of the item.
       * @type {string|number}
       */
      selected: {
        type: String,
        notify: true
      },

      /**
       * Returns the currently selected item.
       *
       * @type {?Object}
       */
      selectedItem: {
        type: Object,
        readOnly: true,
        notify: true
      },

      /**
       * The event that fires from items when they are selected. Selectable
       * will listen for this event from items and update the selection state.
       * Set to empty string to listen to no events.
       */
      activateEvent: {
        type: String,
        value: 'tap',
        observer: '_activateEventChanged'
      },

      /**
       * This is a CSS selector string.  If this is set, only items that match the CSS selector
       * are selectable.
       */
      selectable: String,

      /**
       * The class to set on elements when selected.
       */
      selectedClass: {
        type: String,
        value: 'iron-selected'
      },

      /**
       * The attribute to set on elements when selected.
       */
      selectedAttribute: {
        type: String,
        value: null
      },

      /**
       * Default fallback if the selection based on selected with `attrForSelected`
       * is not found.
       */
      fallbackSelection: {
        type: String,
        value: null
      },

      /**
       * The list of items from which a selection can be made.
       */
      items: {
        type: Array,
        readOnly: true,
        notify: true,
        value: function() {
          return [];
        }
      },

      /**
       * The set of excluded elements where the key is the `localName`
       * of the element that will be ignored from the item list.
       *
       * @default {template: 1}
       */
      _excludedLocalNames: {
        type: Object,
        value: function() {
          return {
            'template': 1,
            'dom-bind': 1,
            'dom-if': 1,
            'dom-repeat': 1,
          };
        }
      }
    },

    observers: [
      '_updateAttrForSelected(attrForSelected)',
      '_updateSelected(selected)',
      '_checkFallback(fallbackSelection)'
    ],

    created: function() {
      this._bindFilterItem = this._filterItem.bind(this);
      this._selection = new Polymer.IronSelection(this._applySelection.bind(this));
    },

    attached: function() {
      this._observer = this._observeItems(this);
      this._addListener(this.activateEvent);
    },

    detached: function() {
      if (this._observer) {
        Polymer.dom(this).unobserveNodes(this._observer);
      }
      this._removeListener(this.activateEvent);
    },

    /**
     * Returns the index of the given item.
     *
     * @method indexOf
     * @param {Object} item
     * @returns Returns the index of the item
     */
    indexOf: function(item) {
      return this.items ? this.items.indexOf(item) : -1;
    },

    /**
     * Selects the given value.
     *
     * @method select
     * @param {string|number} value the value to select.
     */
    select: function(value) {
      this.selected = value;
    },

    /**
     * Selects the previous item.
     *
     * @method selectPrevious
     */
    selectPrevious: function() {
      var length = this.items.length;
      var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % length;
      this.selected = this._indexToValue(index);
    },

    /**
     * Selects the next item.
     *
     * @method selectNext
     */
    selectNext: function() {
      var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.length;
      this.selected = this._indexToValue(index);
    },

    /**
     * Selects the item at the given index.
     *
     * @method selectIndex
     */
    selectIndex: function(index) {
      this.select(this._indexToValue(index));
    },

    /**
     * Force a synchronous update of the `items` property.
     *
     * NOTE: Consider listening for the `iron-items-changed` event to respond to
     * updates to the set of selectable items after updates to the DOM list and
     * selection state have been made.
     *
     * WARNING: If you are using this method, you should probably consider an
     * alternate approach. Synchronously querying for items is potentially
     * slow for many use cases. The `items` property will update asynchronously
     * on its own to reflect selectable items in the DOM.
     */
    forceSynchronousItemUpdate: function() {
      if (this._observer && typeof this._observer.flush === "function") {
        // NOTE(bicknellr): `Polymer.dom.flush` above is no longer sufficient to
        // trigger `observeNodes` callbacks. Polymer 2.x returns an object from
        // `observeNodes` with a `flush` that synchronously gives the callback
        // any pending MutationRecords (retrieved with `takeRecords`). Any case
        // where ShadyDOM flushes were expected to synchronously trigger item
        // updates will now require calling `forceSynchronousItemUpdate`.
        this._observer.flush();
      } else {
        this._updateItems();
      }
    },

    // UNUSED, FOR API COMPATIBILITY
    get _shouldUpdateSelection() {
      return this.selected != null;
    },

    _checkFallback: function() {
      this._updateSelected();
    },

    _addListener: function(eventName) {
      this.listen(this, eventName, '_activateHandler');
    },

    _removeListener: function(eventName) {
      this.unlisten(this, eventName, '_activateHandler');
    },

    _activateEventChanged: function(eventName, old) {
      this._removeListener(old);
      this._addListener(eventName);
    },

    _updateItems: function() {
      var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*');
      nodes = Array.prototype.filter.call(nodes, this._bindFilterItem);
      this._setItems(nodes);
    },

    _updateAttrForSelected: function() {
      if (this.selectedItem) {
        this.selected = this._valueForItem(this.selectedItem);
      }
    },

    _updateSelected: function() {
      this._selectSelected(this.selected);
    },

    _selectSelected: function(selected) {
      if (!this.items) {
        return;
      }

      var item = this._valueToItem(this.selected);
      if (item) {
        this._selection.select(item);
      } else {
        this._selection.clear();
      }
      // Check for items, since this array is populated only when attached
      // Since Number(0) is falsy, explicitly check for undefined
      if (this.fallbackSelection && this.items.length && (this._selection.get() === undefined)) {
        this.selected = this.fallbackSelection;
      }
    },

    _filterItem: function(node) {
      return !this._excludedLocalNames[node.localName];
    },

    _valueToItem: function(value) {
      return (value == null) ? null : this.items[this._valueToIndex(value)];
    },

    _valueToIndex: function(value) {
      if (this.attrForSelected) {
        for (var i = 0, item; item = this.items[i]; i++) {
          if (this._valueForItem(item) == value) {
            return i;
          }
        }
      } else {
        return Number(value);
      }
    },

    _indexToValue: function(index) {
      if (this.attrForSelected) {
        var item = this.items[index];
        if (item) {
          return this._valueForItem(item);
        }
      } else {
        return index;
      }
    },

    _valueForItem: function(item) {
      if (!item) {
        return null;
      }
      if (!this.attrForSelected) {
        var i = this.indexOf(item);
        return i === -1 ? null : i;
      }
      var propValue = item[Polymer.CaseMap.dashToCamelCase(this.attrForSelected)];
      return propValue != undefined ? propValue : item.getAttribute(this.attrForSelected);
    },

    _applySelection: function(item, isSelected) {
      if (this.selectedClass) {
        this.toggleClass(this.selectedClass, isSelected, item);
      }
      if (this.selectedAttribute) {
        this.toggleAttribute(this.selectedAttribute, isSelected, item);
      }
      this._selectionChange();
      this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item});
    },

    _selectionChange: function() {
      this._setSelectedItem(this._selection.get());
    },

    // observe items change under the given node.
    _observeItems: function(node) {
      return Polymer.dom(node).observeNodes(function(mutation) {
        this._updateItems();
        this._updateSelected();

        // Let other interested parties know about the change so that
        // we don't have to recreate mutation observers everywhere.
        this.fire('iron-items-changed', mutation, {
          bubbles: false,
          cancelable: false
        });
      });
    },

    _activateHandler: function(e) {
      var t = e.target;
      var items = this.items;
      while (t && t != this) {
        var i = items.indexOf(t);
        if (i >= 0) {
          var value = this._indexToValue(i);
          this._itemActivate(value, t);
          return;
        }
        t = t.parentNode;
      }
    },

    _itemActivate: function(value, item) {
      if (!this.fire('iron-activate',
          {selected: value, item: item}, {cancelable: true}).defaultPrevented) {
        this.select(value);
      }
    }

  };

</script>
<script>
  /**
   * @polymerBehavior Polymer.IronMultiSelectableBehavior
   */
  Polymer.IronMultiSelectableBehaviorImpl = {
    properties: {

      /**
       * If true, multiple selections are allowed.
       */
      multi: {
        type: Boolean,
        value: false,
        observer: 'multiChanged'
      },

      /**
       * Gets or sets the selected elements. This is used instead of `selected` when `multi`
       * is true.
       */
      selectedValues: {
        type: Array,
        notify: true,
        value: function() {
          return [];
        }
      },

      /**
       * Returns an array of currently selected items.
       */
      selectedItems: {
        type: Array,
        readOnly: true,
        notify: true,
        value: function() {
          return [];
        }
      },

    },

    observers: [
      '_updateSelected(selectedValues.splices)'
    ],

    /**
     * Selects the given value. If the `multi` property is true, then the selected state of the
     * `value` will be toggled; otherwise the `value` will be selected.
     *
     * @method select
     * @param {string|number} value the value to select.
     */
    select: function(value) {
      if (this.multi) {
        this._toggleSelected(value);
      } else {
        this.selected = value;
      }
    },

    multiChanged: function(multi) {
      this._selection.multi = multi;
      this._updateSelected();
    },

    // UNUSED, FOR API COMPATIBILITY
    get _shouldUpdateSelection() {
      return this.selected != null ||
        (this.selectedValues != null && this.selectedValues.length);
    },

    _updateAttrForSelected: function() {
      if (!this.multi) {
        Polymer.IronSelectableBehavior._updateAttrForSelected.apply(this);
      } else if (this.selectedItems && this.selectedItems.length > 0) {
        this.selectedValues = this.selectedItems.map(function(selectedItem) {
          return this._indexToValue(this.indexOf(selectedItem));
        }, this).filter(function(unfilteredValue) {
          return unfilteredValue != null;
        }, this);
      }
    },

    _updateSelected: function() {
      if (this.multi) {
        this._selectMulti(this.selectedValues);
      } else {
        this._selectSelected(this.selected);
      }
    },

    _selectMulti: function(values) {
      values = values || [];

      var selectedItems = (this._valuesToItems(values) || []).filter(function(item) {
        return item !== null && item !== undefined;
      });

      // clear all but the current selected items
      this._selection.clear(selectedItems);

      // select only those not selected yet
      for (var i = 0; i < selectedItems.length; i++) {
        this._selection.setItemSelected(selectedItems[i], true);
      }

      // Check for items, since this array is populated only when attached
      if (this.fallbackSelection && !this._selection.get().length) {
        var fallback = this._valueToItem(this.fallbackSelection);
        if (fallback) {
          this.select(this.fallbackSelection);
        }
      }
    },

    _selectionChange: function() {
      var s = this._selection.get();
      if (this.multi) {
        this._setSelectedItems(s);
        this._setSelectedItem(s.length ? s[0] : null);
      } else {
        if (s !== null && s !== undefined) {
          this._setSelectedItems([s]);
          this._setSelectedItem(s);
        } else {
          this._setSelectedItems([]);
          this._setSelectedItem(null);
        }
      }
    },

    _toggleSelected: function(value) {
      var i = this.selectedValues.indexOf(value);
      var unselected = i < 0;
      if (unselected) {
        this.push('selectedValues',value);
      } else {
        this.splice('selectedValues',i,1);
      }
    },

    _valuesToItems: function(values) {
      return (values == null) ? null : values.map(function(value) {
        return this._valueToItem(value);
      }, this);
    }
  };

  /** @polymerBehavior */
  Polymer.IronMultiSelectableBehavior = [
    Polymer.IronSelectableBehavior,
    Polymer.IronMultiSelectableBehaviorImpl
  ];

</script>
<script>
  /**
   * `Polymer.IronMenuBehavior` implements accessible menu behavior.
   *
   * @demo demo/index.html
   * @polymerBehavior Polymer.IronMenuBehavior
   */
  Polymer.IronMenuBehaviorImpl = {

    properties: {

      /**
       * Returns the currently focused item.
       * @type {?Object}
       */
      focusedItem:
          {observer: '_focusedItemChanged', readOnly: true, type: Object},

      /**
       * The attribute to use on menu items to look up the item title. Typing the
       * first letter of an item when the menu is open focuses that item. If
       * unset, `textContent` will be used.
       */
      attrForItemTitle: {type: String},

      /**
       * @type {boolean}
       */
      disabled: {
        type: Boolean,
        value: false,
        observer: '_disabledChanged',
      },
    },

    /**
     * The list of keys has been taken from
     * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState
     * @private
     */
    _MODIFIER_KEYS: [
      'Alt',
      'AltGraph',
      'CapsLock',
      'Control',
      'Fn',
      'FnLock',
      'Hyper',
      'Meta',
      'NumLock',
      'OS',
      'ScrollLock',
      'Shift',
      'Super',
      'Symbol',
      'SymbolLock'
    ],

    /** @private */
    _SEARCH_RESET_TIMEOUT_MS: 1000,

    /** @private */
    _previousTabIndex: 0,

    hostAttributes: {
      'role': 'menu',
    },

    observers: ['_updateMultiselectable(multi)'],

    listeners: {
      'focus': '_onFocus',
      'keydown': '_onKeydown',
      'iron-items-changed': '_onIronItemsChanged'
    },

    /**
     * @type {!Object}
     */
    keyBindings: {
      'up': '_onUpKey',
      'down': '_onDownKey',
      'esc': '_onEscKey',
      'shift+tab:keydown': '_onShiftTabDown'
    },

    attached: function() {
      this._resetTabindices();
    },

    /**
     * Selects the given value. If the `multi` property is true, then the selected
     * state of the `value` will be toggled; otherwise the `value` will be
     * selected.
     *
     * @param {string|number} value the value to select.
     */
    select: function(value) {
      // Cancel automatically focusing a default item if the menu received focus
      // through a user action selecting a particular item.
      if (this._defaultFocusAsync) {
        this.cancelAsync(this._defaultFocusAsync);
        this._defaultFocusAsync = null;
      }
      var item = this._valueToItem(value);
      if (item && item.hasAttribute('disabled'))
        return;
      this._setFocusedItem(item);
      Polymer.IronMultiSelectableBehaviorImpl.select.apply(this, arguments);
    },

    /**
     * Resets all tabindex attributes to the appropriate value based on the
     * current selection state. The appropriate value is `0` (focusable) for
     * the default selected item, and `-1` (not keyboard focusable) for all
     * other items.
     */
    _resetTabindices: function() {
      var selectedItem = this.multi ?
          (this.selectedItems && this.selectedItems[0]) :
          this.selectedItem;

      this.items.forEach(function(item) {
        item.setAttribute('tabindex', item === selectedItem ? '0' : '-1');
      }, this);
    },

    /**
     * Sets appropriate ARIA based on whether or not the menu is meant to be
     * multi-selectable.
     *
     * @param {boolean} multi True if the menu should be multi-selectable.
     */
    _updateMultiselectable: function(multi) {
      if (multi) {
        this.setAttribute('aria-multiselectable', 'true');
      } else {
        this.removeAttribute('aria-multiselectable');
      }
    },

    /**
     * Given a KeyboardEvent, this method will focus the appropriate item in the
     * menu (if there is a relevant item, and it is possible to focus it).
     *
     * @param {KeyboardEvent} event A KeyboardEvent.
     */
    _focusWithKeyboardEvent: function(event) {
      // Make sure that the key pressed is not a modifier key.
      // getModifierState is not being used, as it is not available in Safari
      // earlier than 10.0.2 (https://trac.webkit.org/changeset/206725/webkit)
      if (this._MODIFIER_KEYS.indexOf(event.key) !== -1)
        return;

      this.cancelDebouncer('_clearSearchText');

      var searchText = this._searchText || '';
      var key = event.key && event.key.length == 1 ?
          event.key :
          String.fromCharCode(event.keyCode);
      searchText += key.toLocaleLowerCase();

      var searchLength = searchText.length;

      for (var i = 0, item; item = this.items[i]; i++) {
        if (item.hasAttribute('disabled')) {
          continue;
        }

        var attr = this.attrForItemTitle || 'textContent';
        var title = (item[attr] || item.getAttribute(attr) || '').trim();

        if (title.length < searchLength) {
          continue;
        }

        if (title.slice(0, searchLength).toLocaleLowerCase() == searchText) {
          this._setFocusedItem(item);
          break;
        }
      }

      this._searchText = searchText;
      this.debounce(
          '_clearSearchText',
          this._clearSearchText,
          this._SEARCH_RESET_TIMEOUT_MS);
    },

    _clearSearchText: function() {
      this._searchText = '';
    },

    /**
     * Focuses the previous item (relative to the currently focused item) in the
     * menu, disabled items will be skipped.
     * Loop until length + 1 to handle case of single item in menu.
     */
    _focusPrevious: function() {
      var length = this.items.length;
      var curFocusIndex = Number(this.indexOf(this.focusedItem));

      for (var i = 1; i < length + 1; i++) {
        var item = this.items[(curFocusIndex - i + length) % length];
        if (!item.hasAttribute('disabled')) {
          var owner = Polymer.dom(item).getOwnerRoot() || document;
          this._setFocusedItem(item);

          // Focus might not have worked, if the element was hidden or not
          // focusable. In that case, try again.
          if (Polymer.dom(owner).activeElement == item) {
            return;
          }
        }
      }
    },

    /**
     * Focuses the next item (relative to the currently focused item) in the
     * menu, disabled items will be skipped.
     * Loop until length + 1 to handle case of single item in menu.
     */
    _focusNext: function() {
      var length = this.items.length;
      var curFocusIndex = Number(this.indexOf(this.focusedItem));

      for (var i = 1; i < length + 1; i++) {
        var item = this.items[(curFocusIndex + i) % length];
        if (!item.hasAttribute('disabled')) {
          var owner = Polymer.dom(item).getOwnerRoot() || document;
          this._setFocusedItem(item);

          // Focus might not have worked, if the element was hidden or not
          // focusable. In that case, try again.
          if (Polymer.dom(owner).activeElement == item) {
            return;
          }
        }
      }
    },

    /**
     * Mutates items in the menu based on provided selection details, so that
     * all items correctly reflect selection state.
     *
     * @param {Element} item An item in the menu.
     * @param {boolean} isSelected True if the item should be shown in a
     * selected state, otherwise false.
     */
    _applySelection: function(item, isSelected) {
      if (isSelected) {
        item.setAttribute('aria-selected', 'true');
      } else {
        item.removeAttribute('aria-selected');
      }
      Polymer.IronSelectableBehavior._applySelection.apply(this, arguments);
    },

    /**
     * Discretely updates tabindex values among menu items as the focused item
     * changes.
     *
     * @param {Element} focusedItem The element that is currently focused.
     * @param {?Element} old The last element that was considered focused, if
     * applicable.
     */
    _focusedItemChanged: function(focusedItem, old) {
      old && old.setAttribute('tabindex', '-1');
      if (focusedItem && !focusedItem.hasAttribute('disabled') &&
          !this.disabled) {
        focusedItem.setAttribute('tabindex', '0');
        focusedItem.focus();
      }
    },

    /**
     * A handler that responds to mutation changes related to the list of items
     * in the menu.
     *
     * @param {CustomEvent} event An event containing mutation records as its
     * detail.
     */
    _onIronItemsChanged: function(event) {
      if (event.detail.addedNodes.length) {
        this._resetTabindices();
      }
    },

    /**
     * Handler that is called when a shift+tab keypress is detected by the menu.
     *
     * @param {CustomEvent} event A key combination event.
     */
    _onShiftTabDown: function(event) {
      var oldTabIndex = this.getAttribute('tabindex');

      Polymer.IronMenuBehaviorImpl._shiftTabPressed = true;

      this._setFocusedItem(null);

      this.setAttribute('tabindex', '-1');

      this.async(function() {
        this.setAttribute('tabindex', oldTabIndex);
        Polymer.IronMenuBehaviorImpl._shiftTabPressed = false;
        // NOTE(cdata): polymer/polymer#1305
      }, 1);
    },

    /**
     * Handler that is called when the menu receives focus.
     *
     * @param {FocusEvent} event A focus event.
     */
    _onFocus: function(event) {
      if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) {
        // do not focus the menu itself
        return;
      }

      // Do not focus the selected tab if the deepest target is part of the
      // menu element's local DOM and is focusable.
      var rootTarget =
          /** @type {?HTMLElement} */ (Polymer.dom(event).rootTarget);
      if (rootTarget !== this && typeof rootTarget.tabIndex !== 'undefined' &&
          !this.isLightDescendant(rootTarget)) {
        return;
      }

      // clear the cached focus item
      this._defaultFocusAsync = this.async(function() {
        // focus the selected item when the menu receives focus, or the first item
        // if no item is selected
        var selectedItem = this.multi ?
            (this.selectedItems && this.selectedItems[0]) :
            this.selectedItem;

        this._setFocusedItem(null);

        if (selectedItem) {
          this._setFocusedItem(selectedItem);
        } else if (this.items[0]) {
          // We find the first none-disabled item (if one exists)
          this._focusNext();
        }
      });
    },

    /**
     * Handler that is called when the up key is pressed.
     *
     * @param {CustomEvent} event A key combination event.
     */
    _onUpKey: function(event) {
      // up and down arrows moves the focus
      this._focusPrevious();
      event.detail.keyboardEvent.preventDefault();
    },

    /**
     * Handler that is called when the down key is pressed.
     *
     * @param {CustomEvent} event A key combination event.
     */
    _onDownKey: function(event) {
      this._focusNext();
      event.detail.keyboardEvent.preventDefault();
    },

    /**
     * Handler that is called when the esc key is pressed.
     *
     * @param {CustomEvent} event A key combination event.
     */
    _onEscKey: function(event) {
      var focusedItem = this.focusedItem;
      if (focusedItem) {
        focusedItem.blur();
      }
    },

    /**
     * Handler that is called when a keydown event is detected.
     *
     * @param {KeyboardEvent} event A keyboard event.
     */
    _onKeydown: function(event) {
      if (!this.keyboardEventMatchesKeys(event, 'up down esc')) {
        // all other keys focus the menu item starting with that character
        this._focusWithKeyboardEvent(event);
      }
      event.stopPropagation();
    },

    // override _activateHandler
    _activateHandler: function(event) {
      Polymer.IronSelectableBehavior._activateHandler.call(this, event);
      event.stopPropagation();
    },

    /**
     * Updates this element's tab index when it's enabled/disabled.
     * @param {boolean} disabled
     */
    _disabledChanged: function(disabled) {
      if (disabled) {
        this._previousTabIndex =
            this.hasAttribute('tabindex') ? this.tabIndex : 0;
        this.removeAttribute(
            'tabindex');  // No tabindex means not tab-able or select-able.
      } else if (!this.hasAttribute('tabindex')) {
        this.setAttribute('tabindex', this._previousTabIndex);
      }
    }
  };

  Polymer.IronMenuBehaviorImpl._shiftTabPressed = false;

  /** @polymerBehavior Polymer.IronMenuBehavior */
  Polymer.IronMenuBehavior = [
    Polymer.IronMultiSelectableBehavior,
    Polymer.IronA11yKeysBehavior,
    Polymer.IronMenuBehaviorImpl
  ];
</script>
<dom-module id="paper-listbox" assetpath="bower_components/paper-listbox/">
  <template>
    <style>
      :host {
        display: block;
        padding: 8px 0;

        background: var(--paper-listbox-background-color, var(--primary-background-color));
        color: var(--paper-listbox-color, var(--primary-text-color));

        @apply --paper-listbox;
      }
    </style>

    <slot></slot>
  </template>

  <script>
    (function() {
    Polymer({
      is: 'paper-listbox',

      behaviors: [Polymer.IronMenuBehavior],

      /** @private */
      hostAttributes: {role: 'listbox'}
    });
    })();
  </script>
</dom-module>
<script>
  /** @polymerBehavior Polymer.PaperButtonBehavior */
  Polymer.PaperButtonBehaviorImpl = {
    properties: {
      /**
       * The z-depth of this element, from 0-5. Setting to 0 will remove the
       * shadow, and each increasing number greater than 0 will be "deeper"
       * than the last.
       *
       * @attribute elevation
       * @type number
       * @default 1
       */
      elevation: {type: Number, reflectToAttribute: true, readOnly: true}
    },

    observers: [
      '_calculateElevation(focused, disabled, active, pressed, receivedFocusFromKeyboard)',
      '_computeKeyboardClass(receivedFocusFromKeyboard)'
    ],

    hostAttributes: {role: 'button', tabindex: '0', animated: true},

    _calculateElevation: function() {
      var e = 1;
      if (this.disabled) {
        e = 0;
      } else if (this.active || this.pressed) {
        e = 4;
      } else if (this.receivedFocusFromKeyboard) {
        e = 3;
      }
      this._setElevation(e);
    },

    _computeKeyboardClass: function(receivedFocusFromKeyboard) {
      this.toggleClass('keyboard-focus', receivedFocusFromKeyboard);
    },

    /**
     * In addition to `IronButtonState` behavior, when space key goes down,
     * create a ripple down effect.
     *
     * @param {!KeyboardEvent} event .
     */
    _spaceKeyDownHandler: function(event) {
      Polymer.IronButtonStateImpl._spaceKeyDownHandler.call(this, event);
      // Ensure that there is at most one ripple when the space key is held down.
      if (this.hasRipple() && this.getRipple().ripples.length < 1) {
        this._ripple.uiDownAction();
      }
    },

    /**
     * In addition to `IronButtonState` behavior, when space key goes up,
     * create a ripple up effect.
     *
     * @param {!KeyboardEvent} event .
     */
    _spaceKeyUpHandler: function(event) {
      Polymer.IronButtonStateImpl._spaceKeyUpHandler.call(this, event);
      if (this.hasRipple()) {
        this._ripple.uiUpAction();
      }
    }
  };

  /** @polymerBehavior */
  Polymer.PaperButtonBehavior = [
    Polymer.IronButtonState,
    Polymer.IronControlState,
    Polymer.PaperRippleBehavior,
    Polymer.PaperButtonBehaviorImpl
  ];
</script>
<dom-module id="paper-material-styles" assetpath="bower_components/paper-styles/element-styles/">
  <template>
    <style>
      :host, html {
        --paper-material: {
          display: block;
          position: relative;
        };
        --paper-material-elevation-1: {
          @apply --shadow-elevation-2dp;
        };
        --paper-material-elevation-2: {
          @apply --shadow-elevation-4dp;
        };
        --paper-material-elevation-3: {
          @apply --shadow-elevation-6dp;
        };
        --paper-material-elevation-4: {
          @apply --shadow-elevation-8dp;
        };
        --paper-material-elevation-5: {
          @apply --shadow-elevation-16dp;
        };
      }
      :host(.paper-material), .paper-material {
        @apply --paper-material;
      }
      :host(.paper-material[elevation="1"]), .paper-material[elevation="1"] {
        @apply --paper-material-elevation-1;
      }
      :host(.paper-material[elevation="2"]), .paper-material[elevation="2"] {
        @apply --paper-material-elevation-2;
      }
      :host(.paper-material[elevation="3"]), .paper-material[elevation="3"] {
        @apply --paper-material-elevation-3;
      }
      :host(.paper-material[elevation="4"]), .paper-material[elevation="4"] {
        @apply --paper-material-elevation-4;
      }
      :host(.paper-material[elevation="5"]), .paper-material[elevation="5"] {
        @apply --paper-material-elevation-5;
      }
    </style>
  </template>
</dom-module>
<dom-module id="paper-button" assetpath="bower_components/paper-button/">
  <template strip-whitespace="">
    <style include="paper-material-styles">
      /* Need to specify the same specificity as the styles imported from paper-material. */
      :host {
        @apply --layout-inline;
        @apply --layout-center-center;
        position: relative;
        box-sizing: border-box;
        min-width: 5.14em;
        margin: 0 0.29em;
        background: transparent;
        -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
        -webkit-tap-highlight-color: transparent;
        font: inherit;
        text-transform: uppercase;
        outline-width: 0;
        border-radius: 3px;
        -moz-user-select: none;
        -ms-user-select: none;
        -webkit-user-select: none;
        user-select: none;
        cursor: pointer;
        z-index: 0;
        padding: 0.7em 0.57em;

        @apply --paper-font-common-base;
        @apply --paper-button;
      }

      :host([elevation="1"]) {
        @apply --paper-material-elevation-1;
      }

      :host([elevation="2"]) {
        @apply --paper-material-elevation-2;
      }

      :host([elevation="3"]) {
        @apply --paper-material-elevation-3;
      }

      :host([elevation="4"]) {
        @apply --paper-material-elevation-4;
      }

      :host([elevation="5"]) {
        @apply --paper-material-elevation-5;
      }

      :host([hidden]) {
        display: none !important;
      }

      :host([raised].keyboard-focus) {
        font-weight: bold;
        @apply --paper-button-raised-keyboard-focus;
      }

      :host(:not([raised]).keyboard-focus) {
        font-weight: bold;
        @apply --paper-button-flat-keyboard-focus;
      }

      :host([disabled]) {
        background: none;
        color: #a8a8a8;
        cursor: auto;
        pointer-events: none;

        @apply --paper-button-disabled;
      }

      :host([disabled][raised]) {
        background: #eaeaea;
      }
        
      :host([animated]) {
        @apply --shadow-transition;
      }

      paper-ripple {
        color: var(--paper-button-ink-color);
      }
    </style>

    <slot></slot>
  </template>

  <script>
    Polymer({
      is: 'paper-button',

      behaviors: [Polymer.PaperButtonBehavior],

      properties: {
        /**
         * If true, the button should be styled with a shadow.
         */
        raised: {
          type: Boolean,
          reflectToAttribute: true,
          value: false,
          observer: '_calculateElevation'
        }
      },

      _calculateElevation: function() {
        if (!this.raised) {
          this._setElevation(0);
        } else {
          Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this);
        }
      }

      /**
      Fired when the animation finishes.
      This is useful if you want to wait until
      the ripple animation finishes to perform some action.

      @event transitionend
      Event param: {{node: Object}} detail Contains the animated node.
      */
    });
  </script>
</dom-module>
<script>
  /** @polymerBehavior Polymer.PaperItemBehavior */
  Polymer.PaperItemBehaviorImpl = {
    hostAttributes: {role: 'option', tabindex: '0'}
  };

  /** @polymerBehavior */
  Polymer.PaperItemBehavior = [
    Polymer.IronButtonState,
    Polymer.IronControlState,
    Polymer.PaperItemBehaviorImpl
  ];
</script>
<dom-module id="paper-item-shared-styles" assetpath="bower_components/paper-item/">
  <template>
    <style>
      :host, .paper-item {
        display: block;
        position: relative;
        min-height: var(--paper-item-min-height, 48px);
        padding: 0px 16px;
      }

      .paper-item {
        @apply --paper-font-subhead;
        border:none;
        outline: none;
        background: white;
        width: 100%;
        text-align: left;
      }

      :host([hidden]), .paper-item[hidden] {
        display: none !important;
      }

      :host(.iron-selected), .paper-item.iron-selected {
        font-weight: var(--paper-item-selected-weight, bold);

        @apply --paper-item-selected;
      }

      :host([disabled]), .paper-item[disabled] {
        color: var(--paper-item-disabled-color, var(--disabled-text-color));

        @apply --paper-item-disabled;
      }

      :host(:focus), .paper-item:focus {
        position: relative;
        outline: 0;

        @apply --paper-item-focused;
      }

      :host(:focus):before, .paper-item:focus:before {
        @apply --layout-fit;

        background: currentColor;
        content: '';
        opacity: var(--dark-divider-opacity);
        pointer-events: none;

        @apply --paper-item-focused-before;
      }
    </style>
  </template>
</dom-module>
<dom-module id="paper-item" assetpath="bower_components/paper-item/">
  <template>
    <style include="paper-item-shared-styles">
      :host {
        @apply --layout-horizontal;
        @apply --layout-center;
        @apply --paper-font-subhead;

        @apply --paper-item;
      }
    </style>
    <slot></slot>
  </template>

  <script>
    Polymer({
      is: 'paper-item',

      behaviors: [Polymer.PaperItemBehavior]
    });
  </script>
</dom-module>
<script>
  /**
   * The `iron-iconset-svg` element allows users to define their own icon sets
   * that contain svg icons. The svg icon elements should be children of the
   * `iron-iconset-svg` element. Multiple icons should be given distinct id's.
   *
   * Using svg elements to create icons has a few advantages over traditional
   * bitmap graphics like jpg or png. Icons that use svg are vector based so
   * they are resolution independent and should look good on any device. They
   * are stylable via css. Icons can be themed, colorized, and even animated.
   *
   * Example:
   *
   *     <iron-iconset-svg name="my-svg-icons" size="24">
   *       <svg>
   *         <defs>
   *           <g id="shape">
   *             <rect x="12" y="0" width="12" height="24" />
   *             <circle cx="12" cy="12" r="12" />
   *           </g>
   *         </defs>
   *       </svg>
   *     </iron-iconset-svg>
   *
   * This will automatically register the icon set "my-svg-icons" to the iconset
   * database.  To use these icons from within another element, make a
   * `iron-iconset` element and call the `byId` method
   * to retrieve a given iconset. To apply a particular icon inside an
   * element use the `applyIcon` method. For example:
   *
   *     iconset.applyIcon(iconNode, 'car');
   *
   * @element iron-iconset-svg
   * @demo demo/index.html
   * @implements {Polymer.Iconset}
   */
  Polymer({
    is: 'iron-iconset-svg',

    properties: {

      /**
       * The name of the iconset.
       */
      name: {type: String, observer: '_nameChanged'},

      /**
       * The size of an individual icon. Note that icons must be square.
       */
      size: {type: Number, value: 24},

      /**
       * Set to true to enable mirroring of icons where specified when they are
       * stamped. Icons that should be mirrored should be decorated with a
       * `mirror-in-rtl` attribute.
       *
       * NOTE: For performance reasons, direction will be resolved once per
       * document per iconset, so moving icons in and out of RTL subtrees will
       * not cause their mirrored state to change.
       */
      rtlMirroring: {type: Boolean, value: false},

      /**
       * Set to true to measure RTL based on the dir attribute on the body or
       * html elements (measured on document.body or document.documentElement as
       * available).
       */
      useGlobalRtlAttribute: {type: Boolean, value: false}
    },

    created: function() {
      this._meta =
          new Polymer.IronMeta({type: 'iconset', key: null, value: null});
    },

    attached: function() {
      this.style.display = 'none';
    },

    /**
     * Construct an array of all icon names in this iconset.
     *
     * @return {!Array} Array of icon names.
     */
    getIconNames: function() {
      this._icons = this._createIconMap();
      return Object.keys(this._icons).map(function(n) {
        return this.name + ':' + n;
      }, this);
    },

    /**
     * Applies an icon to the given element.
     *
     * An svg icon is prepended to the element's shadowRoot if it exists,
     * otherwise to the element itself.
     *
     * If RTL mirroring is enabled, and the icon is marked to be mirrored in
     * RTL, the element will be tested (once and only once ever for each
     * iconset) to determine the direction of the subtree the element is in.
     * This direction will apply to all future icon applications, although only
     * icons marked to be mirrored will be affected.
     *
     * @method applyIcon
     * @param {Element} element Element to which the icon is applied.
     * @param {string} iconName Name of the icon to apply.
     * @return {?Element} The svg element which renders the icon.
     */
    applyIcon: function(element, iconName) {
      // Remove old svg element
      this.removeIcon(element);
      // install new svg element
      var svg = this._cloneIcon(
          iconName, this.rtlMirroring && this._targetIsRTL(element));
      if (svg) {
        // insert svg element into shadow root, if it exists
        var pde = Polymer.dom(element.root || element);
        pde.insertBefore(svg, pde.childNodes[0]);
        return element._svgIcon = svg;
      }
      return null;
    },

    /**
     * Remove an icon from the given element by undoing the changes effected
     * by `applyIcon`.
     *
     * @param {Element} element The element from which the icon is removed.
     */
    removeIcon: function(element) {
      // Remove old svg element
      if (element._svgIcon) {
        Polymer.dom(element.root || element).removeChild(element._svgIcon);
        element._svgIcon = null;
      }
    },

    /**
     * Measures and memoizes the direction of the element. Note that this
     * measurement is only done once and the result is memoized for future
     * invocations.
     */
    _targetIsRTL: function(target) {
      if (this.__targetIsRTL == null) {
        if (this.useGlobalRtlAttribute) {
          var globalElement =
              (document.body && document.body.hasAttribute('dir')) ?
              document.body :
              document.documentElement;

          this.__targetIsRTL = globalElement.getAttribute('dir') === 'rtl';
        } else {
          if (target && target.nodeType !== Node.ELEMENT_NODE) {
            target = target.host;
          }

          this.__targetIsRTL =
              target && window.getComputedStyle(target)['direction'] === 'rtl';
        }
      }

      return this.__targetIsRTL;
    },

    /**
     *
     * When name is changed, register iconset metadata
     *
     */
    _nameChanged: function() {
      this._meta.value = null;
      this._meta.key = this.name;
      this._meta.value = this;

      this.async(function() {
        this.fire('iron-iconset-added', this, {node: window});
      });
    },

    /**
     * Create a map of child SVG elements by id.
     *
     * @return {!Object} Map of id's to SVG elements.
     */
    _createIconMap: function() {
      // Objects chained to Object.prototype (`{}`) have members. Specifically,
      // on FF there is a `watch` method that confuses the icon map, so we
      // need to use a null-based object here.
      var icons = Object.create(null);
      Polymer.dom(this).querySelectorAll('[id]').forEach(function(icon) {
        icons[icon.id] = icon;
      });
      return icons;
    },

    /**
     * Produce installable clone of the SVG element matching `id` in this
     * iconset, or `undefined` if there is no matching element.
     *
     * @return {Element} Returns an installable clone of the SVG element
     * matching `id`.
     */
    _cloneIcon: function(id, mirrorAllowed) {
      // create the icon map on-demand, since the iconset itself has no discrete
      // signal to know when it's children are fully parsed
      this._icons = this._icons || this._createIconMap();
      return this._prepareSvgClone(this._icons[id], this.size, mirrorAllowed);
    },

    /**
     * @param {Element} sourceSvg
     * @param {number} size
     * @param {Boolean} mirrorAllowed
     * @return {Element}
     */
    _prepareSvgClone: function(sourceSvg, size, mirrorAllowed) {
      if (sourceSvg) {
        var content = sourceSvg.cloneNode(true),
            svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
            viewBox =
                content.getAttribute('viewBox') || '0 0 ' + size + ' ' + size,
            cssText =
                'pointer-events: none; display: block; width: 100%; height: 100%;';

        if (mirrorAllowed && content.hasAttribute('mirror-in-rtl')) {
          cssText +=
              '-webkit-transform:scale(-1,1);transform:scale(-1,1);transform-origin:center;';
        }

        svg.setAttribute('viewBox', viewBox);
        svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
        svg.setAttribute('focusable', 'false');
        // TODO(dfreedm): `pointer-events: none` works around
        // https://crbug.com/370136
        // TODO(sjmiles): inline style may not be ideal, but avoids requiring a
        // shadow-root
        svg.style.cssText = cssText;
        svg.appendChild(content).removeAttribute('id');
        return svg;
      }
      return null;
    }

  });
</script>
<iron-iconset-svg name="editor" size="24">
<svg><defs>
<g id="attach-file"><path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"></path></g>
<g id="attach-money"><path d="M11.8 10.9c-2.27-.59-3-1.2-3-2.15 0-1.09 1.01-1.85 2.7-1.85 1.78 0 2.44.85 2.5 2.1h2.21c-.07-1.72-1.12-3.3-3.21-3.81V3h-3v2.16c-1.94.42-3.5 1.68-3.5 3.61 0 2.31 1.91 3.46 4.7 4.13 2.5.6 3 1.48 3 2.41 0 .69-.49 1.79-2.7 1.79-2.06 0-2.87-.92-2.98-2.1h-2.2c.12 2.19 1.76 3.42 3.68 3.83V21h3v-2.15c1.95-.37 3.5-1.5 3.5-3.55 0-2.84-2.43-3.81-4.7-4.4z"></path></g>
<g id="border-all"><path d="M3 3v18h18V3H3zm8 16H5v-6h6v6zm0-8H5V5h6v6zm8 8h-6v-6h6v6zm0-8h-6V5h6v6z"></path></g>
<g id="border-bottom"><path d="M9 11H7v2h2v-2zm4 4h-2v2h2v-2zM9 3H7v2h2V3zm4 8h-2v2h2v-2zM5 3H3v2h2V3zm8 4h-2v2h2V7zm4 4h-2v2h2v-2zm-4-8h-2v2h2V3zm4 0h-2v2h2V3zm2 10h2v-2h-2v2zm0 4h2v-2h-2v2zM5 7H3v2h2V7zm14-4v2h2V3h-2zm0 6h2V7h-2v2zM5 11H3v2h2v-2zM3 21h18v-2H3v2zm2-6H3v2h2v-2z"></path></g>
<g id="border-clear"><path d="M7 5h2V3H7v2zm0 8h2v-2H7v2zm0 8h2v-2H7v2zm4-4h2v-2h-2v2zm0 4h2v-2h-2v2zm-8 0h2v-2H3v2zm0-4h2v-2H3v2zm0-4h2v-2H3v2zm0-4h2V7H3v2zm0-4h2V3H3v2zm8 8h2v-2h-2v2zm8 4h2v-2h-2v2zm0-4h2v-2h-2v2zm0 8h2v-2h-2v2zm0-12h2V7h-2v2zm-8 0h2V7h-2v2zm8-6v2h2V3h-2zm-8 2h2V3h-2v2zm4 16h2v-2h-2v2zm0-8h2v-2h-2v2zm0-8h2V3h-2v2z"></path></g>
<g id="border-color"><path d="M17.75 7L14 3.25l-10 10V17h3.75l10-10zm2.96-2.96c.39-.39.39-1.02 0-1.41L18.37.29c-.39-.39-1.02-.39-1.41 0L15 2.25 18.75 6l1.96-1.96z"></path><path fill-opacity=".36" d="M0 20h24v4H0z"></path></g>
<g id="border-horizontal"><path d="M3 21h2v-2H3v2zM5 7H3v2h2V7zM3 17h2v-2H3v2zm4 4h2v-2H7v2zM5 3H3v2h2V3zm4 0H7v2h2V3zm8 0h-2v2h2V3zm-4 4h-2v2h2V7zm0-4h-2v2h2V3zm6 14h2v-2h-2v2zm-8 4h2v-2h-2v2zm-8-8h18v-2H3v2zM19 3v2h2V3h-2zm0 6h2V7h-2v2zm-8 8h2v-2h-2v2zm4 4h2v-2h-2v2zm4 0h2v-2h-2v2z"></path></g>
<g id="border-inner"><path d="M3 21h2v-2H3v2zm4 0h2v-2H7v2zM5 7H3v2h2V7zM3 17h2v-2H3v2zM9 3H7v2h2V3zM5 3H3v2h2V3zm12 0h-2v2h2V3zm2 6h2V7h-2v2zm0-6v2h2V3h-2zm-4 18h2v-2h-2v2zM13 3h-2v8H3v2h8v8h2v-8h8v-2h-8V3zm6 18h2v-2h-2v2zm0-4h2v-2h-2v2z"></path></g>
<g id="border-left"><path d="M11 21h2v-2h-2v2zm0-4h2v-2h-2v2zm0-12h2V3h-2v2zm0 4h2V7h-2v2zm0 4h2v-2h-2v2zm-4 8h2v-2H7v2zM7 5h2V3H7v2zm0 8h2v-2H7v2zm-4 8h2V3H3v18zM19 9h2V7h-2v2zm-4 12h2v-2h-2v2zm4-4h2v-2h-2v2zm0-14v2h2V3h-2zm0 10h2v-2h-2v2zm0 8h2v-2h-2v2zm-4-8h2v-2h-2v2zm0-8h2V3h-2v2z"></path></g>
<g id="border-outer"><path d="M13 7h-2v2h2V7zm0 4h-2v2h2v-2zm4 0h-2v2h2v-2zM3 3v18h18V3H3zm16 16H5V5h14v14zm-6-4h-2v2h2v-2zm-4-4H7v2h2v-2z"></path></g>
<g id="border-right"><path d="M7 21h2v-2H7v2zM3 5h2V3H3v2zm4 0h2V3H7v2zm0 8h2v-2H7v2zm-4 8h2v-2H3v2zm8 0h2v-2h-2v2zm-8-8h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm8 8h2v-2h-2v2zm4-4h2v-2h-2v2zm4-10v18h2V3h-2zm-4 18h2v-2h-2v2zm0-16h2V3h-2v2zm-4 8h2v-2h-2v2zm0-8h2V3h-2v2zm0 4h2V7h-2v2z"></path></g>
<g id="border-style"><path d="M15 21h2v-2h-2v2zm4 0h2v-2h-2v2zM7 21h2v-2H7v2zm4 0h2v-2h-2v2zm8-4h2v-2h-2v2zm0-4h2v-2h-2v2zM3 3v18h2V5h16V3H3zm16 6h2V7h-2v2z"></path></g>
<g id="border-top"><path d="M7 21h2v-2H7v2zm0-8h2v-2H7v2zm4 0h2v-2h-2v2zm0 8h2v-2h-2v2zm-8-4h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2v-2H3v2zm0-4h2V7H3v2zm8 8h2v-2h-2v2zm8-8h2V7h-2v2zm0 4h2v-2h-2v2zM3 3v2h18V3H3zm16 14h2v-2h-2v2zm-4 4h2v-2h-2v2zM11 9h2V7h-2v2zm8 12h2v-2h-2v2zm-4-8h2v-2h-2v2z"></path></g>
<g id="border-vertical"><path d="M3 9h2V7H3v2zm0-4h2V3H3v2zm4 16h2v-2H7v2zm0-8h2v-2H7v2zm-4 0h2v-2H3v2zm0 8h2v-2H3v2zm0-4h2v-2H3v2zM7 5h2V3H7v2zm12 12h2v-2h-2v2zm-8 4h2V3h-2v18zm8 0h2v-2h-2v2zm0-8h2v-2h-2v2zm0-10v2h2V3h-2zm0 6h2V7h-2v2zm-4-4h2V3h-2v2zm0 16h2v-2h-2v2zm0-8h2v-2h-2v2z"></path></g>
<g id="bubble-chart"><circle cx="7.2" cy="14.4" r="3.2"></circle><circle cx="14.8" cy="18" r="2"></circle><circle cx="15.2" cy="8.8" r="4.8"></circle></g>
<g id="drag-handle"><path d="M20 9H4v2h16V9zM4 15h16v-2H4v2z"></path></g>
<g id="format-align-center"><path d="M7 15v2h10v-2H7zm-4 6h18v-2H3v2zm0-8h18v-2H3v2zm4-6v2h10V7H7zM3 3v2h18V3H3z"></path></g>
<g id="format-align-justify"><path d="M3 21h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18v-2H3v2zm0-4h18V7H3v2zm0-6v2h18V3H3z"></path></g>
<g id="format-align-left"><path d="M15 15H3v2h12v-2zm0-8H3v2h12V7zM3 13h18v-2H3v2zm0 8h18v-2H3v2zM3 3v2h18V3H3z"></path></g>
<g id="format-align-right"><path d="M3 21h18v-2H3v2zm6-4h12v-2H9v2zm-6-4h18v-2H3v2zm6-4h12V7H9v2zM3 3v2h18V3H3z"></path></g>
<g id="format-bold"><path d="M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z"></path></g>
<g id="format-clear"><path d="M3.27 5L2 6.27l6.97 6.97L6.5 19h3l1.57-3.66L16.73 21 18 19.73 3.55 5.27 3.27 5zM6 5v.18L8.82 8h2.4l-.72 1.68 2.1 2.1L14.21 8H20V5H6z"></path></g>
<g id="format-color-fill"><path d="M16.56 8.94L7.62 0 6.21 1.41l2.38 2.38-5.15 5.15c-.59.59-.59 1.54 0 2.12l5.5 5.5c.29.29.68.44 1.06.44s.77-.15 1.06-.44l5.5-5.5c.59-.58.59-1.53 0-2.12zM5.21 10L10 5.21 14.79 10H5.21zM19 11.5s-2 2.17-2 3.5c0 1.1.9 2 2 2s2-.9 2-2c0-1.33-2-3.5-2-3.5z"></path><path fill-opacity=".36" d="M0 20h24v4H0z"></path></g>
<g id="format-color-reset"><path d="M18 14c0-4-6-10.8-6-10.8s-1.33 1.51-2.73 3.52l8.59 8.59c.09-.42.14-.86.14-1.31zm-.88 3.12L12.5 12.5 5.27 5.27 4 6.55l3.32 3.32C6.55 11.32 6 12.79 6 14c0 3.31 2.69 6 6 6 1.52 0 2.9-.57 3.96-1.5l2.63 2.63 1.27-1.27-2.74-2.74z"></path></g>
<g id="format-color-text"><path fill-opacity=".36" d="M0 20h24v4H0z"></path><path d="M11 3L5.5 17h2.25l1.12-3h6.25l1.12 3h2.25L13 3h-2zm-1.38 9L12 5.67 14.38 12H9.62z"></path></g>
<g id="format-indent-decrease"><path d="M11 17h10v-2H11v2zm-8-5l4 4V8l-4 4zm0 9h18v-2H3v2zM3 3v2h18V3H3zm8 6h10V7H11v2zm0 4h10v-2H11v2z"></path></g>
<g id="format-indent-increase"><path d="M3 21h18v-2H3v2zM3 8v8l4-4-4-4zm8 9h10v-2H11v2zM3 3v2h18V3H3zm8 6h10V7H11v2zm0 4h10v-2H11v2z"></path></g>
<g id="format-italic"><path d="M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z"></path></g>
<g id="format-line-spacing"><path d="M6 7h2.5L5 3.5 1.5 7H4v10H1.5L5 20.5 8.5 17H6V7zm4-2v2h12V5H10zm0 14h12v-2H10v2zm0-6h12v-2H10v2z"></path></g>
<g id="format-list-bulleted"><path d="M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z"></path></g>
<g id="format-list-numbered"><path d="M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z"></path></g>
<g id="format-paint"><path d="M18 4V3c0-.55-.45-1-1-1H5c-.55 0-1 .45-1 1v4c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V6h1v4H9v11c0 .55.45 1 1 1h2c.55 0 1-.45 1-1v-9h8V4h-3z"></path></g>
<g id="format-quote"><path d="M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z"></path></g>
<g id="format-shapes"><path d="M23 7V1h-6v2H7V1H1v6h2v10H1v6h6v-2h10v2h6v-6h-2V7h2zM3 3h2v2H3V3zm2 18H3v-2h2v2zm12-2H7v-2H5V7h2V5h10v2h2v10h-2v2zm4 2h-2v-2h2v2zM19 5V3h2v2h-2zm-5.27 9h-3.49l-.73 2H7.89l3.4-9h1.4l3.41 9h-1.63l-.74-2zm-3.04-1.26h2.61L12 8.91l-1.31 3.83z"></path></g>
<g id="format-size"><path d="M9 4v3h5v12h3V7h5V4H9zm-6 8h3v7h3v-7h3V9H3v3z"></path></g>
<g id="format-strikethrough"><path d="M10 19h4v-3h-4v3zM5 4v3h5v3h4V7h5V4H5zM3 14h18v-2H3v2z"></path></g>
<g id="format-textdirection-l-to-r"><path d="M9 10v5h2V4h2v11h2V4h2V2H9C6.79 2 5 3.79 5 6s1.79 4 4 4zm12 8l-4-4v3H5v2h12v3l4-4z"></path></g>
<g id="format-textdirection-r-to-l"><path d="M10 10v5h2V4h2v11h2V4h2V2h-8C7.79 2 6 3.79 6 6s1.79 4 4 4zm-2 7v-3l-4 4 4 4v-3h12v-2H8z"></path></g>
<g id="format-underlined"><path d="M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z"></path></g>
<g id="functions"><path d="M18 4H6v2l6.5 6L6 18v2h12v-3h-7l5-5-5-5h7z"></path></g>
<g id="highlight"><path d="M6 14l3 3v5h6v-5l3-3V9H6zm5-12h2v3h-2zM3.5 5.875L4.914 4.46l2.12 2.122L5.62 7.997zm13.46.71l2.123-2.12 1.414 1.414L18.375 8z"></path></g>
<g id="insert-chart"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z"></path></g>
<g id="insert-comment"><path d="M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z"></path></g>
<g id="insert-drive-file"><path d="M6 2c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6H6zm7 7V3.5L18.5 9H13z"></path></g>
<g id="insert-emoticon"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z"></path></g>
<g id="insert-invitation"><path d="M17 12h-5v5h5v-5zM16 1v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2h-1V1h-2zm3 18H5V8h14v11z"></path></g>
<g id="insert-link"><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"></path></g>
<g id="insert-photo"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"></path></g>
<g id="linear-scale"><path d="M19.5 9.5c-1.03 0-1.9.62-2.29 1.5h-2.92c-.39-.88-1.26-1.5-2.29-1.5s-1.9.62-2.29 1.5H6.79c-.39-.88-1.26-1.5-2.29-1.5C3.12 9.5 2 10.62 2 12s1.12 2.5 2.5 2.5c1.03 0 1.9-.62 2.29-1.5h2.92c.39.88 1.26 1.5 2.29 1.5s1.9-.62 2.29-1.5h2.92c.39.88 1.26 1.5 2.29 1.5 1.38 0 2.5-1.12 2.5-2.5s-1.12-2.5-2.5-2.5z"></path></g>
<g id="merge-type"><path d="M17 20.41L18.41 19 15 15.59 13.59 17 17 20.41zM7.5 8H11v5.59L5.59 19 7 20.41l6-6V8h3.5L12 3.5 7.5 8z"></path></g>
<g id="mode-comment"><path d="M21.99 4c0-1.1-.89-2-1.99-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4-.01-18z"></path></g>
<g id="mode-edit"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"></path></g>
<g id="monetization-on"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1.41 16.09V20h-2.67v-1.93c-1.71-.36-3.16-1.46-3.27-3.4h1.96c.1 1.05.82 1.87 2.65 1.87 1.96 0 2.4-.98 2.4-1.59 0-.83-.44-1.61-2.67-2.14-2.48-.6-4.18-1.62-4.18-3.67 0-1.72 1.39-2.84 3.11-3.21V4h2.67v1.95c1.86.45 2.79 1.86 2.85 3.39H14.3c-.05-1.11-.64-1.87-2.22-1.87-1.5 0-2.4.68-2.4 1.64 0 .84.65 1.39 2.67 1.91s4.18 1.39 4.18 3.91c-.01 1.83-1.38 2.83-3.12 3.16z"></path></g>
<g id="money-off"><path d="M12.5 6.9c1.78 0 2.44.85 2.5 2.1h2.21c-.07-1.72-1.12-3.3-3.21-3.81V3h-3v2.16c-.53.12-1.03.3-1.48.54l1.47 1.47c.41-.17.91-.27 1.51-.27zM5.33 4.06L4.06 5.33 7.5 8.77c0 2.08 1.56 3.21 3.91 3.91l3.51 3.51c-.34.48-1.05.91-2.42.91-2.06 0-2.87-.92-2.98-2.1h-2.2c.12 2.19 1.76 3.42 3.68 3.83V21h3v-2.15c.96-.18 1.82-.55 2.45-1.12l2.22 2.22 1.27-1.27L5.33 4.06z"></path></g>
<g id="multiline-chart"><path d="M22 6.92l-1.41-1.41-2.85 3.21C15.68 6.4 12.83 5 9.61 5 6.72 5 4.07 6.16 2 8l1.42 1.42C5.12 7.93 7.27 7 9.61 7c2.74 0 5.09 1.26 6.77 3.24l-2.88 3.24-4-4L2 16.99l1.5 1.5 6-6.01 4 4 4.05-4.55c.75 1.35 1.25 2.9 1.44 4.55H21c-.22-2.3-.95-4.39-2.04-6.14L22 6.92z"></path></g>
<g id="pie-chart"><path d="M11 2v20c-5.07-.5-9-4.79-9-10s3.93-9.5 9-10zm2.03 0v8.99H22c-.47-4.74-4.24-8.52-8.97-8.99zm0 11.01V22c4.74-.47 8.5-4.25 8.97-8.99h-8.97z"></path></g>
<g id="pie-chart-outlined"><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm1 2.07c3.61.45 6.48 3.33 6.93 6.93H13V4.07zM4 12c0-4.06 3.07-7.44 7-7.93v15.87c-3.93-.5-7-3.88-7-7.94zm9 7.93V13h6.93c-.45 3.61-3.32 6.48-6.93 6.93z"></path></g>
<g id="publish"><path d="M5 4v2h14V4H5zm0 10h4v6h6v-6h4l-7-7-7 7z"></path></g>
<g id="short-text"><path d="M4 9h16v2H4zm0 4h10v2H4z"></path></g>
<g id="show-chart"><path d="M3.5 18.49l6-6.01 4 4L22 6.92l-1.41-1.41-7.09 7.97-4-4L2 16.99z"></path></g>
<g id="space-bar"><path d="M18 9v4H6V9H4v6h16V9z"></path></g>
<g id="strikethrough-s"><path d="M7.24 8.75c-.26-.48-.39-1.03-.39-1.67 0-.61.13-1.16.4-1.67.26-.5.63-.93 1.11-1.29.48-.35 1.05-.63 1.7-.83.66-.19 1.39-.29 2.18-.29.81 0 1.54.11 2.21.34.66.22 1.23.54 1.69.94.47.4.83.88 1.08 1.43.25.55.38 1.15.38 1.81h-3.01c0-.31-.05-.59-.15-.85-.09-.27-.24-.49-.44-.68-.2-.19-.45-.33-.75-.44-.3-.1-.66-.16-1.06-.16-.39 0-.74.04-1.03.13-.29.09-.53.21-.72.36-.19.16-.34.34-.44.55-.1.21-.15.43-.15.66 0 .48.25.88.74 1.21.38.25.77.48 1.41.7H7.39c-.05-.08-.11-.17-.15-.25zM21 12v-2H3v2h9.62c.18.07.4.14.55.2.37.17.66.34.87.51.21.17.35.36.43.57.07.2.11.43.11.69 0 .23-.05.45-.14.66-.09.2-.23.38-.42.53-.19.15-.42.26-.71.35-.29.08-.63.13-1.01.13-.43 0-.83-.04-1.18-.13s-.66-.23-.91-.42c-.25-.19-.45-.44-.59-.75-.14-.31-.25-.76-.25-1.21H6.4c0 .55.08 1.13.24 1.58.16.45.37.85.65 1.21.28.35.6.66.98.92.37.26.78.48 1.22.65.44.17.9.3 1.38.39.48.08.96.13 1.44.13.8 0 1.53-.09 2.18-.28s1.21-.45 1.67-.79c.46-.34.82-.77 1.07-1.27s.38-1.07.38-1.71c0-.6-.1-1.14-.31-1.61-.05-.11-.11-.23-.17-.33H21z"></path></g>
<g id="text-fields"><path d="M2.5 4v3h5v12h3V7h5V4h-13zm19 5h-9v3h3v7h3v-7h3V9z"></path></g>
<g id="title"><path d="M5 4v3h5.5v12h3V7H19V4z"></path></g>
<g id="vertical-align-bottom"><path d="M16 13h-3V3h-2v10H8l4 4 4-4zM4 19v2h16v-2H4z"></path></g>
<g id="vertical-align-center"><path d="M8 19h3v4h2v-4h3l-4-4-4 4zm8-14h-3V1h-2v4H8l4 4 4-4zM4 11v2h16v-2H4z"></path></g>
<g id="vertical-align-top"><path d="M8 11h3v10h2V11h3l-4-4-4 4zM4 3v2h16V3H4z"></path></g>
<g id="wrap-text"><path d="M4 19h6v-2H4v2zM20 5H4v2h16V5zm-3 6H4v2h13.25c1.1 0 2 .9 2 2s-.9 2-2 2H15v-2l-3 3 3 3v-2h2c2.21 0 4-1.79 4-4s-1.79-4-4-4z"></path></g>
</defs></svg>
</iron-iconset-svg>
<dom-module id="iron-collapse" assetpath="bower_components/iron-collapse/">

  <template>

    <style>
      :host {
        display: block;
        transition-duration: var(--iron-collapse-transition-duration, 300ms);
        /* Safari 10 needs this property prefixed to correctly apply the custom property */
        -webkit-transition-duration: var(--iron-collapse-transition-duration, 300ms);
        overflow: visible;
      }

      :host(.iron-collapse-closed) {
        display: none;
      }

      :host(:not(.iron-collapse-opened)) {
        overflow: hidden;
      }
    </style>

    <slot></slot>

  </template>

</dom-module>

<script>
  Polymer({

    is: 'iron-collapse',

    behaviors: [Polymer.IronResizableBehavior],

    properties: {

      /**
       * If true, the orientation is horizontal; otherwise is vertical.
       *
       * @attribute horizontal
       */
      horizontal: {type: Boolean, value: false, observer: '_horizontalChanged'},

      /**
       * Set opened to true to show the collapse element and to false to hide it.
       *
       * @attribute opened
       */
      opened:
          {type: Boolean, value: false, notify: true, observer: '_openedChanged'},

      /**
       * When true, the element is transitioning its opened state. When false,
       * the element has finished opening/closing.
       *
       * @attribute transitioning
       */
      transitioning: {type: Boolean, notify: true, readOnly: true},

      /**
       * Set noAnimation to true to disable animations.
       *
       * @attribute noAnimation
       */
      noAnimation: {type: Boolean},

      /**
       * Stores the desired size of the collapse body.
       * @private
       */
      _desiredSize: {type: String, value: ''}
    },

    get dimension() {
      return this.horizontal ? 'width' : 'height';
    },

    /**
     * `maxWidth` or `maxHeight`.
     * @private
     */
    get _dimensionMax() {
      return this.horizontal ? 'maxWidth' : 'maxHeight';
    },

    /**
     * `max-width` or `max-height`.
     * @private
     */
    get _dimensionMaxCss() {
      return this.horizontal ? 'max-width' : 'max-height';
    },

    hostAttributes: {
      role: 'group',
      'aria-hidden': 'true',
    },

    listeners: {transitionend: '_onTransitionEnd'},

    /**
     * Toggle the opened state.
     *
     * @method toggle
     */
    toggle: function() {
      this.opened = !this.opened;
    },

    show: function() {
      this.opened = true;
    },

    hide: function() {
      this.opened = false;
    },

    /**
     * Updates the size of the element.
     * @param {string} size The new value for `maxWidth`/`maxHeight` as css property value, usually `auto` or `0px`.
     * @param {boolean=} animated if `true` updates the size with an animation, otherwise without.
     */
    updateSize: function(size, animated) {
      // Consider 'auto' as '', to take full size.
      size = size === 'auto' ? '' : size;

      var willAnimate = animated && !this.noAnimation && this.isAttached &&
          this._desiredSize !== size;

      this._desiredSize = size;

      this._updateTransition(false);
      // If we can animate, must do some prep work.
      if (willAnimate) {
        // Animation will start at the current size.
        var startSize = this._calcSize();
        // For `auto` we must calculate what is the final size for the animation.
        // After the transition is done, _transitionEnd will set the size back to
        // `auto`.
        if (size === '') {
          this.style[this._dimensionMax] = '';
          size = this._calcSize();
        }
        // Go to startSize without animation.
        this.style[this._dimensionMax] = startSize;
        // Force layout to ensure transition will go. Set scrollTop to itself
        // so that compilers won't remove it.
        this.scrollTop = this.scrollTop;
        // Enable animation.
        this._updateTransition(true);
        // If final size is the same as startSize it will not animate.
        willAnimate = (size !== startSize);
      }
      // Set the final size.
      this.style[this._dimensionMax] = size;
      // If it won't animate, call transitionEnd to set correct classes.
      if (!willAnimate) {
        this._transitionEnd();
      }
    },

    /**
     * enableTransition() is deprecated, but left over so it doesn't break
     * existing code. Please use `noAnimation` property instead.
     *
     * @method enableTransition
     * @deprecated since version 1.0.4
     */
    enableTransition: function(enabled) {
      Polymer.Base._warn(
          '`enableTransition()` is deprecated, use `noAnimation` instead.');
      this.noAnimation = !enabled;
    },

    _updateTransition: function(enabled) {
      this.style.transitionDuration = (enabled && !this.noAnimation) ? '' : '0s';
    },

    _horizontalChanged: function() {
      this.style.transitionProperty = this._dimensionMaxCss;
      var otherDimension =
          this._dimensionMax === 'maxWidth' ? 'maxHeight' : 'maxWidth';
      this.style[otherDimension] = '';
      this.updateSize(this.opened ? 'auto' : '0px', false);
    },

    _openedChanged: function() {
      this.setAttribute('aria-hidden', !this.opened);

      this._setTransitioning(true);
      this.toggleClass('iron-collapse-closed', false);
      this.toggleClass('iron-collapse-opened', false);
      this.updateSize(this.opened ? 'auto' : '0px', true);

      // Focus the current collapse.
      if (this.opened) {
        this.focus();
      }
    },

    _transitionEnd: function() {
      this.style[this._dimensionMax] = this._desiredSize;
      this.toggleClass('iron-collapse-closed', !this.opened);
      this.toggleClass('iron-collapse-opened', this.opened);
      this._updateTransition(false);
      this.notifyResize();
      this._setTransitioning(false);
    },

    _onTransitionEnd: function(event) {
      if (Polymer.dom(event).rootTarget === this) {
        this._transitionEnd();
      }
    },

    _calcSize: function() {
      return this.getBoundingClientRect()[this.dimension] + 'px';
    }

  });
</script>
<script>
  'use strict';

  Polymer({
    is: 'iron-request',

    hostAttributes: {
      hidden: true
    },

    properties: {

      /**
       * A reference to the XMLHttpRequest instance used to generate the
       * network request.
       *
       * @type {XMLHttpRequest}
       */
      xhr: {
        type: Object,
        notify: true,
        readOnly: true,
        value: function() {
          return new XMLHttpRequest();
        }
      },

      /**
       * A reference to the parsed response body, if the `xhr` has completely
       * resolved.
       *
       * @type {*}
       * @default null
       */
      response: {
        type: Object,
        notify: true,
        readOnly: true,
        value: function() {
          return null;
        }
      },

      /**
       * A reference to the status code, if the `xhr` has completely resolved.
       */
      status: {
        type: Number,
        notify: true,
        readOnly: true,
        value: 0
      },

      /**
       * A reference to the status text, if the `xhr` has completely resolved.
       */
      statusText: {
        type: String,
        notify: true,
        readOnly: true,
        value: ''
      },

      /**
       * A promise that resolves when the `xhr` response comes back, or rejects
       * if there is an error before the `xhr` completes.
       * The resolve callback is called with the original request as an argument.
       * By default, the reject callback is called with an `Error` as an argument.
       * If `rejectWithRequest` is true, the reject callback is called with an
       * object with two keys: `request`, the original request, and `error`, the
       * error object.
       *
       * @type {Promise}
       */
      completes: {
        type: Object,
        readOnly: true,
        notify: true,
        value: function() {
          return new Promise(function(resolve, reject) {
            this.resolveCompletes = resolve;
            this.rejectCompletes = reject;
          }.bind(this));
        }
      },

      /**
       * An object that contains progress information emitted by the XHR if
       * available.
       *
       * @default {}
       */
      progress: {
        type: Object,
        notify: true,
        readOnly: true,
        value: function() {
          return {};
        }
      },

      /**
       * Aborted will be true if an abort of the request is attempted.
       */
      aborted: {
        type: Boolean,
        notify: true,
        readOnly: true,
        value: false,
      },

      /**
       * Errored will be true if the browser fired an error event from the
       * XHR object (mainly network errors).
       */
      errored: {
        type: Boolean,
        notify: true,
        readOnly: true,
        value: false
      },

      /**
       * TimedOut will be true if the XHR threw a timeout event.
       */
      timedOut: {
        type: Boolean,
        notify: true,
        readOnly: true,
        value: false
      }
    },

    /**
     * Succeeded is true if the request succeeded. The request succeeded if it
     * loaded without error, wasn't aborted, and the status code is ≥ 200, and
     * < 300, or if the status code is 0.
     *
     * The status code 0 is accepted as a success because some schemes - e.g.
     * file:// - don't provide status codes.
     *
     * @return {boolean}
     */
    get succeeded() {
      if (this.errored || this.aborted || this.timedOut) {
        return false;
      }
      var status = this.xhr.status || 0;

      // Note: if we are using the file:// protocol, the status code will be 0
      // for all outcomes (successful or otherwise).
      return status === 0 ||
        (status >= 200 && status < 300);
    },

    /**
     * Sends an HTTP request to the server and returns a promise (see the `completes`
     * property for details).
     *
     * The handling of the `body` parameter will vary based on the Content-Type
     * header. See the docs for iron-ajax's `body` property for details.
     *
     * @param {{
     *   url: string,
     *   method: (string|undefined),
     *   async: (boolean|undefined),
     *   body: (ArrayBuffer|ArrayBufferView|Blob|Document|FormData|null|string|undefined|Object),
     *   headers: (Object|undefined),
     *   handleAs: (string|undefined),
     *   jsonPrefix: (string|undefined),
     *   withCredentials: (boolean|undefined),
     *   timeout: (Number|undefined),
     *   rejectWithRequest: (boolean|undefined)}} options -
     *   - url The url to which the request is sent.
     *   - method The HTTP method to use, default is GET.
     *   - async By default, all requests are sent asynchronously. To send synchronous requests,
     *         set to false.
     *   -  body The content for the request body for POST method.
     *   -  headers HTTP request headers.
     *   -  handleAs The response type. Default is 'text'.
     *   -  withCredentials Whether or not to send credentials on the request. Default is false.
     *   -  timeout - Timeout for request, in milliseconds.
     *   -  rejectWithRequest Set to true to include the request object with promise rejections.
     * @return {Promise}
     */
    send: function(options) {
      var xhr = this.xhr;

      if (xhr.readyState > 0) {
        return null;
      }

      xhr.addEventListener('progress', function(progress) {
        this._setProgress({
          lengthComputable: progress.lengthComputable,
          loaded: progress.loaded,
          total: progress.total
        });

        // Webcomponents v1 spec does not fire *-changed events when not connected
        this.fire('iron-request-progress-changed', { value: this.progress });
      }.bind(this))

      xhr.addEventListener('error', function(error) {
        this._setErrored(true);
        this._updateStatus();
        var response = options.rejectWithRequest ? {
          error: error,
          request: this
        } : error;
        this.rejectCompletes(response);
      }.bind(this));

      xhr.addEventListener('timeout', function(error) {
        this._setTimedOut(true);
        this._updateStatus();
        var response = options.rejectWithRequest ? {
          error: error,
          request: this
        } : error;
        this.rejectCompletes(response);
      }.bind(this));

      xhr.addEventListener('abort', function() {
        this._setAborted(true);
        this._updateStatus();
        var error = new Error('Request aborted.');
        var response = options.rejectWithRequest ? {
          error: error,
          request: this
        } : error;
        this.rejectCompletes(response);
      }.bind(this));

      // Called after all of the above.
      xhr.addEventListener('loadend', function() {
        this._updateStatus();
        this._setResponse(this.parseResponse());

        if (!this.succeeded) {
          var error = new Error('The request failed with status code: ' + this.xhr.status);
          var response = options.rejectWithRequest ? {
            error: error,
            request: this
          } : error;
          this.rejectCompletes(response);
          return;
        }

        this.resolveCompletes(this);
      }.bind(this));

      this.url = options.url;
      var isXHRAsync = options.async !== false;
      xhr.open(
        options.method || 'GET',
        options.url,
        isXHRAsync
      );

      var acceptType = {
        'json': 'application/json',
        'text': 'text/plain',
        'html': 'text/html',
        'xml': 'application/xml',
        'arraybuffer': 'application/octet-stream'
      }[options.handleAs];
      var headers = options.headers || Object.create(null);
      var newHeaders = Object.create(null);
      for (var key in headers) {
        newHeaders[key.toLowerCase()] = headers[key];
      }
      headers = newHeaders;

      if (acceptType && !headers['accept']) {
        headers['accept'] = acceptType;
      }
      Object.keys(headers).forEach(function(requestHeader) {
        if (/[A-Z]/.test(requestHeader)) {
          Polymer.Base._error('Headers must be lower case, got', requestHeader);
        }
        xhr.setRequestHeader(
          requestHeader,
          headers[requestHeader]
        );
      }, this);

      if (isXHRAsync) {
        xhr.timeout = options.timeout;

        var handleAs = options.handleAs;

        // If a JSON prefix is present, the responseType must be 'text' or the
        // browser won’t be able to parse the response.
        if (!!options.jsonPrefix || !handleAs) {
          handleAs = 'text';
        }

        // In IE, `xhr.responseType` is an empty string when the response
        // returns. Hence, caching it as `xhr._responseType`.
        xhr.responseType = xhr._responseType = handleAs;

        // Cache the JSON prefix, if it exists.
        if (!!options.jsonPrefix) {
          xhr._jsonPrefix = options.jsonPrefix;
        }
      }

      xhr.withCredentials = !!options.withCredentials;


      var body = this._encodeBodyObject(options.body, headers['content-type']);

      xhr.send(
        /** @type {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|
                   null|string|undefined} */
        (body));

      return this.completes;
    },

    /**
     * Attempts to parse the response body of the XHR. If parsing succeeds,
     * the value returned will be deserialized based on the `responseType`
     * set on the XHR.
     *
     * @return {*} The parsed response,
     * or undefined if there was an empty response or parsing failed.
     */
    parseResponse: function() {
      var xhr = this.xhr;
      var responseType = xhr.responseType || xhr._responseType;
      var preferResponseText = !this.xhr.responseType;
      var prefixLen = (xhr._jsonPrefix && xhr._jsonPrefix.length) || 0;

      try {
        switch (responseType) {
          case 'json':
            // If the xhr object doesn't have a natural `xhr.responseType`,
            // we can assume that the browser hasn't parsed the response for us,
            // and so parsing is our responsibility. Likewise if response is
            // undefined, as there's no way to encode undefined in JSON.
            if (preferResponseText || xhr.response === undefined) {
              // Try to emulate the JSON section of the response body section of
              // the spec: https://xhr.spec.whatwg.org/#response-body
              // That is to say, we try to parse as JSON, but if anything goes
              // wrong return null.
              try {
                return JSON.parse(xhr.responseText);
              } catch (_) {
                console.warn('Failed to parse JSON sent from ' + xhr.responseURL);
                return null;
              }
            }

            return xhr.response;
          case 'xml':
            return xhr.responseXML;
          case 'blob':
          case 'document':
          case 'arraybuffer':
            return xhr.response;
          case 'text':
          default: {
            // If `prefixLen` is set, it implies the response should be parsed
            // as JSON once the prefix of length `prefixLen` is stripped from
            // it. Emulate the behavior above where null is returned on failure
            // to parse.
            if (prefixLen) {
              try {
                return JSON.parse(xhr.responseText.substring(prefixLen));
              } catch (_) {
                console.warn('Failed to parse JSON sent from ' + xhr.responseURL);
                return null;
              }
            }
            return xhr.responseText;
          }
        }
      } catch (e) {
        this.rejectCompletes(new Error('Could not parse response. ' + e.message));
      }
    },

    /**
     * Aborts the request.
     */
    abort: function() {
      this._setAborted(true);
      this.xhr.abort();
    },

    /**
     * @param {*} body The given body of the request to try and encode.
     * @param {?string} contentType The given content type, to infer an encoding
     *     from.
     * @return {*} Either the encoded body as a string, if successful,
     *     or the unaltered body object if no encoding could be inferred.
     */
    _encodeBodyObject: function(body, contentType) {
      if (typeof body == 'string') {
        return body;  // Already encoded.
      }
      var bodyObj = /** @type {Object} */ (body);
      switch(contentType) {
        case('application/json'):
          return JSON.stringify(bodyObj);
        case('application/x-www-form-urlencoded'):
          return this._wwwFormUrlEncode(bodyObj);
      }
      return body;
    },

    /**
     * @param {Object} object The object to encode as x-www-form-urlencoded.
     * @return {string} .
     */
    _wwwFormUrlEncode: function(object) {
      if (!object) {
        return '';
      }
      var pieces = [];
      Object.keys(object).forEach(function(key) {
        // TODO(rictic): handle array values here, in a consistent way with
        //   iron-ajax params.
        pieces.push(
            this._wwwFormUrlEncodePiece(key) + '=' +
            this._wwwFormUrlEncodePiece(object[key]));
      }, this);
      return pieces.join('&');
    },

    /**
     * @param {*} str A key or value to encode as x-www-form-urlencoded.
     * @return {string} .
     */
    _wwwFormUrlEncodePiece: function(str) {
      // Spec says to normalize newlines to \r\n and replace %20 spaces with +.
      // jQuery does this as well, so this is likely to be widely compatible.
      if (str === null || str === undefined || !str.toString) {
        return '';
      }

      return encodeURIComponent(str.toString().replace(/\r?\n/g, '\r\n'))
        .replace(/%20/g, '+');
    },

    /**
     * Updates the status code and status text.
     */
    _updateStatus: function() {
      this._setStatus(this.xhr.status);
      this._setStatusText((this.xhr.statusText === undefined) ? '' : this.xhr.statusText);
    }
  });
</script>
<script>
  'use strict';

  Polymer({

    is: 'iron-ajax',

    /**
     * Fired before a request is sent.
     *
     * @event iron-ajax-presend
     */

    /**
     * Fired when a request is sent.
     *
     * @event request
     */

    /**
     * Fired when a request is sent.
     *
     * @event iron-ajax-request
     */

    /**
     * Fired when a response is received.
     *
     * @event response
     */

    /**
     * Fired when a response is received.
     *
     * @event iron-ajax-response
     */

    /**
     * Fired when an error is received.
     *
     * @event error
     */

    /**
     * Fired when an error is received.
     *
     * @event iron-ajax-error
     */

    hostAttributes: {
      hidden: true
    },

    properties: {
      /**
       * The URL target of the request.
       */
      url: {
        type: String
      },

      /**
       * An object that contains query parameters to be appended to the
       * specified `url` when generating a request. If you wish to set the body
       * content when making a POST request, you should use the `body` property
       * instead.
       */
      params: {
        type: Object,
        value: function() {
          return {};
        }
      },

      /**
       * The HTTP method to use such as 'GET', 'POST', 'PUT', or 'DELETE'.
       * Default is 'GET'.
       */
      method: {
        type: String,
        value: 'GET'
      },

      /**
       * HTTP request headers to send.
       *
       * Example:
       *
       *     <iron-ajax
       *         auto
       *         url="http://somesite.com"
       *         headers='{"X-Requested-With": "XMLHttpRequest"}'
       *         handle-as="json"></iron-ajax>
       *
       * Note: setting a `Content-Type` header here will override the value
       * specified by the `contentType` property of this element.
       */
      headers: {
        type: Object,
        value: function() {
          return {};
        }
      },

      /**
       * Content type to use when sending data. If the `contentType` property
       * is set and a `Content-Type` header is specified in the `headers`
       * property, the `headers` property value will take precedence.
       *
       * Varies the handling of the `body` param.
       */
      contentType: {
        type: String,
        value: null
      },

      /**
       * Body content to send with the request, typically used with "POST"
       * requests.
       *
       * If body is a string it will be sent unmodified.
       *
       * If Content-Type is set to a value listed below, then
       * the body will be encoded accordingly.
       *
       *    * `content-type="application/json"`
       *      * body is encoded like `{"foo":"bar baz","x":1}`
       *    * `content-type="application/x-www-form-urlencoded"`
       *      * body is encoded like `foo=bar+baz&x=1`
       *
       * Otherwise the body will be passed to the browser unmodified, and it
       * will handle any encoding (e.g. for FormData, Blob, ArrayBuffer).
       *
       * @type (ArrayBuffer|ArrayBufferView|Blob|Document|FormData|null|string|undefined|Object)
       */
      body: {
        type: Object,
        value: null
      },

      /**
       * Toggle whether XHR is synchronous or asynchronous. Don't change this
       * to true unless You Know What You Are Doing™.
       */
      sync: {
        type: Boolean,
        value: false
      },

      /**
       * Specifies what data to store in the `response` property, and
       * to deliver as `event.detail.response` in `response` events.
       *
       * One of:
       *
       *    `text`: uses `XHR.responseText`.
       *
       *    `xml`: uses `XHR.responseXML`.
       *
       *    `json`: uses `XHR.responseText` parsed as JSON.
       *
       *    `arraybuffer`: uses `XHR.response`.
       *
       *    `blob`: uses `XHR.response`.
       *
       *    `document`: uses `XHR.response`.
       */
      handleAs: {
        type: String,
        value: 'json'
      },

      /**
       * Set the withCredentials flag on the request.
       */
      withCredentials: {
        type: Boolean,
        value: false
      },

      /**
       * Set the timeout flag on the request.
       */
      timeout: {
        type: Number,
        value: 0
      },

      /**
       * If true, automatically performs an Ajax request when either `url` or
       * `params` changes.
       */
      auto: {
        type: Boolean,
        value: false
      },

      /**
       * If true, error messages will automatically be logged to the console.
       */
      verbose: {
        type: Boolean,
        value: false
      },

      /**
       * The most recent request made by this iron-ajax element.
       *
       * @type {Object|undefined}
       */
      lastRequest: {
        type: Object,
        notify: true,
        readOnly: true
      },

      /**
       * The `progress` property of this element's `lastRequest`.
       *
       * @type {Object|undefined}
       */
      lastProgress: {
        type: Object,
        notify: true,
        readOnly: true
      },

      /**
       * True while lastRequest is in flight.
       */
      loading: {
        type: Boolean,
        notify: true,
        readOnly: true
      },

      /**
       * lastRequest's response.
       *
       * Note that lastResponse and lastError are set when lastRequest finishes,
       * so if loading is true, then lastResponse and lastError will correspond
       * to the result of the previous request.
       *
       * The type of the response is determined by the value of `handleAs` at
       * the time that the request was generated.
       *
       * @type {Object}
       */
      lastResponse: {
        type: Object,
        notify: true,
        readOnly: true
      },

      /**
       * lastRequest's error, if any.
       *
       * @type {Object}
       */
      lastError: {
        type: Object,
        notify: true,
        readOnly: true
      },

      /**
       * An Array of all in-flight requests originating from this iron-ajax
       * element.
       */
      activeRequests: {
        type: Array,
        notify: true,
        readOnly: true,
        value: function() {
          return [];
        }
      },

      /**
       * Length of time in milliseconds to debounce multiple automatically generated requests.
       */
      debounceDuration: {
        type: Number,
        value: 0,
        notify: true
      },

      /**
       * Prefix to be stripped from a JSON response before parsing it.
       *
       * In order to prevent an attack using CSRF with Array responses
       * (http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx/)
       * many backends will mitigate this by prefixing all JSON response bodies
       * with a string that would be nonsensical to a JavaScript parser.
       *
       */
      jsonPrefix: {
        type: String,
        value: ''
      },

      /**
       * By default, iron-ajax's events do not bubble. Setting this attribute will cause its
       * request and response events as well as its iron-ajax-request, -response,  and -error
       * events to bubble to the window object. The vanilla error event never bubbles when
       * using shadow dom even if this.bubbles is true because a scoped flag is not passed with
       * it (first link) and because the shadow dom spec did not used to allow certain events,
       * including events named error, to leak outside of shadow trees (second link).
       * https://www.w3.org/TR/shadow-dom/#scoped-flag
       * https://www.w3.org/TR/2015/WD-shadow-dom-20151215/#events-that-are-not-leaked-into-ancestor-trees
       */
      bubbles: {
        type: Boolean,
        value: false
      },

      /**
       * Changes the [`completes`](iron-request#property-completes) promise chain 
       * from `generateRequest` to reject with an object
       * containing the original request, as well an error message.
       * If false (default), the promise rejects with an error message only.
       */
      rejectWithRequest: {
        type: Boolean,
        value: false
      },

      _boundHandleResponse: {
        type: Function,
        value: function() {
          return this._handleResponse.bind(this);
        }
      }
    },

    observers: [
      '_requestOptionsChanged(url, method, params.*, headers, contentType, ' +
          'body, sync, handleAs, jsonPrefix, withCredentials, timeout, auto)'
    ],

    created: function() {
      this._boundOnProgressChanged = this._onProgressChanged.bind(this);
    },

    /**
     * The query string that should be appended to the `url`, serialized from
     * the current value of `params`.
     *
     * @return {string}
     */
    get queryString () {
      var queryParts = [];
      var param;
      var value;

      for (param in this.params) {
        value = this.params[param];
        param = window.encodeURIComponent(param);

        if (Array.isArray(value)) {
          for (var i = 0; i < value.length; i++) {
            queryParts.push(param + '=' + window.encodeURIComponent(value[i]));
          }
        } else if (value !== null) {
          queryParts.push(param + '=' + window.encodeURIComponent(value));
        } else {
          queryParts.push(param);
        }
      }

      return queryParts.join('&');
    },

    /**
     * The `url` with query string (if `params` are specified), suitable for
     * providing to an `iron-request` instance.
     *
     * @return {string}
     */
    get requestUrl() {
      var queryString = this.queryString;
      var url = this.url || '';

      if (queryString) {
        var bindingChar = url.indexOf('?') >= 0 ? '&' : '?';
        return url + bindingChar + queryString;
      }

      return url;
    },

    /**
     * An object that maps header names to header values, first applying the
     * the value of `Content-Type` and then overlaying the headers specified
     * in the `headers` property.
     *
     * @return {Object}
     */
    get requestHeaders() {
      var headers = {};
      var contentType = this.contentType;
      if (contentType == null && (typeof this.body === 'string')) {
        contentType = 'application/x-www-form-urlencoded';
      }
      if (contentType) {
        headers['content-type'] = contentType;
      }
      var header;

      if (typeof this.headers === 'object') {
        for (header in this.headers) {
          headers[header] = this.headers[header].toString();
        }
      }

      return headers;
    },

    _onProgressChanged: function(event) {
      this._setLastProgress(event.detail.value);
    },

    /**
     * Request options suitable for generating an `iron-request` instance based
     * on the current state of the `iron-ajax` instance's properties.
     *
     * @return {{
     *   url: string,
     *   method: (string|undefined),
     *   async: (boolean|undefined),
     *   body: (ArrayBuffer|ArrayBufferView|Blob|Document|FormData|null|string|undefined|Object),
     *   headers: (Object|undefined),
     *   handleAs: (string|undefined),
     *   jsonPrefix: (string|undefined),
     *   withCredentials: (boolean|undefined)}}
     */
    toRequestOptions: function() {
      return {
        url: this.requestUrl || '',
        method: this.method,
        headers: this.requestHeaders,
        body: this.body,
        async: !this.sync,
        handleAs: this.handleAs,
        jsonPrefix: this.jsonPrefix,
        withCredentials: this.withCredentials,
        timeout: this.timeout,
        rejectWithRequest: this.rejectWithRequest,
      };
    },

    /**
     * Performs an AJAX request to the specified URL.
     *
     * @return {!IronRequestElement}
     */
    generateRequest: function() {
      var request = /** @type {!IronRequestElement} */ (document.createElement('iron-request'));
      var requestOptions = this.toRequestOptions();

      this.push('activeRequests', request);

      request.completes.then(
        this._boundHandleResponse
      ).catch(
        this._handleError.bind(this, request)
      ).then(
        this._discardRequest.bind(this, request)
      );

      var evt = this.fire('iron-ajax-presend', {
        request: request,
        options: requestOptions
      }, {bubbles: this.bubbles, cancelable: true});

      if (evt.defaultPrevented) {
        request.abort();
        request.rejectCompletes(request);
        return request;
      }

      if (this.lastRequest) {
        this.lastRequest.removeEventListener('iron-request-progress-changed',
                this._boundOnProgressChanged);
      }

      request.addEventListener('iron-request-progress-changed',
          this._boundOnProgressChanged);

      request.send(requestOptions);
      this._setLastProgress(null);
      this._setLastRequest(request);
      this._setLoading(true);

      this.fire('request', {
        request: request,
        options: requestOptions
      }, {
        bubbles: this.bubbles,
        composed: true
      });

      this.fire('iron-ajax-request', {
        request: request,
        options: requestOptions
      }, {
        bubbles: this.bubbles,
        composed: true
      });

      return request;
    },

    _handleResponse: function(request) {
      if (request === this.lastRequest) {
        this._setLastResponse(request.response);
        this._setLastError(null);
        this._setLoading(false);
      }
      this.fire('response', request, {
        bubbles: this.bubbles,
        composed: true
      });
      this.fire('iron-ajax-response', request, {
        bubbles: this.bubbles,
        composed: true
      });
    },

    _handleError: function(request, error) {
      if (this.verbose) {
        Polymer.Base._error(error);
      }

      if (request === this.lastRequest) {
        this._setLastError({
          request: request,
          error: error,
          status: request.xhr.status,
          statusText: request.xhr.statusText,
          response: request.xhr.response
        });
        this._setLastResponse(null);
        this._setLoading(false);
      }

      // Tests fail if this goes after the normal this.fire('error', ...)
      this.fire('iron-ajax-error', {
        request: request,
        error: error
      }, {
        bubbles: this.bubbles,
        composed: true
      });

      this.fire('error', {
        request: request,
        error: error
      }, {
        bubbles: this.bubbles,
        composed: true
      });
    },

    _discardRequest: function(request) {
      var requestIndex = this.activeRequests.indexOf(request);

      if (requestIndex > -1) {
        this.splice('activeRequests', requestIndex, 1);
      }
    },

    _requestOptionsChanged: function() {
      this.debounce('generate-request', function() {
        if (this.url == null) {
          return;
        }

        if (this.auto) {
          this.generateRequest();
        }
      }, this.debounceDuration);
    },

  });
</script>
<dom-module id="iron-form" assetpath="bower_components/iron-form/">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>

    <slot></slot>

    <form id="helper" action$="[[action]]" method$="[[method]]" enctype$="[[enctype]]"></form>
  </template>

  <script>
    (function() {

    Polymer({
      is: 'iron-form',

      properties: {
        /*
         * Set this to true if you don't want the form to be submitted through an
         * ajax request, and you want the page to redirect to the action URL
         * after the form has been submitted.
         */
        allowRedirect: {type: Boolean, value: false},
        /**
         * HTTP request headers to send. See PolymerElements/iron-ajax for
         * more details. Only works when `allowRedirect` is false.
         */
        headers: {
          type: Object,
          value: function() {
            return {};
          }
        },
        /**
         * Set the `withCredentials` flag on the request. See
         * PolymerElements/iron-ajax for more details. Only works when
         * `allowRedirect` is false.
         */
        withCredentials: {type: Boolean, value: false},
      },
      /**
       * Fired if the form cannot be submitted because it's invalid.
       *
       * @event iron-form-invalid
       */

      /**
       * Fired after the form is submitted.
       *
       * @event iron-form-submit
       */

      /**
       * Fired before the form is submitted.
       *
       * @event iron-form-presubmit
       */

      /**
       * Fired after the form is reset.
       *
       * @event iron-form-reset
       */

      /**
       * Fired after the form is submitted and a response is received. An
       * IronRequestElement is included as the event.detail object.
       *
       * @event iron-form-response
       */

      /**
       * Fired after the form is submitted and an error is received. An
       * error message is included in event.detail.error and an
       * IronRequestElement is included in event.detail.request.
       *
       * @event iron-form-error
       */

      /**
       * @return {void}
       */
      attached: function() {
        // We might have been detached then re-attached.
        // Avoid searching again for the <form> if we already found it.
        if (this._form) {
          return;
        }
        // Search for the `<form>`, if we don't find it, observe for
        // mutations.
        this._form = Polymer.dom(this).querySelector('form');
        if (this._form) {
          this._init();
          // Since some elements might not be upgraded yet at this time,
          // we won't be able to look into their shadowRoots for submittables.
          // We wait a tick and check again for any missing submittable default
          // values.
          this.async(this._saveInitialValues.bind(this), 1);
        } else {
          this._nodeObserver = Polymer.dom(this).observeNodes(function(mutations) {
            for (var i = 0; i < mutations.addedNodes.length; i++) {
              if (mutations.addedNodes[i].tagName === 'FORM') {
                this._form = mutations.addedNodes[i];
                // At this point in time, all custom elements are expected
                // to be upgraded, hence we'll be able to traverse their
                // shadowRoots.
                this._init();
                Polymer.dom(this).unobserveNodes(this._nodeObserver);
                this._nodeObserver = null;
              }
            }
          }.bind(this));
        }
      },

      /**
       * @return {void}
       */
      detached: function() {
        if (this._nodeObserver) {
          Polymer.dom(this).unobserveNodes(this._nodeObserver);
          this._nodeObserver = null;
        }
      },

      _init: function() {
        this._form.addEventListener('submit', this.submit.bind(this));
        this._form.addEventListener('reset', this.reset.bind(this));

        // Save the initial values.
        this._defaults = this._defaults || new WeakMap();
        this._saveInitialValues();
      },

      /**
       * Saves the values of all form elements that will be used when resetting
       * the form. Initially called asynchronously on attach. Any time you
       * call this function, the previously saved values for a form element will
       * be overwritten.
       *
       * This function is useful if you are dynamically adding elements to
       * the form, or if your elements are asynchronously setting their values.
       * @return {void}
       */
      saveResetValues: function() {
        this._saveInitialValues(true);
      },

      /**
       * @param {boolean=} overwriteValues
       * @return {void}
       */
      _saveInitialValues: function(overwriteValues) {
        var nodes = this._getValidatableElements();
        for (var i = 0; i < nodes.length; i++) {
          var node = nodes[i];
          if (!this._defaults.has(node) || overwriteValues) {
            // Submittables are expected to have `value` property,
            // that's what gets serialized.
            var defaults = {value: node.value};
            if ('checked' in node) {
              defaults.checked = node.checked;
            }
            // In 1.x iron-form would reset `invalid`, so
            // keep it here for backwards compat.
            if ('invalid' in node) {
              defaults.invalid = node.invalid;
            }
            this._defaults.set(node, defaults);
          }
        }
      },

      /**
       * Validates all the required elements (custom and native) in the form.
       * @return {boolean} True if all the elements are valid.
       */
      validate: function() {
        // If you've called this before distribution happened, bail out.
        if (!this._form) {
          return false;
        }

        if (this._form.getAttribute('novalidate') === '')
          return true;

        // Start by making the form check the native elements it knows about.
        var valid = this._form.checkValidity();
        var elements = this._getValidatableElements();

        // Go through all the elements, and validate the custom ones.
        for (var el, i = 0; el = elements[i], i < elements.length; i++) {
          // This is weird to appease the compiler. We assume the custom element
          // has a validate() method, otherwise we can't check it.
          var validatable = /** @type {{validate: (function() : boolean)}} */ (el);
          if (validatable.validate) {
            valid = !!validatable.validate() && valid;
          }
        }
        return valid;
      },

      /**
       * Submits the form.
       *
       * @param {Event=} event
       * @return {void}
       */
      submit: function(event) {
        // We are not using this form for submission, so always cancel its event.
        if (event) {
          event.preventDefault();
        }

        // If you've called this before distribution happened, bail out.
        if (!this._form) {
          return;
        }

        if (!this.validate()) {
          this.fire('iron-form-invalid');
          return;
        }

        // Remove any existing children in the submission form (from a previous
        // submit).
        this.$.helper.textContent = '';

        var json = this.serializeForm();

        // If we want a redirect, submit the form natively.
        if (this.allowRedirect) {
          // If we're submitting the form natively, then create a hidden element for
          // each of the values.
          for (var element in json) {
            this.$.helper.appendChild(
                this._createHiddenElement(element, json[element]));
          }

          // Copy the original form attributes.
          this.$.helper.action = this._form.getAttribute('action');
          this.$.helper.method = this._form.getAttribute('method') || 'GET';
          this.$.helper.contentType = this._form.getAttribute('enctype') ||
              'application/x-www-form-urlencoded';

          this.$.helper.submit();
          this.fire('iron-form-submit');
        } else {
          this._makeAjaxRequest(json);
        }
      },

      /**
       * Resets the form to the default values.
       *
       * @param {Event=} event
       * @return {void}
       */
      reset: function(event) {
        // We are not using this form for submission, so always cancel its event.
        if (event)
          event.preventDefault();

        // If you've called this before distribution happened, bail out.
        if (!this._form) {
          return;
        }

        // Ensure the native form fired the `reset` event.
        // User might have bound `<button on-click="_resetIronForm">`, or directly
        // called `ironForm.reset()`. In these cases we want to first reset the
        // native form.
        if (!event || event.type !== 'reset' || event.target !== this._form) {
          this._form.reset();
          return;
        }

        // Load the initial values.
        var nodes = this._getValidatableElements();
        for (var i = 0; i < nodes.length; i++) {
          var node = nodes[i];
          if (this._defaults.has(node)) {
            var defaults = this._defaults.get(node);
            for (var propName in defaults) {
              node[propName] = defaults[propName];
            }
          }
        }

        this.fire('iron-form-reset');
      },

      /**
       * Serializes the form as will be used in submission. Note that `serialize`
       * is a Polymer reserved keyword, so calling `someIronForm`.serialize()`
       * will give you unexpected results.
       * @return {!Object<string, *>} An object containing name-value pairs for elements that
       *                  would be submitted.
       */
      serializeForm: function() {
        // Only elements that have a `name` and are not disabled are submittable.
        var elements = this._getSubmittableElements();
        var json = {};
        for (var i = 0; i < elements.length; i++) {
          var values = this._serializeElementValues(elements[i]);
          for (var v = 0; v < values.length; v++) {
            this._addSerializedElement(json, elements[i].name, values[v]);
          }
        }
        return json;
      },

      _handleFormResponse: function(event) {
        this.fire('iron-form-response', event.detail);
      },

      _handleFormError: function(event) {
        this.fire('iron-form-error', event.detail);
      },

      _makeAjaxRequest: function(json) {
        // Initialize the iron-ajax element if we haven't already.
        if (!this.request) {
          this.request = document.createElement('iron-ajax');
          this.request.addEventListener(
              'response', this._handleFormResponse.bind(this));
          this.request.addEventListener('error', this._handleFormError.bind(this));
        }

        // Native forms can also index elements magically by their name (can't make
        // this up if I tried) so we need to get the correct attributes, not the
        // elements with those names.
        this.request.url = this._form.getAttribute('action');
        this.request.method = this._form.getAttribute('method') || 'GET';
        this.request.contentType = this._form.getAttribute('enctype') ||
            'application/x-www-form-urlencoded';
        this.request.withCredentials = this.withCredentials;
        this.request.headers = this.headers;

        if (this._form.method.toUpperCase() === 'POST') {
          this.request.body = json;
        } else {
          this.request.params = json;
        }

        // Allow for a presubmit hook
        var event = this.fire('iron-form-presubmit', {}, {cancelable: true});
        if (!event.defaultPrevented) {
          this.request.generateRequest();
          this.fire('iron-form-submit', json);
        }
      },

      _getValidatableElements: function() {
        return this._findElements(
            this._form, true /* ignoreName */, false /* skipSlots */);
      },

      _getSubmittableElements: function() {
        return this._findElements(
            this._form, false /* ignoreName */, false /* skipSlots */);
      },

      /**
       * Traverse the parent element to find and add all submittable nodes to
       * `submittable`.
       * @param  {!Node} parent The parent node
       * @param  {!boolean} ignoreName  Whether the name of the submittable nodes should be disregarded
       * @param  {!boolean} skipSlots  Whether to skip traversing of slot elements
       * @param  {!Array<!Node>=} submittable Reference to the array of submittables
       * @return {!Array<!Node>}
       * @private
       */
      _findElements: function(parent, ignoreName, skipSlots, submittable) {
        submittable = submittable || [];
        var nodes = Polymer.dom(parent).querySelectorAll('*');
        for (var i = 0; i < nodes.length; i++) {
          // An element is submittable if it is not disabled, and if it has a
          // name attribute.
          if (!skipSlots &&
              (nodes[i].localName === 'slot' || nodes[i].localName === 'content')) {
            this._searchSubmittableInSlot(submittable, nodes[i], ignoreName);
          } else {
            this._searchSubmittable(submittable, nodes[i], ignoreName);
          }
        }
        return submittable;
      },

      /**
       * Traverse the distributed nodes of a slot or content element
       * and add all submittable nodes to `submittable`.
       * @param  {!Array<!Node>} submittable Reference to the array of submittables
       * @param  {!Node} node The slot or content node
       * @param  {!boolean} ignoreName  Whether the name of the submittable nodes should be disregarded
       * @return {void}
       * @private
       */
      _searchSubmittableInSlot: function(submittable, node, ignoreName) {
        var assignedNodes = Polymer.dom(node).getDistributedNodes();

        for (var i = 0; i < assignedNodes.length; i++) {
          if (assignedNodes[i].nodeType === Node.TEXT_NODE) {
            continue;
          }

          // Note: assignedNodes does not contain <slot> or <content> because
          // getDistributedNodes flattens the tree.
          this._searchSubmittable(submittable, assignedNodes[i], ignoreName);
          var nestedAssignedNodes =
              Polymer.dom(assignedNodes[i]).querySelectorAll('*');
          for (var j = 0; j < nestedAssignedNodes.length; j++) {
            this._searchSubmittable(
                submittable, nestedAssignedNodes[j], ignoreName);
          }
        }
      },

      /**
       * Traverse the distributed nodes of a slot or content element
       * and add all submittable nodes to `submittable`.
       * @param  {!Array<!Node>} submittable Reference to the array of submittables
       * @param  {!Node} node The node to be
       * @param  {!boolean} ignoreName  Whether the name of the submittable nodes should be disregarded
       * @return {void}
       * @private
       */
      _searchSubmittable: function(submittable, node, ignoreName) {
        if (this._isSubmittable(node, ignoreName)) {
          submittable.push(node);
        } else if (node.root) {
          this._findElements(
              node.root, ignoreName, true /* skipSlots */, submittable);
        }
      },

      /**
       * An element is submittable if it is not disabled, and if it has a
       * 'name' attribute. If we ignore the name, check if is validatable.
       * This allows `_findElements` to decide if to explore an element's shadowRoot
       * or not: an element implementing `validate()` is considered validatable, and
       * we don't search for validatables in its shadowRoot.
       * @param {!Node} node
       * @param {!boolean} ignoreName
       * @return {boolean}
       * @private
       */
      _isSubmittable: function(node, ignoreName) {
        return (
            !node.disabled &&
            (ignoreName ? node.name || typeof node.validate === 'function' :
                          node.name));
      },

      _serializeElementValues: function(element) {
        // We will assume that every custom element that needs to be serialized
        // has a `value` property, and it contains the correct value.
        // The only weird one is an element that implements
        // IronCheckedElementBehaviour, in which case like the native checkbox/radio
        // button, it's only used when checked. For native elements, from
        // https://www.w3.org/TR/html5/forms.html#the-form-element. Native
        // submittable elements: button, input, keygen, object, select, textarea;
        // 1. We will skip `keygen and `object` for this iteration, and deal with
        // them if they're actually required.
        // 2. <button> and <textarea> have a `value` property, so they behave like
        //    the custom elements.
        // 3. <select> can have multiple options selected, in which case its
        //    `value` is incorrect, and we must use the values of each of its
        //    `selectedOptions`
        // 4. <input> can have a whole bunch of behaviours, so it's handled
        // separately.
        // 5. Buttons are hard. The button that was clicked to submit the form
        //    is the one who's name/value gets sent to the server.
        var tag = element.tagName.toLowerCase();
        if (tag === 'button' ||
            (tag === 'input' &&
             (element.type === 'submit' || element.type === 'reset'))) {
          return [];
        }

        if (tag === 'select') {
          return this._serializeSelectValues(element);
        } else if (tag === 'input') {
          return this._serializeInputValues(element);
        } else {
          if (element['_hasIronCheckedElementBehavior'] && !element.checked)
            return [];
          return [element.value];
        }
      },

      _serializeSelectValues: function(element) {
        var values = [];

        // A <select multiple> has an array of options, some of which can be
        // selected.
        for (var i = 0; i < element.options.length; i++) {
          if (element.options[i].selected) {
            values.push(element.options[i].value)
          }
        }
        return values;
      },

      _serializeInputValues: function(element) {
        // Most of the inputs use their 'value' attribute, with the exception
        // of radio buttons, checkboxes and file.
        var type = element.type.toLowerCase();

        // Don't do anything for unchecked checkboxes/radio buttons.
        // Don't do anything for file, since that requires a different request.
        if (((type === 'checkbox' || type === 'radio') && !element.checked) ||
            type === 'file') {
          return [];
        }
        return [element.value];
      },

      _createHiddenElement: function(name, value) {
        var input = document.createElement('input');
        input.setAttribute('type', 'hidden');
        input.setAttribute('name', name);
        input.setAttribute('value', value);
        return input;
      },

      _addSerializedElement: function(json, name, value) {
        // If the name doesn't exist, add it. Otherwise, serialize it to
        // an array,
        if (json[name] === undefined) {
          json[name] = value;
        } else {
          if (!Array.isArray(json[name])) {
            json[name] = [json[name]];
          }
          json[name].push(value);
        }
      }
    });
    })();
  </script>
</dom-module>
<dom-module id="iron-label" assetpath="bower_components/iron-label/">
  <script>
    Polymer.IronLabel = Polymer({
      is: 'iron-label',

      listeners: {
        'tap': '_tapHandler'
      },

      properties: {
        /**
          * An ID reference to another element that needs to be
          * labelled by this `iron-label` element.
          */
        for: {
          type: String,
          value: '',
          reflectToAttribute: true,
          observer: '_forChanged'
        },
        /**
         * @type {Element}
         */
        _forElement: Object
      },

      attached: function() {
        this._forChanged();
      },

      ready: function() {
        this._generateLabelId();
      },

      // generate a unique id for this element
      _generateLabelId: function() {
        if (!this.id) {
          var id = 'iron-label-' + Polymer.IronLabel._labelNumber++;
          Polymer.dom(this).setAttribute('id', id);
        }
      },

      _findTarget: function() {
        if (this.for) {
          // external target
          var scope = Polymer.dom(this).getOwnerRoot();
          return Polymer.dom(scope).querySelector('#' + this.for);
        } else {
          // explicit internal target
          var el = Polymer.dom(this).querySelector('[iron-label-target]');
          if (!el) {
            // implicit internal target
            el = Polymer.dom(this).firstElementChild;
          }
          return el;
        }
      },

      _tapHandler: function(ev) {
        if (!this._forElement) {
          return;
        }
        var target = Polymer.dom(ev).localTarget;
        if (target === this._forElement) {
          return;
        }
        this._forElement.focus();
        this._forElement.click();
      },

      _applyLabelledBy: function() {
        if (this._forElement) {
          Polymer.dom(this._forElement).setAttribute('aria-labelledby', this.id);
        }
      },

      _forChanged: function() {
        if (this._forElement) {
          Polymer.dom(this._forElement).removeAttribute('aria-labelledby');
        }
        this._forElement = this._findTarget();
        this._applyLabelledBy();
      }
    });

    // global counter for unique label ids
    Polymer.IronLabel._labelNumber = 0;
  </script>
</dom-module>
<script>
  /**
   * Singleton IronMeta instance.
   */
  Polymer.IronValidatableBehaviorMeta = null;

  /**
   * `Use Polymer.IronValidatableBehavior` to implement an element that validates user input.
   * Use the related `Polymer.IronValidatorBehavior` to add custom validation logic to an iron-input.
   *
   * By default, an `<iron-form>` element validates its fields when the user presses the submit button.
   * To validate a form imperatively, call the form's `validate()` method, which in turn will
   * call `validate()` on all its children. By using `Polymer.IronValidatableBehavior`, your
   * custom element will get a public `validate()`, which
   * will return the validity of the element, and a corresponding `invalid` attribute,
   * which can be used for styling.
   *
   * To implement the custom validation logic of your element, you must override
   * the protected `_getValidity()` method of this behaviour, rather than `validate()`.
   * See [this](https://github.com/PolymerElements/iron-form/blob/master/demo/simple-element.html)
   * for an example.
   *
   * ### Accessibility
   *
   * Changing the `invalid` property, either manually or by calling `validate()` will update the
   * `aria-invalid` attribute.
   *
   * @demo demo/index.html
   * @polymerBehavior
   */
  Polymer.IronValidatableBehavior = {

    properties: {
      /**
       * Name of the validator to use.
       */
      validator: {
        type: String
      },

      /**
       * True if the last call to `validate` is invalid.
       */
      invalid: {
        notify: true,
        reflectToAttribute: true,
        type: Boolean,
        value: false,
        observer: '_invalidChanged'
      },
    },

    registered: function() {
      Polymer.IronValidatableBehaviorMeta = new Polymer.IronMeta({type: 'validator'});
    },

    _invalidChanged: function() {
      if (this.invalid) {
        this.setAttribute('aria-invalid', 'true');
      } else {
        this.removeAttribute('aria-invalid');
      }
    },

    /* Recompute this every time it's needed, because we don't know if the
     * underlying IronValidatableBehaviorMeta has changed. */
    get _validator() {
      return Polymer.IronValidatableBehaviorMeta &&
          Polymer.IronValidatableBehaviorMeta.byKey(this.validator);
    },

    /**
     * @return {boolean} True if the validator `validator` exists.
     */
    hasValidator: function() {
      return this._validator != null;
    },

    /**
     * Returns true if the `value` is valid, and updates `invalid`. If you want
     * your element to have custom validation logic, do not override this method;
     * override `_getValidity(value)` instead.

     * @param {Object} value Deprecated: The value to be validated. By default,
     * it is passed to the validator's `validate()` function, if a validator is set.
     * If this argument is not specified, then the element's `value` property
     * is used, if it exists.
     * @return {boolean} True if `value` is valid.
     */
    validate: function(value) {
      // If this is an element that also has a value property, and there was
      // no explicit value argument passed, use the element's property instead.
      if (value === undefined && this.value !== undefined)
        this.invalid = !this._getValidity(this.value);
      else
        this.invalid = !this._getValidity(value);
      return !this.invalid;
    },

    /**
     * Returns true if `value` is valid.  By default, it is passed
     * to the validator's `validate()` function, if a validator is set. You
     * should override this method if you want to implement custom validity
     * logic for your element.
     *
     * @param {Object} value The value to be validated.
     * @return {boolean} True if `value` is valid.
     */

    _getValidity: function(value) {
      if (this.hasValidator()) {
        return this._validator.validate(value);
      }
      return true;
    }
  };

</script>
<script>
  /**
  Polymer.IronFormElementBehavior enables a custom element to be included
  in an `iron-form`.

  Events `iron-form-element-register` and `iron-form-element-unregister` are not
  fired on Polymer 2.0.

  @demo demo/index.html
  @polymerBehavior
  */
  Polymer.IronFormElementBehavior = {

    properties: {
      /**
       * Fired when the element is added to an `iron-form`.
       *
       * @event iron-form-element-register
       */

      /**
       * Fired when the element is removed from an `iron-form`.
       *
       * @event iron-form-element-unregister
       */

      /**
       * The name of this element.
       */
      name: {type: String},

      /**
       * The value for this element.
       * @type {*}
       */
      value: {notify: true, type: String},

      /**
       * Set to true to mark the input as required. If used in a form, a
       * custom element that uses this behavior should also use
       * Polymer.IronValidatableBehavior and define a custom validation method.
       * Otherwise, a `required` element will always be considered valid.
       * It's also strongly recommended to provide a visual style for the element
       * when its value is invalid.
       */
      required: {type: Boolean, value: false},

      /**
       * The form that the element is registered to.
       */
      _parentForm: {type: Object}
    },

    attached: function() {
      if (!Polymer.Element) {
        // Note: the iron-form that this element belongs to will set this
        // element's _parentForm property when handling this event.
        this.fire('iron-form-element-register');
      }
    },

    detached: function() {
      if (!Polymer.Element && this._parentForm) {
        this._parentForm.fire('iron-form-element-unregister', {target: this});
      }
    }

  };
</script>
<script>
  /**
   * Use `Polymer.IronCheckedElementBehavior` to implement a custom element
   * that has a `checked` property, which can be used for validation if the
   * element is also `required`. Element instances implementing this behavior
   * will also be registered for use in an `iron-form` element.
   *
   * @demo demo/index.html
   * @polymerBehavior Polymer.IronCheckedElementBehavior
   */
  Polymer.IronCheckedElementBehaviorImpl = {

    properties: {
      /**
       * Fired when the checked state changes.
       *
       * @event iron-change
       */

      /**
       * Gets or sets the state, `true` is checked and `false` is unchecked.
       */
      checked: {
        type: Boolean,
        value: false,
        reflectToAttribute: true,
        notify: true,
        observer: '_checkedChanged'
      },

      /**
       * If true, the button toggles the active state with each tap or press
       * of the spacebar.
       */
      toggles: {type: Boolean, value: true, reflectToAttribute: true},

      /* Overriden from Polymer.IronFormElementBehavior */
      value: {type: String, value: 'on', observer: '_valueChanged'}
    },

    observers: ['_requiredChanged(required)'],

    created: function() {
      // Used by `iron-form` to handle the case that an element with this behavior
      // doesn't have a role of 'checkbox' or 'radio', but should still only be
      // included when the form is serialized if `this.checked === true`.
      this._hasIronCheckedElementBehavior = true;
    },

    /**
     * Returns false if the element is required and not checked, and true
     * otherwise.
     * @param {*=} _value Ignored.
     * @return {boolean} true if `required` is false or if `checked` is true.
     */
    _getValidity: function(_value) {
      return this.disabled || !this.required || this.checked;
    },

    /**
     * Update the aria-required label when `required` is changed.
     */
    _requiredChanged: function() {
      if (this.required) {
        this.setAttribute('aria-required', 'true');
      } else {
        this.removeAttribute('aria-required');
      }
    },

    /**
     * Fire `iron-changed` when the checked state changes.
     */
    _checkedChanged: function() {
      this.active = this.checked;
      this.fire('iron-change');
    },

    /**
     * Reset value to 'on' if it is set to `undefined`.
     */
    _valueChanged: function() {
      if (this.value === undefined || this.value === null) {
        this.value = 'on';
      }
    }
  };

  /** @polymerBehavior Polymer.IronCheckedElementBehavior */
  Polymer.IronCheckedElementBehavior = [
    Polymer.IronFormElementBehavior,
    Polymer.IronValidatableBehavior,
    Polymer.IronCheckedElementBehaviorImpl
  ];
</script>
<script>
  /**
   * Use `Polymer.PaperCheckedElementBehavior` to implement a custom element
   * that has a `checked` property similar to `Polymer.IronCheckedElementBehavior`
   * and is compatible with having a ripple effect.
   * @polymerBehavior Polymer.PaperCheckedElementBehavior
   */
  Polymer.PaperCheckedElementBehaviorImpl = {
    /**
     * Synchronizes the element's checked state with its ripple effect.
     */
    _checkedChanged: function() {
      Polymer.IronCheckedElementBehaviorImpl._checkedChanged.call(this);
      if (this.hasRipple()) {
        if (this.checked) {
          this._ripple.setAttribute('checked', '');
        } else {
          this._ripple.removeAttribute('checked');
        }
      }
    },

    /**
     * Synchronizes the element's `active` and `checked` state.
     */
    _buttonStateChanged: function() {
      Polymer.PaperRippleBehavior._buttonStateChanged.call(this);
      if (this.disabled) {
        return;
      }
      if (this.isAttached) {
        this.checked = this.active;
      }
    }
  };

  /** @polymerBehavior Polymer.PaperCheckedElementBehavior */
  Polymer.PaperCheckedElementBehavior = [
    Polymer.PaperInkyFocusBehavior,
    Polymer.IronCheckedElementBehavior,
    Polymer.PaperCheckedElementBehaviorImpl
  ];
</script>
<dom-module id="paper-checkbox" assetpath="bower_components/paper-checkbox/">
  <template strip-whitespace="">
    <style>
      :host {
        display: inline-block;
        white-space: nowrap;
        cursor: pointer;
        --calculated-paper-checkbox-size: var(--paper-checkbox-size, 18px);
        /* -1px is a sentinel for the default and is replaced in `attached`. */
        --calculated-paper-checkbox-ink-size: var(--paper-checkbox-ink-size, -1px);
        @apply --paper-font-common-base;
        line-height: 0;
        -webkit-tap-highlight-color: transparent;
      }

      :host([hidden]) {
        display: none !important;
      }

      :host(:focus) {
        outline: none;
      }

      .hidden {
        display: none;
      }

      #checkboxContainer {
        display: inline-block;
        position: relative;
        width: var(--calculated-paper-checkbox-size);
        height: var(--calculated-paper-checkbox-size);
        min-width: var(--calculated-paper-checkbox-size);
        margin: var(--paper-checkbox-margin, initial);
        vertical-align: var(--paper-checkbox-vertical-align, middle);
        background-color: var(--paper-checkbox-unchecked-background-color, transparent);
      }

      #ink {
        position: absolute;

        /* Center the ripple in the checkbox by negative offsetting it by
         * (inkWidth - rippleWidth) / 2 */
        top: calc(0px - (var(--calculated-paper-checkbox-ink-size) - var(--calculated-paper-checkbox-size)) / 2);
        left: calc(0px - (var(--calculated-paper-checkbox-ink-size) - var(--calculated-paper-checkbox-size)) / 2);
        width: var(--calculated-paper-checkbox-ink-size);
        height: var(--calculated-paper-checkbox-ink-size);
        color: var(--paper-checkbox-unchecked-ink-color, var(--primary-text-color));
        opacity: 0.6;
        pointer-events: none;
      }

      #ink:dir(rtl) {
        right: calc(0px - (var(--calculated-paper-checkbox-ink-size) - var(--calculated-paper-checkbox-size)) / 2);
        left: auto;
      }

      #ink[checked] {
        color: var(--paper-checkbox-checked-ink-color, var(--primary-color));
      }

      #checkbox {
        position: relative;
        box-sizing: border-box;
        height: 100%;
        border: solid 2px;
        border-color: var(--paper-checkbox-unchecked-color, var(--primary-text-color));
        border-radius: 2px;
        pointer-events: none;
        -webkit-transition: background-color 140ms, border-color 140ms;
        transition: background-color 140ms, border-color 140ms;
      }

      /* checkbox checked animations */
      #checkbox.checked #checkmark {
        -webkit-animation: checkmark-expand 140ms ease-out forwards;
        animation: checkmark-expand 140ms ease-out forwards;
      }

      @-webkit-keyframes checkmark-expand {
        0% {
          -webkit-transform: scale(0, 0) rotate(45deg);
        }
        100% {
          -webkit-transform: scale(1, 1) rotate(45deg);
        }
      }

      @keyframes checkmark-expand {
        0% {
          transform: scale(0, 0) rotate(45deg);
        }
        100% {
          transform: scale(1, 1) rotate(45deg);
        }
      }

      #checkbox.checked {
        background-color: var(--paper-checkbox-checked-color, var(--primary-color));
        border-color: var(--paper-checkbox-checked-color, var(--primary-color));
      }

      #checkmark {
        position: absolute;
        width: 36%;
        height: 70%;
        border-style: solid;
        border-top: none;
        border-left: none;
        border-right-width: calc(2/15 * var(--calculated-paper-checkbox-size));
        border-bottom-width: calc(2/15 * var(--calculated-paper-checkbox-size));
        border-color: var(--paper-checkbox-checkmark-color, white);
        -webkit-transform-origin: 97% 86%;
        transform-origin: 97% 86%;
        box-sizing: content-box; /* protect against page-level box-sizing */
      }

      #checkmark:dir(rtl) {
        -webkit-transform-origin: 50% 14%;
        transform-origin: 50% 14%;
      }

      /* label */
      #checkboxLabel {
        position: relative;
        display: inline-block;
        vertical-align: middle;
        padding-left: var(--paper-checkbox-label-spacing, 8px);
        white-space: normal;
        line-height: normal;
        color: var(--paper-checkbox-label-color, var(--primary-text-color));
        @apply --paper-checkbox-label;
      }

      :host([checked]) #checkboxLabel {
        color: var(--paper-checkbox-label-checked-color, var(--paper-checkbox-label-color, var(--primary-text-color)));
        @apply --paper-checkbox-label-checked;
      }

      #checkboxLabel:dir(rtl) {
        padding-right: var(--paper-checkbox-label-spacing, 8px);
        padding-left: 0;
      }

      #checkboxLabel[hidden] {
        display: none;
      }

      /* disabled state */

      :host([disabled]) #checkbox {
        opacity: 0.5;
        border-color: var(--paper-checkbox-unchecked-color, var(--primary-text-color));
      }

      :host([disabled][checked]) #checkbox {
        background-color: var(--paper-checkbox-unchecked-color, var(--primary-text-color));
        opacity: 0.5;
      }

      :host([disabled]) #checkboxLabel  {
        opacity: 0.65;
      }

      /* invalid state */
      #checkbox.invalid:not(.checked) {
        border-color: var(--paper-checkbox-error-color, var(--error-color));
      }
    </style>

    <div id="checkboxContainer">
      <div id="checkbox" class$="[[_computeCheckboxClass(checked, invalid)]]">
        <div id="checkmark" class$="[[_computeCheckmarkClass(checked)]]"></div>
      </div>
    </div>

    <div id="checkboxLabel"><slot></slot></div>
  </template>

  <script>
    Polymer({
      is: 'paper-checkbox',

      behaviors: [Polymer.PaperCheckedElementBehavior],

      /** @private */
      hostAttributes: {role: 'checkbox', 'aria-checked': false, tabindex: 0},

      properties: {
        /**
         * Fired when the checked state changes due to user interaction.
         *
         * @event change
         */

        /**
         * Fired when the checked state changes.
         *
         * @event iron-change
         */
        ariaActiveAttribute: {type: String, value: 'aria-checked'}
      },

      attached: function() {
        // Wait until styles have resolved to check for the default sentinel.
        // See polymer#4009 for more details.
        Polymer.RenderStatus.afterNextRender(this, function() {
          var inkSize =
              this.getComputedStyleValue('--calculated-paper-checkbox-ink-size')
                  .trim();
          // If unset, compute and set the default `--paper-checkbox-ink-size`.
          if (inkSize === '-1px') {
            var checkboxSizeText =
                this.getComputedStyleValue('--calculated-paper-checkbox-size')
                    .trim();

            var units = 'px';
            var unitsMatches = checkboxSizeText.match(/[A-Za-z]+$/);
            if (unitsMatches !== null) {
              units = unitsMatches[0];
            }

            var checkboxSize = parseFloat(checkboxSizeText);
            var defaultInkSize = (8 / 3) * checkboxSize;

            if (units === 'px') {
              defaultInkSize = Math.floor(defaultInkSize);

              // The checkbox and ripple need to have the same parity so that their
              // centers align.
              if (defaultInkSize % 2 !== checkboxSize % 2) {
                defaultInkSize++;
              }
            }

            this.updateStyles({
              '--paper-checkbox-ink-size': defaultInkSize + units,
            });
          }
        });
      },

      _computeCheckboxClass: function(checked, invalid) {
        var className = '';
        if (checked) {
          className += 'checked ';
        }
        if (invalid) {
          className += 'invalid';
        }
        return className;
      },

      _computeCheckmarkClass: function(checked) {
        return checked ? '' : 'hidden';
      },

      // create ripple inside the checkboxContainer
      _createRipple: function() {
        this._rippleContainer = this.$.checkboxContainer;
        return Polymer.PaperInkyFocusBehaviorImpl._createRipple.call(this);
      }

    });
  </script>
</dom-module>
<iron-iconset-svg name="icons" size="24">
<svg><defs>
<g id="3d-rotation"><path d="M7.52 21.48C4.25 19.94 1.91 16.76 1.55 13H.05C.56 19.16 5.71 24 12 24l.66-.03-3.81-3.81-1.33 1.32zm.89-6.52c-.19 0-.37-.03-.52-.08-.16-.06-.29-.13-.4-.24-.11-.1-.2-.22-.26-.37-.06-.14-.09-.3-.09-.47h-1.3c0 .36.07.68.21.95.14.27.33.5.56.69.24.18.51.32.82.41.3.1.62.15.96.15.37 0 .72-.05 1.03-.15.32-.1.6-.25.83-.44s.42-.43.55-.72c.13-.29.2-.61.2-.97 0-.19-.02-.38-.07-.56-.05-.18-.12-.35-.23-.51-.1-.16-.24-.3-.4-.43-.17-.13-.37-.23-.61-.31.2-.09.37-.2.52-.33.15-.13.27-.27.37-.42.1-.15.17-.3.22-.46.05-.16.07-.32.07-.48 0-.36-.06-.68-.18-.96-.12-.28-.29-.51-.51-.69-.2-.19-.47-.33-.77-.43C9.1 8.05 8.76 8 8.39 8c-.36 0-.69.05-1 .16-.3.11-.57.26-.79.45-.21.19-.38.41-.51.67-.12.26-.18.54-.18.85h1.3c0-.17.03-.32.09-.45s.14-.25.25-.34c.11-.09.23-.17.38-.22.15-.05.3-.08.48-.08.4 0 .7.1.89.31.19.2.29.49.29.86 0 .18-.03.34-.08.49-.05.15-.14.27-.25.37-.11.1-.25.18-.41.24-.16.06-.36.09-.58.09H7.5v1.03h.77c.22 0 .42.02.6.07s.33.13.45.23c.12.11.22.24.29.4.07.16.1.35.1.57 0 .41-.12.72-.35.93-.23.23-.55.33-.95.33zm8.55-5.92c-.32-.33-.7-.59-1.14-.77-.43-.18-.92-.27-1.46-.27H12v8h2.3c.55 0 1.06-.09 1.51-.27.45-.18.84-.43 1.16-.76.32-.33.57-.73.74-1.19.17-.47.26-.99.26-1.57v-.4c0-.58-.09-1.1-.26-1.57-.18-.47-.43-.87-.75-1.2zm-.39 3.16c0 .42-.05.79-.14 1.13-.1.33-.24.62-.43.85-.19.23-.43.41-.71.53-.29.12-.62.18-.99.18h-.91V9.12h.97c.72 0 1.27.23 1.64.69.38.46.57 1.12.57 1.99v.4zM12 0l-.66.03 3.81 3.81 1.33-1.33c3.27 1.55 5.61 4.72 5.96 8.48h1.5C23.44 4.84 18.29 0 12 0z"></path></g>
<g id="accessibility"><path d="M12 2c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm9 7h-6v13h-2v-6h-2v6H9V9H3V7h18v2z"></path></g>
<g id="accessible"><circle cx="12" cy="4" r="2"></circle><path d="M19 13v-2c-1.54.02-3.09-.75-4.07-1.83l-1.29-1.43c-.17-.19-.38-.34-.61-.45-.01 0-.01-.01-.02-.01H13c-.35-.2-.75-.3-1.19-.26C10.76 7.11 10 8.04 10 9.09V15c0 1.1.9 2 2 2h5v5h2v-5.5c0-1.1-.9-2-2-2h-3v-3.45c1.29 1.07 3.25 1.94 5 1.95zm-6.17 5c-.41 1.16-1.52 2-2.83 2-1.66 0-3-1.34-3-3 0-1.31.84-2.41 2-2.83V12.1c-2.28.46-4 2.48-4 4.9 0 2.76 2.24 5 5 5 2.42 0 4.44-1.72 4.9-4h-2.07z"></path></g>
<g id="account-balance"><path d="M4 10v7h3v-7H4zm6 0v7h3v-7h-3zM2 22h19v-3H2v3zm14-12v7h3v-7h-3zm-4.5-9L2 6v2h19V6l-9.5-5z"></path></g>
<g id="account-balance-wallet"><path d="M21 18v1c0 1.1-.9 2-2 2H5c-1.11 0-2-.9-2-2V5c0-1.1.89-2 2-2h14c1.1 0 2 .9 2 2v1h-9c-1.11 0-2 .9-2 2v8c0 1.1.89 2 2 2h9zm-9-2h10V8H12v8zm4-2.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"></path></g>
<g id="account-box"><path d="M3 5v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2H5c-1.11 0-2 .9-2 2zm12 4c0 1.66-1.34 3-3 3s-3-1.34-3-3 1.34-3 3-3 3 1.34 3 3zm-9 8c0-2 4-3.1 6-3.1s6 1.1 6 3.1v1H6v-1z"></path></g>
<g id="account-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"></path></g>
<g id="add"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"></path></g>
<g id="add-alert"><path d="M10.01 21.01c0 1.1.89 1.99 1.99 1.99s1.99-.89 1.99-1.99h-3.98zm8.87-4.19V11c0-3.25-2.25-5.97-5.29-6.69v-.72C13.59 2.71 12.88 2 12 2s-1.59.71-1.59 1.59v.72C7.37 5.03 5.12 7.75 5.12 11v5.82L3 18.94V20h18v-1.06l-2.12-2.12zM16 13.01h-3v3h-2v-3H8V11h3V8h2v3h3v2.01z"></path></g>
<g id="add-box"><path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"></path></g>
<g id="add-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"></path></g>
<g id="add-circle-outline"><path d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></g>
<g id="add-shopping-cart"><path d="M11 9h2V6h3V4h-3V1h-2v3H8v2h3v3zm-4 9c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zm10 0c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2zm-9.83-3.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.86-7.01L19.42 4h-.01l-1.1 2-2.76 5H8.53l-.13-.27L6.16 6l-.95-2-.94-2H1v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.13 0-.25-.11-.25-.25z"></path></g>
<g id="alarm"><path d="M22 5.72l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM7.88 3.39L6.6 1.86 2 5.71l1.29 1.53 4.59-3.85zM12.5 8H11v6l4.75 2.85.75-1.23-4-2.37V8zM12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 16c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"></path></g>
<g id="alarm-add"><path d="M7.88 3.39L6.6 1.86 2 5.71l1.29 1.53 4.59-3.85zM22 5.72l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 16c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7zm1-11h-2v3H8v2h3v3h2v-3h3v-2h-3V9z"></path></g>
<g id="alarm-off"><path d="M12 6c3.87 0 7 3.13 7 7 0 .84-.16 1.65-.43 2.4l1.52 1.52c.58-1.19.91-2.51.91-3.92 0-4.97-4.03-9-9-9-1.41 0-2.73.33-3.92.91L9.6 6.43C10.35 6.16 11.16 6 12 6zm10-.28l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM2.92 2.29L1.65 3.57 2.98 4.9l-1.11.93 1.42 1.42 1.11-.94.8.8C3.83 8.69 3 10.75 3 13c0 4.97 4.02 9 9 9 2.25 0 4.31-.83 5.89-2.2l2.2 2.2 1.27-1.27L3.89 3.27l-.97-.98zm13.55 16.1C15.26 19.39 13.7 20 12 20c-3.87 0-7-3.13-7-7 0-1.7.61-3.26 1.61-4.47l9.86 9.86zM8.02 3.28L6.6 1.86l-.86.71 1.42 1.42.86-.71z"></path></g>
<g id="alarm-on"><path d="M22 5.72l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM7.88 3.39L6.6 1.86 2 5.71l1.29 1.53 4.59-3.85zM12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 16c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7zm-1.46-5.47L8.41 12.4l-1.06 1.06 3.18 3.18 6-6-1.06-1.06-4.93 4.95z"></path></g>
<g id="all-out"><path d="M16.21 4.16l4 4v-4zm4 12l-4 4h4zm-12 4l-4-4v4zm-4-12l4-4h-4zm12.95-.95c-2.73-2.73-7.17-2.73-9.9 0s-2.73 7.17 0 9.9 7.17 2.73 9.9 0 2.73-7.16 0-9.9zm-1.1 8.8c-2.13 2.13-5.57 2.13-7.7 0s-2.13-5.57 0-7.7 5.57-2.13 7.7 0 2.13 5.57 0 7.7z"></path></g>
<g id="android"><path d="M6 18c0 .55.45 1 1 1h1v3.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5V19h2v3.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5V19h1c.55 0 1-.45 1-1V8H6v10zM3.5 8C2.67 8 2 8.67 2 9.5v7c0 .83.67 1.5 1.5 1.5S5 17.33 5 16.5v-7C5 8.67 4.33 8 3.5 8zm17 0c-.83 0-1.5.67-1.5 1.5v7c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5v-7c0-.83-.67-1.5-1.5-1.5zm-4.97-5.84l1.3-1.3c.2-.2.2-.51 0-.71-.2-.2-.51-.2-.71 0l-1.48 1.48C13.85 1.23 12.95 1 12 1c-.96 0-1.86.23-2.66.63L7.85.15c-.2-.2-.51-.2-.71 0-.2.2-.2.51 0 .71l1.31 1.31C6.97 3.26 6 5.01 6 7h12c0-1.99-.97-3.75-2.47-4.84zM10 5H9V4h1v1zm5 0h-1V4h1v1z"></path></g>
<g id="announcement"><path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 9h-2V5h2v6zm0 4h-2v-2h2v2z"></path></g>
<g id="apps"><path d="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z"></path></g>
<g id="archive"><path d="M20.54 5.23l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6.02 3 6.5V19c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.48-.17-.93-.46-1.27zM12 17.5L6.5 12H10v-2h4v2h3.5L12 17.5zM5.12 5l.81-1h12l.94 1H5.12z"></path></g>
<g id="arrow-back"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"></path></g>
<g id="arrow-downward"><path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"></path></g>
<g id="arrow-drop-down"><path d="M7 10l5 5 5-5z"></path></g>
<g id="arrow-drop-down-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 12l-4-4h8l-4 4z"></path></g>
<g id="arrow-drop-up"><path d="M7 14l5-5 5 5z"></path></g>
<g id="arrow-forward"><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"></path></g>
<g id="arrow-upward"><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"></path></g>
<g id="aspect-ratio"><path d="M19 12h-2v3h-3v2h5v-5zM7 9h3V7H5v5h2V9zm14-6H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16.01H3V4.99h18v14.02z"></path></g>
<g id="assessment"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z"></path></g>
<g id="assignment"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"></path></g>
<g id="assignment-ind"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm0 4c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm6 12H6v-1.4c0-2 4-3.1 6-3.1s6 1.1 6 3.1V19z"></path></g>
<g id="assignment-late"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-6 15h-2v-2h2v2zm0-4h-2V8h2v6zm-1-9c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1z"></path></g>
<g id="assignment-return"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm4 12h-4v3l-5-5 5-5v3h4v4z"></path></g>
<g id="assignment-returned"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm0 15l-5-5h3V9h4v4h3l-5 5z"></path></g>
<g id="assignment-turned-in"><path d="M19 3h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm-2 14l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9l-8 8z"></path></g>
<g id="attachment"><path d="M2 12.5C2 9.46 4.46 7 7.5 7H18c2.21 0 4 1.79 4 4s-1.79 4-4 4H9.5C8.12 15 7 13.88 7 12.5S8.12 10 9.5 10H17v2H9.41c-.55 0-.55 1 0 1H18c1.1 0 2-.9 2-2s-.9-2-2-2H7.5C5.57 9 4 10.57 4 12.5S5.57 16 7.5 16H17v2H7.5C4.46 18 2 15.54 2 12.5z"></path></g>
<g id="autorenew"><path d="M12 6v3l4-4-4-4v3c-4.42 0-8 3.58-8 8 0 1.57.46 3.03 1.24 4.26L6.7 14.8c-.45-.83-.7-1.79-.7-2.8 0-3.31 2.69-6 6-6zm6.76 1.74L17.3 9.2c.44.84.7 1.79.7 2.8 0 3.31-2.69 6-6 6v-3l-4 4 4 4v-3c4.42 0 8-3.58 8-8 0-1.57-.46-3.03-1.24-4.26z"></path></g>
<g id="backspace"><path d="M22 3H7c-.69 0-1.23.35-1.59.88L0 12l5.41 8.11c.36.53.9.89 1.59.89h15c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-3 12.59L17.59 17 14 13.41 10.41 17 9 15.59 12.59 12 9 8.41 10.41 7 14 10.59 17.59 7 19 8.41 15.41 12 19 15.59z"></path></g>
<g id="backup"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"></path></g>
<g id="block"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12zm8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8z"></path></g>
<g id="book"><path d="M18 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 4h5v8l-2.5-1.5L6 12V4z"></path></g>
<g id="bookmark"><path d="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2z"></path></g>
<g id="bookmark-border"><path d="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2zm0 15l-5-2.18L7 18V5h10v13z"></path></g>
<g id="bug-report"><path d="M20 8h-2.81c-.45-.78-1.07-1.45-1.82-1.96L17 4.41 15.59 3l-2.17 2.17C12.96 5.06 12.49 5 12 5c-.49 0-.96.06-1.41.17L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8zm-6 8h-4v-2h4v2zm0-4h-4v-2h4v2z"></path></g>
<g id="build"><path d="M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z"></path></g>
<g id="cached"><path d="M19 8l-4 4h3c0 3.31-2.69 6-6 6-1.01 0-1.97-.25-2.8-.7l-1.46 1.46C8.97 19.54 10.43 20 12 20c4.42 0 8-3.58 8-8h3l-4-4zM6 12c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46C15.03 4.46 13.57 4 12 4c-4.42 0-8 3.58-8 8H1l4 4 4-4H6z"></path></g>
<g id="camera-enhance"><path d="M9 3L7.17 5H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2h-3.17L15 3H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-1l1.25-2.75L16 13l-2.75-1.25L12 9l-1.25 2.75L8 13l2.75 1.25z"></path></g>
<g id="cancel"><path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"></path></g>
<g id="card-giftcard"><path d="M20 6h-2.18c.11-.31.18-.65.18-1 0-1.66-1.34-3-3-3-1.05 0-1.96.54-2.5 1.35l-.5.67-.5-.68C10.96 2.54 10.05 2 9 2 7.34 2 6 3.34 6 5c0 .35.07.69.18 1H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-5-2c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM9 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm11 15H4v-2h16v2zm0-5H4V8h5.08L7 10.83 8.62 12 11 8.76l1-1.36 1 1.36L15.38 12 17 10.83 14.92 8H20v6z"></path></g>
<g id="card-membership"><path d="M20 2H4c-1.11 0-2 .89-2 2v11c0 1.11.89 2 2 2h4v5l4-2 4 2v-5h4c1.11 0 2-.89 2-2V4c0-1.11-.89-2-2-2zm0 13H4v-2h16v2zm0-5H4V4h16v6z"></path></g>
<g id="card-travel"><path d="M20 6h-3V4c0-1.11-.89-2-2-2H9c-1.11 0-2 .89-2 2v2H4c-1.11 0-2 .89-2 2v11c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zM9 4h6v2H9V4zm11 15H4v-2h16v2zm0-5H4V8h3v2h2V8h6v2h2V8h3v6z"></path></g>
<g id="change-history"><path d="M12 7.77L18.39 18H5.61L12 7.77M12 4L2 20h20L12 4z"></path></g>
<g id="check"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"></path></g>
<g id="check-box"><path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"></path></g>
<g id="check-box-outline-blank"><path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"></path></g>
<g id="check-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"></path></g>
<g id="chevron-left"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></path></g>
<g id="chevron-right"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"></path></g>
<g id="chrome-reader-mode"><path d="M13 12h7v1.5h-7zm0-2.5h7V11h-7zm0 5h7V16h-7zM21 4H3c-1.1 0-2 .9-2 2v13c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 15h-9V6h9v13z"></path></g>
<g id="class"><path d="M18 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 4h5v8l-2.5-1.5L6 12V4z"></path></g>
<g id="clear"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path></g>
<g id="close"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path></g>
<g id="cloud"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z"></path></g>
<g id="cloud-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.5 14H8c-1.66 0-3-1.34-3-3s1.34-3 3-3l.14.01C8.58 8.28 10.13 7 12 7c2.21 0 4 1.79 4 4h.5c1.38 0 2.5 1.12 2.5 2.5S17.88 16 16.5 16z"></path></g>
<g id="cloud-done"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM10 17l-3.5-3.5 1.41-1.41L10 14.17 15.18 9l1.41 1.41L10 17z"></path></g>
<g id="cloud-download"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z"></path></g>
<g id="cloud-off"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4c-1.48 0-2.85.43-4.01 1.17l1.46 1.46C10.21 6.23 11.08 6 12 6c3.04 0 5.5 2.46 5.5 5.5v.5H19c1.66 0 3 1.34 3 3 0 1.13-.64 2.11-1.56 2.62l1.45 1.45C23.16 18.16 24 16.68 24 15c0-2.64-2.05-4.78-4.65-4.96zM3 5.27l2.75 2.74C2.56 8.15 0 10.77 0 14c0 3.31 2.69 6 6 6h11.73l2 2L21 20.73 4.27 4 3 5.27zM7.73 10l8 8H6c-2.21 0-4-1.79-4-4s1.79-4 4-4h1.73z"></path></g>
<g id="cloud-queue"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4s1.79-4 4-4h.71C7.37 7.69 9.48 6 12 6c3.04 0 5.5 2.46 5.5 5.5v.5H19c1.66 0 3 1.34 3 3s-1.34 3-3 3z"></path></g>
<g id="cloud-upload"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"></path></g>
<g id="code"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"></path></g>
<g id="compare-arrows"><path d="M9.01 14H2v2h7.01v3L13 15l-3.99-4v3zm5.98-1v-3H22V8h-7.01V5L11 9l3.99 4z"></path></g>
<g id="content-copy"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"></path></g>
<g id="content-cut"><path d="M9.64 7.64c.23-.5.36-1.05.36-1.64 0-2.21-1.79-4-4-4S2 3.79 2 6s1.79 4 4 4c.59 0 1.14-.13 1.64-.36L10 12l-2.36 2.36C7.14 14.13 6.59 14 6 14c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4c0-.59-.13-1.14-.36-1.64L12 14l7 7h3v-1L9.64 7.64zM6 8c-1.1 0-2-.89-2-2s.9-2 2-2 2 .89 2 2-.9 2-2 2zm0 12c-1.1 0-2-.89-2-2s.9-2 2-2 2 .89 2 2-.9 2-2 2zm6-7.5c-.28 0-.5-.22-.5-.5s.22-.5.5-.5.5.22.5.5-.22.5-.5.5zM19 3l-6 6 2 2 7-7V3z"></path></g>
<g id="content-paste"><path d="M19 2h-4.18C14.4.84 13.3 0 12 0c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z"></path></g>
<g id="copyright"><path d="M10.08 10.86c.05-.33.16-.62.3-.87s.34-.46.59-.62c.24-.15.54-.22.91-.23.23.01.44.05.63.13.2.09.38.21.52.36s.25.33.34.53.13.42.14.64h1.79c-.02-.47-.11-.9-.28-1.29s-.4-.73-.7-1.01-.66-.5-1.08-.66-.88-.23-1.39-.23c-.65 0-1.22.11-1.7.34s-.88.53-1.2.92-.56.84-.71 1.36S8 11.29 8 11.87v.27c0 .58.08 1.12.23 1.64s.39.97.71 1.35.72.69 1.2.91 1.05.34 1.7.34c.47 0 .91-.08 1.32-.23s.77-.36 1.08-.63.56-.58.74-.94.29-.74.3-1.15h-1.79c-.01.21-.06.4-.15.58s-.21.33-.36.46-.32.23-.52.3c-.19.07-.39.09-.6.1-.36-.01-.66-.08-.89-.23-.25-.16-.45-.37-.59-.62s-.25-.55-.3-.88-.08-.67-.08-1v-.27c0-.35.03-.68.08-1.01zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></g>
<g id="create"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"></path></g>
<g id="create-new-folder"><path d="M20 6h-8l-2-2H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-1 8h-3v3h-2v-3h-3v-2h3V9h2v3h3v2z"></path></g>
<g id="credit-card"><path d="M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z"></path></g>
<g id="dashboard"><path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"></path></g>
<g id="date-range"><path d="M9 11H7v2h2v-2zm4 0h-2v2h2v-2zm4 0h-2v2h2v-2zm2-7h-1V2h-2v2H8V2H6v2H5c-1.11 0-1.99.9-1.99 2L3 20c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V9h14v11z"></path></g>
<g id="delete"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"></path></g>
<g id="delete-forever"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zm2.46-7.12l1.41-1.41L12 12.59l2.12-2.12 1.41 1.41L13.41 14l2.12 2.12-1.41 1.41L12 15.41l-2.12 2.12-1.41-1.41L10.59 14l-2.13-2.12zM15.5 4l-1-1h-5l-1 1H5v2h14V4z"></path></g>
<g id="delete-sweep"><path d="M15 16h4v2h-4zm0-8h7v2h-7zm0 4h6v2h-6zM3 18c0 1.1.9 2 2 2h6c1.1 0 2-.9 2-2V8H3v10zM14 5h-3l-1-1H6L5 5H2v2h12z"></path></g>
<g id="description"><path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"></path></g>
<g id="dns"><path d="M20 13H4c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h16c.55 0 1-.45 1-1v-6c0-.55-.45-1-1-1zM7 19c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM20 3H4c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h16c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1zM7 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"></path></g>
<g id="done"><path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"></path></g>
<g id="done-all"><path d="M18 7l-1.41-1.41-6.34 6.34 1.41 1.41L18 7zm4.24-1.41L11.66 16.17 7.48 12l-1.41 1.41L11.66 19l12-12-1.42-1.41zM.41 13.41L6 19l1.41-1.41L1.83 12 .41 13.41z"></path></g>
<g id="donut-large"><path d="M11 5.08V2c-5 .5-9 4.81-9 10s4 9.5 9 10v-3.08c-3-.48-6-3.4-6-6.92s3-6.44 6-6.92zM18.97 11H22c-.47-5-4-8.53-9-9v3.08C16 5.51 18.54 8 18.97 11zM13 18.92V22c5-.47 8.53-4 9-9h-3.03c-.43 3-2.97 5.49-5.97 5.92z"></path></g>
<g id="donut-small"><path d="M11 9.16V2c-5 .5-9 4.79-9 10s4 9.5 9 10v-7.16c-1-.41-2-1.52-2-2.84s1-2.43 2-2.84zM14.86 11H22c-.48-4.75-4-8.53-9-9v7.16c1 .3 1.52.98 1.86 1.84zM13 14.84V22c5-.47 8.52-4.25 9-9h-7.14c-.34.86-.86 1.54-1.86 1.84z"></path></g>
<g id="drafts"><path d="M21.99 8c0-.72-.37-1.35-.94-1.7L12 1 2.95 6.3C2.38 6.65 2 7.28 2 8v10c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2l-.01-10zM12 13L3.74 7.84 12 3l8.26 4.84L12 13z"></path></g>
<g id="eject"><path d="M5 17h14v2H5zm7-12L5.33 15h13.34z"></path></g>
<g id="error"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"></path></g>
<g id="error-outline"><path d="M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"></path></g>
<g id="euro-symbol"><path d="M15 18.5c-2.51 0-4.68-1.42-5.76-3.5H15v-2H8.58c-.05-.33-.08-.66-.08-1s.03-.67.08-1H15V9H9.24C10.32 6.92 12.5 5.5 15 5.5c1.61 0 3.09.59 4.23 1.57L21 5.3C19.41 3.87 17.3 3 15 3c-3.92 0-7.24 2.51-8.48 6H3v2h3.06c-.04.33-.06.66-.06 1 0 .34.02.67.06 1H3v2h3.52c1.24 3.49 4.56 6 8.48 6 2.31 0 4.41-.87 6-2.3l-1.78-1.77c-1.13.98-2.6 1.57-4.22 1.57z"></path></g>
<g id="event"><path d="M17 12h-5v5h5v-5zM16 1v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2h-1V1h-2zm3 18H5V8h14v11z"></path></g>
<g id="event-seat"><path d="M4 18v3h3v-3h10v3h3v-6H4zm15-8h3v3h-3zM2 10h3v3H2zm15 3H7V5c0-1.1.9-2 2-2h6c1.1 0 2 .9 2 2v8z"></path></g>
<g id="exit-to-app"><path d="M10.09 15.59L11.5 17l5-5-5-5-1.41 1.41L12.67 11H3v2h9.67l-2.58 2.59zM19 3H5c-1.11 0-2 .9-2 2v4h2V5h14v14H5v-4H3v4c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"></path></g>
<g id="expand-less"><path d="M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z"></path></g>
<g id="expand-more"><path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"></path></g>
<g id="explore"><path d="M12 10.9c-.61 0-1.1.49-1.1 1.1s.49 1.1 1.1 1.1c.61 0 1.1-.49 1.1-1.1s-.49-1.1-1.1-1.1zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm2.19 12.19L6 18l3.81-8.19L18 6l-3.81 8.19z"></path></g>
<g id="extension"><path d="M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5C13 2.12 11.88 1 10.5 1S8 2.12 8 3.5V5H4c-1.1 0-1.99.9-1.99 2v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7 1.49 0 2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5c1.38 0 2.5-1.12 2.5-2.5S21.88 11 20.5 11z"></path></g>
<g id="face"><path d="M9 11.75c-.69 0-1.25.56-1.25 1.25s.56 1.25 1.25 1.25 1.25-.56 1.25-1.25-.56-1.25-1.25-1.25zm6 0c-.69 0-1.25.56-1.25 1.25s.56 1.25 1.25 1.25 1.25-.56 1.25-1.25-.56-1.25-1.25-1.25zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8 0-.29.02-.58.05-.86 2.36-1.05 4.23-2.98 5.21-5.37C11.07 8.33 14.05 10 17.42 10c.78 0 1.53-.09 2.25-.26.21.71.33 1.47.33 2.26 0 4.41-3.59 8-8 8z"></path></g>
<g id="favorite"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"></path></g>
<g id="favorite-border"><path d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z"></path></g>
<g id="feedback"><path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 12h-2v-2h2v2zm0-4h-2V6h2v4z"></path></g>
<g id="file-download"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path></g>
<g id="file-upload"><path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"></path></g>
<g id="filter-list"><path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"></path></g>
<g id="find-in-page"><path d="M20 19.59V8l-6-6H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c.45 0 .85-.15 1.19-.4l-4.43-4.43c-.8.52-1.74.83-2.76.83-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5c0 1.02-.31 1.96-.83 2.75L20 19.59zM9 13c0 1.66 1.34 3 3 3s3-1.34 3-3-1.34-3-3-3-3 1.34-3 3z"></path></g>
<g id="find-replace"><path d="M11 6c1.38 0 2.63.56 3.54 1.46L12 10h6V4l-2.05 2.05C14.68 4.78 12.93 4 11 4c-3.53 0-6.43 2.61-6.92 6H6.1c.46-2.28 2.48-4 4.9-4zm5.64 9.14c.66-.9 1.12-1.97 1.28-3.14H15.9c-.46 2.28-2.48 4-4.9 4-1.38 0-2.63-.56-3.54-1.46L10 12H4v6l2.05-2.05C7.32 17.22 9.07 18 11 18c1.55 0 2.98-.51 4.14-1.36L20 21.49 21.49 20l-4.85-4.86z"></path></g>
<g id="fingerprint"><path d="M17.81 4.47c-.08 0-.16-.02-.23-.06C15.66 3.42 14 3 12.01 3c-1.98 0-3.86.47-5.57 1.41-.24.13-.54.04-.68-.2-.13-.24-.04-.55.2-.68C7.82 2.52 9.86 2 12.01 2c2.13 0 3.99.47 6.03 1.52.25.13.34.43.21.67-.09.18-.26.28-.44.28zM3.5 9.72c-.1 0-.2-.03-.29-.09-.23-.16-.28-.47-.12-.7.99-1.4 2.25-2.5 3.75-3.27C9.98 4.04 14 4.03 17.15 5.65c1.5.77 2.76 1.86 3.75 3.25.16.22.11.54-.12.7-.23.16-.54.11-.7-.12-.9-1.26-2.04-2.25-3.39-2.94-2.87-1.47-6.54-1.47-9.4.01-1.36.7-2.5 1.7-3.4 2.96-.08.14-.23.21-.39.21zm6.25 12.07c-.13 0-.26-.05-.35-.15-.87-.87-1.34-1.43-2.01-2.64-.69-1.23-1.05-2.73-1.05-4.34 0-2.97 2.54-5.39 5.66-5.39s5.66 2.42 5.66 5.39c0 .28-.22.5-.5.5s-.5-.22-.5-.5c0-2.42-2.09-4.39-4.66-4.39-2.57 0-4.66 1.97-4.66 4.39 0 1.44.32 2.77.93 3.85.64 1.15 1.08 1.64 1.85 2.42.19.2.19.51 0 .71-.11.1-.24.15-.37.15zm7.17-1.85c-1.19 0-2.24-.3-3.1-.89-1.49-1.01-2.38-2.65-2.38-4.39 0-.28.22-.5.5-.5s.5.22.5.5c0 1.41.72 2.74 1.94 3.56.71.48 1.54.71 2.54.71.24 0 .64-.03 1.04-.1.27-.05.53.13.58.41.05.27-.13.53-.41.58-.57.11-1.07.12-1.21.12zM14.91 22c-.04 0-.09-.01-.13-.02-1.59-.44-2.63-1.03-3.72-2.1-1.4-1.39-2.17-3.24-2.17-5.22 0-1.62 1.38-2.94 3.08-2.94 1.7 0 3.08 1.32 3.08 2.94 0 1.07.93 1.94 2.08 1.94s2.08-.87 2.08-1.94c0-3.77-3.25-6.83-7.25-6.83-2.84 0-5.44 1.58-6.61 4.03-.39.81-.59 1.76-.59 2.8 0 .78.07 2.01.67 3.61.1.26-.03.55-.29.64-.26.1-.55-.04-.64-.29-.49-1.31-.73-2.61-.73-3.96 0-1.2.23-2.29.68-3.24 1.33-2.79 4.28-4.6 7.51-4.6 4.55 0 8.25 3.51 8.25 7.83 0 1.62-1.38 2.94-3.08 2.94s-3.08-1.32-3.08-2.94c0-1.07-.93-1.94-2.08-1.94s-2.08.87-2.08 1.94c0 1.71.66 3.31 1.87 4.51.95.94 1.86 1.46 3.27 1.85.27.07.42.35.35.61-.05.23-.26.38-.47.38z"></path></g>
<g id="first-page"><path d="M18.41 16.59L13.82 12l4.59-4.59L17 6l-6 6 6 6zM6 6h2v12H6z"></path></g>
<g id="flag"><path d="M14.4 6L14 4H5v17h2v-7h5.6l.4 2h7V6z"></path></g>
<g id="flight-land"><path d="M2.5 19h19v2h-19zm7.18-5.73l4.35 1.16 5.31 1.42c.8.21 1.62-.26 1.84-1.06.21-.8-.26-1.62-1.06-1.84l-5.31-1.42-2.76-9.02L10.12 2v8.28L5.15 8.95l-.93-2.32-1.45-.39v5.17l1.6.43 5.31 1.43z"></path></g>
<g id="flight-takeoff"><path d="M2.5 19h19v2h-19zm19.57-9.36c-.21-.8-1.04-1.28-1.84-1.06L14.92 10l-6.9-6.43-1.93.51 4.14 7.17-4.97 1.33-1.97-1.54-1.45.39 1.82 3.16.77 1.33 1.6-.43 5.31-1.42 4.35-1.16L21 11.49c.81-.23 1.28-1.05 1.07-1.85z"></path></g>
<g id="flip-to-back"><path d="M9 7H7v2h2V7zm0 4H7v2h2v-2zm0-8c-1.11 0-2 .9-2 2h2V3zm4 12h-2v2h2v-2zm6-12v2h2c0-1.1-.9-2-2-2zm-6 0h-2v2h2V3zM9 17v-2H7c0 1.1.89 2 2 2zm10-4h2v-2h-2v2zm0-4h2V7h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2zM5 7H3v12c0 1.1.89 2 2 2h12v-2H5V7zm10-2h2V3h-2v2zm0 12h2v-2h-2v2z"></path></g>
<g id="flip-to-front"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm2 4v-2H3c0 1.1.89 2 2 2zM3 9h2V7H3v2zm12 12h2v-2h-2v2zm4-18H9c-1.11 0-2 .9-2 2v10c0 1.1.89 2 2 2h10c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 12H9V5h10v10zm-8 6h2v-2h-2v2zm-4 0h2v-2H7v2z"></path></g>
<g id="folder"><path d="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"></path></g>
<g id="folder-open"><path d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V8h16v10z"></path></g>
<g id="folder-shared"><path d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-5 3c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm4 8h-8v-1c0-1.33 2.67-2 4-2s4 .67 4 2v1z"></path></g>
<g id="font-download"><path d="M9.93 13.5h4.14L12 7.98zM20 2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-4.05 16.5l-1.14-3H9.17l-1.12 3H5.96l5.11-13h1.86l5.11 13h-2.09z"></path></g>
<g id="forward"><path d="M12 8V4l8 8-8 8v-4H4V8z"></path></g>
<g id="fullscreen"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"></path></g>
<g id="fullscreen-exit"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"></path></g>
<g id="g-translate"><path d="M20 5h-9.12L10 2H4c-1.1 0-2 .9-2 2v13c0 1.1.9 2 2 2h7l1 3h8c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zM7.17 14.59c-2.25 0-4.09-1.83-4.09-4.09s1.83-4.09 4.09-4.09c1.04 0 1.99.37 2.74 1.07l.07.06-1.23 1.18-.06-.05c-.29-.27-.78-.59-1.52-.59-1.31 0-2.38 1.09-2.38 2.42s1.07 2.42 2.38 2.42c1.37 0 1.96-.87 2.12-1.46H7.08V9.91h3.95l.01.07c.04.21.05.4.05.61 0 2.35-1.61 4-3.92 4zm6.03-1.71c.33.6.74 1.18 1.19 1.7l-.54.53-.65-2.23zm.77-.76h-.99l-.31-1.04h3.99s-.34 1.31-1.56 2.74c-.52-.62-.89-1.23-1.13-1.7zM21 20c0 .55-.45 1-1 1h-7l2-2-.81-2.77.92-.92L17.79 18l.73-.73-2.71-2.68c.9-1.03 1.6-2.25 1.92-3.51H19v-1.04h-3.64V9h-1.04v1.04h-1.96L11.18 6H20c.55 0 1 .45 1 1v13z"></path></g>
<g id="gavel"><path d="M1 21h12v2H1zM5.245 8.07l2.83-2.827 14.14 14.142-2.828 2.828zM12.317 1l5.657 5.656-2.83 2.83-5.654-5.66zM3.825 9.485l5.657 5.657-2.828 2.828-5.657-5.657z"></path></g>
<g id="gesture"><path d="M4.59 6.89c.7-.71 1.4-1.35 1.71-1.22.5.2 0 1.03-.3 1.52-.25.42-2.86 3.89-2.86 6.31 0 1.28.48 2.34 1.34 2.98.75.56 1.74.73 2.64.46 1.07-.31 1.95-1.4 3.06-2.77 1.21-1.49 2.83-3.44 4.08-3.44 1.63 0 1.65 1.01 1.76 1.79-3.78.64-5.38 3.67-5.38 5.37 0 1.7 1.44 3.09 3.21 3.09 1.63 0 4.29-1.33 4.69-6.1H21v-2.5h-2.47c-.15-1.65-1.09-4.2-4.03-4.2-2.25 0-4.18 1.91-4.94 2.84-.58.73-2.06 2.48-2.29 2.72-.25.3-.68.84-1.11.84-.45 0-.72-.83-.36-1.92.35-1.09 1.4-2.86 1.85-3.52.78-1.14 1.3-1.92 1.3-3.28C8.95 3.69 7.31 3 6.44 3 5.12 3 3.97 4 3.72 4.25c-.36.36-.66.66-.88.93l1.75 1.71zm9.29 11.66c-.31 0-.74-.26-.74-.72 0-.6.73-2.2 2.87-2.76-.3 2.69-1.43 3.48-2.13 3.48z"></path></g>
<g id="get-app"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path></g>
<g id="gif"><path d="M11.5 9H13v6h-1.5zM9 9H6c-.6 0-1 .5-1 1v4c0 .5.4 1 1 1h3c.6 0 1-.5 1-1v-2H8.5v1.5h-2v-3H10V10c0-.5-.4-1-1-1zm10 1.5V9h-4.5v6H16v-2h2v-1.5h-2v-1z"></path></g>
<g id="grade"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"></path></g>
<g id="group-work"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM8 17.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5zM9.5 8c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5S9.5 9.38 9.5 8zm6.5 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"></path></g>
<g id="help"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"></path></g>
<g id="help-outline"><path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"></path></g>
<g id="highlight-off"><path d="M14.59 8L12 10.59 9.41 8 8 9.41 10.59 12 8 14.59 9.41 16 12 13.41 14.59 16 16 14.59 13.41 12 16 9.41 14.59 8zM12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></g>
<g id="history"><path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"></path></g>
<g id="home"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"></path></g>
<g id="hourglass-empty"><path d="M6 2v6h.01L6 8.01 10 12l-4 4 .01.01H6V22h12v-5.99h-.01L18 16l-4-4 4-3.99-.01-.01H18V2H6zm10 14.5V20H8v-3.5l4-4 4 4zm-4-5l-4-4V4h8v3.5l-4 4z"></path></g>
<g id="hourglass-full"><path d="M6 2v6h.01L6 8.01 10 12l-4 4 .01.01H6V22h12v-5.99h-.01L18 16l-4-4 4-3.99-.01-.01H18V2H6z"></path></g>
<g id="http"><path d="M4.5 11h-2V9H1v6h1.5v-2.5h2V15H6V9H4.5v2zm2.5-.5h1.5V15H10v-4.5h1.5V9H7v1.5zm5.5 0H14V15h1.5v-4.5H17V9h-4.5v1.5zm9-1.5H18v6h1.5v-2h2c.8 0 1.5-.7 1.5-1.5v-1c0-.8-.7-1.5-1.5-1.5zm0 2.5h-2v-1h2v1z"></path></g>
<g id="https"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"></path></g>
<g id="important-devices"><path d="M23 11.01L18 11c-.55 0-1 .45-1 1v9c0 .55.45 1 1 1h5c.55 0 1-.45 1-1v-9c0-.55-.45-.99-1-.99zM23 20h-5v-7h5v7zM20 2H2C.89 2 0 2.89 0 4v12c0 1.1.89 2 2 2h7v2H7v2h8v-2h-2v-2h2v-2H2V4h18v5h2V4c0-1.11-.9-2-2-2zm-8.03 7L11 6l-.97 3H7l2.47 1.76-.94 2.91 2.47-1.8 2.47 1.8-.94-2.91L15 9h-3.03z"></path></g>
<g id="inbox"><path d="M19 3H4.99c-1.11 0-1.98.89-1.98 2L3 19c0 1.1.88 2 1.99 2H19c1.1 0 2-.9 2-2V5c0-1.11-.9-2-2-2zm0 12h-4c0 1.66-1.35 3-3 3s-3-1.34-3-3H4.99V5H19v10z"></path></g>
<g id="indeterminate-check-box"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10H7v-2h10v2z"></path></g>
<g id="info"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"></path></g>
<g id="info-outline"><path d="M11 17h2v-6h-2v6zm1-15C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zM11 9h2V7h-2v2z"></path></g>
<g id="input"><path d="M21 3.01H3c-1.1 0-2 .9-2 2V9h2V4.99h18v14.03H3V15H1v4.01c0 1.1.9 1.98 2 1.98h18c1.1 0 2-.88 2-1.98v-14c0-1.11-.9-2-2-2zM11 16l4-4-4-4v3H1v2h10v3z"></path></g>
<g id="invert-colors"><path d="M17.66 7.93L12 2.27 6.34 7.93c-3.12 3.12-3.12 8.19 0 11.31C7.9 20.8 9.95 21.58 12 21.58c2.05 0 4.1-.78 5.66-2.34 3.12-3.12 3.12-8.19 0-11.31zM12 19.59c-1.6 0-3.11-.62-4.24-1.76C6.62 16.69 6 15.19 6 13.59s.62-3.11 1.76-4.24L12 5.1v14.49z"></path></g>
<g id="label"><path d="M17.63 5.84C17.27 5.33 16.67 5 16 5L5 5.01C3.9 5.01 3 5.9 3 7v10c0 1.1.9 1.99 2 1.99L16 19c.67 0 1.27-.33 1.63-.84L22 12l-4.37-6.16z"></path></g>
<g id="label-outline"><path d="M17.63 5.84C17.27 5.33 16.67 5 16 5L5 5.01C3.9 5.01 3 5.9 3 7v10c0 1.1.9 1.99 2 1.99L16 19c.67 0 1.27-.33 1.63-.84L22 12l-4.37-6.16zM16 17H5V7h11l3.55 5L16 17z"></path></g>
<g id="language"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"></path></g>
<g id="last-page"><path d="M5.59 7.41L10.18 12l-4.59 4.59L7 18l6-6-6-6zM16 6h2v12h-2z"></path></g>
<g id="launch"><path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"></path></g>
<g id="lightbulb-outline"><path d="M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6C7.8 12.16 7 10.63 7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z"></path></g>
<g id="line-style"><path d="M3 16h5v-2H3v2zm6.5 0h5v-2h-5v2zm6.5 0h5v-2h-5v2zM3 20h2v-2H3v2zm4 0h2v-2H7v2zm4 0h2v-2h-2v2zm4 0h2v-2h-2v2zm4 0h2v-2h-2v2zM3 12h8v-2H3v2zm10 0h8v-2h-8v2zM3 4v4h18V4H3z"></path></g>
<g id="line-weight"><path d="M3 17h18v-2H3v2zm0 3h18v-1H3v1zm0-7h18v-3H3v3zm0-9v4h18V4H3z"></path></g>
<g id="link"><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"></path></g>
<g id="list"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"></path></g>
<g id="lock"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"></path></g>
<g id="lock-open"><path d="M12 17c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm6-9h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6h1.9c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm0 12H6V10h12v10z"></path></g>
<g id="lock-outline"><path d="M12 17c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm6-9h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM8.9 6c0-1.71 1.39-3.1 3.1-3.1s3.1 1.39 3.1 3.1v2H8.9V6zM18 20H6V10h12v10z"></path></g>
<g id="low-priority"><path d="M14 5h8v2h-8zm0 5.5h8v2h-8zm0 5.5h8v2h-8zM2 11.5C2 15.08 4.92 18 8.5 18H9v2l3-3-3-3v2h-.5C6.02 16 4 13.98 4 11.5S6.02 7 8.5 7H12V5H8.5C4.92 5 2 7.92 2 11.5z"></path></g>
<g id="loyalty"><path d="M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.36.86.58 1.41.58.55 0 1.05-.22 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7zm11.77 8.27L13 19.54l-4.27-4.27C8.28 14.81 8 14.19 8 13.5c0-1.38 1.12-2.5 2.5-2.5.69 0 1.32.28 1.77.74l.73.72.73-.73c.45-.45 1.08-.73 1.77-.73 1.38 0 2.5 1.12 2.5 2.5 0 .69-.28 1.32-.73 1.77z"></path></g>
<g id="mail"><path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"></path></g>
<g id="markunread"><path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"></path></g>
<g id="markunread-mailbox"><path d="M20 6H10v6H8V4h6V0H6v6H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2z"></path></g>
<g id="menu"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"></path></g>
<g id="more-horiz"><path d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"></path></g>
<g id="more-vert"><path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"></path></g>
<g id="motorcycle"><path d="M19.44 9.03L15.41 5H11v2h3.59l2 2H5c-2.8 0-5 2.2-5 5s2.2 5 5 5c2.46 0 4.45-1.69 4.9-4h1.65l2.77-2.77c-.21.54-.32 1.14-.32 1.77 0 2.8 2.2 5 5 5s5-2.2 5-5c0-2.65-1.97-4.77-4.56-4.97zM7.82 15C7.4 16.15 6.28 17 5 17c-1.63 0-3-1.37-3-3s1.37-3 3-3c1.28 0 2.4.85 2.82 2H5v2h2.82zM19 17c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3z"></path></g>
<g id="move-to-inbox"><path d="M19 3H4.99c-1.11 0-1.98.9-1.98 2L3 19c0 1.1.88 2 1.99 2H19c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 12h-4c0 1.66-1.35 3-3 3s-3-1.34-3-3H4.99V5H19v10zm-3-5h-2V7h-4v3H8l4 4 4-4z"></path></g>
<g id="next-week"><path d="M20 7h-4V5c0-.55-.22-1.05-.59-1.41C15.05 3.22 14.55 3 14 3h-4c-1.1 0-2 .9-2 2v2H4c-1.1 0-2 .9-2 2v11c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2zM10 5h4v2h-4V5zm1 13.5l-1-1 3-3-3-3 1-1 4 4-4 4z"></path></g>
<g id="note-add"><path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 14h-3v3h-2v-3H8v-2h3v-3h2v3h3v2zm-3-7V3.5L18.5 9H13z"></path></g>
<g id="offline-pin"><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm5 16H7v-2h10v2zm-6.7-4L7 10.7l1.4-1.4 1.9 1.9 5.3-5.3L17 7.3 10.3 14z"></path></g>
<g id="opacity"><path d="M17.66 8L12 2.35 6.34 8C4.78 9.56 4 11.64 4 13.64s.78 4.11 2.34 5.67 3.61 2.35 5.66 2.35 4.1-.79 5.66-2.35S20 15.64 20 13.64 19.22 9.56 17.66 8zM6 14c.01-2 .62-3.27 1.76-4.4L12 5.27l4.24 4.38C17.38 10.77 17.99 12 18 14H6z"></path></g>
<g id="open-in-browser"><path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h4v-2H5V8h14v10h-4v2h4c1.1 0 2-.9 2-2V6c0-1.1-.89-2-2-2zm-7 6l-4 4h3v6h2v-6h3l-4-4z"></path></g>
<g id="open-in-new"><path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"></path></g>
<g id="open-with"><path d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"></path></g>
<g id="pageview"><path d="M11.5 9C10.12 9 9 10.12 9 11.5s1.12 2.5 2.5 2.5 2.5-1.12 2.5-2.5S12.88 9 11.5 9zM20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-3.21 14.21l-2.91-2.91c-.69.44-1.51.7-2.39.7C9.01 16 7 13.99 7 11.5S9.01 7 11.5 7 16 9.01 16 11.5c0 .88-.26 1.69-.7 2.39l2.91 2.9-1.42 1.42z"></path></g>
<g id="pan-tool"><path d="M23 5.5V20c0 2.2-1.8 4-4 4h-7.3c-1.08 0-2.1-.43-2.85-1.19L1 14.83s1.26-1.23 1.3-1.25c.22-.19.49-.29.79-.29.22 0 .42.06.6.16.04.01 4.31 2.46 4.31 2.46V4c0-.83.67-1.5 1.5-1.5S11 3.17 11 4v7h1V1.5c0-.83.67-1.5 1.5-1.5S15 .67 15 1.5V11h1V2.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5V11h1V5.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5z"></path></g>
<g id="payment"><path d="M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z"></path></g>
<g id="perm-camera-mic"><path d="M20 5h-3.17L15 3H9L7.17 5H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7v-2.09c-2.83-.48-5-2.94-5-5.91h2c0 2.21 1.79 4 4 4s4-1.79 4-4h2c0 2.97-2.17 5.43-5 5.91V21h7c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-6 8c0 1.1-.9 2-2 2s-2-.9-2-2V9c0-1.1.9-2 2-2s2 .9 2 2v4z"></path></g>
<g id="perm-contact-calendar"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm6 12H6v-1c0-2 4-3.1 6-3.1s6 1.1 6 3.1v1z"></path></g>
<g id="perm-data-setting"><path d="M18.99 11.5c.34 0 .67.03 1 .07L20 0 0 20h11.56c-.04-.33-.07-.66-.07-1 0-4.14 3.36-7.5 7.5-7.5zm3.71 7.99c.02-.16.04-.32.04-.49 0-.17-.01-.33-.04-.49l1.06-.83c.09-.08.12-.21.06-.32l-1-1.73c-.06-.11-.19-.15-.31-.11l-1.24.5c-.26-.2-.54-.37-.85-.49l-.19-1.32c-.01-.12-.12-.21-.24-.21h-2c-.12 0-.23.09-.25.21l-.19 1.32c-.3.13-.59.29-.85.49l-1.24-.5c-.11-.04-.24 0-.31.11l-1 1.73c-.06.11-.04.24.06.32l1.06.83c-.02.16-.03.32-.03.49 0 .17.01.33.03.49l-1.06.83c-.09.08-.12.21-.06.32l1 1.73c.06.11.19.15.31.11l1.24-.5c.26.2.54.37.85.49l.19 1.32c.02.12.12.21.25.21h2c.12 0 .23-.09.25-.21l.19-1.32c.3-.13.59-.29.84-.49l1.25.5c.11.04.24 0 .31-.11l1-1.73c.06-.11.03-.24-.06-.32l-1.07-.83zm-3.71 1.01c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"></path></g>
<g id="perm-device-information"><path d="M13 7h-2v2h2V7zm0 4h-2v6h2v-6zm4-9.99L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z"></path></g>
<g id="perm-identity"><path d="M12 5.9c1.16 0 2.1.94 2.1 2.1s-.94 2.1-2.1 2.1S9.9 9.16 9.9 8s.94-2.1 2.1-2.1m0 9c2.97 0 6.1 1.46 6.1 2.1v1.1H5.9V17c0-.64 3.13-2.1 6.1-2.1M12 4C9.79 4 8 5.79 8 8s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 9c-2.67 0-8 1.34-8 4v3h16v-3c0-2.66-5.33-4-8-4z"></path></g>
<g id="perm-media"><path d="M2 6H0v5h.01L0 20c0 1.1.9 2 2 2h18v-2H2V6zm20-2h-8l-2-2H6c-1.1 0-1.99.9-1.99 2L4 16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM7 15l4.5-6 3.5 4.51 2.5-3.01L21 15H7z"></path></g>
<g id="perm-phone-msg"><path d="M20 15.5c-1.25 0-2.45-.2-3.57-.57-.35-.11-.74-.03-1.02.24l-2.2 2.2c-2.83-1.44-5.15-3.75-6.59-6.58l2.2-2.21c.28-.27.36-.66.25-1.01C8.7 6.45 8.5 5.25 8.5 4c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1 0 9.39 7.61 17 17 17 .55 0 1-.45 1-1v-3.5c0-.55-.45-1-1-1zM12 3v10l3-3h6V3h-9z"></path></g>
<g id="perm-scan-wifi"><path d="M12 3C6.95 3 3.15 4.85 0 7.23L12 22 24 7.25C20.85 4.87 17.05 3 12 3zm1 13h-2v-6h2v6zm-2-8V6h2v2h-2z"></path></g>
<g id="pets"><circle cx="4.5" cy="9.5" r="2.5"></circle><circle cx="9" cy="5.5" r="2.5"></circle><circle cx="15" cy="5.5" r="2.5"></circle><circle cx="19.5" cy="9.5" r="2.5"></circle><path d="M17.34 14.86c-.87-1.02-1.6-1.89-2.48-2.91-.46-.54-1.05-1.08-1.75-1.32-.11-.04-.22-.07-.33-.09-.25-.04-.52-.04-.78-.04s-.53 0-.79.05c-.11.02-.22.05-.33.09-.7.24-1.28.78-1.75 1.32-.87 1.02-1.6 1.89-2.48 2.91-1.31 1.31-2.92 2.76-2.62 4.79.29 1.02 1.02 2.03 2.33 2.32.73.15 3.06-.44 5.54-.44h.18c2.48 0 4.81.58 5.54.44 1.31-.29 2.04-1.31 2.33-2.32.31-2.04-1.3-3.49-2.61-4.8z"></path></g>
<g id="picture-in-picture"><path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 1.98 2 1.98h18c1.1 0 2-.88 2-1.98V5c0-1.1-.9-2-2-2zm0 16.01H3V4.98h18v14.03z"></path></g>
<g id="picture-in-picture-alt"><path d="M19 11h-8v6h8v-6zm4 8V4.98C23 3.88 22.1 3 21 3H3c-1.1 0-2 .88-2 1.98V19c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2zm-2 .02H3V4.97h18v14.05z"></path></g>
<g id="play-for-work"><path d="M11 5v5.59H7.5l4.5 4.5 4.5-4.5H13V5h-2zm-5 9c0 3.31 2.69 6 6 6s6-2.69 6-6h-2c0 2.21-1.79 4-4 4s-4-1.79-4-4H6z"></path></g>
<g id="polymer"><path d="M19 4h-4L7.11 16.63 4.5 12 9 4H5L.5 12 5 20h4l7.89-12.63L19.5 12 15 20h4l4.5-8z"></path></g>
<g id="power-settings-new"><path d="M13 3h-2v10h2V3zm4.83 2.17l-1.42 1.42C17.99 7.86 19 9.81 19 12c0 3.87-3.13 7-7 7s-7-3.13-7-7c0-2.19 1.01-4.14 2.58-5.42L6.17 5.17C4.23 6.82 3 9.26 3 12c0 4.97 4.03 9 9 9s9-4.03 9-9c0-2.74-1.23-5.18-3.17-6.83z"></path></g>
<g id="pregnant-woman"><path d="M9 4c0-1.11.89-2 2-2s2 .89 2 2-.89 2-2 2-2-.89-2-2zm7 9c-.01-1.34-.83-2.51-2-3 0-1.66-1.34-3-3-3s-3 1.34-3 3v7h2v5h3v-5h3v-4z"></path></g>
<g id="print"><path d="M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z"></path></g>
<g id="query-builder"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z"></path></g>
<g id="question-answer"><path d="M21 6h-2v9H6v2c0 .55.45 1 1 1h11l4 4V7c0-.55-.45-1-1-1zm-4 6V3c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1v14l4-4h10c.55 0 1-.45 1-1z"></path></g>
<g id="radio-button-checked"><path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zm0-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"></path></g>
<g id="radio-button-unchecked"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"></path></g>
<g id="receipt"><path d="M18 17H6v-2h12v2zm0-4H6v-2h12v2zm0-4H6V7h12v2zM3 22l1.5-1.5L6 22l1.5-1.5L9 22l1.5-1.5L12 22l1.5-1.5L15 22l1.5-1.5L18 22l1.5-1.5L21 22V2l-1.5 1.5L18 2l-1.5 1.5L15 2l-1.5 1.5L12 2l-1.5 1.5L9 2 7.5 3.5 6 2 4.5 3.5 3 2v20z"></path></g>
<g id="record-voice-over"><circle cx="9" cy="9" r="4"></circle><path d="M9 15c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4zm7.76-9.64l-1.68 1.69c.84 1.18.84 2.71 0 3.89l1.68 1.69c2.02-2.02 2.02-5.07 0-7.27zM20.07 2l-1.63 1.63c2.77 3.02 2.77 7.56 0 10.74L20.07 16c3.9-3.89 3.91-9.95 0-14z"></path></g>
<g id="redeem"><path d="M20 6h-2.18c.11-.31.18-.65.18-1 0-1.66-1.34-3-3-3-1.05 0-1.96.54-2.5 1.35l-.5.67-.5-.68C10.96 2.54 10.05 2 9 2 7.34 2 6 3.34 6 5c0 .35.07.69.18 1H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-5-2c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM9 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm11 15H4v-2h16v2zm0-5H4V8h5.08L7 10.83 8.62 12 11 8.76l1-1.36 1 1.36L15.38 12 17 10.83 14.92 8H20v6z"></path></g>
<g id="redo"><path d="M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z"></path></g>
<g id="refresh"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"></path></g>
<g id="remove"><path d="M19 13H5v-2h14v2z"></path></g>
<g id="remove-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11H7v-2h10v2z"></path></g>
<g id="remove-circle-outline"><path d="M7 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></g>
<g id="remove-shopping-cart"><path d="M22.73 22.73L2.77 2.77 2 2l-.73-.73L0 2.54l4.39 4.39 2.21 4.66-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h7.46l1.38 1.38c-.5.36-.83.95-.83 1.62 0 1.1.89 2 1.99 2 .67 0 1.26-.33 1.62-.84L21.46 24l1.27-1.27zM7.42 15c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h2.36l2 2H7.42zm8.13-2c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.08-.14.12-.31.12-.48 0-.55-.45-1-1-1H6.54l9.01 9zM7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2z"></path></g>
<g id="reorder"><path d="M3 15h18v-2H3v2zm0 4h18v-2H3v2zm0-8h18V9H3v2zm0-6v2h18V5H3z"></path></g>
<g id="reply"><path d="M10 9V5l-7 7 7 7v-4.1c5 0 8.5 1.6 11 5.1-1-5-4-10-11-11z"></path></g>
<g id="reply-all"><path d="M7 8V5l-7 7 7 7v-3l-4-4 4-4zm6 1V5l-7 7 7 7v-4.1c5 0 8.5 1.6 11 5.1-1-5-4-10-11-11z"></path></g>
<g id="report"><path d="M15.73 3H8.27L3 8.27v7.46L8.27 21h7.46L21 15.73V8.27L15.73 3zM12 17.3c-.72 0-1.3-.58-1.3-1.3 0-.72.58-1.3 1.3-1.3.72 0 1.3.58 1.3 1.3 0 .72-.58 1.3-1.3 1.3zm1-4.3h-2V7h2v6z"></path></g>
<g id="report-problem"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"></path></g>
<g id="restore"><path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"></path></g>
<g id="restore-page"><path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm-2 16c-2.05 0-3.81-1.24-4.58-3h1.71c.63.9 1.68 1.5 2.87 1.5 1.93 0 3.5-1.57 3.5-3.5S13.93 9.5 12 9.5c-1.35 0-2.52.78-3.1 1.9l1.6 1.6h-4V9l1.3 1.3C8.69 8.92 10.23 8 12 8c2.76 0 5 2.24 5 5s-2.24 5-5 5z"></path></g>
<g id="room"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"></path></g>
<g id="rounded-corner"><path d="M19 19h2v2h-2v-2zm0-2h2v-2h-2v2zM3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm0-4h2V3H3v2zm4 0h2V3H7v2zm8 16h2v-2h-2v2zm-4 0h2v-2h-2v2zm4 0h2v-2h-2v2zm-8 0h2v-2H7v2zm-4 0h2v-2H3v2zM21 8c0-2.76-2.24-5-5-5h-5v2h5c1.65 0 3 1.35 3 3v5h2V8z"></path></g>
<g id="rowing"><path d="M8.5 14.5L4 19l1.5 1.5L9 17h2l-2.5-2.5zM15 1c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 20.01L18 24l-2.99-3.01V19.5l-7.1-7.09c-.31.05-.61.07-.91.07v-2.16c1.66.03 3.61-.87 4.67-2.04l1.4-1.55c.19-.21.43-.38.69-.5.29-.14.62-.23.96-.23h.03C15.99 6.01 17 7.02 17 8.26v5.75c0 .84-.35 1.61-.92 2.16l-3.58-3.58v-2.27c-.63.52-1.43 1.02-2.29 1.39L16.5 18H18l3 3.01z"></path></g>
<g id="save"><path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"></path></g>
<g id="schedule"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z"></path></g>
<g id="search"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path></g>
<g id="select-all"><path d="M3 5h2V3c-1.1 0-2 .9-2 2zm0 8h2v-2H3v2zm4 8h2v-2H7v2zM3 9h2V7H3v2zm10-6h-2v2h2V3zm6 0v2h2c0-1.1-.9-2-2-2zM5 21v-2H3c0 1.1.9 2 2 2zm-2-4h2v-2H3v2zM9 3H7v2h2V3zm2 18h2v-2h-2v2zm8-8h2v-2h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2zm0-12h2V7h-2v2zm0 8h2v-2h-2v2zm-4 4h2v-2h-2v2zm0-16h2V3h-2v2zM7 17h10V7H7v10zm2-8h6v6H9V9z"></path></g>
<g id="send"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"></path></g>
<g id="settings"><path d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"></path></g>
<g id="settings-applications"><path d="M12 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm7-7H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-1.75 9c0 .23-.02.46-.05.68l1.48 1.16c.13.11.17.3.08.45l-1.4 2.42c-.09.15-.27.21-.43.15l-1.74-.7c-.36.28-.76.51-1.18.69l-.26 1.85c-.03.17-.18.3-.35.3h-2.8c-.17 0-.32-.13-.35-.29l-.26-1.85c-.43-.18-.82-.41-1.18-.69l-1.74.7c-.16.06-.34 0-.43-.15l-1.4-2.42c-.09-.15-.05-.34.08-.45l1.48-1.16c-.03-.23-.05-.46-.05-.69 0-.23.02-.46.05-.68l-1.48-1.16c-.13-.11-.17-.3-.08-.45l1.4-2.42c.09-.15.27-.21.43-.15l1.74.7c.36-.28.76-.51 1.18-.69l.26-1.85c.03-.17.18-.3.35-.3h2.8c.17 0 .32.13.35.29l.26 1.85c.43.18.82.41 1.18.69l1.74-.7c.16-.06.34 0 .43.15l1.4 2.42c.09.15.05.34-.08.45l-1.48 1.16c.03.23.05.46.05.69z"></path></g>
<g id="settings-backup-restore"><path d="M14 12c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm-2-9c-4.97 0-9 4.03-9 9H0l4 4 4-4H5c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.51 0-2.91-.49-4.06-1.3l-1.42 1.44C8.04 20.3 9.94 21 12 21c4.97 0 9-4.03 9-9s-4.03-9-9-9z"></path></g>
<g id="settings-bluetooth"><path d="M11 24h2v-2h-2v2zm-4 0h2v-2H7v2zm8 0h2v-2h-2v2zm2.71-18.29L12 0h-1v7.59L6.41 3 5 4.41 10.59 10 5 15.59 6.41 17 11 12.41V20h1l5.71-5.71-4.3-4.29 4.3-4.29zM13 3.83l1.88 1.88L13 7.59V3.83zm1.88 10.46L13 16.17v-3.76l1.88 1.88z"></path></g>
<g id="settings-brightness"><path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16.01H3V4.99h18v14.02zM8 16h2.5l1.5 1.5 1.5-1.5H16v-2.5l1.5-1.5-1.5-1.5V8h-2.5L12 6.5 10.5 8H8v2.5L6.5 12 8 13.5V16zm4-7c1.66 0 3 1.34 3 3s-1.34 3-3 3V9z"></path></g>
<g id="settings-cell"><path d="M7 24h2v-2H7v2zm4 0h2v-2h-2v2zm4 0h2v-2h-2v2zM16 .01L8 0C6.9 0 6 .9 6 2v16c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V2c0-1.1-.9-1.99-2-1.99zM16 16H8V4h8v12z"></path></g>
<g id="settings-ethernet"><path d="M7.77 6.76L6.23 5.48.82 12l5.41 6.52 1.54-1.28L3.42 12l4.35-5.24zM7 13h2v-2H7v2zm10-2h-2v2h2v-2zm-6 2h2v-2h-2v2zm6.77-7.52l-1.54 1.28L20.58 12l-4.35 5.24 1.54 1.28L23.18 12l-5.41-6.52z"></path></g>
<g id="settings-input-antenna"><path d="M12 5c-3.87 0-7 3.13-7 7h2c0-2.76 2.24-5 5-5s5 2.24 5 5h2c0-3.87-3.13-7-7-7zm1 9.29c.88-.39 1.5-1.26 1.5-2.29 0-1.38-1.12-2.5-2.5-2.5S9.5 10.62 9.5 12c0 1.02.62 1.9 1.5 2.29v3.3L7.59 21 9 22.41l3-3 3 3L16.41 21 13 17.59v-3.3zM12 1C5.93 1 1 5.93 1 12h2c0-4.97 4.03-9 9-9s9 4.03 9 9h2c0-6.07-4.93-11-11-11z"></path></g>
<g id="settings-input-component"><path d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"></path></g>
<g id="settings-input-composite"><path d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"></path></g>
<g id="settings-input-hdmi"><path d="M18 7V4c0-1.1-.9-2-2-2H8c-1.1 0-2 .9-2 2v3H5v6l3 6v3h8v-3l3-6V7h-1zM8 4h8v3h-2V5h-1v2h-2V5h-1v2H8V4z"></path></g>
<g id="settings-input-svideo"><path d="M8 11.5c0-.83-.67-1.5-1.5-1.5S5 10.67 5 11.5 5.67 13 6.5 13 8 12.33 8 11.5zm7-5c0-.83-.67-1.5-1.5-1.5h-3C9.67 5 9 5.67 9 6.5S9.67 8 10.5 8h3c.83 0 1.5-.67 1.5-1.5zM8.5 15c-.83 0-1.5.67-1.5 1.5S7.67 18 8.5 18s1.5-.67 1.5-1.5S9.33 15 8.5 15zM12 1C5.93 1 1 5.93 1 12s4.93 11 11 11 11-4.93 11-11S18.07 1 12 1zm0 20c-4.96 0-9-4.04-9-9s4.04-9 9-9 9 4.04 9 9-4.04 9-9 9zm5.5-11c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm-2 5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5z"></path></g>
<g id="settings-overscan"><path d="M12.01 5.5L10 8h4l-1.99-2.5zM18 10v4l2.5-1.99L18 10zM6 10l-2.5 2.01L6 14v-4zm8 6h-4l2.01 2.5L14 16zm7-13H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16.01H3V4.99h18v14.02z"></path></g>
<g id="settings-phone"><path d="M13 9h-2v2h2V9zm4 0h-2v2h2V9zm3 6.5c-1.25 0-2.45-.2-3.57-.57-.35-.11-.74-.03-1.02.24l-2.2 2.2c-2.83-1.44-5.15-3.75-6.59-6.58l2.2-2.21c.28-.27.36-.66.25-1.01C8.7 6.45 8.5 5.25 8.5 4c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1 0 9.39 7.61 17 17 17 .55 0 1-.45 1-1v-3.5c0-.55-.45-1-1-1zM19 9v2h2V9h-2z"></path></g>
<g id="settings-power"><path d="M7 24h2v-2H7v2zm4 0h2v-2h-2v2zm2-22h-2v10h2V2zm3.56 2.44l-1.45 1.45C16.84 6.94 18 8.83 18 11c0 3.31-2.69 6-6 6s-6-2.69-6-6c0-2.17 1.16-4.06 2.88-5.12L7.44 4.44C5.36 5.88 4 8.28 4 11c0 4.42 3.58 8 8 8s8-3.58 8-8c0-2.72-1.36-5.12-3.44-6.56zM15 24h2v-2h-2v2z"></path></g>
<g id="settings-remote"><path d="M15 9H9c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h6c.55 0 1-.45 1-1V10c0-.55-.45-1-1-1zm-3 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM7.05 6.05l1.41 1.41C9.37 6.56 10.62 6 12 6s2.63.56 3.54 1.46l1.41-1.41C15.68 4.78 13.93 4 12 4s-3.68.78-4.95 2.05zM12 0C8.96 0 6.21 1.23 4.22 3.22l1.41 1.41C7.26 3.01 9.51 2 12 2s4.74 1.01 6.36 2.64l1.41-1.41C17.79 1.23 15.04 0 12 0z"></path></g>
<g id="settings-voice"><path d="M7 24h2v-2H7v2zm5-11c1.66 0 2.99-1.34 2.99-3L15 4c0-1.66-1.34-3-3-3S9 2.34 9 4v6c0 1.66 1.34 3 3 3zm-1 11h2v-2h-2v2zm4 0h2v-2h-2v2zm4-14h-1.7c0 3-2.54 5.1-5.3 5.1S6.7 13 6.7 10H5c0 3.41 2.72 6.23 6 6.72V20h2v-3.28c3.28-.49 6-3.31 6-6.72z"></path></g>
<g id="shop"><path d="M16 6V4c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H2v13c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6h-6zm-6-2h4v2h-4V4zM9 18V9l7.5 4L9 18z"></path></g>
<g id="shop-two"><path d="M3 9H1v11c0 1.11.89 2 2 2h14c1.11 0 2-.89 2-2H3V9zm15-4V3c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H5v11c0 1.11.89 2 2 2h14c1.11 0 2-.89 2-2V5h-5zm-6-2h4v2h-4V3zm0 12V8l5.5 3-5.5 4z"></path></g>
<g id="shopping-basket"><path d="M17.21 9l-4.38-6.56c-.19-.28-.51-.42-.83-.42-.32 0-.64.14-.83.43L6.79 9H2c-.55 0-1 .45-1 1 0 .09.01.18.04.27l2.54 9.27c.23.84 1 1.46 1.92 1.46h13c.92 0 1.69-.62 1.93-1.46l2.54-9.27L23 10c0-.55-.45-1-1-1h-4.79zM9 9l3-4.4L15 9H9zm3 8c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"></path></g>
<g id="shopping-cart"><path d="M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zM1 2v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.08-.14.12-.31.12-.48 0-.55-.45-1-1-1H5.21l-.94-2H1zm16 16c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z"></path></g>
<g id="sort"><path d="M3 18h6v-2H3v2zM3 6v2h18V6H3zm0 7h12v-2H3v2z"></path></g>
<g id="speaker-notes"><path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM8 14H6v-2h2v2zm0-3H6V9h2v2zm0-3H6V6h2v2zm7 6h-5v-2h5v2zm3-3h-8V9h8v2zm0-3h-8V6h8v2z"></path></g>
<g id="speaker-notes-off"><path d="M10.54 11l-.54-.54L7.54 8 6 6.46 2.38 2.84 1.27 1.73 0 3l2.01 2.01L2 22l4-4h9l5.73 5.73L22 22.46 17.54 18l-7-7zM8 14H6v-2h2v2zm-2-3V9l2 2H6zm14-9H4.08L10 7.92V6h8v2h-7.92l1 1H18v2h-4.92l6.99 6.99C21.14 17.95 22 17.08 22 16V4c0-1.1-.9-2-2-2z"></path></g>
<g id="spellcheck"><path d="M12.45 16h2.09L9.43 3H7.57L2.46 16h2.09l1.12-3h5.64l1.14 3zm-6.02-5L8.5 5.48 10.57 11H6.43zm15.16.59l-8.09 8.09L9.83 16l-1.41 1.41 5.09 5.09L23 13l-1.41-1.41z"></path></g>
<g id="star"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"></path></g>
<g id="star-border"><path d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z"></path></g>
<g id="star-half"><path d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4V6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z"></path></g>
<g id="stars"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm4.24 16L12 15.45 7.77 18l1.12-4.81-3.73-3.23 4.92-.42L12 5l1.92 4.53 4.92.42-3.73 3.23L16.23 18z"></path></g>
<g id="store"><path d="M20 4H4v2h16V4zm1 10v-2l-1-5H4l-1 5v2h1v6h10v-6h4v6h2v-6h1zm-9 4H6v-4h6v4z"></path></g>
<g id="subdirectory-arrow-left"><path d="M11 9l1.42 1.42L8.83 14H18V4h2v12H8.83l3.59 3.58L11 21l-6-6 6-6z"></path></g>
<g id="subdirectory-arrow-right"><path d="M19 15l-6 6-1.42-1.42L15.17 16H4V4h2v10h9.17l-3.59-3.58L13 9l6 6z"></path></g>
<g id="subject"><path d="M14 17H4v2h10v-2zm6-8H4v2h16V9zM4 15h16v-2H4v2zM4 5v2h16V5H4z"></path></g>
<g id="supervisor-account"><path d="M16.5 12c1.38 0 2.49-1.12 2.49-2.5S17.88 7 16.5 7C15.12 7 14 8.12 14 9.5s1.12 2.5 2.5 2.5zM9 11c1.66 0 2.99-1.34 2.99-3S10.66 5 9 5C7.34 5 6 6.34 6 8s1.34 3 3 3zm7.5 3c-1.83 0-5.5.92-5.5 2.75V19h11v-2.25c0-1.83-3.67-2.75-5.5-2.75zM9 13c-2.33 0-7 1.17-7 3.5V19h7v-2.25c0-.85.33-2.34 2.37-3.47C10.5 13.1 9.66 13 9 13z"></path></g>
<g id="swap-horiz"><path d="M6.99 11L3 15l3.99 4v-3H14v-2H6.99v-3zM21 9l-3.99-4v3H10v2h7.01v3L21 9z"></path></g>
<g id="swap-vert"><path d="M16 17.01V10h-2v7.01h-3L15 21l4-3.99h-3zM9 3L5 6.99h3V14h2V6.99h3L9 3z"></path></g>
<g id="swap-vertical-circle"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM6.5 9L10 5.5 13.5 9H11v4H9V9H6.5zm11 6L14 18.5 10.5 15H13v-4h2v4h2.5z"></path></g>
<g id="system-update-alt"><path d="M12 16.5l4-4h-3v-9h-2v9H8l4 4zm9-13h-6v1.99h6v14.03H3V5.49h6V3.5H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2v-14c0-1.1-.9-2-2-2z"></path></g>
<g id="tab"><path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h10v4h8v10z"></path></g>
<g id="tab-unselected"><path d="M1 9h2V7H1v2zm0 4h2v-2H1v2zm0-8h2V3c-1.1 0-2 .9-2 2zm8 16h2v-2H9v2zm-8-4h2v-2H1v2zm2 4v-2H1c0 1.1.9 2 2 2zM21 3h-8v6h10V5c0-1.1-.9-2-2-2zm0 14h2v-2h-2v2zM9 5h2V3H9v2zM5 21h2v-2H5v2zM5 5h2V3H5v2zm16 16c1.1 0 2-.9 2-2h-2v2zm0-8h2v-2h-2v2zm-8 8h2v-2h-2v2zm4 0h2v-2h-2v2z"></path></g>
<g id="text-format"><path d="M5 17v2h14v-2H5zm4.5-4.2h5l.9 2.2h2.1L12.75 4h-1.5L6.5 15h2.1l.9-2.2zM12 5.98L13.87 11h-3.74L12 5.98z"></path></g>
<g id="theaters"><path d="M18 3v2h-2V3H8v2H6V3H4v18h2v-2h2v2h8v-2h2v2h2V3h-2zM8 17H6v-2h2v2zm0-4H6v-2h2v2zm0-4H6V7h2v2zm10 8h-2v-2h2v2zm0-4h-2v-2h2v2zm0-4h-2V7h2v2z"></path></g>
<g id="thumb-down"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v1.91l.01.01L1 14c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"></path></g>
<g id="thumb-up"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-1.91l-.01-.01L23 10z"></path></g>
<g id="thumbs-up-down"><path d="M12 6c0-.55-.45-1-1-1H5.82l.66-3.18.02-.23c0-.31-.13-.59-.33-.8L5.38 0 .44 4.94C.17 5.21 0 5.59 0 6v6.5c0 .83.67 1.5 1.5 1.5h6.75c.62 0 1.15-.38 1.38-.91l2.26-5.29c.07-.17.11-.36.11-.55V6zm10.5 4h-6.75c-.62 0-1.15.38-1.38.91l-2.26 5.29c-.07.17-.11.36-.11.55V18c0 .55.45 1 1 1h5.18l-.66 3.18-.02.24c0 .31.13.59.33.8l.79.78 4.94-4.94c.27-.27.44-.65.44-1.06v-6.5c0-.83-.67-1.5-1.5-1.5z"></path></g>
<g id="timeline"><path d="M23 8c0 1.1-.9 2-2 2-.18 0-.35-.02-.51-.07l-3.56 3.55c.05.16.07.34.07.52 0 1.1-.9 2-2 2s-2-.9-2-2c0-.18.02-.36.07-.52l-2.55-2.55c-.16.05-.34.07-.52.07s-.36-.02-.52-.07l-4.55 4.56c.05.16.07.33.07.51 0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2c.18 0 .35.02.51.07l4.56-4.55C8.02 9.36 8 9.18 8 9c0-1.1.9-2 2-2s2 .9 2 2c0 .18-.02.36-.07.52l2.55 2.55c.16-.05.34-.07.52-.07s.36.02.52.07l3.55-3.56C19.02 8.35 19 8.18 19 8c0-1.1.9-2 2-2s2 .9 2 2z"></path></g>
<g id="toc"><path d="M3 9h14V7H3v2zm0 4h14v-2H3v2zm0 4h14v-2H3v2zm16 0h2v-2h-2v2zm0-10v2h2V7h-2zm0 6h2v-2h-2v2z"></path></g>
<g id="today"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z"></path></g>
<g id="toll"><path d="M15 4c-4.42 0-8 3.58-8 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6zM3 12c0-2.61 1.67-4.83 4-5.65V4.26C3.55 5.15 1 8.27 1 12s2.55 6.85 6 7.74v-2.09c-2.33-.82-4-3.04-4-5.65z"></path></g>
<g id="touch-app"><path d="M9 11.24V7.5C9 6.12 10.12 5 11.5 5S14 6.12 14 7.5v3.74c1.21-.81 2-2.18 2-3.74C16 5.01 13.99 3 11.5 3S7 5.01 7 7.5c0 1.56.79 2.93 2 3.74zm9.84 4.63l-4.54-2.26c-.17-.07-.35-.11-.54-.11H13v-6c0-.83-.67-1.5-1.5-1.5S10 6.67 10 7.5v10.74l-3.43-.72c-.08-.01-.15-.03-.24-.03-.31 0-.59.13-.79.33l-.79.8 4.94 4.94c.27.27.65.44 1.06.44h6.79c.75 0 1.33-.55 1.44-1.28l.75-5.27c.01-.07.02-.14.02-.2 0-.62-.38-1.16-.91-1.38z"></path></g>
<g id="track-changes"><path d="M19.07 4.93l-1.41 1.41C19.1 7.79 20 9.79 20 12c0 4.42-3.58 8-8 8s-8-3.58-8-8c0-4.08 3.05-7.44 7-7.93v2.02C8.16 6.57 6 9.03 6 12c0 3.31 2.69 6 6 6s6-2.69 6-6c0-1.66-.67-3.16-1.76-4.24l-1.41 1.41C15.55 9.9 16 10.9 16 12c0 2.21-1.79 4-4 4s-4-1.79-4-4c0-1.86 1.28-3.41 3-3.86v2.14c-.6.35-1 .98-1 1.72 0 1.1.9 2 2 2s2-.9 2-2c0-.74-.4-1.38-1-1.72V2h-1C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10c0-2.76-1.12-5.26-2.93-7.07z"></path></g>
<g id="translate"><path d="M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"></path></g>
<g id="trending-down"><path d="M16 18l2.29-2.29-4.88-4.88-4 4L2 7.41 3.41 6l6 6 4-4 6.3 6.29L22 12v6z"></path></g>
<g id="trending-flat"><path d="M22 12l-4-4v3H3v2h15v3z"></path></g>
<g id="trending-up"><path d="M16 6l2.29 2.29-4.88 4.88-4-4L2 16.59 3.41 18l6-6 4 4 6.3-6.29L22 12V6z"></path></g>
<g id="turned-in"><path d="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2z"></path></g>
<g id="turned-in-not"><path d="M17 3H7c-1.1 0-1.99.9-1.99 2L5 21l7-3 7 3V5c0-1.1-.9-2-2-2zm0 15l-5-2.18L7 18V5h10v13z"></path></g>
<g id="unarchive"><path d="M20.55 5.22l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88.21-1.15.55L3.46 5.22C3.17 5.57 3 6.01 3 6.5V19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.49-.17-.93-.45-1.28zM12 9.5l5.5 5.5H14v2h-4v-2H6.5L12 9.5zM5.12 5l.82-1h12l.93 1H5.12z"></path></g>
<g id="undo"><path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"></path></g>
<g id="unfold-less"><path d="M7.41 18.59L8.83 20 12 16.83 15.17 20l1.41-1.41L12 14l-4.59 4.59zm9.18-13.18L15.17 4 12 7.17 8.83 4 7.41 5.41 12 10l4.59-4.59z"></path></g>
<g id="unfold-more"><path d="M12 5.83L15.17 9l1.41-1.41L12 3 7.41 7.59 8.83 9 12 5.83zm0 12.34L8.83 15l-1.41 1.41L12 21l4.59-4.59L15.17 15 12 18.17z"></path></g>
<g id="update"><path d="M21 10.12h-6.78l2.74-2.82c-2.73-2.7-7.15-2.8-9.88-.1-2.73 2.71-2.73 7.08 0 9.79 2.73 2.71 7.15 2.71 9.88 0C18.32 15.65 19 14.08 19 12.1h2c0 1.98-.88 4.55-2.64 6.29-3.51 3.48-9.21 3.48-12.72 0-3.5-3.47-3.53-9.11-.02-12.58 3.51-3.47 9.14-3.47 12.65 0L21 3v7.12zM12.5 8v4.25l3.5 2.08-.72 1.21L11 13V8h1.5z"></path></g>
<g id="verified-user"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm-2 16l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9l-8 8z"></path></g>
<g id="view-agenda"><path d="M20 13H3c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h17c.55 0 1-.45 1-1v-6c0-.55-.45-1-1-1zm0-10H3c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h17c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1z"></path></g>
<g id="view-array"><path d="M4 18h3V5H4v13zM18 5v13h3V5h-3zM8 18h9V5H8v13z"></path></g>
<g id="view-carousel"><path d="M7 19h10V4H7v15zm-5-2h4V6H2v11zM18 6v11h4V6h-4z"></path></g>
<g id="view-column"><path d="M10 18h5V5h-5v13zm-6 0h5V5H4v13zM16 5v13h5V5h-5z"></path></g>
<g id="view-day"><path d="M2 21h19v-3H2v3zM20 8H3c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h17c.55 0 1-.45 1-1V9c0-.55-.45-1-1-1zM2 3v3h19V3H2z"></path></g>
<g id="view-headline"><path d="M4 15h16v-2H4v2zm0 4h16v-2H4v2zm0-8h16V9H4v2zm0-6v2h16V5H4z"></path></g>
<g id="view-list"><path d="M4 14h4v-4H4v4zm0 5h4v-4H4v4zM4 9h4V5H4v4zm5 5h12v-4H9v4zm0 5h12v-4H9v4zM9 5v4h12V5H9z"></path></g>
<g id="view-module"><path d="M4 11h5V5H4v6zm0 7h5v-6H4v6zm6 0h5v-6h-5v6zm6 0h5v-6h-5v6zm-6-7h5V5h-5v6zm6-6v6h5V5h-5z"></path></g>
<g id="view-quilt"><path d="M10 18h5v-6h-5v6zm-6 0h5V5H4v13zm12 0h5v-6h-5v6zM10 5v6h11V5H10z"></path></g>
<g id="view-stream"><path d="M4 18h17v-6H4v6zM4 5v6h17V5H4z"></path></g>
<g id="view-week"><path d="M6 5H3c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h3c.55 0 1-.45 1-1V6c0-.55-.45-1-1-1zm14 0h-3c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h3c.55 0 1-.45 1-1V6c0-.55-.45-1-1-1zm-7 0h-3c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h3c.55 0 1-.45 1-1V6c0-.55-.45-1-1-1z"></path></g>
<g id="visibility"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"></path></g>
<g id="visibility-off"><path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"></path></g>
<g id="warning"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"></path></g>
<g id="watch-later"><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm4.2 14.2L11 13V7h1.5v5.2l4.5 2.7-.8 1.3z"></path></g>
<g id="weekend"><path d="M21 10c-1.1 0-2 .9-2 2v3H5v-3c0-1.1-.9-2-2-2s-2 .9-2 2v5c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2v-5c0-1.1-.9-2-2-2zm-3-5H6c-1.1 0-2 .9-2 2v2.15c1.16.41 2 1.51 2 2.82V14h12v-2.03c0-1.3.84-2.4 2-2.82V7c0-1.1-.9-2-2-2z"></path></g>
<g id="work"><path d="M20 6h-4V4c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-6 0h-4V4h4v2z"></path></g>
<g id="youtube-searched-for"><path d="M17.01 14h-.8l-.27-.27c.98-1.14 1.57-2.61 1.57-4.23 0-3.59-2.91-6.5-6.5-6.5s-6.5 3-6.5 6.5H2l3.84 4 4.16-4H6.51C6.51 7 8.53 5 11.01 5s4.5 2.01 4.5 4.5c0 2.48-2.02 4.5-4.5 4.5-.65 0-1.26-.14-1.82-.38L7.71 15.1c.97.57 2.09.9 3.3.9 1.61 0 3.08-.59 4.22-1.57l.27.27v.79l5.01 4.99L22 19l-4.99-5z"></path></g>
<g id="zoom-in"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14zm2.5-4h-2v2H9v-2H7V9h2V7h1v2h2v1z"></path></g>
<g id="zoom-out"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14zM7 9h5v1H7z"></path></g>
</defs></svg>
</iron-iconset-svg>
<dom-module id="paper-item-body" assetpath="bower_components/paper-item/">
  <template>
    <style>
      :host {
        overflow: hidden; /* needed for text-overflow: ellipsis to work on ff */
        @apply --layout-vertical;
        @apply --layout-center-justified;
        @apply --layout-flex;
      }

      :host([two-line]) {
        min-height: var(--paper-item-body-two-line-min-height, 72px);
      }

      :host([three-line]) {
        min-height: var(--paper-item-body-three-line-min-height, 88px);
      }

      :host > ::slotted(*) {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }

      :host > ::slotted([secondary]) {
        @apply --paper-font-body1;

        color: var(--paper-item-body-secondary-color, var(--secondary-text-color));

        @apply --paper-item-body-secondary;
      }
    </style>

    <slot></slot>
  </template>

  <script>
    Polymer({is: 'paper-item-body'});
  </script>
</dom-module>
<dom-module id="simple-paper-item" assetpath="bower_components/paper-collapse-item/">
	<template>
		<style include="paper-item-shared-styles"></style>
		<style>
			:host {
				@apply --layout-horizontal;
				@apply --layout-center ;
				@apply --paper-font-subhead ;
				@apply --paper-item ;
			}
		</style>

		<slot></slot>
	</template>

	<script>
		Polymer({
			is: 'simple-paper-item',
			behaviors: [
				Polymer.IronControlState,
				Polymer.PaperItemBehaviorImpl
			]
		});
	</script>
</dom-module>
<dom-module id="paper-collapse-item" assetpath="bower_components/paper-collapse-item/">

	<template>

		<style>
			.header {
				min-height: 48px;
				color: var(--primary-text-color);
				@apply --layout-horizontal;
				@apply --layout-center;
				@apply --paper-font-subhead;
				@apply --paper-collapse-item-header;
			}

			.icon {
				margin-right: 24px;
				--iron-icon-height: 32px;
				--iron-icon-width: 32px;
				@apply --paper-collapse-item-icon;
			}

			.icon, .toogle {
				color: var(--disabled-text-color);
			}

			.html {
				@apply --layout-flex;
			}

			.content {
				color: var(--primary-text-color);
				@apply --paper-font-body1;
				@apply --paper-collapse-item-content;
			}
			
			.toggle {
				position: absolute;
				right: 0px;
			}

			simple-paper-item {
				@apply --paper-collapse-simple-paper-item-styles;
			}
		</style>

		<simple-paper-item>
			<paper-item-body>
				<div class="header" on-tap="_toggleOpened">
					<template is="dom-if" if="[[_or(icon, src)]]">
						<iron-icon class="icon" src="[[src]]" icon="[[icon]]"></iron-icon>
					</template>
					<template is="dom-if" if="[[header]]">
						<div class="html" inner-h-t-m-l="[[header]]"></div>
					</template>
					<slot class="html" name="header"></slot>
					<paper-icon-button class="toggle" icon="[[_toggleIcon]]"></paper-icon-button>
				</div>
				<iron-collapse class="content" opened="{{opened}}">
					<slot></slot>
				</iron-collapse>
			</paper-item-body>
		</simple-paper-item>

	</template>
</dom-module>

<script>

(function() {

	Polymer({
		is: 'paper-collapse-item',
		properties: {
			/**
			 * Text in the header row
			 */
			header: String,

			/**
			 * The name of the icon to use. The name should be of the
			 * form: iconset_name:icon_name.
			 */
			icon: String,

			/**
			 * If using paper-collapse-item without an iconset, you can set the
			 * src to be the URL of an individual icon image file. Note that
			 * this will take precedence over a given icon attribute.
			 */
			src: String,

			/**
			 * True if the content section is opened
			 */
			opened: {
				type: Boolean,
				reflectToAttribute: true,
				notify: true,
				observer: '_openedChanged'
			},

			_toggleIcon: {
				type: String,
				computed: '_computeToggleIcon(opened)'
			}
		},

		_openedChanged: function(value, old) {
			// Ignore initialization calls
			if (old !== undefined) {
				this.fire('toggle', this);
			}
		},
		// Private methods
		/**
		 * Fired whenever the status is changed (opened/closed)
		 *
		 * @event toggle
		 */
		_toggleOpened: function(e) {
			this.set('opened', !this.opened);
		},
		_computeToggleIcon: function(opened) {
			return opened ? 'icons:expand-less' : 'icons:expand-more';
		},
		_or: function(value1, value2) {
			return value1 || value2;
		}
	});

})();

</script>
<dom-module id="iron-a11y-announcer" assetpath="bower_components/iron-a11y-announcer/">
  <template>
    <style>
      :host {
        display: inline-block;
        position: fixed;
        clip: rect(0px,0px,0px,0px);
      }
    </style>
    <div aria-live$="[[mode]]">[[_text]]</div>
  </template>

  <script>

    (function() {
      'use strict';

      Polymer.IronA11yAnnouncer = Polymer({
        is: 'iron-a11y-announcer',

        properties: {

          /**
           * The value of mode is used to set the `aria-live` attribute
           * for the element that will be announced. Valid values are: `off`,
           * `polite` and `assertive`.
           */
          mode: {
            type: String,
            value: 'polite'
          },

          _text: {
            type: String,
            value: ''
          }
        },

        created: function() {
          if (!Polymer.IronA11yAnnouncer.instance) {
            Polymer.IronA11yAnnouncer.instance = this;
          }

          document.body.addEventListener('iron-announce', this._onIronAnnounce.bind(this));
        },

        /**
         * Cause a text string to be announced by screen readers.
         *
         * @param {string} text The text that should be announced.
         */
        announce: function(text) {
          this._text = '';
          this.async(function() {
            this._text = text;
          }, 100);
        },

        _onIronAnnounce: function(event) {
          if (event.detail && event.detail.text) {
            this.announce(event.detail.text);
          }
        }
      });

      Polymer.IronA11yAnnouncer.instance = null;

      Polymer.IronA11yAnnouncer.requestAvailability = function() {
        if (!Polymer.IronA11yAnnouncer.instance) {
          Polymer.IronA11yAnnouncer.instance = document.createElement('iron-a11y-announcer');
        }

        document.body.appendChild(Polymer.IronA11yAnnouncer.instance);
      };
    })();

  </script>
</dom-module>
<dom-module id="iron-input" assetpath="bower_components/iron-input/">
  <template>
    <style>
      :host {
        display: inline-block;
      }
    </style>
    <slot id="content"></slot>
  </template>
  <script>
    Polymer({
      is: 'iron-input',

      behaviors: [Polymer.IronValidatableBehavior],

      /**
       * Fired whenever `validate()` is called.
       *
       * @event iron-input-validate
       */

      properties: {

        /**
         * Use this property instead of `value` for two-way data binding, or to
         * set a default value for the input. **Do not** use the distributed
         * input's `value` property to set a default value.
         */
        bindValue: {type: String, value: ''},

        /**
         * Computed property that echoes `bindValue` (mostly used for Polymer 1.0
         * backcompatibility, if you were one-way binding to the Polymer 1.0
         * `input is="iron-input"` value attribute).
         */
        value: {type: String, computed: '_computeValue(bindValue)'},

        /**
         * Regex-like list of characters allowed as input; all characters not in the
         * list will be rejected. The recommended format should be a list of allowed
         * characters, for example, `[a-zA-Z0-9.+-!;:]`.
         *
         * This pattern represents the allowed characters for the field; as the user
         * inputs text, each individual character will be checked against the
         * pattern (rather than checking the entire value as a whole). If a
         * character is not a match, it will be rejected.
         *
         * Pasted input will have each character checked individually; if any
         * character doesn't match `allowedPattern`, the entire pasted string will
         * be rejected.
         *
         * Note: if you were using `iron-input` in 1.0, you were also required to
         * set `prevent-invalid-input`. This is no longer needed as of Polymer 2.0,
         * and will be set automatically for you if an `allowedPattern` is provided.
         *
         */
        allowedPattern: {type: String},

        /**
         * Set to true to auto-validate the input value as you type.
         */
        autoValidate: {type: Boolean, value: false},

        /**
         * The native input element.
         */
        _inputElement: Object,
      },

      observers: ['_bindValueChanged(bindValue, _inputElement)'],

      listeners: {'input': '_onInput', 'keypress': '_onKeypress'},

      created: function() {
        Polymer.IronA11yAnnouncer.requestAvailability();
        this._previousValidInput = '';
        this._patternAlreadyChecked = false;
      },

      attached: function() {
        // If the input is added at a later time, update the internal reference.
        this._observer = Polymer.dom(this).observeNodes(function(info) {
          this._initSlottedInput();
        }.bind(this));
      },

      detached: function() {
        if (this._observer) {
          Polymer.dom(this).unobserveNodes(this._observer);
          this._observer = null;
        }
      },

      /**
       * Returns the distributed input element.
       */
      get inputElement() {
        return this._inputElement;
      },

      _initSlottedInput: function() {
        this._inputElement = this.getEffectiveChildren()[0];

        if (this.inputElement && this.inputElement.value) {
          this.bindValue = this.inputElement.value;
        }

        this.fire('iron-input-ready');
      },

      get _patternRegExp() {
        var pattern;
        if (this.allowedPattern) {
          pattern = new RegExp(this.allowedPattern);
        } else {
          switch (this.inputElement.type) {
            case 'number':
              pattern = /[0-9.,e-]/;
              break;
          }
        }
        return pattern;
      },

      /**
       * @suppress {checkTypes}
       */
      _bindValueChanged: function(bindValue, inputElement) {
        // The observer could have run before attached() when we have actually
        // initialized this property.
        if (!inputElement) {
          return;
        }

        if (bindValue === undefined) {
          inputElement.value = null;
        } else if (bindValue !== inputElement.value) {
          this.inputElement.value = bindValue;
        }

        if (this.autoValidate) {
          this.validate();
        }

        // manually notify because we don't want to notify until after setting value
        this.fire('bind-value-changed', {value: bindValue});
      },

      _onInput: function() {
        // Need to validate each of the characters pasted if they haven't
        // been validated inside `_onKeypress` already.
        if (this.allowedPattern && !this._patternAlreadyChecked) {
          var valid = this._checkPatternValidity();
          if (!valid) {
            this._announceInvalidCharacter(
                'Invalid string of characters not entered.');
            this.inputElement.value = this._previousValidInput;
          }
        }
        this.bindValue = this._previousValidInput = this.inputElement.value;
        this._patternAlreadyChecked = false;
      },

      _isPrintable: function(event) {
        // What a control/printable character is varies wildly based on the browser.
        // - most control characters (arrows, backspace) do not send a `keypress`
        // event
        //   in Chrome, but the *do* on Firefox
        // - in Firefox, when they do send a `keypress` event, control chars have
        //   a charCode = 0, keyCode = xx (for ex. 40 for down arrow)
        // - printable characters always send a keypress event.
        // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the
        // keyCode
        //   always matches the charCode.
        // None of this makes any sense.

        // For these keys, ASCII code == browser keycode.
        var anyNonPrintable = (event.keyCode == 8) ||  // backspace
            (event.keyCode == 9) ||                    // tab
            (event.keyCode == 13) ||                   // enter
            (event.keyCode == 27);                     // escape

        // For these keys, make sure it's a browser keycode and not an ASCII code.
        var mozNonPrintable = (event.keyCode == 19) ||  // pause
            (event.keyCode == 20) ||                    // caps lock
            (event.keyCode == 45) ||                    // insert
            (event.keyCode == 46) ||                    // delete
            (event.keyCode == 144) ||                   // num lock
            (event.keyCode == 145) ||                   // scroll lock
            (event.keyCode > 32 &&
             event.keyCode < 41) ||  // page up/down, end, home, arrows
            (event.keyCode > 111 && event.keyCode < 124);  // fn keys

        return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable);
      },

      _onKeypress: function(event) {
        if (!this.allowedPattern && this.inputElement.type !== 'number') {
          return;
        }
        var regexp = this._patternRegExp;
        if (!regexp) {
          return;
        }

        // Handle special keys and backspace
        if (event.metaKey || event.ctrlKey || event.altKey) {
          return;
        }

        // Check the pattern either here or in `_onInput`, but not in both.
        this._patternAlreadyChecked = true;

        var thisChar = String.fromCharCode(event.charCode);
        if (this._isPrintable(event) && !regexp.test(thisChar)) {
          event.preventDefault();
          this._announceInvalidCharacter(
              'Invalid character ' + thisChar + ' not entered.');
        }
      },

      _checkPatternValidity: function() {
        var regexp = this._patternRegExp;
        if (!regexp) {
          return true;
        }
        for (var i = 0; i < this.inputElement.value.length; i++) {
          if (!regexp.test(this.inputElement.value[i])) {
            return false;
          }
        }
        return true;
      },

      /**
       * Returns true if `value` is valid. The validator provided in `validator`
       * will be used first, then any constraints.
       * @return {boolean} True if the value is valid.
       */
      validate: function() {
        if (!this.inputElement) {
          this.invalid = false;
          return true;
        }

        // Use the nested input's native validity.
        var valid = this.inputElement.checkValidity();

        // Only do extra checking if the browser thought this was valid.
        if (valid) {
          // Empty, required input is invalid
          if (this.required && this.bindValue === '') {
            valid = false;
          } else if (this.hasValidator()) {
            valid =
                Polymer.IronValidatableBehavior.validate.call(this, this.bindValue);
          }
        }

        this.invalid = !valid;
        this.fire('iron-input-validate');
        return valid;
      },

      _announceInvalidCharacter: function(message) {
        this.fire('iron-announce', {text: message});
      },

      _computeValue: function(bindValue) {
        return bindValue;
      }
    });
  </script>
</dom-module>
<script>
  // Generate unique, monotonically increasing IDs for labels (needed by
  // aria-labelledby) and add-ons.
  Polymer.PaperInputHelper = {};
  Polymer.PaperInputHelper.NextLabelID = 1;
  Polymer.PaperInputHelper.NextAddonID = 1;
  Polymer.PaperInputHelper.NextInputID = 1;

  /**
   * Use `Polymer.PaperInputBehavior` to implement inputs with
   * `<paper-input-container>`. This behavior is implemented by `<paper-input>`.
   * It exposes a number of properties from
   * `<paper-input-container>` and `<input is="iron-input">` and they should be
   * bound in your template.
   *
   * The input element can be accessed by the `inputElement` property if you need
   * to access properties or methods that are not exposed.
   * @polymerBehavior Polymer.PaperInputBehavior
   */
  Polymer.PaperInputBehaviorImpl = {

    properties: {
      /**
       * Fired when the input changes due to user interaction.
       *
       * @event change
       */

      /**
       * The label for this input. If you're using PaperInputBehavior to
       * implement your own paper-input-like element, bind this to
       * `<label>`'s content and `hidden` property, e.g.
       * `<label hidden$="[[!label]]">[[label]]</label>` in your `template`
       */
      label: {type: String},

      /**
       * The value for this input. If you're using PaperInputBehavior to
       * implement your own paper-input-like element, bind this to
       * the `<iron-input>`'s `bindValue`
       * property, or the value property of your input that is `notify:true`.
       * @type {*}
       */
      value: {notify: true, type: String},

      /**
       * Set to true to disable this input. If you're using PaperInputBehavior to
       * implement your own paper-input-like element, bind this to
       * both the `<paper-input-container>`'s and the input's `disabled` property.
       */
      disabled: {type: Boolean, value: false},

      /**
       * Returns true if the value is invalid. If you're using PaperInputBehavior
       * to implement your own paper-input-like element, bind this to both the
       * `<paper-input-container>`'s and the input's `invalid` property.
       *
       * If `autoValidate` is true, the `invalid` attribute is managed
       * automatically, which can clobber attempts to manage it manually.
       */
      invalid: {type: Boolean, value: false, notify: true},

      /**
       * Set this to specify the pattern allowed by `preventInvalidInput`. If
       * you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `allowedPattern`
       * property.
       */
      allowedPattern: {type: String},

      /**
       * The type of the input. The supported types are the
       * [native input's
       * types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Form_<input>_types).
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the (Polymer 1) `<input is="iron-input">`'s or
       * (Polymer 2)
       * `<iron-input>`'s `type` property.
       */
      type: {type: String},

      /**
       * The datalist of the input (if any). This should match the id of an
       * existing `<datalist>`. If you're using PaperInputBehavior to implement
       * your own paper-input-like element, bind this to the `<input
       * is="iron-input">`'s `list` property.
       */
      list: {type: String},

      /**
       * A pattern to validate the `input` with. If you're using
       * PaperInputBehavior to implement your own paper-input-like element, bind
       * this to the `<input is="iron-input">`'s `pattern` property.
       */
      pattern: {type: String},

      /**
       * Set to true to mark the input as required. If you're using
       * PaperInputBehavior to implement your own paper-input-like element, bind
       * this to the `<input is="iron-input">`'s `required` property.
       */
      required: {type: Boolean, value: false},

      /**
       * The error message to display when the input is invalid. If you're using
       * PaperInputBehavior to implement your own paper-input-like element,
       * bind this to the `<paper-input-error>`'s content, if using.
       */
      errorMessage: {type: String},

      /**
       * Set to true to show a character counter.
       */
      charCounter: {type: Boolean, value: false},

      /**
       * Set to true to disable the floating label. If you're using
       * PaperInputBehavior to implement your own paper-input-like element, bind
       * this to the `<paper-input-container>`'s `noLabelFloat` property.
       */
      noLabelFloat: {type: Boolean, value: false},

      /**
       * Set to true to always float the label. If you're using PaperInputBehavior
       * to implement your own paper-input-like element, bind this to the
       * `<paper-input-container>`'s `alwaysFloatLabel` property.
       */
      alwaysFloatLabel: {type: Boolean, value: false},

      /**
       * Set to true to auto-validate the input value. If you're using
       * PaperInputBehavior to implement your own paper-input-like element, bind
       * this to the `<paper-input-container>`'s `autoValidate` property.
       */
      autoValidate: {type: Boolean, value: false},

      /**
       * Name of the validator to use. If you're using PaperInputBehavior to
       * implement your own paper-input-like element, bind this to
       * the `<input is="iron-input">`'s `validator` property.
       */
      validator: {type: String},

      // HTMLInputElement attributes for binding if needed

      /**
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `autocomplete`
       * property.
       */
      autocomplete: {type: String, value: 'off'},

      /**
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `autofocus`
       * property.
       */
      autofocus: {type: Boolean, observer: '_autofocusChanged'},

      /**
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `inputmode`
       * property.
       */
      inputmode: {type: String},

      /**
       * The minimum length of the input value.
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `minlength`
       * property.
       */
      minlength: {type: Number},

      /**
       * The maximum length of the input value.
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `maxlength`
       * property.
       */
      maxlength: {type: Number},

      /**
       * The minimum (numeric or date-time) input value.
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `min` property.
       */
      min: {type: String},

      /**
       * The maximum (numeric or date-time) input value.
       * Can be a String (e.g. `"2000-01-01"`) or a Number (e.g. `2`).
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `max` property.
       */
      max: {type: String},

      /**
       * Limits the numeric or date-time increments.
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `step` property.
       */
      step: {type: String},

      /**
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `name` property.
       */
      name: {type: String},

      /**
       * A placeholder string in addition to the label. If this is set, the label
       * will always float.
       */
      placeholder: {
        type: String,
        // need to set a default so _computeAlwaysFloatLabel is run
        value: ''
      },

      /**
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `readonly`
       * property.
       */
      readonly: {type: Boolean, value: false},

      /**
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `size` property.
       */
      size: {type: Number},

      // Nonstandard attributes for binding if needed

      /**
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `autocapitalize`
       * property.
       */
      autocapitalize: {type: String, value: 'none'},

      /**
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `autocorrect`
       * property.
       */
      autocorrect: {type: String, value: 'off'},

      /**
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `autosave`
       * property, used with type=search.
       */
      autosave: {type: String},

      /**
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `results` property,
       * used with type=search.
       */
      results: {type: Number},

      /**
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the `<input is="iron-input">`'s `accept` property,
       * used with type=file.
       */
      accept: {type: String},

      /**
       * If you're using PaperInputBehavior to implement your own paper-input-like
       * element, bind this to the`<input is="iron-input">`'s `multiple` property,
       * used with type=file.
       */
      multiple: {type: Boolean},

      /** @private */
      _ariaDescribedBy: {type: String, value: ''},

      /** @private */
      _ariaLabelledBy: {type: String, value: ''},

      /** @private */
      _inputId: {type: String, value: ''}
    },

    listeners: {
      'addon-attached': '_onAddonAttached',
    },

    /**
     * @type {!Object}
     */
    keyBindings: {'shift+tab:keydown': '_onShiftTabDown'},

    /** @private */
    hostAttributes: {tabindex: 0},

    /**
     * Returns a reference to the input element.
     * @return {!HTMLElement}
     */
    get inputElement() {
      // Chrome generates audit errors if an <input type="password"> has a
      // duplicate ID, which is almost always true in Shady DOM. Generate
      // a unique ID instead.
      if (!this.$) {
        this.$ = {}
      }
      if (!this.$.input) {
        this._generateInputId();
        this.$.input = this.$$('#' + this._inputId);
      }
      return this.$.input;
    },

    /**
     * Returns a reference to the focusable element.
     * @return {!HTMLElement}
     */
    get _focusableElement() {
      return this.inputElement;
    },

    created: function() {
      // These types have some default placeholder text; overlapping
      // the label on top of it looks terrible. Auto-float the label in this case.
      this._typesThatHaveText =
          ['date', 'datetime', 'datetime-local', 'month', 'time', 'week', 'file'];
    },

    attached: function() {
      this._updateAriaLabelledBy();

      // In the 2.0 version of the element, this is handled in `onIronInputReady`,
      // i.e. after the native input has finished distributing. In the 1.0
      // version, the input is in the shadow tree, so it's already available.
      if (!Polymer.Element && this.inputElement &&
          this._typesThatHaveText.indexOf(this.inputElement.type) !== -1) {
        this.alwaysFloatLabel = true;
      }
    },

    _appendStringWithSpace: function(str, more) {
      if (str) {
        str = str + ' ' + more;
      } else {
        str = more;
      }
      return str;
    },

    _onAddonAttached: function(event) {
      var target = Polymer.dom(event).rootTarget;
      if (target.id) {
        this._ariaDescribedBy =
            this._appendStringWithSpace(this._ariaDescribedBy, target.id);
      } else {
        var id = 'paper-input-add-on-' + Polymer.PaperInputHelper.NextAddonID++;
        target.id = id;
        this._ariaDescribedBy =
            this._appendStringWithSpace(this._ariaDescribedBy, id);
      }
    },

    /**
     * Validates the input element and sets an error style if needed.
     *
     * @return {boolean}
     */
    validate: function() {
      return this.inputElement.validate();
    },

    /**
     * Forward focus to inputElement. Overriden from IronControlState.
     */
    _focusBlurHandler: function(event) {
      Polymer.IronControlState._focusBlurHandler.call(this, event);

      // Forward the focus to the nested input.
      if (this.focused && !this._shiftTabPressed && this._focusableElement) {
        this._focusableElement.focus();
      }
    },

    /**
     * Handler that is called when a shift+tab keypress is detected by the menu.
     *
     * @param {CustomEvent} event A key combination event.
     */
    _onShiftTabDown: function(event) {
      var oldTabIndex = this.getAttribute('tabindex');
      this._shiftTabPressed = true;
      this.setAttribute('tabindex', '-1');
      this.async(function() {
        this.setAttribute('tabindex', oldTabIndex);
        this._shiftTabPressed = false;
      }, 1);
    },

    /**
     * If `autoValidate` is true, then validates the element.
     */
    _handleAutoValidate: function() {
      if (this.autoValidate)
        this.validate();
    },

    /**
     * Restores the cursor to its original position after updating the value.
     * @param {string} newValue The value that should be saved.
     */
    updateValueAndPreserveCaret: function(newValue) {
      // Not all elements might have selection, and even if they have the
      // right properties, accessing them might throw an exception (like for
      // <input type=number>)
      try {
        var start = this.inputElement.selectionStart;
        this.value = newValue;

        // The cursor automatically jumps to the end after re-setting the value,
        // so restore it to its original position.
        this.inputElement.selectionStart = start;
        this.inputElement.selectionEnd = start;
      } catch (e) {
        // Just set the value and give up on the caret.
        this.value = newValue;
      }
    },

    _computeAlwaysFloatLabel: function(alwaysFloatLabel, placeholder) {
      return placeholder || alwaysFloatLabel;
    },

    _updateAriaLabelledBy: function() {
      var label = Polymer.dom(this.root).querySelector('label');
      if (!label) {
        this._ariaLabelledBy = '';
        return;
      }
      var labelledBy;
      if (label.id) {
        labelledBy = label.id;
      } else {
        labelledBy =
            'paper-input-label-' + Polymer.PaperInputHelper.NextLabelID++;
        label.id = labelledBy;
      }
      this._ariaLabelledBy = labelledBy;
    },

    _generateInputId: function() {
      if (!this._inputId || this._inputId === '') {
        this._inputId = 'input-' + Polymer.PaperInputHelper.NextInputID++;
      }
    },

    _onChange: function(event) {
      // In the Shadow DOM, the `change` event is not leaked into the
      // ancestor tree, so we must do this manually.
      // See
      // https://w3c.github.io/webcomponents/spec/shadow/#events-that-are-not-leaked-into-ancestor-trees.
      if (this.shadowRoot) {
        this.fire(
            event.type,
            {sourceEvent: event},
            {node: this, bubbles: event.bubbles, cancelable: event.cancelable});
      }
    },

    _autofocusChanged: function() {
      // Firefox doesn't respect the autofocus attribute if it's applied after
      // the page is loaded (Chrome/WebKit do respect it), preventing an
      // autofocus attribute specified in markup from taking effect when the
      // element is upgraded. As a workaround, if the autofocus property is set,
      // and the focus hasn't already been moved elsewhere, we take focus.
      if (this.autofocus && this._focusableElement) {
        // In IE 11, the default document.activeElement can be the page's
        // outermost html element, but there are also cases (under the
        // polyfill?) in which the activeElement is not a real HTMLElement, but
        // just a plain object. We identify the latter case as having no valid
        // activeElement.
        var activeElement = document.activeElement;
        var isActiveElementValid = activeElement instanceof HTMLElement;

        // Has some other element has already taken the focus?
        var isSomeElementActive = isActiveElementValid &&
            activeElement !== document.body &&
            activeElement !== document.documentElement; /* IE 11 */
        if (!isSomeElementActive) {
          // No specific element has taken the focus yet, so we can take it.
          this._focusableElement.focus();
        }
      }
    }
  };

  /** @polymerBehavior */
  Polymer.PaperInputBehavior = [
    Polymer.IronControlState,
    Polymer.IronA11yKeysBehavior,
    Polymer.PaperInputBehaviorImpl
  ];
</script>
<script>
  /**
   * Use `Polymer.PaperInputAddonBehavior` to implement an add-on for
   * `<paper-input-container>`. A add-on appears below the input, and may display
   * information based on the input value and validity such as a character counter
   * or an error message.
   * @polymerBehavior
   */
  Polymer.PaperInputAddonBehavior = {
    attached: function() {
      this.fire('addon-attached');
    },

    /**
     * The function called by `<paper-input-container>` when the input value or
     * validity changes.
     * @param {{
     *   invalid: boolean,
     *   inputElement: (Element|undefined),
     *   value: (string|undefined)
     * }} state -
     *     inputElement: The input element.
     *     value: The input value.
     *     invalid: True if the input value is invalid.
     */
    update: function(state) {}

  };
</script>
<dom-module id="paper-input-char-counter" assetpath="bower_components/paper-input/">
  <template>
    <style>
      :host {
        display: inline-block;
        float: right;

        @apply --paper-font-caption;
        @apply --paper-input-char-counter;
      }

      :host([hidden]) {
        display: none !important;
      }

      :host(:dir(rtl)) {
        float: left;
      }
    </style>

    <span>[[_charCounterStr]]</span>
  </template>
</dom-module>

<script>
  Polymer({
    is: 'paper-input-char-counter',

    behaviors: [Polymer.PaperInputAddonBehavior],

    properties: {_charCounterStr: {type: String, value: '0'}},

    /**
     * This overrides the update function in PaperInputAddonBehavior.
     * @param {{
     *   inputElement: (Element|undefined),
     *   value: (string|undefined),
     *   invalid: boolean
     * }} state -
     *     inputElement: The input element.
     *     value: The input value.
     *     invalid: True if the input value is invalid.
     */
    update: function(state) {
      if (!state.inputElement) {
        return;
      }

      state.value = state.value || '';

      var counter = state.value.toString().length.toString();

      if (state.inputElement.hasAttribute('maxlength')) {
        counter += '/' + state.inputElement.getAttribute('maxlength');
      }

      this._charCounterStr = counter;
    }
  });
</script>
<custom-style>
  <style is="custom-style">
    html {
      --paper-input-container-shared-input-style: {
        position: relative; /* to make a stacking context */
        outline: none;
        box-shadow: none;
        padding: 0;
        margin: 0;
        width: 100%;
        max-width: 100%;
        background: transparent;
        border: none;
        color: var(--paper-input-container-input-color, var(--primary-text-color));
        -webkit-appearance: none;
        text-align: inherit;
        vertical-align: bottom;

        @apply --paper-font-subhead;
      };
    }
  </style>
</custom-style>

<dom-module id="paper-input-container" assetpath="bower_components/paper-input/">
  <template>
    <style>
      :host {
        display: block;
        padding: 8px 0;
        @apply --paper-input-container;
      }

      :host([inline]) {
        display: inline-block;
      }

      :host([disabled]) {
        pointer-events: none;
        opacity: 0.33;

        @apply --paper-input-container-disabled;
      }

      :host([hidden]) {
        display: none !important;
      }

      [hidden] {
        display: none !important;
      }

      .floated-label-placeholder {
        @apply --paper-font-caption;
      }

      .underline {
        height: 2px;
        position: relative;
      }

      .focused-line {
        @apply --layout-fit;
        border-bottom: 2px solid var(--paper-input-container-focus-color, var(--primary-color));

        -webkit-transform-origin: center center;
        transform-origin: center center;
        -webkit-transform: scale3d(0,1,1);
        transform: scale3d(0,1,1);

        @apply --paper-input-container-underline-focus;
      }

      .underline.is-highlighted .focused-line {
        -webkit-transform: none;
        transform: none;
        -webkit-transition: -webkit-transform 0.25s;
        transition: transform 0.25s;

        @apply --paper-transition-easing;
      }

      .underline.is-invalid .focused-line {
        border-color: var(--paper-input-container-invalid-color, var(--error-color));
        -webkit-transform: none;
        transform: none;
        -webkit-transition: -webkit-transform 0.25s;
        transition: transform 0.25s;

        @apply --paper-transition-easing;
      }

      .unfocused-line {
        @apply --layout-fit;
        border-bottom: 1px solid var(--paper-input-container-color, var(--secondary-text-color));
        @apply --paper-input-container-underline;
      }

      :host([disabled]) .unfocused-line {
        border-bottom: 1px dashed;
        border-color: var(--paper-input-container-color, var(--secondary-text-color));
        @apply --paper-input-container-underline-disabled;
      }

      .input-wrapper {
        @apply --layout-horizontal;
        @apply --layout-center;
        position: relative;
      }

      .input-content {
        @apply --layout-flex-auto;
        @apply --layout-relative;
        max-width: 100%;
      }

      .input-content ::slotted(label),
      .input-content ::slotted(.paper-input-label) {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        font: inherit;
        color: var(--paper-input-container-color, var(--secondary-text-color));
        -webkit-transition: -webkit-transform 0.25s, width 0.25s;
        transition: transform 0.25s, width 0.25s;
        -webkit-transform-origin: left top;
        transform-origin: left top;
        /* Fix for safari not focusing 0-height date/time inputs with -webkit-apperance: none; */
        min-height: 1px;

        @apply --paper-font-common-nowrap;
        @apply --paper-font-subhead;
        @apply --paper-input-container-label;
        @apply --paper-transition-easing;
      }

      .input-content.label-is-floating ::slotted(label),
      .input-content.label-is-floating ::slotted(.paper-input-label) {
        -webkit-transform: translateY(-75%) scale(0.75);
        transform: translateY(-75%) scale(0.75);

        /* Since we scale to 75/100 of the size, we actually have 100/75 of the
        original space now available */
        width: 133%;

        @apply --paper-input-container-label-floating;
      }

      :host(:dir(rtl)) .input-content.label-is-floating ::slotted(label),
      :host(:dir(rtl)) .input-content.label-is-floating ::slotted(.paper-input-label) {
        right: 0;
        left: auto;
        -webkit-transform-origin: right top;
        transform-origin: right top;
      }

      .input-content.label-is-highlighted ::slotted(label),
      .input-content.label-is-highlighted ::slotted(.paper-input-label) {
        color: var(--paper-input-container-focus-color, var(--primary-color));

        @apply --paper-input-container-label-focus;
      }

      .input-content.is-invalid ::slotted(label),
      .input-content.is-invalid ::slotted(.paper-input-label) {
        color: var(--paper-input-container-invalid-color, var(--error-color));
      }

      .input-content.label-is-hidden ::slotted(label),
      .input-content.label-is-hidden ::slotted(.paper-input-label) {
        visibility: hidden;
      }

      .input-content ::slotted(input),
      .input-content ::slotted(iron-input),
      .input-content ::slotted(textarea),
      .input-content ::slotted(iron-autogrow-textarea),
      .input-content ::slotted(.paper-input-input) {
        @apply --paper-input-container-shared-input-style;
        /* The apply shim doesn't apply the nested color custom property,
          so we have to re-apply it here. */
        color: var(--paper-input-container-input-color, var(--primary-text-color));
        @apply --paper-input-container-input;
      }

      .input-content ::slotted(input)::-webkit-outer-spin-button,
      .input-content ::slotted(input)::-webkit-inner-spin-button {
        @apply --paper-input-container-input-webkit-spinner;
      }

      .input-content.focused ::slotted(input),
      .input-content.focused ::slotted(iron-input),
      .input-content.focused ::slotted(textarea),
      .input-content.focused ::slotted(iron-autogrow-textarea),
      .input-content.focused ::slotted(.paper-input-input) {
        @apply --paper-input-container-input-focus;
      }

      .input-content.is-invalid ::slotted(input),
      .input-content.is-invalid ::slotted(iron-input),
      .input-content.is-invalid ::slotted(textarea),
      .input-content.is-invalid ::slotted(iron-autogrow-textarea),
      .input-content.is-invalid ::slotted(.paper-input-input) {
        @apply --paper-input-container-input-invalid;
      }

      .prefix ::slotted(*) {
        display: inline-block;
        @apply --paper-font-subhead;
        @apply --layout-flex-none;
        @apply --paper-input-prefix;
      }

      .suffix ::slotted(*) {
        display: inline-block;
        @apply --paper-font-subhead;
        @apply --layout-flex-none;

        @apply --paper-input-suffix;
      }

      /* Firefox sets a min-width on the input, which can cause layout issues */
      .input-content ::slotted(input) {
        min-width: 0;
      }

      .input-content ::slotted(textarea) {
        resize: none;
      }

      .add-on-content {
        position: relative;
      }

      .add-on-content.is-invalid ::slotted(*) {
        color: var(--paper-input-container-invalid-color, var(--error-color));
      }

      .add-on-content.is-highlighted ::slotted(*) {
        color: var(--paper-input-container-focus-color, var(--primary-color));
      }
    </style>

    <div class="floated-label-placeholder" aria-hidden="true" hidden="[[noLabelFloat]]">&nbsp;</div>

    <div class="input-wrapper">
      <span class="prefix"><slot name="prefix"></slot></span>

      <div class$="[[_computeInputContentClass(noLabelFloat,alwaysFloatLabel,focused,invalid,_inputHasContent)]]" id="labelAndInputContainer">
        <slot name="label"></slot>
        <slot name="input"></slot>
      </div>

      <span class="suffix"><slot name="suffix"></slot></span>
    </div>

    <div class$="[[_computeUnderlineClass(focused,invalid)]]">
      <div class="unfocused-line"></div>
      <div class="focused-line"></div>
    </div>

    <div class$="[[_computeAddOnContentClass(focused,invalid)]]">
      <slot name="add-on"></slot>
    </div>
  </template>
</dom-module>

<script>
  Polymer({
    is: 'paper-input-container',

    properties: {
      /**
       * Set to true to disable the floating label. The label disappears when the
       * input value is not null.
       */
      noLabelFloat: {type: Boolean, value: false},

      /**
       * Set to true to always float the floating label.
       */
      alwaysFloatLabel: {type: Boolean, value: false},

      /**
       * The attribute to listen for value changes on.
       */
      attrForValue: {type: String, value: 'bind-value'},

      /**
       * Set to true to auto-validate the input value when it changes.
       */
      autoValidate: {type: Boolean, value: false},

      /**
       * True if the input is invalid. This property is set automatically when the
       * input value changes if auto-validating, or when the `iron-input-validate`
       * event is heard from a child.
       */
      invalid: {observer: '_invalidChanged', type: Boolean, value: false},

      /**
       * True if the input has focus.
       */
      focused: {readOnly: true, type: Boolean, value: false, notify: true},

      _addons: {
        type: Array
        // do not set a default value here intentionally - it will be initialized
        // lazily when a distributed child is attached, which may occur before
        // configuration for this element in polyfill.
      },

      _inputHasContent: {type: Boolean, value: false},

      _inputSelector:
          {type: String, value: 'input,iron-input,textarea,.paper-input-input'},

      _boundOnFocus: {
        type: Function,
        value: function() {
          return this._onFocus.bind(this);
        }
      },

      _boundOnBlur: {
        type: Function,
        value: function() {
          return this._onBlur.bind(this);
        }
      },

      _boundOnInput: {
        type: Function,
        value: function() {
          return this._onInput.bind(this);
        }
      },

      _boundValueChanged: {
        type: Function,
        value: function() {
          return this._onValueChanged.bind(this);
        }
      }
    },

    listeners: {
      'addon-attached': '_onAddonAttached',
      'iron-input-validate': '_onIronInputValidate'
    },

    get _valueChangedEvent() {
      return this.attrForValue + '-changed';
    },

    get _propertyForValue() {
      return Polymer.CaseMap.dashToCamelCase(this.attrForValue);
    },

    get _inputElement() {
      return Polymer.dom(this).querySelector(this._inputSelector);
    },

    get _inputElementValue() {
      return this._inputElement[this._propertyForValue] ||
          this._inputElement.value;
    },

    ready: function() {
      // Paper-input treats a value of undefined differently at startup than
      // the rest of the time (specifically: it does not validate it at startup,
      // but it does after that. We need to track whether the first time we
      // encounter the value is basically this first time, so that we can validate
      // it correctly the rest of the time. See
      // https://github.com/PolymerElements/paper-input/issues/605
      this.__isFirstValueUpdate = true;
      if (!this._addons) {
        this._addons = [];
      }
      this.addEventListener('focus', this._boundOnFocus, true);
      this.addEventListener('blur', this._boundOnBlur, true);
    },

    attached: function() {
      if (this.attrForValue) {
        this._inputElement.addEventListener(
            this._valueChangedEvent, this._boundValueChanged);
      } else {
        this.addEventListener('input', this._onInput);
      }

      // Only validate when attached if the input already has a value.
      if (this._inputElementValue && this._inputElementValue != '') {
        this._handleValueAndAutoValidate(this._inputElement);
      } else {
        this._handleValue(this._inputElement);
      }
    },

    /** @private */
    _onAddonAttached: function(event) {
      if (!this._addons) {
        this._addons = [];
      }
      var target = event.target;
      if (this._addons.indexOf(target) === -1) {
        this._addons.push(target);
        if (this.isAttached) {
          this._handleValue(this._inputElement);
        }
      }
    },

    /** @private */
    _onFocus: function() {
      this._setFocused(true);
    },

    /** @private */
    _onBlur: function() {
      this._setFocused(false);
      this._handleValueAndAutoValidate(this._inputElement);
    },

    /** @private */
    _onInput: function(event) {
      this._handleValueAndAutoValidate(event.target);
    },

    /** @private */
    _onValueChanged: function(event) {
      var input = event.target;

      // Paper-input treats a value of undefined differently at startup than
      // the rest of the time (specifically: it does not validate it at startup,
      // but it does after that. If this is in fact the bootup case, ignore
      // validation, just this once.
      if (this.__isFirstValueUpdate) {
        this.__isFirstValueUpdate = false;
        if (input.value === undefined || input.value === '') {
          return;
        }
      }

      this._handleValueAndAutoValidate(event.target);
    },

    /** @private */
    _handleValue: function(inputElement) {
      var value = this._inputElementValue;

      // type="number" hack needed because this.value is empty until it's valid
      if (value || value === 0 ||
          (inputElement.type === 'number' && !inputElement.checkValidity())) {
        this._inputHasContent = true;
      } else {
        this._inputHasContent = false;
      }

      this.updateAddons(
          {inputElement: inputElement, value: value, invalid: this.invalid});
    },

    /** @private */
    _handleValueAndAutoValidate: function(inputElement) {
      if (this.autoValidate && inputElement) {
        var valid;

        if (inputElement.validate) {
          valid = inputElement.validate(this._inputElementValue);
        } else {
          valid = inputElement.checkValidity();
        }
        this.invalid = !valid;
      }

      // Call this last to notify the add-ons.
      this._handleValue(inputElement);
    },

    /** @private */
    _onIronInputValidate: function(event) {
      this.invalid = this._inputElement.invalid;
    },

    /** @private */
    _invalidChanged: function() {
      if (this._addons) {
        this.updateAddons({invalid: this.invalid});
      }
    },

    /**
     * Call this to update the state of add-ons.
     * @param {Object} state Add-on state.
     */
    updateAddons: function(state) {
      for (var addon, index = 0; addon = this._addons[index]; index++) {
        addon.update(state);
      }
    },

    /** @private */
    _computeInputContentClass: function(
        noLabelFloat, alwaysFloatLabel, focused, invalid, _inputHasContent) {
      var cls = 'input-content';
      if (!noLabelFloat) {
        var label = this.querySelector('label');

        if (alwaysFloatLabel || _inputHasContent) {
          cls += ' label-is-floating';
          // If the label is floating, ignore any offsets that may have been
          // applied from a prefix element.
          this.$.labelAndInputContainer.style.position = 'static';

          if (invalid) {
            cls += ' is-invalid';
          } else if (focused) {
            cls += ' label-is-highlighted';
          }
        } else {
          // When the label is not floating, it should overlap the input element.
          if (label) {
            this.$.labelAndInputContainer.style.position = 'relative';
          }
          if (invalid) {
            cls += ' is-invalid';
          }
        }
      } else {
        if (_inputHasContent) {
          cls += ' label-is-hidden';
        }
        if (invalid) {
          cls += ' is-invalid';
        }
      }
      if (focused) {
        cls += ' focused';
      }
      return cls;
    },

    /** @private */
    _computeUnderlineClass: function(focused, invalid) {
      var cls = 'underline';
      if (invalid) {
        cls += ' is-invalid';
      } else if (focused) {
        cls += ' is-highlighted'
      }
      return cls;
    },

    /** @private */
    _computeAddOnContentClass: function(focused, invalid) {
      var cls = 'add-on-content';
      if (invalid) {
        cls += ' is-invalid';
      } else if (focused) {
        cls += ' is-highlighted'
      }
      return cls;
    }
  });
</script>
<dom-module id="paper-input-error" assetpath="bower_components/paper-input/">
  <template>
    <style>
      :host {
        display: inline-block;
        visibility: hidden;

        color: var(--paper-input-container-invalid-color, var(--error-color));

        @apply --paper-font-caption;
        @apply --paper-input-error;
        position: absolute;
        left:0;
        right:0;
      }

      :host([invalid]) {
        visibility: visible;
      };
    </style>

    <slot></slot>
  </template>
</dom-module>

<script>
  Polymer({
    is: 'paper-input-error',

    behaviors: [Polymer.PaperInputAddonBehavior],

    properties: {
      /**
       * True if the error is showing.
       */
      invalid: {readOnly: true, reflectToAttribute: true, type: Boolean}
    },

    /**
     * This overrides the update function in PaperInputAddonBehavior.
     * @param {{
     *   inputElement: (Element|undefined),
     *   value: (string|undefined),
     *   invalid: boolean
     * }} state -
     *     inputElement: The input element.
     *     value: The input value.
     *     invalid: True if the input value is invalid.
     */
    update: function(state) {
      this._setInvalid(state.invalid);
    }
  });
</script>
<dom-module id="paper-input" assetpath="bower_components/paper-input/">
  <template>
    <style>
      :host {
        display: block;
      }

      :host([focused]) {
        outline: none;
      }

      :host([hidden]) {
        display: none !important;
      }

      input {
        /* Firefox sets a min-width on the input, which can cause layout issues */
        min-width: 0;
      }

      /* In 1.x, the <input> is distributed to paper-input-container, which styles it.
      In 2.x the <iron-input> is distributed to paper-input-container, which styles
      it, but in order for this to work correctly, we need to reset some
      of the native input's properties to inherit (from the iron-input) */
      iron-input > input {
        @apply --paper-input-container-shared-input-style;
        font-family: inherit;
        font-weight: inherit;
        font-size: inherit;
        letter-spacing: inherit;
        word-spacing: inherit;
        line-height: inherit;
        text-shadow: inherit;
        color: inherit;
        cursor: inherit;
      }

      input:disabled {
        @apply --paper-input-container-input-disabled;
      }

      input::-webkit-outer-spin-button,
      input::-webkit-inner-spin-button {
        @apply --paper-input-container-input-webkit-spinner;
      }

      input::-webkit-clear-button {
        @apply --paper-input-container-input-webkit-clear;
      }

      input::-webkit-calendar-picker-indicator {
        @apply --paper-input-container-input-webkit-calendar-picker-indicator;
      }

      input::-webkit-input-placeholder {
        color: var(--paper-input-container-color, var(--secondary-text-color));
      }

      input:-moz-placeholder {
        color: var(--paper-input-container-color, var(--secondary-text-color));
      }

      input::-moz-placeholder {
        color: var(--paper-input-container-color, var(--secondary-text-color));
      }

      input::-ms-clear {
        @apply --paper-input-container-ms-clear;
      }

      input::-ms-reveal {
        @apply --paper-input-container-ms-reveal;
      }

      input:-ms-input-placeholder {
        color: var(--paper-input-container-color, var(--secondary-text-color));
      }

      label {
        pointer-events: none;
      }
    </style>

    <paper-input-container id="container" no-label-float="[[noLabelFloat]]" always-float-label="[[_computeAlwaysFloatLabel(alwaysFloatLabel,placeholder)]]" auto-validate$="[[autoValidate]]" disabled$="[[disabled]]" invalid="[[invalid]]">

      <slot name="prefix" slot="prefix"></slot>

      <label hidden$="[[!label]]" aria-hidden="true" for$="[[_inputId]]" slot="label">[[label]]</label>

      <span id="template-placeholder"></span>

      <slot name="suffix" slot="suffix"></slot>

      <template is="dom-if" if="[[errorMessage]]">
        <paper-input-error aria-live="assertive" slot="add-on">[[errorMessage]]</paper-input-error>
      </template>

      <template is="dom-if" if="[[charCounter]]">
        <paper-input-char-counter slot="add-on"></paper-input-char-counter>
      </template>

    </paper-input-container>
  </template>

  <template id="v0">
    <input is="iron-input" slot="input" class="input-element" id$="[[_inputId]]" aria-labelledby$="[[_ariaLabelledBy]]" aria-describedby$="[[_ariaDescribedBy]]" disabled$="[[disabled]]" title$="[[title]]" bind-value="{{value}}" invalid="{{invalid}}" prevent-invalid-input="[[preventInvalidInput]]" allowed-pattern="[[allowedPattern]]" validator="[[validator]]" type$="[[type]]" pattern$="[[pattern]]" required$="[[required]]" autocomplete$="[[autocomplete]]" autofocus$="[[autofocus]]" inputmode$="[[inputmode]]" minlength$="[[minlength]]" maxlength$="[[maxlength]]" min$="[[min]]" max$="[[max]]" step$="[[step]]" name$="[[name]]" placeholder$="[[placeholder]]" readonly$="[[readonly]]" list$="[[list]]" size$="[[size]]" autocapitalize$="[[autocapitalize]]" autocorrect$="[[autocorrect]]" on-change="_onChange" tabindex$="[[tabIndex]]" autosave$="[[autosave]]" results$="[[results]]" accept$="[[accept]]" multiple$="[[multiple]]">
  </template>

  <template id="v1">
    <iron-input bind-value="{{value}}" slot="input" class="input-element" id$="[[_inputId]]" maxlength$="[[maxlength]]" allowed-pattern="[[allowedPattern]]" invalid="{{invalid}}" validator="[[validator]]">
      <input aria-labelledby$="[[_ariaLabelledBy]]" aria-describedby$="[[_ariaDescribedBy]]" disabled$="[[disabled]]" title$="[[title]]" type$="[[type]]" pattern$="[[pattern]]" required$="[[required]]" autocomplete$="[[autocomplete]]" autofocus$="[[autofocus]]" inputmode$="[[inputmode]]" minlength$="[[minlength]]" maxlength$="[[maxlength]]" min$="[[min]]" max$="[[max]]" step$="[[step]]" name$="[[name]]" placeholder$="[[placeholder]]" readonly$="[[readonly]]" list$="[[list]]" size$="[[size]]" autocapitalize$="[[autocapitalize]]" autocorrect$="[[autocorrect]]" on-change="_onChange" tabindex$="[[tabIndex]]" autosave$="[[autosave]]" results$="[[results]]" accept$="[[accept]]" multiple$="[[multiple]]">
    </iron-input>
  </template>

</dom-module>

<script>
  Polymer({
    is: 'paper-input',

    behaviors: [Polymer.PaperInputBehavior, Polymer.IronFormElementBehavior],

    properties: {
      value: {
        // Required for the correct TypeScript type-generation
        type: String
      }
    },

    beforeRegister: function() {
      // We need to tell which kind of of template to stamp based on
      // what kind of `iron-input` we got, but because of polyfills and
      // custom elements differences between v0 and v1, the safest bet is
      // to check a particular method we know the iron-input#2.x can have.
      // If it doesn't have it, then it's an iron-input#1.x.
      var ironInput = document.createElement('iron-input');
      var version =
          typeof ironInput._initSlottedInput == 'function' ? 'v1' : 'v0';
      var template = Polymer.DomModule.import('paper-input', 'template');
      var inputTemplate =
          Polymer.DomModule.import('paper-input', 'template#' + version);
      var inputPlaceholder =
          template.content.querySelector('#template-placeholder');
      if (inputPlaceholder) {
        inputPlaceholder.parentNode.replaceChild(
            inputTemplate.content, inputPlaceholder);
      }
      // else it's already been processed, probably in superclass
    },

    /**
     * Returns a reference to the focusable element. Overridden from
     * PaperInputBehavior to correctly focus the native input.
     *
     * @return {!HTMLElement}
     */
    get _focusableElement() {
      return Polymer.Element ? this.inputElement._inputElement :
                               this.inputElement;
    },

    // Note: This event is only available in the 1.0 version of this element.
    // In 2.0, the functionality of `_onIronInputReady` is done in
    // PaperInputBehavior::attached.
    listeners: {'iron-input-ready': '_onIronInputReady'},

    _onIronInputReady: function() {
      // Even though this is only used in the next line, save this for
      // backwards compatibility, since the native input had this ID until 2.0.5.
      if (!this.$.nativeInput) {
        this.$.nativeInput = this.$$('input');
      }
      if (this.inputElement &&
          this._typesThatHaveText.indexOf(this.$.nativeInput.type) !== -1) {
        this.alwaysFloatLabel = true;
      }

      // Only validate when attached if the input already has a value.
      if (!!this.inputElement.bindValue) {
        this.$.container._handleValueAndAutoValidate(this.inputElement);
      }
    },
  });
</script>
<iron-iconset-svg name="paper-dropdown-menu" size="24">
<svg><defs>
<g id="arrow-drop-down"><path d="M7 10l5 5 5-5z"></path></g>
</defs></svg>
</iron-iconset-svg>
<dom-module id="paper-dropdown-menu-shared-styles" assetpath="bower_components/paper-dropdown-menu/">
  <template>
    <style>
      :host {
        display: inline-block;
        position: relative;
        text-align: left;

        /* NOTE(cdata): Both values are needed, since some phones require the
         * value to be `transparent`.
         */
        -webkit-tap-highlight-color: rgba(0,0,0,0);
        -webkit-tap-highlight-color: transparent;

        --paper-input-container-input: {
          overflow: hidden;
          white-space: nowrap;
          text-overflow: ellipsis;
          max-width: 100%;
          box-sizing: border-box;
          cursor: pointer;
        };

        @apply --paper-dropdown-menu;
      }

      :host([disabled]) {
        @apply --paper-dropdown-menu-disabled;
      }

      :host([noink]) paper-ripple {
        display: none;
      }

      :host([no-label-float]) paper-ripple {
        top: 8px;
      }

      paper-ripple {
        top: 12px;
        left: 0px;
        bottom: 8px;
        right: 0px;

        @apply --paper-dropdown-menu-ripple;
      }

      paper-menu-button {
        display: block;
        padding: 0;

        @apply --paper-dropdown-menu-button;
      }

      paper-input {
        @apply --paper-dropdown-menu-input;
      }

      iron-icon {
        color: var(--disabled-text-color);

        @apply --paper-dropdown-menu-icon;
      }
    </style>
  </template>
</dom-module>
<dom-module id="paper-dropdown-menu" assetpath="bower_components/paper-dropdown-menu/">
  <template>
    <style include="paper-dropdown-menu-shared-styles"></style>

    <span role="button"></span>
    <paper-menu-button id="menuButton" vertical-align="[[verticalAlign]]" horizontal-align="[[horizontalAlign]]" dynamic-align="[[dynamicAlign]]" vertical-offset="[[_computeMenuVerticalOffset(noLabelFloat, verticalOffset)]]" disabled="[[disabled]]" no-animations="[[noAnimations]]" on-iron-select="_onIronSelect" on-iron-deselect="_onIronDeselect" opened="{{opened}}" close-on-activate="" allow-outside-scroll="[[allowOutsideScroll]]" restore-focus-on-close="[[restoreFocusOnClose]]">
      <div class="dropdown-trigger" slot="dropdown-trigger">
        <paper-ripple></paper-ripple>
        <paper-input type="text" invalid="[[invalid]]" readonly="" disabled="[[disabled]]" value="[[value]]" placeholder="[[placeholder]]" error-message="[[errorMessage]]" always-float-label="[[alwaysFloatLabel]]" no-label-float="[[noLabelFloat]]" label="[[label]]">
          <iron-icon icon="paper-dropdown-menu:arrow-drop-down" suffix="" slot="suffix"></iron-icon>
        </paper-input>
      </div>
      <slot id="content" name="dropdown-content" slot="dropdown-content"></slot>
    </paper-menu-button>
  </template>

  <script>
    (function() {
      'use strict';

      Polymer({
        is: 'paper-dropdown-menu',

        behaviors: [
          Polymer.IronButtonState,
          Polymer.IronControlState,
          Polymer.IronFormElementBehavior,
          Polymer.IronValidatableBehavior
        ],

        properties: {
          /**
           * The derived "label" of the currently selected item. This value
           * is the `label` property on the selected item if set, or else the
           * trimmed text content of the selected item.
           */
          selectedItemLabel: {
            type: String,
            notify: true,
            readOnly: true
          },

          /**
           * The last selected item. An item is selected if the dropdown menu has
           * a child with slot `dropdown-content`, and that child triggers an
           * `iron-select` event with the selected `item` in the `detail`.
           *
           * @type {?Object}
           */
          selectedItem: {
            type: Object,
            notify: true,
            readOnly: true
          },

          /**
           * The value for this element that will be used when submitting in
           * a form. It reflects the value of `selectedItemLabel`. If set directly,
           * it will not update the `selectedItemLabel` value.
           */
          value: {
            type: String,
            notify: true,
          },

          /**
           * The label for the dropdown.
           */
          label: {
            type: String
          },

          /**
           * The placeholder for the dropdown.
           */
          placeholder: {
            type: String
          },

          /**
           * The error message to display when invalid.
           */
          errorMessage: {
              type: String
          },

          /**
           * True if the dropdown is open. Otherwise, false.
           */
          opened: {
            type: Boolean,
            notify: true,
            value: false,
            observer: '_openedChanged'
          },

          /**
           * By default, the dropdown will constrain scrolling on the page
           * to itself when opened.
           * Set to true in order to prevent scroll from being constrained
           * to the dropdown when it opens.
           */
          allowOutsideScroll: {
            type: Boolean,
            value: false
          },

          /**
           * Set to true to disable the floating label. Bind this to the
           * `<paper-input-container>`'s `noLabelFloat` property.
           */
          noLabelFloat: {
              type: Boolean,
              value: false,
              reflectToAttribute: true
          },

          /**
           * Set to true to always float the label. Bind this to the
           * `<paper-input-container>`'s `alwaysFloatLabel` property.
           */
          alwaysFloatLabel: {
            type: Boolean,
            value: false
          },

          /**
           * Set to true to disable animations when opening and closing the
           * dropdown.
           */
          noAnimations: {
            type: Boolean,
            value: false
          },

          /**
           * The orientation against which to align the menu dropdown
           * horizontally relative to the dropdown trigger.
           */
          horizontalAlign: {
            type: String,
            value: 'right'
          },

          /**
           * The orientation against which to align the menu dropdown
           * vertically relative to the dropdown trigger.
           */
          verticalAlign: {
            type: String,
            value: 'top'
          },

          /**
           * Overrides the vertical offset computed in
           * _computeMenuVerticalOffset.
           */
           verticalOffset: Number,

          /**
           * If true, the `horizontalAlign` and `verticalAlign` properties will
           * be considered preferences instead of strict requirements when
           * positioning the dropdown and may be changed if doing so reduces
           * the area of the dropdown falling outside of `fitInto`.
           */
          dynamicAlign: {
            type: Boolean
          },

          /**
           * Whether focus should be restored to the dropdown when the menu closes.
           */
          restoreFocusOnClose: {
            type: Boolean,
            value: true
          },
        },

        listeners: {
          'tap': '_onTap'
        },

        /**
         * @type {!Object}
         */
        keyBindings: {
          'up down': 'open',
          'esc': 'close'
        },

        hostAttributes: {
          role: 'combobox',
          'aria-autocomplete': 'none',
          'aria-haspopup': 'true'
        },

        observers: [
          '_selectedItemChanged(selectedItem)'
        ],

        attached: function() {
          // NOTE(cdata): Due to timing, a preselected value in a `IronSelectable`
          // child will cause an `iron-select` event to fire while the element is
          // still in a `DocumentFragment`. This has the effect of causing
          // handlers not to fire. So, we double check this value on attached:
          var contentElement = this.contentElement;
          if (contentElement && contentElement.selectedItem) {
            this._setSelectedItem(contentElement.selectedItem);
          }
        },

        /**
         * The content element that is contained by the dropdown menu, if any.
         */
        get contentElement() {
          // Polymer 2.x returns slot.assignedNodes which can contain text nodes.
          var nodes = Polymer.dom(this.$.content).getDistributedNodes();
          for (var i = 0, l = nodes.length; i < l; i++) {
            if (nodes[i].nodeType === Node.ELEMENT_NODE) {
              return nodes[i];
            }
          }
        },

        /**
         * Show the dropdown content.
         */
        open: function() {
          this.$.menuButton.open();
        },

        /**
         * Hide the dropdown content.
         */
        close: function() {
          this.$.menuButton.close();
        },

        /**
         * A handler that is called when `iron-select` is fired.
         *
         * @param {CustomEvent} event An `iron-select` event.
         */
        _onIronSelect: function(event) {
          this._setSelectedItem(event.detail.item);
        },

        /**
         * A handler that is called when `iron-deselect` is fired.
         *
         * @param {CustomEvent} event An `iron-deselect` event.
         */
        _onIronDeselect: function(event) {
          this._setSelectedItem(null);
        },

        /**
         * A handler that is called when the dropdown is tapped.
         *
         * @param {CustomEvent} event A tap event.
         */
        _onTap: function(event) {
          if (Polymer.Gestures.findOriginalTarget(event) === this) {
            this.open();
          }
        },

        /**
         * Compute the label for the dropdown given a selected item.
         *
         * @param {Element} selectedItem A selected Element item, with an
         * optional `label` property.
         */
        _selectedItemChanged: function(selectedItem) {
          var value = '';
          if (!selectedItem) {
            value = '';
          } else {
            value = selectedItem.label || selectedItem.getAttribute('label') || selectedItem.textContent.trim();
          }

          this.value = value;
          this._setSelectedItemLabel(value);
        },

        /**
         * Compute the vertical offset of the menu based on the value of
         * `noLabelFloat`.
         *
         * @param {boolean} noLabelFloat True if the label should not float
         * @param {number=} opt_verticalOffset Optional offset from the user
         * above the input, otherwise false.
         */
        _computeMenuVerticalOffset: function(noLabelFloat, opt_verticalOffset) {
          // Override offset if it's passed from the user.
          if (opt_verticalOffset) { return opt_verticalOffset; }

          // NOTE(cdata): These numbers are somewhat magical because they are
          // derived from the metrics of elements internal to `paper-input`'s
          // template. The metrics will change depending on whether or not the
          // input has a floating label.
          return noLabelFloat ? -4 : 8;
        },

        /**
         * Returns false if the element is required and does not have a selection,
         * and true otherwise.
         * @param {*=} _value Ignored.
         * @return {boolean} true if `required` is false, or if `required` is true
         * and the element has a valid selection.
         */
        _getValidity: function(_value) {
          return this.disabled || !this.required || (this.required && !!this.value);
        },

        _openedChanged: function() {
          var openState = this.opened ? 'true' : 'false';
          var e = this.contentElement;
          if (e) {
            e.setAttribute('aria-expanded', openState);
          }
        }
      });
    })();
  </script>
</dom-module>
<script>// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
//     You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//     See the License for the specific language governing permissions and
// limitations under the License.

!function(){var a={},b={},c={};!function(a,b){function c(a){if("number"==typeof a)return a;var b={};for(var c in a)b[c]=a[c];return b}function d(){this._delay=0,this._endDelay=0,this._fill="none",this._iterationStart=0,this._iterations=1,this._duration=0,this._playbackRate=1,this._direction="normal",this._easing="linear",this._easingFunction=x}function e(){return a.isDeprecated("Invalid timing inputs","2016-03-02","TypeError exceptions will be thrown instead.",!0)}function f(b,c,e){var f=new d;return c&&(f.fill="both",f.duration="auto"),"number"!=typeof b||isNaN(b)?void 0!==b&&Object.getOwnPropertyNames(b).forEach(function(c){if("auto"!=b[c]){if(("number"==typeof f[c]||"duration"==c)&&("number"!=typeof b[c]||isNaN(b[c])))return;if("fill"==c&&-1==v.indexOf(b[c]))return;if("direction"==c&&-1==w.indexOf(b[c]))return;if("playbackRate"==c&&1!==b[c]&&a.isDeprecated("AnimationEffectTiming.playbackRate","2014-11-28","Use Animation.playbackRate instead."))return;f[c]=b[c]}}):f.duration=b,f}function g(a){return"number"==typeof a&&(a=isNaN(a)?{duration:0}:{duration:a}),a}function h(b,c){return b=a.numericTimingToObject(b),f(b,c)}function i(a,b,c,d){return a<0||a>1||c<0||c>1?x:function(e){function f(a,b,c){return 3*a*(1-c)*(1-c)*c+3*b*(1-c)*c*c+c*c*c}if(e<=0){var g=0;return a>0?g=b/a:!b&&c>0&&(g=d/c),g*e}if(e>=1){var h=0;return c<1?h=(d-1)/(c-1):1==c&&a<1&&(h=(b-1)/(a-1)),1+h*(e-1)}for(var i=0,j=1;i<j;){var k=(i+j)/2,l=f(a,c,k);if(Math.abs(e-l)<1e-5)return f(b,d,k);l<e?i=k:j=k}return f(b,d,k)}}function j(a,b){return function(c){if(c>=1)return 1;var d=1/a;return(c+=b*d)-c%d}}function k(a){C||(C=document.createElement("div").style),C.animationTimingFunction="",C.animationTimingFunction=a;var b=C.animationTimingFunction;if(""==b&&e())throw new TypeError(a+" is not a valid value for easing");return b}function l(a){if("linear"==a)return x;var b=E.exec(a);if(b)return i.apply(this,b.slice(1).map(Number));var c=F.exec(a);if(c)return j(Number(c[1]),A);var d=G.exec(a);return d?j(Number(d[1]),{start:y,middle:z,end:A}[d[2]]):B[a]||x}function m(a){return Math.abs(n(a)/a.playbackRate)}function n(a){return 0===a.duration||0===a.iterations?0:a.duration*a.iterations}function o(a,b,c){if(null==b)return H;var d=c.delay+a+c.endDelay;return b<Math.min(c.delay,d)?I:b>=Math.min(c.delay+a,d)?J:K}function p(a,b,c,d,e){switch(d){case I:return"backwards"==b||"both"==b?0:null;case K:return c-e;case J:return"forwards"==b||"both"==b?a:null;case H:return null}}function q(a,b,c,d,e){var f=e;return 0===a?b!==I&&(f+=c):f+=d/a,f}function r(a,b,c,d,e,f){var g=a===1/0?b%1:a%1;return 0!==g||c!==J||0===d||0===e&&0!==f||(g=1),g}function s(a,b,c,d){return a===J&&b===1/0?1/0:1===c?Math.floor(d)-1:Math.floor(d)}function t(a,b,c){var d=a;if("normal"!==a&&"reverse"!==a){var e=b;"alternate-reverse"===a&&(e+=1),d="normal",e!==1/0&&e%2!=0&&(d="reverse")}return"normal"===d?c:1-c}function u(a,b,c){var d=o(a,b,c),e=p(a,c.fill,b,d,c.delay);if(null===e)return null;var f=q(c.duration,d,c.iterations,e,c.iterationStart),g=r(f,c.iterationStart,d,c.iterations,e,c.duration),h=s(d,c.iterations,g,f),i=t(c.direction,h,g);return c._easingFunction(i)}var v="backwards|forwards|both|none".split("|"),w="reverse|alternate|alternate-reverse".split("|"),x=function(a){return a};d.prototype={_setMember:function(b,c){this["_"+b]=c,this._effect&&(this._effect._timingInput[b]=c,this._effect._timing=a.normalizeTimingInput(this._effect._timingInput),this._effect.activeDuration=a.calculateActiveDuration(this._effect._timing),this._effect._animation&&this._effect._animation._rebuildUnderlyingAnimation())},get playbackRate(){return this._playbackRate},set delay(a){this._setMember("delay",a)},get delay(){return this._delay},set endDelay(a){this._setMember("endDelay",a)},get endDelay(){return this._endDelay},set fill(a){this._setMember("fill",a)},get fill(){return this._fill},set iterationStart(a){if((isNaN(a)||a<0)&&e())throw new TypeError("iterationStart must be a non-negative number, received: "+a);this._setMember("iterationStart",a)},get iterationStart(){return this._iterationStart},set duration(a){if("auto"!=a&&(isNaN(a)||a<0)&&e())throw new TypeError("duration must be non-negative or auto, received: "+a);this._setMember("duration",a)},get duration(){return this._duration},set direction(a){this._setMember("direction",a)},get direction(){return this._direction},set easing(a){this._easingFunction=l(k(a)),this._setMember("easing",a)},get easing(){return this._easing},set iterations(a){if((isNaN(a)||a<0)&&e())throw new TypeError("iterations must be non-negative, received: "+a);this._setMember("iterations",a)},get iterations(){return this._iterations}};var y=1,z=.5,A=0,B={ease:i(.25,.1,.25,1),"ease-in":i(.42,0,1,1),"ease-out":i(0,0,.58,1),"ease-in-out":i(.42,0,.58,1),"step-start":j(1,y),"step-middle":j(1,z),"step-end":j(1,A)},C=null,D="\\s*(-?\\d+\\.?\\d*|-?\\.\\d+)\\s*",E=new RegExp("cubic-bezier\\("+D+","+D+","+D+","+D+"\\)"),F=/steps\(\s*(\d+)\s*\)/,G=/steps\(\s*(\d+)\s*,\s*(start|middle|end)\s*\)/,H=0,I=1,J=2,K=3;a.cloneTimingInput=c,a.makeTiming=f,a.numericTimingToObject=g,a.normalizeTimingInput=h,a.calculateActiveDuration=m,a.calculateIterationProgress=u,a.calculatePhase=o,a.normalizeEasing=k,a.parseEasingFunction=l}(a),function(a,b){function c(a,b){return a in k?k[a][b]||b:b}function d(a){return"display"===a||0===a.lastIndexOf("animation",0)||0===a.lastIndexOf("transition",0)}function e(a,b,e){if(!d(a)){var f=h[a];if(f){i.style[a]=b;for(var g in f){var j=f[g],k=i.style[j];e[j]=c(j,k)}}else e[a]=c(a,b)}}function f(a){var b=[];for(var c in a)if(!(c in["easing","offset","composite"])){var d=a[c];Array.isArray(d)||(d=[d]);for(var e,f=d.length,g=0;g<f;g++)e={},e.offset="offset"in a?a.offset:1==f?1:g/(f-1),"easing"in a&&(e.easing=a.easing),"composite"in a&&(e.composite=a.composite),e[c]=d[g],b.push(e)}return b.sort(function(a,b){return a.offset-b.offset}),b}function g(b){function c(){var a=d.length;null==d[a-1].offset&&(d[a-1].offset=1),a>1&&null==d[0].offset&&(d[0].offset=0);for(var b=0,c=d[0].offset,e=1;e<a;e++){var f=d[e].offset;if(null!=f){for(var g=1;g<e-b;g++)d[b+g].offset=c+(f-c)*g/(e-b);b=e,c=f}}}if(null==b)return[];window.Symbol&&Symbol.iterator&&Array.prototype.from&&b[Symbol.iterator]&&(b=Array.from(b)),Array.isArray(b)||(b=f(b));for(var d=b.map(function(b){var c={};for(var d in b){var f=b[d];if("offset"==d){if(null!=f){if(f=Number(f),!isFinite(f))throw new TypeError("Keyframe offsets must be numbers.");if(f<0||f>1)throw new TypeError("Keyframe offsets must be between 0 and 1.")}}else if("composite"==d){if("add"==f||"accumulate"==f)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"NotSupportedError",message:"add compositing is not supported"};if("replace"!=f)throw new TypeError("Invalid composite mode "+f+".")}else f="easing"==d?a.normalizeEasing(f):""+f;e(d,f,c)}return void 0==c.offset&&(c.offset=null),void 0==c.easing&&(c.easing="linear"),c}),g=!0,h=-1/0,i=0;i<d.length;i++){var j=d[i].offset;if(null!=j){if(j<h)throw new TypeError("Keyframes are not loosely sorted by offset. Sort or specify offsets.");h=j}else g=!1}return d=d.filter(function(a){return a.offset>=0&&a.offset<=1}),g||c(),d}var h={background:["backgroundImage","backgroundPosition","backgroundSize","backgroundRepeat","backgroundAttachment","backgroundOrigin","backgroundClip","backgroundColor"],border:["borderTopColor","borderTopStyle","borderTopWidth","borderRightColor","borderRightStyle","borderRightWidth","borderBottomColor","borderBottomStyle","borderBottomWidth","borderLeftColor","borderLeftStyle","borderLeftWidth"],borderBottom:["borderBottomWidth","borderBottomStyle","borderBottomColor"],borderColor:["borderTopColor","borderRightColor","borderBottomColor","borderLeftColor"],borderLeft:["borderLeftWidth","borderLeftStyle","borderLeftColor"],borderRadius:["borderTopLeftRadius","borderTopRightRadius","borderBottomRightRadius","borderBottomLeftRadius"],borderRight:["borderRightWidth","borderRightStyle","borderRightColor"],borderTop:["borderTopWidth","borderTopStyle","borderTopColor"],borderWidth:["borderTopWidth","borderRightWidth","borderBottomWidth","borderLeftWidth"],flex:["flexGrow","flexShrink","flexBasis"],font:["fontFamily","fontSize","fontStyle","fontVariant","fontWeight","lineHeight"],margin:["marginTop","marginRight","marginBottom","marginLeft"],outline:["outlineColor","outlineStyle","outlineWidth"],padding:["paddingTop","paddingRight","paddingBottom","paddingLeft"]},i=document.createElementNS("http://www.w3.org/1999/xhtml","div"),j={thin:"1px",medium:"3px",thick:"5px"},k={borderBottomWidth:j,borderLeftWidth:j,borderRightWidth:j,borderTopWidth:j,fontSize:{"xx-small":"60%","x-small":"75%",small:"89%",medium:"100%",large:"120%","x-large":"150%","xx-large":"200%"},fontWeight:{normal:"400",bold:"700"},outlineWidth:j,textShadow:{none:"0px 0px 0px transparent"},boxShadow:{none:"0px 0px 0px 0px transparent"}};a.convertToArrayForm=f,a.normalizeKeyframes=g}(a),function(a){var b={};a.isDeprecated=function(a,c,d,e){var f=e?"are":"is",g=new Date,h=new Date(c);return h.setMonth(h.getMonth()+3),!(g<h&&(a in b||console.warn("Web Animations: "+a+" "+f+" deprecated and will stop working on "+h.toDateString()+". "+d),b[a]=!0,1))},a.deprecated=function(b,c,d,e){var f=e?"are":"is";if(a.isDeprecated(b,c,d,e))throw new Error(b+" "+f+" no longer supported. "+d)}}(a),function(){if(document.documentElement.animate){var c=document.documentElement.animate([],0),d=!0;if(c&&(d=!1,"play|currentTime|pause|reverse|playbackRate|cancel|finish|startTime|playState".split("|").forEach(function(a){void 0===c[a]&&(d=!0)})),!d)return}!function(a,b,c){function d(a){for(var b={},c=0;c<a.length;c++)for(var d in a[c])if("offset"!=d&&"easing"!=d&&"composite"!=d){var e={offset:a[c].offset,easing:a[c].easing,value:a[c][d]};b[d]=b[d]||[],b[d].push(e)}for(var f in b){var g=b[f];if(0!=g[0].offset||1!=g[g.length-1].offset)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"NotSupportedError",message:"Partial keyframes are not supported"}}return b}function e(c){var d=[];for(var e in c)for(var f=c[e],g=0;g<f.length-1;g++){var h=g,i=g+1,j=f[h].offset,k=f[i].offset,l=j,m=k;0==g&&(l=-1/0,0==k&&(i=h)),g==f.length-2&&(m=1/0,1==j&&(h=i)),d.push({applyFrom:l,applyTo:m,startOffset:f[h].offset,endOffset:f[i].offset,easingFunction:a.parseEasingFunction(f[h].easing),property:e,interpolation:b.propertyInterpolation(e,f[h].value,f[i].value)})}return d.sort(function(a,b){return a.startOffset-b.startOffset}),d}b.convertEffectInput=function(c){var f=a.normalizeKeyframes(c),g=d(f),h=e(g);return function(a,c){if(null!=c)h.filter(function(a){return c>=a.applyFrom&&c<a.applyTo}).forEach(function(d){var e=c-d.startOffset,f=d.endOffset-d.startOffset,g=0==f?0:d.easingFunction(e/f);b.apply(a,d.property,d.interpolation(g))});else for(var d in g)"offset"!=d&&"easing"!=d&&"composite"!=d&&b.clear(a,d)}}}(a,b),function(a,b,c){function d(a){return a.replace(/-(.)/g,function(a,b){return b.toUpperCase()})}function e(a,b,c){h[c]=h[c]||[],h[c].push([a,b])}function f(a,b,c){for(var f=0;f<c.length;f++){e(a,b,d(c[f]))}}function g(c,e,f){var g=c;/-/.test(c)&&!a.isDeprecated("Hyphenated property names","2016-03-22","Use camelCase instead.",!0)&&(g=d(c)),"initial"!=e&&"initial"!=f||("initial"==e&&(e=i[g]),"initial"==f&&(f=i[g]));for(var j=e==f?[]:h[g],k=0;j&&k<j.length;k++){var l=j[k][0](e),m=j[k][0](f);if(void 0!==l&&void 0!==m){var n=j[k][1](l,m);if(n){var o=b.Interpolation.apply(null,n);return function(a){return 0==a?e:1==a?f:o(a)}}}}return b.Interpolation(!1,!0,function(a){return a?f:e})}var h={};b.addPropertiesHandler=f;var i={backgroundColor:"transparent",backgroundPosition:"0% 0%",borderBottomColor:"currentColor",borderBottomLeftRadius:"0px",borderBottomRightRadius:"0px",borderBottomWidth:"3px",borderLeftColor:"currentColor",borderLeftWidth:"3px",borderRightColor:"currentColor",borderRightWidth:"3px",borderSpacing:"2px",borderTopColor:"currentColor",borderTopLeftRadius:"0px",borderTopRightRadius:"0px",borderTopWidth:"3px",bottom:"auto",clip:"rect(0px, 0px, 0px, 0px)",color:"black",fontSize:"100%",fontWeight:"400",height:"auto",left:"auto",letterSpacing:"normal",lineHeight:"120%",marginBottom:"0px",marginLeft:"0px",marginRight:"0px",marginTop:"0px",maxHeight:"none",maxWidth:"none",minHeight:"0px",minWidth:"0px",opacity:"1.0",outlineColor:"invert",outlineOffset:"0px",outlineWidth:"3px",paddingBottom:"0px",paddingLeft:"0px",paddingRight:"0px",paddingTop:"0px",right:"auto",strokeDasharray:"none",strokeDashoffset:"0px",textIndent:"0px",textShadow:"0px 0px 0px transparent",top:"auto",transform:"",verticalAlign:"0px",visibility:"visible",width:"auto",wordSpacing:"normal",zIndex:"auto"};b.propertyInterpolation=g}(a,b),function(a,b,c){function d(b){var c=a.calculateActiveDuration(b),d=function(d){return a.calculateIterationProgress(c,d,b)};return d._totalDuration=b.delay+c+b.endDelay,d}b.KeyframeEffect=function(c,e,f,g){var h,i=d(a.normalizeTimingInput(f)),j=b.convertEffectInput(e),k=function(){j(c,h)};return k._update=function(a){return null!==(h=i(a))},k._clear=function(){j(c,null)},k._hasSameTarget=function(a){return c===a},k._target=c,k._totalDuration=i._totalDuration,k._id=g,k}}(a,b),function(a,b){a.apply=function(b,c,d){b.style[a.propertyName(c)]=d},a.clear=function(b,c){b.style[a.propertyName(c)]=""}}(b),function(a){window.Element.prototype.animate=function(b,c){var d="";return c&&c.id&&(d=c.id),a.timeline._play(a.KeyframeEffect(this,b,c,d))}}(b),function(a,b){function c(a,b,d){if("number"==typeof a&&"number"==typeof b)return a*(1-d)+b*d;if("boolean"==typeof a&&"boolean"==typeof b)return d<.5?a:b;if(a.length==b.length){for(var e=[],f=0;f<a.length;f++)e.push(c(a[f],b[f],d));return e}throw"Mismatched interpolation arguments "+a+":"+b}a.Interpolation=function(a,b,d){return function(e){return d(c(a,b,e))}}}(b),function(a,b,c){a.sequenceNumber=0;var d=function(a,b,c){this.target=a,this.currentTime=b,this.timelineTime=c,this.type="finish",this.bubbles=!1,this.cancelable=!1,this.currentTarget=a,this.defaultPrevented=!1,this.eventPhase=Event.AT_TARGET,this.timeStamp=Date.now()};b.Animation=function(b){this.id="",b&&b._id&&(this.id=b._id),this._sequenceNumber=a.sequenceNumber++,this._currentTime=0,this._startTime=null,this._paused=!1,this._playbackRate=1,this._inTimeline=!0,this._finishedFlag=!0,this.onfinish=null,this._finishHandlers=[],this._effect=b,this._inEffect=this._effect._update(0),this._idle=!0,this._currentTimePending=!1},b.Animation.prototype={_ensureAlive:function(){this.playbackRate<0&&0===this.currentTime?this._inEffect=this._effect._update(-1):this._inEffect=this._effect._update(this.currentTime),this._inTimeline||!this._inEffect&&this._finishedFlag||(this._inTimeline=!0,b.timeline._animations.push(this))},_tickCurrentTime:function(a,b){a!=this._currentTime&&(this._currentTime=a,this._isFinished&&!b&&(this._currentTime=this._playbackRate>0?this._totalDuration:0),this._ensureAlive())},get currentTime(){return this._idle||this._currentTimePending?null:this._currentTime},set currentTime(a){a=+a,isNaN(a)||(b.restart(),this._paused||null==this._startTime||(this._startTime=this._timeline.currentTime-a/this._playbackRate),this._currentTimePending=!1,this._currentTime!=a&&(this._idle&&(this._idle=!1,this._paused=!0),this._tickCurrentTime(a,!0),b.applyDirtiedAnimation(this)))},get startTime(){return this._startTime},set startTime(a){a=+a,isNaN(a)||this._paused||this._idle||(this._startTime=a,this._tickCurrentTime((this._timeline.currentTime-this._startTime)*this.playbackRate),b.applyDirtiedAnimation(this))},get playbackRate(){return this._playbackRate},set playbackRate(a){if(a!=this._playbackRate){var c=this.currentTime;this._playbackRate=a,this._startTime=null,"paused"!=this.playState&&"idle"!=this.playState&&(this._finishedFlag=!1,this._idle=!1,this._ensureAlive(),b.applyDirtiedAnimation(this)),null!=c&&(this.currentTime=c)}},get _isFinished(){return!this._idle&&(this._playbackRate>0&&this._currentTime>=this._totalDuration||this._playbackRate<0&&this._currentTime<=0)},get _totalDuration(){return this._effect._totalDuration},get playState(){return this._idle?"idle":null==this._startTime&&!this._paused&&0!=this.playbackRate||this._currentTimePending?"pending":this._paused?"paused":this._isFinished?"finished":"running"},_rewind:function(){if(this._playbackRate>=0)this._currentTime=0;else{if(!(this._totalDuration<1/0))throw new DOMException("Unable to rewind negative playback rate animation with infinite duration","InvalidStateError");this._currentTime=this._totalDuration}},play:function(){this._paused=!1,(this._isFinished||this._idle)&&(this._rewind(),this._startTime=null),this._finishedFlag=!1,this._idle=!1,this._ensureAlive(),b.applyDirtiedAnimation(this)},pause:function(){this._isFinished||this._paused||this._idle?this._idle&&(this._rewind(),this._idle=!1):this._currentTimePending=!0,this._startTime=null,this._paused=!0},finish:function(){this._idle||(this.currentTime=this._playbackRate>0?this._totalDuration:0,this._startTime=this._totalDuration-this.currentTime,this._currentTimePending=!1,b.applyDirtiedAnimation(this))},cancel:function(){this._inEffect&&(this._inEffect=!1,this._idle=!0,this._paused=!1,this._finishedFlag=!0,this._currentTime=0,this._startTime=null,this._effect._update(null),b.applyDirtiedAnimation(this))},reverse:function(){this.playbackRate*=-1,this.play()},addEventListener:function(a,b){"function"==typeof b&&"finish"==a&&this._finishHandlers.push(b)},removeEventListener:function(a,b){if("finish"==a){var c=this._finishHandlers.indexOf(b);c>=0&&this._finishHandlers.splice(c,1)}},_fireEvents:function(a){if(this._isFinished){if(!this._finishedFlag){var b=new d(this,this._currentTime,a),c=this._finishHandlers.concat(this.onfinish?[this.onfinish]:[]);setTimeout(function(){c.forEach(function(a){a.call(b.target,b)})},0),this._finishedFlag=!0}}else this._finishedFlag=!1},_tick:function(a,b){this._idle||this._paused||(null==this._startTime?b&&(this.startTime=a-this._currentTime/this.playbackRate):this._isFinished||this._tickCurrentTime((a-this._startTime)*this.playbackRate)),b&&(this._currentTimePending=!1,this._fireEvents(a))},get _needsTick(){return this.playState in{pending:1,running:1}||!this._finishedFlag},_targetAnimations:function(){var a=this._effect._target;return a._activeAnimations||(a._activeAnimations=[]),a._activeAnimations},_markTarget:function(){var a=this._targetAnimations();-1===a.indexOf(this)&&a.push(this)},_unmarkTarget:function(){var a=this._targetAnimations(),b=a.indexOf(this);-1!==b&&a.splice(b,1)}}}(a,b),function(a,b,c){function d(a){var b=j;j=[],a<q.currentTime&&(a=q.currentTime),q._animations.sort(e),q._animations=h(a,!0,q._animations)[0],b.forEach(function(b){b[1](a)}),g(),l=void 0}function e(a,b){return a._sequenceNumber-b._sequenceNumber}function f(){this._animations=[],this.currentTime=window.performance&&performance.now?performance.now():0}function g(){o.forEach(function(a){a()}),o.length=0}function h(a,c,d){p=!0,n=!1,b.timeline.currentTime=a,m=!1;var e=[],f=[],g=[],h=[];return d.forEach(function(b){b._tick(a,c),b._inEffect?(f.push(b._effect),b._markTarget()):(e.push(b._effect),b._unmarkTarget()),b._needsTick&&(m=!0);var d=b._inEffect||b._needsTick;b._inTimeline=d,d?g.push(b):h.push(b)}),o.push.apply(o,e),o.push.apply(o,f),m&&requestAnimationFrame(function(){}),p=!1,[g,h]}var i=window.requestAnimationFrame,j=[],k=0;window.requestAnimationFrame=function(a){var b=k++;return 0==j.length&&i(d),j.push([b,a]),b},window.cancelAnimationFrame=function(a){j.forEach(function(b){b[0]==a&&(b[1]=function(){})})},f.prototype={_play:function(c){c._timing=a.normalizeTimingInput(c.timing);var d=new b.Animation(c);return d._idle=!1,d._timeline=this,this._animations.push(d),b.restart(),b.applyDirtiedAnimation(d),d}};var l=void 0,m=!1,n=!1;b.restart=function(){return m||(m=!0,requestAnimationFrame(function(){}),n=!0),n},b.applyDirtiedAnimation=function(a){if(!p){a._markTarget();var c=a._targetAnimations();c.sort(e),h(b.timeline.currentTime,!1,c.slice())[1].forEach(function(a){var b=q._animations.indexOf(a);-1!==b&&q._animations.splice(b,1)}),g()}};var o=[],p=!1,q=new f;b.timeline=q}(a,b),function(a){function b(a,b){var c=a.exec(b);if(c)return c=a.ignoreCase?c[0].toLowerCase():c[0],[c,b.substr(c.length)]}function c(a,b){b=b.replace(/^\s*/,"");var c=a(b);if(c)return[c[0],c[1].replace(/^\s*/,"")]}function d(a,d,e){a=c.bind(null,a);for(var f=[];;){var g=a(e);if(!g)return[f,e];if(f.push(g[0]),e=g[1],!(g=b(d,e))||""==g[1])return[f,e];e=g[1]}}function e(a,b){for(var c=0,d=0;d<b.length&&(!/\s|,/.test(b[d])||0!=c);d++)if("("==b[d])c++;else if(")"==b[d]&&(c--,0==c&&d++,c<=0))break;var e=a(b.substr(0,d));return void 0==e?void 0:[e,b.substr(d)]}function f(a,b){for(var c=a,d=b;c&&d;)c>d?c%=d:d%=c;return c=a*b/(c+d)}function g(a){return function(b){var c=a(b);return c&&(c[0]=void 0),c}}function h(a,b){return function(c){return a(c)||[b,c]}}function i(b,c){for(var d=[],e=0;e<b.length;e++){var f=a.consumeTrimmed(b[e],c);if(!f||""==f[0])return;void 0!==f[0]&&d.push(f[0]),c=f[1]}if(""==c)return d}function j(a,b,c,d,e){for(var g=[],h=[],i=[],j=f(d.length,e.length),k=0;k<j;k++){var l=b(d[k%d.length],e[k%e.length]);if(!l)return;g.push(l[0]),h.push(l[1]),i.push(l[2])}return[g,h,function(b){var d=b.map(function(a,b){return i[b](a)}).join(c);return a?a(d):d}]}function k(a,b,c){for(var d=[],e=[],f=[],g=0,h=0;h<c.length;h++)if("function"==typeof c[h]){var i=c[h](a[g],b[g++]);d.push(i[0]),e.push(i[1]),f.push(i[2])}else!function(a){d.push(!1),e.push(!1),f.push(function(){return c[a]})}(h);return[d,e,function(a){for(var b="",c=0;c<a.length;c++)b+=f[c](a[c]);return b}]}a.consumeToken=b,a.consumeTrimmed=c,a.consumeRepeated=d,a.consumeParenthesised=e,a.ignore=g,a.optional=h,a.consumeList=i,a.mergeNestedRepeated=j.bind(null,null),a.mergeWrappedNestedRepeated=j,a.mergeList=k}(b),function(a){function b(b){function c(b){var c=a.consumeToken(/^inset/i,b);return c?(d.inset=!0,c):(c=a.consumeLengthOrPercent(b))?(d.lengths.push(c[0]),c):(c=a.consumeColor(b),c?(d.color=c[0],c):void 0)}var d={inset:!1,lengths:[],color:null},e=a.consumeRepeated(c,/^/,b);if(e&&e[0].length)return[d,e[1]]}function c(c){var d=a.consumeRepeated(b,/^,/,c);if(d&&""==d[1])return d[0]}function d(b,c){for(;b.lengths.length<Math.max(b.lengths.length,c.lengths.length);)b.lengths.push({px:0});for(;c.lengths.length<Math.max(b.lengths.length,c.lengths.length);)c.lengths.push({px:0});if(b.inset==c.inset&&!!b.color==!!c.color){for(var d,e=[],f=[[],0],g=[[],0],h=0;h<b.lengths.length;h++){var i=a.mergeDimensions(b.lengths[h],c.lengths[h],2==h);f[0].push(i[0]),g[0].push(i[1]),e.push(i[2])}if(b.color&&c.color){var j=a.mergeColors(b.color,c.color);f[1]=j[0],g[1]=j[1],d=j[2]}return[f,g,function(a){for(var c=b.inset?"inset ":" ",f=0;f<e.length;f++)c+=e[f](a[0][f])+" ";return d&&(c+=d(a[1])),c}]}}function e(b,c,d,e){function f(a){return{inset:a,color:[0,0,0,0],lengths:[{px:0},{px:0},{px:0},{px:0}]}}for(var g=[],h=[],i=0;i<d.length||i<e.length;i++){var j=d[i]||f(e[i].inset),k=e[i]||f(d[i].inset);g.push(j),h.push(k)}return a.mergeNestedRepeated(b,c,g,h)}var f=e.bind(null,d,", ");a.addPropertiesHandler(c,f,["box-shadow","text-shadow"])}(b),function(a,b){function c(a){return a.toFixed(3).replace(/0+$/,"").replace(/\.$/,"")}function d(a,b,c){return Math.min(b,Math.max(a,c))}function e(a){if(/^\s*[-+]?(\d*\.)?\d+\s*$/.test(a))return Number(a)}function f(a,b){return[a,b,c]}function g(a,b){if(0!=a)return i(0,1/0)(a,b)}function h(a,b){return[a,b,function(a){return Math.round(d(1,1/0,a))}]}function i(a,b){return function(e,f){return[e,f,function(e){return c(d(a,b,e))}]}}function j(a){var b=a.trim().split(/\s*[\s,]\s*/);if(0!==b.length){for(var c=[],d=0;d<b.length;d++){var f=e(b[d]);if(void 0===f)return;c.push(f)}return c}}function k(a,b){if(a.length==b.length)return[a,b,function(a){return a.map(c).join(" ")}]}function l(a,b){return[a,b,Math.round]}a.clamp=d,a.addPropertiesHandler(j,k,["stroke-dasharray"]),a.addPropertiesHandler(e,i(0,1/0),["border-image-width","line-height"]),a.addPropertiesHandler(e,i(0,1),["opacity","shape-image-threshold"]),a.addPropertiesHandler(e,g,["flex-grow","flex-shrink"]),a.addPropertiesHandler(e,h,["orphans","widows"]),a.addPropertiesHandler(e,l,["z-index"]),a.parseNumber=e,a.parseNumberList=j,a.mergeNumbers=f,a.numberToString=c}(b),function(a,b){function c(a,b){if("visible"==a||"visible"==b)return[0,1,function(c){return c<=0?a:c>=1?b:"visible"}]}a.addPropertiesHandler(String,c,["visibility"])}(b),function(a,b){function c(a){a=a.trim(),f.fillStyle="#000",f.fillStyle=a;var b=f.fillStyle;if(f.fillStyle="#fff",f.fillStyle=a,b==f.fillStyle){f.fillRect(0,0,1,1);var c=f.getImageData(0,0,1,1).data;f.clearRect(0,0,1,1);var d=c[3]/255;return[c[0]*d,c[1]*d,c[2]*d,d]}}function d(b,c){return[b,c,function(b){function c(a){return Math.max(0,Math.min(255,a))}if(b[3])for(var d=0;d<3;d++)b[d]=Math.round(c(b[d]/b[3]));return b[3]=a.numberToString(a.clamp(0,1,b[3])),"rgba("+b.join(",")+")"}]}var e=document.createElementNS("http://www.w3.org/1999/xhtml","canvas");e.width=e.height=1;var f=e.getContext("2d");a.addPropertiesHandler(c,d,["background-color","border-bottom-color","border-left-color","border-right-color","border-top-color","color","fill","flood-color","lighting-color","outline-color","stop-color","stroke","text-decoration-color"]),a.consumeColor=a.consumeParenthesised.bind(null,c),a.mergeColors=d}(b),function(a,b){function c(a){function b(){var b=h.exec(a);g=b?b[0]:void 0}function c(){var a=Number(g);return b(),a}function d(){if("("!==g)return c();b();var a=f();return")"!==g?NaN:(b(),a)}function e(){for(var a=d();"*"===g||"/"===g;){var c=g;b();var e=d();"*"===c?a*=e:a/=e}return a}function f(){for(var a=e();"+"===g||"-"===g;){var c=g;b();var d=e();"+"===c?a+=d:a-=d}return a}var g,h=/([\+\-\w\.]+|[\(\)\*\/])/g;return b(),f()}function d(a,b){if("0"==(b=b.trim().toLowerCase())&&"px".search(a)>=0)return{px:0};if(/^[^(]*$|^calc/.test(b)){b=b.replace(/calc\(/g,"(");var d={};b=b.replace(a,function(a){return d[a]=null,"U"+a});for(var e="U("+a.source+")",f=b.replace(/[-+]?(\d*\.)?\d+([Ee][-+]?\d+)?/g,"N").replace(new RegExp("N"+e,"g"),"D").replace(/\s[+-]\s/g,"O").replace(/\s/g,""),g=[/N\*(D)/g,/(N|D)[*\/]N/g,/(N|D)O\1/g,/\((N|D)\)/g],h=0;h<g.length;)g[h].test(f)?(f=f.replace(g[h],"$1"),h=0):h++;if("D"==f){for(var i in d){var j=c(b.replace(new RegExp("U"+i,"g"),"").replace(new RegExp(e,"g"),"*0"));if(!isFinite(j))return;d[i]=j}return d}}}function e(a,b){return f(a,b,!0)}function f(b,c,d){var e,f=[];for(e in b)f.push(e);for(e in c)f.indexOf(e)<0&&f.push(e);return b=f.map(function(a){return b[a]||0}),c=f.map(function(a){return c[a]||0}),[b,c,function(b){var c=b.map(function(c,e){return 1==b.length&&d&&(c=Math.max(c,0)),a.numberToString(c)+f[e]}).join(" + ");return b.length>1?"calc("+c+")":c}]}var g="px|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc",h=d.bind(null,new RegExp(g,"g")),i=d.bind(null,new RegExp(g+"|%","g")),j=d.bind(null,/deg|rad|grad|turn/g);a.parseLength=h,a.parseLengthOrPercent=i,a.consumeLengthOrPercent=a.consumeParenthesised.bind(null,i),a.parseAngle=j,a.mergeDimensions=f;var k=a.consumeParenthesised.bind(null,h),l=a.consumeRepeated.bind(void 0,k,/^/),m=a.consumeRepeated.bind(void 0,l,/^,/);a.consumeSizePairList=m;var n=function(a){var b=m(a);if(b&&""==b[1])return b[0]},o=a.mergeNestedRepeated.bind(void 0,e," "),p=a.mergeNestedRepeated.bind(void 0,o,",");a.mergeNonNegativeSizePair=o,a.addPropertiesHandler(n,p,["background-size"]),a.addPropertiesHandler(i,e,["border-bottom-width","border-image-width","border-left-width","border-right-width","border-top-width","flex-basis","font-size","height","line-height","max-height","max-width","outline-width","width"]),a.addPropertiesHandler(i,f,["border-bottom-left-radius","border-bottom-right-radius","border-top-left-radius","border-top-right-radius","bottom","left","letter-spacing","margin-bottom","margin-left","margin-right","margin-top","min-height","min-width","outline-offset","padding-bottom","padding-left","padding-right","padding-top","perspective","right","shape-margin","stroke-dashoffset","text-indent","top","vertical-align","word-spacing"])}(b),function(a,b){function c(b){return a.consumeLengthOrPercent(b)||a.consumeToken(/^auto/,b)}function d(b){var d=a.consumeList([a.ignore(a.consumeToken.bind(null,/^rect/)),a.ignore(a.consumeToken.bind(null,/^\(/)),a.consumeRepeated.bind(null,c,/^,/),a.ignore(a.consumeToken.bind(null,/^\)/))],b);if(d&&4==d[0].length)return d[0]}function e(b,c){return"auto"==b||"auto"==c?[!0,!1,function(d){var e=d?b:c;if("auto"==e)return"auto";var f=a.mergeDimensions(e,e);return f[2](f[0])}]:a.mergeDimensions(b,c)}function f(a){return"rect("+a+")"}var g=a.mergeWrappedNestedRepeated.bind(null,f,e,", ");a.parseBox=d,a.mergeBoxes=g,a.addPropertiesHandler(d,g,["clip"])}(b),function(a,b){function c(a){return function(b){var c=0;return a.map(function(a){return a===k?b[c++]:a})}}function d(a){return a}function e(b){if("none"==(b=b.toLowerCase().trim()))return[];for(var c,d=/\s*(\w+)\(([^)]*)\)/g,e=[],f=0;c=d.exec(b);){if(c.index!=f)return;f=c.index+c[0].length;var g=c[1],h=n[g];if(!h)return;var i=c[2].split(","),j=h[0];if(j.length<i.length)return;for(var k=[],o=0;o<j.length;o++){var p,q=i[o],r=j[o];if(void 0===(p=q?{A:function(b){return"0"==b.trim()?m:a.parseAngle(b)},N:a.parseNumber,T:a.parseLengthOrPercent,L:a.parseLength}[r.toUpperCase()](q):{a:m,n:k[0],t:l}[r]))return;k.push(p)}if(e.push({t:g,d:k}),d.lastIndex==b.length)return e}}function f(a){return a.toFixed(6).replace(".000000","")}function g(b,c){if(b.decompositionPair!==c){b.decompositionPair=c;var d=a.makeMatrixDecomposition(b)}if(c.decompositionPair!==b){c.decompositionPair=b;var e=a.makeMatrixDecomposition(c)}return null==d[0]||null==e[0]?[[!1],[!0],function(a){return a?c[0].d:b[0].d}]:(d[0].push(0),e[0].push(1),[d,e,function(b){var c=a.quat(d[0][3],e[0][3],b[5]);return a.composeMatrix(b[0],b[1],b[2],c,b[4]).map(f).join(",")}])}function h(a){return a.replace(/[xy]/,"")}function i(a){return a.replace(/(x|y|z|3d)?$/,"3d")}function j(b,c){var d=a.makeMatrixDecomposition&&!0,e=!1;if(!b.length||!c.length){b.length||(e=!0,b=c,c=[]);for(var f=0;f<b.length;f++){var j=b[f].t,k=b[f].d,l="scale"==j.substr(0,5)?1:0;c.push({t:j,d:k.map(function(a){if("number"==typeof a)return l;var b={};for(var c in a)b[c]=l;return b})})}}var m=function(a,b){return"perspective"==a&&"perspective"==b||("matrix"==a||"matrix3d"==a)&&("matrix"==b||"matrix3d"==b)},o=[],p=[],q=[];if(b.length!=c.length){if(!d)return;var r=g(b,c);o=[r[0]],p=[r[1]],q=[["matrix",[r[2]]]]}else for(var f=0;f<b.length;f++){var j,s=b[f].t,t=c[f].t,u=b[f].d,v=c[f].d,w=n[s],x=n[t];if(m(s,t)){if(!d)return;var r=g([b[f]],[c[f]]);o.push(r[0]),p.push(r[1]),q.push(["matrix",[r[2]]])}else{if(s==t)j=s;else if(w[2]&&x[2]&&h(s)==h(t))j=h(s),u=w[2](u),v=x[2](v);else{if(!w[1]||!x[1]||i(s)!=i(t)){if(!d)return;var r=g(b,c);o=[r[0]],p=[r[1]],q=[["matrix",[r[2]]]];break}j=i(s),u=w[1](u),v=x[1](v)}for(var y=[],z=[],A=[],B=0;B<u.length;B++){var C="number"==typeof u[B]?a.mergeNumbers:a.mergeDimensions,r=C(u[B],v[B]);y[B]=r[0],z[B]=r[1],A.push(r[2])}o.push(y),p.push(z),q.push([j,A])}}if(e){var D=o;o=p,p=D}return[o,p,function(a){return a.map(function(a,b){var c=a.map(function(a,c){return q[b][1][c](a)}).join(",");return"matrix"==q[b][0]&&16==c.split(",").length&&(q[b][0]="matrix3d"),q[b][0]+"("+c+")"}).join(" ")}]}var k=null,l={px:0},m={deg:0},n={matrix:["NNNNNN",[k,k,0,0,k,k,0,0,0,0,1,0,k,k,0,1],d],matrix3d:["NNNNNNNNNNNNNNNN",d],rotate:["A"],rotatex:["A"],rotatey:["A"],rotatez:["A"],rotate3d:["NNNA"],perspective:["L"],scale:["Nn",c([k,k,1]),d],scalex:["N",c([k,1,1]),c([k,1])],scaley:["N",c([1,k,1]),c([1,k])],scalez:["N",c([1,1,k])],scale3d:["NNN",d],skew:["Aa",null,d],skewx:["A",null,c([k,m])],skewy:["A",null,c([m,k])],translate:["Tt",c([k,k,l]),d],translatex:["T",c([k,l,l]),c([k,l])],translatey:["T",c([l,k,l]),c([l,k])],translatez:["L",c([l,l,k])],translate3d:["TTL",d]};a.addPropertiesHandler(e,j,["transform"]),a.transformToSvgMatrix=function(b){var c=a.transformListToMatrix(e(b));return"matrix("+f(c[0])+" "+f(c[1])+" "+f(c[4])+" "+f(c[5])+" "+f(c[12])+" "+f(c[13])+")"}}(b),function(a,b){function c(a,b){b.concat([a]).forEach(function(b){b in document.documentElement.style&&(d[a]=b),e[b]=a})}var d={},e={};c("transform",["webkitTransform","msTransform"]),c("transformOrigin",["webkitTransformOrigin"]),c("perspective",["webkitPerspective"]),c("perspectiveOrigin",["webkitPerspectiveOrigin"]),a.propertyName=function(a){return d[a]||a},a.unprefixedPropertyName=function(a){return e[a]||a}}(b)}(),function(){if(void 0===document.createElement("div").animate([]).oncancel){var a;if(window.performance&&performance.now)var a=function(){return performance.now()};else var a=function(){return Date.now()};var b=function(a,b,c){this.target=a,this.currentTime=b,this.timelineTime=c,this.type="cancel",this.bubbles=!1,this.cancelable=!1,this.currentTarget=a,this.defaultPrevented=!1,this.eventPhase=Event.AT_TARGET,this.timeStamp=Date.now()},c=window.Element.prototype.animate;window.Element.prototype.animate=function(d,e){var f=c.call(this,d,e);f._cancelHandlers=[],f.oncancel=null;var g=f.cancel;f.cancel=function(){g.call(this);var c=new b(this,null,a()),d=this._cancelHandlers.concat(this.oncancel?[this.oncancel]:[]);setTimeout(function(){d.forEach(function(a){a.call(c.target,c)})},0)};var h=f.addEventListener;f.addEventListener=function(a,b){"function"==typeof b&&"cancel"==a?this._cancelHandlers.push(b):h.call(this,a,b)};var i=f.removeEventListener;return f.removeEventListener=function(a,b){if("cancel"==a){var c=this._cancelHandlers.indexOf(b);c>=0&&this._cancelHandlers.splice(c,1)}else i.call(this,a,b)},f}}}(),function(a){var b=document.documentElement,c=null,d=!1;try{var e=getComputedStyle(b).getPropertyValue("opacity"),f="0"==e?"1":"0";c=b.animate({opacity:[f,f]},{duration:1}),c.currentTime=0,d=getComputedStyle(b).getPropertyValue("opacity")==f}catch(a){}finally{c&&c.cancel()}if(!d){var g=window.Element.prototype.animate;window.Element.prototype.animate=function(b,c){return window.Symbol&&Symbol.iterator&&Array.prototype.from&&b[Symbol.iterator]&&(b=Array.from(b)),Array.isArray(b)||null===b||(b=a.convertToArrayForm(b)),g.call(this,b,c)}}}(a),function(a,b,c){function d(a){var c=b.timeline;c.currentTime=a,c._discardAnimations(),0==c._animations.length?f=!1:requestAnimationFrame(d)}var e=window.requestAnimationFrame;window.requestAnimationFrame=function(a){return e(function(c){b.timeline._updateAnimationsPromises(),a(c),b.timeline._updateAnimationsPromises()})},b.AnimationTimeline=function(){this._animations=[],this.currentTime=void 0},b.AnimationTimeline.prototype={getAnimations:function(){return this._discardAnimations(),this._animations.slice()},_updateAnimationsPromises:function(){b.animationsWithPromises=b.animationsWithPromises.filter(function(a){return a._updatePromises()})},_discardAnimations:function(){this._updateAnimationsPromises(),this._animations=this._animations.filter(function(a){return"finished"!=a.playState&&"idle"!=a.playState})},_play:function(a){var c=new b.Animation(a,this);return this._animations.push(c),b.restartWebAnimationsNextTick(),c._updatePromises(),c._animation.play(),c._updatePromises(),c},play:function(a){return a&&a.remove(),this._play(a)}};var f=!1;b.restartWebAnimationsNextTick=function(){f||(f=!0,requestAnimationFrame(d))};var g=new b.AnimationTimeline;b.timeline=g;try{Object.defineProperty(window.document,"timeline",{configurable:!0,get:function(){return g}})}catch(a){}try{window.document.timeline=g}catch(a){}}(0,c),function(a,b,c){b.animationsWithPromises=[],b.Animation=function(b,c){if(this.id="",b&&b._id&&(this.id=b._id),this.effect=b,b&&(b._animation=this),!c)throw new Error("Animation with null timeline is not supported");this._timeline=c,this._sequenceNumber=a.sequenceNumber++,this._holdTime=0,this._paused=!1,this._isGroup=!1,this._animation=null,this._childAnimations=[],this._callback=null,this._oldPlayState="idle",this._rebuildUnderlyingAnimation(),this._animation.cancel(),this._updatePromises()},b.Animation.prototype={_updatePromises:function(){var a=this._oldPlayState,b=this.playState;return this._readyPromise&&b!==a&&("idle"==b?(this._rejectReadyPromise(),this._readyPromise=void 0):"pending"==a?this._resolveReadyPromise():"pending"==b&&(this._readyPromise=void 0)),this._finishedPromise&&b!==a&&("idle"==b?(this._rejectFinishedPromise(),this._finishedPromise=void 0):"finished"==b?this._resolveFinishedPromise():"finished"==a&&(this._finishedPromise=void 0)),this._oldPlayState=this.playState,this._readyPromise||this._finishedPromise},_rebuildUnderlyingAnimation:function(){this._updatePromises();var a,c,d,e,f=!!this._animation;f&&(a=this.playbackRate,c=this._paused,d=this.startTime,e=this.currentTime,this._animation.cancel(),this._animation._wrapper=null,this._animation=null),(!this.effect||this.effect instanceof window.KeyframeEffect)&&(this._animation=b.newUnderlyingAnimationForKeyframeEffect(this.effect),b.bindAnimationForKeyframeEffect(this)),(this.effect instanceof window.SequenceEffect||this.effect instanceof window.GroupEffect)&&(this._animation=b.newUnderlyingAnimationForGroup(this.effect),b.bindAnimationForGroup(this)),this.effect&&this.effect._onsample&&b.bindAnimationForCustomEffect(this),f&&(1!=a&&(this.playbackRate=a),null!==d?this.startTime=d:null!==e?this.currentTime=e:null!==this._holdTime&&(this.currentTime=this._holdTime),c&&this.pause()),this._updatePromises()},_updateChildren:function(){if(this.effect&&"idle"!=this.playState){var a=this.effect._timing.delay;this._childAnimations.forEach(function(c){this._arrangeChildren(c,a),this.effect instanceof window.SequenceEffect&&(a+=b.groupChildDuration(c.effect))}.bind(this))}},_setExternalAnimation:function(a){if(this.effect&&this._isGroup)for(var b=0;b<this.effect.children.length;b++)this.effect.children[b]._animation=a,this._childAnimations[b]._setExternalAnimation(a)},_constructChildAnimations:function(){if(this.effect&&this._isGroup){var a=this.effect._timing.delay;this._removeChildAnimations(),this.effect.children.forEach(function(c){var d=b.timeline._play(c);this._childAnimations.push(d),d.playbackRate=this.playbackRate,this._paused&&d.pause(),c._animation=this.effect._animation,this._arrangeChildren(d,a),this.effect instanceof window.SequenceEffect&&(a+=b.groupChildDuration(c))}.bind(this))}},_arrangeChildren:function(a,b){null===this.startTime?a.currentTime=this.currentTime-b/this.playbackRate:a.startTime!==this.startTime+b/this.playbackRate&&(a.startTime=this.startTime+b/this.playbackRate)},get timeline(){return this._timeline},get playState(){return this._animation?this._animation.playState:"idle"},get finished(){return window.Promise?(this._finishedPromise||(-1==b.animationsWithPromises.indexOf(this)&&b.animationsWithPromises.push(this),this._finishedPromise=new Promise(function(a,b){this._resolveFinishedPromise=function(){a(this)},this._rejectFinishedPromise=function(){b({type:DOMException.ABORT_ERR,name:"AbortError"})}}.bind(this)),"finished"==this.playState&&this._resolveFinishedPromise()),this._finishedPromise):(console.warn("Animation Promises require JavaScript Promise constructor"),null)},get ready(){return window.Promise?(this._readyPromise||(-1==b.animationsWithPromises.indexOf(this)&&b.animationsWithPromises.push(this),this._readyPromise=new Promise(function(a,b){this._resolveReadyPromise=function(){a(this)},this._rejectReadyPromise=function(){b({type:DOMException.ABORT_ERR,name:"AbortError"})}}.bind(this)),"pending"!==this.playState&&this._resolveReadyPromise()),this._readyPromise):(console.warn("Animation Promises require JavaScript Promise constructor"),null)},get onfinish(){return this._animation.onfinish},set onfinish(a){this._animation.onfinish="function"==typeof a?function(b){b.target=this,a.call(this,b)}.bind(this):a},get oncancel(){return this._animation.oncancel},set oncancel(a){this._animation.oncancel="function"==typeof a?function(b){b.target=this,a.call(this,b)}.bind(this):a},get currentTime(){this._updatePromises();var a=this._animation.currentTime;return this._updatePromises(),a},set currentTime(a){this._updatePromises(),this._animation.currentTime=isFinite(a)?a:Math.sign(a)*Number.MAX_VALUE,this._register(),this._forEachChild(function(b,c){b.currentTime=a-c}),this._updatePromises()},get startTime(){return this._animation.startTime},set startTime(a){this._updatePromises(),this._animation.startTime=isFinite(a)?a:Math.sign(a)*Number.MAX_VALUE,this._register(),this._forEachChild(function(b,c){b.startTime=a+c}),this._updatePromises()},get playbackRate(){return this._animation.playbackRate},set playbackRate(a){this._updatePromises();var b=this.currentTime;this._animation.playbackRate=a,this._forEachChild(function(b){b.playbackRate=a}),null!==b&&(this.currentTime=b),this._updatePromises()},play:function(){this._updatePromises(),this._paused=!1,this._animation.play(),-1==this._timeline._animations.indexOf(this)&&this._timeline._animations.push(this),this._register(),b.awaitStartTime(this),this._forEachChild(function(a){var b=a.currentTime;a.play(),a.currentTime=b}),this._updatePromises()},pause:function(){this._updatePromises(),this.currentTime&&(this._holdTime=this.currentTime),this._animation.pause(),this._register(),this._forEachChild(function(a){a.pause()}),this._paused=!0,this._updatePromises()},finish:function(){this._updatePromises(),this._animation.finish(),this._register(),this._updatePromises()},cancel:function(){this._updatePromises(),this._animation.cancel(),this._register(),this._removeChildAnimations(),this._updatePromises()},reverse:function(){this._updatePromises();var a=this.currentTime;this._animation.reverse(),this._forEachChild(function(a){a.reverse()}),null!==a&&(this.currentTime=a),this._updatePromises()},addEventListener:function(a,b){var c=b;"function"==typeof b&&(c=function(a){a.target=this,b.call(this,a)}.bind(this),b._wrapper=c),this._animation.addEventListener(a,c)},removeEventListener:function(a,b){this._animation.removeEventListener(a,b&&b._wrapper||b)},_removeChildAnimations:function(){for(;this._childAnimations.length;)this._childAnimations.pop().cancel()},_forEachChild:function(b){var c=0;if(this.effect.children&&this._childAnimations.length<this.effect.children.length&&this._constructChildAnimations(),this._childAnimations.forEach(function(a){b.call(this,a,c),this.effect instanceof window.SequenceEffect&&(c+=a.effect.activeDuration)}.bind(this)),"pending"!=this.playState){var d=this.effect._timing,e=this.currentTime;null!==e&&(e=a.calculateIterationProgress(a.calculateActiveDuration(d),e,d)),(null==e||isNaN(e))&&this._removeChildAnimations()}}},window.Animation=b.Animation}(a,c),function(a,b,c){function d(b){this._frames=a.normalizeKeyframes(b)}function e(){for(var a=!1;i.length;)i.shift()._updateChildren(),a=!0;return a}var f=function(a){if(a._animation=void 0,a instanceof window.SequenceEffect||a instanceof window.GroupEffect)for(var b=0;b<a.children.length;b++)f(a.children[b])};b.removeMulti=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c];d._parent?(-1==b.indexOf(d._parent)&&b.push(d._parent),d._parent.children.splice(d._parent.children.indexOf(d),1),d._parent=null,f(d)):d._animation&&d._animation.effect==d&&(d._animation.cancel(),d._animation.effect=new KeyframeEffect(null,[]),d._animation._callback&&(d._animation._callback._animation=null),d._animation._rebuildUnderlyingAnimation(),f(d))}for(c=0;c<b.length;c++)b[c]._rebuild()},b.KeyframeEffect=function(b,c,e,f){return this.target=b,this._parent=null,e=a.numericTimingToObject(e),this._timingInput=a.cloneTimingInput(e),this._timing=a.normalizeTimingInput(e),this.timing=a.makeTiming(e,!1,this),this.timing._effect=this,"function"==typeof c?(a.deprecated("Custom KeyframeEffect","2015-06-22","Use KeyframeEffect.onsample instead."),this._normalizedKeyframes=c):this._normalizedKeyframes=new d(c),this._keyframes=c,this.activeDuration=a.calculateActiveDuration(this._timing),this._id=f,this},b.KeyframeEffect.prototype={getFrames:function(){return"function"==typeof this._normalizedKeyframes?this._normalizedKeyframes:this._normalizedKeyframes._frames},set onsample(a){if("function"==typeof this.getFrames())throw new Error("Setting onsample on custom effect KeyframeEffect is not supported.");this._onsample=a,this._animation&&this._animation._rebuildUnderlyingAnimation()},get parent(){return this._parent},clone:function(){if("function"==typeof this.getFrames())throw new Error("Cloning custom effects is not supported.");var b=new KeyframeEffect(this.target,[],a.cloneTimingInput(this._timingInput),this._id);return b._normalizedKeyframes=this._normalizedKeyframes,b._keyframes=this._keyframes,b},remove:function(){b.removeMulti([this])}};var g=Element.prototype.animate;Element.prototype.animate=function(a,c){var d="";return c&&c.id&&(d=c.id),b.timeline._play(new b.KeyframeEffect(this,a,c,d))};var h=document.createElementNS("http://www.w3.org/1999/xhtml","div");b.newUnderlyingAnimationForKeyframeEffect=function(a){if(a){var b=a.target||h,c=a._keyframes;"function"==typeof c&&(c=[]);var d=a._timingInput;d.id=a._id}else var b=h,c=[],d=0;return g.apply(b,[c,d])},b.bindAnimationForKeyframeEffect=function(a){a.effect&&"function"==typeof a.effect._normalizedKeyframes&&b.bindAnimationForCustomEffect(a)};var i=[];b.awaitStartTime=function(a){null===a.startTime&&a._isGroup&&(0==i.length&&requestAnimationFrame(e),i.push(a))};var j=window.getComputedStyle;Object.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable:!0,value:function(){b.timeline._updateAnimationsPromises();var a=j.apply(this,arguments);return e()&&(a=j.apply(this,arguments)),b.timeline._updateAnimationsPromises(),a}}),window.KeyframeEffect=b.KeyframeEffect,window.Element.prototype.getAnimations=function(){return document.timeline.getAnimations().filter(function(a){return null!==a.effect&&a.effect.target==this}.bind(this))}}(a,c),function(a,b,c){function d(a){a._registered||(a._registered=!0,g.push(a),h||(h=!0,requestAnimationFrame(e)))}function e(a){var b=g;g=[],b.sort(function(a,b){return a._sequenceNumber-b._sequenceNumber}),b=b.filter(function(a){a();var b=a._animation?a._animation.playState:"idle";return"running"!=b&&"pending"!=b&&(a._registered=!1),a._registered}),g.push.apply(g,b),g.length?(h=!0,requestAnimationFrame(e)):h=!1}var f=(document.createElementNS("http://www.w3.org/1999/xhtml","div"),0);b.bindAnimationForCustomEffect=function(b){var c,e=b.effect.target,g="function"==typeof b.effect.getFrames();c=g?b.effect.getFrames():b.effect._onsample;var h=b.effect.timing,i=null;h=a.normalizeTimingInput(h);var j=function(){var d=j._animation?j._animation.currentTime:null;null!==d&&(d=a.calculateIterationProgress(a.calculateActiveDuration(h),d,h),isNaN(d)&&(d=null)),d!==i&&(g?c(d,e,b.effect):c(d,b.effect,b.effect._animation)),i=d};j._animation=b,j._registered=!1,j._sequenceNumber=f++,b._callback=j,d(j)};var g=[],h=!1;b.Animation.prototype._register=function(){this._callback&&d(this._callback)}}(a,c),function(a,b,c){function d(a){return a._timing.delay+a.activeDuration+a._timing.endDelay}function e(b,c,d){this._id=d,this._parent=null,this.children=b||[],this._reparent(this.children),c=a.numericTimingToObject(c),this._timingInput=a.cloneTimingInput(c),this._timing=a.normalizeTimingInput(c,!0),this.timing=a.makeTiming(c,!0,this),this.timing._effect=this,"auto"===this._timing.duration&&(this._timing.duration=this.activeDuration)}window.SequenceEffect=function(){e.apply(this,arguments)},window.GroupEffect=function(){e.apply(this,arguments)},e.prototype={_isAncestor:function(a){for(var b=this;null!==b;){if(b==a)return!0;b=b._parent}return!1},_rebuild:function(){for(var a=this;a;)"auto"===a.timing.duration&&(a._timing.duration=a.activeDuration),a=a._parent;this._animation&&this._animation._rebuildUnderlyingAnimation()},_reparent:function(a){b.removeMulti(a);for(var c=0;c<a.length;c++)a[c]._parent=this},_putChild:function(a,b){for(var c=b?"Cannot append an ancestor or self":"Cannot prepend an ancestor or self",d=0;d<a.length;d++)if(this._isAncestor(a[d]))throw{type:DOMException.HIERARCHY_REQUEST_ERR,name:"HierarchyRequestError",message:c};for(var d=0;d<a.length;d++)b?this.children.push(a[d]):this.children.unshift(a[d]);this._reparent(a),this._rebuild()},append:function(){this._putChild(arguments,!0)},prepend:function(){this._putChild(arguments,!1)},get parent(){return this._parent},get firstChild(){return this.children.length?this.children[0]:null},get lastChild(){return this.children.length?this.children[this.children.length-1]:null},clone:function(){for(var b=a.cloneTimingInput(this._timingInput),c=[],d=0;d<this.children.length;d++)c.push(this.children[d].clone());return this instanceof GroupEffect?new GroupEffect(c,b):new SequenceEffect(c,b)},remove:function(){b.removeMulti([this])}},window.SequenceEffect.prototype=Object.create(e.prototype),Object.defineProperty(window.SequenceEffect.prototype,"activeDuration",{get:function(){var a=0;return this.children.forEach(function(b){a+=d(b)}),Math.max(a,0)}}),window.GroupEffect.prototype=Object.create(e.prototype),Object.defineProperty(window.GroupEffect.prototype,"activeDuration",{get:function(){var a=0;return this.children.forEach(function(b){a=Math.max(a,d(b))}),a}}),b.newUnderlyingAnimationForGroup=function(c){var d,e=null,f=function(b){var c=d._wrapper;if(c&&"pending"!=c.playState&&c.effect)return null==b?void c._removeChildAnimations():0==b&&c.playbackRate<0&&(e||(e=a.normalizeTimingInput(c.effect.timing)),b=a.calculateIterationProgress(a.calculateActiveDuration(e),-1,e),isNaN(b)||null==b)?(c._forEachChild(function(a){a.currentTime=-1}),void c._removeChildAnimations()):void 0},g=new KeyframeEffect(null,[],c._timing,c._id);return g.onsample=f,d=b.timeline._play(g)},b.bindAnimationForGroup=function(a){a._animation._wrapper=a,a._isGroup=!0,b.awaitStartTime(a),a._constructChildAnimations(),a._setExternalAnimation(a)},b.groupChildDuration=d}(a,c)}();
//# sourceMappingURL=web-animations-next-lite.min.js.map</script>
<dom-module id="paper-dropdown-input" assetpath="bower_components/paper-dropdown-input/">
  <template>
    <style include="paper-dropdown-menu-shared-styles"></style>

    <style>
      :host([no-search]) [search-bar] {
        display: none;
      }
      :host ::content hr {
        border-top: 1px solid var(--disabled-text-color);
        border-bottom: none;
        opacity: .5;
      }
      .dropdown-content > paper-input {
        padding-left: 1em;
        --paper-input-container-input-color: black;
        --paper-input-container-underline: {
          display: none;
        };
        --paper-input-container-underline-focus: {
          display: none;
        };
        --paper-input-container: {
          padding: 0;
        };
      }
      paper-menu-button {
        --paper-input-container-input-color: var(--primary-text-color);
      }
      .dropdown-content > p {
        padding-left: 1em;
      }
      [search-bar] {
        display: flex;
        flex-direction: row;
        align-items: center;
      }
      [search-bar] iron-input {
        flex: 1;
        display: flex;
        flex-direction: row;
      }
      [search-bar] input {
        border: none;
        flex: 1;
        outline: none;
        font-size: 1em;
        min-width: 0;
        margin-bottom: 3px;
      }
      [search-bar] iron-icon {
        margin-left: 1em;
      }
      [warning] {
        background: #FFE0B2; /* paper-orange-100 */
        margin: 0;
        padding: .5em 1em;
      }
    </style>

    <span role="button"></span>
    <paper-menu-button id="menuButton" vertical-align="[[verticalAlign]]" horizontal-align="[[horizontalAlign]]" dynamic-align="[[dynamicAlign]]" vertical-offset="[[_computeMenuVerticalOffset(noLabelFloat)]]" disabled="[[_disabledOrReadonly(disabled, readonly)]]" no-animations="[[noAnimations]]" on-iron-select="_onIronSelect" on-iron-deselect="_onIronDeselect" opened="{{opened}}" close-on-activate="" no-overlap="" allow-outside-scroll="[[allowOutsideScroll]]" restore-focus-on-close="[[restoreFocusOnClose]]">
      <div class="dropdown-trigger" slot="dropdown-trigger">
        <paper-ripple></paper-ripple>
        <paper-input type="text" invalid="[[invalid]]" readonly="" disabled="[[disabled]]" name="[[name]]" value="[[selectedItemLabel]]" placeholder="[[placeholder]]" error-message="[[errorMessage]]" always-float-label="[[alwaysFloatLabel]]" no-label-float="[[noLabelFloat]]" label="[[label]]">
          <iron-icon icon="paper-dropdown-menu:arrow-drop-down" suffix="" slot="suffix"></iron-icon>
        </paper-input>
      </div>
      <paper-listbox class="dropdown-content" slot="dropdown-content" id="menu" selectable="[[selectable]]" on-tap="_checkSelectChange" stop-keyboard-event-propagation="">

        <div search-bar="" on-tap="_stopEvent" disabled="">
          <iron-icon icon="search"></iron-icon>
          <iron-input bind-value="{{searchValue}}">
            <input id="searchInput" value="{{value::input}}" is="iron-input" bind-value="{{searchValue}}">
          </iron-input>
          <paper-icon-button on-click="clearSearch" icon="clear" alt="clear" title="clear"></paper-icon-button>
        </div>

        <template is="dom-if" if="[[tooBig]]">
          <p warning="" disabled=""><iron-icon icon="warning"></iron-icon>Only showing the first [[maxSize]] items</p>
        </template>
        <template is="dom-if" if="[[!_filtereditems.length]]">
          <template is="dom-if" if="[[!_showFreeInput(freedom, searchValue)]]">
            <p warning="" disabled="">No results were found</p>
          </template>
          <template is="dom-if" if="[[_showFreeInput(freedom, searchValue)]]">
            <p warning="" disabled="">No results were found<br>would you like to use the search value?</p>
            <paper-item>[[searchValue]]</paper-item>
          </template>
        </template>
        <slot id="content"></slot>
      </paper-listbox>
    </paper-menu-button>




    <template id="menuTemplate">
      <template is="dom-repeat" items="[[items]]" as="item">
        <paper-item>[[item]]</paper-item>
      </template>
		</template>

  </template>

  <script>
    (function() {
      'use strict';

      Polymer({
        is: 'paper-dropdown-input',

        behaviors: [
          Polymer.IronButtonState,
          Polymer.IronControlState,
          Polymer.IronFormElementBehavior,
          Polymer.Templatizer,
          Polymer.IronValidatableBehavior
        ],

        properties: {
          /**
           * The derived "label" of the currently selected item. This value
           * is the `label` property on the selected item if set, or else the
           * trimmed text content of the selected item.
           */
          selectedItemLabel: {
            type: String,
            notify: true,
            readOnly: true
          },

          /**
           * The last selected item. An item is selected if the dropdown menu has
           * a child with slot `dropdown-content`, and that child triggers an
           * `iron-select` event with the selected `item` in the `detail`.
           *
           * @type {?Object}
           */
          selectedItem: {
            type: Object,
            notify: true,
            readOnly: true
          },

          /**
           * The value for this element that will be used when submitting in
           * a form. It is read only, and will always have the same value
           * as `selectedItemLabel`.
           */
          value: {
            type: String,
            notify: true,
            readOnly: true
          },

          /**
           * The label for the dropdown.
           */
          label: {
            type: String
          },

          /**
           * The placeholder for the dropdown.
           */
          placeholder: {
            type: String
          },

          /**
           * The error message to display when invalid.
           */
          errorMessage: {
              type: String
          },

          /**
           * True if the dropdown is open. Otherwise, false.
           */
          opened: {
            type: Boolean,
            notify: true,
            value: false,
            observer: '_openedChanged'
          },

          /**
           * By default, the dropdown will constrain scrolling on the page
           * to itself when opened.
           * Set to true in order to prevent scroll from being constrained
           * to the dropdown when it opens.
           */
          allowOutsideScroll: {
            type: Boolean,
            value: false
          },

          /**
           * Set to true to disable the floating label. Bind this to the
           * `<paper-input-container>`'s `noLabelFloat` property.
           */
          noLabelFloat: {
              type: Boolean,
              value: false,
              reflectToAttribute: true
          },

          /**
           * Set to true to always float the label. Bind this to the
           * `<paper-input-container>`'s `alwaysFloatLabel` property.
           */
          alwaysFloatLabel: {
            type: Boolean,
            value: false
          },

          /**
           * Set to true to disable animations when opening and closing the
           * dropdown.
           */
          noAnimations: {
            type: Boolean,
            value: false
          },

          /**
           * The orientation against which to align the menu dropdown
           * horizontally relative to the dropdown trigger.
           */
          horizontalAlign: {
            type: String,
            value: 'left'
          },

          /**
           * The orientation against which to align the menu dropdown
           * vertically relative to the dropdown trigger.
           */
          verticalAlign: {
            type: String,
            value: 'top'
          },

          /**
           * If true, the `horizontalAlign` and `verticalAlign` properties will
           * be considered preferences instead of strict requirements when
           * positioning the dropdown and may be changed if doing so reduces
           * the area of the dropdown falling outside of `fitInto`.
           */
          dynamicAlign: {
            type: Boolean
          },

          /**
           * Whether focus should be restored to the dropdown when the menu closes.
           */
          restoreFocusOnClose: {
            type: Boolean,
            value: true
          },

          /**
           * The maximum amount of items the dropdown will render
           * User will be asked to enter a more specific query if this
           * threshold is exceeded
           */
          maxSize: {
            type: Number,
            value: 50
          },
          /**
           * Set to true if the maxSize threshold is exceeded
           */
          tooBig: {
            type: Boolean,
            notify: true,
            readOnly: true
          },

          /**
           * If set, the user can choose to use the entered search query
           * as the value.
           * This makes the element behave more like an autocompletion element.
           */
          freedom: {
            type: Boolean,
            value: false
          },

          /**
           * The items that this element will filter on, and present to the
           * user if it matches the user's search query
           */
          items: {
            type: Array,
            value: function() {return []}
          },

          /**
           * The current search query entered by the user
           */
          searchValue: {
            type: String,
            notify: true,
            value: ""
          },

          /**
           * The property name that items will have that paper-dropdown-input
           * can filter on
           */
          filterProperty: {
            type: String,
            value: "value"
          },

          /**
           * The filter function, executed each time 'items' or 'searchValue' changes
           * The default function expects an array of strings or an array of
           * objects containing the 'value' property
           */
          filter: {
            type: Function,
            value: function() {
              return function(items, searchValue, filterProperty) {
                // older version of filter did not have filterProperty as an argument
                // fallback for backwards compatibility
                if (!filterProperty) {
                  filterProperty = this.filterProperty;
                }
                if (!searchValue) {
                  return items;
                } else {
                  var _searchValue = searchValue.toLowerCase();
                  return items.filter( function(item) {
                    if (!item[filterProperty] && typeof item != "string") {
                      console.error("paper-dropdown-input: item in `items`:", item, " is not a string or does not contain `" + filterProperty + "` property");
                      return true; // everything goes through
                    } else {
                      return (item[filterProperty] || item).toLowerCase().indexOf(_searchValue) > -1;
                    }
                  });
                }
              }
            }
          },

          /**
           * The remaining items after filtering.
           * These are shown to the user in the dropdown.
           * This list is truncated if 'maxSize' is exceeded
           */
          _filtereditems: {
            type: Array,
            computed: "_filterItems(items, searchValue, filterProperty)"
          },

          /**
           * Makes the element read-only. The dropdown will not open.
           */
          readonly: {
            type: Boolean,
            value: false,
            reflectToAttribute: true
          },

          /**
           * passed to paper-listbox.
           * https://www.webcomponents.org/element/PolymerElements/paper-listbox/paper-listbox#property-selectable
           */
          selectable: {
            type: String
          },

          /**
           * disables search, making it more like a regular dropdown.
           */
          noSearch: {
            type: Boolean,
            value: false,
            reflectToAttribute: true
          }
        },

        listeners: {
          'tap': '_onTap',
          'dom-change': '_refit'
          // 'neon-animation-finish': '_focusSearch'
        },

        keyBindings: {
          'up down': 'open',
          'esc': 'close'
        },

        hostAttributes: {
          role: 'combobox',
          'aria-autocomplete': 'none',
          'aria-haspopup': 'true'
        },

        observers: [
          '_selectedItemChanged(selectedItem)',
          '_updateTemplate(_filtereditems)',
          '_updateSelectedItem(items.length, selectedItemLabel, filterProperty)',
          '_updateSelectedItemLabel(items, value, filterProperty)'
        ],

        attached: function() {
          // NOTE(cdata): Due to timing, a preselected value in a `IronSelectable`
          // child will cause an `iron-select` event to fire while the element is
          // still in a `DocumentFragment`. This has the effect of causing
          // handlers not to fire. So, we double check this value on attached:
          var contentElement = this.contentElement;
          if (contentElement && contentElement.selectedItem) {
            this._setSelectedItem(contentElement.selectedItem);
          }
          if (!this._isSetupTemplate){
            this._isSetupTemplate = true;
            this._setupTemplate();
          }
        },

        /**
         * The content element that is contained by the dropdown menu, if any.
         */
        get contentElement() {
          return this.$.menu;
        },

        /**
         * Show the dropdown content.
         */
        open: function() {
          this.$.menuButton.open();
        },

        /**
         * Hide the dropdown content.
         */
        close: function() {
          this.$.menuButton.close();
        },

        /**
         * A handler that is called when `iron-select` is fired.
         *
         * @param {CustomEvent} event An `iron-select` event.
         */
        _onIronSelect: function(event) {
          this._setSelectedItem(event.detail.item);
        },

        /**
         * A handler that is called when `iron-deselect` is fired.
         *
         * @param {CustomEvent} event An `iron-deselect` event.
         */
        _onIronDeselect: function(event) {
          this._setSelectedItem(null);
        },

        /**
         * A handler that is called when the dropdown is tapped.
         *
         * @param {CustomEvent} event A tap event.
         */
        _onTap: function(event) {
          if (Polymer.Gestures.findOriginalTarget(event) === this) {
            this.open();
          }
        },

        /**
         * Compute the label for the dropdown given a selected item.
         *
         * @param {Element} selectedItem A selected Element item, with an
         * optional `label` property.
         */
        _selectedItemChanged: function(selectedItem) {
          if (this.opened) { // user is searching
            return;
          }
          var value = '';
          var displayValue = '';
          if (!selectedItem) {
            value = '';
          } else {
            displayValue = selectedItem.textContent.trim() || selectedItem.label;
            value = selectedItem.label || selectedItem.getAttribute('label') || selectedItem.textContent.trim();
          }
          if (!value && this.selectedItemLabel) { // selected item re-appeared in _filteredItems
            return;
          }
          this._setValue(value);
          // this._setSelectedItemLabel(value);
          this._setSelectedItemLabel(displayValue);
        },

        /**
         * Compute the vertical offset of the menu based on the value of
         * `noLabelFloat`.
         *
         * @param {boolean} noLabelFloat True if the label should not float
         * above the input, otherwise false.
         */
        _computeMenuVerticalOffset: function(noLabelFloat) {
          // NOTE(cdata): These numbers are somewhat magical because they are
          // derived from the metrics of elements internal to `paper-input`'s
          // template. The metrics will change depending on whether or not the
          // input has a floating label.
          return noLabelFloat ? -4 : 8;
        },

        /**
         * Returns false if the element is required and does not have a selection,
         * and true otherwise.
         * @param {*=} _value Ignored.
         * @return {boolean} true if `required` is false, or if `required` is true
         * and the element has a valid selection.
         */
        _getValidity: function(_value) {
          return this.disabled || !this.required || (this.required && !!this.value);
        },

        _openedChanged: function() {
          var openState = this.opened ? 'true' : 'false';
          var e = this.contentElement;
          if (e) {
            e.setAttribute('aria-expanded', openState);
          }
          if (openState) {
            setTimeout(this._focusSearch.bind(this), 100);
          }
        },




        _focusSearch: function() {
          this.$.searchInput.focus();
          this.$.searchInput.select();
        },

        /**
         * Sets up the template
         */
        _setupTemplate: function() {
          // when the user clears the search field, the selectedItem is null
          // this causes an iron-select event causing the dropdown to close
          this.$.menuButton._onIronSelect = function(event) {
            var value = this.selectedItem && (this.selectedItem.label || this.selectedItem.getAttribute('label') || this.selectedItem.textContent.trim());
            if (!value && this.selectedItemLabel) {
              return;
            }
            if (!this.ignoreSelect) {
              this.$.menuButton.close();
            }
          }.bind(this);

          // normally a user can select an option by typign the first letter
          // this clashes with the search field
          this.$.menu._focusWithKeyboardEvent = function() {};

          var template = Polymer.dom(this).querySelector('template') || this.$.menuTemplate;
          this.templatize(template);
          this._templateInstance = this.stamp({
            items: this._filtereditems
          });
          var dom = Polymer.dom(this);
          dom.appendChild(this._templateInstance.root);
          // dom.insertBefore(this._templateInstance.root, dom.firstChild);
        },

        /**
         * Updates the stamped template's data
         *
         * @param {Array.<Object>} filtereditems the new list to display in the dropdown
         */
        _updateTemplate: function(filtereditems) {
          if (this._templateInstance) {
            this._templateInstance.items = filtereditems;
          }
        },

        _refit: function() {
          if (this.opened) {
            this.$.menuButton.$.dropdown.refit();
          }
        },

        /**
         * Returns a filtered version of items, based on if the array object matched 'searchValue'
         *
         * @param {Array.<Object>} items the array of items to filter
         * @param {String} searchValue value to filter with
         * @return {Array.<Object>} the filtered array
         */
        _filterItems: function(items, searchValue, filterProperty) {
          var result = this.filter ? this.filter(items, searchValue, filterProperty) : items;
          if (this.maxSize > 0) {
            this._setTooBig(result.length > this.maxSize);
            return result.slice(0,this.maxSize);
          }
          else {
            this._setTooBig(false);
            return result;
          }
        },

        _stopEvent: function(event) {
          event.stopPropagation();
        },

        /**
         * Clears 'searchValue' and focuses the search input
         */
        clearSearch: function(event) {
          event && this._stopEvent(event);
          this.searchValue = "";
          this._focusSearch();
        },

        _updateSelectedItem: function(itemsLength, selectedItemLabel, filterProperty) {
          var displayValue = this.selectedItem && (this.selectedItem.textContent.trim() || this.selectedItem.label);
          var value = this.selectedItem && (this.selectedItem.label || this.selectedItem.getAttribute('label') || displayValue);
          if (selectedItemLabel && value != selectedItemLabel) {
            var index = this.$.menu.items.findIndex( function(item) {
              return item.label == selectedItemLabel ||
                     item[filterProperty] == selectedItemLabel ||
                     item.value == selectedItemLabel ||
                     item == selectedItemLabel;
            } );
            index > -1 && this.$.menu.select(index);
          }
        },

        _updateSelectedItemLabel: function(items, value, filterProperty) {
          if (value === undefined) {
            return;
          }
          if (value === null || value == "") {
            this._setSelectedItemLabel(null);
            this._setSelectedItem(null);
            this.$.menu.selected = null;
            return;
          }
          var selectedItem = items.find( function(item) {
            return item[filterProperty] == value || item.label == value || item == value
          });
          if (selectedItem) {
            var displayValue = selectedItem[filterProperty] || selectedItem.label || selectedItem;
            this._setSelectedItemLabel(displayValue);
          }
          else {
            this.$.menu.selected = null;
            if (this.freedom) {
              this._setSelectedItemLabel(value);
            }
            else {
              this._setSelectedItemLabel(null);
            }
          }
        },

        /*
         * selectedItem can get screwed up.
         * say you selected the first item in the list, and you start searching.
         * You click the first item in the filtered list, the change will not
         * get picked up. This fixed this edge case.
         */
        _checkSelectChange: function(event) {
          // execute ~after~ the normal update flow
          // otherwise we would always detect the edge case
          setTimeout(function () {
            if (!this.selectedItemLabel) { return }

            // 'Polymer.dom(event.detail.sourceEvent).localTarget' is erroring out with Firefox
            // when running in a full Polymer 2.x context
            var selectedItem;
            if (event.composedPath && event.composedPath()[0]) {
               selectedItem = event.composedPath()[0];
            }
            else {
               selectedItem = Polymer.dom(event.detail.sourceEvent).localTarget;
            }

            var selectedItemLabel = selectedItem.textContent.trim() || selectedItem.label;

            if (selectedItemLabel !== this.selectedItemLabel) {
              this.opened = false;
              this._selectedItemChanged(this.selectedItem);
            }
          }.bind(this), 0);
        },

        _disabledOrReadonly: function(disabled, readonly) {
          return disabled || readonly;
        },

        _showFreeInput: function(freedom, searchValue) {
          return freedom && searchValue != "";
        }
      });
    })();
  </script>
</dom-module>
<script>
  /**
   * `iron-range-behavior` provides the behavior for something with a minimum to
   * maximum range.
   *
   * @demo demo/index.html
   * @polymerBehavior
   */
  Polymer.IronRangeBehavior = {

    properties: {

      /**
       * The number that represents the current value.
       */
      value: {type: Number, value: 0, notify: true, reflectToAttribute: true},

      /**
       * The number that indicates the minimum value of the range.
       */
      min: {type: Number, value: 0, notify: true},

      /**
       * The number that indicates the maximum value of the range.
       */
      max: {type: Number, value: 100, notify: true},

      /**
       * Specifies the value granularity of the range's value.
       */
      step: {type: Number, value: 1, notify: true},

      /**
       * Returns the ratio of the value.
       */
      ratio: {type: Number, value: 0, readOnly: true, notify: true},
    },

    observers: ['_update(value, min, max, step)'],

    _calcRatio: function(value) {
      return (this._clampValue(value) - this.min) / (this.max - this.min);
    },

    _clampValue: function(value) {
      return Math.min(this.max, Math.max(this.min, this._calcStep(value)));
    },

    _calcStep: function(value) {
      // polymer/issues/2493
      value = parseFloat(value);

      if (!this.step) {
        return value;
      }

      var numSteps = Math.round((value - this.min) / this.step);
      if (this.step < 1) {
        /**
         * For small values of this.step, if we calculate the step using
         * `Math.round(value / step) * step` we may hit a precision point issue
         * eg. 0.1 * 0.2 =  0.020000000000000004
         * http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
         *
         * as a work around we can divide by the reciprocal of `step`
         */
        return numSteps / (1 / this.step) + this.min;
      } else {
        return numSteps * this.step + this.min;
      }
    },

    _validateValue: function() {
      var v = this._clampValue(this.value);
      this.value = this.oldValue = isNaN(v) ? this.oldValue : v;
      return this.value !== v;
    },

    _update: function() {
      this._validateValue();
      this._setRatio(this._calcRatio(this.value) * 100);
    }

  };
</script>
<dom-module id="paper-progress" assetpath="bower_components/paper-progress/">
  <template>
    <style>
      :host {
        display: block;
        width: 200px;
        position: relative;
        overflow: hidden;
      }

      :host([hidden]), [hidden] {
        display: none !important;
      }

      #progressContainer {
        @apply --paper-progress-container;
        position: relative;
      }

      #progressContainer,
      /* the stripe for the indeterminate animation*/
      .indeterminate::after {
        height: var(--paper-progress-height, 4px);
      }

      #primaryProgress,
      #secondaryProgress,
      .indeterminate::after {
        @apply --layout-fit;
      }

      #progressContainer,
      .indeterminate::after {
        background: var(--paper-progress-container-color, var(--google-grey-300));
      }

      :host(.transiting) #primaryProgress,
      :host(.transiting) #secondaryProgress {
        -webkit-transition-property: -webkit-transform;
        transition-property: transform;

        /* Duration */
        -webkit-transition-duration: var(--paper-progress-transition-duration, 0.08s);
        transition-duration: var(--paper-progress-transition-duration, 0.08s);

        /* Timing function */
        -webkit-transition-timing-function: var(--paper-progress-transition-timing-function, ease);
        transition-timing-function: var(--paper-progress-transition-timing-function, ease);

        /* Delay */
        -webkit-transition-delay: var(--paper-progress-transition-delay, 0s);
        transition-delay: var(--paper-progress-transition-delay, 0s);
      }

      #primaryProgress,
      #secondaryProgress {
        @apply --layout-fit;
        -webkit-transform-origin: left center;
        transform-origin: left center;
        -webkit-transform: scaleX(0);
        transform: scaleX(0);
        will-change: transform;
      }

      #primaryProgress {
        background: var(--paper-progress-active-color, var(--google-green-500));
      }

      #secondaryProgress {
        background: var(--paper-progress-secondary-color, var(--google-green-100));
      }

      :host([disabled]) #primaryProgress {
        background: var(--paper-progress-disabled-active-color, var(--google-grey-500));
      }

      :host([disabled]) #secondaryProgress {
        background: var(--paper-progress-disabled-secondary-color, var(--google-grey-300));
      }

      :host(:not([disabled])) #primaryProgress.indeterminate {
        -webkit-transform-origin: right center;
        transform-origin: right center;
        -webkit-animation: indeterminate-bar var(--paper-progress-indeterminate-cycle-duration, 2s) linear infinite;
        animation: indeterminate-bar var(--paper-progress-indeterminate-cycle-duration, 2s) linear infinite;
      }

      :host(:not([disabled])) #primaryProgress.indeterminate::after {
        content: "";
        -webkit-transform-origin: center center;
        transform-origin: center center;

        -webkit-animation: indeterminate-splitter var(--paper-progress-indeterminate-cycle-duration, 2s) linear infinite;
        animation: indeterminate-splitter var(--paper-progress-indeterminate-cycle-duration, 2s) linear infinite;
      }

      @-webkit-keyframes indeterminate-bar {
        0% {
          -webkit-transform: scaleX(1) translateX(-100%);
        }
        50% {
          -webkit-transform: scaleX(1) translateX(0%);
        }
        75% {
          -webkit-transform: scaleX(1) translateX(0%);
          -webkit-animation-timing-function: cubic-bezier(.28,.62,.37,.91);
        }
        100% {
          -webkit-transform: scaleX(0) translateX(0%);
        }
      }

      @-webkit-keyframes indeterminate-splitter {
        0% {
          -webkit-transform: scaleX(.75) translateX(-125%);
        }
        30% {
          -webkit-transform: scaleX(.75) translateX(-125%);
          -webkit-animation-timing-function: cubic-bezier(.42,0,.6,.8);
        }
        90% {
          -webkit-transform: scaleX(.75) translateX(125%);
        }
        100% {
          -webkit-transform: scaleX(.75) translateX(125%);
        }
      }

      @keyframes indeterminate-bar {
        0% {
          transform: scaleX(1) translateX(-100%);
        }
        50% {
          transform: scaleX(1) translateX(0%);
        }
        75% {
          transform: scaleX(1) translateX(0%);
          animation-timing-function: cubic-bezier(.28,.62,.37,.91);
        }
        100% {
          transform: scaleX(0) translateX(0%);
        }
      }

      @keyframes indeterminate-splitter {
        0% {
          transform: scaleX(.75) translateX(-125%);
        }
        30% {
          transform: scaleX(.75) translateX(-125%);
          animation-timing-function: cubic-bezier(.42,0,.6,.8);
        }
        90% {
          transform: scaleX(.75) translateX(125%);
        }
        100% {
          transform: scaleX(.75) translateX(125%);
        }
      }
    </style>

    <div id="progressContainer">
      <div id="secondaryProgress" hidden$="[[_hideSecondaryProgress(secondaryRatio)]]"></div>
      <div id="primaryProgress"></div>
    </div>
  </template>
</dom-module>

<script>
  Polymer({
    is: 'paper-progress',

    behaviors: [Polymer.IronRangeBehavior],

    properties: {
      /**
       * The number that represents the current secondary progress.
       */
      secondaryProgress: {type: Number, value: 0},

      /**
       * The secondary ratio
       */
      secondaryRatio: {type: Number, value: 0, readOnly: true},

      /**
       * Use an indeterminate progress indicator.
       */
      indeterminate:
          {type: Boolean, value: false, observer: '_toggleIndeterminate'},

      /**
       * True if the progress is disabled.
       */
      disabled: {
        type: Boolean,
        value: false,
        reflectToAttribute: true,
        observer: '_disabledChanged'
      }
    },

    observers:
        ['_progressChanged(secondaryProgress, value, min, max, indeterminate)'],

    hostAttributes: {role: 'progressbar'},

    _toggleIndeterminate: function(indeterminate) {
      // If we use attribute/class binding, the animation sometimes doesn't
      // translate properly on Safari 7.1. So instead, we toggle the class here in
      // the update method.
      this.toggleClass('indeterminate', indeterminate, this.$.primaryProgress);
    },

    _transformProgress: function(progress, ratio) {
      var transform = 'scaleX(' + (ratio / 100) + ')';
      progress.style.transform = progress.style.webkitTransform = transform;
    },

    _mainRatioChanged: function(ratio) {
      this._transformProgress(this.$.primaryProgress, ratio);
    },

    _progressChanged: function(
        secondaryProgress, value, min, max, indeterminate) {
      secondaryProgress = this._clampValue(secondaryProgress);
      value = this._clampValue(value);

      var secondaryRatio = this._calcRatio(secondaryProgress) * 100;
      var mainRatio = this._calcRatio(value) * 100;

      this._setSecondaryRatio(secondaryRatio);
      this._transformProgress(this.$.secondaryProgress, secondaryRatio);
      this._transformProgress(this.$.primaryProgress, mainRatio);

      this.secondaryProgress = secondaryProgress;

      if (indeterminate) {
        this.removeAttribute('aria-valuenow');
      } else {
        this.setAttribute('aria-valuenow', value);
      }
      this.setAttribute('aria-valuemin', min);
      this.setAttribute('aria-valuemax', max);
    },

    _disabledChanged: function(disabled) {
      this.setAttribute('aria-disabled', disabled ? 'true' : 'false');
    },

    _hideSecondaryProgress: function(secondaryRatio) {
      return secondaryRatio === 0;
    }
  });
</script>
<dom-module id="paper-range-slider" assetpath="bower_components/paper-range-slider/">
    <template>
        <style>
            /* local styles go here */
            :host {
              @apply --layout;
              @apply --layout-justified;
              @apply --layout-center;

              --paper-range-slider-width: 200px;

              --paper-range-slider-lower-color:             var(--paper-grey-400);
              --paper-range-slider-active-color:            var(--primary-color);
              --paper-range-slider-higher-color:            var(--paper-grey-400);
              --paper-range-slider-knob-color:              var(--primary-color);
              --paper-range-slider-pin-color:               var(--primary-color);
              --paper-range-slider-pin-start-color:         var(--paper-grey-400);
              --paper-range-slider-knob-start-color:        transparent;
              --paper-range-slider-knob-start-border-color: var(--paper-grey-400);
            }

            #sliderOuterDiv_0 {
              display: inline-block;
              width: var(--paper-range-slider-width);
            }

            #sliderOuterDiv_1 {
              position: relative;
              height: calc(30px + var(--paper-single-range-slider-height, 2px));
              margin-left: 0;
              margin-right: 0;
              margin-top: 0;
              margin-bottom: 0;
            }

            /* mimic the size of the #sliderKnob of paper-single-range-slider */
            .sliderKnobMinMax {
              position: absolute;
              left: 0;
              top: 0;
              margin-left: calc(-15px - var(--paper-single-range-slider-height, 2px)/2);
              width: calc(30px + var(--paper-single-range-slider-height, 2px));
              height: calc(30px + var(--paper-single-range-slider-height, 2px));
              /*background: #2196F3; opacity: 0.3;*/
            }

            .divSpanWidth {
                position:absolute;
                width:100%;
                display:block;
                top:0px;
            }

            #sliderMax {
                line-height: normal;
                --paper-single-range-slider-bar-color:               transparent;
                --paper-single-range-slider-knob-color:              var(--paper-range-slider-knob-color);
                --paper-single-range-slider-pin-color:               var(--paper-range-slider-pin-color);
                --paper-single-range-slider-active-color:            var(--paper-range-slider-active-color);
                --paper-single-range-slider-secondary-color:         var(--paper-range-slider-higher-color);
                --paper-single-range-slider-pin-start-color:         var(--paper-range-slider-pin-start-color);
                --paper-single-range-slider-knob-start-color:        var(--paper-range-slider-knob-start-color);
                --paper-single-range-slider-knob-start-border-color: var(--paper-range-slider-knob-start-border-color);
            }
            #sliderMin {
                line-height: normal;
                --paper-single-range-slider-active-color:            var(--paper-range-slider-lower-color);
                --paper-single-range-slider-secondary-color:         transparent;
                --paper-single-range-slider-knob-color:              var(--paper-range-slider-knob-color);
                --paper-single-range-slider-pin-color:               var(--paper-range-slider-pin-color);
                --paper-single-range-slider-pin-start-color:         var(--paper-range-slider-pin-start-color);
                --paper-single-range-slider-knob-start-color:        var(--paper-range-slider-knob-start-color);
                --paper-single-range-slider-knob-start-border-color: var(--paper-range-slider-knob-start-border-color);
            }
        </style>

        <div id="sliderOuterDiv_0" style="[[mainDivStyle]]">
            <div id="sliderOuterDiv_1">
              <div id="backDiv" class="divSpanWidth" on-down="_backDivDown" on-tap="_backDivTap" on-up="_backDivUp" on-track="_backDivOnTrack" on-transitionend="_backDivTransEnd">
                  <div id="backDivInner_0" style="line-height:200%;"><br></div>
              </div>

              <div class="divSpanWidth" style="pointer-events: none;">
                <paper-single-range-slider id="sliderMax" display-function="[[displayFunction]]" disabled$="[[disabled]]" on-down="_sliderMaxDown" on-up="_sliderMaxUp" step="[[step]]" min="[[min]]" max="[[max]]" value="[[valueMax]]" secondary-progress="[[max]]" style="width:100%;">
                </paper-single-range-slider>
              </div>

              <div class="divSpanWidth" style="pointer-events: none;">
                <paper-single-range-slider id="sliderMin" display-function="[[displayFunction]]" disabled$="[[disabled]]" on-down="_sliderMinDown" on-up="_sliderMinUp" noink="" step="[[step]]" min="[[min]]" max="[[max]]" value="[[valueMin]]" style="width:100%;">
                </paper-single-range-slider>
              </div>

              <div id="backDivInner_1" style="line-height:100%;"><br></div>
            </div>
        </div>
    </template>

    <script>
        class PaperRangeSlider extends Polymer.GestureEventListeners(Polymer.Element) {
            static get is() { return 'paper-range-slider' }

            // -----------------------------------------------------------------------------------------------------------
            // properties
            // -----------------------------------------------------------------------------------------------------------
            static get properties() {
                return  {
                  /**
                   * Used to customize the displayed value of the pin. E.g. the value can be prefixed with a '$' like '$99'
                   */
                  displayFunction: {
                    type: Function,
                    value: function () {
                      return function (value) {
                        return value;
                      }
                    }
                  },

                  /**
                   * the width of the element in pixels.
                   */
                  sliderWidth: {
                      type: String,
                      value: "",
                      notify: true,
                      reflectToAttribute: true
                  },

                  /**
                   * the style attribute of the element.
                   */
                  mainDivStyle: {
                      type: Object,
                      value: function() {return {}},
                      notify: true,
                      reflectToAttribute: true
                  },

                  /**
                   * the minimal value (lower range) of the slider.
                   */
                  min: {
                      type: Number,
                      value: 0,
                      notify: true,
                      reflectToAttribute: true
                  },

                  /**
                   * the maximal value (upper range) of the slider.
                   */
                  max: {
                      type: Number,
                      value: 100,
                      notify: true,
                      reflectToAttribute: true
                  },

                  /**
                   * the current value of the lower range of the slider.
                   */
                  valueMin: {
                      type: Number,
                      value: 0,
                      notify: false,
                      reflectToAttribute: true
                  },

                  /**
                   * the current value of the upper range of the slider.
                   */
                  valueMax: {
                      type: Number,
                      value: 100,
                      notify: false,
                      reflectToAttribute: true
                  },

                  /**
                   * the minimal step-change of a knob on the slider
                   */
                  step: {
                      type: Number,
                      value: 1,
                      notify: true,
                      reflectToAttribute: true
                  },

                  /**
                   * optional minimal value for the difference between valueMin and valueMax
                   * by default this is negative (valueDiffMin is ignored)
                   */
                  valueDiffMin: {
                      type: Number,
                      value: 0,
                      notify: true,
                      reflectToAttribute: true
                  },

                  /**
                   * optional maximal value for the difference between valueMin and valueMax
                   * by default this is negative (valueDiffMax is ignored)
                   */
                  valueDiffMax: {
                      type: Number,
                      value: 0,
                      notify: true,
                      reflectToAttribute: true
                  },

                  /**
                   * if true, pins with numeric value label are shown when the slider thumb
                   * is pressed. Use for settings for which users need to know the exact
                   * value of the setting.
                   */
                  alwaysShowPin: {
                      type: Boolean,
                      value: false,
                      notify: true
                  },

                  /**
                   * if true, pins with numeric value label are shown when the slider thumb
                   * is pressed. Use for settings for which users need to know the exact
                   * value of the setting.
                   */
                  pin: {
                      type: Boolean,
                      value: false,
                      notify: true
                  },

                  /**
                   * if true, the slider thumb snaps to tick marks evenly spaced based
                   * on the `step` property value.
                   */
                  snaps: {
                      type: Boolean,
                      value: false,
                      notify: true
                  },

                  /**
                   * if true, the slider is disabled.
                   */
                  disabled: {
                      type: Boolean,
                      value: false,
                      notify: true
                  },

                  /**
                   * an option to "revert" from the paper-range-slider to a single paper-single-range-slider.
                   */
                  singleSlider: {
                      type: Boolean,
                      value: false,
                      notify: true
                  },

                  /**
                   * time-window (in msec) to keep the slider._setTransiting(true) for the
                   * two paper-single-range-slider elements, when using the _setValuesTrans() method to change the
                   * selected range. This should be slightly higher than the transition time defined for the
                   * paper-single-range-slider (which, as of paper-single-range-slider-v1.0.11, is 0.08s/0.18s).
                   */
                  transDuration: {
                      type: Number,
                      value: 250,
                  },

                  /**
                   * if set to true, tapping the slider below or above the selected range
                   * will update the selected range.
                   */
                  tapValueExtend: {
                      type: Boolean,
                      value: true,
                      notify: true
                  },

                  /**
                   * if set to true, tapping the slider inside the selected range
                   * will update the selected range.
                   */
                  tapValueReduce: {
                      type: Boolean,
                      value: false,
                      notify: true
                  },

                  /**
                   * if set to true, tapping the slider will update the selected range,
                   * while keeping the same difference between valueMin and valueMax.
                   * tapValueMove supersedes tapValueExtend and tapValueReduce
                   */
                  tapValueMove: {
                      type: Boolean,
                      value: false,
                      notify: true
                  },
              }
            }


            // -----------------------------------------------------------------------------------------------------------
            // initial settings
            // -----------------------------------------------------------------------------------------------------------
            ready() {
                super.ready();
                this.inInit = true;

                var this_      = this;
                var nInitTries = 0;
                
                var sliderMin = this.shadowRoot.querySelector("#sliderMin");
                var sliderMax = this.shadowRoot.querySelector("#sliderMax");

                // initialization once the paper-single-range-slider elements have been rendered
                function _ready() {
                    if(sliderMin.isReady && sliderMax.isReady) {
                        this_.init();

                        // setup listeners for updating everything whenever a knob is affected 
                        sliderMin.addEventListener('immediate-value-change', function(customEvent) {
                            this_._setValueMinMax(this_._getValuesMinMax(this.immediateValue,null),'immediate-value-change');
                        });
                        
                        sliderMax.addEventListener('immediate-value-change', function(customEvent) {
                            this_._setValueMinMax(this_._getValuesMinMax(null,this.immediateValue),'immediate-value-change');
                        });

                        sliderMin.addEventListener('change', function(customEvent) {
                            this_._setValueMinMax(this_._getValuesMinMax(this.immediateValue,null),'change');

                            if(this_.alwaysShowPin) {
                                this_.$.sliderMin._expandKnob();
                            }
                        });
                        
                        sliderMax.addEventListener('change', function(customEvent) {
                            this_._setValueMinMax(this_._getValuesMinMax(null,this.immediateValue),'change');

                            if(this_.alwaysShowPin) {
                                this_.$.sliderMax._expandKnob();
                            }
                        });

                        this_.inInit = false;
                    }
                    else {
                        if(nInitTries < 1000) {
                            setTimeout(function() { _ready(); }, 10);
                        }
                        else {
                            console.error("could not properly initialize the underlying paper-single-range-slider elements ...");
                        }
                        nInitTries++;
                    }
                }
                _ready();

                return;
            }

            /**
             * initialize basic properties (can be called again by the user)
             * @method init
             */
            init() {
                this.setSingleSlider(this.singleSlider);
                this.setDisabled(this.disabled);

                // some basic properties
                if(this.alwaysShowPin) { this.pin = true; }

                this.shadowRoot.querySelector("#sliderMin").pin   = this.pin;
                this.shadowRoot.querySelector("#sliderMax").pin   = this.pin;
                this.shadowRoot.querySelector("#sliderMin").snaps = this.snaps;
                this.shadowRoot.querySelector("#sliderMax").snaps = this.snaps;

                if(this.sliderWidth != "") {
                    this.updateStyles({
                      '--paper-range-slider-width': this.sliderWidth,
                    });
                }

                // since the two single sliders are overlaid, we need to remove foreground color
                var sliderBar = this.shadowRoot.querySelector("#sliderMin").getEle('#sliderBar');
                if(sliderBar) {
                    var progressContainer = sliderBar.$$('#progressContainer');
                    if(progressContainer) progressContainer.style.background = "transparent";
                }

                // internal variable to prevent unneeded fire on updates
                this._prevUpdateValues = [this.min, this.max]

                // set internal variables to control the minimal and maximal difference between selected values
                this._setValueDiff();

                // initial setting after verifying this._valueDiffMin, this._valueDiffMax
                this._setValueMinMax(this._getValuesMinMax(this.valueMin, this.valueMax),'init');

                // activate the pins, and never hide
                if(this.alwaysShowPin) {
                    this.shadowRoot.querySelector("#sliderMin")._expandKnob();
                    this.shadowRoot.querySelector("#sliderMax")._expandKnob();
                }

                return;
            }

            // internal variables for minimal/maximal difference between this.valueMin, this.valueMax
            // each one is between zero and the maximal difference available in the range, and
            // the this._valueDiffMin can not be larger than this._valueDiffMax
            _setValueDiff() {
                this._valueDiffMax = Math.max(this.valueDiffMax, 0);
                this._valueDiffMin = Math.max(this.valueDiffMin, 0);

                return;
            }

            // get a new set of min/max values, following predefined rules for overlap of the two
            _getValuesMinMax(valueMin,valueMax) {
                var hasMin = (valueMin != null && valueMin >= this.min && valueMin <= this.max);
                var hasMax = (valueMax != null && valueMax >= this.min && valueMax <= this.max);

                if(!hasMin && !hasMax) { return [this.valueMin,this.valueMax]; }

                var valueNowMin = hasMin ? valueMin : this.valueMin;
                var valueNowMax = hasMax ? valueMax : this.valueMax;

                valueNowMin = Math.min(Math.max(valueNowMin, this.min), this.max)
                valueNowMax = Math.min(Math.max(valueNowMax, this.min), this.max)

                var diffNow  = valueNowMax - valueNowMin;

                // the anchor is the valueMin if it is explicitly provided
                if(hasMin) {
                    if(diffNow < this._valueDiffMin) {
                        valueNowMax = Math.min(this.max, valueNowMin + this._valueDiffMin);
                        diffNow  = valueNowMax - valueNowMin;
                        if(diffNow < this._valueDiffMin) {
                            valueNowMin = valueNowMax - this._valueDiffMin;
                        }
                    }
                    else if(diffNow > this._valueDiffMax && this._valueDiffMax > 0) {
                        valueNowMax = valueNowMin + this._valueDiffMax;
                    }
                }
                // if no valueMin given, decide the anchor is valueMax
                else {
                    if(diffNow < this._valueDiffMin) {
                        valueNowMin = Math.max(this.min, valueNowMax - this._valueDiffMin);
                        diffNow  = valueNowMax - valueNowMin;
                        if(diffNow < this._valueDiffMin) {
                            valueNowMax = valueNowMin + this._valueDiffMin;
                        }
                    }
                    else if(diffNow > this._valueDiffMax && this._valueDiffMax > 0) {
                        valueNowMin = valueNowMax - this._valueDiffMax;
                    }
                }

                return [valueNowMin, valueNowMax];
            }

            // set the value of the low edge of the selected range
            _setValueMin(value) {
                value = Math.max(value, this.min);
                this.shadowRoot.querySelector("#sliderMin").value = value;
                this.valueMin = value;
                return;
            }

            // set the value of the high edge of the selected range
            _setValueMax(value) {
                value = Math.min(value, this.max);
                this.shadowRoot.querySelector("#sliderMax").value = value;
                this.valueMax               = value;

                return;
            }

            // set the values of the low/high edges of the selected range and broadcast the change
            _setValueMinMax(valuesMinMax, eventName) {
                this._setValueMin(valuesMinMax[0]);
                this._setValueMax(valuesMinMax[1]);

                // fire to indicate an update of this.valueMin and/or this.valueMax
                this.updateValues(eventName);

                return;
            }

            // set this.valueMin and/or this.valueMax (can input null values or out-of-range
            // values in order to set only one of the two)
            _setValuesNoTrans(valueMin,valueMax,eventName) {
                // some sanity checks/changes
                if(valueMin != null) {
                    if(valueMin < this.min || valueMin > this.max) valueMin = null;
                }
                if(valueMax != null) {
                    if(valueMax < this.min || valueMax > this.max) valueMax = null;
                }
                if(valueMin != null && valueMax != null) {
                    valueMin = Math.min(valueMin,valueMax);
                }

                // now update the values
                this._setValueMinMax(this._getValuesMinMax(valueMin,valueMax), eventName);

                return;
            }

            // _setValuesNoTrans with a transition
            _setValuesTrans(valueMin,valueMax,eventName) {
                this.shadowRoot.querySelector("#sliderMin")._setTransiting(true);
                this.shadowRoot.querySelector("#sliderMax")._setTransiting(true);

                this._setValuesNoTrans(valueMin,valueMax,eventName);

                var this_ = this;
                setTimeout(function() {
                    this_.$.sliderMin._setTransiting(false);
                    this_.$.sliderMax._setTransiting(false);
                }, this_.transDuration);

                return;
            }

            // interface for functions to control the draggable invisible div which
            // spans the distance between the knobs
            _backDivOnTrack(event) {
                event.stopPropagation();
              
                switch (event.detail.state) {
                    case 'start':
                        this._backDivTrackStart(event);
                        break;
                    case 'track':
                        this._backDivTrackDuring(event);
                        break;
                    case 'end':
                        this._backDivTrackEnd();
                        break;
                }

              return;
            }

            // place-holder function for possible later implementation
            _backDivTrackStart(event) { return; }

            // function to enable dragging both knobs by using the invisible
            // div which spans the distance in between
            _backDivTrackDuring(e) {
                var sliderMin = this.shadowRoot.querySelector("#sliderMin");
                var sliderMax = this.shadowRoot.querySelector("#sliderMax");

                this._x1_Min = this._x0_Min + e.detail.dx;
                var immediateValueMin = sliderMin._calcStep(this._getRatioPos(sliderMin, this._x1_Min/this._xWidth));

                this._x1_Max = this._x0_Max + e.detail.dx;
                var immediateValueMax = sliderMax._calcStep(this._getRatioPos(sliderMax, this._x1_Max/this._xWidth));

                if(immediateValueMin >= this.min && immediateValueMax <= this.max) {
                    this._setValuesWithCurrentDiff(immediateValueMin, immediateValueMax, false);
                }

                return;
            }

            _setValuesWithCurrentDiff(valueMin, valueMax, useTrans) {
                var diffMin = this._valueDiffMin;
                var diffMax = this._valueDiffMax;

                this._valueDiffMin = this.valueMax - this.valueMin;
                this._valueDiffMax = this.valueMax - this.valueMin;

                if(useTrans) this._setValuesTrans(valueMin, valueMax, '_setValuesWithCurrentDiff');
                else         this._setValuesNoTrans(valueMin, valueMax, '_setValuesWithCurrentDiff');

                this._valueDiffMin = diffMin;
                this._valueDiffMax = diffMax;

                return;
            }
            
            // place-holder function for at the end of the dragging event, so the following
            _backDivTrackEnd() { return; }

            // _sliderMinDown, _sliderMaxDown, _sliderMinUp, _sliderMaxUp
            //      show/hide pins (if defined) for one knob, when the other knob is pressed
            _sliderMinDown() {
                this.shadowRoot.querySelector("#sliderMax")._expandKnob();
                
                return;
            }

            _sliderMaxDown(event) {
                if(!this.singleSlider) {
                    this.shadowRoot.querySelector("#sliderMin")._expandKnob();
                }
                else {
                    this._setValuesNoTrans(null,this._getEventValue(event),'_sliderMaxDown');
                }

                return;
            }

            _sliderMinUp() {
                if(this.alwaysShowPin) this.shadowRoot.querySelector("#sliderMin")._expandKnob();
                else                   this.shadowRoot.querySelector("#sliderMax")._resetKnob();
                
                return;
            }

            _sliderMaxUp() {
                if(this.alwaysShowPin) this.shadowRoot.querySelector("#sliderMax")._expandKnob();
                else {
                    this.shadowRoot.querySelector("#sliderMin")._resetKnob();
                    if(this.singleSlider) this.shadowRoot.querySelector("#sliderMax")._resetKnob();
                }
                
                return;
            }

            // function to calculate the value from an event
            _getEventValue(event) {
                var width = this.shadowRoot.querySelector("#sliderMax").getEle('#sliderContainer').offsetWidth;
                var rect  = this.shadowRoot.querySelector("#sliderMax").getEle('#sliderContainer').getBoundingClientRect();
                var ratio = (event.detail.x - rect.left) / width;
                var value = this.min + ratio * (this.max - this.min);

                return value;
            }


            // set the value if tapping the slider below or above the selected range
            _backDivTap(event) {
                this._setValueNow = function(valueMin,valueMax) {
                    if(this.tapValueMove) { this._setValuesWithCurrentDiff(valueMin,valueMax,true); }
                    else                  { this._setValuesTrans(valueMin,valueMax,'_backDivTap');  }
                    return;
                }
                
                var value = this._getEventValue(event);
                if(value > this.valueMin && value < this.valueMax) {
                    if(this.tapValueReduce) {
                        var isLow = value < (this.valueMin + (this.valueMax - this.valueMin)/2);
                        if(isLow) { this._setValueNow(value,null); }
                        else      { this._setValueNow(null,value); }
                    }
                }
                else if(this.tapValueExtend || this.tapValueMove) {
                    if(value < this.valueMin) { this._setValueNow(value,null); }
                    if(value > this.valueMax) { this._setValueNow(null,value); }
                }

                return;
            }

            // initialization before starting the dragging of the invisible
            // div which spans the distance in between
            _backDivDown(event) {
                // show pins if defined
                this._sliderMinDown();
                this._sliderMaxDown();

                // get the initial positions of knobs before dragging starts
                this._xWidth =  this.shadowRoot.querySelector("#sliderMin").getEle('#sliderBar').offsetWidth;
                this._x0_Min = (this.shadowRoot.querySelector("#sliderMin").ratio / 100.) * this._xWidth;
                this._x0_Max = (this.shadowRoot.querySelector("#sliderMax").ratio / 100.) * this._xWidth;

                return;
            }

            // finalization after ending the dragging of the invisible
            // div which spans the distance in between
            _backDivUp() {
                // hide pins if defined
                this._sliderMinUp();
                this._sliderMaxUp();

                return;
            }

            // place-holder function for possible later implementation
            _backDivTransEnd(event) { return; }

            // the position of the knob for a given single slider, for a given ratio
            _getRatioPos(slider, ratio) {
                return Math.max(slider.min, Math.min(slider.max, (slider.max - slider.min) * ratio + slider.min));
            }

            // helper function to cast to a boolean
            _toBool(valIn) { return (valIn === "false" || valIn === "0") ? false : Boolean(valIn); }

            /**
             * set this.valueMin and/or this.valueMax (can input null values or out-of-range
             * values in order to set only one of the two) - this is just a public
             * wrapper for this._setValuesNoTrans(), but including transition animation
             * @method _setValuesTrans
             */
            setValues(valueMin,valueMax,eventName) {
                var eventNameNow = eventName ? eventName : 'setValues';
                this._setValuesTrans(valueMin,valueMax,eventNameNow);
                return;
            }

            /**
             * fire whenever this.valueMin or this.valueMax are changed
             * @method updateValues
             */
            updateValues(eventName) {
                var isNewMin = (this._prevUpdateValues[0] != this.valueMin);
                var isNewMax = (this._prevUpdateValues[1] != this.valueMax);
                if(isNewMin || isNewMax) {
                    this._prevUpdateValues = [this.valueMin, this.valueMax];

                    // fire events
                    if(!this.inInit) {
                        this.dispatchEvent(new CustomEvent('updateValues', { bubbles:true, composed:true, detail:{this:this, eventName:eventName} }));
                        
                        if(isNewMin) {
                            this.dispatchEvent(
                                new CustomEvent('value-min-changed', { bubbles:true, composed:true, detail:{this:this, value:this.valueMin, eventName:eventName} })
                            );
                        }
                        if(isNewMax) {
                            this.dispatchEvent(
                                new CustomEvent('value-max-changed', { bubbles:true, composed:true, detail:{this:this, value:this.valueMax, eventName:eventName} })
                            );
                        }
                    }
                }
                return;
            }

            /**
             * set the minimal value (lower range) of the slider
             * @method setMin
             */
            setMin(minIn) {
                // paper-single-range-slider needs a safety check that the min value we are going to set is
                // not larger than the max value which is already set
                if(this.max < minIn) this.max = minIn;

                this.min = minIn;
                this._prevUpdateValues = [this.min, this.max];

                // update the selected value if it is now outside of the lower bound,
                // or just update the position of the overlay divs for the min/max knobs
                if(this.valueMin < this.min) this._setValuesNoTrans(this.min,null,'setMin');

                return;
            }

            /**
             * set the maximal value (upper range) of the slider
             * @method setMax
             */
            setMax(maxIn) {
                // paper-single-range-slider needs a safety check that the min value we are going to set is
                // not larger than the max value which is already set
                if(this.min > maxIn) this.min = maxIn;

                this.max = maxIn;
                this._prevUpdateValues = [this.min, this.max];

                // update the selected value if it is now outside of the upper bound,
                // or just update the position of the overlay divs for the min/max knobs
                if(this.valueMax > this.max) this._setValuesNoTrans(null,this.max,'setMax');

                return;
            }

            /**
             * set the minimal step-change of a knob on the slider
             * @method setMax
             */
            setStep(stepIn) {
                this.step = stepIn;
                return;
            }

            /**
             * get tne ratio (within [0,1]) corresponding to the min/max values
             * @method getRatio
             */
            getRatio() {
                var ratioMin = this.shadowRoot.querySelector("#sliderMin").ratio / 100.;
                var ratioMax = this.shadowRoot.querySelector("#sliderMax").ratio / 100.;
                return [ratioMin, ratioMax];
            }

            /**
             * set the minimal difference between selected values
             * @method setValueDiffMin
             */
            setValueDiffMin(valueDiffMin) {
                this._valueDiffMin = valueDiffMin;
                return;
            }

            /**
             * set the maximal difference between selected values
             * @method setValueDiffMax
             */
            setValueDiffMax(valueDiffMax) {
                this._valueDiffMax = valueDiffMax;
                return;
            }

            /**
             * set the tapValueExtend property
             * @method setValueDiffMax
             */
            setTapValueExtend(isTapValueExtend) {
                this.tapValueExtend = this._toBool(isTapValueExtend);
                return;
            }

            /**
             * set the tapValueReduce property
             * @method setValueDiffMax
             */
            setTapValueReduce(isTapValueReduce) {
                this.tapValueReduce = this._toBool(isTapValueReduce);
                return;
            }

            /**
             * set the tapValueMove property
             * @method setValueDiffMax
             */
            setTapValueMove(isTapValueMove) {
                this.tapValueMove = this._toBool(isTapValueMove);
                return;
            }

            /**
             * set the disabled parameter
             * @method setValueDiffMax
             */
            setDisabled(isDisabled) {
                this.disabled = this._toBool(isDisabled);
                var pointEvt  = this.disabled ? "none" : "auto";

                this.shadowRoot.querySelector("#sliderMax").getEle('#sliderKnob').style.pointerEvents = pointEvt;
                this.shadowRoot.querySelector("#sliderMin").getEle('#sliderKnob').style.pointerEvents = pointEvt;
                this.shadowRoot.querySelector("#sliderOuterDiv_1").style.pointerEvents                = pointEvt;

                return;
            }

            /**
             * change the slider between the default paper-range-slider and a paper-single-range-slider
             * @method setValueDiffMax
             */
            setSingleSlider(isSingle) {
                this.singleSlider = this._toBool(isSingle);

                var sliderMin = this.shadowRoot.querySelector("#sliderMin");
                var sliderMax = this.shadowRoot.querySelector("#sliderMax");
                var backDiv   = this.shadowRoot.querySelector("#backDiv");
                var knobMin   = sliderMin.getEle('#sliderKnob');
                var knobMax   = sliderMax.getEle('#sliderKnob');

                if(isSingle) {
                    backDiv.style.display         = 'none';
                    sliderMax.style.pointerEvents = 'auto';
                    sliderMax.style.display       = '';
                    sliderMin.style.display       = 'none';
                    backDiv.style.cursor          = 'default';
                    knobMax.style.cursor          = 'default';
                    knobMin.style.cursor          = 'default';
                }
                else {
                    backDiv.style.display         = 'block';
                    sliderMax.style.pointerEvents = 'none';
                    sliderMax.style.display       = '';
                    sliderMin.style.display       = '';
                    backDiv.style.cursor          = 'ew-resize';
                    knobMax.style.cursor          = 'col-resize';
                    knobMin.style.cursor          = 'col-resize';
                }

                // disable some of the interface of the two single sliders,
                // but keep the knobs active if not disabled
                sliderMax.getEle('#sliderContainer').style.pointerEvents = this.singleSlider ? "auto" : "none";
                sliderMin.getEle('#sliderContainer').style.pointerEvents = "none";

                return;
            }
        }
        customElements.define(PaperRangeSlider.is, PaperRangeSlider);
    </script>
</dom-module>



<dom-module id="paper-single-range-slider" assetpath="bower_components/paper-range-slider/">
  <template strip-whitespace="">
    <style>
      :host {
        @apply --layout;
        @apply --layout-justified;
        @apply --layout-center;
        width: 200px;
        cursor: default;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
        --paper-progress-active-color: var(--paper-single-range-slider-active-color, var(--google-blue-700));
        --paper-progress-secondary-color: var(--paper-single-range-slider-secondary-color, var(--google-blue-300));
        --paper-progress-disabled-active-color: var(--paper-single-range-slider-disabled-active-color, var(--paper-grey-400));
        --paper-progress-disabled-secondary-color: var(--paper-single-range-slider-disabled-secondary-color, var(--paper-grey-400));
        --calculated-paper-single-range-slider-height: var(--paper-single-range-slider-height, 2px);
      }

      /* focus shows the ripple */
      :host(:focus) {
        outline: none;
      }

      /** 
       * NOTE(keanulee): Though :host-context is not universally supported, some pages
       * still rely on paper-single-range-slider being flipped when dir="rtl" is set on body. For full
       * compatability, dir="rtl" must be explicitly set on paper-single-range-slider.
       */
      :host-context([dir="rtl"]) #sliderContainer {
        -webkit-transform: scaleX(-1);
        transform: scaleX(-1);
      }

      /** 
       * NOTE(keanulee): This is separate from the rule above because :host-context may
       * not be recognized.
       */
      :host([dir="rtl"]) #sliderContainer {
        -webkit-transform: scaleX(-1);
        transform: scaleX(-1);
      }

      /** 
       * NOTE(keanulee): Needed to override the :host-context rule (where supported)
       * to support LTR sliders in RTL pages.
       */
      :host([dir="ltr"]) #sliderContainer {
        -webkit-transform: scaleX(1);
        transform: scaleX(1);
      }

      #sliderContainer {
        position: relative;
        width: 100%;
        height: calc(30px + var(--calculated-paper-single-range-slider-height));
        margin-left: calc(15px + var(--calculated-paper-single-range-slider-height)/2);
        margin-right: calc(15px + var(--calculated-paper-single-range-slider-height)/2);
      }

      #sliderContainer:focus {
        outline: 0;
      }

      #sliderContainer.editable {
        margin-top: 12px;
        margin-bottom: 12px;
      }

      .bar-container {
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        overflow: hidden;
      }

      .ring > .bar-container {
        left: calc(5px + var(--calculated-paper-single-range-slider-height)/2);
        transition: left 0.18s ease;
      }

      .ring.expand.dragging > .bar-container {
        transition: none;
      }

      .ring.expand:not(.pin) > .bar-container {
        left: calc(8px + var(--calculated-paper-single-range-slider-height)/2);
      }

      #sliderBar {
        padding: 15px 0;
        width: 100%;
        background-color: var(--paper-single-range-slider-bar-color, transparent);
        --paper-progress-container-color: var(--paper-single-range-slider-container-color, var(--paper-grey-400));
        --paper-progress-height: var(--calculated-paper-single-range-slider-height);
      }

      .slider-markers {
        position: absolute;
        top: calc(14px + var(--paper-single-range-slider-height,2px)/2);
        height: var(--calculated-paper-single-range-slider-height);
        left: 0;
        right: -1px;
        box-sizing: border-box;
        pointer-events: none;
        @apply --layout-horizontal;
      }

      .slider-marker {
        @apply --layout-flex;
      }
      .slider-markers::after,
      .slider-marker::after {
        content: "";
        display: block;
        margin-left: -1px;
        width: 2px;
        height: var(--calculated-paper-single-range-slider-height);
        border-radius: 50%;
        background-color: var(--paper-single-range-slider-markers-color, #000);
      }

      .slider-knob {
        position: absolute;
        left: 0;
        top: 0;
        margin-left: calc(-15px - var(--calculated-paper-single-range-slider-height)/2);
        width: calc(30px + var(--calculated-paper-single-range-slider-height));
        height: calc(30px + var(--calculated-paper-single-range-slider-height));
      }

      .transiting > .slider-knob {
        transition: left 0.08s ease;
      }

      .slider-knob:focus {
        outline: none;
      }

      .slider-knob.dragging {
        transition: none;
      }

      .snaps > .slider-knob.dragging {
        transition: -webkit-transform 0.08s ease;
        transition: transform 0.08s ease;
      }

      .slider-knob-inner {
        margin: 10px;
        width: calc(100% - 20px);
        height: calc(100% - 20px);
        background-color: var(--paper-single-range-slider-knob-color, var(--google-blue-700));
        border: 2px solid var(--paper-single-range-slider-knob-color, var(--google-blue-700));
        border-radius: 50%;

        -moz-box-sizing: border-box;
        box-sizing: border-box;

        transition-property: -webkit-transform, background-color, border;
        transition-property: transform, background-color, border;
        transition-duration: 0.18s;
        transition-timing-function: ease;
      }

      .expand:not(.pin) > .slider-knob > .slider-knob-inner {
        -webkit-transform: scale(1.5);
        transform: scale(1.5);
      }

      .ring > .slider-knob > .slider-knob-inner {
        background-color: var(--paper-single-range-slider-knob-start-color, transparent);
        border: 2px solid var(--paper-single-range-slider-knob-start-border-color, var(--paper-grey-400));
      }

      .slider-knob-inner::before {
        background-color: var(--paper-single-range-slider-pin-color, var(--google-blue-700));
      }

      .pin > .slider-knob > .slider-knob-inner::before {
        content: "";
        position: absolute;
        top: 0;
        left: 50%;
        margin-left: -13px;
        width: 26px;
        height: 26px;
        border-radius: 50% 50% 50% 0;

        -webkit-transform: rotate(-45deg) scale(0) translate(0);
        transform: rotate(-45deg) scale(0) translate(0);
      }

      .slider-knob-inner::before,
      .slider-knob-inner::after {
        transition: -webkit-transform .18s ease, background-color .18s ease;
        transition: transform .18s ease, background-color .18s ease;
      }

      .pin.ring > .slider-knob > .slider-knob-inner::before {
        background-color: var(--paper-single-range-slider-pin-start-color, var(--paper-grey-400));
      }

      .pin.expand > .slider-knob > .slider-knob-inner::before {
        -webkit-transform: rotate(-45deg) scale(1) translate(17px, -17px);
        transform: rotate(-45deg) scale(1) translate(17px, -17px);
      }

      .pin > .slider-knob > .slider-knob-inner::after {
        content: attr(value);
        position: absolute;
        top: 0;
        left: 50%;
        margin-left: -16px;
        width: 32px;
        height: 26px;
        text-align: center;
        color: var(--paper-single-range-slider-font-color, #fff);
        font-size: 10px;

        -webkit-transform: scale(0) translate(0);
        transform: scale(0) translate(0);
        @apply paper-single-range-slider-knob-inner-text;
      }

      .pin.expand > .slider-knob > .slider-knob-inner::after {
        -webkit-transform: scale(1) translate(0, -17px);
        transform: scale(1) translate(0, -17px);
      }

      /* paper-input */
      .slider-input {
        width: 50px;
        overflow: hidden;
        --paper-input-container-input: {
          text-align: center;
        };
        @apply --paper-single-range-slider-input;
      }

      /* disabled state */
      #sliderContainer.disabled {
        pointer-events: none;
      }

      .disabled > .slider-knob > .slider-knob-inner {
        background-color: var(--paper-single-range-slider-disabled-knob-color, var(--paper-grey-400));
        border: 2px solid var(--paper-single-range-slider-disabled-knob-color, var(--paper-grey-400));
        -webkit-transform: scale3d(0.75, 0.75, 1);
        transform: scale3d(0.75, 0.75, 1);
      }

      .disabled.ring > .slider-knob > .slider-knob-inner {
        background-color: var(--paper-single-range-slider-knob-start-color, transparent);
        border: 2px solid var(--paper-single-range-slider-knob-start-border-color, var(--paper-grey-400));
      }

      paper-ripple {
        color: var(--paper-single-range-slider-knob-color, var(--google-blue-700));
      }
    </style>

    <div id="sliderContainer" class$="[[_getClassNames(disabled, pin, snaps, immediateValue, min, expand, dragging, transiting, editable)]]">
      <div class="bar-container">
        <paper-progress disabled$="[[disabled]]" id="sliderBar" aria-hidden="true" min="[[min]]" max="[[max]]" step="[[step]]" value="[[immediateValue]]" secondary-progress="[[secondaryProgress]]" on-down="_bardown" on-up="_resetKnob" on-track="_onTrack">
        </paper-progress>
      </div>

      <template is="dom-if" if="[[snaps]]">
        <div class="slider-markers">
          <template is="dom-repeat" items="[[markers]]">
            <div class="slider-marker"></div>
          </template>
        </div>
      </template>

      <div id="sliderKnob" class="slider-knob" on-down="_knobdown" on-up="_resetKnob" on-track="_onTrack" on-transitionend="_knobTransitionEnd">
          <div class="slider-knob-inner" value$="[[displayFunction(immediateValue)]]"></div>
      </div>
    </div>

    <template is="dom-if" if="[[editable]]">
      <paper-input id="input" type="number" step="[[step]]" min="[[min]]" max="[[max]]" class="slider-input" disabled$="[[disabled]]" value="[[immediateValue]]" on-change="_changeValue" on-keydown="_inputKeyDown" no-label-float="">
      </paper-input>
    </template>
  </template>

  <script>
    Polymer({
      is: 'paper-single-range-slider',

      behaviors: [
        Polymer.IronA11yKeysBehavior,
        Polymer.IronFormElementBehavior,
        Polymer.PaperInkyFocusBehavior,
        Polymer.IronRangeBehavior
      ],

      properties: {
        /**
         * Used to customize the displayed value of the pin. E.g. the value can be prefixed with a '$' like '$99'
         */
        displayFunction: {
          type: Function,
          value: function () {
            return function (value) {
              return value;
            }
          }
        },

        /**
         * If true, the slider thumb snaps to tick marks evenly spaced based
         * on the `step` property value.
         */
        snaps: {
          type: Boolean,
          value: false,
          notify: true
        },

        /**
         * If true, a pin with numeric value label is shown when the slider thumb
         * is pressed. Use for settings for which users need to know the exact
         * value of the setting.
         */
        pin: {
          type: Boolean,
          value: false,
          notify: true
        },

        /**
         * The number that represents the current secondary progress.
         */
        secondaryProgress: {
          type: Number,
          value: 0,
          notify: true,
          observer: '_secondaryProgressChanged'
        },

        /**
         * If true, an input is shown and user can use it to set the slider value.
         */
        editable: {
          type: Boolean,
          value: false
        },

        /**
         * The immediate value of the slider.  This value is updated while the user
         * is dragging the slider.
         */
        immediateValue: {
          type: Number,
          value: 0,
          readOnly: true,
          notify: true
        },

        /**
         * The maximum number of markers
         */
        maxMarkers: {
          type: Number,
          value: 0,
          notify: true
        },

        /**
         * If true, the knob is expanded
         */
        expand: {
          type: Boolean,
          value: false,
          readOnly: true
        },

        /**
         * True when the user is dragging the slider.
         */
        dragging: {
          type: Boolean,
          value: false,
          readOnly: true
        },

        transiting: {
          type: Boolean,
          value: false,
          readOnly: true
        },

        markers: {
          type: Array,
          readOnly: true,
          value: function() {
              return [];
          }
        },
      },

      observers: [
        '_updateKnob(value, min, max, snaps, step)',
        '_valueChanged(value)',
        '_immediateValueChanged(immediateValue)',
        '_updateMarkers(maxMarkers, min, max, snaps)'
      ],

      hostAttributes: {
        role: 'slider',
        tabindex: 0
      },

      keyBindings: {
        'left': '_leftKey',
        'right': '_rightKey',
        'down pagedown home': '_decrementKey',
        'up pageup end': '_incrementKey'
      },

      ready: function() {
        this.isReady = true;
        return;
      },

      /**
       * Increases value by `step` but not above `max`.
       * @method increment
       */
      increment: function() {
        this.value = this._clampValue(this.value + this.step);
      },

      /**
       * Decreases value by `step` but not below `min`.
       * @method decrement
       */
      decrement: function() {
        this.value = this._clampValue(this.value - this.step);
      },

      _updateKnob: function(value, min, max, snaps, step) {
        this.setAttribute('aria-valuemin', min);
        this.setAttribute('aria-valuemax', max);
        this.setAttribute('aria-valuenow', value);

        this._positionKnob(this._calcRatio(value) * 100);
      },

      _valueChanged: function() {
        this.fire('value-change', {composed: true});
      },

      _immediateValueChanged: function() {
        if (this.dragging) {
          this.fire('immediate-value-change', {composed: true});
        } else {
          this.value = this.immediateValue;
        }
      },

      _secondaryProgressChanged: function() {
        this.secondaryProgress = this._clampValue(this.secondaryProgress);
      },

      _expandKnob: function() {
        this._setExpand(true);
      },

      _resetKnob: function() {
        this.cancelDebouncer('expandKnob');
        this._setExpand(false);
      },

      _positionKnob: function(ratio) {
        this._setImmediateValue(this._calcStep(this._calcKnobPosition(ratio)));
        this._setRatio(this._calcRatio(this.immediateValue) * 100);

        this.$.sliderKnob.style.left = this.ratio + '%';
        if (this.dragging) {
          this._knobstartx = (this.ratio * this._w) / 100;
          this.translate3d(0, 0, 0, this.$.sliderKnob);
        }
      },

      _calcKnobPosition: function(ratio) {
        return (this.max - this.min) * ratio / 100 + this.min;
      },

      _onTrack: function(event) {
        event.stopPropagation();
        switch (event.detail.state) {
          case 'start':
            this._trackStart(event);
            break;
          case 'track':
            this._trackX(event);
            break;
          case 'end':
            this._trackEnd();
            break;
        }
      },

      _trackStart: function(event) {
        this._setTransiting(false);
        this._w = this.$.sliderBar.offsetWidth;
        this._x = this.ratio * this._w / 100;
        this._startx = this._x;
        this._knobstartx = this._startx;
        this._minx = - this._startx;
        this._maxx = this._w - this._startx;
        this.$.sliderKnob.classList.add('dragging');
        this._setDragging(true);
      },

      _trackX: function(event) {
        if (!this.dragging) {
          this._trackStart(event);
        }

        var direction = this._isRTL ? -1 : 1;
        var dx = Math.min(
            this._maxx, Math.max(this._minx, event.detail.dx * direction));
        this._x = this._startx + dx;

        var immediateValue = this._calcStep(this._calcKnobPosition(this._x / this._w * 100));
        this._setImmediateValue(immediateValue);

        // update knob's position
        var translateX = ((this._calcRatio(this.immediateValue) * this._w) - this._knobstartx);
        this.translate3d(translateX + 'px', 0, 0, this.$.sliderKnob);
      },

      _trackEnd: function() {
        var s = this.$.sliderKnob.style;

        this.$.sliderKnob.classList.remove('dragging');
        this._setDragging(false);
        this._resetKnob();
        this.value = this.immediateValue;

        s.transform = s.webkitTransform = '';

        this.fire('change', {composed: true});
      },

      _knobdown: function(event) {
        this._expandKnob();

        // cancel selection
        event.preventDefault();

        // set the focus manually because we will called prevent default
        this.focus();
      },

      _bardown: function(event) {
        this._w = this.$.sliderBar.offsetWidth;
        var rect = this.$.sliderBar.getBoundingClientRect();
        var ratio = (event.detail.x - rect.left) / this._w * 100;
        if (this._isRTL) {
          ratio = 100 - ratio;
        }
        var prevRatio = this.ratio;

        this._setTransiting(true);

        this._positionKnob(ratio);

        this.debounce('expandKnob', this._expandKnob, 60);

        // if the ratio doesn't change, sliderKnob's animation won't start
        // and `_knobTransitionEnd` won't be called
        // Therefore, we need to manually update the `transiting` state

        if (prevRatio === this.ratio) {
          this._setTransiting(false);
        }

        this.async(function() {
          this.fire('change', {composed: true});
        });

        // cancel selection
        event.preventDefault();

        // set the focus manually because we will called prevent default
        this.focus();
      },

      _knobTransitionEnd: function(event) {
        if (event.target === this.$.sliderKnob) {
          this._setTransiting(false);
        }
      },

      _updateMarkers: function(maxMarkers, min, max, snaps) {
        if (!snaps) {
          this._setMarkers([]);
        }
        var steps = Math.round((max - min) / this.step);
        if (steps > maxMarkers) {
          steps = maxMarkers;
        }
        if (steps < 0 || !isFinite(steps)) {
          steps = 0;
        }
        this._setMarkers(new Array(steps));
      },

      _mergeClasses: function(classes) {
        return Object.keys(classes).filter(
          function(className) {
            return classes[className];
          }).join(' ');
      },

      _getClassNames: function() {
        return this._mergeClasses({
          disabled: this.disabled,
          pin: this.pin,
          snaps: this.snaps,
          ring: this.immediateValue <= this.min,
          expand: this.expand,
          dragging: this.dragging,
          transiting: this.transiting,
          editable: this.editable
        });
      },

      get _isRTL() {
        if (this.__isRTL === undefined) {
          this.__isRTL = window.getComputedStyle(this)['direction'] === 'rtl';
        }
        return this.__isRTL;
      },

      _leftKey: function(event) {
        if (this._isRTL)
          this._incrementKey(event);
        else
          this._decrementKey(event);
      },

      _rightKey: function(event) {
        if (this._isRTL)
          this._decrementKey(event);
        else
          this._incrementKey(event);
      },

      _incrementKey: function(event) {
        if (!this.disabled) {
          if (event.detail.key === 'end') {
            this.value = this.max;
          } else {
            this.increment();
          }
          this.fire('change');
          event.preventDefault();
        }
      },

      _decrementKey: function(event) {
        if (!this.disabled) {
          if (event.detail.key === 'home') {
            this.value = this.min;
          } else {
            this.decrement();
          }
          this.fire('change');
          event.preventDefault();
        }
      },

      _changeValue: function(event) {
        this.value = event.target.value;
        this.fire('change', {composed: true});
      },

      _inputKeyDown: function(event) {
        event.stopPropagation();
      },

      // create the element ripple inside the `sliderKnob`
      _createRipple: function() {
        this._rippleContainer = this.$.sliderKnob;
        return Polymer.PaperInkyFocusBehaviorImpl._createRipple.call(this);
      },

      // Hide the ripple when user is not interacting with keyboard.
      // This behavior is different from other ripple-y controls, but is
      // according to spec: https://www.google.com/design/spec/components/sliders.html
      _focusedChanged: function(receivedFocusFromKeyboard) {
        if (receivedFocusFromKeyboard) {
          this.ensureRipple();
        }
        if (this.hasRipple()) {
          // note, ripple must be un-hidden prior to setting `holdDown`
          if (receivedFocusFromKeyboard) {
            this._ripple.style.display = '';
          } else {
            this._ripple.style.display = 'none';
          }
          this._ripple.holdDown = receivedFocusFromKeyboard;
        }
      },

      getEle: function(tag) { return this.shadowRoot.querySelector(tag); }

    });

    /**
     * Fired when the slider's value changes.
     *
     * @event value-change
     */

    /**
     * Fired when the slider's immediateValue changes. Only occurs while the
     * user is dragging.
     *
     * To detect changes to immediateValue that happen for any input (i.e.
     * dragging, tapping, clicking, etc.) listen for immediate-value-changed
     * instead.
     *
     * @event immediate-value-change
     */

    /**
     * Fired when the slider's value changes due to user interaction.
     *
     * Changes to the slider's value due to changes in an underlying
     * bound variable will not trigger this event.
     *
     * @event change
     */
  </script>
</dom-module>






<script>
  /**
  `iron-a11y-keys` provides a cross-browser interface for processing
  keyboard commands. The interface adheres to [WAI-ARIA best
  practices](http://www.w3.org/TR/wai-aria-practices/#kbd_general_binding).
  It uses an expressive syntax to filter key presses.

  ## Basic usage

  The sample code below is a portion of a custom element. The goal is to call
  the `onEnter` method whenever the `paper-input` element is in focus and
  the `Enter` key is pressed.

      <iron-a11y-keys id="a11y" target="[[target]]" keys="enter"
                          on-keys-pressed="onEnter"></iron-a11y-keys>
      <paper-input id="input"
                   placeholder="Type something. Press enter. Check console."
                   value="{{userInput::input}}"></paper-input>

  The custom element declares an `iron-a11y-keys` element that is bound to a
  property called `target`. The `target` property
  needs to evaluate to the `paper-input` node. `iron-a11y-keys` registers
  an event handler for the target node using Polymer's [annotated event handler
  syntax](https://www.polymer-project.org/1.0/docs/devguide/events.html#annotated-listeners).
  `{{userInput::input}}` sets the `userInput` property to the user's input on each
  keystroke.

  The last step is to link the two elements within the custom element's
  registration.

      ...
      properties: {
        userInput: {
          type: String,
          notify: true,
        },
        target: {
          type: Object,
          value: function() {
            return this.$.input;
          }
        },
      },
      onEnter: function() {
        console.log(this.userInput);
      }
      ...

  ## The `keys` attribute

  The `keys` attribute expresses what combination of keys triggers the event.

  The attribute accepts a space-separated, plus-sign-concatenated
  set of modifier keys and some common keyboard keys.

  The common keys are: `a-z`, `0-9` (top row and number pad), `*` (shift 8 and
  number pad), `F1-F12`, `Page Up`, `Page Down`, `Left Arrow`, `Right Arrow`,
  `Down Arrow`, `Up Arrow`, `Home`, `End`, `Escape`, `Space`, `Tab`, `Enter`.

  The modifier keys are: `Shift`, `Control`, `Alt`, `Meta`.

  All keys are expected to be lowercase and shortened. E.g.
  `Left Arrow` is `left`, `Page Down` is `pagedown`, `Control` is `ctrl`,
  `F1` is `f1`, `Escape` is `esc`, etc.

  ### Grammar

  Below is the
  [EBNF](http://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_Form) Grammar
  of the `keys` attribute.

      modifier = "shift" | "ctrl" | "alt" | "meta";
      ascii = ? /[a-z0-9]/ ? ;
      fnkey = ? f1 through f12 ? ;
      arrow = "up" | "down" | "left" | "right" ;
      key = "tab" | "esc" | "space" | "*" | "pageup" | "pagedown" |
            "home" | "end" | arrow | ascii | fnkey;
      event = "keypress" | "keydown" | "keyup";
      keycombo = { modifier, "+" }, key, [ ":", event ] ;
      keys = keycombo, { " ", keycombo } ;

  ### Example

  Given the following value for `keys`:

  `ctrl+shift+f7 up pagedown esc space alt+m`

  The event is fired if any of the following key combinations are fired:
  `Control` and `Shift` and `F7` keys, `Up Arrow` key, `Page Down` key,
  `Escape` key, `Space` key, `Alt` and `M` keys.

  ### WAI-ARIA Slider Example

  The following is an example of the set of keys that fulfills WAI-ARIA's
  "slider" role [best
  practices](http://www.w3.org/TR/wai-aria-practices/#slider):

      <iron-a11y-keys target="[[target]]" keys="left pagedown down"
                      on-keys-pressed="decrement"></iron-a11y-keys>
      <iron-a11y-keys target="[[target]]" keys="right pageup up"
                      on-keys-pressed="increment"></iron-a11y-keys>
      <iron-a11y-keys target="[[target]]" keys="home"
                      on-keys-pressed="setMin"></iron-a11y-keys>
      <iron-a11y-keys target="[[target]]" keys="end"
                      on-keys-pressed="setMax"></iron-a11y-keys>

  The `target` properties must evaluate to a node. See the basic usage
  example above.

  Each of the values for the `on-keys-pressed` attributes must evalute
  to methods. The `increment` method should move the slider a set amount
  toward the maximum value. `decrement` should move the slider a set amount
  toward the minimum value. `setMin` should move the slider to the minimum
  value. `setMax` should move the slider to the maximum value.

  @demo demo/index.html
  */



  Polymer({
    is: 'iron-a11y-keys',

    behaviors: [Polymer.IronA11yKeysBehavior],

    properties: {
      /** @type {?Node} */
      target: {type: Object, observer: '_targetChanged'},

      /**
       * Space delimited list of keys where each key follows the format:
       * `[MODIFIER+]*KEY[:EVENT]`.
       * e.g. `keys="space ctrl+shift+tab enter:keyup"`.
       * More detail can be found in the "Grammar" section of the documentation
       */
      keys: {type: String, reflectToAttribute: true, observer: '_keysChanged'}
    },

    attached: function() {
      if (!this.target) {
        this.target = this.parentNode;
      }
    },

    _targetChanged: function(target) {
      this.keyEventTarget = target;
    },

    _keysChanged: function() {
      this.removeOwnKeyBindings();
      this.addOwnKeyBinding(this.keys, '_fireKeysPressed');
    },

    _fireKeysPressed: function(event) {
      this.fire('keys-pressed', event.detail, {});
    }
  });
</script>
<dom-module id="paper-badge" assetpath="bower_components/paper-badge/">
  <template>
    <style>
      :host {
        display: block;
        position: absolute;
        outline: none;
      }

      :host([hidden]), [hidden] {
        display: none !important;
      }

      iron-icon {
        --iron-icon-width: 12px;
        --iron-icon-height: 12px;
      }

      .badge {
        @apply --layout;
        @apply --layout-center-center;
        @apply --paper-font-common-base;

        font-weight: normal;
        font-size: 11px;
        border-radius: 50%;
        margin-left: var(--paper-badge-margin-left, 0px);
        margin-bottom: var(--paper-badge-margin-bottom, 0px);
        width: var(--paper-badge-width, 20px);
        height: var(--paper-badge-height, 20px);
        background-color: var(--paper-badge-background, var(--accent-color));
        opacity: var(--paper-badge-opacity, 1.0);
        color: var(--paper-badge-text-color, white);

        @apply --paper-badge;
      }
    </style>

    <div class="badge">
      <iron-icon hidden$="{{!_computeIsIconBadge(icon)}}" icon="{{icon}}"></iron-icon>
      <span id="badge-text" hidden$="{{_computeIsIconBadge(icon)}}">{{label}}</span>
    </div>
  </template>

  <script>
    Polymer({
      is: 'paper-badge',

      /** @private */
      hostAttributes: {
        role: 'status',
        tabindex: 0
      },

      behaviors: [
        Polymer.IronResizableBehavior
      ],

      listeners: {
        'iron-resize': 'updatePosition'
      },

      properties: {
        /**
         * The id of the element that the badge is anchored to. This element
         * must be a sibling of the badge.
         */
        for: {
          type: String,
          observer: '_forChanged'
        },

        /**
         * The label displayed in the badge. The label is centered, and ideally
         * should have very few characters.
         */
        label: {
          type: String,
          observer: '_labelChanged'
        },

        /**
         * An iron-icon ID. When given, the badge content will use an
         * `<iron-icon>` element displaying the given icon ID rather than the
         * label text. However, the label text will still be used for
         * accessibility purposes.
         */
        icon: {
          type: String,
          value: ''
        },

        _boundNotifyResize: {
          type: Function,
          value: function() {
            return this.notifyResize.bind(this);
          }
        },

        _boundUpdateTarget: {
          type: Function,
          value: function() {
            return this._updateTarget.bind(this);
          }
        }
      },

      attached: function() {
        // Polymer 2.x does not have this.offsetParent defined by attached
        requestAnimationFrame(this._boundUpdateTarget);
      },

      attributeChanged: function (name) {
        if (name === 'hidden') {
          this.updatePosition();
        }
      },

      _forChanged: function() {
        // The first time the property is set is before the badge is attached,
        // which means we're not ready to position it yet.
        if (!this.isAttached) {
          return;
        }
        this._updateTarget();
      },

      _labelChanged: function() {
        this.setAttribute('aria-label', this.label);
      },

      _updateTarget: function() {
        this._target = this.target;
        requestAnimationFrame(this._boundNotifyResize);
      },

      _computeIsIconBadge: function(icon) {
        return icon.length > 0;
      },

      /**
       * Returns the target element that this badge is anchored to. It is
       * either the element given by the `for` attribute, or the immediate parent
       * of the badge.
       */
      get target () {
        var parentNode = Polymer.dom(this).parentNode;
        // If the parentNode is a document fragment, then we need to use the host.
        var ownerRoot = Polymer.dom(this).getOwnerRoot();
        var target;

        if (this.for) {
          target = Polymer.dom(ownerRoot).querySelector('#' + this.for);
        } else {
          target = parentNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE ?
              ownerRoot.host : parentNode;
        }

        return target;
      },

      /**
       * Repositions the badge relative to its anchor element. This is called
       * automatically when the badge is attached or an `iron-resize` event is
       * fired (for exmaple if the window has resized, or your target is a
       * custom element that implements IronResizableBehavior).
       *
       * You should call this in all other cases when the achor's position
       * might have changed (for example, if it's visibility has changed, or
       * you've manually done a page re-layout).
       */
      updatePosition: function() {
        if (!this._target || !this.offsetParent) {
          return;
        }

        var parentRect = this.offsetParent.getBoundingClientRect();
        var targetRect = this._target.getBoundingClientRect();
        var thisRect = this.getBoundingClientRect();

        this.style.left = targetRect.left - parentRect.left +
            (targetRect.width - thisRect.width / 2) + 'px';
        this.style.top = targetRect.top - parentRect.top -
            (thisRect.height / 2) + 'px';
      }
    })
  </script>
</dom-module>
<script>
/**
 * `Polymer.ValueArrayFirebaseMixin` ensures the binding between array values and firebase objects.
 *
 * @demo demo/index.html
 */

ValueArrayFirebaseMixin = function(superClass) {
	return class extends superClass {
	    static get properties() {
			return  {
			    /**
			     * `valueObject` store value as object keys `{"value1": true, "value2": true}`
			     */
			    valueObject: {
			      type: Object,
			      notify: true
			    },
			
			    /**
			     * `valueArray` store value as array `["value1", "value2"]`
			     */
			    valueArray: {
			      type: Array,
			      notify: true
			    },
			
			    /**
			     * `valueJoin` joins values in a string
			     */
			    valueJoin: {
			      type: String,
			      notify: true,
			      readOnly: true,
			    }
			  }
	    }
	    static get observers() {
		     return [
			  '_observeValueObject(valueObject.*)',
			  '_observeValueArrayInit(valueArray)',
			  '_observeValueArray(valueArray.splices)',
			]
		}
		//called when we set/reset valueArray 
		_observeValueArrayInit(valueArray) {
		  this._isInitiatingValueArray = true;
		  if (valueArray && !this._isUpdatingValueObject) {
		    var o = {};
		    this.valueArray.forEach(function(item) {
		      o[item] = true;
		    });
		    this.set('valueObject', o);
		    this._setValueJoin(this.valueArray.join(', '));
		  }
		  delete this._isInitiatingValueArray;
		}

		/**
		 * `_observeValueArray` ensures valueArray and valueObject are in sync
		 */
		_observeValueArray(splices) {
		  if (!splices) {
		    return;
		  }
		  this._isUpdatingValueArray = true;
		  // console.log('ARRAY SPLICE', splices);
		  if (!this._isUpdatingValueObject) {
		    splices.indexSplices.forEach(function(splice) {
		      splice.removed.forEach(function(removed) {
		        this.set('valueObject.' + removed, null); // so that it is deleted at firebase level
		        this.dispatchEvent(new CustomEvent("tag-removed",{bubbles: true, composed: true,detail: removed}));
		      }, this);
		      if (splice.addedCount) {
		        for (var i = 0; i < splice.addedCount; i++) {
		          var index = splice.index + i;
		          var newValue = splice.object[index];
		          this.set('valueObject.' + newValue, true);
		          this.dispatchEvent(new CustomEvent("tag-added",{bubbles: true, composed: true,detail: newValue}));
		        }
		      }
		    }, this);
		
		    this._setValueJoin((this.valueArray || []).join(', '));
		  }
		  delete this._isUpdatingValueArray;
		}
		
		/**
		 * `_observeValueObject` ensures valueArray and valueObject are in sync
		 */
		_observeValueObject(obj) {
		  this._isUpdatingValueObject = true;
		  if (!this._isUpdatingValueArray && !this._isInitiatingValueArray) {
		    var keys = Object.keys(this.valueObject).filter(function(item) {
		      return !!item;
		    });
		    this.syncValueArrayWithKeys(keys);
		
		    this._setValueJoin(this.valueArray.join(', '));
		  }
		  delete this._isUpdatingValueObject;
		}
		
		syncValueArrayWithKeys(keys) {
		  if (!this.valueArray) {
		    this.valueArray = [];
		  }
		  // removed values not present in keys
		  var tmpArray = [].concat(this.valueArray);
		  tmpArray.forEach(function(v) {
		    if (keys.indexOf(v) < 0) {
		      this.splice('valueArray', this.valueArray.indexOf(v), 1);
		    }
		  }, this);
		  // add all missing keys
		  keys.forEach(function(k) {
		    if (this.valueArray.indexOf(k) < 0) {
		      this.push('valueArray', k);
		    }
		  }, this);
		}
	}
};
</script>
<script>
/**
 * `Polymer.PaperTagsMixin` defines recurent properties in paper-tags family (paper-tags, paper-tags-input, paper-tags-dropdown)
 *
 * @demo demo/index.html
 */
PaperTagsMixin = function(superClass) {
	return class extends ValueArrayFirebaseMixin(superClass) {
	    static get properties() {
			return  {
			    /**
			     * `itemClass` the class applied to tag items.  If `classAccessor` is defined, `classAccessor` will have precedence
			     */
			    itemClass: String,
			
			    /**
			     * `icon` icon to be applied to the tag item when it is not removable. If `iconAccessor` is defined, `iconAccessor` will have precedence
			     */
			    icon: String,
			
			    /**
			     * `keyPath` key for retrieving the `id` from item data object
			     */
			    keyPath: {
			      type: String,
			      value: 'id'
			    },
			    
			    /**
			     * `labelPath` key for retrieving the `label` from item data object
			     */
			    labelPath: {
			      type: String,
			      value: 'label'
			    },
			    
			    /**
			     * `classAccessor` key for retrieving the `class` from item data object
			     */
			    classAccessor: String,
			    
			    /**
			     * `iconAccessor` key for retrieving the `icon` from item data object
			     */
			    iconAccessor: String,
			
			    /**
			     * `preventRemoveTag` hide the `close` icon and prevent removing a tag when true. 
			     */
			    preventRemoveTag: {
			      type: Boolean,
			      value: false
			    }
			  }
		}
	}
}
</script>
<dom-module id="paper-tags" assetpath="bower_components/paper-tags/">

  <template>
  <style>
  [hidden] {
    display: none !important;
  }

  :host {
    display: block;
    @apply --paper-font-common-base;
    @apply --paper-tags;
  }
  
  div.paper-tag-item {
    margin-bottom: var(--paper-tag-margin, 3px);
    border: 1px solid var(--paper-tag-focus-color, var(--default-primary-color));
    font-size: 13px;
    color: var(--paper-tag-text-color, var(--secondary-text-color));
    border-radius: 4px;
    @apply --paper-tag-item;
  }
  
  .paper-tag-item:last-of-type {
    margin-right: var(--paper-tag-margin, 3px);
  }
  
  paper-icon-button {
    color: var(--paper-tag-focus-color,var(--default-primary-color));
    width: 20px;
    height: 20px;
    padding: 0;
  }
  
  .paper-tag-item-label {
    padding: var(--paper-tag-margin, 3px);
  }
  
  .paper-tag-item {
    display: inline-block;
  }
  </style>
    <template is="dom-repeat" id="tagRepeat" items="[[items]]" mutable-data="">
      <div class$="paper-tag-item [[_computeClass(item, classAccessor)]]">
        <span class="paper-tag-item-label">[[_computeLabel(item, labelPath)]]</span>
        <paper-icon-button icon="icons:close" hidden$="[[preventRemoveTag]]" on-tap="_removeTag"></paper-icon-button>
        <paper-icon-button icon="[[_computeIcon(item, iconAccessor)]]" hidden$="[[_isHidden(preventRemoveTag, iconAccessor)]]"></paper-icon-button>
      </div>
    </template>
  </template>
</dom-module>
<script>
class PaperTags extends Polymer.MutableData(PaperTagsMixin(Polymer.Element)) {
	static get is() {
	    return 'paper-tags'
	}
	static get properties() {
	    return {
		    /**
		     * `items` the Array of tags
		     */
		    items: {
		      type: Array,
		      notify: true,
		      value: function() {
		        return [];
		      }
		    }
  		}
	}
	
	_computeIcon(item, iconAccessor) {
	  return item[iconAccessor] || this.icon;
	}
	
	_computeClass(item, classAccessor) {
	  return item[classAccessor] || this.itemClass;
	}
	
	_computeLabel(item, labelPath) {
	  return item[labelPath] || item;
	}
	
	_isHidden(preventRemoveTag, iconAccessor) {
		if (!preventRemoveTag) {
			return true;
		} else if (preventRemoveTag && iconAccessor) {
			return false;
		} else {
			return true;
		}
	}

	_removeTag(event) {
	  var self = this;
	  if (this.readonly) {
	    return;
	  }
	  event.detail.isRemoved = true;
	  var model = event.model,
	    index = this.items.indexOf(model.item);
	
	  if (index > -1) {
	    event.stopPropagation();
	    var remove_func = function() {
		      self.splice('items', index, 1);
	    };
	    this._paper_tag_remove_item = Polymer.Debouncer.debounce(
	       this._paper_tag_remove_item, // initially undefined
	       Polymer.Async.timeOut.after(10),remove_func);
	    // this.items = this.items.slice();
	    // this.fire('tag-removed', model.item);
	  }
	}
};
customElements.define(PaperTags.is, PaperTags);

</script>
<dom-module id="paper-tags-input" assetpath="bower_components/paper-tags/">
  <template>
    <style>
    :host {
      display: block;
      --paper-tag-margin: 3px;
    }

    paper-input {
      --paper-input-container-input: {
        margin-bottom: var(--paper-tag-margin);
      }
    }

    paper-tags {
    }

    paper-tags[readonly] {
      opacity: 0.6;
    }

    paper-badge {
      display: inline-block;
      position: inherit;
    }

    .paper-tags-counter {
      display: inline-block;
    }
    </style>
    <paper-input id="tagInput" always-float-label="" on-keydown="_keyDown" readonly$="[[readonly]]" placeholder$="[[placeholder]]" disabled$="[[disabled]]" invalid="[[invalid]]" label="[[label]]" value="{{value}}" maxlength$="[[maxlength]]" error-message="[[errorMessage]]" minlength$="[[minlength]]">
      <slot name="[prefix]" slot="prefix"></slot>
      <paper-tags id="paperTags" readonly$="[[readonly]]" items="{{items}}" icon="[[icon]]" item-class="[[itemClass]]" class-accessor="[[classAccessor]]" label-path="[[labelPath]]" icon-accessor="[[iconAccessor]]" prevent-remove-tag="[[preventRemoveTag]]" slot="prefix"></paper-tags>
      <slot name="[suffix]" slot="suffix"></slot>
    </paper-input>
    <template is="dom-if" if="{{showCounter}}">
      <div class="paper-tags-counter">
        <paper-badge class="paper-tags-badge" label="[[items.length]]"></paper-badge> [[showCounter]]
      </div>
    </template>
  </template>
</dom-module>
<script>
class PaperTagsInput extends PaperTagsMixin(Polymer.Element) {
	static get is() {
	    return 'paper-tags-input'
	}
	static get properties() {
	    return {
		    /**
		     * `preventRemoveTag` hide the `close` icon and prevent removing a tag when true.
		     */
		    preventRemoveTag: {
		      type: Boolean,
		      value: false
		    },

		    /**
		     * `items` the Array of tags
		     */
		    items: {
		      type: Array,
		      notify: true
		    },
		    /**
		     * `delimiter` defaults to comma (,)
		     */
		    delimiter: {
		      type: String,
		      value: ','
		    },
		    /**
		     * `showCounter` display a paper-badge with `showCounter` as textContent
		     */
		    showCounter: String,

		    /**
		     * `maxTags` The maximum allowed number of tags (yet to be implemented)
		     */
		    maxTags: Number,

		    /**
		     * `minTags` The minimum allowed number of tags (yet to be implemented)
		     */
		    minTags: Number,

		    /**
		     * `tagTpl` a template Object used when creating new tags. If not defuned, new tags will jus be Strings
		     */
		    tagTpl: Object,

		    /**
		     * `value` the underlying paper-input value
		     */
		    value: {
		      type: String,
		      notify: true
		    },

		    /**
		     * `allowAdd` determines if new tags are allowed on pressing `Enter` or not.
		     */
		    allowAdd: {
		      type: Boolean,
		      value: true
		    }
	  }
	}
	static get observers() {
    	return [
		  '_observeTagItemsInit(items)',
		  '_observeTagItems(items.splices)'
		]
	}

	_observeTagItemsInit(items) {
	  if(items && !this.valueArray) {
	    this._observeTagItems(items)

	  }

	}
	_observeTagItems(splices) {
	  if(!splices) {
	    return
	  }
	  var keys = [];
	  this._isUpdatingingItems = true;
	  this.items.forEach(function(item) {
	    var id = item[this.keyPath] || item;
	    keys.push(id + '');
	  }, this);

	  this.syncValueArrayWithKeys(keys)

	  delete this._isUpdatingingItems;
	}

	_removeLast() {
    	this.splice('items', this.items.length - 1);
	}

  	removeAll () {
    	this.splice("items", 0);
  	}


    removeAll() {
        this.splice("items", 0);
    }

    removeTag (tag) {
    	var position = this.items.indexOf(tag);
    	if(position > -1) {
      		this.splice('items', position, 1 );
    	}
  	}

	findTag(tag) {
	  return this.items.includes(tag);
	}

	_addTag(tag) {
	  if (this.tagTpl) {
	    var id = tag;
	    tag = JSON.parse(JSON.stringify(this.tagTpl));
	    tag[this.keyPath] = id + '';
	    if (this.labelPath) {
	      tag[this.labelPath] = id;
	    }
	  }
	  this.push('items', tag);
	  this.items = this.items.slice();
	  // this.fire('tag-added', tag);
	}

	_keyDown(event) {
	  var keyVal = event.which;
	  if (keyVal === 13 && this.allowAdd) {
	    var tags = event.target.value.split(this.delimiter);
	    var me = this;

	    tags.forEach(function(tag) {
	     if (!me.findTag(tag)) {
	        me._addTag(tag);
	        event.target.value = '';
	      }
	    });

	  } else if (keyVal === 8 && event.target.value === '') {
	    this._removeLast();
	  }
	}
	/**
	 * Returns a reference to the focusable element.
	 */
	get_focusableElement() {
	  return this.$.tagInput.inputElement;
	}

};
customElements.define(PaperTagsInput.is, PaperTagsInput);
</script>
<dom-module id="paper-tags-dropdown" assetpath="bower_components/paper-tags/">
  <template>
    <style include="paper-dropdown-menu-shared-styles">
    :host: {
      display: block;
    }

    #filterCt {
      color: var(--default-primary-color);
      padding: 2px 8;
    }

    paper-listbox {
      min-width: 150px;
      @apply(--paper-listbox);
    }
    </style>
    <div role="button"></div>
    <paper-menu-button id="menuButton" vertical-align="[[verticalAlign]]" horizontal-align="[[horizontalAlign]]" horizontal-offset="[[horizontalOffset]]" vertical-offset="[[_computeMenuVerticalOffset(noLabelFloat, verticalOffset)]]" disabled="[[disabled]]" no-animations="[[noAnimations]]" opened="{{opened}}">
      <div slot="dropdown-trigger">
        <paper-ripple></paper-ripple>
        <iron-a11y-keys id="a11y" target="[[keyTarget]]" keys="[[_computeKeys()]]" on-keys-pressed="_onAscii"></iron-a11y-keys>
        <paper-tags-input max-tags="[[maxTags]]" prevent-remove-tag="[[preventRemoveTag]]" min-tags="[[minTags]]" id="tagInput" value-object="{{valueObject}}" value-array="{{valueArray}}" on-tap="_onTagTap" tag-tpl="[[tagTpl]]" items="[[tagItems]]" value="{{value}}" icon="[[icon]]" item-class="[[itemClass]]" class-accessor="[[classAccessor]]" label-path="[[labelPath]]" icon-accessor="[[iconAccessor]]" key-path="[[keyPath]]" invalid="[[invalid]]" readonly="[[readonly]]" disabled="[[disabled]]" placeholder="[[placeholder]]" error-message="[[errorMessage]]" label="[[label]]" show-counter="[[showCounter]]">
          <iron-icon icon="paper-dropdown-menu:arrow-drop-down" suffix=""></iron-icon>
        </paper-tags-input>
      </div>
      <paper-listbox slot="dropdown-content" disabled="[[disabled]]" selected-values="{{valueArray}}" attr-for-selected="name" multi="">
        <div id="filterCt" hidden$="[[!searchString]]">
          <span>filter:  {{searchString}}</span>
        </div>
        <template is="dom-repeat" items="[[items]]" filter="{{_computeFilter(searchString)}}">
          <paper-item name="[[_computeItemValue(item, keyPath)]]">[[_computeItemLabel(item, labelPath)]]</paper-item>
        </template>
      </paper-listbox>
      <content id="content" slot="dropdown-content"></content>
    </paper-menu-button>
  </template>
  <script>
  class PaperTagsDropdown extends Polymer.mixinBehaviors([Polymer.IronValidatableBehavior,Polymer.IronFormElementBehavior, Polymer.IronButtonState,Polymer.IronControlState],Polymer.Element) {
	static get is() {
	    return 'paper-tags-dropdown'
	}

	static get properties() {
	    return {
	        /**
	         * `valueObject` store value as object keys `{"value1": true, "value2": true}`
	         */
	        valueObject: {
	          type: Object,
	          notify: true,
			  observer: '_observeValueArray'
	        },

	        /**
	         * `valueArray` store value as array `["value1", "value2"]`
	         */
	        valueArray: {
	          type: Array,
	          notify: true,
	        },

	        keyPath: {
	          type: String,
	          value: 'id'
	        },

	        labelPath: {
	          type: String,
	          value: ''
	        },


	        searchString: {
	          type: String,
	          value: ''
	        },

	        items: {
	          type: Array,
	          value: function() {
	            return [];
	          }
	        },

	        tagItems: {
	          type: Array
	        },

	        /**
	         * When true, `showCounter` display a counter counting the number of tags
	         */
	        showCounter: {
	          type: String
	        },

	        /*
	         * `maxTags` the maximum allowed number of tags
	         */
	        maxTags: {
	          type: Number
	        },

	        /*
	         * `minTags` the minimum allowed number of tags
	         */
	        minTags: {
	          type: Number
	        },

	        /*
	         * `readonly` - reflects to `disabled` when set to true.
	         */
	        readonly: {
	          type: Boolean,
	          observer: '_observeReadonly'
	        },

	        /**
	         * `tagTpl` a template Object used when creating new tags (e.g. '{"id":null, "label":"hello"}'). If not defuned, new tags will jus be Strings
	         */
	        tagTpl: {
	          type: Object
	        },

	        keyTarget: {
	          type: Object,
	          // value: function() {
	          //   return this.$.tagInput;
	          // }
	        },

	        /**
	         * The derived "label" of the currently selected item. This value
	         * is the `label` property on the selected item if set, or else the
	         * trimmed text content of the selected item.
	         */
	        selectedItemLabel: {
	          type: String,
	          notify: true,
	          readOnly: true
	        },

	        /**
	         * The label for the dropdown.
	         */
	        label: {
	          type: String
	        },

	        /**
	         * The placeholder for the dropdown.
	         */
	        placeholder: {
	          type: String
	        },

	        /**
	         * The error message to display when invalid.
	         */
	        errorMessage: {
	          type: String
	        },

	        /**
	         * True if the dropdown is open. Otherwise, false.
	         */
	        opened: {
	          type: Boolean,
	          notify: true,
	          value: false,
	          observer: '_openedChanged'
	        },

	        /**
	         * Set to true to disable the floating label. Bind this to the
	         * `<paper-input-container>`'s `noLabelFloat` property.
	         */
	        noLabelFloat: {
	          type: Boolean,
	          value: false,
	          reflectToAttribute: true
	        },

	        /**
	         * Set to true to always float the label. Bind this to the
	         * `<paper-input-container>`'s `alwaysFloatLabel` property.
	         */
	        alwaysFloatLabel: {
	          type: Boolean,
	          value: false
	        },

	        /**
	         * Set to true to disable animations when opening and closing the
	         * dropdown.
	         */
	        noAnimations: {
	          type: Boolean,
	          value: false
	        },

	        /**
	         * The orientation against which to align the menu dropdown
	         * horizontally relative to the dropdown trigger.
	         */
	        horizontalAlign: {
	          type: String,
	          value: 'right'
	        },

            /**
            * Horizontal offset of the dropdown menu relative to the
            * dropdown trigger.
            */
            horizontalOffset: {
                type: Number,
                value: 0,
            },
	        /**
	         * The orientation against which to align the menu dropdown
	         * vertically relative to the dropdown trigger.
	         */
	        verticalAlign: {
	          type: String,
	          value: 'top'
	        },

          /**
           * Vertical offset of the dropdown menu relative to the
           * dropdown trigger.
           */
          verticalOffset: {
            type: Number,
            value: 0,
          }
		}
	}


	static get listeners() {
	     return {
		 'tap': '_onTap'
	     }
	}
	static get keyBindings() {
	     return{
		 'up down': 'open',
		  'esc': 'close'

		}
	}
	static get hostAttributes() {
	     return {
			role: 'combobox',
		  'aria-autocomplete': 'none',
		  'aria-haspopup': 'true'
		}
	}



	static get observers() {
	     return [
		 '_observeItems(items)',
		  '_observeValueArray(valueArray.splices)',
		]
	}

	_observeReadonly(readonly, old) {
	  if (readonly) {
	    this.disabled = true;
	    // remember that disabled property depends on readonly for run-time changes
	    this._disabledFromReadonly = true;
	    return;
	  }
	  if (old && readonly === false && this._disabledFromReadonly) {
	    this.disabled = false;
	  }
	}

	_observeItems(items) {
	  // run every time item is set ore reset
	  if (items) {
	    this._isInitiatingItems = true;
	    this.set('tagItems', this._filterValueItems(this.valueArray));

	    delete this._isInitiatingItems;
	  }
	}

	_observeValueArray(splices) {
		// run on any change to the valueArray
      	this.set('tagItems', this._filterValueItems(this.valueArray));
    }

	_filterValueItems(valueArray) {
		var filteredVals = [];
		if (valueArray && this.items && this.items.length > 0) {
		  filteredVals = this.items.filter(function(item) {
			var itemKey = this.keyPath;
			if (item[itemKey]) {
			  return this.valueArray.indexOf(item[itemKey] + '') > -1;
			}else {
			  return this.valueArray.indexOf(item) > -1;
			}
		  }, this);
		}
		return filteredVals;
	}

	ready() {
		if(super.ready){
		    super.ready();
		}
	    this.$.menuButton.ignoreSelect = true;
	    this.$.tagInput.allowAdd = false;
	    if (this.valueObject && !this.valueArray) {
	      this.set('valueArray', Object.keys(this.valueObject));
	    }
	}

	connectedCallback() {
		if(super.connectedCallback){
			super.connectedCallback();
		}
		this.keyTarget = this.$.tagInput;
		// NOTE(cdata): Due to timing, a preselected value in a `IronSelectable`
		// child will cause an `iron-select` event to fire while the element is
		// still in a `DocumentFragment`. This has the effect of causing
		// handlers not to fire. So, we double check this value on attached:
		var contentElement = this.contentElement;
		if (contentElement && contentElement.selectedItem) {
		  this._setSelectedItem(contentElement.selectedItem);
		}
	}


    /**
     * The content element that is contained by the dropdown menu, if any.
     */
    get contentElement() {
      return Polymer.dom(this.$.content).getDistributedNodes()[0];
    }

    /**
     * Show the dropdown content.
     */
    open() {
      this.$.menuButton.open();
    }

    /**
     * Hide the dropdown content.
     */
    close() {
      this.$.menuButton.close();
    }

    _computeItemValue(item, keyPath) {
      return (item[keyPath] || item) + '';
    }

    _computeItemLabel(item, labelPath) {
      if (!labelPath) {
      	return item;
      }

      var parts = labelPath.split(".");
      var label = item;
      parts.forEach(function(p) {
        if (label) {
          label = label[p];
        }
      });
      return label;
    }

    _computeKeys() {
      var s = ' ';
      for (var i = 48; i <= 126; i++) {
        s += String.fromCharCode(i) + ' ';
      }
      //adding backspace
      s += 'backspace' + ' ';
      return s;
    }

    _computeFilter(string) {
      // return a filter function for the current search string
      if (!string) {
        return null;
      }
      string = string.toLowerCase();
      var labelPath = this.labelPath;

      if (!labelPath) {
      	return function(item) {
      		return item && item.toLowerCase().indexOf(string) > -1;
      	};
      }

      var parts = labelPath.split('.');

      return function(item) {
        var label = item;
        parts.forEach(function(p) {
          if (label) {
            label = label[p];
          }
        });
        return label && label.toLowerCase().indexOf(string) > -1;
      };

    }

    /**
     * A handler that is called when the dropdown is tapped.
     *
     * @param {CustomEvent} event A tap event.
     */
    _onTap(event) {
      if (Polymer.Gestures.findOriginalTarget(event) === this) {
        this.open();
      }
    }

    _onTagTap(e) {
      if (e.detail.isRemoved || (e.srcElement.localName === 'input')) {
        e.stopPropagation();
      }
    }

    _onAscii(event) {
      this.open();
      this.async(function() {
        this.set('searchString', this.$.tagInput.value)
      }, 30);

    }

    /**
     * Compute the vertical offset of the menu based on the value of
     * `noLabelFloat`.
     *
     * @param {boolean} noLabelFloat True if the label should not float
     * above the input, otherwise false.
     */
    _computeMenuVerticalOffset(noLabelFloat) {
      // NOTE(cdata): These numbers are somewhat magical because they are
      // derived from the metrics of elements internal to `paper-input`'s
      // template. The metrics will change depending on whether or not the
      // input has a floating label.
        if (this.verticalOffset) {
          return this.verticalOffset;
        } else {
          return noLabelFloat ? -4 : 8;
        }
    }

    /**
     * Returns false if the element is required and does not have a selection,
     * and true otherwise.
     * @param {*=} _value Ignored.
     * @return {boolean} true if `required` is false, or if `required` is true
     * and the element has a valid selection.
     */
    _getValidity(_value) {
      return this.disabled || !this.required || (this.required && !!this.value);
    }

    _openedChanged() {
      var openState = this.opened ? 'true' : 'false';
      var e = this.contentElement;
      if (e) {
        e.setAttribute('aria-expanded', openState);
      }
    }
};

customElements.define(PaperTagsDropdown.is, PaperTagsDropdown);
  </script>
</dom-module>
<dom-module id="paper-toggle-button" assetpath="bower_components/paper-toggle-button/">
  <template strip-whitespace="">

    <style>
      :host {
        display: inline-block;
        @apply --layout-horizontal;
        @apply --layout-center;
        @apply --paper-font-common-base;
      }

      :host([disabled]) {
        pointer-events: none;
      }

      :host(:focus) {
        outline:none;
      }

      .toggle-bar {
        position: absolute;
        height: 100%;
        width: 100%;
        border-radius: 8px;
        pointer-events: none;
        opacity: 0.4;
        transition: background-color linear .08s;
        background-color: var(--paper-toggle-button-unchecked-bar-color, #000000);

        @apply --paper-toggle-button-unchecked-bar;
      }

      .toggle-button {
        position: absolute;
        top: -3px;
        left: 0;
        height: 20px;
        width: 20px;
        border-radius: 50%;
        box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.6);
        transition: -webkit-transform linear .08s, background-color linear .08s;
        transition: transform linear .08s, background-color linear .08s;
        will-change: transform;
        background-color: var(--paper-toggle-button-unchecked-button-color, var(--paper-grey-50));

        @apply --paper-toggle-button-unchecked-button;
      }

      .toggle-button.dragging {
        -webkit-transition: none;
        transition: none;
      }

      :host([checked]:not([disabled])) .toggle-bar {
        opacity: 0.5;
        background-color: var(--paper-toggle-button-checked-bar-color, var(--primary-color));

        @apply --paper-toggle-button-checked-bar;
      }

      :host([disabled]) .toggle-bar {
        background-color: #000;
        opacity: 0.12;
      }

      :host([checked]) .toggle-button {
        -webkit-transform: translate(16px, 0);
        transform: translate(16px, 0);
      }

      :host([checked]:not([disabled])) .toggle-button {
        background-color: var(--paper-toggle-button-checked-button-color, var(--primary-color));

        @apply --paper-toggle-button-checked-button;
      }

      :host([disabled]) .toggle-button {
        background-color: #bdbdbd;
        opacity: 1;
      }

      .toggle-ink {
        position: absolute;
        top: -14px;
        left: -14px;
        right: auto;
        bottom: auto;
        width: 48px;
        height: 48px;
        opacity: 0.5;
        pointer-events: none;
        color: var(--paper-toggle-button-unchecked-ink-color, var(--primary-text-color));

        @apply --paper-toggle-button-unchecked-ink;
      }

      :host([checked]) .toggle-ink {
        color: var(--paper-toggle-button-checked-ink-color, var(--primary-color));

        @apply --paper-toggle-button-checked-ink;
      }

      .toggle-container {
        display: inline-block;
        position: relative;
        width: 36px;
        height: 14px;
        /* The toggle button has an absolute position of -3px; The extra 1px
        /* accounts for the toggle button shadow box. */
        margin: 4px 1px;
      }

      .toggle-label {
        position: relative;
        display: inline-block;
        vertical-align: middle;
        padding-left: var(--paper-toggle-button-label-spacing, 8px);
        pointer-events: none;
        color: var(--paper-toggle-button-label-color, var(--primary-text-color));
      }

      /* invalid state */
      :host([invalid]) .toggle-bar {
        background-color: var(--paper-toggle-button-invalid-bar-color, var(--error-color));
      }

      :host([invalid]) .toggle-button {
        background-color: var(--paper-toggle-button-invalid-button-color, var(--error-color));
      }

      :host([invalid]) .toggle-ink {
        color: var(--paper-toggle-button-invalid-ink-color, var(--error-color));
      }
    </style>

    <div class="toggle-container">
      <div id="toggleBar" class="toggle-bar"></div>
      <div id="toggleButton" class="toggle-button"></div>
    </div>

    <div class="toggle-label"><slot></slot></div>

  </template>

  <script>
    Polymer({
      is: 'paper-toggle-button',

      behaviors: [Polymer.PaperCheckedElementBehavior],

      /** @private */
      hostAttributes: {role: 'button', 'aria-pressed': 'false', tabindex: 0},

      properties: {
          /**
           * Fired when the checked state changes due to user interaction.
           *
           * @event change
           */
          /**
           * Fired when the checked state changes.
           *
           * @event iron-change
           */
      },

      listeners: {track: '_ontrack'},

      attached: function() {
        Polymer.RenderStatus.afterNextRender(this, function() {
          Polymer.Gestures.setTouchAction(this, 'pan-y');
        });
      },

      _ontrack: function(event) {
        var track = event.detail;
        if (track.state === 'start') {
          this._trackStart(track);
        } else if (track.state === 'track') {
          this._trackMove(track);
        } else if (track.state === 'end') {
          this._trackEnd(track);
        }
      },

      _trackStart: function(track) {
        this._width = this.$.toggleBar.offsetWidth / 2;
        /*
         * keep an track-only check state to keep the dragging behavior smooth
         * while toggling activations
         */
        this._trackChecked = this.checked;
        this.$.toggleButton.classList.add('dragging');
      },

      _trackMove: function(track) {
        var dx = track.dx;
        this._x = Math.min(
            this._width, Math.max(0, this._trackChecked ? this._width + dx : dx));
        this.translate3d(this._x + 'px', 0, 0, this.$.toggleButton);
        this._userActivate(this._x > (this._width / 2));
      },

      _trackEnd: function(track) {
        this.$.toggleButton.classList.remove('dragging');
        this.transform('', this.$.toggleButton);
      },

      // customize the element's ripple
      _createRipple: function() {
        this._rippleContainer = this.$.toggleButton;
        var ripple = Polymer.PaperRippleBehavior._createRipple();
        ripple.id = 'ink';
        ripple.setAttribute('recenters', '');
        ripple.classList.add('circle', 'toggle-ink');
        return ripple;
      }

    });
  </script>
</dom-module>
<script>/**
 * @license
 * Lodash <https://lodash.com/>
 * Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
 * Released under MIT license <https://lodash.com/license>
 * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
 * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
 */
;(function() {

  /** Used as a safe reference for `undefined` in pre-ES5 environments. */
  var undefined;

  /** Used as the semantic version number. */
  var VERSION = '4.17.15';

  /** Used as the size to enable large array optimizations. */
  var LARGE_ARRAY_SIZE = 200;

  /** Error message constants. */
  var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.',
      FUNC_ERROR_TEXT = 'Expected a function';

  /** Used to stand-in for `undefined` hash values. */
  var HASH_UNDEFINED = '__lodash_hash_undefined__';

  /** Used as the maximum memoize cache size. */
  var MAX_MEMOIZE_SIZE = 500;

  /** Used as the internal argument placeholder. */
  var PLACEHOLDER = '__lodash_placeholder__';

  /** Used to compose bitmasks for cloning. */
  var CLONE_DEEP_FLAG = 1,
      CLONE_FLAT_FLAG = 2,
      CLONE_SYMBOLS_FLAG = 4;

  /** Used to compose bitmasks for value comparisons. */
  var COMPARE_PARTIAL_FLAG = 1,
      COMPARE_UNORDERED_FLAG = 2;

  /** Used to compose bitmasks for function metadata. */
  var WRAP_BIND_FLAG = 1,
      WRAP_BIND_KEY_FLAG = 2,
      WRAP_CURRY_BOUND_FLAG = 4,
      WRAP_CURRY_FLAG = 8,
      WRAP_CURRY_RIGHT_FLAG = 16,
      WRAP_PARTIAL_FLAG = 32,
      WRAP_PARTIAL_RIGHT_FLAG = 64,
      WRAP_ARY_FLAG = 128,
      WRAP_REARG_FLAG = 256,
      WRAP_FLIP_FLAG = 512;

  /** Used as default options for `_.truncate`. */
  var DEFAULT_TRUNC_LENGTH = 30,
      DEFAULT_TRUNC_OMISSION = '...';

  /** Used to detect hot functions by number of calls within a span of milliseconds. */
  var HOT_COUNT = 800,
      HOT_SPAN = 16;

  /** Used to indicate the type of lazy iteratees. */
  var LAZY_FILTER_FLAG = 1,
      LAZY_MAP_FLAG = 2,
      LAZY_WHILE_FLAG = 3;

  /** Used as references for various `Number` constants. */
  var INFINITY = 1 / 0,
      MAX_SAFE_INTEGER = 9007199254740991,
      MAX_INTEGER = 1.7976931348623157e+308,
      NAN = 0 / 0;

  /** Used as references for the maximum length and index of an array. */
  var MAX_ARRAY_LENGTH = 4294967295,
      MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1,
      HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;

  /** Used to associate wrap methods with their bit flags. */
  var wrapFlags = [
    ['ary', WRAP_ARY_FLAG],
    ['bind', WRAP_BIND_FLAG],
    ['bindKey', WRAP_BIND_KEY_FLAG],
    ['curry', WRAP_CURRY_FLAG],
    ['curryRight', WRAP_CURRY_RIGHT_FLAG],
    ['flip', WRAP_FLIP_FLAG],
    ['partial', WRAP_PARTIAL_FLAG],
    ['partialRight', WRAP_PARTIAL_RIGHT_FLAG],
    ['rearg', WRAP_REARG_FLAG]
  ];

  /** `Object#toString` result references. */
  var argsTag = '[object Arguments]',
      arrayTag = '[object Array]',
      asyncTag = '[object AsyncFunction]',
      boolTag = '[object Boolean]',
      dateTag = '[object Date]',
      domExcTag = '[object DOMException]',
      errorTag = '[object Error]',
      funcTag = '[object Function]',
      genTag = '[object GeneratorFunction]',
      mapTag = '[object Map]',
      numberTag = '[object Number]',
      nullTag = '[object Null]',
      objectTag = '[object Object]',
      promiseTag = '[object Promise]',
      proxyTag = '[object Proxy]',
      regexpTag = '[object RegExp]',
      setTag = '[object Set]',
      stringTag = '[object String]',
      symbolTag = '[object Symbol]',
      undefinedTag = '[object Undefined]',
      weakMapTag = '[object WeakMap]',
      weakSetTag = '[object WeakSet]';

  var arrayBufferTag = '[object ArrayBuffer]',
      dataViewTag = '[object DataView]',
      float32Tag = '[object Float32Array]',
      float64Tag = '[object Float64Array]',
      int8Tag = '[object Int8Array]',
      int16Tag = '[object Int16Array]',
      int32Tag = '[object Int32Array]',
      uint8Tag = '[object Uint8Array]',
      uint8ClampedTag = '[object Uint8ClampedArray]',
      uint16Tag = '[object Uint16Array]',
      uint32Tag = '[object Uint32Array]';

  /** Used to match empty string literals in compiled template source. */
  var reEmptyStringLeading = /\b__p \+= '';/g,
      reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
      reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;

  /** Used to match HTML entities and HTML characters. */
  var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g,
      reUnescapedHtml = /[&<>"']/g,
      reHasEscapedHtml = RegExp(reEscapedHtml.source),
      reHasUnescapedHtml = RegExp(reUnescapedHtml.source);

  /** Used to match template delimiters. */
  var reEscape = /<%-([\s\S]+?)%>/g,
      reEvaluate = /<%([\s\S]+?)%>/g,
      reInterpolate = /<%=([\s\S]+?)%>/g;

  /** Used to match property names within property paths. */
  var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
      reIsPlainProp = /^\w*$/,
      rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;

  /**
   * Used to match `RegExp`
   * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
   */
  var reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
      reHasRegExpChar = RegExp(reRegExpChar.source);

  /** Used to match leading and trailing whitespace. */
  var reTrim = /^\s+|\s+$/g,
      reTrimStart = /^\s+/,
      reTrimEnd = /\s+$/;

  /** Used to match wrap detail comments. */
  var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,
      reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/,
      reSplitDetails = /,? & /;

  /** Used to match words composed of alphanumeric characters. */
  var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;

  /** Used to match backslashes in property paths. */
  var reEscapeChar = /\\(\\)?/g;

  /**
   * Used to match
   * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components).
   */
  var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;

  /** Used to match `RegExp` flags from their coerced string values. */
  var reFlags = /\w*$/;

  /** Used to detect bad signed hexadecimal string values. */
  var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;

  /** Used to detect binary string values. */
  var reIsBinary = /^0b[01]+$/i;

  /** Used to detect host constructors (Safari). */
  var reIsHostCtor = /^\[object .+?Constructor\]$/;

  /** Used to detect octal string values. */
  var reIsOctal = /^0o[0-7]+$/i;

  /** Used to detect unsigned integer values. */
  var reIsUint = /^(?:0|[1-9]\d*)$/;

  /** Used to match Latin Unicode letters (excluding mathematical operators). */
  var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g;

  /** Used to ensure capturing order of template delimiters. */
  var reNoMatch = /($^)/;

  /** Used to match unescaped characters in compiled string literals. */
  var reUnescapedString = /['\n\r\u2028\u2029\\]/g;

  /** Used to compose unicode character classes. */
  var rsAstralRange = '\\ud800-\\udfff',
      rsComboMarksRange = '\\u0300-\\u036f',
      reComboHalfMarksRange = '\\ufe20-\\ufe2f',
      rsComboSymbolsRange = '\\u20d0-\\u20ff',
      rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange,
      rsDingbatRange = '\\u2700-\\u27bf',
      rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff',
      rsMathOpRange = '\\xac\\xb1\\xd7\\xf7',
      rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf',
      rsPunctuationRange = '\\u2000-\\u206f',
      rsSpaceRange = ' \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000',
      rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde',
      rsVarRange = '\\ufe0e\\ufe0f',
      rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange;

  /** Used to compose unicode capture groups. */
  var rsApos = "['\u2019]",
      rsAstral = '[' + rsAstralRange + ']',
      rsBreak = '[' + rsBreakRange + ']',
      rsCombo = '[' + rsComboRange + ']',
      rsDigits = '\\d+',
      rsDingbat = '[' + rsDingbatRange + ']',
      rsLower = '[' + rsLowerRange + ']',
      rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']',
      rsFitz = '\\ud83c[\\udffb-\\udfff]',
      rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')',
      rsNonAstral = '[^' + rsAstralRange + ']',
      rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}',
      rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]',
      rsUpper = '[' + rsUpperRange + ']',
      rsZWJ = '\\u200d';

  /** Used to compose unicode regexes. */
  var rsMiscLower = '(?:' + rsLower + '|' + rsMisc + ')',
      rsMiscUpper = '(?:' + rsUpper + '|' + rsMisc + ')',
      rsOptContrLower = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?',
      rsOptContrUpper = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?',
      reOptMod = rsModifier + '?',
      rsOptVar = '[' + rsVarRange + ']?',
      rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*',
      rsOrdLower = '\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])',
      rsOrdUpper = '\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])',
      rsSeq = rsOptVar + reOptMod + rsOptJoin,
      rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq,
      rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';

  /** Used to match apostrophes. */
  var reApos = RegExp(rsApos, 'g');

  /**
   * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and
   * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols).
   */
  var reComboMark = RegExp(rsCombo, 'g');

  /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
  var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');

  /** Used to match complex or compound words. */
  var reUnicodeWord = RegExp([
    rsUpper + '?' + rsLower + '+' + rsOptContrLower + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')',
    rsMiscUpper + '+' + rsOptContrUpper + '(?=' + [rsBreak, rsUpper + rsMiscLower, '$'].join('|') + ')',
    rsUpper + '?' + rsMiscLower + '+' + rsOptContrLower,
    rsUpper + '+' + rsOptContrUpper,
    rsOrdUpper,
    rsOrdLower,
    rsDigits,
    rsEmoji
  ].join('|'), 'g');

  /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
  var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange  + rsComboRange + rsVarRange + ']');

  /** Used to detect strings that need a more robust regexp to match words. */
  var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;

  /** Used to assign default `context` object properties. */
  var contextProps = [
    'Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array',
    'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object',
    'Promise', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError', 'Uint8Array',
    'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap',
    '_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout'
  ];

  /** Used to make template sourceURLs easier to identify. */
  var templateCounter = -1;

  /** Used to identify `toStringTag` values of typed arrays. */
  var typedArrayTags = {};
  typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
  typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
  typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
  typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
  typedArrayTags[uint32Tag] = true;
  typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
  typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
  typedArrayTags[dataViewTag] = typedArrayTags[dateTag] =
  typedArrayTags[errorTag] = typedArrayTags[funcTag] =
  typedArrayTags[mapTag] = typedArrayTags[numberTag] =
  typedArrayTags[objectTag] = typedArrayTags[regexpTag] =
  typedArrayTags[setTag] = typedArrayTags[stringTag] =
  typedArrayTags[weakMapTag] = false;

  /** Used to identify `toStringTag` values supported by `_.clone`. */
  var cloneableTags = {};
  cloneableTags[argsTag] = cloneableTags[arrayTag] =
  cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] =
  cloneableTags[boolTag] = cloneableTags[dateTag] =
  cloneableTags[float32Tag] = cloneableTags[float64Tag] =
  cloneableTags[int8Tag] = cloneableTags[int16Tag] =
  cloneableTags[int32Tag] = cloneableTags[mapTag] =
  cloneableTags[numberTag] = cloneableTags[objectTag] =
  cloneableTags[regexpTag] = cloneableTags[setTag] =
  cloneableTags[stringTag] = cloneableTags[symbolTag] =
  cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
  cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
  cloneableTags[errorTag] = cloneableTags[funcTag] =
  cloneableTags[weakMapTag] = false;

  /** Used to map Latin Unicode letters to basic Latin letters. */
  var deburredLetters = {
    // Latin-1 Supplement block.
    '\xc0': 'A',  '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A',
    '\xe0': 'a',  '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a',
    '\xc7': 'C',  '\xe7': 'c',
    '\xd0': 'D',  '\xf0': 'd',
    '\xc8': 'E',  '\xc9': 'E', '\xca': 'E', '\xcb': 'E',
    '\xe8': 'e',  '\xe9': 'e', '\xea': 'e', '\xeb': 'e',
    '\xcc': 'I',  '\xcd': 'I', '\xce': 'I', '\xcf': 'I',
    '\xec': 'i',  '\xed': 'i', '\xee': 'i', '\xef': 'i',
    '\xd1': 'N',  '\xf1': 'n',
    '\xd2': 'O',  '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O',
    '\xf2': 'o',  '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o',
    '\xd9': 'U',  '\xda': 'U', '\xdb': 'U', '\xdc': 'U',
    '\xf9': 'u',  '\xfa': 'u', '\xfb': 'u', '\xfc': 'u',
    '\xdd': 'Y',  '\xfd': 'y', '\xff': 'y',
    '\xc6': 'Ae', '\xe6': 'ae',
    '\xde': 'Th', '\xfe': 'th',
    '\xdf': 'ss',
    // Latin Extended-A block.
    '\u0100': 'A',  '\u0102': 'A', '\u0104': 'A',
    '\u0101': 'a',  '\u0103': 'a', '\u0105': 'a',
    '\u0106': 'C',  '\u0108': 'C', '\u010a': 'C', '\u010c': 'C',
    '\u0107': 'c',  '\u0109': 'c', '\u010b': 'c', '\u010d': 'c',
    '\u010e': 'D',  '\u0110': 'D', '\u010f': 'd', '\u0111': 'd',
    '\u0112': 'E',  '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E',
    '\u0113': 'e',  '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e',
    '\u011c': 'G',  '\u011e': 'G', '\u0120': 'G', '\u0122': 'G',
    '\u011d': 'g',  '\u011f': 'g', '\u0121': 'g', '\u0123': 'g',
    '\u0124': 'H',  '\u0126': 'H', '\u0125': 'h', '\u0127': 'h',
    '\u0128': 'I',  '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I',
    '\u0129': 'i',  '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i',
    '\u0134': 'J',  '\u0135': 'j',
    '\u0136': 'K',  '\u0137': 'k', '\u0138': 'k',
    '\u0139': 'L',  '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L',
    '\u013a': 'l',  '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l',
    '\u0143': 'N',  '\u0145': 'N', '\u0147': 'N', '\u014a': 'N',
    '\u0144': 'n',  '\u0146': 'n', '\u0148': 'n', '\u014b': 'n',
    '\u014c': 'O',  '\u014e': 'O', '\u0150': 'O',
    '\u014d': 'o',  '\u014f': 'o', '\u0151': 'o',
    '\u0154': 'R',  '\u0156': 'R', '\u0158': 'R',
    '\u0155': 'r',  '\u0157': 'r', '\u0159': 'r',
    '\u015a': 'S',  '\u015c': 'S', '\u015e': 'S', '\u0160': 'S',
    '\u015b': 's',  '\u015d': 's', '\u015f': 's', '\u0161': 's',
    '\u0162': 'T',  '\u0164': 'T', '\u0166': 'T',
    '\u0163': 't',  '\u0165': 't', '\u0167': 't',
    '\u0168': 'U',  '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U',
    '\u0169': 'u',  '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u',
    '\u0174': 'W',  '\u0175': 'w',
    '\u0176': 'Y',  '\u0177': 'y', '\u0178': 'Y',
    '\u0179': 'Z',  '\u017b': 'Z', '\u017d': 'Z',
    '\u017a': 'z',  '\u017c': 'z', '\u017e': 'z',
    '\u0132': 'IJ', '\u0133': 'ij',
    '\u0152': 'Oe', '\u0153': 'oe',
    '\u0149': "'n", '\u017f': 's'
  };

  /** Used to map characters to HTML entities. */
  var htmlEscapes = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#39;'
  };

  /** Used to map HTML entities to characters. */
  var htmlUnescapes = {
    '&amp;': '&',
    '&lt;': '<',
    '&gt;': '>',
    '&quot;': '"',
    '&#39;': "'"
  };

  /** Used to escape characters for inclusion in compiled string literals. */
  var stringEscapes = {
    '\\': '\\',
    "'": "'",
    '\n': 'n',
    '\r': 'r',
    '\u2028': 'u2028',
    '\u2029': 'u2029'
  };

  /** Built-in method references without a dependency on `root`. */
  var freeParseFloat = parseFloat,
      freeParseInt = parseInt;

  /** Detect free variable `global` from Node.js. */
  var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;

  /** Detect free variable `self`. */
  var freeSelf = typeof self == 'object' && self && self.Object === Object && self;

  /** Used as a reference to the global object. */
  var root = freeGlobal || freeSelf || Function('return this')();

  /** Detect free variable `exports`. */
  var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;

  /** Detect free variable `module`. */
  var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;

  /** Detect the popular CommonJS extension `module.exports`. */
  var moduleExports = freeModule && freeModule.exports === freeExports;

  /** Detect free variable `process` from Node.js. */
  var freeProcess = moduleExports && freeGlobal.process;

  /** Used to access faster Node.js helpers. */
  var nodeUtil = (function() {
    try {
      // Use `util.types` for Node.js 10+.
      var types = freeModule && freeModule.require && freeModule.require('util').types;

      if (types) {
        return types;
      }

      // Legacy `process.binding('util')` for Node.js < 10.
      return freeProcess && freeProcess.binding && freeProcess.binding('util');
    } catch (e) {}
  }());

  /* Node.js helper references. */
  var nodeIsArrayBuffer = nodeUtil && nodeUtil.isArrayBuffer,
      nodeIsDate = nodeUtil && nodeUtil.isDate,
      nodeIsMap = nodeUtil && nodeUtil.isMap,
      nodeIsRegExp = nodeUtil && nodeUtil.isRegExp,
      nodeIsSet = nodeUtil && nodeUtil.isSet,
      nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;

  /*--------------------------------------------------------------------------*/

  /**
   * A faster alternative to `Function#apply`, this function invokes `func`
   * with the `this` binding of `thisArg` and the arguments of `args`.
   *
   * @private
   * @param {Function} func The function to invoke.
   * @param {*} thisArg The `this` binding of `func`.
   * @param {Array} args The arguments to invoke `func` with.
   * @returns {*} Returns the result of `func`.
   */
  function apply(func, thisArg, args) {
    switch (args.length) {
      case 0: return func.call(thisArg);
      case 1: return func.call(thisArg, args[0]);
      case 2: return func.call(thisArg, args[0], args[1]);
      case 3: return func.call(thisArg, args[0], args[1], args[2]);
    }
    return func.apply(thisArg, args);
  }

  /**
   * A specialized version of `baseAggregator` for arrays.
   *
   * @private
   * @param {Array} [array] The array to iterate over.
   * @param {Function} setter The function to set `accumulator` values.
   * @param {Function} iteratee The iteratee to transform keys.
   * @param {Object} accumulator The initial aggregated object.
   * @returns {Function} Returns `accumulator`.
   */
  function arrayAggregator(array, setter, iteratee, accumulator) {
    var index = -1,
        length = array == null ? 0 : array.length;

    while (++index < length) {
      var value = array[index];
      setter(accumulator, value, iteratee(value), array);
    }
    return accumulator;
  }

  /**
   * A specialized version of `_.forEach` for arrays without support for
   * iteratee shorthands.
   *
   * @private
   * @param {Array} [array] The array to iterate over.
   * @param {Function} iteratee The function invoked per iteration.
   * @returns {Array} Returns `array`.
   */
  function arrayEach(array, iteratee) {
    var index = -1,
        length = array == null ? 0 : array.length;

    while (++index < length) {
      if (iteratee(array[index], index, array) === false) {
        break;
      }
    }
    return array;
  }

  /**
   * A specialized version of `_.forEachRight` for arrays without support for
   * iteratee shorthands.
   *
   * @private
   * @param {Array} [array] The array to iterate over.
   * @param {Function} iteratee The function invoked per iteration.
   * @returns {Array} Returns `array`.
   */
  function arrayEachRight(array, iteratee) {
    var length = array == null ? 0 : array.length;

    while (length--) {
      if (iteratee(array[length], length, array) === false) {
        break;
      }
    }
    return array;
  }

  /**
   * A specialized version of `_.every` for arrays without support for
   * iteratee shorthands.
   *
   * @private
   * @param {Array} [array] The array to iterate over.
   * @param {Function} predicate The function invoked per iteration.
   * @returns {boolean} Returns `true` if all elements pass the predicate check,
   *  else `false`.
   */
  function arrayEvery(array, predicate) {
    var index = -1,
        length = array == null ? 0 : array.length;

    while (++index < length) {
      if (!predicate(array[index], index, array)) {
        return false;
      }
    }
    return true;
  }

  /**
   * A specialized version of `_.filter` for arrays without support for
   * iteratee shorthands.
   *
   * @private
   * @param {Array} [array] The array to iterate over.
   * @param {Function} predicate The function invoked per iteration.
   * @returns {Array} Returns the new filtered array.
   */
  function arrayFilter(array, predicate) {
    var index = -1,
        length = array == null ? 0 : array.length,
        resIndex = 0,
        result = [];

    while (++index < length) {
      var value = array[index];
      if (predicate(value, index, array)) {
        result[resIndex++] = value;
      }
    }
    return result;
  }

  /**
   * A specialized version of `_.includes` for arrays without support for
   * specifying an index to search from.
   *
   * @private
   * @param {Array} [array] The array to inspect.
   * @param {*} target The value to search for.
   * @returns {boolean} Returns `true` if `target` is found, else `false`.
   */
  function arrayIncludes(array, value) {
    var length = array == null ? 0 : array.length;
    return !!length && baseIndexOf(array, value, 0) > -1;
  }

  /**
   * This function is like `arrayIncludes` except that it accepts a comparator.
   *
   * @private
   * @param {Array} [array] The array to inspect.
   * @param {*} target The value to search for.
   * @param {Function} comparator The comparator invoked per element.
   * @returns {boolean} Returns `true` if `target` is found, else `false`.
   */
  function arrayIncludesWith(array, value, comparator) {
    var index = -1,
        length = array == null ? 0 : array.length;

    while (++index < length) {
      if (comparator(value, array[index])) {
        return true;
      }
    }
    return false;
  }

  /**
   * A specialized version of `_.map` for arrays without support for iteratee
   * shorthands.
   *
   * @private
   * @param {Array} [array] The array to iterate over.
   * @param {Function} iteratee The function invoked per iteration.
   * @returns {Array} Returns the new mapped array.
   */
  function arrayMap(array, iteratee) {
    var index = -1,
        length = array == null ? 0 : array.length,
        result = Array(length);

    while (++index < length) {
      result[index] = iteratee(array[index], index, array);
    }
    return result;
  }

  /**
   * Appends the elements of `values` to `array`.
   *
   * @private
   * @param {Array} array The array to modify.
   * @param {Array} values The values to append.
   * @returns {Array} Returns `array`.
   */
  function arrayPush(array, values) {
    var index = -1,
        length = values.length,
        offset = array.length;

    while (++index < length) {
      array[offset + index] = values[index];
    }
    return array;
  }

  /**
   * A specialized version of `_.reduce` for arrays without support for
   * iteratee shorthands.
   *
   * @private
   * @param {Array} [array] The array to iterate over.
   * @param {Function} iteratee The function invoked per iteration.
   * @param {*} [accumulator] The initial value.
   * @param {boolean} [initAccum] Specify using the first element of `array` as
   *  the initial value.
   * @returns {*} Returns the accumulated value.
   */
  function arrayReduce(array, iteratee, accumulator, initAccum) {
    var index = -1,
        length = array == null ? 0 : array.length;

    if (initAccum && length) {
      accumulator = array[++index];
    }
    while (++index < length) {
      accumulator = iteratee(accumulator, array[index], index, array);
    }
    return accumulator;
  }

  /**
   * A specialized version of `_.reduceRight` for arrays without support for
   * iteratee shorthands.
   *
   * @private
   * @param {Array} [array] The array to iterate over.
   * @param {Function} iteratee The function invoked per iteration.
   * @param {*} [accumulator] The initial value.
   * @param {boolean} [initAccum] Specify using the last element of `array` as
   *  the initial value.
   * @returns {*} Returns the accumulated value.
   */
  function arrayReduceRight(array, iteratee, accumulator, initAccum) {
    var length = array == null ? 0 : array.length;
    if (initAccum && length) {
      accumulator = array[--length];
    }
    while (length--) {
      accumulator = iteratee(accumulator, array[length], length, array);
    }
    return accumulator;
  }

  /**
   * A specialized version of `_.some` for arrays without support for iteratee
   * shorthands.
   *
   * @private
   * @param {Array} [array] The array to iterate over.
   * @param {Function} predicate The function invoked per iteration.
   * @returns {boolean} Returns `true` if any element passes the predicate check,
   *  else `false`.
   */
  function arraySome(array, predicate) {
    var index = -1,
        length = array == null ? 0 : array.length;

    while (++index < length) {
      if (predicate(array[index], index, array)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Gets the size of an ASCII `string`.
   *
   * @private
   * @param {string} string The string inspect.
   * @returns {number} Returns the string size.
   */
  var asciiSize = baseProperty('length');

  /**
   * Converts an ASCII `string` to an array.
   *
   * @private
   * @param {string} string The string to convert.
   * @returns {Array} Returns the converted array.
   */
  function asciiToArray(string) {
    return string.split('');
  }

  /**
   * Splits an ASCII `string` into an array of its words.
   *
   * @private
   * @param {string} The string to inspect.
   * @returns {Array} Returns the words of `string`.
   */
  function asciiWords(string) {
    return string.match(reAsciiWord) || [];
  }

  /**
   * The base implementation of methods like `_.findKey` and `_.findLastKey`,
   * without support for iteratee shorthands, which iterates over `collection`
   * using `eachFunc`.
   *
   * @private
   * @param {Array|Object} collection The collection to inspect.
   * @param {Function} predicate The function invoked per iteration.
   * @param {Function} eachFunc The function to iterate over `collection`.
   * @returns {*} Returns the found element or its key, else `undefined`.
   */
  function baseFindKey(collection, predicate, eachFunc) {
    var result;
    eachFunc(collection, function(value, key, collection) {
      if (predicate(value, key, collection)) {
        result = key;
        return false;
      }
    });
    return result;
  }

  /**
   * The base implementation of `_.findIndex` and `_.findLastIndex` without
   * support for iteratee shorthands.
   *
   * @private
   * @param {Array} array The array to inspect.
   * @param {Function} predicate The function invoked per iteration.
   * @param {number} fromIndex The index to search from.
   * @param {boolean} [fromRight] Specify iterating from right to left.
   * @returns {number} Returns the index of the matched value, else `-1`.
   */
  function baseFindIndex(array, predicate, fromIndex, fromRight) {
    var length = array.length,
        index = fromIndex + (fromRight ? 1 : -1);

    while ((fromRight ? index-- : ++index < length)) {
      if (predicate(array[index], index, array)) {
        return index;
      }
    }
    return -1;
  }

  /**
   * The base implementation of `_.indexOf` without `fromIndex` bounds checks.
   *
   * @private
   * @param {Array} array The array to inspect.
   * @param {*} value The value to search for.
   * @param {number} fromIndex The index to search from.
   * @returns {number} Returns the index of the matched value, else `-1`.
   */
  function baseIndexOf(array, value, fromIndex) {
    return value === value
      ? strictIndexOf(array, value, fromIndex)
      : baseFindIndex(array, baseIsNaN, fromIndex);
  }

  /**
   * This function is like `baseIndexOf` except that it accepts a comparator.
   *
   * @private
   * @param {Array} array The array to inspect.
   * @param {*} value The value to search for.
   * @param {number} fromIndex The index to search from.
   * @param {Function} comparator The comparator invoked per element.
   * @returns {number} Returns the index of the matched value, else `-1`.
   */
  function baseIndexOfWith(array, value, fromIndex, comparator) {
    var index = fromIndex - 1,
        length = array.length;

    while (++index < length) {
      if (comparator(array[index], value)) {
        return index;
      }
    }
    return -1;
  }

  /**
   * The base implementation of `_.isNaN` without support for number objects.
   *
   * @private
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
   */
  function baseIsNaN(value) {
    return value !== value;
  }

  /**
   * The base implementation of `_.mean` and `_.meanBy` without support for
   * iteratee shorthands.
   *
   * @private
   * @param {Array} array The array to iterate over.
   * @param {Function} iteratee The function invoked per iteration.
   * @returns {number} Returns the mean.
   */
  function baseMean(array, iteratee) {
    var length = array == null ? 0 : array.length;
    return length ? (baseSum(array, iteratee) / length) : NAN;
  }

  /**
   * The base implementation of `_.property` without support for deep paths.
   *
   * @private
   * @param {string} key The key of the property to get.
   * @returns {Function} Returns the new accessor function.
   */
  function baseProperty(key) {
    return function(object) {
      return object == null ? undefined : object[key];
    };
  }

  /**
   * The base implementation of `_.propertyOf` without support for deep paths.
   *
   * @private
   * @param {Object} object The object to query.
   * @returns {Function} Returns the new accessor function.
   */
  function basePropertyOf(object) {
    return function(key) {
      return object == null ? undefined : object[key];
    };
  }

  /**
   * The base implementation of `_.reduce` and `_.reduceRight`, without support
   * for iteratee shorthands, which iterates over `collection` using `eachFunc`.
   *
   * @private
   * @param {Array|Object} collection The collection to iterate over.
   * @param {Function} iteratee The function invoked per iteration.
   * @param {*} accumulator The initial value.
   * @param {boolean} initAccum Specify using the first or last element of
   *  `collection` as the initial value.
   * @param {Function} eachFunc The function to iterate over `collection`.
   * @returns {*} Returns the accumulated value.
   */
  function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) {
    eachFunc(collection, function(value, index, collection) {
      accumulator = initAccum
        ? (initAccum = false, value)
        : iteratee(accumulator, value, index, collection);
    });
    return accumulator;
  }

  /**
   * The base implementation of `_.sortBy` which uses `comparer` to define the
   * sort order of `array` and replaces criteria objects with their corresponding
   * values.
   *
   * @private
   * @param {Array} array The array to sort.
   * @param {Function} comparer The function to define sort order.
   * @returns {Array} Returns `array`.
   */
  function baseSortBy(array, comparer) {
    var length = array.length;

    array.sort(comparer);
    while (length--) {
      array[length] = array[length].value;
    }
    return array;
  }

  /**
   * The base implementation of `_.sum` and `_.sumBy` without support for
   * iteratee shorthands.
   *
   * @private
   * @param {Array} array The array to iterate over.
   * @param {Function} iteratee The function invoked per iteration.
   * @returns {number} Returns the sum.
   */
  function baseSum(array, iteratee) {
    var result,
        index = -1,
        length = array.length;

    while (++index < length) {
      var current = iteratee(array[index]);
      if (current !== undefined) {
        result = result === undefined ? current : (result + current);
      }
    }
    return result;
  }

  /**
   * The base implementation of `_.times` without support for iteratee shorthands
   * or max array length checks.
   *
   * @private
   * @param {number} n The number of times to invoke `iteratee`.
   * @param {Function} iteratee The function invoked per iteration.
   * @returns {Array} Returns the array of results.
   */
  function baseTimes(n, iteratee) {
    var index = -1,
        result = Array(n);

    while (++index < n) {
      result[index] = iteratee(index);
    }
    return result;
  }

  /**
   * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array
   * of key-value pairs for `object` corresponding to the property names of `props`.
   *
   * @private
   * @param {Object} object The object to query.
   * @param {Array} props The property names to get values for.
   * @returns {Object} Returns the key-value pairs.
   */
  function baseToPairs(object, props) {
    return arrayMap(props, function(key) {
      return [key, object[key]];
    });
  }

  /**
   * The base implementation of `_.unary` without support for storing metadata.
   *
   * @private
   * @param {Function} func The function to cap arguments for.
   * @returns {Function} Returns the new capped function.
   */
  function baseUnary(func) {
    return function(value) {
      return func(value);
    };
  }

  /**
   * The base implementation of `_.values` and `_.valuesIn` which creates an
   * array of `object` property values corresponding to the property names
   * of `props`.
   *
   * @private
   * @param {Object} object The object to query.
   * @param {Array} props The property names to get values for.
   * @returns {Object} Returns the array of property values.
   */
  function baseValues(object, props) {
    return arrayMap(props, function(key) {
      return object[key];
    });
  }

  /**
   * Checks if a `cache` value for `key` exists.
   *
   * @private
   * @param {Object} cache The cache to query.
   * @param {string} key The key of the entry to check.
   * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
   */
  function cacheHas(cache, key) {
    return cache.has(key);
  }

  /**
   * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol
   * that is not found in the character symbols.
   *
   * @private
   * @param {Array} strSymbols The string symbols to inspect.
   * @param {Array} chrSymbols The character symbols to find.
   * @returns {number} Returns the index of the first unmatched string symbol.
   */
  function charsStartIndex(strSymbols, chrSymbols) {
    var index = -1,
        length = strSymbols.length;

    while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
    return index;
  }

  /**
   * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol
   * that is not found in the character symbols.
   *
   * @private
   * @param {Array} strSymbols The string symbols to inspect.
   * @param {Array} chrSymbols The character symbols to find.
   * @returns {number} Returns the index of the last unmatched string symbol.
   */
  function charsEndIndex(strSymbols, chrSymbols) {
    var index = strSymbols.length;

    while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
    return index;
  }

  /**
   * Gets the number of `placeholder` occurrences in `array`.
   *
   * @private
   * @param {Array} array The array to inspect.
   * @param {*} placeholder The placeholder to search for.
   * @returns {number} Returns the placeholder count.
   */
  function countHolders(array, placeholder) {
    var length = array.length,
        result = 0;

    while (length--) {
      if (array[length] === placeholder) {
        ++result;
      }
    }
    return result;
  }

  /**
   * Used by `_.deburr` to convert Latin-1 Supplement and Latin Extended-A
   * letters to basic Latin letters.
   *
   * @private
   * @param {string} letter The matched letter to deburr.
   * @returns {string} Returns the deburred letter.
   */
  var deburrLetter = basePropertyOf(deburredLetters);

  /**
   * Used by `_.escape` to convert characters to HTML entities.
   *
   * @private
   * @param {string} chr The matched character to escape.
   * @returns {string} Returns the escaped character.
   */
  var escapeHtmlChar = basePropertyOf(htmlEscapes);

  /**
   * Used by `_.template` to escape characters for inclusion in compiled string literals.
   *
   * @private
   * @param {string} chr The matched character to escape.
   * @returns {string} Returns the escaped character.
   */
  function escapeStringChar(chr) {
    return '\\' + stringEscapes[chr];
  }

  /**
   * Gets the value at `key` of `object`.
   *
   * @private
   * @param {Object} [object] The object to query.
   * @param {string} key The key of the property to get.
   * @returns {*} Returns the property value.
   */
  function getValue(object, key) {
    return object == null ? undefined : object[key];
  }

  /**
   * Checks if `string` contains Unicode symbols.
   *
   * @private
   * @param {string} string The string to inspect.
   * @returns {boolean} Returns `true` if a symbol is found, else `false`.
   */
  function hasUnicode(string) {
    return reHasUnicode.test(string);
  }

  /**
   * Checks if `string` contains a word composed of Unicode symbols.
   *
   * @private
   * @param {string} string The string to inspect.
   * @returns {boolean} Returns `true` if a word is found, else `false`.
   */
  function hasUnicodeWord(string) {
    return reHasUnicodeWord.test(string);
  }

  /**
   * Converts `iterator` to an array.
   *
   * @private
   * @param {Object} iterator The iterator to convert.
   * @returns {Array} Returns the converted array.
   */
  function iteratorToArray(iterator) {
    var data,
        result = [];

    while (!(data = iterator.next()).done) {
      result.push(data.value);
    }
    return result;
  }

  /**
   * Converts `map` to its key-value pairs.
   *
   * @private
   * @param {Object} map The map to convert.
   * @returns {Array} Returns the key-value pairs.
   */
  function mapToArray(map) {
    var index = -1,
        result = Array(map.size);

    map.forEach(function(value, key) {
      result[++index] = [key, value];
    });
    return result;
  }

  /**
   * Creates a unary function that invokes `func` with its argument transformed.
   *
   * @private
   * @param {Function} func The function to wrap.
   * @param {Function} transform The argument transform.
   * @returns {Function} Returns the new function.
   */
  function overArg(func, transform) {
    return function(arg) {
      return func(transform(arg));
    };
  }

  /**
   * Replaces all `placeholder` elements in `array` with an internal placeholder
   * and returns an array of their indexes.
   *
   * @private
   * @param {Array} array The array to modify.
   * @param {*} placeholder The placeholder to replace.
   * @returns {Array} Returns the new array of placeholder indexes.
   */
  function replaceHolders(array, placeholder) {
    var index = -1,
        length = array.length,
        resIndex = 0,
        result = [];

    while (++index < length) {
      var value = array[index];
      if (value === placeholder || value === PLACEHOLDER) {
        array[index] = PLACEHOLDER;
        result[resIndex++] = index;
      }
    }
    return result;
  }

  /**
   * Converts `set` to an array of its values.
   *
   * @private
   * @param {Object} set The set to convert.
   * @returns {Array} Returns the values.
   */
  function setToArray(set) {
    var index = -1,
        result = Array(set.size);

    set.forEach(function(value) {
      result[++index] = value;
    });
    return result;
  }

  /**
   * Converts `set` to its value-value pairs.
   *
   * @private
   * @param {Object} set The set to convert.
   * @returns {Array} Returns the value-value pairs.
   */
  function setToPairs(set) {
    var index = -1,
        result = Array(set.size);

    set.forEach(function(value) {
      result[++index] = [value, value];
    });
    return result;
  }

  /**
   * A specialized version of `_.indexOf` which performs strict equality
   * comparisons of values, i.e. `===`.
   *
   * @private
   * @param {Array} array The array to inspect.
   * @param {*} value The value to search for.
   * @param {number} fromIndex The index to search from.
   * @returns {number} Returns the index of the matched value, else `-1`.
   */
  function strictIndexOf(array, value, fromIndex) {
    var index = fromIndex - 1,
        length = array.length;

    while (++index < length) {
      if (array[index] === value) {
        return index;
      }
    }
    return -1;
  }

  /**
   * A specialized version of `_.lastIndexOf` which performs strict equality
   * comparisons of values, i.e. `===`.
   *
   * @private
   * @param {Array} array The array to inspect.
   * @param {*} value The value to search for.
   * @param {number} fromIndex The index to search from.
   * @returns {number} Returns the index of the matched value, else `-1`.
   */
  function strictLastIndexOf(array, value, fromIndex) {
    var index = fromIndex + 1;
    while (index--) {
      if (array[index] === value) {
        return index;
      }
    }
    return index;
  }

  /**
   * Gets the number of symbols in `string`.
   *
   * @private
   * @param {string} string The string to inspect.
   * @returns {number} Returns the string size.
   */
  function stringSize(string) {
    return hasUnicode(string)
      ? unicodeSize(string)
      : asciiSize(string);
  }

  /**
   * Converts `string` to an array.
   *
   * @private
   * @param {string} string The string to convert.
   * @returns {Array} Returns the converted array.
   */
  function stringToArray(string) {
    return hasUnicode(string)
      ? unicodeToArray(string)
      : asciiToArray(string);
  }

  /**
   * Used by `_.unescape` to convert HTML entities to characters.
   *
   * @private
   * @param {string} chr The matched character to unescape.
   * @returns {string} Returns the unescaped character.
   */
  var unescapeHtmlChar = basePropertyOf(htmlUnescapes);

  /**
   * Gets the size of a Unicode `string`.
   *
   * @private
   * @param {string} string The string inspect.
   * @returns {number} Returns the string size.
   */
  function unicodeSize(string) {
    var result = reUnicode.lastIndex = 0;
    while (reUnicode.test(string)) {
      ++result;
    }
    return result;
  }

  /**
   * Converts a Unicode `string` to an array.
   *
   * @private
   * @param {string} string The string to convert.
   * @returns {Array} Returns the converted array.
   */
  function unicodeToArray(string) {
    return string.match(reUnicode) || [];
  }

  /**
   * Splits a Unicode `string` into an array of its words.
   *
   * @private
   * @param {string} The string to inspect.
   * @returns {Array} Returns the words of `string`.
   */
  function unicodeWords(string) {
    return string.match(reUnicodeWord) || [];
  }

  /*--------------------------------------------------------------------------*/

  /**
   * Create a new pristine `lodash` function using the `context` object.
   *
   * @static
   * @memberOf _
   * @since 1.1.0
   * @category Util
   * @param {Object} [context=root] The context object.
   * @returns {Function} Returns a new `lodash` function.
   * @example
   *
   * _.mixin({ 'foo': _.constant('foo') });
   *
   * var lodash = _.runInContext();
   * lodash.mixin({ 'bar': lodash.constant('bar') });
   *
   * _.isFunction(_.foo);
   * // => true
   * _.isFunction(_.bar);
   * // => false
   *
   * lodash.isFunction(lodash.foo);
   * // => false
   * lodash.isFunction(lodash.bar);
   * // => true
   *
   * // Create a suped-up `defer` in Node.js.
   * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
   */
  var runInContext = (function runInContext(context) {
    context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps));

    /** Built-in constructor references. */
    var Array = context.Array,
        Date = context.Date,
        Error = context.Error,
        Function = context.Function,
        Math = context.Math,
        Object = context.Object,
        RegExp = context.RegExp,
        String = context.String,
        TypeError = context.TypeError;

    /** Used for built-in method references. */
    var arrayProto = Array.prototype,
        funcProto = Function.prototype,
        objectProto = Object.prototype;

    /** Used to detect overreaching core-js shims. */
    var coreJsData = context['__core-js_shared__'];

    /** Used to resolve the decompiled source of functions. */
    var funcToString = funcProto.toString;

    /** Used to check objects for own properties. */
    var hasOwnProperty = objectProto.hasOwnProperty;

    /** Used to generate unique IDs. */
    var idCounter = 0;

    /** Used to detect methods masquerading as native. */
    var maskSrcKey = (function() {
      var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
      return uid ? ('Symbol(src)_1.' + uid) : '';
    }());

    /**
     * Used to resolve the
     * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
     * of values.
     */
    var nativeObjectToString = objectProto.toString;

    /** Used to infer the `Object` constructor. */
    var objectCtorString = funcToString.call(Object);

    /** Used to restore the original `_` reference in `_.noConflict`. */
    var oldDash = root._;

    /** Used to detect if a method is native. */
    var reIsNative = RegExp('^' +
      funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
      .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
    );

    /** Built-in value references. */
    var Buffer = moduleExports ? context.Buffer : undefined,
        Symbol = context.Symbol,
        Uint8Array = context.Uint8Array,
        allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined,
        getPrototype = overArg(Object.getPrototypeOf, Object),
        objectCreate = Object.create,
        propertyIsEnumerable = objectProto.propertyIsEnumerable,
        splice = arrayProto.splice,
        spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined,
        symIterator = Symbol ? Symbol.iterator : undefined,
        symToStringTag = Symbol ? Symbol.toStringTag : undefined;

    var defineProperty = (function() {
      try {
        var func = getNative(Object, 'defineProperty');
        func({}, '', {});
        return func;
      } catch (e) {}
    }());

    /** Mocked built-ins. */
    var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout,
        ctxNow = Date && Date.now !== root.Date.now && Date.now,
        ctxSetTimeout = context.setTimeout !== root.setTimeout && context.setTimeout;

    /* Built-in method references for those with the same name as other `lodash` methods. */
    var nativeCeil = Math.ceil,
        nativeFloor = Math.floor,
        nativeGetSymbols = Object.getOwnPropertySymbols,
        nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined,
        nativeIsFinite = context.isFinite,
        nativeJoin = arrayProto.join,
        nativeKeys = overArg(Object.keys, Object),
        nativeMax = Math.max,
        nativeMin = Math.min,
        nativeNow = Date.now,
        nativeParseInt = context.parseInt,
        nativeRandom = Math.random,
        nativeReverse = arrayProto.reverse;

    /* Built-in method references that are verified to be native. */
    var DataView = getNative(context, 'DataView'),
        Map = getNative(context, 'Map'),
        Promise = getNative(context, 'Promise'),
        Set = getNative(context, 'Set'),
        WeakMap = getNative(context, 'WeakMap'),
        nativeCreate = getNative(Object, 'create');

    /** Used to store function metadata. */
    var metaMap = WeakMap && new WeakMap;

    /** Used to lookup unminified function names. */
    var realNames = {};

    /** Used to detect maps, sets, and weakmaps. */
    var dataViewCtorString = toSource(DataView),
        mapCtorString = toSource(Map),
        promiseCtorString = toSource(Promise),
        setCtorString = toSource(Set),
        weakMapCtorString = toSource(WeakMap);

    /** Used to convert symbols to primitives and strings. */
    var symbolProto = Symbol ? Symbol.prototype : undefined,
        symbolValueOf = symbolProto ? symbolProto.valueOf : undefined,
        symbolToString = symbolProto ? symbolProto.toString : undefined;

    /*------------------------------------------------------------------------*/

    /**
     * Creates a `lodash` object which wraps `value` to enable implicit method
     * chain sequences. Methods that operate on and return arrays, collections,
     * and functions can be chained together. Methods that retrieve a single value
     * or may return a primitive value will automatically end the chain sequence
     * and return the unwrapped value. Otherwise, the value must be unwrapped
     * with `_#value`.
     *
     * Explicit chain sequences, which must be unwrapped with `_#value`, may be
     * enabled using `_.chain`.
     *
     * The execution of chained methods is lazy, that is, it's deferred until
     * `_#value` is implicitly or explicitly called.
     *
     * Lazy evaluation allows several methods to support shortcut fusion.
     * Shortcut fusion is an optimization to merge iteratee calls; this avoids
     * the creation of intermediate arrays and can greatly reduce the number of
     * iteratee executions. Sections of a chain sequence qualify for shortcut
     * fusion if the section is applied to an array and iteratees accept only
     * one argument. The heuristic for whether a section qualifies for shortcut
     * fusion is subject to change.
     *
     * Chaining is supported in custom builds as long as the `_#value` method is
     * directly or indirectly included in the build.
     *
     * In addition to lodash methods, wrappers have `Array` and `String` methods.
     *
     * The wrapper `Array` methods are:
     * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift`
     *
     * The wrapper `String` methods are:
     * `replace` and `split`
     *
     * The wrapper methods that support shortcut fusion are:
     * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`,
     * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`,
     * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray`
     *
     * The chainable wrapper methods are:
     * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`,
     * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`,
     * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`,
     * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`,
     * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`,
     * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`,
     * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`,
     * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`,
     * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`,
     * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`,
     * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`,
     * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`,
     * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`,
     * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`,
     * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`,
     * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`,
     * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`,
     * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`,
     * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`,
     * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`,
     * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`,
     * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`,
     * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`,
     * `zipObject`, `zipObjectDeep`, and `zipWith`
     *
     * The wrapper methods that are **not** chainable by default are:
     * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`,
     * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`,
     * `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`,
     * `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`,
     * `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`,
     * `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`,
     * `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`,
     * `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`,
     * `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`,
     * `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`,
     * `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`,
     * `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`,
     * `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`,
     * `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`,
     * `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`,
     * `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`,
     * `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`,
     * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`,
     * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`,
     * `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`,
     * `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`,
     * `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`,
     * `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`,
     * `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`,
     * `upperFirst`, `value`, and `words`
     *
     * @name _
     * @constructor
     * @category Seq
     * @param {*} value The value to wrap in a `lodash` instance.
     * @returns {Object} Returns the new `lodash` wrapper instance.
     * @example
     *
     * function square(n) {
     *   return n * n;
     * }
     *
     * var wrapped = _([1, 2, 3]);
     *
     * // Returns an unwrapped value.
     * wrapped.reduce(_.add);
     * // => 6
     *
     * // Returns a wrapped value.
     * var squares = wrapped.map(square);
     *
     * _.isArray(squares);
     * // => false
     *
     * _.isArray(squares.value());
     * // => true
     */
    function lodash(value) {
      if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
        if (value instanceof LodashWrapper) {
          return value;
        }
        if (hasOwnProperty.call(value, '__wrapped__')) {
          return wrapperClone(value);
        }
      }
      return new LodashWrapper(value);
    }

    /**
     * The base implementation of `_.create` without support for assigning
     * properties to the created object.
     *
     * @private
     * @param {Object} proto The object to inherit from.
     * @returns {Object} Returns the new object.
     */
    var baseCreate = (function() {
      function object() {}
      return function(proto) {
        if (!isObject(proto)) {
          return {};
        }
        if (objectCreate) {
          return objectCreate(proto);
        }
        object.prototype = proto;
        var result = new object;
        object.prototype = undefined;
        return result;
      };
    }());

    /**
     * The function whose prototype chain sequence wrappers inherit from.
     *
     * @private
     */
    function baseLodash() {
      // No operation performed.
    }

    /**
     * The base constructor for creating `lodash` wrapper objects.
     *
     * @private
     * @param {*} value The value to wrap.
     * @param {boolean} [chainAll] Enable explicit method chain sequences.
     */
    function LodashWrapper(value, chainAll) {
      this.__wrapped__ = value;
      this.__actions__ = [];
      this.__chain__ = !!chainAll;
      this.__index__ = 0;
      this.__values__ = undefined;
    }

    /**
     * By default, the template delimiters used by lodash are like those in
     * embedded Ruby (ERB) as well as ES2015 template strings. Change the
     * following template settings to use alternative delimiters.
     *
     * @static
     * @memberOf _
     * @type {Object}
     */
    lodash.templateSettings = {

      /**
       * Used to detect `data` property values to be HTML-escaped.
       *
       * @memberOf _.templateSettings
       * @type {RegExp}
       */
      'escape': reEscape,

      /**
       * Used to detect code to be evaluated.
       *
       * @memberOf _.templateSettings
       * @type {RegExp}
       */
      'evaluate': reEvaluate,

      /**
       * Used to detect `data` property values to inject.
       *
       * @memberOf _.templateSettings
       * @type {RegExp}
       */
      'interpolate': reInterpolate,

      /**
       * Used to reference the data object in the template text.
       *
       * @memberOf _.templateSettings
       * @type {string}
       */
      'variable': '',

      /**
       * Used to import variables into the compiled template.
       *
       * @memberOf _.templateSettings
       * @type {Object}
       */
      'imports': {

        /**
         * A reference to the `lodash` function.
         *
         * @memberOf _.templateSettings.imports
         * @type {Function}
         */
        '_': lodash
      }
    };

    // Ensure wrappers are instances of `baseLodash`.
    lodash.prototype = baseLodash.prototype;
    lodash.prototype.constructor = lodash;

    LodashWrapper.prototype = baseCreate(baseLodash.prototype);
    LodashWrapper.prototype.constructor = LodashWrapper;

    /*------------------------------------------------------------------------*/

    /**
     * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
     *
     * @private
     * @constructor
     * @param {*} value The value to wrap.
     */
    function LazyWrapper(value) {
      this.__wrapped__ = value;
      this.__actions__ = [];
      this.__dir__ = 1;
      this.__filtered__ = false;
      this.__iteratees__ = [];
      this.__takeCount__ = MAX_ARRAY_LENGTH;
      this.__views__ = [];
    }

    /**
     * Creates a clone of the lazy wrapper object.
     *
     * @private
     * @name clone
     * @memberOf LazyWrapper
     * @returns {Object} Returns the cloned `LazyWrapper` object.
     */
    function lazyClone() {
      var result = new LazyWrapper(this.__wrapped__);
      result.__actions__ = copyArray(this.__actions__);
      result.__dir__ = this.__dir__;
      result.__filtered__ = this.__filtered__;
      result.__iteratees__ = copyArray(this.__iteratees__);
      result.__takeCount__ = this.__takeCount__;
      result.__views__ = copyArray(this.__views__);
      return result;
    }

    /**
     * Reverses the direction of lazy iteration.
     *
     * @private
     * @name reverse
     * @memberOf LazyWrapper
     * @returns {Object} Returns the new reversed `LazyWrapper` object.
     */
    function lazyReverse() {
      if (this.__filtered__) {
        var result = new LazyWrapper(this);
        result.__dir__ = -1;
        result.__filtered__ = true;
      } else {
        result = this.clone();
        result.__dir__ *= -1;
      }
      return result;
    }

    /**
     * Extracts the unwrapped value from its lazy wrapper.
     *
     * @private
     * @name value
     * @memberOf LazyWrapper
     * @returns {*} Returns the unwrapped value.
     */
    function lazyValue() {
      var array = this.__wrapped__.value(),
          dir = this.__dir__,
          isArr = isArray(array),
          isRight = dir < 0,
          arrLength = isArr ? array.length : 0,
          view = getView(0, arrLength, this.__views__),
          start = view.start,
          end = view.end,
          length = end - start,
          index = isRight ? end : (start - 1),
          iteratees = this.__iteratees__,
          iterLength = iteratees.length,
          resIndex = 0,
          takeCount = nativeMin(length, this.__takeCount__);

      if (!isArr || (!isRight && arrLength == length && takeCount == length)) {
        return baseWrapperValue(array, this.__actions__);
      }
      var result = [];

      outer:
      while (length-- && resIndex < takeCount) {
        index += dir;

        var iterIndex = -1,
            value = array[index];

        while (++iterIndex < iterLength) {
          var data = iteratees[iterIndex],
              iteratee = data.iteratee,
              type = data.type,
              computed = iteratee(value);

          if (type == LAZY_MAP_FLAG) {
            value = computed;
          } else if (!computed) {
            if (type == LAZY_FILTER_FLAG) {
              continue outer;
            } else {
              break outer;
            }
          }
        }
        result[resIndex++] = value;
      }
      return result;
    }

    // Ensure `LazyWrapper` is an instance of `baseLodash`.
    LazyWrapper.prototype = baseCreate(baseLodash.prototype);
    LazyWrapper.prototype.constructor = LazyWrapper;

    /*------------------------------------------------------------------------*/

    /**
     * Creates a hash object.
     *
     * @private
     * @constructor
     * @param {Array} [entries] The key-value pairs to cache.
     */
    function Hash(entries) {
      var index = -1,
          length = entries == null ? 0 : entries.length;

      this.clear();
      while (++index < length) {
        var entry = entries[index];
        this.set(entry[0], entry[1]);
      }
    }

    /**
     * Removes all key-value entries from the hash.
     *
     * @private
     * @name clear
     * @memberOf Hash
     */
    function hashClear() {
      this.__data__ = nativeCreate ? nativeCreate(null) : {};
      this.size = 0;
    }

    /**
     * Removes `key` and its value from the hash.
     *
     * @private
     * @name delete
     * @memberOf Hash
     * @param {Object} hash The hash to modify.
     * @param {string} key The key of the value to remove.
     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
     */
    function hashDelete(key) {
      var result = this.has(key) && delete this.__data__[key];
      this.size -= result ? 1 : 0;
      return result;
    }

    /**
     * Gets the hash value for `key`.
     *
     * @private
     * @name get
     * @memberOf Hash
     * @param {string} key The key of the value to get.
     * @returns {*} Returns the entry value.
     */
    function hashGet(key) {
      var data = this.__data__;
      if (nativeCreate) {
        var result = data[key];
        return result === HASH_UNDEFINED ? undefined : result;
      }
      return hasOwnProperty.call(data, key) ? data[key] : undefined;
    }

    /**
     * Checks if a hash value for `key` exists.
     *
     * @private
     * @name has
     * @memberOf Hash
     * @param {string} key The key of the entry to check.
     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
     */
    function hashHas(key) {
      var data = this.__data__;
      return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key);
    }

    /**
     * Sets the hash `key` to `value`.
     *
     * @private
     * @name set
     * @memberOf Hash
     * @param {string} key The key of the value to set.
     * @param {*} value The value to set.
     * @returns {Object} Returns the hash instance.
     */
    function hashSet(key, value) {
      var data = this.__data__;
      this.size += this.has(key) ? 0 : 1;
      data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
      return this;
    }

    // Add methods to `Hash`.
    Hash.prototype.clear = hashClear;
    Hash.prototype['delete'] = hashDelete;
    Hash.prototype.get = hashGet;
    Hash.prototype.has = hashHas;
    Hash.prototype.set = hashSet;

    /*------------------------------------------------------------------------*/

    /**
     * Creates an list cache object.
     *
     * @private
     * @constructor
     * @param {Array} [entries] The key-value pairs to cache.
     */
    function ListCache(entries) {
      var index = -1,
          length = entries == null ? 0 : entries.length;

      this.clear();
      while (++index < length) {
        var entry = entries[index];
        this.set(entry[0], entry[1]);
      }
    }

    /**
     * Removes all key-value entries from the list cache.
     *
     * @private
     * @name clear
     * @memberOf ListCache
     */
    function listCacheClear() {
      this.__data__ = [];
      this.size = 0;
    }

    /**
     * Removes `key` and its value from the list cache.
     *
     * @private
     * @name delete
     * @memberOf ListCache
     * @param {string} key The key of the value to remove.
     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
     */
    function listCacheDelete(key) {
      var data = this.__data__,
          index = assocIndexOf(data, key);

      if (index < 0) {
        return false;
      }
      var lastIndex = data.length - 1;
      if (index == lastIndex) {
        data.pop();
      } else {
        splice.call(data, index, 1);
      }
      --this.size;
      return true;
    }

    /**
     * Gets the list cache value for `key`.
     *
     * @private
     * @name get
     * @memberOf ListCache
     * @param {string} key The key of the value to get.
     * @returns {*} Returns the entry value.
     */
    function listCacheGet(key) {
      var data = this.__data__,
          index = assocIndexOf(data, key);

      return index < 0 ? undefined : data[index][1];
    }

    /**
     * Checks if a list cache value for `key` exists.
     *
     * @private
     * @name has
     * @memberOf ListCache
     * @param {string} key The key of the entry to check.
     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
     */
    function listCacheHas(key) {
      return assocIndexOf(this.__data__, key) > -1;
    }

    /**
     * Sets the list cache `key` to `value`.
     *
     * @private
     * @name set
     * @memberOf ListCache
     * @param {string} key The key of the value to set.
     * @param {*} value The value to set.
     * @returns {Object} Returns the list cache instance.
     */
    function listCacheSet(key, value) {
      var data = this.__data__,
          index = assocIndexOf(data, key);

      if (index < 0) {
        ++this.size;
        data.push([key, value]);
      } else {
        data[index][1] = value;
      }
      return this;
    }

    // Add methods to `ListCache`.
    ListCache.prototype.clear = listCacheClear;
    ListCache.prototype['delete'] = listCacheDelete;
    ListCache.prototype.get = listCacheGet;
    ListCache.prototype.has = listCacheHas;
    ListCache.prototype.set = listCacheSet;

    /*------------------------------------------------------------------------*/

    /**
     * Creates a map cache object to store key-value pairs.
     *
     * @private
     * @constructor
     * @param {Array} [entries] The key-value pairs to cache.
     */
    function MapCache(entries) {
      var index = -1,
          length = entries == null ? 0 : entries.length;

      this.clear();
      while (++index < length) {
        var entry = entries[index];
        this.set(entry[0], entry[1]);
      }
    }

    /**
     * Removes all key-value entries from the map.
     *
     * @private
     * @name clear
     * @memberOf MapCache
     */
    function mapCacheClear() {
      this.size = 0;
      this.__data__ = {
        'hash': new Hash,
        'map': new (Map || ListCache),
        'string': new Hash
      };
    }

    /**
     * Removes `key` and its value from the map.
     *
     * @private
     * @name delete
     * @memberOf MapCache
     * @param {string} key The key of the value to remove.
     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
     */
    function mapCacheDelete(key) {
      var result = getMapData(this, key)['delete'](key);
      this.size -= result ? 1 : 0;
      return result;
    }

    /**
     * Gets the map value for `key`.
     *
     * @private
     * @name get
     * @memberOf MapCache
     * @param {string} key The key of the value to get.
     * @returns {*} Returns the entry value.
     */
    function mapCacheGet(key) {
      return getMapData(this, key).get(key);
    }

    /**
     * Checks if a map value for `key` exists.
     *
     * @private
     * @name has
     * @memberOf MapCache
     * @param {string} key The key of the entry to check.
     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
     */
    function mapCacheHas(key) {
      return getMapData(this, key).has(key);
    }

    /**
     * Sets the map `key` to `value`.
     *
     * @private
     * @name set
     * @memberOf MapCache
     * @param {string} key The key of the value to set.
     * @param {*} value The value to set.
     * @returns {Object} Returns the map cache instance.
     */
    function mapCacheSet(key, value) {
      var data = getMapData(this, key),
          size = data.size;

      data.set(key, value);
      this.size += data.size == size ? 0 : 1;
      return this;
    }

    // Add methods to `MapCache`.
    MapCache.prototype.clear = mapCacheClear;
    MapCache.prototype['delete'] = mapCacheDelete;
    MapCache.prototype.get = mapCacheGet;
    MapCache.prototype.has = mapCacheHas;
    MapCache.prototype.set = mapCacheSet;

    /*------------------------------------------------------------------------*/

    /**
     *
     * Creates an array cache object to store unique values.
     *
     * @private
     * @constructor
     * @param {Array} [values] The values to cache.
     */
    function SetCache(values) {
      var index = -1,
          length = values == null ? 0 : values.length;

      this.__data__ = new MapCache;
      while (++index < length) {
        this.add(values[index]);
      }
    }

    /**
     * Adds `value` to the array cache.
     *
     * @private
     * @name add
     * @memberOf SetCache
     * @alias push
     * @param {*} value The value to cache.
     * @returns {Object} Returns the cache instance.
     */
    function setCacheAdd(value) {
      this.__data__.set(value, HASH_UNDEFINED);
      return this;
    }

    /**
     * Checks if `value` is in the array cache.
     *
     * @private
     * @name has
     * @memberOf SetCache
     * @param {*} value The value to search for.
     * @returns {number} Returns `true` if `value` is found, else `false`.
     */
    function setCacheHas(value) {
      return this.__data__.has(value);
    }

    // Add methods to `SetCache`.
    SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
    SetCache.prototype.has = setCacheHas;

    /*------------------------------------------------------------------------*/

    /**
     * Creates a stack cache object to store key-value pairs.
     *
     * @private
     * @constructor
     * @param {Array} [entries] The key-value pairs to cache.
     */
    function Stack(entries) {
      var data = this.__data__ = new ListCache(entries);
      this.size = data.size;
    }

    /**
     * Removes all key-value entries from the stack.
     *
     * @private
     * @name clear
     * @memberOf Stack
     */
    function stackClear() {
      this.__data__ = new ListCache;
      this.size = 0;
    }

    /**
     * Removes `key` and its value from the stack.
     *
     * @private
     * @name delete
     * @memberOf Stack
     * @param {string} key The key of the value to remove.
     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
     */
    function stackDelete(key) {
      var data = this.__data__,
          result = data['delete'](key);

      this.size = data.size;
      return result;
    }

    /**
     * Gets the stack value for `key`.
     *
     * @private
     * @name get
     * @memberOf Stack
     * @param {string} key The key of the value to get.
     * @returns {*} Returns the entry value.
     */
    function stackGet(key) {
      return this.__data__.get(key);
    }

    /**
     * Checks if a stack value for `key` exists.
     *
     * @private
     * @name has
     * @memberOf Stack
     * @param {string} key The key of the entry to check.
     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
     */
    function stackHas(key) {
      return this.__data__.has(key);
    }

    /**
     * Sets the stack `key` to `value`.
     *
     * @private
     * @name set
     * @memberOf Stack
     * @param {string} key The key of the value to set.
     * @param {*} value The value to set.
     * @returns {Object} Returns the stack cache instance.
     */
    function stackSet(key, value) {
      var data = this.__data__;
      if (data instanceof ListCache) {
        var pairs = data.__data__;
        if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) {
          pairs.push([key, value]);
          this.size = ++data.size;
          return this;
        }
        data = this.__data__ = new MapCache(pairs);
      }
      data.set(key, value);
      this.size = data.size;
      return this;
    }

    // Add methods to `Stack`.
    Stack.prototype.clear = stackClear;
    Stack.prototype['delete'] = stackDelete;
    Stack.prototype.get = stackGet;
    Stack.prototype.has = stackHas;
    Stack.prototype.set = stackSet;

    /*------------------------------------------------------------------------*/

    /**
     * Creates an array of the enumerable property names of the array-like `value`.
     *
     * @private
     * @param {*} value The value to query.
     * @param {boolean} inherited Specify returning inherited property names.
     * @returns {Array} Returns the array of property names.
     */
    function arrayLikeKeys(value, inherited) {
      var isArr = isArray(value),
          isArg = !isArr && isArguments(value),
          isBuff = !isArr && !isArg && isBuffer(value),
          isType = !isArr && !isArg && !isBuff && isTypedArray(value),
          skipIndexes = isArr || isArg || isBuff || isType,
          result = skipIndexes ? baseTimes(value.length, String) : [],
          length = result.length;

      for (var key in value) {
        if ((inherited || hasOwnProperty.call(value, key)) &&
            !(skipIndexes && (
               // Safari 9 has enumerable `arguments.length` in strict mode.
               key == 'length' ||
               // Node.js 0.10 has enumerable non-index properties on buffers.
               (isBuff && (key == 'offset' || key == 'parent')) ||
               // PhantomJS 2 has enumerable non-index properties on typed arrays.
               (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) ||
               // Skip index properties.
               isIndex(key, length)
            ))) {
          result.push(key);
        }
      }
      return result;
    }

    /**
     * A specialized version of `_.sample` for arrays.
     *
     * @private
     * @param {Array} array The array to sample.
     * @returns {*} Returns the random element.
     */
    function arraySample(array) {
      var length = array.length;
      return length ? array[baseRandom(0, length - 1)] : undefined;
    }

    /**
     * A specialized version of `_.sampleSize` for arrays.
     *
     * @private
     * @param {Array} array The array to sample.
     * @param {number} n The number of elements to sample.
     * @returns {Array} Returns the random elements.
     */
    function arraySampleSize(array, n) {
      return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length));
    }

    /**
     * A specialized version of `_.shuffle` for arrays.
     *
     * @private
     * @param {Array} array The array to shuffle.
     * @returns {Array} Returns the new shuffled array.
     */
    function arrayShuffle(array) {
      return shuffleSelf(copyArray(array));
    }

    /**
     * This function is like `assignValue` except that it doesn't assign
     * `undefined` values.
     *
     * @private
     * @param {Object} object The object to modify.
     * @param {string} key The key of the property to assign.
     * @param {*} value The value to assign.
     */
    function assignMergeValue(object, key, value) {
      if ((value !== undefined && !eq(object[key], value)) ||
          (value === undefined && !(key in object))) {
        baseAssignValue(object, key, value);
      }
    }

    /**
     * Assigns `value` to `key` of `object` if the existing value is not equivalent
     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
     * for equality comparisons.
     *
     * @private
     * @param {Object} object The object to modify.
     * @param {string} key The key of the property to assign.
     * @param {*} value The value to assign.
     */
    function assignValue(object, key, value) {
      var objValue = object[key];
      if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
          (value === undefined && !(key in object))) {
        baseAssignValue(object, key, value);
      }
    }

    /**
     * Gets the index at which the `key` is found in `array` of key-value pairs.
     *
     * @private
     * @param {Array} array The array to inspect.
     * @param {*} key The key to search for.
     * @returns {number} Returns the index of the matched value, else `-1`.
     */
    function assocIndexOf(array, key) {
      var length = array.length;
      while (length--) {
        if (eq(array[length][0], key)) {
          return length;
        }
      }
      return -1;
    }

    /**
     * Aggregates elements of `collection` on `accumulator` with keys transformed
     * by `iteratee` and values set by `setter`.
     *
     * @private
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} setter The function to set `accumulator` values.
     * @param {Function} iteratee The iteratee to transform keys.
     * @param {Object} accumulator The initial aggregated object.
     * @returns {Function} Returns `accumulator`.
     */
    function baseAggregator(collection, setter, iteratee, accumulator) {
      baseEach(collection, function(value, key, collection) {
        setter(accumulator, value, iteratee(value), collection);
      });
      return accumulator;
    }

    /**
     * The base implementation of `_.assign` without support for multiple sources
     * or `customizer` functions.
     *
     * @private
     * @param {Object} object The destination object.
     * @param {Object} source The source object.
     * @returns {Object} Returns `object`.
     */
    function baseAssign(object, source) {
      return object && copyObject(source, keys(source), object);
    }

    /**
     * The base implementation of `_.assignIn` without support for multiple sources
     * or `customizer` functions.
     *
     * @private
     * @param {Object} object The destination object.
     * @param {Object} source The source object.
     * @returns {Object} Returns `object`.
     */
    function baseAssignIn(object, source) {
      return object && copyObject(source, keysIn(source), object);
    }

    /**
     * The base implementation of `assignValue` and `assignMergeValue` without
     * value checks.
     *
     * @private
     * @param {Object} object The object to modify.
     * @param {string} key The key of the property to assign.
     * @param {*} value The value to assign.
     */
    function baseAssignValue(object, key, value) {
      if (key == '__proto__' && defineProperty) {
        defineProperty(object, key, {
          'configurable': true,
          'enumerable': true,
          'value': value,
          'writable': true
        });
      } else {
        object[key] = value;
      }
    }

    /**
     * The base implementation of `_.at` without support for individual paths.
     *
     * @private
     * @param {Object} object The object to iterate over.
     * @param {string[]} paths The property paths to pick.
     * @returns {Array} Returns the picked elements.
     */
    function baseAt(object, paths) {
      var index = -1,
          length = paths.length,
          result = Array(length),
          skip = object == null;

      while (++index < length) {
        result[index] = skip ? undefined : get(object, paths[index]);
      }
      return result;
    }

    /**
     * The base implementation of `_.clamp` which doesn't coerce arguments.
     *
     * @private
     * @param {number} number The number to clamp.
     * @param {number} [lower] The lower bound.
     * @param {number} upper The upper bound.
     * @returns {number} Returns the clamped number.
     */
    function baseClamp(number, lower, upper) {
      if (number === number) {
        if (upper !== undefined) {
          number = number <= upper ? number : upper;
        }
        if (lower !== undefined) {
          number = number >= lower ? number : lower;
        }
      }
      return number;
    }

    /**
     * The base implementation of `_.clone` and `_.cloneDeep` which tracks
     * traversed objects.
     *
     * @private
     * @param {*} value The value to clone.
     * @param {boolean} bitmask The bitmask flags.
     *  1 - Deep clone
     *  2 - Flatten inherited properties
     *  4 - Clone symbols
     * @param {Function} [customizer] The function to customize cloning.
     * @param {string} [key] The key of `value`.
     * @param {Object} [object] The parent object of `value`.
     * @param {Object} [stack] Tracks traversed objects and their clone counterparts.
     * @returns {*} Returns the cloned value.
     */
    function baseClone(value, bitmask, customizer, key, object, stack) {
      var result,
          isDeep = bitmask & CLONE_DEEP_FLAG,
          isFlat = bitmask & CLONE_FLAT_FLAG,
          isFull = bitmask & CLONE_SYMBOLS_FLAG;

      if (customizer) {
        result = object ? customizer(value, key, object, stack) : customizer(value);
      }
      if (result !== undefined) {
        return result;
      }
      if (!isObject(value)) {
        return value;
      }
      var isArr = isArray(value);
      if (isArr) {
        result = initCloneArray(value);
        if (!isDeep) {
          return copyArray(value, result);
        }
      } else {
        var tag = getTag(value),
            isFunc = tag == funcTag || tag == genTag;

        if (isBuffer(value)) {
          return cloneBuffer(value, isDeep);
        }
        if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
          result = (isFlat || isFunc) ? {} : initCloneObject(value);
          if (!isDeep) {
            return isFlat
              ? copySymbolsIn(value, baseAssignIn(result, value))
              : copySymbols(value, baseAssign(result, value));
          }
        } else {
          if (!cloneableTags[tag]) {
            return object ? value : {};
          }
          result = initCloneByTag(value, tag, isDeep);
        }
      }
      // Check for circular references and return its corresponding clone.
      stack || (stack = new Stack);
      var stacked = stack.get(value);
      if (stacked) {
        return stacked;
      }
      stack.set(value, result);

      if (isSet(value)) {
        value.forEach(function(subValue) {
          result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack));
        });
      } else if (isMap(value)) {
        value.forEach(function(subValue, key) {
          result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack));
        });
      }

      var keysFunc = isFull
        ? (isFlat ? getAllKeysIn : getAllKeys)
        : (isFlat ? keysIn : keys);

      var props = isArr ? undefined : keysFunc(value);
      arrayEach(props || value, function(subValue, key) {
        if (props) {
          key = subValue;
          subValue = value[key];
        }
        // Recursively populate clone (susceptible to call stack limits).
        assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack));
      });
      return result;
    }

    /**
     * The base implementation of `_.conforms` which doesn't clone `source`.
     *
     * @private
     * @param {Object} source The object of property predicates to conform to.
     * @returns {Function} Returns the new spec function.
     */
    function baseConforms(source) {
      var props = keys(source);
      return function(object) {
        return baseConformsTo(object, source, props);
      };
    }

    /**
     * The base implementation of `_.conformsTo` which accepts `props` to check.
     *
     * @private
     * @param {Object} object The object to inspect.
     * @param {Object} source The object of property predicates to conform to.
     * @returns {boolean} Returns `true` if `object` conforms, else `false`.
     */
    function baseConformsTo(object, source, props) {
      var length = props.length;
      if (object == null) {
        return !length;
      }
      object = Object(object);
      while (length--) {
        var key = props[length],
            predicate = source[key],
            value = object[key];

        if ((value === undefined && !(key in object)) || !predicate(value)) {
          return false;
        }
      }
      return true;
    }

    /**
     * The base implementation of `_.delay` and `_.defer` which accepts `args`
     * to provide to `func`.
     *
     * @private
     * @param {Function} func The function to delay.
     * @param {number} wait The number of milliseconds to delay invocation.
     * @param {Array} args The arguments to provide to `func`.
     * @returns {number|Object} Returns the timer id or timeout object.
     */
    function baseDelay(func, wait, args) {
      if (typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      return setTimeout(function() { func.apply(undefined, args); }, wait);
    }

    /**
     * The base implementation of methods like `_.difference` without support
     * for excluding multiple arrays or iteratee shorthands.
     *
     * @private
     * @param {Array} array The array to inspect.
     * @param {Array} values The values to exclude.
     * @param {Function} [iteratee] The iteratee invoked per element.
     * @param {Function} [comparator] The comparator invoked per element.
     * @returns {Array} Returns the new array of filtered values.
     */
    function baseDifference(array, values, iteratee, comparator) {
      var index = -1,
          includes = arrayIncludes,
          isCommon = true,
          length = array.length,
          result = [],
          valuesLength = values.length;

      if (!length) {
        return result;
      }
      if (iteratee) {
        values = arrayMap(values, baseUnary(iteratee));
      }
      if (comparator) {
        includes = arrayIncludesWith;
        isCommon = false;
      }
      else if (values.length >= LARGE_ARRAY_SIZE) {
        includes = cacheHas;
        isCommon = false;
        values = new SetCache(values);
      }
      outer:
      while (++index < length) {
        var value = array[index],
            computed = iteratee == null ? value : iteratee(value);

        value = (comparator || value !== 0) ? value : 0;
        if (isCommon && computed === computed) {
          var valuesIndex = valuesLength;
          while (valuesIndex--) {
            if (values[valuesIndex] === computed) {
              continue outer;
            }
          }
          result.push(value);
        }
        else if (!includes(values, computed, comparator)) {
          result.push(value);
        }
      }
      return result;
    }

    /**
     * The base implementation of `_.forEach` without support for iteratee shorthands.
     *
     * @private
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {Array|Object} Returns `collection`.
     */
    var baseEach = createBaseEach(baseForOwn);

    /**
     * The base implementation of `_.forEachRight` without support for iteratee shorthands.
     *
     * @private
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {Array|Object} Returns `collection`.
     */
    var baseEachRight = createBaseEach(baseForOwnRight, true);

    /**
     * The base implementation of `_.every` without support for iteratee shorthands.
     *
     * @private
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} predicate The function invoked per iteration.
     * @returns {boolean} Returns `true` if all elements pass the predicate check,
     *  else `false`
     */
    function baseEvery(collection, predicate) {
      var result = true;
      baseEach(collection, function(value, index, collection) {
        result = !!predicate(value, index, collection);
        return result;
      });
      return result;
    }

    /**
     * The base implementation of methods like `_.max` and `_.min` which accepts a
     * `comparator` to determine the extremum value.
     *
     * @private
     * @param {Array} array The array to iterate over.
     * @param {Function} iteratee The iteratee invoked per iteration.
     * @param {Function} comparator The comparator used to compare values.
     * @returns {*} Returns the extremum value.
     */
    function baseExtremum(array, iteratee, comparator) {
      var index = -1,
          length = array.length;

      while (++index < length) {
        var value = array[index],
            current = iteratee(value);

        if (current != null && (computed === undefined
              ? (current === current && !isSymbol(current))
              : comparator(current, computed)
            )) {
          var computed = current,
              result = value;
        }
      }
      return result;
    }

    /**
     * The base implementation of `_.fill` without an iteratee call guard.
     *
     * @private
     * @param {Array} array The array to fill.
     * @param {*} value The value to fill `array` with.
     * @param {number} [start=0] The start position.
     * @param {number} [end=array.length] The end position.
     * @returns {Array} Returns `array`.
     */
    function baseFill(array, value, start, end) {
      var length = array.length;

      start = toInteger(start);
      if (start < 0) {
        start = -start > length ? 0 : (length + start);
      }
      end = (end === undefined || end > length) ? length : toInteger(end);
      if (end < 0) {
        end += length;
      }
      end = start > end ? 0 : toLength(end);
      while (start < end) {
        array[start++] = value;
      }
      return array;
    }

    /**
     * The base implementation of `_.filter` without support for iteratee shorthands.
     *
     * @private
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} predicate The function invoked per iteration.
     * @returns {Array} Returns the new filtered array.
     */
    function baseFilter(collection, predicate) {
      var result = [];
      baseEach(collection, function(value, index, collection) {
        if (predicate(value, index, collection)) {
          result.push(value);
        }
      });
      return result;
    }

    /**
     * The base implementation of `_.flatten` with support for restricting flattening.
     *
     * @private
     * @param {Array} array The array to flatten.
     * @param {number} depth The maximum recursion depth.
     * @param {boolean} [predicate=isFlattenable] The function invoked per iteration.
     * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks.
     * @param {Array} [result=[]] The initial result value.
     * @returns {Array} Returns the new flattened array.
     */
    function baseFlatten(array, depth, predicate, isStrict, result) {
      var index = -1,
          length = array.length;

      predicate || (predicate = isFlattenable);
      result || (result = []);

      while (++index < length) {
        var value = array[index];
        if (depth > 0 && predicate(value)) {
          if (depth > 1) {
            // Recursively flatten arrays (susceptible to call stack limits).
            baseFlatten(value, depth - 1, predicate, isStrict, result);
          } else {
            arrayPush(result, value);
          }
        } else if (!isStrict) {
          result[result.length] = value;
        }
      }
      return result;
    }

    /**
     * The base implementation of `baseForOwn` which iterates over `object`
     * properties returned by `keysFunc` and invokes `iteratee` for each property.
     * Iteratee functions may exit iteration early by explicitly returning `false`.
     *
     * @private
     * @param {Object} object The object to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @param {Function} keysFunc The function to get the keys of `object`.
     * @returns {Object} Returns `object`.
     */
    var baseFor = createBaseFor();

    /**
     * This function is like `baseFor` except that it iterates over properties
     * in the opposite order.
     *
     * @private
     * @param {Object} object The object to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @param {Function} keysFunc The function to get the keys of `object`.
     * @returns {Object} Returns `object`.
     */
    var baseForRight = createBaseFor(true);

    /**
     * The base implementation of `_.forOwn` without support for iteratee shorthands.
     *
     * @private
     * @param {Object} object The object to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {Object} Returns `object`.
     */
    function baseForOwn(object, iteratee) {
      return object && baseFor(object, iteratee, keys);
    }

    /**
     * The base implementation of `_.forOwnRight` without support for iteratee shorthands.
     *
     * @private
     * @param {Object} object The object to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {Object} Returns `object`.
     */
    function baseForOwnRight(object, iteratee) {
      return object && baseForRight(object, iteratee, keys);
    }

    /**
     * The base implementation of `_.functions` which creates an array of
     * `object` function property names filtered from `props`.
     *
     * @private
     * @param {Object} object The object to inspect.
     * @param {Array} props The property names to filter.
     * @returns {Array} Returns the function names.
     */
    function baseFunctions(object, props) {
      return arrayFilter(props, function(key) {
        return isFunction(object[key]);
      });
    }

    /**
     * The base implementation of `_.get` without support for default values.
     *
     * @private
     * @param {Object} object The object to query.
     * @param {Array|string} path The path of the property to get.
     * @returns {*} Returns the resolved value.
     */
    function baseGet(object, path) {
      path = castPath(path, object);

      var index = 0,
          length = path.length;

      while (object != null && index < length) {
        object = object[toKey(path[index++])];
      }
      return (index && index == length) ? object : undefined;
    }

    /**
     * The base implementation of `getAllKeys` and `getAllKeysIn` which uses
     * `keysFunc` and `symbolsFunc` to get the enumerable property names and
     * symbols of `object`.
     *
     * @private
     * @param {Object} object The object to query.
     * @param {Function} keysFunc The function to get the keys of `object`.
     * @param {Function} symbolsFunc The function to get the symbols of `object`.
     * @returns {Array} Returns the array of property names and symbols.
     */
    function baseGetAllKeys(object, keysFunc, symbolsFunc) {
      var result = keysFunc(object);
      return isArray(object) ? result : arrayPush(result, symbolsFunc(object));
    }

    /**
     * The base implementation of `getTag` without fallbacks for buggy environments.
     *
     * @private
     * @param {*} value The value to query.
     * @returns {string} Returns the `toStringTag`.
     */
    function baseGetTag(value) {
      if (value == null) {
        return value === undefined ? undefinedTag : nullTag;
      }
      return (symToStringTag && symToStringTag in Object(value))
        ? getRawTag(value)
        : objectToString(value);
    }

    /**
     * The base implementation of `_.gt` which doesn't coerce arguments.
     *
     * @private
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @returns {boolean} Returns `true` if `value` is greater than `other`,
     *  else `false`.
     */
    function baseGt(value, other) {
      return value > other;
    }

    /**
     * The base implementation of `_.has` without support for deep paths.
     *
     * @private
     * @param {Object} [object] The object to query.
     * @param {Array|string} key The key to check.
     * @returns {boolean} Returns `true` if `key` exists, else `false`.
     */
    function baseHas(object, key) {
      return object != null && hasOwnProperty.call(object, key);
    }

    /**
     * The base implementation of `_.hasIn` without support for deep paths.
     *
     * @private
     * @param {Object} [object] The object to query.
     * @param {Array|string} key The key to check.
     * @returns {boolean} Returns `true` if `key` exists, else `false`.
     */
    function baseHasIn(object, key) {
      return object != null && key in Object(object);
    }

    /**
     * The base implementation of `_.inRange` which doesn't coerce arguments.
     *
     * @private
     * @param {number} number The number to check.
     * @param {number} start The start of the range.
     * @param {number} end The end of the range.
     * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
     */
    function baseInRange(number, start, end) {
      return number >= nativeMin(start, end) && number < nativeMax(start, end);
    }

    /**
     * The base implementation of methods like `_.intersection`, without support
     * for iteratee shorthands, that accepts an array of arrays to inspect.
     *
     * @private
     * @param {Array} arrays The arrays to inspect.
     * @param {Function} [iteratee] The iteratee invoked per element.
     * @param {Function} [comparator] The comparator invoked per element.
     * @returns {Array} Returns the new array of shared values.
     */
    function baseIntersection(arrays, iteratee, comparator) {
      var includes = comparator ? arrayIncludesWith : arrayIncludes,
          length = arrays[0].length,
          othLength = arrays.length,
          othIndex = othLength,
          caches = Array(othLength),
          maxLength = Infinity,
          result = [];

      while (othIndex--) {
        var array = arrays[othIndex];
        if (othIndex && iteratee) {
          array = arrayMap(array, baseUnary(iteratee));
        }
        maxLength = nativeMin(array.length, maxLength);
        caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120))
          ? new SetCache(othIndex && array)
          : undefined;
      }
      array = arrays[0];

      var index = -1,
          seen = caches[0];

      outer:
      while (++index < length && result.length < maxLength) {
        var value = array[index],
            computed = iteratee ? iteratee(value) : value;

        value = (comparator || value !== 0) ? value : 0;
        if (!(seen
              ? cacheHas(seen, computed)
              : includes(result, computed, comparator)
            )) {
          othIndex = othLength;
          while (--othIndex) {
            var cache = caches[othIndex];
            if (!(cache
                  ? cacheHas(cache, computed)
                  : includes(arrays[othIndex], computed, comparator))
                ) {
              continue outer;
            }
          }
          if (seen) {
            seen.push(computed);
          }
          result.push(value);
        }
      }
      return result;
    }

    /**
     * The base implementation of `_.invert` and `_.invertBy` which inverts
     * `object` with values transformed by `iteratee` and set by `setter`.
     *
     * @private
     * @param {Object} object The object to iterate over.
     * @param {Function} setter The function to set `accumulator` values.
     * @param {Function} iteratee The iteratee to transform values.
     * @param {Object} accumulator The initial inverted object.
     * @returns {Function} Returns `accumulator`.
     */
    function baseInverter(object, setter, iteratee, accumulator) {
      baseForOwn(object, function(value, key, object) {
        setter(accumulator, iteratee(value), key, object);
      });
      return accumulator;
    }

    /**
     * The base implementation of `_.invoke` without support for individual
     * method arguments.
     *
     * @private
     * @param {Object} object The object to query.
     * @param {Array|string} path The path of the method to invoke.
     * @param {Array} args The arguments to invoke the method with.
     * @returns {*} Returns the result of the invoked method.
     */
    function baseInvoke(object, path, args) {
      path = castPath(path, object);
      object = parent(object, path);
      var func = object == null ? object : object[toKey(last(path))];
      return func == null ? undefined : apply(func, object, args);
    }

    /**
     * The base implementation of `_.isArguments`.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is an `arguments` object,
     */
    function baseIsArguments(value) {
      return isObjectLike(value) && baseGetTag(value) == argsTag;
    }

    /**
     * The base implementation of `_.isArrayBuffer` without Node.js optimizations.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`.
     */
    function baseIsArrayBuffer(value) {
      return isObjectLike(value) && baseGetTag(value) == arrayBufferTag;
    }

    /**
     * The base implementation of `_.isDate` without Node.js optimizations.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a date object, else `false`.
     */
    function baseIsDate(value) {
      return isObjectLike(value) && baseGetTag(value) == dateTag;
    }

    /**
     * The base implementation of `_.isEqual` which supports partial comparisons
     * and tracks traversed objects.
     *
     * @private
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @param {boolean} bitmask The bitmask flags.
     *  1 - Unordered comparison
     *  2 - Partial comparison
     * @param {Function} [customizer] The function to customize comparisons.
     * @param {Object} [stack] Tracks traversed `value` and `other` objects.
     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
     */
    function baseIsEqual(value, other, bitmask, customizer, stack) {
      if (value === other) {
        return true;
      }
      if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) {
        return value !== value && other !== other;
      }
      return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack);
    }

    /**
     * A specialized version of `baseIsEqual` for arrays and objects which performs
     * deep comparisons and tracks traversed objects enabling objects with circular
     * references to be compared.
     *
     * @private
     * @param {Object} object The object to compare.
     * @param {Object} other The other object to compare.
     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
     * @param {Function} customizer The function to customize comparisons.
     * @param {Function} equalFunc The function to determine equivalents of values.
     * @param {Object} [stack] Tracks traversed `object` and `other` objects.
     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
     */
    function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) {
      var objIsArr = isArray(object),
          othIsArr = isArray(other),
          objTag = objIsArr ? arrayTag : getTag(object),
          othTag = othIsArr ? arrayTag : getTag(other);

      objTag = objTag == argsTag ? objectTag : objTag;
      othTag = othTag == argsTag ? objectTag : othTag;

      var objIsObj = objTag == objectTag,
          othIsObj = othTag == objectTag,
          isSameTag = objTag == othTag;

      if (isSameTag && isBuffer(object)) {
        if (!isBuffer(other)) {
          return false;
        }
        objIsArr = true;
        objIsObj = false;
      }
      if (isSameTag && !objIsObj) {
        stack || (stack = new Stack);
        return (objIsArr || isTypedArray(object))
          ? equalArrays(object, other, bitmask, customizer, equalFunc, stack)
          : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack);
      }
      if (!(bitmask & COMPARE_PARTIAL_FLAG)) {
        var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
            othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');

        if (objIsWrapped || othIsWrapped) {
          var objUnwrapped = objIsWrapped ? object.value() : object,
              othUnwrapped = othIsWrapped ? other.value() : other;

          stack || (stack = new Stack);
          return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack);
        }
      }
      if (!isSameTag) {
        return false;
      }
      stack || (stack = new Stack);
      return equalObjects(object, other, bitmask, customizer, equalFunc, stack);
    }

    /**
     * The base implementation of `_.isMap` without Node.js optimizations.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a map, else `false`.
     */
    function baseIsMap(value) {
      return isObjectLike(value) && getTag(value) == mapTag;
    }

    /**
     * The base implementation of `_.isMatch` without support for iteratee shorthands.
     *
     * @private
     * @param {Object} object The object to inspect.
     * @param {Object} source The object of property values to match.
     * @param {Array} matchData The property names, values, and compare flags to match.
     * @param {Function} [customizer] The function to customize comparisons.
     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
     */
    function baseIsMatch(object, source, matchData, customizer) {
      var index = matchData.length,
          length = index,
          noCustomizer = !customizer;

      if (object == null) {
        return !length;
      }
      object = Object(object);
      while (index--) {
        var data = matchData[index];
        if ((noCustomizer && data[2])
              ? data[1] !== object[data[0]]
              : !(data[0] in object)
            ) {
          return false;
        }
      }
      while (++index < length) {
        data = matchData[index];
        var key = data[0],
            objValue = object[key],
            srcValue = data[1];

        if (noCustomizer && data[2]) {
          if (objValue === undefined && !(key in object)) {
            return false;
          }
        } else {
          var stack = new Stack;
          if (customizer) {
            var result = customizer(objValue, srcValue, key, object, source, stack);
          }
          if (!(result === undefined
                ? baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG, customizer, stack)
                : result
              )) {
            return false;
          }
        }
      }
      return true;
    }

    /**
     * The base implementation of `_.isNative` without bad shim checks.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a native function,
     *  else `false`.
     */
    function baseIsNative(value) {
      if (!isObject(value) || isMasked(value)) {
        return false;
      }
      var pattern = isFunction(value) ? reIsNative : reIsHostCtor;
      return pattern.test(toSource(value));
    }

    /**
     * The base implementation of `_.isRegExp` without Node.js optimizations.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a regexp, else `false`.
     */
    function baseIsRegExp(value) {
      return isObjectLike(value) && baseGetTag(value) == regexpTag;
    }

    /**
     * The base implementation of `_.isSet` without Node.js optimizations.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a set, else `false`.
     */
    function baseIsSet(value) {
      return isObjectLike(value) && getTag(value) == setTag;
    }

    /**
     * The base implementation of `_.isTypedArray` without Node.js optimizations.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
     */
    function baseIsTypedArray(value) {
      return isObjectLike(value) &&
        isLength(value.length) && !!typedArrayTags[baseGetTag(value)];
    }

    /**
     * The base implementation of `_.iteratee`.
     *
     * @private
     * @param {*} [value=_.identity] The value to convert to an iteratee.
     * @returns {Function} Returns the iteratee.
     */
    function baseIteratee(value) {
      // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
      // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
      if (typeof value == 'function') {
        return value;
      }
      if (value == null) {
        return identity;
      }
      if (typeof value == 'object') {
        return isArray(value)
          ? baseMatchesProperty(value[0], value[1])
          : baseMatches(value);
      }
      return property(value);
    }

    /**
     * The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
     *
     * @private
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of property names.
     */
    function baseKeys(object) {
      if (!isPrototype(object)) {
        return nativeKeys(object);
      }
      var result = [];
      for (var key in Object(object)) {
        if (hasOwnProperty.call(object, key) && key != 'constructor') {
          result.push(key);
        }
      }
      return result;
    }

    /**
     * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense.
     *
     * @private
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of property names.
     */
    function baseKeysIn(object) {
      if (!isObject(object)) {
        return nativeKeysIn(object);
      }
      var isProto = isPrototype(object),
          result = [];

      for (var key in object) {
        if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
          result.push(key);
        }
      }
      return result;
    }

    /**
     * The base implementation of `_.lt` which doesn't coerce arguments.
     *
     * @private
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @returns {boolean} Returns `true` if `value` is less than `other`,
     *  else `false`.
     */
    function baseLt(value, other) {
      return value < other;
    }

    /**
     * The base implementation of `_.map` without support for iteratee shorthands.
     *
     * @private
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} iteratee The function invoked per iteration.
     * @returns {Array} Returns the new mapped array.
     */
    function baseMap(collection, iteratee) {
      var index = -1,
          result = isArrayLike(collection) ? Array(collection.length) : [];

      baseEach(collection, function(value, key, collection) {
        result[++index] = iteratee(value, key, collection);
      });
      return result;
    }

    /**
     * The base implementation of `_.matches` which doesn't clone `source`.
     *
     * @private
     * @param {Object} source The object of property values to match.
     * @returns {Function} Returns the new spec function.
     */
    function baseMatches(source) {
      var matchData = getMatchData(source);
      if (matchData.length == 1 && matchData[0][2]) {
        return matchesStrictComparable(matchData[0][0], matchData[0][1]);
      }
      return function(object) {
        return object === source || baseIsMatch(object, source, matchData);
      };
    }

    /**
     * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`.
     *
     * @private
     * @param {string} path The path of the property to get.
     * @param {*} srcValue The value to match.
     * @returns {Function} Returns the new spec function.
     */
    function baseMatchesProperty(path, srcValue) {
      if (isKey(path) && isStrictComparable(srcValue)) {
        return matchesStrictComparable(toKey(path), srcValue);
      }
      return function(object) {
        var objValue = get(object, path);
        return (objValue === undefined && objValue === srcValue)
          ? hasIn(object, path)
          : baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG);
      };
    }

    /**
     * The base implementation of `_.merge` without support for multiple sources.
     *
     * @private
     * @param {Object} object The destination object.
     * @param {Object} source The source object.
     * @param {number} srcIndex The index of `source`.
     * @param {Function} [customizer] The function to customize merged values.
     * @param {Object} [stack] Tracks traversed source values and their merged
     *  counterparts.
     */
    function baseMerge(object, source, srcIndex, customizer, stack) {
      if (object === source) {
        return;
      }
      baseFor(source, function(srcValue, key) {
        stack || (stack = new Stack);
        if (isObject(srcValue)) {
          baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
        }
        else {
          var newValue = customizer
            ? customizer(safeGet(object, key), srcValue, (key + ''), object, source, stack)
            : undefined;

          if (newValue === undefined) {
            newValue = srcValue;
          }
          assignMergeValue(object, key, newValue);
        }
      }, keysIn);
    }

    /**
     * A specialized version of `baseMerge` for arrays and objects which performs
     * deep merges and tracks traversed objects enabling objects with circular
     * references to be merged.
     *
     * @private
     * @param {Object} object The destination object.
     * @param {Object} source The source object.
     * @param {string} key The key of the value to merge.
     * @param {number} srcIndex The index of `source`.
     * @param {Function} mergeFunc The function to merge values.
     * @param {Function} [customizer] The function to customize assigned values.
     * @param {Object} [stack] Tracks traversed source values and their merged
     *  counterparts.
     */
    function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) {
      var objValue = safeGet(object, key),
          srcValue = safeGet(source, key),
          stacked = stack.get(srcValue);

      if (stacked) {
        assignMergeValue(object, key, stacked);
        return;
      }
      var newValue = customizer
        ? customizer(objValue, srcValue, (key + ''), object, source, stack)
        : undefined;

      var isCommon = newValue === undefined;

      if (isCommon) {
        var isArr = isArray(srcValue),
            isBuff = !isArr && isBuffer(srcValue),
            isTyped = !isArr && !isBuff && isTypedArray(srcValue);

        newValue = srcValue;
        if (isArr || isBuff || isTyped) {
          if (isArray(objValue)) {
            newValue = objValue;
          }
          else if (isArrayLikeObject(objValue)) {
            newValue = copyArray(objValue);
          }
          else if (isBuff) {
            isCommon = false;
            newValue = cloneBuffer(srcValue, true);
          }
          else if (isTyped) {
            isCommon = false;
            newValue = cloneTypedArray(srcValue, true);
          }
          else {
            newValue = [];
          }
        }
        else if (isPlainObject(srcValue) || isArguments(srcValue)) {
          newValue = objValue;
          if (isArguments(objValue)) {
            newValue = toPlainObject(objValue);
          }
          else if (!isObject(objValue) || isFunction(objValue)) {
            newValue = initCloneObject(srcValue);
          }
        }
        else {
          isCommon = false;
        }
      }
      if (isCommon) {
        // Recursively merge objects and arrays (susceptible to call stack limits).
        stack.set(srcValue, newValue);
        mergeFunc(newValue, srcValue, srcIndex, customizer, stack);
        stack['delete'](srcValue);
      }
      assignMergeValue(object, key, newValue);
    }

    /**
     * The base implementation of `_.nth` which doesn't coerce arguments.
     *
     * @private
     * @param {Array} array The array to query.
     * @param {number} n The index of the element to return.
     * @returns {*} Returns the nth element of `array`.
     */
    function baseNth(array, n) {
      var length = array.length;
      if (!length) {
        return;
      }
      n += n < 0 ? length : 0;
      return isIndex(n, length) ? array[n] : undefined;
    }

    /**
     * The base implementation of `_.orderBy` without param guards.
     *
     * @private
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
     * @param {string[]} orders The sort orders of `iteratees`.
     * @returns {Array} Returns the new sorted array.
     */
    function baseOrderBy(collection, iteratees, orders) {
      var index = -1;
      iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(getIteratee()));

      var result = baseMap(collection, function(value, key, collection) {
        var criteria = arrayMap(iteratees, function(iteratee) {
          return iteratee(value);
        });
        return { 'criteria': criteria, 'index': ++index, 'value': value };
      });

      return baseSortBy(result, function(object, other) {
        return compareMultiple(object, other, orders);
      });
    }

    /**
     * The base implementation of `_.pick` without support for individual
     * property identifiers.
     *
     * @private
     * @param {Object} object The source object.
     * @param {string[]} paths The property paths to pick.
     * @returns {Object} Returns the new object.
     */
    function basePick(object, paths) {
      return basePickBy(object, paths, function(value, path) {
        return hasIn(object, path);
      });
    }

    /**
     * The base implementation of  `_.pickBy` without support for iteratee shorthands.
     *
     * @private
     * @param {Object} object The source object.
     * @param {string[]} paths The property paths to pick.
     * @param {Function} predicate The function invoked per property.
     * @returns {Object} Returns the new object.
     */
    function basePickBy(object, paths, predicate) {
      var index = -1,
          length = paths.length,
          result = {};

      while (++index < length) {
        var path = paths[index],
            value = baseGet(object, path);

        if (predicate(value, path)) {
          baseSet(result, castPath(path, object), value);
        }
      }
      return result;
    }

    /**
     * A specialized version of `baseProperty` which supports deep paths.
     *
     * @private
     * @param {Array|string} path The path of the property to get.
     * @returns {Function} Returns the new accessor function.
     */
    function basePropertyDeep(path) {
      return function(object) {
        return baseGet(object, path);
      };
    }

    /**
     * The base implementation of `_.pullAllBy` without support for iteratee
     * shorthands.
     *
     * @private
     * @param {Array} array The array to modify.
     * @param {Array} values The values to remove.
     * @param {Function} [iteratee] The iteratee invoked per element.
     * @param {Function} [comparator] The comparator invoked per element.
     * @returns {Array} Returns `array`.
     */
    function basePullAll(array, values, iteratee, comparator) {
      var indexOf = comparator ? baseIndexOfWith : baseIndexOf,
          index = -1,
          length = values.length,
          seen = array;

      if (array === values) {
        values = copyArray(values);
      }
      if (iteratee) {
        seen = arrayMap(array, baseUnary(iteratee));
      }
      while (++index < length) {
        var fromIndex = 0,
            value = values[index],
            computed = iteratee ? iteratee(value) : value;

        while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) {
          if (seen !== array) {
            splice.call(seen, fromIndex, 1);
          }
          splice.call(array, fromIndex, 1);
        }
      }
      return array;
    }

    /**
     * The base implementation of `_.pullAt` without support for individual
     * indexes or capturing the removed elements.
     *
     * @private
     * @param {Array} array The array to modify.
     * @param {number[]} indexes The indexes of elements to remove.
     * @returns {Array} Returns `array`.
     */
    function basePullAt(array, indexes) {
      var length = array ? indexes.length : 0,
          lastIndex = length - 1;

      while (length--) {
        var index = indexes[length];
        if (length == lastIndex || index !== previous) {
          var previous = index;
          if (isIndex(index)) {
            splice.call(array, index, 1);
          } else {
            baseUnset(array, index);
          }
        }
      }
      return array;
    }

    /**
     * The base implementation of `_.random` without support for returning
     * floating-point numbers.
     *
     * @private
     * @param {number} lower The lower bound.
     * @param {number} upper The upper bound.
     * @returns {number} Returns the random number.
     */
    function baseRandom(lower, upper) {
      return lower + nativeFloor(nativeRandom() * (upper - lower + 1));
    }

    /**
     * The base implementation of `_.range` and `_.rangeRight` which doesn't
     * coerce arguments.
     *
     * @private
     * @param {number} start The start of the range.
     * @param {number} end The end of the range.
     * @param {number} step The value to increment or decrement by.
     * @param {boolean} [fromRight] Specify iterating from right to left.
     * @returns {Array} Returns the range of numbers.
     */
    function baseRange(start, end, step, fromRight) {
      var index = -1,
          length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
          result = Array(length);

      while (length--) {
        result[fromRight ? length : ++index] = start;
        start += step;
      }
      return result;
    }

    /**
     * The base implementation of `_.repeat` which doesn't coerce arguments.
     *
     * @private
     * @param {string} string The string to repeat.
     * @param {number} n The number of times to repeat the string.
     * @returns {string} Returns the repeated string.
     */
    function baseRepeat(string, n) {
      var result = '';
      if (!string || n < 1 || n > MAX_SAFE_INTEGER) {
        return result;
      }
      // Leverage the exponentiation by squaring algorithm for a faster repeat.
      // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details.
      do {
        if (n % 2) {
          result += string;
        }
        n = nativeFloor(n / 2);
        if (n) {
          string += string;
        }
      } while (n);

      return result;
    }

    /**
     * The base implementation of `_.rest` which doesn't validate or coerce arguments.
     *
     * @private
     * @param {Function} func The function to apply a rest parameter to.
     * @param {number} [start=func.length-1] The start position of the rest parameter.
     * @returns {Function} Returns the new function.
     */
    function baseRest(func, start) {
      return setToString(overRest(func, start, identity), func + '');
    }

    /**
     * The base implementation of `_.sample`.
     *
     * @private
     * @param {Array|Object} collection The collection to sample.
     * @returns {*} Returns the random element.
     */
    function baseSample(collection) {
      return arraySample(values(collection));
    }

    /**
     * The base implementation of `_.sampleSize` without param guards.
     *
     * @private
     * @param {Array|Object} collection The collection to sample.
     * @param {number} n The number of elements to sample.
     * @returns {Array} Returns the random elements.
     */
    function baseSampleSize(collection, n) {
      var array = values(collection);
      return shuffleSelf(array, baseClamp(n, 0, array.length));
    }

    /**
     * The base implementation of `_.set`.
     *
     * @private
     * @param {Object} object The object to modify.
     * @param {Array|string} path The path of the property to set.
     * @param {*} value The value to set.
     * @param {Function} [customizer] The function to customize path creation.
     * @returns {Object} Returns `object`.
     */
    function baseSet(object, path, value, customizer) {
      if (!isObject(object)) {
        return object;
      }
      path = castPath(path, object);

      var index = -1,
          length = path.length,
          lastIndex = length - 1,
          nested = object;

      while (nested != null && ++index < length) {
        var key = toKey(path[index]),
            newValue = value;

        if (index != lastIndex) {
          var objValue = nested[key];
          newValue = customizer ? customizer(objValue, key, nested) : undefined;
          if (newValue === undefined) {
            newValue = isObject(objValue)
              ? objValue
              : (isIndex(path[index + 1]) ? [] : {});
          }
        }
        assignValue(nested, key, newValue);
        nested = nested[key];
      }
      return object;
    }

    /**
     * The base implementation of `setData` without support for hot loop shorting.
     *
     * @private
     * @param {Function} func The function to associate metadata with.
     * @param {*} data The metadata.
     * @returns {Function} Returns `func`.
     */
    var baseSetData = !metaMap ? identity : function(func, data) {
      metaMap.set(func, data);
      return func;
    };

    /**
     * The base implementation of `setToString` without support for hot loop shorting.
     *
     * @private
     * @param {Function} func The function to modify.
     * @param {Function} string The `toString` result.
     * @returns {Function} Returns `func`.
     */
    var baseSetToString = !defineProperty ? identity : function(func, string) {
      return defineProperty(func, 'toString', {
        'configurable': true,
        'enumerable': false,
        'value': constant(string),
        'writable': true
      });
    };

    /**
     * The base implementation of `_.shuffle`.
     *
     * @private
     * @param {Array|Object} collection The collection to shuffle.
     * @returns {Array} Returns the new shuffled array.
     */
    function baseShuffle(collection) {
      return shuffleSelf(values(collection));
    }

    /**
     * The base implementation of `_.slice` without an iteratee call guard.
     *
     * @private
     * @param {Array} array The array to slice.
     * @param {number} [start=0] The start position.
     * @param {number} [end=array.length] The end position.
     * @returns {Array} Returns the slice of `array`.
     */
    function baseSlice(array, start, end) {
      var index = -1,
          length = array.length;

      if (start < 0) {
        start = -start > length ? 0 : (length + start);
      }
      end = end > length ? length : end;
      if (end < 0) {
        end += length;
      }
      length = start > end ? 0 : ((end - start) >>> 0);
      start >>>= 0;

      var result = Array(length);
      while (++index < length) {
        result[index] = array[index + start];
      }
      return result;
    }

    /**
     * The base implementation of `_.some` without support for iteratee shorthands.
     *
     * @private
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} predicate The function invoked per iteration.
     * @returns {boolean} Returns `true` if any element passes the predicate check,
     *  else `false`.
     */
    function baseSome(collection, predicate) {
      var result;

      baseEach(collection, function(value, index, collection) {
        result = predicate(value, index, collection);
        return !result;
      });
      return !!result;
    }

    /**
     * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which
     * performs a binary search of `array` to determine the index at which `value`
     * should be inserted into `array` in order to maintain its sort order.
     *
     * @private
     * @param {Array} array The sorted array to inspect.
     * @param {*} value The value to evaluate.
     * @param {boolean} [retHighest] Specify returning the highest qualified index.
     * @returns {number} Returns the index at which `value` should be inserted
     *  into `array`.
     */
    function baseSortedIndex(array, value, retHighest) {
      var low = 0,
          high = array == null ? low : array.length;

      if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {
        while (low < high) {
          var mid = (low + high) >>> 1,
              computed = array[mid];

          if (computed !== null && !isSymbol(computed) &&
              (retHighest ? (computed <= value) : (computed < value))) {
            low = mid + 1;
          } else {
            high = mid;
          }
        }
        return high;
      }
      return baseSortedIndexBy(array, value, identity, retHighest);
    }

    /**
     * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy`
     * which invokes `iteratee` for `value` and each element of `array` to compute
     * their sort ranking. The iteratee is invoked with one argument; (value).
     *
     * @private
     * @param {Array} array The sorted array to inspect.
     * @param {*} value The value to evaluate.
     * @param {Function} iteratee The iteratee invoked per element.
     * @param {boolean} [retHighest] Specify returning the highest qualified index.
     * @returns {number} Returns the index at which `value` should be inserted
     *  into `array`.
     */
    function baseSortedIndexBy(array, value, iteratee, retHighest) {
      value = iteratee(value);

      var low = 0,
          high = array == null ? 0 : array.length,
          valIsNaN = value !== value,
          valIsNull = value === null,
          valIsSymbol = isSymbol(value),
          valIsUndefined = value === undefined;

      while (low < high) {
        var mid = nativeFloor((low + high) / 2),
            computed = iteratee(array[mid]),
            othIsDefined = computed !== undefined,
            othIsNull = computed === null,
            othIsReflexive = computed === computed,
            othIsSymbol = isSymbol(computed);

        if (valIsNaN) {
          var setLow = retHighest || othIsReflexive;
        } else if (valIsUndefined) {
          setLow = othIsReflexive && (retHighest || othIsDefined);
        } else if (valIsNull) {
          setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull);
        } else if (valIsSymbol) {
          setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol);
        } else if (othIsNull || othIsSymbol) {
          setLow = false;
        } else {
          setLow = retHighest ? (computed <= value) : (computed < value);
        }
        if (setLow) {
          low = mid + 1;
        } else {
          high = mid;
        }
      }
      return nativeMin(high, MAX_ARRAY_INDEX);
    }

    /**
     * The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without
     * support for iteratee shorthands.
     *
     * @private
     * @param {Array} array The array to inspect.
     * @param {Function} [iteratee] The iteratee invoked per element.
     * @returns {Array} Returns the new duplicate free array.
     */
    function baseSortedUniq(array, iteratee) {
      var index = -1,
          length = array.length,
          resIndex = 0,
          result = [];

      while (++index < length) {
        var value = array[index],
            computed = iteratee ? iteratee(value) : value;

        if (!index || !eq(computed, seen)) {
          var seen = computed;
          result[resIndex++] = value === 0 ? 0 : value;
        }
      }
      return result;
    }

    /**
     * The base implementation of `_.toNumber` which doesn't ensure correct
     * conversions of binary, hexadecimal, or octal string values.
     *
     * @private
     * @param {*} value The value to process.
     * @returns {number} Returns the number.
     */
    function baseToNumber(value) {
      if (typeof value == 'number') {
        return value;
      }
      if (isSymbol(value)) {
        return NAN;
      }
      return +value;
    }

    /**
     * The base implementation of `_.toString` which doesn't convert nullish
     * values to empty strings.
     *
     * @private
     * @param {*} value The value to process.
     * @returns {string} Returns the string.
     */
    function baseToString(value) {
      // Exit early for strings to avoid a performance hit in some environments.
      if (typeof value == 'string') {
        return value;
      }
      if (isArray(value)) {
        // Recursively convert values (susceptible to call stack limits).
        return arrayMap(value, baseToString) + '';
      }
      if (isSymbol(value)) {
        return symbolToString ? symbolToString.call(value) : '';
      }
      var result = (value + '');
      return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
    }

    /**
     * The base implementation of `_.uniqBy` without support for iteratee shorthands.
     *
     * @private
     * @param {Array} array The array to inspect.
     * @param {Function} [iteratee] The iteratee invoked per element.
     * @param {Function} [comparator] The comparator invoked per element.
     * @returns {Array} Returns the new duplicate free array.
     */
    function baseUniq(array, iteratee, comparator) {
      var index = -1,
          includes = arrayIncludes,
          length = array.length,
          isCommon = true,
          result = [],
          seen = result;

      if (comparator) {
        isCommon = false;
        includes = arrayIncludesWith;
      }
      else if (length >= LARGE_ARRAY_SIZE) {
        var set = iteratee ? null : createSet(array);
        if (set) {
          return setToArray(set);
        }
        isCommon = false;
        includes = cacheHas;
        seen = new SetCache;
      }
      else {
        seen = iteratee ? [] : result;
      }
      outer:
      while (++index < length) {
        var value = array[index],
            computed = iteratee ? iteratee(value) : value;

        value = (comparator || value !== 0) ? value : 0;
        if (isCommon && computed === computed) {
          var seenIndex = seen.length;
          while (seenIndex--) {
            if (seen[seenIndex] === computed) {
              continue outer;
            }
          }
          if (iteratee) {
            seen.push(computed);
          }
          result.push(value);
        }
        else if (!includes(seen, computed, comparator)) {
          if (seen !== result) {
            seen.push(computed);
          }
          result.push(value);
        }
      }
      return result;
    }

    /**
     * The base implementation of `_.unset`.
     *
     * @private
     * @param {Object} object The object to modify.
     * @param {Array|string} path The property path to unset.
     * @returns {boolean} Returns `true` if the property is deleted, else `false`.
     */
    function baseUnset(object, path) {
      path = castPath(path, object);
      object = parent(object, path);
      return object == null || delete object[toKey(last(path))];
    }

    /**
     * The base implementation of `_.update`.
     *
     * @private
     * @param {Object} object The object to modify.
     * @param {Array|string} path The path of the property to update.
     * @param {Function} updater The function to produce the updated value.
     * @param {Function} [customizer] The function to customize path creation.
     * @returns {Object} Returns `object`.
     */
    function baseUpdate(object, path, updater, customizer) {
      return baseSet(object, path, updater(baseGet(object, path)), customizer);
    }

    /**
     * The base implementation of methods like `_.dropWhile` and `_.takeWhile`
     * without support for iteratee shorthands.
     *
     * @private
     * @param {Array} array The array to query.
     * @param {Function} predicate The function invoked per iteration.
     * @param {boolean} [isDrop] Specify dropping elements instead of taking them.
     * @param {boolean} [fromRight] Specify iterating from right to left.
     * @returns {Array} Returns the slice of `array`.
     */
    function baseWhile(array, predicate, isDrop, fromRight) {
      var length = array.length,
          index = fromRight ? length : -1;

      while ((fromRight ? index-- : ++index < length) &&
        predicate(array[index], index, array)) {}

      return isDrop
        ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length))
        : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index));
    }

    /**
     * The base implementation of `wrapperValue` which returns the result of
     * performing a sequence of actions on the unwrapped `value`, where each
     * successive action is supplied the return value of the previous.
     *
     * @private
     * @param {*} value The unwrapped value.
     * @param {Array} actions Actions to perform to resolve the unwrapped value.
     * @returns {*} Returns the resolved value.
     */
    function baseWrapperValue(value, actions) {
      var result = value;
      if (result instanceof LazyWrapper) {
        result = result.value();
      }
      return arrayReduce(actions, function(result, action) {
        return action.func.apply(action.thisArg, arrayPush([result], action.args));
      }, result);
    }

    /**
     * The base implementation of methods like `_.xor`, without support for
     * iteratee shorthands, that accepts an array of arrays to inspect.
     *
     * @private
     * @param {Array} arrays The arrays to inspect.
     * @param {Function} [iteratee] The iteratee invoked per element.
     * @param {Function} [comparator] The comparator invoked per element.
     * @returns {Array} Returns the new array of values.
     */
    function baseXor(arrays, iteratee, comparator) {
      var length = arrays.length;
      if (length < 2) {
        return length ? baseUniq(arrays[0]) : [];
      }
      var index = -1,
          result = Array(length);

      while (++index < length) {
        var array = arrays[index],
            othIndex = -1;

        while (++othIndex < length) {
          if (othIndex != index) {
            result[index] = baseDifference(result[index] || array, arrays[othIndex], iteratee, comparator);
          }
        }
      }
      return baseUniq(baseFlatten(result, 1), iteratee, comparator);
    }

    /**
     * This base implementation of `_.zipObject` which assigns values using `assignFunc`.
     *
     * @private
     * @param {Array} props The property identifiers.
     * @param {Array} values The property values.
     * @param {Function} assignFunc The function to assign values.
     * @returns {Object} Returns the new object.
     */
    function baseZipObject(props, values, assignFunc) {
      var index = -1,
          length = props.length,
          valsLength = values.length,
          result = {};

      while (++index < length) {
        var value = index < valsLength ? values[index] : undefined;
        assignFunc(result, props[index], value);
      }
      return result;
    }

    /**
     * Casts `value` to an empty array if it's not an array like object.
     *
     * @private
     * @param {*} value The value to inspect.
     * @returns {Array|Object} Returns the cast array-like object.
     */
    function castArrayLikeObject(value) {
      return isArrayLikeObject(value) ? value : [];
    }

    /**
     * Casts `value` to `identity` if it's not a function.
     *
     * @private
     * @param {*} value The value to inspect.
     * @returns {Function} Returns cast function.
     */
    function castFunction(value) {
      return typeof value == 'function' ? value : identity;
    }

    /**
     * Casts `value` to a path array if it's not one.
     *
     * @private
     * @param {*} value The value to inspect.
     * @param {Object} [object] The object to query keys on.
     * @returns {Array} Returns the cast property path array.
     */
    function castPath(value, object) {
      if (isArray(value)) {
        return value;
      }
      return isKey(value, object) ? [value] : stringToPath(toString(value));
    }

    /**
     * A `baseRest` alias which can be replaced with `identity` by module
     * replacement plugins.
     *
     * @private
     * @type {Function}
     * @param {Function} func The function to apply a rest parameter to.
     * @returns {Function} Returns the new function.
     */
    var castRest = baseRest;

    /**
     * Casts `array` to a slice if it's needed.
     *
     * @private
     * @param {Array} array The array to inspect.
     * @param {number} start The start position.
     * @param {number} [end=array.length] The end position.
     * @returns {Array} Returns the cast slice.
     */
    function castSlice(array, start, end) {
      var length = array.length;
      end = end === undefined ? length : end;
      return (!start && end >= length) ? array : baseSlice(array, start, end);
    }

    /**
     * A simple wrapper around the global [`clearTimeout`](https://mdn.io/clearTimeout).
     *
     * @private
     * @param {number|Object} id The timer id or timeout object of the timer to clear.
     */
    var clearTimeout = ctxClearTimeout || function(id) {
      return root.clearTimeout(id);
    };

    /**
     * Creates a clone of  `buffer`.
     *
     * @private
     * @param {Buffer} buffer The buffer to clone.
     * @param {boolean} [isDeep] Specify a deep clone.
     * @returns {Buffer} Returns the cloned buffer.
     */
    function cloneBuffer(buffer, isDeep) {
      if (isDeep) {
        return buffer.slice();
      }
      var length = buffer.length,
          result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length);

      buffer.copy(result);
      return result;
    }

    /**
     * Creates a clone of `arrayBuffer`.
     *
     * @private
     * @param {ArrayBuffer} arrayBuffer The array buffer to clone.
     * @returns {ArrayBuffer} Returns the cloned array buffer.
     */
    function cloneArrayBuffer(arrayBuffer) {
      var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
      new Uint8Array(result).set(new Uint8Array(arrayBuffer));
      return result;
    }

    /**
     * Creates a clone of `dataView`.
     *
     * @private
     * @param {Object} dataView The data view to clone.
     * @param {boolean} [isDeep] Specify a deep clone.
     * @returns {Object} Returns the cloned data view.
     */
    function cloneDataView(dataView, isDeep) {
      var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
      return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength);
    }

    /**
     * Creates a clone of `regexp`.
     *
     * @private
     * @param {Object} regexp The regexp to clone.
     * @returns {Object} Returns the cloned regexp.
     */
    function cloneRegExp(regexp) {
      var result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
      result.lastIndex = regexp.lastIndex;
      return result;
    }

    /**
     * Creates a clone of the `symbol` object.
     *
     * @private
     * @param {Object} symbol The symbol object to clone.
     * @returns {Object} Returns the cloned symbol object.
     */
    function cloneSymbol(symbol) {
      return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};
    }

    /**
     * Creates a clone of `typedArray`.
     *
     * @private
     * @param {Object} typedArray The typed array to clone.
     * @param {boolean} [isDeep] Specify a deep clone.
     * @returns {Object} Returns the cloned typed array.
     */
    function cloneTypedArray(typedArray, isDeep) {
      var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
      return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
    }

    /**
     * Compares values to sort them in ascending order.
     *
     * @private
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @returns {number} Returns the sort order indicator for `value`.
     */
    function compareAscending(value, other) {
      if (value !== other) {
        var valIsDefined = value !== undefined,
            valIsNull = value === null,
            valIsReflexive = value === value,
            valIsSymbol = isSymbol(value);

        var othIsDefined = other !== undefined,
            othIsNull = other === null,
            othIsReflexive = other === other,
            othIsSymbol = isSymbol(other);

        if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) ||
            (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) ||
            (valIsNull && othIsDefined && othIsReflexive) ||
            (!valIsDefined && othIsReflexive) ||
            !valIsReflexive) {
          return 1;
        }
        if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) ||
            (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) ||
            (othIsNull && valIsDefined && valIsReflexive) ||
            (!othIsDefined && valIsReflexive) ||
            !othIsReflexive) {
          return -1;
        }
      }
      return 0;
    }

    /**
     * Used by `_.orderBy` to compare multiple properties of a value to another
     * and stable sort them.
     *
     * If `orders` is unspecified, all values are sorted in ascending order. Otherwise,
     * specify an order of "desc" for descending or "asc" for ascending sort order
     * of corresponding values.
     *
     * @private
     * @param {Object} object The object to compare.
     * @param {Object} other The other object to compare.
     * @param {boolean[]|string[]} orders The order to sort by for each property.
     * @returns {number} Returns the sort order indicator for `object`.
     */
    function compareMultiple(object, other, orders) {
      var index = -1,
          objCriteria = object.criteria,
          othCriteria = other.criteria,
          length = objCriteria.length,
          ordersLength = orders.length;

      while (++index < length) {
        var result = compareAscending(objCriteria[index], othCriteria[index]);
        if (result) {
          if (index >= ordersLength) {
            return result;
          }
          var order = orders[index];
          return result * (order == 'desc' ? -1 : 1);
        }
      }
      // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
      // that causes it, under certain circumstances, to provide the same value for
      // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247
      // for more details.
      //
      // This also ensures a stable sort in V8 and other engines.
      // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details.
      return object.index - other.index;
    }

    /**
     * Creates an array that is the composition of partially applied arguments,
     * placeholders, and provided arguments into a single array of arguments.
     *
     * @private
     * @param {Array} args The provided arguments.
     * @param {Array} partials The arguments to prepend to those provided.
     * @param {Array} holders The `partials` placeholder indexes.
     * @params {boolean} [isCurried] Specify composing for a curried function.
     * @returns {Array} Returns the new array of composed arguments.
     */
    function composeArgs(args, partials, holders, isCurried) {
      var argsIndex = -1,
          argsLength = args.length,
          holdersLength = holders.length,
          leftIndex = -1,
          leftLength = partials.length,
          rangeLength = nativeMax(argsLength - holdersLength, 0),
          result = Array(leftLength + rangeLength),
          isUncurried = !isCurried;

      while (++leftIndex < leftLength) {
        result[leftIndex] = partials[leftIndex];
      }
      while (++argsIndex < holdersLength) {
        if (isUncurried || argsIndex < argsLength) {
          result[holders[argsIndex]] = args[argsIndex];
        }
      }
      while (rangeLength--) {
        result[leftIndex++] = args[argsIndex++];
      }
      return result;
    }

    /**
     * This function is like `composeArgs` except that the arguments composition
     * is tailored for `_.partialRight`.
     *
     * @private
     * @param {Array} args The provided arguments.
     * @param {Array} partials The arguments to append to those provided.
     * @param {Array} holders The `partials` placeholder indexes.
     * @params {boolean} [isCurried] Specify composing for a curried function.
     * @returns {Array} Returns the new array of composed arguments.
     */
    function composeArgsRight(args, partials, holders, isCurried) {
      var argsIndex = -1,
          argsLength = args.length,
          holdersIndex = -1,
          holdersLength = holders.length,
          rightIndex = -1,
          rightLength = partials.length,
          rangeLength = nativeMax(argsLength - holdersLength, 0),
          result = Array(rangeLength + rightLength),
          isUncurried = !isCurried;

      while (++argsIndex < rangeLength) {
        result[argsIndex] = args[argsIndex];
      }
      var offset = argsIndex;
      while (++rightIndex < rightLength) {
        result[offset + rightIndex] = partials[rightIndex];
      }
      while (++holdersIndex < holdersLength) {
        if (isUncurried || argsIndex < argsLength) {
          result[offset + holders[holdersIndex]] = args[argsIndex++];
        }
      }
      return result;
    }

    /**
     * Copies the values of `source` to `array`.
     *
     * @private
     * @param {Array} source The array to copy values from.
     * @param {Array} [array=[]] The array to copy values to.
     * @returns {Array} Returns `array`.
     */
    function copyArray(source, array) {
      var index = -1,
          length = source.length;

      array || (array = Array(length));
      while (++index < length) {
        array[index] = source[index];
      }
      return array;
    }

    /**
     * Copies properties of `source` to `object`.
     *
     * @private
     * @param {Object} source The object to copy properties from.
     * @param {Array} props The property identifiers to copy.
     * @param {Object} [object={}] The object to copy properties to.
     * @param {Function} [customizer] The function to customize copied values.
     * @returns {Object} Returns `object`.
     */
    function copyObject(source, props, object, customizer) {
      var isNew = !object;
      object || (object = {});

      var index = -1,
          length = props.length;

      while (++index < length) {
        var key = props[index];

        var newValue = customizer
          ? customizer(object[key], source[key], key, object, source)
          : undefined;

        if (newValue === undefined) {
          newValue = source[key];
        }
        if (isNew) {
          baseAssignValue(object, key, newValue);
        } else {
          assignValue(object, key, newValue);
        }
      }
      return object;
    }

    /**
     * Copies own symbols of `source` to `object`.
     *
     * @private
     * @param {Object} source The object to copy symbols from.
     * @param {Object} [object={}] The object to copy symbols to.
     * @returns {Object} Returns `object`.
     */
    function copySymbols(source, object) {
      return copyObject(source, getSymbols(source), object);
    }

    /**
     * Copies own and inherited symbols of `source` to `object`.
     *
     * @private
     * @param {Object} source The object to copy symbols from.
     * @param {Object} [object={}] The object to copy symbols to.
     * @returns {Object} Returns `object`.
     */
    function copySymbolsIn(source, object) {
      return copyObject(source, getSymbolsIn(source), object);
    }

    /**
     * Creates a function like `_.groupBy`.
     *
     * @private
     * @param {Function} setter The function to set accumulator values.
     * @param {Function} [initializer] The accumulator object initializer.
     * @returns {Function} Returns the new aggregator function.
     */
    function createAggregator(setter, initializer) {
      return function(collection, iteratee) {
        var func = isArray(collection) ? arrayAggregator : baseAggregator,
            accumulator = initializer ? initializer() : {};

        return func(collection, setter, getIteratee(iteratee, 2), accumulator);
      };
    }

    /**
     * Creates a function like `_.assign`.
     *
     * @private
     * @param {Function} assigner The function to assign values.
     * @returns {Function} Returns the new assigner function.
     */
    function createAssigner(assigner) {
      return baseRest(function(object, sources) {
        var index = -1,
            length = sources.length,
            customizer = length > 1 ? sources[length - 1] : undefined,
            guard = length > 2 ? sources[2] : undefined;

        customizer = (assigner.length > 3 && typeof customizer == 'function')
          ? (length--, customizer)
          : undefined;

        if (guard && isIterateeCall(sources[0], sources[1], guard)) {
          customizer = length < 3 ? undefined : customizer;
          length = 1;
        }
        object = Object(object);
        while (++index < length) {
          var source = sources[index];
          if (source) {
            assigner(object, source, index, customizer);
          }
        }
        return object;
      });
    }

    /**
     * Creates a `baseEach` or `baseEachRight` function.
     *
     * @private
     * @param {Function} eachFunc The function to iterate over a collection.
     * @param {boolean} [fromRight] Specify iterating from right to left.
     * @returns {Function} Returns the new base function.
     */
    function createBaseEach(eachFunc, fromRight) {
      return function(collection, iteratee) {
        if (collection == null) {
          return collection;
        }
        if (!isArrayLike(collection)) {
          return eachFunc(collection, iteratee);
        }
        var length = collection.length,
            index = fromRight ? length : -1,
            iterable = Object(collection);

        while ((fromRight ? index-- : ++index < length)) {
          if (iteratee(iterable[index], index, iterable) === false) {
            break;
          }
        }
        return collection;
      };
    }

    /**
     * Creates a base function for methods like `_.forIn` and `_.forOwn`.
     *
     * @private
     * @param {boolean} [fromRight] Specify iterating from right to left.
     * @returns {Function} Returns the new base function.
     */
    function createBaseFor(fromRight) {
      return function(object, iteratee, keysFunc) {
        var index = -1,
            iterable = Object(object),
            props = keysFunc(object),
            length = props.length;

        while (length--) {
          var key = props[fromRight ? length : ++index];
          if (iteratee(iterable[key], key, iterable) === false) {
            break;
          }
        }
        return object;
      };
    }

    /**
     * Creates a function that wraps `func` to invoke it with the optional `this`
     * binding of `thisArg`.
     *
     * @private
     * @param {Function} func The function to wrap.
     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
     * @param {*} [thisArg] The `this` binding of `func`.
     * @returns {Function} Returns the new wrapped function.
     */
    function createBind(func, bitmask, thisArg) {
      var isBind = bitmask & WRAP_BIND_FLAG,
          Ctor = createCtor(func);

      function wrapper() {
        var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
        return fn.apply(isBind ? thisArg : this, arguments);
      }
      return wrapper;
    }

    /**
     * Creates a function like `_.lowerFirst`.
     *
     * @private
     * @param {string} methodName The name of the `String` case method to use.
     * @returns {Function} Returns the new case function.
     */
    function createCaseFirst(methodName) {
      return function(string) {
        string = toString(string);

        var strSymbols = hasUnicode(string)
          ? stringToArray(string)
          : undefined;

        var chr = strSymbols
          ? strSymbols[0]
          : string.charAt(0);

        var trailing = strSymbols
          ? castSlice(strSymbols, 1).join('')
          : string.slice(1);

        return chr[methodName]() + trailing;
      };
    }

    /**
     * Creates a function like `_.camelCase`.
     *
     * @private
     * @param {Function} callback The function to combine each word.
     * @returns {Function} Returns the new compounder function.
     */
    function createCompounder(callback) {
      return function(string) {
        return arrayReduce(words(deburr(string).replace(reApos, '')), callback, '');
      };
    }

    /**
     * Creates a function that produces an instance of `Ctor` regardless of
     * whether it was invoked as part of a `new` expression or by `call` or `apply`.
     *
     * @private
     * @param {Function} Ctor The constructor to wrap.
     * @returns {Function} Returns the new wrapped function.
     */
    function createCtor(Ctor) {
      return function() {
        // Use a `switch` statement to work with class constructors. See
        // http://ecma-international.org/ecma-262/7.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
        // for more details.
        var args = arguments;
        switch (args.length) {
          case 0: return new Ctor;
          case 1: return new Ctor(args[0]);
          case 2: return new Ctor(args[0], args[1]);
          case 3: return new Ctor(args[0], args[1], args[2]);
          case 4: return new Ctor(args[0], args[1], args[2], args[3]);
          case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]);
          case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);
          case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
        }
        var thisBinding = baseCreate(Ctor.prototype),
            result = Ctor.apply(thisBinding, args);

        // Mimic the constructor's `return` behavior.
        // See https://es5.github.io/#x13.2.2 for more details.
        return isObject(result) ? result : thisBinding;
      };
    }

    /**
     * Creates a function that wraps `func` to enable currying.
     *
     * @private
     * @param {Function} func The function to wrap.
     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
     * @param {number} arity The arity of `func`.
     * @returns {Function} Returns the new wrapped function.
     */
    function createCurry(func, bitmask, arity) {
      var Ctor = createCtor(func);

      function wrapper() {
        var length = arguments.length,
            args = Array(length),
            index = length,
            placeholder = getHolder(wrapper);

        while (index--) {
          args[index] = arguments[index];
        }
        var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder)
          ? []
          : replaceHolders(args, placeholder);

        length -= holders.length;
        if (length < arity) {
          return createRecurry(
            func, bitmask, createHybrid, wrapper.placeholder, undefined,
            args, holders, undefined, undefined, arity - length);
        }
        var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
        return apply(fn, this, args);
      }
      return wrapper;
    }

    /**
     * Creates a `_.find` or `_.findLast` function.
     *
     * @private
     * @param {Function} findIndexFunc The function to find the collection index.
     * @returns {Function} Returns the new find function.
     */
    function createFind(findIndexFunc) {
      return function(collection, predicate, fromIndex) {
        var iterable = Object(collection);
        if (!isArrayLike(collection)) {
          var iteratee = getIteratee(predicate, 3);
          collection = keys(collection);
          predicate = function(key) { return iteratee(iterable[key], key, iterable); };
        }
        var index = findIndexFunc(collection, predicate, fromIndex);
        return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined;
      };
    }

    /**
     * Creates a `_.flow` or `_.flowRight` function.
     *
     * @private
     * @param {boolean} [fromRight] Specify iterating from right to left.
     * @returns {Function} Returns the new flow function.
     */
    function createFlow(fromRight) {
      return flatRest(function(funcs) {
        var length = funcs.length,
            index = length,
            prereq = LodashWrapper.prototype.thru;

        if (fromRight) {
          funcs.reverse();
        }
        while (index--) {
          var func = funcs[index];
          if (typeof func != 'function') {
            throw new TypeError(FUNC_ERROR_TEXT);
          }
          if (prereq && !wrapper && getFuncName(func) == 'wrapper') {
            var wrapper = new LodashWrapper([], true);
          }
        }
        index = wrapper ? index : length;
        while (++index < length) {
          func = funcs[index];

          var funcName = getFuncName(func),
              data = funcName == 'wrapper' ? getData(func) : undefined;

          if (data && isLaziable(data[0]) &&
                data[1] == (WRAP_ARY_FLAG | WRAP_CURRY_FLAG | WRAP_PARTIAL_FLAG | WRAP_REARG_FLAG) &&
                !data[4].length && data[9] == 1
              ) {
            wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
          } else {
            wrapper = (func.length == 1 && isLaziable(func))
              ? wrapper[funcName]()
              : wrapper.thru(func);
          }
        }
        return function() {
          var args = arguments,
              value = args[0];

          if (wrapper && args.length == 1 && isArray(value)) {
            return wrapper.plant(value).value();
          }
          var index = 0,
              result = length ? funcs[index].apply(this, args) : value;

          while (++index < length) {
            result = funcs[index].call(this, result);
          }
          return result;
        };
      });
    }

    /**
     * Creates a function that wraps `func` to invoke it with optional `this`
     * binding of `thisArg`, partial application, and currying.
     *
     * @private
     * @param {Function|string} func The function or method name to wrap.
     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
     * @param {*} [thisArg] The `this` binding of `func`.
     * @param {Array} [partials] The arguments to prepend to those provided to
     *  the new function.
     * @param {Array} [holders] The `partials` placeholder indexes.
     * @param {Array} [partialsRight] The arguments to append to those provided
     *  to the new function.
     * @param {Array} [holdersRight] The `partialsRight` placeholder indexes.
     * @param {Array} [argPos] The argument positions of the new function.
     * @param {number} [ary] The arity cap of `func`.
     * @param {number} [arity] The arity of `func`.
     * @returns {Function} Returns the new wrapped function.
     */
    function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
      var isAry = bitmask & WRAP_ARY_FLAG,
          isBind = bitmask & WRAP_BIND_FLAG,
          isBindKey = bitmask & WRAP_BIND_KEY_FLAG,
          isCurried = bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG),
          isFlip = bitmask & WRAP_FLIP_FLAG,
          Ctor = isBindKey ? undefined : createCtor(func);

      function wrapper() {
        var length = arguments.length,
            args = Array(length),
            index = length;

        while (index--) {
          args[index] = arguments[index];
        }
        if (isCurried) {
          var placeholder = getHolder(wrapper),
              holdersCount = countHolders(args, placeholder);
        }
        if (partials) {
          args = composeArgs(args, partials, holders, isCurried);
        }
        if (partialsRight) {
          args = composeArgsRight(args, partialsRight, holdersRight, isCurried);
        }
        length -= holdersCount;
        if (isCurried && length < arity) {
          var newHolders = replaceHolders(args, placeholder);
          return createRecurry(
            func, bitmask, createHybrid, wrapper.placeholder, thisArg,
            args, newHolders, argPos, ary, arity - length
          );
        }
        var thisBinding = isBind ? thisArg : this,
            fn = isBindKey ? thisBinding[func] : func;

        length = args.length;
        if (argPos) {
          args = reorder(args, argPos);
        } else if (isFlip && length > 1) {
          args.reverse();
        }
        if (isAry && ary < length) {
          args.length = ary;
        }
        if (this && this !== root && this instanceof wrapper) {
          fn = Ctor || createCtor(fn);
        }
        return fn.apply(thisBinding, args);
      }
      return wrapper;
    }

    /**
     * Creates a function like `_.invertBy`.
     *
     * @private
     * @param {Function} setter The function to set accumulator values.
     * @param {Function} toIteratee The function to resolve iteratees.
     * @returns {Function} Returns the new inverter function.
     */
    function createInverter(setter, toIteratee) {
      return function(object, iteratee) {
        return baseInverter(object, setter, toIteratee(iteratee), {});
      };
    }

    /**
     * Creates a function that performs a mathematical operation on two values.
     *
     * @private
     * @param {Function} operator The function to perform the operation.
     * @param {number} [defaultValue] The value used for `undefined` arguments.
     * @returns {Function} Returns the new mathematical operation function.
     */
    function createMathOperation(operator, defaultValue) {
      return function(value, other) {
        var result;
        if (value === undefined && other === undefined) {
          return defaultValue;
        }
        if (value !== undefined) {
          result = value;
        }
        if (other !== undefined) {
          if (result === undefined) {
            return other;
          }
          if (typeof value == 'string' || typeof other == 'string') {
            value = baseToString(value);
            other = baseToString(other);
          } else {
            value = baseToNumber(value);
            other = baseToNumber(other);
          }
          result = operator(value, other);
        }
        return result;
      };
    }

    /**
     * Creates a function like `_.over`.
     *
     * @private
     * @param {Function} arrayFunc The function to iterate over iteratees.
     * @returns {Function} Returns the new over function.
     */
    function createOver(arrayFunc) {
      return flatRest(function(iteratees) {
        iteratees = arrayMap(iteratees, baseUnary(getIteratee()));
        return baseRest(function(args) {
          var thisArg = this;
          return arrayFunc(iteratees, function(iteratee) {
            return apply(iteratee, thisArg, args);
          });
        });
      });
    }

    /**
     * Creates the padding for `string` based on `length`. The `chars` string
     * is truncated if the number of characters exceeds `length`.
     *
     * @private
     * @param {number} length The padding length.
     * @param {string} [chars=' '] The string used as padding.
     * @returns {string} Returns the padding for `string`.
     */
    function createPadding(length, chars) {
      chars = chars === undefined ? ' ' : baseToString(chars);

      var charsLength = chars.length;
      if (charsLength < 2) {
        return charsLength ? baseRepeat(chars, length) : chars;
      }
      var result = baseRepeat(chars, nativeCeil(length / stringSize(chars)));
      return hasUnicode(chars)
        ? castSlice(stringToArray(result), 0, length).join('')
        : result.slice(0, length);
    }

    /**
     * Creates a function that wraps `func` to invoke it with the `this` binding
     * of `thisArg` and `partials` prepended to the arguments it receives.
     *
     * @private
     * @param {Function} func The function to wrap.
     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
     * @param {*} thisArg The `this` binding of `func`.
     * @param {Array} partials The arguments to prepend to those provided to
     *  the new function.
     * @returns {Function} Returns the new wrapped function.
     */
    function createPartial(func, bitmask, thisArg, partials) {
      var isBind = bitmask & WRAP_BIND_FLAG,
          Ctor = createCtor(func);

      function wrapper() {
        var argsIndex = -1,
            argsLength = arguments.length,
            leftIndex = -1,
            leftLength = partials.length,
            args = Array(leftLength + argsLength),
            fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;

        while (++leftIndex < leftLength) {
          args[leftIndex] = partials[leftIndex];
        }
        while (argsLength--) {
          args[leftIndex++] = arguments[++argsIndex];
        }
        return apply(fn, isBind ? thisArg : this, args);
      }
      return wrapper;
    }

    /**
     * Creates a `_.range` or `_.rangeRight` function.
     *
     * @private
     * @param {boolean} [fromRight] Specify iterating from right to left.
     * @returns {Function} Returns the new range function.
     */
    function createRange(fromRight) {
      return function(start, end, step) {
        if (step && typeof step != 'number' && isIterateeCall(start, end, step)) {
          end = step = undefined;
        }
        // Ensure the sign of `-0` is preserved.
        start = toFinite(start);
        if (end === undefined) {
          end = start;
          start = 0;
        } else {
          end = toFinite(end);
        }
        step = step === undefined ? (start < end ? 1 : -1) : toFinite(step);
        return baseRange(start, end, step, fromRight);
      };
    }

    /**
     * Creates a function that performs a relational operation on two values.
     *
     * @private
     * @param {Function} operator The function to perform the operation.
     * @returns {Function} Returns the new relational operation function.
     */
    function createRelationalOperation(operator) {
      return function(value, other) {
        if (!(typeof value == 'string' && typeof other == 'string')) {
          value = toNumber(value);
          other = toNumber(other);
        }
        return operator(value, other);
      };
    }

    /**
     * Creates a function that wraps `func` to continue currying.
     *
     * @private
     * @param {Function} func The function to wrap.
     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
     * @param {Function} wrapFunc The function to create the `func` wrapper.
     * @param {*} placeholder The placeholder value.
     * @param {*} [thisArg] The `this` binding of `func`.
     * @param {Array} [partials] The arguments to prepend to those provided to
     *  the new function.
     * @param {Array} [holders] The `partials` placeholder indexes.
     * @param {Array} [argPos] The argument positions of the new function.
     * @param {number} [ary] The arity cap of `func`.
     * @param {number} [arity] The arity of `func`.
     * @returns {Function} Returns the new wrapped function.
     */
    function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) {
      var isCurry = bitmask & WRAP_CURRY_FLAG,
          newHolders = isCurry ? holders : undefined,
          newHoldersRight = isCurry ? undefined : holders,
          newPartials = isCurry ? partials : undefined,
          newPartialsRight = isCurry ? undefined : partials;

      bitmask |= (isCurry ? WRAP_PARTIAL_FLAG : WRAP_PARTIAL_RIGHT_FLAG);
      bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG);

      if (!(bitmask & WRAP_CURRY_BOUND_FLAG)) {
        bitmask &= ~(WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG);
      }
      var newData = [
        func, bitmask, thisArg, newPartials, newHolders, newPartialsRight,
        newHoldersRight, argPos, ary, arity
      ];

      var result = wrapFunc.apply(undefined, newData);
      if (isLaziable(func)) {
        setData(result, newData);
      }
      result.placeholder = placeholder;
      return setWrapToString(result, func, bitmask);
    }

    /**
     * Creates a function like `_.round`.
     *
     * @private
     * @param {string} methodName The name of the `Math` method to use when rounding.
     * @returns {Function} Returns the new round function.
     */
    function createRound(methodName) {
      var func = Math[methodName];
      return function(number, precision) {
        number = toNumber(number);
        precision = precision == null ? 0 : nativeMin(toInteger(precision), 292);
        if (precision && nativeIsFinite(number)) {
          // Shift with exponential notation to avoid floating-point issues.
          // See [MDN](https://mdn.io/round#Examples) for more details.
          var pair = (toString(number) + 'e').split('e'),
              value = func(pair[0] + 'e' + (+pair[1] + precision));

          pair = (toString(value) + 'e').split('e');
          return +(pair[0] + 'e' + (+pair[1] - precision));
        }
        return func(number);
      };
    }

    /**
     * Creates a set object of `values`.
     *
     * @private
     * @param {Array} values The values to add to the set.
     * @returns {Object} Returns the new set.
     */
    var createSet = !(Set && (1 / setToArray(new Set([,-0]))[1]) == INFINITY) ? noop : function(values) {
      return new Set(values);
    };

    /**
     * Creates a `_.toPairs` or `_.toPairsIn` function.
     *
     * @private
     * @param {Function} keysFunc The function to get the keys of a given object.
     * @returns {Function} Returns the new pairs function.
     */
    function createToPairs(keysFunc) {
      return function(object) {
        var tag = getTag(object);
        if (tag == mapTag) {
          return mapToArray(object);
        }
        if (tag == setTag) {
          return setToPairs(object);
        }
        return baseToPairs(object, keysFunc(object));
      };
    }

    /**
     * Creates a function that either curries or invokes `func` with optional
     * `this` binding and partially applied arguments.
     *
     * @private
     * @param {Function|string} func The function or method name to wrap.
     * @param {number} bitmask The bitmask flags.
     *    1 - `_.bind`
     *    2 - `_.bindKey`
     *    4 - `_.curry` or `_.curryRight` of a bound function
     *    8 - `_.curry`
     *   16 - `_.curryRight`
     *   32 - `_.partial`
     *   64 - `_.partialRight`
     *  128 - `_.rearg`
     *  256 - `_.ary`
     *  512 - `_.flip`
     * @param {*} [thisArg] The `this` binding of `func`.
     * @param {Array} [partials] The arguments to be partially applied.
     * @param {Array} [holders] The `partials` placeholder indexes.
     * @param {Array} [argPos] The argument positions of the new function.
     * @param {number} [ary] The arity cap of `func`.
     * @param {number} [arity] The arity of `func`.
     * @returns {Function} Returns the new wrapped function.
     */
    function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
      var isBindKey = bitmask & WRAP_BIND_KEY_FLAG;
      if (!isBindKey && typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      var length = partials ? partials.length : 0;
      if (!length) {
        bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG);
        partials = holders = undefined;
      }
      ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0);
      arity = arity === undefined ? arity : toInteger(arity);
      length -= holders ? holders.length : 0;

      if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) {
        var partialsRight = partials,
            holdersRight = holders;

        partials = holders = undefined;
      }
      var data = isBindKey ? undefined : getData(func);

      var newData = [
        func, bitmask, thisArg, partials, holders, partialsRight, holdersRight,
        argPos, ary, arity
      ];

      if (data) {
        mergeData(newData, data);
      }
      func = newData[0];
      bitmask = newData[1];
      thisArg = newData[2];
      partials = newData[3];
      holders = newData[4];
      arity = newData[9] = newData[9] === undefined
        ? (isBindKey ? 0 : func.length)
        : nativeMax(newData[9] - length, 0);

      if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) {
        bitmask &= ~(WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG);
      }
      if (!bitmask || bitmask == WRAP_BIND_FLAG) {
        var result = createBind(func, bitmask, thisArg);
      } else if (bitmask == WRAP_CURRY_FLAG || bitmask == WRAP_CURRY_RIGHT_FLAG) {
        result = createCurry(func, bitmask, arity);
      } else if ((bitmask == WRAP_PARTIAL_FLAG || bitmask == (WRAP_BIND_FLAG | WRAP_PARTIAL_FLAG)) && !holders.length) {
        result = createPartial(func, bitmask, thisArg, partials);
      } else {
        result = createHybrid.apply(undefined, newData);
      }
      var setter = data ? baseSetData : setData;
      return setWrapToString(setter(result, newData), func, bitmask);
    }

    /**
     * Used by `_.defaults` to customize its `_.assignIn` use to assign properties
     * of source objects to the destination object for all destination properties
     * that resolve to `undefined`.
     *
     * @private
     * @param {*} objValue The destination value.
     * @param {*} srcValue The source value.
     * @param {string} key The key of the property to assign.
     * @param {Object} object The parent object of `objValue`.
     * @returns {*} Returns the value to assign.
     */
    function customDefaultsAssignIn(objValue, srcValue, key, object) {
      if (objValue === undefined ||
          (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) {
        return srcValue;
      }
      return objValue;
    }

    /**
     * Used by `_.defaultsDeep` to customize its `_.merge` use to merge source
     * objects into destination objects that are passed thru.
     *
     * @private
     * @param {*} objValue The destination value.
     * @param {*} srcValue The source value.
     * @param {string} key The key of the property to merge.
     * @param {Object} object The parent object of `objValue`.
     * @param {Object} source The parent object of `srcValue`.
     * @param {Object} [stack] Tracks traversed source values and their merged
     *  counterparts.
     * @returns {*} Returns the value to assign.
     */
    function customDefaultsMerge(objValue, srcValue, key, object, source, stack) {
      if (isObject(objValue) && isObject(srcValue)) {
        // Recursively merge objects and arrays (susceptible to call stack limits).
        stack.set(srcValue, objValue);
        baseMerge(objValue, srcValue, undefined, customDefaultsMerge, stack);
        stack['delete'](srcValue);
      }
      return objValue;
    }

    /**
     * Used by `_.omit` to customize its `_.cloneDeep` use to only clone plain
     * objects.
     *
     * @private
     * @param {*} value The value to inspect.
     * @param {string} key The key of the property to inspect.
     * @returns {*} Returns the uncloned value or `undefined` to defer cloning to `_.cloneDeep`.
     */
    function customOmitClone(value) {
      return isPlainObject(value) ? undefined : value;
    }

    /**
     * A specialized version of `baseIsEqualDeep` for arrays with support for
     * partial deep comparisons.
     *
     * @private
     * @param {Array} array The array to compare.
     * @param {Array} other The other array to compare.
     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
     * @param {Function} customizer The function to customize comparisons.
     * @param {Function} equalFunc The function to determine equivalents of values.
     * @param {Object} stack Tracks traversed `array` and `other` objects.
     * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
     */
    function equalArrays(array, other, bitmask, customizer, equalFunc, stack) {
      var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
          arrLength = array.length,
          othLength = other.length;

      if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
        return false;
      }
      // Assume cyclic values are equal.
      var stacked = stack.get(array);
      if (stacked && stack.get(other)) {
        return stacked == other;
      }
      var index = -1,
          result = true,
          seen = (bitmask & COMPARE_UNORDERED_FLAG) ? new SetCache : undefined;

      stack.set(array, other);
      stack.set(other, array);

      // Ignore non-index properties.
      while (++index < arrLength) {
        var arrValue = array[index],
            othValue = other[index];

        if (customizer) {
          var compared = isPartial
            ? customizer(othValue, arrValue, index, other, array, stack)
            : customizer(arrValue, othValue, index, array, other, stack);
        }
        if (compared !== undefined) {
          if (compared) {
            continue;
          }
          result = false;
          break;
        }
        // Recursively compare arrays (susceptible to call stack limits).
        if (seen) {
          if (!arraySome(other, function(othValue, othIndex) {
                if (!cacheHas(seen, othIndex) &&
                    (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {
                  return seen.push(othIndex);
                }
              })) {
            result = false;
            break;
          }
        } else if (!(
              arrValue === othValue ||
                equalFunc(arrValue, othValue, bitmask, customizer, stack)
            )) {
          result = false;
          break;
        }
      }
      stack['delete'](array);
      stack['delete'](other);
      return result;
    }

    /**
     * A specialized version of `baseIsEqualDeep` for comparing objects of
     * the same `toStringTag`.
     *
     * **Note:** This function only supports comparing values with tags of
     * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
     *
     * @private
     * @param {Object} object The object to compare.
     * @param {Object} other The other object to compare.
     * @param {string} tag The `toStringTag` of the objects to compare.
     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
     * @param {Function} customizer The function to customize comparisons.
     * @param {Function} equalFunc The function to determine equivalents of values.
     * @param {Object} stack Tracks traversed `object` and `other` objects.
     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
     */
    function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) {
      switch (tag) {
        case dataViewTag:
          if ((object.byteLength != other.byteLength) ||
              (object.byteOffset != other.byteOffset)) {
            return false;
          }
          object = object.buffer;
          other = other.buffer;

        case arrayBufferTag:
          if ((object.byteLength != other.byteLength) ||
              !equalFunc(new Uint8Array(object), new Uint8Array(other))) {
            return false;
          }
          return true;

        case boolTag:
        case dateTag:
        case numberTag:
          // Coerce booleans to `1` or `0` and dates to milliseconds.
          // Invalid dates are coerced to `NaN`.
          return eq(+object, +other);

        case errorTag:
          return object.name == other.name && object.message == other.message;

        case regexpTag:
        case stringTag:
          // Coerce regexes to strings and treat strings, primitives and objects,
          // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
          // for more details.
          return object == (other + '');

        case mapTag:
          var convert = mapToArray;

        case setTag:
          var isPartial = bitmask & COMPARE_PARTIAL_FLAG;
          convert || (convert = setToArray);

          if (object.size != other.size && !isPartial) {
            return false;
          }
          // Assume cyclic values are equal.
          var stacked = stack.get(object);
          if (stacked) {
            return stacked == other;
          }
          bitmask |= COMPARE_UNORDERED_FLAG;

          // Recursively compare objects (susceptible to call stack limits).
          stack.set(object, other);
          var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack);
          stack['delete'](object);
          return result;

        case symbolTag:
          if (symbolValueOf) {
            return symbolValueOf.call(object) == symbolValueOf.call(other);
          }
      }
      return false;
    }

    /**
     * A specialized version of `baseIsEqualDeep` for objects with support for
     * partial deep comparisons.
     *
     * @private
     * @param {Object} object The object to compare.
     * @param {Object} other The other object to compare.
     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
     * @param {Function} customizer The function to customize comparisons.
     * @param {Function} equalFunc The function to determine equivalents of values.
     * @param {Object} stack Tracks traversed `object` and `other` objects.
     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
     */
    function equalObjects(object, other, bitmask, customizer, equalFunc, stack) {
      var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
          objProps = getAllKeys(object),
          objLength = objProps.length,
          othProps = getAllKeys(other),
          othLength = othProps.length;

      if (objLength != othLength && !isPartial) {
        return false;
      }
      var index = objLength;
      while (index--) {
        var key = objProps[index];
        if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
          return false;
        }
      }
      // Assume cyclic values are equal.
      var stacked = stack.get(object);
      if (stacked && stack.get(other)) {
        return stacked == other;
      }
      var result = true;
      stack.set(object, other);
      stack.set(other, object);

      var skipCtor = isPartial;
      while (++index < objLength) {
        key = objProps[index];
        var objValue = object[key],
            othValue = other[key];

        if (customizer) {
          var compared = isPartial
            ? customizer(othValue, objValue, key, other, object, stack)
            : customizer(objValue, othValue, key, object, other, stack);
        }
        // Recursively compare objects (susceptible to call stack limits).
        if (!(compared === undefined
              ? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack))
              : compared
            )) {
          result = false;
          break;
        }
        skipCtor || (skipCtor = key == 'constructor');
      }
      if (result && !skipCtor) {
        var objCtor = object.constructor,
            othCtor = other.constructor;

        // Non `Object` object instances with different constructors are not equal.
        if (objCtor != othCtor &&
            ('constructor' in object && 'constructor' in other) &&
            !(typeof objCtor == 'function' && objCtor instanceof objCtor &&
              typeof othCtor == 'function' && othCtor instanceof othCtor)) {
          result = false;
        }
      }
      stack['delete'](object);
      stack['delete'](other);
      return result;
    }

    /**
     * A specialized version of `baseRest` which flattens the rest array.
     *
     * @private
     * @param {Function} func The function to apply a rest parameter to.
     * @returns {Function} Returns the new function.
     */
    function flatRest(func) {
      return setToString(overRest(func, undefined, flatten), func + '');
    }

    /**
     * Creates an array of own enumerable property names and symbols of `object`.
     *
     * @private
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of property names and symbols.
     */
    function getAllKeys(object) {
      return baseGetAllKeys(object, keys, getSymbols);
    }

    /**
     * Creates an array of own and inherited enumerable property names and
     * symbols of `object`.
     *
     * @private
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of property names and symbols.
     */
    function getAllKeysIn(object) {
      return baseGetAllKeys(object, keysIn, getSymbolsIn);
    }

    /**
     * Gets metadata for `func`.
     *
     * @private
     * @param {Function} func The function to query.
     * @returns {*} Returns the metadata for `func`.
     */
    var getData = !metaMap ? noop : function(func) {
      return metaMap.get(func);
    };

    /**
     * Gets the name of `func`.
     *
     * @private
     * @param {Function} func The function to query.
     * @returns {string} Returns the function name.
     */
    function getFuncName(func) {
      var result = (func.name + ''),
          array = realNames[result],
          length = hasOwnProperty.call(realNames, result) ? array.length : 0;

      while (length--) {
        var data = array[length],
            otherFunc = data.func;
        if (otherFunc == null || otherFunc == func) {
          return data.name;
        }
      }
      return result;
    }

    /**
     * Gets the argument placeholder value for `func`.
     *
     * @private
     * @param {Function} func The function to inspect.
     * @returns {*} Returns the placeholder value.
     */
    function getHolder(func) {
      var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func;
      return object.placeholder;
    }

    /**
     * Gets the appropriate "iteratee" function. If `_.iteratee` is customized,
     * this function returns the custom method, otherwise it returns `baseIteratee`.
     * If arguments are provided, the chosen function is invoked with them and
     * its result is returned.
     *
     * @private
     * @param {*} [value] The value to convert to an iteratee.
     * @param {number} [arity] The arity of the created iteratee.
     * @returns {Function} Returns the chosen function or its result.
     */
    function getIteratee() {
      var result = lodash.iteratee || iteratee;
      result = result === iteratee ? baseIteratee : result;
      return arguments.length ? result(arguments[0], arguments[1]) : result;
    }

    /**
     * Gets the data for `map`.
     *
     * @private
     * @param {Object} map The map to query.
     * @param {string} key The reference key.
     * @returns {*} Returns the map data.
     */
    function getMapData(map, key) {
      var data = map.__data__;
      return isKeyable(key)
        ? data[typeof key == 'string' ? 'string' : 'hash']
        : data.map;
    }

    /**
     * Gets the property names, values, and compare flags of `object`.
     *
     * @private
     * @param {Object} object The object to query.
     * @returns {Array} Returns the match data of `object`.
     */
    function getMatchData(object) {
      var result = keys(object),
          length = result.length;

      while (length--) {
        var key = result[length],
            value = object[key];

        result[length] = [key, value, isStrictComparable(value)];
      }
      return result;
    }

    /**
     * Gets the native function at `key` of `object`.
     *
     * @private
     * @param {Object} object The object to query.
     * @param {string} key The key of the method to get.
     * @returns {*} Returns the function if it's native, else `undefined`.
     */
    function getNative(object, key) {
      var value = getValue(object, key);
      return baseIsNative(value) ? value : undefined;
    }

    /**
     * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
     *
     * @private
     * @param {*} value The value to query.
     * @returns {string} Returns the raw `toStringTag`.
     */
    function getRawTag(value) {
      var isOwn = hasOwnProperty.call(value, symToStringTag),
          tag = value[symToStringTag];

      try {
        value[symToStringTag] = undefined;
        var unmasked = true;
      } catch (e) {}

      var result = nativeObjectToString.call(value);
      if (unmasked) {
        if (isOwn) {
          value[symToStringTag] = tag;
        } else {
          delete value[symToStringTag];
        }
      }
      return result;
    }

    /**
     * Creates an array of the own enumerable symbols of `object`.
     *
     * @private
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of symbols.
     */
    var getSymbols = !nativeGetSymbols ? stubArray : function(object) {
      if (object == null) {
        return [];
      }
      object = Object(object);
      return arrayFilter(nativeGetSymbols(object), function(symbol) {
        return propertyIsEnumerable.call(object, symbol);
      });
    };

    /**
     * Creates an array of the own and inherited enumerable symbols of `object`.
     *
     * @private
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of symbols.
     */
    var getSymbolsIn = !nativeGetSymbols ? stubArray : function(object) {
      var result = [];
      while (object) {
        arrayPush(result, getSymbols(object));
        object = getPrototype(object);
      }
      return result;
    };

    /**
     * Gets the `toStringTag` of `value`.
     *
     * @private
     * @param {*} value The value to query.
     * @returns {string} Returns the `toStringTag`.
     */
    var getTag = baseGetTag;

    // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.
    if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) ||
        (Map && getTag(new Map) != mapTag) ||
        (Promise && getTag(Promise.resolve()) != promiseTag) ||
        (Set && getTag(new Set) != setTag) ||
        (WeakMap && getTag(new WeakMap) != weakMapTag)) {
      getTag = function(value) {
        var result = baseGetTag(value),
            Ctor = result == objectTag ? value.constructor : undefined,
            ctorString = Ctor ? toSource(Ctor) : '';

        if (ctorString) {
          switch (ctorString) {
            case dataViewCtorString: return dataViewTag;
            case mapCtorString: return mapTag;
            case promiseCtorString: return promiseTag;
            case setCtorString: return setTag;
            case weakMapCtorString: return weakMapTag;
          }
        }
        return result;
      };
    }

    /**
     * Gets the view, applying any `transforms` to the `start` and `end` positions.
     *
     * @private
     * @param {number} start The start of the view.
     * @param {number} end The end of the view.
     * @param {Array} transforms The transformations to apply to the view.
     * @returns {Object} Returns an object containing the `start` and `end`
     *  positions of the view.
     */
    function getView(start, end, transforms) {
      var index = -1,
          length = transforms.length;

      while (++index < length) {
        var data = transforms[index],
            size = data.size;

        switch (data.type) {
          case 'drop':      start += size; break;
          case 'dropRight': end -= size; break;
          case 'take':      end = nativeMin(end, start + size); break;
          case 'takeRight': start = nativeMax(start, end - size); break;
        }
      }
      return { 'start': start, 'end': end };
    }

    /**
     * Extracts wrapper details from the `source` body comment.
     *
     * @private
     * @param {string} source The source to inspect.
     * @returns {Array} Returns the wrapper details.
     */
    function getWrapDetails(source) {
      var match = source.match(reWrapDetails);
      return match ? match[1].split(reSplitDetails) : [];
    }

    /**
     * Checks if `path` exists on `object`.
     *
     * @private
     * @param {Object} object The object to query.
     * @param {Array|string} path The path to check.
     * @param {Function} hasFunc The function to check properties.
     * @returns {boolean} Returns `true` if `path` exists, else `false`.
     */
    function hasPath(object, path, hasFunc) {
      path = castPath(path, object);

      var index = -1,
          length = path.length,
          result = false;

      while (++index < length) {
        var key = toKey(path[index]);
        if (!(result = object != null && hasFunc(object, key))) {
          break;
        }
        object = object[key];
      }
      if (result || ++index != length) {
        return result;
      }
      length = object == null ? 0 : object.length;
      return !!length && isLength(length) && isIndex(key, length) &&
        (isArray(object) || isArguments(object));
    }

    /**
     * Initializes an array clone.
     *
     * @private
     * @param {Array} array The array to clone.
     * @returns {Array} Returns the initialized clone.
     */
    function initCloneArray(array) {
      var length = array.length,
          result = new array.constructor(length);

      // Add properties assigned by `RegExp#exec`.
      if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
        result.index = array.index;
        result.input = array.input;
      }
      return result;
    }

    /**
     * Initializes an object clone.
     *
     * @private
     * @param {Object} object The object to clone.
     * @returns {Object} Returns the initialized clone.
     */
    function initCloneObject(object) {
      return (typeof object.constructor == 'function' && !isPrototype(object))
        ? baseCreate(getPrototype(object))
        : {};
    }

    /**
     * Initializes an object clone based on its `toStringTag`.
     *
     * **Note:** This function only supports cloning values with tags of
     * `Boolean`, `Date`, `Error`, `Map`, `Number`, `RegExp`, `Set`, or `String`.
     *
     * @private
     * @param {Object} object The object to clone.
     * @param {string} tag The `toStringTag` of the object to clone.
     * @param {boolean} [isDeep] Specify a deep clone.
     * @returns {Object} Returns the initialized clone.
     */
    function initCloneByTag(object, tag, isDeep) {
      var Ctor = object.constructor;
      switch (tag) {
        case arrayBufferTag:
          return cloneArrayBuffer(object);

        case boolTag:
        case dateTag:
          return new Ctor(+object);

        case dataViewTag:
          return cloneDataView(object, isDeep);

        case float32Tag: case float64Tag:
        case int8Tag: case int16Tag: case int32Tag:
        case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
          return cloneTypedArray(object, isDeep);

        case mapTag:
          return new Ctor;

        case numberTag:
        case stringTag:
          return new Ctor(object);

        case regexpTag:
          return cloneRegExp(object);

        case setTag:
          return new Ctor;

        case symbolTag:
          return cloneSymbol(object);
      }
    }

    /**
     * Inserts wrapper `details` in a comment at the top of the `source` body.
     *
     * @private
     * @param {string} source The source to modify.
     * @returns {Array} details The details to insert.
     * @returns {string} Returns the modified source.
     */
    function insertWrapDetails(source, details) {
      var length = details.length;
      if (!length) {
        return source;
      }
      var lastIndex = length - 1;
      details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex];
      details = details.join(length > 2 ? ', ' : ' ');
      return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n');
    }

    /**
     * Checks if `value` is a flattenable `arguments` object or array.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
     */
    function isFlattenable(value) {
      return isArray(value) || isArguments(value) ||
        !!(spreadableSymbol && value && value[spreadableSymbol]);
    }

    /**
     * Checks if `value` is a valid array-like index.
     *
     * @private
     * @param {*} value The value to check.
     * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
     * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
     */
    function isIndex(value, length) {
      var type = typeof value;
      length = length == null ? MAX_SAFE_INTEGER : length;

      return !!length &&
        (type == 'number' ||
          (type != 'symbol' && reIsUint.test(value))) &&
            (value > -1 && value % 1 == 0 && value < length);
    }

    /**
     * Checks if the given arguments are from an iteratee call.
     *
     * @private
     * @param {*} value The potential iteratee value argument.
     * @param {*} index The potential iteratee index or key argument.
     * @param {*} object The potential iteratee object argument.
     * @returns {boolean} Returns `true` if the arguments are from an iteratee call,
     *  else `false`.
     */
    function isIterateeCall(value, index, object) {
      if (!isObject(object)) {
        return false;
      }
      var type = typeof index;
      if (type == 'number'
            ? (isArrayLike(object) && isIndex(index, object.length))
            : (type == 'string' && index in object)
          ) {
        return eq(object[index], value);
      }
      return false;
    }

    /**
     * Checks if `value` is a property name and not a property path.
     *
     * @private
     * @param {*} value The value to check.
     * @param {Object} [object] The object to query keys on.
     * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
     */
    function isKey(value, object) {
      if (isArray(value)) {
        return false;
      }
      var type = typeof value;
      if (type == 'number' || type == 'symbol' || type == 'boolean' ||
          value == null || isSymbol(value)) {
        return true;
      }
      return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
        (object != null && value in Object(object));
    }

    /**
     * Checks if `value` is suitable for use as unique object key.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
     */
    function isKeyable(value) {
      var type = typeof value;
      return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
        ? (value !== '__proto__')
        : (value === null);
    }

    /**
     * Checks if `func` has a lazy counterpart.
     *
     * @private
     * @param {Function} func The function to check.
     * @returns {boolean} Returns `true` if `func` has a lazy counterpart,
     *  else `false`.
     */
    function isLaziable(func) {
      var funcName = getFuncName(func),
          other = lodash[funcName];

      if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) {
        return false;
      }
      if (func === other) {
        return true;
      }
      var data = getData(other);
      return !!data && func === data[0];
    }

    /**
     * Checks if `func` has its source masked.
     *
     * @private
     * @param {Function} func The function to check.
     * @returns {boolean} Returns `true` if `func` is masked, else `false`.
     */
    function isMasked(func) {
      return !!maskSrcKey && (maskSrcKey in func);
    }

    /**
     * Checks if `func` is capable of being masked.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `func` is maskable, else `false`.
     */
    var isMaskable = coreJsData ? isFunction : stubFalse;

    /**
     * Checks if `value` is likely a prototype object.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
     */
    function isPrototype(value) {
      var Ctor = value && value.constructor,
          proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;

      return value === proto;
    }

    /**
     * Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` if suitable for strict
     *  equality comparisons, else `false`.
     */
    function isStrictComparable(value) {
      return value === value && !isObject(value);
    }

    /**
     * A specialized version of `matchesProperty` for source values suitable
     * for strict equality comparisons, i.e. `===`.
     *
     * @private
     * @param {string} key The key of the property to get.
     * @param {*} srcValue The value to match.
     * @returns {Function} Returns the new spec function.
     */
    function matchesStrictComparable(key, srcValue) {
      return function(object) {
        if (object == null) {
          return false;
        }
        return object[key] === srcValue &&
          (srcValue !== undefined || (key in Object(object)));
      };
    }

    /**
     * A specialized version of `_.memoize` which clears the memoized function's
     * cache when it exceeds `MAX_MEMOIZE_SIZE`.
     *
     * @private
     * @param {Function} func The function to have its output memoized.
     * @returns {Function} Returns the new memoized function.
     */
    function memoizeCapped(func) {
      var result = memoize(func, function(key) {
        if (cache.size === MAX_MEMOIZE_SIZE) {
          cache.clear();
        }
        return key;
      });

      var cache = result.cache;
      return result;
    }

    /**
     * Merges the function metadata of `source` into `data`.
     *
     * Merging metadata reduces the number of wrappers used to invoke a function.
     * This is possible because methods like `_.bind`, `_.curry`, and `_.partial`
     * may be applied regardless of execution order. Methods like `_.ary` and
     * `_.rearg` modify function arguments, making the order in which they are
     * executed important, preventing the merging of metadata. However, we make
     * an exception for a safe combined case where curried functions have `_.ary`
     * and or `_.rearg` applied.
     *
     * @private
     * @param {Array} data The destination metadata.
     * @param {Array} source The source metadata.
     * @returns {Array} Returns `data`.
     */
    function mergeData(data, source) {
      var bitmask = data[1],
          srcBitmask = source[1],
          newBitmask = bitmask | srcBitmask,
          isCommon = newBitmask < (WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG | WRAP_ARY_FLAG);

      var isCombo =
        ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_CURRY_FLAG)) ||
        ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_REARG_FLAG) && (data[7].length <= source[8])) ||
        ((srcBitmask == (WRAP_ARY_FLAG | WRAP_REARG_FLAG)) && (source[7].length <= source[8]) && (bitmask == WRAP_CURRY_FLAG));

      // Exit early if metadata can't be merged.
      if (!(isCommon || isCombo)) {
        return data;
      }
      // Use source `thisArg` if available.
      if (srcBitmask & WRAP_BIND_FLAG) {
        data[2] = source[2];
        // Set when currying a bound function.
        newBitmask |= bitmask & WRAP_BIND_FLAG ? 0 : WRAP_CURRY_BOUND_FLAG;
      }
      // Compose partial arguments.
      var value = source[3];
      if (value) {
        var partials = data[3];
        data[3] = partials ? composeArgs(partials, value, source[4]) : value;
        data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4];
      }
      // Compose partial right arguments.
      value = source[5];
      if (value) {
        partials = data[5];
        data[5] = partials ? composeArgsRight(partials, value, source[6]) : value;
        data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6];
      }
      // Use source `argPos` if available.
      value = source[7];
      if (value) {
        data[7] = value;
      }
      // Use source `ary` if it's smaller.
      if (srcBitmask & WRAP_ARY_FLAG) {
        data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]);
      }
      // Use source `arity` if one is not provided.
      if (data[9] == null) {
        data[9] = source[9];
      }
      // Use source `func` and merge bitmasks.
      data[0] = source[0];
      data[1] = newBitmask;

      return data;
    }

    /**
     * This function is like
     * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
     * except that it includes inherited enumerable properties.
     *
     * @private
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of property names.
     */
    function nativeKeysIn(object) {
      var result = [];
      if (object != null) {
        for (var key in Object(object)) {
          result.push(key);
        }
      }
      return result;
    }

    /**
     * Converts `value` to a string using `Object.prototype.toString`.
     *
     * @private
     * @param {*} value The value to convert.
     * @returns {string} Returns the converted string.
     */
    function objectToString(value) {
      return nativeObjectToString.call(value);
    }

    /**
     * A specialized version of `baseRest` which transforms the rest array.
     *
     * @private
     * @param {Function} func The function to apply a rest parameter to.
     * @param {number} [start=func.length-1] The start position of the rest parameter.
     * @param {Function} transform The rest array transform.
     * @returns {Function} Returns the new function.
     */
    function overRest(func, start, transform) {
      start = nativeMax(start === undefined ? (func.length - 1) : start, 0);
      return function() {
        var args = arguments,
            index = -1,
            length = nativeMax(args.length - start, 0),
            array = Array(length);

        while (++index < length) {
          array[index] = args[start + index];
        }
        index = -1;
        var otherArgs = Array(start + 1);
        while (++index < start) {
          otherArgs[index] = args[index];
        }
        otherArgs[start] = transform(array);
        return apply(func, this, otherArgs);
      };
    }

    /**
     * Gets the parent value at `path` of `object`.
     *
     * @private
     * @param {Object} object The object to query.
     * @param {Array} path The path to get the parent value of.
     * @returns {*} Returns the parent value.
     */
    function parent(object, path) {
      return path.length < 2 ? object : baseGet(object, baseSlice(path, 0, -1));
    }

    /**
     * Reorder `array` according to the specified indexes where the element at
     * the first index is assigned as the first element, the element at
     * the second index is assigned as the second element, and so on.
     *
     * @private
     * @param {Array} array The array to reorder.
     * @param {Array} indexes The arranged array indexes.
     * @returns {Array} Returns `array`.
     */
    function reorder(array, indexes) {
      var arrLength = array.length,
          length = nativeMin(indexes.length, arrLength),
          oldArray = copyArray(array);

      while (length--) {
        var index = indexes[length];
        array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined;
      }
      return array;
    }

    /**
     * Gets the value at `key`, unless `key` is "__proto__" or "constructor".
     *
     * @private
     * @param {Object} object The object to query.
     * @param {string} key The key of the property to get.
     * @returns {*} Returns the property value.
     */
    function safeGet(object, key) {
      if (key === 'constructor' && typeof object[key] === 'function') {
        return;
      }

      if (key == '__proto__') {
        return;
      }

      return object[key];
    }

    /**
     * Sets metadata for `func`.
     *
     * **Note:** If this function becomes hot, i.e. is invoked a lot in a short
     * period of time, it will trip its breaker and transition to an identity
     * function to avoid garbage collection pauses in V8. See
     * [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070)
     * for more details.
     *
     * @private
     * @param {Function} func The function to associate metadata with.
     * @param {*} data The metadata.
     * @returns {Function} Returns `func`.
     */
    var setData = shortOut(baseSetData);

    /**
     * A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout).
     *
     * @private
     * @param {Function} func The function to delay.
     * @param {number} wait The number of milliseconds to delay invocation.
     * @returns {number|Object} Returns the timer id or timeout object.
     */
    var setTimeout = ctxSetTimeout || function(func, wait) {
      return root.setTimeout(func, wait);
    };

    /**
     * Sets the `toString` method of `func` to return `string`.
     *
     * @private
     * @param {Function} func The function to modify.
     * @param {Function} string The `toString` result.
     * @returns {Function} Returns `func`.
     */
    var setToString = shortOut(baseSetToString);

    /**
     * Sets the `toString` method of `wrapper` to mimic the source of `reference`
     * with wrapper details in a comment at the top of the source body.
     *
     * @private
     * @param {Function} wrapper The function to modify.
     * @param {Function} reference The reference function.
     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
     * @returns {Function} Returns `wrapper`.
     */
    function setWrapToString(wrapper, reference, bitmask) {
      var source = (reference + '');
      return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask)));
    }

    /**
     * Creates a function that'll short out and invoke `identity` instead
     * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN`
     * milliseconds.
     *
     * @private
     * @param {Function} func The function to restrict.
     * @returns {Function} Returns the new shortable function.
     */
    function shortOut(func) {
      var count = 0,
          lastCalled = 0;

      return function() {
        var stamp = nativeNow(),
            remaining = HOT_SPAN - (stamp - lastCalled);

        lastCalled = stamp;
        if (remaining > 0) {
          if (++count >= HOT_COUNT) {
            return arguments[0];
          }
        } else {
          count = 0;
        }
        return func.apply(undefined, arguments);
      };
    }

    /**
     * A specialized version of `_.shuffle` which mutates and sets the size of `array`.
     *
     * @private
     * @param {Array} array The array to shuffle.
     * @param {number} [size=array.length] The size of `array`.
     * @returns {Array} Returns `array`.
     */
    function shuffleSelf(array, size) {
      var index = -1,
          length = array.length,
          lastIndex = length - 1;

      size = size === undefined ? length : size;
      while (++index < size) {
        var rand = baseRandom(index, lastIndex),
            value = array[rand];

        array[rand] = array[index];
        array[index] = value;
      }
      array.length = size;
      return array;
    }

    /**
     * Converts `string` to a property path array.
     *
     * @private
     * @param {string} string The string to convert.
     * @returns {Array} Returns the property path array.
     */
    var stringToPath = memoizeCapped(function(string) {
      var result = [];
      if (string.charCodeAt(0) === 46 /* . */) {
        result.push('');
      }
      string.replace(rePropName, function(match, number, quote, subString) {
        result.push(quote ? subString.replace(reEscapeChar, '$1') : (number || match));
      });
      return result;
    });

    /**
     * Converts `value` to a string key if it's not a string or symbol.
     *
     * @private
     * @param {*} value The value to inspect.
     * @returns {string|symbol} Returns the key.
     */
    function toKey(value) {
      if (typeof value == 'string' || isSymbol(value)) {
        return value;
      }
      var result = (value + '');
      return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
    }

    /**
     * Converts `func` to its source code.
     *
     * @private
     * @param {Function} func The function to convert.
     * @returns {string} Returns the source code.
     */
    function toSource(func) {
      if (func != null) {
        try {
          return funcToString.call(func);
        } catch (e) {}
        try {
          return (func + '');
        } catch (e) {}
      }
      return '';
    }

    /**
     * Updates wrapper `details` based on `bitmask` flags.
     *
     * @private
     * @returns {Array} details The details to modify.
     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
     * @returns {Array} Returns `details`.
     */
    function updateWrapDetails(details, bitmask) {
      arrayEach(wrapFlags, function(pair) {
        var value = '_.' + pair[0];
        if ((bitmask & pair[1]) && !arrayIncludes(details, value)) {
          details.push(value);
        }
      });
      return details.sort();
    }

    /**
     * Creates a clone of `wrapper`.
     *
     * @private
     * @param {Object} wrapper The wrapper to clone.
     * @returns {Object} Returns the cloned wrapper.
     */
    function wrapperClone(wrapper) {
      if (wrapper instanceof LazyWrapper) {
        return wrapper.clone();
      }
      var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__);
      result.__actions__ = copyArray(wrapper.__actions__);
      result.__index__  = wrapper.__index__;
      result.__values__ = wrapper.__values__;
      return result;
    }

    /*------------------------------------------------------------------------*/

    /**
     * Creates an array of elements split into groups the length of `size`.
     * If `array` can't be split evenly, the final chunk will be the remaining
     * elements.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Array
     * @param {Array} array The array to process.
     * @param {number} [size=1] The length of each chunk
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {Array} Returns the new array of chunks.
     * @example
     *
     * _.chunk(['a', 'b', 'c', 'd'], 2);
     * // => [['a', 'b'], ['c', 'd']]
     *
     * _.chunk(['a', 'b', 'c', 'd'], 3);
     * // => [['a', 'b', 'c'], ['d']]
     */
    function chunk(array, size, guard) {
      if ((guard ? isIterateeCall(array, size, guard) : size === undefined)) {
        size = 1;
      } else {
        size = nativeMax(toInteger(size), 0);
      }
      var length = array == null ? 0 : array.length;
      if (!length || size < 1) {
        return [];
      }
      var index = 0,
          resIndex = 0,
          result = Array(nativeCeil(length / size));

      while (index < length) {
        result[resIndex++] = baseSlice(array, index, (index += size));
      }
      return result;
    }

    /**
     * Creates an array with all falsey values removed. The values `false`, `null`,
     * `0`, `""`, `undefined`, and `NaN` are falsey.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Array
     * @param {Array} array The array to compact.
     * @returns {Array} Returns the new array of filtered values.
     * @example
     *
     * _.compact([0, 1, false, 2, '', 3]);
     * // => [1, 2, 3]
     */
    function compact(array) {
      var index = -1,
          length = array == null ? 0 : array.length,
          resIndex = 0,
          result = [];

      while (++index < length) {
        var value = array[index];
        if (value) {
          result[resIndex++] = value;
        }
      }
      return result;
    }

    /**
     * Creates a new array concatenating `array` with any additional arrays
     * and/or values.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The array to concatenate.
     * @param {...*} [values] The values to concatenate.
     * @returns {Array} Returns the new concatenated array.
     * @example
     *
     * var array = [1];
     * var other = _.concat(array, 2, [3], [[4]]);
     *
     * console.log(other);
     * // => [1, 2, 3, [4]]
     *
     * console.log(array);
     * // => [1]
     */
    function concat() {
      var length = arguments.length;
      if (!length) {
        return [];
      }
      var args = Array(length - 1),
          array = arguments[0],
          index = length;

      while (index--) {
        args[index - 1] = arguments[index];
      }
      return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1));
    }

    /**
     * Creates an array of `array` values not included in the other given arrays
     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
     * for equality comparisons. The order and references of result values are
     * determined by the first array.
     *
     * **Note:** Unlike `_.pullAll`, this method returns a new array.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Array
     * @param {Array} array The array to inspect.
     * @param {...Array} [values] The values to exclude.
     * @returns {Array} Returns the new array of filtered values.
     * @see _.without, _.xor
     * @example
     *
     * _.difference([2, 1], [2, 3]);
     * // => [1]
     */
    var difference = baseRest(function(array, values) {
      return isArrayLikeObject(array)
        ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true))
        : [];
    });

    /**
     * This method is like `_.difference` except that it accepts `iteratee` which
     * is invoked for each element of `array` and `values` to generate the criterion
     * by which they're compared. The order and references of result values are
     * determined by the first array. The iteratee is invoked with one argument:
     * (value).
     *
     * **Note:** Unlike `_.pullAllBy`, this method returns a new array.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The array to inspect.
     * @param {...Array} [values] The values to exclude.
     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
     * @returns {Array} Returns the new array of filtered values.
     * @example
     *
     * _.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor);
     * // => [1.2]
     *
     * // The `_.property` iteratee shorthand.
     * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
     * // => [{ 'x': 2 }]
     */
    var differenceBy = baseRest(function(array, values) {
      var iteratee = last(values);
      if (isArrayLikeObject(iteratee)) {
        iteratee = undefined;
      }
      return isArrayLikeObject(array)
        ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee, 2))
        : [];
    });

    /**
     * This method is like `_.difference` except that it accepts `comparator`
     * which is invoked to compare elements of `array` to `values`. The order and
     * references of result values are determined by the first array. The comparator
     * is invoked with two arguments: (arrVal, othVal).
     *
     * **Note:** Unlike `_.pullAllWith`, this method returns a new array.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The array to inspect.
     * @param {...Array} [values] The values to exclude.
     * @param {Function} [comparator] The comparator invoked per element.
     * @returns {Array} Returns the new array of filtered values.
     * @example
     *
     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
     *
     * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
     * // => [{ 'x': 2, 'y': 1 }]
     */
    var differenceWith = baseRest(function(array, values) {
      var comparator = last(values);
      if (isArrayLikeObject(comparator)) {
        comparator = undefined;
      }
      return isArrayLikeObject(array)
        ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator)
        : [];
    });

    /**
     * Creates a slice of `array` with `n` elements dropped from the beginning.
     *
     * @static
     * @memberOf _
     * @since 0.5.0
     * @category Array
     * @param {Array} array The array to query.
     * @param {number} [n=1] The number of elements to drop.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.drop([1, 2, 3]);
     * // => [2, 3]
     *
     * _.drop([1, 2, 3], 2);
     * // => [3]
     *
     * _.drop([1, 2, 3], 5);
     * // => []
     *
     * _.drop([1, 2, 3], 0);
     * // => [1, 2, 3]
     */
    function drop(array, n, guard) {
      var length = array == null ? 0 : array.length;
      if (!length) {
        return [];
      }
      n = (guard || n === undefined) ? 1 : toInteger(n);
      return baseSlice(array, n < 0 ? 0 : n, length);
    }

    /**
     * Creates a slice of `array` with `n` elements dropped from the end.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Array
     * @param {Array} array The array to query.
     * @param {number} [n=1] The number of elements to drop.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.dropRight([1, 2, 3]);
     * // => [1, 2]
     *
     * _.dropRight([1, 2, 3], 2);
     * // => [1]
     *
     * _.dropRight([1, 2, 3], 5);
     * // => []
     *
     * _.dropRight([1, 2, 3], 0);
     * // => [1, 2, 3]
     */
    function dropRight(array, n, guard) {
      var length = array == null ? 0 : array.length;
      if (!length) {
        return [];
      }
      n = (guard || n === undefined) ? 1 : toInteger(n);
      n = length - n;
      return baseSlice(array, 0, n < 0 ? 0 : n);
    }

    /**
     * Creates a slice of `array` excluding elements dropped from the end.
     * Elements are dropped until `predicate` returns falsey. The predicate is
     * invoked with three arguments: (value, index, array).
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Array
     * @param {Array} array The array to query.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * var users = [
     *   { 'user': 'barney',  'active': true },
     *   { 'user': 'fred',    'active': false },
     *   { 'user': 'pebbles', 'active': false }
     * ];
     *
     * _.dropRightWhile(users, function(o) { return !o.active; });
     * // => objects for ['barney']
     *
     * // The `_.matches` iteratee shorthand.
     * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false });
     * // => objects for ['barney', 'fred']
     *
     * // The `_.matchesProperty` iteratee shorthand.
     * _.dropRightWhile(users, ['active', false]);
     * // => objects for ['barney']
     *
     * // The `_.property` iteratee shorthand.
     * _.dropRightWhile(users, 'active');
     * // => objects for ['barney', 'fred', 'pebbles']
     */
    function dropRightWhile(array, predicate) {
      return (array && array.length)
        ? baseWhile(array, getIteratee(predicate, 3), true, true)
        : [];
    }

    /**
     * Creates a slice of `array` excluding elements dropped from the beginning.
     * Elements are dropped until `predicate` returns falsey. The predicate is
     * invoked with three arguments: (value, index, array).
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Array
     * @param {Array} array The array to query.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * var users = [
     *   { 'user': 'barney',  'active': false },
     *   { 'user': 'fred',    'active': false },
     *   { 'user': 'pebbles', 'active': true }
     * ];
     *
     * _.dropWhile(users, function(o) { return !o.active; });
     * // => objects for ['pebbles']
     *
     * // The `_.matches` iteratee shorthand.
     * _.dropWhile(users, { 'user': 'barney', 'active': false });
     * // => objects for ['fred', 'pebbles']
     *
     * // The `_.matchesProperty` iteratee shorthand.
     * _.dropWhile(users, ['active', false]);
     * // => objects for ['pebbles']
     *
     * // The `_.property` iteratee shorthand.
     * _.dropWhile(users, 'active');
     * // => objects for ['barney', 'fred', 'pebbles']
     */
    function dropWhile(array, predicate) {
      return (array && array.length)
        ? baseWhile(array, getIteratee(predicate, 3), true)
        : [];
    }

    /**
     * Fills elements of `array` with `value` from `start` up to, but not
     * including, `end`.
     *
     * **Note:** This method mutates `array`.
     *
     * @static
     * @memberOf _
     * @since 3.2.0
     * @category Array
     * @param {Array} array The array to fill.
     * @param {*} value The value to fill `array` with.
     * @param {number} [start=0] The start position.
     * @param {number} [end=array.length] The end position.
     * @returns {Array} Returns `array`.
     * @example
     *
     * var array = [1, 2, 3];
     *
     * _.fill(array, 'a');
     * console.log(array);
     * // => ['a', 'a', 'a']
     *
     * _.fill(Array(3), 2);
     * // => [2, 2, 2]
     *
     * _.fill([4, 6, 8, 10], '*', 1, 3);
     * // => [4, '*', '*', 10]
     */
    function fill(array, value, start, end) {
      var length = array == null ? 0 : array.length;
      if (!length) {
        return [];
      }
      if (start && typeof start != 'number' && isIterateeCall(array, value, start)) {
        start = 0;
        end = length;
      }
      return baseFill(array, value, start, end);
    }

    /**
     * This method is like `_.find` except that it returns the index of the first
     * element `predicate` returns truthy for instead of the element itself.
     *
     * @static
     * @memberOf _
     * @since 1.1.0
     * @category Array
     * @param {Array} array The array to inspect.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @param {number} [fromIndex=0] The index to search from.
     * @returns {number} Returns the index of the found element, else `-1`.
     * @example
     *
     * var users = [
     *   { 'user': 'barney',  'active': false },
     *   { 'user': 'fred',    'active': false },
     *   { 'user': 'pebbles', 'active': true }
     * ];
     *
     * _.findIndex(users, function(o) { return o.user == 'barney'; });
     * // => 0
     *
     * // The `_.matches` iteratee shorthand.
     * _.findIndex(users, { 'user': 'fred', 'active': false });
     * // => 1
     *
     * // The `_.matchesProperty` iteratee shorthand.
     * _.findIndex(users, ['active', false]);
     * // => 0
     *
     * // The `_.property` iteratee shorthand.
     * _.findIndex(users, 'active');
     * // => 2
     */
    function findIndex(array, predicate, fromIndex) {
      var length = array == null ? 0 : array.length;
      if (!length) {
        return -1;
      }
      var index = fromIndex == null ? 0 : toInteger(fromIndex);
      if (index < 0) {
        index = nativeMax(length + index, 0);
      }
      return baseFindIndex(array, getIteratee(predicate, 3), index);
    }

    /**
     * This method is like `_.findIndex` except that it iterates over elements
     * of `collection` from right to left.
     *
     * @static
     * @memberOf _
     * @since 2.0.0
     * @category Array
     * @param {Array} array The array to inspect.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @param {number} [fromIndex=array.length-1] The index to search from.
     * @returns {number} Returns the index of the found element, else `-1`.
     * @example
     *
     * var users = [
     *   { 'user': 'barney',  'active': true },
     *   { 'user': 'fred',    'active': false },
     *   { 'user': 'pebbles', 'active': false }
     * ];
     *
     * _.findLastIndex(users, function(o) { return o.user == 'pebbles'; });
     * // => 2
     *
     * // The `_.matches` iteratee shorthand.
     * _.findLastIndex(users, { 'user': 'barney', 'active': true });
     * // => 0
     *
     * // The `_.matchesProperty` iteratee shorthand.
     * _.findLastIndex(users, ['active', false]);
     * // => 2
     *
     * // The `_.property` iteratee shorthand.
     * _.findLastIndex(users, 'active');
     * // => 0
     */
    function findLastIndex(array, predicate, fromIndex) {
      var length = array == null ? 0 : array.length;
      if (!length) {
        return -1;
      }
      var index = length - 1;
      if (fromIndex !== undefined) {
        index = toInteger(fromIndex);
        index = fromIndex < 0
          ? nativeMax(length + index, 0)
          : nativeMin(index, length - 1);
      }
      return baseFindIndex(array, getIteratee(predicate, 3), index, true);
    }

    /**
     * Flattens `array` a single level deep.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Array
     * @param {Array} array The array to flatten.
     * @returns {Array} Returns the new flattened array.
     * @example
     *
     * _.flatten([1, [2, [3, [4]], 5]]);
     * // => [1, 2, [3, [4]], 5]
     */
    function flatten(array) {
      var length = array == null ? 0 : array.length;
      return length ? baseFlatten(array, 1) : [];
    }

    /**
     * Recursively flattens `array`.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Array
     * @param {Array} array The array to flatten.
     * @returns {Array} Returns the new flattened array.
     * @example
     *
     * _.flattenDeep([1, [2, [3, [4]], 5]]);
     * // => [1, 2, 3, 4, 5]
     */
    function flattenDeep(array) {
      var length = array == null ? 0 : array.length;
      return length ? baseFlatten(array, INFINITY) : [];
    }

    /**
     * Recursively flatten `array` up to `depth` times.
     *
     * @static
     * @memberOf _
     * @since 4.4.0
     * @category Array
     * @param {Array} array The array to flatten.
     * @param {number} [depth=1] The maximum recursion depth.
     * @returns {Array} Returns the new flattened array.
     * @example
     *
     * var array = [1, [2, [3, [4]], 5]];
     *
     * _.flattenDepth(array, 1);
     * // => [1, 2, [3, [4]], 5]
     *
     * _.flattenDepth(array, 2);
     * // => [1, 2, 3, [4], 5]
     */
    function flattenDepth(array, depth) {
      var length = array == null ? 0 : array.length;
      if (!length) {
        return [];
      }
      depth = depth === undefined ? 1 : toInteger(depth);
      return baseFlatten(array, depth);
    }

    /**
     * The inverse of `_.toPairs`; this method returns an object composed
     * from key-value `pairs`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} pairs The key-value pairs.
     * @returns {Object} Returns the new object.
     * @example
     *
     * _.fromPairs([['a', 1], ['b', 2]]);
     * // => { 'a': 1, 'b': 2 }
     */
    function fromPairs(pairs) {
      var index = -1,
          length = pairs == null ? 0 : pairs.length,
          result = {};

      while (++index < length) {
        var pair = pairs[index];
        result[pair[0]] = pair[1];
      }
      return result;
    }

    /**
     * Gets the first element of `array`.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @alias first
     * @category Array
     * @param {Array} array The array to query.
     * @returns {*} Returns the first element of `array`.
     * @example
     *
     * _.head([1, 2, 3]);
     * // => 1
     *
     * _.head([]);
     * // => undefined
     */
    function head(array) {
      return (array && array.length) ? array[0] : undefined;
    }

    /**
     * Gets the index at which the first occurrence of `value` is found in `array`
     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
     * for equality comparisons. If `fromIndex` is negative, it's used as the
     * offset from the end of `array`.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Array
     * @param {Array} array The array to inspect.
     * @param {*} value The value to search for.
     * @param {number} [fromIndex=0] The index to search from.
     * @returns {number} Returns the index of the matched value, else `-1`.
     * @example
     *
     * _.indexOf([1, 2, 1, 2], 2);
     * // => 1
     *
     * // Search from the `fromIndex`.
     * _.indexOf([1, 2, 1, 2], 2, 2);
     * // => 3
     */
    function indexOf(array, value, fromIndex) {
      var length = array == null ? 0 : array.length;
      if (!length) {
        return -1;
      }
      var index = fromIndex == null ? 0 : toInteger(fromIndex);
      if (index < 0) {
        index = nativeMax(length + index, 0);
      }
      return baseIndexOf(array, value, index);
    }

    /**
     * Gets all but the last element of `array`.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Array
     * @param {Array} array The array to query.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.initial([1, 2, 3]);
     * // => [1, 2]
     */
    function initial(array) {
      var length = array == null ? 0 : array.length;
      return length ? baseSlice(array, 0, -1) : [];
    }

    /**
     * Creates an array of unique values that are included in all given arrays
     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
     * for equality comparisons. The order and references of result values are
     * determined by the first array.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Array
     * @param {...Array} [arrays] The arrays to inspect.
     * @returns {Array} Returns the new array of intersecting values.
     * @example
     *
     * _.intersection([2, 1], [2, 3]);
     * // => [2]
     */
    var intersection = baseRest(function(arrays) {
      var mapped = arrayMap(arrays, castArrayLikeObject);
      return (mapped.length && mapped[0] === arrays[0])
        ? baseIntersection(mapped)
        : [];
    });

    /**
     * This method is like `_.intersection` except that it accepts `iteratee`
     * which is invoked for each element of each `arrays` to generate the criterion
     * by which they're compared. The order and references of result values are
     * determined by the first array. The iteratee is invoked with one argument:
     * (value).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {...Array} [arrays] The arrays to inspect.
     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
     * @returns {Array} Returns the new array of intersecting values.
     * @example
     *
     * _.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor);
     * // => [2.1]
     *
     * // The `_.property` iteratee shorthand.
     * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
     * // => [{ 'x': 1 }]
     */
    var intersectionBy = baseRest(function(arrays) {
      var iteratee = last(arrays),
          mapped = arrayMap(arrays, castArrayLikeObject);

      if (iteratee === last(mapped)) {
        iteratee = undefined;
      } else {
        mapped.pop();
      }
      return (mapped.length && mapped[0] === arrays[0])
        ? baseIntersection(mapped, getIteratee(iteratee, 2))
        : [];
    });

    /**
     * This method is like `_.intersection` except that it accepts `comparator`
     * which is invoked to compare elements of `arrays`. The order and references
     * of result values are determined by the first array. The comparator is
     * invoked with two arguments: (arrVal, othVal).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {...Array} [arrays] The arrays to inspect.
     * @param {Function} [comparator] The comparator invoked per element.
     * @returns {Array} Returns the new array of intersecting values.
     * @example
     *
     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
     *
     * _.intersectionWith(objects, others, _.isEqual);
     * // => [{ 'x': 1, 'y': 2 }]
     */
    var intersectionWith = baseRest(function(arrays) {
      var comparator = last(arrays),
          mapped = arrayMap(arrays, castArrayLikeObject);

      comparator = typeof comparator == 'function' ? comparator : undefined;
      if (comparator) {
        mapped.pop();
      }
      return (mapped.length && mapped[0] === arrays[0])
        ? baseIntersection(mapped, undefined, comparator)
        : [];
    });

    /**
     * Converts all elements in `array` into a string separated by `separator`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The array to convert.
     * @param {string} [separator=','] The element separator.
     * @returns {string} Returns the joined string.
     * @example
     *
     * _.join(['a', 'b', 'c'], '~');
     * // => 'a~b~c'
     */
    function join(array, separator) {
      return array == null ? '' : nativeJoin.call(array, separator);
    }

    /**
     * Gets the last element of `array`.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Array
     * @param {Array} array The array to query.
     * @returns {*} Returns the last element of `array`.
     * @example
     *
     * _.last([1, 2, 3]);
     * // => 3
     */
    function last(array) {
      var length = array == null ? 0 : array.length;
      return length ? array[length - 1] : undefined;
    }

    /**
     * This method is like `_.indexOf` except that it iterates over elements of
     * `array` from right to left.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Array
     * @param {Array} array The array to inspect.
     * @param {*} value The value to search for.
     * @param {number} [fromIndex=array.length-1] The index to search from.
     * @returns {number} Returns the index of the matched value, else `-1`.
     * @example
     *
     * _.lastIndexOf([1, 2, 1, 2], 2);
     * // => 3
     *
     * // Search from the `fromIndex`.
     * _.lastIndexOf([1, 2, 1, 2], 2, 2);
     * // => 1
     */
    function lastIndexOf(array, value, fromIndex) {
      var length = array == null ? 0 : array.length;
      if (!length) {
        return -1;
      }
      var index = length;
      if (fromIndex !== undefined) {
        index = toInteger(fromIndex);
        index = index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1);
      }
      return value === value
        ? strictLastIndexOf(array, value, index)
        : baseFindIndex(array, baseIsNaN, index, true);
    }

    /**
     * Gets the element at index `n` of `array`. If `n` is negative, the nth
     * element from the end is returned.
     *
     * @static
     * @memberOf _
     * @since 4.11.0
     * @category Array
     * @param {Array} array The array to query.
     * @param {number} [n=0] The index of the element to return.
     * @returns {*} Returns the nth element of `array`.
     * @example
     *
     * var array = ['a', 'b', 'c', 'd'];
     *
     * _.nth(array, 1);
     * // => 'b'
     *
     * _.nth(array, -2);
     * // => 'c';
     */
    function nth(array, n) {
      return (array && array.length) ? baseNth(array, toInteger(n)) : undefined;
    }

    /**
     * Removes all given values from `array` using
     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
     * for equality comparisons.
     *
     * **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove`
     * to remove elements from an array by predicate.
     *
     * @static
     * @memberOf _
     * @since 2.0.0
     * @category Array
     * @param {Array} array The array to modify.
     * @param {...*} [values] The values to remove.
     * @returns {Array} Returns `array`.
     * @example
     *
     * var array = ['a', 'b', 'c', 'a', 'b', 'c'];
     *
     * _.pull(array, 'a', 'c');
     * console.log(array);
     * // => ['b', 'b']
     */
    var pull = baseRest(pullAll);

    /**
     * This method is like `_.pull` except that it accepts an array of values to remove.
     *
     * **Note:** Unlike `_.difference`, this method mutates `array`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The array to modify.
     * @param {Array} values The values to remove.
     * @returns {Array} Returns `array`.
     * @example
     *
     * var array = ['a', 'b', 'c', 'a', 'b', 'c'];
     *
     * _.pullAll(array, ['a', 'c']);
     * console.log(array);
     * // => ['b', 'b']
     */
    function pullAll(array, values) {
      return (array && array.length && values && values.length)
        ? basePullAll(array, values)
        : array;
    }

    /**
     * This method is like `_.pullAll` except that it accepts `iteratee` which is
     * invoked for each element of `array` and `values` to generate the criterion
     * by which they're compared. The iteratee is invoked with one argument: (value).
     *
     * **Note:** Unlike `_.differenceBy`, this method mutates `array`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The array to modify.
     * @param {Array} values The values to remove.
     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
     * @returns {Array} Returns `array`.
     * @example
     *
     * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
     *
     * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');
     * console.log(array);
     * // => [{ 'x': 2 }]
     */
    function pullAllBy(array, values, iteratee) {
      return (array && array.length && values && values.length)
        ? basePullAll(array, values, getIteratee(iteratee, 2))
        : array;
    }

    /**
     * This method is like `_.pullAll` except that it accepts `comparator` which
     * is invoked to compare elements of `array` to `values`. The comparator is
     * invoked with two arguments: (arrVal, othVal).
     *
     * **Note:** Unlike `_.differenceWith`, this method mutates `array`.
     *
     * @static
     * @memberOf _
     * @since 4.6.0
     * @category Array
     * @param {Array} array The array to modify.
     * @param {Array} values The values to remove.
     * @param {Function} [comparator] The comparator invoked per element.
     * @returns {Array} Returns `array`.
     * @example
     *
     * var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }];
     *
     * _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual);
     * console.log(array);
     * // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }]
     */
    function pullAllWith(array, values, comparator) {
      return (array && array.length && values && values.length)
        ? basePullAll(array, values, undefined, comparator)
        : array;
    }

    /**
     * Removes elements from `array` corresponding to `indexes` and returns an
     * array of removed elements.
     *
     * **Note:** Unlike `_.at`, this method mutates `array`.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Array
     * @param {Array} array The array to modify.
     * @param {...(number|number[])} [indexes] The indexes of elements to remove.
     * @returns {Array} Returns the new array of removed elements.
     * @example
     *
     * var array = ['a', 'b', 'c', 'd'];
     * var pulled = _.pullAt(array, [1, 3]);
     *
     * console.log(array);
     * // => ['a', 'c']
     *
     * console.log(pulled);
     * // => ['b', 'd']
     */
    var pullAt = flatRest(function(array, indexes) {
      var length = array == null ? 0 : array.length,
          result = baseAt(array, indexes);

      basePullAt(array, arrayMap(indexes, function(index) {
        return isIndex(index, length) ? +index : index;
      }).sort(compareAscending));

      return result;
    });

    /**
     * Removes all elements from `array` that `predicate` returns truthy for
     * and returns an array of the removed elements. The predicate is invoked
     * with three arguments: (value, index, array).
     *
     * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull`
     * to pull elements from an array by value.
     *
     * @static
     * @memberOf _
     * @since 2.0.0
     * @category Array
     * @param {Array} array The array to modify.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @returns {Array} Returns the new array of removed elements.
     * @example
     *
     * var array = [1, 2, 3, 4];
     * var evens = _.remove(array, function(n) {
     *   return n % 2 == 0;
     * });
     *
     * console.log(array);
     * // => [1, 3]
     *
     * console.log(evens);
     * // => [2, 4]
     */
    function remove(array, predicate) {
      var result = [];
      if (!(array && array.length)) {
        return result;
      }
      var index = -1,
          indexes = [],
          length = array.length;

      predicate = getIteratee(predicate, 3);
      while (++index < length) {
        var value = array[index];
        if (predicate(value, index, array)) {
          result.push(value);
          indexes.push(index);
        }
      }
      basePullAt(array, indexes);
      return result;
    }

    /**
     * Reverses `array` so that the first element becomes the last, the second
     * element becomes the second to last, and so on.
     *
     * **Note:** This method mutates `array` and is based on
     * [`Array#reverse`](https://mdn.io/Array/reverse).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The array to modify.
     * @returns {Array} Returns `array`.
     * @example
     *
     * var array = [1, 2, 3];
     *
     * _.reverse(array);
     * // => [3, 2, 1]
     *
     * console.log(array);
     * // => [3, 2, 1]
     */
    function reverse(array) {
      return array == null ? array : nativeReverse.call(array);
    }

    /**
     * Creates a slice of `array` from `start` up to, but not including, `end`.
     *
     * **Note:** This method is used instead of
     * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are
     * returned.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Array
     * @param {Array} array The array to slice.
     * @param {number} [start=0] The start position.
     * @param {number} [end=array.length] The end position.
     * @returns {Array} Returns the slice of `array`.
     */
    function slice(array, start, end) {
      var length = array == null ? 0 : array.length;
      if (!length) {
        return [];
      }
      if (end && typeof end != 'number' && isIterateeCall(array, start, end)) {
        start = 0;
        end = length;
      }
      else {
        start = start == null ? 0 : toInteger(start);
        end = end === undefined ? length : toInteger(end);
      }
      return baseSlice(array, start, end);
    }

    /**
     * Uses a binary search to determine the lowest index at which `value`
     * should be inserted into `array` in order to maintain its sort order.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Array
     * @param {Array} array The sorted array to inspect.
     * @param {*} value The value to evaluate.
     * @returns {number} Returns the index at which `value` should be inserted
     *  into `array`.
     * @example
     *
     * _.sortedIndex([30, 50], 40);
     * // => 1
     */
    function sortedIndex(array, value) {
      return baseSortedIndex(array, value);
    }

    /**
     * This method is like `_.sortedIndex` except that it accepts `iteratee`
     * which is invoked for `value` and each element of `array` to compute their
     * sort ranking. The iteratee is invoked with one argument: (value).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The sorted array to inspect.
     * @param {*} value The value to evaluate.
     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
     * @returns {number} Returns the index at which `value` should be inserted
     *  into `array`.
     * @example
     *
     * var objects = [{ 'x': 4 }, { 'x': 5 }];
     *
     * _.sortedIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
     * // => 0
     *
     * // The `_.property` iteratee shorthand.
     * _.sortedIndexBy(objects, { 'x': 4 }, 'x');
     * // => 0
     */
    function sortedIndexBy(array, value, iteratee) {
      return baseSortedIndexBy(array, value, getIteratee(iteratee, 2));
    }

    /**
     * This method is like `_.indexOf` except that it performs a binary
     * search on a sorted `array`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The array to inspect.
     * @param {*} value The value to search for.
     * @returns {number} Returns the index of the matched value, else `-1`.
     * @example
     *
     * _.sortedIndexOf([4, 5, 5, 5, 6], 5);
     * // => 1
     */
    function sortedIndexOf(array, value) {
      var length = array == null ? 0 : array.length;
      if (length) {
        var index = baseSortedIndex(array, value);
        if (index < length && eq(array[index], value)) {
          return index;
        }
      }
      return -1;
    }

    /**
     * This method is like `_.sortedIndex` except that it returns the highest
     * index at which `value` should be inserted into `array` in order to
     * maintain its sort order.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Array
     * @param {Array} array The sorted array to inspect.
     * @param {*} value The value to evaluate.
     * @returns {number} Returns the index at which `value` should be inserted
     *  into `array`.
     * @example
     *
     * _.sortedLastIndex([4, 5, 5, 5, 6], 5);
     * // => 4
     */
    function sortedLastIndex(array, value) {
      return baseSortedIndex(array, value, true);
    }

    /**
     * This method is like `_.sortedLastIndex` except that it accepts `iteratee`
     * which is invoked for `value` and each element of `array` to compute their
     * sort ranking. The iteratee is invoked with one argument: (value).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The sorted array to inspect.
     * @param {*} value The value to evaluate.
     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
     * @returns {number} Returns the index at which `value` should be inserted
     *  into `array`.
     * @example
     *
     * var objects = [{ 'x': 4 }, { 'x': 5 }];
     *
     * _.sortedLastIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
     * // => 1
     *
     * // The `_.property` iteratee shorthand.
     * _.sortedLastIndexBy(objects, { 'x': 4 }, 'x');
     * // => 1
     */
    function sortedLastIndexBy(array, value, iteratee) {
      return baseSortedIndexBy(array, value, getIteratee(iteratee, 2), true);
    }

    /**
     * This method is like `_.lastIndexOf` except that it performs a binary
     * search on a sorted `array`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The array to inspect.
     * @param {*} value The value to search for.
     * @returns {number} Returns the index of the matched value, else `-1`.
     * @example
     *
     * _.sortedLastIndexOf([4, 5, 5, 5, 6], 5);
     * // => 3
     */
    function sortedLastIndexOf(array, value) {
      var length = array == null ? 0 : array.length;
      if (length) {
        var index = baseSortedIndex(array, value, true) - 1;
        if (eq(array[index], value)) {
          return index;
        }
      }
      return -1;
    }

    /**
     * This method is like `_.uniq` except that it's designed and optimized
     * for sorted arrays.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The array to inspect.
     * @returns {Array} Returns the new duplicate free array.
     * @example
     *
     * _.sortedUniq([1, 1, 2]);
     * // => [1, 2]
     */
    function sortedUniq(array) {
      return (array && array.length)
        ? baseSortedUniq(array)
        : [];
    }

    /**
     * This method is like `_.uniqBy` except that it's designed and optimized
     * for sorted arrays.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The array to inspect.
     * @param {Function} [iteratee] The iteratee invoked per element.
     * @returns {Array} Returns the new duplicate free array.
     * @example
     *
     * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);
     * // => [1.1, 2.3]
     */
    function sortedUniqBy(array, iteratee) {
      return (array && array.length)
        ? baseSortedUniq(array, getIteratee(iteratee, 2))
        : [];
    }

    /**
     * Gets all but the first element of `array`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The array to query.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.tail([1, 2, 3]);
     * // => [2, 3]
     */
    function tail(array) {
      var length = array == null ? 0 : array.length;
      return length ? baseSlice(array, 1, length) : [];
    }

    /**
     * Creates a slice of `array` with `n` elements taken from the beginning.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Array
     * @param {Array} array The array to query.
     * @param {number} [n=1] The number of elements to take.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.take([1, 2, 3]);
     * // => [1]
     *
     * _.take([1, 2, 3], 2);
     * // => [1, 2]
     *
     * _.take([1, 2, 3], 5);
     * // => [1, 2, 3]
     *
     * _.take([1, 2, 3], 0);
     * // => []
     */
    function take(array, n, guard) {
      if (!(array && array.length)) {
        return [];
      }
      n = (guard || n === undefined) ? 1 : toInteger(n);
      return baseSlice(array, 0, n < 0 ? 0 : n);
    }

    /**
     * Creates a slice of `array` with `n` elements taken from the end.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Array
     * @param {Array} array The array to query.
     * @param {number} [n=1] The number of elements to take.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * _.takeRight([1, 2, 3]);
     * // => [3]
     *
     * _.takeRight([1, 2, 3], 2);
     * // => [2, 3]
     *
     * _.takeRight([1, 2, 3], 5);
     * // => [1, 2, 3]
     *
     * _.takeRight([1, 2, 3], 0);
     * // => []
     */
    function takeRight(array, n, guard) {
      var length = array == null ? 0 : array.length;
      if (!length) {
        return [];
      }
      n = (guard || n === undefined) ? 1 : toInteger(n);
      n = length - n;
      return baseSlice(array, n < 0 ? 0 : n, length);
    }

    /**
     * Creates a slice of `array` with elements taken from the end. Elements are
     * taken until `predicate` returns falsey. The predicate is invoked with
     * three arguments: (value, index, array).
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Array
     * @param {Array} array The array to query.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * var users = [
     *   { 'user': 'barney',  'active': true },
     *   { 'user': 'fred',    'active': false },
     *   { 'user': 'pebbles', 'active': false }
     * ];
     *
     * _.takeRightWhile(users, function(o) { return !o.active; });
     * // => objects for ['fred', 'pebbles']
     *
     * // The `_.matches` iteratee shorthand.
     * _.takeRightWhile(users, { 'user': 'pebbles', 'active': false });
     * // => objects for ['pebbles']
     *
     * // The `_.matchesProperty` iteratee shorthand.
     * _.takeRightWhile(users, ['active', false]);
     * // => objects for ['fred', 'pebbles']
     *
     * // The `_.property` iteratee shorthand.
     * _.takeRightWhile(users, 'active');
     * // => []
     */
    function takeRightWhile(array, predicate) {
      return (array && array.length)
        ? baseWhile(array, getIteratee(predicate, 3), false, true)
        : [];
    }

    /**
     * Creates a slice of `array` with elements taken from the beginning. Elements
     * are taken until `predicate` returns falsey. The predicate is invoked with
     * three arguments: (value, index, array).
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Array
     * @param {Array} array The array to query.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @returns {Array} Returns the slice of `array`.
     * @example
     *
     * var users = [
     *   { 'user': 'barney',  'active': false },
     *   { 'user': 'fred',    'active': false },
     *   { 'user': 'pebbles', 'active': true }
     * ];
     *
     * _.takeWhile(users, function(o) { return !o.active; });
     * // => objects for ['barney', 'fred']
     *
     * // The `_.matches` iteratee shorthand.
     * _.takeWhile(users, { 'user': 'barney', 'active': false });
     * // => objects for ['barney']
     *
     * // The `_.matchesProperty` iteratee shorthand.
     * _.takeWhile(users, ['active', false]);
     * // => objects for ['barney', 'fred']
     *
     * // The `_.property` iteratee shorthand.
     * _.takeWhile(users, 'active');
     * // => []
     */
    function takeWhile(array, predicate) {
      return (array && array.length)
        ? baseWhile(array, getIteratee(predicate, 3))
        : [];
    }

    /**
     * Creates an array of unique values, in order, from all given arrays using
     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
     * for equality comparisons.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Array
     * @param {...Array} [arrays] The arrays to inspect.
     * @returns {Array} Returns the new array of combined values.
     * @example
     *
     * _.union([2], [1, 2]);
     * // => [2, 1]
     */
    var union = baseRest(function(arrays) {
      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true));
    });

    /**
     * This method is like `_.union` except that it accepts `iteratee` which is
     * invoked for each element of each `arrays` to generate the criterion by
     * which uniqueness is computed. Result values are chosen from the first
     * array in which the value occurs. The iteratee is invoked with one argument:
     * (value).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {...Array} [arrays] The arrays to inspect.
     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
     * @returns {Array} Returns the new array of combined values.
     * @example
     *
     * _.unionBy([2.1], [1.2, 2.3], Math.floor);
     * // => [2.1, 1.2]
     *
     * // The `_.property` iteratee shorthand.
     * _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
     * // => [{ 'x': 1 }, { 'x': 2 }]
     */
    var unionBy = baseRest(function(arrays) {
      var iteratee = last(arrays);
      if (isArrayLikeObject(iteratee)) {
        iteratee = undefined;
      }
      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), getIteratee(iteratee, 2));
    });

    /**
     * This method is like `_.union` except that it accepts `comparator` which
     * is invoked to compare elements of `arrays`. Result values are chosen from
     * the first array in which the value occurs. The comparator is invoked
     * with two arguments: (arrVal, othVal).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {...Array} [arrays] The arrays to inspect.
     * @param {Function} [comparator] The comparator invoked per element.
     * @returns {Array} Returns the new array of combined values.
     * @example
     *
     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
     *
     * _.unionWith(objects, others, _.isEqual);
     * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
     */
    var unionWith = baseRest(function(arrays) {
      var comparator = last(arrays);
      comparator = typeof comparator == 'function' ? comparator : undefined;
      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator);
    });

    /**
     * Creates a duplicate-free version of an array, using
     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
     * for equality comparisons, in which only the first occurrence of each element
     * is kept. The order of result values is determined by the order they occur
     * in the array.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Array
     * @param {Array} array The array to inspect.
     * @returns {Array} Returns the new duplicate free array.
     * @example
     *
     * _.uniq([2, 1, 2]);
     * // => [2, 1]
     */
    function uniq(array) {
      return (array && array.length) ? baseUniq(array) : [];
    }

    /**
     * This method is like `_.uniq` except that it accepts `iteratee` which is
     * invoked for each element in `array` to generate the criterion by which
     * uniqueness is computed. The order of result values is determined by the
     * order they occur in the array. The iteratee is invoked with one argument:
     * (value).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The array to inspect.
     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
     * @returns {Array} Returns the new duplicate free array.
     * @example
     *
     * _.uniqBy([2.1, 1.2, 2.3], Math.floor);
     * // => [2.1, 1.2]
     *
     * // The `_.property` iteratee shorthand.
     * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
     * // => [{ 'x': 1 }, { 'x': 2 }]
     */
    function uniqBy(array, iteratee) {
      return (array && array.length) ? baseUniq(array, getIteratee(iteratee, 2)) : [];
    }

    /**
     * This method is like `_.uniq` except that it accepts `comparator` which
     * is invoked to compare elements of `array`. The order of result values is
     * determined by the order they occur in the array.The comparator is invoked
     * with two arguments: (arrVal, othVal).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {Array} array The array to inspect.
     * @param {Function} [comparator] The comparator invoked per element.
     * @returns {Array} Returns the new duplicate free array.
     * @example
     *
     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];
     *
     * _.uniqWith(objects, _.isEqual);
     * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
     */
    function uniqWith(array, comparator) {
      comparator = typeof comparator == 'function' ? comparator : undefined;
      return (array && array.length) ? baseUniq(array, undefined, comparator) : [];
    }

    /**
     * This method is like `_.zip` except that it accepts an array of grouped
     * elements and creates an array regrouping the elements to their pre-zip
     * configuration.
     *
     * @static
     * @memberOf _
     * @since 1.2.0
     * @category Array
     * @param {Array} array The array of grouped elements to process.
     * @returns {Array} Returns the new array of regrouped elements.
     * @example
     *
     * var zipped = _.zip(['a', 'b'], [1, 2], [true, false]);
     * // => [['a', 1, true], ['b', 2, false]]
     *
     * _.unzip(zipped);
     * // => [['a', 'b'], [1, 2], [true, false]]
     */
    function unzip(array) {
      if (!(array && array.length)) {
        return [];
      }
      var length = 0;
      array = arrayFilter(array, function(group) {
        if (isArrayLikeObject(group)) {
          length = nativeMax(group.length, length);
          return true;
        }
      });
      return baseTimes(length, function(index) {
        return arrayMap(array, baseProperty(index));
      });
    }

    /**
     * This method is like `_.unzip` except that it accepts `iteratee` to specify
     * how regrouped values should be combined. The iteratee is invoked with the
     * elements of each group: (...group).
     *
     * @static
     * @memberOf _
     * @since 3.8.0
     * @category Array
     * @param {Array} array The array of grouped elements to process.
     * @param {Function} [iteratee=_.identity] The function to combine
     *  regrouped values.
     * @returns {Array} Returns the new array of regrouped elements.
     * @example
     *
     * var zipped = _.zip([1, 2], [10, 20], [100, 200]);
     * // => [[1, 10, 100], [2, 20, 200]]
     *
     * _.unzipWith(zipped, _.add);
     * // => [3, 30, 300]
     */
    function unzipWith(array, iteratee) {
      if (!(array && array.length)) {
        return [];
      }
      var result = unzip(array);
      if (iteratee == null) {
        return result;
      }
      return arrayMap(result, function(group) {
        return apply(iteratee, undefined, group);
      });
    }

    /**
     * Creates an array excluding all given values using
     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
     * for equality comparisons.
     *
     * **Note:** Unlike `_.pull`, this method returns a new array.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Array
     * @param {Array} array The array to inspect.
     * @param {...*} [values] The values to exclude.
     * @returns {Array} Returns the new array of filtered values.
     * @see _.difference, _.xor
     * @example
     *
     * _.without([2, 1, 2, 3], 1, 2);
     * // => [3]
     */
    var without = baseRest(function(array, values) {
      return isArrayLikeObject(array)
        ? baseDifference(array, values)
        : [];
    });

    /**
     * Creates an array of unique values that is the
     * [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
     * of the given arrays. The order of result values is determined by the order
     * they occur in the arrays.
     *
     * @static
     * @memberOf _
     * @since 2.4.0
     * @category Array
     * @param {...Array} [arrays] The arrays to inspect.
     * @returns {Array} Returns the new array of filtered values.
     * @see _.difference, _.without
     * @example
     *
     * _.xor([2, 1], [2, 3]);
     * // => [1, 3]
     */
    var xor = baseRest(function(arrays) {
      return baseXor(arrayFilter(arrays, isArrayLikeObject));
    });

    /**
     * This method is like `_.xor` except that it accepts `iteratee` which is
     * invoked for each element of each `arrays` to generate the criterion by
     * which by which they're compared. The order of result values is determined
     * by the order they occur in the arrays. The iteratee is invoked with one
     * argument: (value).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {...Array} [arrays] The arrays to inspect.
     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
     * @returns {Array} Returns the new array of filtered values.
     * @example
     *
     * _.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor);
     * // => [1.2, 3.4]
     *
     * // The `_.property` iteratee shorthand.
     * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
     * // => [{ 'x': 2 }]
     */
    var xorBy = baseRest(function(arrays) {
      var iteratee = last(arrays);
      if (isArrayLikeObject(iteratee)) {
        iteratee = undefined;
      }
      return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee, 2));
    });

    /**
     * This method is like `_.xor` except that it accepts `comparator` which is
     * invoked to compare elements of `arrays`. The order of result values is
     * determined by the order they occur in the arrays. The comparator is invoked
     * with two arguments: (arrVal, othVal).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Array
     * @param {...Array} [arrays] The arrays to inspect.
     * @param {Function} [comparator] The comparator invoked per element.
     * @returns {Array} Returns the new array of filtered values.
     * @example
     *
     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
     *
     * _.xorWith(objects, others, _.isEqual);
     * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
     */
    var xorWith = baseRest(function(arrays) {
      var comparator = last(arrays);
      comparator = typeof comparator == 'function' ? comparator : undefined;
      return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator);
    });

    /**
     * Creates an array of grouped elements, the first of which contains the
     * first elements of the given arrays, the second of which contains the
     * second elements of the given arrays, and so on.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Array
     * @param {...Array} [arrays] The arrays to process.
     * @returns {Array} Returns the new array of grouped elements.
     * @example
     *
     * _.zip(['a', 'b'], [1, 2], [true, false]);
     * // => [['a', 1, true], ['b', 2, false]]
     */
    var zip = baseRest(unzip);

    /**
     * This method is like `_.fromPairs` except that it accepts two arrays,
     * one of property identifiers and one of corresponding values.
     *
     * @static
     * @memberOf _
     * @since 0.4.0
     * @category Array
     * @param {Array} [props=[]] The property identifiers.
     * @param {Array} [values=[]] The property values.
     * @returns {Object} Returns the new object.
     * @example
     *
     * _.zipObject(['a', 'b'], [1, 2]);
     * // => { 'a': 1, 'b': 2 }
     */
    function zipObject(props, values) {
      return baseZipObject(props || [], values || [], assignValue);
    }

    /**
     * This method is like `_.zipObject` except that it supports property paths.
     *
     * @static
     * @memberOf _
     * @since 4.1.0
     * @category Array
     * @param {Array} [props=[]] The property identifiers.
     * @param {Array} [values=[]] The property values.
     * @returns {Object} Returns the new object.
     * @example
     *
     * _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
     * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
     */
    function zipObjectDeep(props, values) {
      return baseZipObject(props || [], values || [], baseSet);
    }

    /**
     * This method is like `_.zip` except that it accepts `iteratee` to specify
     * how grouped values should be combined. The iteratee is invoked with the
     * elements of each group: (...group).
     *
     * @static
     * @memberOf _
     * @since 3.8.0
     * @category Array
     * @param {...Array} [arrays] The arrays to process.
     * @param {Function} [iteratee=_.identity] The function to combine
     *  grouped values.
     * @returns {Array} Returns the new array of grouped elements.
     * @example
     *
     * _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) {
     *   return a + b + c;
     * });
     * // => [111, 222]
     */
    var zipWith = baseRest(function(arrays) {
      var length = arrays.length,
          iteratee = length > 1 ? arrays[length - 1] : undefined;

      iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined;
      return unzipWith(arrays, iteratee);
    });

    /*------------------------------------------------------------------------*/

    /**
     * Creates a `lodash` wrapper instance that wraps `value` with explicit method
     * chain sequences enabled. The result of such sequences must be unwrapped
     * with `_#value`.
     *
     * @static
     * @memberOf _
     * @since 1.3.0
     * @category Seq
     * @param {*} value The value to wrap.
     * @returns {Object} Returns the new `lodash` wrapper instance.
     * @example
     *
     * var users = [
     *   { 'user': 'barney',  'age': 36 },
     *   { 'user': 'fred',    'age': 40 },
     *   { 'user': 'pebbles', 'age': 1 }
     * ];
     *
     * var youngest = _
     *   .chain(users)
     *   .sortBy('age')
     *   .map(function(o) {
     *     return o.user + ' is ' + o.age;
     *   })
     *   .head()
     *   .value();
     * // => 'pebbles is 1'
     */
    function chain(value) {
      var result = lodash(value);
      result.__chain__ = true;
      return result;
    }

    /**
     * This method invokes `interceptor` and returns `value`. The interceptor
     * is invoked with one argument; (value). The purpose of this method is to
     * "tap into" a method chain sequence in order to modify intermediate results.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Seq
     * @param {*} value The value to provide to `interceptor`.
     * @param {Function} interceptor The function to invoke.
     * @returns {*} Returns `value`.
     * @example
     *
     * _([1, 2, 3])
     *  .tap(function(array) {
     *    // Mutate input array.
     *    array.pop();
     *  })
     *  .reverse()
     *  .value();
     * // => [2, 1]
     */
    function tap(value, interceptor) {
      interceptor(value);
      return value;
    }

    /**
     * This method is like `_.tap` except that it returns the result of `interceptor`.
     * The purpose of this method is to "pass thru" values replacing intermediate
     * results in a method chain sequence.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Seq
     * @param {*} value The value to provide to `interceptor`.
     * @param {Function} interceptor The function to invoke.
     * @returns {*} Returns the result of `interceptor`.
     * @example
     *
     * _('  abc  ')
     *  .chain()
     *  .trim()
     *  .thru(function(value) {
     *    return [value];
     *  })
     *  .value();
     * // => ['abc']
     */
    function thru(value, interceptor) {
      return interceptor(value);
    }

    /**
     * This method is the wrapper version of `_.at`.
     *
     * @name at
     * @memberOf _
     * @since 1.0.0
     * @category Seq
     * @param {...(string|string[])} [paths] The property paths to pick.
     * @returns {Object} Returns the new `lodash` wrapper instance.
     * @example
     *
     * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
     *
     * _(object).at(['a[0].b.c', 'a[1]']).value();
     * // => [3, 4]
     */
    var wrapperAt = flatRest(function(paths) {
      var length = paths.length,
          start = length ? paths[0] : 0,
          value = this.__wrapped__,
          interceptor = function(object) { return baseAt(object, paths); };

      if (length > 1 || this.__actions__.length ||
          !(value instanceof LazyWrapper) || !isIndex(start)) {
        return this.thru(interceptor);
      }
      value = value.slice(start, +start + (length ? 1 : 0));
      value.__actions__.push({
        'func': thru,
        'args': [interceptor],
        'thisArg': undefined
      });
      return new LodashWrapper(value, this.__chain__).thru(function(array) {
        if (length && !array.length) {
          array.push(undefined);
        }
        return array;
      });
    });

    /**
     * Creates a `lodash` wrapper instance with explicit method chain sequences enabled.
     *
     * @name chain
     * @memberOf _
     * @since 0.1.0
     * @category Seq
     * @returns {Object} Returns the new `lodash` wrapper instance.
     * @example
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36 },
     *   { 'user': 'fred',   'age': 40 }
     * ];
     *
     * // A sequence without explicit chaining.
     * _(users).head();
     * // => { 'user': 'barney', 'age': 36 }
     *
     * // A sequence with explicit chaining.
     * _(users)
     *   .chain()
     *   .head()
     *   .pick('user')
     *   .value();
     * // => { 'user': 'barney' }
     */
    function wrapperChain() {
      return chain(this);
    }

    /**
     * Executes the chain sequence and returns the wrapped result.
     *
     * @name commit
     * @memberOf _
     * @since 3.2.0
     * @category Seq
     * @returns {Object} Returns the new `lodash` wrapper instance.
     * @example
     *
     * var array = [1, 2];
     * var wrapped = _(array).push(3);
     *
     * console.log(array);
     * // => [1, 2]
     *
     * wrapped = wrapped.commit();
     * console.log(array);
     * // => [1, 2, 3]
     *
     * wrapped.last();
     * // => 3
     *
     * console.log(array);
     * // => [1, 2, 3]
     */
    function wrapperCommit() {
      return new LodashWrapper(this.value(), this.__chain__);
    }

    /**
     * Gets the next value on a wrapped object following the
     * [iterator protocol](https://mdn.io/iteration_protocols#iterator).
     *
     * @name next
     * @memberOf _
     * @since 4.0.0
     * @category Seq
     * @returns {Object} Returns the next iterator value.
     * @example
     *
     * var wrapped = _([1, 2]);
     *
     * wrapped.next();
     * // => { 'done': false, 'value': 1 }
     *
     * wrapped.next();
     * // => { 'done': false, 'value': 2 }
     *
     * wrapped.next();
     * // => { 'done': true, 'value': undefined }
     */
    function wrapperNext() {
      if (this.__values__ === undefined) {
        this.__values__ = toArray(this.value());
      }
      var done = this.__index__ >= this.__values__.length,
          value = done ? undefined : this.__values__[this.__index__++];

      return { 'done': done, 'value': value };
    }

    /**
     * Enables the wrapper to be iterable.
     *
     * @name Symbol.iterator
     * @memberOf _
     * @since 4.0.0
     * @category Seq
     * @returns {Object} Returns the wrapper object.
     * @example
     *
     * var wrapped = _([1, 2]);
     *
     * wrapped[Symbol.iterator]() === wrapped;
     * // => true
     *
     * Array.from(wrapped);
     * // => [1, 2]
     */
    function wrapperToIterator() {
      return this;
    }

    /**
     * Creates a clone of the chain sequence planting `value` as the wrapped value.
     *
     * @name plant
     * @memberOf _
     * @since 3.2.0
     * @category Seq
     * @param {*} value The value to plant.
     * @returns {Object} Returns the new `lodash` wrapper instance.
     * @example
     *
     * function square(n) {
     *   return n * n;
     * }
     *
     * var wrapped = _([1, 2]).map(square);
     * var other = wrapped.plant([3, 4]);
     *
     * other.value();
     * // => [9, 16]
     *
     * wrapped.value();
     * // => [1, 4]
     */
    function wrapperPlant(value) {
      var result,
          parent = this;

      while (parent instanceof baseLodash) {
        var clone = wrapperClone(parent);
        clone.__index__ = 0;
        clone.__values__ = undefined;
        if (result) {
          previous.__wrapped__ = clone;
        } else {
          result = clone;
        }
        var previous = clone;
        parent = parent.__wrapped__;
      }
      previous.__wrapped__ = value;
      return result;
    }

    /**
     * This method is the wrapper version of `_.reverse`.
     *
     * **Note:** This method mutates the wrapped array.
     *
     * @name reverse
     * @memberOf _
     * @since 0.1.0
     * @category Seq
     * @returns {Object} Returns the new `lodash` wrapper instance.
     * @example
     *
     * var array = [1, 2, 3];
     *
     * _(array).reverse().value()
     * // => [3, 2, 1]
     *
     * console.log(array);
     * // => [3, 2, 1]
     */
    function wrapperReverse() {
      var value = this.__wrapped__;
      if (value instanceof LazyWrapper) {
        var wrapped = value;
        if (this.__actions__.length) {
          wrapped = new LazyWrapper(this);
        }
        wrapped = wrapped.reverse();
        wrapped.__actions__.push({
          'func': thru,
          'args': [reverse],
          'thisArg': undefined
        });
        return new LodashWrapper(wrapped, this.__chain__);
      }
      return this.thru(reverse);
    }

    /**
     * Executes the chain sequence to resolve the unwrapped value.
     *
     * @name value
     * @memberOf _
     * @since 0.1.0
     * @alias toJSON, valueOf
     * @category Seq
     * @returns {*} Returns the resolved unwrapped value.
     * @example
     *
     * _([1, 2, 3]).value();
     * // => [1, 2, 3]
     */
    function wrapperValue() {
      return baseWrapperValue(this.__wrapped__, this.__actions__);
    }

    /*------------------------------------------------------------------------*/

    /**
     * Creates an object composed of keys generated from the results of running
     * each element of `collection` thru `iteratee`. The corresponding value of
     * each key is the number of times the key was returned by `iteratee`. The
     * iteratee is invoked with one argument: (value).
     *
     * @static
     * @memberOf _
     * @since 0.5.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
     * @returns {Object} Returns the composed aggregate object.
     * @example
     *
     * _.countBy([6.1, 4.2, 6.3], Math.floor);
     * // => { '4': 1, '6': 2 }
     *
     * // The `_.property` iteratee shorthand.
     * _.countBy(['one', 'two', 'three'], 'length');
     * // => { '3': 2, '5': 1 }
     */
    var countBy = createAggregator(function(result, value, key) {
      if (hasOwnProperty.call(result, key)) {
        ++result[key];
      } else {
        baseAssignValue(result, key, 1);
      }
    });

    /**
     * Checks if `predicate` returns truthy for **all** elements of `collection`.
     * Iteration is stopped once `predicate` returns falsey. The predicate is
     * invoked with three arguments: (value, index|key, collection).
     *
     * **Note:** This method returns `true` for
     * [empty collections](https://en.wikipedia.org/wiki/Empty_set) because
     * [everything is true](https://en.wikipedia.org/wiki/Vacuous_truth) of
     * elements of empty collections.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {boolean} Returns `true` if all elements pass the predicate check,
     *  else `false`.
     * @example
     *
     * _.every([true, 1, null, 'yes'], Boolean);
     * // => false
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36, 'active': false },
     *   { 'user': 'fred',   'age': 40, 'active': false }
     * ];
     *
     * // The `_.matches` iteratee shorthand.
     * _.every(users, { 'user': 'barney', 'active': false });
     * // => false
     *
     * // The `_.matchesProperty` iteratee shorthand.
     * _.every(users, ['active', false]);
     * // => true
     *
     * // The `_.property` iteratee shorthand.
     * _.every(users, 'active');
     * // => false
     */
    function every(collection, predicate, guard) {
      var func = isArray(collection) ? arrayEvery : baseEvery;
      if (guard && isIterateeCall(collection, predicate, guard)) {
        predicate = undefined;
      }
      return func(collection, getIteratee(predicate, 3));
    }

    /**
     * Iterates over elements of `collection`, returning an array of all elements
     * `predicate` returns truthy for. The predicate is invoked with three
     * arguments: (value, index|key, collection).
     *
     * **Note:** Unlike `_.remove`, this method returns a new array.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @returns {Array} Returns the new filtered array.
     * @see _.reject
     * @example
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36, 'active': true },
     *   { 'user': 'fred',   'age': 40, 'active': false }
     * ];
     *
     * _.filter(users, function(o) { return !o.active; });
     * // => objects for ['fred']
     *
     * // The `_.matches` iteratee shorthand.
     * _.filter(users, { 'age': 36, 'active': true });
     * // => objects for ['barney']
     *
     * // The `_.matchesProperty` iteratee shorthand.
     * _.filter(users, ['active', false]);
     * // => objects for ['fred']
     *
     * // The `_.property` iteratee shorthand.
     * _.filter(users, 'active');
     * // => objects for ['barney']
     */
    function filter(collection, predicate) {
      var func = isArray(collection) ? arrayFilter : baseFilter;
      return func(collection, getIteratee(predicate, 3));
    }

    /**
     * Iterates over elements of `collection`, returning the first element
     * `predicate` returns truthy for. The predicate is invoked with three
     * arguments: (value, index|key, collection).
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Collection
     * @param {Array|Object} collection The collection to inspect.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @param {number} [fromIndex=0] The index to search from.
     * @returns {*} Returns the matched element, else `undefined`.
     * @example
     *
     * var users = [
     *   { 'user': 'barney',  'age': 36, 'active': true },
     *   { 'user': 'fred',    'age': 40, 'active': false },
     *   { 'user': 'pebbles', 'age': 1,  'active': true }
     * ];
     *
     * _.find(users, function(o) { return o.age < 40; });
     * // => object for 'barney'
     *
     * // The `_.matches` iteratee shorthand.
     * _.find(users, { 'age': 1, 'active': true });
     * // => object for 'pebbles'
     *
     * // The `_.matchesProperty` iteratee shorthand.
     * _.find(users, ['active', false]);
     * // => object for 'fred'
     *
     * // The `_.property` iteratee shorthand.
     * _.find(users, 'active');
     * // => object for 'barney'
     */
    var find = createFind(findIndex);

    /**
     * This method is like `_.find` except that it iterates over elements of
     * `collection` from right to left.
     *
     * @static
     * @memberOf _
     * @since 2.0.0
     * @category Collection
     * @param {Array|Object} collection The collection to inspect.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @param {number} [fromIndex=collection.length-1] The index to search from.
     * @returns {*} Returns the matched element, else `undefined`.
     * @example
     *
     * _.findLast([1, 2, 3, 4], function(n) {
     *   return n % 2 == 1;
     * });
     * // => 3
     */
    var findLast = createFind(findLastIndex);

    /**
     * Creates a flattened array of values by running each element in `collection`
     * thru `iteratee` and flattening the mapped results. The iteratee is invoked
     * with three arguments: (value, index|key, collection).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @returns {Array} Returns the new flattened array.
     * @example
     *
     * function duplicate(n) {
     *   return [n, n];
     * }
     *
     * _.flatMap([1, 2], duplicate);
     * // => [1, 1, 2, 2]
     */
    function flatMap(collection, iteratee) {
      return baseFlatten(map(collection, iteratee), 1);
    }

    /**
     * This method is like `_.flatMap` except that it recursively flattens the
     * mapped results.
     *
     * @static
     * @memberOf _
     * @since 4.7.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @returns {Array} Returns the new flattened array.
     * @example
     *
     * function duplicate(n) {
     *   return [[[n, n]]];
     * }
     *
     * _.flatMapDeep([1, 2], duplicate);
     * // => [1, 1, 2, 2]
     */
    function flatMapDeep(collection, iteratee) {
      return baseFlatten(map(collection, iteratee), INFINITY);
    }

    /**
     * This method is like `_.flatMap` except that it recursively flattens the
     * mapped results up to `depth` times.
     *
     * @static
     * @memberOf _
     * @since 4.7.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @param {number} [depth=1] The maximum recursion depth.
     * @returns {Array} Returns the new flattened array.
     * @example
     *
     * function duplicate(n) {
     *   return [[[n, n]]];
     * }
     *
     * _.flatMapDepth([1, 2], duplicate, 2);
     * // => [[1, 1], [2, 2]]
     */
    function flatMapDepth(collection, iteratee, depth) {
      depth = depth === undefined ? 1 : toInteger(depth);
      return baseFlatten(map(collection, iteratee), depth);
    }

    /**
     * Iterates over elements of `collection` and invokes `iteratee` for each element.
     * The iteratee is invoked with three arguments: (value, index|key, collection).
     * Iteratee functions may exit iteration early by explicitly returning `false`.
     *
     * **Note:** As with other "Collections" methods, objects with a "length"
     * property are iterated like arrays. To avoid this behavior use `_.forIn`
     * or `_.forOwn` for object iteration.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @alias each
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @returns {Array|Object} Returns `collection`.
     * @see _.forEachRight
     * @example
     *
     * _.forEach([1, 2], function(value) {
     *   console.log(value);
     * });
     * // => Logs `1` then `2`.
     *
     * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
     *   console.log(key);
     * });
     * // => Logs 'a' then 'b' (iteration order is not guaranteed).
     */
    function forEach(collection, iteratee) {
      var func = isArray(collection) ? arrayEach : baseEach;
      return func(collection, getIteratee(iteratee, 3));
    }

    /**
     * This method is like `_.forEach` except that it iterates over elements of
     * `collection` from right to left.
     *
     * @static
     * @memberOf _
     * @since 2.0.0
     * @alias eachRight
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @returns {Array|Object} Returns `collection`.
     * @see _.forEach
     * @example
     *
     * _.forEachRight([1, 2], function(value) {
     *   console.log(value);
     * });
     * // => Logs `2` then `1`.
     */
    function forEachRight(collection, iteratee) {
      var func = isArray(collection) ? arrayEachRight : baseEachRight;
      return func(collection, getIteratee(iteratee, 3));
    }

    /**
     * Creates an object composed of keys generated from the results of running
     * each element of `collection` thru `iteratee`. The order of grouped values
     * is determined by the order they occur in `collection`. The corresponding
     * value of each key is an array of elements responsible for generating the
     * key. The iteratee is invoked with one argument: (value).
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
     * @returns {Object} Returns the composed aggregate object.
     * @example
     *
     * _.groupBy([6.1, 4.2, 6.3], Math.floor);
     * // => { '4': [4.2], '6': [6.1, 6.3] }
     *
     * // The `_.property` iteratee shorthand.
     * _.groupBy(['one', 'two', 'three'], 'length');
     * // => { '3': ['one', 'two'], '5': ['three'] }
     */
    var groupBy = createAggregator(function(result, value, key) {
      if (hasOwnProperty.call(result, key)) {
        result[key].push(value);
      } else {
        baseAssignValue(result, key, [value]);
      }
    });

    /**
     * Checks if `value` is in `collection`. If `collection` is a string, it's
     * checked for a substring of `value`, otherwise
     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
     * is used for equality comparisons. If `fromIndex` is negative, it's used as
     * the offset from the end of `collection`.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Collection
     * @param {Array|Object|string} collection The collection to inspect.
     * @param {*} value The value to search for.
     * @param {number} [fromIndex=0] The index to search from.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
     * @returns {boolean} Returns `true` if `value` is found, else `false`.
     * @example
     *
     * _.includes([1, 2, 3], 1);
     * // => true
     *
     * _.includes([1, 2, 3], 1, 2);
     * // => false
     *
     * _.includes({ 'a': 1, 'b': 2 }, 1);
     * // => true
     *
     * _.includes('abcd', 'bc');
     * // => true
     */
    function includes(collection, value, fromIndex, guard) {
      collection = isArrayLike(collection) ? collection : values(collection);
      fromIndex = (fromIndex && !guard) ? toInteger(fromIndex) : 0;

      var length = collection.length;
      if (fromIndex < 0) {
        fromIndex = nativeMax(length + fromIndex, 0);
      }
      return isString(collection)
        ? (fromIndex <= length && collection.indexOf(value, fromIndex) > -1)
        : (!!length && baseIndexOf(collection, value, fromIndex) > -1);
    }

    /**
     * Invokes the method at `path` of each element in `collection`, returning
     * an array of the results of each invoked method. Any additional arguments
     * are provided to each invoked method. If `path` is a function, it's invoked
     * for, and `this` bound to, each element in `collection`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Array|Function|string} path The path of the method to invoke or
     *  the function invoked per iteration.
     * @param {...*} [args] The arguments to invoke each method with.
     * @returns {Array} Returns the array of results.
     * @example
     *
     * _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
     * // => [[1, 5, 7], [1, 2, 3]]
     *
     * _.invokeMap([123, 456], String.prototype.split, '');
     * // => [['1', '2', '3'], ['4', '5', '6']]
     */
    var invokeMap = baseRest(function(collection, path, args) {
      var index = -1,
          isFunc = typeof path == 'function',
          result = isArrayLike(collection) ? Array(collection.length) : [];

      baseEach(collection, function(value) {
        result[++index] = isFunc ? apply(path, value, args) : baseInvoke(value, path, args);
      });
      return result;
    });

    /**
     * Creates an object composed of keys generated from the results of running
     * each element of `collection` thru `iteratee`. The corresponding value of
     * each key is the last element responsible for generating the key. The
     * iteratee is invoked with one argument: (value).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
     * @returns {Object} Returns the composed aggregate object.
     * @example
     *
     * var array = [
     *   { 'dir': 'left', 'code': 97 },
     *   { 'dir': 'right', 'code': 100 }
     * ];
     *
     * _.keyBy(array, function(o) {
     *   return String.fromCharCode(o.code);
     * });
     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
     *
     * _.keyBy(array, 'dir');
     * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
     */
    var keyBy = createAggregator(function(result, value, key) {
      baseAssignValue(result, key, value);
    });

    /**
     * Creates an array of values by running each element in `collection` thru
     * `iteratee`. The iteratee is invoked with three arguments:
     * (value, index|key, collection).
     *
     * Many lodash methods are guarded to work as iteratees for methods like
     * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
     *
     * The guarded methods are:
     * `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`,
     * `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`,
     * `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`,
     * `template`, `trim`, `trimEnd`, `trimStart`, and `words`
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @returns {Array} Returns the new mapped array.
     * @example
     *
     * function square(n) {
     *   return n * n;
     * }
     *
     * _.map([4, 8], square);
     * // => [16, 64]
     *
     * _.map({ 'a': 4, 'b': 8 }, square);
     * // => [16, 64] (iteration order is not guaranteed)
     *
     * var users = [
     *   { 'user': 'barney' },
     *   { 'user': 'fred' }
     * ];
     *
     * // The `_.property` iteratee shorthand.
     * _.map(users, 'user');
     * // => ['barney', 'fred']
     */
    function map(collection, iteratee) {
      var func = isArray(collection) ? arrayMap : baseMap;
      return func(collection, getIteratee(iteratee, 3));
    }

    /**
     * This method is like `_.sortBy` except that it allows specifying the sort
     * orders of the iteratees to sort by. If `orders` is unspecified, all values
     * are sorted in ascending order. Otherwise, specify an order of "desc" for
     * descending or "asc" for ascending sort order of corresponding values.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]]
     *  The iteratees to sort by.
     * @param {string[]} [orders] The sort orders of `iteratees`.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
     * @returns {Array} Returns the new sorted array.
     * @example
     *
     * var users = [
     *   { 'user': 'fred',   'age': 48 },
     *   { 'user': 'barney', 'age': 34 },
     *   { 'user': 'fred',   'age': 40 },
     *   { 'user': 'barney', 'age': 36 }
     * ];
     *
     * // Sort by `user` in ascending order and by `age` in descending order.
     * _.orderBy(users, ['user', 'age'], ['asc', 'desc']);
     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
     */
    function orderBy(collection, iteratees, orders, guard) {
      if (collection == null) {
        return [];
      }
      if (!isArray(iteratees)) {
        iteratees = iteratees == null ? [] : [iteratees];
      }
      orders = guard ? undefined : orders;
      if (!isArray(orders)) {
        orders = orders == null ? [] : [orders];
      }
      return baseOrderBy(collection, iteratees, orders);
    }

    /**
     * Creates an array of elements split into two groups, the first of which
     * contains elements `predicate` returns truthy for, the second of which
     * contains elements `predicate` returns falsey for. The predicate is
     * invoked with one argument: (value).
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @returns {Array} Returns the array of grouped elements.
     * @example
     *
     * var users = [
     *   { 'user': 'barney',  'age': 36, 'active': false },
     *   { 'user': 'fred',    'age': 40, 'active': true },
     *   { 'user': 'pebbles', 'age': 1,  'active': false }
     * ];
     *
     * _.partition(users, function(o) { return o.active; });
     * // => objects for [['fred'], ['barney', 'pebbles']]
     *
     * // The `_.matches` iteratee shorthand.
     * _.partition(users, { 'age': 1, 'active': false });
     * // => objects for [['pebbles'], ['barney', 'fred']]
     *
     * // The `_.matchesProperty` iteratee shorthand.
     * _.partition(users, ['active', false]);
     * // => objects for [['barney', 'pebbles'], ['fred']]
     *
     * // The `_.property` iteratee shorthand.
     * _.partition(users, 'active');
     * // => objects for [['fred'], ['barney', 'pebbles']]
     */
    var partition = createAggregator(function(result, value, key) {
      result[key ? 0 : 1].push(value);
    }, function() { return [[], []]; });

    /**
     * Reduces `collection` to a value which is the accumulated result of running
     * each element in `collection` thru `iteratee`, where each successive
     * invocation is supplied the return value of the previous. If `accumulator`
     * is not given, the first element of `collection` is used as the initial
     * value. The iteratee is invoked with four arguments:
     * (accumulator, value, index|key, collection).
     *
     * Many lodash methods are guarded to work as iteratees for methods like
     * `_.reduce`, `_.reduceRight`, and `_.transform`.
     *
     * The guarded methods are:
     * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`,
     * and `sortBy`
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @param {*} [accumulator] The initial value.
     * @returns {*} Returns the accumulated value.
     * @see _.reduceRight
     * @example
     *
     * _.reduce([1, 2], function(sum, n) {
     *   return sum + n;
     * }, 0);
     * // => 3
     *
     * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
     *   (result[value] || (result[value] = [])).push(key);
     *   return result;
     * }, {});
     * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed)
     */
    function reduce(collection, iteratee, accumulator) {
      var func = isArray(collection) ? arrayReduce : baseReduce,
          initAccum = arguments.length < 3;

      return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach);
    }

    /**
     * This method is like `_.reduce` except that it iterates over elements of
     * `collection` from right to left.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @param {*} [accumulator] The initial value.
     * @returns {*} Returns the accumulated value.
     * @see _.reduce
     * @example
     *
     * var array = [[0, 1], [2, 3], [4, 5]];
     *
     * _.reduceRight(array, function(flattened, other) {
     *   return flattened.concat(other);
     * }, []);
     * // => [4, 5, 2, 3, 0, 1]
     */
    function reduceRight(collection, iteratee, accumulator) {
      var func = isArray(collection) ? arrayReduceRight : baseReduce,
          initAccum = arguments.length < 3;

      return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight);
    }

    /**
     * The opposite of `_.filter`; this method returns the elements of `collection`
     * that `predicate` does **not** return truthy for.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @returns {Array} Returns the new filtered array.
     * @see _.filter
     * @example
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36, 'active': false },
     *   { 'user': 'fred',   'age': 40, 'active': true }
     * ];
     *
     * _.reject(users, function(o) { return !o.active; });
     * // => objects for ['fred']
     *
     * // The `_.matches` iteratee shorthand.
     * _.reject(users, { 'age': 40, 'active': true });
     * // => objects for ['barney']
     *
     * // The `_.matchesProperty` iteratee shorthand.
     * _.reject(users, ['active', false]);
     * // => objects for ['fred']
     *
     * // The `_.property` iteratee shorthand.
     * _.reject(users, 'active');
     * // => objects for ['barney']
     */
    function reject(collection, predicate) {
      var func = isArray(collection) ? arrayFilter : baseFilter;
      return func(collection, negate(getIteratee(predicate, 3)));
    }

    /**
     * Gets a random element from `collection`.
     *
     * @static
     * @memberOf _
     * @since 2.0.0
     * @category Collection
     * @param {Array|Object} collection The collection to sample.
     * @returns {*} Returns the random element.
     * @example
     *
     * _.sample([1, 2, 3, 4]);
     * // => 2
     */
    function sample(collection) {
      var func = isArray(collection) ? arraySample : baseSample;
      return func(collection);
    }

    /**
     * Gets `n` random elements at unique keys from `collection` up to the
     * size of `collection`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Collection
     * @param {Array|Object} collection The collection to sample.
     * @param {number} [n=1] The number of elements to sample.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {Array} Returns the random elements.
     * @example
     *
     * _.sampleSize([1, 2, 3], 2);
     * // => [3, 1]
     *
     * _.sampleSize([1, 2, 3], 4);
     * // => [2, 3, 1]
     */
    function sampleSize(collection, n, guard) {
      if ((guard ? isIterateeCall(collection, n, guard) : n === undefined)) {
        n = 1;
      } else {
        n = toInteger(n);
      }
      var func = isArray(collection) ? arraySampleSize : baseSampleSize;
      return func(collection, n);
    }

    /**
     * Creates an array of shuffled values, using a version of the
     * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Collection
     * @param {Array|Object} collection The collection to shuffle.
     * @returns {Array} Returns the new shuffled array.
     * @example
     *
     * _.shuffle([1, 2, 3, 4]);
     * // => [4, 1, 3, 2]
     */
    function shuffle(collection) {
      var func = isArray(collection) ? arrayShuffle : baseShuffle;
      return func(collection);
    }

    /**
     * Gets the size of `collection` by returning its length for array-like
     * values or the number of own enumerable string keyed properties for objects.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Collection
     * @param {Array|Object|string} collection The collection to inspect.
     * @returns {number} Returns the collection size.
     * @example
     *
     * _.size([1, 2, 3]);
     * // => 3
     *
     * _.size({ 'a': 1, 'b': 2 });
     * // => 2
     *
     * _.size('pebbles');
     * // => 7
     */
    function size(collection) {
      if (collection == null) {
        return 0;
      }
      if (isArrayLike(collection)) {
        return isString(collection) ? stringSize(collection) : collection.length;
      }
      var tag = getTag(collection);
      if (tag == mapTag || tag == setTag) {
        return collection.size;
      }
      return baseKeys(collection).length;
    }

    /**
     * Checks if `predicate` returns truthy for **any** element of `collection`.
     * Iteration is stopped once `predicate` returns truthy. The predicate is
     * invoked with three arguments: (value, index|key, collection).
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {boolean} Returns `true` if any element passes the predicate check,
     *  else `false`.
     * @example
     *
     * _.some([null, 0, 'yes', false], Boolean);
     * // => true
     *
     * var users = [
     *   { 'user': 'barney', 'active': true },
     *   { 'user': 'fred',   'active': false }
     * ];
     *
     * // The `_.matches` iteratee shorthand.
     * _.some(users, { 'user': 'barney', 'active': false });
     * // => false
     *
     * // The `_.matchesProperty` iteratee shorthand.
     * _.some(users, ['active', false]);
     * // => true
     *
     * // The `_.property` iteratee shorthand.
     * _.some(users, 'active');
     * // => true
     */
    function some(collection, predicate, guard) {
      var func = isArray(collection) ? arraySome : baseSome;
      if (guard && isIterateeCall(collection, predicate, guard)) {
        predicate = undefined;
      }
      return func(collection, getIteratee(predicate, 3));
    }

    /**
     * Creates an array of elements, sorted in ascending order by the results of
     * running each element in a collection thru each iteratee. This method
     * performs a stable sort, that is, it preserves the original sort order of
     * equal elements. The iteratees are invoked with one argument: (value).
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {...(Function|Function[])} [iteratees=[_.identity]]
     *  The iteratees to sort by.
     * @returns {Array} Returns the new sorted array.
     * @example
     *
     * var users = [
     *   { 'user': 'fred',   'age': 48 },
     *   { 'user': 'barney', 'age': 36 },
     *   { 'user': 'fred',   'age': 40 },
     *   { 'user': 'barney', 'age': 34 }
     * ];
     *
     * _.sortBy(users, [function(o) { return o.user; }]);
     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
     *
     * _.sortBy(users, ['user', 'age']);
     * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
     */
    var sortBy = baseRest(function(collection, iteratees) {
      if (collection == null) {
        return [];
      }
      var length = iteratees.length;
      if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {
        iteratees = [];
      } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {
        iteratees = [iteratees[0]];
      }
      return baseOrderBy(collection, baseFlatten(iteratees, 1), []);
    });

    /*------------------------------------------------------------------------*/

    /**
     * Gets the timestamp of the number of milliseconds that have elapsed since
     * the Unix epoch (1 January 1970 00:00:00 UTC).
     *
     * @static
     * @memberOf _
     * @since 2.4.0
     * @category Date
     * @returns {number} Returns the timestamp.
     * @example
     *
     * _.defer(function(stamp) {
     *   console.log(_.now() - stamp);
     * }, _.now());
     * // => Logs the number of milliseconds it took for the deferred invocation.
     */
    var now = ctxNow || function() {
      return root.Date.now();
    };

    /*------------------------------------------------------------------------*/

    /**
     * The opposite of `_.before`; this method creates a function that invokes
     * `func` once it's called `n` or more times.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Function
     * @param {number} n The number of calls before `func` is invoked.
     * @param {Function} func The function to restrict.
     * @returns {Function} Returns the new restricted function.
     * @example
     *
     * var saves = ['profile', 'settings'];
     *
     * var done = _.after(saves.length, function() {
     *   console.log('done saving!');
     * });
     *
     * _.forEach(saves, function(type) {
     *   asyncSave({ 'type': type, 'complete': done });
     * });
     * // => Logs 'done saving!' after the two async saves have completed.
     */
    function after(n, func) {
      if (typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      n = toInteger(n);
      return function() {
        if (--n < 1) {
          return func.apply(this, arguments);
        }
      };
    }

    /**
     * Creates a function that invokes `func`, with up to `n` arguments,
     * ignoring any additional arguments.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Function
     * @param {Function} func The function to cap arguments for.
     * @param {number} [n=func.length] The arity cap.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {Function} Returns the new capped function.
     * @example
     *
     * _.map(['6', '8', '10'], _.ary(parseInt, 1));
     * // => [6, 8, 10]
     */
    function ary(func, n, guard) {
      n = guard ? undefined : n;
      n = (func && n == null) ? func.length : n;
      return createWrap(func, WRAP_ARY_FLAG, undefined, undefined, undefined, undefined, n);
    }

    /**
     * Creates a function that invokes `func`, with the `this` binding and arguments
     * of the created function, while it's called less than `n` times. Subsequent
     * calls to the created function return the result of the last `func` invocation.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Function
     * @param {number} n The number of calls at which `func` is no longer invoked.
     * @param {Function} func The function to restrict.
     * @returns {Function} Returns the new restricted function.
     * @example
     *
     * jQuery(element).on('click', _.before(5, addContactToList));
     * // => Allows adding up to 4 contacts to the list.
     */
    function before(n, func) {
      var result;
      if (typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      n = toInteger(n);
      return function() {
        if (--n > 0) {
          result = func.apply(this, arguments);
        }
        if (n <= 1) {
          func = undefined;
        }
        return result;
      };
    }

    /**
     * Creates a function that invokes `func` with the `this` binding of `thisArg`
     * and `partials` prepended to the arguments it receives.
     *
     * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
     * may be used as a placeholder for partially applied arguments.
     *
     * **Note:** Unlike native `Function#bind`, this method doesn't set the "length"
     * property of bound functions.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Function
     * @param {Function} func The function to bind.
     * @param {*} thisArg The `this` binding of `func`.
     * @param {...*} [partials] The arguments to be partially applied.
     * @returns {Function} Returns the new bound function.
     * @example
     *
     * function greet(greeting, punctuation) {
     *   return greeting + ' ' + this.user + punctuation;
     * }
     *
     * var object = { 'user': 'fred' };
     *
     * var bound = _.bind(greet, object, 'hi');
     * bound('!');
     * // => 'hi fred!'
     *
     * // Bound with placeholders.
     * var bound = _.bind(greet, object, _, '!');
     * bound('hi');
     * // => 'hi fred!'
     */
    var bind = baseRest(function(func, thisArg, partials) {
      var bitmask = WRAP_BIND_FLAG;
      if (partials.length) {
        var holders = replaceHolders(partials, getHolder(bind));
        bitmask |= WRAP_PARTIAL_FLAG;
      }
      return createWrap(func, bitmask, thisArg, partials, holders);
    });

    /**
     * Creates a function that invokes the method at `object[key]` with `partials`
     * prepended to the arguments it receives.
     *
     * This method differs from `_.bind` by allowing bound functions to reference
     * methods that may be redefined or don't yet exist. See
     * [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern)
     * for more details.
     *
     * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic
     * builds, may be used as a placeholder for partially applied arguments.
     *
     * @static
     * @memberOf _
     * @since 0.10.0
     * @category Function
     * @param {Object} object The object to invoke the method on.
     * @param {string} key The key of the method.
     * @param {...*} [partials] The arguments to be partially applied.
     * @returns {Function} Returns the new bound function.
     * @example
     *
     * var object = {
     *   'user': 'fred',
     *   'greet': function(greeting, punctuation) {
     *     return greeting + ' ' + this.user + punctuation;
     *   }
     * };
     *
     * var bound = _.bindKey(object, 'greet', 'hi');
     * bound('!');
     * // => 'hi fred!'
     *
     * object.greet = function(greeting, punctuation) {
     *   return greeting + 'ya ' + this.user + punctuation;
     * };
     *
     * bound('!');
     * // => 'hiya fred!'
     *
     * // Bound with placeholders.
     * var bound = _.bindKey(object, 'greet', _, '!');
     * bound('hi');
     * // => 'hiya fred!'
     */
    var bindKey = baseRest(function(object, key, partials) {
      var bitmask = WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG;
      if (partials.length) {
        var holders = replaceHolders(partials, getHolder(bindKey));
        bitmask |= WRAP_PARTIAL_FLAG;
      }
      return createWrap(key, bitmask, object, partials, holders);
    });

    /**
     * Creates a function that accepts arguments of `func` and either invokes
     * `func` returning its result, if at least `arity` number of arguments have
     * been provided, or returns a function that accepts the remaining `func`
     * arguments, and so on. The arity of `func` may be specified if `func.length`
     * is not sufficient.
     *
     * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
     * may be used as a placeholder for provided arguments.
     *
     * **Note:** This method doesn't set the "length" property of curried functions.
     *
     * @static
     * @memberOf _
     * @since 2.0.0
     * @category Function
     * @param {Function} func The function to curry.
     * @param {number} [arity=func.length] The arity of `func`.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {Function} Returns the new curried function.
     * @example
     *
     * var abc = function(a, b, c) {
     *   return [a, b, c];
     * };
     *
     * var curried = _.curry(abc);
     *
     * curried(1)(2)(3);
     * // => [1, 2, 3]
     *
     * curried(1, 2)(3);
     * // => [1, 2, 3]
     *
     * curried(1, 2, 3);
     * // => [1, 2, 3]
     *
     * // Curried with placeholders.
     * curried(1)(_, 3)(2);
     * // => [1, 2, 3]
     */
    function curry(func, arity, guard) {
      arity = guard ? undefined : arity;
      var result = createWrap(func, WRAP_CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
      result.placeholder = curry.placeholder;
      return result;
    }

    /**
     * This method is like `_.curry` except that arguments are applied to `func`
     * in the manner of `_.partialRight` instead of `_.partial`.
     *
     * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
     * builds, may be used as a placeholder for provided arguments.
     *
     * **Note:** This method doesn't set the "length" property of curried functions.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Function
     * @param {Function} func The function to curry.
     * @param {number} [arity=func.length] The arity of `func`.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {Function} Returns the new curried function.
     * @example
     *
     * var abc = function(a, b, c) {
     *   return [a, b, c];
     * };
     *
     * var curried = _.curryRight(abc);
     *
     * curried(3)(2)(1);
     * // => [1, 2, 3]
     *
     * curried(2, 3)(1);
     * // => [1, 2, 3]
     *
     * curried(1, 2, 3);
     * // => [1, 2, 3]
     *
     * // Curried with placeholders.
     * curried(3)(1, _)(2);
     * // => [1, 2, 3]
     */
    function curryRight(func, arity, guard) {
      arity = guard ? undefined : arity;
      var result = createWrap(func, WRAP_CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
      result.placeholder = curryRight.placeholder;
      return result;
    }

    /**
     * Creates a debounced function that delays invoking `func` until after `wait`
     * milliseconds have elapsed since the last time the debounced function was
     * invoked. The debounced function comes with a `cancel` method to cancel
     * delayed `func` invocations and a `flush` method to immediately invoke them.
     * Provide `options` to indicate whether `func` should be invoked on the
     * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
     * with the last arguments provided to the debounced function. Subsequent
     * calls to the debounced function return the result of the last `func`
     * invocation.
     *
     * **Note:** If `leading` and `trailing` options are `true`, `func` is
     * invoked on the trailing edge of the timeout only if the debounced function
     * is invoked more than once during the `wait` timeout.
     *
     * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
     * until to the next tick, similar to `setTimeout` with a timeout of `0`.
     *
     * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
     * for details over the differences between `_.debounce` and `_.throttle`.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Function
     * @param {Function} func The function to debounce.
     * @param {number} [wait=0] The number of milliseconds to delay.
     * @param {Object} [options={}] The options object.
     * @param {boolean} [options.leading=false]
     *  Specify invoking on the leading edge of the timeout.
     * @param {number} [options.maxWait]
     *  The maximum time `func` is allowed to be delayed before it's invoked.
     * @param {boolean} [options.trailing=true]
     *  Specify invoking on the trailing edge of the timeout.
     * @returns {Function} Returns the new debounced function.
     * @example
     *
     * // Avoid costly calculations while the window size is in flux.
     * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
     *
     * // Invoke `sendMail` when clicked, debouncing subsequent calls.
     * jQuery(element).on('click', _.debounce(sendMail, 300, {
     *   'leading': true,
     *   'trailing': false
     * }));
     *
     * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
     * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
     * var source = new EventSource('/stream');
     * jQuery(source).on('message', debounced);
     *
     * // Cancel the trailing debounced invocation.
     * jQuery(window).on('popstate', debounced.cancel);
     */
    function debounce(func, wait, options) {
      var lastArgs,
          lastThis,
          maxWait,
          result,
          timerId,
          lastCallTime,
          lastInvokeTime = 0,
          leading = false,
          maxing = false,
          trailing = true;

      if (typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      wait = toNumber(wait) || 0;
      if (isObject(options)) {
        leading = !!options.leading;
        maxing = 'maxWait' in options;
        maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
        trailing = 'trailing' in options ? !!options.trailing : trailing;
      }

      function invokeFunc(time) {
        var args = lastArgs,
            thisArg = lastThis;

        lastArgs = lastThis = undefined;
        lastInvokeTime = time;
        result = func.apply(thisArg, args);
        return result;
      }

      function leadingEdge(time) {
        // Reset any `maxWait` timer.
        lastInvokeTime = time;
        // Start the timer for the trailing edge.
        timerId = setTimeout(timerExpired, wait);
        // Invoke the leading edge.
        return leading ? invokeFunc(time) : result;
      }

      function remainingWait(time) {
        var timeSinceLastCall = time - lastCallTime,
            timeSinceLastInvoke = time - lastInvokeTime,
            timeWaiting = wait - timeSinceLastCall;

        return maxing
          ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
          : timeWaiting;
      }

      function shouldInvoke(time) {
        var timeSinceLastCall = time - lastCallTime,
            timeSinceLastInvoke = time - lastInvokeTime;

        // Either this is the first call, activity has stopped and we're at the
        // trailing edge, the system time has gone backwards and we're treating
        // it as the trailing edge, or we've hit the `maxWait` limit.
        return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
          (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
      }

      function timerExpired() {
        var time = now();
        if (shouldInvoke(time)) {
          return trailingEdge(time);
        }
        // Restart the timer.
        timerId = setTimeout(timerExpired, remainingWait(time));
      }

      function trailingEdge(time) {
        timerId = undefined;

        // Only invoke if we have `lastArgs` which means `func` has been
        // debounced at least once.
        if (trailing && lastArgs) {
          return invokeFunc(time);
        }
        lastArgs = lastThis = undefined;
        return result;
      }

      function cancel() {
        if (timerId !== undefined) {
          clearTimeout(timerId);
        }
        lastInvokeTime = 0;
        lastArgs = lastCallTime = lastThis = timerId = undefined;
      }

      function flush() {
        return timerId === undefined ? result : trailingEdge(now());
      }

      function debounced() {
        var time = now(),
            isInvoking = shouldInvoke(time);

        lastArgs = arguments;
        lastThis = this;
        lastCallTime = time;

        if (isInvoking) {
          if (timerId === undefined) {
            return leadingEdge(lastCallTime);
          }
          if (maxing) {
            // Handle invocations in a tight loop.
            clearTimeout(timerId);
            timerId = setTimeout(timerExpired, wait);
            return invokeFunc(lastCallTime);
          }
        }
        if (timerId === undefined) {
          timerId = setTimeout(timerExpired, wait);
        }
        return result;
      }
      debounced.cancel = cancel;
      debounced.flush = flush;
      return debounced;
    }

    /**
     * Defers invoking the `func` until the current call stack has cleared. Any
     * additional arguments are provided to `func` when it's invoked.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Function
     * @param {Function} func The function to defer.
     * @param {...*} [args] The arguments to invoke `func` with.
     * @returns {number} Returns the timer id.
     * @example
     *
     * _.defer(function(text) {
     *   console.log(text);
     * }, 'deferred');
     * // => Logs 'deferred' after one millisecond.
     */
    var defer = baseRest(function(func, args) {
      return baseDelay(func, 1, args);
    });

    /**
     * Invokes `func` after `wait` milliseconds. Any additional arguments are
     * provided to `func` when it's invoked.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Function
     * @param {Function} func The function to delay.
     * @param {number} wait The number of milliseconds to delay invocation.
     * @param {...*} [args] The arguments to invoke `func` with.
     * @returns {number} Returns the timer id.
     * @example
     *
     * _.delay(function(text) {
     *   console.log(text);
     * }, 1000, 'later');
     * // => Logs 'later' after one second.
     */
    var delay = baseRest(function(func, wait, args) {
      return baseDelay(func, toNumber(wait) || 0, args);
    });

    /**
     * Creates a function that invokes `func` with arguments reversed.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Function
     * @param {Function} func The function to flip arguments for.
     * @returns {Function} Returns the new flipped function.
     * @example
     *
     * var flipped = _.flip(function() {
     *   return _.toArray(arguments);
     * });
     *
     * flipped('a', 'b', 'c', 'd');
     * // => ['d', 'c', 'b', 'a']
     */
    function flip(func) {
      return createWrap(func, WRAP_FLIP_FLAG);
    }

    /**
     * Creates a function that memoizes the result of `func`. If `resolver` is
     * provided, it determines the cache key for storing the result based on the
     * arguments provided to the memoized function. By default, the first argument
     * provided to the memoized function is used as the map cache key. The `func`
     * is invoked with the `this` binding of the memoized function.
     *
     * **Note:** The cache is exposed as the `cache` property on the memoized
     * function. Its creation may be customized by replacing the `_.memoize.Cache`
     * constructor with one whose instances implement the
     * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
     * method interface of `clear`, `delete`, `get`, `has`, and `set`.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Function
     * @param {Function} func The function to have its output memoized.
     * @param {Function} [resolver] The function to resolve the cache key.
     * @returns {Function} Returns the new memoized function.
     * @example
     *
     * var object = { 'a': 1, 'b': 2 };
     * var other = { 'c': 3, 'd': 4 };
     *
     * var values = _.memoize(_.values);
     * values(object);
     * // => [1, 2]
     *
     * values(other);
     * // => [3, 4]
     *
     * object.a = 2;
     * values(object);
     * // => [1, 2]
     *
     * // Modify the result cache.
     * values.cache.set(object, ['a', 'b']);
     * values(object);
     * // => ['a', 'b']
     *
     * // Replace `_.memoize.Cache`.
     * _.memoize.Cache = WeakMap;
     */
    function memoize(func, resolver) {
      if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      var memoized = function() {
        var args = arguments,
            key = resolver ? resolver.apply(this, args) : args[0],
            cache = memoized.cache;

        if (cache.has(key)) {
          return cache.get(key);
        }
        var result = func.apply(this, args);
        memoized.cache = cache.set(key, result) || cache;
        return result;
      };
      memoized.cache = new (memoize.Cache || MapCache);
      return memoized;
    }

    // Expose `MapCache`.
    memoize.Cache = MapCache;

    /**
     * Creates a function that negates the result of the predicate `func`. The
     * `func` predicate is invoked with the `this` binding and arguments of the
     * created function.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Function
     * @param {Function} predicate The predicate to negate.
     * @returns {Function} Returns the new negated function.
     * @example
     *
     * function isEven(n) {
     *   return n % 2 == 0;
     * }
     *
     * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
     * // => [1, 3, 5]
     */
    function negate(predicate) {
      if (typeof predicate != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      return function() {
        var args = arguments;
        switch (args.length) {
          case 0: return !predicate.call(this);
          case 1: return !predicate.call(this, args[0]);
          case 2: return !predicate.call(this, args[0], args[1]);
          case 3: return !predicate.call(this, args[0], args[1], args[2]);
        }
        return !predicate.apply(this, args);
      };
    }

    /**
     * Creates a function that is restricted to invoking `func` once. Repeat calls
     * to the function return the value of the first invocation. The `func` is
     * invoked with the `this` binding and arguments of the created function.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Function
     * @param {Function} func The function to restrict.
     * @returns {Function} Returns the new restricted function.
     * @example
     *
     * var initialize = _.once(createApplication);
     * initialize();
     * initialize();
     * // => `createApplication` is invoked once
     */
    function once(func) {
      return before(2, func);
    }

    /**
     * Creates a function that invokes `func` with its arguments transformed.
     *
     * @static
     * @since 4.0.0
     * @memberOf _
     * @category Function
     * @param {Function} func The function to wrap.
     * @param {...(Function|Function[])} [transforms=[_.identity]]
     *  The argument transforms.
     * @returns {Function} Returns the new function.
     * @example
     *
     * function doubled(n) {
     *   return n * 2;
     * }
     *
     * function square(n) {
     *   return n * n;
     * }
     *
     * var func = _.overArgs(function(x, y) {
     *   return [x, y];
     * }, [square, doubled]);
     *
     * func(9, 3);
     * // => [81, 6]
     *
     * func(10, 5);
     * // => [100, 10]
     */
    var overArgs = castRest(function(func, transforms) {
      transforms = (transforms.length == 1 && isArray(transforms[0]))
        ? arrayMap(transforms[0], baseUnary(getIteratee()))
        : arrayMap(baseFlatten(transforms, 1), baseUnary(getIteratee()));

      var funcsLength = transforms.length;
      return baseRest(function(args) {
        var index = -1,
            length = nativeMin(args.length, funcsLength);

        while (++index < length) {
          args[index] = transforms[index].call(this, args[index]);
        }
        return apply(func, this, args);
      });
    });

    /**
     * Creates a function that invokes `func` with `partials` prepended to the
     * arguments it receives. This method is like `_.bind` except it does **not**
     * alter the `this` binding.
     *
     * The `_.partial.placeholder` value, which defaults to `_` in monolithic
     * builds, may be used as a placeholder for partially applied arguments.
     *
     * **Note:** This method doesn't set the "length" property of partially
     * applied functions.
     *
     * @static
     * @memberOf _
     * @since 0.2.0
     * @category Function
     * @param {Function} func The function to partially apply arguments to.
     * @param {...*} [partials] The arguments to be partially applied.
     * @returns {Function} Returns the new partially applied function.
     * @example
     *
     * function greet(greeting, name) {
     *   return greeting + ' ' + name;
     * }
     *
     * var sayHelloTo = _.partial(greet, 'hello');
     * sayHelloTo('fred');
     * // => 'hello fred'
     *
     * // Partially applied with placeholders.
     * var greetFred = _.partial(greet, _, 'fred');
     * greetFred('hi');
     * // => 'hi fred'
     */
    var partial = baseRest(function(func, partials) {
      var holders = replaceHolders(partials, getHolder(partial));
      return createWrap(func, WRAP_PARTIAL_FLAG, undefined, partials, holders);
    });

    /**
     * This method is like `_.partial` except that partially applied arguments
     * are appended to the arguments it receives.
     *
     * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic
     * builds, may be used as a placeholder for partially applied arguments.
     *
     * **Note:** This method doesn't set the "length" property of partially
     * applied functions.
     *
     * @static
     * @memberOf _
     * @since 1.0.0
     * @category Function
     * @param {Function} func The function to partially apply arguments to.
     * @param {...*} [partials] The arguments to be partially applied.
     * @returns {Function} Returns the new partially applied function.
     * @example
     *
     * function greet(greeting, name) {
     *   return greeting + ' ' + name;
     * }
     *
     * var greetFred = _.partialRight(greet, 'fred');
     * greetFred('hi');
     * // => 'hi fred'
     *
     * // Partially applied with placeholders.
     * var sayHelloTo = _.partialRight(greet, 'hello', _);
     * sayHelloTo('fred');
     * // => 'hello fred'
     */
    var partialRight = baseRest(function(func, partials) {
      var holders = replaceHolders(partials, getHolder(partialRight));
      return createWrap(func, WRAP_PARTIAL_RIGHT_FLAG, undefined, partials, holders);
    });

    /**
     * Creates a function that invokes `func` with arguments arranged according
     * to the specified `indexes` where the argument value at the first index is
     * provided as the first argument, the argument value at the second index is
     * provided as the second argument, and so on.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Function
     * @param {Function} func The function to rearrange arguments for.
     * @param {...(number|number[])} indexes The arranged argument indexes.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var rearged = _.rearg(function(a, b, c) {
     *   return [a, b, c];
     * }, [2, 0, 1]);
     *
     * rearged('b', 'c', 'a')
     * // => ['a', 'b', 'c']
     */
    var rearg = flatRest(function(func, indexes) {
      return createWrap(func, WRAP_REARG_FLAG, undefined, undefined, undefined, indexes);
    });

    /**
     * Creates a function that invokes `func` with the `this` binding of the
     * created function and arguments from `start` and beyond provided as
     * an array.
     *
     * **Note:** This method is based on the
     * [rest parameter](https://mdn.io/rest_parameters).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Function
     * @param {Function} func The function to apply a rest parameter to.
     * @param {number} [start=func.length-1] The start position of the rest parameter.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var say = _.rest(function(what, names) {
     *   return what + ' ' + _.initial(names).join(', ') +
     *     (_.size(names) > 1 ? ', & ' : '') + _.last(names);
     * });
     *
     * say('hello', 'fred', 'barney', 'pebbles');
     * // => 'hello fred, barney, & pebbles'
     */
    function rest(func, start) {
      if (typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      start = start === undefined ? start : toInteger(start);
      return baseRest(func, start);
    }

    /**
     * Creates a function that invokes `func` with the `this` binding of the
     * create function and an array of arguments much like
     * [`Function#apply`](http://www.ecma-international.org/ecma-262/7.0/#sec-function.prototype.apply).
     *
     * **Note:** This method is based on the
     * [spread operator](https://mdn.io/spread_operator).
     *
     * @static
     * @memberOf _
     * @since 3.2.0
     * @category Function
     * @param {Function} func The function to spread arguments over.
     * @param {number} [start=0] The start position of the spread.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var say = _.spread(function(who, what) {
     *   return who + ' says ' + what;
     * });
     *
     * say(['fred', 'hello']);
     * // => 'fred says hello'
     *
     * var numbers = Promise.all([
     *   Promise.resolve(40),
     *   Promise.resolve(36)
     * ]);
     *
     * numbers.then(_.spread(function(x, y) {
     *   return x + y;
     * }));
     * // => a Promise of 76
     */
    function spread(func, start) {
      if (typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      start = start == null ? 0 : nativeMax(toInteger(start), 0);
      return baseRest(function(args) {
        var array = args[start],
            otherArgs = castSlice(args, 0, start);

        if (array) {
          arrayPush(otherArgs, array);
        }
        return apply(func, this, otherArgs);
      });
    }

    /**
     * Creates a throttled function that only invokes `func` at most once per
     * every `wait` milliseconds. The throttled function comes with a `cancel`
     * method to cancel delayed `func` invocations and a `flush` method to
     * immediately invoke them. Provide `options` to indicate whether `func`
     * should be invoked on the leading and/or trailing edge of the `wait`
     * timeout. The `func` is invoked with the last arguments provided to the
     * throttled function. Subsequent calls to the throttled function return the
     * result of the last `func` invocation.
     *
     * **Note:** If `leading` and `trailing` options are `true`, `func` is
     * invoked on the trailing edge of the timeout only if the throttled function
     * is invoked more than once during the `wait` timeout.
     *
     * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
     * until to the next tick, similar to `setTimeout` with a timeout of `0`.
     *
     * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
     * for details over the differences between `_.throttle` and `_.debounce`.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Function
     * @param {Function} func The function to throttle.
     * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
     * @param {Object} [options={}] The options object.
     * @param {boolean} [options.leading=true]
     *  Specify invoking on the leading edge of the timeout.
     * @param {boolean} [options.trailing=true]
     *  Specify invoking on the trailing edge of the timeout.
     * @returns {Function} Returns the new throttled function.
     * @example
     *
     * // Avoid excessively updating the position while scrolling.
     * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
     *
     * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
     * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
     * jQuery(element).on('click', throttled);
     *
     * // Cancel the trailing throttled invocation.
     * jQuery(window).on('popstate', throttled.cancel);
     */
    function throttle(func, wait, options) {
      var leading = true,
          trailing = true;

      if (typeof func != 'function') {
        throw new TypeError(FUNC_ERROR_TEXT);
      }
      if (isObject(options)) {
        leading = 'leading' in options ? !!options.leading : leading;
        trailing = 'trailing' in options ? !!options.trailing : trailing;
      }
      return debounce(func, wait, {
        'leading': leading,
        'maxWait': wait,
        'trailing': trailing
      });
    }

    /**
     * Creates a function that accepts up to one argument, ignoring any
     * additional arguments.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Function
     * @param {Function} func The function to cap arguments for.
     * @returns {Function} Returns the new capped function.
     * @example
     *
     * _.map(['6', '8', '10'], _.unary(parseInt));
     * // => [6, 8, 10]
     */
    function unary(func) {
      return ary(func, 1);
    }

    /**
     * Creates a function that provides `value` to `wrapper` as its first
     * argument. Any additional arguments provided to the function are appended
     * to those provided to the `wrapper`. The wrapper is invoked with the `this`
     * binding of the created function.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Function
     * @param {*} value The value to wrap.
     * @param {Function} [wrapper=identity] The wrapper function.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var p = _.wrap(_.escape, function(func, text) {
     *   return '<p>' + func(text) + '</p>';
     * });
     *
     * p('fred, barney, & pebbles');
     * // => '<p>fred, barney, &amp; pebbles</p>'
     */
    function wrap(value, wrapper) {
      return partial(castFunction(wrapper), value);
    }

    /*------------------------------------------------------------------------*/

    /**
     * Casts `value` as an array if it's not one.
     *
     * @static
     * @memberOf _
     * @since 4.4.0
     * @category Lang
     * @param {*} value The value to inspect.
     * @returns {Array} Returns the cast array.
     * @example
     *
     * _.castArray(1);
     * // => [1]
     *
     * _.castArray({ 'a': 1 });
     * // => [{ 'a': 1 }]
     *
     * _.castArray('abc');
     * // => ['abc']
     *
     * _.castArray(null);
     * // => [null]
     *
     * _.castArray(undefined);
     * // => [undefined]
     *
     * _.castArray();
     * // => []
     *
     * var array = [1, 2, 3];
     * console.log(_.castArray(array) === array);
     * // => true
     */
    function castArray() {
      if (!arguments.length) {
        return [];
      }
      var value = arguments[0];
      return isArray(value) ? value : [value];
    }

    /**
     * Creates a shallow clone of `value`.
     *
     * **Note:** This method is loosely based on the
     * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm)
     * and supports cloning arrays, array buffers, booleans, date objects, maps,
     * numbers, `Object` objects, regexes, sets, strings, symbols, and typed
     * arrays. The own enumerable properties of `arguments` objects are cloned
     * as plain objects. An empty object is returned for uncloneable values such
     * as error objects, functions, DOM nodes, and WeakMaps.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Lang
     * @param {*} value The value to clone.
     * @returns {*} Returns the cloned value.
     * @see _.cloneDeep
     * @example
     *
     * var objects = [{ 'a': 1 }, { 'b': 2 }];
     *
     * var shallow = _.clone(objects);
     * console.log(shallow[0] === objects[0]);
     * // => true
     */
    function clone(value) {
      return baseClone(value, CLONE_SYMBOLS_FLAG);
    }

    /**
     * This method is like `_.clone` except that it accepts `customizer` which
     * is invoked to produce the cloned value. If `customizer` returns `undefined`,
     * cloning is handled by the method instead. The `customizer` is invoked with
     * up to four arguments; (value [, index|key, object, stack]).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to clone.
     * @param {Function} [customizer] The function to customize cloning.
     * @returns {*} Returns the cloned value.
     * @see _.cloneDeepWith
     * @example
     *
     * function customizer(value) {
     *   if (_.isElement(value)) {
     *     return value.cloneNode(false);
     *   }
     * }
     *
     * var el = _.cloneWith(document.body, customizer);
     *
     * console.log(el === document.body);
     * // => false
     * console.log(el.nodeName);
     * // => 'BODY'
     * console.log(el.childNodes.length);
     * // => 0
     */
    function cloneWith(value, customizer) {
      customizer = typeof customizer == 'function' ? customizer : undefined;
      return baseClone(value, CLONE_SYMBOLS_FLAG, customizer);
    }

    /**
     * This method is like `_.clone` except that it recursively clones `value`.
     *
     * @static
     * @memberOf _
     * @since 1.0.0
     * @category Lang
     * @param {*} value The value to recursively clone.
     * @returns {*} Returns the deep cloned value.
     * @see _.clone
     * @example
     *
     * var objects = [{ 'a': 1 }, { 'b': 2 }];
     *
     * var deep = _.cloneDeep(objects);
     * console.log(deep[0] === objects[0]);
     * // => false
     */
    function cloneDeep(value) {
      return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG);
    }

    /**
     * This method is like `_.cloneWith` except that it recursively clones `value`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to recursively clone.
     * @param {Function} [customizer] The function to customize cloning.
     * @returns {*} Returns the deep cloned value.
     * @see _.cloneWith
     * @example
     *
     * function customizer(value) {
     *   if (_.isElement(value)) {
     *     return value.cloneNode(true);
     *   }
     * }
     *
     * var el = _.cloneDeepWith(document.body, customizer);
     *
     * console.log(el === document.body);
     * // => false
     * console.log(el.nodeName);
     * // => 'BODY'
     * console.log(el.childNodes.length);
     * // => 20
     */
    function cloneDeepWith(value, customizer) {
      customizer = typeof customizer == 'function' ? customizer : undefined;
      return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG, customizer);
    }

    /**
     * Checks if `object` conforms to `source` by invoking the predicate
     * properties of `source` with the corresponding property values of `object`.
     *
     * **Note:** This method is equivalent to `_.conforms` when `source` is
     * partially applied.
     *
     * @static
     * @memberOf _
     * @since 4.14.0
     * @category Lang
     * @param {Object} object The object to inspect.
     * @param {Object} source The object of property predicates to conform to.
     * @returns {boolean} Returns `true` if `object` conforms, else `false`.
     * @example
     *
     * var object = { 'a': 1, 'b': 2 };
     *
     * _.conformsTo(object, { 'b': function(n) { return n > 1; } });
     * // => true
     *
     * _.conformsTo(object, { 'b': function(n) { return n > 2; } });
     * // => false
     */
    function conformsTo(object, source) {
      return source == null || baseConformsTo(object, source, keys(source));
    }

    /**
     * Performs a
     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
     * comparison between two values to determine if they are equivalent.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
     * @example
     *
     * var object = { 'a': 1 };
     * var other = { 'a': 1 };
     *
     * _.eq(object, object);
     * // => true
     *
     * _.eq(object, other);
     * // => false
     *
     * _.eq('a', 'a');
     * // => true
     *
     * _.eq('a', Object('a'));
     * // => false
     *
     * _.eq(NaN, NaN);
     * // => true
     */
    function eq(value, other) {
      return value === other || (value !== value && other !== other);
    }

    /**
     * Checks if `value` is greater than `other`.
     *
     * @static
     * @memberOf _
     * @since 3.9.0
     * @category Lang
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @returns {boolean} Returns `true` if `value` is greater than `other`,
     *  else `false`.
     * @see _.lt
     * @example
     *
     * _.gt(3, 1);
     * // => true
     *
     * _.gt(3, 3);
     * // => false
     *
     * _.gt(1, 3);
     * // => false
     */
    var gt = createRelationalOperation(baseGt);

    /**
     * Checks if `value` is greater than or equal to `other`.
     *
     * @static
     * @memberOf _
     * @since 3.9.0
     * @category Lang
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @returns {boolean} Returns `true` if `value` is greater than or equal to
     *  `other`, else `false`.
     * @see _.lte
     * @example
     *
     * _.gte(3, 1);
     * // => true
     *
     * _.gte(3, 3);
     * // => true
     *
     * _.gte(1, 3);
     * // => false
     */
    var gte = createRelationalOperation(function(value, other) {
      return value >= other;
    });

    /**
     * Checks if `value` is likely an `arguments` object.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is an `arguments` object,
     *  else `false`.
     * @example
     *
     * _.isArguments(function() { return arguments; }());
     * // => true
     *
     * _.isArguments([1, 2, 3]);
     * // => false
     */
    var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) {
      return isObjectLike(value) && hasOwnProperty.call(value, 'callee') &&
        !propertyIsEnumerable.call(value, 'callee');
    };

    /**
     * Checks if `value` is classified as an `Array` object.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is an array, else `false`.
     * @example
     *
     * _.isArray([1, 2, 3]);
     * // => true
     *
     * _.isArray(document.body.children);
     * // => false
     *
     * _.isArray('abc');
     * // => false
     *
     * _.isArray(_.noop);
     * // => false
     */
    var isArray = Array.isArray;

    /**
     * Checks if `value` is classified as an `ArrayBuffer` object.
     *
     * @static
     * @memberOf _
     * @since 4.3.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`.
     * @example
     *
     * _.isArrayBuffer(new ArrayBuffer(2));
     * // => true
     *
     * _.isArrayBuffer(new Array(2));
     * // => false
     */
    var isArrayBuffer = nodeIsArrayBuffer ? baseUnary(nodeIsArrayBuffer) : baseIsArrayBuffer;

    /**
     * Checks if `value` is array-like. A value is considered array-like if it's
     * not a function and has a `value.length` that's an integer greater than or
     * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
     * @example
     *
     * _.isArrayLike([1, 2, 3]);
     * // => true
     *
     * _.isArrayLike(document.body.children);
     * // => true
     *
     * _.isArrayLike('abc');
     * // => true
     *
     * _.isArrayLike(_.noop);
     * // => false
     */
    function isArrayLike(value) {
      return value != null && isLength(value.length) && !isFunction(value);
    }

    /**
     * This method is like `_.isArrayLike` except that it also checks if `value`
     * is an object.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is an array-like object,
     *  else `false`.
     * @example
     *
     * _.isArrayLikeObject([1, 2, 3]);
     * // => true
     *
     * _.isArrayLikeObject(document.body.children);
     * // => true
     *
     * _.isArrayLikeObject('abc');
     * // => false
     *
     * _.isArrayLikeObject(_.noop);
     * // => false
     */
    function isArrayLikeObject(value) {
      return isObjectLike(value) && isArrayLike(value);
    }

    /**
     * Checks if `value` is classified as a boolean primitive or object.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a boolean, else `false`.
     * @example
     *
     * _.isBoolean(false);
     * // => true
     *
     * _.isBoolean(null);
     * // => false
     */
    function isBoolean(value) {
      return value === true || value === false ||
        (isObjectLike(value) && baseGetTag(value) == boolTag);
    }

    /**
     * Checks if `value` is a buffer.
     *
     * @static
     * @memberOf _
     * @since 4.3.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
     * @example
     *
     * _.isBuffer(new Buffer(2));
     * // => true
     *
     * _.isBuffer(new Uint8Array(2));
     * // => false
     */
    var isBuffer = nativeIsBuffer || stubFalse;

    /**
     * Checks if `value` is classified as a `Date` object.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a date object, else `false`.
     * @example
     *
     * _.isDate(new Date);
     * // => true
     *
     * _.isDate('Mon April 23 2012');
     * // => false
     */
    var isDate = nodeIsDate ? baseUnary(nodeIsDate) : baseIsDate;

    /**
     * Checks if `value` is likely a DOM element.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`.
     * @example
     *
     * _.isElement(document.body);
     * // => true
     *
     * _.isElement('<body>');
     * // => false
     */
    function isElement(value) {
      return isObjectLike(value) && value.nodeType === 1 && !isPlainObject(value);
    }

    /**
     * Checks if `value` is an empty object, collection, map, or set.
     *
     * Objects are considered empty if they have no own enumerable string keyed
     * properties.
     *
     * Array-like values such as `arguments` objects, arrays, buffers, strings, or
     * jQuery-like collections are considered empty if they have a `length` of `0`.
     * Similarly, maps and sets are considered empty if they have a `size` of `0`.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is empty, else `false`.
     * @example
     *
     * _.isEmpty(null);
     * // => true
     *
     * _.isEmpty(true);
     * // => true
     *
     * _.isEmpty(1);
     * // => true
     *
     * _.isEmpty([1, 2, 3]);
     * // => false
     *
     * _.isEmpty({ 'a': 1 });
     * // => false
     */
    function isEmpty(value) {
      if (value == null) {
        return true;
      }
      if (isArrayLike(value) &&
          (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' ||
            isBuffer(value) || isTypedArray(value) || isArguments(value))) {
        return !value.length;
      }
      var tag = getTag(value);
      if (tag == mapTag || tag == setTag) {
        return !value.size;
      }
      if (isPrototype(value)) {
        return !baseKeys(value).length;
      }
      for (var key in value) {
        if (hasOwnProperty.call(value, key)) {
          return false;
        }
      }
      return true;
    }

    /**
     * Performs a deep comparison between two values to determine if they are
     * equivalent.
     *
     * **Note:** This method supports comparing arrays, array buffers, booleans,
     * date objects, error objects, maps, numbers, `Object` objects, regexes,
     * sets, strings, symbols, and typed arrays. `Object` objects are compared
     * by their own, not inherited, enumerable properties. Functions and DOM
     * nodes are compared by strict equality, i.e. `===`.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Lang
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
     * @example
     *
     * var object = { 'a': 1 };
     * var other = { 'a': 1 };
     *
     * _.isEqual(object, other);
     * // => true
     *
     * object === other;
     * // => false
     */
    function isEqual(value, other) {
      return baseIsEqual(value, other);
    }

    /**
     * This method is like `_.isEqual` except that it accepts `customizer` which
     * is invoked to compare values. If `customizer` returns `undefined`, comparisons
     * are handled by the method instead. The `customizer` is invoked with up to
     * six arguments: (objValue, othValue [, index|key, object, other, stack]).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @param {Function} [customizer] The function to customize comparisons.
     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
     * @example
     *
     * function isGreeting(value) {
     *   return /^h(?:i|ello)$/.test(value);
     * }
     *
     * function customizer(objValue, othValue) {
     *   if (isGreeting(objValue) && isGreeting(othValue)) {
     *     return true;
     *   }
     * }
     *
     * var array = ['hello', 'goodbye'];
     * var other = ['hi', 'goodbye'];
     *
     * _.isEqualWith(array, other, customizer);
     * // => true
     */
    function isEqualWith(value, other, customizer) {
      customizer = typeof customizer == 'function' ? customizer : undefined;
      var result = customizer ? customizer(value, other) : undefined;
      return result === undefined ? baseIsEqual(value, other, undefined, customizer) : !!result;
    }

    /**
     * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`,
     * `SyntaxError`, `TypeError`, or `URIError` object.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is an error object, else `false`.
     * @example
     *
     * _.isError(new Error);
     * // => true
     *
     * _.isError(Error);
     * // => false
     */
    function isError(value) {
      if (!isObjectLike(value)) {
        return false;
      }
      var tag = baseGetTag(value);
      return tag == errorTag || tag == domExcTag ||
        (typeof value.message == 'string' && typeof value.name == 'string' && !isPlainObject(value));
    }

    /**
     * Checks if `value` is a finite primitive number.
     *
     * **Note:** This method is based on
     * [`Number.isFinite`](https://mdn.io/Number/isFinite).
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a finite number, else `false`.
     * @example
     *
     * _.isFinite(3);
     * // => true
     *
     * _.isFinite(Number.MIN_VALUE);
     * // => true
     *
     * _.isFinite(Infinity);
     * // => false
     *
     * _.isFinite('3');
     * // => false
     */
    function isFinite(value) {
      return typeof value == 'number' && nativeIsFinite(value);
    }

    /**
     * Checks if `value` is classified as a `Function` object.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a function, else `false`.
     * @example
     *
     * _.isFunction(_);
     * // => true
     *
     * _.isFunction(/abc/);
     * // => false
     */
    function isFunction(value) {
      if (!isObject(value)) {
        return false;
      }
      // The use of `Object#toString` avoids issues with the `typeof` operator
      // in Safari 9 which returns 'object' for typed arrays and other constructors.
      var tag = baseGetTag(value);
      return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;
    }

    /**
     * Checks if `value` is an integer.
     *
     * **Note:** This method is based on
     * [`Number.isInteger`](https://mdn.io/Number/isInteger).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is an integer, else `false`.
     * @example
     *
     * _.isInteger(3);
     * // => true
     *
     * _.isInteger(Number.MIN_VALUE);
     * // => false
     *
     * _.isInteger(Infinity);
     * // => false
     *
     * _.isInteger('3');
     * // => false
     */
    function isInteger(value) {
      return typeof value == 'number' && value == toInteger(value);
    }

    /**
     * Checks if `value` is a valid array-like length.
     *
     * **Note:** This method is loosely based on
     * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
     * @example
     *
     * _.isLength(3);
     * // => true
     *
     * _.isLength(Number.MIN_VALUE);
     * // => false
     *
     * _.isLength(Infinity);
     * // => false
     *
     * _.isLength('3');
     * // => false
     */
    function isLength(value) {
      return typeof value == 'number' &&
        value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
    }

    /**
     * Checks if `value` is the
     * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
     * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is an object, else `false`.
     * @example
     *
     * _.isObject({});
     * // => true
     *
     * _.isObject([1, 2, 3]);
     * // => true
     *
     * _.isObject(_.noop);
     * // => true
     *
     * _.isObject(null);
     * // => false
     */
    function isObject(value) {
      var type = typeof value;
      return value != null && (type == 'object' || type == 'function');
    }

    /**
     * Checks if `value` is object-like. A value is object-like if it's not `null`
     * and has a `typeof` result of "object".
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
     * @example
     *
     * _.isObjectLike({});
     * // => true
     *
     * _.isObjectLike([1, 2, 3]);
     * // => true
     *
     * _.isObjectLike(_.noop);
     * // => false
     *
     * _.isObjectLike(null);
     * // => false
     */
    function isObjectLike(value) {
      return value != null && typeof value == 'object';
    }

    /**
     * Checks if `value` is classified as a `Map` object.
     *
     * @static
     * @memberOf _
     * @since 4.3.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a map, else `false`.
     * @example
     *
     * _.isMap(new Map);
     * // => true
     *
     * _.isMap(new WeakMap);
     * // => false
     */
    var isMap = nodeIsMap ? baseUnary(nodeIsMap) : baseIsMap;

    /**
     * Performs a partial deep comparison between `object` and `source` to
     * determine if `object` contains equivalent property values.
     *
     * **Note:** This method is equivalent to `_.matches` when `source` is
     * partially applied.
     *
     * Partial comparisons will match empty array and empty object `source`
     * values against any array or object value, respectively. See `_.isEqual`
     * for a list of supported value comparisons.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Lang
     * @param {Object} object The object to inspect.
     * @param {Object} source The object of property values to match.
     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
     * @example
     *
     * var object = { 'a': 1, 'b': 2 };
     *
     * _.isMatch(object, { 'b': 2 });
     * // => true
     *
     * _.isMatch(object, { 'b': 1 });
     * // => false
     */
    function isMatch(object, source) {
      return object === source || baseIsMatch(object, source, getMatchData(source));
    }

    /**
     * This method is like `_.isMatch` except that it accepts `customizer` which
     * is invoked to compare values. If `customizer` returns `undefined`, comparisons
     * are handled by the method instead. The `customizer` is invoked with five
     * arguments: (objValue, srcValue, index|key, object, source).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {Object} object The object to inspect.
     * @param {Object} source The object of property values to match.
     * @param {Function} [customizer] The function to customize comparisons.
     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
     * @example
     *
     * function isGreeting(value) {
     *   return /^h(?:i|ello)$/.test(value);
     * }
     *
     * function customizer(objValue, srcValue) {
     *   if (isGreeting(objValue) && isGreeting(srcValue)) {
     *     return true;
     *   }
     * }
     *
     * var object = { 'greeting': 'hello' };
     * var source = { 'greeting': 'hi' };
     *
     * _.isMatchWith(object, source, customizer);
     * // => true
     */
    function isMatchWith(object, source, customizer) {
      customizer = typeof customizer == 'function' ? customizer : undefined;
      return baseIsMatch(object, source, getMatchData(source), customizer);
    }

    /**
     * Checks if `value` is `NaN`.
     *
     * **Note:** This method is based on
     * [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as
     * global [`isNaN`](https://mdn.io/isNaN) which returns `true` for
     * `undefined` and other non-number values.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
     * @example
     *
     * _.isNaN(NaN);
     * // => true
     *
     * _.isNaN(new Number(NaN));
     * // => true
     *
     * isNaN(undefined);
     * // => true
     *
     * _.isNaN(undefined);
     * // => false
     */
    function isNaN(value) {
      // An `NaN` primitive is the only value that is not equal to itself.
      // Perform the `toStringTag` check first to avoid errors with some
      // ActiveX objects in IE.
      return isNumber(value) && value != +value;
    }

    /**
     * Checks if `value` is a pristine native function.
     *
     * **Note:** This method can't reliably detect native functions in the presence
     * of the core-js package because core-js circumvents this kind of detection.
     * Despite multiple requests, the core-js maintainer has made it clear: any
     * attempt to fix the detection will be obstructed. As a result, we're left
     * with little choice but to throw an error. Unfortunately, this also affects
     * packages, like [babel-polyfill](https://www.npmjs.com/package/babel-polyfill),
     * which rely on core-js.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a native function,
     *  else `false`.
     * @example
     *
     * _.isNative(Array.prototype.push);
     * // => true
     *
     * _.isNative(_);
     * // => false
     */
    function isNative(value) {
      if (isMaskable(value)) {
        throw new Error(CORE_ERROR_TEXT);
      }
      return baseIsNative(value);
    }

    /**
     * Checks if `value` is `null`.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is `null`, else `false`.
     * @example
     *
     * _.isNull(null);
     * // => true
     *
     * _.isNull(void 0);
     * // => false
     */
    function isNull(value) {
      return value === null;
    }

    /**
     * Checks if `value` is `null` or `undefined`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is nullish, else `false`.
     * @example
     *
     * _.isNil(null);
     * // => true
     *
     * _.isNil(void 0);
     * // => true
     *
     * _.isNil(NaN);
     * // => false
     */
    function isNil(value) {
      return value == null;
    }

    /**
     * Checks if `value` is classified as a `Number` primitive or object.
     *
     * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are
     * classified as numbers, use the `_.isFinite` method.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a number, else `false`.
     * @example
     *
     * _.isNumber(3);
     * // => true
     *
     * _.isNumber(Number.MIN_VALUE);
     * // => true
     *
     * _.isNumber(Infinity);
     * // => true
     *
     * _.isNumber('3');
     * // => false
     */
    function isNumber(value) {
      return typeof value == 'number' ||
        (isObjectLike(value) && baseGetTag(value) == numberTag);
    }

    /**
     * Checks if `value` is a plain object, that is, an object created by the
     * `Object` constructor or one with a `[[Prototype]]` of `null`.
     *
     * @static
     * @memberOf _
     * @since 0.8.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     * }
     *
     * _.isPlainObject(new Foo);
     * // => false
     *
     * _.isPlainObject([1, 2, 3]);
     * // => false
     *
     * _.isPlainObject({ 'x': 0, 'y': 0 });
     * // => true
     *
     * _.isPlainObject(Object.create(null));
     * // => true
     */
    function isPlainObject(value) {
      if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
        return false;
      }
      var proto = getPrototype(value);
      if (proto === null) {
        return true;
      }
      var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
      return typeof Ctor == 'function' && Ctor instanceof Ctor &&
        funcToString.call(Ctor) == objectCtorString;
    }

    /**
     * Checks if `value` is classified as a `RegExp` object.
     *
     * @static
     * @memberOf _
     * @since 0.1.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a regexp, else `false`.
     * @example
     *
     * _.isRegExp(/abc/);
     * // => true
     *
     * _.isRegExp('/abc/');
     * // => false
     */
    var isRegExp = nodeIsRegExp ? baseUnary(nodeIsRegExp) : baseIsRegExp;

    /**
     * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754
     * double precision number which isn't the result of a rounded unsafe integer.
     *
     * **Note:** This method is based on
     * [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a safe integer, else `false`.
     * @example
     *
     * _.isSafeInteger(3);
     * // => true
     *
     * _.isSafeInteger(Number.MIN_VALUE);
     * // => false
     *
     * _.isSafeInteger(Infinity);
     * // => false
     *
     * _.isSafeInteger('3');
     * // => false
     */
    function isSafeInteger(value) {
      return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER;
    }

    /**
     * Checks if `value` is classified as a `Set` object.
     *
     * @static
     * @memberOf _
     * @since 4.3.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a set, else `false`.
     * @example
     *
     * _.isSet(new Set);
     * // => true
     *
     * _.isSet(new WeakSet);
     * // => false
     */
    var isSet = nodeIsSet ? baseUnary(nodeIsSet) : baseIsSet;

    /**
     * Checks if `value` is classified as a `String` primitive or object.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a string, else `false`.
     * @example
     *
     * _.isString('abc');
     * // => true
     *
     * _.isString(1);
     * // => false
     */
    function isString(value) {
      return typeof value == 'string' ||
        (!isArray(value) && isObjectLike(value) && baseGetTag(value) == stringTag);
    }

    /**
     * Checks if `value` is classified as a `Symbol` primitive or object.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
     * @example
     *
     * _.isSymbol(Symbol.iterator);
     * // => true
     *
     * _.isSymbol('abc');
     * // => false
     */
    function isSymbol(value) {
      return typeof value == 'symbol' ||
        (isObjectLike(value) && baseGetTag(value) == symbolTag);
    }

    /**
     * Checks if `value` is classified as a typed array.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
     * @example
     *
     * _.isTypedArray(new Uint8Array);
     * // => true
     *
     * _.isTypedArray([]);
     * // => false
     */
    var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray;

    /**
     * Checks if `value` is `undefined`.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
     * @example
     *
     * _.isUndefined(void 0);
     * // => true
     *
     * _.isUndefined(null);
     * // => false
     */
    function isUndefined(value) {
      return value === undefined;
    }

    /**
     * Checks if `value` is classified as a `WeakMap` object.
     *
     * @static
     * @memberOf _
     * @since 4.3.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a weak map, else `false`.
     * @example
     *
     * _.isWeakMap(new WeakMap);
     * // => true
     *
     * _.isWeakMap(new Map);
     * // => false
     */
    function isWeakMap(value) {
      return isObjectLike(value) && getTag(value) == weakMapTag;
    }

    /**
     * Checks if `value` is classified as a `WeakSet` object.
     *
     * @static
     * @memberOf _
     * @since 4.3.0
     * @category Lang
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a weak set, else `false`.
     * @example
     *
     * _.isWeakSet(new WeakSet);
     * // => true
     *
     * _.isWeakSet(new Set);
     * // => false
     */
    function isWeakSet(value) {
      return isObjectLike(value) && baseGetTag(value) == weakSetTag;
    }

    /**
     * Checks if `value` is less than `other`.
     *
     * @static
     * @memberOf _
     * @since 3.9.0
     * @category Lang
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @returns {boolean} Returns `true` if `value` is less than `other`,
     *  else `false`.
     * @see _.gt
     * @example
     *
     * _.lt(1, 3);
     * // => true
     *
     * _.lt(3, 3);
     * // => false
     *
     * _.lt(3, 1);
     * // => false
     */
    var lt = createRelationalOperation(baseLt);

    /**
     * Checks if `value` is less than or equal to `other`.
     *
     * @static
     * @memberOf _
     * @since 3.9.0
     * @category Lang
     * @param {*} value The value to compare.
     * @param {*} other The other value to compare.
     * @returns {boolean} Returns `true` if `value` is less than or equal to
     *  `other`, else `false`.
     * @see _.gte
     * @example
     *
     * _.lte(1, 3);
     * // => true
     *
     * _.lte(3, 3);
     * // => true
     *
     * _.lte(3, 1);
     * // => false
     */
    var lte = createRelationalOperation(function(value, other) {
      return value <= other;
    });

    /**
     * Converts `value` to an array.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Lang
     * @param {*} value The value to convert.
     * @returns {Array} Returns the converted array.
     * @example
     *
     * _.toArray({ 'a': 1, 'b': 2 });
     * // => [1, 2]
     *
     * _.toArray('abc');
     * // => ['a', 'b', 'c']
     *
     * _.toArray(1);
     * // => []
     *
     * _.toArray(null);
     * // => []
     */
    function toArray(value) {
      if (!value) {
        return [];
      }
      if (isArrayLike(value)) {
        return isString(value) ? stringToArray(value) : copyArray(value);
      }
      if (symIterator && value[symIterator]) {
        return iteratorToArray(value[symIterator]());
      }
      var tag = getTag(value),
          func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values);

      return func(value);
    }

    /**
     * Converts `value` to a finite number.
     *
     * @static
     * @memberOf _
     * @since 4.12.0
     * @category Lang
     * @param {*} value The value to convert.
     * @returns {number} Returns the converted number.
     * @example
     *
     * _.toFinite(3.2);
     * // => 3.2
     *
     * _.toFinite(Number.MIN_VALUE);
     * // => 5e-324
     *
     * _.toFinite(Infinity);
     * // => 1.7976931348623157e+308
     *
     * _.toFinite('3.2');
     * // => 3.2
     */
    function toFinite(value) {
      if (!value) {
        return value === 0 ? value : 0;
      }
      value = toNumber(value);
      if (value === INFINITY || value === -INFINITY) {
        var sign = (value < 0 ? -1 : 1);
        return sign * MAX_INTEGER;
      }
      return value === value ? value : 0;
    }

    /**
     * Converts `value` to an integer.
     *
     * **Note:** This method is loosely based on
     * [`ToInteger`](http://www.ecma-international.org/ecma-262/7.0/#sec-tointeger).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to convert.
     * @returns {number} Returns the converted integer.
     * @example
     *
     * _.toInteger(3.2);
     * // => 3
     *
     * _.toInteger(Number.MIN_VALUE);
     * // => 0
     *
     * _.toInteger(Infinity);
     * // => 1.7976931348623157e+308
     *
     * _.toInteger('3.2');
     * // => 3
     */
    function toInteger(value) {
      var result = toFinite(value),
          remainder = result % 1;

      return result === result ? (remainder ? result - remainder : result) : 0;
    }

    /**
     * Converts `value` to an integer suitable for use as the length of an
     * array-like object.
     *
     * **Note:** This method is based on
     * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to convert.
     * @returns {number} Returns the converted integer.
     * @example
     *
     * _.toLength(3.2);
     * // => 3
     *
     * _.toLength(Number.MIN_VALUE);
     * // => 0
     *
     * _.toLength(Infinity);
     * // => 4294967295
     *
     * _.toLength('3.2');
     * // => 3
     */
    function toLength(value) {
      return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0;
    }

    /**
     * Converts `value` to a number.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to process.
     * @returns {number} Returns the number.
     * @example
     *
     * _.toNumber(3.2);
     * // => 3.2
     *
     * _.toNumber(Number.MIN_VALUE);
     * // => 5e-324
     *
     * _.toNumber(Infinity);
     * // => Infinity
     *
     * _.toNumber('3.2');
     * // => 3.2
     */
    function toNumber(value) {
      if (typeof value == 'number') {
        return value;
      }
      if (isSymbol(value)) {
        return NAN;
      }
      if (isObject(value)) {
        var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
        value = isObject(other) ? (other + '') : other;
      }
      if (typeof value != 'string') {
        return value === 0 ? value : +value;
      }
      value = value.replace(reTrim, '');
      var isBinary = reIsBinary.test(value);
      return (isBinary || reIsOctal.test(value))
        ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
        : (reIsBadHex.test(value) ? NAN : +value);
    }

    /**
     * Converts `value` to a plain object flattening inherited enumerable string
     * keyed properties of `value` to own properties of the plain object.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Lang
     * @param {*} value The value to convert.
     * @returns {Object} Returns the converted plain object.
     * @example
     *
     * function Foo() {
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.assign({ 'a': 1 }, new Foo);
     * // => { 'a': 1, 'b': 2 }
     *
     * _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
     * // => { 'a': 1, 'b': 2, 'c': 3 }
     */
    function toPlainObject(value) {
      return copyObject(value, keysIn(value));
    }

    /**
     * Converts `value` to a safe integer. A safe integer can be compared and
     * represented correctly.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to convert.
     * @returns {number} Returns the converted integer.
     * @example
     *
     * _.toSafeInteger(3.2);
     * // => 3
     *
     * _.toSafeInteger(Number.MIN_VALUE);
     * // => 0
     *
     * _.toSafeInteger(Infinity);
     * // => 9007199254740991
     *
     * _.toSafeInteger('3.2');
     * // => 3
     */
    function toSafeInteger(value) {
      return value
        ? baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER)
        : (value === 0 ? value : 0);
    }

    /**
     * Converts `value` to a string. An empty string is returned for `null`
     * and `undefined` values. The sign of `-0` is preserved.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Lang
     * @param {*} value The value to convert.
     * @returns {string} Returns the converted string.
     * @example
     *
     * _.toString(null);
     * // => ''
     *
     * _.toString(-0);
     * // => '-0'
     *
     * _.toString([1, 2, 3]);
     * // => '1,2,3'
     */
    function toString(value) {
      return value == null ? '' : baseToString(value);
    }

    /*------------------------------------------------------------------------*/

    /**
     * Assigns own enumerable string keyed properties of source objects to the
     * destination object. Source objects are applied from left to right.
     * Subsequent sources overwrite property assignments of previous sources.
     *
     * **Note:** This method mutates `object` and is loosely based on
     * [`Object.assign`](https://mdn.io/Object/assign).
     *
     * @static
     * @memberOf _
     * @since 0.10.0
     * @category Object
     * @param {Object} object The destination object.
     * @param {...Object} [sources] The source objects.
     * @returns {Object} Returns `object`.
     * @see _.assignIn
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     * }
     *
     * function Bar() {
     *   this.c = 3;
     * }
     *
     * Foo.prototype.b = 2;
     * Bar.prototype.d = 4;
     *
     * _.assign({ 'a': 0 }, new Foo, new Bar);
     * // => { 'a': 1, 'c': 3 }
     */
    var assign = createAssigner(function(object, source) {
      if (isPrototype(source) || isArrayLike(source)) {
        copyObject(source, keys(source), object);
        return;
      }
      for (var key in source) {
        if (hasOwnProperty.call(source, key)) {
          assignValue(object, key, source[key]);
        }
      }
    });

    /**
     * This method is like `_.assign` except that it iterates over own and
     * inherited source properties.
     *
     * **Note:** This method mutates `object`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @alias extend
     * @category Object
     * @param {Object} object The destination object.
     * @param {...Object} [sources] The source objects.
     * @returns {Object} Returns `object`.
     * @see _.assign
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     * }
     *
     * function Bar() {
     *   this.c = 3;
     * }
     *
     * Foo.prototype.b = 2;
     * Bar.prototype.d = 4;
     *
     * _.assignIn({ 'a': 0 }, new Foo, new Bar);
     * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }
     */
    var assignIn = createAssigner(function(object, source) {
      copyObject(source, keysIn(source), object);
    });

    /**
     * This method is like `_.assignIn` except that it accepts `customizer`
     * which is invoked to produce the assigned values. If `customizer` returns
     * `undefined`, assignment is handled by the method instead. The `customizer`
     * is invoked with five arguments: (objValue, srcValue, key, object, source).
     *
     * **Note:** This method mutates `object`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @alias extendWith
     * @category Object
     * @param {Object} object The destination object.
     * @param {...Object} sources The source objects.
     * @param {Function} [customizer] The function to customize assigned values.
     * @returns {Object} Returns `object`.
     * @see _.assignWith
     * @example
     *
     * function customizer(objValue, srcValue) {
     *   return _.isUndefined(objValue) ? srcValue : objValue;
     * }
     *
     * var defaults = _.partialRight(_.assignInWith, customizer);
     *
     * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
     * // => { 'a': 1, 'b': 2 }
     */
    var assignInWith = createAssigner(function(object, source, srcIndex, customizer) {
      copyObject(source, keysIn(source), object, customizer);
    });

    /**
     * This method is like `_.assign` except that it accepts `customizer`
     * which is invoked to produce the assigned values. If `customizer` returns
     * `undefined`, assignment is handled by the method instead. The `customizer`
     * is invoked with five arguments: (objValue, srcValue, key, object, source).
     *
     * **Note:** This method mutates `object`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Object
     * @param {Object} object The destination object.
     * @param {...Object} sources The source objects.
     * @param {Function} [customizer] The function to customize assigned values.
     * @returns {Object} Returns `object`.
     * @see _.assignInWith
     * @example
     *
     * function customizer(objValue, srcValue) {
     *   return _.isUndefined(objValue) ? srcValue : objValue;
     * }
     *
     * var defaults = _.partialRight(_.assignWith, customizer);
     *
     * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
     * // => { 'a': 1, 'b': 2 }
     */
    var assignWith = createAssigner(function(object, source, srcIndex, customizer) {
      copyObject(source, keys(source), object, customizer);
    });

    /**
     * Creates an array of values corresponding to `paths` of `object`.
     *
     * @static
     * @memberOf _
     * @since 1.0.0
     * @category Object
     * @param {Object} object The object to iterate over.
     * @param {...(string|string[])} [paths] The property paths to pick.
     * @returns {Array} Returns the picked values.
     * @example
     *
     * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
     *
     * _.at(object, ['a[0].b.c', 'a[1]']);
     * // => [3, 4]
     */
    var at = flatRest(baseAt);

    /**
     * Creates an object that inherits from the `prototype` object. If a
     * `properties` object is given, its own enumerable string keyed properties
     * are assigned to the created object.
     *
     * @static
     * @memberOf _
     * @since 2.3.0
     * @category Object
     * @param {Object} prototype The object to inherit from.
     * @param {Object} [properties] The properties to assign to the object.
     * @returns {Object} Returns the new object.
     * @example
     *
     * function Shape() {
     *   this.x = 0;
     *   this.y = 0;
     * }
     *
     * function Circle() {
     *   Shape.call(this);
     * }
     *
     * Circle.prototype = _.create(Shape.prototype, {
     *   'constructor': Circle
     * });
     *
     * var circle = new Circle;
     * circle instanceof Circle;
     * // => true
     *
     * circle instanceof Shape;
     * // => true
     */
    function create(prototype, properties) {
      var result = baseCreate(prototype);
      return properties == null ? result : baseAssign(result, properties);
    }

    /**
     * Assigns own and inherited enumerable string keyed properties of source
     * objects to the destination object for all destination properties that
     * resolve to `undefined`. Source objects are applied from left to right.
     * Once a property is set, additional values of the same property are ignored.
     *
     * **Note:** This method mutates `object`.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Object
     * @param {Object} object The destination object.
     * @param {...Object} [sources] The source objects.
     * @returns {Object} Returns `object`.
     * @see _.defaultsDeep
     * @example
     *
     * _.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
     * // => { 'a': 1, 'b': 2 }
     */
    var defaults = baseRest(function(object, sources) {
      object = Object(object);

      var index = -1;
      var length = sources.length;
      var guard = length > 2 ? sources[2] : undefined;

      if (guard && isIterateeCall(sources[0], sources[1], guard)) {
        length = 1;
      }

      while (++index < length) {
        var source = sources[index];
        var props = keysIn(source);
        var propsIndex = -1;
        var propsLength = props.length;

        while (++propsIndex < propsLength) {
          var key = props[propsIndex];
          var value = object[key];

          if (value === undefined ||
              (eq(value, objectProto[key]) && !hasOwnProperty.call(object, key))) {
            object[key] = source[key];
          }
        }
      }

      return object;
    });

    /**
     * This method is like `_.defaults` except that it recursively assigns
     * default properties.
     *
     * **Note:** This method mutates `object`.
     *
     * @static
     * @memberOf _
     * @since 3.10.0
     * @category Object
     * @param {Object} object The destination object.
     * @param {...Object} [sources] The source objects.
     * @returns {Object} Returns `object`.
     * @see _.defaults
     * @example
     *
     * _.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } });
     * // => { 'a': { 'b': 2, 'c': 3 } }
     */
    var defaultsDeep = baseRest(function(args) {
      args.push(undefined, customDefaultsMerge);
      return apply(mergeWith, undefined, args);
    });

    /**
     * This method is like `_.find` except that it returns the key of the first
     * element `predicate` returns truthy for instead of the element itself.
     *
     * @static
     * @memberOf _
     * @since 1.1.0
     * @category Object
     * @param {Object} object The object to inspect.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @returns {string|undefined} Returns the key of the matched element,
     *  else `undefined`.
     * @example
     *
     * var users = {
     *   'barney':  { 'age': 36, 'active': true },
     *   'fred':    { 'age': 40, 'active': false },
     *   'pebbles': { 'age': 1,  'active': true }
     * };
     *
     * _.findKey(users, function(o) { return o.age < 40; });
     * // => 'barney' (iteration order is not guaranteed)
     *
     * // The `_.matches` iteratee shorthand.
     * _.findKey(users, { 'age': 1, 'active': true });
     * // => 'pebbles'
     *
     * // The `_.matchesProperty` iteratee shorthand.
     * _.findKey(users, ['active', false]);
     * // => 'fred'
     *
     * // The `_.property` iteratee shorthand.
     * _.findKey(users, 'active');
     * // => 'barney'
     */
    function findKey(object, predicate) {
      return baseFindKey(object, getIteratee(predicate, 3), baseForOwn);
    }

    /**
     * This method is like `_.findKey` except that it iterates over elements of
     * a collection in the opposite order.
     *
     * @static
     * @memberOf _
     * @since 2.0.0
     * @category Object
     * @param {Object} object The object to inspect.
     * @param {Function} [predicate=_.identity] The function invoked per iteration.
     * @returns {string|undefined} Returns the key of the matched element,
     *  else `undefined`.
     * @example
     *
     * var users = {
     *   'barney':  { 'age': 36, 'active': true },
     *   'fred':    { 'age': 40, 'active': false },
     *   'pebbles': { 'age': 1,  'active': true }
     * };
     *
     * _.findLastKey(users, function(o) { return o.age < 40; });
     * // => returns 'pebbles' assuming `_.findKey` returns 'barney'
     *
     * // The `_.matches` iteratee shorthand.
     * _.findLastKey(users, { 'age': 36, 'active': true });
     * // => 'barney'
     *
     * // The `_.matchesProperty` iteratee shorthand.
     * _.findLastKey(users, ['active', false]);
     * // => 'fred'
     *
     * // The `_.property` iteratee shorthand.
     * _.findLastKey(users, 'active');
     * // => 'pebbles'
     */
    function findLastKey(object, predicate) {
      return baseFindKey(object, getIteratee(predicate, 3), baseForOwnRight);
    }

    /**
     * Iterates over own and inherited enumerable string keyed properties of an
     * object and invokes `iteratee` for each property. The iteratee is invoked
     * with three arguments: (value, key, object). Iteratee functions may exit
     * iteration early by explicitly returning `false`.
     *
     * @static
     * @memberOf _
     * @since 0.3.0
     * @category Object
     * @param {Object} object The object to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @returns {Object} Returns `object`.
     * @see _.forInRight
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.forIn(new Foo, function(value, key) {
     *   console.log(key);
     * });
     * // => Logs 'a', 'b', then 'c' (iteration order is not guaranteed).
     */
    function forIn(object, iteratee) {
      return object == null
        ? object
        : baseFor(object, getIteratee(iteratee, 3), keysIn);
    }

    /**
     * This method is like `_.forIn` except that it iterates over properties of
     * `object` in the opposite order.
     *
     * @static
     * @memberOf _
     * @since 2.0.0
     * @category Object
     * @param {Object} object The object to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @returns {Object} Returns `object`.
     * @see _.forIn
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.forInRight(new Foo, function(value, key) {
     *   console.log(key);
     * });
     * // => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'.
     */
    function forInRight(object, iteratee) {
      return object == null
        ? object
        : baseForRight(object, getIteratee(iteratee, 3), keysIn);
    }

    /**
     * Iterates over own enumerable string keyed properties of an object and
     * invokes `iteratee` for each property. The iteratee is invoked with three
     * arguments: (value, key, object). Iteratee functions may exit iteration
     * early by explicitly returning `false`.
     *
     * @static
     * @memberOf _
     * @since 0.3.0
     * @category Object
     * @param {Object} object The object to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @returns {Object} Returns `object`.
     * @see _.forOwnRight
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.forOwn(new Foo, function(value, key) {
     *   console.log(key);
     * });
     * // => Logs 'a' then 'b' (iteration order is not guaranteed).
     */
    function forOwn(object, iteratee) {
      return object && baseForOwn(object, getIteratee(iteratee, 3));
    }

    /**
     * This method is like `_.forOwn` except that it iterates over properties of
     * `object` in the opposite order.
     *
     * @static
     * @memberOf _
     * @since 2.0.0
     * @category Object
     * @param {Object} object The object to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @returns {Object} Returns `object`.
     * @see _.forOwn
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.forOwnRight(new Foo, function(value, key) {
     *   console.log(key);
     * });
     * // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'.
     */
    function forOwnRight(object, iteratee) {
      return object && baseForOwnRight(object, getIteratee(iteratee, 3));
    }

    /**
     * Creates an array of function property names from own enumerable properties
     * of `object`.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Object
     * @param {Object} object The object to inspect.
     * @returns {Array} Returns the function names.
     * @see _.functionsIn
     * @example
     *
     * function Foo() {
     *   this.a = _.constant('a');
     *   this.b = _.constant('b');
     * }
     *
     * Foo.prototype.c = _.constant('c');
     *
     * _.functions(new Foo);
     * // => ['a', 'b']
     */
    function functions(object) {
      return object == null ? [] : baseFunctions(object, keys(object));
    }

    /**
     * Creates an array of function property names from own and inherited
     * enumerable properties of `object`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Object
     * @param {Object} object The object to inspect.
     * @returns {Array} Returns the function names.
     * @see _.functions
     * @example
     *
     * function Foo() {
     *   this.a = _.constant('a');
     *   this.b = _.constant('b');
     * }
     *
     * Foo.prototype.c = _.constant('c');
     *
     * _.functionsIn(new Foo);
     * // => ['a', 'b', 'c']
     */
    function functionsIn(object) {
      return object == null ? [] : baseFunctions(object, keysIn(object));
    }

    /**
     * Gets the value at `path` of `object`. If the resolved value is
     * `undefined`, the `defaultValue` is returned in its place.
     *
     * @static
     * @memberOf _
     * @since 3.7.0
     * @category Object
     * @param {Object} object The object to query.
     * @param {Array|string} path The path of the property to get.
     * @param {*} [defaultValue] The value returned for `undefined` resolved values.
     * @returns {*} Returns the resolved value.
     * @example
     *
     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
     *
     * _.get(object, 'a[0].b.c');
     * // => 3
     *
     * _.get(object, ['a', '0', 'b', 'c']);
     * // => 3
     *
     * _.get(object, 'a.b.c', 'default');
     * // => 'default'
     */
    function get(object, path, defaultValue) {
      var result = object == null ? undefined : baseGet(object, path);
      return result === undefined ? defaultValue : result;
    }

    /**
     * Checks if `path` is a direct property of `object`.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Object
     * @param {Object} object The object to query.
     * @param {Array|string} path The path to check.
     * @returns {boolean} Returns `true` if `path` exists, else `false`.
     * @example
     *
     * var object = { 'a': { 'b': 2 } };
     * var other = _.create({ 'a': _.create({ 'b': 2 }) });
     *
     * _.has(object, 'a');
     * // => true
     *
     * _.has(object, 'a.b');
     * // => true
     *
     * _.has(object, ['a', 'b']);
     * // => true
     *
     * _.has(other, 'a');
     * // => false
     */
    function has(object, path) {
      return object != null && hasPath(object, path, baseHas);
    }

    /**
     * Checks if `path` is a direct or inherited property of `object`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Object
     * @param {Object} object The object to query.
     * @param {Array|string} path The path to check.
     * @returns {boolean} Returns `true` if `path` exists, else `false`.
     * @example
     *
     * var object = _.create({ 'a': _.create({ 'b': 2 }) });
     *
     * _.hasIn(object, 'a');
     * // => true
     *
     * _.hasIn(object, 'a.b');
     * // => true
     *
     * _.hasIn(object, ['a', 'b']);
     * // => true
     *
     * _.hasIn(object, 'b');
     * // => false
     */
    function hasIn(object, path) {
      return object != null && hasPath(object, path, baseHasIn);
    }

    /**
     * Creates an object composed of the inverted keys and values of `object`.
     * If `object` contains duplicate values, subsequent values overwrite
     * property assignments of previous values.
     *
     * @static
     * @memberOf _
     * @since 0.7.0
     * @category Object
     * @param {Object} object The object to invert.
     * @returns {Object} Returns the new inverted object.
     * @example
     *
     * var object = { 'a': 1, 'b': 2, 'c': 1 };
     *
     * _.invert(object);
     * // => { '1': 'c', '2': 'b' }
     */
    var invert = createInverter(function(result, value, key) {
      if (value != null &&
          typeof value.toString != 'function') {
        value = nativeObjectToString.call(value);
      }

      result[value] = key;
    }, constant(identity));

    /**
     * This method is like `_.invert` except that the inverted object is generated
     * from the results of running each element of `object` thru `iteratee`. The
     * corresponding inverted value of each inverted key is an array of keys
     * responsible for generating the inverted value. The iteratee is invoked
     * with one argument: (value).
     *
     * @static
     * @memberOf _
     * @since 4.1.0
     * @category Object
     * @param {Object} object The object to invert.
     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
     * @returns {Object} Returns the new inverted object.
     * @example
     *
     * var object = { 'a': 1, 'b': 2, 'c': 1 };
     *
     * _.invertBy(object);
     * // => { '1': ['a', 'c'], '2': ['b'] }
     *
     * _.invertBy(object, function(value) {
     *   return 'group' + value;
     * });
     * // => { 'group1': ['a', 'c'], 'group2': ['b'] }
     */
    var invertBy = createInverter(function(result, value, key) {
      if (value != null &&
          typeof value.toString != 'function') {
        value = nativeObjectToString.call(value);
      }

      if (hasOwnProperty.call(result, value)) {
        result[value].push(key);
      } else {
        result[value] = [key];
      }
    }, getIteratee);

    /**
     * Invokes the method at `path` of `object`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Object
     * @param {Object} object The object to query.
     * @param {Array|string} path The path of the method to invoke.
     * @param {...*} [args] The arguments to invoke the method with.
     * @returns {*} Returns the result of the invoked method.
     * @example
     *
     * var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] };
     *
     * _.invoke(object, 'a[0].b.c.slice', 1, 3);
     * // => [2, 3]
     */
    var invoke = baseRest(baseInvoke);

    /**
     * Creates an array of the own enumerable property names of `object`.
     *
     * **Note:** Non-object values are coerced to objects. See the
     * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
     * for more details.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Object
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of property names.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.keys(new Foo);
     * // => ['a', 'b'] (iteration order is not guaranteed)
     *
     * _.keys('hi');
     * // => ['0', '1']
     */
    function keys(object) {
      return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
    }

    /**
     * Creates an array of the own and inherited enumerable property names of `object`.
     *
     * **Note:** Non-object values are coerced to objects.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Object
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of property names.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.keysIn(new Foo);
     * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
     */
    function keysIn(object) {
      return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object);
    }

    /**
     * The opposite of `_.mapValues`; this method creates an object with the
     * same values as `object` and keys generated by running each own enumerable
     * string keyed property of `object` thru `iteratee`. The iteratee is invoked
     * with three arguments: (value, key, object).
     *
     * @static
     * @memberOf _
     * @since 3.8.0
     * @category Object
     * @param {Object} object The object to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @returns {Object} Returns the new mapped object.
     * @see _.mapValues
     * @example
     *
     * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) {
     *   return key + value;
     * });
     * // => { 'a1': 1, 'b2': 2 }
     */
    function mapKeys(object, iteratee) {
      var result = {};
      iteratee = getIteratee(iteratee, 3);

      baseForOwn(object, function(value, key, object) {
        baseAssignValue(result, iteratee(value, key, object), value);
      });
      return result;
    }

    /**
     * Creates an object with the same keys as `object` and values generated
     * by running each own enumerable string keyed property of `object` thru
     * `iteratee`. The iteratee is invoked with three arguments:
     * (value, key, object).
     *
     * @static
     * @memberOf _
     * @since 2.4.0
     * @category Object
     * @param {Object} object The object to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @returns {Object} Returns the new mapped object.
     * @see _.mapKeys
     * @example
     *
     * var users = {
     *   'fred':    { 'user': 'fred',    'age': 40 },
     *   'pebbles': { 'user': 'pebbles', 'age': 1 }
     * };
     *
     * _.mapValues(users, function(o) { return o.age; });
     * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
     *
     * // The `_.property` iteratee shorthand.
     * _.mapValues(users, 'age');
     * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
     */
    function mapValues(object, iteratee) {
      var result = {};
      iteratee = getIteratee(iteratee, 3);

      baseForOwn(object, function(value, key, object) {
        baseAssignValue(result, key, iteratee(value, key, object));
      });
      return result;
    }

    /**
     * This method is like `_.assign` except that it recursively merges own and
     * inherited enumerable string keyed properties of source objects into the
     * destination object. Source properties that resolve to `undefined` are
     * skipped if a destination value exists. Array and plain object properties
     * are merged recursively. Other objects and value types are overridden by
     * assignment. Source objects are applied from left to right. Subsequent
     * sources overwrite property assignments of previous sources.
     *
     * **Note:** This method mutates `object`.
     *
     * @static
     * @memberOf _
     * @since 0.5.0
     * @category Object
     * @param {Object} object The destination object.
     * @param {...Object} [sources] The source objects.
     * @returns {Object} Returns `object`.
     * @example
     *
     * var object = {
     *   'a': [{ 'b': 2 }, { 'd': 4 }]
     * };
     *
     * var other = {
     *   'a': [{ 'c': 3 }, { 'e': 5 }]
     * };
     *
     * _.merge(object, other);
     * // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
     */
    var merge = createAssigner(function(object, source, srcIndex) {
      baseMerge(object, source, srcIndex);
    });

    /**
     * This method is like `_.merge` except that it accepts `customizer` which
     * is invoked to produce the merged values of the destination and source
     * properties. If `customizer` returns `undefined`, merging is handled by the
     * method instead. The `customizer` is invoked with six arguments:
     * (objValue, srcValue, key, object, source, stack).
     *
     * **Note:** This method mutates `object`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Object
     * @param {Object} object The destination object.
     * @param {...Object} sources The source objects.
     * @param {Function} customizer The function to customize assigned values.
     * @returns {Object} Returns `object`.
     * @example
     *
     * function customizer(objValue, srcValue) {
     *   if (_.isArray(objValue)) {
     *     return objValue.concat(srcValue);
     *   }
     * }
     *
     * var object = { 'a': [1], 'b': [2] };
     * var other = { 'a': [3], 'b': [4] };
     *
     * _.mergeWith(object, other, customizer);
     * // => { 'a': [1, 3], 'b': [2, 4] }
     */
    var mergeWith = createAssigner(function(object, source, srcIndex, customizer) {
      baseMerge(object, source, srcIndex, customizer);
    });

    /**
     * The opposite of `_.pick`; this method creates an object composed of the
     * own and inherited enumerable property paths of `object` that are not omitted.
     *
     * **Note:** This method is considerably slower than `_.pick`.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Object
     * @param {Object} object The source object.
     * @param {...(string|string[])} [paths] The property paths to omit.
     * @returns {Object} Returns the new object.
     * @example
     *
     * var object = { 'a': 1, 'b': '2', 'c': 3 };
     *
     * _.omit(object, ['a', 'c']);
     * // => { 'b': '2' }
     */
    var omit = flatRest(function(object, paths) {
      var result = {};
      if (object == null) {
        return result;
      }
      var isDeep = false;
      paths = arrayMap(paths, function(path) {
        path = castPath(path, object);
        isDeep || (isDeep = path.length > 1);
        return path;
      });
      copyObject(object, getAllKeysIn(object), result);
      if (isDeep) {
        result = baseClone(result, CLONE_DEEP_FLAG | CLONE_FLAT_FLAG | CLONE_SYMBOLS_FLAG, customOmitClone);
      }
      var length = paths.length;
      while (length--) {
        baseUnset(result, paths[length]);
      }
      return result;
    });

    /**
     * The opposite of `_.pickBy`; this method creates an object composed of
     * the own and inherited enumerable string keyed properties of `object` that
     * `predicate` doesn't return truthy for. The predicate is invoked with two
     * arguments: (value, key).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Object
     * @param {Object} object The source object.
     * @param {Function} [predicate=_.identity] The function invoked per property.
     * @returns {Object} Returns the new object.
     * @example
     *
     * var object = { 'a': 1, 'b': '2', 'c': 3 };
     *
     * _.omitBy(object, _.isNumber);
     * // => { 'b': '2' }
     */
    function omitBy(object, predicate) {
      return pickBy(object, negate(getIteratee(predicate)));
    }

    /**
     * Creates an object composed of the picked `object` properties.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Object
     * @param {Object} object The source object.
     * @param {...(string|string[])} [paths] The property paths to pick.
     * @returns {Object} Returns the new object.
     * @example
     *
     * var object = { 'a': 1, 'b': '2', 'c': 3 };
     *
     * _.pick(object, ['a', 'c']);
     * // => { 'a': 1, 'c': 3 }
     */
    var pick = flatRest(function(object, paths) {
      return object == null ? {} : basePick(object, paths);
    });

    /**
     * Creates an object composed of the `object` properties `predicate` returns
     * truthy for. The predicate is invoked with two arguments: (value, key).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Object
     * @param {Object} object The source object.
     * @param {Function} [predicate=_.identity] The function invoked per property.
     * @returns {Object} Returns the new object.
     * @example
     *
     * var object = { 'a': 1, 'b': '2', 'c': 3 };
     *
     * _.pickBy(object, _.isNumber);
     * // => { 'a': 1, 'c': 3 }
     */
    function pickBy(object, predicate) {
      if (object == null) {
        return {};
      }
      var props = arrayMap(getAllKeysIn(object), function(prop) {
        return [prop];
      });
      predicate = getIteratee(predicate);
      return basePickBy(object, props, function(value, path) {
        return predicate(value, path[0]);
      });
    }

    /**
     * This method is like `_.get` except that if the resolved value is a
     * function it's invoked with the `this` binding of its parent object and
     * its result is returned.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Object
     * @param {Object} object The object to query.
     * @param {Array|string} path The path of the property to resolve.
     * @param {*} [defaultValue] The value returned for `undefined` resolved values.
     * @returns {*} Returns the resolved value.
     * @example
     *
     * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
     *
     * _.result(object, 'a[0].b.c1');
     * // => 3
     *
     * _.result(object, 'a[0].b.c2');
     * // => 4
     *
     * _.result(object, 'a[0].b.c3', 'default');
     * // => 'default'
     *
     * _.result(object, 'a[0].b.c3', _.constant('default'));
     * // => 'default'
     */
    function result(object, path, defaultValue) {
      path = castPath(path, object);

      var index = -1,
          length = path.length;

      // Ensure the loop is entered when path is empty.
      if (!length) {
        length = 1;
        object = undefined;
      }
      while (++index < length) {
        var value = object == null ? undefined : object[toKey(path[index])];
        if (value === undefined) {
          index = length;
          value = defaultValue;
        }
        object = isFunction(value) ? value.call(object) : value;
      }
      return object;
    }

    /**
     * Sets the value at `path` of `object`. If a portion of `path` doesn't exist,
     * it's created. Arrays are created for missing index properties while objects
     * are created for all other missing properties. Use `_.setWith` to customize
     * `path` creation.
     *
     * **Note:** This method mutates `object`.
     *
     * @static
     * @memberOf _
     * @since 3.7.0
     * @category Object
     * @param {Object} object The object to modify.
     * @param {Array|string} path The path of the property to set.
     * @param {*} value The value to set.
     * @returns {Object} Returns `object`.
     * @example
     *
     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
     *
     * _.set(object, 'a[0].b.c', 4);
     * console.log(object.a[0].b.c);
     * // => 4
     *
     * _.set(object, ['x', '0', 'y', 'z'], 5);
     * console.log(object.x[0].y.z);
     * // => 5
     */
    function set(object, path, value) {
      return object == null ? object : baseSet(object, path, value);
    }

    /**
     * This method is like `_.set` except that it accepts `customizer` which is
     * invoked to produce the objects of `path`.  If `customizer` returns `undefined`
     * path creation is handled by the method instead. The `customizer` is invoked
     * with three arguments: (nsValue, key, nsObject).
     *
     * **Note:** This method mutates `object`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Object
     * @param {Object} object The object to modify.
     * @param {Array|string} path The path of the property to set.
     * @param {*} value The value to set.
     * @param {Function} [customizer] The function to customize assigned values.
     * @returns {Object} Returns `object`.
     * @example
     *
     * var object = {};
     *
     * _.setWith(object, '[0][1]', 'a', Object);
     * // => { '0': { '1': 'a' } }
     */
    function setWith(object, path, value, customizer) {
      customizer = typeof customizer == 'function' ? customizer : undefined;
      return object == null ? object : baseSet(object, path, value, customizer);
    }

    /**
     * Creates an array of own enumerable string keyed-value pairs for `object`
     * which can be consumed by `_.fromPairs`. If `object` is a map or set, its
     * entries are returned.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @alias entries
     * @category Object
     * @param {Object} object The object to query.
     * @returns {Array} Returns the key-value pairs.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.toPairs(new Foo);
     * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed)
     */
    var toPairs = createToPairs(keys);

    /**
     * Creates an array of own and inherited enumerable string keyed-value pairs
     * for `object` which can be consumed by `_.fromPairs`. If `object` is a map
     * or set, its entries are returned.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @alias entriesIn
     * @category Object
     * @param {Object} object The object to query.
     * @returns {Array} Returns the key-value pairs.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.toPairsIn(new Foo);
     * // => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed)
     */
    var toPairsIn = createToPairs(keysIn);

    /**
     * An alternative to `_.reduce`; this method transforms `object` to a new
     * `accumulator` object which is the result of running each of its own
     * enumerable string keyed properties thru `iteratee`, with each invocation
     * potentially mutating the `accumulator` object. If `accumulator` is not
     * provided, a new object with the same `[[Prototype]]` will be used. The
     * iteratee is invoked with four arguments: (accumulator, value, key, object).
     * Iteratee functions may exit iteration early by explicitly returning `false`.
     *
     * @static
     * @memberOf _
     * @since 1.3.0
     * @category Object
     * @param {Object} object The object to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @param {*} [accumulator] The custom accumulator value.
     * @returns {*} Returns the accumulated value.
     * @example
     *
     * _.transform([2, 3, 4], function(result, n) {
     *   result.push(n *= n);
     *   return n % 2 == 0;
     * }, []);
     * // => [4, 9]
     *
     * _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
     *   (result[value] || (result[value] = [])).push(key);
     * }, {});
     * // => { '1': ['a', 'c'], '2': ['b'] }
     */
    function transform(object, iteratee, accumulator) {
      var isArr = isArray(object),
          isArrLike = isArr || isBuffer(object) || isTypedArray(object);

      iteratee = getIteratee(iteratee, 4);
      if (accumulator == null) {
        var Ctor = object && object.constructor;
        if (isArrLike) {
          accumulator = isArr ? new Ctor : [];
        }
        else if (isObject(object)) {
          accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {};
        }
        else {
          accumulator = {};
        }
      }
      (isArrLike ? arrayEach : baseForOwn)(object, function(value, index, object) {
        return iteratee(accumulator, value, index, object);
      });
      return accumulator;
    }

    /**
     * Removes the property at `path` of `object`.
     *
     * **Note:** This method mutates `object`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Object
     * @param {Object} object The object to modify.
     * @param {Array|string} path The path of the property to unset.
     * @returns {boolean} Returns `true` if the property is deleted, else `false`.
     * @example
     *
     * var object = { 'a': [{ 'b': { 'c': 7 } }] };
     * _.unset(object, 'a[0].b.c');
     * // => true
     *
     * console.log(object);
     * // => { 'a': [{ 'b': {} }] };
     *
     * _.unset(object, ['a', '0', 'b', 'c']);
     * // => true
     *
     * console.log(object);
     * // => { 'a': [{ 'b': {} }] };
     */
    function unset(object, path) {
      return object == null ? true : baseUnset(object, path);
    }

    /**
     * This method is like `_.set` except that accepts `updater` to produce the
     * value to set. Use `_.updateWith` to customize `path` creation. The `updater`
     * is invoked with one argument: (value).
     *
     * **Note:** This method mutates `object`.
     *
     * @static
     * @memberOf _
     * @since 4.6.0
     * @category Object
     * @param {Object} object The object to modify.
     * @param {Array|string} path The path of the property to set.
     * @param {Function} updater The function to produce the updated value.
     * @returns {Object} Returns `object`.
     * @example
     *
     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
     *
     * _.update(object, 'a[0].b.c', function(n) { return n * n; });
     * console.log(object.a[0].b.c);
     * // => 9
     *
     * _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; });
     * console.log(object.x[0].y.z);
     * // => 0
     */
    function update(object, path, updater) {
      return object == null ? object : baseUpdate(object, path, castFunction(updater));
    }

    /**
     * This method is like `_.update` except that it accepts `customizer` which is
     * invoked to produce the objects of `path`.  If `customizer` returns `undefined`
     * path creation is handled by the method instead. The `customizer` is invoked
     * with three arguments: (nsValue, key, nsObject).
     *
     * **Note:** This method mutates `object`.
     *
     * @static
     * @memberOf _
     * @since 4.6.0
     * @category Object
     * @param {Object} object The object to modify.
     * @param {Array|string} path The path of the property to set.
     * @param {Function} updater The function to produce the updated value.
     * @param {Function} [customizer] The function to customize assigned values.
     * @returns {Object} Returns `object`.
     * @example
     *
     * var object = {};
     *
     * _.updateWith(object, '[0][1]', _.constant('a'), Object);
     * // => { '0': { '1': 'a' } }
     */
    function updateWith(object, path, updater, customizer) {
      customizer = typeof customizer == 'function' ? customizer : undefined;
      return object == null ? object : baseUpdate(object, path, castFunction(updater), customizer);
    }

    /**
     * Creates an array of the own enumerable string keyed property values of `object`.
     *
     * **Note:** Non-object values are coerced to objects.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Object
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of property values.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.values(new Foo);
     * // => [1, 2] (iteration order is not guaranteed)
     *
     * _.values('hi');
     * // => ['h', 'i']
     */
    function values(object) {
      return object == null ? [] : baseValues(object, keys(object));
    }

    /**
     * Creates an array of the own and inherited enumerable string keyed property
     * values of `object`.
     *
     * **Note:** Non-object values are coerced to objects.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Object
     * @param {Object} object The object to query.
     * @returns {Array} Returns the array of property values.
     * @example
     *
     * function Foo() {
     *   this.a = 1;
     *   this.b = 2;
     * }
     *
     * Foo.prototype.c = 3;
     *
     * _.valuesIn(new Foo);
     * // => [1, 2, 3] (iteration order is not guaranteed)
     */
    function valuesIn(object) {
      return object == null ? [] : baseValues(object, keysIn(object));
    }

    /*------------------------------------------------------------------------*/

    /**
     * Clamps `number` within the inclusive `lower` and `upper` bounds.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Number
     * @param {number} number The number to clamp.
     * @param {number} [lower] The lower bound.
     * @param {number} upper The upper bound.
     * @returns {number} Returns the clamped number.
     * @example
     *
     * _.clamp(-10, -5, 5);
     * // => -5
     *
     * _.clamp(10, -5, 5);
     * // => 5
     */
    function clamp(number, lower, upper) {
      if (upper === undefined) {
        upper = lower;
        lower = undefined;
      }
      if (upper !== undefined) {
        upper = toNumber(upper);
        upper = upper === upper ? upper : 0;
      }
      if (lower !== undefined) {
        lower = toNumber(lower);
        lower = lower === lower ? lower : 0;
      }
      return baseClamp(toNumber(number), lower, upper);
    }

    /**
     * Checks if `n` is between `start` and up to, but not including, `end`. If
     * `end` is not specified, it's set to `start` with `start` then set to `0`.
     * If `start` is greater than `end` the params are swapped to support
     * negative ranges.
     *
     * @static
     * @memberOf _
     * @since 3.3.0
     * @category Number
     * @param {number} number The number to check.
     * @param {number} [start=0] The start of the range.
     * @param {number} end The end of the range.
     * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
     * @see _.range, _.rangeRight
     * @example
     *
     * _.inRange(3, 2, 4);
     * // => true
     *
     * _.inRange(4, 8);
     * // => true
     *
     * _.inRange(4, 2);
     * // => false
     *
     * _.inRange(2, 2);
     * // => false
     *
     * _.inRange(1.2, 2);
     * // => true
     *
     * _.inRange(5.2, 4);
     * // => false
     *
     * _.inRange(-3, -2, -6);
     * // => true
     */
    function inRange(number, start, end) {
      start = toFinite(start);
      if (end === undefined) {
        end = start;
        start = 0;
      } else {
        end = toFinite(end);
      }
      number = toNumber(number);
      return baseInRange(number, start, end);
    }

    /**
     * Produces a random number between the inclusive `lower` and `upper` bounds.
     * If only one argument is provided a number between `0` and the given number
     * is returned. If `floating` is `true`, or either `lower` or `upper` are
     * floats, a floating-point number is returned instead of an integer.
     *
     * **Note:** JavaScript follows the IEEE-754 standard for resolving
     * floating-point values which can produce unexpected results.
     *
     * @static
     * @memberOf _
     * @since 0.7.0
     * @category Number
     * @param {number} [lower=0] The lower bound.
     * @param {number} [upper=1] The upper bound.
     * @param {boolean} [floating] Specify returning a floating-point number.
     * @returns {number} Returns the random number.
     * @example
     *
     * _.random(0, 5);
     * // => an integer between 0 and 5
     *
     * _.random(5);
     * // => also an integer between 0 and 5
     *
     * _.random(5, true);
     * // => a floating-point number between 0 and 5
     *
     * _.random(1.2, 5.2);
     * // => a floating-point number between 1.2 and 5.2
     */
    function random(lower, upper, floating) {
      if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) {
        upper = floating = undefined;
      }
      if (floating === undefined) {
        if (typeof upper == 'boolean') {
          floating = upper;
          upper = undefined;
        }
        else if (typeof lower == 'boolean') {
          floating = lower;
          lower = undefined;
        }
      }
      if (lower === undefined && upper === undefined) {
        lower = 0;
        upper = 1;
      }
      else {
        lower = toFinite(lower);
        if (upper === undefined) {
          upper = lower;
          lower = 0;
        } else {
          upper = toFinite(upper);
        }
      }
      if (lower > upper) {
        var temp = lower;
        lower = upper;
        upper = temp;
      }
      if (floating || lower % 1 || upper % 1) {
        var rand = nativeRandom();
        return nativeMin(lower + (rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1)))), upper);
      }
      return baseRandom(lower, upper);
    }

    /*------------------------------------------------------------------------*/

    /**
     * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase).
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category String
     * @param {string} [string=''] The string to convert.
     * @returns {string} Returns the camel cased string.
     * @example
     *
     * _.camelCase('Foo Bar');
     * // => 'fooBar'
     *
     * _.camelCase('--foo-bar--');
     * // => 'fooBar'
     *
     * _.camelCase('__FOO_BAR__');
     * // => 'fooBar'
     */
    var camelCase = createCompounder(function(result, word, index) {
      word = word.toLowerCase();
      return result + (index ? capitalize(word) : word);
    });

    /**
     * Converts the first character of `string` to upper case and the remaining
     * to lower case.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category String
     * @param {string} [string=''] The string to capitalize.
     * @returns {string} Returns the capitalized string.
     * @example
     *
     * _.capitalize('FRED');
     * // => 'Fred'
     */
    function capitalize(string) {
      return upperFirst(toString(string).toLowerCase());
    }

    /**
     * Deburrs `string` by converting
     * [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)
     * and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A)
     * letters to basic Latin letters and removing
     * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category String
     * @param {string} [string=''] The string to deburr.
     * @returns {string} Returns the deburred string.
     * @example
     *
     * _.deburr('déjà vu');
     * // => 'deja vu'
     */
    function deburr(string) {
      string = toString(string);
      return string && string.replace(reLatin, deburrLetter).replace(reComboMark, '');
    }

    /**
     * Checks if `string` ends with the given target string.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category String
     * @param {string} [string=''] The string to inspect.
     * @param {string} [target] The string to search for.
     * @param {number} [position=string.length] The position to search up to.
     * @returns {boolean} Returns `true` if `string` ends with `target`,
     *  else `false`.
     * @example
     *
     * _.endsWith('abc', 'c');
     * // => true
     *
     * _.endsWith('abc', 'b');
     * // => false
     *
     * _.endsWith('abc', 'b', 2);
     * // => true
     */
    function endsWith(string, target, position) {
      string = toString(string);
      target = baseToString(target);

      var length = string.length;
      position = position === undefined
        ? length
        : baseClamp(toInteger(position), 0, length);

      var end = position;
      position -= target.length;
      return position >= 0 && string.slice(position, end) == target;
    }

    /**
     * Converts the characters "&", "<", ">", '"', and "'" in `string` to their
     * corresponding HTML entities.
     *
     * **Note:** No other characters are escaped. To escape additional
     * characters use a third-party library like [_he_](https://mths.be/he).
     *
     * Though the ">" character is escaped for symmetry, characters like
     * ">" and "/" don't need escaping in HTML and have no special meaning
     * unless they're part of a tag or unquoted attribute value. See
     * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands)
     * (under "semi-related fun fact") for more details.
     *
     * When working with HTML you should always
     * [quote attribute values](http://wonko.com/post/html-escaping) to reduce
     * XSS vectors.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category String
     * @param {string} [string=''] The string to escape.
     * @returns {string} Returns the escaped string.
     * @example
     *
     * _.escape('fred, barney, & pebbles');
     * // => 'fred, barney, &amp; pebbles'
     */
    function escape(string) {
      string = toString(string);
      return (string && reHasUnescapedHtml.test(string))
        ? string.replace(reUnescapedHtml, escapeHtmlChar)
        : string;
    }

    /**
     * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+",
     * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category String
     * @param {string} [string=''] The string to escape.
     * @returns {string} Returns the escaped string.
     * @example
     *
     * _.escapeRegExp('[lodash](https://lodash.com/)');
     * // => '\[lodash\]\(https://lodash\.com/\)'
     */
    function escapeRegExp(string) {
      string = toString(string);
      return (string && reHasRegExpChar.test(string))
        ? string.replace(reRegExpChar, '\\$&')
        : string;
    }

    /**
     * Converts `string` to
     * [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles).
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category String
     * @param {string} [string=''] The string to convert.
     * @returns {string} Returns the kebab cased string.
     * @example
     *
     * _.kebabCase('Foo Bar');
     * // => 'foo-bar'
     *
     * _.kebabCase('fooBar');
     * // => 'foo-bar'
     *
     * _.kebabCase('__FOO_BAR__');
     * // => 'foo-bar'
     */
    var kebabCase = createCompounder(function(result, word, index) {
      return result + (index ? '-' : '') + word.toLowerCase();
    });

    /**
     * Converts `string`, as space separated words, to lower case.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category String
     * @param {string} [string=''] The string to convert.
     * @returns {string} Returns the lower cased string.
     * @example
     *
     * _.lowerCase('--Foo-Bar--');
     * // => 'foo bar'
     *
     * _.lowerCase('fooBar');
     * // => 'foo bar'
     *
     * _.lowerCase('__FOO_BAR__');
     * // => 'foo bar'
     */
    var lowerCase = createCompounder(function(result, word, index) {
      return result + (index ? ' ' : '') + word.toLowerCase();
    });

    /**
     * Converts the first character of `string` to lower case.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category String
     * @param {string} [string=''] The string to convert.
     * @returns {string} Returns the converted string.
     * @example
     *
     * _.lowerFirst('Fred');
     * // => 'fred'
     *
     * _.lowerFirst('FRED');
     * // => 'fRED'
     */
    var lowerFirst = createCaseFirst('toLowerCase');

    /**
     * Pads `string` on the left and right sides if it's shorter than `length`.
     * Padding characters are truncated if they can't be evenly divided by `length`.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category String
     * @param {string} [string=''] The string to pad.
     * @param {number} [length=0] The padding length.
     * @param {string} [chars=' '] The string used as padding.
     * @returns {string} Returns the padded string.
     * @example
     *
     * _.pad('abc', 8);
     * // => '  abc   '
     *
     * _.pad('abc', 8, '_-');
     * // => '_-abc_-_'
     *
     * _.pad('abc', 3);
     * // => 'abc'
     */
    function pad(string, length, chars) {
      string = toString(string);
      length = toInteger(length);

      var strLength = length ? stringSize(string) : 0;
      if (!length || strLength >= length) {
        return string;
      }
      var mid = (length - strLength) / 2;
      return (
        createPadding(nativeFloor(mid), chars) +
        string +
        createPadding(nativeCeil(mid), chars)
      );
    }

    /**
     * Pads `string` on the right side if it's shorter than `length`. Padding
     * characters are truncated if they exceed `length`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category String
     * @param {string} [string=''] The string to pad.
     * @param {number} [length=0] The padding length.
     * @param {string} [chars=' '] The string used as padding.
     * @returns {string} Returns the padded string.
     * @example
     *
     * _.padEnd('abc', 6);
     * // => 'abc   '
     *
     * _.padEnd('abc', 6, '_-');
     * // => 'abc_-_'
     *
     * _.padEnd('abc', 3);
     * // => 'abc'
     */
    function padEnd(string, length, chars) {
      string = toString(string);
      length = toInteger(length);

      var strLength = length ? stringSize(string) : 0;
      return (length && strLength < length)
        ? (string + createPadding(length - strLength, chars))
        : string;
    }

    /**
     * Pads `string` on the left side if it's shorter than `length`. Padding
     * characters are truncated if they exceed `length`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category String
     * @param {string} [string=''] The string to pad.
     * @param {number} [length=0] The padding length.
     * @param {string} [chars=' '] The string used as padding.
     * @returns {string} Returns the padded string.
     * @example
     *
     * _.padStart('abc', 6);
     * // => '   abc'
     *
     * _.padStart('abc', 6, '_-');
     * // => '_-_abc'
     *
     * _.padStart('abc', 3);
     * // => 'abc'
     */
    function padStart(string, length, chars) {
      string = toString(string);
      length = toInteger(length);

      var strLength = length ? stringSize(string) : 0;
      return (length && strLength < length)
        ? (createPadding(length - strLength, chars) + string)
        : string;
    }

    /**
     * Converts `string` to an integer of the specified radix. If `radix` is
     * `undefined` or `0`, a `radix` of `10` is used unless `value` is a
     * hexadecimal, in which case a `radix` of `16` is used.
     *
     * **Note:** This method aligns with the
     * [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`.
     *
     * @static
     * @memberOf _
     * @since 1.1.0
     * @category String
     * @param {string} string The string to convert.
     * @param {number} [radix=10] The radix to interpret `value` by.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {number} Returns the converted integer.
     * @example
     *
     * _.parseInt('08');
     * // => 8
     *
     * _.map(['6', '08', '10'], _.parseInt);
     * // => [6, 8, 10]
     */
    function parseInt(string, radix, guard) {
      if (guard || radix == null) {
        radix = 0;
      } else if (radix) {
        radix = +radix;
      }
      return nativeParseInt(toString(string).replace(reTrimStart, ''), radix || 0);
    }

    /**
     * Repeats the given string `n` times.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category String
     * @param {string} [string=''] The string to repeat.
     * @param {number} [n=1] The number of times to repeat the string.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {string} Returns the repeated string.
     * @example
     *
     * _.repeat('*', 3);
     * // => '***'
     *
     * _.repeat('abc', 2);
     * // => 'abcabc'
     *
     * _.repeat('abc', 0);
     * // => ''
     */
    function repeat(string, n, guard) {
      if ((guard ? isIterateeCall(string, n, guard) : n === undefined)) {
        n = 1;
      } else {
        n = toInteger(n);
      }
      return baseRepeat(toString(string), n);
    }

    /**
     * Replaces matches for `pattern` in `string` with `replacement`.
     *
     * **Note:** This method is based on
     * [`String#replace`](https://mdn.io/String/replace).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category String
     * @param {string} [string=''] The string to modify.
     * @param {RegExp|string} pattern The pattern to replace.
     * @param {Function|string} replacement The match replacement.
     * @returns {string} Returns the modified string.
     * @example
     *
     * _.replace('Hi Fred', 'Fred', 'Barney');
     * // => 'Hi Barney'
     */
    function replace() {
      var args = arguments,
          string = toString(args[0]);

      return args.length < 3 ? string : string.replace(args[1], args[2]);
    }

    /**
     * Converts `string` to
     * [snake case](https://en.wikipedia.org/wiki/Snake_case).
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category String
     * @param {string} [string=''] The string to convert.
     * @returns {string} Returns the snake cased string.
     * @example
     *
     * _.snakeCase('Foo Bar');
     * // => 'foo_bar'
     *
     * _.snakeCase('fooBar');
     * // => 'foo_bar'
     *
     * _.snakeCase('--FOO-BAR--');
     * // => 'foo_bar'
     */
    var snakeCase = createCompounder(function(result, word, index) {
      return result + (index ? '_' : '') + word.toLowerCase();
    });

    /**
     * Splits `string` by `separator`.
     *
     * **Note:** This method is based on
     * [`String#split`](https://mdn.io/String/split).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category String
     * @param {string} [string=''] The string to split.
     * @param {RegExp|string} separator The separator pattern to split by.
     * @param {number} [limit] The length to truncate results to.
     * @returns {Array} Returns the string segments.
     * @example
     *
     * _.split('a-b-c', '-', 2);
     * // => ['a', 'b']
     */
    function split(string, separator, limit) {
      if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) {
        separator = limit = undefined;
      }
      limit = limit === undefined ? MAX_ARRAY_LENGTH : limit >>> 0;
      if (!limit) {
        return [];
      }
      string = toString(string);
      if (string && (
            typeof separator == 'string' ||
            (separator != null && !isRegExp(separator))
          )) {
        separator = baseToString(separator);
        if (!separator && hasUnicode(string)) {
          return castSlice(stringToArray(string), 0, limit);
        }
      }
      return string.split(separator, limit);
    }

    /**
     * Converts `string` to
     * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage).
     *
     * @static
     * @memberOf _
     * @since 3.1.0
     * @category String
     * @param {string} [string=''] The string to convert.
     * @returns {string} Returns the start cased string.
     * @example
     *
     * _.startCase('--foo-bar--');
     * // => 'Foo Bar'
     *
     * _.startCase('fooBar');
     * // => 'Foo Bar'
     *
     * _.startCase('__FOO_BAR__');
     * // => 'FOO BAR'
     */
    var startCase = createCompounder(function(result, word, index) {
      return result + (index ? ' ' : '') + upperFirst(word);
    });

    /**
     * Checks if `string` starts with the given target string.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category String
     * @param {string} [string=''] The string to inspect.
     * @param {string} [target] The string to search for.
     * @param {number} [position=0] The position to search from.
     * @returns {boolean} Returns `true` if `string` starts with `target`,
     *  else `false`.
     * @example
     *
     * _.startsWith('abc', 'a');
     * // => true
     *
     * _.startsWith('abc', 'b');
     * // => false
     *
     * _.startsWith('abc', 'b', 1);
     * // => true
     */
    function startsWith(string, target, position) {
      string = toString(string);
      position = position == null
        ? 0
        : baseClamp(toInteger(position), 0, string.length);

      target = baseToString(target);
      return string.slice(position, position + target.length) == target;
    }

    /**
     * Creates a compiled template function that can interpolate data properties
     * in "interpolate" delimiters, HTML-escape interpolated data properties in
     * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data
     * properties may be accessed as free variables in the template. If a setting
     * object is given, it takes precedence over `_.templateSettings` values.
     *
     * **Note:** In the development build `_.template` utilizes
     * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
     * for easier debugging.
     *
     * For more information on precompiling templates see
     * [lodash's custom builds documentation](https://lodash.com/custom-builds).
     *
     * For more information on Chrome extension sandboxes see
     * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval).
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category String
     * @param {string} [string=''] The template string.
     * @param {Object} [options={}] The options object.
     * @param {RegExp} [options.escape=_.templateSettings.escape]
     *  The HTML "escape" delimiter.
     * @param {RegExp} [options.evaluate=_.templateSettings.evaluate]
     *  The "evaluate" delimiter.
     * @param {Object} [options.imports=_.templateSettings.imports]
     *  An object to import into the template as free variables.
     * @param {RegExp} [options.interpolate=_.templateSettings.interpolate]
     *  The "interpolate" delimiter.
     * @param {string} [options.sourceURL='lodash.templateSources[n]']
     *  The sourceURL of the compiled template.
     * @param {string} [options.variable='obj']
     *  The data object variable name.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {Function} Returns the compiled template function.
     * @example
     *
     * // Use the "interpolate" delimiter to create a compiled template.
     * var compiled = _.template('hello <%= user %>!');
     * compiled({ 'user': 'fred' });
     * // => 'hello fred!'
     *
     * // Use the HTML "escape" delimiter to escape data property values.
     * var compiled = _.template('<b><%- value %></b>');
     * compiled({ 'value': '<script>' });
     * // => '<b>&lt;script&gt;</b>'
     *
     * // Use the "evaluate" delimiter to execute JavaScript and generate HTML.
     * var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
     * compiled({ 'users': ['fred', 'barney'] });
     * // => '<li>fred</li><li>barney</li>'
     *
     * // Use the internal `print` function in "evaluate" delimiters.
     * var compiled = _.template('<% print("hello " + user); %>!');
     * compiled({ 'user': 'barney' });
     * // => 'hello barney!'
     *
     * // Use the ES template literal delimiter as an "interpolate" delimiter.
     * // Disable support by replacing the "interpolate" delimiter.
     * var compiled = _.template('hello ${ user }!');
     * compiled({ 'user': 'pebbles' });
     * // => 'hello pebbles!'
     *
     * // Use backslashes to treat delimiters as plain text.
     * var compiled = _.template('<%= "\\<%- value %\\>" %>');
     * compiled({ 'value': 'ignored' });
     * // => '<%- value %>'
     *
     * // Use the `imports` option to import `jQuery` as `jq`.
     * var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
     * var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
     * compiled({ 'users': ['fred', 'barney'] });
     * // => '<li>fred</li><li>barney</li>'
     *
     * // Use the `sourceURL` option to specify a custom sourceURL for the template.
     * var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' });
     * compiled(data);
     * // => Find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector.
     *
     * // Use the `variable` option to ensure a with-statement isn't used in the compiled template.
     * var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
     * compiled.source;
     * // => function(data) {
     * //   var __t, __p = '';
     * //   __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
     * //   return __p;
     * // }
     *
     * // Use custom template delimiters.
     * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
     * var compiled = _.template('hello {{ user }}!');
     * compiled({ 'user': 'mustache' });
     * // => 'hello mustache!'
     *
     * // Use the `source` property to inline compiled templates for meaningful
     * // line numbers in error messages and stack traces.
     * fs.writeFileSync(path.join(process.cwd(), 'jst.js'), '\
     *   var JST = {\
     *     "main": ' + _.template(mainText).source + '\
     *   };\
     * ');
     */
    function template(string, options, guard) {
      // Based on John Resig's `tmpl` implementation
      // (http://ejohn.org/blog/javascript-micro-templating/)
      // and Laura Doktorova's doT.js (https://github.com/olado/doT).
      var settings = lodash.templateSettings;

      if (guard && isIterateeCall(string, options, guard)) {
        options = undefined;
      }
      string = toString(string);
      options = assignInWith({}, options, settings, customDefaultsAssignIn);

      var imports = assignInWith({}, options.imports, settings.imports, customDefaultsAssignIn),
          importsKeys = keys(imports),
          importsValues = baseValues(imports, importsKeys);

      var isEscaping,
          isEvaluating,
          index = 0,
          interpolate = options.interpolate || reNoMatch,
          source = "__p += '";

      // Compile the regexp to match each delimiter.
      var reDelimiters = RegExp(
        (options.escape || reNoMatch).source + '|' +
        interpolate.source + '|' +
        (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
        (options.evaluate || reNoMatch).source + '|$'
      , 'g');

      // Use a sourceURL for easier debugging.
      // The sourceURL gets injected into the source that's eval-ed, so be careful
      // with lookup (in case of e.g. prototype pollution), and strip newlines if any.
      // A newline wouldn't be a valid sourceURL anyway, and it'd enable code injection.
      var sourceURL = '//# sourceURL=' +
        (hasOwnProperty.call(options, 'sourceURL')
          ? (options.sourceURL + '').replace(/[\r\n]/g, ' ')
          : ('lodash.templateSources[' + (++templateCounter) + ']')
        ) + '\n';

      string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
        interpolateValue || (interpolateValue = esTemplateValue);

        // Escape characters that can't be included in string literals.
        source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar);

        // Replace delimiters with snippets.
        if (escapeValue) {
          isEscaping = true;
          source += "' +\n__e(" + escapeValue + ") +\n'";
        }
        if (evaluateValue) {
          isEvaluating = true;
          source += "';\n" + evaluateValue + ";\n__p += '";
        }
        if (interpolateValue) {
          source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
        }
        index = offset + match.length;

        // The JS engine embedded in Adobe products needs `match` returned in
        // order to produce the correct `offset` value.
        return match;
      });

      source += "';\n";

      // If `variable` is not specified wrap a with-statement around the generated
      // code to add the data object to the top of the scope chain.
      // Like with sourceURL, we take care to not check the option's prototype,
      // as this configuration is a code injection vector.
      var variable = hasOwnProperty.call(options, 'variable') && options.variable;
      if (!variable) {
        source = 'with (obj) {\n' + source + '\n}\n';
      }
      // Cleanup code by stripping empty strings.
      source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
        .replace(reEmptyStringMiddle, '$1')
        .replace(reEmptyStringTrailing, '$1;');

      // Frame code as the function body.
      source = 'function(' + (variable || 'obj') + ') {\n' +
        (variable
          ? ''
          : 'obj || (obj = {});\n'
        ) +
        "var __t, __p = ''" +
        (isEscaping
           ? ', __e = _.escape'
           : ''
        ) +
        (isEvaluating
          ? ', __j = Array.prototype.join;\n' +
            "function print() { __p += __j.call(arguments, '') }\n"
          : ';\n'
        ) +
        source +
        'return __p\n}';

      var result = attempt(function() {
        return Function(importsKeys, sourceURL + 'return ' + source)
          .apply(undefined, importsValues);
      });

      // Provide the compiled function's source by its `toString` method or
      // the `source` property as a convenience for inlining compiled templates.
      result.source = source;
      if (isError(result)) {
        throw result;
      }
      return result;
    }

    /**
     * Converts `string`, as a whole, to lower case just like
     * [String#toLowerCase](https://mdn.io/toLowerCase).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category String
     * @param {string} [string=''] The string to convert.
     * @returns {string} Returns the lower cased string.
     * @example
     *
     * _.toLower('--Foo-Bar--');
     * // => '--foo-bar--'
     *
     * _.toLower('fooBar');
     * // => 'foobar'
     *
     * _.toLower('__FOO_BAR__');
     * // => '__foo_bar__'
     */
    function toLower(value) {
      return toString(value).toLowerCase();
    }

    /**
     * Converts `string`, as a whole, to upper case just like
     * [String#toUpperCase](https://mdn.io/toUpperCase).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category String
     * @param {string} [string=''] The string to convert.
     * @returns {string} Returns the upper cased string.
     * @example
     *
     * _.toUpper('--foo-bar--');
     * // => '--FOO-BAR--'
     *
     * _.toUpper('fooBar');
     * // => 'FOOBAR'
     *
     * _.toUpper('__foo_bar__');
     * // => '__FOO_BAR__'
     */
    function toUpper(value) {
      return toString(value).toUpperCase();
    }

    /**
     * Removes leading and trailing whitespace or specified characters from `string`.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category String
     * @param {string} [string=''] The string to trim.
     * @param {string} [chars=whitespace] The characters to trim.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {string} Returns the trimmed string.
     * @example
     *
     * _.trim('  abc  ');
     * // => 'abc'
     *
     * _.trim('-_-abc-_-', '_-');
     * // => 'abc'
     *
     * _.map(['  foo  ', '  bar  '], _.trim);
     * // => ['foo', 'bar']
     */
    function trim(string, chars, guard) {
      string = toString(string);
      if (string && (guard || chars === undefined)) {
        return string.replace(reTrim, '');
      }
      if (!string || !(chars = baseToString(chars))) {
        return string;
      }
      var strSymbols = stringToArray(string),
          chrSymbols = stringToArray(chars),
          start = charsStartIndex(strSymbols, chrSymbols),
          end = charsEndIndex(strSymbols, chrSymbols) + 1;

      return castSlice(strSymbols, start, end).join('');
    }

    /**
     * Removes trailing whitespace or specified characters from `string`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category String
     * @param {string} [string=''] The string to trim.
     * @param {string} [chars=whitespace] The characters to trim.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {string} Returns the trimmed string.
     * @example
     *
     * _.trimEnd('  abc  ');
     * // => '  abc'
     *
     * _.trimEnd('-_-abc-_-', '_-');
     * // => '-_-abc'
     */
    function trimEnd(string, chars, guard) {
      string = toString(string);
      if (string && (guard || chars === undefined)) {
        return string.replace(reTrimEnd, '');
      }
      if (!string || !(chars = baseToString(chars))) {
        return string;
      }
      var strSymbols = stringToArray(string),
          end = charsEndIndex(strSymbols, stringToArray(chars)) + 1;

      return castSlice(strSymbols, 0, end).join('');
    }

    /**
     * Removes leading whitespace or specified characters from `string`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category String
     * @param {string} [string=''] The string to trim.
     * @param {string} [chars=whitespace] The characters to trim.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {string} Returns the trimmed string.
     * @example
     *
     * _.trimStart('  abc  ');
     * // => 'abc  '
     *
     * _.trimStart('-_-abc-_-', '_-');
     * // => 'abc-_-'
     */
    function trimStart(string, chars, guard) {
      string = toString(string);
      if (string && (guard || chars === undefined)) {
        return string.replace(reTrimStart, '');
      }
      if (!string || !(chars = baseToString(chars))) {
        return string;
      }
      var strSymbols = stringToArray(string),
          start = charsStartIndex(strSymbols, stringToArray(chars));

      return castSlice(strSymbols, start).join('');
    }

    /**
     * Truncates `string` if it's longer than the given maximum string length.
     * The last characters of the truncated string are replaced with the omission
     * string which defaults to "...".
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category String
     * @param {string} [string=''] The string to truncate.
     * @param {Object} [options={}] The options object.
     * @param {number} [options.length=30] The maximum string length.
     * @param {string} [options.omission='...'] The string to indicate text is omitted.
     * @param {RegExp|string} [options.separator] The separator pattern to truncate to.
     * @returns {string} Returns the truncated string.
     * @example
     *
     * _.truncate('hi-diddly-ho there, neighborino');
     * // => 'hi-diddly-ho there, neighbo...'
     *
     * _.truncate('hi-diddly-ho there, neighborino', {
     *   'length': 24,
     *   'separator': ' '
     * });
     * // => 'hi-diddly-ho there,...'
     *
     * _.truncate('hi-diddly-ho there, neighborino', {
     *   'length': 24,
     *   'separator': /,? +/
     * });
     * // => 'hi-diddly-ho there...'
     *
     * _.truncate('hi-diddly-ho there, neighborino', {
     *   'omission': ' [...]'
     * });
     * // => 'hi-diddly-ho there, neig [...]'
     */
    function truncate(string, options) {
      var length = DEFAULT_TRUNC_LENGTH,
          omission = DEFAULT_TRUNC_OMISSION;

      if (isObject(options)) {
        var separator = 'separator' in options ? options.separator : separator;
        length = 'length' in options ? toInteger(options.length) : length;
        omission = 'omission' in options ? baseToString(options.omission) : omission;
      }
      string = toString(string);

      var strLength = string.length;
      if (hasUnicode(string)) {
        var strSymbols = stringToArray(string);
        strLength = strSymbols.length;
      }
      if (length >= strLength) {
        return string;
      }
      var end = length - stringSize(omission);
      if (end < 1) {
        return omission;
      }
      var result = strSymbols
        ? castSlice(strSymbols, 0, end).join('')
        : string.slice(0, end);

      if (separator === undefined) {
        return result + omission;
      }
      if (strSymbols) {
        end += (result.length - end);
      }
      if (isRegExp(separator)) {
        if (string.slice(end).search(separator)) {
          var match,
              substring = result;

          if (!separator.global) {
            separator = RegExp(separator.source, toString(reFlags.exec(separator)) + 'g');
          }
          separator.lastIndex = 0;
          while ((match = separator.exec(substring))) {
            var newEnd = match.index;
          }
          result = result.slice(0, newEnd === undefined ? end : newEnd);
        }
      } else if (string.indexOf(baseToString(separator), end) != end) {
        var index = result.lastIndexOf(separator);
        if (index > -1) {
          result = result.slice(0, index);
        }
      }
      return result + omission;
    }

    /**
     * The inverse of `_.escape`; this method converts the HTML entities
     * `&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;` in `string` to
     * their corresponding characters.
     *
     * **Note:** No other HTML entities are unescaped. To unescape additional
     * HTML entities use a third-party library like [_he_](https://mths.be/he).
     *
     * @static
     * @memberOf _
     * @since 0.6.0
     * @category String
     * @param {string} [string=''] The string to unescape.
     * @returns {string} Returns the unescaped string.
     * @example
     *
     * _.unescape('fred, barney, &amp; pebbles');
     * // => 'fred, barney, & pebbles'
     */
    function unescape(string) {
      string = toString(string);
      return (string && reHasEscapedHtml.test(string))
        ? string.replace(reEscapedHtml, unescapeHtmlChar)
        : string;
    }

    /**
     * Converts `string`, as space separated words, to upper case.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category String
     * @param {string} [string=''] The string to convert.
     * @returns {string} Returns the upper cased string.
     * @example
     *
     * _.upperCase('--foo-bar');
     * // => 'FOO BAR'
     *
     * _.upperCase('fooBar');
     * // => 'FOO BAR'
     *
     * _.upperCase('__foo_bar__');
     * // => 'FOO BAR'
     */
    var upperCase = createCompounder(function(result, word, index) {
      return result + (index ? ' ' : '') + word.toUpperCase();
    });

    /**
     * Converts the first character of `string` to upper case.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category String
     * @param {string} [string=''] The string to convert.
     * @returns {string} Returns the converted string.
     * @example
     *
     * _.upperFirst('fred');
     * // => 'Fred'
     *
     * _.upperFirst('FRED');
     * // => 'FRED'
     */
    var upperFirst = createCaseFirst('toUpperCase');

    /**
     * Splits `string` into an array of its words.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category String
     * @param {string} [string=''] The string to inspect.
     * @param {RegExp|string} [pattern] The pattern to match words.
     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
     * @returns {Array} Returns the words of `string`.
     * @example
     *
     * _.words('fred, barney, & pebbles');
     * // => ['fred', 'barney', 'pebbles']
     *
     * _.words('fred, barney, & pebbles', /[^, ]+/g);
     * // => ['fred', 'barney', '&', 'pebbles']
     */
    function words(string, pattern, guard) {
      string = toString(string);
      pattern = guard ? undefined : pattern;

      if (pattern === undefined) {
        return hasUnicodeWord(string) ? unicodeWords(string) : asciiWords(string);
      }
      return string.match(pattern) || [];
    }

    /*------------------------------------------------------------------------*/

    /**
     * Attempts to invoke `func`, returning either the result or the caught error
     * object. Any additional arguments are provided to `func` when it's invoked.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Util
     * @param {Function} func The function to attempt.
     * @param {...*} [args] The arguments to invoke `func` with.
     * @returns {*} Returns the `func` result or error object.
     * @example
     *
     * // Avoid throwing errors for invalid selectors.
     * var elements = _.attempt(function(selector) {
     *   return document.querySelectorAll(selector);
     * }, '>_>');
     *
     * if (_.isError(elements)) {
     *   elements = [];
     * }
     */
    var attempt = baseRest(function(func, args) {
      try {
        return apply(func, undefined, args);
      } catch (e) {
        return isError(e) ? e : new Error(e);
      }
    });

    /**
     * Binds methods of an object to the object itself, overwriting the existing
     * method.
     *
     * **Note:** This method doesn't set the "length" property of bound functions.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Util
     * @param {Object} object The object to bind and assign the bound methods to.
     * @param {...(string|string[])} methodNames The object method names to bind.
     * @returns {Object} Returns `object`.
     * @example
     *
     * var view = {
     *   'label': 'docs',
     *   'click': function() {
     *     console.log('clicked ' + this.label);
     *   }
     * };
     *
     * _.bindAll(view, ['click']);
     * jQuery(element).on('click', view.click);
     * // => Logs 'clicked docs' when clicked.
     */
    var bindAll = flatRest(function(object, methodNames) {
      arrayEach(methodNames, function(key) {
        key = toKey(key);
        baseAssignValue(object, key, bind(object[key], object));
      });
      return object;
    });

    /**
     * Creates a function that iterates over `pairs` and invokes the corresponding
     * function of the first predicate to return truthy. The predicate-function
     * pairs are invoked with the `this` binding and arguments of the created
     * function.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Util
     * @param {Array} pairs The predicate-function pairs.
     * @returns {Function} Returns the new composite function.
     * @example
     *
     * var func = _.cond([
     *   [_.matches({ 'a': 1 }),           _.constant('matches A')],
     *   [_.conforms({ 'b': _.isNumber }), _.constant('matches B')],
     *   [_.stubTrue,                      _.constant('no match')]
     * ]);
     *
     * func({ 'a': 1, 'b': 2 });
     * // => 'matches A'
     *
     * func({ 'a': 0, 'b': 1 });
     * // => 'matches B'
     *
     * func({ 'a': '1', 'b': '2' });
     * // => 'no match'
     */
    function cond(pairs) {
      var length = pairs == null ? 0 : pairs.length,
          toIteratee = getIteratee();

      pairs = !length ? [] : arrayMap(pairs, function(pair) {
        if (typeof pair[1] != 'function') {
          throw new TypeError(FUNC_ERROR_TEXT);
        }
        return [toIteratee(pair[0]), pair[1]];
      });

      return baseRest(function(args) {
        var index = -1;
        while (++index < length) {
          var pair = pairs[index];
          if (apply(pair[0], this, args)) {
            return apply(pair[1], this, args);
          }
        }
      });
    }

    /**
     * Creates a function that invokes the predicate properties of `source` with
     * the corresponding property values of a given object, returning `true` if
     * all predicates return truthy, else `false`.
     *
     * **Note:** The created function is equivalent to `_.conformsTo` with
     * `source` partially applied.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Util
     * @param {Object} source The object of property predicates to conform to.
     * @returns {Function} Returns the new spec function.
     * @example
     *
     * var objects = [
     *   { 'a': 2, 'b': 1 },
     *   { 'a': 1, 'b': 2 }
     * ];
     *
     * _.filter(objects, _.conforms({ 'b': function(n) { return n > 1; } }));
     * // => [{ 'a': 1, 'b': 2 }]
     */
    function conforms(source) {
      return baseConforms(baseClone(source, CLONE_DEEP_FLAG));
    }

    /**
     * Creates a function that returns `value`.
     *
     * @static
     * @memberOf _
     * @since 2.4.0
     * @category Util
     * @param {*} value The value to return from the new function.
     * @returns {Function} Returns the new constant function.
     * @example
     *
     * var objects = _.times(2, _.constant({ 'a': 1 }));
     *
     * console.log(objects);
     * // => [{ 'a': 1 }, { 'a': 1 }]
     *
     * console.log(objects[0] === objects[1]);
     * // => true
     */
    function constant(value) {
      return function() {
        return value;
      };
    }

    /**
     * Checks `value` to determine whether a default value should be returned in
     * its place. The `defaultValue` is returned if `value` is `NaN`, `null`,
     * or `undefined`.
     *
     * @static
     * @memberOf _
     * @since 4.14.0
     * @category Util
     * @param {*} value The value to check.
     * @param {*} defaultValue The default value.
     * @returns {*} Returns the resolved value.
     * @example
     *
     * _.defaultTo(1, 10);
     * // => 1
     *
     * _.defaultTo(undefined, 10);
     * // => 10
     */
    function defaultTo(value, defaultValue) {
      return (value == null || value !== value) ? defaultValue : value;
    }

    /**
     * Creates a function that returns the result of invoking the given functions
     * with the `this` binding of the created function, where each successive
     * invocation is supplied the return value of the previous.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Util
     * @param {...(Function|Function[])} [funcs] The functions to invoke.
     * @returns {Function} Returns the new composite function.
     * @see _.flowRight
     * @example
     *
     * function square(n) {
     *   return n * n;
     * }
     *
     * var addSquare = _.flow([_.add, square]);
     * addSquare(1, 2);
     * // => 9
     */
    var flow = createFlow();

    /**
     * This method is like `_.flow` except that it creates a function that
     * invokes the given functions from right to left.
     *
     * @static
     * @since 3.0.0
     * @memberOf _
     * @category Util
     * @param {...(Function|Function[])} [funcs] The functions to invoke.
     * @returns {Function} Returns the new composite function.
     * @see _.flow
     * @example
     *
     * function square(n) {
     *   return n * n;
     * }
     *
     * var addSquare = _.flowRight([square, _.add]);
     * addSquare(1, 2);
     * // => 9
     */
    var flowRight = createFlow(true);

    /**
     * This method returns the first argument it receives.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Util
     * @param {*} value Any value.
     * @returns {*} Returns `value`.
     * @example
     *
     * var object = { 'a': 1 };
     *
     * console.log(_.identity(object) === object);
     * // => true
     */
    function identity(value) {
      return value;
    }

    /**
     * Creates a function that invokes `func` with the arguments of the created
     * function. If `func` is a property name, the created function returns the
     * property value for a given element. If `func` is an array or object, the
     * created function returns `true` for elements that contain the equivalent
     * source properties, otherwise it returns `false`.
     *
     * @static
     * @since 4.0.0
     * @memberOf _
     * @category Util
     * @param {*} [func=_.identity] The value to convert to a callback.
     * @returns {Function} Returns the callback.
     * @example
     *
     * var users = [
     *   { 'user': 'barney', 'age': 36, 'active': true },
     *   { 'user': 'fred',   'age': 40, 'active': false }
     * ];
     *
     * // The `_.matches` iteratee shorthand.
     * _.filter(users, _.iteratee({ 'user': 'barney', 'active': true }));
     * // => [{ 'user': 'barney', 'age': 36, 'active': true }]
     *
     * // The `_.matchesProperty` iteratee shorthand.
     * _.filter(users, _.iteratee(['user', 'fred']));
     * // => [{ 'user': 'fred', 'age': 40 }]
     *
     * // The `_.property` iteratee shorthand.
     * _.map(users, _.iteratee('user'));
     * // => ['barney', 'fred']
     *
     * // Create custom iteratee shorthands.
     * _.iteratee = _.wrap(_.iteratee, function(iteratee, func) {
     *   return !_.isRegExp(func) ? iteratee(func) : function(string) {
     *     return func.test(string);
     *   };
     * });
     *
     * _.filter(['abc', 'def'], /ef/);
     * // => ['def']
     */
    function iteratee(func) {
      return baseIteratee(typeof func == 'function' ? func : baseClone(func, CLONE_DEEP_FLAG));
    }

    /**
     * Creates a function that performs a partial deep comparison between a given
     * object and `source`, returning `true` if the given object has equivalent
     * property values, else `false`.
     *
     * **Note:** The created function is equivalent to `_.isMatch` with `source`
     * partially applied.
     *
     * Partial comparisons will match empty array and empty object `source`
     * values against any array or object value, respectively. See `_.isEqual`
     * for a list of supported value comparisons.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Util
     * @param {Object} source The object of property values to match.
     * @returns {Function} Returns the new spec function.
     * @example
     *
     * var objects = [
     *   { 'a': 1, 'b': 2, 'c': 3 },
     *   { 'a': 4, 'b': 5, 'c': 6 }
     * ];
     *
     * _.filter(objects, _.matches({ 'a': 4, 'c': 6 }));
     * // => [{ 'a': 4, 'b': 5, 'c': 6 }]
     */
    function matches(source) {
      return baseMatches(baseClone(source, CLONE_DEEP_FLAG));
    }

    /**
     * Creates a function that performs a partial deep comparison between the
     * value at `path` of a given object to `srcValue`, returning `true` if the
     * object value is equivalent, else `false`.
     *
     * **Note:** Partial comparisons will match empty array and empty object
     * `srcValue` values against any array or object value, respectively. See
     * `_.isEqual` for a list of supported value comparisons.
     *
     * @static
     * @memberOf _
     * @since 3.2.0
     * @category Util
     * @param {Array|string} path The path of the property to get.
     * @param {*} srcValue The value to match.
     * @returns {Function} Returns the new spec function.
     * @example
     *
     * var objects = [
     *   { 'a': 1, 'b': 2, 'c': 3 },
     *   { 'a': 4, 'b': 5, 'c': 6 }
     * ];
     *
     * _.find(objects, _.matchesProperty('a', 4));
     * // => { 'a': 4, 'b': 5, 'c': 6 }
     */
    function matchesProperty(path, srcValue) {
      return baseMatchesProperty(path, baseClone(srcValue, CLONE_DEEP_FLAG));
    }

    /**
     * Creates a function that invokes the method at `path` of a given object.
     * Any additional arguments are provided to the invoked method.
     *
     * @static
     * @memberOf _
     * @since 3.7.0
     * @category Util
     * @param {Array|string} path The path of the method to invoke.
     * @param {...*} [args] The arguments to invoke the method with.
     * @returns {Function} Returns the new invoker function.
     * @example
     *
     * var objects = [
     *   { 'a': { 'b': _.constant(2) } },
     *   { 'a': { 'b': _.constant(1) } }
     * ];
     *
     * _.map(objects, _.method('a.b'));
     * // => [2, 1]
     *
     * _.map(objects, _.method(['a', 'b']));
     * // => [2, 1]
     */
    var method = baseRest(function(path, args) {
      return function(object) {
        return baseInvoke(object, path, args);
      };
    });

    /**
     * The opposite of `_.method`; this method creates a function that invokes
     * the method at a given path of `object`. Any additional arguments are
     * provided to the invoked method.
     *
     * @static
     * @memberOf _
     * @since 3.7.0
     * @category Util
     * @param {Object} object The object to query.
     * @param {...*} [args] The arguments to invoke the method with.
     * @returns {Function} Returns the new invoker function.
     * @example
     *
     * var array = _.times(3, _.constant),
     *     object = { 'a': array, 'b': array, 'c': array };
     *
     * _.map(['a[2]', 'c[0]'], _.methodOf(object));
     * // => [2, 0]
     *
     * _.map([['a', '2'], ['c', '0']], _.methodOf(object));
     * // => [2, 0]
     */
    var methodOf = baseRest(function(object, args) {
      return function(path) {
        return baseInvoke(object, path, args);
      };
    });

    /**
     * Adds all own enumerable string keyed function properties of a source
     * object to the destination object. If `object` is a function, then methods
     * are added to its prototype as well.
     *
     * **Note:** Use `_.runInContext` to create a pristine `lodash` function to
     * avoid conflicts caused by modifying the original.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Util
     * @param {Function|Object} [object=lodash] The destination object.
     * @param {Object} source The object of functions to add.
     * @param {Object} [options={}] The options object.
     * @param {boolean} [options.chain=true] Specify whether mixins are chainable.
     * @returns {Function|Object} Returns `object`.
     * @example
     *
     * function vowels(string) {
     *   return _.filter(string, function(v) {
     *     return /[aeiou]/i.test(v);
     *   });
     * }
     *
     * _.mixin({ 'vowels': vowels });
     * _.vowels('fred');
     * // => ['e']
     *
     * _('fred').vowels().value();
     * // => ['e']
     *
     * _.mixin({ 'vowels': vowels }, { 'chain': false });
     * _('fred').vowels();
     * // => ['e']
     */
    function mixin(object, source, options) {
      var props = keys(source),
          methodNames = baseFunctions(source, props);

      if (options == null &&
          !(isObject(source) && (methodNames.length || !props.length))) {
        options = source;
        source = object;
        object = this;
        methodNames = baseFunctions(source, keys(source));
      }
      var chain = !(isObject(options) && 'chain' in options) || !!options.chain,
          isFunc = isFunction(object);

      arrayEach(methodNames, function(methodName) {
        var func = source[methodName];
        object[methodName] = func;
        if (isFunc) {
          object.prototype[methodName] = function() {
            var chainAll = this.__chain__;
            if (chain || chainAll) {
              var result = object(this.__wrapped__),
                  actions = result.__actions__ = copyArray(this.__actions__);

              actions.push({ 'func': func, 'args': arguments, 'thisArg': object });
              result.__chain__ = chainAll;
              return result;
            }
            return func.apply(object, arrayPush([this.value()], arguments));
          };
        }
      });

      return object;
    }

    /**
     * Reverts the `_` variable to its previous value and returns a reference to
     * the `lodash` function.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Util
     * @returns {Function} Returns the `lodash` function.
     * @example
     *
     * var lodash = _.noConflict();
     */
    function noConflict() {
      if (root._ === this) {
        root._ = oldDash;
      }
      return this;
    }

    /**
     * This method returns `undefined`.
     *
     * @static
     * @memberOf _
     * @since 2.3.0
     * @category Util
     * @example
     *
     * _.times(2, _.noop);
     * // => [undefined, undefined]
     */
    function noop() {
      // No operation performed.
    }

    /**
     * Creates a function that gets the argument at index `n`. If `n` is negative,
     * the nth argument from the end is returned.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Util
     * @param {number} [n=0] The index of the argument to return.
     * @returns {Function} Returns the new pass-thru function.
     * @example
     *
     * var func = _.nthArg(1);
     * func('a', 'b', 'c', 'd');
     * // => 'b'
     *
     * var func = _.nthArg(-2);
     * func('a', 'b', 'c', 'd');
     * // => 'c'
     */
    function nthArg(n) {
      n = toInteger(n);
      return baseRest(function(args) {
        return baseNth(args, n);
      });
    }

    /**
     * Creates a function that invokes `iteratees` with the arguments it receives
     * and returns their results.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Util
     * @param {...(Function|Function[])} [iteratees=[_.identity]]
     *  The iteratees to invoke.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var func = _.over([Math.max, Math.min]);
     *
     * func(1, 2, 3, 4);
     * // => [4, 1]
     */
    var over = createOver(arrayMap);

    /**
     * Creates a function that checks if **all** of the `predicates` return
     * truthy when invoked with the arguments it receives.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Util
     * @param {...(Function|Function[])} [predicates=[_.identity]]
     *  The predicates to check.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var func = _.overEvery([Boolean, isFinite]);
     *
     * func('1');
     * // => true
     *
     * func(null);
     * // => false
     *
     * func(NaN);
     * // => false
     */
    var overEvery = createOver(arrayEvery);

    /**
     * Creates a function that checks if **any** of the `predicates` return
     * truthy when invoked with the arguments it receives.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Util
     * @param {...(Function|Function[])} [predicates=[_.identity]]
     *  The predicates to check.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var func = _.overSome([Boolean, isFinite]);
     *
     * func('1');
     * // => true
     *
     * func(null);
     * // => true
     *
     * func(NaN);
     * // => false
     */
    var overSome = createOver(arraySome);

    /**
     * Creates a function that returns the value at `path` of a given object.
     *
     * @static
     * @memberOf _
     * @since 2.4.0
     * @category Util
     * @param {Array|string} path The path of the property to get.
     * @returns {Function} Returns the new accessor function.
     * @example
     *
     * var objects = [
     *   { 'a': { 'b': 2 } },
     *   { 'a': { 'b': 1 } }
     * ];
     *
     * _.map(objects, _.property('a.b'));
     * // => [2, 1]
     *
     * _.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b');
     * // => [1, 2]
     */
    function property(path) {
      return isKey(path) ? baseProperty(toKey(path)) : basePropertyDeep(path);
    }

    /**
     * The opposite of `_.property`; this method creates a function that returns
     * the value at a given path of `object`.
     *
     * @static
     * @memberOf _
     * @since 3.0.0
     * @category Util
     * @param {Object} object The object to query.
     * @returns {Function} Returns the new accessor function.
     * @example
     *
     * var array = [0, 1, 2],
     *     object = { 'a': array, 'b': array, 'c': array };
     *
     * _.map(['a[2]', 'c[0]'], _.propertyOf(object));
     * // => [2, 0]
     *
     * _.map([['a', '2'], ['c', '0']], _.propertyOf(object));
     * // => [2, 0]
     */
    function propertyOf(object) {
      return function(path) {
        return object == null ? undefined : baseGet(object, path);
      };
    }

    /**
     * Creates an array of numbers (positive and/or negative) progressing from
     * `start` up to, but not including, `end`. A step of `-1` is used if a negative
     * `start` is specified without an `end` or `step`. If `end` is not specified,
     * it's set to `start` with `start` then set to `0`.
     *
     * **Note:** JavaScript follows the IEEE-754 standard for resolving
     * floating-point values which can produce unexpected results.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Util
     * @param {number} [start=0] The start of the range.
     * @param {number} end The end of the range.
     * @param {number} [step=1] The value to increment or decrement by.
     * @returns {Array} Returns the range of numbers.
     * @see _.inRange, _.rangeRight
     * @example
     *
     * _.range(4);
     * // => [0, 1, 2, 3]
     *
     * _.range(-4);
     * // => [0, -1, -2, -3]
     *
     * _.range(1, 5);
     * // => [1, 2, 3, 4]
     *
     * _.range(0, 20, 5);
     * // => [0, 5, 10, 15]
     *
     * _.range(0, -4, -1);
     * // => [0, -1, -2, -3]
     *
     * _.range(1, 4, 0);
     * // => [1, 1, 1]
     *
     * _.range(0);
     * // => []
     */
    var range = createRange();

    /**
     * This method is like `_.range` except that it populates values in
     * descending order.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Util
     * @param {number} [start=0] The start of the range.
     * @param {number} end The end of the range.
     * @param {number} [step=1] The value to increment or decrement by.
     * @returns {Array} Returns the range of numbers.
     * @see _.inRange, _.range
     * @example
     *
     * _.rangeRight(4);
     * // => [3, 2, 1, 0]
     *
     * _.rangeRight(-4);
     * // => [-3, -2, -1, 0]
     *
     * _.rangeRight(1, 5);
     * // => [4, 3, 2, 1]
     *
     * _.rangeRight(0, 20, 5);
     * // => [15, 10, 5, 0]
     *
     * _.rangeRight(0, -4, -1);
     * // => [-3, -2, -1, 0]
     *
     * _.rangeRight(1, 4, 0);
     * // => [1, 1, 1]
     *
     * _.rangeRight(0);
     * // => []
     */
    var rangeRight = createRange(true);

    /**
     * This method returns a new empty array.
     *
     * @static
     * @memberOf _
     * @since 4.13.0
     * @category Util
     * @returns {Array} Returns the new empty array.
     * @example
     *
     * var arrays = _.times(2, _.stubArray);
     *
     * console.log(arrays);
     * // => [[], []]
     *
     * console.log(arrays[0] === arrays[1]);
     * // => false
     */
    function stubArray() {
      return [];
    }

    /**
     * This method returns `false`.
     *
     * @static
     * @memberOf _
     * @since 4.13.0
     * @category Util
     * @returns {boolean} Returns `false`.
     * @example
     *
     * _.times(2, _.stubFalse);
     * // => [false, false]
     */
    function stubFalse() {
      return false;
    }

    /**
     * This method returns a new empty object.
     *
     * @static
     * @memberOf _
     * @since 4.13.0
     * @category Util
     * @returns {Object} Returns the new empty object.
     * @example
     *
     * var objects = _.times(2, _.stubObject);
     *
     * console.log(objects);
     * // => [{}, {}]
     *
     * console.log(objects[0] === objects[1]);
     * // => false
     */
    function stubObject() {
      return {};
    }

    /**
     * This method returns an empty string.
     *
     * @static
     * @memberOf _
     * @since 4.13.0
     * @category Util
     * @returns {string} Returns the empty string.
     * @example
     *
     * _.times(2, _.stubString);
     * // => ['', '']
     */
    function stubString() {
      return '';
    }

    /**
     * This method returns `true`.
     *
     * @static
     * @memberOf _
     * @since 4.13.0
     * @category Util
     * @returns {boolean} Returns `true`.
     * @example
     *
     * _.times(2, _.stubTrue);
     * // => [true, true]
     */
    function stubTrue() {
      return true;
    }

    /**
     * Invokes the iteratee `n` times, returning an array of the results of
     * each invocation. The iteratee is invoked with one argument; (index).
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Util
     * @param {number} n The number of times to invoke `iteratee`.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @returns {Array} Returns the array of results.
     * @example
     *
     * _.times(3, String);
     * // => ['0', '1', '2']
     *
     *  _.times(4, _.constant(0));
     * // => [0, 0, 0, 0]
     */
    function times(n, iteratee) {
      n = toInteger(n);
      if (n < 1 || n > MAX_SAFE_INTEGER) {
        return [];
      }
      var index = MAX_ARRAY_LENGTH,
          length = nativeMin(n, MAX_ARRAY_LENGTH);

      iteratee = getIteratee(iteratee);
      n -= MAX_ARRAY_LENGTH;

      var result = baseTimes(length, iteratee);
      while (++index < n) {
        iteratee(index);
      }
      return result;
    }

    /**
     * Converts `value` to a property path array.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Util
     * @param {*} value The value to convert.
     * @returns {Array} Returns the new property path array.
     * @example
     *
     * _.toPath('a.b.c');
     * // => ['a', 'b', 'c']
     *
     * _.toPath('a[0].b.c');
     * // => ['a', '0', 'b', 'c']
     */
    function toPath(value) {
      if (isArray(value)) {
        return arrayMap(value, toKey);
      }
      return isSymbol(value) ? [value] : copyArray(stringToPath(toString(value)));
    }

    /**
     * Generates a unique ID. If `prefix` is given, the ID is appended to it.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Util
     * @param {string} [prefix=''] The value to prefix the ID with.
     * @returns {string} Returns the unique ID.
     * @example
     *
     * _.uniqueId('contact_');
     * // => 'contact_104'
     *
     * _.uniqueId();
     * // => '105'
     */
    function uniqueId(prefix) {
      var id = ++idCounter;
      return toString(prefix) + id;
    }

    /*------------------------------------------------------------------------*/

    /**
     * Adds two numbers.
     *
     * @static
     * @memberOf _
     * @since 3.4.0
     * @category Math
     * @param {number} augend The first number in an addition.
     * @param {number} addend The second number in an addition.
     * @returns {number} Returns the total.
     * @example
     *
     * _.add(6, 4);
     * // => 10
     */
    var add = createMathOperation(function(augend, addend) {
      return augend + addend;
    }, 0);

    /**
     * Computes `number` rounded up to `precision`.
     *
     * @static
     * @memberOf _
     * @since 3.10.0
     * @category Math
     * @param {number} number The number to round up.
     * @param {number} [precision=0] The precision to round up to.
     * @returns {number} Returns the rounded up number.
     * @example
     *
     * _.ceil(4.006);
     * // => 5
     *
     * _.ceil(6.004, 2);
     * // => 6.01
     *
     * _.ceil(6040, -2);
     * // => 6100
     */
    var ceil = createRound('ceil');

    /**
     * Divide two numbers.
     *
     * @static
     * @memberOf _
     * @since 4.7.0
     * @category Math
     * @param {number} dividend The first number in a division.
     * @param {number} divisor The second number in a division.
     * @returns {number} Returns the quotient.
     * @example
     *
     * _.divide(6, 4);
     * // => 1.5
     */
    var divide = createMathOperation(function(dividend, divisor) {
      return dividend / divisor;
    }, 1);

    /**
     * Computes `number` rounded down to `precision`.
     *
     * @static
     * @memberOf _
     * @since 3.10.0
     * @category Math
     * @param {number} number The number to round down.
     * @param {number} [precision=0] The precision to round down to.
     * @returns {number} Returns the rounded down number.
     * @example
     *
     * _.floor(4.006);
     * // => 4
     *
     * _.floor(0.046, 2);
     * // => 0.04
     *
     * _.floor(4060, -2);
     * // => 4000
     */
    var floor = createRound('floor');

    /**
     * Computes the maximum value of `array`. If `array` is empty or falsey,
     * `undefined` is returned.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Math
     * @param {Array} array The array to iterate over.
     * @returns {*} Returns the maximum value.
     * @example
     *
     * _.max([4, 2, 8, 6]);
     * // => 8
     *
     * _.max([]);
     * // => undefined
     */
    function max(array) {
      return (array && array.length)
        ? baseExtremum(array, identity, baseGt)
        : undefined;
    }

    /**
     * This method is like `_.max` except that it accepts `iteratee` which is
     * invoked for each element in `array` to generate the criterion by which
     * the value is ranked. The iteratee is invoked with one argument: (value).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Math
     * @param {Array} array The array to iterate over.
     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
     * @returns {*} Returns the maximum value.
     * @example
     *
     * var objects = [{ 'n': 1 }, { 'n': 2 }];
     *
     * _.maxBy(objects, function(o) { return o.n; });
     * // => { 'n': 2 }
     *
     * // The `_.property` iteratee shorthand.
     * _.maxBy(objects, 'n');
     * // => { 'n': 2 }
     */
    function maxBy(array, iteratee) {
      return (array && array.length)
        ? baseExtremum(array, getIteratee(iteratee, 2), baseGt)
        : undefined;
    }

    /**
     * Computes the mean of the values in `array`.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Math
     * @param {Array} array The array to iterate over.
     * @returns {number} Returns the mean.
     * @example
     *
     * _.mean([4, 2, 8, 6]);
     * // => 5
     */
    function mean(array) {
      return baseMean(array, identity);
    }

    /**
     * This method is like `_.mean` except that it accepts `iteratee` which is
     * invoked for each element in `array` to generate the value to be averaged.
     * The iteratee is invoked with one argument: (value).
     *
     * @static
     * @memberOf _
     * @since 4.7.0
     * @category Math
     * @param {Array} array The array to iterate over.
     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
     * @returns {number} Returns the mean.
     * @example
     *
     * var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
     *
     * _.meanBy(objects, function(o) { return o.n; });
     * // => 5
     *
     * // The `_.property` iteratee shorthand.
     * _.meanBy(objects, 'n');
     * // => 5
     */
    function meanBy(array, iteratee) {
      return baseMean(array, getIteratee(iteratee, 2));
    }

    /**
     * Computes the minimum value of `array`. If `array` is empty or falsey,
     * `undefined` is returned.
     *
     * @static
     * @since 0.1.0
     * @memberOf _
     * @category Math
     * @param {Array} array The array to iterate over.
     * @returns {*} Returns the minimum value.
     * @example
     *
     * _.min([4, 2, 8, 6]);
     * // => 2
     *
     * _.min([]);
     * // => undefined
     */
    function min(array) {
      return (array && array.length)
        ? baseExtremum(array, identity, baseLt)
        : undefined;
    }

    /**
     * This method is like `_.min` except that it accepts `iteratee` which is
     * invoked for each element in `array` to generate the criterion by which
     * the value is ranked. The iteratee is invoked with one argument: (value).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Math
     * @param {Array} array The array to iterate over.
     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
     * @returns {*} Returns the minimum value.
     * @example
     *
     * var objects = [{ 'n': 1 }, { 'n': 2 }];
     *
     * _.minBy(objects, function(o) { return o.n; });
     * // => { 'n': 1 }
     *
     * // The `_.property` iteratee shorthand.
     * _.minBy(objects, 'n');
     * // => { 'n': 1 }
     */
    function minBy(array, iteratee) {
      return (array && array.length)
        ? baseExtremum(array, getIteratee(iteratee, 2), baseLt)
        : undefined;
    }

    /**
     * Multiply two numbers.
     *
     * @static
     * @memberOf _
     * @since 4.7.0
     * @category Math
     * @param {number} multiplier The first number in a multiplication.
     * @param {number} multiplicand The second number in a multiplication.
     * @returns {number} Returns the product.
     * @example
     *
     * _.multiply(6, 4);
     * // => 24
     */
    var multiply = createMathOperation(function(multiplier, multiplicand) {
      return multiplier * multiplicand;
    }, 1);

    /**
     * Computes `number` rounded to `precision`.
     *
     * @static
     * @memberOf _
     * @since 3.10.0
     * @category Math
     * @param {number} number The number to round.
     * @param {number} [precision=0] The precision to round to.
     * @returns {number} Returns the rounded number.
     * @example
     *
     * _.round(4.006);
     * // => 4
     *
     * _.round(4.006, 2);
     * // => 4.01
     *
     * _.round(4060, -2);
     * // => 4100
     */
    var round = createRound('round');

    /**
     * Subtract two numbers.
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Math
     * @param {number} minuend The first number in a subtraction.
     * @param {number} subtrahend The second number in a subtraction.
     * @returns {number} Returns the difference.
     * @example
     *
     * _.subtract(6, 4);
     * // => 2
     */
    var subtract = createMathOperation(function(minuend, subtrahend) {
      return minuend - subtrahend;
    }, 0);

    /**
     * Computes the sum of the values in `array`.
     *
     * @static
     * @memberOf _
     * @since 3.4.0
     * @category Math
     * @param {Array} array The array to iterate over.
     * @returns {number} Returns the sum.
     * @example
     *
     * _.sum([4, 2, 8, 6]);
     * // => 20
     */
    function sum(array) {
      return (array && array.length)
        ? baseSum(array, identity)
        : 0;
    }

    /**
     * This method is like `_.sum` except that it accepts `iteratee` which is
     * invoked for each element in `array` to generate the value to be summed.
     * The iteratee is invoked with one argument: (value).
     *
     * @static
     * @memberOf _
     * @since 4.0.0
     * @category Math
     * @param {Array} array The array to iterate over.
     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
     * @returns {number} Returns the sum.
     * @example
     *
     * var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
     *
     * _.sumBy(objects, function(o) { return o.n; });
     * // => 20
     *
     * // The `_.property` iteratee shorthand.
     * _.sumBy(objects, 'n');
     * // => 20
     */
    function sumBy(array, iteratee) {
      return (array && array.length)
        ? baseSum(array, getIteratee(iteratee, 2))
        : 0;
    }

    /*------------------------------------------------------------------------*/

    // Add methods that return wrapped values in chain sequences.
    lodash.after = after;
    lodash.ary = ary;
    lodash.assign = assign;
    lodash.assignIn = assignIn;
    lodash.assignInWith = assignInWith;
    lodash.assignWith = assignWith;
    lodash.at = at;
    lodash.before = before;
    lodash.bind = bind;
    lodash.bindAll = bindAll;
    lodash.bindKey = bindKey;
    lodash.castArray = castArray;
    lodash.chain = chain;
    lodash.chunk = chunk;
    lodash.compact = compact;
    lodash.concat = concat;
    lodash.cond = cond;
    lodash.conforms = conforms;
    lodash.constant = constant;
    lodash.countBy = countBy;
    lodash.create = create;
    lodash.curry = curry;
    lodash.curryRight = curryRight;
    lodash.debounce = debounce;
    lodash.defaults = defaults;
    lodash.defaultsDeep = defaultsDeep;
    lodash.defer = defer;
    lodash.delay = delay;
    lodash.difference = difference;
    lodash.differenceBy = differenceBy;
    lodash.differenceWith = differenceWith;
    lodash.drop = drop;
    lodash.dropRight = dropRight;
    lodash.dropRightWhile = dropRightWhile;
    lodash.dropWhile = dropWhile;
    lodash.fill = fill;
    lodash.filter = filter;
    lodash.flatMap = flatMap;
    lodash.flatMapDeep = flatMapDeep;
    lodash.flatMapDepth = flatMapDepth;
    lodash.flatten = flatten;
    lodash.flattenDeep = flattenDeep;
    lodash.flattenDepth = flattenDepth;
    lodash.flip = flip;
    lodash.flow = flow;
    lodash.flowRight = flowRight;
    lodash.fromPairs = fromPairs;
    lodash.functions = functions;
    lodash.functionsIn = functionsIn;
    lodash.groupBy = groupBy;
    lodash.initial = initial;
    lodash.intersection = intersection;
    lodash.intersectionBy = intersectionBy;
    lodash.intersectionWith = intersectionWith;
    lodash.invert = invert;
    lodash.invertBy = invertBy;
    lodash.invokeMap = invokeMap;
    lodash.iteratee = iteratee;
    lodash.keyBy = keyBy;
    lodash.keys = keys;
    lodash.keysIn = keysIn;
    lodash.map = map;
    lodash.mapKeys = mapKeys;
    lodash.mapValues = mapValues;
    lodash.matches = matches;
    lodash.matchesProperty = matchesProperty;
    lodash.memoize = memoize;
    lodash.merge = merge;
    lodash.mergeWith = mergeWith;
    lodash.method = method;
    lodash.methodOf = methodOf;
    lodash.mixin = mixin;
    lodash.negate = negate;
    lodash.nthArg = nthArg;
    lodash.omit = omit;
    lodash.omitBy = omitBy;
    lodash.once = once;
    lodash.orderBy = orderBy;
    lodash.over = over;
    lodash.overArgs = overArgs;
    lodash.overEvery = overEvery;
    lodash.overSome = overSome;
    lodash.partial = partial;
    lodash.partialRight = partialRight;
    lodash.partition = partition;
    lodash.pick = pick;
    lodash.pickBy = pickBy;
    lodash.property = property;
    lodash.propertyOf = propertyOf;
    lodash.pull = pull;
    lodash.pullAll = pullAll;
    lodash.pullAllBy = pullAllBy;
    lodash.pullAllWith = pullAllWith;
    lodash.pullAt = pullAt;
    lodash.range = range;
    lodash.rangeRight = rangeRight;
    lodash.rearg = rearg;
    lodash.reject = reject;
    lodash.remove = remove;
    lodash.rest = rest;
    lodash.reverse = reverse;
    lodash.sampleSize = sampleSize;
    lodash.set = set;
    lodash.setWith = setWith;
    lodash.shuffle = shuffle;
    lodash.slice = slice;
    lodash.sortBy = sortBy;
    lodash.sortedUniq = sortedUniq;
    lodash.sortedUniqBy = sortedUniqBy;
    lodash.split = split;
    lodash.spread = spread;
    lodash.tail = tail;
    lodash.take = take;
    lodash.takeRight = takeRight;
    lodash.takeRightWhile = takeRightWhile;
    lodash.takeWhile = takeWhile;
    lodash.tap = tap;
    lodash.throttle = throttle;
    lodash.thru = thru;
    lodash.toArray = toArray;
    lodash.toPairs = toPairs;
    lodash.toPairsIn = toPairsIn;
    lodash.toPath = toPath;
    lodash.toPlainObject = toPlainObject;
    lodash.transform = transform;
    lodash.unary = unary;
    lodash.union = union;
    lodash.unionBy = unionBy;
    lodash.unionWith = unionWith;
    lodash.uniq = uniq;
    lodash.uniqBy = uniqBy;
    lodash.uniqWith = uniqWith;
    lodash.unset = unset;
    lodash.unzip = unzip;
    lodash.unzipWith = unzipWith;
    lodash.update = update;
    lodash.updateWith = updateWith;
    lodash.values = values;
    lodash.valuesIn = valuesIn;
    lodash.without = without;
    lodash.words = words;
    lodash.wrap = wrap;
    lodash.xor = xor;
    lodash.xorBy = xorBy;
    lodash.xorWith = xorWith;
    lodash.zip = zip;
    lodash.zipObject = zipObject;
    lodash.zipObjectDeep = zipObjectDeep;
    lodash.zipWith = zipWith;

    // Add aliases.
    lodash.entries = toPairs;
    lodash.entriesIn = toPairsIn;
    lodash.extend = assignIn;
    lodash.extendWith = assignInWith;

    // Add methods to `lodash.prototype`.
    mixin(lodash, lodash);

    /*------------------------------------------------------------------------*/

    // Add methods that return unwrapped values in chain sequences.
    lodash.add = add;
    lodash.attempt = attempt;
    lodash.camelCase = camelCase;
    lodash.capitalize = capitalize;
    lodash.ceil = ceil;
    lodash.clamp = clamp;
    lodash.clone = clone;
    lodash.cloneDeep = cloneDeep;
    lodash.cloneDeepWith = cloneDeepWith;
    lodash.cloneWith = cloneWith;
    lodash.conformsTo = conformsTo;
    lodash.deburr = deburr;
    lodash.defaultTo = defaultTo;
    lodash.divide = divide;
    lodash.endsWith = endsWith;
    lodash.eq = eq;
    lodash.escape = escape;
    lodash.escapeRegExp = escapeRegExp;
    lodash.every = every;
    lodash.find = find;
    lodash.findIndex = findIndex;
    lodash.findKey = findKey;
    lodash.findLast = findLast;
    lodash.findLastIndex = findLastIndex;
    lodash.findLastKey = findLastKey;
    lodash.floor = floor;
    lodash.forEach = forEach;
    lodash.forEachRight = forEachRight;
    lodash.forIn = forIn;
    lodash.forInRight = forInRight;
    lodash.forOwn = forOwn;
    lodash.forOwnRight = forOwnRight;
    lodash.get = get;
    lodash.gt = gt;
    lodash.gte = gte;
    lodash.has = has;
    lodash.hasIn = hasIn;
    lodash.head = head;
    lodash.identity = identity;
    lodash.includes = includes;
    lodash.indexOf = indexOf;
    lodash.inRange = inRange;
    lodash.invoke = invoke;
    lodash.isArguments = isArguments;
    lodash.isArray = isArray;
    lodash.isArrayBuffer = isArrayBuffer;
    lodash.isArrayLike = isArrayLike;
    lodash.isArrayLikeObject = isArrayLikeObject;
    lodash.isBoolean = isBoolean;
    lodash.isBuffer = isBuffer;
    lodash.isDate = isDate;
    lodash.isElement = isElement;
    lodash.isEmpty = isEmpty;
    lodash.isEqual = isEqual;
    lodash.isEqualWith = isEqualWith;
    lodash.isError = isError;
    lodash.isFinite = isFinite;
    lodash.isFunction = isFunction;
    lodash.isInteger = isInteger;
    lodash.isLength = isLength;
    lodash.isMap = isMap;
    lodash.isMatch = isMatch;
    lodash.isMatchWith = isMatchWith;
    lodash.isNaN = isNaN;
    lodash.isNative = isNative;
    lodash.isNil = isNil;
    lodash.isNull = isNull;
    lodash.isNumber = isNumber;
    lodash.isObject = isObject;
    lodash.isObjectLike = isObjectLike;
    lodash.isPlainObject = isPlainObject;
    lodash.isRegExp = isRegExp;
    lodash.isSafeInteger = isSafeInteger;
    lodash.isSet = isSet;
    lodash.isString = isString;
    lodash.isSymbol = isSymbol;
    lodash.isTypedArray = isTypedArray;
    lodash.isUndefined = isUndefined;
    lodash.isWeakMap = isWeakMap;
    lodash.isWeakSet = isWeakSet;
    lodash.join = join;
    lodash.kebabCase = kebabCase;
    lodash.last = last;
    lodash.lastIndexOf = lastIndexOf;
    lodash.lowerCase = lowerCase;
    lodash.lowerFirst = lowerFirst;
    lodash.lt = lt;
    lodash.lte = lte;
    lodash.max = max;
    lodash.maxBy = maxBy;
    lodash.mean = mean;
    lodash.meanBy = meanBy;
    lodash.min = min;
    lodash.minBy = minBy;
    lodash.stubArray = stubArray;
    lodash.stubFalse = stubFalse;
    lodash.stubObject = stubObject;
    lodash.stubString = stubString;
    lodash.stubTrue = stubTrue;
    lodash.multiply = multiply;
    lodash.nth = nth;
    lodash.noConflict = noConflict;
    lodash.noop = noop;
    lodash.now = now;
    lodash.pad = pad;
    lodash.padEnd = padEnd;
    lodash.padStart = padStart;
    lodash.parseInt = parseInt;
    lodash.random = random;
    lodash.reduce = reduce;
    lodash.reduceRight = reduceRight;
    lodash.repeat = repeat;
    lodash.replace = replace;
    lodash.result = result;
    lodash.round = round;
    lodash.runInContext = runInContext;
    lodash.sample = sample;
    lodash.size = size;
    lodash.snakeCase = snakeCase;
    lodash.some = some;
    lodash.sortedIndex = sortedIndex;
    lodash.sortedIndexBy = sortedIndexBy;
    lodash.sortedIndexOf = sortedIndexOf;
    lodash.sortedLastIndex = sortedLastIndex;
    lodash.sortedLastIndexBy = sortedLastIndexBy;
    lodash.sortedLastIndexOf = sortedLastIndexOf;
    lodash.startCase = startCase;
    lodash.startsWith = startsWith;
    lodash.subtract = subtract;
    lodash.sum = sum;
    lodash.sumBy = sumBy;
    lodash.template = template;
    lodash.times = times;
    lodash.toFinite = toFinite;
    lodash.toInteger = toInteger;
    lodash.toLength = toLength;
    lodash.toLower = toLower;
    lodash.toNumber = toNumber;
    lodash.toSafeInteger = toSafeInteger;
    lodash.toString = toString;
    lodash.toUpper = toUpper;
    lodash.trim = trim;
    lodash.trimEnd = trimEnd;
    lodash.trimStart = trimStart;
    lodash.truncate = truncate;
    lodash.unescape = unescape;
    lodash.uniqueId = uniqueId;
    lodash.upperCase = upperCase;
    lodash.upperFirst = upperFirst;

    // Add aliases.
    lodash.each = forEach;
    lodash.eachRight = forEachRight;
    lodash.first = head;

    mixin(lodash, (function() {
      var source = {};
      baseForOwn(lodash, function(func, methodName) {
        if (!hasOwnProperty.call(lodash.prototype, methodName)) {
          source[methodName] = func;
        }
      });
      return source;
    }()), { 'chain': false });

    /*------------------------------------------------------------------------*/

    /**
     * The semantic version number.
     *
     * @static
     * @memberOf _
     * @type {string}
     */
    lodash.VERSION = VERSION;

    // Assign default placeholders.
    arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function(methodName) {
      lodash[methodName].placeholder = lodash;
    });

    // Add `LazyWrapper` methods for `_.drop` and `_.take` variants.
    arrayEach(['drop', 'take'], function(methodName, index) {
      LazyWrapper.prototype[methodName] = function(n) {
        n = n === undefined ? 1 : nativeMax(toInteger(n), 0);

        var result = (this.__filtered__ && !index)
          ? new LazyWrapper(this)
          : this.clone();

        if (result.__filtered__) {
          result.__takeCount__ = nativeMin(n, result.__takeCount__);
        } else {
          result.__views__.push({
            'size': nativeMin(n, MAX_ARRAY_LENGTH),
            'type': methodName + (result.__dir__ < 0 ? 'Right' : '')
          });
        }
        return result;
      };

      LazyWrapper.prototype[methodName + 'Right'] = function(n) {
        return this.reverse()[methodName](n).reverse();
      };
    });

    // Add `LazyWrapper` methods that accept an `iteratee` value.
    arrayEach(['filter', 'map', 'takeWhile'], function(methodName, index) {
      var type = index + 1,
          isFilter = type == LAZY_FILTER_FLAG || type == LAZY_WHILE_FLAG;

      LazyWrapper.prototype[methodName] = function(iteratee) {
        var result = this.clone();
        result.__iteratees__.push({
          'iteratee': getIteratee(iteratee, 3),
          'type': type
        });
        result.__filtered__ = result.__filtered__ || isFilter;
        return result;
      };
    });

    // Add `LazyWrapper` methods for `_.head` and `_.last`.
    arrayEach(['head', 'last'], function(methodName, index) {
      var takeName = 'take' + (index ? 'Right' : '');

      LazyWrapper.prototype[methodName] = function() {
        return this[takeName](1).value()[0];
      };
    });

    // Add `LazyWrapper` methods for `_.initial` and `_.tail`.
    arrayEach(['initial', 'tail'], function(methodName, index) {
      var dropName = 'drop' + (index ? '' : 'Right');

      LazyWrapper.prototype[methodName] = function() {
        return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
      };
    });

    LazyWrapper.prototype.compact = function() {
      return this.filter(identity);
    };

    LazyWrapper.prototype.find = function(predicate) {
      return this.filter(predicate).head();
    };

    LazyWrapper.prototype.findLast = function(predicate) {
      return this.reverse().find(predicate);
    };

    LazyWrapper.prototype.invokeMap = baseRest(function(path, args) {
      if (typeof path == 'function') {
        return new LazyWrapper(this);
      }
      return this.map(function(value) {
        return baseInvoke(value, path, args);
      });
    });

    LazyWrapper.prototype.reject = function(predicate) {
      return this.filter(negate(getIteratee(predicate)));
    };

    LazyWrapper.prototype.slice = function(start, end) {
      start = toInteger(start);

      var result = this;
      if (result.__filtered__ && (start > 0 || end < 0)) {
        return new LazyWrapper(result);
      }
      if (start < 0) {
        result = result.takeRight(-start);
      } else if (start) {
        result = result.drop(start);
      }
      if (end !== undefined) {
        end = toInteger(end);
        result = end < 0 ? result.dropRight(-end) : result.take(end - start);
      }
      return result;
    };

    LazyWrapper.prototype.takeRightWhile = function(predicate) {
      return this.reverse().takeWhile(predicate).reverse();
    };

    LazyWrapper.prototype.toArray = function() {
      return this.take(MAX_ARRAY_LENGTH);
    };

    // Add `LazyWrapper` methods to `lodash.prototype`.
    baseForOwn(LazyWrapper.prototype, function(func, methodName) {
      var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName),
          isTaker = /^(?:head|last)$/.test(methodName),
          lodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName],
          retUnwrapped = isTaker || /^find/.test(methodName);

      if (!lodashFunc) {
        return;
      }
      lodash.prototype[methodName] = function() {
        var value = this.__wrapped__,
            args = isTaker ? [1] : arguments,
            isLazy = value instanceof LazyWrapper,
            iteratee = args[0],
            useLazy = isLazy || isArray(value);

        var interceptor = function(value) {
          var result = lodashFunc.apply(lodash, arrayPush([value], args));
          return (isTaker && chainAll) ? result[0] : result;
        };

        if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
          // Avoid lazy use if the iteratee has a "length" value other than `1`.
          isLazy = useLazy = false;
        }
        var chainAll = this.__chain__,
            isHybrid = !!this.__actions__.length,
            isUnwrapped = retUnwrapped && !chainAll,
            onlyLazy = isLazy && !isHybrid;

        if (!retUnwrapped && useLazy) {
          value = onlyLazy ? value : new LazyWrapper(this);
          var result = func.apply(value, args);
          result.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });
          return new LodashWrapper(result, chainAll);
        }
        if (isUnwrapped && onlyLazy) {
          return func.apply(this, args);
        }
        result = this.thru(interceptor);
        return isUnwrapped ? (isTaker ? result.value()[0] : result.value()) : result;
      };
    });

    // Add `Array` methods to `lodash.prototype`.
    arrayEach(['pop', 'push', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
      var func = arrayProto[methodName],
          chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
          retUnwrapped = /^(?:pop|shift)$/.test(methodName);

      lodash.prototype[methodName] = function() {
        var args = arguments;
        if (retUnwrapped && !this.__chain__) {
          var value = this.value();
          return func.apply(isArray(value) ? value : [], args);
        }
        return this[chainName](function(value) {
          return func.apply(isArray(value) ? value : [], args);
        });
      };
    });

    // Map minified method names to their real names.
    baseForOwn(LazyWrapper.prototype, function(func, methodName) {
      var lodashFunc = lodash[methodName];
      if (lodashFunc) {
        var key = lodashFunc.name + '';
        if (!hasOwnProperty.call(realNames, key)) {
          realNames[key] = [];
        }
        realNames[key].push({ 'name': methodName, 'func': lodashFunc });
      }
    });

    realNames[createHybrid(undefined, WRAP_BIND_KEY_FLAG).name] = [{
      'name': 'wrapper',
      'func': undefined
    }];

    // Add methods to `LazyWrapper`.
    LazyWrapper.prototype.clone = lazyClone;
    LazyWrapper.prototype.reverse = lazyReverse;
    LazyWrapper.prototype.value = lazyValue;

    // Add chain sequence methods to the `lodash` wrapper.
    lodash.prototype.at = wrapperAt;
    lodash.prototype.chain = wrapperChain;
    lodash.prototype.commit = wrapperCommit;
    lodash.prototype.next = wrapperNext;
    lodash.prototype.plant = wrapperPlant;
    lodash.prototype.reverse = wrapperReverse;
    lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;

    // Add lazy aliases.
    lodash.prototype.first = lodash.prototype.head;

    if (symIterator) {
      lodash.prototype[symIterator] = wrapperToIterator;
    }
    return lodash;
  });

  /*--------------------------------------------------------------------------*/

  // Export lodash.
  var _ = runInContext();

  // Some AMD build optimizers, like r.js, check for condition patterns like:
  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
    // Expose Lodash on the global object to prevent errors when Lodash is
    // loaded by a script tag in the presence of an AMD loader.
    // See http://requirejs.org/docs/errors.html#mismatch for more details.
    // Use `_.noConflict` to remove Lodash from the global object.
    root._ = _;

    // Define as an anonymous module so, through path mapping, it can be
    // referenced as the "underscore" module.
    define(function() {
      return _;
    });
  }
  // Check for `exports` after `define` in case a build optimizer adds it.
  else if (freeModule) {
    // Export for Node.js.
    (freeModule.exports = _)._ = _;
    // Export for CommonJS support.
    freeExports._ = _;
  }
  else {
    // Export to the global object.
    root._ = _;
  }
}.call(this));
</script>

<dom-module id="data-filter" assetpath="bower_components/table-filter/">
    <template>
        <style>
            :host {
                display: inline-block;
                width: 100%;
                --selection-visibility: hidden;
            }

            table.gridtable {
                height: 100%;
                width: 100%;
                /* margin-left: 10px; */
                /*width: 98%;*/
                /* margin: 25px auto; */
                grid-column: span 4;
                border-collapse: collapse;
                border-spacing: 0;
                border: 1px solid #eee;
                border-bottom: 2px solid #4285f4;
                /* box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.10), 
                0px 10px 20px rgba(0, 0, 0, 0.05),
                0px 20px 20px rgba(0, 0, 0, 0.05),
                0px 30px 20px rgba(0, 0, 0, 0.05);*/
            }

            th,
            td {
                /* color: #999; */
                /* border: 1px solid #eee; */
                padding: 7px 14px;
                border-collapse: collapse;
                /* text-align: center; */
                margin-left: 5px;
                white-space: nowrap;
            }

            tr,
            thead,
            th {
                border: 1px solid #eee;
            }

            tbody tr:hover {
                background: #4285f4;
                opacity: 0.7;
                color: #fff;
            }

            thead th {
                cursor: pointer;
                background: #4285f4;
                opacity: 0.7;
                color: #fff;
                text-transform: uppercase;
                font-size: 12px;
            }

            paper-tags-dropdown {
                width: 100%;
            }

            paper-collapse-item {
                --paper-collapse-simple-paper-item-styles: {
                    border-bottom: 1px solid var(--light-theme-divider-color);
                }
                ;
            }

            paper-checkbox {
                width: 100%;
                padding: 8px;
            }

            paper-range-slider {
                --paper-range-slider-width: 100%;
            }

            paper-input-container {
                padding: 0px;
            }

            input.searchInput {
                @apply --paper-input-container-shared-input-style;
            }

            .flexchild-vertical {
                @apply --layout-flex;
                /*border-bottom: 1px solid rgba(34, 36, 38, .15);*/
                padding: 10px;
            }

            .table-headers {
                font-weight: bold;
                white-space: nowrap;
            }

            .category-headers {
                color: var(--paper-input-container-input-color, var(--primary-text-color));
                @apply --paper-font-subhead;
                font-size: 12px;
            }

            .sItem {
                grid-column: span 1;
            }

            .small-icon {
                --iron-icon-height: 16px;
                --iron-icon-width: 16px;
            }

            .container {
                display: grid;
                grid-template-columns: minmax(200px, 1fr) 4fr;
                max-height: inherit;
                overflow-x: auto;
                overflow-y: hidden;
            }

            .autoItem {
                display: var(--selection-visibility);
            }

            .sButton {
                height: fit-content;
                margin: 8px 0;
            }

            .columnSelection {
                min-height: 0px;
            }

            .filterItems {
                display: inline;
            }

            #selectionContainer {
                padding: 0 10px;
                display: grid;
                grid-template-rows: auto 75px 100%;
                grid-template-columns: auto repeat(4, minmax(100px, 1fr));
                grid-column-gap: 15px;
                grid-column: 2;
                max-height: inherit;
                height: 100%;
            }

            #filterContainer {
                grid-column: 1;
                max-height: inherit;
                height: 100%;
            }

            #filterOptions {
                overflow: auto;
                max-height: inherit;
                height: calc(100% - 100px);
            }

            #selector {
                display: none;
            }

            #sperc {
                position: relative;
                left: -15px;
            }

            #percentSelection {
                margin: 8px 0;
            }

            #tableContainer {
                width: 100%;
                grid-column: span 5;
                overflow: auto;
                max-height: inherit;
                height: calc(100% - 125px);
            }

            #filtersList {
                grid-column: span 5;
                overflow: auto;
                height: 50px;
            }
        </style>
        <div class="container">
            <div id="filterContainer">
                <paper-button id="reset-button" on-click="resetFilters" raised="">Reset</paper-button>
                <paper-button id="apply-button" on-click="applyFilters" raised="">Apply</paper-button>
                <div id="searchbox">
                    <paper-input value="{{filterSearch}}" no-label-float="">
                        <paper-icon-button icon="search" role="button" slot="prefix"></paper-icon-button>
                        <paper-icon-button icon="clear" slot="suffix" on-click="_resetFilterSearch"></paper-icon-button>
                    </paper-input>
                </div>
                <div id="filterOptions">
                    <template is="dom-repeat" items="{{_attributes}}">
                        <template is="dom-if" if="[[_settingType(item.type, 'boolean')]]">
                            <paper-collapse-item header="{{item.name}}" hidden$="{{item.hidden}}">
                                <div class="flexchild-vertical collapse-content">
                                    <paper-toggle-button name="{{item.name}}" on-checked-changed="addFilter"></paper-toggle-button>
                                    <iron-label for="{{item.id}}">{{input.label}}</iron-label>
                                </div>
                            </paper-collapse-item>
                        </template>
                        <template is="dom-if" if="[[_settingType(item.type, 'category')]]">
                            <paper-collapse-item header="{{item.name}}" hidden$="{{item.hidden}}">
                                <div class="flexchild-vertical collapse-content">
                                    <paper-tags-dropdown name="{{item.name}}" label="{{item.name}}" on-value-array-changed="addFilter" type="{{item.type}}" items="{{item.values}}" vertical-offset="0" horizontal-offset="-150">
                                    </paper-tags-dropdown>
                                </div>
                            </paper-collapse-item>
                        </template>
                        <template is="dom-if" if="[[_settingType(item.type, 'number')]]">
                            <paper-collapse-item header="{{item.name}}" hidden$="{{item.hidden}}">
                                <div class="flexchild-vertical collapse-content">
                                    <iron-label class="category-headers">
                                        <div title="text">{{item.name}}</div>
                                        <paper-range-slider pin="" name="{{item.name}}" label="{{item.name}}" class="slider" value-min="[[_getItemMin(item)]]" value-max="[[_getItemMax(item)]]" min="[[_getItemMin(item)]]" max="[[_getItemMax(item)]]"></paper-range-slider>
                                    </iron-label>
                                </div>
                            </paper-collapse-item>
                        </template>
                    </template>
                </div>
            </div>

            <array-selector id="selector" items="{{tableData}}" selected="{{selectedItems}}" multi="" toggle=""></array-selector>

            <div id="selectionContainer">
                <div id="filtersList">
                    <template is="dom-repeat" items="{{_filterCategories(filterStringArray.*)}}" as="category" mutable-data="">
                        <iron-label>{{category}}:</iron-label>
                        <paper-tags class="filterItems" items="[[_filterValues(category, filterStringArray.*)]]" prevent-remove-tag=""></paper-tags>
                    </template>
                </div>
                <paper-menu-button class="sItem sButton" id="columnSelector" ignore-select="">
                    <paper-button slot="dropdown-trigger">
                        Columns
                        <iron-icon icon="icons:expand-more"></iron-icon>
                    </paper-button>
                    <paper-listbox slot="dropdown-content">
                        <template is="dom-repeat" items="[[defaultColumns]]" as="column">
                            <paper-item class="columnSelection" role="menuitemcheckbox">
                                <paper-checkbox name="{{column}}" checked="" on-click="toggleColumn">{{column}}</paper-checkbox>
                            </paper-item>
                        </template>
                    </paper-listbox>
                </paper-menu-button>
                <paper-dropdown-menu class="sItem" on-value-changed="_sContainerChanged" label="Selection Type">
                    <paper-listbox slot="dropdown-content" selected="1">
                        <paper-item>Auto</paper-item>
                        <paper-item>Manual</paper-item>
                    </paper-listbox>
                </paper-dropdown-menu>
                <paper-dropdown-menu id="smode" class="sItem autoItem" label="select ">
                    <paper-listbox slot="dropdown-content" selected="0">
                        <paper-item>Random</paper-item>
                        <paper-item>Top</paper-item>
                    </paper-listbox>
                </paper-dropdown-menu>
                <iron-label id="percentSelection" class="category-headers sItem autoItem">
                    Selection %
                    <paper-range-slider main-div-style="{ 'display': 'inline'}" id="sperc" class="sItem autoItem" single-slider="" always-show-pin="" name="selection %" label="selection %" min="0" max="100" value="0"></paper-range-slider>
                </iron-label>
                <paper-button class="sItem autoItem sButton" on-click="applySelection" raised="">Apply Selection</paper-button>
                <div id="tableContainer">
                    <table class="gridtable">
                        <thead>
                            <tr>
                                <th>
                                    <input type="checkbox" checked="[[_isAllSelected(selectedItems.*)]]" indeterminate="[[_isIndeterminate(selectedItems.*)]]" on-click="_toggleSelectAll">
                                </th>
                                <template is="dom-repeat" items="[[showColumns]]" as="column">
                                    <th class="table-headers" on-click="sortColumn">
                                        <span>[[column]]
                                            <iron-icon class="small-icon" icon="icons:sort"></iron-icon>
                                        </span>
                                    </th>
                                </template>
                            </tr>
                        </thead>
                        <tbody>
                            <template id="dataTable" is="dom-repeat" items="[[tableData]]" as="row" initial-count="30">
                                <tr on-click="toggleSelection">
                                    <td>
                                        <input class="row-check" type="checkbox">
                                    </td>
                                    <template is="dom-repeat" items="[[showColumns]]" as="column">
                                        <td>[[ _getItem(row, column) ]]</td>
                                    </template>
                                </tr>
                            </template>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </template>

    <script>
        /**
         * # Data Filter Element
         * A fast and customizable polymer component for filtering arrays. 
         *
         * ### Features
         * - Automatically detect Attribute types from the `data` array.
         * - Token based filtering of attributes
         * - Flattens `data` for easy viewing as table
         * - Customizable filter components
         * - Client side filtering of large arrays
         * - Supports Manual/Auto selection of rows
         * - If `Auto` selected, supports configurable selection Modes
         *   - Top - select top `x%` of rows
         *   - Random - selection random `x%` of rows
         *
         * ### Types of Filters
         *
         * Attribute Type | Component | Component url
         * ---------------|-----------|--------------
         * Categorical | `paper-tags` | [webcomponents.org](https://www.webcomponents.org/element/PolymerEl/paper-tags)
         * Numerical | `paper-rangle-slider` | [webcomponents.org](https://www.webcomponents.org/element/IftachSadeh/paper-range-slider)
         * Boolean | `paper-toggle-button` | [webcomponents.org](https://www.webcomponents.org/element/@polymer/paper-toggle-button)
         *
         * ### Adding a data filter component to an application
         * In typical use, just add `<data-filter>` to the HTML page:
         *
         *      <data-filter data="[]"></data-filter>
         *
         * @customElement
         * @polymer
         * @demo demo/index.html
         */
        class DataFilter extends Polymer.MutableData(Polymer.Element) {
            static get is() {
                return 'data-filter';
            }

            static get properties() {
                return {
                    /** 
                     * List of filters set on the data 
                     */
                    filters: {
                        type: Object,
                        notify: true,
                        value: {},
                    },

                    /** 
                     * List of filters as string 
                     * @protected
                     */
                    filterStringArray: {
                        type: Array,
                        notify: true
                    },

                    /** 
                     * Search for filters in sidebar 
                     * @protected
                     */
                    filterSearch: {
                        type: String,
                        notify: true,
                        value: '',
                        observer: "_searchChanged"
                    },

                    /** Data attribute to the element. Array of objects. */
                    data: {
                        type: Array,
                        observer: "_dataChanged"
                    },

                    /** 
                     * Internal data array used for rendering & filtering. 
                     * @protected
                     */
                    tableData: {
                        type: Array,
                        notify: true
                    },

                    /**
                     * Internal data array after flattening
                     * @protected
                     */
                    flattenedData: {
                        type: Array,
                        notify: true
                    },

                    /**
                     * Only show these columns from the `data` array. 
                     */
                    showColumns: {
                        type: Array,
                        notify: true,
                        value: []
                    },

                    /**
                     * Used for fixing the order from the `data` array. 
                     * @protected
                     */
                    defaultColumns: {
                        type: Array,
                        value: []
                    },

                    /**
                     * Default Available filter attributes on the `data` array. 
                     * @protected
                     */
                    _defaultAttributes: {
                        type: Array,
                        value: [],
                        notify: true,
                    },

                    /**
                     * Available filter attributes on the `data` array. 
                     * @protected
                     */
                    _attributes: {
                        type: Array,
                        value: [],
                        notify: true
                    },

                    /**
                     * Selected rows from the `data` array. 
                     */
                    selectedItems: {
                        type: Array,
                        notify: true
                    }
                }
            }

            /**
             * Handler to sort columns in the table.
             */
            sortColumn(e) {
                var self = this;
                // var category = e.target.id.split('-')[2];
                var category = e.model.column;

                this.set("tableData", _.sortBy(this.tableData, [category]));
                this.$.dataTable.render();
            }

            /**
             * Currently applied filters.
             * @protected
             */
            _filterCategories(filters) {
                var categories = [];
                Object.keys(this.filters).forEach((item) => {
                    if (this.filters[item].length > 0) {
                        categories.push(item);
                    }
                })
                return categories;
            }

            /**
             * @protected
             */
            _filterValues(category, filterStringArray) {
                //filter string array is not used, it's just to trigger rerender of dom-repeat
                var filter;
                if (typeof (this.filters[category][0]) === "number") {
                    filter = [this.filters[category].join('-')];
                } else {
                    filter = this.filters[category];
                }

                return filter;
            }

            /**
             * if setting type and expected are same. 
             * @return {boolean} true if type is expected
             * @protected
             */
            _settingType(type, expected) {
                if (type == expected) {
                    return true;
                }

                return false;
            }

            /**
             * Get minimum value of an attribute
             * @return {number} minimum.
             * @protected
             */
            _getItemMin(item) {
                return item.values[1];
            }

            /**
             * Get maximum value of an attribute
             * @return {number} maximum.
             * @protected
             */
            _getItemMax(item) {
                return item.values[0];
            }

            /**
             * DOM helper to render a row, column value in the table.
             * @return {String} a row[column].
             * @protected
             */
            _getItem(row, column) {
                return row[column];
            }

            /**
             * If only a few rows are selected in the table.
             * @return {boolean} return true if indeterminate.
             * @protected
             */
            _isIndeterminate() {
                if (this.selectedItems && this.selectedItems.length > 0 && this.selectedItems.length != this.tableData
                    .length) {
                    return true;
                }
                return false;
            }

            /**
             * If all rows are selected in the table.
             * @return {boolean} return true if all rows are selected.
             * @protected
             */
            _isAllSelected() {
                if (this.selectedItems && this.selectedItems.length > 0 && this.selectedItems.length === this.tableData
                    .length) {
                    return true;
                }
                return false;
            }

            /**
             * Update selection Container visibility.
             * @protected
             */
            _sContainerChanged(e) {
                if (e.target.value == "Auto") {
                    this.updateStyles({
                        '--selection-visibility': 'visible'
                    });
                } else {
                    this.updateStyles({
                        '--selection-visibility': 'none'
                    });
                }
            }

            /**
             * Handler to toggle row selection.
             */
            toggleSelection(e) {
                var item = this.$.dataTable.itemForElement(e.target);
                this.$.selector.select(item);
                e.currentTarget.querySelector("input").checked = this.$.selector.isSelected(item);
            }

            /**
             * Handler to toggle columns.
             */
            toggleColumn(e) {
                var self = this;
                if (!e.target.checked) {
                    this.splice('showColumns', this.showColumns.indexOf(e.target.name), 1);
                } else {
                    var showColumnIndex = 0;
                    this.defaultColumns.forEach((item, index) => {
                        if (item === e.target.name) {
                            this.splice('showColumns', showColumnIndex, 0, item);
                        } else if (this.showColumns[showColumnIndex] === this.defaultColumns[index]) {
                            showColumnIndex++;
                        }
                    });
                }
            }

            /**
             * Handler to add a new filter.
             */
            addFilter(e) {
                var category = e.target.label;

                if (e.target.type === "number") {
                    if (parseInt(e.detail)) {
                        this.filters[category].push(parseInt(e.detail));
                    }
                } else if (e.target.type === "category") {
                    if (e.target.valueArray.length > 0) {
                        this.filters[category] = e.target.valueArray;
                    }
                } else {
                    this.filters[category].push(e.detail);
                }
            }

            /**
             * Handler to add numerical range filter.
             * @param {string} attribute to add filter in the `data`.
             * @param {number} filter min
             * @param {number} filter max.
             */
            addSliderFilter(name, min, max) {
                var category = name;
                this.filters[category] = [min, max];
            }

            /**
             * Handler to apply all selected filters to the `data`
             */
            applyFilters() {
                var self = this;
                var filterString = [];

                for (var key in this.filters) {
                    var categoryFilters = []
                    this.filters[key].forEach(function (val, index) {
                        if (val !== "Any") {
                            if (typeof (val) === "string") {
                                categoryFilters.push("item['" + key + "'] == '" + val + "'");
                            } else if (typeof (val) === "number") {
                                if (index == 0) {
                                    categoryFilters.push("item['" + key + "'] >= " + val);
                                } else if (index == 1) {
                                    categoryFilters[categoryFilters.length - 1] += " && item['" + key +
                                        "'] <= " + val;
                                }
                            } else {
                                categoryFilters.push("item['" + key + "'] == " + val);
                            }
                        }
                    });
                    if (categoryFilters.length !== 0) {
                        filterString.push(categoryFilters);
                    }
                }

                if (filterString.length === 0) {
                    return;
                }

                filterString.forEach(function (val, index) {
                    filterString[index] = "(" + val.join(" || ") + ")";
                });

                this.set("filterStringArray", filterString);

                var fString = filterString.join(" && ");
                var newData = this.flattenedData.filter(item => eval(fString));
                this.set("tableData", newData);
            }

            /**
             * Handler to apply `Auto` or `Manual`, `Top` or `Random` selection of rows to the table.
             */
            applySelection(e) {
                var self = this;
                var selectionType = this.$.smode.value;
                var selectionperc = this.$.sperc.valueMax;

                self.$.selector.clearSelection();
                var data = _.range(self.tableData.length);
                var count = Math.round((data.length * selectionperc) / 100);

                var elems = self.shadowRoot.querySelectorAll(".row-check");
                elems.forEach(function (elem) {
                    elem.checked = false;
                });

                if (selectionperc === 100) {
                    self._selectAll();
                    return;
                }

                switch (selectionType) {
                    case 'Random':
                        var randomSamples = _.sampleSize(data, count);
                        _.each(randomSamples, function (rs) {
                            if (!self.$.selector.isSelected(self.tableData[rs])) {
                                self.$.selector.select(self.tableData[rs]);
                            }
                            elems[rs].checked = true;
                        });
                        break;
                    case 'Top':
                        _.each(_.slice(data, 0, count), function (rs) {
                            if (!self.$.selector.isSelected(self.tableData[rs])) {
                                self.$.selector.select(self.tableData[rs]);
                            }
                            elems[rs].checked = true;
                        });
                        break;
                }
            }

            /**
             * Handler to reset all filters to `data`.
             */
            resetFilters(e) {
                var self = this;

                for (var key in this.filters) {
                    this.filters[key] = [];
                }

                this.shadowRoot.querySelectorAll("paper-tags-dropdown").forEach(function (elem) {
                    var input = elem.shadowRoot.querySelector("paper-tags-input");
                    input.removeAll();
                });

                this.shadowRoot.querySelectorAll(".slider").forEach(function (elem) {
                    self._attributes.forEach(function (ta) {
                        if (ta.name == elem.name) {
                            elem.valueMax = self._getItemMax(ta);
                            elem.valueMin = self._getItemMin(ta);
                        }
                    });
                });

                this.set("tableData", this.flattenedData);
                this.set("filterStringArray", []);
            }

            /**
             * Handler to select all rows in the table.
             * @param {string} attribute to add filter in the `data`.
             * @param {number} filter min
             * @param {number} filter max.
             */
            _toggleSelectAll(e) {
                var self = this;
                if (e.target.checked) {
                    this._selectAll();
                } else {
                    this._deselectAll();
                }
            }

            /**
             * Select all rows in the table.
             */
            _selectAll() {
                var self = this;
                this.tableData.forEach(function (item) {
                    if (!self.$.selector.isSelected(item)) {
                        self.$.selector.select(item);
                    }
                });

                var elems = this.shadowRoot.querySelectorAll(".row-check");
                elems.forEach(function (elem) {
                    elem.checked = true;
                });
            }

            /**
             * Deselect all rows in the table.
             */
            _deselectAll() {
                this.$.selector.clearSelection();
                var elems = this.shadowRoot.querySelectorAll(".row-check");
                elems.forEach(function (elem) {
                    elem.checked = false;
                });
            }

            /**
             * Handler that resets the components if the data attribute changes.
             */
            _dataChanged(newData, oldData) {
                var self = this;

                var data = self.data;

                // sanitize data - flatten array

                data = data.map(self._flattenObject);

                this.set('flattenedData', data);
                self.tableData = self.flattenedData;

                var allColumns = []
                self.tableData.forEach(function(td) {
                    allColumns = allColumns.concat(Object.keys(td));
                });

                allColumns = _.uniq(allColumns)

                if (self.showColumns.length == 0) {
                    self.set("showColumns", allColumns.filter(function(e) { return e !== 'id' }));
                    self.set("defaultColumns", allColumns);
                }

                var attributes = self._buildAttributes(self.tableData, self.showColumns);
                self._attributes = attributes;

                // TODO: Replace with polymer's this.async
                setTimeout(function () {
                    var prs = self.shadowRoot.querySelectorAll(".slider");
                    for (var i = 0; i < prs.length; i++) {
                        var elem = prs[i];
                        elem.addEventListener('updateValues', function (customEvent) {
                            self.addSliderFilter(this.name, this.valueMin, this.valueMax);
                        });
                    }
                }, 1000);
            }

            /**
             * Handler that updates the search fo filter components in the sidebar.
             */
            _searchChanged(newVal, oldVal) {
                var self = this;
                this._attributes.forEach(function (item, index) {
                    if (newVal === '' || item.name.toLowerCase().indexOf(newVal.toLowerCase()) !== -1) {
                        self.set("_attributes." + index + ".hidden", false);
                    } else {
                        self.set("_attributes." + index + ".hidden", true);
                    }
                });
            }

            /**
             * Handler to reset search.
             */
            _resetFilterSearch(e) {
                this.set("filterSearch", '');
            }

            /**
             * Generate attrivbutes from the data array and figure out column types.
             * @protected
             */
            _buildAttributes(data, columns) {
                var self = this;
                var attributes = [];
                var parsedData = {};

                data.forEach(function (d) {
                    d.checked = false;
                });

                columns.forEach(function (key) {
                    parsedData[key] = data.map(function (x) {
                        return x[key];
                    });
                    var isNum = !parsedData[key].some(isNaN);
                    self.filters[key] = [];

                    if (isNum) {
                        attributes.push({
                            "name": key,
                            "type": "number",
                            "values": [parsedData[key].reduce(function (a, b) {
                                    return Math.max(a, b);
                                }),
                                parsedData[key].reduce(function (a, b) {
                                    return Math.min(a, b);
                                })
                            ],
                            "hidden": false,
                        });
                    } else {
                        var isBoolean = !parsedData[key].some(isNaN);

                        if (isBoolean) {
                            attributes.push({
                                "name": key,
                                "type": "boolean",
                                "values": [true, false],
                                "hidden": false,
                            });
                        } else {
                            attributes.push({
                                "name": key,
                                "type": "category",
                                "values": _.filter(_.uniq(parsedData[key]), (o) => {
                                    return !(o === undefined)
                                }),
                                "hidden": false,
                            });
                        }
                    }
                });

                return attributes;
            }

            /**
             * Flatten the `data` array.
             * @protected
             */
            _flattenObject(data) {
                var result = {};

                function _flatten(cur, prop) {
                    if (Object(cur) !== cur) {
                        result[prop] = cur;
                    } else if (Array.isArray(cur)) {
                        for (var i = 0, l = cur.length; i < l; i++)
                            _flatten(cur[i], prop + "[" + i + "]");
                        if (l == 0)
                            result[prop] = [];
                    } else {
                        var isEmpty = true;
                        for (var p in cur) {
                            isEmpty = false;
                            _flatten(cur[p], prop ? prop + "." + p : p);
                        }
                        if (isEmpty && prop)
                            result[prop] = {};
                    }
                }
                _flatten(data, "");
                return result;
            }

            /**
             * Helper Sort function.
             */
            _sortAlphaNum(a, b) {
                var reA = /[^a-zA-Z]/g;
                var reN = /[^0-9]/g;
                var aA = String(a).replace(reA, "");
                var bA = String(b).replace(reA, "");
                if (aA === bA) {
                    var aN = parseInt(String(a).replace(reN, ""), 10);
                    var bN = parseInt(String(b).replace(reN, ""), 10);
                    return aN === bN ? 0 : aN > bN ? 1 : -1;
                } else {
                    return aA > bA ? 1 : -1;
                }
            }

        }
        customElements.define(DataFilter.is, DataFilter);
    </script>
</dom-module><script>
  /**
   * `Polymer.IronMenubarBehavior` implements accessible menubar behavior.
   *
   * @polymerBehavior Polymer.IronMenubarBehavior
   */
  Polymer.IronMenubarBehaviorImpl = {

    hostAttributes: {'role': 'menubar'},

    /**
     * @type {!Object}
     */
    keyBindings: {'left': '_onLeftKey', 'right': '_onRightKey'},

    _onUpKey: function(event) {
      this.focusedItem.click();
      event.detail.keyboardEvent.preventDefault();
    },

    _onDownKey: function(event) {
      this.focusedItem.click();
      event.detail.keyboardEvent.preventDefault();
    },

    get _isRTL() {
      return window.getComputedStyle(this)['direction'] === 'rtl';
    },

    _onLeftKey: function(event) {
      if (this._isRTL) {
        this._focusNext();
      } else {
        this._focusPrevious();
      }
      event.detail.keyboardEvent.preventDefault();
    },

    _onRightKey: function(event) {
      if (this._isRTL) {
        this._focusPrevious();
      } else {
        this._focusNext();
      }
      event.detail.keyboardEvent.preventDefault();
    },

    _onKeydown: function(event) {
      if (this.keyboardEventMatchesKeys(event, 'up down left right esc')) {
        return;
      }

      // all other keys focus the menu item starting with that character
      this._focusWithKeyboardEvent(event);
    }

  };

  /** @polymerBehavior Polymer.IronMenubarBehavior */
  Polymer.IronMenubarBehavior =
      [Polymer.IronMenuBehavior, Polymer.IronMenubarBehaviorImpl];
</script>
<iron-iconset-svg name="paper-tabs" size="24">
<svg><defs>
<g id="chevron-left"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></path></g>
<g id="chevron-right"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"></path></g>
</defs></svg>
</iron-iconset-svg>
<dom-module id="paper-tab" assetpath="bower_components/paper-tabs/">
  <template>
    <style>
      :host {
        @apply --layout-inline;
        @apply --layout-center;
        @apply --layout-center-justified;
        @apply --layout-flex-auto;

        position: relative;
        padding: 0 12px;
        overflow: hidden;
        cursor: pointer;
        vertical-align: middle;

        @apply --paper-font-common-base;
        @apply --paper-tab;
      }

      :host(:focus) {
        outline: none;
      }

      :host([link]) {
        padding: 0;
      }

      .tab-content {
        height: 100%;
        transform: translateZ(0);
          -webkit-transform: translateZ(0);
        transition: opacity 0.1s cubic-bezier(0.4, 0.0, 1, 1);
        @apply --layout-horizontal;
        @apply --layout-center-center;
        @apply --layout-flex-auto;
        @apply --paper-tab-content;
      }

      :host(:not(.iron-selected)) > .tab-content {
        opacity: 0.8;

        @apply --paper-tab-content-unselected;
      }

      :host(:focus) .tab-content {
        opacity: 1;
        font-weight: 700;
      }

      paper-ripple {
        color: var(--paper-tab-ink, var(--paper-yellow-a100));
      }

      .tab-content > ::slotted(a) {
        @apply --layout-flex-auto;

        height: 100%;
      }
    </style>

    <div class="tab-content">
      <slot></slot>
    </div>
  </template>

  <script>
    Polymer({
      is: 'paper-tab',

      behaviors: [
        Polymer.IronControlState,
        Polymer.IronButtonState,
        Polymer.PaperRippleBehavior
      ],

      properties: {

        /**
         * If true, the tab will forward keyboard clicks (enter/space) to
         * the first anchor element found in its descendants
         */
        link: {type: Boolean, value: false, reflectToAttribute: true}

      },

      /** @private */
      hostAttributes: {role: 'tab'},

      listeners: {down: '_updateNoink', tap: '_onTap'},

      attached: function() {
        this._updateNoink();
      },

      get _parentNoink() {
        var parent = Polymer.dom(this).parentNode;
        return !!parent && !!parent.noink;
      },

      _updateNoink: function() {
        this.noink = !!this.noink || !!this._parentNoink;
      },

      _onTap: function(event) {
        if (this.link) {
          var anchor = this.queryEffectiveChildren('a');

          if (!anchor) {
            return;
          }

          // Don't get stuck in a loop delegating
          // the listener from the child anchor
          if (event.target === anchor) {
            return;
          }

          anchor.click();
        }
      }

    });
  </script>
</dom-module>
<dom-module id="paper-tabs" assetpath="bower_components/paper-tabs/">
  <template>
    <style>
      :host {
        @apply --layout;
        @apply --layout-center;

        height: 48px;
        font-size: 14px;
        font-weight: 500;
        overflow: hidden;
        -moz-user-select: none;
        -ms-user-select: none;
        -webkit-user-select: none;
        user-select: none;

        /* NOTE: Both values are needed, since some phones require the value to be `transparent`. */
        -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
        -webkit-tap-highlight-color: transparent;

        @apply --paper-tabs;
      }

      :host(:dir(rtl)) {
        @apply --layout-horizontal-reverse;
      }

      #tabsContainer {
        position: relative;
        height: 100%;
        white-space: nowrap;
        overflow: hidden;
        @apply --layout-flex-auto;
        @apply --paper-tabs-container;
      }

      #tabsContent {
        height: 100%;
        -moz-flex-basis: auto;
        -ms-flex-basis: auto;
        flex-basis: auto;
        @apply --paper-tabs-content;
      }

      #tabsContent.scrollable {
        position: absolute;
        white-space: nowrap;
      }

      #tabsContent:not(.scrollable),
      #tabsContent.scrollable.fit-container {
        @apply --layout-horizontal;
      }

      #tabsContent.scrollable.fit-container {
        min-width: 100%;
      }

      #tabsContent.scrollable.fit-container > ::slotted(*) {
        /* IE - prevent tabs from compressing when they should scroll. */
        -ms-flex: 1 0 auto;
        -webkit-flex: 1 0 auto;
        flex: 1 0 auto;
      }

      .hidden {
        display: none;
      }

      .not-visible {
        opacity: 0;
        cursor: default;
      }

      paper-icon-button {
        width: 48px;
        height: 48px;
        padding: 12px;
        margin: 0 4px;
      }

      #selectionBar {
        position: absolute;
        height: 0;
        bottom: 0;
        left: 0;
        right: 0;
        border-bottom: 2px solid var(--paper-tabs-selection-bar-color, var(--paper-yellow-a100));
          -webkit-transform: scale(0);
        transform: scale(0);
          -webkit-transform-origin: left center;
        transform-origin: left center;
          transition: -webkit-transform;
        transition: transform;

        @apply --paper-tabs-selection-bar;
      }

      #selectionBar.align-bottom {
        top: 0;
        bottom: auto;
      }

      #selectionBar.expand {
        transition-duration: 0.15s;
        transition-timing-function: cubic-bezier(0.4, 0.0, 1, 1);
      }

      #selectionBar.contract {
        transition-duration: 0.18s;
        transition-timing-function: cubic-bezier(0.0, 0.0, 0.2, 1);
      }

      #tabsContent > ::slotted(:not(#selectionBar)) {
        height: 100%;
      }
    </style>

    <paper-icon-button icon="paper-tabs:chevron-left" class$="[[_computeScrollButtonClass(_leftHidden, scrollable, hideScrollButtons)]]" on-up="_onScrollButtonUp" on-down="_onLeftScrollButtonDown" tabindex="-1"></paper-icon-button>

    <div id="tabsContainer" on-track="_scroll" on-down="_down">
      <div id="tabsContent" class$="[[_computeTabsContentClass(scrollable, fitContainer)]]">
        <div id="selectionBar" class$="[[_computeSelectionBarClass(noBar, alignBottom)]]" on-transitionend="_onBarTransitionEnd"></div>
        <slot></slot>
      </div>
    </div>

    <paper-icon-button icon="paper-tabs:chevron-right" class$="[[_computeScrollButtonClass(_rightHidden, scrollable, hideScrollButtons)]]" on-up="_onScrollButtonUp" on-down="_onRightScrollButtonDown" tabindex="-1"></paper-icon-button>

  </template>

  <script>
    Polymer({
      is: 'paper-tabs',

      behaviors: [Polymer.IronResizableBehavior, Polymer.IronMenubarBehavior],

      properties: {
        /**
         * If true, ink ripple effect is disabled. When this property is changed,
         * all descendant `<paper-tab>` elements have their `noink` property
         * changed to the new value as well.
         */
        noink: {type: Boolean, value: false, observer: '_noinkChanged'},

        /**
         * If true, the bottom bar to indicate the selected tab will not be shown.
         */
        noBar: {type: Boolean, value: false},

        /**
         * If true, the slide effect for the bottom bar is disabled.
         */
        noSlide: {type: Boolean, value: false},

        /**
         * If true, tabs are scrollable and the tab width is based on the label
         * width.
         */
        scrollable: {type: Boolean, value: false},

        /**
         * If true, tabs expand to fit their container. This currently only applies
         * when scrollable is true.
         */
        fitContainer: {type: Boolean, value: false},

        /**
         * If true, dragging on the tabs to scroll is disabled.
         */
        disableDrag: {type: Boolean, value: false},

        /**
         * If true, scroll buttons (left/right arrow) will be hidden for scrollable
         * tabs.
         */
        hideScrollButtons: {type: Boolean, value: false},

        /**
         * If true, the tabs are aligned to bottom (the selection bar appears at the
         * top).
         */
        alignBottom: {type: Boolean, value: false},

        selectable: {type: String, value: 'paper-tab'},

        /**
         * If true, tabs are automatically selected when focused using the
         * keyboard.
         */
        autoselect: {type: Boolean, value: false},

        /**
         * The delay (in milliseconds) between when the user stops interacting
         * with the tabs through the keyboard and when the focused item is
         * automatically selected (if `autoselect` is true).
         */
        autoselectDelay: {type: Number, value: 0},

        _step: {type: Number, value: 10},

        _holdDelay: {type: Number, value: 1},

        _leftHidden: {type: Boolean, value: false},

        _rightHidden: {type: Boolean, value: false},

        _previousTab: {type: Object}
      },

      /** @private */
      hostAttributes: {role: 'tablist'},

      listeners: {
        'iron-resize': '_onTabSizingChanged',
        'iron-items-changed': '_onTabSizingChanged',
        'iron-select': '_onIronSelect',
        'iron-deselect': '_onIronDeselect'
      },

      /**
       * @type {!Object}
       */
      keyBindings: {'left:keyup right:keyup': '_onArrowKeyup'},

      created: function() {
        this._holdJob = null;
        this._pendingActivationItem = undefined;
        this._pendingActivationTimeout = undefined;
        this._bindDelayedActivationHandler =
            this._delayedActivationHandler.bind(this);
        this.addEventListener('blur', this._onBlurCapture.bind(this), true);
      },

      ready: function() {
        this.setScrollDirection('y', this.$.tabsContainer);
      },

      detached: function() {
        this._cancelPendingActivation();
      },

      _noinkChanged: function(noink) {
        var childTabs = Polymer.dom(this).querySelectorAll('paper-tab');
        childTabs.forEach(
            noink ? this._setNoinkAttribute : this._removeNoinkAttribute);
      },

      _setNoinkAttribute: function(element) {
        element.setAttribute('noink', '');
      },

      _removeNoinkAttribute: function(element) {
        element.removeAttribute('noink');
      },

      _computeScrollButtonClass: function(
          hideThisButton, scrollable, hideScrollButtons) {
        if (!scrollable || hideScrollButtons) {
          return 'hidden';
        }

        if (hideThisButton) {
          return 'not-visible';
        }

        return '';
      },

      _computeTabsContentClass: function(scrollable, fitContainer) {
        return scrollable ? 'scrollable' + (fitContainer ? ' fit-container' : '') :
                            ' fit-container';
      },

      _computeSelectionBarClass: function(noBar, alignBottom) {
        if (noBar) {
          return 'hidden';
        } else if (alignBottom) {
          return 'align-bottom';
        }

        return '';
      },

      // TODO(cdata): Add `track` response back in when gesture lands.

      _onTabSizingChanged: function() {
        this.debounce('_onTabSizingChanged', function() {
          this._scroll();
          this._tabChanged(this.selectedItem);
        }, 10);
      },

      _onIronSelect: function(event) {
        this._tabChanged(event.detail.item, this._previousTab);
        this._previousTab = event.detail.item;
        this.cancelDebouncer('tab-changed');
      },

      _onIronDeselect: function(event) {
        this.debounce('tab-changed', function() {
          this._tabChanged(null, this._previousTab);
          this._previousTab = null;
          // See polymer/polymer#1305
        }, 1);
      },

      _activateHandler: function() {
        // Cancel item activations scheduled by keyboard events when any other
        // action causes an item to be activated (e.g. clicks).
        this._cancelPendingActivation();

        Polymer.IronMenuBehaviorImpl._activateHandler.apply(this, arguments);
      },

      /**
       * Activates an item after a delay (in milliseconds).
       */
      _scheduleActivation: function(item, delay) {
        this._pendingActivationItem = item;
        this._pendingActivationTimeout =
            this.async(this._bindDelayedActivationHandler, delay);
      },

      /**
       * Activates the last item given to `_scheduleActivation`.
       */
      _delayedActivationHandler: function() {
        var item = this._pendingActivationItem;
        this._pendingActivationItem = undefined;
        this._pendingActivationTimeout = undefined;
        item.fire(this.activateEvent, null, {bubbles: true, cancelable: true});
      },

      /**
       * Cancels a previously scheduled item activation made with
       * `_scheduleActivation`.
       */
      _cancelPendingActivation: function() {
        if (this._pendingActivationTimeout !== undefined) {
          this.cancelAsync(this._pendingActivationTimeout);
          this._pendingActivationItem = undefined;
          this._pendingActivationTimeout = undefined;
        }
      },

      _onArrowKeyup: function(event) {
        if (this.autoselect) {
          this._scheduleActivation(this.focusedItem, this.autoselectDelay);
        }
      },

      _onBlurCapture: function(event) {
        // Cancel a scheduled item activation (if any) when that item is
        // blurred.
        if (event.target === this._pendingActivationItem) {
          this._cancelPendingActivation();
        }
      },

      get _tabContainerScrollSize() {
        return Math.max(
            0, this.$.tabsContainer.scrollWidth - this.$.tabsContainer.offsetWidth);
      },

      _scroll: function(e, detail) {
        if (!this.scrollable) {
          return;
        }

        var ddx = (detail && -detail.ddx) || 0;
        this._affectScroll(ddx);
      },

      _down: function(e) {
        // go one beat async to defeat IronMenuBehavior
        // autorefocus-on-no-selection timeout
        this.async(function() {
          if (this._defaultFocusAsync) {
            this.cancelAsync(this._defaultFocusAsync);
            this._defaultFocusAsync = null;
          }
        }, 1);
      },

      _affectScroll: function(dx) {
        this.$.tabsContainer.scrollLeft += dx;

        var scrollLeft = this.$.tabsContainer.scrollLeft;

        this._leftHidden = scrollLeft === 0;
        this._rightHidden = scrollLeft === this._tabContainerScrollSize;
      },

      _onLeftScrollButtonDown: function() {
        this._scrollToLeft();
        this._holdJob = setInterval(this._scrollToLeft.bind(this), this._holdDelay);
      },

      _onRightScrollButtonDown: function() {
        this._scrollToRight();
        this._holdJob =
            setInterval(this._scrollToRight.bind(this), this._holdDelay);
      },

      _onScrollButtonUp: function() {
        clearInterval(this._holdJob);
        this._holdJob = null;
      },

      _scrollToLeft: function() {
        this._affectScroll(-this._step);
      },

      _scrollToRight: function() {
        this._affectScroll(this._step);
      },

      _tabChanged: function(tab, old) {
        if (!tab) {
          // Remove the bar without animation.
          this.$.selectionBar.classList.remove('expand');
          this.$.selectionBar.classList.remove('contract');
          this._positionBar(0, 0);
          return;
        }

        var r = this.$.tabsContent.getBoundingClientRect();
        var w = r.width;
        var tabRect = tab.getBoundingClientRect();
        var tabOffsetLeft = tabRect.left - r.left;

        this._pos = {
          width: this._calcPercent(tabRect.width, w),
          left: this._calcPercent(tabOffsetLeft, w)
        };

        if (this.noSlide || old == null) {
          // Position the bar without animation.
          this.$.selectionBar.classList.remove('expand');
          this.$.selectionBar.classList.remove('contract');
          this._positionBar(this._pos.width, this._pos.left);
          return;
        }

        var oldRect = old.getBoundingClientRect();
        var oldIndex = this.items.indexOf(old);
        var index = this.items.indexOf(tab);
        var m = 5;

        // bar animation: expand
        this.$.selectionBar.classList.add('expand');

        var moveRight = oldIndex < index;
        var isRTL = this._isRTL;
        if (isRTL) {
          moveRight = !moveRight;
        }

        if (moveRight) {
          this._positionBar(
              this._calcPercent(tabRect.left + tabRect.width - oldRect.left, w) - m,
              this._left);
        } else {
          this._positionBar(
              this._calcPercent(oldRect.left + oldRect.width - tabRect.left, w) - m,
              this._calcPercent(tabOffsetLeft, w) + m);
        }

        if (this.scrollable) {
          this._scrollToSelectedIfNeeded(tabRect.width, tabOffsetLeft);
        }
      },

      _scrollToSelectedIfNeeded: function(tabWidth, tabOffsetLeft) {
        var l = tabOffsetLeft - this.$.tabsContainer.scrollLeft;
        if (l < 0) {
          this.$.tabsContainer.scrollLeft += l;
        } else {
          l += (tabWidth - this.$.tabsContainer.offsetWidth);
          if (l > 0) {
            this.$.tabsContainer.scrollLeft += l;
          }
        }
      },

      _calcPercent: function(w, w0) {
        return 100 * w / w0;
      },

      _positionBar: function(width, left) {
        width = width || 0;
        left = left || 0;

        this._width = width;
        this._left = left;
        this.transform(
            'translateX(' + left + '%) scaleX(' + (width / 100) + ')',
            this.$.selectionBar);
      },

      _onBarTransitionEnd: function(e) {
        var cl = this.$.selectionBar.classList;
        // bar animation: expand -> contract
        if (cl.contains('expand')) {
          cl.remove('expand');
          cl.add('contract');
          this._positionBar(this._pos.width, this._pos.left);
          // bar animation done
        } else if (cl.contains('contract')) {
          cl.remove('contract');
        }
      }
    });
  </script>
</dom-module>
<dom-module id="iron-pages" assetpath="bower_components/iron-pages/">

  <template>
    <style>
      :host {
        display: block;
      }

      :host > ::slotted(:not(.iron-selected)) {
        display: none !important;
      }
    </style>

    <slot></slot>
  </template>

  <script>
    Polymer({

      is: 'iron-pages',

      behaviors: [
        Polymer.IronResizableBehavior,
        Polymer.IronSelectableBehavior
      ],

      properties: {

        // as the selected page is the only one visible, activateEvent
        // is both non-sensical and problematic; e.g. in cases where a user
        // handler attempts to change the page and the activateEvent
        // handler immediately changes it back
        activateEvent: {
          type: String,
          value: null
        }

      },

      observers: [
        '_selectedPageChanged(selected)'
      ],

      _selectedPageChanged: function(selected, old) {
        this.async(this.notifyResize);
      }
    });

  </script>
</dom-module>
<script>
    /**
     * Use `Polymer.PaperDropdownBehavior` to implement a custom validation
     * and filter methods.
     * @polymerBehavior Polymer.PaperDropdownBehavior
     */
    Polymer.PaperDropdownBehaviorImpl = {
        properties: {
            /**
             * Maximum number of items to be selected if item is required and
             * multiple items can be selected, ignored otherwise.
             */
            maxLength: {
                type: Number
            },

            /**
             * Minimum number of items to be selected if item is required and
             * multiple items can be selected, ignored otherwise.
             */
            minLength: {
                type: Number
            }
        },

        /**
         * Checks if item satisfies the filter condition.
         * If it satisfies and has to be shown to the user,
         * true is returned, else false is returned.
         *
         * Override this method to implement your own custom filter
         * condition.
         *
         * @param searchText Text user entered in search field
         * @param item Current Item
         * @return {boolean}
         * @protected
         */
        _filterCheck: function (searchText, item) {
            var currentValue = this._getItemLabel(item);
            if (searchText == "" || currentValue == "") {
                return true;
            } else {
                var re = new RegExp(searchText, "gi");
                if (re.exec(currentValue) != null) {
                    return true;
                } else {
                    return false;
                }
            }
        },

        /**
         * Returns false if the element is required and don't have any value, and true otherwise.
         * @param value.
         * @return {boolean} true if required is true and has atleast one value OR values selected
         *                      is greater than minLenght but less than max length.
         */
        _getValidity: function (value) {
            if (this.multi) {
                if (this.disabled) {
                    // Return true if disabled.
                    return true;
                } else if (this.minLength || this.maxLength) {
                    // Return false if items selected is less than minLength.
                    if (this.minLength && this.value && this.value.length < this.minLength)
                        return false;

                    // Return false if items selected is greater than maxLength.
                    if (this.maxLength && this.value && this.value.length > this.maxLength)
                        return false;

                    // Return true as number of items selected in the 
                    // required range.
                    return true;
                } else if (!this.required) {
                    // Return true if input is not required
                    // and there is no min or max number of 
                    // elements that needs to be selected.
                    return true;
                } else {
                    // Item is required so value must be set.
                    return (this.value != null && this.value != "");
                }
            } else {
                return this.disabled || !this.required || (this.value != null && this.value != "");
            }
        }
    };

    /** @polymerBehavior Polymer.PaperDropdownBehavior */
    Polymer.PaperDropdownBehavior = [
        Polymer.IronFormElementBehavior,
        Polymer.IronValidatableBehavior,
        Polymer.PaperDropdownBehaviorImpl
    ];
</script>
<dom-module id="paper-dropdown" assetpath="bower_components/paper-dropdown/">
    <template>
        <style>
            #search-box {
                box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.1), 0 0 0 0 rgba(0, 0, 0, 0.14), 0 0 0 0 rgba(0, 0, 0, 0.12);
                padding: 0 2px 0 16px;
                border: none;
                width: 100%;
                height: 48px;
                line-height: 48px;
            }

            :host * {
                outline: none;
            }

            paper-listbox {
                overflow-x: hidden;
            }

            :host([multi]) ::slotted(paper-item) {
                background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSczMCcgaGVpZ2h0PSczMCc+PHBhdGggZD0nTTE5IDV2MTRINVY1aDE0bTAtMkg1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6Jy8+PC9zdmc+") no-repeat;
                background-position: 7px 12px;
                padding-left: 40px;
            }

            :host([multi]) ::slotted(paper-item.iron-selected) {
                background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSczMCcgaGVpZ2h0PSczMCc+PHBhdGggZD0nTTE5IDNINWMtMS4xMSAwLTIgLjktMiAydjE0YzAgMS4xLjg5IDIgMiAyaDE0YzEuMTEgMCAyLS45IDItMlY1YzAtMS4xLS44OS0yLTItMnptLTkgMTRsLTUtNSAxLjQxLTEuNDFMMTAgMTQuMTdsNy41OS03LjU5TDE5IDhsLTkgOXonLz48L3N2Zz4=") no-repeat;
                background-position: 7px 12px;
            }

            /* paper-dropdown-menu {
                min-width: 330px;
            }

            paper-dropdown-menu, paper-listbox {
                min-width: 330px;
            } */
        </style>
        <paper-dropdown-menu id="dropdownMenu" label="{{label}}" opened="{{opened}}" error-message="{{errorMessage}}" allow-outside-scroll="{{allowOutsideScroll}}" no-label-float="{{noLabelFloat}}" always-float-label="{{alwaysFloatLabel}}" no-animations="{{noAnimations}}" horizontal-align="{{horizontalAlign}}" vertical-align="{{verticalAlign}}" dynamic-align="{{dynamicAlign}}" restore-focus-on-close="{{restoreFocusOnClose}}" disabled="{{disabled}}" selected-item="{{_selectedItem}}" selected-item-label="{{_selectedItemLabel}}" invalid="[[invalid]]">
            <paper-listbox id="list" items="{{_items}}" slot="dropdown-content" attr-for-selected="{{_attrForSelected}}" selected="{{_selected}}" selected-items="{{_selectedItems}}" multi="{{multi}}">
                <template is="dom-if" if="{{searchable}}">
                    <iron-input bind-value="{{_searchText}}">
                        <input id="search-box" placeholder="{{searchPlaceholder}}" type="text" on-tap="_stopEventPropagation" on-keydown="_stopEventPropagation" on-keyup="_stopEventPropagation">
                    </iron-input>
                </template>
                <slot></slot>
            </paper-listbox>
        </paper-dropdown-menu>
    </template>
    <script>
        /**
         *   `paper-dropdown` is a wrapper for `paper-dropdown-menu` to enable various features like multi-select, search / filter of
         *    items, key value pair and 2-way binding on value.
         *
         *   Values can be bound using `value` attribute.
         *
         *      <paper-dropdown label="Fruit" value="{{value}}">
         *          <paper-item>Apple</paper-item>
         *          <paper-item>Banana</paper-item>
         *          <paper-item>Mango</paper-item>
         *          <paper-item>Orange</paper-item>
         *          <paper-item>Tomato</paper-item>
         *      </paper-dropdown>
         *
         *   Each item can have a key-label pair where key is what stored in the model but
         *   label is what user sees. This can be done using `value` attribute for `paper-item`
         *
         *      <paper-dropdown label="Fruit" value="{{value}}">
         *          <paper-item value="apple">Apple</paper-item>
         *          <paper-item value="banana">Banana</paper-item>
         *          <paper-item value="mango">Mango</paper-item>
         *          <paper-item value="orange">Orange</paper-item>
         *          <paper-item value="tomato">Tomato</paper-item>
         *      </paper-dropdown>
         *
         *   It also has an optional parameter named `searchable`, which when set to true
         *   will add a text field at the start of the dropdown which users can use to filter
         *   out the items in the dropdown.
         *
         *      <paper-dropdown label="Fruit" value="{{value}}" searchable="true">
         *          <paper-item value="apple">Apple</paper-item>
         *          <paper-item value="banana">Banana</paper-item>
         *          <paper-item value="mango">Mango</paper-item>
         *          <paper-item value="orange">Orange</paper-item>
         *          <paper-item value="tomato">Tomato</paper-item>
         *      </paper-dropdown>
         *
         *   For multi-select, set `mutli` parameter to true. In this case however, paper-item's
         *   `value` attribute must be set.
         *
         *      <paper-dropdown label="Fruit" value="{{value}}" multi="true" searchable="true">
         *          <paper-item value="apple">Apple</paper-item>
         *          <paper-item value="banana">Banana</paper-item>
         *          <paper-item value="mango">Mango</paper-item>
         *          <paper-item value="orange">Orange</paper-item>
         *          <paper-item value="tomato">Tomato</paper-item>
         *      </paper-dropdown>
         *
         *   **Styling**
         *
         *   Since `paper-dropdown` is wrapper around `paper-dropdown-menu`, you can use any of the
         *   `paper-dropdown-menu`, `paper-input-container` and `paper-menu-button` style mixins and
         *   custom properties to style the internal input and menu button respectively.
         *
         * @element paper-dropdown
         * @demo demo/index.html
         */
        class PaperDropDown extends Polymer.Element {
            static get is() {
                return 'paper-dropdown'
            }

            /**
             * @event open is fired when `paper-dropdown` opens.
             */

            /**
             * @event close is fired when `paper-dropdown` closes.
             */
            static get properties() {
                return {
                    /**
                     * search placeholder text if searchable is True
                     */
                    searchPlaceholder: {
                        type: String,
                        value: "Search..."
                    },
                    /**
                     * search placeholder text if searchable is True
                     */
                    selectedPlaceholder: {
                        type: String,
                        value: "items selected"
                    },
                    /**
                     * Label shown against the dropdown.
                     */
                    label: {
                        type: String
                    },

                    /**
                     * If true, dropdown will be disabled.
                     */
                    disabled: {
                        type: Boolean,
                        value: false
                    },

                    /**
                     * Value of the dropdown
                     */
                    value: {
                        type: Object,
                        observer: '_updateSelected',
                        notify: true
                    },

                    /**
                     * Index of the selected item.
                     */
                    selected: {
                        type: Number,
                        observer: '_updateValue',
                        notify: true
                    },

                    /**
                     * This is true if the dropdown is in open state
                     */
                    opened: {
                        type: Boolean,
                        notify: true,
                        value: false,
                        observer: '_onOpenedChanged'
                    },

                    /**
                     * If true, a text field is shown at the top of dropdown which
                     * user can use to search/filter for an item.
                     */
                    searchable: {
                        type: Boolean,
                        value: false
                    },

                    /**
                     * The error message to display when invalid.
                     */
                    errorMessage: {
                        type: String
                    },

                    /**
                     * By default, the dropdown will constrain scrolling on the page
                     * to itself when opened.
                     * Set to true in order to prevent scroll from being constrained
                     * to the dropdown when it opens.
                     */
                    allowOutsideScroll: {
                        type: Boolean,
                        value: false
                    },

                    /**
                     * Set to true to disable the floating label. Bind this to the
                     * `<paper-input-container>`'s `noLabelFloat` property.
                     */
                    noLabelFloat: {
                        type: Boolean,
                        value: false,
                        reflectToAttribute: true
                    },

                    /**
                     * Set to true to always float the label. Bind this to the
                     * `<paper-input-container>`'s `alwaysFloatLabel` property.
                     */
                    alwaysFloatLabel: {
                        type: Boolean,
                        value: false
                    },

                    /**
                     * Set to true to disable animations when opening and closing the
                     * dropdown.
                     */
                    noAnimations: {
                        type: Boolean,
                        value: false
                    },

                    /**
                     * The orientation against which to align the menu dropdown
                     * horizontally relative to the dropdown trigger.
                     */
                    horizontalAlign: {
                        type: String,
                        value: 'right'
                    },

                    /**
                     * The orientation against which to align the menu dropdown
                     * vertically relative to the dropdown trigger.
                     */
                    verticalAlign: {
                        type: String,
                        value: 'top'
                    },

                    /**
                     * If true, the `horizontalAlign` and `verticalAlign` properties will
                     * be considered preferences instead of strict requirements when
                     * positioning the dropdown and may be changed if doing so reduces
                     * the area of the dropdown falling outside of `fitInto`.
                     */
                    dynamicAlign: {
                        type: Boolean
                    },

                    /**
                     * Whether focus should be restored to the dropdown when the menu closes.
                     */
                    restoreFocusOnClose: {
                        type: Boolean,
                        value: true
                    },

                    /**
                     * If true, multiple options can be selected.
                     */
                    multi: {
                        type: Boolean,
                        value: false,
                        observer: '_multiChanged'
                    },
                    /**
                     * Key code for UP Arrow.
                     *
                     * @constant
                     * @private
                     */
                    UP_KEY_CODE: {
                        type: Number,
                        value: 38
                    },
                    /**
                     * Key code for DOWN Arrow.
                     *
                     * @constant
                     * @private
                     */
                    DOWN_KEY_CODE:{
                        type: Number,
                        value: 40
                    }
                }
            }

            static get observers() {
                return [
                    '_filter(_searchText)',
                    '_itemsChanged(_items)',
                    '_updateValue(_selected)',
                    '_selectedItemsChanged(_selectedItems)',
                    '_updateSelectedItemLabel(_selectedItemLabel)',
                    '_updateSelectedItemLabel(_selectedItems)'
                ]
            }

            /**
             * This method is automatically called when paper-dropdown is
             * stamped to DOM. ITs main purpose is to initialize the
             * component's variables.
             *
             * @private
             */
            ready() {
                super.ready();
                this.set('_searchText', '');
                this.set('_attrForSelected', null);
            }

            /**
             * Opens the dropdown.
             *
             * @public
             */
            open() {
                this.$.dropdownMenu.open();
            }

            /**
             * Closes the dropdown.
             *
             * @public
             */
            close() {
                this.$.dropdownMenu.close();
            }

            /**
             * If multi is true, it updates dropdown's menu button
             * not to close on item select so that user can select
             * multiple options.
             *
             * @param multi
             * @private
             */
            _multiChanged(multi) {
                if (multi) {
                    this.$.dropdownMenu.$.menuButton.set('closeOnActivate', false);
                    this.$.dropdownMenu.$.menuButton.set('ignoreSelect', true);
                } else {
                    this.$.dropdownMenu.$.menuButton.set('closeOnActivate', true);
                    this.$.dropdownMenu.$.menuButton.set('ignoreSelect', false);
                }
            }

            /**
             * Updates value and selected on _selectedItems change.
             * <b>Note:</b> This function only executes in case of multi select enabled.
             *
             * @param selectedItems
             * @private
             */
            _selectedItemsChanged(selectedItems) {
                if (this.multi) {
                    this.set('value', selectedItems.map((function (item) {
                        return this._getItemValue(item);
                    }).bind(this)));

                    var items = this.$.list.items;
                    this.set('selected', selectedItems.map(function (item) {
                        return items.indexOf(item);
                    }));
                }
            }

            /**
             * Updates selected item's label to customize value shown in
             * paper-drodown-menu's paper-input.
             *
             * @param label
             * @private
             */
            _updateSelectedItemLabel(label) {
                if (this.multi) {
                    if (this._selectedItems.length > 1) {
                        this.$.dropdownMenu._setSelectedItemLabel(this._selectedItems.length + ` ${this.selectedPlaceholder}`);
                    } else if (this._selectedItems.length == 1) {
                        this.$.dropdownMenu._setSelectedItemLabel(this._selectedItems[0].textContent.trim());
                    } else {
                        this.$.dropdownMenu._setSelectedItemLabel(null);
                    }
                }
            }

            /**
             * Returns value of given Item.
             *
             * @param item
             * @returns {string} Label/Value of the item.
             * @private
             */
            _getItemValue(item) {
                if (item) {
                    if (item.getAttribute('value'))
                        return item.getAttribute('value');
                    else
                        return item.textContent.trim();
                }
            }

            /**
             * Updates selected & _attrForSelected on items change.
             *
             * @param items
             * @private
             */
            _itemsChanged(items) {
                if (items.length > 0) {
                    this._updateSelected(this.value);
                    if (this.multi) {
                        for (var i = 0; i < items.length; i++) {
                            if (items[i].getAttribute('value')) {
                                this.set('_attrForSelected', 'value');
                                return;
                            }
                        }
                        this.set('_attrForSelected', null);
                    }
                }
            }

            /**
             * Returns the value of the item for given index.
             *
             * @param index Index of the item
             * @param items Items in the listbox
             * @private
             */
            _getItemValueFromItems(index, items) {
                return this._getItemValue(items[index]);
            }

            /**
             * Returns the Label for the given Item.
             *
             * @param item
             * @return {string} Label of the item.
             * @private
             */
            _getItemLabel(item) {
                return item.textContent.trim();
            }

            /**
             * Returns the Label shown to user for the item at the
             * given index.
             *
             * @param index Index of the item.
             * @param items Items in listbox.
             * @returns {string} Label of the item.
             * @private
             */
            _getItemLabelFromItems(index, items) {
                return this._getItemLabel(items[index]);
            }

            /**
             * Updates `selected` property according to `value` property.
             * Sets selected to -1 if value is not found.
             *
             * @param value
             * @private
             */
            _updateSelected(value) {
                var items = this.$.list.items;
                if (items.length > 0) {
                    if (this.multi) {
                        this.$.list.set('selectedValues', value);
                    } else {
                        for (var i = 0; i < items.length; i++) {
                            if (this._getItemValueFromItems(i, items) == value) {
                                this.set('selected', i);
                                this.set('_selected', i);
                                return;
                            }
                        }
                        this.set('_selected', -1);
                        this.set('selected', -1);
                    }
                }
            }

            /**
             * Updates `value` property according to `selected` property.
             *
             * @param selected
             * @private
             */
            _updateValue(selected) {
                var items = this.$.list.items;
                if (items.length > 0 && !this.multi) {
                    if (selected > -1) {
                        this.set('value', this._getItemValueFromItems(selected, items));
                        return;
                    } else {
                        this.set('value', null);
                    }
                }
            }

            /**
             * Called whenever `opened` changes.
             * Fires `opened` or `closed` events.
             * Also, clears `_searchText` variable on close.
             *
             * @param opened
             * @private
             */
            _onOpenedChanged(opened) {
                if (opened) {
                    this.dispatchEvent(new CustomEvent('open', {detail: null, bubbles: false}));
                } else {
                    this.set('_searchText', '');
                    this.dispatchEvent(new CustomEvent('close', {detail: null, bubbles: false}));
                }
            }

            /**
             * Stops event propagation if up/down keys is not pressed.
             *
             * @param e Event
             * @private
             */
            _stopEventPropagation(e) {
                if (e.keyCode != this.UP_KEY_CODE && e.keyCode != this.DOWN_KEY_CODE) {
                    e.stopPropagation();
                }
            }

            /**
             * Shows/Hides listbox items based on searchText
             *
             * @param searchText Text to be matched in item's label.
             * @private
             */
            _filter(searchText) {
                var items = this.$.list.items;
                for (var i = 0; i < items.length; i++) {
                    console.log("pos", i, items);
                    var display;
                    if (this._filterCheck(searchText, items[i])) {
                        display = 'flex';
                    } else {
                        display = 'none';
                    }
                    items[i].style.display = display;
                }
            }
        }

        window.customElements.define(PaperDropDown.is, PaperDropDown);
    </script>
</dom-module>
<dom-module id="epiviz-add-chart">

    <template>

        <style>
            paper-button {
                --paper-button: {
                    display: inline-block;
                    background: #4285f4;
                    color: #fff;
                    padding: 5px;
                }
                --paper-button-disabled: {
                    color: white
                };
            }

            iron-image {
                padding: 1em;
            }

            iron-pages {
                max-height: inherit;
            }

            data-filter {
                max-height: calc(72vh - 100px);
            }

            paper-tabs {
                --paper-tab-ink: var(--paper-blue-grey-200);
                --paper-tabs-selection-bar-color: var(--paper-blue-500);
                background-color: var(--google-grey-300);
            }

            .dialog-content {
                max-height: inherit;
            }

            .buttonContainer {
                display: inline-block;
                padding: 8px;
            }

            .infoMessage {
                text-align: center;
                @apply --paper-font-subhead;
            }

            #title {
                display: inline;
                @apply --paper-font-title;
                position: relative;
                top: 8px;
            }

        </style>
        <template is="dom-if" if="{{!_isEnvironment(_charts)}}">
            <div class="buttonContainer">
                <paper-button raised="" on-tap="_showModal">
                    <iron-icon icon="editor:insert-chart"></iron-icon>
                    <span>Add Chart</span>
                </paper-button>
            </div>
        </template>

        <template is="dom-if" if="{{_isEnvironment(_charts)}}">
            <paper-menu-button dynamic-align="" horizontal-align="right" vertical-offset="42">
                <paper-button slot="dropdown-trigger" raised="">
                    <iron-icon icon="editor:insert-chart"></iron-icon>
                    <span>Add Chart</span>
                </paper-button>
                <paper-listbox slot="dropdown-content">
                    <paper-item on-tap="_showModal">Add Genomic Range</paper-item>
                    <paper-item on-tap="_addNavigation">Add Navigation</paper-item>
                </paper-listbox>
            </paper-menu-button>
        </template>

        <paper-dialog id="modal" modal="">
            <div class="header">
                <div id="title">Add a</div>
                <paper-dropdown label="Chart" value="{{selectedChart}}" no-animations="" no-label-float="">
                    <template is="dom-repeat" items="{{_charts}}">
                        <paper-item on-click="_chartSelectionChanged" value$="[[item]]">[[item]]</paper-item>
                    </template>
                </paper-dropdown>
                <div id="title">and select datasets below</div>
                <paper-tabs selected="{{tabSelected}}" noink="">
                    <paper-tab>All datasets </paper-tab>
                    <paper-tab>Use from existing charts</paper-tab>
                </paper-tabs>
            </div>
            <paper-dialog-scrollable>
                <iron-pages selected="{{tabSelected}}">
                    <div class="dialog-content">
                        <template is="dom-if" if="[[_tableDataExists(_jsonMeasurements)]]">
                            <data-filter data="{{_jsonMeasurements}}" class="new-charts" selected-items="{{selectedMeasurements}}">
                            </data-filter>
                        </template>
                        <p class="infoMessage" hidden$="[[_tableDataExists(_jsonMeasurements)]]">Choose a Chart Type</p>
                    </div>
                    <div class="dialog-content">
                        <template is="dom-if" if="[[_tableDataExists(_existingChartMeasurements)]]">
                            <data-filter data="{{_existingChartMeasurements}}" class="curr-charts" selected-items="{{selectedExistingMeasurements}}">
                            </data-filter>
                        </template>
                        <p class="infoMessage" hidden$="[[_tableDataExists(_existingChartMeasurements)]]">No Existing Charts</p>
                    </div>
                </iron-pages>
            </paper-dialog-scrollable>
            <div class="buttons">
                <paper-button on-tap="_submit" disabled="{{!_enableButton(selectedChart, selectedMeasurements.*, selectedExistingMeasurements.*, tabSelected.*)}}" dialog-confirm="" autofocus="">Add Chart</paper-button>
                <paper-button on-tap="_close" dialog-dismiss="">Close</paper-button>
            </div>
        </paper-dialog>
        </template>

    <script>

        // Extend Polymer.Element base class
        class EpivizAddChart extends Polymer.Element {

            static get is() { return 'epiviz-add-chart'; }

            static get properties() {
                return {
                    /**
                    * currently available measurements on the app 
                    * uses measurements available from the `epiviz-data-source` element.
                    *
                    * @type {Array.<epiviz.ui.charts.CustomSetting>}
                    */
                    measurements: {
                        type: Object,
                        notify: true
                    },

                    /**
                    * selected measurements from browser.
                    * chartType:
                    * measurement:
                    * @type {Object}
                    */
                    selection: {
                        type: Object,
                        notify: true
                    },

                    selectedMeasurements: {
                        type: Array,
                        notify: true,
                    },

                    selectedExistingMeasurements: {
                        type: Array,
                        notify: true,
                    },

                    /**
                    * selected chart from the browser.
                    *
                    * @type {Array.<epiviz.ui.charts.CustomSetting>}
                    */
                    selectedChart: {
                        type: String,
                        notify: true,
                        value: "MultiStackedLineTrack",
                        observer: "_refitModal",
                    },

                    /**
                    * currently available chart types on the app.
                    */
                    _chartTypes: {
                        type: Object,
                        notify: true,
                        reflectToAttribute: true,
                        value: function () {
                            return {
                                "GenesTrack": "epiviz-genes-track",
                                "BlocksTrack": 'epiviz-blocks-track',
                                "StackedBlocksTrack": 'epiviz-stacked-blocks-track',
                                "HeatmapPlot": "epiviz-heatmap-plot",
                                "ScatterPlot": "epiviz-scatter-plot",
                                "LineTrack": 'epiviz-line-track',
                                "StackedLineTrack": 'epiviz-stacked-line-track',
                                "MultiStackedLineTrack": 'epiviz-multistacked-line-track',
                                "MirrorLineTrack": "epiviz-line-track-mirror",
                                "LinePlot": 'epiviz-line-plot',
                                "StackedLinePlot": 'epiviz-stacked-line-plot',
                                "EpivizNavigation": 'epiviz-navigation'
                            }
                        }
                    },

                    _charts: {
                        type: Array,
                        notify: true,
                        computed: "_getCharts(_chartTypes, _parentContainer)"
                    },

                    /**
                    * parentContainer
                    */
                    _parentContainer: {
                        type: Object,
                    },

                    _jsonMeasurements: {
                        type: Array,
                    },

                    _existingChartMeasurements: {
                        type: Array,
                    },

                    tabSelected: {
                        type: Number,
                        notify: true,
                        value: function () {
                            return 0;
                        },
                        observer: "_refitModal",
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                ]
            }

            constructor() {
                super();
            }

            connectedCallback() {
                super.connectedCallback();
            }

            disconnectedCallback() {
                super.connectedCallback();
            }

            _getJSONMeasurements(data) {
                return data.raw();
            }

            /**
             * Get Available Chart Types
             */
            _getCharts(chartTypes, parentContainer) {
                if (parentContainer && parentContainer.nodeName === "EPIVIZ-NAVIGATION") {
                    delete chartTypes["EpivizNavigation"];
                }
                return Object.keys(chartTypes);
            }

            _isEnvironment(chartTypes) {
                return chartTypes.indexOf("EpivizNavigation") !== -1;
            }

            _tableDataExists(tableData) {
                return tableData.length > 0;
            }

            _enableButton(selectedChart, selectedMeasurements, selectedExistingMeasurements, tabSelected) {
                if (selectedChart !== null && this._charts.includes(selectedChart)) {
                    if (this.tabSelected === 1 && this.selectedExistingMeasurements && this.selectedExistingMeasurements.length > 0) {
                        return true;
                    }

                    if (this.tabSelected === 0 && this.selectedMeasurements && this.selectedMeasurements.length > 0) {
                        return true;
                    }
                }
                return false;
            }

            /**
             * API to add a chart
             * @param {chartType}: chart type to add/create. Must be one of the defined chart types in _chartTypes attribute.
             * @param {measurements}: measurements to use.
             * TODO:
             * @param {data}: data for the chart (for json-based-charts). 
             */
            _addChart(chartType, measurements) {

                var self = this;
                var envElement = self.parentNode.parentNode.parentNode.parentNode.nodeName == "EPIVIZ-NAVIGATION" ? self.parentNode.parentNode.parentNode.parentNode : self.parentNode.parentNode.parentNode;
                var envElement = self._parentContainer;

                var chartElem = document.createElement(self._chartTypes[chartType]);
                chartElem.slot = "charts";
                chartElem.range = envElement.range;
                var chartElemType = chartElem._createChart();

                chartElem.setAttribute("measurements", JSON.stringify(measurements));
                envElement.appendChild(chartElem);
            }

            _refitModal() {
                setTimeout(() => this.$.modal.refit(), 0);
            }

            _addNavigation() {
                var envElement = this._parentContainer;
                var elem = document.createElement("epiviz-navigation");

                elem.setAttribute("chr", "chr19");
                elem.setAttribute("start", 10084603);
                elem.setAttribute("end", 10312571);
                elem.setAttribute("no-logo", true);
                elem.slot = "charts";

                envElement.appendChild(elem);
            }

            _showModal(event) {
                var envElement = this._parentContainer;
                var currentCharts = [];
                var currentChartObj = {};
                let navChildren =
                    Polymer.FlattenedNodesObserver.getFlattenedNodes(envElement).filter(n => n.nodeType === Node.ELEMENT_NODE);
                var numChildren = navChildren.length;
                for (var index = 0; index < numChildren; index++) {
                    var currentChild = navChildren[index];
                    currentCharts.push({ "node": currentChild.nodeName, "id": currentChild.plotId });
                    currentChartObj[currentChild.plotId] = currentChild.measurements;
                }
                this.currentChartObj = currentChartObj;
                this.set("_existingChartMeasurements", currentCharts);

                this.set("selectedChart", "MultiStackedLineTrack");
                this.selectedChart = "MultiStackedLineTrack";
                this._chartSelectionChanged();

                this.$.modal.open();
            }

            /**
             * show the measurement browser
             */
            _chartSelectionChanged(event) {
                var self = this;
                var envElement = self._parentContainer;

                self.selection = {};
                self.selection.chartType = self._charts[self.selectedChart];
                self.measurements = envElement.measurementSet;

                if (event && event.target.nodeName === "PAPER-ITEM") {
                    $(event.target).parent().parent()[0].selected = null;
                }

                if (self.selection.chartType == "GenesTrack") {
                    var data = envElement.measurementSet.subset(function (m) { return m.defaultChartType() == "Genes Track" });
                    var datasourceGroups = {};
                    data.foreach(function (m) {
                        if (data.dataprovider && data.dataprovider != m.dataprovider()) { return; }
                        if (data.annotation) {
                            for (var key in data.annotation) {
                                if (!data.annotation.hasOwnProperty(key)) { continue; }
                                if (!m.annotation() || m.annotation()[key] != data.annotation[key]) { return; }
                            }
                        }
                        datasourceGroups[m.datasourceGroup()] = ["Genes track", false];
                    });

                    // self.set("_jsonMeasurements", data.raw());
                }
                else {
                    var data = envElement.measurementAll;
                    var source = "epiviz";

                    // self.set("_jsonMeasurements", data.raw());
                }
                var nData = [];
                var mData = data.raw();
                mData.forEach(function (d, i) {

                    var temp = d.annotation;
                    if (temp == undefined) {
                        temp = {};
                    }

                    delete temp["tags"];
                    delete d["type"];
                    delete d["datasourceId"];
                    delete d["datasourceGroup"];
                    delete d["dataprovider"];
                    delete d["formula"];
                    delete d["defaultChartType"];
                    delete d["metadata"];
                    delete d["minValue"];
                    delete d["maxValue"];

                    d.annotation = temp;
                    nData[i] = d;
                });
                
                self.set("_jsonMeasurements", nData);
                
                this.$.modal.refit();
            }

            _close() {
                this.selectedChart = null;
                var newCharts = this.shadowRoot.querySelector("data-filter.new-charts");
                var currCharts = this.shadowRoot.querySelector("data-filter.curr-charts");
                if (newCharts) {
                    newCharts._deselectAll();
                }
                if (currCharts) {
                    currCharts._deselectAll();
                }
                this.set("selectedMeasurements", []);
                this.set("selectedExistingMeasurements", []);
                this.set("_jsonMeasurements", []);
            }

            _submit() {
                var envElement = this._parentContainer;
                var fids;
                var fmeasurements;
                if (this.tabSelected === 1) {
                    fids = this.selectedExistingMeasurements.map(x => x.id);
                    fmeasurements = this.currentChartObj[fids[0]];
                } else if (this.tabSelected === 0) {
                    fids = this.selectedMeasurements.map(x => x.id);
                    fmeasurements = envElement.measurementAll.subset(function (m) { return fids.includes(m.id()) });
                    fmeasurements = fmeasurements.raw();
                }


                this.selection = {};
                this.selection.chartType = this.selectedChart;

                var chartElem = document.createElement(this._chartTypes[this.selection.chartType]);
                chartElem.slot = "charts";
                chartElem.range = envElement.range;
                var chartElemType = chartElem._createChart();

                chartElem.setAttribute("measurements", JSON.stringify(fmeasurements));
                envElement.appendChild(chartElem);

                this._close();
            }

            /**
             * handles form show modal action
             */
            showAdd(target, callback) {
                this.callback = callback;
                this.$.modal.positionTarget = target;
                this.$.modal.open();
            }

            /**
             * handles form close modal action
             */
            closeAddDialog() {
                this.$.modal.close();
            }
        };

        customElements.define(EpivizAddChart.is, EpivizAddChart);
    </script>
</dom-module><script>
    /**
     * `ChartAddBehavior` object manages the `<epiviz-add-chart>` element. 
     * Environment and Navigation elements inherit this behavior to add a new chart.
     *
     * @polymerBehavior
    **/

    EpivizChartAddBehavior = function (superClass) {
        return class extends superClass {
            constructor() {
                super();
            }

            static get properties() {
                return {};
            }

            /**
             * Shows the `<epiviz-add-chart>` element
             */
            _showAddDialog() { }

            /**
             * Initializes the `<epiviz-add-chart>` element
             */
            _initializeAddDialog() {
                var chartContainer = this.$.header;
                var currAddElem = this.shadowRoot.querySelector('epiviz-add-chart');

                if (currAddElem) {
                    var dataManagerElem = document.querySelector('epiviz-data-source');
                    if (dataManagerElem) {
                        Polymer.dom(currAddElem).measurements = dataManagerElem.measurementSet;
                    }
                }
            }

            /**
             * Intializes chart container DOM element for settings and colors elements.
             */
            _initializeDialogContainer() { }

            ready() {
                super.ready();
                this._initializeDialogContainer();
            }
        }
    }
</script><script>
    /**
     * `ChartWorkspaceBehavior` is an interface to save & load workspaces as html attributes. 
     * `epiviz-navigation` & `epiviz-environment` inherit this behavior.
     *
     * @polymerBehavior
    **/
    EpivizChartWorkspaceBehavior = function (superClass) {
        return class extends superClass {
            constructor() {
                super();
            }

            static get properties() {
                return {
                    /**
                    * current workspace object.
                    *
                    * @type {Object}
                    */
                    workspace: {
                        type: Object,
                        notify: true
                        // observer: 'loadWorkspace'
                    },

                    /**
                    * current workspace name.
                    *
                    * @type {String}
                    */
                    workspaceName: {
                        type: String,
                        notify: true,
                        value: "default"
                    }
                };
            }

            /**
             * load workspace from the `workspace` property of the chart.
             */
            loadWorkspace(workspace) {

                var self = this;

                self.measurements = self.workspace.measurements;
                self.chr = self.workspace.chr;
                self.start = self.workspace.start;
                self.end = self.workspace.end;
                self.plotId = self.workspace.plotId;
                self.collapsed = self.workspace.collapsed ? self.workspace.collapsed : false;
                self.noLogo = true;

                for (var key in self.workspace.children) {
                    var chartWorkspaceObj = self.workspace.children[key];

                    var chart = document.createElement(chartWorkspaceObj.node);
                    chart.slot = "charts";
                    if (chartWorkspaceObj.node == "EPIVIZ-NAVIGATION") {
                        chart.chr = chartWorkspaceObj.chr;
                        chart.start = chartWorkspaceObj.start;
                        chart.end = chartWorkspaceObj.end;
                        // chart.node = chartWorkspaceObj.nodeName;
                        chart.collapsed = chartWorkspaceObj.collapsed ? chartWorkspaceObj.collapsed : false;
                        chart.range = new epiviz.datatypes.GenomicRange(chart.chr, chart.start, chart.end - chart.start);
                        chart._strRange = chart.chr + ": " + chart.start + " - " + chart.end;
                        chart.noLogo = true;
                        self.appendChild(chart);

                        for (var navs in chartWorkspaceObj.children) {
                            var chartNavWorkspaceObj = chartWorkspaceObj.children[navs];
                            var navChart = document.createElement(chartNavWorkspaceObj.node);

                            navChart.slot = "charts";
                            navChart.measurements = chartNavWorkspaceObj.measurements;
                            navChart.dimS = chartNavWorkspaceObj.dims;
                            navChart.chartSettings = chartNavWorkspaceObj.chartSettings;
                            navChart.chartColors = chartNavWorkspaceObj.chartColors;

                            chart.appendChild(navChart);
                        }
                    }
                    else {
                        chart.measurements = chartWorkspaceObj.measurements;
                        chart.dimS = chartWorkspaceObj.dims;
                        chart.chartSettings = chartWorkspaceObj.chartSettings;
                        chart.chartColors = chartWorkspaceObj.chartColors;
                        self.appendChild(chart);
                    }
                }
            }

            /**
             * Save current list of charts to the workspace property.
             *
             * @fires workspaceChanged
             */
            saveWorkspace() {
                var self = this;

                var workspaceObj = {};
                workspaceObj.measurements = self.measurements ? self.measurements : null;
                workspaceObj.plotId = self.plotId;
                workspaceObj.chr = self.chr;
                workspaceObj.start = self.start;
                workspaceObj.end = self.end;
                workspaceObj.node = self.nodeName;
                workspaceObj.children = {};
                workspaceObj.collapsed = self.collapsed ? self.collapsed : null;


                let navChildren =
                    Polymer.FlattenedNodesObserver.getFlattenedNodes(this).filter(n => n.nodeType === Node.ELEMENT_NODE);

                var numChildren = navChildren.length;
                for (var index = 0; index < numChildren; index++) {

                    var childObj = {};
                    var currentChild = navChildren[index];
                    childObj[currentChild.plotId] = {};

                    if (currentChild.nodeName === "EPIVIZ-NAVIGATION") {

                        childObj[currentChild.plotId].chr = currentChild.chr;
                        childObj[currentChild.plotId].start = currentChild.start;
                        childObj[currentChild.plotId].end = currentChild.end;
                        childObj[currentChild.plotId].node = currentChild.nodeName;
                        childObj[currentChild.plotId].children = {};

                        let navChildren2 =
                            Polymer.FlattenedNodesObserver.getFlattenedNodes(currentChild).filter(n => n.nodeType === Node.ELEMENT_NODE);

                        var numChildren2 = navChildren2.length;
                        for (var nindex = 0; nindex < numChildren2; nindex++) {
                            var currentNavChild = navChildren2[nindex];
                            childObj[currentChild.plotId].children[currentNavChild.plotId] = {
                                "node": currentNavChild.nodeName,
                                "measurements": currentNavChild.measurements ? currentNavChild.measurements : null,
                                "dims": currentNavChild.dimS ? currentNavChild.dimS : null,
                                "chartSettings": currentNavChild.chartSettings,
                                "chartColors": currentNavChild.chartColors
                            }
                        }
                    }
                    else {
                        childObj[currentChild.plotId] = {
                            "node": currentChild.nodeName,
                            "measurements": currentChild.measurements ? currentChild.measurements : null,
                            "dims": currentChild.dimS ? currentChild.dimS : null,
                            "chartSettings": currentChild.chartSettings,
                            "chartColors": currentChild.chartColors
                        };
                    }
                    workspaceObj.children[currentChild.plotId] = childObj[currentChild.plotId];
                }
                self.workspace = workspaceObj;

                if (self.nodeName == "EPIVIZ-NAVIGATION") {

                    if (self._parentContainer && self._parentContainer.nodeName == "EPIVIZ-ENVIRONMENT") {
                        self._parentContainer.dispatchEvent(new CustomEvent('navWorkspaceChanged',
                            {
                                detail: {
                                    id: self.plotId
                                },
                                bubbles: true
                            }
                        )
                        );
                    }
                    else {
                        /**
                         * fires event when a chart workspace has changed. 
                         * Notifies `epiviz-workspace` element to save to firebase.
                         *
                         * @event workspaceChanged
                         * @type {object}
                         */
                        document.dispatchEvent(new CustomEvent('workspaceChanged',
                            {
                                detail: {
                                    id: self.plotId,
                                    data: self.workspace
                                },
                                bubbles: true
                            }
                        )
                        );
                    }
                }
                else {
                    /**
                     * fires event when a chart workspace has changed. 
                     * Notifies `epiviz-workspace` element to save to firebase.
                     *
                     * @event workspaceChanged
                     * @type {object}
                     */
                    document.dispatchEvent(new CustomEvent('workspaceChanged',
                        {
                            detail: {
                                id: self.plotId,
                                data: self.workspace
                            },
                            bubbles: true
                        }
                    )
                    );
                }
            }
        }
    }
</script><script>
    /**
     * `ChartGridBehavior` object initializes grid layout system for plots and tracks. 
     * All elements that need to conform to a grid should include and call this behavior. 
     *
     * @polymerBehavior
    **/

    EpivizChartGridBehavior = function (superClass) {
        return class extends superClass {
            constructor() {
                super();
            }

            ready() {
                super.ready();
                // Only useful if you have draggable as well
                this._initializeDragHandle();
            }

            static get properties() {
                return {};
            }

            /**
            * Add drag handle to charts only
            */
            _initializeDragHandle() {
                if (this.nodeName.indexOf("NAVIGATION") === -1) {
                    var dragHandle = document.createElement("div");
                    dragHandle.className = "dragHandle";
                    dragHandle.slot = "dragHandle";
                    this.appendChild(dragHandle);
                }
            }

            /**
            * Initializes grid behavior
            */
            _initializeGrid() {
                if (this.nodeName.indexOf("TRACK") != -1) {
                    this.classList.add("grid-track");
                } else if (this.nodeName.indexOf("NAVIGATION") != -1) {
                    this.classList.add("grid-navigation");
                } else {
                    // is plot
                    this.classList.add("grid-plot");
                }

                if (this.chartWidth) {
                    this._setColumnSpan(this.chartWidth);
                }
            }

            /**
             * Manage resizing buttons of chats
             */
            _initializeResizeButtons() {
                var chartSettingsContainer = this.shadowRoot.querySelector('#chartSettingsContainer');
                var chartContainer = this.shadowRoot.querySelector('#' + this.plotId);
                var currExpandIcon = this.shadowRoot.querySelector('#chartExpandIcon');
                var currContractIcon = this.shadowRoot.querySelector('#chartContractIcon');

                if (currExpandIcon == null) {
                    var iconElem = document.createElement('paper-icon-button');
                    iconElem.id = "chartExpandIcon";
                    iconElem.icon = "chevron-right";

                    iconElem.addEventListener("click", this._expand.bind(this));
                    chartSettingsContainer.appendChild(iconElem);
                }

                if (currContractIcon == null) {
                    var iconElem = document.createElement('paper-icon-button');
                    iconElem.id = "chartContractIcon";
                    iconElem.icon = "chevron-left";

                    iconElem.addEventListener("click", this._contract.bind(this));
                    chartSettingsContainer.appendChild(iconElem);
                }
            }

            _getStyle(elem, prop) {

                var style;
                if (window.ShadyCSS) {
                    return ShadyCSS.getComputedStyleValue(elem, prop);
                } else {
                    return getComputedStyle(elem).getPropertyValue(prop);
                }
            }

            /**
             * Callback to increase size of chart by 1 `grid-column`
             */
            _expand() {
                var prop = this._getStyle(this, "grid-column-start");
                var columnSpan = this._getColumnSpan(prop);
                if (columnSpan < 6) {
                    $(this).css("grid-column-start", "span " + (columnSpan + 1));
                    this._onResize();
                }
            }

            /**
             * Callback to decrease size of chart by 1 `grid-column`
             */
            _contract() {
                var prop = this._getStyle(this, "grid-column-start");
                var columnSpan = this._getColumnSpan(prop);
                if (columnSpan > 1) {
                    $(this).css("grid-column-start", "span " + (columnSpan - 1));
                    this._onResize();
                }
            }

            /**
             * helper function to get `grid-column` of current chart
             */
            _getColumnSpan(gridColumn) {
                // span 2 / auto
                if (gridColumn != "auto" && gridColumn  != "") {
                    var val = parseInt(gridColumn.split(' ')[1]);
                    if (!val) {
                        console.log("grid column error, not a number");
                    }
                    return val;
                }

                return 6;
            }

            /**
             * helper function to set `grid-column` of current chart
             */
            _setColumnSpan(gridColumn) {

                if (gridColumn > 6) {gridColumn = 6;}
                if (gridColumn < 1) {gridColumn = 1;}

                var prop = this._getStyle(this, "grid-column-start");
                var columnSpan = this._getColumnSpan(prop);
                if (columnSpan > 1) {
                    $(this).css("grid-column-start", "span " + (gridColumn));
                    this._onResize();
                }
            }
        }
    }
</script><iron-iconset-svg name="image" size="24">
<svg><defs>
<g id="add-a-photo"><path d="M3 4V1h2v3h3v2H5v3H3V6H0V4h3zm3 6V7h3V4h7l1.83 2H21c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H5c-1.1 0-2-.9-2-2V10h3zm7 9c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-3.2-5c0 1.77 1.43 3.2 3.2 3.2s3.2-1.43 3.2-3.2-1.43-3.2-3.2-3.2-3.2 1.43-3.2 3.2z"></path></g>
<g id="add-to-photos"><path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9h-4v4h-2v-4H9V9h4V5h2v4h4v2z"></path></g>
<g id="adjust"><path d="M12 2C6.49 2 2 6.49 2 12s4.49 10 10 10 10-4.49 10-10S17.51 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm3-8c0 1.66-1.34 3-3 3s-3-1.34-3-3 1.34-3 3-3 3 1.34 3 3z"></path></g>
<g id="assistant"><path d="M19 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h4l3 3 3-3h4c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-5.12 10.88L12 17l-1.88-4.12L6 11l4.12-1.88L12 5l1.88 4.12L18 11l-4.12 1.88z"></path></g>
<g id="assistant-photo"><path d="M14.4 6L14 4H5v17h2v-7h5.6l.4 2h7V6z"></path></g>
<g id="audiotrack"><path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7z"></path></g>
<g id="blur-circular"><path d="M10 9c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zM7 9.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm3 7c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm-3-3c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm3-6c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM14 9c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-1.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zm3 6c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm0-4c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm2-3.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm0-3.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z"></path></g>
<g id="blur-linear"><path d="M5 17.5c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5-1.5.67-1.5 1.5.67 1.5 1.5 1.5zM9 13c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0-4c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zM3 21h18v-2H3v2zM5 9.5c.83 0 1.5-.67 1.5-1.5S5.83 6.5 5 6.5 3.5 7.17 3.5 8 4.17 9.5 5 9.5zm0 4c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5-1.5.67-1.5 1.5.67 1.5 1.5 1.5zM9 17c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm8-.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM3 3v2h18V3H3zm14 5.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zm0 4c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM13 9c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0 4c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0 4c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1z"></path></g>
<g id="blur-off"><path d="M14 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm-.2 4.48l.2.02c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5-1.5.67-1.5 1.5l.02.2c.09.67.61 1.19 1.28 1.28zM14 3.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zm-4 0c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zm11 7c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM10 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm8 8c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0-4c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0-4c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm-4 13.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM2.5 5.27l3.78 3.78L6 9c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1c0-.1-.03-.19-.06-.28l2.81 2.81c-.71.11-1.25.73-1.25 1.47 0 .83.67 1.5 1.5 1.5.74 0 1.36-.54 1.47-1.25l2.81 2.81c-.09-.03-.18-.06-.28-.06-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1c0-.1-.03-.19-.06-.28l3.78 3.78L20 20.23 3.77 4 2.5 5.27zM10 17c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm11-3.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM6 13c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zM3 9.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm7 11c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM6 17c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm-3-3.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5z"></path></g>
<g id="blur-on"><path d="M6 13c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-8c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm-3 .5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM6 5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm15 5.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM14 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0-3.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zm-11 10c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm7 7c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm0-17c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM10 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0 5.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm8 .5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-8c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm3 8.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM14 17c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 3.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm-4-12c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0 8.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm4-4.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-4c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5z"></path></g>
<g id="brightness-1"><circle cx="12" cy="12" r="10"></circle></g>
<g id="brightness-2"><path d="M10 2c-1.82 0-3.53.5-5 1.35C7.99 5.08 10 8.3 10 12s-2.01 6.92-5 8.65C6.47 21.5 8.18 22 10 22c5.52 0 10-4.48 10-10S15.52 2 10 2z"></path></g>
<g id="brightness-3"><path d="M9 2c-1.05 0-2.05.16-3 .46 4.06 1.27 7 5.06 7 9.54 0 4.48-2.94 8.27-7 9.54.95.3 1.95.46 3 .46 5.52 0 10-4.48 10-10S14.52 2 9 2z"></path></g>
<g id="brightness-4"><path d="M20 8.69V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12 20 8.69zM12 18c-.89 0-1.74-.2-2.5-.55C11.56 16.5 13 14.42 13 12s-1.44-4.5-3.5-5.45C10.26 6.2 11.11 6 12 6c3.31 0 6 2.69 6 6s-2.69 6-6 6z"></path></g>
<g id="brightness-5"><path d="M20 15.31L23.31 12 20 8.69V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69zM12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6z"></path></g>
<g id="brightness-6"><path d="M20 15.31L23.31 12 20 8.69V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69zM12 18V6c3.31 0 6 2.69 6 6s-2.69 6-6 6z"></path></g>
<g id="brightness-7"><path d="M20 8.69V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12 20 8.69zM12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6zm0-10c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4z"></path></g>
<g id="broken-image"><path d="M21 5v6.59l-3-3.01-4 4.01-4-4-4 4-3-3.01V5c0-1.1.9-2 2-2h14c1.1 0 2 .9 2 2zm-3 6.42l3 3.01V19c0 1.1-.9 2-2 2H5c-1.1 0-2-.9-2-2v-6.58l3 2.99 4-4 4 4 4-3.99z"></path></g>
<g id="brush"><path d="M7 14c-1.66 0-3 1.34-3 3 0 1.31-1.16 2-2 2 .92 1.22 2.49 2 4 2 2.21 0 4-1.79 4-4 0-1.66-1.34-3-3-3zm13.71-9.37l-1.34-1.34c-.39-.39-1.02-.39-1.41 0L9 12.25 11.75 15l8.96-8.96c.39-.39.39-1.02 0-1.41z"></path></g>
<g id="burst-mode"><path d="M1 5h2v14H1zm4 0h2v14H5zm17 0H10c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V6c0-.55-.45-1-1-1zM11 17l2.5-3.15L15.29 16l2.5-3.22L21 17H11z"></path></g>
<g id="camera"><path d="M9.4 10.5l4.77-8.26C13.47 2.09 12.75 2 12 2c-2.4 0-4.6.85-6.32 2.25l3.66 6.35.06-.1zM21.54 9c-.92-2.92-3.15-5.26-6-6.34L11.88 9h9.66zm.26 1h-7.49l.29.5 4.76 8.25C21 16.97 22 14.61 22 12c0-.69-.07-1.35-.2-2zM8.54 12l-3.9-6.75C3.01 7.03 2 9.39 2 12c0 .69.07 1.35.2 2h7.49l-1.15-2zm-6.08 3c.92 2.92 3.15 5.26 6 6.34L12.12 15H2.46zm11.27 0l-3.9 6.76c.7.15 1.42.24 2.17.24 2.4 0 4.6-.85 6.32-2.25l-3.66-6.35-.93 1.6z"></path></g>
<g id="camera-alt"><circle cx="12" cy="12" r="3.2"></circle><path d="M9 2L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"></path></g>
<g id="camera-front"><path d="M10 20H5v2h5v2l3-3-3-3v2zm4 0v2h5v-2h-5zM12 8c1.1 0 2-.9 2-2s-.9-2-2-2-1.99.9-1.99 2S10.9 8 12 8zm5-8H7C5.9 0 5 .9 5 2v14c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V2c0-1.1-.9-2-2-2zM7 2h10v10.5c0-1.67-3.33-2.5-5-2.5s-5 .83-5 2.5V2z"></path></g>
<g id="camera-rear"><path d="M10 20H5v2h5v2l3-3-3-3v2zm4 0v2h5v-2h-5zm3-20H7C5.9 0 5 .9 5 2v14c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V2c0-1.1-.9-2-2-2zm-5 6c-1.11 0-2-.9-2-2s.89-2 1.99-2 2 .9 2 2C14 5.1 13.1 6 12 6z"></path></g>
<g id="camera-roll"><path d="M14 5c0-1.1-.9-2-2-2h-1V2c0-.55-.45-1-1-1H6c-.55 0-1 .45-1 1v1H4c-1.1 0-2 .9-2 2v15c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2h8V5h-8zm-2 13h-2v-2h2v2zm0-9h-2V7h2v2zm4 9h-2v-2h2v2zm0-9h-2V7h2v2zm4 9h-2v-2h2v2zm0-9h-2V7h2v2z"></path></g>
<g id="center-focus-strong"><path d="M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm-7 7H3v4c0 1.1.9 2 2 2h4v-2H5v-4zM5 5h4V3H5c-1.1 0-2 .9-2 2v4h2V5zm14-2h-4v2h4v4h2V5c0-1.1-.9-2-2-2zm0 16h-4v2h4c1.1 0 2-.9 2-2v-4h-2v4z"></path></g>
<g id="center-focus-weak"><path d="M5 15H3v4c0 1.1.9 2 2 2h4v-2H5v-4zM5 5h4V3H5c-1.1 0-2 .9-2 2v4h2V5zm14-2h-4v2h4v4h2V5c0-1.1-.9-2-2-2zm0 16h-4v2h4c1.1 0 2-.9 2-2v-4h-2v4zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"></path></g>
<g id="collections"><path d="M22 16V4c0-1.1-.9-2-2-2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2zm-11-4l2.03 2.71L16 11l4 5H8l3-4zM2 6v14c0 1.1.9 2 2 2h14v-2H4V6H2z"></path></g>
<g id="collections-bookmark"><path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 10l-2.5-1.5L15 12V4h5v8z"></path></g>
<g id="color-lens"><path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"></path></g>
<g id="colorize"><path d="M20.71 5.63l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-3.12 3.12-1.93-1.91-1.41 1.41 1.42 1.42L3 16.25V21h4.75l8.92-8.92 1.42 1.42 1.41-1.41-1.92-1.92 3.12-3.12c.4-.4.4-1.03.01-1.42zM6.92 19L5 17.08l8.06-8.06 1.92 1.92L6.92 19z"></path></g>
<g id="compare"><path d="M10 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h5v2h2V1h-2v2zm0 15H5l5-6v6zm9-15h-5v2h5v13l-5-6v9h5c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"></path></g>
<g id="control-point"><path d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.49 2 2 6.49 2 12s4.49 10 10 10 10-4.49 10-10S17.51 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></g>
<g id="control-point-duplicate"><path d="M16 8h-2v3h-3v2h3v3h2v-3h3v-2h-3zM2 12c0-2.79 1.64-5.2 4.01-6.32V3.52C2.52 4.76 0 8.09 0 12s2.52 7.24 6.01 8.48v-2.16C3.64 17.2 2 14.79 2 12zm13-9c-4.96 0-9 4.04-9 9s4.04 9 9 9 9-4.04 9-9-4.04-9-9-9zm0 16c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7z"></path></g>
<g id="crop"><path d="M17 15h2V7c0-1.1-.9-2-2-2H9v2h8v8zM7 17V1H5v4H1v2h4v10c0 1.1.9 2 2 2h10v4h2v-4h4v-2H7z"></path></g>
<g id="crop-16-9"><path d="M19 6H5c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H5V8h14v8z"></path></g>
<g id="crop-3-2"><path d="M19 4H5c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H5V6h14v12z"></path></g>
<g id="crop-5-4"><path d="M19 5H5c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 12H5V7h14v10z"></path></g>
<g id="crop-7-5"><path d="M19 7H5c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2zm0 8H5V9h14v6z"></path></g>
<g id="crop-din"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z"></path></g>
<g id="crop-free"><path d="M3 5v4h2V5h4V3H5c-1.1 0-2 .9-2 2zm2 10H3v4c0 1.1.9 2 2 2h4v-2H5v-4zm14 4h-4v2h4c1.1 0 2-.9 2-2v-4h-2v4zm0-16h-4v2h4v4h2V5c0-1.1-.9-2-2-2z"></path></g>
<g id="crop-landscape"><path d="M19 5H5c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 12H5V7h14v10z"></path></g>
<g id="crop-original"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zm-5.04-6.71l-2.75 3.54-1.96-2.36L6.5 17h11l-3.54-4.71z"></path></g>
<g id="crop-portrait"><path d="M17 3H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H7V5h10v14z"></path></g>
<g id="crop-rotate"><path d="M7.47 21.49C4.2 19.93 1.86 16.76 1.5 13H0c.51 6.16 5.66 11 11.95 11 .23 0 .44-.02.66-.03L8.8 20.15l-1.33 1.34zM12.05 0c-.23 0-.44.02-.66.04l3.81 3.81 1.33-1.33C19.8 4.07 22.14 7.24 22.5 11H24c-.51-6.16-5.66-11-11.95-11zM16 14h2V8c0-1.11-.9-2-2-2h-6v2h6v6zm-8 2V4H6v2H4v2h2v8c0 1.1.89 2 2 2h8v2h2v-2h2v-2H8z"></path></g>
<g id="crop-square"><path d="M18 4H6c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H6V6h12v12z"></path></g>
<g id="dehaze"><path d="M2 15.5v2h20v-2H2zm0-5v2h20v-2H2zm0-5v2h20v-2H2z"></path></g>
<g id="details"><path d="M3 4l9 16 9-16H3zm3.38 2h11.25L12 16 6.38 6z"></path></g>
<g id="edit"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"></path></g>
<g id="exposure"><path d="M15 17v2h2v-2h2v-2h-2v-2h-2v2h-2v2h2zm5-15H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM5 5h6v2H5V5zm15 15H4L20 4v16z"></path></g>
<g id="exposure-neg-1"><path d="M4 11v2h8v-2H4zm15 7h-2V7.38L14 8.4V6.7L18.7 5h.3v13z"></path></g>
<g id="exposure-neg-2"><path d="M15.05 16.29l2.86-3.07c.38-.39.72-.79 1.04-1.18.32-.39.59-.78.82-1.17.23-.39.41-.78.54-1.17s.19-.79.19-1.18c0-.53-.09-1.02-.27-1.46-.18-.44-.44-.81-.78-1.11-.34-.31-.77-.54-1.26-.71-.51-.16-1.08-.24-1.72-.24-.69 0-1.31.11-1.85.32-.54.21-1 .51-1.36.88-.37.37-.65.8-.84 1.3-.18.47-.27.97-.28 1.5h2.14c.01-.31.05-.6.13-.87.09-.29.23-.54.4-.75.18-.21.41-.37.68-.49.27-.12.6-.18.96-.18.31 0 .58.05.81.15.23.1.43.25.59.43.16.18.28.4.37.65.08.25.13.52.13.81 0 .22-.03.43-.08.65-.06.22-.15.45-.29.7-.14.25-.32.53-.56.83-.23.3-.52.65-.88 1.03l-4.17 4.55V18H21v-1.71h-5.95zM2 11v2h8v-2H2z"></path></g>
<g id="exposure-plus-1"><path d="M10 7H8v4H4v2h4v4h2v-4h4v-2h-4V7zm10 11h-2V7.38L15 8.4V6.7L19.7 5h.3v13z"></path></g>
<g id="exposure-plus-2"><path d="M16.05 16.29l2.86-3.07c.38-.39.72-.79 1.04-1.18.32-.39.59-.78.82-1.17.23-.39.41-.78.54-1.17.13-.39.19-.79.19-1.18 0-.53-.09-1.02-.27-1.46-.18-.44-.44-.81-.78-1.11-.34-.31-.77-.54-1.26-.71-.51-.16-1.08-.24-1.72-.24-.69 0-1.31.11-1.85.32-.54.21-1 .51-1.36.88-.37.37-.65.8-.84 1.3-.18.47-.27.97-.28 1.5h2.14c.01-.31.05-.6.13-.87.09-.29.23-.54.4-.75.18-.21.41-.37.68-.49.27-.12.6-.18.96-.18.31 0 .58.05.81.15.23.1.43.25.59.43.16.18.28.4.37.65.08.25.13.52.13.81 0 .22-.03.43-.08.65-.06.22-.15.45-.29.7-.14.25-.32.53-.56.83-.23.3-.52.65-.88 1.03l-4.17 4.55V18H22v-1.71h-5.95zM8 7H6v4H2v2h4v4h2v-4h4v-2H8V7z"></path></g>
<g id="exposure-zero"><path d="M16.14 12.5c0 1-.1 1.85-.3 2.55-.2.7-.48 1.27-.83 1.7-.36.44-.79.75-1.3.95-.51.2-1.07.3-1.7.3-.62 0-1.18-.1-1.69-.3-.51-.2-.95-.51-1.31-.95-.36-.44-.65-1.01-.85-1.7-.2-.7-.3-1.55-.3-2.55v-2.04c0-1 .1-1.85.3-2.55.2-.7.48-1.26.84-1.69.36-.43.8-.74 1.31-.93C10.81 5.1 11.38 5 12 5c.63 0 1.19.1 1.7.29.51.19.95.5 1.31.93.36.43.64.99.84 1.69.2.7.3 1.54.3 2.55v2.04zm-2.11-2.36c0-.64-.05-1.18-.13-1.62-.09-.44-.22-.79-.4-1.06-.17-.27-.39-.46-.64-.58-.25-.13-.54-.19-.86-.19-.32 0-.61.06-.86.18s-.47.31-.64.58c-.17.27-.31.62-.4 1.06s-.13.98-.13 1.62v2.67c0 .64.05 1.18.14 1.62.09.45.23.81.4 1.09s.39.48.64.61.54.19.87.19c.33 0 .62-.06.87-.19s.46-.33.63-.61c.17-.28.3-.64.39-1.09.09-.45.13-.99.13-1.62v-2.66z"></path></g>
<g id="filter"><path d="M15.96 10.29l-2.75 3.54-1.96-2.36L8.5 15h11l-3.54-4.71zM3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14z"></path></g>
<g id="filter-1"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm11 10h2V5h-4v2h2v8zm7-14H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14z"></path></g>
<g id="filter-2"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14zm-4-4h-4v-2h2c1.1 0 2-.89 2-2V7c0-1.11-.9-2-2-2h-4v2h4v2h-2c-1.1 0-2 .89-2 2v4h6v-2z"></path></g>
<g id="filter-3"><path d="M21 1H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14zM3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm14 8v-1.5c0-.83-.67-1.5-1.5-1.5.83 0 1.5-.67 1.5-1.5V7c0-1.11-.9-2-2-2h-4v2h4v2h-2v2h2v2h-4v2h4c1.1 0 2-.89 2-2z"></path></g>
<g id="filter-4"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm12 10h2V5h-2v4h-2V5h-2v6h4v4zm6-14H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14z"></path></g>
<g id="filter-5"><path d="M21 1H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14zM3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm14 8v-2c0-1.11-.9-2-2-2h-2V7h4V5h-6v6h4v2h-4v2h4c1.1 0 2-.89 2-2z"></path></g>
<g id="filter-6"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14zm-8-2h2c1.1 0 2-.89 2-2v-2c0-1.11-.9-2-2-2h-2V7h4V5h-4c-1.1 0-2 .89-2 2v6c0 1.11.9 2 2 2zm0-4h2v2h-2v-2z"></path></g>
<g id="filter-7"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14zm-8-2l4-8V5h-6v2h4l-4 8h2z"></path></g>
<g id="filter-8"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14zm-8-2h2c1.1 0 2-.89 2-2v-1.5c0-.83-.67-1.5-1.5-1.5.83 0 1.5-.67 1.5-1.5V7c0-1.11-.9-2-2-2h-2c-1.1 0-2 .89-2 2v1.5c0 .83.67 1.5 1.5 1.5-.83 0-1.5.67-1.5 1.5V13c0 1.11.9 2 2 2zm0-8h2v2h-2V7zm0 4h2v2h-2v-2z"></path></g>
<g id="filter-9"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14zM15 5h-2c-1.1 0-2 .89-2 2v2c0 1.11.9 2 2 2h2v2h-4v2h4c1.1 0 2-.89 2-2V7c0-1.11-.9-2-2-2zm0 4h-2V7h2v2z"></path></g>
<g id="filter-9-plus"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm11 7V8c0-1.11-.9-2-2-2h-1c-1.1 0-2 .89-2 2v1c0 1.11.9 2 2 2h1v1H9v2h3c1.1 0 2-.89 2-2zm-3-3V8h1v1h-1zm10-8H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 8h-2V7h-2v2h-2v2h2v2h2v-2h2v6H7V3h14v6z"></path></g>
<g id="filter-b-and-w"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16l-7-8v8H5l7-8V5h7v14z"></path></g>
<g id="filter-center-focus"><path d="M5 15H3v4c0 1.1.9 2 2 2h4v-2H5v-4zM5 5h4V3H5c-1.1 0-2 .9-2 2v4h2V5zm14-2h-4v2h4v4h2V5c0-1.1-.9-2-2-2zm0 16h-4v2h4c1.1 0 2-.9 2-2v-4h-2v4zM12 9c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"></path></g>
<g id="filter-drama"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.61 5.64 5.36 8.04 2.35 8.36 0 10.9 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4h2c0-2.76-1.86-5.08-4.4-5.78C8.61 6.88 10.2 6 12 6c3.03 0 5.5 2.47 5.5 5.5v.5H19c1.65 0 3 1.35 3 3s-1.35 3-3 3z"></path></g>
<g id="filter-frames"><path d="M20 4h-4l-4-4-4 4H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H4V6h4.52l3.52-3.5L15.52 6H20v14zM18 8H6v10h12"></path></g>
<g id="filter-hdr"><path d="M14 6l-3.75 5 2.85 3.8-1.6 1.2C9.81 13.75 7 10 7 10l-6 8h22L14 6z"></path></g>
<g id="filter-none"><path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14z"></path></g>
<g id="filter-tilt-shift"><path d="M11 4.07V2.05c-2.01.2-3.84 1-5.32 2.21L7.1 5.69c1.11-.86 2.44-1.44 3.9-1.62zm7.32.19C16.84 3.05 15.01 2.25 13 2.05v2.02c1.46.18 2.79.76 3.9 1.62l1.42-1.43zM19.93 11h2.02c-.2-2.01-1-3.84-2.21-5.32L18.31 7.1c.86 1.11 1.44 2.44 1.62 3.9zM5.69 7.1L4.26 5.68C3.05 7.16 2.25 8.99 2.05 11h2.02c.18-1.46.76-2.79 1.62-3.9zM4.07 13H2.05c.2 2.01 1 3.84 2.21 5.32l1.43-1.43c-.86-1.1-1.44-2.43-1.62-3.89zM15 12c0-1.66-1.34-3-3-3s-3 1.34-3 3 1.34 3 3 3 3-1.34 3-3zm3.31 4.9l1.43 1.43c1.21-1.48 2.01-3.32 2.21-5.32h-2.02c-.18 1.45-.76 2.78-1.62 3.89zM13 19.93v2.02c2.01-.2 3.84-1 5.32-2.21l-1.43-1.43c-1.1.86-2.43 1.44-3.89 1.62zm-7.32-.19C7.16 20.95 9 21.75 11 21.95v-2.02c-1.46-.18-2.79-.76-3.9-1.62l-1.42 1.43z"></path></g>
<g id="filter-vintage"><path d="M18.7 12.4c-.28-.16-.57-.29-.86-.4.29-.11.58-.24.86-.4 1.92-1.11 2.99-3.12 3-5.19-1.79-1.03-4.07-1.11-6 0-.28.16-.54.35-.78.54.05-.31.08-.63.08-.95 0-2.22-1.21-4.15-3-5.19C10.21 1.85 9 3.78 9 6c0 .32.03.64.08.95-.24-.2-.5-.39-.78-.55-1.92-1.11-4.2-1.03-6 0 0 2.07 1.07 4.08 3 5.19.28.16.57.29.86.4-.29.11-.58.24-.86.4-1.92 1.11-2.99 3.12-3 5.19 1.79 1.03 4.07 1.11 6 0 .28-.16.54-.35.78-.54-.05.32-.08.64-.08.96 0 2.22 1.21 4.15 3 5.19 1.79-1.04 3-2.97 3-5.19 0-.32-.03-.64-.08-.95.24.2.5.38.78.54 1.92 1.11 4.2 1.03 6 0-.01-2.07-1.08-4.08-3-5.19zM12 16c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z"></path></g>
<g id="flare"><path d="M7 11H1v2h6v-2zm2.17-3.24L7.05 5.64 5.64 7.05l2.12 2.12 1.41-1.41zM13 1h-2v6h2V1zm5.36 6.05l-1.41-1.41-2.12 2.12 1.41 1.41 2.12-2.12zM17 11v2h6v-2h-6zm-5-2c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3zm2.83 7.24l2.12 2.12 1.41-1.41-2.12-2.12-1.41 1.41zm-9.19.71l1.41 1.41 2.12-2.12-1.41-1.41-2.12 2.12zM11 23h2v-6h-2v6z"></path></g>
<g id="flash-auto"><path d="M3 2v12h3v9l7-12H9l4-9H3zm16 0h-2l-3.2 9h1.9l.7-2h3.2l.7 2h1.9L19 2zm-2.15 5.65L18 4l1.15 3.65h-2.3z"></path></g>
<g id="flash-off"><path d="M3.27 3L2 4.27l5 5V13h3v9l3.58-6.14L17.73 20 19 18.73 3.27 3zM17 10h-4l4-8H7v2.18l8.46 8.46L17 10z"></path></g>
<g id="flash-on"><path d="M7 2v11h3v9l7-12h-4l4-8z"></path></g>
<g id="flip"><path d="M15 21h2v-2h-2v2zm4-12h2V7h-2v2zM3 5v14c0 1.1.9 2 2 2h4v-2H5V5h4V3H5c-1.1 0-2 .9-2 2zm16-2v2h2c0-1.1-.9-2-2-2zm-8 20h2V1h-2v22zm8-6h2v-2h-2v2zM15 5h2V3h-2v2zm4 8h2v-2h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2z"></path></g>
<g id="gradient"><path d="M11 9h2v2h-2zm-2 2h2v2H9zm4 0h2v2h-2zm2-2h2v2h-2zM7 9h2v2H7zm12-6H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 18H7v-2h2v2zm4 0h-2v-2h2v2zm4 0h-2v-2h2v2zm2-7h-2v2h2v2h-2v-2h-2v2h-2v-2h-2v2H9v-2H7v2H5v-2h2v-2H5V5h14v6z"></path></g>
<g id="grain"><path d="M10 12c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zM6 8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12-8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-4 8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm4-4c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-4-4c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-4-4c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"></path></g>
<g id="grid-off"><path d="M8 4v1.45l2 2V4h4v4h-3.45l2 2H14v1.45l2 2V10h4v4h-3.45l2 2H20v1.45l2 2V4c0-1.1-.9-2-2-2H4.55l2 2H8zm8 0h4v4h-4V4zM1.27 1.27L0 2.55l2 2V20c0 1.1.9 2 2 2h15.46l2 2 1.27-1.27L1.27 1.27zM10 12.55L11.45 14H10v-1.45zm-6-6L5.45 8H4V6.55zM8 20H4v-4h4v4zm0-6H4v-4h3.45l.55.55V14zm6 6h-4v-4h3.45l.55.54V20zm2 0v-1.46L17.46 20H16z"></path></g>
<g id="grid-on"><path d="M20 2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM8 20H4v-4h4v4zm0-6H4v-4h4v4zm0-6H4V4h4v4zm6 12h-4v-4h4v4zm0-6h-4v-4h4v4zm0-6h-4V4h4v4zm6 12h-4v-4h4v4zm0-6h-4v-4h4v4zm0-6h-4V4h4v4z"></path></g>
<g id="hdr-off"><path d="M17.5 15v-2h1.1l.9 2H21l-.9-2.1c.5-.2.9-.8.9-1.4v-1c0-.8-.7-1.5-1.5-1.5H16v4.9l1.1 1.1h.4zm0-4.5h2v1h-2v-1zm-4.5 0v.4l1.5 1.5v-1.9c0-.8-.7-1.5-1.5-1.5h-1.9l1.5 1.5h.4zm-3.5-1l-7-7-1.1 1L6.9 9h-.4v2h-2V9H3v6h1.5v-2.5h2V15H8v-4.9l1.5 1.5V15h3.4l7.6 7.6 1.1-1.1-12.1-12z"></path></g>
<g id="hdr-on"><path d="M21 11.5v-1c0-.8-.7-1.5-1.5-1.5H16v6h1.5v-2h1.1l.9 2H21l-.9-2.1c.5-.3.9-.8.9-1.4zm-1.5 0h-2v-1h2v1zm-13-.5h-2V9H3v6h1.5v-2.5h2V15H8V9H6.5v2zM13 9H9.5v6H13c.8 0 1.5-.7 1.5-1.5v-3c0-.8-.7-1.5-1.5-1.5zm0 4.5h-2v-3h2v3z"></path></g>
<g id="hdr-strong"><path d="M17 6c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zM5 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"></path></g>
<g id="hdr-weak"><path d="M5 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm12-2c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm0 10c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z"></path></g>
<g id="healing"><path d="M17.73 12.02l3.98-3.98c.39-.39.39-1.02 0-1.41l-4.34-4.34c-.39-.39-1.02-.39-1.41 0l-3.98 3.98L8 2.29C7.8 2.1 7.55 2 7.29 2c-.25 0-.51.1-.7.29L2.25 6.63c-.39.39-.39 1.02 0 1.41l3.98 3.98L2.25 16c-.39.39-.39 1.02 0 1.41l4.34 4.34c.39.39 1.02.39 1.41 0l3.98-3.98 3.98 3.98c.2.2.45.29.71.29.26 0 .51-.1.71-.29l4.34-4.34c.39-.39.39-1.02 0-1.41l-3.99-3.98zM12 9c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm-4.71 1.96L3.66 7.34l3.63-3.63 3.62 3.62-3.62 3.63zM10 13c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm2 2c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm2-4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm2.66 9.34l-3.63-3.62 3.63-3.63 3.62 3.62-3.62 3.63z"></path></g>
<g id="image"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"></path></g>
<g id="image-aspect-ratio"><path d="M16 10h-2v2h2v-2zm0 4h-2v2h2v-2zm-8-4H6v2h2v-2zm4 0h-2v2h2v-2zm8-6H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H4V6h16v12z"></path></g>
<g id="iso"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM5.5 7.5h2v-2H9v2h2V9H9v2H7.5V9h-2V7.5zM19 19H5L19 5v14zm-2-2v-1.5h-5V17h5z"></path></g>
<g id="landscape"><path d="M14 6l-3.75 5 2.85 3.8-1.6 1.2C9.81 13.75 7 10 7 10l-6 8h22L14 6z"></path></g>
<g id="leak-add"><path d="M6 3H3v3c1.66 0 3-1.34 3-3zm8 0h-2c0 4.97-4.03 9-9 9v2c6.08 0 11-4.93 11-11zm-4 0H8c0 2.76-2.24 5-5 5v2c3.87 0 7-3.13 7-7zm0 18h2c0-4.97 4.03-9 9-9v-2c-6.07 0-11 4.93-11 11zm8 0h3v-3c-1.66 0-3 1.34-3 3zm-4 0h2c0-2.76 2.24-5 5-5v-2c-3.87 0-7 3.13-7 7z"></path></g>
<g id="leak-remove"><path d="M10 3H8c0 .37-.04.72-.12 1.06l1.59 1.59C9.81 4.84 10 3.94 10 3zM3 4.27l2.84 2.84C5.03 7.67 4.06 8 3 8v2c1.61 0 3.09-.55 4.27-1.46L8.7 9.97C7.14 11.24 5.16 12 3 12v2c2.71 0 5.19-.99 7.11-2.62l2.5 2.5C10.99 15.81 10 18.29 10 21h2c0-2.16.76-4.14 2.03-5.69l1.43 1.43C14.55 17.91 14 19.39 14 21h2c0-1.06.33-2.03.89-2.84L19.73 21 21 19.73 4.27 3 3 4.27zM14 3h-2c0 1.5-.37 2.91-1.02 4.16l1.46 1.46C13.42 6.98 14 5.06 14 3zm5.94 13.12c.34-.08.69-.12 1.06-.12v-2c-.94 0-1.84.19-2.66.52l1.6 1.6zm-4.56-4.56l1.46 1.46C18.09 12.37 19.5 12 21 12v-2c-2.06 0-3.98.58-5.62 1.56z"></path></g>
<g id="lens"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"></path></g>
<g id="linked-camera"><circle cx="12" cy="14" r="3.2"></circle><path d="M16 3.33c2.58 0 4.67 2.09 4.67 4.67H22c0-3.31-2.69-6-6-6v1.33M16 6c1.11 0 2 .89 2 2h1.33c0-1.84-1.49-3.33-3.33-3.33V6"></path><path d="M17 9c0-1.11-.89-2-2-2V4H9L7.17 6H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V9h-5zm-5 10c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"></path></g>
<g id="looks"><path d="M12 10c-3.86 0-7 3.14-7 7h2c0-2.76 2.24-5 5-5s5 2.24 5 5h2c0-3.86-3.14-7-7-7zm0-4C5.93 6 1 10.93 1 17h2c0-4.96 4.04-9 9-9s9 4.04 9 9h2c0-6.07-4.93-11-11-11z"></path></g>
<g id="looks-3"><path d="M19.01 3h-14c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 7.5c0 .83-.67 1.5-1.5 1.5.83 0 1.5.67 1.5 1.5V15c0 1.11-.9 2-2 2h-4v-2h4v-2h-2v-2h2V9h-4V7h4c1.1 0 2 .89 2 2v1.5z"></path></g>
<g id="looks-4"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 14h-2v-4H9V7h2v4h2V7h2v10z"></path></g>
<g id="looks-5"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 6h-4v2h2c1.1 0 2 .89 2 2v2c0 1.11-.9 2-2 2H9v-2h4v-2H9V7h6v2z"></path></g>
<g id="looks-6"><path d="M11 15h2v-2h-2v2zm8-12H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 6h-4v2h2c1.1 0 2 .89 2 2v2c0 1.11-.9 2-2 2h-2c-1.1 0-2-.89-2-2V9c0-1.11.9-2 2-2h4v2z"></path></g>
<g id="looks-one"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14h-2V9h-2V7h4v10z"></path></g>
<g id="looks-two"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4 8c0 1.11-.9 2-2 2h-2v2h4v2H9v-4c0-1.11.9-2 2-2h2V9H9V7h4c1.1 0 2 .89 2 2v2z"></path></g>
<g id="loupe"><path d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.49 2 2 6.49 2 12s4.49 10 10 10h8c1.1 0 2-.9 2-2v-8c0-5.51-4.49-10-10-10zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></g>
<g id="monochrome-photos"><path d="M20 5h-3.2L15 3H9L7.2 5H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 14h-8v-1c-2.8 0-5-2.2-5-5s2.2-5 5-5V7h8v12zm-3-6c0-2.8-2.2-5-5-5v1.8c1.8 0 3.2 1.4 3.2 3.2s-1.4 3.2-3.2 3.2V18c2.8 0 5-2.2 5-5zm-8.2 0c0 1.8 1.4 3.2 3.2 3.2V9.8c-1.8 0-3.2 1.4-3.2 3.2z"></path></g>
<g id="movie-creation"><path d="M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4h-4z"></path></g>
<g id="movie-filter"><path d="M18 4l2 3h-3l-2-3h-2l2 3h-3l-2-3H8l2 3H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4h-4zm-6.75 11.25L10 18l-1.25-2.75L6 14l2.75-1.25L10 10l1.25 2.75L14 14l-2.75 1.25zm5.69-3.31L16 14l-.94-2.06L13 11l2.06-.94L16 8l.94 2.06L19 11l-2.06.94z"></path></g>
<g id="music-note"><path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"></path></g>
<g id="nature"><path d="M13 16.12c3.47-.41 6.17-3.36 6.17-6.95 0-3.87-3.13-7-7-7s-7 3.13-7 7c0 3.47 2.52 6.34 5.83 6.89V20H5v2h14v-2h-6v-3.88z"></path></g>
<g id="nature-people"><path d="M22.17 9.17c0-3.87-3.13-7-7-7s-7 3.13-7 7c0 3.47 2.52 6.34 5.83 6.89V20H6v-3h1v-4c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1v4h1v5h16v-2h-3v-3.88c3.47-.41 6.17-3.36 6.17-6.95zM4.5 11c.83 0 1.5-.67 1.5-1.5S5.33 8 4.5 8 3 8.67 3 9.5 3.67 11 4.5 11z"></path></g>
<g id="navigate-before"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></path></g>
<g id="navigate-next"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"></path></g>
<g id="palette"><path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"></path></g>
<g id="panorama"><path d="M23 18V6c0-1.1-.9-2-2-2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2zM8.5 12.5l2.5 3.01L14.5 11l4.5 6H5l3.5-4.5z"></path></g>
<g id="panorama-fish-eye"><path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></g>
<g id="panorama-horizontal"><path d="M20 6.54v10.91c-2.6-.77-5.28-1.16-8-1.16-2.72 0-5.4.39-8 1.16V6.54c2.6.77 5.28 1.16 8 1.16 2.72.01 5.4-.38 8-1.16M21.43 4c-.1 0-.2.02-.31.06C18.18 5.16 15.09 5.7 12 5.7c-3.09 0-6.18-.55-9.12-1.64-.11-.04-.22-.06-.31-.06-.34 0-.57.23-.57.63v14.75c0 .39.23.62.57.62.1 0 .2-.02.31-.06 2.94-1.1 6.03-1.64 9.12-1.64 3.09 0 6.18.55 9.12 1.64.11.04.21.06.31.06.33 0 .57-.23.57-.63V4.63c0-.4-.24-.63-.57-.63z"></path></g>
<g id="panorama-vertical"><path d="M19.94 21.12c-1.1-2.94-1.64-6.03-1.64-9.12 0-3.09.55-6.18 1.64-9.12.04-.11.06-.22.06-.31 0-.34-.23-.57-.63-.57H4.63c-.4 0-.63.23-.63.57 0 .1.02.2.06.31C5.16 5.82 5.71 8.91 5.71 12c0 3.09-.55 6.18-1.64 9.12-.05.11-.07.22-.07.31 0 .33.23.57.63.57h14.75c.39 0 .63-.24.63-.57-.01-.1-.03-.2-.07-.31zM6.54 20c.77-2.6 1.16-5.28 1.16-8 0-2.72-.39-5.4-1.16-8h10.91c-.77 2.6-1.16 5.28-1.16 8 0 2.72.39 5.4 1.16 8H6.54z"></path></g>
<g id="panorama-wide-angle"><path d="M12 6c2.45 0 4.71.2 7.29.64.47 1.78.71 3.58.71 5.36 0 1.78-.24 3.58-.71 5.36-2.58.44-4.84.64-7.29.64s-4.71-.2-7.29-.64C4.24 15.58 4 13.78 4 12c0-1.78.24-3.58.71-5.36C7.29 6.2 9.55 6 12 6m0-2c-2.73 0-5.22.24-7.95.72l-.93.16-.25.9C2.29 7.85 2 9.93 2 12s.29 4.15.87 6.22l.25.89.93.16c2.73.49 5.22.73 7.95.73s5.22-.24 7.95-.72l.93-.16.25-.89c.58-2.08.87-4.16.87-6.23s-.29-4.15-.87-6.22l-.25-.89-.93-.16C17.22 4.24 14.73 4 12 4z"></path></g>
<g id="photo"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"></path></g>
<g id="photo-album"><path d="M18 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 4h5v8l-2.5-1.5L6 12V4zm0 15l3-3.86 2.14 2.58 3-3.86L18 19H6z"></path></g>
<g id="photo-camera"><circle cx="12" cy="12" r="3.2"></circle><path d="M9 2L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"></path></g>
<g id="photo-filter"><path d="M19.02 10v9H5V5h9V3H5.02c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-9h-2zM17 10l.94-2.06L20 7l-2.06-.94L17 4l-.94 2.06L14 7l2.06.94zm-3.75.75L12 8l-1.25 2.75L8 12l2.75 1.25L12 16l1.25-2.75L16 12z"></path></g>
<g id="photo-library"><path d="M22 16V4c0-1.1-.9-2-2-2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2zm-11-4l2.03 2.71L16 11l4 5H8l3-4zM2 6v14c0 1.1.9 2 2 2h14v-2H4V6H2z"></path></g>
<g id="photo-size-select-actual"><path d="M21 3H3C2 3 1 4 1 5v14c0 1.1.9 2 2 2h18c1 0 2-1 2-2V5c0-1-1-2-2-2zM5 17l3.5-4.5 2.5 3.01L14.5 11l4.5 6H5z"></path></g>
<g id="photo-size-select-large"><path d="M21 15h2v2h-2v-2zm0-4h2v2h-2v-2zm2 8h-2v2c1 0 2-1 2-2zM13 3h2v2h-2V3zm8 4h2v2h-2V7zm0-4v2h2c0-1-1-2-2-2zM1 7h2v2H1V7zm16-4h2v2h-2V3zm0 16h2v2h-2v-2zM3 3C2 3 1 4 1 5h2V3zm6 0h2v2H9V3zM5 3h2v2H5V3zm-4 8v8c0 1.1.9 2 2 2h12V11H1zm2 8l2.5-3.21 1.79 2.15 2.5-3.22L13 19H3z"></path></g>
<g id="photo-size-select-small"><path d="M23 15h-2v2h2v-2zm0-4h-2v2h2v-2zm0 8h-2v2c1 0 2-1 2-2zM15 3h-2v2h2V3zm8 4h-2v2h2V7zm-2-4v2h2c0-1-1-2-2-2zM3 21h8v-6H1v4c0 1.1.9 2 2 2zM3 7H1v2h2V7zm12 12h-2v2h2v-2zm4-16h-2v2h2V3zm0 16h-2v2h2v-2zM3 3C2 3 1 4 1 5h2V3zm0 8H1v2h2v-2zm8-8H9v2h2V3zM7 3H5v2h2V3z"></path></g>
<g id="picture-as-pdf"><path d="M20 2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8.5 7.5c0 .83-.67 1.5-1.5 1.5H9v2H7.5V7H10c.83 0 1.5.67 1.5 1.5v1zm5 2c0 .83-.67 1.5-1.5 1.5h-2.5V7H15c.83 0 1.5.67 1.5 1.5v3zm4-3H19v1h1.5V11H19v2h-1.5V7h3v1.5zM9 9.5h1v-1H9v1zM4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm10 5.5h1v-3h-1v3z"></path></g>
<g id="portrait"><path d="M12 12.25c1.24 0 2.25-1.01 2.25-2.25S13.24 7.75 12 7.75 9.75 8.76 9.75 10s1.01 2.25 2.25 2.25zm4.5 4c0-1.5-3-2.25-4.5-2.25s-4.5.75-4.5 2.25V17h9v-.75zM19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z"></path></g>
<g id="remove-red-eye"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"></path></g>
<g id="rotate-90-degrees-ccw"><path d="M7.34 6.41L.86 12.9l6.49 6.48 6.49-6.48-6.5-6.49zM3.69 12.9l3.66-3.66L11 12.9l-3.66 3.66-3.65-3.66zm15.67-6.26C17.61 4.88 15.3 4 13 4V.76L8.76 5 13 9.24V6c1.79 0 3.58.68 4.95 2.05 2.73 2.73 2.73 7.17 0 9.9C16.58 19.32 14.79 20 13 20c-.97 0-1.94-.21-2.84-.61l-1.49 1.49C10.02 21.62 11.51 22 13 22c2.3 0 4.61-.88 6.36-2.64 3.52-3.51 3.52-9.21 0-12.72z"></path></g>
<g id="rotate-left"><path d="M7.11 8.53L5.7 7.11C4.8 8.27 4.24 9.61 4.07 11h2.02c.14-.87.49-1.72 1.02-2.47zM6.09 13H4.07c.17 1.39.72 2.73 1.62 3.89l1.41-1.42c-.52-.75-.87-1.59-1.01-2.47zm1.01 5.32c1.16.9 2.51 1.44 3.9 1.61V17.9c-.87-.15-1.71-.49-2.46-1.03L7.1 18.32zM13 4.07V1L8.45 5.55 13 10V6.09c2.84.48 5 2.94 5 5.91s-2.16 5.43-5 5.91v2.02c3.95-.49 7-3.85 7-7.93s-3.05-7.44-7-7.93z"></path></g>
<g id="rotate-right"><path d="M15.55 5.55L11 1v3.07C7.06 4.56 4 7.92 4 12s3.05 7.44 7 7.93v-2.02c-2.84-.48-5-2.94-5-5.91s2.16-5.43 5-5.91V10l4.55-4.45zM19.93 11c-.17-1.39-.72-2.73-1.62-3.89l-1.42 1.42c.54.75.88 1.6 1.02 2.47h2.02zM13 17.9v2.02c1.39-.17 2.74-.71 3.9-1.61l-1.44-1.44c-.75.54-1.59.89-2.46 1.03zm3.89-2.42l1.42 1.41c.9-1.16 1.45-2.5 1.62-3.89h-2.02c-.14.87-.48 1.72-1.02 2.48z"></path></g>
<g id="slideshow"><path d="M10 8v8l5-4-5-4zm9-5H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z"></path></g>
<g id="straighten"><path d="M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H3V8h2v4h2V8h2v4h2V8h2v4h2V8h2v4h2V8h2v8z"></path></g>
<g id="style"><path d="M2.53 19.65l1.34.56v-9.03l-2.43 5.86c-.41 1.02.08 2.19 1.09 2.61zm19.5-3.7L17.07 3.98c-.31-.75-1.04-1.21-1.81-1.23-.26 0-.53.04-.79.15L7.1 5.95c-.75.31-1.21 1.03-1.23 1.8-.01.27.04.54.15.8l4.96 11.97c.31.76 1.05 1.22 1.83 1.23.26 0 .52-.05.77-.15l7.36-3.05c1.02-.42 1.51-1.59 1.09-2.6zM7.88 8.75c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-2 11c0 1.1.9 2 2 2h1.45l-3.45-8.34v6.34z"></path></g>
<g id="switch-camera"><path d="M20 4h-3.17L15 2H9L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-5 11.5V13H9v2.5L5.5 12 9 8.5V11h6V8.5l3.5 3.5-3.5 3.5z"></path></g>
<g id="switch-video"><path d="M18 9.5V6c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h14c.55 0 1-.45 1-1v-3.5l4 4v-13l-4 4zm-5 6V13H7v2.5L3.5 12 7 8.5V11h6V8.5l3.5 3.5-3.5 3.5z"></path></g>
<g id="tag-faces"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z"></path></g>
<g id="texture"><path d="M19.51 3.08L3.08 19.51c.09.34.27.65.51.9.25.24.56.42.9.51L20.93 4.49c-.19-.69-.73-1.23-1.42-1.41zM11.88 3L3 11.88v2.83L14.71 3h-2.83zM5 3c-1.1 0-2 .9-2 2v2l4-4H5zm14 18c.55 0 1.05-.22 1.41-.59.37-.36.59-.86.59-1.41v-2l-4 4h2zm-9.71 0h2.83L21 12.12V9.29L9.29 21z"></path></g>
<g id="timelapse"><path d="M16.24 7.76C15.07 6.59 13.54 6 12 6v6l-4.24 4.24c2.34 2.34 6.14 2.34 8.49 0 2.34-2.34 2.34-6.14-.01-8.48zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"></path></g>
<g id="timer"><path d="M15 1H9v2h6V1zm-4 13h2V8h-2v6zm8.03-6.61l1.42-1.42c-.43-.51-.9-.99-1.41-1.41l-1.42 1.42C16.07 4.74 14.12 4 12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9 9-4.03 9-9c0-2.12-.74-4.07-1.97-5.61zM12 20c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z"></path></g>
<g id="timer-10"><path d="M0 7.72V9.4l3-1V18h2V6h-.25L0 7.72zm23.78 6.65c-.14-.28-.35-.53-.63-.74-.28-.21-.61-.39-1.01-.53s-.85-.27-1.35-.38c-.35-.07-.64-.15-.87-.23-.23-.08-.41-.16-.55-.25-.14-.09-.23-.19-.28-.3-.05-.11-.08-.24-.08-.39 0-.14.03-.28.09-.41.06-.13.15-.25.27-.34.12-.1.27-.18.45-.24s.4-.09.64-.09c.25 0 .47.04.66.11.19.07.35.17.48.29.13.12.22.26.29.42.06.16.1.32.1.49h1.95c0-.39-.08-.75-.24-1.09-.16-.34-.39-.63-.69-.88-.3-.25-.66-.44-1.09-.59C21.49 9.07 21 9 20.46 9c-.51 0-.98.07-1.39.21-.41.14-.77.33-1.06.57-.29.24-.51.52-.67.84-.16.32-.23.65-.23 1.01s.08.69.23.96c.15.28.36.52.64.73.27.21.6.38.98.53.38.14.81.26 1.27.36.39.08.71.17.95.26s.43.19.57.29c.13.1.22.22.27.34.05.12.07.25.07.39 0 .32-.13.57-.4.77-.27.2-.66.29-1.17.29-.22 0-.43-.02-.64-.08-.21-.05-.4-.13-.56-.24-.17-.11-.3-.26-.41-.44-.11-.18-.17-.41-.18-.67h-1.89c0 .36.08.71.24 1.05.16.34.39.65.7.93.31.27.69.49 1.15.66.46.17.98.25 1.58.25.53 0 1.01-.06 1.44-.19.43-.13.8-.31 1.11-.54.31-.23.54-.51.71-.83.17-.32.25-.67.25-1.06-.02-.4-.09-.74-.24-1.02zm-9.96-7.32c-.34-.4-.75-.7-1.23-.88-.47-.18-1.01-.27-1.59-.27-.58 0-1.11.09-1.59.27-.48.18-.89.47-1.23.88-.34.41-.6.93-.79 1.59-.18.65-.28 1.45-.28 2.39v1.92c0 .94.09 1.74.28 2.39.19.66.45 1.19.8 1.6.34.41.75.71 1.23.89.48.18 1.01.28 1.59.28.59 0 1.12-.09 1.59-.28.48-.18.88-.48 1.22-.89.34-.41.6-.94.78-1.6.18-.65.28-1.45.28-2.39v-1.92c0-.94-.09-1.74-.28-2.39-.18-.66-.44-1.19-.78-1.59zm-.92 6.17c0 .6-.04 1.11-.12 1.53-.08.42-.2.76-.36 1.02-.16.26-.36.45-.59.57-.23.12-.51.18-.82.18-.3 0-.58-.06-.82-.18s-.44-.31-.6-.57c-.16-.26-.29-.6-.38-1.02-.09-.42-.13-.93-.13-1.53v-2.5c0-.6.04-1.11.13-1.52.09-.41.21-.74.38-1 .16-.25.36-.43.6-.55.24-.11.51-.17.81-.17.31 0 .58.06.81.17.24.11.44.29.6.55.16.25.29.58.37.99.08.41.13.92.13 1.52v2.51z"></path></g>
<g id="timer-3"><path d="M11.61 12.97c-.16-.24-.36-.46-.62-.65-.25-.19-.56-.35-.93-.48.3-.14.57-.3.8-.5.23-.2.42-.41.57-.64.15-.23.27-.46.34-.71.08-.24.11-.49.11-.73 0-.55-.09-1.04-.28-1.46-.18-.42-.44-.77-.78-1.06-.33-.28-.73-.5-1.2-.64-.45-.13-.97-.2-1.53-.2-.55 0-1.06.08-1.52.24-.47.17-.87.4-1.2.69-.33.29-.6.63-.78 1.03-.2.39-.29.83-.29 1.29h1.98c0-.26.05-.49.14-.69.09-.2.22-.38.38-.52.17-.14.36-.25.58-.33.22-.08.46-.12.73-.12.61 0 1.06.16 1.36.47.3.31.44.75.44 1.32 0 .27-.04.52-.12.74-.08.22-.21.41-.38.57-.17.16-.38.28-.63.37-.25.09-.55.13-.89.13H6.72v1.57H7.9c.34 0 .64.04.91.11.27.08.5.19.69.35.19.16.34.36.44.61.1.24.16.54.16.87 0 .62-.18 1.09-.53 1.42-.35.33-.84.49-1.45.49-.29 0-.56-.04-.8-.13-.24-.08-.44-.2-.61-.36-.17-.16-.3-.34-.39-.56-.09-.22-.14-.46-.14-.72H4.19c0 .55.11 1.03.32 1.45.21.42.5.77.86 1.05s.77.49 1.24.63.96.21 1.48.21c.57 0 1.09-.08 1.58-.23.49-.15.91-.38 1.26-.68.36-.3.64-.66.84-1.1.2-.43.3-.93.3-1.48 0-.29-.04-.58-.11-.86-.08-.25-.19-.51-.35-.76zm9.26 1.4c-.14-.28-.35-.53-.63-.74-.28-.21-.61-.39-1.01-.53s-.85-.27-1.35-.38c-.35-.07-.64-.15-.87-.23-.23-.08-.41-.16-.55-.25-.14-.09-.23-.19-.28-.3-.05-.11-.08-.24-.08-.39s.03-.28.09-.41c.06-.13.15-.25.27-.34.12-.1.27-.18.45-.24s.4-.09.64-.09c.25 0 .47.04.66.11.19.07.35.17.48.29.13.12.22.26.29.42.06.16.1.32.1.49h1.95c0-.39-.08-.75-.24-1.09-.16-.34-.39-.63-.69-.88-.3-.25-.66-.44-1.09-.59-.43-.15-.92-.22-1.46-.22-.51 0-.98.07-1.39.21-.41.14-.77.33-1.06.57-.29.24-.51.52-.67.84-.16.32-.23.65-.23 1.01s.08.68.23.96c.15.28.37.52.64.73.27.21.6.38.98.53.38.14.81.26 1.27.36.39.08.71.17.95.26s.43.19.57.29c.13.1.22.22.27.34.05.12.07.25.07.39 0 .32-.13.57-.4.77-.27.2-.66.29-1.17.29-.22 0-.43-.02-.64-.08-.21-.05-.4-.13-.56-.24-.17-.11-.3-.26-.41-.44-.11-.18-.17-.41-.18-.67h-1.89c0 .36.08.71.24 1.05.16.34.39.65.7.93.31.27.69.49 1.15.66.46.17.98.25 1.58.25.53 0 1.01-.06 1.44-.19.43-.13.8-.31 1.11-.54.31-.23.54-.51.71-.83.17-.32.25-.67.25-1.06-.02-.4-.09-.74-.24-1.02z"></path></g>
<g id="timer-off"><path d="M19.04 4.55l-1.42 1.42C16.07 4.74 14.12 4 12 4c-1.83 0-3.53.55-4.95 1.48l1.46 1.46C9.53 6.35 10.73 6 12 6c3.87 0 7 3.13 7 7 0 1.27-.35 2.47-.94 3.49l1.45 1.45C20.45 16.53 21 14.83 21 13c0-2.12-.74-4.07-1.97-5.61l1.42-1.42-1.41-1.42zM15 1H9v2h6V1zm-4 8.44l2 2V8h-2v1.44zM3.02 4L1.75 5.27 4.5 8.03C3.55 9.45 3 11.16 3 13c0 4.97 4.02 9 9 9 1.84 0 3.55-.55 4.98-1.5l2.5 2.5 1.27-1.27-7.71-7.71L3.02 4zM12 20c-3.87 0-7-3.13-7-7 0-1.28.35-2.48.95-3.52l9.56 9.56c-1.03.61-2.23.96-3.51.96z"></path></g>
<g id="tonality"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.94-.49-7-3.85-7-7.93s3.05-7.44 7-7.93v15.86zm2-15.86c1.03.13 2 .45 2.87.93H13v-.93zM13 7h5.24c.25.31.48.65.68 1H13V7zm0 3h6.74c.08.33.15.66.19 1H13v-1zm0 9.93V19h2.87c-.87.48-1.84.8-2.87.93zM18.24 17H13v-1h5.92c-.2.35-.43.69-.68 1zm1.5-3H13v-1h6.93c-.04.34-.11.67-.19 1z"></path></g>
<g id="transform"><path d="M22 18v-2H8V4h2L7 1 4 4h2v2H2v2h4v8c0 1.1.9 2 2 2h8v2h-2l3 3 3-3h-2v-2h4zM10 8h6v6h2V8c0-1.1-.9-2-2-2h-6v2z"></path></g>
<g id="tune"><path d="M3 17v2h6v-2H3zM3 5v2h10V5H3zm10 16v-2h8v-2h-8v-2h-2v6h2zM7 9v2H3v2h4v2h2V9H7zm14 4v-2H11v2h10zm-6-4h2V7h4V5h-4V3h-2v6z"></path></g>
<g id="view-comfy"><path d="M3 9h4V5H3v4zm0 5h4v-4H3v4zm5 0h4v-4H8v4zm5 0h4v-4h-4v4zM8 9h4V5H8v4zm5-4v4h4V5h-4zm5 9h4v-4h-4v4zM3 19h4v-4H3v4zm5 0h4v-4H8v4zm5 0h4v-4h-4v4zm5 0h4v-4h-4v4zm0-14v4h4V5h-4z"></path></g>
<g id="view-compact"><path d="M3 19h6v-7H3v7zm7 0h12v-7H10v7zM3 5v6h19V5H3z"></path></g>
<g id="vignette"><path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-9 15c-4.42 0-8-2.69-8-6s3.58-6 8-6 8 2.69 8 6-3.58 6-8 6z"></path></g>
<g id="wb-auto"><path d="M6.85 12.65h2.3L8 9l-1.15 3.65zM22 7l-1.2 6.29L19.3 7h-1.6l-1.49 6.29L15 7h-.76C12.77 5.17 10.53 4 8 4c-4.42 0-8 3.58-8 8s3.58 8 8 8c3.13 0 5.84-1.81 7.15-4.43l.1.43H17l1.5-6.1L20 16h1.75l2.05-9H22zm-11.7 9l-.7-2H6.4l-.7 2H3.8L7 7h2l3.2 9h-1.9z"></path></g>
<g id="wb-cloudy"><path d="M19.36 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.64-4.96z"></path></g>
<g id="wb-incandescent"><path d="M3.55 18.54l1.41 1.41 1.79-1.8-1.41-1.41-1.79 1.8zM11 22.45h2V19.5h-2v2.95zM4 10.5H1v2h3v-2zm11-4.19V1.5H9v4.81C7.21 7.35 6 9.28 6 11.5c0 3.31 2.69 6 6 6s6-2.69 6-6c0-2.22-1.21-4.15-3-5.19zm5 4.19v2h3v-2h-3zm-2.76 7.66l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4z"></path></g>
<g id="wb-iridescent"><path d="M5 14.5h14v-6H5v6zM11 .55V3.5h2V.55h-2zm8.04 2.5l-1.79 1.79 1.41 1.41 1.8-1.79-1.42-1.41zM13 22.45V19.5h-2v2.95h2zm7.45-3.91l-1.8-1.79-1.41 1.41 1.79 1.8 1.42-1.42zM3.55 4.46l1.79 1.79 1.41-1.41-1.79-1.79-1.41 1.41zm1.41 15.49l1.79-1.8-1.41-1.41-1.79 1.79 1.41 1.42z"></path></g>
<g id="wb-sunny"><path d="M6.76 4.84l-1.8-1.79-1.41 1.41 1.79 1.79 1.42-1.41zM4 10.5H1v2h3v-2zm9-9.95h-2V3.5h2V.55zm7.45 3.91l-1.41-1.41-1.79 1.79 1.41 1.41 1.79-1.79zm-3.21 13.7l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4zM20 10.5v2h3v-2h-3zm-8-5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm-1 16.95h2V19.5h-2v2.95zm-7.45-3.91l1.41 1.41 1.79-1.8-1.41-1.41-1.79 1.8z"></path></g>
</defs></svg>
</iron-iconset-svg>
<iron-iconset-svg size="24" name="swatch">
<svg><defs>
<g id="format-color-fill"><path d="M16.56 8.94L7.62 0 6.21 1.41l2.38 2.38-5.15 5.15c-.59.59-.59 1.54 0 2.12l5.5 5.5c.29.29.68.44 1.06.44s.77-.15 1.06-.44l5.5-5.5c.59-.58.59-1.53 0-2.12zM5.21 10L10 5.21 14.79 10H5.21zM19 11.5s-2 2.17-2 3.5c0 1.1.9 2 2 2s2-.9 2-2c0-1.33-2-3.5-2-3.5z"></path><path fill-opacity=".36" d="M0 20h24v4H0z"></path></g>
</defs></svg>
</iron-iconset-svg>
<dom-module id="paper-swatch-picker" assetpath="bower_components/paper-swatch-picker/">
  <template>
    <style>
      :host {
        display: inline-block;
        position: relative;
      }

      :host(:focus) {
        outline: none;
      }

      .color {
        box-sizing: border-box;
        width: var(--paper-swatch-picker-color-size, 20px);
        height: var(--paper-swatch-picker-color-size, 20px);
        display: inline-block;
        padding: 0;
        margin: 0;
        cursor: pointer;
        font-size: 0;
        position: relative;
      }

      /* If we just scale the paper-item when hovering, this will end up
       * adding scrollbars to the paper-listbox that are hard to get rid of.
       * An easy workaround is to use an :after pseudo element instead. */
      .color:after {
        @apply --layout-fit;
        background: currentColor;
        content: '';
        -webkit-transition: -webkit-transform 0.2s;
        transition: transform .2s;
        z-index: 0;
      }

      .color:hover:after, .color:focus:after {
        -webkit-transform: scale(1.3, 1.3);
        transform: scale(1.3, 1.3);
        outline: none;
        z-index: 1;
      }

      paper-icon-button {
        @apply --paper-swatch-picker-icon;
      }

      paper-item {
        margin: 0;
        padding: 0;
        min-height: 0;

        --paper-item-focused-before: {
          opacity: 0;
        };
      }

      paper-listbox {
        padding: 0;
        font-size: 0;
        overflow: hidden;
        @apply --layout-vertical;
        @apply --layout-wrap;
      }
    </style>

    <paper-menu-button vertical-align="[[verticalAlign]]" horizontal-align="[[horizontalAlign]]">
      <paper-icon-button id="iconButton" icon="[[icon]]" slot="dropdown-trigger" class="dropdown-trigger" alt="color picker" noink$="[[noink]]">
      </paper-icon-button>
      <paper-listbox slot="dropdown-content" class="dropdown-content" id="container">
        <template is="dom-repeat" items="[[colorList]]">
          <paper-item class="color">[[item]]</paper-item>
        </template>
      </paper-listbox>
    </paper-menu-button>
  </template>

  <script>
    Polymer({
      is: 'paper-swatch-picker',

      hostAttributes: {'tabindex': 0},

      listeners: {'paper-dropdown-open': '_onOpen', 'iron-select': '_onColorTap'},

      /**
       * Fired when a color has been selected
       *
       * @event color-picker-selected
       */

      properties: {
        /**
         * The selected color, as hex (i.e. #ffffff).
         * value.
         */
        color: {type: String, notify: true, observer: '_colorChanged'},

        /**
         * The colors to be displayed. By default, these are the Material Design
         * colors. This array is arranged by "generic color", so for example,
         * all the reds (from light to dark), then the pinks, then the blues, etc.
         * Depending on how many of these generic colors you have, you should
         * update the `columnCount` property.
         */
        colorList: {
          type: Array,
          value: function() {
            return this.defaultColors();
          },
          observer: '_colorListChanged'
        },

        /* The number of columns to display in the picker. This corresponds to
         * the number of generic colors (i.e. not counting the light/dark) variants
         * of a specific color) you are using in your `colorList`. For example,
         * the Material Design palette has 18 colors */
        columnCount: {type: Number, value: 18, observer: '_columnCountChanged'},

        /**
         * The orientation against which to align the menu dropdown
         * horizontally relative to the dropdown trigger.
         */
        horizontalAlign: {type: String, value: 'left', reflectToAttribute: true},

        /**
         * The orientation against which to align the menu dropdown
         * vertically relative to the dropdown trigger.
         */
        verticalAlign: {type: String, value: 'top', reflectToAttribute: true},

        /**
         * The name of the icon to use for the button used as a dropdown trigger.
         * The name should be of the form: `iconset_name:icon_name`.
         * You must manually import the icon/iconset you wish you use.
         */
        icon: {type: String, value: 'swatch:format-color-fill'},

        /**
         * If true, the color picker button will not produce a ripple effect when
         * interacted with via the pointer.
         */
        noink: {type: Boolean}
      },

      attached: function() {
        // Note: we won't actually render these color boxes unless the menu is
        // actually tapped.
        this._renderedColors = false;
        this._updateSize();
      },

      /**
       * Returns the default Material Design colors.
       *
       * @return {Array[string]}
       */
      defaultColors: function() {
        return [
          '#ffebee', '#ffcdd2', '#ef9a9a', '#e57373', '#ef5350', '#f44336',
          '#e53935', '#d32f2f', '#c62828', '#b71c1c', '#fce4ec', '#f8bbd0',
          '#f48fb1', '#f06292', '#ec407a', '#e91e63', '#d81b60', '#c2185b',
          '#ad1457', '#880e4f', '#f3e5f5', '#e1bee7', '#ce93d8', '#ba68c8',
          '#ab47bc', '#9c27b0', '#8e24aa', '#7b1fa2', '#6a1b9a', '#4a148c',
          '#ede7f6', '#d1c4e9', '#b39ddb', '#9575cd', '#7e57c2', '#673ab7',
          '#5e35b1', '#512da8', '#4527a0', '#311b92', '#e8eaf6', '#c5cae9',
          '#9fa8da', '#7986cb', '#5c6bc0', '#3f51b5', '#3949ab', '#303f9f',
          '#283593', '#1a237e', '#e3f2fd', '#bbdefb', '#90caf9', '#64b5f6',
          '#42a5f5', '#2196f3', '#1e88e5', '#1976d2', '#1565c0', '#0d47a1',
          '#e1f5fe', '#b3e5fc', '#81d4fa', '#4fc3f7', '#29b6f6', '#03a9f4',
          '#039be5', '#0288d1', '#0277bd', '#01579b', '#e0f7fa', '#b2ebf2',
          '#80deea', '#4dd0e1', '#26c6da', '#00bcd4', '#00acc1', '#0097a7',
          '#00838f', '#006064', '#e0f2f1', '#b2dfdb', '#80cbc4', '#4db6ac',
          '#26a69a', '#009688', '#00897b', '#00796b', '#00695c', '#004d40',
          '#e8f5e9', '#c8e6c9', '#a5d6a7', '#81c784', '#66bb6a', '#4caf50',
          '#43a047', '#388e3c', '#2e7d32', '#1b5e20', '#f1f8e9', '#dcedc8',
          '#c5e1a5', '#aed581', '#9ccc65', '#8bc34a', '#7cb342', '#689f38',
          '#558b2f', '#33691e', '#f9fbe7', '#f0f4c3', '#e6ee9c', '#dce775',
          '#d4e157', '#cddc39', '#c0ca33', '#afb42b', '#9e9d24', '#827717',
          '#fffde7', '#fff9c4', '#fff59d', '#fff176', '#ffee58', '#ffeb3b',
          '#fdd835', '#fbc02d', '#f9a825', '#f57f17', '#fff8e1', '#ffecb3',
          '#ffe082', '#ffd54f', '#ffca28', '#ffc107', '#ffb300', '#ffa000',
          '#ff8f00', '#ff6f00', '#fff3e0', '#ffe0b2', '#ffcc80', '#ffb74d',
          '#ffa726', '#ff9800', '#fb8c00', '#f57c00', '#ef6c00', '#e65100',
          '#fbe9e7', '#ffccbc', '#ffab91', '#ff8a65', '#ff7043', '#ff5722',
          '#f4511e', '#e64a19', '#d84315', '#bf360c', '#efebe9', '#d7ccc8',
          '#bcaaa4', '#a1887f', '#8d6e63', '#795548', '#6d4c41', '#5d4037',
          '#4e342e', '#3e2723', '#fafafa', '#f5f5f5', '#eeeeee', '#e0e0e0',
          '#bdbdbd', '#9e9e9e', '#757575', '#616161', '#424242', '#212121'
        ];
      },

      _onOpen: function() {
        // Fill in the colors if we haven't already.
        if (this._renderedColors)
          return;

        var colorBoxes = this.$.container.querySelectorAll('.color');
        for (var i = 0; i < colorBoxes.length; i++) {
          colorBoxes[i].style.color = colorBoxes[i].innerHTML;
        }
        this._renderedColors = true;
      },

      _addOverflowClass: function() {
        this.$.container.toggleClass('opened', true);
      },

      _removeOverflowClass: function() {
        this.$.container.toggleClass('opened', false);
      },

      _onColorTap: function(event) {
        var item = event.detail.item;
        // The inner `span` element has the background color;
        var color = item.style.color;

        // If it's in rgb format, convert it first.
        this.color = color.indexOf('rgb') === -1 ? color : this._rgbToHex(color);
        this.fire('color-picker-selected', {color: this.color});
      },

      _colorChanged: function() {
        this.$.iconButton.style.color = this.color;
      },

      _colorListChanged: function() {
        // Fall back to the first color, if the new color list doesn't contain the
        // selected color. Bad news: the color could be either toLowerCase or
        // uppercase so uhhh try both.
        if (this.color && this.color !== '' &&
            this.colorList.indexOf(this.color) === -1 &&
            this.colorList.indexOf(String(this.color).toLowerCase()) === -1) {
          this.color = this.colorList[0];
        }
        this._updateSize();
      },

      _columnCountChanged: function() {
        this._updateSize();
      },

      /**
       * Takes an rgb(r, g, b) style string and converts it to a #ffffff hex value.
       */
      _rgbToHex: function(rgb) {
        // Split the rgb(r, g, b) string up.
        var split = rgb.split('(')[1].split(')')[0].split(',');

        if (split.length != 3)
          return '';

        // From https://gist.github.com/lrvick/2080648.
        var bin = split[0] << 16 | split[1] << 8 | split[2];
        return (function(h) {
          return '#' + new Array(7 - h.length).join('0') + h;
        })(bin.toString(16).toLowerCase());
      },

      _updateSize: function() {
        // Fit the color boxes in columns. We first need to get the width of
        // a color box (which is customizable), and then change the box's
        // width to fit all the columns.
        var sizeOfAColorDiv =
            this.getComputedStyleValue('--paper-swatch-picker-color-size');
        if (!sizeOfAColorDiv || sizeOfAColorDiv == '') {  // Default value case
          sizeOfAColorDiv = 20;
        } else {
          sizeOfAColorDiv = sizeOfAColorDiv.replace('px', '');
        }

        var rowCount = Math.ceil(this.colorList.length / this.columnCount);
        this.$.container.style.height = rowCount * sizeOfAColorDiv + 'px';
        this.$.container.style.width = this.columnCount * sizeOfAColorDiv + 'px';
        this._renderedColors = false;
      }
    });
  </script>
</dom-module>
<dom-module id="epiviz-chart-colors">
    <template>
        <style>
            #formSettings {
                min-width: 500px;
            }

            #scrollContent {
                margin-bottom: 10px;
            }

            .color-item {
                display: block;
                text-align: center;
            }

            .container {
                display: grid;
                grid-auto-flow: column dense;
            }
        </style>

        <div id="settings">
            <paper-dialog id="modal" modal="">
                <h2>Chart Color Settings</h2>
                <iron-form id="formSettings">
                    <form>
                        <div class="container">
                            <template id="colorPickers" is="dom-repeat" items="{{labels}}">
                                <div class="color-item">
                                    <paper-swatch-picker id="[[_getId(item)]]" name="{{item}}" color="[[_getColor(item)]]"></paper-swatch-picker>
                                    <iron-label for="{{_getId(item)}}">{{item}}</iron-label>
                                </div>
                            </template>
                        </div>

                        <div class="buttons container flex-end-justified">
                            <paper-button id="submit" on-tap="_submit" raised="">Apply</paper-button>
                            <paper-button id="cancel" on-tap="_cancel" raised="">Cancel</paper-button>
                        </div>
                    </form>
                </iron-form>
            </paper-dialog>
        </div>

    </template>

    <script>

        // Extend Polymer.Element base class
        class EpivizChartColors extends Polymer.Element {

            static get is() { return 'epiviz-chart-colors'; }

            static get properties() {
                return {
                    /**
                    * Default chart color labels.
                    *
                    * @type {Object<string, string>}
                    */
                    labels: {
                        type: Object,
                        notify: true
                    },

                    /**
                    * Default chart color palettes.
                    *
                    * @type {Array<epiviz.ui.charts.ColorPalette>}
                    */
                    palettes: {
                        type: Array,
                        notify: true
                    },

                    /**
                    * currently selected chart colors.
                    *
                    * @type {Object<string, string>}
                    */
                    selected: {
                        type: Object,
                        notify: true
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                ]
            }

            constructor() {
                super();
                // this.addEventListener('submit.click', e => this._submit(e));
                // this.addEventListener('cancel.click', e => this._cancel(e));
            }

            connectedCallback() {
                var self = this;
                super.connectedCallback();

                this.$.formSettings.addEventListener('iron-form-submit', this._form_submit.bind(this));
                var colorPickers = this.$.colorPickers;
                colorPickers.addEventListener('dom-change', function(e) {
                    // auto-binding template is ready.
                    var labels = self.$.colorPickers.items;
                    var colorSwatch = self.shadowRoot.querySelectorAll("paper-swatch-picker");
                    colorSwatch.forEach(function(item, index) {
                        var defaultColors = item.defaultColors();
                        defaultColors[0] = self._getColor(labels[index]);
                        item.set('colorList', defaultColors);
                        item.set('color', self._getColor(labels[index]));
                    });
                });
            }

            disconnectedCallback() {
                super.connectedCallback();
            }

            /**
             * Get Color for a given label/Key.
             *
             * @param {string} label label/key to search for.
             *
             * @return {Object} color set for the given label
             */
            _getColor(label) { 
                return this.selected.getByKey(label);
            }

            /**
             * UI Helper function to find group
             *
             * @param {string} value index number
             * @param {number} expeced expected group
             * @return {boolean} returns True if value in expected
             */
            _isIndexGroup(value, expected) {
                if (value % 3 == expected) {
                    return true;
                }

                return false;
            }

            /**
             * UI Helper function to create Id for a given label (ignore spaces)
             *
             * @param {string} label string
             * 
             * @return {string} return label ignoring white spaces
             */
            _getId(label) {
                return label.split(' ').join('');
            }

            /**
             * handles form submit action
             */
            _submit(event) {
                this.$.formSettings.submit();
            }

            /**
             * handles form submit action
             */
            _form_submit(event) {
                var self = this;

                //collect all colors set
                var colors = [];
                for (var i = 0; i < this.labels.length; i++) {
                    var swatch = self.shadowRoot.querySelector('#' + this._getId(this.labels[i]));
                    colors.push(swatch.color);
                }

                self.selected = new epiviz.ui.charts.ColorPalette(colors, undefined, undefined, self.selected.keyIndices());

                // this.vals = event.detail;
                this.callback(colors);
                this.closeColors();
            }

            /**
             * handles form cancel action
             */
            _cancel(event) {
                this.closeColors();
            }

            /**
             * handles element modal show action
             */
            showColors(target, callback) {
                var self = this;
                // this.$.scrollContent.dialogElement = this.$.modal;
                this.callback = callback;
                this.$.modal.positionTarget = target;
                this.$.modal.open();
            }

            /**
             * handles element modal close action
             */
            closeColors() {
                this.$.modal.close();
            }
        };

        customElements.define(EpivizChartColors.is, EpivizChartColors);

    </script>
</dom-module><script>
    /**
     * `ChartColorsBehavior` manages the `<epiviz-chart-colors>` element for each chart. 
     * All charts inherit this behavior to update chart colors.
     *
     * @polymerBehavior
    **/
    EpivizChartColorsBehavior = function (superClass) {
        return class extends superClass {
            constructor() {
                super();
            }

            static get properties() {
                return {};
            }

            // static get observers() {
            //     return [ ];
            // }

            /**
            * Shows the `<epiviz-chart-colors>` element
            */
            _showColorsDialog() {
                var self = this;
                var chartContainer = this.shadowRoot.querySelector('#' + this.plotId);
                var currColors = this.shadowRoot.querySelector('epiviz-chart-colors');

                if (currColors == null) {
                    var currColors = document.createElement('epiviz-chart-colors');
                    currColors.setAttribute('labels', JSON.stringify(self.chart.colorLabels()));
                    currColors.palettes = self.config.colorPalettes;
                    currColors.selected = self.chart.colors();
                    chartContainer.appendChild(currColors);
                }
                else {
                    currColors.setAttribute('labels', JSON.stringify(self.chart.colorLabels()));
                    currColors.selected = self.chart.colors();
                }

                currColors.showColors(this.shadowRoot, function (newColors) {
                    self.chartColors = newColors;
                });
            }

            /**
            * Initializes the `<epiviz-chart-colors>` element
            */
            _initializeColorsDialog() {
                var chartSettingsContainer = this.shadowRoot.querySelector('#chartSettingsContainer');
                var chartContainer = this.shadowRoot.querySelector('#' + this.plotId);
                var currColorIcon = this.shadowRoot.querySelector('#chartColorsIcon');

                if (currColorIcon == null) {
                    var iconElem = document.createElement('paper-icon-button');
                    iconElem.id = "chartColorsIcon";
                    iconElem.icon = "image:color-lens";

                    iconElem.addEventListener("click", this._showColorsDialog.bind(this));
                    chartSettingsContainer.appendChild(iconElem);
                }
            }
        }
    }
</script><dom-module id="epiviz-chart-settings">

    <template>
        <style>
            .flex {
                /*@apply(--layout-horizontal); */
                display: grid;
                grid-auto-rows: auto;
                grid-template-columns: 1fr 1fr;
                grid-column-gap: 20px;
            }

            .flex-end-justified {
                @apply(--layout-horizontal);
                @apply(--layout-end-justified);
            }

            .collapse-content {
                padding: 15px;
                border-top: none;
                display: block;
                margin: 3px;
            }

            .collapse-header {
                padding: 10px 15px;
                background-color: #f3f3f3;
                border: 1px solid #dedede;
                border-radius: 5px;
                cursor: pointer;
                width: 100%;
                text-align: left;
                margin-top: 10px;
            }

            #formSettings {
                min-width: 400px;
                overflow: scroll;
                grid-row: 2;
            }

            #scrollContent {
                margin-bottom: 10px;
            }

            #modal {
                display: grid;
                grid-template-rows: 50px minmax(auto, 1fr) 100px;
                max-height: 70%;
                top: 10%;
            }

            .header {
                grid-row: 1;
            }

            .footer {
                grid-row: 3;
            }

            paper-collapse-item {
                --paper-collapse-simple-paper-item-styles: {
                    border: 1px solid var(--light-theme-divider-color);
                }
                ;
            }

            paper-toggle-button {
                padding-top: 8px;
            }

            .toggleLabel {
                padding-top: 8px;
            }
        </style>

        <div id="settings">
            <paper-dialog id="modal" modal="">
                <h2 class="header">Title and Margins</h2>
                <iron-form id="formSettings">
                    <form>
                        <paper-collapse-item id="commonSettings" header="Chart Title and Margins">
                            <div class="collapse-content">
                                <div class="container flex">
                                    <template is="dom-repeat" items="{{defs}}" filter="_isCommonSetting">
                                        <template restamp="true" is="dom-if" if="[[_settingType(item.type, 'boolean')]]">
                                            <iron-label for="{{item.id}}">
                                                <div class="toggleLabel">{{item.label}}</div>
                                                <paper-toggle-button name="{{item.id}}" label="{{item.label}}" checked="[[_getValue(item.id)]]" toggles=""></paper-toggle-button>
                                            </iron-label>
                                        </template>
                                        <template restamp="true" is="dom-if" if="[[_settingType(item.type, 'string')]]">
                                            <paper-input name="{{item.id}}" label="{{item.label}}" value="[[_getValue(item.id)]]"></paper-input>
                                        </template>
                                        <template restamp="true" is="dom-if" if="[[_settingType(item.type, 'number')]]">
                                            <paper-input name="{{item.id}}" type="number" label="{{item.label}}" value="[[_getValue(item.id)]]"></paper-input>
                                        </template>
                                        <template restamp="true" is="dom-if" if="[[_settingType(item.type, 'array')]]">
                                            <paper-dropdown-input name="{{item.id}}" value="[[_getValue(item.id)]]" label="{{item.label}}" items="{{item.possibleValues}}"></paper-dropdown-input>
                                        </template>
                                    </template>
                                </div>
                            </div>
                        </paper-collapse-item>
                        <paper-collapse-item id="statsSettings" header="Chart Settings">
                            <div class="collapse-content">
                                <div class="container flex">
                                    <template is="dom-repeat" items="{{defs}}" filter="_isStatSetting">
                                        <template restamp="true" is="dom-if" if="[[_settingType(item.type, 'boolean')]]">
                                            <iron-label for="{{item.id}}">
                                                <div class="toggleLabel">{{item.label}}</div>
                                                <paper-toggle-button name="{{item.id}}" label="{{item.label}}" checked="[[_getValue(item.id)]]" toggles=""></paper-toggle-button>
                                            </iron-label>
                                        </template>
                                        <template restamp="true" is="dom-if" if="[[_settingType(item.type, 'string')]]">
                                            <paper-input name="{{item.id}}" label="{{item.label}}" value="[[_getValue(item.id)]]"></paper-input>
                                        </template>
                                        <template restamp="true" is="dom-if" if="[[_settingType(item.type, 'number')]]">
                                            <paper-input name="{{item.id}}" label="{{item.label}}" value="[[_getValue(item.id)]]"></paper-input>
                                        </template>
                                        <template restamp="true" is="dom-if" if="[[_settingType(item.type, 'array')]]">
                                            <paper-dropdown-input name="{{item.id}}" value="[[_getValue(item.id)]]" label="{{item.label}}" items="{{item.possibleValues}}"></paper-dropdown-input>
                                        </template>
                                    </template>
                                </div>
                                <span hidden$="{{_hasStatSetting()}}">No Chart Settings</span>
                            </div>
                        </paper-collapse-item>

                        </form>
                </iron-form>
                <div class="footer">
                    <paper-button id="submit" on-tap="_submit" raised="">Apply</paper-button>
                    <paper-button id="cancel" on-tap="_cancel" raised="">Cancel</paper-button>
                </div>
            </paper-dialog>
        </div>

    </template>

    <script>
        // Extend Polymer.Element base class
        class EpivizChartSettings extends Polymer.Element {

            static get is() { return 'epiviz-chart-settings'; }

            static get properties() {
                return {
                    /**
                    * Default chart setting definitions.
                    *
                    * @type {Array.<epiviz.ui.charts.CustomSetting>}
                    */
                    defs: {
                        type: Array,
                        notify: true
                    },

                    /**
                    * Currently set chart setting values.
                    *
                    * @type {Object.<string, *>}
                    */
                    vals: {
                        type: Object,
                        notify: true
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                ]
            }

            constructor() {
                super();
                // this.addEventListener('submit.click', e => this._submit(e));
                // this.addEventListener('cancel.click', e => this._cancel(e));
            }

            connectedCallback() {
                super.connectedCallback();
                for (var i = 0; i < this.defs.length; i++) {
                    // def = this.defs[i];
                    var id = this.defs[i].id;
                    var expected = ['categorical', 'measurementsMetadata', 'measurementsAnnotation']
                    if (expected.indexOf(this.defs[i].type) != -1) {
                        if (this.defs[i].possibleValues == null) {
                            this.defs[i].possibleValues = [this.vals[id]];
                        }
                        else if (this.defs[i].possibleValues.indexOf(this.vals[id]) === -1) {
                            this.defs[i].possibleValues.push(this.vals[id]);
                        }
                    }
                }

                this.$.formSettings.addEventListener('iron-form-submit', this._form_submit.bind(this));

            }

            disconnectedCallback() {
                super.connectedCallback();
            }

            /**
             * Toggle handler for showing chart settings
             */
            toggleCommon(event) {
                this.$.commonSettings.toggle();
                event.preventDefault();
                event.stopPropagation();
            }

            /**
             * Toggle handler for showing chart stat settings
             */
            toggleStats(event) {
                this.$.statsSettings.toggle();
                event.preventDefault();
                event.stopPropagation();
            }

            /**
             * whether a setting is a common setting.
             *
             * @param {string} item setting name/label.
             *
             * @return {boolean} if item is common setting
             */
            _isCommonSetting(item) {
                var commonSettings = ['title', 'marginTop', 'marginBottom', 'marginRight', 'marginLeft'];
                if (commonSettings.indexOf(item.id) != -1) {
                    return true;
                }

                return false;
            }

            /**
             * whether a setting is a stat setting.
             *
             * @param {string} item setting name/label.
             *
             * @return {boolean} if item is stat setting
             */
            _isStatSetting(item) {
                return !this._isCommonSetting(item);
            }

            _hasStatSetting() {
                var self = this;
                var hasStatSetting = false;
                this.defs.some(function (item) {
                    if (self._isStatSetting(item)) {
                        hasStatSetting = true;
                        return true;
                    }
                });
                return hasStatSetting;
            }

            /**
             * UI helper function for finding a setting type
             *
             * @param {string} type input setting type.
             * @param {string} expected expected setting type.
             * @return {boolean} return true if type and expected match
             */
            _settingType(type, expected) {
                if (expected == 'array') {
                    expected = ['categorical', 'measurementsMetadata', 'measurementsAnnotation'];
                    return expected.indexOf(type) === -1 ? false : true;

                    // if(expected.indexOf(type) != -1) {
                    //     var def = null;
                    //     for(var i=0; i< this.defs.length; i++){
                    //         if(this.defs[i].id == id) {
                    //             def = this.defs[i];
                    //         }
                    //     }

                    //     if(def.possibleValues === null) {return false;}
                    //     else {return true;}
                    // }
                }
                return type == expected;
            }

            /**
             * UI helper function for grouping settings by type
             *
             * @param {string} value input setting index.
             * @param {string} type expected setting group.
             * @return {boolean} return true if value and type match
             */
            _isIndex(value, type) {
                if ((value % 2 == 0 && type == "even") || (value % 2 == 1 && type == "odd")) {
                    return true;
                }

                return false;
            }

            /**
             * UI helper function get value for a given setting id
             *
             * @param {string} id setting id.
             * @return {number|string} return setting value
             */
            _getValue(id) {
                // var def = null;
                return this.vals[id];
            }

            /**
             * handles form submit action
             */
            _submit(event) {
                this.$.formSettings.submit();
            }

            /**
             * handles form submit action
             */
            _form_submit(event) {
                var self = this;
                this.vals = event.detail;
                //  format vals types
                this.defs.forEach(function (def) {
                    if (def.type == "number" && self.vals[def.id] != "default") {
                        self.vals[def.id] = parseFloat(self.vals[def.id]);
                    }

                    if (def.type == "boolean") {
                        if (self.vals[def.id] && self.vals[def.id][0] == "on") {
                            self.vals[def.id] = true;
                        }
                        else {
                            self.vals[def.id] = false;
                        }
                    }
                });
                this.callback(this.vals);
                this.closeSettings();
            }

            /**
             * handles form cancel action
             */
            _cancel(event) {
                this.closeSettings();
            }

            /**
             * handles form show modal action
             */
            showSettings(target, callback) {
                this.callback = callback;
                this.$.modal.open();
            }

            /**
             * handles form close modal action
             */
            closeSettings() {
                this.$.modal.close();
            }
        };

        customElements.define(EpivizChartSettings.is, EpivizChartSettings);
    </script>
</dom-module><script>
    /**
     * `ChartSettingsBehavior` object manages the `<epiviz-chart-settings>` element for each chart. 
     * All charts inherit this behavior to update chart settings.
     *
     * @polymerBehavior
    **/
    EpivizChartSettingsBehavior = function (superClass) {
        return class extends superClass {
            constructor() {
                super();
            }

            static get properties() {
                return {};
            }

            /**
            * Shows the `<epiviz-chart-settings>` element
            */
            _showSettingsDialog() {
                var self = this;
                var chartContainer = this.shadowRoot.querySelector('#' + this.plotId);
                var currSetting = this.shadowRoot.querySelector('epiviz-chart-settings');

                if (currSetting == null) {
                    var currSetting = document.createElement('epiviz-chart-settings');
                    // self.visualization().properties().customSettingsDefs
                    currSetting.setAttribute('defs', JSON.stringify(self.chart.properties().customSettingsDefs));
                    currSetting.setAttribute('vals', JSON.stringify(self.chartSettings));

                    chartContainer.appendChild(currSetting);
                }
                else {
                    currSetting.setAttribute('vals', JSON.stringify(self.chartSettings));
                }

                currSetting.showSettings(this.shadowRoot, function (newSettings) {
                    self.chartSettings = newSettings;
                });
            }

            /**
            * Initializes the `<epiviz-chart-settings>` element
            */
            _initializeSettingsDialog() {
                var chartSettingsContainer = this.shadowRoot.querySelector('#chartSettingsContainer');
                var chartContainer = this.shadowRoot.querySelector('#' + this.plotId);
                var currSettingIcon = this.shadowRoot.querySelector('#chartSettingsIcon');

                if (currSettingIcon == null) {
                    var iconElem = document.createElement('paper-icon-button');
                    iconElem.id = "chartSettingsIcon";
                    iconElem.icon = "settings";

                    iconElem.addEventListener("click", this._showSettingsDialog.bind(this));
                    chartSettingsContainer.appendChild(iconElem);
                }
            }
        }
    }
</script><link rel="import" type="css" href="chart-shared-css.html"><script>
    /**
     * `ChartRemoveBehavior` object manages the `<epiviz-chart-remove>` element for each chart. 
     * All charts inherit this behavior to remove itself from DOM.
     *
     * @polymerBehavior
    **/
    EpivizChartRemoveBehavior = function (superClass) {
        return class extends superClass {
            constructor() {
                super();
            }

            static get properties() {
                return {};
            }

            /**
            * Shows the remove element
            */
            _showRemoveDialog() {
                var self = this;

                if (self._parentContainer) {
                    self._parentContainer.removeChild(self);

                    let navChildren =
                        Polymer.FlattenedNodesObserver.getFlattenedNodes(self._parentContainer).filter(n => n.nodeType === Node.ELEMENT_NODE)
                    for (var nindex = 0; nindex < navChildren.length; nindex++) {
                        var child = navChildren[nindex];
                        if (child.plotId == self.plotId) {
                            this.shadowRoot.querySelector(child).remove();
                            this.shadowRoot.querySelector("[plot-id=" + self.plotId + "]").remove();
                        }
                    }
                }
                else {
                    this.shadowRoot.querySelector(self.root).remove();
                    this.shadowRoot.querySelector("[plot-id=" + self.plotId + "]").remove();
                }
            }

            /**
            * Initializes the `<epiviz-chart-remove>` element
            */
            _initializeRemoveDialog() {
                var chartSettingsContainer = this.shadowRoot.querySelector('#chartSettingsContainer');
                var chartContainer = this.shadowRoot.querySelector('#' + this.plotId);
                var currSettingIcon = this.shadowRoot.querySelector('#chartRemoveIcon');

                if (currSettingIcon == null) {
                    var iconElem = document.createElement('paper-icon-button');
                    iconElem.id = "chartRemoveIcon";
                    iconElem.icon = "icons:remove-circle";

                    iconElem.addEventListener("click", this._showRemoveDialog.bind(this));

                    chartSettingsContainer.appendChild(iconElem);
                }
            }
        }
    }
</script><link rel="import" type="css" href="chart-shared-css.html">

<dom-module id="epiviz-blocks-track">
    <template>
        <style include="shared-settings"></style>
        <style include="chart-shared-css"></style>
        <style>
            :host {
                width: 100%;
                height: 100%;
                display: inline-block;
                border: 1px solid black;
                border-radius: 5px;
                resize: vertical;
                overflow: auto;
                transition: width 0.01s, height 0.01s;
                position: relative;
            }
        </style>

        <paper-spinner-lite active="" class="green"></paper-spinner-lite>
        <div id="chart" on-mouseover="hostHovered" on-mouseout="hostUnhovered">
            <div id="{{plotId}}"></div>
        </div>

    </template>

    <script>

        // Extend Polymer.Element base class
        class EpivizBlocksTrack extends EpivizChartGridBehavior(EpivizChartRemoveBehavior(EpivizChartColorsBehavior(EpivizChartSettingsBehavior(EpivizChartBehavior(Polymer.Element))))) {

            static get is() { return 'epiviz-blocks-track'; }

            static get properties() {
                return {
                    /**
                    * Default chart properties for blocks track.
                    *
                    * @type {Object}
                    */
                    configSrc: {
                        type: Object,
                        value: function () {

                            epiviz.Config.SETTINGS = {
                                dataProviders: [
                                    ["epiviz.data.WebServerDataProvider", "umd", "http://epiviz-dev.cbcb.umd.edu/api/"]
                                ],
                                workspacesDataProvider: sprintf('epiviz.data.EmptyResponseDataProvider', 'empty', ''),
                                useCache: true,

                                chartSettings: {
                                    default: {
                                        colors: 'd3-category10',
                                        decorations: [
                                            'epiviz.ui.charts.decoration.RemoveChartButton',
                                            'epiviz.ui.charts.decoration.SaveChartButton',
                                            'epiviz.ui.charts.decoration.CustomSettingsButton',
                                            'epiviz.ui.charts.decoration.EditCodeButton',

                                            'epiviz.ui.charts.decoration.ChartColorsButton',
                                            'epiviz.ui.charts.decoration.ChartLoaderAnimation',
                                            'epiviz.ui.charts.decoration.ChartResize'
                                        ]
                                    },

                                    plot: {
                                        width: 400,
                                        height: 400,
                                        margins: new epiviz.ui.charts.Margins(15, 30, 30, 15),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    track: {
                                        width: 900,
                                        height: 90,
                                        margins: new epiviz.ui.charts.Margins(25, 20, 23, 10),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    'epiviz.plugins.charts.BlocksTrack': {
                                        height: 120
                                    },
                                },

                                chartCustomSettings: {
                                    'epiviz.plugins.charts.BlocksTrack': {
                                        minBlockDistance: 3,
                                        useColorBy: false,
                                        blockColorBy: ''
                                    }
                                },

                                colorPalettes: [
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#025167', '#e7003e', '#ffcd00', '#057d9f', '#970026', '#ffe373', '#ff8100'],
                                        'Epiviz v1.0 Colors', 'epiviz-v1'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Epiviz v2.0 Bright', 'epiviz-v2-bright'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#b8d2eb', '#f2aeac', '#d8e4aa', '#cccccc', '#f2d1b0', '#d4b2d3', '#ddb8a9', '#ebbfd9'],
                                        'Epiviz v2.0 Light', 'epiviz-v2-light'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#599ad3', '#f1595f', '#79c36a', '#727272', '#f9a65a', '#9e66ab', '#cd7058', '#d77fb3'],
                                        'Epiviz v2.0 Medium', 'epiviz-v2-medium'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"],
                                        'D3 Category 10', 'd3-category10'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"],
                                        'D3 Category 20', 'd3-category20'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6"],
                                        'D3 Category 20b', 'd3-category20b'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9"],
                                        'D3 Category 20c', 'd3-category20c'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#f9a65a', '#599ad3', '#79c36a', '#f1595f', '#727272', '#cd7058', '#d77fb3'],
                                        'Genes Default', 'genes-default'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Heatmap Default', 'heatmap-default')
                                ]
                            };

                            return epiviz.Config.SETTINGS;
                        }
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                ]
            }

            constructor() {
                super();
            }

            connectedCallback() {
                super.connectedCallback();

                var self = this;

                if (self.useDefaultDataProvider) {

                    self.measurements = self.measurements || [{
                        'id': 'cancer',
                        'name': 'Expression Colon Cancer',
                        'type': 'feature',
                        'datasourceId': 'gene_expression',
                        'datasourceGroup': 'affymetrix_probeset',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }, {
                        'id': 'normal',
                        'name': 'Expression Colon Normal',
                        'type': 'feature',
                        'datasourceId': 'gene_expression',
                        'datasourceGroup': 'affymetrix_probeset',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }];

                    self.range = self.range || new epiviz.datatypes.GenomicRange("chr11", 80000000, 3000000);
                    self._measurementsChanged();

                    var chartMeasMap = {};
                    chartMeasMap[self.plotId] = self.visConfigSelection.measurements;

                    var dataProviderFactory = new epiviz.data.DataProviderFactory(self.config);
                    var dataManager = new epiviz.data.DataManager(self.config, dataProviderFactory);

                    dataManager.getData(self.range, chartMeasMap, function (id, data) {
                        self.data = data;
                    });
                }
                self._initializeGrid();
                self._measurementsChanged();
            }

            disconnectedCallback() {
                super.connectedCallback();
            }

            ready() {
                super.ready();
                this.plotId = self.plotId || this._generatePlotId();
                // this.scopeSubtree(this.$.chart, true);
                this.config = new epiviz.Config(this.configSrc);
            }

            /**
             * Draws the chart.
             *
             * @param {Object<epiviz.datatypes.GenomicRange>} range genomic range.
             * @param {Object<epiviz.datatypes.MapGenomicData>} data to plot
             */
            _draw() {
                if (this.canvas) {
                    this.chart.drawCanvas(this.range, this.data);
                }
                else {
                    this.chart.draw(this.range, this.data);
                }
                this.shadowRoot.querySelector("paper-spinner-lite").active = false;
            }

            /**
             * Creates an instance of the blocks track chart.
             *
             * @return {epiviz.plugins.charts.BlocksTrackType} BlocksTrack chart object
             */
            _createChart() {
                return new epiviz.plugins.charts.BlocksTrackType(new epiviz.Config(this.configSrc));
            }
        };

        customElements.define(EpivizBlocksTrack.is, EpivizBlocksTrack);
    </script>
</dom-module><link rel="import" type="css" href="chart-shared-css.html">


<dom-module id="epiviz-stacked-blocks-track">
    <template>
        <style include="shared-settings"></style>
        <style include="chart-shared-css"></style>
        <style>
            :host {
                width: 100%;
                height: 100%;
                display: inline-block;
                border: 1px solid black;
                border-radius: 5px;
                position: relative;
                resize: vertical;
                overflow: auto;
                transition: width 0.01s, height 0.01s;
            }
/* 
            .chart-title {
                text-shadow: 2px 2px 4px #000000;
            } */
        </style>

        <paper-spinner-lite active="" class="green"></paper-spinner-lite>
        <div id="chart" on-mouseover="hostHovered" on-mouseout="hostUnhovered">
            <slot name="dragHandle"></slot>
            <div id="{{plotId}}"></div>
        </div>

    </template>

    <script>
        // Extend Polymer.Element base class
        class EpivizStackedBlocksTrack extends EpivizChartGridBehavior(EpivizChartRemoveBehavior(EpivizChartColorsBehavior(EpivizChartSettingsBehavior(EpivizChartBehavior(Polymer.Element))))) {

            static get is() { return 'epiviz-stacked-blocks-track'; }

            static get properties() {
                return {
                    /**
                    * Default chart properties for blocks track.
                    * @type {Object}
                    */
                    configSrc: {
                        type: Object,
                        value: function () {

                            epiviz.Config.SETTINGS = {
                                dataProviders: [
                                    ["epiviz.data.WebServerDataProvider", "umd", "http://epiviz-dev.cbcb.umd.edu/api/"]
                                ],
                                workspacesDataProvider: sprintf('epiviz.data.EmptyResponseDataProvider', 'empty', ''),
                                useCache: true,

                                chartSettings: {
                                    default: {
                                        colors: 'd3-category10',
                                        decorations: [
                                            'epiviz.ui.charts.decoration.RemoveChartButton',
                                            'epiviz.ui.charts.decoration.SaveChartButton',
                                            'epiviz.ui.charts.decoration.CustomSettingsButton',
                                            'epiviz.ui.charts.decoration.EditCodeButton',

                                            'epiviz.ui.charts.decoration.ChartColorsButton',
                                            'epiviz.ui.charts.decoration.ChartLoaderAnimation',
                                            'epiviz.ui.charts.decoration.ChartResize'
                                        ]
                                    },

                                    plot: {
                                        width: 400,
                                        height: 400,
                                        margins: new epiviz.ui.charts.Margins(15, 30, 30, 15),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    track: {
                                        width: 900,
                                        height: 90,
                                        margins: new epiviz.ui.charts.Margins(25, 20, 23, 10),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    'epiviz.plugins.charts.StackedBlocksTrack': {
                                        height: 120
                                    },
                                },

                                chartCustomSettings: {
                                    'epiviz.plugins.charts.StackedBlocksTrack': {
                                        minBlockDistance: 3,
                                        useColorBy: false,
                                        blockColorBy: ''
                                    }
                                },

                                colorPalettes: [
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#025167', '#e7003e', '#ffcd00', '#057d9f', '#970026', '#ffe373', '#ff8100'],
                                        'Epiviz v1.0 Colors', 'epiviz-v1'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Epiviz v2.0 Bright', 'epiviz-v2-bright'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#b8d2eb', '#f2aeac', '#d8e4aa', '#cccccc', '#f2d1b0', '#d4b2d3', '#ddb8a9', '#ebbfd9'],
                                        'Epiviz v2.0 Light', 'epiviz-v2-light'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#599ad3', '#f1595f', '#79c36a', '#727272', '#f9a65a', '#9e66ab', '#cd7058', '#d77fb3'],
                                        'Epiviz v2.0 Medium', 'epiviz-v2-medium'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"],
                                        'D3 Category 10', 'd3-category10'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"],
                                        'D3 Category 20', 'd3-category20'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6"],
                                        'D3 Category 20b', 'd3-category20b'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9"],
                                        'D3 Category 20c', 'd3-category20c'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#f9a65a', '#599ad3', '#79c36a', '#f1595f', '#727272', '#cd7058', '#d77fb3'],
                                        'Genes Default', 'genes-default'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Heatmap Default', 'heatmap-default')
                                ]
                            };

                            return epiviz.Config.SETTINGS;
                        }
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                ]
            }

            constructor() {
                super();
            }

            connectedCallback() {
                super.connectedCallback();

                var self = this;

                if (self.useDefaultDataProvider) {

                    self.measurements = self.measurements || [{
                        'id': 'cancer',
                        'name': 'Expression Colon Cancer',
                        'type': 'feature',
                        'datasourceId': 'gene_expression',
                        'datasourceGroup': 'affymetrix_probeset',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }, {
                        'id': 'normal',
                        'name': 'Expression Colon Normal',
                        'type': 'feature',
                        'datasourceId': 'gene_expression',
                        'datasourceGroup': 'affymetrix_probeset',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }];

                    self.range = self.range || new epiviz.datatypes.GenomicRange("chr11", 80000000, 3000000);
                    // self._measurementsChanged();

                    var chartMeasMap = {};
                    chartMeasMap[self.plotId] = self.visConfigSelection.measurements;

                    var dataProviderFactory = new epiviz.data.DataProviderFactory(self.config);
                    var dataManager = new epiviz.data.DataManager(self.config, dataProviderFactory);

                    dataManager.getData(self.range, chartMeasMap, function (id, data) {
                        self.data = data;
                    });
                }
                self._initializeGrid();
                self._measurementsChanged();
            }

            disconnectedCallback() {
                super.connectedCallback();
            }

            ready() {
                super.ready();
                this.plotId = self.plotId || this._generatePlotId();
                // this.scopeSubtree(this.$.chart, true);
                this.config = new epiviz.Config(this.configSrc);
            }

            /**
            * Draws the chart.
            *
            * @param {Object<epiviz.datatypes.GenomicRange>} range genomic range.
            * @param {Object<epiviz.datatypes.MapGenomicData>} data to plot
            */
            _draw() {
                if (this.canvas) {
                    this.chart.drawCanvas(this.range, this.data);
                }
                else {
                    this.chart.draw(this.range, this.data);
                }
                this.shadowRoot.querySelector("paper-spinner-lite").active = false;
            }

            /**
             * Creates an instance of the blocks track chart.
             *
             * @return {epiviz.plugins.charts.StackedBlocksTrackType} StackedBlocksTrack chart object
             */
            _createChart() {
                return new epiviz.plugins.charts.StackedBlocksTrackType(new epiviz.Config(this.configSrc));
            }
        };

        customElements.define(EpivizStackedBlocksTrack.is, EpivizStackedBlocksTrack);

        // Polymer({
        //     /* Custom element html tag */
        //     is: 'epiviz-stacked-blocks-track',
        //     behaviors: [epiviz.ChartBehavior, epiviz.ChartSettingsBehavior, epiviz.ChartColorsBehavior, Polymer.IronResizableBehavior, epiviz.ChartRemoveBehavior, epiviz.ChartDraggableBehavior],
        // });
    </script>
</dom-module><link rel="import" type="css" href="chart-shared-css.html">

<dom-module id="epiviz-genes-track">
    <template>
        <style include="shared-settings"></style>
        <style include="chart-shared-css"></style>
        <style>
            :host {
                width: 100%;
                height: 100%;
                box-sizing: border-box;
                display: inline-block;
                border: 1px solid black;
                border-radius: 5px;
                transition: width 0.01s, height 0.01s;
                resize: vertical;
                overflow: auto;
                position: relative;
            }

            [hidden] {
                display: none;
            }
        </style>

        <paper-spinner-lite active="" class="green"></paper-spinner-lite>
        <div id="chart" on-drag="hostDragged" on-mouseover="hostHovered" on-mouseout="hostUnhovered">
            <slot name="dragHandle"></slot>
            <div id="{{plotId}}"></div>
        </div>

    </template>

    <script>

        // Extend Polymer.Element base class
        // EpivizChartRemoveBehavior(EpivizChartColorsBehavior(EpivizChartSettingsBehavior(EpivizChartBehavior(Polymer.Element))))

        class EpivizGenesTrack extends EpivizChartGridBehavior(EpivizChartRemoveBehavior(EpivizChartColorsBehavior(EpivizChartSettingsBehavior(EpivizChartBehavior(Polymer.Element))))) {

            static get is() { return 'epiviz-genes-track'; }

            static get properties() {
                return {
                    /**
                    * Default chart properties for genes track.
                    *
                    * @type {Object}
                    */
                    configSrc: {
                        type: Object,
                        notify: true,
                        value: function () {

                            epiviz.Config.SETTINGS = {
                                dataProviders: [
                                    ["epiviz.data.WebServerDataProvider", "umd", "http://epiviz-dev.cbcb.umd.edu/api/"]
                                ],
                                workspacesDataProvider: sprintf('epiviz.data.EmptyResponseDataProvider', 'empty', ''),
                                useCache: true,

                                chartSettings: {
                                    default: {
                                        colors: 'd3-category10',
                                        decorations: [
                                            'epiviz.ui.charts.decoration.RemoveChartButton',
                                            'epiviz.ui.charts.decoration.SaveChartButton',
                                            'epiviz.ui.charts.decoration.CustomSettingsButton',
                                            'epiviz.ui.charts.decoration.EditCodeButton',

                                            'epiviz.ui.charts.decoration.ChartColorsButton',
                                            'epiviz.ui.charts.decoration.ChartLoaderAnimation',
                                            'epiviz.ui.charts.decoration.ChartResize'
                                        ]
                                    },

                                    plot: {
                                        width: 400,
                                        height: 400,
                                        margins: new epiviz.ui.charts.Margins(15, 30, 30, 15),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    track: {
                                        width: 900,
                                        height: 90,
                                        margins: new epiviz.ui.charts.Margins(25, 20, 23, 10),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    'epiviz.plugins.charts.GenesTrack': {
                                        height: 120,
                                        colors: 'genes-default'
                                    },
                                },

                                chartCustomSettings: {
                                    'epiviz.plugins.charts.GenesTrack': {

                                    },
                                },

                                colorPalettes: [
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#025167', '#e7003e', '#ffcd00', '#057d9f', '#970026', '#ffe373', '#ff8100'],
                                        'Epiviz v1.0 Colors', 'epiviz-v1'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Epiviz v2.0 Bright', 'epiviz-v2-bright'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#b8d2eb', '#f2aeac', '#d8e4aa', '#cccccc', '#f2d1b0', '#d4b2d3', '#ddb8a9', '#ebbfd9'],
                                        'Epiviz v2.0 Light', 'epiviz-v2-light'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#599ad3', '#f1595f', '#79c36a', '#727272', '#f9a65a', '#9e66ab', '#cd7058', '#d77fb3'],
                                        'Epiviz v2.0 Medium', 'epiviz-v2-medium'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"],
                                        'D3 Category 10', 'd3-category10'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"],
                                        'D3 Category 20', 'd3-category20'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6"],
                                        'D3 Category 20b', 'd3-category20b'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9"],
                                        'D3 Category 20c', 'd3-category20c'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#f9a65a', '#599ad3', '#79c36a', '#f1595f', '#727272', '#cd7058', '#d77fb3'],
                                        'Genes Default', 'genes-default'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Heatmap Default', 'heatmap-default')
                                ]
                            };

                            return epiviz.Config.SETTINGS;
                        }
                    }
                }
            }

            static get observers() {
                return []
            }

            constructor() {
                super();
            }

            connectedCallback() {
                super.connectedCallback();

                var self = this;

                if (self.useDefaultDataProvider) {

                    self.measurements = self.measurements || [{
                        'id': 'genes',
                        'name': 'genes',
                        'type': 'range',
                        'datasourceId': 'genes',
                        'datasourceGroup': 'genes',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': "Genes Track",
                        'annotation': null,
                        'minValue': null,
                        'maxValue': null,
                        'metadata': ['gene', 'exon_starts', 'exon_ends']
                    }];

                    self.range = self.range || new epiviz.datatypes.GenomicRange("chr11", 80000000, 3000000);

                    // self._measurementsChanged();

                    var chartMeasMap = {};
                    chartMeasMap[self.plotId] = self.visConfigSelection.measurements;

                    var dataProviderFactory = new epiviz.data.DataProviderFactory(self.config);
                    var dataManager = new epiviz.data.DataManager(self.config, dataProviderFactory);

                    dataManager.getData(self.range, chartMeasMap, function (id, data) {
                        self.data = data;
                    });
                }
                self._initializeGrid();
                self._measurementsChanged();
            }

            disconnectedCallback() {
                super.disconnectedCallback();
            }

            ready() {
                super.ready();
                this.plotId = self.plotId || this._generatePlotId();
                // this.scopeSubtree(this.$.chart, true);
                this.config = new epiviz.Config(this.configSrc);
            }

            /**
            * Draws the chart.
            *
            * @param {Object<epiviz.datatypes.GenomicRange>} range genomic range.
            * @param {Object<epiviz.datatypes.MapGenomicData>} data to plot
            */
            _draw() {
                var self = this;
                
                if (this.canvas) {
                    this.chart.drawCanvas(this.range, this.data);
                }
                else {
                    this.chart.draw(this.range, this.data);
                }

                self.chart._propagateNavigationChanges.addListener(new epiviz.events.EventListener(function(e) {
                    self.dispatchEvent(new CustomEvent('GenesTrackRangeUpdate',
                            {
                                detail: {
                                    id: e.id,
                                    range: e.range,
                                },
                                bubbles: true
                            }
                        )
                    );                
                }));

                this.shadowRoot.querySelector("paper-spinner-lite").active = false;
            }

            /**
            * Creates an instance of the genes track chart.
            *
            * @return {epiviz.plugins.charts.GenesTrackType} BlocksTrack chart object
            */
            _createChart() {
                return new epiviz.plugins.charts.GenesTrackType(new epiviz.Config(this.configSrc));
            }
        };

        customElements.define(EpivizGenesTrack.is, EpivizGenesTrack);
    </script>
</dom-module><link rel="import" type="css" href="chart-shared-css.html">

<dom-module id="epiviz-heatmap-plot">
    <template>
        <style include="shared-settings"></style>
        <style include="chart-shared-css"></style>
        <style>
            :host {
                width: 100%;
                height: 100%;
                display: inline-block;
                border: 1px solid black;
                border-radius: 5px;
                resize: vertical;
                overflow: auto;
                transition: width 0.01s, height 0.01s;
                position: relative;
            }
        </style>

        <paper-spinner-lite active="" class="green"></paper-spinner-lite>
        <div id="chart" on-mouseover="hostHovered" on-mouseout="hostUnhovered">
            <slot name="dragHandle"></slot>
            <div id="{{plotId}}"></div>
        </div>

    </template>

    <script>

        // Extend Polymer.Element base class
        class EpivizHeatmapPlot extends EpivizChartGridBehavior(EpivizChartRemoveBehavior(EpivizChartColorsBehavior(EpivizChartSettingsBehavior(EpivizChartBehavior(Polymer.Element))))) {

            static get is() { return 'epiviz-heatmap-plot'; }

            static get properties() {
                return {

                    /**
                    * Default chart properties for heatmap plot.
                    *
                    * @type {Object}
                    */
                    configSrc: {
                        type: Object,
                        notify: true,
                        value: function () {

                            epiviz.Config.SETTINGS = {
                                dataProviders: [
                                    ["epiviz.data.WebServerDataProvider", "umd", "http://epiviz-dev.cbcb.umd.edu/api/"]
                                ],
                                workspacesDataProvider: sprintf('epiviz.data.EmptyResponseDataProvider', 'empty', ''),
                                useCache: true,

                                chartSettings: {
                                    default: {
                                        colors: 'd3-category10',
                                        decorations: [
                                            'epiviz.ui.charts.decoration.RemoveChartButton',
                                            'epiviz.ui.charts.decoration.SaveChartButton',
                                            'epiviz.ui.charts.decoration.CustomSettingsButton',
                                            'epiviz.ui.charts.decoration.EditCodeButton',

                                            'epiviz.ui.charts.decoration.ChartColorsButton',
                                            'epiviz.ui.charts.decoration.ChartLoaderAnimation',
                                            'epiviz.ui.charts.decoration.ChartResize'
                                        ]
                                    },

                                    plot: {
                                        width: 400,
                                        height: 400,
                                        margins: new epiviz.ui.charts.Margins(15, 30, 30, 15),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    track: {
                                        width: '100%',
                                        height: 90,
                                        margins: new epiviz.ui.charts.Margins(25, 20, 23, 10),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },
                                    'epiviz.plugins.charts.HeatmapPlot': {
                                        width: 800,
                                        height: 400,
                                        margins: new epiviz.ui.charts.Margins(80, 120, 40, 40),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton',
                                            'epiviz.ui.charts.decoration.ChartOrderByMeasurementsCodeButton',
                                            'epiviz.ui.charts.decoration.ChartColorByRowCodeButton'
                                        ],
                                        colors: 'heatmap-default'
                                    }
                                },

                                chartCustomSettings: {
                                    'epiviz.plugins.charts.HeatmapPlot': {
                                        colLabel: 'label',
                                        maxColumns: 120,
                                        clusteringAlg: 'agglomerative'
                                    }
                                },

                                clustering: {
                                    algorithms: [
                                        'epiviz.ui.charts.transform.clustering.NoneClustering',
                                        'epiviz.ui.charts.transform.clustering.AgglomerativeClustering'
                                    ],
                                    metrics: ['epiviz.ui.charts.transform.clustering.EuclideanMetric'],
                                    linkages: ['epiviz.ui.charts.transform.clustering.CompleteLinkage']
                                },
                                colorPalettes: [
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#025167', '#e7003e', '#ffcd00', '#057d9f', '#970026', '#ffe373', '#ff8100'],
                                        'Epiviz v1.0 Colors', 'epiviz-v1'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Epiviz v2.0 Bright', 'epiviz-v2-bright'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#b8d2eb', '#f2aeac', '#d8e4aa', '#cccccc', '#f2d1b0', '#d4b2d3', '#ddb8a9', '#ebbfd9'],
                                        'Epiviz v2.0 Light', 'epiviz-v2-light'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#599ad3', '#f1595f', '#79c36a', '#727272', '#f9a65a', '#9e66ab', '#cd7058', '#d77fb3'],
                                        'Epiviz v2.0 Medium', 'epiviz-v2-medium'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"],
                                        'D3 Category 10', 'd3-category10'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"],
                                        'D3 Category 20', 'd3-category20'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6"],
                                        'D3 Category 20b', 'd3-category20b'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9"],
                                        'D3 Category 20c', 'd3-category20c'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#f9a65a', '#599ad3', '#79c36a', '#f1595f', '#727272', '#cd7058', '#d77fb3'],
                                        'Genes Default', 'genes-default'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Heatmap Default', 'heatmap-default')
                                ]
                            };

                            return epiviz.Config.SETTINGS;
                        }
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                ]
            }

            constructor() {
                super();
            }

            connectedCallback() {
                super.connectedCallback();

                var self = this;

                if (self.useDefaultDataProvider) {

                    self.measurements = self.measurements || [{
                        'id': 'e027',
                        'name': 'Expression Colon Cancer',
                        'type': 'feature',
                        'datasourceId': 'roadmap_rnaseq',
                        'datasourceGroup': 'roadmap_rnaseq',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }, {
                        'id': 'e066',
                        'name': 'Expression Colon Normal',
                        'type': 'feature',
                        'datasourceId': 'roadmap_rnaseq',
                        'datasourceGroup': 'roadmap_rnaseq',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }];

                    self.range = self.range || new epiviz.datatypes.GenomicRange("chr11", 80000000, 3000000);
                    // self._measurementsChanged();

                    var chartMeasMap = {};
                    chartMeasMap[self.plotId] = self.visConfigSelection.measurements;
                    var dataProviderFactory = new epiviz.data.DataProviderFactory(self.config);
                    var dataManager = new epiviz.data.DataManager(self.config, dataProviderFactory);

                    dataManager.getData(self.range, chartMeasMap, function (id, data) {
                        self.data = data;
                    });
                }

                self._initializeGrid();
                self._measurementsChanged();
            }

            disconnectedCallback() {
                super.connectedCallback();
            }

            ready() {
                super.ready();
                this.plotId = self.plotId || this._generatePlotId();
                // this.scopeSubtree(this.$.chart, true);
                this.config = new epiviz.Config(this.configSrc);
                epiviz.ui.charts.transform.clustering.ClusteringAlgorithmFactory.initialize(this.config);
            }

            /**
            * Draws the chart.
            *
            * @param {Object<epiviz.datatypes.GenomicRange>} range genomic range.
            * @param {Object<epiviz.datatypes.MapGenomicData>} data to plot
            */
            _draw() {
                if (this.canvas) {
                    this.chart.drawCanvas(this.range, this.data);
                }
                else {
                    this.chart.draw(this.range, this.data);
                }
                this.shadowRoot.querySelector("paper-spinner-lite").active = false;
            }

            /**
             * Creates an instance of the Heatmap plot chart.
             *
             * @return {epiviz.plugins.charts.HeatmapPlotType} HeatmapPlot chart object
             */
            _createChart() {
                var self = this;
                return new epiviz.plugins.charts.HeatmapPlotType(new epiviz.Config(this.configSrc));
            }
        };

        customElements.define(EpivizHeatmapPlot.is, EpivizHeatmapPlot);
    </script>
</dom-module><link rel="import" type="css" href="chart-shared-css.html">

<dom-module id="epiviz-line-plot">
    <template>
        <style include="shared-settings"></style>
        <style include="chart-shared-css"></style>
        <style>
            :host {
                display: inline-block;
                width: 100%;
                height: 100%;
                border: 1px solid black;
                border-radius: 5px;
                position: relative;
                resize: vertical;
                overflow: auto;
                transition: width 0.01s, height 0.01s;
            }
        </style>

        <paper-spinner-lite active="" class="green"></paper-spinner-lite>
        <div id="chart" on-mouseover="hostHovered" on-mouseout="hostUnhovered">
            <slot name="dragHandle"></slot>
            <div id="{{plotId}}"></div>
        </div>

    </template>

    <script>
        // Extend Polymer.Element base class
        class EpivizLinePlot extends EpivizChartGridBehavior(EpivizChartRemoveBehavior(EpivizChartColorsBehavior(EpivizChartSettingsBehavior(EpivizChartBehavior(Polymer.Element))))) {

            static get is() { return 'epiviz-line-plot'; }

            static get properties() {
                return {
                    /**
                    * Default chart properties for line plot.
                    *
                    * @type {Object}
                    */
                    configSrc: {
                        type: Object,
                        notify: true,
                        value: function () {

                            epiviz.Config.SETTINGS = {
                                dataProviders: [
                                    ["epiviz.data.WebServerDataProvider", "umd", "http://epiviz-dev.cbcb.umd.edu/api/"]
                                ],
                                workspacesDataProvider: sprintf('epiviz.data.EmptyResponseDataProvider', 'empty', ''),
                                useCache: true,

                                chartSettings: {
                                    default: {
                                        colors: 'd3-category10',
                                        decorations: [
                                            'epiviz.ui.charts.decoration.RemoveChartButton',
                                            'epiviz.ui.charts.decoration.SaveChartButton',
                                            'epiviz.ui.charts.decoration.CustomSettingsButton',
                                            'epiviz.ui.charts.decoration.EditCodeButton',

                                            'epiviz.ui.charts.decoration.ChartColorsButton',
                                            'epiviz.ui.charts.decoration.ChartLoaderAnimation',
                                            'epiviz.ui.charts.decoration.ChartResize'
                                        ]
                                    },

                                    plot: {
                                        width: 400,
                                        height: 400,
                                        margins: new epiviz.ui.charts.Margins(15, 30, 30, 15),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    track: {
                                        width: '100%',
                                        height: 90,
                                        margins: new epiviz.ui.charts.Margins(25, 20, 23, 10),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    'epiviz.plugins.charts.LinePlot': {
                                        width: 800,
                                        height: 400,
                                        margins: new epiviz.ui.charts.Margins(30, 30, 50, 15),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton',
                                            'epiviz.ui.charts.decoration.ChartColorByRowCodeButton',
                                            'epiviz.ui.charts.decoration.ChartOrderByMeasurementsCodeButton'
                                        ],
                                        colors: 'd3-category20b'
                                    }
                                },

                                chartCustomSettings: {

                                },

                                colorPalettes: [
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#025167', '#e7003e', '#ffcd00', '#057d9f', '#970026', '#ffe373', '#ff8100'],
                                        'Epiviz v1.0 Colors', 'epiviz-v1'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Epiviz v2.0 Bright', 'epiviz-v2-bright'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#b8d2eb', '#f2aeac', '#d8e4aa', '#cccccc', '#f2d1b0', '#d4b2d3', '#ddb8a9', '#ebbfd9'],
                                        'Epiviz v2.0 Light', 'epiviz-v2-light'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#599ad3', '#f1595f', '#79c36a', '#727272', '#f9a65a', '#9e66ab', '#cd7058', '#d77fb3'],
                                        'Epiviz v2.0 Medium', 'epiviz-v2-medium'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"],
                                        'D3 Category 10', 'd3-category10'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"],
                                        'D3 Category 20', 'd3-category20'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6"],
                                        'D3 Category 20b', 'd3-category20b'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9"],
                                        'D3 Category 20c', 'd3-category20c'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#f9a65a', '#599ad3', '#79c36a', '#f1595f', '#727272', '#cd7058', '#d77fb3'],
                                        'Genes Default', 'genes-default'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Heatmap Default', 'heatmap-default')
                                ]
                            };

                            return epiviz.Config.SETTINGS;
                        }
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                ]
            }

            constructor() {
                super();
            }

            connectedCallback() {
                super.connectedCallback();

                var self = this;

                if (self.useDefaultDataProvider) {

                    self.measurements = self.measurements || [{
                        'id': 'e027',
                        'name': 'Expression Colon Cancer',
                        'type': 'feature',
                        'datasourceId': 'roadmap_rnaseq',
                        'datasourceGroup': 'roadmap_rnaseq',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }, {
                        'id': 'e066',
                        'name': 'Expression Colon Normal',
                        'type': 'feature',
                        'datasourceId': 'roadmap_rnaseq',
                        'datasourceGroup': 'roadmap_rnaseq',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }];

                    self.range = self.range || new epiviz.datatypes.GenomicRange("chr11", 80000000, 3000000);

                    // self._measurementsChanged();

                    var chartMeasMap = {};
                    chartMeasMap[self.plotId] = self.visConfigSelection.measurements;

                    var dataProviderFactory = new epiviz.data.DataProviderFactory(self.config);
                    var dataManager = new epiviz.data.DataManager(self.config, dataProviderFactory);

                    dataManager.getData(self.range, chartMeasMap, function (id, data) {
                        self.data = data;
                    });
                }
                self._initializeGrid();
                self._measurementsChanged();
            }

            disconnectedCallback() {
                super.connectedCallback();
            }

            ready() {
                super.ready();
                this.plotId = self.plotId || this._generatePlotId();
                // this.scopeSubtree(this.$.chart, true);
                this.config = new epiviz.Config(this.configSrc);
            }

            /**
            * Draws the chart.
            *
            * @param {Object<epiviz.datatypes.GenomicRange>} range genomic range.
            * @param {Object<epiviz.datatypes.MapGenomicData>} data to plot
            */
            _draw() {
                if (this.canvas) {
                    this.chart.drawCanvas(this.range, this.data);
                }
                else {
                    this.chart.draw(this.range, this.data);
                }
                this.shadowRoot.querySelector("paper-spinner-lite").active = false;
            }

            /**
             * Creates an instance of the line plot chart.
             *
             * @return {epiviz.plugins.charts.LinePlotType} LinePlot chart object
             */
            _createChart() {
                return new epiviz.plugins.charts.LinePlotType(new epiviz.Config(this.configSrc));
            }
        };

        customElements.define(EpivizLinePlot.is, EpivizLinePlot);

        // Polymer({
        //     /* Custom element html tag */
        //     is: 'epiviz-line-plot',
        //     behaviors: [epiviz.ChartBehavior, epiviz.ChartSettingsBehavior, epiviz.ChartColorsBehavior, epiviz.ChartRemoveBehavior, Polymer.IronResizableBehavior, epiviz.ChartDraggableBehavior],
        // });
    </script>
</dom-module><script>
    /**
     * `ChartRemoveBehavior` object manages the `<epiviz-chart-log-ratio>` element for each chart. 
     * All charts inherit this behavior to remove itself from DOM.
     *
     * @polymerBehavior
    **/
    EpivizChartLogRatioBehavior = function (superClass) {
        return class extends superClass {
            constructor() {
                super();
            }

            static get properties() {
                return {};
            }

            /**
            * Shows the remove element
            */
            _showLogRatioDialog() {
                var self = this;

                self.isLogRatio  = !self.isLogRatio;

                self._toggleLogRatioIcon();

                if (!self.isLogRatio) {
                    self.chartSettings = self._originalSettings
                    // self.chart.setCustomSettingsValues(self._originalSettings);
                    self.data = self._originalData
                    // self.chart.setCustomSettingsValues(self._originalSettings);
                    // self._draw();
                    return;
                }


                if (!self._originalData) {
                    self._originalData = self.data
                }

                if (!self._originalSettings) {
                    self._originalSettings = JSON.parse(JSON.stringify(self.chartSettings))
                }

                self.chartSettings =  JSON.parse(JSON.stringify(self._originalSettings));


                if (self.measurements.length == 2) {

                    var groupByMarker = new epiviz.ui.charts.markers.VisualizationMarker(
                        "logratio", "logratio", "logratio", 
                        "function(data) {  return null; }", 
                        "function(m, data, preMarkResult) {  return 'log2 ratio'; }"
                    );

                    var aggregator = epiviz.ui.charts.markers.MeasurementAggregators["log"];
                    self._logData = new epiviz.datatypes.MeasurementAggregatedGenomicData(self._originalData, groupByMarker, aggregator);
                    self._logData.ready(function() {
                        if (self._logData.isReady()) {
                            // self.chart._lastData = newData;
                            self.data = self._logData
                            self.chartSettings.yMax = Math.log2(self.chartSettings.yMax);
                            self.chartSettings.yMin = -1 * self.chartSettings.yMax;
                            self.chart.setCustomSettingsValues(self.chartSettings);
                            self._draw();
                        }
                    })
                }
            }

            /**
            * Initializes the `<epiviz-chart-logratio>` element
            */
            _initializeLogRatioDialog() {
                var chartSettingsContainer = this.shadowRoot.querySelector('#chartSettingsContainer');
                var chartContainer = this.shadowRoot.querySelector('#' + this.plotId);
                var currSettingIcon = this.shadowRoot.querySelector('#chartLogRatioIcon');

                if (currSettingIcon == null) {
                    var iconElem = document.createElement('button');
                    iconElem.id = "chartLogRatioIcon";
                    iconElem.className = "setIcon";
                    iconElem.innerHTML = "Log2 ratio |";
                    // iconElem.icon = "icons:alarm";

                    iconElem.addEventListener("click", this._showLogRatioDialog.bind(this));

                    chartSettingsContainer.appendChild(iconElem);
                }
            }

            _toggleLogRatioIcon() {

                var self = this;

                var active = this.shadowRoot.querySelector('#chartLogRatioIcon');
                var dis1 = this.shadowRoot.querySelector('#chartAverageIcon');
                var dis2 = this.shadowRoot.querySelector('#chartNegationIcon');

                if(self.isLogRatio) {
                    active.style.color = 'blue';
                }
                else {
                    active.style.color = 'black';

                }

                self.isAverage = self.isNegation = false;
                dis1.style.color = dis2.style.color = 'black';
            }
        }
    }
</script><script>
    /**
     * `ChartRemoveBehavior` object manages the `<epiviz-chart-negation>` element for each chart. 
     * All charts inherit this behavior to remove itself from DOM.
     *
     * @polymerBehavior
    **/
    EpivizChartNegationBehavior = function (superClass) {
        return class extends superClass {
            constructor() {
                super();
            }

            static get properties() {
                return {};
            }

            get_invert_data(lData) {
                var self = this;

                var sumExp = new epiviz.datatypes.PartialSummarizedExperiment();
                var counter = 0;
                var invert = 0;

                lData.foreach(function(measurement, series, seriesIndex) {
                    if(counter == 0) {
                        var rowData = series._container.rowData(measurement);
                        sumExp._rowData = rowData;
                        counter++;
                    }

                    var featureValues = series._container.values(measurement);
                    var valData = [];

                    if(featureValues._values != undefined) {
                        if (invert == 1) {
                            featureValues._values.forEach(function(val, i) {
                                valData[i] = -1 * val; 
                            });
                        }
                        else {
                            featureValues._values.forEach(function(val, i) {
                                valData[i] = val; 
                            });
                        }

                    }
                    else {
                        valData = undefined;
                    }

                    var newValueData = new epiviz.datatypes.FeatureValueArray(measurement, featureValues._boundaries, featureValues._globalStartIndex, valData);

                    sumExp.addValues(newValueData);
                    invert++;
                });

                var msDataMap = new epiviz.measurements.MeasurementHashtable();

                lData.foreach(function(m) {
                    m._maxValue = m._maxValue;
                    m._minValue = -1 * m._maxValue;
                    var msData = new epiviz.datatypes.MeasurementGenomicDataWrapper(m, sumExp);
                    msDataMap.put(m, msData);
                });

                var genomicData = new epiviz.datatypes.MapGenomicData(msDataMap);

                return genomicData
            }

            /**
            * Shows the remove element
            */
            _showNegationDialog() {
                var self = this;

                self.isNegation  = !self.isNegation;

                self._toggleNegationIcon();

                if (!self.isNegation) {
                    self.chartSettings = self._originalSettings
                    // self.chart.setCustomSettingsValues(self._originalSettings);
                    self.data = self._originalData
                    // self.chart.setCustomSettingsValues(self._originalSettings);
                    // self._draw();
                    return;
                }

                if (!self._originalData) {
                    self._originalData = self.data
                }

                if (!self._originalSettings) {
                    self._originalSettings = JSON.parse(JSON.stringify(self.chartSettings))
                }

                self.chartSettings =  JSON.parse(JSON.stringify(self._originalSettings));

                if (self.measurements.length == 2) {

                    // self._negateData = jQuery.extend({}, self._originalData)
                    // self._negateData._map._order[1].value._container._values._order[0].value._values.forEach(function(k, i) {
                    //     self._negateData._map._order[1].value._container._values._order[0].value._values[i] = -1 * k;
                    // });

                    self._negateData = self.get_invert_data(self._originalData);

                    self.chartSettings.yMin = -1 * self.chartSettings.yMax;
                    self.chart.setCustomSettingsValues(self.chartSettings);

                    self.data = self._negateData;
                    self._draw();

                    
                    // var groupByMarker = new epiviz.ui.charts.markers.VisualizationMarker(
                    //     "negation", "negation", "negation", 
                    //     "function(data) {  return null; }", 
                    //     "function(m, data, preMarkResult) {  return 'data'; }"
                    // );

                    // var aggregator = epiviz.ui.charts.markers.MeasurementAggregators["negate"];
                    // self._negateData = new epiviz.datatypes.MeasurementAggregatedGenomicData(self._originalData, groupByMarker, aggregator);
                    // self._negateData.ready(function() {
                    //     if (self._negateData.isReady()) {
                    //         // self.chart._lastData = newData;
                    //         // self.chart.draw(self.range, _negateData);
                    //         self.data = self._negateData
                    //     }
                    // })
                }
            }

            /**
            * Initializes the `<epiviz-chart-logratio>` element
            */
            _initializeNegationDialog() {
                var chartSettingsContainer = this.shadowRoot.querySelector('#chartSettingsContainer');
                var chartContainer = this.shadowRoot.querySelector('#' + this.plotId);
                var currSettingIcon = this.shadowRoot.querySelector('#chartNegationIcon');

                if (currSettingIcon == null) {
                    var iconElem = document.createElement('button');
                    iconElem.id = "chartNegationIcon";
                    iconElem.className = "setIcon";
                    iconElem.innerHTML = "Mirror Tracks |";
                    // iconElem.icon = "icons:announcement";

                    iconElem.addEventListener("click", this._showNegationDialog.bind(this));

                    chartSettingsContainer.appendChild(iconElem);
                }
            }

            _toggleNegationIcon() {

                var self = this;


                var dis1 = this.shadowRoot.querySelector('#chartLogRatioIcon');
                var dis2 = this.shadowRoot.querySelector('#chartAverageIcon');
                var active = this.shadowRoot.querySelector('#chartNegationIcon');

                if(self.isNegation) {
                    active.style.color = 'blue';
                }
                else {
                    active.style.color = 'black';

                }

                self.isLogRatio = self.isAverage = false;
                dis1.style.color = dis2.style.color = 'black';
            }
        }
    }
</script><script>
    /**
     * `ChartRemoveBehavior` object manages the `<epiviz-chart-average>` element for each chart. 
     * All charts inherit this behavior to remove itself from DOM.
     *
     * @polymerBehavior
    **/
    EpivizChartAverageBehavior = function (superClass) {
        return class extends superClass {
            constructor() {
                super();
            }

            static get properties() {
                return {};
            }

            /**
            * Shows the remove element
            */
            _showAverageDialog() {
                var self = this;

                self.isAverage  = !self.isAverage;

                self._toggleAverageIcon();

                if (!self.isAverage) {
                    self.chartSettings = self._originalSettings
                    self.data = self._originalData
                    // self.chart.setCustomSettingsValues(self._originalSettings);
                    // self._draw();
                    return;
                }


                if (!self._originalData) {
                    self._originalData = self.data
                }

                if (!self._originalSettings) {
                    self._originalSettings = JSON.parse(JSON.stringify(self.chartSettings))
                }

                self.chartSettings = JSON.parse(JSON.stringify(self._originalSettings));

                    var groupByMarker = new epiviz.ui.charts.markers.VisualizationMarker(
                        "average", "average", "average", 
                        "function(data) {  return null; }", 
                        "function(m, data, preMarkResult) {  return 'average'; }"
                    );

                    var aggregator = epiviz.ui.charts.markers.MeasurementAggregators["mean-stdev"];
                    self._averageData = new epiviz.datatypes.MeasurementAggregatedGenomicData(self._originalData, groupByMarker, aggregator);
                    self._averageData.ready(function() {
                        if (self._averageData.isReady()) {
                            // self.chart._lastData = newData;
                            self.data = self._averageData
                            self.chart.setCustomSettingsValues(self.chartSettings);

                            self._draw();
                        }
                    })
                // }
            }

            /**
            * Initializes the `<epiviz-chart-logratio>` element
            */
            _initializeAverageDialog() {
                var chartSettingsContainer = this.shadowRoot.querySelector('#chartSettingsContainer');
                var chartContainer = this.shadowRoot.querySelector('#' + this.plotId);
                var currSettingIcon = this.shadowRoot.querySelector('#chartAverageIcon');

                if (currSettingIcon == null) {
                    var iconElem = document.createElement('button');
                    iconElem.innerHTML = "Mean |";
                    iconElem.className = "setIcon";
                    iconElem.id = "chartAverageIcon";
                    // iconElem.icon = "icons:alarm";

                    iconElem.addEventListener("click", this._showAverageDialog.bind(this));

                    chartSettingsContainer.appendChild(iconElem);
                }
            }

            _toggleAverageIcon() {
                var self = this;


                var dis1 = this.shadowRoot.querySelector('#chartLogRatioIcon');
                var active = this.shadowRoot.querySelector('#chartAverageIcon');
                var dis2 = this.shadowRoot.querySelector('#chartNegationIcon');

                if(self.isAverage) {
                    active.style.color = 'blue';
                }
                else {
                    active.style.color = 'black';

                }

                self.isLogRatio = self.isNegation = false;

                dis1.style.color = dis2.style.color = 'black';
            }
        }
    }
</script><link rel="import" type="css" href="chart-shared-css.html">

<dom-module id="epiviz-line-track">
    <template>
        <style include="shared-settings"></style>
        <style include="chart-shared-css"></style>
        <style>
            :host {
                width: 100%;
                height: 100%;
                display: inline-block;
                border: 1px solid black;
                border-radius: 5px;
                resize: vertical;
                overflow: auto;
                transition: width 0.01s, height 0.01s;
                position: relative;
            }
        </style>

        <paper-spinner-lite active="" class="green"></paper-spinner-lite>
        <div id="chart" on-mouseover="hostHovered" on-mouseout="hostUnhovered">
            <slot name="dragHandle"></slot>
            <div id="{{plotId}}"></div>
        </div>

    </template>

    <script>
        // Extend Polymer.Element base class
        class EpivizLineTrack extends EpivizChartGridBehavior(
            EpivizChartAverageBehavior(
                EpivizChartNegationBehavior(
                    EpivizChartLogRatioBehavior(
                        EpivizChartRemoveBehavior(
                            EpivizChartColorsBehavior(
                                EpivizChartSettingsBehavior(
                                    EpivizChartBehavior(
                                        Polymer.Element)))))))) {

            static get is() { return 'epiviz-line-track'; }

            static get properties() {
                return {
                    /**
                    * Default chart properties for line track.
                    *
                    * @type {Object}
                    */
                    configSrc: {
                        type: Object,
                        notify: true,
                        value: function () {

                            epiviz.Config.SETTINGS = {
                                dataProviders: [
                                    ["epiviz.data.WebServerDataProvider", "umd", "http://epiviz-dev.cbcb.umd.edu/api/"]
                                ],
                                workspacesDataProvider: sprintf('epiviz.data.EmptyResponseDataProvider', 'empty', ''),
                                useCache: true,

                                chartSettings: {
                                    default: {
                                        colors: 'd3-category10',
                                        decorations: [
                                            'epiviz.ui.charts.decoration.RemoveChartButton',
                                            'epiviz.ui.charts.decoration.SaveChartButton',
                                            'epiviz.ui.charts.decoration.CustomSettingsButton',
                                            'epiviz.ui.charts.decoration.EditCodeButton',

                                            'epiviz.ui.charts.decoration.ChartColorsButton',
                                            'epiviz.ui.charts.decoration.ChartLoaderAnimation',
                                            'epiviz.ui.charts.decoration.ChartResize'
                                        ]
                                    },

                                    plot: {
                                        width: 400,
                                        height: 400,
                                        margins: new epiviz.ui.charts.Margins(15, 30, 30, 15),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    track: {
                                        width: 900,
                                        height: 90,
                                        margins: new epiviz.ui.charts.Margins(25, 20, 23, 10),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    'epiviz.plugins.charts.LineTrack': {
                                        colors: 'epiviz-v2-bright',
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton',
                                            'epiviz.ui.charts.decoration.ChartColorByMeasurementsCodeButton'
                                        ]
                                    },
                                },

                                chartCustomSettings: {
                                    'epiviz.plugins.charts.LineTrack': {
                                        step: 1,
                                        showPoints: false,
                                        showLines: true,
                                        pointRadius: 1,
                                        lineThickness: 2
                                    },
                                },

                                colorPalettes: [
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#025167', '#e7003e', '#ffcd00', '#057d9f', '#970026', '#ffe373', '#ff8100'],
                                        'Epiviz v1.0 Colors', 'epiviz-v1'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Epiviz v2.0 Bright', 'epiviz-v2-bright'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#b8d2eb', '#f2aeac', '#d8e4aa', '#cccccc', '#f2d1b0', '#d4b2d3', '#ddb8a9', '#ebbfd9'],
                                        'Epiviz v2.0 Light', 'epiviz-v2-light'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#599ad3', '#f1595f', '#79c36a', '#727272', '#f9a65a', '#9e66ab', '#cd7058', '#d77fb3'],
                                        'Epiviz v2.0 Medium', 'epiviz-v2-medium'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"],
                                        'D3 Category 10', 'd3-category10'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"],
                                        'D3 Category 20', 'd3-category20'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6"],
                                        'D3 Category 20b', 'd3-category20b'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9"],
                                        'D3 Category 20c', 'd3-category20c'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#f9a65a', '#599ad3', '#79c36a', '#f1595f', '#727272', '#cd7058', '#d77fb3'],
                                        'Genes Default', 'genes-default'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Heatmap Default', 'heatmap-default')
                                ]
                            };

                            return epiviz.Config.SETTINGS;
                        }
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                ]
            }

            constructor() {
                super();
            }

            connectedCallback() {
                super.connectedCallback();

                var self = this;

                if (self.useDefaultDataProvider) {

                    self.measurements = self.measurements || [{
                        'id': 'e027',
                        'name': 'Expression Colon Cancer',
                        'type': 'feature',
                        'datasourceId': 'roadmap_rnaseq',
                        'datasourceGroup': 'roadmap_rnaseq',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }, {
                        'id': 'e066',
                        'name': 'Expression Colon Normal',
                        'type': 'feature',
                        'datasourceId': 'roadmap_rnaseq',
                        'datasourceGroup': 'roadmap_rnaseq',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }];

                    self.range = self.range || new epiviz.datatypes.GenomicRange("chr11", 80000000, 3000000);

                    // self._measurementsChanged();

                    var chartMeasMap = {};
                    chartMeasMap[self.plotId] = self.visConfigSelection.measurements;

                    var dataProviderFactory = new epiviz.data.DataProviderFactory(self.config);
                    var dataManager = new epiviz.data.DataManager(self.config, dataProviderFactory);

                    dataManager.getData(self.range, chartMeasMap, function (id, data) {
                        self.data = data;
                        //self._draw();
                    });
                }
                self._initializeGrid();
                self._measurementsChanged();
            }

            disconnectedCallback() {
                super.connectedCallback();
            }

            ready() {
                super.ready();
                this.plotId = self.plotId || this._generatePlotId();
                // this.scopeSubtree(this.$.chart, true);
                this.config = new epiviz.Config(this.configSrc);
            }

            /**
            * Draws the chart.
            *
            * @param {Object<epiviz.datatypes.GenomicRange>} range genomic range.
            * @param {Object<epiviz.datatypes.MapGenomicData>} data to plot
            */
            _draw() {
                if (this.canvas) {
                    this.chart.drawCanvas(this.range, this.data);
                }
                else {
                    this.chart.draw(this.range, this.data, undefined, undefined, self.isNegation);
                }
                this.shadowRoot.querySelector("paper-spinner-lite").active = false;
            }

            /**
             * Creates an instance of the line track chart.
             *
             * @return {epiviz.plugins.charts.LineTrackType} LineTrack chart object
             */
            _createChart() {
                return new epiviz.plugins.charts.LineTrackType(new epiviz.Config(this.configSrc));
            }
        };

        customElements.define(EpivizLineTrack.is, EpivizLineTrack);

        // Polymer({
        //     /* Custom element html tag */
        //     is: 'epiviz-line-track',
        //     behaviors: [epiviz.ChartBehavior, epiviz.ChartSettingsBehavior, epiviz.ChartColorsBehavior, epiviz.ChartRemoveBehavior, Polymer.IronResizableBehavior, epiviz.ChartDraggableBehavior],
        // });
    </script>
</dom-module><link rel="import" type="css" href="chart-shared-css.html">

<dom-module id="epiviz-line-track-mirror">
    <template>
        <style include="shared-settings"></style>
        <style include="chart-shared-css"></style>
        <style>
            :host {
                width: 100%;
                height: 100%;
                display: inline-block;
                border: 1px solid black;
                border-radius: 5px;
                resize: vertical;
                overflow: auto;
                transition: width 0.01s, height 0.01s;
                position: relative;
            }
        </style>

        <paper-spinner-lite active="" class="green"></paper-spinner-lite>
        <div id="chart" on-mouseover="hostHovered" on-mouseout="hostUnhovered">
            <slot name="dragHandle"></slot>
            <div id="{{plotId}}"></div>
        </div>

    </template>

    <script>
        // Extend Polymer.Element base class
        class EpivizLineTrackMirror extends EpivizChartGridBehavior(
            EpivizChartAverageBehavior(
                EpivizChartNegationBehavior(
                    EpivizChartLogRatioBehavior(
                        EpivizChartRemoveBehavior(
                            EpivizChartColorsBehavior(
                                EpivizChartSettingsBehavior(
                                    EpivizChartBehavior(
                                        Polymer.Element)))))))) {

            static get is() { return 'epiviz-line-track-mirror'; }

            static get properties() {
                return {
                    /**
                    * Default chart properties for line track.
                    *
                    * @type {Object}
                    */
                    configSrc: {
                        type: Object,
                        notify: true,
                        value: function () {

                            epiviz.Config.SETTINGS = {
                                dataProviders: [
                                    ["epiviz.data.WebServerDataProvider", "umd", "http://epiviz-dev.cbcb.umd.edu/api/"]
                                ],
                                workspacesDataProvider: sprintf('epiviz.data.EmptyResponseDataProvider', 'empty', ''),
                                useCache: true,

                                chartSettings: {
                                    default: {
                                        colors: 'd3-category10',
                                        decorations: [
                                            'epiviz.ui.charts.decoration.RemoveChartButton',
                                            'epiviz.ui.charts.decoration.SaveChartButton',
                                            'epiviz.ui.charts.decoration.CustomSettingsButton',
                                            'epiviz.ui.charts.decoration.EditCodeButton',

                                            'epiviz.ui.charts.decoration.ChartColorsButton',
                                            'epiviz.ui.charts.decoration.ChartLoaderAnimation',
                                            'epiviz.ui.charts.decoration.ChartResize'
                                        ]
                                    },

                                    plot: {
                                        width: 400,
                                        height: 400,
                                        margins: new epiviz.ui.charts.Margins(15, 30, 30, 15),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    track: {
                                        width: 900,
                                        height: 90,
                                        margins: new epiviz.ui.charts.Margins(25, 20, 23, 10),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    'epiviz.plugins.charts.LineTrack': {
                                        colors: 'epiviz-v2-bright',
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton',
                                            'epiviz.ui.charts.decoration.ChartColorByMeasurementsCodeButton'
                                        ]
                                    },
                                },

                                chartCustomSettings: {
                                    'epiviz.plugins.charts.LineTrack': {
                                        step: 1,
                                        showPoints: false,
                                        showLines: true,
                                        pointRadius: 1,
                                        lineThickness: 2
                                    },
                                },

                                colorPalettes: [
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#025167', '#e7003e', '#ffcd00', '#057d9f', '#970026', '#ffe373', '#ff8100'],
                                        'Epiviz v1.0 Colors', 'epiviz-v1'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Epiviz v2.0 Bright', 'epiviz-v2-bright'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#b8d2eb', '#f2aeac', '#d8e4aa', '#cccccc', '#f2d1b0', '#d4b2d3', '#ddb8a9', '#ebbfd9'],
                                        'Epiviz v2.0 Light', 'epiviz-v2-light'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#599ad3', '#f1595f', '#79c36a', '#727272', '#f9a65a', '#9e66ab', '#cd7058', '#d77fb3'],
                                        'Epiviz v2.0 Medium', 'epiviz-v2-medium'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"],
                                        'D3 Category 10', 'd3-category10'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"],
                                        'D3 Category 20', 'd3-category20'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6"],
                                        'D3 Category 20b', 'd3-category20b'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9"],
                                        'D3 Category 20c', 'd3-category20c'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#f9a65a', '#599ad3', '#79c36a', '#f1595f', '#727272', '#cd7058', '#d77fb3'],
                                        'Genes Default', 'genes-default'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Heatmap Default', 'heatmap-default')
                                ]
                            };

                            return epiviz.Config.SETTINGS;
                        }
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                ]
            }

            constructor() {
                super();
            }

            connectedCallback() {
                super.connectedCallback();

                var self = this;
                self._initializeGrid();
                self._measurementsChanged();
            }

            disconnectedCallback() {
                super.connectedCallback();
            }

            ready() {
                super.ready();
                this.plotId = self.plotId || this._generatePlotId();
                // this.scopeSubtree(this.$.chart, true);
                this.config = new epiviz.Config(this.configSrc);
            }

            /**
            * Draws the chart.
            *
            * @param {Object<epiviz.datatypes.GenomicRange>} range genomic range.
            * @param {Object<epiviz.datatypes.MapGenomicData>} data to plot
            */
            _draw() {
                var self = this;


                if (self.invertcell) {
                    var data = this.get_invert_data_mirror1(this.data, this.invertcell);
                }
                else {
                    var data = this.get_invert_data(this.data);
                }

                this.isNegation = true;

                if (this.canvas) {
                    this.chart.drawCanvas(this.range, this.data);
                }
                else {
                    this.chart.draw(this.range, data, undefined, undefined, self.isNegation);
                }

                this.shadowRoot.querySelector("paper-spinner-lite").active = false;
            }

            get_invert_data_mirror1(lData, celltype) {
                var self = this;

                var sumExp = new epiviz.datatypes.PartialSummarizedExperiment();
                var counter = 0;
                // var invert = 0;

                lData.foreach(function(measurement, series, seriesIndex) {
                    if(counter == 0) {
                        var rowData = series._container.rowData(measurement);
                        sumExp._rowData = rowData;
                        counter++;
                    }

                    var featureValues = series._container.values(measurement);
                    var valData = [];

                    if(featureValues._values != undefined) {

                        if(measurement.id().indexOf(celltype) == -1) {
                            featureValues._values.forEach(function(val, i) {
                                valData[i] = -1 * val; 
                            });
                        }
                        else {
                            featureValues._values.forEach(function(val, i) {
                                valData[i] = val; 
                            });
                        }

                        // invert++;
                    }
                    else {
                        valData = undefined;
                    }

                    var newValueData = new epiviz.datatypes.FeatureValueArray(measurement, featureValues._boundaries, featureValues._globalStartIndex, valData);
                    sumExp.addValues(newValueData);
                    // invert++;
                });

                var msDataMap = new epiviz.measurements.MeasurementHashtable();

                lData.foreach(function(m) {
                    m._maxValue = m._maxValue;
                    m._minValue = -1 * m._maxValue;
                    var msData = new epiviz.datatypes.MeasurementGenomicDataWrapper(m, sumExp);
                    msDataMap.put(m, msData);
                });

                var genomicData = new epiviz.datatypes.MapGenomicData(msDataMap);

                return genomicData
            }

            get_invert_data(lData) {
                var self = this;

                var sumExp = new epiviz.datatypes.PartialSummarizedExperiment();
                var counter = 0;
                // var invert = 0;

                lData.foreach(function(measurement, series, seriesIndex) {
                    if(counter == 0) {
                        var rowData = series._container.rowData(measurement);
                        sumExp._rowData = rowData;
                        counter++;
                    }

                    var featureValues = series._container.values(measurement);
                    var valData = [];

                    if(featureValues._values != undefined) {
                        // if (measurement.name().indexOf("Lamp5") != -1 || measurement.name().indexOf("Sst") != -1 ||
                        // measurement.name().indexOf("Vip") != -1 || measurement.name().indexOf("Pvalb") != -1) {
                        if( (measurement.name().indexOf("Lamp5") != -1 || measurement.name().indexOf("Sst") != -1 ||
                        measurement.name().indexOf("Vip") != -1 || measurement.name().indexOf("Pvalb") != -1) || (measurement.id().indexOf("gaba") != -1)) {
                            featureValues._values.forEach(function(val, i) {
                                valData[i] = -1 * val; 
                            });
                        }
                        else {
                            featureValues._values.forEach(function(val, i) {
                                valData[i] = val; 
                            });
                        }
                    }
                    else {
                        valData = undefined;
                    }

                    var newValueData = new epiviz.datatypes.FeatureValueArray(measurement, featureValues._boundaries, featureValues._globalStartIndex, valData);
                    sumExp.addValues(newValueData);
                    // invert++;
                });

                var msDataMap = new epiviz.measurements.MeasurementHashtable();

                lData.foreach(function(m) {
                    m._maxValue = m._maxValue;
                    m._minValue = -1 * m._maxValue;
                    var msData = new epiviz.datatypes.MeasurementGenomicDataWrapper(m, sumExp);
                    msDataMap.put(m, msData);
                });

                var genomicData = new epiviz.datatypes.MapGenomicData(msDataMap);

                return genomicData
            }

            /**
             * Creates an instance of the line track chart.
             *
             * @return {epiviz.plugins.charts.LineTrackType} LineTrack chart object
             */
            _createChart() {
                return new epiviz.plugins.charts.LineTrackType(new epiviz.Config(this.configSrc));
            }
        };

        customElements.define(EpivizLineTrackMirror.is, EpivizLineTrackMirror);

        // Polymer({
        //     /* Custom element html tag */
        //     is: 'epiviz-line-track-mirror',
        //     behaviors: [epiviz.ChartBehavior, epiviz.ChartSettingsBehavior, epiviz.ChartColorsBehavior, epiviz.ChartRemoveBehavior, Polymer.IronResizableBehavior, epiviz.ChartDraggableBehavior],
        // });
    </script>
</dom-module><link rel="import" type="css" href="chart-shared-css.html">

<dom-module id="epiviz-scatter-plot">
    <template>
        <style include="shared-settings"></style>
        <style include="chart-shared-css"></style>
        <style>
            :host {
                width: 100%;
                height: 100%;
                border: 1px solid black;
                border-radius: 5px;
                display: inline-block;
                transition: width 0.01s, height 0.01s;
                resize: vertical;
                overflow: auto;
                position: relative;
            }
        </style>

        <paper-spinner-lite active="" class="green"></paper-spinner-lite>
        <div id="chart" on-mouseover="hostHovered" on-mouseout="hostUnhovered">
            <slot name="dragHandle"></slot>
            <div id="{{plotId}}"></div>
        </div>

    </template>

    <script>
        // Extend Polymer.Element base class
        class EpivizScatterPlot extends EpivizChartGridBehavior(EpivizChartRemoveBehavior(EpivizChartColorsBehavior(EpivizChartSettingsBehavior(EpivizChartBehavior(Polymer.Element))))) {

            static get is() { return 'epiviz-scatter-plot'; }

            static get properties() {
                return {
                    /**
                    * Default chart properties for scatter plot.
                    *
                    * @type {Object}
                    */
                    configSrc: {
                        type: Object,
                        notify: true,
                        value: function () {

                            epiviz.Config.SETTINGS = {
                                dataProviders: [
                                    ["epiviz.data.WebServerDataProvider", "umd", "https://epiviz-dev.cbcb.umd.edu/api/"]
                                ],
                                workspacesDataProvider: sprintf('epiviz.data.EmptyResponseDataProvider', 'empty', ''),
                                useCache: true,

                                chartSettings: {
                                    default: {
                                        colors: 'd3-category10',
                                        decorations: [
                                            'epiviz.ui.charts.decoration.RemoveChartButton',
                                            'epiviz.ui.charts.decoration.SaveChartButton',
                                            'epiviz.ui.charts.decoration.CustomSettingsButton',
                                            'epiviz.ui.charts.decoration.EditCodeButton',

                                            'epiviz.ui.charts.decoration.ChartColorsButton',
                                            'epiviz.ui.charts.decoration.ChartLoaderAnimation',
                                            'epiviz.ui.charts.decoration.ChartResize'
                                        ]
                                    },

                                    plot: {
                                        width: 400,
                                        height: 400,
                                        margins: new epiviz.ui.charts.Margins(15, 30, 30, 15),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    track: {
                                        width: '100%',
                                        height: 90,
                                        margins: new epiviz.ui.charts.Margins(25, 20, 23, 10),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    'epiviz.plugins.charts.ScatterPlot': {
                                        margins: new epiviz.ui.charts.Margins(15, 50, 50, 15),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ChartColorByRowCodeButton'
                                        ]
                                    }
                                },

                                chartCustomSettings: {
                                    'epiviz.plugins.charts.ScatterPlot': {
                                        circleRadiusRatio: 0.01
                                    }
                                },

                                colorPalettes: [
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#025167', '#e7003e', '#ffcd00', '#057d9f', '#970026', '#ffe373', '#ff8100'],
                                        'Epiviz v1.0 Colors', 'epiviz-v1'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Epiviz v2.0 Bright', 'epiviz-v2-bright'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#b8d2eb', '#f2aeac', '#d8e4aa', '#cccccc', '#f2d1b0', '#d4b2d3', '#ddb8a9', '#ebbfd9'],
                                        'Epiviz v2.0 Light', 'epiviz-v2-light'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#599ad3', '#f1595f', '#79c36a', '#727272', '#f9a65a', '#9e66ab', '#cd7058', '#d77fb3'],
                                        'Epiviz v2.0 Medium', 'epiviz-v2-medium'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"],
                                        'D3 Category 10', 'd3-category10'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"],
                                        'D3 Category 20', 'd3-category20'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6"],
                                        'D3 Category 20b', 'd3-category20b'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9"],
                                        'D3 Category 20c', 'd3-category20c'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#f9a65a', '#599ad3', '#79c36a', '#f1595f', '#727272', '#cd7058', '#d77fb3'],
                                        'Genes Default', 'genes-default'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Heatmap Default', 'heatmap-default')
                                ]
                            };

                            return epiviz.Config.SETTINGS;
                        }
                    },

                    colorByRegions: {
                        type: Array,
                        notify: true
                        // observer: "_colorByRegionsChanged"
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                ]
            }

            constructor() {
                super();
            }

            connectedCallback() {
                super.connectedCallback();
                var self = this;

                if (self.useDefaultDataProvider) {

                    self.measurements = self.measurements || [{
                        'id': 'e027',
                        'name': 'Expression Colon Cancer',
                        'type': 'feature',
                        'datasourceId': 'roadmap_rnaseq',
                        'datasourceGroup': 'roadmap_rnaseq',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }, {
                        'id': 'e066',
                        'name': 'Expression Colon Normal',
                        'type': 'feature',
                        'datasourceId': 'roadmap_rnaseq',
                        'datasourceGroup': 'roadmap_rnaseq',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }];

                    self.range = self.range || new epiviz.datatypes.GenomicRange("chr11", 80000000, 3000000);

                    self._measurementsChanged();

                    var chartMeasMap = {};
                    chartMeasMap[self.plotId] = self.visConfigSelection.measurements;

                    var dataProviderFactory = new epiviz.data.DataProviderFactory(self.config);
                    var dataManager = new epiviz.data.DataManager(self.config, dataProviderFactory);

                    dataManager.getData(self.range, chartMeasMap, function (id, data) {
                        self.data = data;
                        //self._draw();
                    });
                }

                self._initializeGrid();
                self._measurementsChanged();
            }

            disconnectedCallback() {
                super.connectedCallback();
            }

            ready() {
                super.ready();
                this.plotId = self.plotId || this._generatePlotId();
                // this.scopeSubtree(this.$.chart, true);
                this.config = new epiviz.Config(this.configSrc);
            }

            /**
            * Draws the chart.
            *
            * @param {Object<epiviz.datatypes.GenomicRange>} range genomic range.
            * @param {Object<epiviz.datatypes.MapGenomicData>} data to plot
            */
            _draw() {
                var time = Date.now();
                if (this.canvas) {
                    this.chart.drawCanvas(this.range, this.data);
                }
                else {
                    this.chart.draw(this.range, this.data);
                }
                // console.error('{ "total_start_time" : ' + (Date.now() - time) + "}");
                var event = new CustomEvent("total_draw_time", { detail: { "total_draw_time": Date.now() - time } });
                document.dispatchEvent(event);
                // if (this.colorByRegions) {
                //     this._colorByRegionsChanged();
                // }
                this.shadowRoot.querySelector("paper-spinner-lite").active = false;
            }

            /**
             * Creates an instance of the scatter plot chart.
             *
             * @return {epiviz.plugins.charts.ScatterPlotType} ScatterPlot chart object
             */
            _createChart() {
                return new epiviz.plugins.charts.ScatterPlotType(new epiviz.Config(this.configSrc));
            }

            /**
            * Color data points on scatterplot by regions
            *
            * @return {epiviz.plugins.charts.GenesTrackType} BlocksTrack chart object
            */
            _colorByRegionsChanged() {
                var self = this;
                var itemsGroup = self.chart._chartContent.select('.items');
                var selection = itemsGroup.selectAll('circle');

                var chartRanges = self.colorByRegions;

                // self.colorByRegions.forEach(function(region) {
                //     chartRanges.push(
                //         region.seqName(), 
                //         region.start(), 
                //         region.end() - region.start());
                // });

                selection
                    .each(
                        /**
                         * @param {epiviz.ui.charts.ChartObject} d
                         */
                        function (d) {
                            var circle = d3.select(this);

                            var dRange = new epiviz.datatypes.GenomicRange(
                                d.valueItems[0][0].rowItem.seqName(),
                                d.valueItems[0][0].rowItem.start(),
                                d.valueItems[0][0].rowItem.end() - d.valueItems[0][0].rowItem.start());

                            var inRegion = false;

                            chartRanges.forEach(function (r, i) {
                                if (dRange.overlapsWith(r)) {
                                    var fill = self.chart.colors().get(i + 1);
                                    circle.style('fill', fill);
                                    inRegion = true;
                                }
                                else if (!inRegion && i == chartRanges.length - 1) {
                                    var fill = self.chart.colors().get(0);
                                    circle.style('fill', fill);
                                    inRegion = true;
                                }
                            });
                        });
            }
        };

        customElements.define(EpivizScatterPlot.is, EpivizScatterPlot);
    </script>
</dom-module><link rel="import" type="css" href="chart-shared-css.html">

<dom-module id="epiviz-stacked-line-track">
    <template>
        <style include="shared-settings"></style>
        <style include="chart-shared-css"></style>
        <style>
            :host {
                width: 100%;
                height: 100%;
                display: inline-block;
                border: 1px solid black;
                border-radius: 5px;
                resize: vertical;
                overflow: auto;
                transition: width 0.01s, height 0.01s;
                position: relative;
            }
        </style>

        <paper-spinner-lite active="" class="green"></paper-spinner-lite>
        <div id="chart" on-mouseover="hostHovered" on-mouseout="hostUnhovered">
            <slot name="dragHandle"></slot>
            <div id="{{plotId}}"></div>
        </div>

    </template>

    <script>

        // Extend Polymer.Element base class
        class EpivizStackedLineTrack extends EpivizChartGridBehavior(EpivizChartRemoveBehavior(EpivizChartColorsBehavior(EpivizChartSettingsBehavior(EpivizChartBehavior(Polymer.Element))))) {

            static get is() { return 'epiviz-stacked-line-track'; }

            static get properties() {
                return {
                    /**
                    * Default chart properties for stacked-line track.
                    *
                    * @type {Object}
                    */
                    configSrc: {
                        type: Object,
                        notify: true,
                        value: function () {

                            epiviz.Config.SETTINGS = {
                                dataProviders: [
                                    ["epiviz.data.WebServerDataProvider", "umd", "http://epiviz-dev.cbcb.umd.edu/api/"]
                                ],
                                workspacesDataProvider: sprintf('epiviz.data.EmptyResponseDataProvider', 'empty', ''),
                                useCache: true,

                                chartSettings: {
                                    default: {
                                        colors: 'd3-category10',
                                        decorations: [
                                            'epiviz.ui.charts.decoration.RemoveChartButton',
                                            'epiviz.ui.charts.decoration.SaveChartButton',
                                            'epiviz.ui.charts.decoration.CustomSettingsButton',
                                            'epiviz.ui.charts.decoration.EditCodeButton',

                                            'epiviz.ui.charts.decoration.ChartColorsButton',
                                            'epiviz.ui.charts.decoration.ChartLoaderAnimation',
                                            'epiviz.ui.charts.decoration.ChartResize'
                                        ]
                                    },

                                    plot: {
                                        width: 400,
                                        height: 400,
                                        margins: new epiviz.ui.charts.Margins(15, 30, 30, 15),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    track: {
                                        width: 900,
                                        height: 90,
                                        margins: new epiviz.ui.charts.Margins(25, 20, 23, 10),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    'epiviz.plugins.charts.StackedLineTrack': {
                                        height: 300
                                    },
                                },

                                chartCustomSettings: {

                                },

                                colorPalettes: [
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#025167', '#e7003e', '#ffcd00', '#057d9f', '#970026', '#ffe373', '#ff8100'],
                                        'Epiviz v1.0 Colors', 'epiviz-v1'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Epiviz v2.0 Bright', 'epiviz-v2-bright'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#b8d2eb', '#f2aeac', '#d8e4aa', '#cccccc', '#f2d1b0', '#d4b2d3', '#ddb8a9', '#ebbfd9'],
                                        'Epiviz v2.0 Light', 'epiviz-v2-light'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#599ad3', '#f1595f', '#79c36a', '#727272', '#f9a65a', '#9e66ab', '#cd7058', '#d77fb3'],
                                        'Epiviz v2.0 Medium', 'epiviz-v2-medium'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"],
                                        'D3 Category 10', 'd3-category10'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"],
                                        'D3 Category 20', 'd3-category20'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6"],
                                        'D3 Category 20b', 'd3-category20b'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9"],
                                        'D3 Category 20c', 'd3-category20c'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#f9a65a', '#599ad3', '#79c36a', '#f1595f', '#727272', '#cd7058', '#d77fb3'],
                                        'Genes Default', 'genes-default'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Heatmap Default', 'heatmap-default')
                                ]
                            };

                            return epiviz.Config.SETTINGS;
                        }
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                ]
            }

            constructor() {
                super();
            }

            connectedCallback() {
                super.connectedCallback();

                var self = this;

                if (self.useDefaultDataProvider) {

                    self.measurements = self.measurements || [{
                        'id': 'e027',
                        'name': 'Expression Colon Cancer',
                        'type': 'feature',
                        'datasourceId': 'roadmap_rnaseq',
                        'datasourceGroup': 'roadmap_rnaseq',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }, {
                        'id': 'e066',
                        'name': 'Expression Colon Normal',
                        'type': 'feature',
                        'datasourceId': 'roadmap_rnaseq',
                        'datasourceGroup': 'roadmap_rnaseq',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }];

                    self.range = self.range || new epiviz.datatypes.GenomicRange("chr11", 80000000, 3000000);

                    // self._measurementsChanged();

                    var chartMeasMap = {};
                    chartMeasMap[self.plotId] = self.visConfigSelection.measurements;

                    var dataProviderFactory = new epiviz.data.DataProviderFactory(self.config);
                    var dataManager = new epiviz.data.DataManager(self.config, dataProviderFactory);

                    dataManager.getData(self.range, chartMeasMap, function (id, data) {
                        self.data = data;
                    });
                }

                self._initializeGrid();
                self._measurementsChanged();
            }

            disconnectedCallback() {
                super.connectedCallback();
            }

            ready() {
                super.ready();
                this.plotId = self.plotId || this._generatePlotId();
                // this.scopeSubtree(this.$.chart, true);
                this.config = new epiviz.Config(this.configSrc);
            }

            /**
            * Draws the chart.
            *
            * @param {Object<epiviz.datatypes.GenomicRange>} range genomic range.
            * @param {Object<epiviz.datatypes.MapGenomicData>} data to plot
            */
            _draw() {
                if (this.canvas) {
                    this.chart.drawCanvas(this.range, this.data);
                }
                else {
                    this.chart.draw(this.range, this.data);
                }
                this.shadowRoot.querySelector("paper-spinner-lite").active = false;
            }

            /**
             * Creates an instance of the stacked-line track chart.
             *
             * @return {epiviz.plugins.charts.StackedLineTrackType} StackedLineTrack chart object
             */
            _createChart() {
                return new epiviz.plugins.charts.StackedLineTrackType(new epiviz.Config(this.configSrc));
            }
        };

        customElements.define(EpivizStackedLineTrack.is, EpivizStackedLineTrack);

        // Polymer({
        //     /* Custom element html tag */
        //     is: 'epiviz-stacked-line-track',
        //     behaviors: [epiviz.ChartBehavior, epiviz.ChartSettingsBehavior, epiviz.ChartColorsBehavior, epiviz.ChartRemoveBehavior, Polymer.IronResizableBehavior, epiviz.ChartDraggableBehavior],
        // });
    </script>
</dom-module><link rel="import" type="css" href="chart-shared-css.html">

<dom-module id="epiviz-stacked-line-plot">
    <template>
        <style include="shared-settings"></style>
        <style include="chart-shared-css"></style>
        <style>
            :host {
                width: 100%;
                height: 100%;
                display: inline-block;
                border: 1px solid black;
                border-radius: 5px;
                resize: vertical;
                overflow: auto;
                transition: width 0.01s, height 0.01s;
                position: relative;
            }
        </style>

        <paper-spinner-lite active="" class="green"></paper-spinner-lite>
        <div id="chart" on-mouseover="hostHovered" on-mouseout="hostUnhovered">
            <slot name="dragHandle"></slot>
            <div id="{{plotId}}"></div>
        </div>

    </template>

    <script>
        // Extend Polymer.Element base class
        class EpivizStackedLinePlot extends EpivizChartGridBehavior(EpivizChartRemoveBehavior(EpivizChartColorsBehavior(EpivizChartSettingsBehavior(EpivizChartBehavior(Polymer.Element))))) {

            static get is() { return 'epiviz-stacked-line-plot'; }

            static get properties() {
                return {
                    /**
                    * Default chart properties for stacked-line plot.
                    *
                    * @type {Object}
                    */
                    configSrc: {
                        type: Object,
                        notify: true,
                        value: function () {

                            epiviz.Config.SETTINGS = {
                                dataProviders: [
                                    ["epiviz.data.WebServerDataProvider", "umd", "http://epiviz-dev.cbcb.umd.edu/api/"]
                                ],
                                workspacesDataProvider: sprintf('epiviz.data.EmptyResponseDataProvider', 'empty', ''),
                                useCache: true,

                                chartSettings: {
                                    default: {
                                        colors: 'd3-category10',
                                        decorations: [
                                            'epiviz.ui.charts.decoration.RemoveChartButton',
                                            'epiviz.ui.charts.decoration.SaveChartButton',
                                            'epiviz.ui.charts.decoration.CustomSettingsButton',
                                            'epiviz.ui.charts.decoration.EditCodeButton',

                                            'epiviz.ui.charts.decoration.ChartColorsButton',
                                            'epiviz.ui.charts.decoration.ChartLoaderAnimation',
                                            'epiviz.ui.charts.decoration.ChartResize'
                                        ]
                                    },

                                    plot: {
                                        width: 400,
                                        height: 400,
                                        margins: new epiviz.ui.charts.Margins(15, 30, 30, 15),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    track: {
                                        width: '100%',
                                        height: 90,
                                        margins: new epiviz.ui.charts.Margins(25, 20, 23, 10),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    'epiviz.plugins.charts.StackedLinePlot': {
                                        width: 800,
                                        height: 400,
                                        margins: new epiviz.ui.charts.Margins(30, 30, 50, 15),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ChartGroupByMeasurementsCodeButton',
                                            'epiviz.ui.charts.decoration.ChartColorByRowCodeButton',
                                            'epiviz.ui.charts.decoration.ChartOrderByMeasurementsCodeButton'
                                        ],
                                        colors: 'd3-category20b'
                                    }
                                },

                                chartCustomSettings: {
                                    'epiviz.plugins.charts.StackedLinePlot': {
                                        colLabel: 'label'
                                    }
                                },

                                colorPalettes: [
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#025167', '#e7003e', '#ffcd00', '#057d9f', '#970026', '#ffe373', '#ff8100'],
                                        'Epiviz v1.0 Colors', 'epiviz-v1'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Epiviz v2.0 Bright', 'epiviz-v2-bright'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#b8d2eb', '#f2aeac', '#d8e4aa', '#cccccc', '#f2d1b0', '#d4b2d3', '#ddb8a9', '#ebbfd9'],
                                        'Epiviz v2.0 Light', 'epiviz-v2-light'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#599ad3', '#f1595f', '#79c36a', '#727272', '#f9a65a', '#9e66ab', '#cd7058', '#d77fb3'],
                                        'Epiviz v2.0 Medium', 'epiviz-v2-medium'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"],
                                        'D3 Category 10', 'd3-category10'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"],
                                        'D3 Category 20', 'd3-category20'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6"],
                                        'D3 Category 20b', 'd3-category20b'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9"],
                                        'D3 Category 20c', 'd3-category20c'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#f9a65a', '#599ad3', '#79c36a', '#f1595f', '#727272', '#cd7058', '#d77fb3'],
                                        'Genes Default', 'genes-default'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Heatmap Default', 'heatmap-default')
                                ]
                            };

                            return epiviz.Config.SETTINGS;
                        }
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                ]
            }

            constructor() {
                super();
            }

            connectedCallback() {
                super.connectedCallback();

                var self = this;

                if (self.useDefaultDataProvider) {

                    self.measurements = self.measurements || [{
                        'id': 'e027',
                        'name': 'Expression Colon Cancer',
                        'type': 'feature',
                        'datasourceId': 'roadmap_rnaseq',
                        'datasourceGroup': 'roadmap_rnaseq',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }, {
                        'id': 'e066',
                        'name': 'Expression Colon Normal',
                        'type': 'feature',
                        'datasourceId': 'roadmap_rnaseq',
                        'datasourceGroup': 'roadmap_rnaseq',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }];

                    self.range = self.range || new epiviz.datatypes.GenomicRange("chr11", 80000000, 3000000);

                    // self._measurementsChanged();

                    var chartMeasMap = {};
                    chartMeasMap[self.plotId] = self.visConfigSelection.measurements;

                    var dataProviderFactory = new epiviz.data.DataProviderFactory(self.config);
                    var dataManager = new epiviz.data.DataManager(self.config, dataProviderFactory);

                    dataManager.getData(self.range, chartMeasMap, function (id, data) {
                        self.data = data;
                    });
                }
                self._initializeGrid();
                self._measurementsChanged();
            }

            disconnectedCallback() {
                super.connectedCallback();
            }

            ready() {
                super.ready();
                this.plotId = self.plotId || this._generatePlotId();
                // this.scopeSubtree(this.$.chart, true);
                this.config = new epiviz.Config(this.configSrc);
            }

            /**
            * Draws the chart.
            *
            * @param {Object<epiviz.datatypes.GenomicRange>} range genomic range.
            * @param {Object<epiviz.datatypes.MapGenomicData>} data to plot
            */
            _draw() {
                if (this.canvas) {
                    this.chart.drawCanvas(this.range, this.data);
                }
                else {
                    this.chart.draw(this.range, this.data);
                }
                this.shadowRoot.querySelector("paper-spinner-lite").active = false;
            }

            /**
             * Creates an instance of the stacked-line plot chart.
             *
             * @return {epiviz.plugins.charts.StackedLinePlotType} StackedLinePlot chart object
             */
            _createChart() {
                return new epiviz.plugins.charts.StackedLinePlotType(new epiviz.Config(this.configSrc));
            }
        };

        customElements.define(EpivizStackedLinePlot.is, EpivizStackedLinePlot);

        // Polymer({
        //     /* Custom element html tag */
        //     is: 'epiviz-stacked-line-plot',
        //     behaviors: [epiviz.ChartBehavior, epiviz.ChartSettingsBehavior, epiviz.ChartColorsBehavior, epiviz.ChartRemoveBehavior, Polymer.IronResizableBehavior, epiviz.ChartDraggableBehavior],
        // });
    </script>
</dom-module><dom-module id="epiviz-chart-showtracks">

    <template>
        <style>
            paper-dropdown {
                --paper-input-container-label: {
                    /* color: var(--paper-pink-500); */
                    font-style: italic;
                    /* text-align: center; */
                    font-weight: bold;
                    /* width: 150px; */
                };
                --paper-input-container-input: {
                    color: var(--paper-indigo-500);
                    font-style: normal;
                    font-family: serif;
                    text-transform: uppercase;
                    width: 150px;
                };
                /* Only show the underline when focused. */
                --paper-input-container-underline: {
                    display: none;
                    width: 150px;
                };
                --paper-input-container-underline-focus: {
                    border-color: var(--paper-cyan-500);
                    width: 150px;
                };

                --paper-dropdown-menu: {
                    min-width: 100px;
                }

                --paper-listbox: {
                    min-width: 150px;
                }
            }

            paper-item {
                font-style: italic !important;
                font-weight: normal !important;
            }

            paper-item.iron-selected {
                font-style: normal !important;
                font-weight: bold !important;
            }

        </style>

        <paper-dropdown label="Hide/Show Tracks" value="{{selectedItems}}" multi="" no-animations="" no-label-float="">
            <template id="itemRepeater" is="dom-repeat" items="[[items]]">
                <paper-item value$="[[item]]">[[item]]</paper-item>
            </template>
        </paper-dropdown>

        </template>

    <script>
        // Extend Polymer.Element base class
        class EpivizChartShowTracks extends Polymer.Element {

            static get is() { return 'epiviz-chart-showtracks'; }

            static get properties() {
                return {
                    items: {
                        type: Array,
                        notify: true
                    },
                    
                    settings: {
                        type: Object,
                        notify: true
                    },

                    selectedItems: {
                        type: Array,
                        notify: true,
                        observer: "_onSelectedItemsChanged"
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                ]
            }

            constructor() {
                super();
            }

            connectedCallback() {
                var self = this;
                super.connectedCallback();

                if (this.settings["showTracks"] == "default") {
                    self.selectedItems = this.items;
                } else {
                    self.selectedItems = this.settings["showTracks"].split(",");
                }
            }

            disconnectedCallback() {
                super.connectedCallback();
            }

            /**
             * handles form submit action
             */
            _submit(event) {
                this.$.formSettings.submit();
            }

            _onSelectedItemsChanged(newVal, oldVal) {
                var self = this;
                if (newVal && newVal.length > 0) {
                    this.settings["showTracks"] = newVal.join(",");
                    self._parentNode.chartSettings = this.settings;
                    // TODO: have to force change currently, not sure why!
                    self._parentNode._draw();
                }
            }

            /**
             * handles form submit action
             */
            _form_submit(event) {
                var self = this;
                this.settings["showTracks"] = this.shadowRoot.querySelector("paper-dropdown").selectedItems.join(",");

                this.callback(this.settings);
                this.closeSettings();
            }

            /**
             * handles form cancel action
             */
            _cancel(event) {
                this.closeSettings();
            }

            /**
             * handles form show modal action
             */
            showSettings(target, callback) {
                this.callback = callback;
                this.$.modal.open();
            }

            /**
             * handles form close modal action
             */
            closeSettings() {
                this.$.modal.close();
            }
        };

        customElements.define(EpivizChartShowTracks.is, EpivizChartShowTracks);
    </script>
</dom-module><script>
    /**
     * `ChartSettingsBehavior` object manages the `<epiviz-chart-settings>` element for each chart. 
     * All charts inherit this behavior to update chart settings.
     *
     * @polymerBehavior
    **/
    EpivizChartShowTracksBehavior = function (superClass) {
        return class extends superClass {
            constructor() {
                super();
            }

            static get properties() {
                return {};
            }

            /**
            * Shows the `<epiviz-chart-settings>` element
            */
            _showTracksDialog() {
                var self = this;
                var chartContainer = this.shadowRoot.querySelector('#' + this.plotId);
                var currSetting = this.shadowRoot.querySelector('epiviz-chart-showtracks');

                var options = [];
                if(self.dimS && self.dimS.length > 0) {
                    
                    self.dimS.forEach(function(m) {
                        options.push(m);
                    });
                } else if (self.measurements.length > 0) {
                    self.measurements.forEach(function(m) {
                        options.push(m.id);
                    });
                }

                if (currSetting == null) {
                    var currSetting = document.createElement('epiviz-chart-showtracks');
                  
                    currSetting.setAttribute('items', JSON.stringify(options));
                    currSetting.setAttribute('settings', JSON.stringify(self.chartSettings));
                    chartContainer.appendChild(currSetting);
                }
                else {
                    currSetting.setAttribute('items', JSON.stringify(options));
                    currSetting.setAttribute('settings', JSON.stringify(self.chartSettings));
                }

                currSetting.showSettings(this.shadowRoot, function (newSettings) {
                    self.chartSettings = newSettings;
                });
            }

            /**
            * Initializes the `<epiviz-chart-settings>` element
            */
            _initializeShowTracksDialog() {
                var self = this;

                var chartSettingsContainer = this.shadowRoot.querySelector('#chartSettingsLeftContainer');
                var currSettingIcon = this.shadowRoot.querySelector('#chartShowTracksIcon');

                if (currSettingIcon == null) {
                    var currSetting = document.createElement('epiviz-chart-showtracks');
                    currSetting.id = "chartShowTracksIcon";

                    var options = [];
                    if(self.dimS && self.dimS.length > 0) {
                        
                        self.dimS.forEach(function(m) {
                            options.push(m);
                        });
                    } else if (self.measurements.length > 0) {
                        self.measurements.forEach(function(m) {
                            options.push(m.id);
                        });
                    }

                    currSetting.setAttribute('items', JSON.stringify(options));
                    currSetting.setAttribute('settings', JSON.stringify(self.chartSettings));
                    // 

                    // if (self.chartSettings["showTracks"] == "default") {
                    //     currSetting.setAttribute('selected-items', JSON.stringify(options));
                    // } else {
                    //     currSetting.setAttribute('selected-items', JSON.stringify(self.chartSettings["showTracks"].split(",")));
                    // }

                    currSetting._parentNode = self;
                    chartSettingsContainer.appendChild(currSetting);

                    // var iconElem = document.createElement('paper-icon-button');
                    // iconElem.id = "chartShowTracksIcon";
                    // iconElem.icon = "editor:insert-chart";

                    // iconElem.addEventListener("click", this._showTracksDialog.bind(this));
                    // chartSettingsContainer.appendChild(iconElem);
                }
            }
        }
    }
</script><script>
    /**
     * `FixedAxisBehavior` object toggles the fixed axis on StackedLinePlots. 
     * <epiviz-multistacked-line-plot> inherit this behavior to update chart settings.
     *
     * @polymerBehavior
    **/
    EpivizFixedAxisBehavior = function (superClass) {
        return class extends superClass {
            constructor() {
                super();
            }

            static get properties() {
                return {
                    toggleFixed: {
                        type: Boolean
                    }
                };
            }

            /**
            * Shows the `<epiviz-chart-settings>` element
            */
            _showFixedAxisDialog() {
                var self = this;
                self.toggleFixed = !self.toggleFixed;

                var currSettingIcon = this.shadowRoot.querySelector('#chartFixedAxisIcon');

                if (self.toggleFixed) {
                    currSettingIcon.className = "fa fa-link";
                } else {
                    currSettingIcon.className = "fa fa-chain-broken";
                }

                self.chartSettings["autoScale"] = self.toggleFixed;
            }

            /**
            * Initializes the `<epiviz-chart-settings>` element
            */
            _initializeFixedAxisDialog() {
                var FixedAxisContainer = this.shadowRoot.querySelector('#chartSettingsContainer');
                var chartContainer = this.shadowRoot.querySelector('#' + this.plotId);
                var currSettingIcon = this.shadowRoot.querySelector('#chartFixedAxisIcon');

                this.toggleFixed = this.chartSettings["autoScale"];

                if (currSettingIcon == null) {

                    var iconElem = document.createElement('paper-icon-button');
                    iconElem.id = "chartFixedAxisIcon";

                    if (this.toggleFixed) {
                        iconElem.className = "fa fa-link";
                    } else {
                        iconElem.className = "fa fa-chain-broken";
                    }

                    iconElem.addEventListener("click", this._showFixedAxisDialog.bind(this));
                    FixedAxisContainer.appendChild(iconElem);
                }
            }
        }
    }
</script><dom-module id="chart-shared-css">
    <template>
        <style>
            .base-chart {}

            .chart-legend {
                border: 1px solid #dcdcdc;
                background-color: #ffffff;
            }

            .visualization .visualization-title {
                font-family: sans-serif;
                font-size: 14px;
                font-weight: bold;
            }

            text {
                font-family: Arial;
                font-size: 9pt;
            }

            .base-chart path,
            .base-chart line {
                fill: none;
                stroke: #565656;
                shape-rendering: crispEdges;
                stroke-width: 1px;
            }

            .base-chart text {
                font-family: sans-serif;
                font-size: 11px;
            }

            /* Sunburst */

            .unselectable-text {
                -moz-user-select: -moz-none;
                -khtml-user-select: none;
                -webkit-user-select: none;

                /**
             *    Introduced in IE 10.
             *   See http://ie.microsoft.com/testdrive/HTML5/msUserSelect/
             */
                -ms-user-select: none;
                user-select: none;
                cursor: context-menu;
            }

            .node-label {
                font-family: Verdana, Geneva, sans-serif;
                font-size: 11px;
                text-anchor: middle;
            }

            .node-arc {
                stroke: #fff;
                fill-rule: evenodd;
            }

            /* Scatter plot */

            .scatter-plot {}

            .scatter-plot .items {}

            .scatter-plot .selected {
                fill: #1a6d00;
                stroke: #ffc600;
                stroke-width: 2;
                stroke-opacity: 1.0;
                opacity: 1;
            }

            .scatter-plot .hovered {
                fill: #1a6d00;
                stroke: #ffc600;
                stroke-width: 2;
                stroke-opacity: 1.0;
                opacity: 1;
            }

            /* Genes Track */

            .genes-track {}

            .genes-track .items {
                shape-rendering: auto;
                stroke: #555555;
                fill-opacity: 0.6;
            }

            .genes-track .exons {
                stroke: none;
            }

            .genes-track .gene-name {
                font-weight: bold;
                font-size: 12;
                fill: #000000;
                stroke: none;
                fill-opacity: 0;
            }

            .genes-track .selected {
                fill-opacity: 1;
                stroke: #555555;
            }

            .genes-track .selected .gene-body {
                stroke: #555555;
                stroke-width: 3;
                stroke-opacity: 0.7;
            }

            .genes-track .hovered {
                fill-opacity: 1;
                stroke: #555555;
            }

            .genes-track .hovered .gene-body {
                stroke: #555555;
                stroke-width: 3;
                stroke-opacity: 0.7;
            }

            .genes-track .hovered .gene-name {
                fill-opacity: 1;
            }

            .genes-track .selected .gene-name {
                fill-opacity: 1;
            }

            /* Blocks Track */

            .blocks-track {}

            .blocks-track .items {
                shape-rendering: crispEdges;
                fill-opacity: 0.6;
                stroke: #555555;
                stroke-opacity: 0.5;
                stroke-width: 1;
            }

            .blocks-track .selected {
                stroke: #ffc600;
                stroke-width: 5;
                stroke-opacity: 0.7;
                fill-opacity: 1;
            }

            .blocks-track .hovered {
                stroke: #ffc600;
                stroke-width: 5;
                stroke-opacity: 0.7;
                fill-opacity: 1;
            }

            /* Heatmap */

            .heatmap-plot .row-legend {
                font-weight: bold;
                font-size: 11px;
                text-anchor: end;
            }

            .heatmap-plot .items {
                stroke: #efefef;
                stroke-opacity: 0.3;
                shape-rendering: 'crispEdges';
            }

            .heatmap-plot .selected {
                stroke: #ffc600;
                stroke-width: 2;
                stroke-opacity: 1.0;
                fill-opacity: 1;
            }

            .heatmap-plot .hovered {
                stroke: #ffc600;
                stroke-width: 2;
                stroke-opacity: 1.0;
                fill-opacity: 1;
            }

            .heatmap-plot .col-text {
                font-weight: bold;
                text-anchor: start;
                font-size: 11px;
            }

            .heatmap-plot .row-text {
                font-weight: bold;
                text-anchor: end;
                font-size: 11px;
            }

            .heatmap-plot .selected .col-text {
                font-weight: bold;
                fill-opacity: 1;
                font-size: 10px;
            }

            .heatmap-plot .hovered .col-text {
                font-weight: bold;
                fill-opacity: 1;
                font-size: 10px;
            }

            .heatmap-plot .selected .row-text {
                font-weight: bold;
                fill-opacity: 1;
                font-size: 10px;
            }

            .heatmap-plot .hovered .row-text {
                font-weight: bold;
                fill-opacity: 1;
                font-size: 10px;
            }

            /* Line Plot */

            .line-plot .items .bg-line {
                stroke: #dddddd;
                stroke-opacity: 0.1;
            }

            .line-plot .hovered .bg-line {
                stroke-opacity: 1 !important;
                opacity: 1 !important;
            }

            /* Line Track */

            .line-track {}

            /* Stacked Line Plot */

            .stacked-line-plot {}

            .stacked-line-plot .item {
                stroke-width: 0;
            }

            .stacked-line-plot .selected .item {
                fill: #1a6d00;
                stroke: #ffc600;
                stroke-width: 2;
                stroke-opacity: 1.0;
                opacity: 1 !important;
            }

            .stacked-line-plot .hovered .item {
                fill: #1a6d00;
                stroke: #ffc600;
                stroke-width: 2px !important;
                stroke-opacity: 1.0;
                opacity: 1 !important;
            }

            /* icicle */

            .icicle .item rect {
                stroke: #fff;
                stroke-width: 0;
                fill-rule: evenodd;
            }

            .icicle .icon {
                font-family: 'epiviz-icons';
                speak: none;
                font-style: normal;
                font-weight: normal;
                font-variant: normal;
                text-transform: none;
                line-height: 1;

                /* For SVG text */
                /* text-anchor: end; */
                /* alignment-baseline: before-edge; */
                /* Better Font Rendering =========== */
                -webkit-font-smoothing: antialiased;
                -moz-osx-font-smoothing: grayscale;
                font-size: 16px;
                color: #000000;
            }

            .icicle .large-icon {
                font-weight: bold !important;
                font-size: 24px !important;
            }

            .icicle .none-select .icon::before {
                content: "\f379";
            }

            .icicle .node-select .icon::before {
                content: "\f3b3";
            }

            .icicle .leaves-select .icon::before {
                content: "\f3b2";
                /*content: '\e900';*/
            }

            .icicle .custom-select .icon::before {
                content: "\f3b4";
            }

            .icicle .none-select rect {
                opacity: 0.35;
                fill: #bfbfbf;
            }

            .icicle .node-select rect {
                opacity: 1;
            }

            .icicle .custom-select rect {
                opacity: 0.9;
            }

            .icicle .leaves-select rect {
                opacity: 1;
            }

            .icicle .selected rect {
                stroke: #ffc600;
                stroke-width: 2;
                stroke-opacity: 1.0;
                fill-opacity: 1 !important;
            }

            .icicle .hovered rect {
                stroke: #ffc600;
                stroke-width: 2;
                stroke-opacity: 1.0;
                fill-opacity: 1 !important;
            }

            .icicle .unhovered rect {
                fill-opacity: 0.5;
            }

            .icicle .unselected rect {
                fill-opacity: 0.5;
            }

            .icicle .dragstart rect {
                fill-opacity: 0.5;
            }

            .icicle .selected {
                cursor: w-resize;
            }

            .icicle .item text {
                text-anchor: middle;
                font-family: sans-serif;
                font-size: 14px;
                font-weight: bold;
            }

            ::slotted(.dragHandle) {
                opacity: 0;
                width: calc(100% - 120px);
                height: 15px;
            }

            ::slotted(.dragHandle:hover), ::slotted(.dragHandle:active) {
                opacity: 1;
                cursor: move;
                background-color: var(--google-grey-300);
            }

            .setIcon {
                background-color: white; /* Green */
                border: none;
                color: black;
                padding: 2px;
                margin: 1px;
                height: 24px;
                text-align: center;
                text-decoration: none;
            }

            [hidden] {
                display: none!important;
            }

        </style>
    </template>
</dom-module><dom-module id="epiviz-multistacked-line-track">
    <template>
        <style include="shared-settings"></style>
        <style include="chart-shared-css"></style>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

        <style>
            :host {
                width: 100%;
                height: 100%;
                display: inline-block;
                border: 1px solid black;
                border-radius: 5px;
                resize: vertical;
                overflow: auto;
                transition: width 0.01s, height 0.01s;
                position: relative;
            }
        </style>

        <paper-spinner-lite active="" class="green"></paper-spinner-lite>
        <div id="chart" on-mouseover="hostHovered" on-mouseout="hostUnhovered">
            <slot name="dragHandle"></slot>
            <div id="{{plotId}}"></div>
        </div>

    </template>

    <script>

        // Extend Polymer.Element base class
        class EpivizMultiStackedLineTrack extends EpivizFixedAxisBehavior(EpivizChartShowTracksBehavior(EpivizChartGridBehavior(EpivizChartRemoveBehavior(EpivizChartColorsBehavior(EpivizChartSettingsBehavior(EpivizChartBehavior(Polymer.Element))))))) {

            static get is() { return 'epiviz-multistacked-line-track'; }

            static get properties() {
                return {
                    /**
                    * Default chart properties for stacked-line track.
                    *
                    * @type {Object}
                    */
                    configSrc: {
                        type: Object,
                        notify: true,
                        value: function () {

                            epiviz.Config.SETTINGS = {
                                dataProviders: [
                                    ["epiviz.data.WebServerDataProvider", "umd", "http://epiviz-dev.cbcb.umd.edu/api/"]
                                ],
                                workspacesDataProvider: sprintf('epiviz.data.EmptyResponseDataProvider', 'empty', ''),
                                useCache: true,

                                chartSettings: {
                                    default: {
                                        colors: 'd3-category10',
                                        decorations: [
                                            'epiviz.ui.charts.decoration.RemoveChartButton',
                                            'epiviz.ui.charts.decoration.SaveChartButton',
                                            'epiviz.ui.charts.decoration.CustomSettingsButton',
                                            'epiviz.ui.charts.decoration.EditCodeButton',

                                            'epiviz.ui.charts.decoration.ChartColorsButton',
                                            'epiviz.ui.charts.decoration.ChartLoaderAnimation',
                                            'epiviz.ui.charts.decoration.ChartResize'
                                        ]
                                    },

                                    plot: {
                                        width: 400,
                                        height: 400,
                                        margins: new epiviz.ui.charts.Margins(15, 30, 30, 15),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    track: {
                                        width: 900,
                                        height: 90,
                                        margins: new epiviz.ui.charts.Margins(25, 20, 23, 10),
                                        decorations: [
                                            'epiviz.ui.charts.decoration.ToggleTooltipButton',

                                            'epiviz.ui.charts.decoration.ChartTooltip',
                                            'epiviz.ui.charts.decoration.ChartFilterCodeButton'
                                        ]
                                    },

                                    'epiviz.plugins.charts.StackedLineTrack': {
                                        height: 300
                                    },
                                },

                                chartCustomSettings: {
                                    'epiviz.plugins.charts.MultiStackedLineTrackType': {
                                        step: 1,
                                        showPoints: false,
                                        showLines: true,
                                        pointRadius: 1,
                                        lineThickness: 2,
                                        interpolation: "basis-open"
                                    }
                                },

                                colorPalettes: [
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#025167', '#e7003e', '#ffcd00', '#057d9f', '#970026', '#ffe373', '#ff8100'],
                                        'Epiviz v1.0 Colors', 'epiviz-v1'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Epiviz v2.0 Bright', 'epiviz-v2-bright'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#b8d2eb', '#f2aeac', '#d8e4aa', '#cccccc', '#f2d1b0', '#d4b2d3', '#ddb8a9', '#ebbfd9'],
                                        'Epiviz v2.0 Light', 'epiviz-v2-light'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#599ad3', '#f1595f', '#79c36a', '#727272', '#f9a65a', '#9e66ab', '#cd7058', '#d77fb3'],
                                        'Epiviz v2.0 Medium', 'epiviz-v2-medium'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"],
                                        'D3 Category 10', 'd3-category10'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"],
                                        'D3 Category 20', 'd3-category20'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6"],
                                        'D3 Category 20b', 'd3-category20b'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ["#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9"],
                                        'D3 Category 20c', 'd3-category20c'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#f9a65a', '#599ad3', '#79c36a', '#f1595f', '#727272', '#cd7058', '#d77fb3'],
                                        'Genes Default', 'genes-default'),
                                    new epiviz.ui.charts.ColorPalette(
                                        ['#1859a9', '#ed2d2e', '#008c47', '#010101', '#f37d22', '#662c91', '#a11d20', '#b33893'],
                                        'Heatmap Default', 'heatmap-default')
                                ]
                            };

                            return epiviz.Config.SETTINGS;
                        }
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                ]
            }

            constructor() {
                super();
            }

            connectedCallback() {
                super.connectedCallback();

                var self = this;

                if (self.useDefaultDataProvider) {

                    self.measurements = self.measurements || [{
                        'id': 'e027',
                        'name': 'Expression Colon Cancer',
                        'type': 'feature',
                        'datasourceId': 'roadmap_rnaseq',
                        'datasourceGroup': 'roadmap_rnaseq',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }, {
                        'id': 'e066',
                        'name': 'Expression Colon Normal',
                        'type': 'feature',
                        'datasourceId': 'roadmap_rnaseq',
                        'datasourceGroup': 'roadmap_rnaseq',
                        'dataprovider': 'umd',
                        'formula': null,
                        'defaultChartType': null,
                        'annotation': null,
                        'minValue': -3,
                        'maxValue': 20,
                        'metadata': ['probe']
                    }];

                    self.range = self.range || new epiviz.datatypes.GenomicRange("chr11", 80000000, 3000000);

                    // self._measurementsChanged();

                    var chartMeasMap = {};
                    chartMeasMap[self.plotId] = self.visConfigSelection.measurements;

                    var dataProviderFactory = new epiviz.data.DataProviderFactory(self.config);
                    var dataManager = new epiviz.data.DataManager(self.config, dataProviderFactory);

                    dataManager.getData(self.range, chartMeasMap, function (id, data) {
                        self.data = data;
                    });
                }

                self._initializeGrid();
                self._measurementsChanged();

                if (self.dimS) {
                    this.style = "min-height:" + (self.dimS.length * 100) + "px;"
                } else if (self.measurements) {
                    this.style = "min-height:" + (self.measurements.length * 100) + "px;"
                } else {
                    this.style = "min-height:" + (300) + "px;"
                }
            }

            disconnectedCallback() {
                super.connectedCallback();
            }

            ready() {
                super.ready();
                this.plotId = self.plotId || this._generatePlotId();
                // this.scopeSubtree(this.$.chart, true);
                this.config = new epiviz.Config(this.configSrc);
            }

            /**
            * Draws the chart.
            *
            * @param {Object<epiviz.datatypes.GenomicRange>} range genomic range.
            * @param {Object<epiviz.datatypes.MapGenomicData>} data to plot
            */
            _draw() {
                if (this.canvas) {
                    this.chart.drawCanvas(this.range, this.data);
                }
                else {
                    this.chart.draw(this.range, this.data);
                }
                this.shadowRoot.querySelector("paper-spinner-lite").active = false;
            }

            /**
             * Creates an instance of the stacked-line track chart.
             *
             * @return {epiviz.plugins.charts.StackedLineTrackType} StackedLineTrack chart object
             */
            _createChart() {
                return new epiviz.plugins.charts.MultiStackedLineTrackType(new epiviz.Config(this.configSrc));
            }
        };

        customElements.define(EpivizMultiStackedLineTrack.is, EpivizMultiStackedLineTrack);

        // Polymer({
        //     /* Custom element html tag */
        //     is: 'epiviz-multistacked-line-track',
        //     behaviors: [epiviz.ChartBehavior, epiviz.ChartSettingsBehavior, epiviz.ChartColorsBehavior, epiviz.ChartRemoveBehavior, Polymer.IronResizableBehavior, epiviz.ChartDraggableBehavior],
        // });
    </script>
</dom-module><script>(function() {
  var cyto_chr = {}, d3 = window.d3;

  if (typeof module !== 'undefined' && module.exports) {
    module.exports = cyto_chr;
    d3 = require('d3');
  } else if(typeof angular !== 'undefined') {} else {
    window.cyto_chr = cyto_chr;
  }
  
(function(cyto_chr, d3) {
  'use strict'
  cyto_chr.margin = {
    top: 38,
    left: 14,
    right: 5
  };

  var CHR1_BP_END = 248956422;
  var CHR1_BP_MID = 121700000;

  var Chromosome = function() {
    this._segment = '1';
    this._selectionMode = 'single';
    this._domTarget = d3.select(document.documentElement);
    this._resolution = "550";
    this._width = null;
    this._height = 17;
    this._useRelative = true;
    this._showAxis = false;
    this.dispatch = d3.dispatch('bandclick', 'selectorchange', 'selectorend', 'selectordelete', "selectorhover", "selectorunhover");
    this.rendered = false;
    this.selectors = [];
    this.model = [];
    this.maxBasePair = 0;
    this.xscale = d3.scale.linear();
  };

  Chromosome.prototype.getMaxBasepair = function() {
    return this.maxBasePair;
  };

  Chromosome.prototype.segment = function (a) {
    if (typeof a === 'number') a = a.toString();
    return cyto_chr.InitGetterSetter.call(this, '_segment', a);
  };

  Chromosome.prototype.selectionMode = function (a) {
    return cyto_chr.InitGetterSetter.call(this, '_selectionMode', a);
  };

  Chromosome.prototype.target = function (a) {
    // if(typeof a === 'string') 
    a = d3.select(a);
    // if(a.empty()) {
    //   throw "Error: Invalid dom target";
    // }
    return cyto_chr.InitGetterSetter.call(this, '_domTarget', a);
  };

  Chromosome.prototype.resolution = function (a) {
    if (typeof a === 'number') a = a.toString();
    if (a === "400" || a === "550" || a ==="850" || a === "1200" || a === undefined) {
      return cyto_chr.InitGetterSetter.call(this, '_resolution', a);
    } else {
      throw "Error: Invalid resolution. Please enter 400, 550, 850, or 1200 only.";
    }
  };

  Chromosome.prototype.width = function (a) {
    return cyto_chr.InitGetterSetter.call(this, '_width', a);
  };

  Chromosome.prototype.height = function (a) {
    return cyto_chr.InitGetterSetter.call(this, '_height', a);
  };

  Chromosome.prototype.useRelative = function (a) {
    return cyto_chr.InitGetterSetter.call(this, '_useRelative', a);
  };

  Chromosome.prototype.showAxis = function (a) {
    return cyto_chr.InitGetterSetter.call(this, '_showAxis', a);
  };

  Chromosome.prototype.doHover = function() {
    // console.log(this.selectors[0]);
    this.svgTarget.selectAll(".extRect rect").style("stroke", "#ffc600").style("stroke-width", "5").style("stroke-opacity", "1.0");
  };

  Chromosome.prototype.doUnhover = function() {
    this.svgTarget.selectAll(".extRect rect").style("stroke-opacity", "0");
  };

  Chromosome.prototype.on = function(e, listener) {
    if (!this.dispatch.hasOwnProperty(e)) throw "Error: No event for " + e;
    this.dispatch.on(e, listener);
  };

  Chromosome.prototype.config = function(type, arg) {
    return this[type](arg);
  };

  Chromosome.prototype.renderAxis = function () {
    var bpAxis = d3.svg.axis()
      .scale(this.xscale)
      .tickFormat(d3.format('s'))
      .orient("bottom");

    if (this._useRelative && (this._segment === "Y" || this._segment === "22" || this._segment === "21" || this._segment === "20" || this._segment === "19")) {
      bpAxis.ticks(6);
    }

    var axisg = this.svgTarget.append('g')
      .classed('bp-axis', true)
      .attr('transform', 'translate('+ cyto_chr.margin.left + ',' + (this._height + cyto_chr.margin.top + 6) + ")");

      axisg.call(bpAxis);

    axisg.selectAll('text')
      // .attr('transform', 'rotate(-50)')
      .style('font', '8px sans-serif');

    axisg.selectAll('path, line')
      .style({
        "fill": "none",
        "stroke": "#666666",
        "shape-rendering": "crispEdges"
      });
  };

  Chromosome.prototype.remove = function() {
    this.svgTarget.remove();
  };

  // Chromosome.prototype.moveSelectorTo = function(start, stop) {
  //   if(arguments.length !== 2) {
  //     throw "Error moveSelectorTo: Invalid number of arguments. Both start and stop coordinates are required";
  //   }

  //   if (this.selectors.length === 0) {
  //     this.newSelector(0, "10000000");
  //   } else {
  //     this.selectors[0].move(start, stop);
  //   }

  // };

  Chromosome.prototype.newSelector = function(bp_start, bp_stop) {

    var self = this;

    function selectorRemoveCB(sel) {
      self.dispatch.selectordelete(sel);
      var index = self.selectors.indexOf(sel);
      self.selectors.splice(index, 1);
    }

    var ve = cyto_chr.selector(selectorRemoveCB)
      .x(cyto_chr.margin.left)
      .y(cyto_chr.margin.top - (this._height / 4))
      .height(this._height + (this._height / 2))
      .xscale(this.xscale)
      .extent([bp_start, bp_stop])
      .target(this.svgTarget)
      .render();

    // ve.dispatch.on('change', function(d) {
    //   self.dispatch.selectorchange(d);
    // });

    // ve.dispatch.on('changeend', function(d) {
    //   self.dispatch.selectorend(d);
    // });

    ve.dispatch.on('selectorhover', function(d) {
      self.dispatch.selectorhover(d);
    });

    ve.dispatch.on('selectorunhover', function(d) {
      self.dispatch.selectorunhover(d);
    });

    this.selectors.push(ve);
  };

  Chromosome.prototype.getSelections = function() {

    var ret = [];
    for(var i = 0; i < this.selectors.length; i++) {
      var sel = this.selectors[i].extent();
      ret.push({
        start: sel[0],
        stop: sel[1]
      })
    }
    return ret;
  };

  Chromosome.prototype.getSelectedBands = function(sensitivity) {

    var results = [];
    if (this.selectors.length > 0) {

      var se = this.selectors[0].extent();
      var selStart = +se[0];
      var selStop = +se[1];

      if (typeof sensitivity !== 'undefined') {
        selStart -= sensitivity;
        selStop += sensitivity;
      }

      results = this.model.slice().filter(function(e) {
        var bStart = +e.bp_start;
        var bStop = +e.bp_stop;

        if ((selStart >= bStart && selStart < bStop) ||
          (selStop > bStart && selStop <= bStop) ||
          (selStart <= bStart && selStop >= bStop)) {
          return true;
        } else {
          return false;
        }
      });
    }

    return results;
  };

  Chromosome.prototype.getSVGTarget = function() {
    return this.svgTarget;
  };

  Chromosome.prototype.render = function (zoomRange) {

    var self = this;

    if(self.rendered) {
      self.selectors = [];
      self.remove();
    }

    if(self._width === null) {
      var parentWidth = d3.select(self._domTarget[0][0].parentNode).node().getBoundingClientRect().width;
      self.width(parentWidth)
    }

    cyto_chr.modelLoader.load(this._segment, this._resolution, function(data) {
      self.model = data;
      self.maxBasePair = d3.max(data, function(d) {
        return +d.bp_stop;
      });

      self.segMid = 0;
      for(var j =0; j < data.length;j++) {
        if(data[j].stain ==="acen") {
          self.segMid = data[j].bp_stop;
          break;
        }
      }

      var rangeTo = self._useRelative ? (self.maxBasePair / CHR1_BP_END) * self._width : self._width;
      rangeTo -= (cyto_chr.margin.left + cyto_chr.margin.right);

      if (zoomRange) {
        self.xscale.domain([zoomRange[0], zoomRange[1]]);
      } else {
        self.xscale.domain([1, self.maxBasePair]);
      }

      self.xscale.range([0, rangeTo]);

      var h = self._height + 62;
      // var h = self._height;
      var w = self._width;

      self.svgTarget = self._domTarget
        .style('height', h + 'px')
        .style('width', (w+5) + 'px')
        .append('svg')
        .attr('width', w)
        .attr('height', h);

      var bands = self.svgTarget.selectAll('g')
        .data(data).enter();

      // self.svgTarget.append('text')
      //   .text("chr: " + self._segment)
      //   .attr('x', w - 77)
      //   .attr('y', cyto_chr.margin.top + (self._height/ 3) + 2)
      //   .attr('text-anchor','middle')
      //   .style('font', '12px sans-serif');

      function bpCoord(bp) {
        var xshift = 0;
        if(self.alignCentromere && self._segment !== "1") {
          xshift = self.xscale(CHR1_BP_MID) - self.xscale(self.segMid);
        }

        return self.xscale(bp) + cyto_chr.margin.left + xshift;
      }

      bands.append('g')
        .each(function(d, i) {

          var elem = d3.select(this);

          function applyBorder() {
            this
              .attr('stroke', '#000000')
              .attr('stroke-width', 0.2);
          }

          function drawRoundedRect(d, r, tl, tr, bl, br) {
            return this.append('path')
              .attr("d", cyto_chr.roundedRect(bpCoord(d.bp_start), cyto_chr.margin.top, bpCoord(d.bp_stop) - bpCoord(d.bp_start), self._height, r, tl, tr, bl, br))
              .style('fill', cyto_chr.getStainColour(d.stain, d.density));
          }

          var labelSkipFactor = self._resolution === '1200' ? 8 : 2;

          if (self._useRelative && self._resolution == "1200" && self._segment == 'Y') {
            labelSkipFactor = 12;
          }

          // if(i % labelSkipFactor === 0) {
          //   var bmid = (bpCoord(d.bp_stop) + bpCoord(d.bp_start)) / 2;
          //   elem.append('line')
          //     .attr('x1', bmid)
          //     .attr('y1', cyto_chr.margin.top)
          //     .attr('x2', bmid)
          //     .attr('y2', cyto_chr.margin.top - 4)
          //     .style('stroke', 'grey')
          //     .style('stroke-width',1);

          //   elem.append('text')
          //     .attr('transform', 'translate(' + bmid + ',' + (cyto_chr.margin.top - 6) + ')rotate(-50)')
          //     .style('font', '8px sans-serif')
          //     .text(d.arm + d.band);
          // }

          var rect;
          var w = bpCoord(d.bp_stop) - bpCoord(d.bp_start);
          var acenThreshold = (self._resolution == "1200") ? 7 : 6;
          if (i === 0 && w > 10) {
            rect = drawRoundedRect.call(elem, d, 4, true, false, true, false);
            applyBorder.call(rect);
          } else if (d.stain === "acen" && (w > acenThreshold)) {

            if (d.arm === "p") {
              rect = drawRoundedRect.call(elem, d, 5, false, true, false, true);

            } else if(d.arm === "q") {
              rect = drawRoundedRect.call(elem, d, 5, true, false, true, false);
            }
          } else if (i === data.length - 1) {

            rect = drawRoundedRect.call(elem, d, 5, false, true, false, true);
            applyBorder.call(rect);

          } else {

            var ys = d.stain === "stalk" ? cyto_chr.margin.top + (self._height / 4) : cyto_chr.margin.top;
            var hs = d.stain === "stalk" ? self._height / 2 : self._height;
            rect = elem.append('rect')
              .attr('x', bpCoord(d.bp_start))
              .attr('y', ys)
              .attr('height', hs)
              .attr('width', self.xscale(d.bp_stop) - self.xscale(d.bp_start))
              .style('fill', cyto_chr.getStainColour(d.stain, d.density));
            applyBorder.call(rect);
          }

          rect.append('title')
            .text(d.arm + d.band)

          rect.on('mouseover', function(d) {
            var e = d3.select(this)
              .style('opacity', "0.5")
              .style('cursor', 'pointer');

            if (d.stain === "gneg") {
              e.style('fill', cyto_chr.getStainColour("gpos", "25"));
            }

          });

          rect.on('mouseout', function(d) {
            var e = d3.select(this)
              .style('opacity', "1")
              .style('cursor', 'default');

            if (d.stain === "gneg") {
              e.style('fill', cyto_chr.getStainColour("gneg"));
            }
          });

          // rect.on('click', function(d) {
          //   // if (self.selectors.length === 0 || (self._selectionMode === 'multi' && d3.event.altKey)) {
          //   //   self.newSelector(d.bp_start, d.bp_stop);
          //   // }

          //   // if (self._selectionMode === 'single' && self.selectors.length > 0) {
          //   //   self.moveSelectorTo(d.bp_start, d.bp_stop);
          //   // }
          //   // self.dispatch.bandclick(d);
          // });
        });

      if (self._showAxis) {
        self.renderAxis();
      }

      self.rendered = true;
    });

    return self;
  };

  cyto_chr.chromosome = function() {
    return new Chromosome();
  };

})(cyto_chr || {}, d3);

(function (cyto_chr, d3) {

  var defaultDataURLs = {
    "400" : "ideogram_9606_GCF_000001305.14_400_V1",
    "550" : "ideogram_9606_GCF_000001305.14_550_V1",
    "850" : "ideogram_9606_GCF_000001305.14_850_V1",
    "1200" : "ideogram_9606_GCF_000001305.13_1200_v1"
  };

  var baseDir = 'data/';

  function CacheInstance() {
    this.status = "notloaded";
    this.cache = [];
  }

  var dataCache = {
    "400" : new CacheInstance,
    "550" : new CacheInstance,
    "850" : new CacheInstance,
    "1200" : new CacheInstance
  };

  var callQueue = [];

  function loadData(file, res, cb) {

    var c = dataCache[res];
    if (c.cache.length === 0) {
      if (c.status === "loading") {
        callQueue.push({
          res: res,
          cb: cb
        });

        return;
      } else if (c.status === "notloaded") {
        c.status = "loading";
        d3.tsv(file, function(d) {
          c.cache = d;
          c.status = "loaded";
          cb(d);

          while(callQueue.length > 0) {
            var cbq = callQueue.shift();
            cbq.cb(d);
          }

        });
      }
    } else {
      cb(c.cache);
    }
  }

  function getChromosomeData(chr, resolution, cb) {

    chr = chr || '1';
    resolution = resolution || "550";

    var fileName = defaultDataURLs[resolution];

    var data = [{"#chromosome":"1","arm":"p","band":"36.3","iscn_start":"0","iscn_stop":"451","bp_start":"1","bp_stop":"7100000","stain":"gneg","density":""},{"#chromosome":"1","arm":"p","band":"36.2","iscn_start":"451","iscn_stop":"682","bp_start":"7100001","bp_stop":"15900000","stain":"gpos","density":"100"},{"#chromosome":"1","arm":"p","band":"36.1","iscn_start":"682","iscn_stop":"1259","bp_start":"15900001","bp_stop":"27600000","stain":"gneg","density":""},{"#chromosome":"1","arm":"p","band":"35","iscn_start":"1259","iscn_stop":"1583","bp_start":"27600001","bp_stop":"34300000","stain":"gpos","density":"100"},{"#chromosome":"1","arm":"p","band":"34.3","iscn_start":"1583","iscn_stop":"1779","bp_start":"34300001","bp_stop":"39600000","stain":"gneg","density":""},{"#chromosome":"1","arm":"p","band":"34.2","iscn_start":"1779","iscn_stop":"1999","bp_start":"39600001","bp_stop":"43700000","stain":"gpos","density":"25"},{"#chromosome":"1","arm":"p","band":"34.1","iscn_start":"1999","iscn_stop":"2184","bp_start":"43700001","bp_stop":"46300000","stain":"gneg","density":""},{"#chromosome":"1","arm":"p","band":"33","iscn_start":"2184","iscn_stop":"2519","bp_start":"46300001","bp_stop":"50200000","stain":"gpos","density":"75"},{"#chromosome":"1","arm":"p","band":"32.3","iscn_start":"2519","iscn_stop":"2723","bp_start":"50200001","bp_stop":"55600000","stain":"gneg","density":""},{"#chromosome":"1","arm":"p","band":"32.2","iscn_start":"2723","iscn_stop":"2825","bp_start":"55600001","bp_stop":"58500000","stain":"gpos","density":"50"},{"#chromosome":"1","arm":"p","band":"32.1","iscn_start":"2825","iscn_stop":"3050","bp_start":"58500001","bp_stop":"60800000","stain":"gneg","density":""},{"#chromosome":"1","arm":"p","band":"31.3","iscn_start":"3050","iscn_stop":"3387","bp_start":"60800001","bp_stop":"68500000","stain":"gpos","density":"50"},{"#chromosome":"1","arm":"p","band":"31.2","iscn_start":"3387","iscn_stop":"3705","bp_start":"68500001","bp_stop":"69300000","stain":"gneg","density":""},{"#chromosome":"1","arm":"p","band":"31.1","iscn_start":"3705","iscn_stop":"4598","bp_start":"69300001","bp_stop":"84400000","stain":"gpos","density":"100"},{"#chromosome":"1","arm":"p","band":"22.3","iscn_start":"4598","iscn_stop":"4933","bp_start":"84400001","bp_stop":"87900000","stain":"gneg","density":""},{"#chromosome":"1","arm":"p","band":"22.2","iscn_start":"4933","iscn_stop":"5148","bp_start":"87900001","bp_stop":"91500000","stain":"gpos","density":"75"},{"#chromosome":"1","arm":"p","band":"22.1","iscn_start":"5148","iscn_stop":"5430","bp_start":"91500001","bp_stop":"94300000","stain":"gneg","density":""},{"#chromosome":"1","arm":"p","band":"21","iscn_start":"5430","iscn_stop":"6274","bp_start":"94300001","bp_stop":"106700000","stain":"gpos","density":"100"},{"#chromosome":"1","arm":"p","band":"13.3","iscn_start":"6274","iscn_stop":"6560","bp_start":"106700001","bp_stop":"111200000","stain":"gneg","density":""},{"#chromosome":"1","arm":"p","band":"13.2","iscn_start":"6560","iscn_stop":"6827","bp_start":"111200001","bp_stop":"115500000","stain":"gpos","density":"50"},{"#chromosome":"1","arm":"p","band":"13.1","iscn_start":"6827","iscn_stop":"7094","bp_start":"115500001","bp_stop":"117200000","stain":"gneg","density":""},{"#chromosome":"1","arm":"p","band":"12","iscn_start":"7094","iscn_stop":"7279","bp_start":"117200001","bp_stop":"120400000","stain":"gpos","density":"50"},{"#chromosome":"1","arm":"p","band":"11","iscn_start":"7279","iscn_stop":"7394","bp_start":"120400001","bp_stop":"123400000","stain":"acen","density":""},{"#chromosome":"1","arm":"q","band":"11","iscn_start":"7394","iscn_stop":"7554","bp_start":"123400001","bp_stop":"125100000","stain":"acen","density":""},{"#chromosome":"1","arm":"q","band":"12","iscn_start":"7554","iscn_stop":"8672","bp_start":"125100001","bp_stop":"143200000","stain":"gvar","density":""},{"#chromosome":"1","arm":"q","band":"21.1","iscn_start":"8672","iscn_stop":"8915","bp_start":"143200001","bp_stop":"147500000","stain":"gneg","density":""},{"#chromosome":"1","arm":"q","band":"21.2","iscn_start":"8915","iscn_stop":"9094","bp_start":"147500001","bp_stop":"150600000","stain":"gpos","density":"50"},{"#chromosome":"1","arm":"q","band":"21.3","iscn_start":"9094","iscn_stop":"9350","bp_start":"150600001","bp_stop":"155100000","stain":"gneg","density":""},{"#chromosome":"1","arm":"q","band":"22","iscn_start":"9350","iscn_stop":"9685","bp_start":"155100001","bp_stop":"156600000","stain":"gpos","density":"50"},{"#chromosome":"1","arm":"q","band":"23","iscn_start":"9685","iscn_stop":"10160","bp_start":"156600001","bp_stop":"165500000","stain":"gneg","density":""},{"#chromosome":"1","arm":"q","band":"24","iscn_start":"10160","iscn_stop":"10565","bp_start":"165500001","bp_stop":"173000000","stain":"gpos","density":"100"},{"#chromosome":"1","arm":"q","band":"25","iscn_start":"10565","iscn_stop":"11005","bp_start":"173000001","bp_stop":"185800000","stain":"gneg","density":""},{"#chromosome":"1","arm":"q","band":"31","iscn_start":"11005","iscn_stop":"12207","bp_start":"185800001","bp_stop":"198700000","stain":"gpos","density":"100"},{"#chromosome":"1","arm":"q","band":"32.1","iscn_start":"12207","iscn_stop":"12757","bp_start":"198700001","bp_stop":"207100000","stain":"gneg","density":""},{"#chromosome":"1","arm":"q","band":"32.2","iscn_start":"12757","iscn_stop":"12988","bp_start":"207100001","bp_stop":"211300000","stain":"gpos","density":"25"},{"#chromosome":"1","arm":"q","band":"32.3","iscn_start":"12988","iscn_stop":"13272","bp_start":"211300001","bp_stop":"214400000","stain":"gneg","density":""},{"#chromosome":"1","arm":"q","band":"41","iscn_start":"13272","iscn_stop":"13920","bp_start":"214400001","bp_stop":"223900000","stain":"gpos","density":"100"},{"#chromosome":"1","arm":"q","band":"42.1","iscn_start":"13920","iscn_stop":"14212","bp_start":"223900001","bp_stop":"230500000","stain":"gneg","density":""},{"#chromosome":"1","arm":"q","band":"42.2","iscn_start":"14212","iscn_stop":"14296","bp_start":"230500001","bp_stop":"234600000","stain":"gpos","density":"50"},{"#chromosome":"1","arm":"q","band":"42.3","iscn_start":"14296","iscn_stop":"14406","bp_start":"234600001","bp_stop":"236400000","stain":"gneg","density":""},{"#chromosome":"1","arm":"q","band":"43","iscn_start":"14406","iscn_stop":"14776","bp_start":"236400001","bp_stop":"243500000","stain":"gpos","density":"75"},{"#chromosome":"1","arm":"q","band":"44","iscn_start":"14776","iscn_stop":"15100","bp_start":"243500001","bp_stop":"248956422","stain":"gneg","density":""},{"#chromosome":"2","arm":"p","band":"25.3","iscn_start":"0","iscn_stop":"193","bp_start":"1","bp_stop":"4400000","stain":"gneg","density":""},{"#chromosome":"2","arm":"p","band":"25.2","iscn_start":"193","iscn_stop":"282","bp_start":"4400001","bp_stop":"6900000","stain":"gpos","density":"50"},{"#chromosome":"2","arm":"p","band":"25.1","iscn_start":"282","iscn_stop":"475","bp_start":"6900001","bp_stop":"12000000","stain":"gneg","density":""},{"#chromosome":"2","arm":"p","band":"24","iscn_start":"475","iscn_stop":"931","bp_start":"12000001","bp_stop":"23800000","stain":"gpos","density":"100"},{"#chromosome":"2","arm":"p","band":"23","iscn_start":"931","iscn_stop":"1558","bp_start":"23800001","bp_stop":"31800000","stain":"gneg","density":""},{"#chromosome":"2","arm":"p","band":"22","iscn_start":"1558","iscn_stop":"2145","bp_start":"31800001","bp_stop":"41500000","stain":"gpos","density":"100"},{"#chromosome":"2","arm":"p","band":"21","iscn_start":"2145","iscn_stop":"2650","bp_start":"41500001","bp_stop":"47500000","stain":"gneg","density":""},{"#chromosome":"2","arm":"p","band":"16","iscn_start":"2650","iscn_stop":"3439","bp_start":"47500001","bp_stop":"61000000","stain":"gpos","density":"100"},{"#chromosome":"2","arm":"p","band":"15","iscn_start":"3439","iscn_stop":"3682","bp_start":"61000001","bp_stop":"63900000","stain":"gneg","density":""},{"#chromosome":"2","arm":"p","band":"14","iscn_start":"3682","iscn_stop":"3976","bp_start":"63900001","bp_stop":"68400000","stain":"gpos","density":"50"},{"#chromosome":"2","arm":"p","band":"13","iscn_start":"3976","iscn_stop":"4572","bp_start":"68400001","bp_stop":"74800000","stain":"gneg","density":""},{"#chromosome":"2","arm":"p","band":"12","iscn_start":"4572","iscn_stop":"5068","bp_start":"74800001","bp_stop":"83100000","stain":"gpos","density":"100"},{"#chromosome":"2","arm":"p","band":"11.2","iscn_start":"5068","iscn_stop":"5534","bp_start":"83100001","bp_stop":"91800000","stain":"gneg","density":""},{"#chromosome":"2","arm":"p","band":"11.1","iscn_start":"5534","iscn_stop":"5665","bp_start":"91800001","bp_stop":"93900000","stain":"acen","density":""},{"#chromosome":"2","arm":"q","band":"11.1","iscn_start":"5665","iscn_stop":"5828","bp_start":"93900001","bp_stop":"96000000","stain":"acen","density":""},{"#chromosome":"2","arm":"q","band":"11.2","iscn_start":"5828","iscn_stop":"6181","bp_start":"96000001","bp_stop":"102100000","stain":"gneg","density":""},{"#chromosome":"2","arm":"q","band":"12","iscn_start":"6181","iscn_stop":"6602","bp_start":"102100001","bp_stop":"108700000","stain":"gpos","density":"100"},{"#chromosome":"2","arm":"q","band":"13","iscn_start":"6602","iscn_stop":"6928","bp_start":"108700001","bp_stop":"112200000","stain":"gneg","density":""},{"#chromosome":"2","arm":"q","band":"14.1","iscn_start":"6928","iscn_stop":"7254","bp_start":"112200001","bp_stop":"118100000","stain":"gpos","density":"50"},{"#chromosome":"2","arm":"q","band":"14.2","iscn_start":"7254","iscn_stop":"7336","bp_start":"118100001","bp_stop":"121600000","stain":"gneg","density":""},{"#chromosome":"2","arm":"q","band":"14.3","iscn_start":"7336","iscn_stop":"7662","bp_start":"121600001","bp_stop":"129100000","stain":"gpos","density":"50"},{"#chromosome":"2","arm":"q","band":"21.1","iscn_start":"7662","iscn_stop":"7920","bp_start":"129100001","bp_stop":"131700000","stain":"gneg","density":""},{"#chromosome":"2","arm":"q","band":"21.2","iscn_start":"7920"
                ,"iscn_stop":"8177","bp_start":"131700001","bp_stop":"134300000","stain":"gpos","density":"25"},{"#chromosome":"2","arm":"q","band":"21.3","iscn_start":"8177","iscn_stop":"8314","bp_start":"134300001","bp_stop":"136100000","stain":"gneg","density":""},{"#chromosome":"2","arm":"q","band":"22","iscn_start":"8314","iscn_stop":"9048","bp_start":"136100001","bp_stop":"147900000","stain":"gpos","density":"100"},{"#chromosome":"2","arm":"q","band":"23","iscn_start":"9048","iscn_stop":"9374","bp_start":"147900001","bp_stop":"154000000","stain":"gneg","density":""},{"#chromosome":"2","arm":"q","band":"24.1","iscn_start":"9374","iscn_stop":"9650","bp_start":"154000001","bp_stop":"158900000","stain":"gpos","density":"75"},{"#chromosome":"2","arm":"q","band":"24.2","iscn_start":"9650","iscn_stop":"9868","bp_start":"158900001","bp_stop":"162900000","stain":"gneg","density":""},{"#chromosome":"2","arm":"q","band":"24.3","iscn_start":"9868","iscn_stop":"10202","bp_start":"162900001","bp_stop":"168900000","stain":"gpos","density":"75"},{"#chromosome":"2","arm":"q","band":"31","iscn_start":"10202","iscn_stop":"10854","bp_start":"168900001","bp_stop":"182100000","stain":"gneg","density":""},{"#chromosome":"2","arm":"q","band":"32.1","iscn_start":"10854","iscn_stop":"11271","bp_start":"182100001","bp_stop":"188500000","stain":"gpos","density":"75"},{"#chromosome":"2","arm":"q","band":"32.2","iscn_start":"11271","iscn_stop":"11427","bp_start":"188500001","bp_stop":"191100000","stain":"gneg","density":""},{"#chromosome":"2","arm":"q","band":"32.3","iscn_start":"11427","iscn_stop":"11792","bp_start":"191100001","bp_stop":"196600000","stain":"gpos","density":"75"},{"#chromosome":"2","arm":"q","band":"33","iscn_start":"11792","iscn_stop":"12362","bp_start":"196600001","bp_stop":"208200000","stain":"gneg","density":""},{"#chromosome":"2","arm":"q","band":"34","iscn_start":"12362","iscn_stop":"12811","bp_start":"208200001","bp_stop":"214500000","stain":"gpos","density":"100"},{"#chromosome":"2","arm":"q","band":"35","iscn_start":"12811","iscn_stop":"13164","bp_start":"214500001","bp_stop":"220700000","stain":"gneg","density":""},{"#chromosome":"2","arm":"q","band":"36","iscn_start":"13164","iscn_stop":"13653","bp_start":"220700001","bp_stop":"230100000","stain":"gpos","density":"100"},{"#chromosome":"2","arm":"q","band":"37.1","iscn_start":"13653","iscn_stop":"13930","bp_start":"230100001","bp_stop":"234700000","stain":"gneg","density":""},{"#chromosome":"2","arm":"q","band":"37.2","iscn_start":"13930","iscn_stop":"14027","bp_start":"234700001","bp_stop":"236400000","stain":"gpos","density":"50"},{"#chromosome":"2","arm":"q","band":"37.3","iscn_start":"14027","iscn_stop":"14400","bp_start":"236400001","bp_stop":"242193529","stain":"gneg","density":""},{"#chromosome":"3","arm":"p","band":"26","iscn_start":"0","iscn_stop":"257","bp_start":"1","bp_stop":"8100000","stain":"gpos","density":"100"},{"#chromosome":"3","arm":"p","band":"25","iscn_start":"257","iscn_stop":"649","bp_start":"8100001","bp_stop":"16300000","stain":"gneg","density":""},{"#chromosome":"3","arm":"p","band":"24.3","iscn_start":"649","iscn_stop":"1010","bp_start":"16300001","bp_stop":"23800000","stain":"gpos","density":"100"},{"#chromosome":"3","arm":"p","band":"24.2","iscn_start":"1010","iscn_stop":"1114","bp_start":"23800001","bp_stop":"26300000","stain":"gneg","density":""},{"#chromosome":"3","arm":"p","band":"24.1","iscn_start":"1114","iscn_stop":"1355","bp_start":"26300001","bp_stop":"30800000","stain":"gpos","density":"75"},{"#chromosome":"3","arm":"p","band":"23","iscn_start":"1355","iscn_stop":"1574","bp_start":"30800001","bp_stop":"32000000","stain":"gneg","density":""},{"#chromosome":"3","arm":"p","band":"22","iscn_start":"1574","iscn_stop":"1966","bp_start":"32000001","bp_stop":"43600000","stain":"gpos","density":"100"},{"#chromosome":"3","arm":"p","band":"21.3","iscn_start":"1966","iscn_stop":"2910","bp_start":"43600001","bp_stop":"50600000","stain":"gneg","density":""},{"#chromosome":"3","arm":"p","band":"21.2","iscn_start":"2910","iscn_stop":"3041","bp_start":"50600001","bp_stop":"52300000","stain":"gpos","density":"25"},{"#chromosome":"3","arm":"p","band":"21.1","iscn_start":"3041","iscn_stop":"3302","bp_start":"52300001","bp_stop":"54400000","stain":"gneg","density":""},{"#chromosome":"3","arm":"p","band":"14.3","iscn_start":"3302","iscn_stop":"3575","bp_start":"54400001","bp_stop":"58600000","stain":"gpos","density":"50"},{"#chromosome":"3","arm":"p","band":"14.2","iscn_start":"3575","iscn_stop":"3847","bp_start":"58600001","bp_stop":"63800000","stain":"gneg","density":""},{"#chromosome":"3","arm":"p","band":"14.1","iscn_start":"3847","iscn_stop":"4103","bp_start":"63800001","bp_stop":"69700000","stain":"gpos","density":"50"},{"#chromosome":"3","arm":"p","band":"13","iscn_start":"4103","iscn_stop":"4456","bp_start":"69700001","bp_stop":"74100000","stain":"gneg","density":""},{"#chromosome":"3","arm":"p","band":"12","iscn_start":"4456","iscn_stop":"5439","bp_start":"74100001","bp_stop":"87100000","stain":"gpos","density":"100"},{"#chromosome":"3","arm":"p","band":"11","iscn_start":"5439","iscn_stop":"5573","bp_start":"87100001","bp_stop":"90900000","stain":"acen","density":""},{"#chromosome":"3","arm":"q","band":"11.1","iscn_start":"5573","iscn_stop":"5733","bp_start":"90900001","bp_stop":"94000000","stain":"acen","density":""},{"#chromosome":"3","arm":"q","band":"11.2","iscn_start":"5733","iscn_stop":"6034","bp_start":"94000001","bp_stop":"98600000","stain":"gvar","density":""},{"#chromosome":"3","arm":"q","band":"12","iscn_start":"6034","iscn_stop":"6175","bp_start":"98600001","bp_stop":"103100000","stain":"gneg","density":""},{"#chromosome":"3","arm":"q","band":"13.1","iscn_start":"6175","iscn_stop":"6665","bp_start":"103100001","bp_stop":"111600000","stain":"gpos","density":"100"},{"#chromosome":"3","arm":"q","band":"13.2","iscn_start":"6665","iscn_stop":"6806","bp_start":"111600001","bp_stop":"113700000","stain":"gneg","density":""},{"#chromosome":"3","arm":"q","band":"13.3","iscn_start":"6806","iscn_stop":"7389","bp_start":"113700001","bp_stop":"122200000","stain":"gpos","density":"100"},{"#chromosome":"3","arm":"q","band":"21","iscn_start":"7389","iscn_stop":"7926","bp_start":"122200001","bp_stop":"129500000","stain":"gneg","density":""},{"#chromosome":"3","arm":"q","band":"22","iscn_start":"7926","iscn_stop":"8255","bp_start":"129500001","bp_stop":"139000000","stain":"gpos","density":"100"},{"#chromosome":"3","arm":"q","band":"23","iscn_start":"8255","iscn_stop":"8538","bp_start":"139000001","bp_stop":"143100000","stain":"gneg","density":""},{"#chromosome":"3","arm":"q","band":"24","iscn_start":"8538","iscn_stop":"9206","bp_start":"143100001","bp_stop":"149200000","stain":"gpos","density":"100"},{"#chromosome":"3","arm":"q","band":"25.1","iscn_start":"9206","iscn_stop":"9334","bp_start":"149200001","bp_stop":"152300000","stain":"gneg","density":""},{"#chromosome":"3","arm":"q","band":"25.2","iscn_start":"9334","iscn_stop":"9443","bp_start":"152300001","bp_stop":"155300000","stain":"gpos","density":"50"},{"#chromosome":"3","arm":"q","band":"25.3","iscn_start":"9443","iscn_stop":"9639","bp_start":"155300001","bp_stop":"161000000","stain":"gneg","density":""},{"#chromosome":"3","arm":"q","band":"26.1","iscn_start":"9639","iscn_stop":"10147","bp_start":"161000001","bp_stop":"167900000","stain":"gpos","density":"100"},{"#chromosome":"3","arm":"q","band":"26.2","iscn_start":"10147","iscn_stop":"10269","bp_start":"167900001","bp_stop":"171200000","stain":"gneg","density":""},{"#chromosome":"3","arm":"q","band":"26.3","iscn_start":"10269","iscn_stop":"10702","bp_start":"171200001","bp_stop":"183000000","stain":"gpos","density":"100"},{"#chromosome":"3","arm":"q","band":"27","iscn_start":"10702","iscn_stop":"11022","bp_start":"183000001","bp_stop":"188200000","stain":"gneg","density":""},{"#chromosome":"3","arm":"q","band":"28","iscn_start":"11022","iscn_stop":"11352","bp_start":"188200001","bp_stop":"192600000","stain":"gpos","density":"75"},{"#chromosome":"3","arm":"q","band":"29","iscn_start":"11352","iscn_stop":"11700","bp_start":"192600001","bp_stop":"198295559","stain":"gneg","density":""},{"#chromosome":"4","arm":"p","band":"16","iscn_start":"0","iscn_stop":"843","bp_start":"1","bp_stop":"11300000","stain":"gneg","density":""},{"#chromosome":"4","arm":"p","band":"15.3","iscn_start":"843","iscn_stop":"1330","bp_start":"11300001","bp_stop":"21300000","stain":"gpos","density":"100"},{"#chromosome":"4","arm":"p","band":"15.2","iscn_start":"1330","iscn_stop":"1449","bp_start":"21300001","bp_stop":"27700000","stain":"gneg","density":""},{"#chromosome":"4","arm":"p","band":"15.1","iscn_start":"1449","iscn_stop":"1857","bp_start":"27700001","bp_stop":"35800000","stain":"gpos","density":"100"},{"#chromosome":"4","arm":"p","band":"14","iscn_start":"1857","iscn_stop":"2292","bp_start":"35800001","bp_stop":"41200000","stain":"gneg","density":""},{"#chromosome":"4","arm":"p","band":"13","iscn_start":"2292","iscn_stop":"2647","bp_start":"41200001","bp_stop":"44600000","stain":"gpos","density":"50"},{"#chromosome":"4","arm":"p","band":"12","iscn_start":"2647","iscn_stop":"2950","bp_start":"44600001","bp_stop":"48200000","stain":"gneg","density":""},{"#chromosome":"4","arm":"p","band":"11","iscn_start":"2950","iscn_stop":"3095","bp_start":"48200001","bp_stop":"50000000","stain":"acen","density":""},{"#chromosome":"4","arm":"q","band":"11","iscn_start":"3095","iscn_stop":"3211","bp_start":"50000001","bp_stop":"51800000","stain":"acen","density":""},{"#chromosome":"4","arm":"q","band":"12","iscn_start":"3211","iscn_stop":"3649","bp_start":"51800001","bp_stop":"58500000","stain":"gneg","density":""},{"#chromosome":"4","arm":"q","band":"13.1","iscn_start":"3649","iscn_stop":"3993","bp_start":"58500001","bp_stop":"65500000","stain":"gpos","density":"100"},{"#chromosome":"4","arm":"q","band":"13.2","iscn_start":"3993","iscn_stop":"4147",
                "bp_start":"65500001","bp_stop":"69400000","stain":"gneg","density":""},{"#chromosome":"4","arm":"q","band":"13.3","iscn_start":"4147","iscn_stop":"4422","bp_start":"69400001","bp_stop":"75300000","stain":"gpos","density":"75"},{"#chromosome":"4","arm":"q","band":"21.1","iscn_start":"4422","iscn_stop":"4577","bp_start":"75300001","bp_stop":"78000000","stain":"gneg","density":""},{"#chromosome":"4","arm":"q","band":"21.2","iscn_start":"4577","iscn_stop":"4920","bp_start":"78000001","bp_stop":"86000000","stain":"gpos","density":"100"},{"#chromosome":"4","arm":"q","band":"21.3","iscn_start":"4920","iscn_stop":"5169","bp_start":"86000001","bp_stop":"87100000","stain":"gneg","density":""},{"#chromosome":"4","arm":"q","band":"22","iscn_start":"5169","iscn_stop":"5735","bp_start":"87100001","bp_stop":"97900000","stain":"gpos","density":"100"},{"#chromosome":"4","arm":"q","band":"23","iscn_start":"5735","iscn_stop":"5838","bp_start":"97900001","bp_stop":"100100000","stain":"gneg","density":""},{"#chromosome":"4","arm":"q","band":"24","iscn_start":"5838","iscn_stop":"6353","bp_start":"100100001","bp_stop":"106700000","stain":"gpos","density":"50"},{"#chromosome":"4","arm":"q","band":"25","iscn_start":"6353","iscn_stop":"6560","bp_start":"106700001","bp_stop":"113200000","stain":"gneg","density":""},{"#chromosome":"4","arm":"q","band":"26","iscn_start":"6560","iscn_stop":"7294","bp_start":"113200001","bp_stop":"119900000","stain":"gpos","density":"75"},{"#chromosome":"4","arm":"q","band":"27","iscn_start":"7294","iscn_stop":"7577","bp_start":"119900001","bp_stop":"122800000","stain":"gneg","density":""},{"#chromosome":"4","arm":"q","band":"28","iscn_start":"7577","iscn_stop":"8440","bp_start":"122800001","bp_stop":"138500000","stain":"gpos","density":"100"},{"#chromosome":"4","arm":"q","band":"31.1","iscn_start":"8440","iscn_stop":"8813","bp_start":"138500001","bp_stop":"140600000","stain":"gneg","density":""},{"#chromosome":"4","arm":"q","band":"31.2","iscn_start":"8813","iscn_stop":"9071","bp_start":"140600001","bp_stop":"150200000","stain":"gpos","density":"100"},{"#chromosome":"4","arm":"q","band":"31.3","iscn_start":"9071","iscn_stop":"9431","bp_start":"150200001","bp_stop":"154600000","stain":"gneg","density":""},{"#chromosome":"4","arm":"q","band":"32","iscn_start":"9431","iscn_stop":"10050","bp_start":"154600001","bp_stop":"169200000","stain":"gpos","density":"100"},{"#chromosome":"4","arm":"q","band":"33","iscn_start":"10050","iscn_stop":"10281","bp_start":"169200001","bp_stop":"171000000","stain":"gneg","density":""},{"#chromosome":"4","arm":"q","band":"34","iscn_start":"10281","iscn_stop":"10732","bp_start":"171000001","bp_stop":"182300000","stain":"gpos","density":"100"},{"#chromosome":"4","arm":"q","band":"35","iscn_start":"10732","iscn_stop":"11170","bp_start":"182300001","bp_stop":"190214555","stain":"gneg","density":""},{"#chromosome":"5","arm":"p","band":"15.3","iscn_start":"0","iscn_stop":"574","bp_start":"1","bp_stop":"9900000","stain":"gneg","density":""},{"#chromosome":"5","arm":"p","band":"15.2","iscn_start":"574","iscn_stop":"815","bp_start":"9900001","bp_stop":"15000000","stain":"gpos","density":"50"},{"#chromosome":"5","arm":"p","band":"15.1","iscn_start":"815","iscn_stop":"1028","bp_start":"15000001","bp_stop":"18400000","stain":"gneg","density":""},{"#chromosome":"5","arm":"p","band":"14","iscn_start":"1028","iscn_stop":"1897","bp_start":"18400001","bp_stop":"28900000","stain":"gpos","density":"100"},{"#chromosome":"5","arm":"p","band":"13.3","iscn_start":"1897","iscn_stop":"2123","bp_start":"28900001","bp_stop":"33800000","stain":"gneg","density":""},{"#chromosome":"5","arm":"p","band":"13.2","iscn_start":"2123","iscn_stop":"2301","bp_start":"33800001","bp_stop":"38400000","stain":"gpos","density":"25"},{"#chromosome":"5","arm":"p","band":"13.1","iscn_start":"2301","iscn_stop":"2444","bp_start":"38400001","bp_stop":"42500000","stain":"gneg","density":""},{"#chromosome":"5","arm":"p","band":"12","iscn_start":"2444","iscn_stop":"2712","bp_start":"42500001","bp_stop":"46100000","stain":"gpos","density":"50"},{"#chromosome":"5","arm":"p","band":"11","iscn_start":"2712","iscn_stop":"2845","bp_start":"46100001","bp_stop":"48800000","stain":"acen","density":""},{"#chromosome":"5","arm":"q","band":"11.1","iscn_start":"2845","iscn_stop":"2990","bp_start":"48800001","bp_stop":"51400000","stain":"acen","density":""},{"#chromosome":"5","arm":"q","band":"11.2","iscn_start":"2990","iscn_stop":"3518","bp_start":"51400001","bp_stop":"59600000","stain":"gneg","density":""},{"#chromosome":"5","arm":"q","band":"12","iscn_start":"3518","iscn_stop":"3966","bp_start":"59600001","bp_stop":"67400000","stain":"gpos","density":"100"},{"#chromosome":"5","arm":"q","band":"13.1","iscn_start":"3966","iscn_stop":"4249","bp_start":"67400001","bp_stop":"69100000","stain":"gneg","density":""},{"#chromosome":"5","arm":"q","band":"13.2","iscn_start":"4249","iscn_stop":"4462","bp_start":"69100001","bp_stop":"74000000","stain":"gpos","density":"50"},{"#chromosome":"5","arm":"q","band":"13.3","iscn_start":"4462","iscn_stop":"4691","bp_start":"74000001","bp_stop":"77600000","stain":"gneg","density":""},{"#chromosome":"5","arm":"q","band":"14","iscn_start":"4691","iscn_stop":"5496","bp_start":"77600001","bp_stop":"93000000","stain":"gpos","density":"100"},{"#chromosome":"5","arm":"q","band":"15","iscn_start":"5496","iscn_stop":"5747","bp_start":"93000001","bp_stop":"98900000","stain":"gneg","density":""},{"#chromosome":"5","arm":"q","band":"21","iscn_start":"5747","iscn_stop":"6577","bp_start":"98900001","bp_stop":"110200000","stain":"gpos","density":"100"},{"#chromosome":"5","arm":"q","band":"22","iscn_start":"6577","iscn_stop":"6802","bp_start":"110200001","bp_stop":"115900000","stain":"gneg","density":""},{"#chromosome":"5","arm":"q","band":"23.1","iscn_start":"6802","iscn_stop":"7130","bp_start":"115900001","bp_stop":"122100000","stain":"gpos","density":"75"},{"#chromosome":"5","arm":"q","band":"23.2","iscn_start":"7130","iscn_stop":"7513","bp_start":"122100001","bp_stop":"127900000","stain":"gneg","density":""},{"#chromosome":"5","arm":"q","band":"23.3","iscn_start":"7513","iscn_stop":"7751","bp_start":"127900001","bp_stop":"131200000","stain":"gpos","density":"75"},{"#chromosome":"5","arm":"q","band":"31.1","iscn_start":"7751","iscn_stop":"8146","bp_start":"131200001","bp_stop":"136900000","stain":"gneg","density":""},{"#chromosome":"5","arm":"q","band":"31.2","iscn_start":"8146","iscn_stop":"8381","bp_start":"136900001","bp_stop":"140100000","stain":"gpos","density":"25"},{"#chromosome":"5","arm":"q","band":"31.3","iscn_start":"8381","iscn_stop":"8740","bp_start":"140100001","bp_stop":"145100000","stain":"gneg","density":""},{"#chromosome":"5","arm":"q","band":"32","iscn_start":"8740","iscn_stop":"9189","bp_start":"145100001","bp_stop":"150400000","stain":"gpos","density":"75"},{"#chromosome":"5","arm":"q","band":"33.1","iscn_start":"9189","iscn_stop":"9331","bp_start":"150400001","bp_stop":"153300000","stain":"gneg","density":""},{"#chromosome":"5","arm":"q","band":"33.2","iscn_start":"9331","iscn_stop":"9432","bp_start":"153300001","bp_stop":"156300000","stain":"gpos","density":"50"},{"#chromosome":"5","arm":"q","band":"33.3","iscn_start":"9432","iscn_stop":"9558","bp_start":"156300001","bp_stop":"160500000","stain":"gneg","density":""},{"#chromosome":"5","arm":"q","band":"34","iscn_start":"9558","iscn_stop":"10138","bp_start":"160500001","bp_stop":"169000000","stain":"gpos","density":"100"},{"#chromosome":"5","arm":"q","band":"35.1","iscn_start":"10138","iscn_stop":"10271","bp_start":"169000001","bp_stop":"173300000","stain":"gneg","density":""},{"#chromosome":"5","arm":"q","band":"35.2","iscn_start":"10271","iscn_stop":"10388","bp_start":"173300001","bp_stop":"177100000","stain":"gpos","density":"25"},{"#chromosome":"5","arm":"q","band":"35.3","iscn_start":"10388","iscn_stop":"10600","bp_start":"177100001","bp_stop":"181538259","stain":"gneg","density":""},{"#chromosome":"6","arm":"p","band":"25","iscn_start":"0","iscn_stop":"405","bp_start":"1","bp_stop":"7100000","stain":"gneg","density":""},{"#chromosome":"6","arm":"p","band":"24","iscn_start":"405","iscn_stop":"643","bp_start":"7100001","bp_stop":"13400000","stain":"gpos","density":"100"},{"#chromosome":"6","arm":"p","band":"23","iscn_start":"643","iscn_stop":"1047","bp_start":"13400001","bp_stop":"15200000","stain":"gneg","density":""},{"#chromosome":"6","arm":"p","band":"22.3","iscn_start":"1047","iscn_stop":"1381","bp_start":"15200001","bp_stop":"25200000","stain":"gpos","density":"75"},{"#chromosome":"6","arm":"p","band":"22.2","iscn_start":"1381","iscn_stop":"1541","bp_start":"25200001","bp_stop":"27100000","stain":"gneg","density":""},{"#chromosome":"6","arm":"p","band":"22.1","iscn_start":"1541","iscn_stop":"1773","bp_start":"27100001","bp_stop":"30500000","stain":"gpos","density":"50"},{"#chromosome":"6","arm":"p","band":"21.3","iscn_start":"1773","iscn_stop":"2428","bp_start":"30500001","bp_stop":"36600000","stain":"gneg","density":""},{"#chromosome":"6","arm":"p","band":"21.2","iscn_start":"2428","iscn_stop":"2571","bp_start":"36600001","bp_stop":"40500000","stain":"gpos","density":"25"},{"#chromosome":"6","arm":"p","band":"21.1","iscn_start":"2571","iscn_stop":"2999","bp_start":"40500001","bp_stop":"46200000","stain":"gneg","density":""},{"#chromosome":"6","arm":"p","band":"12","iscn_start":"2999","iscn_stop":"3618","bp_start":"46200001","bp_stop":"57200000","stain":"gpos","density":"100"},{"#chromosome":"6","arm":"p","band":"11.2","iscn_start":"3618","iscn_stop":"3714","bp_start":"57200001","bp_stop":"58500000","stain":"gneg","density":""},{"#chromosome":"6","arm":"p","band":"11.1","iscn_start":"3714","iscn_stop":"3856","bp_start":"58500001","bp_stop":"59800000","stain":"acen","density":""},{"#chromosome":"6","arm":"q","band":"11","iscn_start":"3856","iscn_stop":"3999","bp_start":"59800001","bp_stop":"62700000","stain":"acen",
                "density":""},{"#chromosome":"6","arm":"q","band":"12","iscn_start":"3999","iscn_stop":"4439","bp_start":"62700001","bp_stop":"69200000","stain":"gpos","density":"100"},{"#chromosome":"6","arm":"q","band":"13","iscn_start":"4439","iscn_stop":"4701","bp_start":"69200001","bp_stop":"75200000","stain":"gneg","density":""},{"#chromosome":"6","arm":"q","band":"14","iscn_start":"4701","iscn_stop":"5129","bp_start":"75200001","bp_stop":"87300000","stain":"gpos","density":"100"},{"#chromosome":"6","arm":"q","band":"15","iscn_start":"5129","iscn_stop":"5402","bp_start":"87300001","bp_stop":"92500000","stain":"gneg","density":""},{"#chromosome":"6","arm":"q","band":"16.1","iscn_start":"5402","iscn_stop":"5742","bp_start":"92500001","bp_stop":"98900000","stain":"gpos","density":"100"},{"#chromosome":"6","arm":"q","band":"16.2","iscn_start":"5742","iscn_stop":"5807","bp_start":"98900001","bp_stop":"100000000","stain":"gneg","density":""},{"#chromosome":"6","arm":"q","band":"16.3","iscn_start":"5807","iscn_stop":"6068","bp_start":"100000001","bp_stop":"105000000","stain":"gpos","density":"100"},{"#chromosome":"6","arm":"q","band":"21","iscn_start":"6068","iscn_stop":"6746","bp_start":"105000001","bp_stop":"114200000","stain":"gneg","density":""},{"#chromosome":"6","arm":"q","band":"22.1","iscn_start":"6746","iscn_stop":"6955","bp_start":"114200001","bp_stop":"117900000","stain":"gpos","density":"75"},{"#chromosome":"6","arm":"q","band":"22.2","iscn_start":"6955","iscn_stop":"7067","bp_start":"117900001","bp_stop":"118100000","stain":"gneg","density":""},{"#chromosome":"6","arm":"q","band":"22.3","iscn_start":"7067","iscn_stop":"7793","bp_start":"118100001","bp_stop":"130000000","stain":"gpos","density":"100"},{"#chromosome":"6","arm":"q","band":"23.1","iscn_start":"7793","iscn_stop":"7962","bp_start":"130000001","bp_stop":"130900000","stain":"gneg","density":""},{"#chromosome":"6","arm":"q","band":"23.2","iscn_start":"7962","iscn_stop":"8096","bp_start":"130900001","bp_stop":"134700000","stain":"gpos","density":"50"},{"#chromosome":"6","arm":"q","band":"23.3","iscn_start":"8096","iscn_stop":"8221","bp_start":"134700001","bp_stop":"138300000","stain":"gneg","density":""},{"#chromosome":"6","arm":"q","band":"24","iscn_start":"8221","iscn_stop":"8851","bp_start":"138300001","bp_stop":"148500000","stain":"gpos","density":"100"},{"#chromosome":"6","arm":"q","band":"25.1","iscn_start":"8851","iscn_stop":"8996","bp_start":"148500001","bp_stop":"152100000","stain":"gneg","density":""},{"#chromosome":"6","arm":"q","band":"25.2","iscn_start":"8996","iscn_stop":"9119","bp_start":"152100001","bp_stop":"155200000","stain":"gpos","density":"50"},{"#chromosome":"6","arm":"q","band":"25.3","iscn_start":"9119","iscn_stop":"9386","bp_start":"155200001","bp_stop":"160600000","stain":"gneg","density":""},{"#chromosome":"6","arm":"q","band":"26","iscn_start":"9386","iscn_stop":"9696","bp_start":"160600001","bp_stop":"164100000","stain":"gpos","density":"50"},{"#chromosome":"6","arm":"q","band":"27","iscn_start":"9696","iscn_stop":"10100","bp_start":"164100001","bp_stop":"170805979","stain":"gneg","density":""},{"#chromosome":"7","arm":"p","band":"22","iscn_start":"0","iscn_stop":"470","bp_start":"1","bp_stop":"7200000","stain":"gneg","density":""},{"#chromosome":"7","arm":"p","band":"21","iscn_start":"470","iscn_stop":"1300","bp_start":"7200001","bp_stop":"20900000","stain":"gpos","density":"100"},{"#chromosome":"7","arm":"p","band":"15.3","iscn_start":"1300","iscn_stop":"1555","bp_start":"20900001","bp_stop":"25500000","stain":"gneg","density":""},{"#chromosome":"7","arm":"p","band":"15.2","iscn_start":"1555","iscn_stop":"1700","bp_start":"25500001","bp_stop":"27900000","stain":"gpos","density":"50"},{"#chromosome":"7","arm":"p","band":"15.1","iscn_start":"1700","iscn_stop":"1894","bp_start":"27900001","bp_stop":"28800000","stain":"gneg","density":""},{"#chromosome":"7","arm":"p","band":"14","iscn_start":"1894","iscn_stop":"2339","bp_start":"28800001","bp_stop":"43300000","stain":"gpos","density":"100"},{"#chromosome":"7","arm":"p","band":"13","iscn_start":"2339","iscn_stop":"2698","bp_start":"43300001","bp_stop":"45400000","stain":"gneg","density":""},{"#chromosome":"7","arm":"p","band":"12","iscn_start":"2698","iscn_stop":"3069","bp_start":"45400001","bp_stop":"53900000","stain":"gpos","density":"100"},{"#chromosome":"7","arm":"p","band":"11.2","iscn_start":"3069","iscn_stop":"3267","bp_start":"53900001","bp_stop":"58100000","stain":"gneg","density":""},{"#chromosome":"7","arm":"p","band":"11.1","iscn_start":"3267","iscn_stop":"3391","bp_start":"58100001","bp_stop":"60100000","stain":"acen","density":""},{"#chromosome":"7","arm":"q","band":"11.1","iscn_start":"3391","iscn_stop":"3513","bp_start":"60100001","bp_stop":"62100000","stain":"acen","density":""},{"#chromosome":"7","arm":"q","band":"11.21","iscn_start":"3742","iscn_stop":"4012","bp_start":"62100001","bp_stop":"67500000","stain":"gneg","density":""},{"#chromosome":"7","arm":"q","band":"11.22","iscn_start":"4012","iscn_stop":"4200","bp_start":"67500001","bp_stop":"72700000","stain":"gpos","density":"50"},{"#chromosome":"7","arm":"q","band":"11.23","iscn_start":"4200","iscn_stop":"4605","bp_start":"72700001","bp_stop":"77900000","stain":"gneg","density":""},{"#chromosome":"7","arm":"q","band":"21.1","iscn_start":"4605","iscn_stop":"5263","bp_start":"77900001","bp_stop":"91500000","stain":"gpos","density":"100"},{"#chromosome":"7","arm":"q","band":"21.2","iscn_start":"5263","iscn_stop":"5371","bp_start":"91500001","bp_stop":"93300000","stain":"gneg","density":""},{"#chromosome":"7","arm":"q","band":"21.3","iscn_start":"5371","iscn_stop":"5612","bp_start":"93300001","bp_stop":"98400000","stain":"gpos","density":"75"},{"#chromosome":"7","arm":"q","band":"22","iscn_start":"5612","iscn_stop":"6292","bp_start":"98400001","bp_stop":"107800000","stain":"gneg","density":""},{"#chromosome":"7","arm":"q","band":"31.1","iscn_start":"6292","iscn_stop":"6584","bp_start":"107800001","bp_stop":"115000000","stain":"gpos","density":"75"},{"#chromosome":"7","arm":"q","band":"31.2","iscn_start":"6584","iscn_stop":"6836","bp_start":"115000001","bp_stop":"117700000","stain":"gneg","density":""},{"#chromosome":"7","arm":"q","band":"31.3","iscn_start":"6836","iscn_stop":"7517","bp_start":"117700001","bp_stop":"127500000","stain":"gpos","density":"100"},{"#chromosome":"7","arm":"q","band":"32","iscn_start":"7517","iscn_stop":"7966","bp_start":"127500001","bp_stop":"132900000","stain":"gneg","density":""},{"#chromosome":"7","arm":"q","band":"33","iscn_start":"7966","iscn_stop":"8221","bp_start":"132900001","bp_stop":"138500000","stain":"gpos","density":"50"},{"#chromosome":"7","arm":"q","band":"34","iscn_start":"8221","iscn_stop":"8440","bp_start":"138500001","bp_stop":"143400000","stain":"gneg","density":""},{"#chromosome":"7","arm":"q","band":"35","iscn_start":"8440","iscn_stop":"8719","bp_start":"143400001","bp_stop":"148200000","stain":"gpos","density":"75"},{"#chromosome":"7","arm":"q","band":"36","iscn_start":"8719","iscn_stop":"9350","bp_start":"148200001","bp_stop":"159345973","stain":"gneg","density":""},{"#chromosome":"8","arm":"p","band":"23.3","iscn_start":"0","iscn_stop":"102","bp_start":"1","bp_stop":"2300000","stain":"gneg","density":""},{"#chromosome":"8","arm":"p","band":"23.2","iscn_start":"102","iscn_stop":"293","bp_start":"2300001","bp_stop":"6300000","stain":"gpos","density":"75"},{"#chromosome":"8","arm":"p","band":"23.1","iscn_start":"293","iscn_stop":"610","bp_start":"6300001","bp_stop":"12800000","stain":"gneg","density":""},{"#chromosome":"8","arm":"p","band":"22","iscn_start":"610","iscn_stop":"1138","bp_start":"12800001","bp_stop":"19200000","stain":"gpos","density":"100"},{"#chromosome":"8","arm":"p","band":"21.3","iscn_start":"1138","iscn_stop":"1288","bp_start":"19200001","bp_stop":"23500000","stain":"gneg","density":""},{"#chromosome":"8","arm":"p","band":"21.2","iscn_start":"1288","iscn_stop":"1449","bp_start":"23500001","bp_stop":"27500000","stain":"gpos","density":"50"},{"#chromosome":"8","arm":"p","band":"21.1","iscn_start":"1449","iscn_stop":"1656","bp_start":"27500001","bp_stop":"29000000","stain":"gneg","density":""},{"#chromosome":"8","arm":"p","band":"12","iscn_start":"1656","iscn_stop":"2266","bp_start":"29000001","bp_stop":"36700000","stain":"gpos","density":"75"},{"#chromosome":"8","arm":"p","band":"11.2","iscn_start":"2266","iscn_stop":"2611","bp_start":"36700001","bp_stop":"43200000","stain":"gneg","density":""},{"#chromosome":"8","arm":"p","band":"11.1","iscn_start":"2611","iscn_stop":"2743","bp_start":"43200001","bp_stop":"45200000","stain":"acen","density":""},{"#chromosome":"8","arm":"q","band":"11.1","iscn_start":"2743","iscn_stop":"2891","bp_start":"45200001","bp_stop":"47200000","stain":"acen","density":""},{"#chromosome":"8","arm":"q","band":"11.21","iscn_start":"2946","iscn_stop":"3001","bp_start":"47200001","bp_stop":"51300000","stain":"gneg","density":""},{"#chromosome":"8","arm":"q","band":"11.22","iscn_start":"3001","iscn_stop":"3070","bp_start":"51300001","bp_stop":"51700000","stain":"gpos","density":"75"},{"#chromosome":"8","arm":"q","band":"11.23","iscn_start":"3070","iscn_stop":"3148","bp_start":"51700001","bp_stop":"54600000","stain":"gneg","density":""},{"#chromosome":"8","arm":"q","band":"12","iscn_start":"3148","iscn_stop":"3641","bp_start":"54600001","bp_stop":"65100000","stain":"gpos","density":"100"},{"#chromosome":"8","arm":"q","band":"13","iscn_start":"3641","iscn_stop":"4145","bp_start":"65100001","bp_stop":"72000000","stain":"gneg","density":""},{"#chromosome":"8","arm":"q","band":"21.1","iscn_start":"4145","iscn_stop":"4756","bp_start":"72000001","bp_stop":"83500000","stain":"gpos","density":"100"},{"#chromosome":"8","arm":"q","band":"21.2","iscn_start":"4756","iscn_stop":"4885","bp_start":"83500001","bp_stop":"85900000","stain":"gneg","density":""},{"#chromosome":"8","arm":"q","band":"21.3","iscn_start":"4885",
                "iscn_stop":"5506","bp_start":"85900001","bp_stop":"92300000","stain":"gpos","density":"100"},{"#chromosome":"8","arm":"q","band":"22.1","iscn_start":"5506","iscn_stop":"5856","bp_start":"92300001","bp_stop":"97900000","stain":"gneg","density":""},{"#chromosome":"8","arm":"q","band":"22.2","iscn_start":"5856","iscn_stop":"5996","bp_start":"97900001","bp_stop":"100500000","stain":"gpos","density":"25"},{"#chromosome":"8","arm":"q","band":"22.3","iscn_start":"5996","iscn_stop":"6276","bp_start":"100500001","bp_stop":"105100000","stain":"gneg","density":""},{"#chromosome":"8","arm":"q","band":"23","iscn_start":"6276","iscn_stop":"7026","bp_start":"105100001","bp_stop":"116700000","stain":"gpos","density":"100"},{"#chromosome":"8","arm":"q","band":"24.1","iscn_start":"7026","iscn_stop":"7628","bp_start":"116700001","bp_stop":"126300000","stain":"gneg","density":""},{"#chromosome":"8","arm":"q","band":"24.2","iscn_start":"7628","iscn_stop":"7885","bp_start":"126300001","bp_stop":"138900000","stain":"gpos","density":"100"},{"#chromosome":"8","arm":"q","band":"24.3","iscn_start":"7885","iscn_stop":"8250","bp_start":"138900001","bp_stop":"145138636","stain":"gneg","density":""},{"#chromosome":"9","arm":"p","band":"24","iscn_start":"0","iscn_stop":"355","bp_start":"1","bp_stop":"9000000","stain":"gneg","density":""},{"#chromosome":"9","arm":"p","band":"23","iscn_start":"355","iscn_stop":"841","bp_start":"9000001","bp_stop":"14200000","stain":"gpos","density":"75"},{"#chromosome":"9","arm":"p","band":"22","iscn_start":"841","iscn_stop":"942","bp_start":"14200001","bp_stop":"19900000","stain":"gneg","density":""},{"#chromosome":"9","arm":"p","band":"21","iscn_start":"942","iscn_stop":"1661","bp_start":"19900001","bp_stop":"33200000","stain":"gpos","density":"100"},{"#chromosome":"9","arm":"p","band":"13","iscn_start":"1661","iscn_stop":"2148","bp_start":"33200001","bp_stop":"39000000","stain":"gneg","density":""},{"#chromosome":"9","arm":"p","band":"12","iscn_start":"2148","iscn_stop":"2522","bp_start":"39000001","bp_stop":"40000000","stain":"gpos","density":"50"},{"#chromosome":"9","arm":"p","band":"11","iscn_start":"2522","iscn_stop":"2634","bp_start":"40000001","bp_stop":"43000000","stain":"acen","density":""},{"#chromosome":"9","arm":"q","band":"11","iscn_start":"2634","iscn_stop":"2773","bp_start":"43000001","bp_stop":"45500000","stain":"acen","density":""},{"#chromosome":"9","arm":"q","band":"12","iscn_start":"2773","iscn_stop":"3695","bp_start":"45500001","bp_stop":"61500000","stain":"gvar","density":""},{"#chromosome":"9","arm":"q","band":"13","iscn_start":"3695","iscn_stop":"3864","bp_start":"61500001","bp_stop":"65000000","stain":"gneg","density":""},{"#chromosome":"9","arm":"q","band":"21.1","iscn_start":"3864","iscn_stop":"4232","bp_start":"65000001","bp_stop":"76600000","stain":"gpos","density":"100"},{"#chromosome":"9","arm":"q","band":"21.2","iscn_start":"4232","iscn_stop":"4370","bp_start":"76600001","bp_stop":"78500000","stain":"gneg","density":""},{"#chromosome":"9","arm":"q","band":"21.3","iscn_start":"4370","iscn_stop":"4865","bp_start":"78500001","bp_stop":"87800000","stain":"gpos","density":"100"},{"#chromosome":"9","arm":"q","band":"22.1","iscn_start":"4865","iscn_stop":"5160","bp_start":"87800001","bp_stop":"89200000","stain":"gneg","density":""},{"#chromosome":"9","arm":"q","band":"22.2","iscn_start":"5160","iscn_stop":"5283","bp_start":"89200001","bp_stop":"91200000","stain":"gpos","density":"25"},{"#chromosome":"9","arm":"q","band":"22.3","iscn_start":"5283","iscn_stop":"5857","bp_start":"91200001","bp_stop":"99800000","stain":"gneg","density":""},{"#chromosome":"9","arm":"q","band":"31","iscn_start":"5857","iscn_stop":"6373","bp_start":"99800001","bp_stop":"112100000","stain":"gpos","density":"100"},{"#chromosome":"9","arm":"q","band":"32","iscn_start":"6373","iscn_stop":"6571","bp_start":"112100001","bp_stop":"114900000","stain":"gneg","density":""},{"#chromosome":"9","arm":"q","band":"33","iscn_start":"6571","iscn_stop":"6958","bp_start":"114900001","bp_stop":"127500000","stain":"gpos","density":"100"},{"#chromosome":"9","arm":"q","band":"34.1","iscn_start":"6958","iscn_stop":"7448","bp_start":"127500001","bp_stop":"133100000","stain":"gneg","density":""},{"#chromosome":"9","arm":"q","band":"34.2","iscn_start":"7448","iscn_stop":"7559","bp_start":"133100001","bp_stop":"134500000","stain":"gpos","density":"25"},{"#chromosome":"9","arm":"q","band":"34.3","iscn_start":"7559","iscn_stop":"7950","bp_start":"134500001","bp_stop":"138394717","stain":"gneg","density":""},{"#chromosome":"10","arm":"p","band":"15","iscn_start":"0","iscn_stop":"456","bp_start":"1","bp_stop":"6600000","stain":"gneg","density":""},{"#chromosome":"10","arm":"p","band":"14","iscn_start":"456","iscn_stop":"841","bp_start":"6600001","bp_stop":"12200000","stain":"gpos","density":"75"},{"#chromosome":"10","arm":"p","band":"13","iscn_start":"841","iscn_stop":"1261","bp_start":"12200001","bp_stop":"17300000","stain":"gneg","density":""},{"#chromosome":"10","arm":"p","band":"12.3","iscn_start":"1261","iscn_stop":"1658","bp_start":"17300001","bp_stop":"22300000","stain":"gpos","density":"100"},{"#chromosome":"10","arm":"p","band":"12.2","iscn_start":"1658","iscn_stop":"1711","bp_start":"22300001","bp_stop":"24300000","stain":"gneg","density":""},{"#chromosome":"10","arm":"p","band":"12.1","iscn_start":"1711","iscn_stop":"1923","bp_start":"24300001","bp_stop":"29300000","stain":"gpos","density":"50"},{"#chromosome":"10","arm":"p","band":"11.2","iscn_start":"1923","iscn_stop":"2477","bp_start":"29300001","bp_stop":"38000000","stain":"gneg","density":""},{"#chromosome":"10","arm":"p","band":"11.1","iscn_start":"2477","iscn_stop":"2620","bp_start":"38000001","bp_stop":"39800000","stain":"acen","density":""},{"#chromosome":"10","arm":"q","band":"11.1","iscn_start":"2620","iscn_stop":"2762","bp_start":"39800001","bp_stop":"41600000","stain":"acen","density":""},{"#chromosome":"10","arm":"q","band":"11.2","iscn_start":"2762","iscn_stop":"3222","bp_start":"41600001","bp_stop":"51100000","stain":"gneg","density":""},{"#chromosome":"10","arm":"q","band":"21.1","iscn_start":"3222","iscn_stop":"3832","bp_start":"51100001","bp_stop":"59400000","stain":"gpos","density":"100"},{"#chromosome":"10","arm":"q","band":"21.2","iscn_start":"3832","iscn_stop":"3985","bp_start":"59400001","bp_stop":"62800000","stain":"gneg","density":""},{"#chromosome":"10","arm":"q","band":"21.3","iscn_start":"3985","iscn_stop":"4442","bp_start":"62800001","bp_stop":"68800000","stain":"gpos","density":"100"},{"#chromosome":"10","arm":"q","band":"22.1","iscn_start":"4442","iscn_stop":"4845","bp_start":"68800001","bp_stop":"73100000","stain":"gneg","density":""},{"#chromosome":"10","arm":"q","band":"22.2","iscn_start":"4845","iscn_stop":"5047","bp_start":"73100001","bp_stop":"75900000","stain":"gpos","density":"50"},{"#chromosome":"10","arm":"q","band":"22.3","iscn_start":"5047","iscn_stop":"5388","bp_start":"75900001","bp_stop":"80300000","stain":"gneg","density":""},{"#chromosome":"10","arm":"q","band":"23.1","iscn_start":"5388","iscn_stop":"5566","bp_start":"80300001","bp_stop":"86100000","stain":"gpos","density":"100"},{"#chromosome":"10","arm":"q","band":"23.2","iscn_start":"5566","iscn_stop":"5667","bp_start":"86100001","bp_stop":"87700000","stain":"gneg","density":""},{"#chromosome":"10","arm":"q","band":"23.3","iscn_start":"5667","iscn_stop":"6096","bp_start":"87700001","bp_stop":"95300000","stain":"gpos","density":"100"},{"#chromosome":"10","arm":"q","band":"24.1","iscn_start":"6096","iscn_stop":"6193","bp_start":"95300001","bp_stop":"97500000","stain":"gneg","density":""},{"#chromosome":"10","arm":"q","band":"24.2","iscn_start":"6193","iscn_stop":"6371","bp_start":"97500001","bp_stop":"100100000","stain":"gpos","density":"50"},{"#chromosome":"10","arm":"q","band":"24.3","iscn_start":"6371","iscn_stop":"6644","bp_start":"100100001","bp_stop":"104000000","stain":"gneg","density":""},{"#chromosome":"10","arm":"q","band":"25.1","iscn_start":"6644","iscn_stop":"7017","bp_start":"104000001","bp_stop":"110100000","stain":"gpos","density":"100"},{"#chromosome":"10","arm":"q","band":"25.2","iscn_start":"7017","iscn_stop":"7174","bp_start":"110100001","bp_stop":"113100000","stain":"gneg","density":""},{"#chromosome":"10","arm":"q","band":"25.3","iscn_start":"7174","iscn_stop":"7351","bp_start":"113100001","bp_stop":"117300000","stain":"gpos","density":"75"},{"#chromosome":"10","arm":"q","band":"26.1","iscn_start":"7351","iscn_stop":"7722","bp_start":"117300001","bp_stop":"125700000","stain":"gneg","density":""},{"#chromosome":"10","arm":"q","band":"26.2","iscn_start":"7722","iscn_stop":"7852","bp_start":"125700001","bp_stop":"128800000","stain":"gpos","density":"50"},{"#chromosome":"10","arm":"q","band":"26.3","iscn_start":"7852","iscn_stop":"8050","bp_start":"128800001","bp_stop":"133797422","stain":"gneg","density":""},{"#chromosome":"11","arm":"p","band":"15.5","iscn_start":"0","iscn_stop":"212","bp_start":"1","bp_stop":"2800000","stain":"gneg","density":""},{"#chromosome":"11","arm":"p","band":"15.4","iscn_start":"212","iscn_stop":"425","bp_start":"2800001","bp_stop":"11700000","stain":"gpos","density":"50"},{"#chromosome":"11","arm":"p","band":"15.3","iscn_start":"425","iscn_stop":"687","bp_start":"11700001","bp_stop":"13800000","stain":"gneg","density":""},{"#chromosome":"11","arm":"p","band":"15.2","iscn_start":"687","iscn_stop":"862","bp_start":"13800001","bp_stop":"16900000","stain":"gpos","density":"50"},{"#chromosome":"11","arm":"p","band":"15.1","iscn_start":"862","iscn_stop":"1149","bp_start":"16900001","bp_stop":"22000000","stain":"gneg","density":""},{"#chromosome":"11","arm":"p","band":"14","iscn_start":"1149","iscn_stop":"1957","bp_start":"22000001","bp_stop":"31000000","stain":"gpos","density":"100"},{"#chromosome":"11","arm":"p","band":"13","iscn_start":"1957","iscn_stop":"2262","bp_start":"31000001","bp_stop":"36400000",
                "stain":"gneg","density":""},{"#chromosome":"11","arm":"p","band":"12","iscn_start":"2262","iscn_stop":"2639","bp_start":"36400001","bp_stop":"43400000","stain":"gpos","density":"100"},{"#chromosome":"11","arm":"p","band":"11.2","iscn_start":"2639","iscn_stop":"3186","bp_start":"43400001","bp_stop":"48800000","stain":"gneg","density":""},{"#chromosome":"11","arm":"p","band":"11.12","iscn_start":"3257","iscn_stop":"3309","bp_start":"48800001","bp_stop":"51000000","stain":"gpos","density":"75"},{"#chromosome":"11","arm":"p","band":"11.11","iscn_start":"2872","iscn_stop":"3035","bp_start":"51000001","bp_stop":"53400000","stain":"acen"},{"#chromosome":"11","arm":"q","band":"11","iscn_start":"3348","iscn_stop":"3478","bp_start":"53400001","bp_stop":"55800000","stain":"acen","density":""},{"#chromosome":"11","arm":"q","band":"12","iscn_start":"3478","iscn_stop":"3999","bp_start":"55800001","bp_stop":"63600000","stain":"gpos","density":"100"},{"#chromosome":"11","arm":"q","band":"13.1","iscn_start":"3999","iscn_stop":"4248","bp_start":"63600001","bp_stop":"66100000","stain":"gneg","density":""},{"#chromosome":"11","arm":"q","band":"13.2","iscn_start":"4248","iscn_stop":"4353","bp_start":"66100001","bp_stop":"68700000","stain":"gpos","density":"25"},{"#chromosome":"11","arm":"q","band":"13.3","iscn_start":"4353","iscn_stop":"4584","bp_start":"68700001","bp_stop":"70500000","stain":"gneg","density":""},{"#chromosome":"11","arm":"q","band":"13.4","iscn_start":"4584","iscn_stop":"4708","bp_start":"70500001","bp_stop":"75500000","stain":"gpos","density":"50"},{"#chromosome":"11","arm":"q","band":"13.5","iscn_start":"4708","iscn_stop":"4842","bp_start":"75500001","bp_stop":"77400000","stain":"gneg","density":""},{"#chromosome":"11","arm":"q","band":"14.1","iscn_start":"4842","iscn_stop":"5186","bp_start":"77400001","bp_stop":"85900000","stain":"gpos","density":"100"},{"#chromosome":"11","arm":"q","band":"14.2","iscn_start":"5186","iscn_stop":"5324","bp_start":"85900001","bp_stop":"88600000","stain":"gneg","density":""},{"#chromosome":"11","arm":"q","band":"14.3","iscn_start":"5324","iscn_stop":"5599","bp_start":"88600001","bp_stop":"93000000","stain":"gpos","density":"100"},{"#chromosome":"11","arm":"q","band":"21","iscn_start":"5599","iscn_stop":"5703","bp_start":"93000001","bp_stop":"97400000","stain":"gneg","density":""},{"#chromosome":"11","arm":"q","band":"22.1","iscn_start":"5703","iscn_stop":"5967","bp_start":"97400001","bp_stop":"102300000","stain":"gpos","density":"100"},{"#chromosome":"11","arm":"q","band":"22.2","iscn_start":"5967","iscn_stop":"6114","bp_start":"102300001","bp_stop":"103000000","stain":"gneg","density":""},{"#chromosome":"11","arm":"q","band":"22.3","iscn_start":"6114","iscn_stop":"6363","bp_start":"103000001","bp_stop":"110600000","stain":"gpos","density":"100"},{"#chromosome":"11","arm":"q","band":"23.1","iscn_start":"6363","iscn_stop":"6585","bp_start":"110600001","bp_stop":"112700000","stain":"gneg","density":""},{"#chromosome":"11","arm":"q","band":"23.2","iscn_start":"6585","iscn_stop":"6793","bp_start":"112700001","bp_stop":"114600000","stain":"gpos","density":"50"},{"#chromosome":"11","arm":"q","band":"23.3","iscn_start":"6793","iscn_stop":"7311","bp_start":"114600001","bp_stop":"121300000","stain":"gneg","density":""},{"#chromosome":"11","arm":"q","band":"24","iscn_start":"7311","iscn_stop":"7658","bp_start":"121300001","bp_stop":"130900000","stain":"gpos","density":"100"},{"#chromosome":"11","arm":"q","band":"25","iscn_start":"7658","iscn_stop":"7980","bp_start":"130900001","bp_stop":"135086622","stain":"gneg","density":""},{"#chromosome":"12","arm":"p","band":"13.3","iscn_start":"0","iscn_stop":"522","bp_start":"1","bp_stop":"10000000","stain":"gneg","density":""},{"#chromosome":"12","arm":"p","band":"13.2","iscn_start":"522","iscn_stop":"665","bp_start":"10000001","bp_stop":"12600000","stain":"gpos","density":"75"},{"#chromosome":"12","arm":"p","band":"13.1","iscn_start":"665","iscn_stop":"760","bp_start":"12600001","bp_stop":"14600000","stain":"gneg","density":""},{"#chromosome":"12","arm":"p","band":"12.3","iscn_start":"760","iscn_stop":"1092","bp_start":"14600001","bp_stop":"19800000","stain":"gpos","density":"100"},{"#chromosome":"12","arm":"p","band":"12.2","iscn_start":"1092","iscn_stop":"1160","bp_start":"19800001","bp_stop":"21100000","stain":"gneg","density":""},{"#chromosome":"12","arm":"p","band":"12.1","iscn_start":"1160","iscn_stop":"1492","bp_start":"21100001","bp_stop":"26300000","stain":"gpos","density":"100"},{"#chromosome":"12","arm":"p","band":"11.2","iscn_start":"1492","iscn_stop":"1922","bp_start":"26300001","bp_stop":"33200000","stain":"gneg","density":""},{"#chromosome":"12","arm":"p","band":"11.1","iscn_start":"1922","iscn_stop":"2105","bp_start":"33200001","bp_stop":"35500000","stain":"acen","density":""},{"#chromosome":"12","arm":"q","band":"11","iscn_start":"2105","iscn_stop":"2202","bp_start":"35500001","bp_stop":"37800000","stain":"acen","density":""},{"#chromosome":"12","arm":"q","band":"12","iscn_start":"2202","iscn_stop":"2697","bp_start":"37800001","bp_stop":"46000000","stain":"gpos","density":"100"},{"#chromosome":"12","arm":"q","band":"13.1","iscn_start":"2697","iscn_stop":"3204","bp_start":"46000001","bp_stop":"54500000","stain":"gneg","density":""},{"#chromosome":"12","arm":"q","band":"13.2","iscn_start":"3204","iscn_stop":"3340","bp_start":"54500001","bp_stop":"56200000","stain":"gpos","density":"25"},{"#chromosome":"12","arm":"q","band":"13.3","iscn_start":"3340","iscn_stop":"3430","bp_start":"56200001","bp_stop":"57700000","stain":"gneg","density":""},{"#chromosome":"12","arm":"q","band":"14","iscn_start":"3430","iscn_stop":"3942","bp_start":"57700001","bp_stop":"67300000","stain":"gpos","density":"100"},{"#chromosome":"12","arm":"q","band":"15","iscn_start":"3942","iscn_stop":"4286","bp_start":"67300001","bp_stop":"71100000","stain":"gneg","density":""},{"#chromosome":"12","arm":"q","band":"21.1","iscn_start":"4286","iscn_stop":"4460","bp_start":"71100001","bp_stop":"75300000","stain":"gpos","density":"75"},{"#chromosome":"12","arm":"q","band":"21.2","iscn_start":"4460","iscn_stop":"4664","bp_start":"75300001","bp_stop":"79900000","stain":"gneg","density":""},{"#chromosome":"12","arm":"q","band":"21.3","iscn_start":"4664","iscn_stop":"5293","bp_start":"79900001","bp_stop":"92200000","stain":"gpos","density":"100"},{"#chromosome":"12","arm":"q","band":"22","iscn_start":"5293","iscn_stop":"5716","bp_start":"92200001","bp_stop":"95800000","stain":"gneg","density":""},{"#chromosome":"12","arm":"q","band":"23","iscn_start":"5716","iscn_stop":"6229","bp_start":"95800001","bp_stop":"108600000","stain":"gpos","density":"100"},{"#chromosome":"12","arm":"q","band":"24.1","iscn_start":"6229","iscn_stop":"6714","bp_start":"108600001","bp_stop":"113900000","stain":"gneg","density":""},{"#chromosome":"12","arm":"q","band":"24.2","iscn_start":"6714","iscn_stop":"6917","bp_start":"113900001","bp_stop":"120300000","stain":"gpos","density":"100"},{"#chromosome":"12","arm":"q","band":"24.31","iscn_start":"7227","iscn_stop":"7351","bp_start":"120300001","bp_stop":"125400000","stain":"gneg","density":""},{"#chromosome":"12","arm":"q","band":"24.32","iscn_start":"7351","iscn_stop":"7412","bp_start":"125400001","bp_stop":"128700000","stain":"gpos","density":"50"},{"#chromosome":"12","arm":"q","band":"24.33","iscn_start":"7412","iscn_stop":"7500","bp_start":"128700001","bp_stop":"133275309","stain":"gneg","density":""},{"#chromosome":"13","arm":"p","band":"13","iscn_start":"0","iscn_stop":"301","bp_start":"1","bp_stop":"4600000","stain":"gvar","density":""},{"#chromosome":"13","arm":"p","band":"12","iscn_start":"301","iscn_stop":"602","bp_start":"4600001","bp_stop":"10100000","stain":"stalk","density":""},{"#chromosome":"13","arm":"p","band":"11.2","iscn_start":"602","iscn_stop":"1004","bp_start":"10100001","bp_stop":"16500000","stain":"gvar","density":""},{"#chromosome":"13","arm":"p","band":"11.1","iscn_start":"1004","iscn_stop":"1204","bp_start":"16500001","bp_stop":"17700000","stain":"acen","density":""},{"#chromosome":"13","arm":"q","band":"11","iscn_start":"1204","iscn_stop":"1351","bp_start":"17700001","bp_stop":"18900000","stain":"acen","density":""},{"#chromosome":"13","arm":"q","band":"12.1","iscn_start":"1351","iscn_stop":"1735","bp_start":"18900001","bp_stop":"27200000","stain":"gneg","density":""},{"#chromosome":"13","arm":"q","band":"12.2","iscn_start":"1735","iscn_stop":"1821","bp_start":"27200001","bp_stop":"28300000","stain":"gpos","density":"25"},{"#chromosome":"13","arm":"q","band":"12.3","iscn_start":"1821","iscn_stop":"2019","bp_start":"28300001","bp_stop":"31600000","stain":"gneg","density":""},{"#chromosome":"13","arm":"q","band":"13","iscn_start":"2019","iscn_stop":"2295","bp_start":"31600001","bp_stop":"39500000","stain":"gpos","density":"100"},{"#chromosome":"13","arm":"q","band":"14.1","iscn_start":"2295","iscn_stop":"2690","bp_start":"39500001","bp_stop":"46700000","stain":"gneg","density":""},{"#chromosome":"13","arm":"q","band":"14.2","iscn_start":"2690","iscn_stop":"2841","bp_start":"46700001","bp_stop":"50300000","stain":"gpos","density":"50"},{"#chromosome":"13","arm":"q","band":"14.3","iscn_start":"2841","iscn_stop":"3028","bp_start":"50300001","bp_stop":"54700000","stain":"gneg","density":""},{"#chromosome":"13","arm":"q","band":"21.1","iscn_start":"3028","iscn_stop":"3294","bp_start":"54700001","bp_stop":"59000000","stain":"gpos","density":"100"},{"#chromosome":"13","arm":"q","band":"21.2","iscn_start":"3294","iscn_stop":"3444","bp_start":"59000001","bp_stop":"61800000","stain":"gneg","density":""},{"#chromosome":"13","arm":"q","band":"21.3","iscn_start":"3444","iscn_stop":"4093","bp_start":"61800001","bp_stop":"72800000","stain":"gpos","density":"100"},{"#chromosome":"13","arm":"q","band":"22","iscn_start":"4093","iscn_stop":"4625","bp_start":"72800001","bp_stop":"78500000","stain":"gneg","density":""},
                {"#chromosome":"13","arm":"q","band":"31","iscn_start":"4625","iscn_stop":"5280","bp_start":"78500001","bp_stop":"94400000","stain":"gpos","density":"100"},{"#chromosome":"13","arm":"q","band":"32","iscn_start":"5280","iscn_stop":"5742","bp_start":"94400001","bp_stop":"101100000","stain":"gneg","density":""},{"#chromosome":"13","arm":"q","band":"33","iscn_start":"5742","iscn_stop":"6065","bp_start":"101100001","bp_stop":"109600000","stain":"gpos","density":"100"},{"#chromosome":"13","arm":"q","band":"34","iscn_start":"6065","iscn_stop":"6510","bp_start":"109600001","bp_stop":"114364328","stain":"gneg","density":""},{"#chromosome":"14","arm":"p","band":"13","iscn_start":"0","iscn_stop":"305","bp_start":"1","bp_stop":"3600000","stain":"gvar","density":""},{"#chromosome":"14","arm":"p","band":"12","iscn_start":"305","iscn_stop":"611","bp_start":"3600001","bp_stop":"8000000","stain":"stalk","density":""},{"#chromosome":"14","arm":"p","band":"11.2","iscn_start":"611","iscn_stop":"1018","bp_start":"8000001","bp_stop":"16100000","stain":"gvar","density":""},{"#chromosome":"14","arm":"p","band":"11.1","iscn_start":"1018","iscn_stop":"1222","bp_start":"16100001","bp_stop":"17200000","stain":"acen","density":""},{"#chromosome":"14","arm":"q","band":"11.1","iscn_start":"1222","iscn_stop":"1421","bp_start":"17200001","bp_stop":"18200000","stain":"acen","density":""},{"#chromosome":"14","arm":"q","band":"11.2","iscn_start":"1421","iscn_stop":"1808","bp_start":"18200001","bp_stop":"24100000","stain":"gneg","density":""},{"#chromosome":"14","arm":"q","band":"12","iscn_start":"1808","iscn_stop":"2243","bp_start":"24100001","bp_stop":"32900000","stain":"gpos","density":"100"},{"#chromosome":"14","arm":"q","band":"13","iscn_start":"2243","iscn_stop":"2633","bp_start":"32900001","bp_stop":"37400000","stain":"gneg","density":""},{"#chromosome":"14","arm":"q","band":"21","iscn_start":"2633","iscn_stop":"3441","bp_start":"37400001","bp_stop":"50400000","stain":"gpos","density":"100"},{"#chromosome":"14","arm":"q","band":"22","iscn_start":"3441","iscn_stop":"3894","bp_start":"50400001","bp_stop":"57600000","stain":"gneg","density":""},{"#chromosome":"14","arm":"q","band":"23","iscn_start":"3894","iscn_stop":"4169","bp_start":"57600001","bp_stop":"67400000","stain":"gpos","density":"100"},{"#chromosome":"14","arm":"q","band":"24.1","iscn_start":"4169","iscn_stop":"4587","bp_start":"67400001","bp_stop":"69800000","stain":"gneg","density":""},{"#chromosome":"14","arm":"q","band":"24.2","iscn_start":"4587","iscn_stop":"4786","bp_start":"69800001","bp_stop":"73300000","stain":"gpos","density":"50"},{"#chromosome":"14","arm":"q","band":"24.3","iscn_start":"4786","iscn_stop":"5084","bp_start":"73300001","bp_stop":"78800000","stain":"gneg","density":""},{"#chromosome":"14","arm":"q","band":"31","iscn_start":"5084","iscn_stop":"5599","bp_start":"78800001","bp_stop":"89300000","stain":"gpos","density":"100"},{"#chromosome":"14","arm":"q","band":"32.1","iscn_start":"5599","iscn_stop":"5798","bp_start":"89300001","bp_stop":"95800000","stain":"gneg","density":""},{"#chromosome":"14","arm":"q","band":"32.2","iscn_start":"5798","iscn_stop":"5881","bp_start":"95800001","bp_stop":"100900000","stain":"gpos","density":"50"},{"#chromosome":"14","arm":"q","band":"32.3","iscn_start":"5881","iscn_stop":"6300","bp_start":"100900001","bp_stop":"107043718","stain":"gneg","density":""},{"#chromosome":"15","arm":"p","band":"13","iscn_start":"0","iscn_stop":"319","bp_start":"1","bp_stop":"4200000","stain":"gvar","density":""},{"#chromosome":"15","arm":"p","band":"12","iscn_start":"319","iscn_stop":"637","bp_start":"4200001","bp_stop":"9700000","stain":"stalk","density":""},{"#chromosome":"15","arm":"p","band":"11.2","iscn_start":"637","iscn_stop":"1062","bp_start":"9700001","bp_stop":"17500000","stain":"gvar","density":""},{"#chromosome":"15","arm":"p","band":"11.1","iscn_start":"1062","iscn_stop":"1275","bp_start":"17500001","bp_stop":"19000000","stain":"acen","density":""},{"#chromosome":"15","arm":"q","band":"11.1","iscn_start":"1275","iscn_stop":"1498","bp_start":"19000001","bp_stop":"20500000","stain":"acen","density":""},{"#chromosome":"15","arm":"q","band":"11.2","iscn_start":"1498","iscn_stop":"1657","bp_start":"20500001","bp_stop":"25500000","stain":"gneg","density":""},{"#chromosome":"15","arm":"q","band":"12","iscn_start":"1657","iscn_stop":"1862","bp_start":"25500001","bp_stop":"27800000","stain":"gpos","density":"50"},{"#chromosome":"15","arm":"q","band":"13","iscn_start":"1862","iscn_stop":"2043","bp_start":"27800001","bp_stop":"33400000","stain":"gneg","density":""},{"#chromosome":"15","arm":"q","band":"14","iscn_start":"2043","iscn_stop":"2411","bp_start":"33400001","bp_stop":"39800000","stain":"gpos","density":"75"},{"#chromosome":"15","arm":"q","band":"15","iscn_start":"2411","iscn_stop":"2832","bp_start":"39800001","bp_stop":"44500000","stain":"gneg","density":""},{"#chromosome":"15","arm":"q","band":"21.1","iscn_start":"2832","iscn_stop":"3121","bp_start":"44500001","bp_stop":"49200000","stain":"gpos","density":"75"},{"#chromosome":"15","arm":"q","band":"21.2","iscn_start":"3121","iscn_stop":"3312","bp_start":"49200001","bp_stop":"52600000","stain":"gneg","density":""},{"#chromosome":"15","arm":"q","band":"21.3","iscn_start":"3312","iscn_stop":"3600","bp_start":"52600001","bp_stop":"58800000","stain":"gpos","density":"75"},{"#chromosome":"15","arm":"q","band":"22.1","iscn_start":"3600","iscn_stop":"3724","bp_start":"58800001","bp_stop":"59000000","stain":"gneg","density":""},{"#chromosome":"15","arm":"q","band":"22.2","iscn_start":"3724","iscn_stop":"3820","bp_start":"59000001","bp_stop":"63400000","stain":"gpos","density":"25"},{"#chromosome":"15","arm":"q","band":"22.3","iscn_start":"3820","iscn_stop":"4203","bp_start":"63400001","bp_stop":"67200000","stain":"gneg","density":""},{"#chromosome":"15","arm":"q","band":"23","iscn_start":"4203","iscn_stop":"4471","bp_start":"67200001","bp_stop":"72400000","stain":"gpos","density":"25"},{"#chromosome":"15","arm":"q","band":"24","iscn_start":"4471","iscn_stop":"5002","bp_start":"72400001","bp_stop":"78000000","stain":"gneg","density":""},{"#chromosome":"15","arm":"q","band":"25","iscn_start":"5002","iscn_stop":"5381","bp_start":"78000001","bp_stop":"88500000","stain":"gpos","density":"100"},{"#chromosome":"15","arm":"q","band":"26.1","iscn_start":"5381","iscn_stop":"5650","bp_start":"88500001","bp_stop":"93800000","stain":"gneg","density":""},{"#chromosome":"15","arm":"q","band":"26.2","iscn_start":"5650","iscn_stop":"5861","bp_start":"93800001","bp_stop":"98000000","stain":"gpos","density":"50"},{"#chromosome":"15","arm":"q","band":"26.3","iscn_start":"5861","iscn_stop":"6070","bp_start":"98000001","bp_stop":"101991189","stain":"gneg","density":""},{"#chromosome":"16","arm":"p","band":"13.3","iscn_start":"0","iscn_stop":"410","bp_start":"1","bp_stop":"7800000","stain":"gneg","density":""},{"#chromosome":"16","arm":"p","band":"13.2","iscn_start":"410","iscn_stop":"638","bp_start":"7800001","bp_stop":"10400000","stain":"gpos","density":"50"},{"#chromosome":"16","arm":"p","band":"13.1","iscn_start":"638","iscn_stop":"1012","bp_start":"10400001","bp_stop":"16700000","stain":"gneg","density":""},{"#chromosome":"16","arm":"p","band":"12","iscn_start":"1012","iscn_stop":"1522","bp_start":"16700001","bp_stop":"28500000","stain":"gpos","density":"100"},{"#chromosome":"16","arm":"p","band":"11.2","iscn_start":"1522","iscn_stop":"2060","bp_start":"28500001","bp_stop":"35300000","stain":"gneg","density":""},{"#chromosome":"16","arm":"p","band":"11.1","iscn_start":"2060","iscn_stop":"2187","bp_start":"35300001","bp_stop":"36800000","stain":"acen","density":""},{"#chromosome":"16","arm":"q","band":"11.1","iscn_start":"2187","iscn_stop":"2310","bp_start":"36800001","bp_stop":"38400000","stain":"acen","density":""},{"#chromosome":"16","arm":"q","band":"11.2","iscn_start":"2310","iscn_stop":"2811","bp_start":"38400001","bp_stop":"47000000","stain":"gvar","density":""},{"#chromosome":"16","arm":"q","band":"12.1","iscn_start":"2811","iscn_stop":"2969","bp_start":"47000001","bp_stop":"52600000","stain":"gneg","density":""},{"#chromosome":"16","arm":"q","band":"12.2","iscn_start":"2969","iscn_stop":"3118","bp_start":"52600001","bp_stop":"56000000","stain":"gpos","density":"50"},{"#chromosome":"16","arm":"q","band":"13","iscn_start":"3118","iscn_stop":"3364","bp_start":"56000001","bp_stop":"57300000","stain":"gneg","density":""},{"#chromosome":"16","arm":"q","band":"21","iscn_start":"3364","iscn_stop":"3750","bp_start":"57300001","bp_stop":"66600000","stain":"gpos","density":"100"},{"#chromosome":"16","arm":"q","band":"22","iscn_start":"3750","iscn_stop":"4189","bp_start":"66600001","bp_stop":"74100000","stain":"gneg","density":""},{"#chromosome":"16","arm":"q","band":"23","iscn_start":"4189","iscn_stop":"4655","bp_start":"74100001","bp_stop":"84100000","stain":"gpos","density":"100"},{"#chromosome":"16","arm":"q","band":"24","iscn_start":"4655","iscn_stop":"5120","bp_start":"84100001","bp_stop":"90338345","stain":"gneg","density":""},{"#chromosome":"17","arm":"p","band":"13","iscn_start":"0","iscn_stop":"602","bp_start":"1","bp_stop":"10800000","stain":"gneg","density":""},{"#chromosome":"17","arm":"p","band":"12","iscn_start":"602","iscn_stop":"1051","bp_start":"10800001","bp_stop":"16100000","stain":"gpos","density":"75"},{"#chromosome":"17","arm":"p","band":"11.2","iscn_start":"1051","iscn_stop":"1492","bp_start":"16100001","bp_stop":"22700000","stain":"gneg","density":""},{"#chromosome":"17","arm":"p","band":"11.1","iscn_start":"1492","iscn_stop":"1618","bp_start":"22700001","bp_stop":"25100000","stain":"acen","density":""},{"#chromosome":"17","arm":"q","band":"11.1","iscn_start":"1618","iscn_stop":"1762","bp_start":"25100001","bp_stop":"27400000","stain":"acen","density":""},{"#chromosome":"17","arm":"q","band":"11.2","iscn_start":"1762","iscn_stop":"2050","bp_start":"27400001",
                "bp_stop":"33500000","stain":"gneg","density":""},{"#chromosome":"17","arm":"q","band":"12","iscn_start":"2050","iscn_stop":"2383","bp_start":"33500001","bp_stop":"39800000","stain":"gpos","density":"50"},{"#chromosome":"17","arm":"q","band":"21.1","iscn_start":"2383","iscn_stop":"2554","bp_start":"39800001","bp_stop":"40200000","stain":"gneg","density":""},{"#chromosome":"17","arm":"q","band":"21.2","iscn_start":"2554","iscn_stop":"2669","bp_start":"40200001","bp_stop":"42800000","stain":"gpos","density":"25"},{"#chromosome":"17","arm":"q","band":"21.3","iscn_start":"2669","iscn_stop":"3149","bp_start":"42800001","bp_stop":"52100000","stain":"gneg","density":""},{"#chromosome":"17","arm":"q","band":"22","iscn_start":"3149","iscn_stop":"3680","bp_start":"52100001","bp_stop":"59500000","stain":"gpos","density":"75"},{"#chromosome":"17","arm":"q","band":"23","iscn_start":"3680","iscn_stop":"3950","bp_start":"59500001","bp_stop":"64600000","stain":"gneg","density":""},{"#chromosome":"17","arm":"q","band":"24","iscn_start":"3950","iscn_stop":"4441","bp_start":"64600001","bp_stop":"72900000","stain":"gpos","density":"100"},{"#chromosome":"17","arm":"q","band":"25","iscn_start":"4441","iscn_stop":"4950","bp_start":"72900001","bp_stop":"83257441","stain":"gneg","density":""},{"#chromosome":"18","arm":"p","band":"11.32","iscn_start":"0","iscn_stop":"77","bp_start":"1","bp_stop":"2900000","stain":"gneg","density":""},{"#chromosome":"18","arm":"p","band":"11.31","iscn_start":"77","iscn_stop":"208","bp_start":"2900001","bp_stop":"7200000","stain":"gpos","density":"50"},{"#chromosome":"18","arm":"p","band":"11.2","iscn_start":"623","iscn_stop":"1190","bp_start":"7200001","bp_stop":"15400000","stain":"gneg","density":""},{"#chromosome":"18","arm":"p","band":"11.1","iscn_start":"1190","iscn_stop":"1301","bp_start":"15400001","bp_stop":"18500000","stain":"acen","density":""},{"#chromosome":"18","arm":"q","band":"11.1","iscn_start":"1301","iscn_stop":"1433","bp_start":"18500001","bp_stop":"21500000","stain":"acen","density":""},{"#chromosome":"18","arm":"q","band":"11.2","iscn_start":"1433","iscn_stop":"1862","bp_start":"21500001","bp_stop":"27500000","stain":"gneg","density":""},{"#chromosome":"18","arm":"q","band":"12.1","iscn_start":"1862","iscn_stop":"2223","bp_start":"27500001","bp_stop":"35100000","stain":"gpos","density":"100"},{"#chromosome":"18","arm":"q","band":"12.2","iscn_start":"2223","iscn_stop":"2419","bp_start":"35100001","bp_stop":"39500000","stain":"gneg","density":""},{"#chromosome":"18","arm":"q","band":"12.3","iscn_start":"2419","iscn_stop":"2721","bp_start":"39500001","bp_stop":"45900000","stain":"gpos","density":"75"},{"#chromosome":"18","arm":"q","band":"21.1","iscn_start":"2721","iscn_stop":"3010","bp_start":"45900001","bp_stop":"50700000","stain":"gneg","density":""},{"#chromosome":"18","arm":"q","band":"21.2","iscn_start":"3010","iscn_stop":"3183","bp_start":"50700001","bp_stop":"56200000","stain":"gpos","density":"75"},{"#chromosome":"18","arm":"q","band":"21.3","iscn_start":"3183","iscn_stop":"3449","bp_start":"56200001","bp_stop":"63900000","stain":"gneg","density":""},{"#chromosome":"18","arm":"q","band":"22","iscn_start":"3449","iscn_stop":"4229","bp_start":"63900001","bp_stop":"75400000","stain":"gpos","density":"100"},{"#chromosome":"18","arm":"q","band":"23","iscn_start":"4229","iscn_stop":"4650","bp_start":"75400001","bp_stop":"80373285","stain":"gneg","density":""},{"#chromosome":"19","arm":"p","band":"13.3","iscn_start":"0","iscn_stop":"668","bp_start":"1","bp_stop":"6900000","stain":"gneg","density":""},{"#chromosome":"19","arm":"p","band":"13.2","iscn_start":"668","iscn_stop":"1069","bp_start":"6900001","bp_stop":"12600000","stain":"gpos","density":"25"},{"#chromosome":"19","arm":"p","band":"13.1","iscn_start":"1069","iscn_stop":"1461","bp_start":"12600001","bp_stop":"19900000","stain":"gneg","density":""},{"#chromosome":"19","arm":"p","band":"12","iscn_start":"1461","iscn_stop":"1746","bp_start":"19900001","bp_stop":"24200000","stain":"gvar","density":""},{"#chromosome":"19","arm":"p","band":"11","iscn_start":"1746","iscn_stop":"1880","bp_start":"24200001","bp_stop":"26200000","stain":"acen","density":""},{"#chromosome":"19","arm":"q","band":"11","iscn_start":"1880","iscn_stop":"2019","bp_start":"26200001","bp_stop":"28100000","stain":"acen","density":""},{"#chromosome":"19","arm":"q","band":"12","iscn_start":"2019","iscn_stop":"2342","bp_start":"28100001","bp_stop":"31900000","stain":"gvar","density":""},{"#chromosome":"19","arm":"q","band":"13.1","iscn_start":"2342","iscn_stop":"2943","bp_start":"31900001","bp_stop":"38200000","stain":"gneg","density":""},{"#chromosome":"19","arm":"q","band":"13.2","iscn_start":"2943","iscn_stop":"3344","bp_start":"38200001","bp_stop":"42900000","stain":"gpos","density":"100"},{"#chromosome":"19","arm":"q","band":"13.3","iscn_start":"3344","iscn_stop":"3710","bp_start":"42900001","bp_stop":"50900000","stain":"gneg","density":""},{"#chromosome":"19","arm":"q","band":"13.4","iscn_start":"3710","iscn_stop":"4120","bp_start":"50900001","bp_stop":"58617616","stain":"gpos","density":"100"},{"#chromosome":"20","arm":"p","band":"13","iscn_start":"0","iscn_stop":"471","bp_start":"1","bp_stop":"5100000","stain":"gneg","density":""},{"#chromosome":"20","arm":"p","band":"12","iscn_start":"471","iscn_stop":"1005","bp_start":"5100001","bp_stop":"17900000","stain":"gpos","density":"100"},{"#chromosome":"20","arm":"p","band":"11.2","iscn_start":"1005","iscn_stop":"1593","bp_start":"17900001","bp_stop":"25700000","stain":"gneg","density":""},{"#chromosome":"20","arm":"p","band":"11.1","iscn_start":"1593","iscn_stop":"1729","bp_start":"25700001","bp_stop":"28100000","stain":"acen","density":""},{"#chromosome":"20","arm":"q","band":"11.1","iscn_start":"1729","iscn_stop":"1870","bp_start":"28100001","bp_stop":"30400000","stain":"acen","density":""},{"#chromosome":"20","arm":"q","band":"11.2","iscn_start":"1870","iscn_stop":"2362","bp_start":"30400001","bp_stop":"39000000","stain":"gneg","density":""},{"#chromosome":"20","arm":"q","band":"12","iscn_start":"2362","iscn_stop":"2732","bp_start":"39000001","bp_stop":"43100000","stain":"gpos","density":"75"},{"#chromosome":"20","arm":"q","band":"13.1","iscn_start":"2732","iscn_stop":"3075","bp_start":"43100001","bp_stop":"51200000","stain":"gneg","density":""},{"#chromosome":"20","arm":"q","band":"13.2","iscn_start":"3075","iscn_stop":"3409","bp_start":"51200001","bp_stop":"56400000","stain":"gpos","density":"75"},{"#chromosome":"20","arm":"q","band":"13.3","iscn_start":"3409","iscn_stop":"3770","bp_start":"56400001","bp_stop":"64444167","stain":"gneg","density":""},{"#chromosome":"21","arm":"p","band":"13","iscn_start":"0","iscn_stop":"301","bp_start":"1","bp_stop":"3100000","stain":"gvar","density":""},{"#chromosome":"21","arm":"p","band":"12","iscn_start":"301","iscn_stop":"601","bp_start":"3100001","bp_stop":"7000000","stain":"stalk","density":""},{"#chromosome":"21","arm":"p","band":"11.2","iscn_start":"601","iscn_stop":"1002","bp_start":"7000001","bp_stop":"10900000","stain":"gvar","density":""},{"#chromosome":"21","arm":"p","band":"11.1","iscn_start":"1002","iscn_stop":"1203","bp_start":"10900001","bp_stop":"12000000","stain":"acen","density":""},{"#chromosome":"21","arm":"q","band":"11.1","iscn_start":"1203","iscn_stop":"1341","bp_start":"12000001","bp_stop":"13000000","stain":"acen","density":""},{"#chromosome":"21","arm":"q","band":"11.2","iscn_start":"1341","iscn_stop":"1514","bp_start":"13000001","bp_stop":"15000000","stain":"gneg","density":""},{"#chromosome":"21","arm":"q","band":"21","iscn_start":"1514","iscn_stop":"2204","bp_start":"15000001","bp_stop":"30200000","stain":"gpos","density":"100"},{"#chromosome":"21","arm":"q","band":"22.1","iscn_start":"2204","iscn_stop":"2631","bp_start":"30200001","bp_stop":"38300000","stain":"gneg","density":""},{"#chromosome":"21","arm":"q","band":"22.2","iscn_start":"2631","iscn_stop":"2808","bp_start":"38300001","bp_stop":"41200000","stain":"gpos","density":"50"},{"#chromosome":"21","arm":"q","band":"22.3","iscn_start":"2808","iscn_stop":"3200","bp_start":"41200001","bp_stop":"46709983","stain":"gneg","density":""},{"#chromosome":"22","arm":"p","band":"13","iscn_start":"0","iscn_stop":"308","bp_start":"1","bp_stop":"4300000","stain":"gvar","density":""},{"#chromosome":"22","arm":"p","band":"12","iscn_start":"308","iscn_stop":"604","bp_start":"4300001","bp_stop":"9400000","stain":"stalk","density":""},{"#chromosome":"22","arm":"p","band":"11.2","iscn_start":"604","iscn_stop":"1093","bp_start":"9400001","bp_stop":"13700000","stain":"gvar","density":""},{"#chromosome":"22","arm":"p","band":"11.1","iscn_start":"1093","iscn_stop":"1232","bp_start":"13700001","bp_stop":"15000000","stain":"acen","density":""},{"#chromosome":"22","arm":"q","band":"11.1","iscn_start":"1232","iscn_stop":"1371","bp_start":"15000001","bp_stop":"17400000","stain":"acen","density":""},{"#chromosome":"22","arm":"q","band":"11.2","iscn_start":"1371","iscn_stop":"1887","bp_start":"17400001","bp_stop":"25500000","stain":"gneg","density":""},{"#chromosome":"22","arm":"q","band":"12.1","iscn_start":"1887","iscn_stop":"2000","bp_start":"25500001","bp_stop":"29200000","stain":"gpos","density":"50"},{"#chromosome":"22","arm":"q","band":"12.2","iscn_start":"2000","iscn_stop":"2123","bp_start":"29200001","bp_stop":"31800000","stain":"gneg","density":""},{"#chromosome":"22","arm":"q","band":"12.3","iscn_start":"2123","iscn_stop":"2287","bp_start":"31800001","bp_stop":"37200000","stain":"gpos","density":"50"},{"#chromosome":"22","arm":"q","band":"13.1","iscn_start":"2287","iscn_stop":"2596","bp_start":"37200001","bp_stop":"40600000","stain":"gneg","density":""},{"#chromosome":"22","arm":"q","band":"13.2","iscn_start":"2596","iscn_stop":"2782","bp_start":"40600001","bp_stop":"43800000","stain":"gpos","density":"50"},{"#chromosome":"22","arm":"q","band":"13.3","iscn_start":"2782","iscn_stop":"3400",
                "bp_start":"43800001","bp_stop":"50818468","stain":"gneg","density":""},{"#chromosome":"X","arm":"p","band":"22.3","iscn_start":"0","iscn_stop":"428","bp_start":"1","bp_stop":"9600000","stain":"gneg","density":""},{"#chromosome":"X","arm":"p","band":"22.2","iscn_start":"428","iscn_stop":"642","bp_start":"9600001","bp_stop":"17400000","stain":"gpos","density":"100"},{"#chromosome":"X","arm":"p","band":"22.1","iscn_start":"642","iscn_stop":"1131","bp_start":"17400001","bp_stop":"24900000","stain":"gneg","density":""},{"#chromosome":"X","arm":"p","band":"21.3","iscn_start":"1131","iscn_stop":"1454","bp_start":"24900001","bp_stop":"29300000","stain":"gpos","density":"100"},{"#chromosome":"X","arm":"p","band":"21.2","iscn_start":"1454","iscn_stop":"1575","bp_start":"29300001","bp_stop":"31500000","stain":"gneg","density":""},{"#chromosome":"X","arm":"p","band":"21.1","iscn_start":"1575","iscn_stop":"1977","bp_start":"31500001","bp_stop":"37800000","stain":"gpos","density":"100"},{"#chromosome":"X","arm":"p","band":"11.4","iscn_start":"1977","iscn_stop":"2252","bp_start":"37800001","bp_stop":"42500000","stain":"gneg","density":""},{"#chromosome":"X","arm":"p","band":"11.3","iscn_start":"2252","iscn_stop":"2446","bp_start":"42500001","bp_stop":"47600000","stain":"gpos","density":"75"},{"#chromosome":"X","arm":"p","band":"11.23","iscn_start":"2729","iscn_stop":"2912","bp_start":"47600001","bp_stop":"50100000","stain":"gneg","density":""},{"#chromosome":"X","arm":"p","band":"11.22","iscn_start":"2912","iscn_stop":"3014","bp_start":"50100001","bp_stop":"54800000","stain":"gpos","density":"25"},{"#chromosome":"X","arm":"p","band":"11.21","iscn_start":"3014","iscn_stop":"3057","bp_start":"54800001","bp_stop":"58100000","stain":"gneg","density":""},{"#chromosome":"X","arm":"p","band":"11.1","iscn_start":"3108","iscn_stop":"3241","bp_start":"58100001","bp_stop":"61000000","stain":"acen","density":""},{"#chromosome":"X","arm":"q","band":"11","iscn_start":"3241","iscn_stop":"3341","bp_start":"61000001","bp_stop":"65400000","stain":"acen","density":""},{"#chromosome":"X","arm":"q","band":"12","iscn_start":"3341","iscn_stop":"3672","bp_start":"65400001","bp_stop":"68500000","stain":"gpos","density":"50"},{"#chromosome":"X","arm":"q","band":"13","iscn_start":"3672","iscn_stop":"4405","bp_start":"68500001","bp_stop":"76800000","stain":"gneg","density":""},{"#chromosome":"X","arm":"q","band":"21.1","iscn_start":"4405","iscn_stop":"4740","bp_start":"76800001","bp_stop":"85400000","stain":"gpos","density":"100"},{"#chromosome":"X","arm":"q","band":"21.2","iscn_start":"4740","iscn_stop":"4830","bp_start":"85400001","bp_stop":"87000000","stain":"gneg","density":""},{"#chromosome":"X","arm":"q","band":"21.3","iscn_start":"4830","iscn_stop":"5559","bp_start":"87000001","bp_stop":"99100000","stain":"gpos","density":"100"},{"#chromosome":"X","arm":"q","band":"22.1","iscn_start":"5559","iscn_stop":"5730","bp_start":"99100001","bp_stop":"103300000","stain":"gneg","density":""},{"#chromosome":"X","arm":"q","band":"22.2","iscn_start":"5730","iscn_stop":"5820","bp_start":"103300001","bp_stop":"104500000","stain":"gpos","density":"50"},{"#chromosome":"X","arm":"q","band":"22.3","iscn_start":"5820","iscn_stop":"5951","bp_start":"104500001","bp_stop":"109400000","stain":"gneg","density":""},{"#chromosome":"X","arm":"q","band":"23","iscn_start":"5951","iscn_stop":"6352","bp_start":"109400001","bp_stop":"117400000","stain":"gpos","density":"75"},{"#chromosome":"X","arm":"q","band":"24","iscn_start":"6352","iscn_stop":"6653","bp_start":"117400001","bp_stop":"121800000","stain":"gneg","density":""},{"#chromosome":"X","arm":"q","band":"25","iscn_start":"6653","iscn_stop":"7195","bp_start":"121800001","bp_stop":"129500000","stain":"gpos","density":"100"},{"#chromosome":"X","arm":"q","band":"26","iscn_start":"7195","iscn_stop":"7647","bp_start":"129500001","bp_stop":"138900000","stain":"gneg","density":""},{"#chromosome":"X","arm":"q","band":"27","iscn_start":"7647","iscn_stop":"8168","bp_start":"138900001","bp_stop":"148000000","stain":"gpos","density":"100"},{"#chromosome":"X","arm":"q","band":"28","iscn_start":"8168","iscn_stop":"8610","bp_start":"148000001","bp_stop":"156040895","stain":"gneg","density":""},{"#chromosome":"Y","arm":"p","band":"11.3","iscn_start":"0","iscn_stop":"286","bp_start":"1","bp_stop":"600000","stain":"gpos","density":"100"},{"#chromosome":"Y","arm":"p","band":"11.2","iscn_start":"286","iscn_stop":"751","bp_start":"600001","bp_stop":"10300000","stain":"gneg","density":""},{"#chromosome":"Y","arm":"p","band":"11.1","iscn_start":"751","iscn_stop":"883","bp_start":"10300001","bp_stop":"10400000","stain":"acen","density":""},{"#chromosome":"Y","arm":"q","band":"11.1","iscn_start":"883","iscn_stop":"1002","bp_start":"10400001","bp_stop":"10600000","stain":"acen","density":""},{"#chromosome":"Y","arm":"q","band":"11.21","iscn_start":"1143","iscn_stop":"1268","bp_start":"10600001","bp_stop":"12400000","stain":"gneg","density":""},{"#chromosome":"Y","arm":"q","band":"11.221","iscn_start":"1268","iscn_stop":"1568","bp_start":"12400001","bp_stop":"17100000","stain":"gpos","density":"50"},{"#chromosome":"Y","arm":"q","band":"11.222","iscn_start":"1568","iscn_stop":"1727","bp_start":"17100001","bp_stop":"19600000","stain":"gneg","density":""},{"#chromosome":"Y","arm":"q","band":"11.223","iscn_start":"1727","iscn_stop":"1992","bp_start":"19600001","bp_stop":"23800000","stain":"gpos","density":"50"},{"#chromosome":"Y","arm":"q","band":"11.23","iscn_start":"1992","iscn_stop":"2169","bp_start":"23800001","bp_stop":"26600000","stain":"gneg","density":""},{"#chromosome":"Y","arm":"q","band":"12","iscn_start":"2169","iscn_stop":"3650","bp_start":"26600001","bp_stop":"57227415","stain":"gvar","density":""}];

    var filteredResults = filterByChromosome(data, chr);
    cb(filteredResults);

    // loadData(baseDir + fileName, resolution, function (d) {

    //   if(d) {
    //     var filteredResults = filterByChromosome(d, chr);
    //     cb(filteredResults);
    //   }

    // });
  }

  function filterByChromosome(data, chr) {
    var newAry = [];
    for(var i = 0; i < data.length; i++) {
      if (data[i]['#chromosome'] === chr) {
        newAry.push(data[i]);
      }
    }
    return newAry;
  }

  cyto_chr.modelLoader = {
    load: getChromosomeData,
    setDataDir: function(d) {baseDir = d;},
    getDataDir: function() {return baseDir;}
  };

})(cyto_chr || {}, d3);

(function(cyto_chr, d3) {

  var Selector = function(closecb) {
    this._brush = d3.svg.brush();
    this.dispatch = d3.dispatch('change', 'changeend', 'selectordelete', "selectorhover", "selectorunhover");
    this._x = 0;
    this._y = 0;
    this._extent = [0,0];
    this._height = 0;
    this._closecb = closecb;
  };

  Selector.prototype.test = function(e) {
    var self = this;
    return cyto_chr.InitGetterSetter.call(this, "_test", e, function(){
      self._another = "_that";
    });
  };

  Selector.prototype.extent = function (a) {
    var self = this;
    if(typeof a === "undefined") {
      return self._brush.extent();
    }

    return cyto_chr.InitGetterSetter.call(this, "_extent", a, function(){
      self._brush.extent(a);
    });

  };

  Selector.prototype.xscale = function(a) {
    var self = this;
    return cyto_chr.InitGetterSetter.call(this, "_xscale", a, function(){
      self._brush.x(a);
    });
  };

  Selector.prototype.target = function(a) {
    return cyto_chr.InitGetterSetter.call(this, '_target', a);
  };

  Selector.prototype.height = function(a) {
    return cyto_chr.InitGetterSetter.call(this, '_height', a);
  };

  Selector.prototype.x = function(a) {
    return cyto_chr.InitGetterSetter.call(this, '_x', a);
  };

  Selector.prototype.y = function(a) {
    return cyto_chr.InitGetterSetter.call(this, '_y', a);
  };

  Selector.prototype.render = function() {
    var self = this;

    this.selector = this._target.append('g')
      .classed("extRect", true)
      .attr('transform', 'translate(' + this._x + ',' + this._y + ')')
      .call(this._brush);
    
    this.selector.selectAll(".resize").remove();

    this.selector.selectAll('rect')
      .attr('height', this._height);

    this.selector.select('.background').remove();

    var e = this.selector.select('.extent')
      .style('fill', 'steelblue')
      .style('opacity', '0.5');

    this.selector.selectAll("rect").classed("extent", false).style("cursor", "default");

    this.selector.selectAll("rect")
      .on('mouseover', function() {
        var ext = self._brush.extent();
        self.dispatch.selectorhover(ext);
      })
      .on("mouseout", function() {
        var ext = self._brush.extent();
        self.dispatch.selectorunhover(ext);
      });

    // var cbg_xpos = this._xscale(this._extent[1]) + cyto_chr.margin.left;
    // var cbg_ypos = cyto_chr.margin.top - 3;
    // var cbg = this._target.append('g');
    // cbg.append('title').text('remove');

    // this.deleteButton = cbg.append('circle')
    //   .attr('cx', cbg_xpos)
    //   .attr('cy', cbg_ypos)
    //   .attr('r', 5)
    //   .attr('fill', 'red')
    //   .on('mouseover', function() {
    //     d3.select(this)
    //       .style('cursor', 'pointer');
    //   })
    //   .on('mouseout', function(){
    //     d3.select(this)
    //       .style('cursor', 'default');
    //   })
    //   .on('click', function() {
    //     self.remove();
    //   });

    // this._brush.on('brush', function() {
    //   self.updateXButton();
    //   var ext = self._brush.extent();
    //   // self.dispatch.change(ext);
    // });

    // this._brush.on('brushend', function(d) {
    //   var ext = self._brush.extent();
    //   self.dispatch.changeend(ext);
    // });
    return this;
  };

  Selector.prototype.remove = function() {
    this.selector.remove();
    // this.deleteButton.remove();
    this._closecb(this);
    return this;
  };

  // Selector.prototype.updateXButton = function() {
  //   var e = this._brush.extent();
  //   var new_xpos = this._xscale(e[1]) + cyto_chr.margin.left;
  //   // this.deleteButton.attr('cx', new_xpos);
  // };

  // Selector.prototype.move = function(start, stop) {
  //   this._brush.extent([start, stop]);
  //   this.selector.call(this._brush);
  //   this.updateXButton();
  // };

  cyto_chr.selector = function(cb){
    return new Selector(cb);
  };

})(cyto_chr || {}, d3);

(function(cyto_chr, d3) {

  cyto_chr.initPattern = function () {
    var pg = this.append('pattern')
      .attr('id', 'acen-fill')
      .attr('patternUnits', 'userSpaceOnUse')
      .attr('x', '0')
      .attr('y', '0')
      .attr('width', '10')
      .attr('height', '10')
      .append('g')
      .style({
        "fill": "none",
        "stroke": "#708090",
        "stroke-width": "2"
      });

    pg.append('path')
      .attr('d', "M0,0 l10,10");
    pg.append('path')
      .attr('d','M10,0 l-10,10');
  };

  cyto_chr.roundedRect = function (x, y, w, h, r, tl, tr, bl, br) {
      var retval;
      retval = "M" + (x + r) + "," + y;
      retval += "h" + (w - 2 * r);
      if (tr) {
        retval += "a" + r + "," + r + " 0 0 1 " + r + "," + r;
      } else {
        retval += "h" + r;
        retval += "v" + r;
      }
      retval += "v" + (h - 2 * r);
      if (br) {
        retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + r;
      } else {
        retval += "v" + r;
        retval += "h" + -r;
      }
      retval += "h" + (2 * r - w);
      if (bl) {
        retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + -r;
      } else {
        retval += "h" + -r;
        retval += "v" + -r;
      }
      retval += "v" + (2 * r - h);
      if (tl) {
        retval += "a" + r + "," + r + " 0 0 1 " + r + "," + -r;
      } else {
        retval += "v" + -r;
        retval += "h" + r;
      }
      retval += "z";
      return retval;
    };

  cyto_chr.getStainColour = function (bandtype, density) {

    if(bandtype === "gpos") {
      if(density === "" || density === null) { return "#000000"; }

      switch(density) {
        case "100":
          return "#000000";
        case "75":
          return "#666666";
        case "50":
          return "#999999";
        case "25":
          return "#d9d9d9";
      }
    }

    if (bandtype === "gneg") {
      return "#ffffff";
    }

    if (bandtype === "acen") {
      //return "url(#acen-fill)";
      return "#708090";
    }

    if (bandtype === "gvar") {
      return "#e0e0e0";
    }

    if(bandtype === "stalk") {
      return "#708090";
    }

    return "green";
  };

  cyto_chr.setOption = function (userOption, def) {
      if(typeof userOption !== "undefined") {
        return userOption;
      } else {
        return def;
      }
    };

  cyto_chr.InitGetterSetter = function(prop, arg, cb) {
    if(typeof arg !== 'undefined') {
      this[prop] =  arg;
      if(typeof cb === 'function') {
        cb();
      }
      return this;
    } else {
      return this[prop];
    }
  };

})(cyto_chr || {}, d3);
(function(cyto_chr, d3){
  if (typeof angular === 'undefined') {
    return;
  }

  cyto_chr.modelLoader.setDataDir('./node_modules/cyto-chromosome-vis/data/');

  angular.module('cyto-chromosome-vis',[])
    .directive('cytochromosome',[function() {
      function link(scope, element, attr) {

        attr.resolution = cyto_chr.setOption(attr.resolution, "550");
        attr.width = cyto_chr.setOption(attr.width, 1000);
        attr.segment = cyto_chr.setOption(attr.segment, "1");
        attr.useRelative = cyto_chr.setOption(attr.useRelative, true);
        attr.showAxis = cyto_chr.setOption(attr.showAxis, false);

        cyto_chr.chromosome()
          .target(d3.select(element[0]))
          .width(attr.width)
          .segment(attr.segment)
          .resolution(attr.resolution)
          .useRelative(attr.useRelative == "true")
          .showAxis(attr.showAxis == "true")
          .render();
      }

      return {
        link: link,
        restrict: 'E'
      };
    }])
    .provider('cytochromosome', function(){
      this.build = function() {
        return cyto_chr.chromosome();
      };

      this.setDataDir = function(d) {
        cyto_chr.modelLoader.setDataDir(d);
      };

      this.$get = function() {
        return this;
      };
    });

})(cyto_chr || {}, d3);
}());</script>

<dom-module id="epiviz-ideogram-track">
    <template>
        <style>
            :host {
                display: inline-block;
                min-width: 250px;
            }

            #chart {
                position: relative;
            }
        </style>

        <div id="chart">
            <div id="{{plotId}}"></div>
        </div>

    </template>

    <script>
        // Extend Polymer.Element base class
        class EpivizIdeogramTrack extends Polymer.Element {

            static get is() { return 'epiviz-ideogram-track'; }

            static get properties() {
                return {

                    /**
                    * Chromosome location.
                    * Default Location Attribute to set to all the children chart elements.
                    *
                    * @example: chr1
                    *
                    * @type {string}
                    */
                    chromosome: {
                        type: String,
                        notify: true
                    },

                    /**
                    * Chromosome start. 
                    * Default Chromosome start value to use. (defaults to 0).
                    *
                    * @type {number}
                    */
                    start: {
                        type: Number,
                        notify: true
                    },

                    /**
                    * Chromosome end. 
                    * Default Chromosome end value to use. (defaults to the `<chr>` length).
                    *
                    * @type {number}
                    */
                    end: {
                        type: Number,
                        notify: true
                    },

                    /**
                    * Unique plot ID for the chart
                    *
                    * @type {string}
                    */
                    plotId: {
                        type: String,
                        reflectToAttribute: true,
                        notify: true
                    },

                    /**
                    * Computed Range from `<chr>`, `<start>` & `<end>`. 
                    *
                    * @type {Object<epiviz.datatypes.GenomicRange>}
                    */
                    range: {
                        type: Object,
                        notify: true
                    },

                    /**
                    * Current gene in range (set automatically)
                    *
                    * @type {string}
                    */
                    geneInRange: {
                        type: String,
                        notify: true
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                    '_rangeChanged(chromosome.*, start.*, end.*)'
                ]
            }

            constructor() {
                super();
            }

            connectedCallback() {
                super.connectedCallback();
            }

            disconnectedCallback() {
                super.connectedCallback();
            }

            ready() {
                super.ready();
                var self = this;

                self.plotId = self.plotId || self._generatePlotId();
                var parent = self.parentNode;

                if (parent.nodeName === "IRON-COLLAPSE") {
                    parent = parent.parentNode;
                }

                parent.addEventListener('hoverAllCharts', function (e) {
                    self.chart.hover(e.detail.data);
                }.bind(self));

                parent.addEventListener('unHoverAllCharts', function (e) {
                    self.chart.unHover();
                }.bind(self));

                self.chromosomeFactory = cyto_chr;
                self._rangeChanged();
                self.chartObject = new epiviz.ui.charts.ChartObject(self.plotId, self.start, self.end, undefined, undefined, undefined, undefined, undefined);
            }

            /**
             * Draws the chart.
             */
            _draw() {

                var self = this;

                $(self.shadowRoot.querySelector("#" + self.plotId)).empty();

                self.segment = self.chromosome.replace("chr", "");

                self.chart = self.chromosomeFactory.chromosome()
                    .segment(self.segment)
                    // .resolution("850")
                    .useRelative(false)
                    .showAxis(true)
                    .target(self.shadowRoot.querySelector("#" + self.plotId))
                    // .height(100)
                    .width(300)
                    .render();

                // TODO: use callback instead after the chart is rendered
                setTimeout(function () {
                    self.chart.on("selectorhover", function (e) {
                        self.dispatchEvent(new CustomEvent('hover',
                            {
                                detail: {
                                    data: self.chartObject
                                },
                                bubbles: true
                            }
                        )
                        );
                    }.bind(self));

                    self.chart.on("selectorunhover", function (e) {
                        self.dispatchEvent(new CustomEvent('hover',
                            {
                                detail: {
                                    data: null
                                },
                                bubbles: true
                            }
                        )
                        );
                    }.bind(self));

                    self.chart.getSVGTarget().append('text')
                        .text("chr: " + self.segment)
                        .attr('x', self.chart.getSVGTarget().attr("width") - 285)
                        .attr('y', self.chart.getSVGTarget().attr("height") * 1 / 3)
                        .attr('text-anchor', 'left')
                        .style('font', '12px sans-serif');

                    self.chart.getSVGTarget().append('text')
                        .text("Gene: " + self.geneInRange)
                        .attr('x', self.chart.getSVGTarget().attr("width") - 240)
                        .attr('y', self.chart.getSVGTarget().attr("height") * 1 / 3)
                        .attr('text-anchor', 'left')
                        .style('font', '12px sans-serif');

                    self.chart.newSelector(self.start, self.end);
                    self.chartObject = new epiviz.ui.charts.ChartObject(self.plotId, self.start, self.end, undefined, undefined, undefined, undefined, undefined, self.chromosome);
                }, 1000);
            }

            /**
             * Generates a unique chart ID
             * 
             * @return {string}
             */
            _generatePlotId() {
                var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
                var result = '';
                var size = 7;

                for (var i = 0; i < size; ++i) {
                    result += chars[Math.round(Math.random() * (chars.length - 1))];
                }

                return 'epiviz-' + result;
            }

            /**
             * ChartLocation/RangeChange event handler.
             */
            _rangeChanged() {

                if (!(this.chromosome === undefined || this.start === undefined || this.end === undefined)) {
                    if (this.plotId) {
                        this._draw();
                    }
                }
            }

            /**
             * Hover event handler.
             *
             * @param {object} data data object currently hovered.
             */
            hover(data) {
                this.chart.doHover(data);
            }

            /**
             * unHover event handler.
             */
            unHover() {
                this.chart.doUnhover();
            }
        };

        customElements.define(EpivizIdeogramTrack.is, EpivizIdeogramTrack);
    </script>
</dom-module><script>
    /**
     * `ChartDraggableBehavior` object initializes draggable and resizable behaviors for charts. 
     * All elements that should be draggable should include this behavior.
     *
     * @polymerBehavior
    **/

    EpivizChartDraggableBehavior = function (superClass) {
        return class extends superClass {
            constructor() {
                super();
            }

            static get properties() {
                return {};
            }

            _initializeSortable() {
                $(this).sortable({
                    tolerance: "pointer",
                    cursorAt: { left: 10, top: 10 },
                    items: "> :not(epiviz-navigation)",
                    handle: ".dragHandle",
                });
            }
        }
    }
</script><dom-module id="epiviz-environment">
    <template>
        <style>
            :host {
                display: inline-block;
                /* padding: 15px; */
                width: 99%;
                /* width: auto; */
                /* height: 100%; */
                margin: 10px;
                resize: both;
                border-radius: 5px;
                /* padding: 4px; */
                /* margin:5px; */
                transition: width 0.01s, height 0.01s;
                @apply(--shadow-elevation-2dp);
            }


            #chartSettingsContainer {
                position: absolute;
                top: 0;
                right: 0;
                border: 1px solid #000;
                border-radius: 2px;
                margin: 1px;
            }

            .paper-header {
                background-color: var(--google-grey-300);
                padding-left: 5px;
            }

            #logo {
                vertical-align: middle;
            }


            paper-menu-button {
                float: right;
            }

            iron-label {
                color: #4285f4;
                font-weight: bold;
                font-size: 16px;
            }

            .isempty {
                position: relative;
                top: 50%;
                left: 50%;
                transform: translateX(-50%) translateY(-50%);
            }

            #header {
                height: 50px;
                /* linear-gradient(white, #e0e2e2) */
            }

            .content {
                min-height: 500px;
                padding: 5px;
                /* min height should be height of largest plot */
                display: grid;
                grid-template-columns: repeat(6, 1fr);
                grid-auto-rows: minmax(max-content, 100px);
                grid-row-gap: 2px;
                grid-column-gap: 2px;
            }

            .content ::slotted {
                /* min height should be height of largest plot */
                display: grid;
                grid-template-columns: repeat(6, 1fr);
                grid-auto-rows: minmax(max-content, 100px);
                grid-row-gap: 2px;
                grid-column-gap: 2px;
            }

            .content ::slotted(.grid-plot) {
                grid-row: span 1;
                grid-column: span 2;
            }

            .content ::slotted(.grid-track) {
                grid-column: span 6;
                grid-row: span 1;
            }

            .content ::slotted(.grid-navigation) {
                grid-row: span 1;
                grid-column: span 6;
                order: 2;
            }
        </style>

        <div id="header" class="paper-header">
            <iron-label>EPIVIZ ENVIRONMENT</iron-label>
            <a href="http://epiviz.github.io" target="_blank" hidden$="[[noLogo]]">
                <img id="logo" src="epiviz_4_logo_medium.png" alt="Epiviz" width="100" height="21">
            </a>
            <epiviz-add-chart id="addChart"></epiviz-add-chart>
            <paper-menu-button dynamic-align="" vertical-align="bottom" vertical-offset="24">
                <paper-icon-button icon="menu" class="dropdown-trigger"></paper-icon-button>
                <paper-menu class="dropdown-content">
                    <paper-item>Delete</paper-item>
                    <paper-item>Clone</paper-item>
                </paper-menu>
            </paper-menu-button>
        </div>
        <div class="content chartContainer">
            <slot id="chartEnv" name="charts"></slot>
        </div>
    </template>

    <script>
        // Extend Polymer.Element base class
        class EpivizEnvironment extends EpivizChartDraggableBehavior(EpivizChartWorkspaceBehavior(EpivizChartDataBehavior(EpivizChartAddBehavior(Polymer.Element)))) {

            static get is() { return 'epiviz-environment'; }

            static get properties() {
                return {
                    /**
                     * Measurement Object mapped to measurements provided from `<epiviz-data-source>`.
                     *
                     * @type {Object}
                     */
                    measurements: {
                        type: Object,
                        notify: true,
                        reflectToAttribute: true
                    },

                    /**
                     * Chromosome location.
                     * Default Location Attribute to set to all the children chart elements.
                     *
                     * @example: chr1
                     *
                     * @type {string}
                     */
                    chr: {
                        type: String,
                        notify: true,
                        value: "all"
                    },

                    /**
                     * <Optional> Chromosome start. 
                     * Default Chromosome start value to use. (defaults to 0).
                     *
                     * @type {number}
                     */
                    start: {
                        type: Number,
                        notify: true,
                        value: null
                    },

                    /**
                     * <Optional> Chromosome end. 
                     * Default Chromosome end value to use. (defaults to the `<chr>` length).
                     *
                     * @type {number}
                     */
                    end: {
                        type: Number,
                        notify: true,
                        value: null
                    },

                    /**
                     * Computed Range from `<chr>`, `<start>` & `<end>`. 
                     *
                     * @type {Object<epiviz.datatypes.GenomicRange>}
                     */
                    range: {
                        type: Object,
                        notify: true,
                        computed: 'getGenomicRange(chr, start, end)'
                    },

                    /**
                    * Unique plot-id. an id will be assigned if not set
                    *
                    * @type {string}
                    */
                    plotId: {
                        type: String,
                        reflectToAttribute: true,
                        notify: true
                    },

                    /**
                     * Intiliazes an environment with `<epiviz-navigation>` elements.
                     * 
                     * Can be one of these {region: {chr , start, end}} or {gene} 
                     *
                     * @example: [{region: {chr: 'chr11', start: 20000, end: 600000}{, {gene: 'esr1'}]
                     * 
                     * @type {Array[<Object>]}
                     */
                    initializeRegions: {
                        type: Array,
                        value: []
                    },

                    /**
                     * List of regions currently in workspace
                     * 
                     * @type {Array}
                     */
                    navigationRegions: {
                        type: Array,
                        notify: true,
                        value: []
                    },

                    /**
                     * Whether to show/hide the epiviz logo
                     *
                     * @type {boolean}
                     */
                    noLogo: {
                        type: Boolean,
                        notify: true,
                        reflectToAttribute: true,
                        value: false
                    },

                    /**
                     * Whether to use the default layout
                     *
                     * @type {boolean}
                     */
                    useLayout: {
                        type: Boolean,
                        notify: true,
                        reflectToAttribute: true,
                        value: true
                    },

                    /**
                     * Whether the element has no children
                     *
                     * @type {boolean}
                     */
                    _isEmpty: {
                        type: Boolean,
                        notify: true,
                        value: function () {
                            return false;
                        }
                    }
                }
            }

            static get observers() {
                return [
                    /* observer array just like 1.x */
                    // '_rangeChanged(chr.*, start.*, end.*)',
                    '_navigationRegionsChanged(navigationRegions.*)'
                ]
            }

            constructor() {
                super();
                this.addEventListener('navigationRangeUpdate', e => this._updateNavRegion(e));
                this.addEventListener('hover', e => this.onHover(e));
                this.addEventListener('unHover', e => this.onUnhover(e));
                this.addEventListener('select', e => this.onSelect(e));
                this.addEventListener('deSelect', e => this.onDeSelect(e));
                this.addEventListener('dimChanged', e => this.onDimChanged(e));
                this.addEventListener('iron-resize', e => this._onResize(e));
                this.addEventListener('navWorkspaceChanged', e => this._childWorkspaceChanged(e));

                // this.addEventListener('transitionend', e => this._onResize(e));
            }

            connectedCallback() {
                super.connectedCallback();
                var self = this;
                if (self.initializeRegions.length > 0) {
                    for (var i = 0; i < self.initializeRegions.length; i++) {
                        var navElem = document.createElement("epiviz-navigation");
                        navElem.className = "charts";
                        if (Object.keys(self.initializeRegions[i]).indexOf("gene") != -1) {
                            navElem.gene = self.initializeRegions[i].gene;
                        }
                        else {
                            navElem.chr = self.initializeRegions[i].region.chr;
                            navElem.start = self.initializeRegions[i].region.start;
                            navElem.end = self.initializeRegions[i].region.end;
                        }
                        this.shadowRoot.appendChild(navElem);
                    }
                }

                self._observer = new Polymer.FlattenedNodesObserver(this.$.chartEnv, (info) => {
                    info.addedNodes.forEach(function (node) {
                        node._parentContainer = self;
                        if (node.nodeName === "EPIVIZ-NAVIGATION") {
                            self._getElementSeqInfo(node, Polymer.dom(self));
                            self.push("navigationRegions", { "plotId": node.plotId, "range": node.range });
                            let navChildren =
                                Polymer.FlattenedNodesObserver.getFlattenedNodes(node).filter(n => n.nodeType === Node.ELEMENT_NODE)
                            for (var nindex = 0; nindex < navChildren.length; nindex++) {
                                var currentNavChild = navChildren[nindex];
                                currentNavChild._parentContainer = self;
                                currentNavChild.range = node.range;
                                if (!currentNavChild.data) {
                                    self._getElementData(currentNavChild, self, self.measurements);
                                }
                            }
                        }
                        else {
                            if (!node.data && !node.useDefaultDataProvider) {
                                self._getElementData(node, self, self.measurements);
                            }
                        }
                        self.set('_isEmpty', true);
                    });
                    self.saveWorkspace();
                    // this._processRemovedNodes(info.removedNodes);
                });

                self._initializeSortable();
                self.$.addChart._parentContainer = self;

                self._createPropertyObserver('_rangeChanged(chr.*, start.*, end.*)', false);
            }

            disconnectedCallback() {
                super.disconnectedCallback();
            }

            /* callback function after the element is initialized & attached */
            ready() {
                super.ready();
                var self = this;
                self.plotId = self.plotId || self._generatePlotId();

                if (self.transformDataFunc) {
                    self.transformFunc = new Function('return ' + self.transformDataFunc);
                }
                else {
                    self.transformFunc = self.epivizToJSON;
                }

                // self._getElementSeqInfo(self, self);

                if (self.chr != "all") {
                    self._getElementSeqInfo(self, self);
                }

                if (self.workspace) {
                    self.loadWorkspace(self.workspace);
                }
                self._initializeAddDialog();
            }

            _updateNavRegion(e) {
                for (var regIndex = 0; regIndex < this.navigationRegions.length; regIndex++) {
                    if (e.detail.plotId == this.navigationRegions[regIndex]["plotId"]
                        && (e.detail.range != this.navigationRegions[regIndex]["range"])
                    ) {
                        this.navigationRegions.splice(regIndex, 1);
                        this.push("navigationRegions", { "plotId": e.detail.plotId, "range": e.detail.range });
                    }
                }
            }

            _childWorkspaceChanged(e) {
                e.preventDefault();
                e.stopPropagation();
                this.saveWorkspace();
            }

            /**
             * Resizes the chart.
             */
            _onResize() {
                var self = this;
                let navChildren =
                    Polymer.FlattenedNodesObserver.getFlattenedNodes(this.$.chartEnv).filter(n => n.nodeType === Node.ELEMENT_NODE);
                var numChildren = navChildren.length;
                if (numChildren > 0) { self.set('_isEmpty', true); }
                for (var index = 0; index < numChildren; index++) {
                    var currentChild = navChildren[index];
                    currentChild._onResize();
                }
            }

            /**
             * Creates a epiviz GenomicRange object from chromosome, start and end.
             * 
             * @param {string} chr chromosome location 
             * @param {number} start chromosome start
             * @param {number} end chromosome end 
             * 
             * @return {epiviz.datatypes.GenomicRange}
             */
            getGenomicRange(chr, start, end) {
                return new epiviz.datatypes.GenomicRange(chr, start, end - start);
            }

            _navigationRegionsChanged() {
                var self = this;
                if (this.navigationRegions && this.navigationRegions.length > 0) {
                    this.dispatchEvent(new CustomEvent('navigationRegions',
                        {
                            detail: {
                                data: this.navigationRegions
                            }
                        }
                    ));

                    var nRegions = [];

                    for (var regIndex = 0; regIndex < this.navigationRegions.length; regIndex++) {
                        nRegions.push(this.navigationRegions[regIndex]["range"]);
                    }

                    let navChildren =
                        Polymer.FlattenedNodesObserver.getFlattenedNodes(this.$.chartEnv).filter(n => n.nodeType === Node.ELEMENT_NODE);
                    var numChildren = navChildren.length;
                    if (numChildren > 0) { self.set('_isEmpty', true); }
                    for (var index = 0; index < numChildren; index++) {
                        var currentChild = navChildren[index];
                        if (currentChild.nodeName === "EPIVIZ-SCATTER-PLOT") {
                            currentChild.colorByRegions = nRegions;
                        }
                    }
                }
            }

            /**
             * Hover event handler.
             * Listens to hover events fired from its children elements and propagates to other charts
             *
             * @fires hoverAllCharts
             */
            onHover(e) {

                /**
                 * Propogates hover event to other charts.
                 *
                 * @event hoverAllCharts
                 * @type {object}
                 * @property {object} data - data object currently hovered.
                 */
                this.dispatchEvent(new CustomEvent('hoverAllCharts',
                    {
                        detail: {
                            data: e.detail.data
                        }
                    }
                )
                );
                e.preventDefault();
                e.stopPropagation();
            }

            /**
             * Unhover event handler.
             * Listens to unhover events fired from its children elements and propagates to other charts
             * 
             * @fires unHoverAllCharts
             */
            onUnhover(e) {

                /**
                 * Propogates unHover event to other charts.
                 *
                 * @event unHoverAllCharts
                 */
                this.dispatchEvent(new CustomEvent('unHoverAllCharts',
                    {
                        detail: {
                            data: null
                        }
                    }
                )
                );
                e.preventDefault();
                e.stopPropagation();
            }

            /**
             * select event handler.
             * Listens to select events fired from its children elements and propagates to other charts
             * 
             * @fires selectAllCharts
             */
            onSelect(e) {

                /**
                 * Propogates select event to other charts.
                 *
                 * @event selectAllCharts
                 */
                this.dispatchEvent(new CustomEvent('selectAllCharts',
                    {
                        detail: {
                            data: e.detail.data
                        }
                    }
                )
                );
                e.preventDefault();
                e.stopPropagation();
            }

            /**
             * deSelect event handler.
             * Listens to deSelect events fired from its children elements and propagates to other charts
             * 
             * @fires unSelectAllCharts
             */
            onDeSelect(e) {

                /**
                 * Propogates select event to other charts.
                 *
                 * @event unSelectAllCharts
                 */
                this.dispatchEvent(new CustomEvent('unSelectAllCharts',
                    {
                        detail: {
                            data: null
                        }
                    }
                )
                );
                e.preventDefault();
                e.stopPropagation();
            }

            /**
             * MeasurementChange/ChartDimension event handler.
             * Listens to when a chart dimensions(axes) are changed and updates chart
             */
            onDimChanged(e) {
                var chartId = e.detail.id;

                if (chartId != null) {
                    var currentChild = this.shadowRoot.querySelector('[plot-id$="' + cId + '"]');
                    currentChild.shadowRoot.querySelector("paper-spinner-lite").active = true;

                    this._updateChart(chartId);
                }
                self._initializeRemoveDialog();
            }

            /**
             * Generates a unique chart ID
             * 
             * @return {string}
             */
            _generatePlotId() {
                var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
                var result = '';
                var size = 7;

                for (var i = 0; i < size; ++i) {
                    result += chars[Math.round(Math.random() * (chars.length - 1))];
                }
                return 'epiviz-' + result;
            }

            /**
             * ChartLocation/RangeChange event handler.
             */
            _rangeChanged() {

                var self = this;
                if (self.measurements) {
                    let navChildren =
                        Polymer.FlattenedNodesObserver.getFlattenedNodes(this.$.chartEnv).filter(n => n.nodeType === Node.ELEMENT_NODE);
                    var numChildren = navChildren.length;
                    if (numChildren > 0) { self.set('_isEmpty', true); }
                    for (var index = 0; index < numChildren; index++) {
                        var currentChild = navChildren[index];
                        if (currentChild == undefined || currentChild.plotId == undefined) { return; }
                        if (currentChild.data) { return; }
                        currentChild.range = self.range;
                        // self._updateChart(currentChild.plotId);  
                        self._getElementData(currentChild, self, self.measurements);
                    }
                }
            }
        };

        customElements.define(EpivizEnvironment.is, EpivizEnvironment);
    </script>
</dom-module><dom-module id="epiviz-navigation">
  <template>
    <style include="shared-settings"></style>
    <style>
      :host {
        display: inline-block;
        resize: both;
        width: 99%;
        margin: 10px;
        border-radius: 5px;
        transition: width 0.01s, height 0.01s;
        @apply (--shadow-elevation-2dp);
      }

      .flex {
        @apply (--layout-horizontal);
        align-items: center;
      }

      .flexchild {
        padding: 10px;
      }

      #disableHeader {
        pointer-events: none;
        opacity: 0.4;
      }

      paper-button {
        display: inline-block;
        background: #4285f4;
        color: #fff;
        vertical-align: middle;
      }

      paper-icon-button {
        background-color: #dedede;
        color: black;
        border-radius: 3px;
        padding: 2px;
        width: 28px;
        height: 28px;
      }

      #logo {
        vertical-align: middle;
      }

      .paper-header {
        background-color: var(--google-grey-300);
        padding-left: 5px;
      }

      paper-menu-button {
        float: right;
      }

      iron-label {
        color: #4285f4;
        font-weight: bold;
        font-size: 16px;
      }

      .isempty {
        position: relative;
        top: 50%;
        left: 50%;
        transform: translateX(-50%) translateY(-50%);
      }

      #header {
        height: 50px;
        /* linear-gradient(white, #e0e2e2) */
      }

      .content {
        min-height: 250px;
        padding: 5px;
        overflow: auto;
      }

      .chartContainer {
        /* min height should be height of largest plot */
        display: grid;
        grid-template-columns: repeat(6, 1fr);
        grid-auto-rows: minmax(max-content, 100px);
        grid-row-gap: 2px;
        grid-column-gap: 2px;
      }

      #chartSettingsContainer {
        position: absolute;
        top: 0;
        right: 0;
        border: 1px solid #000;
        border-radius: 2px;
        margin: 1px;
      }

      .chartContainer ::slotted(.grid-plot) {
        grid-row: span 1;
        grid-column: span 2;
      }

      .chartContainer ::slotted(.grid-track) {
        grid-column: span 6;
        grid-row: span 1;
      }

      .chartContainer ::slotted(.grid-navigation) {
        grid-row: span 1;
        grid-column: span 6;
        order: 2;
      }
            
      #header > a {
        text-decoration: none;
      }
    </style>

    <div id="header" class="paper-header">
      <a href="http://epiviz.github.io" target="_blank" hidden$="{{noLogo}}">
        <img id="logo" style="margin-top: 7px;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAAA3CAYAAAAYCDYxAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAACoJJREFUeNrsXT1MHDkUHiLaU9irTwrZ9IEIykhsJFKHSLc0d1I2zVKGq6AL6dgqSQkNm+J0EttsapCySClBIunhiHT1LejqO86P+5wzXnvGM2t7PMRPsvaHWa/f8j6/975neyYuLy+TKFGihCW34k8QJUp4Msmf/PDT7hv2MKD2x6/L5zdJSaZbgz3MsjaFt6bweoDXpO8x03sQTSJKCDLBQ1lmvGJM+0kAab+CQCTQLaHN5PjoBfTuM7270TyihAZMWd6TscJgzwMFI3nBFtqMhS4JpBRFdJnOZ4FPRGvCS3peU1y2zfRYiSZ/s4ApG+ubUAAKQK6i3Xb0NW9Z2whI5zn2sMjaZo6PRWDeYGAGBVA2ZgLjhkNAjujM9N0oGZCbAGVeCRKYTKc6dGrirXU2zk7Z4/pYvz8yroennzuhA5PLFwodfZMmyCG7BiEr5coUhh4nIHjwPid/+ONCjq//BJ2PPeu8xR7aY3QRKjD3FBNN6eBkwFSOyxc4xwWm91CPjZM81ssUr9ZHy8Uus345WbRk6IFf+fCe8JK7rNUzLt1H4yAchh6uwVueqHRh439csrdUjosB08u4Ji3184IMmozblSdBLtnXeLcDkDTdov2Dfe7je5YQIt9J+chLDmZX5BDrn2bsvZRLjljrhRD6FZRazve/mXGJHrPBHqYR2jWS4szmc9ulBoytr/Bk75D3uZoMWgYAvUBo2/cIyiHCve2qkxyaUHalbN00oewK85jbXoGp+MGmAdBWzhzsCjDsh205DF0PAIYzT8ZjQjK9ZeNZtRi+7mlmaApXl6sQqhrqSjpuBUj+jIyrFPIn48ebBkDzlCQOEOadF/yHTYHgeSK8XQrZlDIeqzoLhnqoySlLzb2i+JOJvIvYDcM7Lp+K5GCYCPpSOP2qzFKFRBJ1UyaoQjoL/RNFv6bKJ1mf89FkIzBthHc8B2uY5oEohQyEfkspT4zpPXPpLPSrYwNJ7rH+TqPJRmCaGiiB84UNcCq8EZE7qwEvA6TJ6bUtcKbUKkvNu7Dkr6bx5LxMQx59P0IqAGBKXq6bpDO5FwBZNyVE3jG5NjBwziLs1oX2Riw1csuTZJTwGcJbDj3rVQcQ8yxqII/eycOoaiaja9HBx/r9E0XOPf/w9PNRQWJHlS5cI3fYNcpxsWtOpb5U7G1eoT63xe+3sh+TvAJrZKCvUi4jL7gDAKaB8gs8TbcKMxs84iyIH5UodVbIYqJmYXslgHINk0TelUYEni0qgQDYtkQVLTTH6E+lV5nlmavlfwzkh1aBKRgphbUPAK7ExFDZ864ASsonZ0PKJw31Pmetkfy3AqooOOc07/c8g5JWGW2O2c1VDRZlHxvSQ+SQBS4Tb9lWTIDkrUIoP82x8TWtA1PyIO8NwTkl5JONKm/SRh3zeUFwqsKhoc+8DaBUeaKrBQ0UPrLxTPDGXn+P9480XsCK5wRo5Amqxo04pzTLnvxMZNKRgRK4ljLWtZKhTrFr+VK+fnIDhEJwpg9NToNklLEmnRNNmF7XECs+w1eV0VLOuK7RdYgws8M+T5/dkrwRPSew2yjz9BResp0HVFgDK0+Apwz4+2NMGrnryli8sKeIkob8fz7h+jAueIk3ib6sYm2VUICkEAFwJosQAvHzp+MhaXeXwKsdKkK8lZxEjm7FkpZVNiF/BIM+VBjzCCGTAog1RZiuXNFjSv4UDKd1hNHXsTg/jAsGSPnXheaSZ8gzb5QgpG8gb1ZFCxuSZylTVKcedPKuV2XXU0ir8iBrmHxseM1xSKDSSR8APhWUXoApGem3Bs5z6P1O8eeXIegMwMgGe6oLXw3BKRt7LRlvL2kaiNqGgFhUpAs9n6QPPLZycpC99i2PRprmQTg4+barqoexUwQ6WloIxralASfpTLnoPyUOd9GBF1GFrWMztACRPLY6QFeE9Nn2CMp2oma7aY/nihfyJw2c2MI10ORetMRtQNdUlZ3FxML1WxLBCVJIXim0gOv/Yu0709zMoszZNljKD5muR1Lfi5bGqyKBmkkKUQaypWmT9MkJStJ9S/En+o2WVZ/xfuCzEN7pPOcMwDldUcLnTJh0ri0nZM+JBFOVU+4oQOkr95xThLE2wjvZ6GuWSifUr0zANAG+NG9ZK8NbsnHxEyhkIR2WdaF0KSexG4LzGIZeFVC2kv9LJJRLP1WVRfDeo5R8uyixUVTqCoOxIUOD77KVa9YyfqtSwliUZ3YT9TLL5TSGt7RbJBiA8zY8Z6sCoCRPuCOAspFWl8V+0oYBOOsWV8/opOYRmIkjYGonMU3t0jnpAw+uO6tpOWudb6n3LhHA+T4FnDsw/BABOY28ke+u4et8jw105yukxIlJVVRuewZm6P3qVgItAoQmv5+PMHZXk7+vmOS2pd9UCMQIbfd6l3LZC2IvQ2JsseXrWMgnc6/zxWZqMWqYUBmW5QXhvqSmyatces22wXvOSZ+MWqXRpBDM3b5QUvgl5RJiL8/A6pbtJSkUfZ1c3zdaiEnGxDSbMTFtVhCYsmEObW701pBAbQkg3kmfPLXKSgATRkoh69OU3IuA8IFCW9/eE7XJDfb09+T64WR05Elr3PIOJibdtrmmdH+S0HPuOUUY58JLjZBA0sJ2r6RP3lplZYAJA+1nkEIJcrozX8QQvofCTnFBPk0ej2yeQ4S+qJyiWnCwiSMtqyAqj9HzAMyvYNTULp2RPkVqlZUCpkCMNDLCO04MDVyEt/CQq6wRIHeS64vweT45cKB7lz38zNrfij/vhe45sctEtcTPOjA1JBCvaXojfYrWKisHTCH3amWEtjz3/ACAtiwY1izWsJ4hj5SPDKHzY2ddnmnL+v6NPfyoyzcdnBBgC5SLGgNdd/i1PY3H9rLSZ5xaZZpMJoELhbbCFqqFDIAuoLRifP8SrDDiN7ptJPqze7yeaQu9aafGnoZYOWF/Jw/Qc7GZGtuxjkx3mMCTq/KrngtvKXjNHgMHGb84Ua35IH2EfZWFapWVBiaMlLxTw+BMVx7iPkMjg/kC70dCoOJ3+ErwaHKAdSn3xyTAsfHPJ/pCNXkGKqfQ8w5m6aGl2wvU0fcWPN5Iv8J9Otua8ZFh+rjD2LY0KdQ85bg6vVfG9c6VAKbkRcjDrSb6kxFkuSN4wby3ejgAIAcl6kzGfc/gNnxrkrcbMd4xbsO3mdKvTq72Zno6SKyXpJeUejY2OGsiF5VsoZZZRK72Zd5KKibIPTfY07sZ5NA4QoAkxrVRJiglvQlUjxP1+TqhCU0C875O9wPoehke1YU4069ywBTDW5BDdxFqXozZ5QWA/iAkQMqhLW6T8NgxocK9UF7D24eXLOMGueua8fYcrvRxtiVvMqm4IP+k0HYVrCzloU9ygPGKKKrSYWAge6h1FCcQbFr6DvIy22Ba+YIB3Qlz/D6dpd3CgbwmCx+XoT9f3NBh7687/M4j9p3z+F2slrGcH8ZVlqC2SW0ajcsxyKBB1c6vdfQ7XVrMRaNEj5k541MoOoj/4ihVlFvxJ4gSJQIzSpQoEZhRokRgRokSJQIzSpQIzChRokRgRokSgRklSpQIzChRokRgRolSQbmxa2WjRKmy/CvAADGRSCbKNK1nAAAAAElFTkSuQmCC" alt="Epiviz" width="105" height="30">      
      </a>
      <div style="display:inline-block" hidden$="{{collapsed}}">
        <epiviz-add-chart hidden$="{{hideAddChart}}" id="addChart"></epiviz-add-chart>
        <paper-icon-button hidden$="{{!showViewer}}" style="margin-top:10px;margin-left:10px;" dynamic-align="" vertical-align="bottom" title="Open viewer in a new window" icon="image:filter" on-click="openFullViewer"></paper-icon-button>
        </div>
      <paper-menu-button dynamic-align="" vertical-align="bottom" vertical-offset="24">
        <paper-icon-button icon="menu" slot="dropdown-trigger"></paper-icon-button>
        <paper-listbox slot="dropdown-content">
          <paper-item toggles="" on-click="toggleSelected">Delete</paper-item>
          <paper-item on-click="cloneNavigation">Clone</paper-item>
          <paper-item on-click="toggleHeader">{{ _toggleText }}</paper-item>
        </paper-listbox>
      </paper-menu-button>
    </div>
    <div class="content">
      <div hidden$="{{!collapsed}}">
        <epiviz-ideogram-track hidden$="{{!collapsed}}" gene-in-range="{{geneInRange}}" id="navTrack" chromosome="{{chr}}" start="{{start}}" end="{{end}}"></epiviz-ideogram-track>
      </div>

      <iron-collapse id="navHeader" hidden$="{{collapsed}}" opened="">
        <div class="container flex" id="{{_headerName}}">
          <div class="flexchild" hidden$="{{hideNav}}">
            <paper-icon-button title="move left" icon="icons:chevron-left" on-click="moveLeft"></paper-icon-button>
            <paper-icon-button title="move right" icon="icons:chevron-right" on-click="moveRight"></paper-icon-button>
            <paper-icon-button title="zoom out" icon="icons:zoom-out" on-click="zoomOut"></paper-icon-button>
            <paper-icon-button title="zoom in" icon="icons:zoom-in" on-click="zoomIn"></paper-icon-button>
          </div>
          <div class="flexchild" hidden$="{{hideChrInput}}" on-mouseover="locationHovered" on-mouseout="locationUnhovered">
            <paper-input id="textRange" value="{{_strRange]]" label="Chromosome Location" on-change="_updateStrRange"></paper-input>
          </div>
          <div class="flexchild" hidden$="{{hideSearch}}">
            <paper-dropdown-input id="geneSearch" label="Search Gene/Probe" on-search-value-changed="_searchGene">
                <template>
                  <dom-repeat items="[[items]]" as="item">
                    <template>
                      <paper-item on-tap="_selectGene" role="option" aria-selected="false">
                        <div>
                          [[item.gene]] [[item.chr]]: [[item.start]] -
                          [[item.end]]
                        </div>
                        <paper-ripple></paper-ripple>
                      </paper-item>
                    </template>
                  </dom-repeat>
                </template>
              </paper-dropdown-input>
          </div>
        </div>
        <div class="content chartContainer">
          <slot id="chartNav" name="charts"></slot>
        </div>
      </iron-collapse>
    </div>
  </template>

  <script>
    // Extend Polymer.Element base class
    class EpivizNavigation extends EpivizChartDraggableBehavior(
      EpivizChartGridBehavior(
        EpivizChartWorkspaceBehavior(
          EpivizChartDataBehavior(
            EpivizChartAddBehavior(EpivizChartBehavior(Polymer.Element))
          )
        )
      )
    ) {
      static get is() {
        return "epiviz-navigation";
      }

      static get properties() {
        return {
          /**
           * Chromosome location.
           * Default Location Attribute to set to all the children chart elements.
           *
           * @example: chr1
           *
           * @type {string}
           */
          chr: {
            type: String,
            notify: true
          },

          /**
           * Chromosome start.
           * Default Chromosome start value to use. (defaults to 0).
           *
           * @type {number}
           */
          start: {
            type: Number,
            notify: true
          },

          /**
           * Chromosome end.
           * Default Chromosome end value to use. (defaults to the `<chr>` length).
           *
           * @type {number}
           */
          end: {
            type: Number,
            notify: true
          },

          viewer: {
            type: String
          },

          /**
           * Unique plot-id. an id will be assigned if not set
           *
           * @type {string}
           */
          plotId: {
            type: String,
            reflectToAttribute: true,
            notify: true
          },

          /**
           * Updates location attributes(chr, start, end) to this gene's location.
           *
           * @type {string}
           */
          gene: {
            type: String,
            observer: "_geneUpdate"
          },

          /**
           * Computed Range from `<chr>`, `<start>` & `<end>`.
           *
           * @type {Object<epiviz.datatypes.GenomicRange>}
           */
          range: {
            type: Object,
            observer: "_rangeUpdate"
          },

          /**
           * Computed Range from `<chr>`, `<start>` & `<end>`.
           *
           * @type {string}
           */
          _strRange: {
            type: String,
            notify: true,
            computed: "getStrRange(chr, start, end)"
          },

          /**
           * helper header name
           *
           * @type {string}
           */
          _headerName: {
            type: String,
            notify: true,
            value: "enableHeader"
          },

          /**
           * helper toggle text
           *
           * @type {string}
           */
          _toggleText: {
            type: String,
            notify: true,
            computed: "_computeToggleText(collapsed)"
          },

          /**
           * Default scroll left/right ratio
           *
           * @type {number}
           */
          stepRatio: {
            type: Number,
            notify: true,
            value: 0.2
          },

          /**
           * Default zoom in/out ratio
           *
           * @type {number}
           */
          zoomRatio: {
            type: Number,
            notify: true,
            value: 0.2
          },

          /**
           * Whether the element is collapsed
           *
           * @type {boolean}
           */
          collapsed: {
            type: Boolean,
            notify: true,
            value: function () {
              return false;
            }
          },

          /**
           * Whether the element has no children
           *
           * @type {boolean}
           */
          _isEmpty: {
            type: Boolean,
            notify: true,
            value: function () {
              return false;
            }
          },

          /**
           * Current gene in range (set automatically)
           *
           * @type {string}
           */
          geneInRange: {
            type: String,
            notify: true
          },

          /**
           * helper header class name
           *
           * @type {string}
           */
          headerClassName: {
            type: String,
            notify: true
          },

          /**
           * Whether to hide logo
           *
           * @type {string}
           */
          noLogo: {
            type: Boolean,
            notify: true,
            reflectToAttribute: true,
            value: false
          },

          /**
           * measurements available on this element
           *
           * @type {string}
           */
          measurements: {
            type: Object,
            notify: true,
            reflectToAttribute: true
          },

          measurementSet: {
            type: Object,
            notify: true
          },

          /**
           * Default chart properties for navigation element.
           *
           * @type {Object}
           */
          configSrc: {
            type: Object,
            notify: true,
            value: function () {
              epiviz.Config.SETTINGS = {
                dataProviders: [
                  [
                    "epiviz.data.WebServerDataProvider",
                    "umd",
                    "http://epiviz-dev.cbcb.umd.edu/api/"
                  ]
                ],
                workspacesDataProvider: sprintf(
                  "epiviz.data.EmptyResponseDataProvider",
                  "empty",
                  ""
                ),
                useCache: true,
                colorPalettes: [],
                maxSearchResults: 12
              };
              return epiviz.Config.SETTINGS;
            }
          },

          geneMeasurements: {
            type: Object,
            notify: true
          },

          hideSearch: {
            type: Boolean,
            notify: true,
            value: false
          },

          hideNav: {
            type: Boolean,
            notify: true,
            value: false
          },

          hideChrInput: {
            type: Boolean,
            notify: true,
            value: false
          },

          hideAddChart: {
            type: Boolean,
            notify: true,
            value: false 
          },

          showViewer: {
            type: Boolean,
            notify: true,
            value: false
          },

          _zoomIn: {
            type: Number,
            value: 0
          }

        };
      }

      static get observers() {
        return [
          '_measurementSetChanged(measurementSet.*)'
        ];
      }

      constructor() {
        super();
        this.addEventListener("hover", e => this.onHover(e));
        this.addEventListener("unHover", e => this.onUnhover(e));
        this.addEventListener("select", e => this.onSelect(e));
        this.addEventListener("deSelect", e => this.onDeSelect(e));
        this.addEventListener("GenesTrackRangeUpdate", e => this.onGenesTrackRangeUpdate(e));
        this.addEventListener("iron-resize", e => {
          this._debouncer = Polymer.Debouncer.debounce(
            this._debouncer,
            Polymer.Async.timeOut.after(100),
            () => this._onResize(e)
          );
        });
        this._getElementSeqInfo(this, this);

        // this.addEventListener('transitionend', e => this._onResize(e));
      }

      connectedCallback() {
        super.connectedCallback();
        var self = this;

        self._initializeGrid();
        self._initializeSortable();

        var origCollapsed = self.collapsed || false;

        self.collapsed = false;

        if (!self.dataManager) {
          self._headerName = "disableHeader";
        }

        if (self.useDefaultDataProvider) {
          var dataProviderFactory = new epiviz.data.DataProviderFactory(
            new epiviz.Config(self.configSrc)
          );
          var dataManager = new epiviz.data.DataManager(
            self.config,
            dataProviderFactory
          );

          dataManager.getSeqInfos(function (data) {
            var seqinfo = {};
            data.forEach(function (s) {
              seqinfo[s.seqName] = s;
            });
            self.seqInfo = seqinfo;
          });
        }

        if (self.gene) {
          self._getElementSearch(self.gene, null, null, function (data) {
            self.chr = data[0].chr;
            self.start = data[0].start;
            self.end = data[0].end;
            self.range = self.getGenomicRange(self.chr, self.start, self.end);
            self._strRange = self.getStrRange(self.chr, self.start, self.end);

            self.chartObject = new epiviz.ui.charts.ChartObject(
              self.plotId,
              self.start,
              self.end,
              undefined,
              undefined,
              undefined,
              undefined,
              undefined,
              self.chr
            );
          });
        } else if (self.chr && self.start && self.end) {
          self.range = self.getGenomicRange(self.chr, self.start, self.end);
          self._strRange = self.getStrRange(self.chr, self.start, self.end);

          self.chartObject = new epiviz.ui.charts.ChartObject(
            self.plotId,
            self.start,
            self.end,
            undefined,
            undefined,
            undefined,
            undefined,
            undefined,
            self.chr
          );
        }



        if (origCollapsed != self.collapsed) {
          self.toggleHeader();
        }

        self.$.addChart._parentContainer = self;
      }

      disconnectedCallback() {
        super.connectedCallback();
      }

      _measurementSetChanged(newVal, oldVal) {
        var self = this;
        var origCollapsed = self.collapsed || false;

        if (!origCollapsed) {
        self._observer = new Polymer.FlattenedNodesObserver(
          self.$.chartNav,
          info => {
              // self._observer =
              // Polymer.dom(self.$$("#chartNav")).observeNodes(function(info) {
              info.addedNodes.forEach(function (node) {
                node._parentContainer = self;
                if (
                  node.nodeName.indexOf("EPIVIZ-") != -1 &&
                  node.nodeName.indexOf("JSON") == -1
                ) {
                  // self._updateChart(node.plotId);
                  var measurements = self.measurementSet;
                  if (self._parentContainer) {
                    measurements = self._parentContainer.measurementSet;
                  }
                  self._getElementData(node, self, measurements);

                  self.set("_isEmpty", true);
                }
              });
              self.saveWorkspace();
            }
          );
        }
      }

      /* callback function after the element is initialized */
      ready() {
        super.ready();
        var self = this;

        self.plotId = self.plotId || self._generatePlotId();
        self.config = new epiviz.Config(self.configSrc);
        // self.range = self.getGenomicRange(self.chr, self.start, self.end);

        if (self.transformDataFunc) {
          self.transformFunc = new Function("return " + self.transformDataFunc);
        } else {
          self.transformFunc = self.epivizToJSON;
        }
        self._initializeAddDialog();
        self._initializeGrid();
      }

      /**
       * Resizes the chart.
       */
      _onResize(e) {
        var self = this;
        // if (e.path.indexOf(this.shadowRoot.querySelector(".content")) === -1) {
        //     return;
        // }
        let navChildren = Polymer.FlattenedNodesObserver.getFlattenedNodes(
          self.$.chartNav
        ).filter(n => n.nodeType === Node.ELEMENT_NODE);
        var numChildren = navChildren.length;
        if (numChildren > 0) {
          self.set("_isEmpty", true);
        }
        for (var index = 0; index < numChildren; index++) {
          var currentChild = navChildren[index];
          currentChild._onResize(e);
        }
      }

      _computeToggleText(collapsed) {
        if (collapsed) {
          return "Expand";
        }
        return "Collapse";
      }

      toggleSelected(event) {
        $(event.target)
          .parent()
          .parent()[0].selected = null;
      }

      /**
       * Toggle handler (expand/collapse)
       */
      toggleHeader(event) {
        var self = this;
        this.$.navHeader.toggle();
        this.collapsed = !this.collapsed;
        // unselect the selected paper-item;
        if (event) {
          this.toggleSelected(event);
        }
        // unsafe to reference index 0, but works for now
        var menuButton = self.shadowRoot.querySelector("paper-menu-button");
        menuButton.close();
        if (this.collapsed) {
          // self._observer.disconnect();
          $(this).css("grid-column-start", "span " + 2);
        } else {
          self._observer = new Polymer.FlattenedNodesObserver(
            self.$.chartNav,
            info => {
              // Polymer.dom(self.$$("#chartNav")).observeNodes(function(info) {
              info.addedNodes.forEach(function (node) {
                node._parentContainer = self;
                if (
                  node.nodeName.indexOf("EPIVIZ-") != -1 &&
                  node.nodeName.indexOf("JSON") == -1
                ) {
                  var measurements = self.measurementSet;
                  if (self._parentContainer) {
                    measurements = self._parentContainer.measurementSet;
                  }
                  if (!node.data) {
                    self._getElementData(node, self, measurements);
                  }
                }
              });
              self.saveWorkspace();
            }
          );

          $(this).css("grid-column-start", "span " + 6);
        }

        self._onResize();

        if (event) {
          event.preventDefault();
          event.stopPropagation();
        }
      }

      /**
       * Hover event handler.
       * Listens to hover events fired from its children elements and propagates to other charts.
       *
       * @fires hoverAllCharts
       */
      onHover(e) {
        /**
         * Propogates hover event to other charts.
         *
         * @event hoverAllCharts
         * @type {object}
         * @property {object} data - data object currently hovered.
         */
        this.dispatchEvent(
          new CustomEvent("hoverAllCharts", {
            detail: {
              data: e.detail.data
            }
          })
        );
      }

      onGenesTrackRangeUpdate(e) {
        var self = this;
        var range = e.detail.range;

        self.chr = range.seqName();
        self.start = range.start();
        self.end = range.end();
        self.range = range;
        self._strRange = self.getStrRange(self.chr, self.start, self.end);
      }

      /**
       * Unhover event handler.
       * Listens to unhover events fired from its children elements and propagates to other charts
       *
       * @fires unHoverAllCharts
       */
      onUnhover(e) {
        /**
         * Propogates unHover event to other charts.
         *
         * @event unHoverAllCharts
         */
        this.dispatchEvent(
          new CustomEvent("unHoverAllCharts", {
            detail: {
              data: null
            }
          })
        );
      }

      /**
       * Select event handler.
       * Listens to select events fired from its children elements and propagates to other charts.
       *
       * @fires selectAllCharts
       */
      onSelect(e) {
        /**
         * Propogates select event to other charts.
         *
         * @event selectAllCharts
         * @type {object}
         * @property {object} data - data object currently selected.
         */
        if (e.detail && e.detail.data) {
          this.dispatchEvent(
            new CustomEvent("selectAllCharts", {
              detail: {
                data: e.detail.data
              }
            })
          );
        }
      }

      /**
       * deSelect event handler.
       * Listens to select events fired from its children elements and propagates to other charts.
       *
       * @fires unSelectAllCharts
       */
      onDeSelect(e) {
        /**
         * Propogates select event to other charts.
         *
         * @event unSelectAllCharts
         */
        this.dispatchEvent(
          new CustomEvent("unSelectAllCharts", {
            detail: {
              data: null
            }
          })
        );
      }

      /**
       * ChartLocation/RangeChange event handler.
       * @fires navigationRangeUpdate
       */
      _rangeUpdate(newVal, oldVal) {
        var self = this;
        self.chartObject = new epiviz.ui.charts.ChartObject(
          self.plotId,
          self.start,
          self.end,
          undefined,
          undefined,
          undefined,
          undefined,
          undefined,
          self.chr
        );
        if (!self.collapsed && oldVal) {
          // self._getGenesInRange(self, self);
          // filter out non Epiviz nodes
          let navChildren = Polymer.FlattenedNodesObserver.getFlattenedNodes(
            self
          ).filter(
            n =>
              n.nodeType === Node.ELEMENT_NODE &&
              n.nodeName.indexOf("EPIVIZ") !== -1
          );
          var numChildren = navChildren.length;
          if (numChildren > 0) {
            self.set("_isEmpty", true);
          }
          for (var index = 0; index < numChildren; index++) {
            var currentChild = navChildren[index];
            if (currentChild == undefined || currentChild.plotId == undefined) {
              return;
            }
            currentChild.shadowRoot.querySelector(
              "paper-spinner-lite"
            ).active = true;
            currentChild.range = self.range;
            var measurements = self.measurementSet;
            if (self._parentContainer) {
              measurements = self._parentContainer.measurementSet;
            }
            self._getElementData(currentChild, self, measurements);
          }
        }
        self.dispatchEvent(
          new CustomEvent("navigationRangeUpdate", {
            detail: {
              plotId: self.plotId,
              range: self.range
            }
          })
        );

        if (self._zoomIn != 0 && self._zoomIn % 2 == 0) {
          setTimeout(function () {
            self._reupdateAllData();
          }, 2000);
        }

        // self.chartObject = new epiviz.ui.charts.ChartObject(self.plotId, self.start, self.end, undefined, undefined, undefined, undefined, undefined, self.chr);

        // self.fire('navigationRangeUpdate', {
        //     plotId: self.plotId,
        //     range: self.range
        // });
      }

      /**
       * GeneLocationChange event handler.
       */
      _geneUpdate() {
        var self = this;
        self._getElementSearch(self.gene, null, null, function (data) {
          self.chr = data[0].seqName;
          self.start = data[0].start;
          self.end = data[0].end;
        });
      }

      /**
       * Search handler for a gene (gene search field).
       *
       * @param {string} keyword search string is at e.detail.option.text
       */
      _searchGene(e) {
        var self = this;
        var gene = e.detail.value;
        var field = this.shadowRoot.querySelector("#geneSearch");

        if (gene != "") {
          if (self.useDefaultDataProvider) {
            self.range =
              self.range ||
              new epiviz.datatypes.GenomicRange("chr11", 80000000, 3000000);
            var dataProviderFactory = new epiviz.data.DataProviderFactory(
              new epiviz.Config(self.configSrc)
            );
            var dataManager = new epiviz.data.DataManager(
              self.config,
              dataProviderFactory
            );

            dataManager.search(function (data) {
              // field.suggestions(data);
              field.items = data;
            }, gene);
          } else {
            var result = self._getElementSearch(gene, self, self);
          }
        }
      }

      /**
       * Update Location Attributes when a gene is selected.
       * selected gene value @ e.detail.option
       */
      _selectGene(e) {
        var selected = e.model.item;

        this.chr = selected.chr;
        this.start = selected.start - Math.round(selected.start * 0.01);
        this.end = selected.end + Math.round(selected.end * 0.01);

        this.range = this.getGenomicRange(this.chr, this.start, this.end);
      }

      /**
       * Handles location change (gene location field)
       */
      _updateStrRange(e) {
        var update = e.target.value;
        var split = update.split(":");
        var chr = split[0];
        var rsplit = split[1].split("-");
        var start = parseInt(rsplit[0]);
        var end = parseInt(rsplit[1]);
        var width = end - start;

        if (chr in this.seqInfo) {
          this.chr = chr;
          this.end = Math.min(end, this.seqInfo[this.chr].max);
          if (start > this.end) {
            start = this.end - width;
          }
          this.start = Math.max(0, start);

          this.range = this.getGenomicRange(this.chr, this.start, this.end);
        } else {
          var toast = document.createElement("paper-toast");
          toast.setAttribute("text", "Chromosome does not exist");
          // toast.setAttribute("openned", true);
          Polymer.dom(this.root).appendChild(toast);
          toast.show();
        }
      }

      openFullViewer(e) {
        window.open(this.viewer, "_blank");
      }

      /**
       * Navigation handler - move left
       */
      moveLeft(e) {
        var width = this.end - this.start;
        var newStart = this.start - Math.round(width * this.stepRatio);
        var newEnd = newStart + width;

        this.start = Math.max(0, newStart);
        this.end = Math.min(newEnd, this.seqInfo[this.chr].max);

        this.range = this.getGenomicRange(this.chr, this.start, this.end);
        this._strRange = this.getStrRange(this.chr, this.start, this.end);
      }

      /**
       * Navigation handler - move right
       */
      moveRight(e) {
        var width = this.end - this.start;
        var newStart = this.start + Math.round(width * this.stepRatio);
        var newEnd = newStart + width;

        this.start = Math.max(0, newStart);
        this.end = Math.min(newEnd, this.seqInfo[this.chr].max);

        this.range = this.getGenomicRange(this.chr, this.start, this.end);
        this._strRange = this.getStrRange(this.chr, this.start, this.end);
      }

      /**
       * Navigation handler - zoom in
       */
      zoomIn(e) {

        this._zoomIn++;
        var width = Math.round(this.end - this.start);
        // var mid = Math.round(this.start + width / 2);
        var newWidthFactor = Math.round(width * this.zoomRatio);
        var newStart = this.start + newWidthFactor;
        var newEnd = this.end - newWidthFactor;

        this.start = Math.max(0, newStart);
        this.end = Math.min(newEnd, this.seqInfo[this.chr].max);

        this.range = this.getGenomicRange(this.chr, this.start, this.end);
        this._strRange = this.getStrRange(this.chr, this.start, this.end);
      }

      /**
       * Navigation handler - zoom out
       */
      zoomOut(e) {
        var width = Math.round(this.end - this.start);
        // var mid = Math.round(this.start + width / 2);
        var newWidthFactor = Math.round(width * this.zoomRatio);
        var newStart = this.start - newWidthFactor;
        var newEnd = this.end + newWidthFactor;

        this.start = Math.max(0, newStart);
        this.end = Math.min(newEnd, this.seqInfo[this.chr].max);

        this.range = this.getGenomicRange(this.chr, this.start, this.end);
        this._strRange = this.getStrRange(this.chr, this.start, this.end);
      }

      /**
       * String formatted Range
       *
       * @param {string} chr chromosome location
       * @param {number} start chromosome start
       * @param {number} end chromosome end
       *
       * @return {string} range in string format
       */
      getStrRange(chr, start, end) {
        return chr + ": " + start + " - " + end;
      }

      /**
       * String formatted Range
       *
       * @param {string} chr chromosome location
       * @param {number} start chromosome start
       * @param {number} end chromosome end
       *
       * @return {epiviz.datatypes.GenomicRange} Genomic Range object
       */
      getGenomicRange(chr, start, end) {
        return new epiviz.datatypes.GenomicRange(chr, start, end - start);
      }

      /**
       * Handles data when charts are first initialized.
       */
      _getData() {
        var self = this;
        let navChildren = Polymer.FlattenedNodesObserver.getFlattenedNodes(
          self
        ).filter(n => n.nodeType === Node.ELEMENT_NODE);
        var numChildren = navChildren.length;
        if (numChildren > 0) {
          self.set("_isEmpty", true);
        }
        for (var index = 0; index < numChildren; index++) {
          var currentChild = navChildren[index];
          if (currentChild == undefined || currentChild.plotId == undefined) {
            return;
          }
          currentChild.range = self.range;
          currentChild.shadowRoot.querySelector(
            "paper-spinner-lite"
          ).active = true;
          currentChild._parentContainer = self;
          var measurements = self.measurements;
          if (self._parentContainer) {
            measurements = self._parentContainer.measurements;
          }
          self._getElementData(currentChild, self, measurements);
        }
      }

      /**
       * Clones a navigation element and add it to the page
       */
      cloneNavigation(event) {
        var self = this;

        this.toggleSelected(event);
        var navElem = document.createElement("epiviz-navigation");
        navElem.chr = self.chr;
        navElem.start = self.start;
        navElem.end = self.end;
        navElem.className = "charts";

        Polymer.dom(self.parentNode).appendChild(navElem);
        let navChildren = Polymer.FlattenedNodesObserver.getFlattenedNodes(
          self
        ).filter(n => n.nodeType === Node.ELEMENT_NODE);
        var numChildren = navChildren.length;
        if (numChildren > 0) {
          self.set("_isEmpty", true);
        }
        for (var index = 0; index < numChildren; index++) {
          var currentChild = navChildren[index];
          var childElem = document.createElement(currentChild.nodeName);
          childElem.className = "charts";
          childElem.dimS = currentChild.dimS;
          childElem.measurements = currentChild.measurements;
          childElem.chartSettings = currentChild.chartSettings;
          childElem.chartColors = currentChild.chartColors;
          childElem._parentContainer = self;
          Polymer.dom(navElem).appendChild(childElem);
        }
      }

      /**
       * Handles when mouse-overed on a ramge element.
       */
      locationHovered() {
        var self = this;
        if (!self.$.textRange.focused) {
          this.dispatchEvent(
            new CustomEvent("hover", {
              detail: {
                data: self.chartObject
              }
            })
          );
        }
      }

      /**
       * Handles when mouse-out on a range element.
       */
      locationUnhovered() {
        var self = this;
        this.dispatchEvent(
          new CustomEvent("unHover", {
            detail: {
              data: null
            }
          })
        );
      }

      _generatePlotId() {
        var chars =
          "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        var result = "";
        var size = 7;

        for (var i = 0; i < size; ++i) {
          result += chars[Math.round(Math.random() * (chars.length - 1))];
        }
        return "epiviz-" + result;
      }

      _measurementsChanged() {
        // console.log("ovverriding measurementsChanged function");
      }

      _rangeChanged() {
        // console.log("overriding rangeChanged function");
      }

      /**
       * Creates an instance of the navigation element.
       *
       * @return {null} for consistency with other chart elements
       */
      _createChart() {
        return null;
      }
    }

    customElements.define(EpivizNavigation.is, EpivizNavigation);
  </script>
</dom-module></div>