text: import * as zrUtil from 'zrender/src/core/util';
  
  var RADIAN_EPSILON = 1e-4;
  
  function _trim(str) {
      return str.replace(/^\s+/, '').replace(/\s+$/, '');
  }
  
  /**
   * Linear mapping a value from domain to range
   * @memberOf module:echarts/util/number
   * @param  {(number|Array.<number>)} val
   * @param  {Array.<number>} domain Domain extent domain[0] can be bigger than domain[1]
   * @param  {Array.<number>} range  Range extent range[0] can be bigger than range[1]
   * @param  {boolean} clamp
   * @return {(number|Array.<number>}
   */
  export function linearMap(val, domain, range, clamp) {
      var subDomain = domain[1] - domain[0];
      var subRange = range[1] - range[0];
  
      if (subDomain === 0) {
          return subRange === 0
              ? range[0]
              : (range[0] + range[1]) / 2;
      }
  
      // Avoid accuracy problem in edge, such as
      // 146.39 - 62.83 === 83.55999999999999.
      // See echarts/test/ut/spec/util/number.js#linearMap#accuracyError
      // It is a little verbose for efficiency considering this method
      // is a hotspot.
      if (clamp) {
          if (subDomain > 0) {
              if (val <= domain[0]) {
                  return range[0];
              }
              else if (val >= domain[1]) {
                  return range[1];
              }
          }
          else {
              if (val >= domain[0]) {
                  return range[0];
              }
              else if (val <= domain[1]) {
                  return range[1];
              }
          }
      }
      else {
          if (val === domain[0]) {
              return range[0];
          }
          if (val === domain[1]) {
              return range[1];
          }
      }
  
      return (val - domain[0]) / subDomain * subRange + range[0];
  }
  
  /**
   * Convert a percent string to absolute number.
   * Returns NaN if percent is not a valid string or number
   * @memberOf module:echarts/util/number
   * @param {string|number} percent
   * @param {number} all
   * @return {number}
   */
  export function parsePercent(percent, all) {
      switch (percent) {
          case 'center':
          case 'middle':
              percent = '50%';
              break;
          case 'left':
          case 'top':
              percent = '0%';
              break;
          case 'right':
          case 'bottom':
              percent = '100%';
              break;
      }
      if (typeof percent === 'string') {
          if (_trim(percent).match(/%$/)) {
              return parseFloat(percent) / 100 * all;
          }
  
          return parseFloat(percent);
      }
  
      return percent == null ? NaN : +percent;
  }
  
  /**
   * (1) Fix rounding error of float numbers.
   * (2) Support return string to avoid scientific notation like '3.5e-7'.
   *
   * @param {number} x
   * @param {number} [precision]
   * @param {boolean} [returnStr]
   * @return {number|string}
   */
  export function round(x, precision, returnStr) {
      if (precision == null) {
          precision = 10;
      }
      // Avoid range error
      precision = Math.min(Math.max(0, precision), 20);
      x = (+x).toFixed(precision);
      return returnStr ? x : +x;
  }
  
  export function asc(arr) {
      arr.sort(function (a, b) {
          return a - b;
      });
      return arr;
  }
  
  /**
   * Get precision
   * @param {number} val
   */
  export function getPrecision(val) {
      val = +val;
      if (isNaN(val)) {
          return 0;
      }
      // It is much faster than methods converting number to string as follows
      //      var tmp = val.toString();
      //      return tmp.length - 1 - tmp.indexOf('.');
      // especially when precision is low
      var e = 1;
      var count = 0;
      while (Math.round(val * e) / e !== val) {
          e *= 10;
          count++;
      }
      return count;
  }
  
  /**
   * @param {string|number} val
   * @return {number}
   */
  export function getPrecisionSafe(val) {
      var str = val.toString();
  
      // Consider scientific notation: '3.4e-12' '3.4e+12'
      var eIndex = str.indexOf('e');
      if (eIndex > 0) {
          var precision = +str.slice(eIndex + 1);
          return precision < 0 ? -precision : 0;
      }
      else {
          var dotIndex = str.indexOf('.');
          return dotIndex < 0 ? 0 : str.length - 1 - dotIndex;
      }
  }
  
  /**
   * Minimal dicernible data precisioin according to a single pixel.
   *
   * @param {Array.<number>} dataExtent
   * @param {Array.<number>} pixelExtent
   * @return {number} precision
   */
  export function getPixelPrecision(dataExtent, pixelExtent) {
      var log = Math.log;
      var LN10 = Math.LN10;
      var dataQuantity = Math.floor(log(dataExtent[1] - dataExtent[0]) / LN10);
      var sizeQuantity = Math.round(log(Math.abs(pixelExtent[1] - pixelExtent[0])) / LN10);
      // toFixed() digits argument must be between 0 and 20.
      var precision = Math.min(Math.max(-dataQuantity + sizeQuantity, 0), 20);
      return !isFinite(precision) ? 20 : precision;
  }
  
  /**
   * Get a data of given precision, assuring the sum of percentages
   * in valueList is 1.
   * The largest remainer method is used.
   * https://en.wikipedia.org/wiki/Largest_remainder_method
   *
   * @param {Array.<number>} valueList a list of all data
   * @param {number} idx index of the data to be processed in valueList
   * @param {number} precision integer number showing digits of precision
   * @return {number} percent ranging from 0 to 100
   */
  export function getPercentWithPrecision(valueList, idx, precision) {
      if (!valueList[idx]) {
          return 0;
      }
  
      var sum = zrUtil.reduce(valueList, function (acc, val) {
          return acc + (isNaN(val) ? 0 : val);
      }, 0);
      if (sum === 0) {
          return 0;
      }
  
      var digits = Math.pow(10, precision);
      var votesPerQuota = zrUtil.map(valueList, function (val) {
          return (isNaN(val) ? 0 : val) / sum * digits * 100;
      });
      var targetSeats = digits * 100;
  
      var seats = zrUtil.map(votesPerQuota, function (votes) {
          // Assign automatic seats.
          return Math.floor(votes);
      });
      var currentSum = zrUtil.reduce(seats, function (acc, val) {
          return acc + val;
      }, 0);
  
      var remainder = zrUtil.map(votesPerQuota, function (votes, idx) {
          return votes - seats[idx];
      });
  
      // Has remainding votes.
      while (currentSum < targetSeats) {
          // Find next largest remainder.
          var max = Number.NEGATIVE_INFINITY;
          var maxId = null;
          for (var i = 0, len = remainder.length; i < len; ++i) {
              if (remainder[i] > max) {
                  max = remainder[i];
                  maxId = i;
              }
          }
  
          // Add a vote to max remainder.
          ++seats[maxId];
          remainder[maxId] = 0;
          ++currentSum;
      }
  
      return seats[idx] / digits;
  }
  
  // Number.MAX_SAFE_INTEGER, ie do not support.
  export var MAX_SAFE_INTEGER = 9007199254740991;
  
  /**
   * To 0 - 2 * PI, considering negative radian.
   * @param {number} radian
   * @return {number}
   */
  export function remRadian(radian) {
      var pi2 = Math.PI * 2;
      return (radian % pi2 + pi2) % pi2;
  }
  
  /**
   * @param {type} radian
   * @return {boolean}
   */
  export function isRadianAroundZero(val) {
      return val > -RADIAN_EPSILON && val < RADIAN_EPSILON;
  }
  
  var TIME_REG = /^(?:(\d{4})(?:[-\/](\d{1,2})(?:[-\/](\d{1,2})(?:[T ](\d{1,2})(?::(\d\d)(?::(\d\d)(?:[.,](\d+))?)?)?(Z|[\+\-]\d\d:?\d\d)?)?)?)?)?$/; // jshint ignore:line
  
  /**
   * @param {string|Date|number} value These values can be accepted:
   *   + An instance of Date, represent a time in its own time zone.
   *   + Or string in a subset of ISO 8601, only including:
   *     + only year, month, date: '2012-03', '2012-03-01', '2012-03-01 05', '2012-03-01 05:06',
   *     + separated with T or space: '2012-03-01T12:22:33.123', '2012-03-01 12:22:33.123',
   *     + time zone: '2012-03-01T12:22:33Z', '2012-03-01T12:22:33+8000', '2012-03-01T12:22:33-05:00',
   *     all of which will be treated as local time if time zone is not specified
   *     (see <https://momentjs.com/>).
   *   + Or other string format, including (all of which will be treated as loacal time):
   *     '2012', '2012-3-1', '2012/3/1', '2012/03/01',
   *     '2009/6/12 2:00', '2009/6/12 2:05:08', '2009/6/12 2:05:08.123'
   *   + a timestamp, which represent a time in UTC.
   * @return {Date} date
   */
  export function parseDate(value) {
      if (value instanceof Date) {
          return value;
      }
      else if (typeof value === 'string') {
          // Different browsers parse date in different way, so we parse it manually.
          // Some other issues:
          // new Date('1970-01-01') is UTC,
          // new Date('1970/01/01') and new Date('1970-1-01') is local.
          // See issue #3623
          var match = TIME_REG.exec(value);
  
          if (!match) {
              // return Invalid Date.
              return new Date(NaN);
          }
  
          // Use local time when no timezone offset specifed.
          if (!match[8]) {
              // match[n] can only be string or undefined.
              // But take care of '12' + 1 => '121'.
              return new Date(
                  +match[1],
                  +(match[2] || 1) - 1,
                  +match[3] || 1,
                  +match[4] || 0,
                  +(match[5] || 0),
                  +match[6] || 0,
                  +match[7] || 0
              );
          }
          // Timezoneoffset of Javascript Date has considered DST (Daylight Saving Time,
          // https://tc39.github.io/ecma262/#sec-daylight-saving-time-adjustment).
          // For example, system timezone is set as "Time Zone: America/Toronto",
          // then these code will get different result:
          // `new Date(1478411999999).getTimezoneOffset();  // get 240`
          // `new Date(1478412000000).getTimezoneOffset();  // get 300`
          // So we should not use `new Date`, but use `Date.UTC`.
          else {
              var hour = +match[4] || 0;
              if (match[8].toUpperCase() !== 'Z') {
                  hour -= match[8].slice(0, 3);
              }
              return new Date(Date.UTC(
                  +match[1],
                  +(match[2] || 1) - 1,
                  +match[3] || 1,
                  hour,
                  +(match[5] || 0),
                  +match[6] || 0,
                  +match[7] || 0
              ));
          }
      }
      else if (value == null) {
          return new Date(NaN);
      }
  
      return new Date(Math.round(value));
  }
  
  /**
   * Quantity of a number. e.g. 0.1, 1, 10, 100
   *
   * @param  {number} val
   * @return {number}
   */
  export function quantity(val) {
      return Math.pow(10, quantityExponent(val));
  }
  
  function quantityExponent(val) {
      return Math.floor(Math.log(val) / Math.LN10);
  }
  
  /**
   * find a “nice” number approximately equal to x. Round the number if round = true,
   * take ceiling if round = false. The primary observation is that the “nicest”
   * numbers in decimal are 1, 2, and 5, and all power-of-ten multiples of these numbers.
   *
   * See "Nice Numbers for Graph Labels" of Graphic Gems.
   *
   * @param  {number} val Non-negative value.
   * @param  {boolean} round
   * @return {number}
   */
  export function nice(val, round) {
      var exponent = quantityExponent(val);
      var exp10 = Math.pow(10, exponent);
      var f = val / exp10; // 1 <= f < 10
      var nf;
      if (round) {
          if (f < 1.5) { nf = 1; }
          else if (f < 2.5) { nf = 2; }
          else if (f < 4) { nf = 3; }
          else if (f < 7) { nf = 5; }
          else { nf = 10; }
      }
      else {
          if (f < 1) { nf = 1; }
          else if (f < 2) { nf = 2; }
          else if (f < 3) { nf = 3; }
          else if (f < 5) { nf = 5; }
          else { nf = 10; }
      }
      val = nf * exp10;
  
      // Fix 3 * 0.1 === 0.30000000000000004 issue (see IEEE 754).
      // 20 is the uppper bound of toFixed.
      return exponent >= -20 ? +val.toFixed(exponent < 0 ? -exponent : 0) : val;
  }
  
  /**
   * Order intervals asc, and split them when overlap.
   * expect(numberUtil.reformIntervals([
   *     {interval: [18, 62], close: [1, 1]},
   *     {interval: [-Infinity, -70], close: [0, 0]},
   *     {interval: [-70, -26], close: [1, 1]},
   *     {interval: [-26, 18], close: [1, 1]},
   *     {interval: [62, 150], close: [1, 1]},
   *     {interval: [106, 150], close: [1, 1]},
   *     {interval: [150, Infinity], close: [0, 0]}
   * ])).toEqual([
   *     {interval: [-Infinity, -70], close: [0, 0]},
   *     {interval: [-70, -26], close: [1, 1]},
   *     {interval: [-26, 18], close: [0, 1]},
   *     {interval: [18, 62], close: [0, 1]},
   *     {interval: [62, 150], close: [0, 1]},
   *     {interval: [150, Infinity], close: [0, 0]}
   * ]);
   * @param {Array.<Object>} list, where `close` mean open or close
   *        of the interval, and Infinity can be used.
   * @return {Array.<Object>} The origin list, which has been reformed.
   */
  export function reformIntervals(list) {
      list.sort(function (a, b) {
          return littleThan(a, b, 0) ? -1 : 1;
      });
  
      var curr = -Infinity;
      var currClose = 1;
      for (var i = 0; i < list.length;) {
          var interval = list[i].interval;
          var close = list[i].close;
  
          for (var lg = 0; lg < 2; lg++) {
              if (interval[lg] <= curr) {
                  interval[lg] = curr;
                  close[lg] = !lg ? 1 - currClose : 1;
              }
              curr = interval[lg];
              currClose = close[lg];
          }
  
          if (interval[0] === interval[1] && close[0] * close[1] !== 1) {
              list.splice(i, 1);
          }
          else {
              i++;
          }
      }
  
      return list;
  
      function littleThan(a, b, lg) {
          return a.interval[lg] < b.interval[lg]
              || (
                  a.interval[lg] === b.interval[lg]
                  && (
                      (a.close[lg] - b.close[lg] === (!lg ? 1 : -1))
                      || (!lg && littleThan(a, b, 1))
                  )
              );
      }
  }
  
  /**
   * parseFloat NaNs numeric-cast false positives (null|true|false|"")
   * ...but misinterprets leading-number strings, particularly hex literals ("0x...")
   * subtraction forces infinities to NaN
   *
   * @param {*} v
   * @return {boolean}
   */
  export function isNumeric(v) {
      return v - parseFloat(v) >= 0;
  }
  