座標軸基礎
api文件連結:github.com/d3/d3-axis/…
先上程式碼:
<!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>
</head>
<body>
<div class="control-group">
<button onclick="renderAll(d3.axisBottom)">horizontal bottom</button>
<button onclick="renderAll(d3.axisTop)">horizontal top</button>
<button onclick="renderAll(d3.axisLeft)">vertical left</button>
<button onclick="renderAll(d3.axisRight)">vertical right</button>
</div>
<script src="../d3.js"></script>
<script>
const height = 500,
width = 500,
margin = 25,
offset = 50,
axisWidth = width - 2 * margin
let svg
function createSvg() {
svg = d3.select('body').append('svg')
.classed('axis', true)
.attr('width', width)
.attr('height', height)
}
function renderAxis(fn, scale, i) {
const axis = fn()
.scale(scale)
.ticks(5)
svg.append('g')
.attr('transform', function () {
if ([d3.axisTop, d3.axisBottom].includes(fn)) {
return `translate(${margin},${offset * i})`
} else {
return `translate(${offset * i},${margin})`
}
})
.call(axis)
}
function renderAll(fn) {
if (svg) {
svg.remove()
}
createSvg()
renderAxis(fn, d3.scaleLinear().domain([0, 1000]).range([0, axisWidth]), 1)
renderAxis(fn, d3.scalePow().exponent(2).domain([0, 1000]).range([0, axisWidth]), 2)
renderAxis(fn, d3.scaleTime().domain([new Date(2019, 0, 1), new Date(2020, 0, 1)]).range([0, axisWidth]), 3)
}
複製程式碼
效果如下:
接著我們來解釋座標軸的渲染:
-
座標軸是借於svg來渲染的,所以我們首先在body下新增了個svg元素,然後我們就要在svg上開始繪製座標軸。
-
我們通過offset來使座標軸位置錯開。
-
d3 提供了4個座標軸生成函式,對應四個不同的朝向:
d3.axisTop(scale) 水平座標軸,刻度位於座標軸之上。
d3.axisBottom(scale) 水平座標軸,刻度位於座標軸之上。
d3.axisRight(scale) 垂直座標軸,刻度位於座標軸之右。
d3.axisLeft(scale) 垂直座標軸,刻度位於座標軸之左。
座標軸的刻度是依賴於我們之前提到的尺度 scale 的。可以傳參初始化,也可以使用
scale()
設定尺度。 上面的四個函式可以幫我們生成形如fn(selection) {}
的座標軸生成函式。我們通過呼叫fn(selection)
即可把座標軸渲染在其中,但是習慣上我們更習慣採用selection.call(fn)
的方式, 它的效果等效於將 selection 傳入引數並呼叫函式。 -
tick()
是用來設定刻度相關的,我們會在後面詳細的解釋。
自定義刻度
關於刻度的api如下:
axis.ticks([count[, fromat]])
相當於
axis.tickArguments()
的簡化版, 如axis.ticks(20, 's')
相當於axis.tickArguments([20, 's'])
axis.tickArguments([argument])
引數陣列內可以接收兩個引數,第一個引數作為刻度的參考個數,第二個引數傳遞給
scale.tickFormat()
axis.tickValues([values])
如果指定了 values 陣列,則使用指定的陣列作為刻度而不是自動計算刻度。如果 values 為 null 則清除之前設定的顯示刻度引數,也就是如果之前設定過values 則可以使用 null 將其取消。如果沒有指定 values 則返回當前的刻度值引數,預設為 null。例如使用指定的陣列作為刻度:
const xAxis = d3.axisBottom(xScale) .tickValues([1, 2, 3, 5, 8, 13, 21]); 複製程式碼
axis.tickFormat([format])
如果指定了 format 則設定刻度文字標籤格式化方法。如果沒有指定 format 則返回當前的刻度文字格式化方法,預設為 null。在沒有設定格式化方法的情況下,會使用預設的 scale.tickFormat 去生成刻度文字。 在這種情況下通過 axis.tickArguments 設定的格式化方法會直接被 scale.tickFormat 使用。
axis.tickFormat(d3.format(",.0f")); 複製程式碼
axis.tickSize([size])
如果指定了 size 則同時設定 tickSizeInner 和 tickSizeOuter 刻度的大小,並返回座標軸生成器。如果沒有指定 size 則返回當前的刻度大小,預設為 6。
axis.tickSizeInner([size])
如果指定了 size 則設定內側刻度大小,如果沒有指定 size 則返回當前的刻度大小,預設為 6。內側刻度大小控制著刻度線的長度。
axis.tickSizeOuter([size])
如果指定了 size 則設定外側刻度大小,如果沒有指定 size 則返回當前的刻度大小,預設為 6。外側刻度大小控制著刻度線的長度。外側刻度表示的是座標軸最外側兩端的刻度線。內側刻度和外側刻度不同,內側刻度是一個個單獨的 line 元素,而外側刻度則實際上是座標軸線 path 的一部分。此外外側刻度可能和第一個或最後一個內側刻度重合。
axis.tickPadding([padding])
如果設定了 padding 則設定刻度和刻度文字之間的間距,如果沒有指定 padding 則返回當前的間距,預設為 3 畫素。
<!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>
</head>
<body>
<script src="../d3.js"></script>
<script>
const width = 500,
height = 500,
margin = 25,
axisWidth = width - 2 * margin
const svg = d3.select('body').append('svg')
.classed('axis', true)
.attr('width', width)
.attr('height', height)
const scale = d3.scaleLinear()
.domain([0, 1])
.range([0, axisWidth])
const axis = d3.axisBottom()
.scale(scale)
.ticks(10)
.tickSize(30)
.tickPadding(50)
.tickFormat(d3.format('.0%'))
svg.append('g')
.attr('transform', function () {
return `translate(${margin}, ${margin})`
})
.call(axis)
</script>
</body>
</html>
複製程式碼
效果如下:
繪製完整座標軸和網格線
程式碼如下:
<!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>
.grid-line{
stroke: black;
shape-rendering: crispEdges;
stroke-opacity: .2;
}
</style>
</head>
<body>
<script src="../d3.js"></script>
<script>
const width = 800,
height = 500,
margin = 25
const svg = d3.select('body')
.append('svg')
.classed('axis', true)
.attr('width', width)
.attr('height', height)
function renderXAxis() {
const axisLength = width - 2 * margin
const scale = d3.scaleLinear()
.domain([100, 0])
.range([axisLength, 0])
const xAxis = d3.axisBottom()
.scale(scale)
svg.append('g')
.classed('x-axis', true)
.attr('transform', function () {
return `translate(${margin},${height - margin})`
})
.call(xAxis)
d3.selectAll('g.x-axis g.tick')
.append('line')
.classed('grid-line', true)
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', 0)
.attr('y2', -(height - 2 * margin))
}
function renderYAxis() {
const axisLength = height - 2 * margin
const scale = d3.scaleLinear()
.domain([100, 0])
.range([0, axisLength])
const yAxis = d3.axisLeft()
.scale(scale)
svg.append('g')
.classed('y-axis', true)
.attr('transform', function () {
return `translate(${margin},${margin})`
})
.call(yAxis)
d3.selectAll('g.y-axis g.tick')
.append('line')
.classed('grid-line', true)
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', width - 2 * margin)
.attr('y2', 0)
}
renderXAxis()
renderYAxis()
</script>
</body>
</html>
複製程式碼
效果如下:
- svg 的座標起點位於左上角,往下 y 為正,往右 x 為正。
- 我們在建立 x 軸和 y軸時分別對他們設定transform 使他們移動到對應的位置。
- 因為 y 軸的零點位於最下面,所以傳遞給 yAxis的尺度的值域是顛倒的。
- 繪製網格線時,我們找到每個刻度的 g.tick 元素,像其中新增 line 元素並設定起點終點,以座標刻度為原點,下右為正, 所以 x 軸的網格線的終點 y2 為 -(height - 2 * margin), y 軸的網格線的終點 x2 為 width - 2 * margin。