尺度

简介

D3 提供多种称为 scale 的结构来支持从模型到可视化模型的映射。尺度不仅包括这种映射,同时也是其他组件的基础,例如过渡和坐标轴。

使用连续尺度

  1. div id="linear" class="clear"><span>n</span></div>
  2. <div id="linear-capped" class="clear">
  3. <span>1 &lt;= a*n + b &lt;= 20</span>
  4. </div>
  5. <div id="pow" class="clear"><span>n^2</span></div>
  6. <div id="pow-capped" class="clear">
  7. <span>1 &lt;= a*n^2 + b &lt;= 10</span>
  8. </div>
  9. <div id="log" class="clear"><span>log(n)</span></div>
  10. <div id="log-capped" class="clear">
  11. <span>1 &lt;= a*log(n) + b &lt;= 10</span>
  12. </div>
  13. <script type="text/javascript">
  14. var max = 11, data = [];
  15. for (var i = 1; i < max; ++i) data.push(i);
  16. var linear = d3.scaleLinear() // 线性尺度
  17. .domain([1, 10]) // 值域
  18. .range([1, 10]); // 定义域
  19. var linearCapped = d3.scaleLinear()
  20. .domain([1, 10])
  21. .range([1, 20]);
  22. var pow = d3.scalePow().exponent(2); // 指数为 2 的幂级尺度
  23. var powCapped = d3.scalePow()
  24. .exponent(2)
  25. .domain([1, 10])
  26. .rangeRound([1, 10]); // rangeRound 会对值域的数字进行向上取整
  27. var log = d3.scaleLog(); // 对数尺度
  28. var logCapped = d3.scaleLog()
  29. .domain([1, 10])
  30. .rangeRound([1, 10]);
  31. function render(data, scale, selector) {
  32. d3.select(selector).selectAll("div")
  33. .data(data)
  34. .enter()
  35. .append("div")
  36. .classed("cell", true)
  37. .style("display", "inline-block")
  38. .text(function (d) {
  39. return d3.format(".2")(scale(d), 2);
  40. });
  41. }
  42. render(data, linear, "#linear");
  43. render(data, linearCapped, "#linear-capped");
  44. render(data, pow, "#pow");
  45. render(data, powCapped, "#pow-capped");
  46. render(data, log, "#log");
  47. render(data, logCapped, "#log-capped");
  48. </script>

image.png

时间尺度

  1. <div id="time" class="clear">
  2. <span>Linear Time Progression<br></span>
  3. <span>Mapping [01/01/2016, 12/31/2016] to [0, 1200]<br></span>
  4. </div>
  5. <script type="text/javascript">
  6. var start = new Date(2016, 0, 1), // <-A
  7. end = new Date(2016, 11, 31),
  8. range = [0, 1200],
  9. time = d3.scaleTime().domain([start, end]) // <-B
  10. .rangeRound(range), // <-C
  11. max = 12,
  12. data = [];
  13. for (var i = 0; i < max; ++i){ // <-D
  14. var date = new Date(start.getTime());
  15. date.setMonth(start.getMonth() + i);
  16. data.push(date);
  17. }
  18. function render(data, scale, selector) { // <-E
  19. d3.select(selector).selectAll("div.fixed-cell")
  20. .data(data)
  21. .enter()
  22. .append("div")
  23. .classed("fixed-cell", true)
  24. .style("margin-left", function(d){ // <-F
  25. return scale(d) + "px";
  26. })
  27. .html(function (d) { // <-G
  28. var format = d3.timeFormat("%x"); // <-H
  29. return format(d) + "<br>" + scale(d) + "px";
  30. });
  31. }
  32. render(data, time, "#time");
  33. </script>

image.png

有序尺度

  1. <div id="alphabet" class="clear">
  2. <span>Ordinal Scale with Alphabet<br></span>
  3. <span>Mapping [1..10] to ["a".."j"]<br></span>
  4. </div>
  5. <div id="category10" class="clear">
  6. <span>Ordinal Color Scale Category 10<br></span>
  7. <span>Mapping [1..10] to category 10 colors<br></span>
  8. </div>
  9. <div id="category20" class="clear">
  10. <span>Ordinal Color Scale Category 20<br></span>
  11. <span>Mapping [1..10] to category 20 colors<br></span>
  12. </div>
  13. <div id="category20b" class="clear">
  14. <span>Ordinal Color Scale Category 20b<br></span>
  15. <span>Mapping [1..10] to category 20b colors<br></span>
  16. </div>
  17. <div id="category20c" class="clear">
  18. <span>Ordinal Color Scale Category 20c<br></span>
  19. <span>Mapping [1..10] to category 20c colors<br></span>
  20. </div>
  21. <script type="text/javascript">
  22. var max = 10, data = [];
  23. for (var i = 1; i <= max; ++i) data.push(i);
  24. var alphabet = d3.scaleOrdinal()
  25. .domain(data)
  26. .range(["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]);
  27. function render(data, scale, selector) {
  28. var cells = d3.select(selector).selectAll("div.cell")
  29. .data(data);
  30. cells.enter()
  31. .append("div")
  32. .classed("cell", true)
  33. .style("display", "inline-block")
  34. .style("background-color", function(d){
  35. return scale(d).indexOf("#") >=0 ? scale(d) : "white";
  36. })
  37. .text(function (d) {
  38. return scale(d);
  39. });
  40. }
  41. render(data, alphabet, "#alphabet");
  42. render(data, d3.scaleOrdinal(d3.schemeCategory10), "#category10");
  43. render(data, d3.scaleOrdinal(d3.schemeCategory20), "#category20");
  44. render(data, d3.scaleOrdinal(d3.schemeCategory20b), "#category20b");
  45. render(data, d3.scaleOrdinal(d3.schemeCategory20c), "#category20c");
  46. </script>

image.png

什么是插值

尺度是这样选取不同的值的:给定一个函数 f(X) 在 x0,x1,x1…,xn 处的值。现有 x’,其值在上述取值点之间。那么,求 f(x’) 近似值的过程叫做插值。

字符串插值

  1. <div id="font" class="clear">
  2. <span>Font Interpolation<br></span>
  3. </div>
  4. <script type="text/javascript">
  5. var max = 11, data = [];
  6. var sizeScale = d3.scaleLinear() // <-A
  7. .domain([0, max])
  8. .range([ // <-B
  9. "italic bold 12px/30px Georgia, serif",
  10. "italic bold 120px/180px Georgia, serif"
  11. ]);
  12. for (var i = 0; i < max; ++i) data.push(i);
  13. function render(data, scale, selector) {
  14. var cells = d3.select(selector).selectAll("div.cell")
  15. .data(data);
  16. cells.enter()
  17. .append("div")
  18. .classed("cell", true)
  19. .style("display", "inline-block")
  20. .append("span")
  21. .style("font", function(d,i){
  22. return scale(d);
  23. })
  24. .text(function(d,i){return i;});
  25. }
  26. render(data, sizeScale, "#font");
  27. </script>

image.png

默认情况下,线性标尺将使用 d3.interpolateString 函数来处理基于字符串的值域。d3.interpolateString 函数会找出字符串中内嵌的数字,只针对这些数字进行插值。本例中,实际上是使用线性标尺将定义域映射为字体大小。

颜色插值

  1. <div id="color" class="clear">
  2. <span>Linear Color Interpolation<br></span>
  3. </div>
  4. <div id="color-diverge" class="clear">
  5. <span>Poly-Linear Color Interpolation<br></span>
  6. </div>
  7. <script type="text/javascript">
  8. var max = 21, data = [];
  9. var colorScale = d3.scaleLinear()
  10. .domain([0, max])
  11. .range(["white", "#4169e1"]);
  12. var divergingScale = function(pivot) {
  13. return d3.scaleLinear()
  14. .domain([0, pivot, max])
  15. .range(["white", "#4169e1", "white"])
  16. // 分段标尺是一种非均匀的线性标尺,相当于两种标尺的组合
  17. };
  18. for (var i = 0; i < max; ++i) data.push(i);
  19. function render(data, scale, selector) {
  20. var cells = d3.select(selector).selectAll("div.cell")
  21. .data(data);
  22. cells.enter()
  23. .append("div").merge(cells)
  24. .classed("cell", true)
  25. .style("display", "inline-block")
  26. .style("background-color", function(d){
  27. return scale(d);
  28. })
  29. .text(function(d,i){return i;});
  30. }
  31. render(data, colorScale, "#color");
  32. render(data, divergingScale(5), "#color-diverge");
  33. </script>
  34. <div class="control-group clear">
  35. <button onclick="render(data, divergingScale(5), '#color-diverge')">Pivot at 5</button>
  36. <button onclick="render(data, divergingScale(10), '#color-diverge')">Pivot at 10</button>
  37. <button onclick="render(data, divergingScale(15), '#color-diverge')">Pivot at 15</button>
  38. <button onclick="render(data, divergingScale(20), '#color-diverge')">Pivot at 20</button>
  39. </div>

image.png

复合对象插值

  1. <div id="compound" class="clear">
  2. <span>Compound Interpolation<br></span>
  3. </div>
  4. <script type="text/javascript">
  5. var max = 21, data = [];
  6. var compoundScale = d3.scalePow()
  7. .exponent(2)
  8. .domain([0, max])
  9. .range([
  10. {color:"#add8e6", height:"15px"},
  11. {color:"#4169e1", height:"150px"}
  12. ]);
  13. for (var i = 0; i < max; ++i) data.push(i);
  14. function render(data, scale, selector) {
  15. var bars = d3.select(selector).selectAll("div.v-bar")
  16. .data(data);
  17. bars.enter()
  18. .append("div")
  19. .classed("v-bar", true)
  20. .style("height", function(d){
  21. return scale(d).height;
  22. })
  23. .style("background-color", function(d){
  24. return scale(d).color;
  25. })
  26. .text(function(d,i){return i;});
  27. }
  28. render(data, compoundScale, "#compound");
  29. </script>

image.png

坐标轴

基础

  1. <div class="control-group">
  2. <button onclick="renderAll(d3.axisBottom)">//坐标轴支持四种朝向:上、下、左、右
  3. horizontal bottom
  4. </button>
  5. <button onclick="renderAll(d3.axisTop)">
  6. horizontal top
  7. </button>
  8. <button onclick="renderAll(d3.axisLeft)">
  9. vertical left
  10. </button>
  11. <button onclick="renderAll(d3.axisRight)">
  12. vertical right
  13. </button>
  14. </div>
  15. <script type="text/javascript">
  16. var height = 500,
  17. width = 500,
  18. margin = 25,
  19. offset = 50,
  20. axisWidth = width - 2 * margin,
  21. svg;
  22. function createSvg(){ // <-A
  23. svg = d3.select("body").append("svg")
  24. .attr("class", "axis")
  25. .attr("width", width)
  26. .attr("height", height);
  27. }
  28. function renderAxis(fn, scale, i){
  29. var axis = fn()
  30. .scale(scale)
  31. .ticks(5); // 有五个刻度
  32. svg.append("g")
  33. .attr("transform", function(){
  34. if([d3.axisTop, d3.axisBottom].indexOf(fn) >= 0)
  35. return "translate(" + margin + "," + i * offset + ")";
  36. else
  37. return "translate(" + i * offset + ", " + margin + ")";
  38. })
  39. .call(axis);
  40. }
  41. function renderAll(fn){
  42. if(svg) svg.remove();
  43. createSvg();
  44. renderAxis(fn, d3.scaleLinear()
  45. .domain([0, 1000])
  46. .range([0, axisWidth]), 1);
  47. renderAxis(fn, d3.scalePow()
  48. .exponent(2)
  49. .domain([0, 1000])
  50. .range([0, axisWidth]), 2);
  51. renderAxis(fn, d3.scaleTime()
  52. .domain([new Date(2016, 0, 1), new Date(2017, 0, 1)])
  53. .range([0, axisWidth]), 3);
  54. }
  55. </script>

1.gif

自定义刻度

  1. <script type="text/javascript">
  2. var height = 500,
  3. width = 500,
  4. margin = 25,
  5. axisWidth = width - 2 * margin;
  6. var svg = d3.select("body").append("svg")
  7. .attr("class", "axis")
  8. .attr("width", width)
  9. .attr("height", height);
  10. var scale = d3.scaleLinear()
  11. .domain([0, 1]).range([0, axisWidth]);
  12. var axis = d3.axisBottom()
  13. .scale(scale)
  14. .ticks(10)
  15. .tickSize(12) // 刻度大小 12 px
  16. .tickPadding(10) // 标签数字与坐标轴的距离 10 px
  17. .tickFormat(d3.format(".0%"));
  18. svg.append("g")
  19. .attr("transform", function(){
  20. return "translate(" + margin +
  21. "," + margin + ")";
  22. })
  23. .call(axis);
  24. </script>

image.png

绘制表格线

  1. <script type="text/javascript">
  2. var height = 500,
  3. width = 500,
  4. margin = 25;
  5. var svg = d3.select("body").append("svg")
  6. .attr("class", "axis")
  7. .attr("width", width)
  8. .attr("height", height);
  9. function renderXAxis(){
  10. var axisLength = width - 2 * margin;
  11. var scale = d3.scaleLinear()
  12. .domain([0, 100])
  13. .range([0, axisLength]);
  14. var xAxis = d3.axisBottom()
  15. .scale(scale);
  16. svg.append("g")
  17. .attr("class", "x-axis")
  18. .attr("transform", function(){
  19. return "translate(" + margin + "," + (height - margin) + ")";
  20. })
  21. .call(xAxis);
  22. d3.selectAll("g.x-axis g.tick")
  23. .append("line")
  24. .classed("grid-line", true)
  25. .attr("x1", 0)
  26. .attr("y1", 0)
  27. .attr("x2", 0)
  28. .attr("y2", - (height - 2 * margin));
  29. }
  30. function renderYAxis(){
  31. var axisLength = height - 2 * margin;
  32. var scale = d3.scaleLinear()
  33. .domain([100, 0])
  34. .range([0, axisLength]);
  35. var yAxis = d3.axisLeft()
  36. .scale(scale);
  37. svg.append("g")
  38. .attr("class", "y-axis")
  39. .attr("transform", function(){
  40. return "translate(" + margin + "," + margin + ")";
  41. })
  42. .call(yAxis);
  43. d3.selectAll("g.y-axis g.tick")
  44. .append("line")
  45. .classed("grid-line", true)
  46. .attr("x1", 0)
  47. .attr("y1", 0)
  48. .attr("x2", axisLength)
  49. .attr("y2", 0);
  50. }
  51. renderYAxis();
  52. renderXAxis();
  53. </script>

image.png

动态调整坐标尺度

  1. <script type="text/javascript">
  2. var height = 500,
  3. width = 500,
  4. margin = 25,
  5. xAxis, yAxis, xAxisLength, yAxisLength;
  6. var svg = d3.select("body").append("svg")
  7. .attr("class", "axis")
  8. .attr("width", width)
  9. .attr("height", height);
  10. function renderXAxis(){
  11. xAxisLength = width - 2 * margin;
  12. var scale = d3.scaleLinear()
  13. .domain([0, 100])
  14. .range([0, xAxisLength]);
  15. xAxis = d3.axisBottom()
  16. .scale(scale);
  17. svg.append("g")
  18. .attr("class", "x-axis")
  19. .attr("transform", function(){
  20. return "translate(" + margin + "," + (height - margin) + ")";
  21. })
  22. .call(xAxis);
  23. }
  24. function renderYAxis(){
  25. yAxisLength = height - 2 * margin;
  26. var scale = d3.scaleLinear()
  27. .domain([100, 0])
  28. .range([0, yAxisLength]);
  29. yAxis = d3.axisLeft()
  30. .scale(scale);
  31. svg.append("g")
  32. .attr("class", "y-axis")
  33. .attr("transform", function(){
  34. return "translate(" + margin + "," + margin + ")";
  35. })
  36. .call(yAxis);
  37. }
  38. function rescale(){
  39. var max = Math.round(Math.random() * 100);
  40. xAxis.scale().domain([0, max]);
  41. svg.select("g.x-axis")
  42. .transition()
  43. .call(xAxis);
  44. yAxis.scale().domain([max, 0]);
  45. svg.select("g.y-axis")
  46. .transition()
  47. .call(yAxis);
  48. renderXGridlines();
  49. renderYGridlines();
  50. }
  51. function renderXGridlines(){
  52. d3.selectAll("g.x-axis g.tick")
  53. .select("line.grid-line")
  54. .remove();
  55. d3.selectAll("g.x-axis g.tick")
  56. .append("line")
  57. .classed("grid-line", true)
  58. .attr("x1", 0)
  59. .attr("y1", 0)
  60. .attr("x2", 0)
  61. .attr("y2", - yAxisLength);
  62. }
  63. function renderYGridlines(){
  64. d3.selectAll("g.y-axis g.tick")
  65. .select("line.grid-line").remove();
  66. d3.selectAll("g.y-axis g.tick")
  67. .append("line")
  68. .classed("grid-line", true)
  69. .attr("x1", 0)
  70. .attr("y1", 0)
  71. .attr("x2", xAxisLength)
  72. .attr("y2", 0);
  73. }
  74. renderYAxis();
  75. renderXAxis();
  76. renderXGridlines();
  77. renderYGridlines();
  78. </script>
  79. <div class="control-group">
  80. <button onclick="rescale()">ReScale</button>
  81. </div>

1.gif

参考

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