繁体   English   中英

特殊的甜甜圈图,带有不同的环/弧,用于正值和负值

[英]Special donut chart with different rings/arcs for positive and negative values

我正在尝试在D3中创建一种特殊的甜甜圈图,其中将包含不同的正负值环。 该值可以大于100%或小于-100%,因此会有一个表示剩余值的弧线。 下面是图表的示例图像: 在此处输入图片说明

第一个肯定类别(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;

}

请分享一些实施思路。

好的,这花了一些时间,但似乎可行。 首先,让我们确定您描述为甜甜圈图的内容也可以使用完全相同的数据呈现为一系列条形图。 因此,我从那里开始,最终将其制作成一个甜甜圈图,但同时也保留了条形图的实现。 另一件事是,通用解决方案应该能够将分段包装为任意值,而不仅仅是100,因此我提供了一个滑块,可让您更改该包装值。 最后-这在条形图而不是甜甜圈实现中更容易解释-而不是总是使条形图像文本一样从左到右包装,因此可能需要进行曲折处理,即从左到右然后右包装向左等。 这样做的效果是,当金额在两条单独的线上分成两个部分时,之字形方法会将这两个部分保持相邻。 我添加了一个复选框来打开/关闭此曲折行为。

这是一个工作的jsFiddle及其另一个迭代

这是重要的位:

有一个函数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; });

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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