diff --git a/README.md b/README.md index 625e591..9dc5ad5 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,13 @@ color(20); // "#9a3439" color(50); // "#7b5167" ``` +Or, in shortand: + +```js +var x = d3.scaleLinear([10, 130], [0, 960]); +var color = d3.scaleLinear([10, 100], ["brown", "steelblue"]); +``` + # continuous.invert(value) [<>](https://github.com/d3/d3-scale/blob/master/src/continuous.js "Source") Given a *value* from the [range](#continuous_range), returns the corresponding value from the [domain](#continuous_domain). Inversion is useful for interaction, say to determine the data value corresponding to the position of the mouse. For example, to invert a position encoding: @@ -208,6 +215,8 @@ ticks.map(tickFormat); // ["-100%", "-50%", "+0%", "+50%", "+100%"] If *specifier* uses the format type `s`, the scale will return a [SI-prefix format](https://github.com/d3/d3-format#locale_formatPrefix) based on the largest value in the domain. If the *specifier* already specifies a precision, this method is equivalent to [*locale*.format](https://github.com/d3/d3-format#locale_format). +See also [d3.tickFormat](#tickFormat). + # continuous.nice([count]) [<>](https://github.com/d3/d3-scale/blob/master/src/nice.js "Source") Extends the [domain](#continuous_domain) so that it starts and ends on nice round values. This method typically modifies the scale’s domain, and may only extend the bounds to the nearest round value. An optional tick *count* argument allows greater control over the step size used to extend the bounds, guaranteeing that the returned [ticks](#continuous_ticks) will exactly cover the domain. Nicing is useful if the domain is computed from data, say using [extent](https://github.com/d3/d3-array#extent), and may be irregular. For example, for a domain of [0.201479…, 0.996679…], a nice domain might be [0.2, 1.0]. If the domain has more than two values, nicing the domain only affects the first and last value. See also d3-array’s [tickStep](https://github.com/d3/d3-array#tickStep). @@ -218,19 +227,33 @@ Nicing a scale only modifies the current domain; it does not automatically nice Returns an exact copy of this scale. Changes to this scale will not affect the returned scale, and vice versa. +# d3.tickFormat(start, stop, count[, specifier]) [<>](https://github.com/d3/d3-scale/blob/master/src/tickFormat.js "Source") + +Returns a [number format](https://github.com/d3/d3-format) function suitable for displaying a tick value, automatically computing the appropriate precision based on the fixed interval between tick values, as determined by [d3.tickStep](https://github.com/d3/d3-array/blob/master/README.md#tickStep). + +An optional *specifier* allows a [custom format](https://github.com/d3/d3-format#locale_format) where the precision of the format is automatically set by the scale as appropriate for the tick interval. For example, to format percentage change, you might say: + +```js +var tickFormat = d3.tickFormat(-1, 1, 5, "+%"); + +tickFormat(-0.5); // "-50%" +``` + +If *specifier* uses the format type `s`, the scale will return a [SI-prefix format](https://github.com/d3/d3-format#locale_formatPrefix) based on the larger absolute value of *start* and *stop*. If the *specifier* already specifies a precision, this method is equivalent to [*locale*.format](https://github.com/d3/d3-format#locale_format). + #### Linear Scales -# d3.scaleLinear() [<>](https://github.com/d3/d3-scale/blob/master/src/linear.js "Source") +# d3.scaleLinear([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/linear.js "Source") -Constructs a new [continuous scale](#continuous-scales) with the unit [domain](#continuous_domain) [0, 1], the unit [range](#continuous_range) [0, 1], the [default](https://github.com/d3/d3-interpolate#interpolate) [interpolator](#continuous_interpolate) and [clamping](#continuous_clamp) disabled. Linear scales are a good default choice for continuous quantitative data because they preserve proportional differences. Each range value *y* can be expressed as a function of the domain value *x*: *y* = *mx* + *b*. +Constructs a new [continuous scale](#continuous-scales) with the specified [domain](#continuous_domain) and [range](#continuous_range), the [default](https://github.com/d3/d3-interpolate#interpolate) [interpolator](#continuous_interpolate) and [clamping](#continuous_clamp) disabled. If either *domain* or *range* are not specified, each defaults to [0, 1]. Linear scales are a good default choice for continuous quantitative data because they preserve proportional differences. Each range value *y* can be expressed as a function of the domain value *x*: *y* = *mx* + *b*. #### Power Scales Power scales are similar to [linear scales](#linear-scales), except an exponential transform is applied to the input domain value before the output range value is computed. Each range value *y* can be expressed as a function of the domain value *x*: *y* = *mx^k* + *b*, where *k* is the [exponent](#pow_exponent) value. Power scales also support negative domain values, in which case the input value and the resulting output value are multiplied by -1. -# d3.scalePow() [<>](https://github.com/d3/d3-scale/blob/master/src/pow.js "Source") +# d3.scalePow([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/pow.js "Source") -Constructs a new [continuous scale](#continuous-scales) with the unit [domain](#continuous_domain) [0, 1], the unit [range](#continuous_range) [0, 1], the [exponent](#pow_exponent) 1, the [default](https://github.com/d3/d3-interpolate#interpolate) [interpolator](#continuous_interpolate) and [clamping](#continuous_clamp) disabled. (Note that this is effectively a [linear](#linear-scales) scale until you set a different exponent.) +Constructs a new [continuous scale](#continuous-scales) with the specified [domain](#continuous_domain) and [range](#continuous_range), the [exponent](#pow_exponent) 1, the [default](https://github.com/d3/d3-interpolate#interpolate) [interpolator](#continuous_interpolate) and [clamping](#continuous_clamp) disabled. If either *domain* or *range* are not specified, each defaults to [0, 1]. (Note that this is effectively a [linear](#linear-scales) scale until you set a different exponent.) # pow(value) [<>](https://github.com/d3/d3-scale/blob/master/src/pow.js "Source") @@ -280,9 +303,9 @@ See [*continuous*.nice](#continuous_nice). See [*continuous*.copy](#continuous_copy). -# d3.scaleSqrt() [<>](https://github.com/d3/d3-scale/blob/master/src/pow.js "Source") +# d3.scaleSqrt([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/pow.js "Source") -Constructs a new [continuous](#continuous-scales) [power scale](#power-scales) with the unit [domain](#continuous_domain) [0, 1], the unit [range](#continuous_range) [0, 1], the [exponent](#pow_exponent) 0.5, the [default](https://github.com/d3/d3-interpolate#interpolate) [interpolator](#continuous_interpolate) and [clamping](#continuous_clamp) disabled. This is a convenience method equivalent to `d3.scalePow().exponent(0.5)`. +Constructs a new [continuous](#continuous-scales) [power scale](#power-scales) with the specified [domain](#continuous_domain) and [range](#continuous_range), the [exponent](#pow_exponent) 0.5, the [default](https://github.com/d3/d3-interpolate#interpolate) [interpolator](#continuous_interpolate) and [clamping](#continuous_clamp) disabled. If either *domain* or *range* are not specified, each defaults to [0, 1]. This is a convenience method equivalent to `d3.scalePow(…).exponent(0.5)`. #### Log Scales @@ -290,9 +313,9 @@ Log scales are similar to [linear scales](#linear-scales), except a logarithmic As log(0) = -∞, a log scale domain must be **strictly-positive or strictly-negative**; the domain must not include or cross zero. A log scale with a positive domain has a well-defined behavior for positive values, and a log scale with a negative domain has a well-defined behavior for negative values. (For a negative domain, input and output values are implicitly multiplied by -1.) The behavior of the scale is undefined if you pass a negative value to a log scale with a positive domain or vice versa. -# d3.scaleLog() [<>](https://github.com/d3/d3-scale/blob/master/src/log.js "Source") +# d3.scaleLog([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/log.js "Source") -Constructs a new [continuous scale](#continuous-scales) with the [domain](#log_domain) [1, 10], the unit [range](#log_range) [0, 1], the [base](#log_base) 10, the [default](https://github.com/d3/d3-interpolate#interpolate) [interpolator](#log_interpolate) and [clamping](#log_clamp) disabled. +Constructs a new [continuous scale](#continuous-scales) with the specified [domain](#log_domain) and [range](#log_range), the [base](#log_base) 10, the [default](https://github.com/d3/d3-interpolate#interpolate) [interpolator](#log_interpolate) and [clamping](#log_clamp) disabled. If *domain* is not specified, it defaults to [1, 10]. If *range* is not specified, it defaults to [0, 1]. # log(value) [<>](https://github.com/d3/d3-scale/blob/master/src/log.js "Source") @@ -342,13 +365,25 @@ Like [*continuous*.nice](#continuous_nice), except extends the domain to integer See [*continuous*.copy](#continuous_copy). +#### Symlog Scales + +See “A bi-symmetric log transformation for wide-range data” by Webber for more. + +# d3.scaleSymlog([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/symlog.js "Source") + +Constructs a new [continuous scale](#continuous-scales) with the specified [domain](#continuous_domain) and [range](#continuous_range), the [constant](#symlog_constant) 1, the [default](https://github.com/d3/d3-interpolate#interpolate) [interpolator](#continuous_interpolate) and [clamping](#continuous_clamp) disabled. If *domain* is not specified, it defaults to [0, 1]. If *range* is not specified, it defaults to [0, 1]. + +# symlog.constant([constant]) [<>](https://github.com/d3/d3-scale/blob/master/src/symlog.js "Source") + +If *constant* is specified, sets the symlog constant to the specified number and returns this scale; otherwise returns the current value of the symlog constant, which defaults to 1. See “A bi-symmetric log transformation for wide-range data” by Webber for more. + #### Identity Scales Identity scales are a special case of [linear scales](#linear-scales) where the domain and range are identical; the scale and its invert method are thus the identity function. These scales are occasionally useful when working with pixel coordinates, say in conjunction with an axis or brush. Identity scales do not support [rangeRound](#continuous_rangeRound), [clamp](#continuous_clamp) or [interpolate](#continuous_interpolate). -# d3.scaleIdentity() [<>](https://github.com/d3/d3-scale/blob/master/src/identity.js "Source") +# d3.scaleIdentity([range]) [<>](https://github.com/d3/d3-scale/blob/master/src/identity.js "Source") -Constructs a new identity scale with the unit [domain](#continuous_domain) [0, 1] and the unit [range](#continuous_range) [0, 1]. +Constructs a new identity scale with the specified [domain](#continuous_domain) and [range](#continuous_range). If *range* is not specified, it defaults to [0, 1]. #### Time Scales @@ -369,9 +404,9 @@ x.invert(640); // Sat Jan 01 2000 16:00:00 GMT-0800 (PST) For a valid value *y* in the range, time(time.invert(y)) equals *y*; similarly, for a valid value *x* in the domain, time.invert(time(x)) equals *x*. The invert method is useful for interaction, say to determine the value in the domain that corresponds to the pixel location under the mouse. -# d3.scaleTime() [<>](https://github.com/d3/d3-scale/blob/master/src/time.js "Source") +# d3.scaleTime([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/time.js "Source") -Constructs a new time scale with the [domain](#time_domain) [2000-01-01, 2000-01-02], the unit [range](#time_range) [0, 1], the [default](https://github.com/d3/d3-interpolate#interpolate) [interpolator](#time_interpolate) and [clamping](#time_clamp) disabled. +Constructs a new time scale with the specified [domain](#time_domain) and [range](#time_range), the [default](https://github.com/d3/d3-interpolate#interpolate) [interpolator](#time_interpolate) and [clamping](#time_clamp) disabled. If *domain* is not specified, it defaults to [2000-01-01, 2000-01-02]. If *range* is not specified, it defaults to [0, 1]. # time(value) [<>](https://github.com/d3/d3-scale/blob/master/src/time.js "Source") @@ -486,7 +521,7 @@ An optional tick *count* argument allows greater control over the step size used Nicing is useful if the domain is computed from data, say using [extent](https://github.com/d3/d3-array#extent), and may be irregular. For example, for a domain of [2009-07-13T00:02, 2009-07-13T23:48], the nice domain is [2009-07-13, 2009-07-14]. If the domain has more than two values, nicing the domain only affects the first and last value. -# d3.scaleUtc() [<>](https://github.com/d3/d3-scale/blob/master/src/utcTime.js "Source") +# d3.scaleUtc([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/utcTime.js "Source") Equivalent to [time](#time), but the returned time scale operates in [Coordinated Universal Time](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) rather than local time. @@ -494,9 +529,9 @@ Equivalent to [time](#time), but the returned time scale operates in [Coordinate Sequential scales, like [diverging scales](#diverging-scales), are similar to [continuous scales](#continuous-scales) in that they map a continuous, numeric input domain to a continuous output range. However, unlike continuous scales, the output range of a sequential scale is fixed by its interpolator and not configurable. These scales do not expose [invert](#continuous_invert), [range](#continuous_range), [rangeRound](#continuous_rangeRound) and [interpolate](#continuous_interpolate) methods. -# d3.scaleSequential(interpolator) [<>](https://github.com/d3/d3-scale/blob/master/src/sequential.js "Source") +# d3.scaleSequential([[domain, ]interpolator]) [<>](https://github.com/d3/d3-scale/blob/master/src/sequential.js "Source") -Constructs a new sequential scale with the given [*interpolator*](#sequential_interpolator) function. When the scale is [applied](#_sequential), the interpolator will be invoked with a value typically in the range [0, 1], where 0 represents the minimum value and 1 represents the maximum value. For example, to implement the ill-advised [HSL](https://github.com/d3/d3-color#hsl) rainbow scale: +Constructs a new sequential scale with the specified [*domain*](#sequential_domain) and [*interpolator*](#sequential_interpolator) function. If *domain* is not specified, it defaults to [0, 1]. If *interpolator* is not specified, it defaults to the identity function. When the scale is [applied](#_sequential), the interpolator will be invoked with a value typically in the range [0, 1], where 0 represents the minimum value and 1 represents the maximum value. For example, to implement the ill-advised [HSL](https://github.com/d3/d3-color#hsl) rainbow scale: ```js var rainbow = d3.scaleSequential(function(t) { @@ -530,13 +565,33 @@ If *interpolator* is specified, sets the scale’s interpolator to the specified See [*continuous*.copy](#continuous_copy). +# d3.scaleSequentialLog([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/sequential.js "Source") + +… + +# d3.scaleSequentialPow([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/sequential.js "Source") + +… + +# d3.scaleSequentialSqrt([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/sequential.js "Source") + +… + +# d3.scaleSequentialSymlog([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/sequential.js "Source") + +… + +# d3.scaleSequentialQuantile([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/sequentialQuantile.js "Source") + +… + ### Diverging Scales Diverging scales, like [sequential scales](#sequential-scales), are similar to [continuous scales](#continuous-scales) in that they map a continuous, numeric input domain to a continuous output range. However, unlike continuous scales, the output range of a diverging scale is fixed by its interpolator and not configurable. These scales do not expose [invert](#continuous_invert), [range](#continuous_range), [rangeRound](#continuous_rangeRound) and [interpolate](#continuous_interpolate) methods. -# d3.scaleDiverging(interpolator) [<>](https://github.com/d3/d3-scale/blob/master/src/diverging.js "Source") +# d3.scaleDiverging([[domain, ]interpolator]) [<>](https://github.com/d3/d3-scale/blob/master/src/diverging.js "Source") -Constructs a new diverging scale with the given [*interpolator*](#diverging_interpolator) function. When the scale is [applied](#_diverging), the interpolator will be invoked with a value typically in the range [0, 1], where 0 represents the extreme negative value, 0.5 represents the neutral value, and 1 represents the extreme positive value. For example, using [d3.interpolateSpectral](https://github.com/d3/d3-scale-chromatic/blob/master/README.md#interpolateSpectral): +Constructs a new diverging scale with the specified [*domain*](#diverging_domain) and [*interpolator*](#diverging_interpolator) function. If *domain* is not specified, it defaults to [0, 1]. If *interpolator* is not specified, it defaults to the identity function. When the scale is [applied](#_diverging), the interpolator will be invoked with a value typically in the range [0, 1], where 0 represents the extreme negative value, 0.5 represents the neutral value, and 1 represents the extreme positive value. For example, using [d3.interpolateSpectral](https://github.com/d3/d3-scale-chromatic/blob/master/README.md#interpolateSpectral): ```js var spectral = d3.scaleDiverging(d3.interpolateSpectral); @@ -562,13 +617,25 @@ If *interpolator* is specified, sets the scale’s interpolator to the specified See [*continuous*.copy](#continuous_copy). +# d3.scaleDivergingLog([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/diverging.js "Source") + +… + +# d3.scaleDivergingPow([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/diverging.js "Source") + +… + +# d3.scaleDivergingSqrt([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/diverging.js "Source") + +… + ### Quantize Scales Quantize scales are similar to [linear scales](#linear-scales), except they use a discrete rather than continuous range. The continuous input domain is divided into uniform segments based on the number of values in (*i.e.*, the cardinality of) the output range. Each range value *y* can be expressed as a quantized linear function of the domain value *x*: *y* = *m round(x)* + *b*. See [bl.ocks.org/4060606](http://bl.ocks.org/mbostock/4060606) for an example. -# d3.scaleQuantize() [<>](https://github.com/d3/d3-scale/blob/master/src/quantize.js "Source") +# d3.scaleQuantize([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/quantize.js "Source") -Constructs a new quantize scale with the unit [domain](#quantize_domain) [0, 1] and the unit [range](#quantize_range) [0, 1]. Thus, the default quantize scale is equivalent to the [Math.round](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Math/round) function. +Constructs a new quantize scale with the specified [*domain*](#quantize_domain) and [*range*](#quantize_range). If either *domain* or *range* is not specified, each defaults to [0, 1]. Thus, the default quantize scale is equivalent to the [Math.round](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Math/round) function. # quantize(value) [<>](https://github.com/d3/d3-scale/blob/master/src/quantize.js "Source") @@ -609,7 +676,7 @@ width.invertExtent(2); // [40, 70] # quantize.domain([domain]) [<>](https://github.com/d3/d3-scale/blob/master/src/quantize.js "Source") -If *domain* is specified, sets the scale’s domain to the specified two-element array of numbers. If the elements in the given array are not numbers, they will be coerced to numbers. If *domain* is not specified, returns the scale’s current domain. +If *domain* is specified, sets the scale’s domain to the specified two-element array of numbers. If the elements in the given array are not numbers, they will be coerced to numbers. The numbers must be in ascending order or the behavior of the scale is undefined. If *domain* is not specified, returns the scale’s current domain. # quantize.range([range]) [<>](https://github.com/d3/d3-scale/blob/master/src/quantize.js "Source") @@ -635,9 +702,9 @@ Returns an exact copy of this scale. Changes to this scale will not affect the r Quantile scales map a sampled input domain to a discrete range. The domain is considered continuous and thus the scale will accept any reasonable input value; however, the domain is specified as a discrete set of sample values. The number of values in (the cardinality of) the output range determines the number of quantiles that will be computed from the domain. To compute the quantiles, the domain is sorted, and treated as a [population of discrete values](https://en.wikipedia.org/wiki/Quantile#Quantiles_of_a_population); see d3-array’s [quantile](https://github.com/d3/d3-array#quantile). See [bl.ocks.org/8ca036b3505121279daf](http://bl.ocks.org/mbostock/8ca036b3505121279daf) for an example. -# d3.scaleQuantile() [<>](https://github.com/d3/d3-scale/blob/master/src/quantile.js "Source") +# d3.scaleQuantile([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/quantile.js "Source") -Constructs a new quantile scale with an empty [domain](#quantile_domain) and an empty [range](#quantile_range). The quantile scale is invalid until both a domain and range are specified. +Constructs a new quantile scale with the specified [*domain*](#quantile_domain) and [*range*](#quantile_range). If either *domain* or *range* is not specified, each defaults to the empty array. The quantile scale is invalid until both a domain and range are specified. # quantile(value) [<>](https://github.com/d3/d3-scale/blob/master/src/quantile.js "Source") @@ -667,9 +734,9 @@ Returns an exact copy of this scale. Changes to this scale will not affect the r Threshold scales are similar to [quantize scales](#quantize-scales), except they allow you to map arbitrary subsets of the domain to discrete values in the range. The input domain is still continuous, and divided into slices based on a set of threshold values. See [bl.ocks.org/3306362](http://bl.ocks.org/mbostock/3306362) for an example. -# d3.scaleThreshold() [<>](https://github.com/d3/d3-scale/blob/master/src/threshold.js "Source") +# d3.scaleThreshold([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/threshold.js "Source") -Constructs a new threshold scale with the default [domain](#threshold_domain) [0.5] and the default [range](#threshold_range) [0, 1]. Thus, the default threshold scale is equivalent to the [Math.round](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Math/round) function for numbers; for example threshold(0.49) returns 0, and threshold(0.51) returns 1. +Constructs a new threshold scale with the specified [*domain*](#threshold_domain) and [*range*](#threshold_range). It *domain* is not specified, it defaulst to [0.5]. If *range* is not specified, it defaults to [0, 1]. Thus, the default threshold scale is equivalent to the [Math.round](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Math/round) function for numbers; for example threshold(0.49) returns 0, and threshold(0.51) returns 1. # threshold(value) [<>](https://github.com/d3/d3-scale/blob/master/src/threshold.js "Source") @@ -703,7 +770,7 @@ color.invertExtent("green"); // [1, undefined] # threshold.domain([domain]) [<>](https://github.com/d3/d3-scale/blob/master/src/threshold.js "Source") -If *domain* is specified, sets the scale’s domain to the specified array of values. The values must be in sorted ascending order, or the behavior of the scale is undefined. The values are typically numbers, but any naturally ordered values (such as strings) will work; a threshold scale can be used to encode any type that is ordered. If the number of values in the scale’s range is N+1, the number of values in the scale’s domain must be N. If there are fewer than N elements in the domain, the additional values in the range are ignored. If there are more than N elements in the domain, the scale may return undefined for some inputs. If *domain* is not specified, returns the scale’s current domain. +If *domain* is specified, sets the scale’s domain to the specified array of values. The values must be in ascending order or the behavior of the scale is undefined. The values are typically numbers, but any naturally ordered values (such as strings) will work; a threshold scale can be used to encode any type that is ordered. If the number of values in the scale’s range is N+1, the number of values in the scale’s domain must be N. If there are fewer than N elements in the domain, the additional values in the range are ignored. If there are more than N elements in the domain, the scale may return undefined for some inputs. If *domain* is not specified, returns the scale’s current domain. # threshold.range([range]) [<>](https://github.com/d3/d3-scale/blob/master/src/threshold.js "Source") @@ -717,9 +784,9 @@ Returns an exact copy of this scale. Changes to this scale will not affect the r Unlike [continuous scales](#continuous-scales), ordinal scales have a discrete domain and range. For example, an ordinal scale might map a set of named categories to a set of colors, or determine the horizontal positions of columns in a column chart. -# d3.scaleOrdinal([range]) [<>](https://github.com/d3/d3-scale/blob/master/src/ordinal.js "Source") +# d3.scaleOrdinal([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/ordinal.js "Source") -Constructs a new ordinal scale with an empty [domain](#ordinal_domain) and the specified [*range*](#ordinal_range). If a *range* is not specified, it defaults to the empty array; an ordinal scale always returns undefined until a non-empty range is defined. +Constructs a new ordinal scale with the specified [*domain*](#ordinal_domain) and [*range*](#ordinal_range). If *domain* is not specified, it defaults to the empty array. If *range* is not specified, it defaults to the empty array; an ordinal scale always returns undefined until a non-empty range is defined. # ordinal(value) [<>](https://github.com/d3/d3-scale/blob/master/src/ordinal.js "Source") @@ -753,9 +820,9 @@ Band scales are like [ordinal scales](#ordinal-scales) except the output range i band -# d3.scaleBand() [<>](https://github.com/d3/d3-scale/blob/master/src/band.js "Source") +# d3.scaleBand([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/band.js "Source") -Constructs a new band scale with the empty [domain](#band_domain), the unit [range](#band_range) [0, 1], no [padding](#band_padding), no [rounding](#band_round) and center [alignment](#band_align). +Constructs a new band scale with the specified [*domain*](#band_domain) and [*range*](#band_range), no [padding](#band_padding), no [rounding](#band_round) and center [alignment](#band_align). If *domain* is not specified, it defaults to the empty domain. If *range* is not specified, it defaults to the unit range [0, 1]. # band(*value*) [<>](https://github.com/d3/d3-scale/blob/master/src/band.js "Source") @@ -787,11 +854,11 @@ If *round* is specified, enables or disables rounding accordingly. If rounding i # band.paddingInner([padding]) [<>](https://github.com/d3/d3-scale/blob/master/src/band.js "Source") -If *padding* is specified, sets the inner padding to the specified value which must be in the range [0, 1]. If *padding* is not specified, returns the current inner padding which defaults to 0. The inner padding determines the ratio of the range that is reserved for blank space between bands. +If *padding* is specified, sets the inner padding to the specified number which must be less than or equal to 1. If *padding* is not specified, returns the current inner padding which defaults to 0. The inner padding specifies the proportion of the range that is reserved for blank space between bands; a value of 0 means no blank space between bands, and a value of 1 means a [bandwidth](#band_bandwidth) of zero. # band.paddingOuter([padding]) [<>](https://github.com/d3/d3-scale/blob/master/src/band.js "Source") -If *padding* is specified, sets the outer padding to the specified value which must be in the range [0, 1]. If *padding* is not specified, returns the current outer padding which defaults to 0. The outer padding determines the ratio of the range that is reserved for blank space before the first band and after the last band. +If *padding* is specified, sets the outer padding to the specified number which is typically in the range [0, 1]. If *padding* is not specified, returns the current outer padding which defaults to 0. The outer padding specifies the amount of blank space, in terms of multiples of the [step](#band_step), to reserve before the first band and after the last band. # band.padding([padding]) [<>](https://github.com/d3/d3-scale/blob/master/src/band.js "Source") @@ -799,7 +866,7 @@ A convenience method for setting the [inner](#band_paddingInner) and [outer](#ba # band.align([align]) [<>](https://github.com/d3/d3-scale/blob/master/src/band.js "Source") -If *align* is specified, sets the alignment to the specified value which must be in the range [0, 1]. If *align* is not specified, returns the current alignment which defaults to 0.5. The alignment determines how any leftover unused space in the range is distributed. A value of 0.5 indicates that the leftover space should be equally distributed before the first band and after the last band; *i.e.*, the bands should be centered within the range. A value of 0 or 1 may be used to shift the bands to one side, say to position them adjacent to an axis. +If *align* is specified, sets the alignment to the specified value which must be in the range [0, 1]. If *align* is not specified, returns the current alignment which defaults to 0.5. The alignment specifies how any leftover unused space in the range is distributed. A value of 0.5 indicates that the leftover space should be equally distributed before the first band and after the last band; *i.e.*, the bands should be centered within the range. A value of 0 or 1 may be used to shift the bands to one side, say to position them adjacent to an axis. # band.bandwidth() [<>](https://github.com/d3/d3-scale/blob/master/src/band.js "Source") @@ -819,9 +886,9 @@ Point scales are a variant of [band scales](#band-scales) with the bandwidth fix point -# d3.scalePoint() [<>](https://github.com/d3/d3-scale/blob/master/src/band.js "Source") +# d3.scalePoint([[domain, ]range]) [<>](https://github.com/d3/d3-scale/blob/master/src/band.js "Source") -Constructs a new point scale with the empty [domain](#point_domain), the unit [range](#point_range) [0, 1], no [padding](#point_padding), no [rounding](#point_round) and center [alignment](#point_align). +Constructs a new point scale with the specified [*domain*](#point_domain) and [*range*](#point_range), no [padding](#point_padding), no [rounding](#point_round) and center [alignment](#point_align). If *domain* is not specified, it defaults to the empty domain. If *range* is not specified, it defaults to the unit range [0, 1]. # point(*value*) [<>](https://github.com/d3/d3-scale/blob/master/src/band.js "Source") @@ -853,11 +920,11 @@ If *round* is specified, enables or disables rounding accordingly. If rounding i # point.padding([padding]) [<>](https://github.com/d3/d3-scale/blob/master/src/band.js "Source") -If *padding* is specified, sets the outer padding to the specified value which must be in the range [0, 1]. If *padding* is not specified, returns the current outer padding which defaults to 0. The outer padding determines the ratio of the range that is reserved for blank space before the first point and after the last point. Equivalent to [*band*.paddingOuter](#band_paddingOuter). +If *padding* is specified, sets the outer padding to the specified number which is typically in the range [0, 1]. If *padding* is not specified, returns the current outer padding which defaults to 0. The outer padding specifies the amount of blank space, in terms of multiples of the [step](#band_step), to reserve before the first point and after the last point. Equivalent to [*band*.paddingOuter](#band_paddingOuter). # point.align([align]) [<>](https://github.com/d3/d3-scale/blob/master/src/band.js "Source") -If *align* is specified, sets the alignment to the specified value which must be in the range [0, 1]. If *align* is not specified, returns the current alignment which defaults to 0.5. The alignment determines how any leftover unused space in the range is distributed. A value of 0.5 indicates that the leftover space should be equally distributed before the first point and after the last point; *i.e.*, the points should be centered within the range. A value of 0 or 1 may be used to shift the points to one side, say to position them adjacent to an axis. +If *align* is specified, sets the alignment to the specified value which must be in the range [0, 1]. If *align* is not specified, returns the current alignment which defaults to 0.5. The alignment specifies how any leftover unused space in the range is distributed. A value of 0.5 indicates that the leftover space should be equally distributed before the first point and after the last point; *i.e.*, the points should be centered within the range. A value of 0 or 1 may be used to shift the points to one side, say to position them adjacent to an axis. # point.bandwidth() [<>](https://github.com/d3/d3-scale/blob/master/src/band.js "Source") diff --git a/src/band.js b/src/band.js index 05acdde..89b74e4 100644 --- a/src/band.js +++ b/src/band.js @@ -1,4 +1,5 @@ import {range as sequence} from "d3-array"; +import {initRange} from "./init"; import ordinal from "./ordinal"; export default function band() { @@ -54,15 +55,15 @@ export default function band() { }; scale.padding = function(_) { - return arguments.length ? (paddingInner = paddingOuter = Math.max(0, Math.min(1, _)), rescale()) : paddingInner; + return arguments.length ? (paddingInner = Math.min(1, paddingOuter = +_), rescale()) : paddingInner; }; scale.paddingInner = function(_) { - return arguments.length ? (paddingInner = Math.max(0, Math.min(1, _)), rescale()) : paddingInner; + return arguments.length ? (paddingInner = Math.min(1, _), rescale()) : paddingInner; }; scale.paddingOuter = function(_) { - return arguments.length ? (paddingOuter = Math.max(0, Math.min(1, _)), rescale()) : paddingOuter; + return arguments.length ? (paddingOuter = +_, rescale()) : paddingOuter; }; scale.align = function(_) { @@ -70,16 +71,14 @@ export default function band() { }; scale.copy = function() { - return band() - .domain(domain()) - .range(range) + return band(domain(), range) .round(round) .paddingInner(paddingInner) .paddingOuter(paddingOuter) .align(align); }; - return rescale(); + return initRange.apply(rescale(), arguments); } function pointish(scale) { @@ -97,5 +96,5 @@ function pointish(scale) { } export function point() { - return pointish(band().paddingInner(1)); + return pointish(band.apply(null, arguments).paddingInner(1)); } diff --git a/src/continuous.js b/src/continuous.js index 545c8c9..3cd4016 100644 --- a/src/continuous.js +++ b/src/continuous.js @@ -1,39 +1,37 @@ import {bisect} from "d3-array"; -import {interpolate as interpolateValue, interpolateRound} from "d3-interpolate"; +import {interpolate as interpolateValue, interpolateNumber, interpolateRound} from "d3-interpolate"; import {map, slice} from "./array"; import constant from "./constant"; import number from "./number"; var unit = [0, 1]; -export function deinterpolateLinear(a, b) { +export function identity(x) { + return x; +} + +function normalize(a, b) { return (b -= (a = +a)) ? function(x) { return (x - a) / b; } : constant(b); } -function deinterpolateClamp(deinterpolate) { - return function(a, b) { - var d = deinterpolate(a = +a, b = +b); - return function(x) { return x <= a ? 0 : x >= b ? 1 : d(x); }; - }; -} - -function reinterpolateClamp(reinterpolate) { - return function(a, b) { - var r = reinterpolate(a = +a, b = +b); - return function(t) { return t <= 0 ? a : t >= 1 ? b : r(t); }; - }; +function clamper(domain) { + var a = domain[0], b = domain[domain.length - 1], t; + if (a > b) t = a, a = b, b = t; + return function(x) { return Math.max(a, Math.min(b, x)); }; } -function bimap(domain, range, deinterpolate, reinterpolate) { +// normalize(a, b)(x) takes a domain value x in [a,b] and returns the corresponding parameter t in [0,1]. +// interpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding range value x in [a,b]. +function bimap(domain, range, interpolate) { var d0 = domain[0], d1 = domain[1], r0 = range[0], r1 = range[1]; - if (d1 < d0) d0 = deinterpolate(d1, d0), r0 = reinterpolate(r1, r0); - else d0 = deinterpolate(d0, d1), r0 = reinterpolate(r0, r1); + if (d1 < d0) d0 = normalize(d1, d0), r0 = interpolate(r1, r0); + else d0 = normalize(d0, d1), r0 = interpolate(r0, r1); return function(x) { return r0(d0(x)); }; } -function polymap(domain, range, deinterpolate, reinterpolate) { +function polymap(domain, range, interpolate) { var j = Math.min(domain.length, range.length) - 1, d = new Array(j), r = new Array(j), @@ -46,8 +44,8 @@ function polymap(domain, range, deinterpolate, reinterpolate) { } while (++i < j) { - d[i] = deinterpolate(domain[i], domain[i + 1]); - r[i] = reinterpolate(range[i], range[i + 1]); + d[i] = normalize(domain[i], domain[i + 1]); + r[i] = interpolate(range[i], range[i + 1]); } return function(x) { @@ -61,16 +59,18 @@ export function copy(source, target) { .domain(source.domain()) .range(source.range()) .interpolate(source.interpolate()) - .clamp(source.clamp()); + .clamp(source.clamp()) + .unknown(source.unknown()); } -// deinterpolate(a, b)(x) takes a domain value x in [a,b] and returns the corresponding parameter t in [0,1]. -// reinterpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding domain value x in [a,b]. -export default function continuous(deinterpolate, reinterpolate) { +export function transformer() { var domain = unit, range = unit, interpolate = interpolateValue, - clamp = false, + transform, + untransform, + unknown, + clamp = identity, piecewise, output, input; @@ -82,15 +82,15 @@ export default function continuous(deinterpolate, reinterpolate) { } function scale(x) { - return (output || (output = piecewise(domain, range, clamp ? deinterpolateClamp(deinterpolate) : deinterpolate, interpolate)))(+x); + return isNaN(x = +x) ? unknown : (output || (output = piecewise(domain.map(transform), range, interpolate)))(transform(clamp(x))); } scale.invert = function(y) { - return (input || (input = piecewise(range, domain, deinterpolateLinear, clamp ? reinterpolateClamp(reinterpolate) : reinterpolate)))(+y); + return clamp(untransform((input || (input = piecewise(range, domain.map(transform), interpolateNumber)))(y))); }; scale.domain = function(_) { - return arguments.length ? (domain = map.call(_, number), rescale()) : domain.slice(); + return arguments.length ? (domain = map.call(_, number), clamp === identity || (clamp = clamper(domain)), rescale()) : domain.slice(); }; scale.range = function(_) { @@ -102,12 +102,23 @@ export default function continuous(deinterpolate, reinterpolate) { }; scale.clamp = function(_) { - return arguments.length ? (clamp = !!_, rescale()) : clamp; + return arguments.length ? (clamp = _ ? clamper(domain) : identity, scale) : clamp !== identity; }; scale.interpolate = function(_) { return arguments.length ? (interpolate = _, rescale()) : interpolate; }; - return rescale(); + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : unknown; + }; + + return function(t, u) { + transform = t, untransform = u; + return rescale(); + }; +} + +export default function continuous(transform, untransform) { + return transformer()(transform, untransform); } diff --git a/src/diverging.js b/src/diverging.js index b910644..7d9d1ee 100644 --- a/src/diverging.js +++ b/src/diverging.js @@ -1,20 +1,31 @@ +import {identity} from "./continuous"; +import {initInterpolator} from "./init"; import {linearish} from "./linear"; +import {loggish} from "./log"; +import {copy} from "./sequential"; +import {symlogish} from "./symlog"; +import {powish} from "./pow"; -export default function diverging(interpolator) { +function transformer() { var x0 = 0, x1 = 0.5, x2 = 1, - k10 = 1, - k21 = 1, - clamp = false; + t0, + t1, + t2, + k10, + k21, + interpolator = identity, + transform, + clamp = false, + unknown; function scale(x) { - var t = 0.5 + ((x = +x) - x1) * (x < x1 ? k10 : k21); - return interpolator(clamp ? Math.max(0, Math.min(1, t)) : t); + return isNaN(x = +x) ? unknown : (x = 0.5 + ((x = +transform(x)) - t1) * (x < t1 ? k10 : k21), interpolator(clamp ? Math.max(0, Math.min(1, x)) : x)); } scale.domain = function(_) { - return arguments.length ? (x0 = +_[0], x1 = +_[1], x2 = +_[2], k10 = x0 === x1 ? 0 : 0.5 / (x1 - x0), k21 = x1 === x2 ? 0 : 0.5 / (x2 - x1), scale) : [x0, x1, x2]; + return arguments.length ? (t0 = transform(x0 = +_[0]), t1 = transform(x1 = +_[1]), t2 = transform(x2 = +_[2]), k10 = t0 === t1 ? 0 : 0.5 / (t1 - t0), k21 = t1 === t2 ? 0 : 0.5 / (t2 - t1), scale) : [x0, x1, x2]; }; scale.clamp = function(_) { @@ -25,9 +36,56 @@ export default function diverging(interpolator) { return arguments.length ? (interpolator = _, scale) : interpolator; }; + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : unknown; + }; + + return function(t) { + transform = t, t0 = t(x0), t1 = t(x1), t2 = t(x2), k10 = t0 === t1 ? 0 : 0.5 / (t1 - t0), k21 = t1 === t2 ? 0 : 0.5 / (t2 - t1); + return scale; + }; +} + +export default function diverging() { + var scale = linearish(transformer()(identity)); + + scale.copy = function() { + return copy(scale, diverging()); + }; + + return initInterpolator.apply(scale, arguments); +} + +export function divergingLog() { + var scale = loggish(transformer()).domain([0.1, 1, 10]); + + scale.copy = function() { + return copy(scale, divergingLog()).base(scale.base()); + }; + + return initInterpolator.apply(scale, arguments); +} + +export function divergingSymlog() { + var scale = symlogish(transformer()); + scale.copy = function() { - return diverging(interpolator).domain([x0, x1, x2]).clamp(clamp); + return copy(scale, divergingSymlog()).constant(scale.constant()); }; - return linearish(scale); + return initInterpolator.apply(scale, arguments); +} + +export function divergingPow() { + var scale = powish(transformer()); + + scale.copy = function() { + return copy(scale, divergingPow()).exponent(scale.exponent()); + }; + + return initInterpolator.apply(scale, arguments); +} + +export function divergingSqrt() { + return divergingPow.apply(null, arguments).exponent(0.5); } diff --git a/src/identity.js b/src/identity.js index f7d818e..c0f70b3 100644 --- a/src/identity.js +++ b/src/identity.js @@ -2,11 +2,11 @@ import {map} from "./array"; import {linearish} from "./linear"; import number from "./number"; -export default function identity() { - var domain = [0, 1]; +export default function identity(domain) { + var unknown; function scale(x) { - return +x; + return isNaN(x = +x) ? unknown : x; } scale.invert = scale; @@ -15,9 +15,15 @@ export default function identity() { return arguments.length ? (domain = map.call(_, number), scale) : domain.slice(); }; + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : unknown; + }; + scale.copy = function() { - return identity().domain(domain); + return identity(domain).unknown(unknown); }; + domain = arguments.length ? map.call(domain, number) : [0, 1]; + return linearish(scale); } diff --git a/src/index.js b/src/index.js index beabc46..0c74f90 100644 --- a/src/index.js +++ b/src/index.js @@ -15,6 +15,10 @@ export { default as scaleLog } from "./log"; +export { + default as scaleSymlog +} from "./symlog"; + export { default as scaleOrdinal, implicit as scaleImplicit @@ -46,9 +50,24 @@ export { } from "./utcTime"; export { - default as scaleSequential + default as scaleSequential, + sequentialLog as scaleSequentialLog, + sequentialPow as scaleSequentialPow, + sequentialSqrt as scaleSequentialSqrt, + sequentialSymlog as scaleSequentialSymlog } from "./sequential"; export { - default as scaleDiverging + default as scaleSequentialQuantile +} from "./sequentialQuantile"; + +export { + default as scaleDiverging, + divergingLog as scaleDivergingLog, + divergingPow as scaleDivergingPow, + divergingSqrt as scaleDivergingSqrt } from "./diverging"; + +export { + default as tickFormat +} from "./tickFormat"; diff --git a/src/init.js b/src/init.js new file mode 100644 index 0000000..4328e7a --- /dev/null +++ b/src/init.js @@ -0,0 +1,17 @@ +export function initRange(domain, range) { + switch (arguments.length) { + case 0: break; + case 1: this.range(domain); break; + default: this.range(range).domain(domain); break; + } + return this; +} + +export function initInterpolator(domain, interpolator) { + switch (arguments.length) { + case 0: break; + case 1: this.interpolator(domain); break; + default: this.interpolator(interpolator).domain(domain); break; + } + return this; +} diff --git a/src/linear.js b/src/linear.js index 154c39a..593eca1 100644 --- a/src/linear.js +++ b/src/linear.js @@ -1,6 +1,6 @@ import {ticks, tickIncrement} from "d3-array"; -import {interpolateNumber as reinterpolate} from "d3-interpolate"; -import {default as continuous, copy, deinterpolateLinear as deinterpolate} from "./continuous"; +import continuous, {copy, identity} from "./continuous"; +import {initRange} from "./init"; import tickFormat from "./tickFormat"; export function linearish(scale) { @@ -12,7 +12,8 @@ export function linearish(scale) { }; scale.tickFormat = function(count, specifier) { - return tickFormat(domain(), count, specifier); + var d = domain(); + return tickFormat(d[0], d[d.length - 1], count == null ? 10 : count, specifier); }; scale.nice = function(count) { @@ -59,11 +60,13 @@ export function linearish(scale) { } export default function linear() { - var scale = continuous(deinterpolate, reinterpolate); + var scale = continuous(identity, identity); scale.copy = function() { return copy(scale, linear()); }; + initRange.apply(scale, arguments); + return linearish(scale); } diff --git a/src/log.js b/src/log.js index 58ea679..545f83a 100644 --- a/src/log.js +++ b/src/log.js @@ -1,19 +1,23 @@ import {ticks} from "d3-array"; import {format} from "d3-format"; -import constant from "./constant"; import nice from "./nice"; -import {default as continuous, copy} from "./continuous"; +import {copy, transformer} from "./continuous"; +import {initRange} from "./init"; -function deinterpolate(a, b) { - return (b = Math.log(b / a)) - ? function(x) { return Math.log(x / a) / b; } - : constant(b); +function transformLog(x) { + return Math.log(x); } -function reinterpolate(a, b) { - return a < 0 - ? function(t) { return -Math.pow(-b, t) * Math.pow(-a, 1 - t); } - : function(t) { return Math.pow(b, t) * Math.pow(a, 1 - t); }; +function transformExp(x) { + return Math.exp(x); +} + +function transformLogn(x) { + return -Math.log(-x); +} + +function transformExpn(x) { + return -Math.exp(-x); } function pow10(x) { @@ -39,16 +43,21 @@ function reflect(f) { }; } -export default function log() { - var scale = continuous(deinterpolate, reinterpolate).domain([1, 10]), +export function loggish(transform) { + var scale = transform(transformLog, transformExp), domain = scale.domain, base = 10, - logs = logp(10), - pows = powp(10); + logs, + pows; function rescale() { logs = logp(base), pows = powp(base); - if (domain()[0] < 0) logs = reflect(logs), pows = reflect(pows); + if (domain()[0] < 0) { + logs = reflect(logs), pows = reflect(pows); + transform(transformLogn, transformExpn); + } else { + transform(transformLog, transformExp); + } return scale; } @@ -120,9 +129,17 @@ export default function log() { })); }; + return scale; +} + +export default function log() { + var scale = loggish(transformer()).domain([1, 10]); + scale.copy = function() { - return copy(scale, log().base(base)); + return copy(scale, log()).base(scale.base()); }; + initRange.apply(scale, arguments); + return scale; } diff --git a/src/ordinal.js b/src/ordinal.js index 8ba8774..35d303d 100644 --- a/src/ordinal.js +++ b/src/ordinal.js @@ -1,14 +1,14 @@ import {slice} from "./array"; +import {initRange} from "./init"; export var implicit = {name: "implicit"}; -export default function ordinal(range) { - var index = new Map(), +export default function ordinal() { + var index = new Map, domain = [], + range = [], unknown = implicit; - range = range == null ? [] : slice.call(range); - function scale(d) { var key = d + "", i = index.get(key); if (!i) { @@ -35,11 +35,10 @@ export default function ordinal(range) { }; scale.copy = function() { - return ordinal() - .domain(domain) - .range(range) - .unknown(unknown); + return ordinal(domain, range).unknown(unknown); }; + initRange.apply(scale, arguments); + return scale; } diff --git a/src/pow.js b/src/pow.js index 589141c..e97199b 100644 --- a/src/pow.js +++ b/src/pow.js @@ -1,38 +1,50 @@ -import constant from "./constant"; import {linearish} from "./linear"; -import {default as continuous, copy} from "./continuous"; +import {copy, identity, transformer} from "./continuous"; +import {initRange} from "./init"; -function raise(x, exponent) { - return x < 0 ? -Math.pow(-x, exponent) : Math.pow(x, exponent); +function transformPow(exponent) { + return function(x) { + return x < 0 ? -Math.pow(-x, exponent) : Math.pow(x, exponent); + }; } -export default function pow() { - var exponent = 1, - scale = continuous(deinterpolate, reinterpolate), - domain = scale.domain; - - function deinterpolate(a, b) { - return (b = raise(b, exponent) - (a = raise(a, exponent))) - ? function(x) { return (raise(x, exponent) - a) / b; } - : constant(b); - } +function transformSqrt(x) { + return x < 0 ? -Math.sqrt(-x) : Math.sqrt(x); +} + +function transformSquare(x) { + return x < 0 ? -x * x : x * x; +} + +export function powish(transform) { + var scale = transform(identity, identity), + exponent = 1; - function reinterpolate(a, b) { - b = raise(b, exponent) - (a = raise(a, exponent)); - return function(t) { return raise(a + b * t, 1 / exponent); }; + function rescale() { + return exponent === 1 ? transform(identity, identity) + : exponent === 0.5 ? transform(transformSqrt, transformSquare) + : transform(transformPow(exponent), transformPow(1 / exponent)); } scale.exponent = function(_) { - return arguments.length ? (exponent = +_, domain(domain())) : exponent; + return arguments.length ? (exponent = +_, rescale()) : exponent; }; + return linearish(scale); +} + +export default function pow() { + var scale = powish(transformer()); + scale.copy = function() { - return copy(scale, pow().exponent(exponent)); + return copy(scale, pow()).exponent(scale.exponent()); }; - return linearish(scale); + initRange.apply(scale, arguments); + + return scale; } export function sqrt() { - return pow().exponent(0.5); + return pow.apply(null, arguments).exponent(0.5); } diff --git a/src/quantile.js b/src/quantile.js index 90757a5..40efaed 100644 --- a/src/quantile.js +++ b/src/quantile.js @@ -1,10 +1,12 @@ import {ascending, bisect, quantile as threshold} from "d3-array"; import {slice} from "./array"; +import {initRange} from "./init"; export default function quantile() { var domain = [], range = [], - thresholds = []; + thresholds = [], + unknown; function rescale() { var i = 0, n = Math.max(1, range.length); @@ -14,7 +16,7 @@ export default function quantile() { } function scale(x) { - if (!isNaN(x = +x)) return range[bisect(thresholds, x)]; + return isNaN(x = +x) ? unknown : range[bisect(thresholds, x)]; } scale.invertExtent = function(y) { @@ -37,6 +39,10 @@ export default function quantile() { return arguments.length ? (range = slice.call(_), rescale()) : range.slice(); }; + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : unknown; + }; + scale.quantiles = function() { return thresholds.slice(); }; @@ -44,8 +50,9 @@ export default function quantile() { scale.copy = function() { return quantile() .domain(domain) - .range(range); + .range(range) + .unknown(unknown); }; - return scale; + return initRange.apply(scale, arguments); } diff --git a/src/quantize.js b/src/quantize.js index 189f964..7e7d7cc 100644 --- a/src/quantize.js +++ b/src/quantize.js @@ -1,16 +1,18 @@ import {bisect} from "d3-array"; import {slice} from "./array"; import {linearish} from "./linear"; +import {initRange} from "./init"; export default function quantize() { var x0 = 0, x1 = 1, n = 1, domain = [0.5], - range = [0, 1]; + range = [0, 1], + unknown; function scale(x) { - if (x <= x) return range[bisect(domain, x, 0, n)]; + return x <= x ? range[bisect(domain, x, 0, n)] : unknown; } function rescale() { @@ -36,11 +38,16 @@ export default function quantize() { : [domain[i - 1], domain[i]]; }; + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : scale; + }; + scale.copy = function() { return quantize() .domain([x0, x1]) - .range(range); + .range(range) + .unknown(unknown); }; - return linearish(scale); + return initRange.apply(linearish(scale), arguments); } diff --git a/src/sequential.js b/src/sequential.js index 76a8414..23c5513 100644 --- a/src/sequential.js +++ b/src/sequential.js @@ -1,18 +1,27 @@ +import {identity} from "./continuous"; +import {initInterpolator} from "./init"; import {linearish} from "./linear"; +import {loggish} from "./log"; +import {symlogish} from "./symlog"; +import {powish} from "./pow"; -export default function sequential(interpolator) { +function transformer() { var x0 = 0, x1 = 1, - k10 = 1, - clamp = false; + t0, + t1, + k10, + transform, + interpolator = identity, + clamp = false, + unknown; function scale(x) { - var t = (x - x0) * k10; - return interpolator(clamp ? Math.max(0, Math.min(1, t)) : t); + return isNaN(x = +x) ? unknown : (x = (transform(x) - t0) * k10, interpolator(clamp ? Math.max(0, Math.min(1, x)) : x)); } scale.domain = function(_) { - return arguments.length ? (x0 = +_[0], x1 = +_[1], k10 = x0 === x1 ? 0 : 1 / (x1 - x0), scale) : [x0, x1]; + return arguments.length ? (t0 = transform(x0 = +_[0]), t1 = transform(x1 = +_[1]), k10 = t0 === t1 ? 0 : 1 / (t1 - t0), scale) : [x0, x1]; }; scale.clamp = function(_) { @@ -23,9 +32,64 @@ export default function sequential(interpolator) { return arguments.length ? (interpolator = _, scale) : interpolator; }; + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : unknown; + }; + + return function(t) { + transform = t, t0 = t(x0), t1 = t(x1), k10 = t0 === t1 ? 0 : 1 / (t1 - t0); + return scale; + }; +} + +export function copy(source, target) { + return target + .domain(source.domain()) + .interpolator(source.interpolator()) + .clamp(source.clamp()) + .unknown(source.unknown()); +} + +export default function sequential() { + var scale = linearish(transformer()(identity)); + + scale.copy = function() { + return copy(scale, sequential()); + }; + + return initInterpolator.apply(scale, arguments); +} + +export function sequentialLog() { + var scale = loggish(transformer()).domain([1, 10]); + scale.copy = function() { - return sequential(interpolator).domain([x0, x1]).clamp(clamp); + return copy(scale, sequentialLog()).base(scale.base()); }; - return linearish(scale); + return initInterpolator.apply(scale, arguments); +} + +export function sequentialSymlog() { + var scale = symlogish(transformer()); + + scale.copy = function() { + return copy(scale, sequentialSymlog()).constant(scale.constant()); + }; + + return initInterpolator.apply(scale, arguments); +} + +export function sequentialPow() { + var scale = powish(transformer()); + + scale.copy = function() { + return copy(scale, sequentialPow()).exponent(scale.exponent()); + }; + + return initInterpolator.apply(scale, arguments); +} + +export function sequentialSqrt() { + return sequentialPow.apply(null, arguments).exponent(0.5); } diff --git a/src/sequentialQuantile.js b/src/sequentialQuantile.js new file mode 100644 index 0000000..3e733dc --- /dev/null +++ b/src/sequentialQuantile.js @@ -0,0 +1,30 @@ +import {ascending, bisect} from "d3-array"; +import {identity} from "./continuous"; +import {initInterpolator} from "./init"; + +export default function sequentialQuantile() { + var domain = [], + interpolator = identity; + + function scale(x) { + if (!isNaN(x = +x)) return interpolator((bisect(domain, x) - 1) / (domain.length - 1)); + } + + scale.domain = function(_) { + if (!arguments.length) return domain.slice(); + domain = []; + for (var i = 0, n = _.length, d; i < n; ++i) if (d = _[i], d != null && !isNaN(d = +d)) domain.push(d); + domain.sort(ascending); + return scale; + }; + + scale.interpolator = function(_) { + return arguments.length ? (interpolator = _, scale) : interpolator; + }; + + scale.copy = function() { + return sequentialQuantile(interpolator).domain(domain); + }; + + return initInterpolator.apply(scale, arguments); +} diff --git a/src/symlog.js b/src/symlog.js new file mode 100644 index 0000000..8da440f --- /dev/null +++ b/src/symlog.js @@ -0,0 +1,35 @@ +import {linearish} from "./linear"; +import {copy, transformer} from "./continuous"; +import {initRange} from "./init"; + +function transformSymlog(c) { + return function(x) { + return Math.sign(x) * Math.log1p(Math.abs(x / c)); + }; +} + +function transformSymexp(c) { + return function(x) { + return Math.sign(x) * Math.expm1(Math.abs(x)) * c; + }; +} + +export function symlogish(transform) { + var c = 1, scale = transform(transformSymlog(c), transformSymexp(c)); + + scale.constant = function(_) { + return arguments.length ? transform(transformSymlog(c = +_), transformSymexp(c)) : c; + }; + + return linearish(scale); +} + +export default function symlog() { + var scale = symlogish(transformer()); + + scale.copy = function() { + return copy(scale, symlog()).constant(scale.constant()); + }; + + return initRange.apply(scale, arguments); +} diff --git a/src/threshold.js b/src/threshold.js index 6bd7675..cb11bcb 100644 --- a/src/threshold.js +++ b/src/threshold.js @@ -1,13 +1,15 @@ import {bisect} from "d3-array"; import {slice} from "./array"; +import {initRange} from "./init"; export default function threshold() { var domain = [0.5], range = [0, 1], + unknown, n = 1; function scale(x) { - if (x <= x) return range[bisect(domain, x, 0, n)]; + return x <= x ? range[bisect(domain, x, 0, n)] : unknown; } scale.domain = function(_) { @@ -23,11 +25,16 @@ export default function threshold() { return [domain[i - 1], domain[i]]; }; + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : unknown; + }; + scale.copy = function() { return threshold() .domain(domain) - .range(range); + .range(range) + .unknown(unknown); }; - return scale; + return initRange.apply(scale, arguments); } diff --git a/src/tickFormat.js b/src/tickFormat.js index fd4cdfd..2f370ee 100644 --- a/src/tickFormat.js +++ b/src/tickFormat.js @@ -1,10 +1,8 @@ import {tickStep} from "d3-array"; import {format, formatPrefix, formatSpecifier, precisionFixed, precisionPrefix, precisionRound} from "d3-format"; -export default function(domain, count, specifier) { - var start = domain[0], - stop = domain[domain.length - 1], - step = tickStep(start, stop, count == null ? 10 : count), +export default function(start, stop, count, specifier) { + var step = tickStep(start, stop, count), precision; specifier = formatSpecifier(specifier == null ? ",f" : specifier); switch (specifier.type) { diff --git a/src/time.js b/src/time.js index d6e1ec0..4632d11 100644 --- a/src/time.js +++ b/src/time.js @@ -1,9 +1,9 @@ import {bisector, tickStep} from "d3-array"; -import {interpolateNumber as reinterpolate} from "d3-interpolate"; import {timeYear, timeMonth, timeWeek, timeDay, timeHour, timeMinute, timeSecond, timeMillisecond} from "d3-time"; import {timeFormat} from "d3-time-format"; import {map} from "./array"; -import {default as continuous, copy, deinterpolateLinear as deinterpolate} from "./continuous"; +import continuous, {copy, identity} from "./continuous"; +import {initRange} from "./init"; import nice from "./nice"; var durationSecond = 1000, @@ -23,7 +23,7 @@ function number(t) { } export function calendar(year, month, week, day, hour, minute, second, millisecond, format) { - var scale = continuous(deinterpolate, reinterpolate), + var scale = continuous(identity, identity), invert = scale.invert, domain = scale.domain; @@ -131,5 +131,5 @@ export function calendar(year, month, week, day, hour, minute, second, milliseco } export default function() { - return calendar(timeYear, timeMonth, timeWeek, timeDay, timeHour, timeMinute, timeSecond, timeMillisecond, timeFormat).domain([new Date(2000, 0, 1), new Date(2000, 0, 2)]); + return initRange.apply(calendar(timeYear, timeMonth, timeWeek, timeDay, timeHour, timeMinute, timeSecond, timeMillisecond, timeFormat).domain([new Date(2000, 0, 1), new Date(2000, 0, 2)]), arguments); } diff --git a/src/utcTime.js b/src/utcTime.js index 00ffdb7..dc019b3 100644 --- a/src/utcTime.js +++ b/src/utcTime.js @@ -1,7 +1,8 @@ import {calendar} from "./time"; import {utcFormat} from "d3-time-format"; import {utcYear, utcMonth, utcWeek, utcDay, utcHour, utcMinute, utcSecond, utcMillisecond} from "d3-time"; +import {initRange} from "./init"; export default function() { - return calendar(utcYear, utcMonth, utcWeek, utcDay, utcHour, utcMinute, utcSecond, utcMillisecond, utcFormat).domain([Date.UTC(2000, 0, 1), Date.UTC(2000, 0, 2)]); + return initRange.apply(calendar(utcYear, utcMonth, utcWeek, utcDay, utcHour, utcMinute, utcSecond, utcMillisecond, utcFormat).domain([Date.UTC(2000, 0, 1), Date.UTC(2000, 0, 2)]), arguments); } diff --git a/test/band-test.js b/test/band-test.js index 05d6c9f..1e63971 100644 --- a/test/band-test.js +++ b/test/band-test.js @@ -15,7 +15,7 @@ tape("scaleBand() has the expected defaults", function(test) { }); tape("band(value) computes discrete bands in a continuous range", function(test) { - var s = scale.scaleBand().range([0, 960]); + var s = scale.scaleBand([0, 960]); test.equal(s("foo"), undefined); s.domain(["foo", "bar"]); test.equal(s("foo"), 0); @@ -30,7 +30,7 @@ tape("band(value) computes discrete bands in a continuous range", function(test) }); tape("band(value) returns undefined for values outside the domain", function(test) { - var s = scale.scaleBand().domain(["a", "b", "c"]); + var s = scale.scaleBand(["a", "b", "c"], [0, 1]); test.equal(s("d"), undefined); test.equal(s("e"), undefined); test.equal(s("f"), undefined); @@ -38,7 +38,7 @@ tape("band(value) returns undefined for values outside the domain", function(tes }); tape("band(value) does not implicitly add values to the domain", function(test) { - var s = scale.scaleBand().domain(["a", "b", "c"]); + var s = scale.scaleBand(["a", "b", "c"], [0, 1]); s("d"); s("e"); test.deepEqual(s.domain(), ["a", "b", "c"]); @@ -46,7 +46,7 @@ tape("band(value) does not implicitly add values to the domain", function(test) }); tape("band.step() returns the distance between the starts of adjacent bands", function(test) { - var s = scale.scaleBand().range([0, 960]); + var s = scale.scaleBand([0, 960]); test.equal(s.domain(["foo"]).step(), 960); test.equal(s.domain(["foo", "bar"]).step(), 480); test.equal(s.domain(["foo", "bar", "baz"]).step(), 320); @@ -57,7 +57,7 @@ tape("band.step() returns the distance between the starts of adjacent bands", fu }); tape("band.bandwidth() returns the width of the band", function(test) { - var s = scale.scaleBand().range([0, 960]); + var s = scale.scaleBand([0, 960]); test.equal(s.domain([]).bandwidth(), 960); test.equal(s.domain(["foo"]).bandwidth(), 960); test.equal(s.domain(["foo", "bar"]).bandwidth(), 480); @@ -70,7 +70,7 @@ tape("band.bandwidth() returns the width of the band", function(test) { }); tape("band.domain([]) computes reasonable band and step values", function(test) { - var s = scale.scaleBand().domain([]).range([0, 960]); + var s = scale.scaleBand([0, 960]).domain([]); test.equal(s.step(), 960); test.equal(s.bandwidth(), 960); s.padding(0.5); @@ -83,7 +83,7 @@ tape("band.domain([]) computes reasonable band and step values", function(test) }); tape("band.domain([value]) computes a reasonable singleton band, even with padding", function(test) { - var s = scale.scaleBand().domain(["foo"]).range([0, 960]); + var s = scale.scaleBand([0, 960]).domain(["foo"]); test.equal(s("foo"), 0); test.equal(s.step(), 960); test.equal(s.bandwidth(), 960); @@ -168,10 +168,10 @@ tape("band.paddingInner(p) specifies the inner padding p", function(test) { test.end(); }); -tape("band.paddingInner(p) coerces p to a number in [0, 1]", function(test) { +tape("band.paddingInner(p) coerces p to a number <= 1", function(test) { var s = scale.scaleBand(); test.equal(s.paddingInner("1.0").paddingInner(), 1); - test.equal(s.paddingInner("-1.0").paddingInner(), 0); + test.equal(s.paddingInner("-1.0").paddingInner(), -1); test.equal(s.paddingInner("2.0").paddingInner(), 1); test.ok(Number.isNaN(s.paddingInner(NaN).paddingInner())); test.end(); @@ -187,11 +187,11 @@ tape("band.paddingOuter(p) specifies the outer padding p", function(test) { test.end(); }); -tape("band.paddingOuter(p) coerces p to a number in [0, 1]", function(test) { +tape("band.paddingOuter(p) coerces p to a number", function(test) { var s = scale.scaleBand(); test.equal(s.paddingOuter("1.0").paddingOuter(), 1); - test.equal(s.paddingOuter("-1.0").paddingOuter(), 0); - test.equal(s.paddingOuter("2.0").paddingOuter(), 1); + test.equal(s.paddingOuter("-1.0").paddingOuter(), -1); + test.equal(s.paddingOuter("2.0").paddingOuter(), 2); test.ok(Number.isNaN(s.paddingOuter(NaN).paddingOuter())); test.end(); }); diff --git a/test/diverging-test.js b/test/diverging-test.js index ab00246..4472084 100644 --- a/test/diverging-test.js +++ b/test/diverging-test.js @@ -1,11 +1,10 @@ var tape = require("tape"), scale = require("../"); -tape("scaleDiverging(interpolator) has the expected defaults", function(test) { - var i = function(t) { return t; }, - s = scale.scaleDiverging(i); +tape("scaleDiverging() has the expected defaults", function(test) { + var s = scale.scaleDiverging(); test.deepEqual(s.domain(), [0, 0.5, 1]); - test.equal(s.interpolator(), i); + test.equal(s.interpolator()(0.42), 0.42); test.equal(s.clamp(), false); test.equal(s(-0.5), -0.5); test.equal(s( 0.0), 0.0); @@ -16,7 +15,7 @@ tape("scaleDiverging(interpolator) has the expected defaults", function(test) { }); tape("diverging.clamp(true) enables clamping", function(test) { - var s = scale.scaleDiverging(function(t) { return t; }).clamp(true); + var s = scale.scaleDiverging().clamp(true); test.equal(s.clamp(), true); test.equal(s(-0.5), 0.0); test.equal(s( 0.0), 0.0); @@ -27,7 +26,7 @@ tape("diverging.clamp(true) enables clamping", function(test) { }); tape("diverging.domain() coerces domain values to numbers", function(test) { - var s = scale.scaleDiverging(function(t) { return t; }).domain(["-1.20", " 0", "2.40"]); + var s = scale.scaleDiverging().domain(["-1.20", " 0", "2.40"]); test.deepEqual(s.domain(), [-1.2, 0, 2.4]); test.equal(s(-1.2), 0.000); test.equal(s( 0.6), 0.625); @@ -36,7 +35,7 @@ tape("diverging.domain() coerces domain values to numbers", function(test) { }); tape("diverging.domain() handles a degenerate domain", function(test) { - var s = scale.scaleDiverging(function(t) { return t; }).domain([2, 2, 3]); + var s = scale.scaleDiverging().domain([2, 2, 3]); test.deepEqual(s.domain(), [2, 2, 3]); test.equal(s(-1.2), 0.5); test.equal(s( 0.6), 0.5); @@ -53,7 +52,7 @@ tape("diverging.domain() handles a degenerate domain", function(test) { }); tape("diverging.domain() handles a non-numeric domain", function(test) { - var s = scale.scaleDiverging(function(t) { return t; }).domain([NaN, 2, 3]); + var s = scale.scaleDiverging().domain([NaN, 2, 3]); test.equal(isNaN(s.domain()[0]), true); test.equal(isNaN(s(-1.2)), true); test.equal(isNaN(s( 0.6)), true); @@ -70,13 +69,13 @@ tape("diverging.domain() handles a non-numeric domain", function(test) { }); tape("diverging.domain() only considers the first three elements of the domain", function(test) { - var s = scale.scaleDiverging(function(t) { return t; }).domain([-1, 100, 200, 3]); + var s = scale.scaleDiverging().domain([-1, 100, 200, 3]); test.deepEqual(s.domain(), [-1, 100, 200]); test.end(); }); tape("diverging.copy() returns an isolated copy of the scale", function(test) { - var s1 = scale.scaleDiverging(function(t) { return t; }).domain([1, 2, 3]).clamp(true), + var s1 = scale.scaleDiverging().domain([1, 2, 3]).clamp(true), s2 = s1.copy(); test.deepEqual(s2.domain(), [1, 2, 3]); test.equal(s2.clamp(), true); diff --git a/test/identity-test.js b/test/identity-test.js index cd96217..032d92c 100644 --- a/test/identity-test.js +++ b/test/identity-test.js @@ -8,6 +8,13 @@ tape("scaleIdentity() has the expected defaults", function(test) { test.end(); }); +tape("scaleIdentity(range) sets the domain and range", function(test) { + var s = scale.scaleIdentity([1, 2]); + test.deepEqual(s.domain(), [1, 2]); + test.deepEqual(s.range(), [1, 2]); + test.end(); +}); + tape("identity(x) is the identity function", function(test) { var s = scale.scaleIdentity().domain([1, 2]); test.equal(s(0.5), 0.5); diff --git a/test/linear-test.js b/test/linear-test.js index 2e3183d..831a3de 100644 --- a/test/linear-test.js +++ b/test/linear-test.js @@ -7,10 +7,27 @@ tape("scaleLinear() has the expected defaults", function(test) { test.deepEqual(s.domain(), [0, 1]); test.deepEqual(s.range(), [0, 1]); test.equal(s.clamp(), false); + test.equal(s.unknown(), undefined); test.deepEqual(s.interpolate()({array: ["red"]}, {array: ["blue"]})(0.5), {array: ["rgb(128, 0, 128)"]}); test.end(); }); +tape("scaleLinear(range) sets the range", function(test) { + var s = scale.scaleLinear([1, 2]); + test.deepEqual(s.domain(), [0, 1]); + test.deepEqual(s.range(), [1, 2]); + test.equal(s(0.5), 1.5); + test.end(); +}); + +tape("scaleLinear(domain, range) sets the domain and range", function(test) { + var s = scale.scaleLinear([1, 2], [3, 4]); + test.deepEqual(s.domain(), [1, 2]); + test.deepEqual(s.range(), [3, 4]); + test.equal(s(1.5), 3.5); + test.end(); +}); + tape("linear(x) maps a domain value x to a range value y", function(test) { test.equal(scale.scaleLinear().range([1, 2])(0.5), 1.5); test.end(); @@ -168,6 +185,15 @@ tape("linear.rangeRound(range) is an alias for linear.range(range).interpolate(i test.end(); }); +tape("linear.unknown(value) sets the return value for undefined and NaN input", function(test) { + var s = scale.scaleLinear().unknown(-1); + test.equal(s(undefined), -1); + test.equal(s(NaN), -1); + test.equal(s("N/A"), -1); + test.equal(s(0.4), 0.4); + test.end(); +}); + tape("linear.clamp() is false by default", function(test) { test.equal(scale.scaleLinear().clamp(), false); test.equal(scale.scaleLinear().range([10, 20])(2), 30); @@ -455,3 +481,16 @@ tape("linear.copy() returns a copy with changes to clamping are isolated", funct test.equal(x.clamp(), false); test.end(); }); + +tape("linear.copy() returns a copy with changes to the unknown value are isolated", function(test) { + var x = scale.scaleLinear(), y = x.copy(); + x.unknown(2); + test.equal(x(NaN), 2); + test.equal(isNaN(y(NaN)), true); + test.equal(y.unknown(), undefined); + y.unknown(3); + test.equal(x(NaN), 2); + test.equal(y(NaN), 3); + test.equal(x.unknown(), 2); + test.end(); +}); diff --git a/test/quantile-test.js b/test/quantile-test.js index fa460bc..dfe1cd2 100644 --- a/test/quantile-test.js +++ b/test/quantile-test.js @@ -5,6 +5,7 @@ tape("scaleQuantile() has the expected default", function(test) { var s = scale.scaleQuantile(); test.deepEqual(s.domain(), []); test.deepEqual(s.range(), []); + test.equal(s.unknown(), undefined); test.end(); }); @@ -115,3 +116,14 @@ tape("quantile.invertExtent() returns the first match if duplicate values exist test.deepEqual(s.invertExtent(2), [9, 14.5]); test.end(); }); + +tape("quantile.unknown(value) sets the return value for undefined and NaN input", function(test) { + var s = scale.scaleQuantile().domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20]).range([0, 1, 2, 3]).unknown(-1); + test.equal(s(undefined), -1); + test.equal(s(NaN), -1); + test.equal(s("N/A"), -1); + test.equal(s(2), 0); + test.equal(s(3), 0); + test.equal(s(21), 3); + test.end(); +}); diff --git a/test/sequential-test.js b/test/sequential-test.js index bc51af3..9b59ff1 100644 --- a/test/sequential-test.js +++ b/test/sequential-test.js @@ -1,12 +1,12 @@ var tape = require("tape"), scale = require("../"); -tape("scaleSequential(interpolator) has the expected defaults", function(test) { - var i = function(t) { return t; }, - s = scale.scaleSequential(i); +tape("scaleSequential() has the expected defaults", function(test) { + var s = scale.scaleSequential(); test.deepEqual(s.domain(), [0, 1]); - test.equal(s.interpolator(), i); + test.equal(s.interpolator()(0.42), 0.42); test.equal(s.clamp(), false); + test.equal(s.unknown(), undefined); test.equal(s(-0.5), -0.5); test.equal(s( 0.0), 0.0); test.equal(s( 0.5), 0.5); @@ -16,7 +16,7 @@ tape("scaleSequential(interpolator) has the expected defaults", function(test) { }); tape("sequential.clamp(true) enables clamping", function(test) { - var s = scale.scaleSequential(function(t) { return t; }).clamp(true); + var s = scale.scaleSequential().clamp(true); test.equal(s.clamp(), true); test.equal(s(-0.5), 0.0); test.equal(s( 0.0), 0.0); @@ -26,8 +26,18 @@ tape("sequential.clamp(true) enables clamping", function(test) { test.end(); }); +tape("sequential.unknown(value) sets the return value for undefined and NaN input", function(test) { + var s = scale.scaleSequential().unknown(-1); + test.equal(s.unknown(), -1); + test.equal(s(undefined), -1); + test.equal(s(NaN), -1); + test.equal(s("N/A"), -1); + test.equal(s(0.4), 0.4); + test.end(); +}); + tape("sequential.domain() coerces domain values to numbers", function(test) { - var s = scale.scaleSequential(function(t) { return t; }).domain(["-1.20", "2.40"]); + var s = scale.scaleSequential().domain(["-1.20", "2.40"]); test.deepEqual(s.domain(), [-1.2, 2.4]); test.equal(s(-1.2), 0.0); test.equal(s( 0.6), 0.5); @@ -36,7 +46,7 @@ tape("sequential.domain() coerces domain values to numbers", function(test) { }); tape("sequential.domain() handles a degenerate domain", function(test) { - var s = scale.scaleSequential(function(t) { return t; }).domain([2, 2]); + var s = scale.scaleSequential().domain([2, 2]); test.deepEqual(s.domain(), [2, 2]); test.equal(s(-1.2), 0.0); test.equal(s( 0.6), 0.0); @@ -45,7 +55,7 @@ tape("sequential.domain() handles a degenerate domain", function(test) { }); tape("sequential.domain() handles a non-numeric domain", function(test) { - var s = scale.scaleSequential(function(t) { return t; }).domain([NaN, 2]); + var s = scale.scaleSequential().domain([NaN, 2]); test.equal(isNaN(s.domain()[0]), true); test.equal(s.domain()[1], 2); test.equal(isNaN(s(-1.2)), true); @@ -55,13 +65,13 @@ tape("sequential.domain() handles a non-numeric domain", function(test) { }); tape("sequential.domain() only considers the first and second element of the domain", function(test) { - var s = scale.scaleSequential(function(t) { return t; }).domain([-1, 100, 200]); + var s = scale.scaleSequential().domain([-1, 100, 200]); test.deepEqual(s.domain(), [-1, 100]); test.end(); }); tape("sequential.copy() returns an isolated copy of the scale", function(test) { - var s1 = scale.scaleSequential(function(t) { return t; }).domain([1, 3]).clamp(true), + var s1 = scale.scaleSequential().domain([1, 3]).clamp(true), s2 = s1.copy(); test.deepEqual(s2.domain(), [1, 3]); test.equal(s2.clamp(), true); diff --git a/test/sqrt-test.js b/test/sqrt-test.js new file mode 100644 index 0000000..42dac03 --- /dev/null +++ b/test/sqrt-test.js @@ -0,0 +1,19 @@ +var tape = require("tape"), + scale = require("../"); + +require("./inDelta"); + +tape("scaleSqrt() has the expected defaults", function(test) { + var s = scale.scaleSqrt(); + test.deepEqual(s.domain(), [0, 1]); + test.deepEqual(s.range(), [0, 1]); + test.equal(s.clamp(), false); + test.equal(s.exponent(), 0.5); + test.deepEqual(s.interpolate()({array: ["red"]}, {array: ["blue"]})(0.5), {array: ["rgb(128, 0, 128)"]}); + test.end(); +}); + +tape("sqrt(x) maps a domain value x to a range value y", function(test) { + test.equal(scale.scaleSqrt()(0.5), Math.SQRT1_2); + test.end(); +}); diff --git a/test/tickFormat-test.js b/test/tickFormat-test.js new file mode 100644 index 0000000..a21c278 --- /dev/null +++ b/test/tickFormat-test.js @@ -0,0 +1,48 @@ +var tape = require("tape"), + scale = require("../"); + +tape("d3.tickFormat(start, stop, count) returns a format suitable for the ticks", function(test) { + test.equal(scale.tickFormat(0, 1, 10)(0.2), "0.2"); + test.equal(scale.tickFormat(0, 1, 20)(0.2), "0.20"); + test.equal(scale.tickFormat(-100, 100, 10)(-20), "-20"); + test.end(); +}); + +tape("d3.tickFormat(start, stop, count, specifier) sets the appropriate fixed precision if not specified", function(test) { + test.equal(scale.tickFormat(0, 1, 10, "+f")(0.2), "+0.2"); + test.equal(scale.tickFormat(0, 1, 20, "+f")(0.2), "+0.20"); + test.equal(scale.tickFormat(0, 1, 10, "+%")(0.2), "+20%"); + test.equal(scale.tickFormat(0.19, 0.21, 10, "+%")(0.2), "+20.0%"); + test.end(); +}); + +tape("d3.tickFormat(start, stop, count, specifier) sets the appropriate round precision if not specified", function(test) { + test.equal(scale.tickFormat(0, 9, 10, "")(2.10), "2"); + test.equal(scale.tickFormat(0, 9, 100, "")(2.01), "2"); + test.equal(scale.tickFormat(0, 9, 100, "")(2.11), "2.1"); + test.equal(scale.tickFormat(0, 9, 10, "e")(2.10), "2e+0"); + test.equal(scale.tickFormat(0, 9, 100, "e")(2.01), "2.0e+0"); + test.equal(scale.tickFormat(0, 9, 100, "e")(2.11), "2.1e+0"); + test.equal(scale.tickFormat(0, 9, 10, "g")(2.10), "2"); + test.equal(scale.tickFormat(0, 9, 100, "g")(2.01), "2.0"); + test.equal(scale.tickFormat(0, 9, 100, "g")(2.11), "2.1"); + test.equal(scale.tickFormat(0, 9, 10, "r")(2.10e6), "2000000"); + test.equal(scale.tickFormat(0, 9, 100, "r")(2.01e6), "2000000"); + test.equal(scale.tickFormat(0, 9, 100, "r")(2.11e6), "2100000"); + test.equal(scale.tickFormat(0, 0.9, 10, "p")(0.210), "20%"); + test.equal(scale.tickFormat(0.19, 0.21, 10, "p")(0.201), "20.1%"); + test.end(); +}); + +tape("d3.tickFormat(start, stop, count, specifier) sets the appropriate prefix precision if not specified", function(test) { + test.equal(scale.tickFormat(0, 1e6, 10, "$s")(0.51e6), "$0.5M"); + test.equal(scale.tickFormat(0, 1e6, 100, "$s")(0.501e6), "$0.50M"); + test.end(); +}); + +tape("d3.tickFormat(start, stop, count) uses the default precision when the domain is invalid", function(test) { + var f = scale.tickFormat(0, NaN, 10); + test.equal(f + "", " >-,f"); + test.equal(f(0.12), "0.120000"); + test.end(); +});