在项目中我们经常遇到后台返回的数据并不是我们想要的数据,无法直接当作渲染图形的尺寸。对于比较规则的数据我可以预估一个系数(其实是一种映射关系),达到可视化的效果,这映射关系就是一种scale,如数据 747 通过线性或者指数关系在图形上映射为一个长 200px 的柱形,这样我们可以将这种映射应用于整个数据集合和图形集合中达到预期效果。
数学解释:scale 是支持数据模型到可视化模型的数学映射。看下图:
将数据D表现在图形P上
D中的一个数据和图形某一个属性值,如高度或者宽度甚至颜色,形成对应关系
用数学的方式理解,就是数据D到图形P的映射,f 是他们的映射关系
D3 中提供了各种 scale 来解决这种映射关系
常见的连续性 scale
1、线性尺度 scaleLinear()
//数据集合
const data = [1,2,3,4,5]
//线性尺度 并指出定义域domain和值域range
const linear = d3.scaleLinear().domain([1,5]).range([1,5])
const render = (data, scale) => {
d3.select('#d3Container').selectAll('div').data(data)
.enter().append('div')
.classed('cell', true) // 激活已经存在的 cell 样式
.style('display', 'inline-block')
.text(d => scale(d)) // 直接引用scale计算的值
}
render(data, linear)
这样的输出结果和我们直接使用 d 效果一样,但是这里调用了 scaleLinear() ,接着后面指定了 定义域 domain 和 值域 range ,但是他们的区间相同,可以看作是没有任何的数学变化,这样的线性函数记作 f(n) = n ,是一个恒等函数。
当开始改变值域 如 const linear = d3.scaleLinear().domain([1 ,5]).range([1, 100]) ,结果如下
在这里就把 数据 [1,2,3,4,5] 映射为 range 为 [1, 100] 的样子了,数学解释为
- 定义一个线性关系 f(x) = a * x + b
- 定义域 1 <= x <= 5
- 值域 1 <= f(x) <= 100
- D3自动计算常数 a 和 b ,形成1~100连续递增的集合
如上图展示,结果位数不整齐,D3提供了 format 函数,当然可以使用原生js方法如 toFixed parseInt 等 如
- d3.format('.2f')(scale(d)) 表示保留两位小数
-
d3.format('.2')(scale(d)) 表示保留两位数
-
其它参考d3.format()
2、幂级尺度 scalePow().exponent(n) 当n=2时 即 f(x) = x^2
const data = [1,2,3,4,5]
//幂级尺度
const pow = d3.scalePow().exponent(2)
const render = (data, scale) => {
d3.select('#d3Container').selectAll('div').data(data)
.enter().append('div')
.classed('cell', true)
.style('display', 'inline-block')
.text(d => d3.format('.2f')(scale(d))) //保留2位小数
}
render(data, pow)
同样的,在幂级尺度中也可以指明定义域和值域,值域 range 可以直接圆整取值写为 rangeRound
const data = [1,2,3,4,5]
//幂级尺度
const const pow = d3.scalePow().exponent(2)
.domain([1 ,5])
.rangeRound([10, 500])
const render = (data, scale) => {
d3.select('#d3Container').selectAll('div').data(data)
.enter().append('div')
.classed('cell', true)
.style('display', 'inline-block')
.text(d => scale(d))
}
render(data, pow)
3、对数尺度 scaleLog()
......
//对数尺度
const log = d3.scaleLog().domain([1,5]).range([1,2])
......
.text(d => d3.format('.2f')(scale(d)))
......
4、本节完整代码
<script setup>
import { onMounted } from "vue";
import * as d3 from "d3";
const data = [1,2,3,4,5]
//线性尺度
const linear = d3.scaleLinear().domain([1 ,5]).range([1, 100])
//幂级尺度
const pow = d3.scalePow().exponent(2).domain([1 ,5]).rangeRound([10, 500])
//对数尺度
const log = d3.scaleLog().domain([1,5]).range([1,2])
const render = (data, scale) => {
d3.select('#d3Container').selectAll('div').data(data)
.enter().append('div')
.classed('cell', true)
.style('display', 'inline-block')
.text(d => d3.format('.2f')(scale(d)))
}
onMounted(() => {
render(data, log)
})
</script>
<template>
<div id="d3Container" class="container">
</div>
</template>
<style lang="scss">
.container {
padding: 30px;
font-size: 20px;
width: 50vw;
height: 500px;
margin:auto;
background-color: #fbe9d5;
.v-bar {
display: inline-block;
width: 24px;
background-color: #4c6bf8;
font-size: 12px;
color: #fff;
margin-right: 12px;
border-bottom-left-radius: 12px;
border-bottom-right-radius: 12px;
}
.cell{
font-size: 58px;
border: 2px dashed #f97373;
margin: 12px;
}
}
</style>