d3 在 mousemove 上创建节点的上下文中插入 vs 追加

d3 insert vs append in context of creating nodes on mousemove

在下面的代码中,作者使用 .insert 来定位圆圈 "before" 矩形(我相信实际上它们出现在顶部)而不是直接将它们附加到 svg space。

我认为这没有必要,所以删除了 rect 和 .insert 并将圆形元素直接附加到 svg space。然而,结果是圆圈没有 'draw fast enough'(因为缺乏更明确的解释)。

任何人都可以解释为什么这会发生在我身上,或者给我指出一些确实解释它的文献的方向吗?

var width = Math.max(900, innerWidth),
    height = Math.max(700, innerHeight)

var svg = d3.select("body").append("svg")
    .attr({
        "width": width,
        "height": height
    })

var i = 1;

svg.append("rect")
    .attr({
        "width": width,
        "height": height
    })
    .on("mousemove", particle)

function particle() {
    var m = d3.mouse(this)

    var circle = svg.insert("circle", "rect")
        .attr("cx", m[0])
        .attr("cy", m[1])
        .attr("r", 10)
        .style("stroke", d3.hsl((i = (i + 1) % 360), 1, .5))
        .style("stroke-opacity", 1)
        .transition().duration(1000)
        .ease(Math.sqrt)
        .attr("r", 100)
        .style("stroke-opacity", 1e-6)
}

感谢并归功于 http://techslides.com/over-1000-d3-js-examples-and-demos

我创建了一个 jsfiddle @ http://jsfiddle.net/hiwilson1/mgchrm0w/

据我从文档中得知:

插入(如果有的话)应该比追加慢一点,因为它似乎需要一些额外的检查以防前选择器不匹配任何东西。

除此之外,我同意@user3906922 留下的评论,从 2 个小提琴来看,两者之间的性能似乎没有太大差异。

对我来说,即使在全屏模式下,这两种方法在性能上也没有真正的区别。我认为选择 insert() 而不是 append() 的原因是处理鼠标事件的问题。 hit-testing 上的 SVG 1.1 规范声明:

This specification does not define the behavior of pointer events on the rootmost ‘svg’ element for SVG images which are embedded by reference or inclusion within another document, e.g., whether the rootmost ‘svg’ element embedded in an HTML document intercepts mouse click events; future specifications may define this behavior, but for the purpose of this specification, the behavior is implementation-specific.

<rect> 之前插入圆圈可确保 <rect> 始终呈现在所有圆圈的顶部。此外,在 <rect> 上设置 pointer-events: all 会将其设置为接收任何鼠标事件的第一个目标。通过这种方式,您将拥有一个干净的实现,而不依赖于呈现嵌入式 svg 的用户代理的 实现特定 行为。

正如@altocumulus 所指出的,只是附加圆盘的笔画倾向于阻止 rect 接收 mouseover 事件。您可以通过向圆圈添加填充来夸大效果。所以,这就是 insert()append().

表现更好的原因

使追加工作的另一种方法是将侦听器放在 svg 元素上并利用事件冒泡。 rectcircle mouseover 事件都会冒泡到父 svg。

你可以通过摆弄这个来看到这一切...

        var width = Math.max(900, innerWidth),
            height = Math.max(700, innerHeight),

            svg = d3.select("body").append("svg")
                .attr('id', 'svg')
                .attr({
                    "width": width,
                    "height": height
                })

            i = 1, c = 0,
            method = document.getElementById('metod'),
            fill = document.getElementById('fill'),
            remove = document.getElementById('remove'),

            SelectGroup = function (selectId, onUpdate) {

                var _selectedOptionById = function (id) {
                    var _node = document.getElementById(id);
                    return  function () {
                        return _node[_node.selectedIndex]
                    }
                },

                _selectedOption = _selectedOptionById(selectId);

                return {
                    update: function () {
                        onUpdate.apply(_selectedOption(), arguments)
                    },
                }
            },

            mouseListenerSelector = SelectGroup ('mouseListenerSelector',  function onUpdate (event, listener) {
                //this: selected option node
                //the node 'on' and 'off' attributes are selectors for the event listeners
                //enable the 'on' listener and remove the off listener
                var _selected = this,
                    switchOn = d3.select(_selected.getAttribute('on')),
                    switchOff = d3.select(_selected.getAttribute('off'));

                switchOn.on(event, listener);
                switchOff.on(event, null);
            }),

            rectEventsAuto = document.getElementById('rectEventsAuto'),
            //rectEventsAuto = document.getElementById('rectEventsAuto'),
            
            rect = svg.append("rect")
                .attr('id', 'rect')
                .attr({
                    "width": width,
                    "height": height
                })

            d3.select('#options').on('change', function () {

                svg.selectAll('circle').remove()
                applyListener(mouseListenerSelector, rectEventsAuto.value)

            })


            function applyListener(mouseListenerSelector, rectEventsAuto) {

                if (rectEventsAuto) {
                    rect.attr('style', null)
                } else {
                    rect.attr('style', 'pointer-events: all;')
                }

                mouseListenerSelector.update("mousemove.circles", particle)
                mouseListenerSelector.update(("ontouchstart" in document ? "touchmove" : "mousemove") + ".circles", particle)
            }

            applyListener(mouseListenerSelector, rectEventsAuto.value)


        function particle() {
            var m = d3.mouse(this),

                circle = svg[method.value]("circle", "rect")
                .attr("cx", m[0])
                .attr("cy", m[1])
                .attr("r", 10)
                .style("stroke", d3.hsl((i = (i + 1) % 360), 1, .5))
                .style("stroke-opacity", 1)
                .style("fill", fill.value == 'solid' ? d3.hsl((i = (i + 1) % 360), 1, .5) : fill.value)
                .transition().duration(1000)
                .ease(Math.sqrt)
                .attr("r", 100)
            //.style("stroke-opacity", 1e-6)

            if (remove.value) { circle.remove() }
        }
body {
            margin: 0;
            background: #222;
            min-width: 960px;
        }

        rect {
            fill: none;
            pointer-events: all;
        }

        circle {
            fill: none;
            stroke-width: 2.5px;
        }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
    <div id="options">
        <select id="metod">
            <option value="insert">insert</option>
            <option value="append" selected="selected">append</option>
        </select>
        <select id="fill">
            <option value="solid" selected="selected">solid</option>
            <option value="none">no fill</option>
        </select>
        <select id="remove">
            <option value="true">remove</option>
            <option value="" selected="selected">don't remove</option>
        </select>
        <select id="mouseListenerSelector">
            <option value="true" on ="svg" off="rect">listener on svg</option>
            <option value="" selected="selected" on="rect" off="svg">listener on rect</option>
        </select>
        <select id="rectEventsAuto">
            <option value="true" selected="selected">pointer-events null; on rect</option>
            <option value="">pointer-events: all; on rect</option>
        </select>
    </div>