网站搜索

javascript - 如何使用 d3.js 创建水平图例


我一直在尝试使用 d3.js 为我的图表创建一个水平图例。我在使用动态标签时一直难以正确设置 x 轴的间距。

问题是标签的宽度不一致,这里有一个完整的示例,这是我用来计算 x 位置的函数:

function legendXPosition(data, position, avgFontWidth){
    if(position == 0){
        return 0;
    } else {
        var xPostiion = 0;
        for(i = 0; i < position; i++){
            xPostiion += (data[i].length * avgFontWidth);
        }
        return xPostiion;
    }
}

有人有什么建议可以改进这个吗?

答案#1

我建议参考这个问题:SVG 获取文本元素的宽度

像你现在这样渲染第一个图例条目。存储此条目,或分配 ID,以便你可以通过选择查找它们。

在渲染后续条目时,获取前一个 'text' 元素及 x 偏移量。使用前一个文本元素的精确宽度计算新的图例条目偏移量

var myNewXOffset = myPreviousXOffset + myPreviousText.getBBox().width

答案#2

修改了代码以获得以下结果

你的标签宽度不一致的原因有两个因素。这两个因素都与你计算 x 位置的函数有关:

  1. avgFontWidth 参数

它应该更小。你在示例中将其值分配为 15,但是实际的 averageFontWidth 远小于 15,它在 5 到 7 之间。你的图例标签之间的额外间距来自这个不准确的平均字体宽度值。

你的 avgFontWidth 只是一个平均值。实际上,字体的宽度是变化的。例如,下图中的 r 比 P 窄得多。

因此,当字体家族以某种方式设置时,五个字母的单词 rrrrr 可能比 PPPPP 短得多。总之,你不应该使用 avgFontWidth。正如 cmonkey 所建议的,你应该使用 SVG 获取文本元素宽度。

  1. xPostiion += (data[i].length * avgFontWidth) 的逻辑 上述公式旨在计算文本的长度,但还应加上标签的宽度,xPostiion 应该是
xPostiion += (data[i].length * avgFontWidth) + widthOftheLabel

修改后的代码:

function drawlinegraph(data, startDateString, endDateString, maxValue, minValue, colour,
  divID, labels) {
  var m = {
      top: 60,
      right: 0,
      bottom: 35,
      left: 80
    },
    w = 770 - m.left - m.right,
    h = 180 - m.top - m.bottom,
    dateFormat = "%Y-%m-%d";

  labels = ["ada", "adas", "asdasdasd", "sd"];

  var parseDate = d3.time.format(dateFormat).parse;

  var startDate = parseDate(startDateString);
  var endDate = parseDate(endDateString);

  // Define the x scale
  var x = d3.time.scale()
    .domain([startDate, endDate])
    .range([0, w]);

  // Format x-axis labels
  x.tickFormat(d3.time.format(dateFormat));

  // Define the y scale
  var y = d3.scale.linear()
    .domain([minValue, maxValue])
    .range([h, 0]);

  var graph = d3.select(divID).append("svg:svg")
    .attr("width", w + m.right + m.left)
    .attr("height", h + m.top + m.bottom)
    .append("svg:g")
    .attr("transform", "translate(" + m.left + "," + m.top + ")");

  // create x-axis
  var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom")
    .ticks(4)
    .tickSize(-h)
    .tickFormat(d3.time.format("%Y/%m"));

  // Add the x-axis.
  graph.append("svg:g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + h + ")")
    .call(xAxis);

  // Overide the default behaviour of d3 axis labels
  d3.selectAll(".x.axis g text")[0].forEach(function(e) {
    e.attributes[0].value = 8; // y
  });

  // create y-axis
  var yAxisLeft = d3.svg.axis()
    .scale(y)
    .ticks(12)
    .orient("left")
    .tickSize(-w);

  // Add the y-axis to the left
  graph.append("svg:g")
    .attr("class", "y axis")
    .attr("transform", "translate(0,0)")
    .call(yAxisLeft);

  var i = 0;
  $.each(data, function(key, value) {
    value.forEach(function(d) {
      d.date = parseDate(d.date);
    });

    var line = d3.svg.line()
      .x(function(d) {
        return x(d.date);
      })
      .y(function(d) {
        return y(d.value);
      });
    graph.append("path")
      .datum(value)
      .attr("d", line)
      .attr("class", "line")
      .attr("stroke", colour[i]);
    i++;
  });

  var legend = graph.append("g")
    .attr("class", "legend")
    .attr("height", 100)
    .attr("width", 100)
    .attr('transform', 'translate(-5,' + (h + 35) + ')');


  legend.selectAll('rect')
    .data(labels)
    .enter()
    .append("rect")
    .attr("x", function(d, i) {
      var xPost = legendXPosition(labels, i, 6);
      return xPost;
    })
    .attr("y", -6)
    .attr("width", 20)
    .attr("height", 5)
    .style("fill", function(d, i) {
      var color = colour[i];
      return color;
    });

  legend.selectAll('text')
    .data(labels)
    .enter()
    .append("text")
    .attr("x", function(d, i) {
      var xPost = legendXPositionText(labels, i, 22, 6);
      return xPost;
    })
    .attr("y", -1)
    .text(function(d) {
      return d;
    });
};

function legendXPositionText(data, position, textOffset, avgFontWidth) {
  return legendXPosition(data, position, avgFontWidth) + textOffset;
}

function legendXPosition(data, position, avgFontWidth) {
  if (position == 0) {
    return 0;
  } else {
    var xPostiion = 0;
    for (i = 0; i < position; i++) {
      xPostiion += (data[i].length * avgFontWidth + 40);
    }
    return xPostiion;
  }
}

var benchmark_line_graph_colours = ["#524364", "#937ab1", "#ab5b02", "#faa757"],
  benchmark_line_graph_data = {
    "Beassa ALBI TR ZAR": [{
      "date": "2012-08-31",
      "value": 101.1
    }, {
      "date": "2012-09-28",
      "value": 101.89
    }, {
      "date": "2012-10-31",
      "value": 101.09
    }],
    "FTSE/JSE All Share TR ZAR": [{
      "date": "2012-08-31",
      "value": 99.72
    }, {
      "date": "2012-09-28",
      "value": 101.24
    }, {
      "date": "2012-10-31",
      "value": 105.29
    }],
    "STeFI Composite ZAR": [{
      "date": "2012-08-31",
      "value": 100.23
    }, {
      "date": "2012-09-28",
      "value": 100.52
    }, {
      "date": "2012-10-31",
      "value": 100.77
    }],
    "portfolio": [{
      "date": "2012-08-31",
      "value": 101.55
    }, {
      "date": "2012-09-28",
      "value": 101.15
    }, {
      "date": "2012-10-31",
      "value": 102.08
    }]
  };
drawlinegraph(benchmark_line_graph_data,
  "2012-08-31",
  "2012-10-31",
  105.84700000000001,
  99.163, benchmark_line_graph_colours,
  "body");
body {
  font: 10px sans-serif;
}
.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}
.x.axis text {
  padding-top: 5px;
  text-anchor: "right";
}
.line {
  fill: none;
  stroke-width: 1.5px;
}
.y.axis line,
.y.axis path {
  stroke-dasharray: 2, 2;
}
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.js"></script>