我目前正在尝试使用旭日库创建旭日图。感谢这个论坛的帮助,我能够添加更新功能。现在,当我的数据源更新时,我总是在 ARC 元素转换时收到以下错误消息(下图):
<path> attribute d: Expected arc flag ('0' or '1'),"
如here和here所示,ARC元素的过渡似乎存在问题。 D3 的默认过渡无法正确插入我的 ARC-Elements。
因此,如条目中所述,我将自定义插值器添加为函数并将其链接到我的转换。
不幸的是它不起作用,错误仍然发生。
有人可以向我解释为什么它不起作用以及如何纠正错误吗?
arcTween 函数:
// Custom interpolator
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
我的代码:
// Data
const data1 = {
"name": "TOPICS",
"id": 1,
"children": [{
"name": "Topic A",
"id": 2,
"children": [{
"name": "Sub A1",
"id": 5,
"size": 4
}, {
"name": "Sub A2",
"id": 6,
"size": 4
}]
}, {
"name": "Topic B",
"id": 3,
"children": [{
"name": "Sub B1",
"id": 7,
"size": 3
}, {
"name": "Sub B2",
"id": 8,
"size": 3
}, {
"name": "Sub B3",
"id": 9,
"size": 3
}]
}, {
"name": "Topic C",
"id": 4,
"children": [{
"name": "Sub A3",
"id": 10,
"size": 4
}, {
"name": "Sub A4",
"id": 11,
"size": 4
}]
}]
};
const data2 = {
"name": "TOPICS",
"id": 1,
"children": [{
"name": "Topic A",
"id": 2,
"children": [{
"name": "Sub A1",
"id": 5,
"size": 4
}, {
"name": "Sub A2",
"id": 6,
"size": 4
}]
}, {
"name": "Topic B",
"id": 3,
"children": [{
"name": "Sub B1",
"id": 7,
"size": 3
}, {
"name": "Sub B2",
"id": 8,
"size": 3
}, {
"name": "Sub B3",
"id": 9,
"size": 3
}]
}]
};
//-------------------------------------------------------------------------------------------
// Declare variables
let i_region_static_id = "sunburst",
parentDiv = document.getElementById(i_region_static_id),
width = parentDiv.clientWidth,
height = 450,
root,
rootDepth,
x,
y,
arc,
middleArcLine,
middleAngle,
color = d3.scaleOrdinal(d3.schemeCategory10);
maxRadius = (Math.min(width, height) / 2) - 5;
const partition = d3.partition();
//-----------------------------------------------------------------------------------
// SVG-Element
let svg = d3.select('#' + i_region_static_id).append('svg')
.style('width', width)
.style('height', height)
.attr('viewBox', `${-width / 2} ${-height / 2} ${width} ${height}`)
.on('dblclick', d => {
if (event.detail === 2) focusOn() // Double click
});
//-----------------------------------------------------------------------------------
// X-Scale
x = d3.scaleLinear()
.range([0, 2 * Math.PI])
.clamp(true);
//-----------------------------------------------------------------------------------
// Y-Scale
y = d3.scaleSqrt()
.range([maxRadius * .1, maxRadius]);
//-------------------------------------------------------------------------------------------
// Text-fit constant
const textFits = d => {
const CHAR_SPACE = 6;
const deltaAngle = x(d.x1) - x(d.x0);
const r = Math.max(0, (y(d.y0) + y(d.y1)) / 2);
const perimeter = r * deltaAngle;
return d.data.name.length * CHAR_SPACE < perimeter;
};
//-----------------------------------------------------------------------------------
// Create Arc generator
arc = d3.arc()
.startAngle(d => x(d.x0))
.endAngle(d => x(d.x1))
.innerRadius(d => Math.max(0, y(d.y0)))
.outerRadius(d => Math.max(0, y(d.y1)))
//-----------------------------------------------------------------------------------
middleArcLine = d => {
const halfPi = Math.PI / 2;
const angles = [x(d.x0) - halfPi, x(d.x1) - halfPi];
const r = Math.max(0, (y(d.y0) + y(d.y1)) / 2);
const middleAngle = (angles[1] + angles[0]) / 2;
const invertDirection = middleAngle > 0 && middleAngle < Math.PI; // On lower quadrants write text ccw
if (invertDirection) {
angles.reverse();
}
const path = d3.path();
path.arc(0, 0, r, angles[0], angles[1], invertDirection);
return path.toString();
}
//-------------------------------------------------------------------------------------------
// Check if node in depth
function maxDepth(d) {
if (rootDepth == undefined) { // If user clicks next to sun = root undefined
rootDepth = 0;
}
return ((d.depth - rootDepth) < 2);
}
//-------------------------------------------------------------------------------------------
// Custom interpolator
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
//-------------------------------------------------------------------------------------------
function focusOn(d = {
x0: 0,
x1: 1,
y0: 0,
y1: 1
}) {
// Reset to top-level if no data point specified
// Activate top-level node if no data point specified
if (d.data == undefined) {
svg.selectAll(".slice")
.filter(d => d.parent == undefined && d.children != undefined)
// .each(function(d) {
// activateNode(d);
// });
}
root = d; // Root-node
rootDepth = root.depth; // Root node depth for maxDepth(d)
const transition = svg.transition()
.duration(750)
.tween('scale', () => {
const xd = d3.interpolate(x.domain(), [d.x0, d.x1]),
yd = d3.interpolate(y.domain(), [d.y0, 1]);
return t => {
x.domain(xd(t));
y.domain(yd(t));
};
});
transition.selectAll('.slice')
.attr('display', d => maxDepth(d) ? null : 'none'); // Display nodes only in depth for transition
transition.selectAll('path.main-arc')
.filter(d => maxDepth(d))
.attrTween('d', d => () => arc(d));
transition.selectAll('path.hidden-arc')
.filter(d => maxDepth(d))
.attrTween('d', d => () => middleArcLine(d));
transition.selectAll('text')
.filter(d => maxDepth(d))
.attrTween('display', d => () => textFits(d) ? null : 'none'); // Display text only in depth
moveStackToFront(d);
// Foreground nodes -> inner nodes higher than outer nodes
function moveStackToFront(elD) {
svg.selectAll('.slice').filter(d => d === elD)
.each(function(d) {
this.parentNode.appendChild(this);
if (d.parent) {
moveStackToFront(d.parent);
}
})
}
}
//-------------------------------------------------------------------------------------------
// Initialize and Update sun
function sun(pData) {
// const color = d3.scaleOrdinal(d3.quantize(d3.interpolateRainbow, pData.children.length + 1));
root = d3.hierarchy(pData); //set data
root.sum(d =>
(d.children == undefined) ? ((d.size == undefined) ? 1 : d.size) : 0 //parent value defined by childrens values
);
const slice = svg.selectAll('g.slice')
.data(
partition(root)
.descendants(),
function(d) { return d.data.id; }
);
//-------------------------------------------------------------------------------------------
// Enter Section
const newSlice = slice.enter()
.append('g').attr('class', 'slice')
.attr('display', d => d.depth < 2 ? null : 'none') // Hide levels lower depth
.on('dblclick', (e, d) => {
e.stopPropagation();
focusOn(d);
})
newSlice.append('path')
.attr('class', 'main-arc')
.style('fill', d => (d.data.color == undefined) ? color((d.children ? d : d.parent).data.name) : d.data.color) //set source color, otherwise default color
.attr('d', arc);
newSlice.append('path')
.attr('class', 'hidden-arc')
.attr('id', (_, i) => `hiddenArc${i}`)
.attr('d', middleArcLine);
const text = newSlice.append('text')
.attr('display', d => textFits(d) ? null : 'none'); // Hide text on lower levels
text.append('textPath')
.attr('startOffset', '50%')
.attr('xlink:href', (_, i) => `#hiddenArc${i}`)
.text(d => d.data.name) // Set text in sector
.attr('fill', d => 'black');
// Update Section
slice.select('path')
.attr('class', 'main-arc')
.style('fill', d => (d.data.color == undefined) ? color((d.children ? d : d.parent).data.name) : d.data.color)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
})
.transition().duration(500).attrTween("d", arcTween)
.attr("d", arc);
slice.select('.hidden-arc')
.attr('class', 'hidden-arc')
.attr('id', (_, i) => `hiddenArc${i}`)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
})
.transition().duration(500).attrTween("d", arcTween)
.attr('d', middleArcLine);
slice.select('text')
.attr('display', d => textFits(d) ? null : 'none');
slice.select('textPath')
.attr('startOffset', '50%')
.attr('xlink:href', (_, i) => `#hiddenArc${i}`)
.transition().duration(500)
.text(d => d.data.name) // Set text in sector
.attr('fill', d => 'black');
// Delete Section
// slice.exit().transition().duration(500).style("fill-opacity", 0.2).remove();
slice.exit().transition().duration(500).style("fill-opacity", 0.2).remove();
}
//-------------------------------------------------------------------------------------------
sun(data1)
let i = 0;
d3.interval(() => {
if (i++ % 2 === 0) {
console.log("data2")
sun(data2);
} else {
console.log("data1")
sun(data1);
}
}, 2000)
.slice {
cursor: pointer;
}
.slice .main-arc {
stroke: #fff;
stroke-width: 1px;
}
.slice .hidden-arc {
fill: none;
}
.slice text {
pointer-events: none;
text-anchor: middle;
}
<!DOCTYPE html>
<html>
<!-- Code Vorschlage mit STRG+Leertaste aktivieren-->
<head>
<link rel="stylesheet" href="sun.css">
<meta charset="utf-8" /> <!-- Welche Sonderzeichen verwendet werden können -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <!-- Wie sollte sich die Seite auf einem Handy verhalten -->
<title> Sunbrust2 </title> <!-- title als Tag -->
<!-- Load plotly.js into the DOM -->
<script src='https://cdn.plot.ly/plotly-2.11.1.min.js'></script>
<style>
</style>
</head>
<body>
<div id="sunburst"></div>
<script src="https://d3js.org/d3.v7.js" charset="utf-8"></script>
<!-- <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> -->
<script src="https://unpkg.com/d3fc" charset="utf-8"></script>
</body> <!-- HTML schließender Tag-->
</html> <!-- HTML schließender Tag-->
注意:这是部分答案:我还没有完全让它完成您可能期望的所有事情,但它确实改善了情况。
尽管使用
arcTween
函数,您仍然收到错误“预期弧标志(0 或 1)”的原因是,您随后立即通过调用 attrTween
来覆盖 attr
:
.transition().duration(500).attrTween("d", arcTween)
.attr("d", arc);
如果您在
arcTween
函数中放置断点,它将永远不会被命中,因为该函数永远不会被调用。
因此,第一步是在调用
.attr("d", ...)
后删除 .arcTween
调用。
但是,请不要在进行此更改后立即重新运行代码。如果这样做,您可能会发现浏览器开始使用大量 CPU,选项卡停止响应,您必须终止该选项卡。
这里的问题是d3.interpolate
与
d3.hierarchy
的输出根本不能很好地配合。
d3.interpolate
通过插值对应的属性来插值一对对象,通过插值对应的元素来插值一对数组。
d3.hierarchy
输出的节点具有父属性和子节点,因此当
d3.interpolate
到达一对子节点时,它将尝试对父节点进行插值。然而,
d3.interpolate
没有检测到它已经为父节点设置了插值,因此开始再次对父节点及其子节点进行插值,依此类推......此问题的解决方案是在删除了父属性的对象上调用
d3.interpolate
:
function deepCopyWithoutParents(node) {
var newValue = Object.assign({}, node);
delete newValue.parent;
if (newValue.children) {
newValue.children = newValue.children.map(deepCopyWithoutParents);
}
return newValue;
}
function arcTween(a) {
if (!this._current) {
this._current = deepCopyWithoutParents(a);
}
var i = d3.interpolate(this._current, deepCopyWithoutParents(a));
return function(t) {
return arc(i(t));
};
}
deepCopyWithoutParents
对象返回此处使用的数据对象的深层副本,删除了
parent
属性并假设
children
是唯一不是单个值的属性。您可能会注意到的下一件事是,文本标签最初出现在正确的位置,但在转换开始后立即移动到错误的位置。您需要为文本标签编写一个单独的补间函数,该函数调用
middleArcLine
而不是
arc
,并使用它而不是
arcTween
进行文本标签过渡。正如我在顶部的免责声明中所写,这不是一个完整的答案(至少现在还不是)。特别是,您可能会发现第一个动画立即完成,第三个、第五个、第七个等也是如此。我认为如果您能找到一种方法来初始化
this._current
以存储
data1
之前的值,您可以解决此问题。第一个转换运行,并且在转换完成时将
this._current
设置为最终值。