在数据可视化的过程中,往往需要将一个值转换为另一个值。例如要绘制一个历年产值的柱状图,我们需要把3000万的产值投射为屏幕上300个像素的高度。以数学上的“函数”为例:y=2x+1。在这里,x的取值范围假定为[0, 2],这称为“定义域”。则y的取值范围为[1, 5],这称为“值域”。通过一个函数式提供了定义域到值域的对应关系,D3提供了将一个量转换为另一个量的方法,称为“比例尺”(Scale)。
比例尺是D3的基本概念,每一种比例尺都要指定一个定义域(domain)和值域(range)。比例尺分为两种情况:
(1)连续的定义域对应连续的(或离散的)值域——定量比例尺;
(2)离散的定义域对应离散的值域——序数比例尺;
一、定量比例尺
1. 线性比例尺:计算线性对应关系。将连续的定义域对应到连续的值域上。
- d3.scaleLinear()——创建一个线性比例尺;
- linear(x)——输入定义域x的值,返回值域对应的值;
- linear.invert(y)——输入值域内的值,返回定义域内对应的值;
- linear.domain([numbers])——获取或定义定义域;
- linear.range([values])——获取或定义值域;
- linear.rangeRound([values])——等同于range(),获取或定义值域。比例尺的输出值会进行四舍五入的运算,得到一个整数;
- linear.clamp([boolean])——定义当比例尺得到一个超过定义域的值的时候,是否进行转换。默认为flase表示可以接受超出范围的值;
- linear.nice([count])——将定义域的范围扩展到比较理想的形式;
- linear.ticks([count])——设定或获取定义域内具有代表性的值得数目,count默认为10。该方法主要用于获取坐标轴的刻度;
- linear.tickFormat(count[, format])——设置定义域内具有代表性值的表现形式,主要用于坐标轴上;
<body>
<script>
var linear = d3.scaleLinear() // 创建线性比例尺
.domain([0, 500]) // 定义域
.range([0, 100]); // 值域
console.log(linear(50)); // 输出定义域50所对应的值域值:10
console.log(linear.invert(50)); // 输出值域50所对应的定义域的值:250
console.log(linear.domain()); // 获取定义域
console.log(linear.range()); // 获取值域
console.log(linear(1000)); // 输出200
linear.clamp(true); // 禁止接受超值范围的输入
console.log(linear(1000)); // 只输出定义域最大值500所对应的值域:100
linear.rangeRound([0, 100]);
console.log(linear(13.33)); // 本身的输出是2.66,四舍五入为3
linear.domain([0.12300000, 0.488888888]).nice();
console.log(linear.domain()); // [0.1, 0.5]
linear = d3.scaleLinear()
.domain([-20, 20])
.range([0, 100]);
var ticks = linear.ticks(5);
console.log(ticks); // [-20, -10, 0, 10, 20]
var tickFormat = linear.tickFormat(5, "+"); // 给数字添加符号,0或正数添加+,负数添加-
//for(var i=0; i<ticks.length; i++){
// ticks[i] = tickFormat(ticks[i]);
//}
//console.log(ticks); // [-20, -10, +0, +10, +20]
console.log(ticks.map(tickFormat)); // [-20, -10, +0, +10, +20]
</script>
</body>
定义域和值域也可以定义多个数字,但是两者数量必须相等。
2. 指数比例尺和对数比例尺:用于计算指数对应关系和对数对应关系。将连续的定义域对应到连续的值域上。
方法和线性比例尺一样,指数比例尺(scalePow)多了一个exponent([value]),用于指定指数。对数比例尺(scaleLog)多了一个方法名为base([value]),用于设定对数。
<body>
<script>
// 指数比例尺
var pow = d3.scalePow().exponent(2);
console.log(pow(10)); // 100
// 对数比例尺
var log = d3.scaleLog().base(10);
console.log(log(100)); // 2
// domain和range的意义
var pow1 = d3.scalePow().exponent(3)
.domain([0, 3])
.range([0, 90]);
console.log(pow1(1.5)); // 11.25
</script>
</body>
在指数和对数比例尺情况下,定义domain和range的意义在于,d3会先把定义域按照指数关系计算出来,并对应到值域上面去。比如上面的代码,定义了指数为3,定义域为[0, 3]。实际上,d3建立了一个定义域的指数计算,即[0, 27],并通过线性比例尺将其对应到[0, 90]上面去。然后计算了1.5的3次方为3.375,线性过去以后就是11.25。
3. 量化比例尺和分位比例尺:将连续的定义域对应到离散的值域
量化比例尺(scaleQuantize)将连续的定义域自动对应到离散的值域上。比如定义域为[0, 10],值域为[“red”, “green”, “blue”, “yellow”, “black”]。则转换为:
- [0, 2)——red
- [2, 4)——green
- [4, 6)——blue
- [6, 8)——yellow
- [8, 10)——black
例如下面的代码输入为5个不同灰度的圆:
<body>
<script>
var color = d3.scaleQuantize()
.domain([0, 50])
.range(["#000", "#222", "#444", "#666", "#888"]);
// 定义圆半径
var r = [45, 35, 25, 15, 5];
// 添加svg元素
var svg = d3.select("body").append("svg")
.attr("width", 400)
.attr("height", 400);
// 画圆
svg.selectAll("circle")
.data(r)
.enter()
.append("circle")
.attr("cx", function(d, i){ return 50+i*30; })
.attr("cy", 50)
.attr("r", function(d){ return d; })
.attr("fill", function(d){ return color(d); });
</script>
</body>
分位比例尺(ScaleQuantile),和量化比例尺一样,也是将连续的定义域定义到离散的值域上。和量化比例尺不同的是,量化比例值只考虑定义域的起止值,然后根据值域平均切割。而分位比例尺考虑定义域中的所有的值。如下:
<body>
<script>
// 量化比例尺
var quantize = d3.scaleQuantize()
.domain([0, 10])
.range([1, 100]);
// 分位比例尺
var quantile = d3.scaleQuantile()
.domain([0, 2, 4, 10])
.range([1, 100]);
console.log(quantize(3)); // 1
console.log(quantile(3)); // 100
</script>
</body>
对于ScaleQuantize而言,值域为1和100,则定义域进行两段的平均切割。定义域[0, 10]的平均切割就是中位数5。而ScaleQuantile而言,值域也是离散值1和100,但定义域为[0, 2, 4, 10]。那么它的中位数就是(2+4)/2=3。如果定义域为[0, 2, 4, 9, 10],则它的中位数就是4。如果不确定分位比例尺的分位数,可以通过方法ScaleQuantile.quantiles()来获取。
4. 阈值比例尺:设定N个阈值,将值域分成N+1个。连续的定义域对应离散的自值域。阈值比例尺(Threshold),比如下面的例子:
<body>
<script>
var threshold = d3.scaleThreshold()
.domain([10, 20, 30])
.range(["red", "green", "blue", "black"]);
console.log(threshold(5)); // red
console.log(threshold(15)); // green
console.log(threshold(25)); // blue
console.log(threshold(35)); // black
</script>
</body>
二、序数比例尺
序数比例尺实现离散的定义域到离散的值域的对应关系。
- d3.ScaleOrdinal()——创建一个序数比例尺;
- ordinal(x)——根据定义域的x的值获取值域对应的值;
- ordinal.domain([values])——获取或设置定义域;
- ordinal.range([values])——获取或定义值域;
<body>
<script>
var ordinal = d3.scaleOrdinal()
.domain([1, 2, 3, 4, 5])
.range([10, 20, 30, 40, 50]);
console.log(ordinal(1)); // 10
console.log(ordinal(3)); // 30
console.log(ordinal(5)); // 50
console.log(ordinal(8)); // 不在定义域内,输出为10
</script>
</body>