我正在尝试在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;
}
请分享一些实施的想法。
答案 0 :(得分:3)
好的,这需要一些时间,但似乎有效。首先,让我们确定您描述为圆环图的内容也可以呈现为一系列条形图 - 使用完全相同的数据。所以我从那里开始并最终将它制作成圆环图,但也将条形图实现在那里。另一件事是通用解决方案应该能够将段包装在任何值,而不仅仅是100,所以我包含了一个滑块,可以让你改变包装值。最后 - 这更容易在条形而不是甜甜圈实现中解释 - 而不是总是让条形图从左到右包裹,就像文本一样,可能需要曲折,即从左到右然后向右交替包装 - 左到右等等。这样做的结果是,当一个数量在两条不同的线上被分成两个部分时,之字形方法将使这两个部分保持彼此相邻。我添加了一个复选框来打开/关闭这种曲折行为。
此处有working jsFiddle 和another iteration。
以下是重点:
有一个函数wrap(data, wrapLength)
,它取一个data
值数组和一个wrapLength
来包装这些值。该函数确定哪些数据值必须拆分为子段并返回它们的新数组,每个段的对象具有x1
,x2
和y
值。 x1
和x2
是每个栏的开头和结尾,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; });