1 /**
  2  * Returns a default number format.
  3  *
  4  * @class Represents a number format, converting between a <tt>number</tt> and a
  5  * <tt>string</tt>. This class allows numbers to be formatted with variable
  6  * precision (both for the integral and fractional part of the number), optional
  7  * thousands grouping, and optional padding. The thousands (",") and decimal
  8  * (".") separator can be customized.
  9  *
 10  * @returns {pv.Format.number} a number format.
 11  */
 12 pv.Format.number = function() {
 13   var mini = 0, // default minimum integer digits
 14       maxi = Infinity, // default maximum integer digits
 15       mins = 0, // mini, including group separators
 16       minf = 0, // default minimum fraction digits
 17       maxf = 0, // default maximum fraction digits
 18       maxk = 1, // 10^maxf
 19       padi = "0", // default integer pad
 20       padf = "0", // default fraction pad
 21       padg = true, // whether group separator affects integer padding
 22       decimal = ".", // default decimal separator
 23       group = ","; // default group separator
 24 
 25   /** @private */
 26   function format(x) {
 27     /* Round the fractional part, and split on decimal separator. */
 28     if (Infinity > maxf) x = Math.round(x * maxk) / maxk;
 29     var s = String(Math.abs(x)).split(".");
 30 
 31     /* Pad, truncate and group the integral part. */
 32     var i = s[0], n = (x < 0) ? "-" : "";
 33     if (i.length > maxi) i = i.substring(i.length - maxi);
 34     if (padg && (i.length < mini)) i = n + new Array(mini - i.length + 1).join(padi) + i;
 35     if (i.length > 3) i = i.replace(/\B(?=(?:\d{3})+(?!\d))/g, group);
 36     if (!padg && (i.length < mins)) i = new Array(mins - i.length + 1).join(padi) + n + i;
 37     s[0] = i;
 38 
 39     /* Pad the fractional part. */
 40     var f = s[1] || "";
 41     if (f.length < minf) s[1] = f + new Array(minf - f.length + 1).join(padf);
 42 
 43     return s.join(decimal);
 44   }
 45 
 46   /**
 47    * @function
 48    * @name pv.Format.number.prototype.format
 49    * @param {number} x
 50    * @returns {string}
 51    */
 52   format.format = format;
 53 
 54   /**
 55    * Parses the specified string as a number. Before parsing, leading and
 56    * trailing padding is removed. Group separators are also removed, and the
 57    * decimal separator is replaced with the standard point ("."). The integer
 58    * part is truncated per the maximum integer digits, and the fraction part is
 59    * rounded per the maximum fraction digits.
 60    *
 61    * @function
 62    * @name pv.Format.number.prototype.parse
 63    * @param {string} x the string to parse.
 64    * @returns {number} the parsed number.
 65    */
 66   format.parse = function(x) {
 67     var re = pv.Format.re;
 68 
 69     /* Remove leading and trailing padding. Split on the decimal separator. */
 70     var s = String(x)
 71         .replace(new RegExp("^(" + re(padi) + ")*"), "")
 72         .replace(new RegExp("(" + re(padf) + ")*$"), "")
 73         .split(decimal);
 74 
 75     /* Remove grouping and truncate the integral part. */
 76     var i = s[0].replace(new RegExp(re(group), "g"), "");
 77     if (i.length > maxi) i = i.substring(i.length - maxi);
 78 
 79     /* Round the fractional part. */
 80     var f = s[1] ? Number("0." + s[1]) : 0;
 81     if (Infinity > maxf) f = Math.round(f * maxk) / maxk;
 82 
 83     return Math.round(i) + f;
 84   };
 85 
 86   /**
 87    * Sets or gets the minimum and maximum number of integer digits. This
 88    * controls the number of decimal digits to display before the decimal
 89    * separator for the integral part of the number. If the number of digits is
 90    * smaller than the minimum, the digits are padded; if the number of digits is
 91    * larger, the digits are truncated, showing only the lower-order digits. The
 92    * default range is [0, Infinity].
 93    *
 94    * <p>If only one argument is specified to this method, this value is used as
 95    * both the minimum and maximum number. If no arguments are specified, a
 96    * two-element array is returned containing the minimum and the maximum.
 97    *
 98    * @function
 99    * @name pv.Format.number.prototype.integerDigits
100    * @param {number} [min] the minimum integer digits.
101    * @param {number} [max] the maximum integer digits.
102    * @returns {pv.Format.number} <tt>this</tt>, or the current integer digits.
103    */
104   format.integerDigits = function(min, max) {
105     if (arguments.length) {
106       mini = Number(min);
107       maxi = (arguments.length > 1) ? Number(max) : mini;
108       mins = mini + Math.floor(mini / 3) * group.length;
109       return this;
110     }
111     return [mini, maxi];
112   };
113 
114   /**
115    * Sets or gets the minimum and maximum number of fraction digits. The
116    * controls the number of decimal digits to display after the decimal
117    * separator for the fractional part of the number. If the number of digits is
118    * smaller than the minimum, the digits are padded; if the number of digits is
119    * larger, the fractional part is rounded, showing only the higher-order
120    * digits. The default range is [0, 0].
121    *
122    * <p>If only one argument is specified to this method, this value is used as
123    * both the minimum and maximum number. If no arguments are specified, a
124    * two-element array is returned containing the minimum and the maximum.
125    *
126    * @function
127    * @name pv.Format.number.prototype.fractionDigits
128    * @param {number} [min] the minimum fraction digits.
129    * @param {number} [max] the maximum fraction digits.
130    * @returns {pv.Format.number} <tt>this</tt>, or the current fraction digits.
131    */
132   format.fractionDigits = function(min, max) {
133     if (arguments.length) {
134       minf = Number(min);
135       maxf = (arguments.length > 1) ? Number(max) : minf;
136       maxk = Math.pow(10, maxf);
137       return this;
138     }
139     return [minf, maxf];
140   };
141 
142   /**
143    * Sets or gets the character used to pad the integer part. The integer pad is
144    * used when the number of integer digits is smaller than the minimum. The
145    * default pad character is "0" (zero).
146    *
147    * @param {string} [x] the new pad character.
148    * @returns {pv.Format.number} <tt>this</tt> or the current pad character.
149    */
150   format.integerPad = function(x) {
151     if (arguments.length) {
152       padi = String(x);
153       padg = /\d/.test(padi);
154       return this;
155     }
156     return padi;
157   };
158 
159   /**
160    * Sets or gets the character used to pad the fration part. The fraction pad
161    * is used when the number of fraction digits is smaller than the minimum. The
162    * default pad character is "0" (zero).
163    *
164    * @param {string} [x] the new pad character.
165    * @returns {pv.Format.number} <tt>this</tt> or the current pad character.
166    */
167   format.fractionPad = function(x) {
168     if (arguments.length) {
169       padf = String(x);
170       return this;
171     }
172     return padf;
173   };
174 
175   /**
176    * Sets or gets the character used as the decimal point, separating the
177    * integer and fraction parts of the number. The default decimal point is ".".
178    *
179    * @param {string} [x] the new decimal separator.
180    * @returns {pv.Format.number} <tt>this</tt> or the current decimal separator.
181    */
182   format.decimal = function(x) {
183     if (arguments.length) {
184       decimal = String(x);
185       return this;
186     }
187     return decimal;
188   };
189 
190   /**
191    * Sets or gets the character used as the group separator, grouping integer
192    * digits by thousands. The default decimal point is ",". Grouping can be
193    * disabled by using "" for the separator.
194    *
195    * @param {string} [x] the new group separator.
196    * @returns {pv.Format.number} <tt>this</tt> or the current group separator.
197    */
198   format.group = function(x) {
199     if (arguments.length) {
200       group = x ? String(x) : "";
201       mins = mini + Math.floor(mini / 3) * group.length;
202       return this;
203     }
204     return group;
205   };
206 
207   return format;
208 };
209