d3.js 入門學習記錄(四) 尺度scale的使用

WanFengZ發表於2019-11-10

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 &lt;= a*n + b &lt;= 20</span></div>
<div id="pow" class="clear"><span>n^2</span></div>
<div id="pow-capped" class="clear"><span>1 &lt;= a*n^2 + b &lt;= 10</span></div>
<div id="log" class="clear"><span>log(n)</span></div>
<div id="log-capped" class="clear"><span>1 &lt;= a*log(n) + b &lt;= 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>
複製程式碼

效果如下:

d3.js 入門學習記錄(四) 尺度scale的使用

對於尺度而言,大部分方法的作用其實都是一樣的:

  1. domain() 設定定義域
  2. range() 設定值域
  3. 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>
複製程式碼

效果如下:

d3.js 入門學習記錄(四) 尺度scale的使用

這裡我們建立了定義域在 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>
複製程式碼

效果如下:

d3.js 入門學習記錄(四) 尺度scale的使用

我們建立了兩個有序尺度,一個將數字和字母對應,一個是用預設的顏色方案建立

在 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'])
複製程式碼

發現效果成了這樣

d3.js 入門學習記錄(四) 尺度scale的使用

定義域中只有兩個離散值 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>
複製程式碼

效果如下:

d3.js 入門學習記錄(四) 尺度scale的使用

我們將值域設定為['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>
複製程式碼

效果如下:

d3.js 入門學習記錄(四) 尺度scale的使用

內建的顏色插值器同樣幫我們完成了從 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.js 入門學習記錄(四) 尺度scale的使用

同樣的,d3 內建的物件插值器會對物件進行遞迴插值處理,另外當值域的起始物件和結束物件的屬性不一致時,d3 會把不一致的屬性當成不變的量。

下面的尺度函式會把所有值的 height 屬性都認為 15px

d3.scaleLinear()
    .domain([0, 10])
    .range([
        {color: '#add8e6', height: '15px'},
        {color: '#4169e1'}
    ])
複製程式碼

相關文章