/** * Returns a log scale for the specified domain. The arguments to this * constructor are optional, and equivalent to calling {@link #domain}. * The default domain is [1,10] and the default range is [0,1]. * * @class Represents a log scale. Most commonly, a log scale * represents a 1-dimensional log transformation from a numeric domain of input * data [d0, d1] to a numeric range of * pixels [r0, r1]. The equation for such a * scale is: * *
f(x) = (log(x) - log(d0)) / (log(d1) - * log(d0)) * (r1 - r0) + * r0
* * where log(x) represents the zero-symmetric logarthim of x using * the scale's associated base (default: 10, see {@link pv.logSymmetric}). For * example, a log scale from the domain [1, 100] to range [0, 640]: * *
f(x) = (log(x) - log(1)) / (log(100) - log(1)) * (640 - 0) + 0
* f(x) = log(x) / 2 * 640
* f(x) = log(x) * 320
*
* * Thus, saying * *
    .height(function(d) Math.log(d) * 138.974)
* * is equivalent to * *
    .height(pv.Scale.log(1, 100).range(0, 640))
* * Note that the scale is itself a function, and thus can be used as a property * directly, assuming that the data associated with a mark is a number. While * this is convenient for single-use scales, frequently it is desirable to * define scales globally: * *
var y = pv.Scale.log(1, 100).range(0, 640);
* * The y scale can now be equivalently referenced within a property: * *
    .height(function(d) y(d))
* * Alternatively, if the data are not simple numbers, the appropriate value can * be passed to the y scale (e.g., d.foo). The {@link #by} * method similarly allows the data to be mapped to a numeric value before * performing the log transformation. * * @param {number...} domain... optional domain values. * @extends pv.Scale.quantitative */ pv.Scale.log = function() { var scale = pv.Scale.quantitative(1, 10), b, // logarithm base p, // cached Math.log(b) /** @ignore */ log = function(x) { return Math.log(x) / p; }, /** @ignore */ pow = function(y) { return Math.pow(b, y); }; /** * Returns an array of evenly-spaced, suitably-rounded values in the input * domain. These values are frequently used in conjunction with * {@link pv.Rule} to display tick marks or grid lines. * * @function * @name pv.Scale.log.prototype.ticks * @returns {number[]} an array input domain values to use as ticks. */ scale.ticks = function() { // TODO support non-uniform domains var d = scale.domain(), n = d[0] < 0, i = Math.floor(n ? -log(-d[0]) : log(d[0])), j = Math.ceil(n ? -log(-d[1]) : log(d[1])), ticks = []; if (n) { ticks.push(-pow(-i)); for (; i++ < j;) for (var k = b - 1; k > 0; k--) ticks.push(-pow(-i) * k); } else { for (; i < j; i++) for (var k = 1; k < b; k++) ticks.push(pow(i) * k); ticks.push(pow(i)); } for (i = 0; ticks[i] < d[0]; i++); // strip small values for (j = ticks.length; ticks[j - 1] > d[1]; j--); // strip big values return ticks.slice(i, j); }; /** * Formats the specified tick value using the appropriate precision, assuming * base 10. * * @function * @name pv.Scale.log.prototype.tickFormat * @param {number} t a tick value. * @returns {string} a formatted tick value. */ scale.tickFormat = function(t) { return t.toPrecision(1); }; /** * "Nices" this scale, extending the bounds of the input domain to * evenly-rounded values. This method uses {@link pv.logFloor} and * {@link pv.logCeil}. Nicing is useful if the domain is computed dynamically * from data, and may be irregular. For example, given a domain of * [0.20147987687960267, 0.996679553296417], a call to nice() might * extend the domain to [0.1, 1]. * *

This method must be invoked each time after setting the domain (and * base). * * @function * @name pv.Scale.log.prototype.nice * @returns {pv.Scale.log} this. */ scale.nice = function() { // TODO support non-uniform domains var d = scale.domain(); return scale.domain(pv.logFloor(d[0], b), pv.logCeil(d[1], b)); }; /** * Sets or gets the logarithm base. Defaults to 10. * * @function * @name pv.Scale.log.prototype.base * @param {number} [v] the new base. * @returns {pv.Scale.log} this, or the current base. */ scale.base = function(v) { if (arguments.length) { b = Number(v); p = Math.log(b); scale.transform(log, pow); // update transformed domain return this; } return b; }; scale.domain.apply(scale, arguments); return scale.base(10); };