Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3 insert vs append in context of creating nodes on mousemove

In the following code, the author uses .insert to position circles "before" the rectangle (in reality they appear over the top I believe) rather than appending them directly to the svg space.

I thought this unnecessary so removed the rect and the .insert and appended the circle elements directly to the svg space. However the result is that the circles don't 'draw fast enough' (for lack of a more articulate explanation).

Can anyone either explain why this is happening to me or point me in the direction of some literature that does explain it?

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)
}

Thanks and credit to http://techslides.com/over-1000-d3-js-examples-and-demos.

I've created a jsfiddle @ http://jsfiddle.net/hiwilson1/mgchrm0w/

like image 290
hwilson1 Avatar asked Apr 14 '15 10:04

hwilson1


People also ask

What does d3 append do?

d3 append() append() - Appends a new element with the specified name as the last child of each element in the current selection, returning a new selection containing the appended elements.

What is the correct syntax to draw a circle in d3?

var circle = d3. selectAll("circle"); With a selection, we can make various changes to selected elements.

What is G in d3js?

It appends a 'g' element to the SVG. g element is used to group SVG shapes together, so no it's not d3 specific.

How do you use d3 select this?

select() function in D3. js is used to select the first element that matches the specified selector string. If any element is not matched then it returns the empty selection. If multiple elements are matched with the selector then only the first matching element will be selected.


2 Answers

As pointed out by @altocumulus, it's simply that the strokes of the appended discs tend to block the rect from receiving mouseover events. You can exaggerate the effect by adding fill to the circles. So, that's why insert() performs better than append().

The other way to make append work is to put the listener on the svg element and take advantage of event bubbling. Both rect and circle mouseover events will bubble up to the parent svg.

You can see all this by fiddling around with this...

        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>
like image 141
Cool Blue Avatar answered Oct 11 '22 18:10

Cool Blue


To me there is no real difference in performance between both methods even in fullscreen mode. I think the reason for choosing insert() over append() is rather a matter of handling mouse events. The SVG 1.1 spec states on hit-testing:

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.

Inserting circles before the <rect> ensures that the <rect> will always be rendered on top all circles. Furthermore, setting pointer-events: all on the <rect> will set it as the first target to receive any mouse events. This way you'll have a clean implementation not relying on implementation-specific behaviour of the user agent rendering the embedded svg.

like image 22
altocumulus Avatar answered Oct 11 '22 20:10

altocumulus