d3.js 入門學習記錄(五) 開始繪製座標軸

WanFengZ發表於2019-11-17

座標軸基礎

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)
    }
複製程式碼

效果如下:

d3.js 入門學習記錄(五) 開始繪製座標軸

接著我們來解釋座標軸的渲染:

  1. 座標軸是借於svg來渲染的,所以我們首先在body下新增了個svg元素,然後我們就要在svg上開始繪製座標軸。

  2. 我們通過offset來使座標軸位置錯開。

  3. d3 提供了4個座標軸生成函式,對應四個不同的朝向:

    d3.axisTop(scale) 水平座標軸,刻度位於座標軸之上。

    d3.axisBottom(scale) 水平座標軸,刻度位於座標軸之上。

    d3.axisRight(scale) 垂直座標軸,刻度位於座標軸之右。

    d3.axisLeft(scale) 垂直座標軸,刻度位於座標軸之左。

    座標軸的刻度是依賴於我們之前提到的尺度 scale 的。可以傳參初始化,也可以使用 scale() 設定尺度。 上面的四個函式可以幫我們生成形如 fn(selection) {}的座標軸生成函式。我們通過呼叫 fn(selection) 即可把座標軸渲染在其中,但是習慣上我們更習慣採用 selection.call(fn) 的方式, 它的效果等效於將 selection 傳入引數並呼叫函式。

  4. 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>
複製程式碼

效果如下:

d3.js 入門學習記錄(五) 開始繪製座標軸

繪製完整座標軸和網格線

程式碼如下:

<!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>

複製程式碼

效果如下:

d3.js 入門學習記錄(五) 開始繪製座標軸

  1. svg 的座標起點位於左上角,往下 y 為正,往右 x 為正。
  2. 我們在建立 x 軸和 y軸時分別對他們設定transform 使他們移動到對應的位置。
  3. 因為 y 軸的零點位於最下面,所以傳遞給 yAxis的尺度的值域是顛倒的。
  4. 繪製網格線時,我們找到每個刻度的 g.tick 元素,像其中新增 line 元素並設定起點終點,以座標刻度為原點,下右為正, 所以 x 軸的網格線的終點 y2 為 -(height - 2 * margin), y 軸的網格線的終點 x2 為 width - 2 * margin。

相關文章