带有正负值的不同环/弧的特殊圆环图

时间:2016-06-17 13:22:33

标签: javascript html d3.js

我正在尝试在D3中创建一种特殊的圆环图,其中包含正负值的不同环。值可以大于100%或小于-100%,因此将有一个代表剩余值的弧。以下是图表的示例图片:enter image description here

第一个正面类别(Category_1 - 灰色)值为80,因此80%用灰色填充圆圈,剩下20%用于下一个正面类别。下一个正类别值(Category_2 - 橙色)是160.所以它首先使用Category_1剩下的20%(现在剩下140个值)。然后它正在填充下一个圆圈(向上)100%(现在剩下40个值),而对于剩余值(40),它正在向上创建部分圆圈。

现在,我们将Category_3(暗红色)视为负(-120%),因此如果创建一个向内的圆并将其填充100%(现在剩下20个值),那么它将创建一个向内的弧以保持价值(20)。我们有另一个负面类别(Category_4 - 红色),因此它将从之前的负面类别(Category_3)结束处开始,并从那里填充20%区域。

编辑3:我创建了一个非常基本的基于圆弧的圆环图,当总值超过100时,我能够为剩余的值创建外环。下面是JSFiddle链接:

http://jsfiddle.net/rishabh1990/zmuqze80/

data = [20, 240];

var startAngle = 0;
var previousData = 0;
var exceedingData;
var cumulativeData = 0;
var remainder = 100;
var innerRadius = 60;
var outerRadius = 40;
var filledFlag;

var arc = d3.svg.arc()
  .innerRadius(innerRadius)
  .outerRadius(outerRadius)

for (var i = 0; i < data.length; i++) {

  filledFlag = 0;
  exceedingData = 0;

  console.log("---------- Iteration: " + (i + 1) + "---------");

  if (data[i] > remainder) {
    filledFlag = 1;
    exceedingData = data[i] - remainder;
    console.log("Exceeding: " + exceedingData);
    data[i] = data[i] - exceedingData;
    data.splice(i + 1, 0, exceedingData);
  }

  if( filledFlag === 1) {
    cumulativeData = 0;
  } else {
    cumulativeData += data[i];
  }

  console.log("Previous: " + previousData);
  console.log("Data: " + data, "Current Data: " + data[i]);
  var endAngle = (previousData + (data[i] / 50)) * Math.PI;
  console.log("Start " + startAngle, "End " + endAngle);
  previousData = previousData + data[i] / 50;

  //if(i===1) endAngle = 1.4 * Math.PI;
  //if(i===2) endAngle = 2 * Math.PI;

  var vis = d3.select("#svg_donut");

  arc.startAngle(startAngle).endAngle(endAngle);


  vis.append("path")
    .attr("d", arc)
    .attr("transform", "translate(200,200)")
    .style("fill", function(d) {
      if (i === 0) return "red";
      //if (i === 1) return "green";
      //if (i === 2) return "blue"
      //if (i === 3) return "orange"
      //if (i === 4) return "yellow";
    });

  if (exceedingData > 0) {
    console.log("Increasing Radius From " + outerRadius + " To " + (outerRadius + 40));
    outerRadius = outerRadius + 22;
    innerRadius = innerRadius + 22;
    arc.innerRadius(innerRadius).outerRadius(outerRadius);
    console.log("Outer: ", outerRadius);
  }

  if (remainder === 100) {
    remainder = 100 - data[i];
  } else {
    remainder = 100 - cumulativeData;
  };

  if (filledFlag === 1) {
    remainder = 100;
  }

  console.log("Remainder: " + remainder);

  startAngle = endAngle;

}

请分享一些实施的想法。

1 个答案:

答案 0 :(得分:3)

好的,这需要一些时间,但似乎有效。首先,让我们确定您描述为圆环图的内容也可以呈现为一系列条形图 - 使用完全相同的数据。所以我从那里开始并最终将它制作成圆环图,但也将条形图实现在那里。另一件事是通用解决方案应该能够将段包装在任何值,而不仅仅是100,所以我包含了一个滑块,可以让你改变包装值。最后 - 这更容易在条形而不是甜甜圈实现中解释 - 而不是总是让条形图从左到右包裹,就像文本一样,可能需要曲折,即从左到右然后向右交替包装 - 左到右等等。这样做的结果是,当一个数量在两条不同的线上被分成两个部分时,之字形方法将使这两个部分保持彼此相邻。我添加了一个复选框来打开/关闭这种曲折行为。

此处有working jsFiddle another iteration

以下是重点:

有一个函数wrap(data, wrapLength),它取一个data值数组和一个wrapLength来包装这些值。该函数确定哪些数据值必须拆分为子段并返回它们的新数组,每个段的对象具有x1x2y值。 x1x2是每个栏的开头和结尾,y是栏的行。在圆环图中,这些值等效于每个弧的起始角度(x1),结束角度(x2)和半径(y)。

函数wrap()不知道如何计算负值与正值,因此wrap()必须被调用两次 - 一次是所有的负数,然后是所有的正数。从那里,一些处理选择性地仅应用于否定,然后对两组的组合应用更多处理。最后两段中描述的整个转换集由以下片段捕获。我这里不包括wrap()的实现,只是调用它的代码;也不包括渲染代码,这在生成segments后非常简单。

// Turn N data points into N + x segments, as dictated by wrapLength. Do this separately
// for positive and negative values. They'll be merged further down, after we apply
// a specific transformation to just the negatives
var positiveSegments = wrap(data.filter(function(d) { return d.value > 0; }), wrapLength);
var negativeSegments = wrap(data.filter(function(d) { return d.value < 0; }), wrapLength);

// Flip and offset-by-one the y-value of every negative segment. I.e. 0 becomes -1, 1 becomes -2
negativeSegments.forEach(function(segment) { segment.y = -(segment.y + 1); });

// Flip the order of the negative segments, so that their sorted from negative-most y-value and up
negativeSegments.reverse()

// Combine negative and positive segments
segments = negativeSegments.concat(positiveSegments);

if(zigzag) {
  segments.forEach(function(segment) {
    if(Math.abs(segment.y) % 2 == (segment.y < 0 ? 0 : 1)) { flipSegment(segment, wrapLength); }
  });
}

// Offset the y of every segment (negative or positive) so that the minimum y is 0
// and goes up from there
var maxNegativeY = negativeSegments[0].y * -1;
segments.forEach(function(segment) { segment.y += maxNegativeY; });