2

I am yet a noob for d3 and javascript,while I was writing some code to deal with drag the dots and zoom the coordinate, something weird happened, the dots call the drag function,an outer group() element call the zoom, but when I dragging the dot, the outer group change its translate attribute.Code comes below:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <title>zoom test</title>
    </head>
    <body></body>
        <script type="text/javascript" src="http://d3js.org/d3.v3.min.js">

        </script>
        <script type="text/javascript" charset="utf-8">

            function addElem(container,elem){
                return container.append(elem);
            }

            function createLinearScale(dx,dy,rx,ry){
                return d3.scale.linear()
                     .domain([dx,dy])
                     .range([rx,ry])
            }

            function appendWithData(container,selector,type,id,classed,dataset){
                var result =  container.selectAll(selector)
                        .data(dataset)
                        .enter()
                        .append(type);

                if(id)
                    result.attr("id",id);
                if(classed)
                    result.classed(classed,true);
                return result;
            }

            function getElem(selector){
                return d3.select(selector);
            }

            function getAxis(){
                return d3.svg.axis();
            }

            function drag(){
                return d3.behavior.drag();
            }

            function getThis(){
                return d3.select("this");
            }

            function zoom(){
                return d3.behavior.zoom();
            }

            function arrayDelete(array,target,condition){
                for (var i = 0, l = array.length; i < l; i ++) {
                    var v = arr[i];
                    if((target = v) && condition){
                        array.splice(i,1);
                    }
                }
            }
        </script>

        <script type="text/javascript" charset="utf-8">
        /**
         * Set frame for page
         *
         */
            var body = getElem("body");
            var svg = addElem(body,"svg");
            var outer = addElem(svg,"g");
            var target = addElem(outer,"g");

            var x_axis = addElem(target,"g");
            var y_axis = addElem(target,"g");
            var dots = addElem(target,"g");

            var data = [
            [ 5, 20 ],
            [ 480, 90 ],
            [ 250, 50 ],
            [ 100, 33 ],
            [ 330, 95 ],
            [ 410, 12 ],
            [ 475, 44 ],
            [ 25, 67 ],
            [ 85, 21 ],
            [ 220, 88 ]
            ];

            /**
             * Add axis to chart
             *
             */
            var height = 500;
            var width = 960;

            var x_scale = createLinearScale(0, d3.max(data,function(d){return d[0];}) + 50, 0, width - 10);
            var y_scale = createLinearScale(0, d3.max(data, function(d){return d[1];}) + 50, height - 10, 0);
            var ax_scale = createLinearScale(0, width - 10, 0, d3.max(data,function(d){return d[0];}) + 50);
            var ay_scale = createLinearScale(height -10, 0, 0, d3.max(data, function(d){ return d[1];}) + 50);

            var xaxis = getAxis().scale(x_scale).orient("bottom").ticks(30);
            var yaxis = getAxis().scale(y_scale).orient("right").ticks(10);

            x_axis.attr("transform",function(d){return "translate(0," + (height - 10) + ")"}).call(xaxis);
            y_axis.attr("transform",function(d){return "translate(0,0)"}).call(yaxis);

            /**
             * Add dots * */ 

            var dot = appendWithData(dots,"circle","circle","","datum",data);
            var text = appendWithData(dots,"text","text","","index",data);

            dot.data(data).attr({
                    "id" : function(d,i){return "datum" +i;},
                    "tag": function(d,i){return i + "";},
                    "cx" : function(d){return x_scale(d[0]).toFixed(0)},
                    "cy" : function(d){return y_scale(d[1]).toFixed(0)},
                    "fill" : function(d){return "rgb(0," + (d[1] * 5) % 255 + ",53)"},
                    "r"  : 10 });
            text.data(data).attr({
                    "id" : function(d,i){return "index" + i;},
                    "tag": function(d,i){return i + ""},
                    "y" : function(d){return y_scale(d[1]).toFixed(0)},
                    "x" : function(d){return x_scale(d[0]).toFixed(0)},
                    "transform" : "translate(15,-15)"
                    })
                    .text(function(d){return d[0] + "," + d[1]});

            var flag = 1;
            var drag = drag();

            function dragstart(){
                console.log("dragstart")
                var cur = d3.select(this);
                console.log("drag");
                cur.transition().ease("elastc").attr({
                    "r" : 15
                });
            }

            function dragging(){
                flag = 0;
                var cur = d3.select(this);
                var tag = cur.attr("tag");
                cir = d3.select("circle[tag='" + tag + "']");
                txt = d3.select("text[tag='" + tag + "']");
                console.log(cur);
                console.log(txt);
                var cur_x = d3.event.x;
                var cur_y = d3.event.y;

                //target.attr("transform","translate(0,0)");

                cir.attr({
                    "cx" : function(d){return cur_x.toFixed(0)},
                    "cy" : function(d){return cur_y.toFixed(0)},
                    //"fill" : function(d){return "rgb(0," + (y_scale(cur_y) * 5) % 255 + ",53)"},
                });
                txt.attr({
                    "x" : function(d){return cur_x.toFixed(0)},
                    "y" : function(d){return cur_y.toFixed(0)},
                })
                .text(new Number(ax_scale(cur_x)).toFixed(0) + "," + new Number(ay_scale(cur_y)).toFixed(0));

            }

            function dragged(){
                var cur = d3.select(this);

                cur.transition().ease("elastc").attr({
                    "r" : 10
                });
                flag = 1;
            }

            drag.on("dragstart",dragstart)
                .on("drag",dragging)
                .on("dragend",dragged);
            dot.call(drag);;


            var zoom = zoom();

            function zoomed(){
                //if(flag){
                    console.log("zoomed");
                    outer.attr("transform","translate(" + d3.event.translate + ")  scale(" + d3.event.scale + ")");
                //}
            }
            zoom.on("zoom",zoomed);

            outer.call(zoom);

        </script>       
</html>

As you see, I set a toggle(the variable called flag) to solve the trigger problem,but it cause a new problem, I can't zoom with wheel when the mouse is aloft a blank space.

Kara
  • 6,115
  • 16
  • 50
  • 57
Alfred Chen
  • 179
  • 3
  • 13

1 Answers1

2

If I understand the question correctly, you want the zooming on the outer while you want dragging on the dots. In that case, there were two problems with your program:

  1. Empty g as the outer: A g element is just an empty container and is unable to capture events inside itself. If you want to capture mouse actions anywhere inside the g, you need to include an invisible background rect inside it with pointer-events: all. Also, you want this element to appear before all other elements in the DOM so that it is indeed in the background:

        var bgRect = addElem(outer, "rect");
        bgRect.attr('fill', 'none')
            .attr('stroke', 'none')
            .attr('width', width)
            .attr('height', height);
        var target = addElem(outer, "g");
        // ...
    

    Update: See this question as well: d3.js - mouseover event not working properly on svg group

  2. Transitioning on zooming: The "expected" zoom behavior is that the point under the mouse pointer stays static while everything around the pointer zooms in/out. Hence, the container needs to both scale and transition.

           function zoomed(){
    
                console.log("zoomed");
                outer.attr("transform","translate(" + d3.event.translate + ")  scale(" + d3.event.scale + ")");
    
            }
    

    However, in your case, you do not want the container to transition so that the axis stays firmly rooted at the origin (0, 0). So:

           function zoomed(){
    
                console.log("zoomed");
                outer.attr("transform","translate(" + [0,0] + ")  scale(" + d3.event.scale + ")");
    
            }
    

Working demo with these changes: http://jsfiddle.net/S3GsC/

Community
  • 1
  • 1
musically_ut
  • 34,028
  • 8
  • 94
  • 106
  • I am trying to digest one aspect of your answer: the `g` being an empty container unable to capture events inside itself. Does this apply only to a `g` that is currently empty, i.e. one not yet containing other elements (like circles and rects) or does it apply to a `g` containing one or more elements as well? I ask this because when you do have elements under a `g` then they do respond to mouse events set on the `g`, like when applying zoom behavior to containers, say `someG.call(zoom)`. Is this happening because an _invisible_ rect is being applied automatically by d3? Need to look at source – FernOfTheAndes Mar 15 '14 at 11:39
  • 1
    @FernOfTheAndes If you initiate an event in an _empty_ part of the chart which is contained inside a `g`, the event will be triggered on the container of the `g`, i.e. usually the `svg` element. The event will not bubble through the `g`. See the implementation of `brush` in `d3` to see another example of a background _rect_. – musically_ut Mar 15 '14 at 11:45
  • I have been missing this nuance. I went to this [example](http://bl.ocks.org/mbostock/3892919) and indeed, the empty part of `g` (the space between tick values) does not zoom but both the rect (of course) and the tick values (less intuitive), both non-empty parts of `g`, do zoom. Thanks so much! – FernOfTheAndes Mar 15 '14 at 12:06
  • I think you just solve the problem:),since I am working now,I think I will adapt your answer when I am available,maybe tomorrow,although your code behave flawless before my eyes,I want to digest your code before I accept it.Thanks:) – Alfred Chen Mar 15 '14 at 12:38