简体   繁体   中英

RangePoints for circles of different sizes

Is it possible to specify rangePoints to handle circles of different sizes so that the distances are calculated by their edges rather than their centers?

For example, this is what I am getting:

() represent circle edges; <----> represents distance calculated by rangePoints

(   <----)---(-><-)-------(-> )

^Notice how it looks like the middle circle is closer to the left-most circle. Instead, I'd like to see the following:

(        )<---->(  )<---->(  )

The function I am using looks something like:

var y = d3.scale.ordinal()
   .domain(data.map(function(d) { return d.value}))
   .rangePoints([0, width]);

var item = chart.append("g")
    .selectAll("circle")
         .data(data)
    .enter().append("circle")
    .attr("cy", function(d) { return y(d.value); })

As @LarsKotthoff mentioned, you're going to need a custom scale implementation to do this.

This is going to be somewhat complex in this case, because the scale needs to take into account not only the number of elements in the domain, but also what their exact values are. Basically, when calculating the position of each element, your scale will need to take into account the space taken up by each element preceding it, and also include a spacer based on a division of the total space.

Here's what it would have to do:

First, in setting up the scale you need to do the following:

  • Find the total distance spanned by the output range
  • Find the sum of the diameters of each element in the domain
  • Take the difference of these to find the remaining space available
  • Divide the remaining space by the number of spaces (number of elements minus one)

This will give you a value for the space needed between each circle.

Then for each element:

  • Find the sum of the diameters of the preceding circles
  • Multiply the spacer value by the number of preceding circles
  • Add the sum of these to the radius of the current circle

Because of this, your scale will need to be based on the index as well as the datum.

Here is a pretty crude implementation that assumes the domain is an array of radii and the range is the endpoints of your output:

var customPointScale = function() {
  var domain,
      range;
  // returned scale fn takes datum and index
  function scale(d,i) {
    var n = domain.length,
        totalSpan = range[1] - range[0],
        // loop over the domain to find the sum of the diameters
        sumDiameters = (function(){
          var output = 0;
          for (var a = 0; a < n; a++) {
            // add radius * 2 to get diameter
            output += domain[a] * 2;
          }
          return output;
        })(),
        remainingSpace = totalSpan - sumDiameters,
        // there is one fewer space than the number of elements
        spacer = remainingSpace / (n-1);

    // loop over the elements that came before to find the distance spanned
    var distanceSoFar = (function() {
      var output = 0;
      for(var a = 0; a < i; a++) {
        // diameter + spacer, for each element traversed
        output += (domain[a] * 2) + spacer;
      }
      return output;
    })();

    // return the radius plus the distance traversed so far
    return d + distanceSoFar;
  }
  scale.domain = function(_) {
    if (!arguments.length) return domain;
    domain = _;
    return scale;
  };
  scale.range = function(_) {
    if (!arguments.length) return range;
    range = _;
    return scale;
  };
  return scale;
};

Here's a JSBin that uses this implementation with some example data. Hope that helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM