尺度
简介
D3 提供多种称为 scale 的结构来支持从模型到可视化模型的映射。尺度不仅包括这种映射,同时也是其他组件的基础,例如过渡和坐标轴。
使用连续尺度
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() // 线性尺度.domain([1, 10]) // 值域.range([1, 10]); // 定义域var linearCapped = d3.scaleLinear().domain([1, 10]).range([1, 20]);var pow = d3.scalePow().exponent(2); // 指数为 2 的幂级尺度var powCapped = d3.scalePow().exponent(2).domain([1, 10]).rangeRound([1, 10]); // rangeRound 会对值域的数字进行向上取整var log = d3.scaleLog(); // 对数尺度var 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>

时间尺度
<div id="time" class="clear"><span>Linear Time Progression<br></span><span>Mapping [01/01/2016, 12/31/2016] to [0, 1200]<br></span></div><script type="text/javascript">var start = new Date(2016, 0, 1), // <-Aend = new Date(2016, 11, 31),range = [0, 1200],time = d3.scaleTime().domain([start, end]) // <-B.rangeRound(range), // <-Cmax = 12,data = [];for (var i = 0; i < max; ++i){ // <-Dvar date = new Date(start.getTime());date.setMonth(start.getMonth() + i);data.push(date);}function render(data, scale, selector) { // <-Ed3.select(selector).selectAll("div.fixed-cell").data(data).enter().append("div").classed("fixed-cell", true).style("margin-left", function(d){ // <-Freturn scale(d) + "px";}).html(function (d) { // <-Gvar format = d3.timeFormat("%x"); // <-Hreturn format(d) + "<br>" + scale(d) + "px";});}render(data, time, "#time");</script>

有序尺度
<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 Color Scale Category 10<br></span><span>Mapping [1..10] to category 10 colors<br></span></div><div id="category20" class="clear"><span>Ordinal Color Scale Category 20<br></span><span>Mapping [1..10] to category 20 colors<br></span></div><div id="category20b" class="clear"><span>Ordinal Color Scale Category 20b<br></span><span>Mapping [1..10] to category 20b colors<br></span></div><div id="category20c" class="clear"><span>Ordinal Color Scale Category 20c<br></span><span>Mapping [1..10] to category 20c colors<br></span></div><script type="text/javascript">var max = 10, data = [];for (var i = 1; i <= max; ++i) data.push(i);var alphabet = d3.scaleOrdinal().domain(data).range(["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]);function render(data, scale, selector) {var cells = d3.select(selector).selectAll("div.cell").data(data);cells.enter().append("div").classed("cell", true).style("display", "inline-block").style("background-color", function(d){return scale(d).indexOf("#") >=0 ? scale(d) : "white";}).text(function (d) {return scale(d);});}render(data, alphabet, "#alphabet");render(data, d3.scaleOrdinal(d3.schemeCategory10), "#category10");render(data, d3.scaleOrdinal(d3.schemeCategory20), "#category20");render(data, d3.scaleOrdinal(d3.schemeCategory20b), "#category20b");render(data, d3.scaleOrdinal(d3.schemeCategory20c), "#category20c");</script>

什么是插值
尺度是这样选取不同的值的:给定一个函数 f(X) 在 x0,x1,x1…,xn 处的值。现有 x’,其值在上述取值点之间。那么,求 f(x’) 近似值的过程叫做插值。
字符串插值
<div id="font" class="clear"><span>Font Interpolation<br></span></div><script type="text/javascript">var max = 11, data = [];var sizeScale = d3.scaleLinear() // <-A.domain([0, max]).range([ // <-B"italic bold 12px/30px Georgia, serif","italic bold 120px/180px Georgia, serif"]);for (var i = 0; i < max; ++i) data.push(i);function render(data, scale, selector) {var cells = d3.select(selector).selectAll("div.cell").data(data);cells.enter().append("div").classed("cell", true).style("display", "inline-block").append("span").style("font", function(d,i){return scale(d);}).text(function(d,i){return i;});}render(data, sizeScale, "#font");</script>

默认情况下,线性标尺将使用 d3.interpolateString 函数来处理基于字符串的值域。d3.interpolateString 函数会找出字符串中内嵌的数字,只针对这些数字进行插值。本例中,实际上是使用线性标尺将定义域映射为字体大小。
颜色插值
<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><script type="text/javascript">var max = 21, data = [];var colorScale = d3.scaleLinear().domain([0, max]).range(["white", "#4169e1"]);var divergingScale = function(pivot) {return d3.scaleLinear().domain([0, pivot, max]).range(["white", "#4169e1", "white"])// 分段标尺是一种非均匀的线性标尺,相当于两种标尺的组合};for (var i = 0; i < max; ++i) data.push(i);function render(data, scale, selector) {var cells = d3.select(selector).selectAll("div.cell").data(data);cells.enter().append("div").merge(cells).classed("cell", true).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><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>

复合对象插值
<div id="compound" class="clear"><span>Compound Interpolation<br></span></div><script type="text/javascript">var max = 21, data = [];var compoundScale = d3.scalePow().exponent(2).domain([0, max]).range([{color:"#add8e6", height:"15px"},{color:"#4169e1", height:"150px"}]);for (var i = 0; i < max; ++i) data.push(i);function render(data, scale, selector) {var bars = d3.select(selector).selectAll("div.v-bar").data(data);bars.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>

坐标轴
基础
<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(){ // <-Asvg = d3.select("body").append("svg").attr("class", "axis").attr("width", width).attr("height", height);}function renderAxis(fn, scale, i){var axis = fn().scale(scale).ticks(5); // 有五个刻度svg.append("g").attr("transform", function(){if([d3.axisTop, d3.axisBottom].indexOf(fn) >= 0)return "translate(" + margin + "," + i * offset + ")";elsereturn "translate(" + i * offset + ", " + 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(2016, 0, 1), new Date(2017, 0, 1)]).range([0, axisWidth]), 3);}</script>

自定义刻度
<script type="text/javascript">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) // 刻度大小 12 px.tickPadding(10) // 标签数字与坐标轴的距离 10 px.tickFormat(d3.format(".0%"));svg.append("g").attr("transform", function(){return "translate(" + margin +"," + margin + ")";}).call(axis);</script>

绘制表格线
<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(){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(){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).attr("y2", 0);}renderYAxis();renderXAxis();</script>

动态调整坐标尺度
<script type="text/javascript">var height = 500,width = 500,margin = 25,xAxis, yAxis, xAxisLength, yAxisLength;var svg = d3.select("body").append("svg").attr("class", "axis").attr("width", width).attr("height", height);function renderXAxis(){xAxisLength = width - 2 * margin;var scale = d3.scaleLinear().domain([0, 100]).range([0, xAxisLength]);xAxis = d3.axisBottom().scale(scale);svg.append("g").attr("class", "x-axis").attr("transform", function(){return "translate(" + margin + "," + (height - margin) + ")";}).call(xAxis);}function renderYAxis(){yAxisLength = height - 2 * margin;var scale = d3.scaleLinear().domain([100, 0]).range([0, yAxisLength]);yAxis = d3.axisLeft().scale(scale);svg.append("g").attr("class", "y-axis").attr("transform", function(){return "translate(" + margin + "," + margin + ")";}).call(yAxis);}function rescale(){var max = Math.round(Math.random() * 100);xAxis.scale().domain([0, max]);svg.select("g.x-axis").transition().call(xAxis);yAxis.scale().domain([max, 0]);svg.select("g.y-axis").transition().call(yAxis);renderXGridlines();renderYGridlines();}function renderXGridlines(){d3.selectAll("g.x-axis g.tick").select("line.grid-line").remove();d3.selectAll("g.x-axis g.tick").append("line").classed("grid-line", true).attr("x1", 0).attr("y1", 0).attr("x2", 0).attr("y2", - yAxisLength);}function renderYGridlines(){d3.selectAll("g.y-axis g.tick").select("line.grid-line").remove();d3.selectAll("g.y-axis g.tick").append("line").classed("grid-line", true).attr("x1", 0).attr("y1", 0).attr("x2", xAxisLength).attr("y2", 0);}renderYAxis();renderXAxis();renderXGridlines();renderYGridlines();</script><div class="control-group"><button onclick="rescale()">ReScale</button></div>

参考
【1】Data Visualization and D3.js(视频)
【2】D3 4.x 数据可视化实战手册@[加]朱启
【3】https://github.com/NickQiZhu/d3-cookbook-v2(D3 4.x 数据可视化实战手册 代码)
【4】D3.js 中文文档
