d3中的尺度就如同數學中對映的概念,從定義域對映到值域,d3中尺度初學其實掌握好連續尺度,有序尺度就足夠後面的應用了。
具體尺度的 api 可以跳轉官方文件 github.com/d3/d3/blob/…
連續尺度
連續尺度可以幫我們將連續的定義域對映到一個連續的值域。
連續尺度包括 線性尺度,冪級尺度,對數尺度,時間尺度。
線性尺度 冪級尺度 對數尺度
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.clear {
clear: both;
}
.cell {
min-width: 40px;
min-height: 20px;
margin: 5px;
float: left;
text-align: center;
border: #969696 solid thin;
padding: 5px;
}
</style>
</head>
<body>
<div id="linear" class="clear"><span>n</span></div>
<div id="linear-capped" class="clear"><span>1 <= a*n + b <= 20</span></div>
<div id="pow" class="clear"><span>n^2</span></div>
<div id="pow-capped" class="clear"><span>1 <= a*n^2 + b <= 10</span></div>
<div id="log" class="clear"><span>log(n)</span></div>
<div id="log-capped" class="clear"><span>1 <= a*log(n) + b <= 10</span></div>
<script src="../d3.js"></script>
<script>
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const linear = d3.scaleLinear()
.domain([1, 10])
.range([1, 10])
const linearCapped = d3.scaleLinear()
.domain([1, 10])
.range([1, 10])
const pow = d3.scalePow()
.exponent(2)
.domain([1, 10])
const powCapped = d3.scalePow()
.exponent(2)
.domain([1, 10])
.rangeRound([1, 10])
const log = d3.scaleLog()
.domain([1, 10])
const logCapped = d3.scaleLog()
.domain([1, 10])
.rangeRound([1, 10])
function render(data, scale, selector) {
d3.select(selector).selectAll('div').data(data)
.enter()
.append('div')
.classed('cell', true)
.style('display', 'inline-block')
.text(function (d) {
return d3.format('.2')(scale(d), 2)
})
}
render(data, linear, '#linear')
render(data, linearCapped, '#linear-capped')
render(data, pow, '#pow')
render(data, powCapped, '#pow-capped')
render(data, log, '#log')
render(data, logCapped, '#log-capped')
</script>
</body>
</html>
複製程式碼
效果如下:
對於尺度而言,大部分方法的作用其實都是一樣的:
- domain() 設定定義域
- range() 設定值域
- rangeRound() 設定值域,值域取值前會進行 round 操作
另外對於冪級尺度 scalePow 來說,有 exponent() 設定冪指數,預設為 1,即預設和線性尺度 scaleLinear 相同,對於對數尺度 scaleLog 來說,有 base() 設定對數的基數,預設為 10。
瞭解這些後,上面的例子其實就特別簡單。
時間尺度
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.clear {
clear: both;
}
.fixed-cell {
min-width: 40px;
min-height: 20px;
margin: 5px;
position: fixed;
text-align: center;
border: #969696 solid thin;
padding: 5px;
}
</style>
</head>
<body>
<div id="time" class="clear">
</div>
<script src="../d3.js"></script>
<script>
const start = new Date(2019, 0, 1)
const end = new Date(2019, 11, 31)
const data = []
for(let i = 0; i < 12; i++) {
const date = new Date(start.getTime())
date.setMonth(i)
data.push(date)
}
const time = d3.scaleTime()
.domain([start, end])
.rangeRound([0, 1200])
function render(data, scale, selector) {
d3.select(selector).selectAll('div.fixed-cell')
.data(data)
.enter()
.append('div')
.classed('fixed-cell', true)
.style('margin-left', function (d) {
return scale(d) + 'px'
})
.html(function (d) {
return d3.timeFormat('%x')(d) + '<br/>' + scale(d) + 'px'
})
}
render(data, time, '#time')
</script>
</body>
</html>
複製程式碼
效果如下:
這裡我們建立了定義域在 2019/1/1 到 2019/12/31 的 Date例項,設定其值域為 0 到 1200,data 中的每一個日期資料項都對應到了一個值去設定邊距。
有序尺度
有序尺度是將離散的定義域對映到離散的值域。
經常可以見到的就是搭配d3內建的顏色方案來實現資料對應離散顏色的實現。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.clear {
clear: both;
}
.cell {
min-width: 40px;
min-height: 20px;
margin: 5px;
float: left;
text-align: center;
border: #969696 solid thin;
padding: 5px;
}
</style>
</head>
<body>
<div id="alphabet" class="clear">
<span>Ordinal Scale with Alphabet<br></span>
<span>Mapping [1...10] to ['a'...'j']<br></span>
</div>
<div id="category10" class="clear">
<span>Ordinal Scale with Alphabet<br></span>
<span>Mapping [1...10] to category 10 colors<br></span>
</div>
<script src="../d3.js"></script>
<script>
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const alphabet = d3.scaleOrdinal()
.domain(data)
.range(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'])
function render(data, scale, selector) {
d3.select(selector).selectAll('div.cell')
.data(data)
.enter()
.append('div')
.classed('cell', true)
.style('display', 'inline-block')
.style('background-color', function (d) {
return scale(d).includes('#') ? scale(d) : 'white'
})
.text(function (d) {
return scale(d)
})
}
render(data, alphabet, '#alphabet')
render(data, d3.scaleOrdinal(d3.schemeCategory10), '#category10')
</script>
</body>
</html>
複製程式碼
效果如下:
我們建立了兩個有序尺度,一個將數字和字母對應,一個是用預設的顏色方案建立
在 alphabet 尺度中,不在離散定義域上的值是會重複依照順序來進行對應的:
我們小改下程式碼
const data = [1.1, 1.9, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
const alphabet = d3.scaleOrdinal()
.domain([1, 13])
.range(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'])
複製程式碼
發現效果成了這樣
定義域中只有兩個離散值 1 和 13,他們對應了 a 和 b,然後在 data 的資料對應中,1.1 到 12 都不在定義域內,所以它們離散的對應了字母 c 到 e,並且我們可以得知:不在值域內的離散值輪完一遍是會重複輪值的,並且第一次輪值是從沒有建立對應關係的離散值開始。在值域內的離散值會按照初始順序進行值的對應並固定下來(到 13 時瞬間又對應到了 b )
尺度通過內建的插值器實現了根據不同的輸入選取不同的輸出,另外,插值器在如動畫和佈局管理功能時也起到了核心的作用。下面展示一些在尺度中有大概率使用的三種插值情況:
字串插值
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.clear {
clear: both;
}
.cell {
min-width: 40px;
min-height: 20px;
margin: 5px;
float: left;
text-align: center;
border: #969696 solid thin;
padding: 5px;
}
</style>
</head>
<body>
<div id="font" class="clear"></div>
<script src="../d3.js"></script>
<script>
const data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const sizeScale = d3.scaleLinear()
.domain([0, 10])
.range([
'italic bold 12px/30px Georgia, serif',
'italic bold 120px/180px Georgia, serif'
])
function render(data, scale, selector) {
d3.select(selector).selectAll('div.cell')
.data(data)
.enter()
.append('div')
.classed('cell', true)
.style('display', 'inline-block')
.append('span')
.style('font', function (d) {
return scale(d)
})
.text(function (d, i) {
return i
})
}
render(data, sizeScale, '#font')
</script>
</body>
</html>
複製程式碼
效果如下:
我們將值域設定為['italic bold 12px/30px Georgia, serif', 'italic bold 120px/180px Georgia, serif']
,但是 尺度卻幫我們對映到了各個字型大小的範圍,這是因為 d3 的字串插值器會去找出文字中內嵌的數字,然後針對這些數字進行插值。
顏色插值
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.clear {
clear: both;
}
.control-group {
padding-top: 10px;
margin: 10px;
}
.cell {
min-width: 40px;
min-height: 20px;
margin: 5px;
float: left;
text-align: center;
border: #969696 solid thin;
padding: 5px;
}
</style>
</head>
<body>
<div id="color" class="clear">
<span>Linear Color Interpolation<br></span>
</div>
<div id="color-diverge" class="clear">
<span>Poly-Linear Color Interpolation<br></span>
</div>
<div class="control-group clear">
<button onclick="render(data, divergingScale(5), '#color-diverge')">Pivot at 5</button>
<button onclick="render(data, divergingScale(10), '#color-diverge')">Pivot at 10</button>
<button onclick="render(data, divergingScale(15), '#color-diverge')">Pivot at 15</button>
<button onclick="render(data, divergingScale(20), '#color-diverge')">Pivot at 20</button>
</div>
<script src="../d3.js"></script>
<script>
const max = 21, data = []
for (let i = 0; i < max; i++) {
data.push(i)
}
const colorScale = d3.scaleLinear()
.domain([0, max])
.range(['white', '#4169e1'])
function divergingScale(pivot) {
return d3.scaleLinear()
.domain([0, pivot, max])
.range(['white', '#4169e1', 'white'])
}
function render(data, scale, selector) {
const cells = d3.select(selector).selectAll('div.cell')
.data(data)
cells.enter()
.append('div')
.classed('cell', true)
.merge(cells)
.style('display', 'inline-block')
.style('background-color', function (d) {
return scale(d)
})
.text(function (d, i) {
return i
})
}
render(data, colorScale, '#color')
render(data, divergingScale(5), '#color-diverge')
</script>
</body>
</html>
複製程式碼
效果如下:
內建的顏色插值器同樣幫我們完成了從 white 到 #4169e1 的連續插值,另外我們還使用了分段線性尺度,相當於將兩個線性尺度結合在一起。
複合物件插值器
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.clear {
clear: both;
}
.v-bar {
min-width: 30px;
background-color: #4682b4;
margin-right: 2px;
font-size: 10px;
color: #f0f8ff;
text-align: center;
display: inline-block;
}
</style>
</head>
<body>
<div id="compound" class="clear">
<p>Compound Interpolation </p>
</div>
<script src="../d3.js"></script>
<script>
const max = 21, data = []
for (let i = 0; i < max; i++) {
data.push(i)
}
const compoundScale = d3.scalePow()
.exponent(2)
.domain([0, max])
.range([
{color: '#add8e6', height: '15px'},
{color: '#4169e1', height: '150px'}
])
function render(data, scale, selector) {
d3.select(selector).selectAll('div.v-bar')
.data(data)
.enter()
.append('div')
.classed('v-bar', true)
.style('height', function (d) {
return scale(d).height
})
.style('background-color', function (d) {
return scale(d).color
})
.text(function (d, i) {
return i
})
}
render(data, compoundScale, '#compound')
</script>
</body>
</html>
複製程式碼
效果如下:
同樣的,d3 內建的物件插值器會對物件進行遞迴插值處理,另外當值域的起始物件和結束物件的屬性不一致時,d3 會把不一致的屬性當成不變的量。
下面的尺度函式會把所有值的 height 屬性都認為 15px
:
d3.scaleLinear()
.domain([0, 10])
.range([
{color: '#add8e6', height: '15px'},
{color: '#4169e1'}
])
複製程式碼