D3.js 错误转换弧“<path>属性 d:预期弧标志(‘0’或‘1’),”

问题描述 投票:0回答:1

我目前正在尝试使用旭日库创建旭日图。感谢这个论坛的帮助,我能够添加更新功能。现在,当我的数据源更新时,我总是在 ARC 元素转换时收到以下错误消息(下图):

<path> attribute d: Expected arc flag ('0' or '1'),"

herehere所示,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-->

javascript d3.js
1个回答
0
投票

注意:这是部分答案:我还没有完全让它完成您可能期望的所有事情,但它确实改善了情况。

尽管使用

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
 设置为最终值。

© www.soinside.com 2019 - 2024. All rights reserved.