# d3-scale d3比例尺
对于 continuous(连续的) 定量数据,通常会使用 linear scale(线性比例尺). (对于时间序列则使用 time scale(时间比例尺).) 如果需要也可以使用 power(幂) 或 log(对数) 比例尺。quantize scale(量化比例尺) 可以将连续数据四舍五入到一组固定的离散值中,可以用来生成离散数据。类似的 quantile scale(分位数比例尺) 从样本总体计算分位数,而 threshold scale(阈值比例尺) 可以为一组连续数据指定分割阈值。
对于离散的顺序(有序)或者分类(无序)数据,ordinal scale(序数比例尺) 指定从一组数据值到一组相应视觉属性的显式映射(比如颜色)。相关的 band 和 point 在对分类数据进行位置编码时很有用,如条形图以及分类散点图。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Quantitative Scales</title>
<link rel="stylesheet" type="text/css" href="../../css/styles.css"/>
<script src="http://d3js.org/d3.v6.min.js"></script>
</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 type="text/javascript">
var max = 11, data = [];
for (var i = 1; i < max; ++i) data.push(i);
var linear = d3.scaleLinear() // <-A
.domain([1, 10]) // <-B
.range([1, 10]); // <-C
var linearCapped = d3.scaleLinear()
.domain([1, 10])
.range([1, 20]); // <-D
var pow = d3.scalePow().exponent(2); // <-E
var powCapped = d3.scalePow() // <-F
.exponent(2)
.domain([1, 10])
.rangeRound([1, 10]); // <-G
var log = d3.scaleLog(); // <-H
var logCapped = d3.scaleLog() // <-I
.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) {
console.log(scale(d))
// console.log(d3.format(".2")(scale(d), 2))
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>
比例尺
- A是恒等线性比例尺
- D定义域值域不等,f(n)=a*n+b; 1<=f(n)<=20
- 其他也同上
<!-- Code from d3-graph-gallery.com -->
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v6.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<script>
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 50},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/3_TwoNumOrdered_comma.csv",
// When reading the csv, I must format variables:
function(d){
console.log(d)
return { date : d3.timeParse("%Y-%m-%d")(d.date), value : d.value }
})
.then(data=>{
console.log(data)
// Add X axis --> it is a date format
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) {
return d.date;
}))
.range([ 0, width ]);
console.log(x)
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.value; })])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y));
// Add the area
svg.append("path")
.datum(data)
.attr("fill", "#cce5df")
.attr("stroke", "#69b3a2")
.attr("stroke-width", 1.5)
.attr("d", d3.area()
.x(function(d) { return x(d.date) })
.y0(y(0))
.y1(function(d) { return y(d.value) })
)
})
</script>
# d3.scaleTime 时间比例尺
定义域和值域,分别被称为 domain 和 range 相当于将domain中的数据集映射到range的数据集中。最后还是个执行的函数。
//创建时间比例尺
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) { return d.date; }))
.range([ 0, width ]);
//添加g的位置
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));//将坐标轴绑定于这里
# d3.scaleLinear 线性比例尺
let scale = d3.scaleLinear().domain([1,4,5]).range([0,70,100])
console.log(scale(4.1))//71
- d3.scaleLinear() 创建一个线性比例尺
- linear(x) 输入一个x,返回值域内对应的值
- linear.invert(y) 输入一个y,找出对应的x
- linear.domain([a,b]) 设置/获取定义域
- linear.range([a,b]) 设置/获取值域
- linear.rangeRound([a,b])代替range的话,比例尺输出会四舍五入,为整数
- linear.clamp([boolean]) 默认false,当超出定义域,依然能得到值,这个值可能也超过值域范围,如果为true,则任何超过值域范围的值都会被收缩到值域范围内
- linear.nice([count]) 将定义范围扩展到理想状态,如[0.5000005,0.9]变为[0.5,0.9]
- linear.ticks() 选取坐标轴刻度
- linear.tickFormat()
let linear = d3.scaleLinear()
.domain([-20,20])
.range([0,100])
let ticks = linear.ticks(5)
console.log(ticks)
//[-20, -10, 0, 10, 20]
# d3.scaleBand() 序数比例尺
d3.scaleBand()并不是一个连续性的比例尺,domain()中使用一个数组,不过range()需要是一个连续域。当输入不是domain()中的数据集时,返回undefined。
let scale = d3.scaleBand().domain([1,2,3,4]).range([0,100])
console.log(scale(3))//50
console.log(scale(5))//undefined
# d3.scaleOrdinal() 序数比例尺
d3.scaleOrdinal()的输入域和输出域都使用离散的数据,如果是非domain的值,会返回range中的某一个,切记
let scale = d3.scaleOrdinal().domain(['jack', 'rose', 'john']).range(["t1", "skt", "faker"])
console.log(scale('rose'))//skt
- 当不设置定义域时也可以生效,值域写在函数里或者range里,默认按顺序的定义域
d3.scaleOrdinal().range(["#117312","#278391"])
d3.scaleOrdinal(["#123411","#323461","#189317"])
d3.scaleOrdinal(d3.schemeCategory10)
# d3.scaleQuantize() 量化比例尺
d3.scaleQuantize()也属于连续性比例尺。定义域是连续的,而输出域是离散的。
let scale = d3.scaleQuantize().domain([0, 10]).range(['small', 'medium', 'long'])
console.log(scale(2))//small
console.log(scale(5.5))//medium
console.log(scale(9))//long
console.log(scale(-2))//small
console.log(scale(20))//long
# d3颜色比例尺
console.log(d3.schemeCategory10)//V6保留
console.log(d3.schemeCategory20)//V5移除
console.log(d3.schemeCategory20b)//V5移除
console.log(d3.schemeCategory20c)//V5移除
# d3 scalePow幂级尺度
var pow=d3.scalePow().exponent(n)
//f(n)=n^2
var pow=d3.scalePow().exponent(2).domain([1,10]).rangeRound([1,10])
// f(n)=a*n+b;1<=f(n)<=10
# d3 scalelog 对数尺度
var log =d3.scaleLog()
//默认底数为10
var logCapped = d3.scaleLog() // <-I
.domain([1, 10])
.rangeRound([1, 10]);
//f(n) = a*log(n)+b (1<=f(n)<=10)
# d3.scaleQuantile分位比例尺
var sampleArray = [423,124,66,424,58,10,900,44,1];
var qScale = d3.scaleQuantile().domain(sampleArray).range([0,1,2]);
console.log(qScale(423))//2
console.log(qScale(20))//0
分位数刻度获取一系列值并将它们重新分配到一个集合中同样大小的箱子
# invert()与invertExtent()方法
上述的各种使用比例尺的例子都相当于一个正序的过程,从domain的数据集映射到range数据集中,那么有没有逆序的过程呢?D3中提供了invert()以及invertExtent()方法可以实现这个过程。
let scale = d3.scaleLinear().domain([1,5]).range([0,100])
scale.invert(50) // 输出:3
let scale2 = d3.scaleQuantize().domain([0,10]).range(['small', 'big'])
scale2.invertExtent('small') // 输出:[0,5]
# d3axis坐标轴-d3.axisBottom
d3.axisBottom(xScale)//定义一个axis,由bottom可知,是朝下的
d3.axisLeft 定义一个axis,朝左的;同理还有另外两个坐标轴。
d3.axisTop - 创建一个新的刻度在上的坐标轴生成器
d3.axisRight - 创建一个新的刻度在右的坐标轴生成器
d3.axisBottom - 创建一个新的刻度在下的坐标轴生成器
d3.axisLeft - 创建一个新的刻度在左的坐标轴生成器
axis - 为指定的选择器生成一个坐标轴
axis.scale - 设置坐标轴的比例尺
axis - 为指定的选择器生成一个坐标轴
axis.scale - 设置坐标轴的比例尺
axis.ticks - 自定义刻度的显示方式以及格式化刻度
axis.tickArguments - 自定义如何生成刻度或者格式化刻度
axis.tickValues - 指定固定的刻度值
axis.tickFormat - 指定固定的刻度格式化方式.
axis.tickSize - 设置刻度大小(方向)
axis.tickSizeInner - 设置内侧刻度大小.
axis.tickSizeOuter - 设置外侧(坐标轴两端)刻度大小.
axis.tickPadding - 设置刻度和刻度文本之间的间距
let width=600
let height=600
let svg=d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
let xScale =d3.scaleLinear()
.domain([0,10])
.range([0,300])
let axis=d3.axisTop(xScale)
let gAxis =svg.append("g")
.attr("transform","translate(200,80)").call(axis)
- d3.axisTop() 创建一个方向向上的坐标轴
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
<style>
*{
margin:0;
padding:0
}
.axis path,.axis line{
fill:none;
stroke: #0AAFE6;
shape-rendering: crispedges;
}
.axis text{
color:orange
}
</style>
</head>
<body>
</body>
</html>
<script src="https://d3js.org/d3.v5.min.js"></script>
<!-- <script type="text/javascript" src="d3.js"></script> -->
<script>
let width=600
let height=600
let svg=d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
let xScale =d3.scaleLinear()
.domain([0,10])
.range([0,300])
let axis=d3.axisTop(xScale)
let gAxis =svg.append("g")
.attr("transform","translate(200,80)").attr("class","axis").call(axis)
</script>
let width=600
let height=600
let svg=d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
let xScale =d3.scaleLinear()
.domain([0,10])
.range([0,300])
let axis=d3.axisTop(xScale).tickValues([3,5.5,7])
let gAxis =svg.append("g")
.attr("transform","translate(200,80)").attr("class","axis").call(axis)
let width=600
let height=600
let svg=d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
let xScale =d3.scaleLinear()
.domain([0,10])
.range([0,300])
let axis=d3.axisTop(xScale).ticks(5)
let gAxis =svg.append("g")
.attr("transform","translate(200,80)").attr("class","axis").call(axis)
let width=600
let height=600
let svg=d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
let xScale =d3.scaleLinear()
.domain([0,10])
.range([0,300])
let axis=d3.axisBottom(xScale).ticks(5).tickSize(-5)
let gAxis =svg.append("g")
.attr("transform","translate(200,80)").attr("class","axis").call(axis)
- tickSize() 控制轴线上的刻度线长度和方向
let width=600
let height=600
let svg=d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
let xScale =d3.scaleLinear()
.domain([0,10])
.range([0,300])
let axis=d3.axisBottom(xScale).ticks(5).tickSize(-5).tickFormat(d3.format("$0.1f"))
let gAxis =svg.append("g")
.attr("transform","translate(200,80)").attr("class","axis").call(axis)
- tickFormat 格式化
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
<style>
*{
margin:0;
padding:0
}
.axis path,.axis line{
fill:none;
stroke: #0AAFE6;
shape-rendering: crispedges;
}
.axis text{
color:orange
}
</style>
</head>
<body>
</body>
</html>
<script src="https://d3js.org/d3.v5.min.js"></script>
<!-- <script type="text/javascript" src="d3.js"></script> -->
<script>
let xAxisWidth = 300
let yAxisWidth = 300
let dataset = [50,43,120,87,99,167,142]
var width = 400
var height = 400
var svg=d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
let xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.range([0,300])
var padding={top:20,right:20,bottom:20,left:20};
let yScale = d3.scaleLinear()
.domain([0,d3.max(dataset)])
.rangeRound([0,yAxisWidth])
var rect=svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("fill","steelblue")
.attr("x",function(d,i){return padding.left+xScale(i)})
.attr("y",function(d,i){return height - padding.bottom- yScale(d)})
.attr("width",30)
.attr("height",function(d){return yScale(d)})
var text=svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.attr("fill","white")
.attr("font-size","14px")
.attr("text-anchor","middle")
.attr("x",function(d,i){
return padding.left +xScale(i)
})
.attr("y",function(d){
return height - padding.bottom - yScale(d)
})
.attr("dx",15)
.attr("dy","1em")
.text(function(d){return yScale(d)})
let xAxis =d3.axisBottom(xScale)
let yAxis =d3.axisLeft(yScale)
let gxAxis =svg.append("g")
.attr("class","axis")
.attr("transform","translate(12,380)").call(xAxis)
</script>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Basic Axes</title>
<link rel="stylesheet" type="text/css" href="../../css/styles.css"/>
<script src="http://d3js.org/d3.v6.min.js"></script>
</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 type="text/javascript">
var height = 500,
width = 500,
margin = 25,
offset = 50,
axisWidth = width - 2 * margin,
svg;
function createSvg(){ // <-A
svg = d3.select("body").append("svg") // <-B
.attr("class", "axis") // <-C
.attr("width", width)
.attr("height", height);
}
function renderAxis(fn, scale, i){
var axis = fn() // <-D
.scale(scale) // <-E
.ticks(5); // <-G
svg.append("g")
.attr("transform", function(){ // <-H
if([d3.axisTop, d3.axisBottom].indexOf(fn) >= 0)
return "translate(" + margin + "," + i * offset + ")";
else
return "translate(" + i * offset + ", " + margin + ")";
})
.call(axis); // <-I
}
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(2016, 0, 1), new Date(2017, 0, 1)])
.range([0, axisWidth]), 3);
}
</script>
</body>
</html>
# d3 ticks
大约生成n可刻度点
var axis = fn() // <-D
.scale(scale) // <-E
.ticks(5); // <-G
var height = 500,
width = 500,
margin = 25,
axisWidth = width - 2 * margin;
var svg = d3.select("body").append("svg")
.attr("class", "axis")
.attr("width", width)
.attr("height", height);
var scale = d3.scaleLinear()
.domain([0, 1]).range([0, axisWidth]);
var axis = d3.axisBottom()
.scale(scale)
.ticks(10)
.tickSize(12) // <-A
.tickPadding(50) // <-B
.tickFormat(d3.format(".0%")); // <-C
svg.append("g")
.attr("transform", function(){
return "translate(" + margin +
"," + margin + ")";
})
.call(axis);
tickSize自定义刻度大小 tickPadding标签与坐标轴距离
tickFormat可深度定制返回值
.tickFormat(function(v){
return xxxx
})
# 网格线功能
- 当创建好坐标轴后,坐标轴上的每个点都是有一个g元素包裹着,而且它的class默认名为tick
- svg:g元素中的所有子元素,都自动进行了基础坐标系转换[g坐标上的点只需要考虑相对g的相对坐标就可以了]
- 如果还需要动态调整网格线,可以使用过渡和remove清除之前的网格重新绘制
console.log(d3.selectAll("g.x-axis g.tick")._groups[0][1])
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Axis Grid Lines</title>
<link rel="stylesheet" type="text/css" href="../../css/styles.css"/>
<script src="http://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<script type="text/javascript">
var height = 500,
width = 500,
margin = 25;
var svg = d3.select("body").append("svg")
.attr("class", "axis")
.attr("width", width)
.attr("height", height);
function renderXAxis(){
var axisLength = width - 2 * margin;
var scale = d3.scaleLinear()
.domain([0, 100])
.range([0, axisLength]);
var xAxis = d3.axisBottom()
.scale(scale);
svg.append("g")
.attr("class", "x-axis")
.attr("transform", function(){ // <-A
return "translate(" + margin + "," + (height - margin) + ")";
})
.call(xAxis);
d3.selectAll("g.x-axis g.tick") // <-B
.append("line") // <-C
.classed("grid-line", true)
.attr("x1", 0) // <-D
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", - (height - 2 * margin)); // <-E
}
function renderYAxis(){
var axisLength = height - 2 * margin;
var scale = d3.scaleLinear()
.domain([100, 0])
.range([0, axisLength]);
var yAxis = d3.axisLeft()
.scale(scale);
svg.append("g")
.attr("class", "y-axis")
.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", axisLength) // <-F
.attr("y2", 0);
}
renderYAxis();
renderXAxis();
</script>
</body>
</html>