【问题标题】:d3.js scopes possibly bleeding overd3.js 范围可能溢出
【发布时间】:2017-06-08 14:28:20
【问题描述】:

我有一个基于角度的 d3 指令,用于创建多系列折线图。在该指令内部有mouseovermouseentermousemove 事件的事件。出于某种原因,他们流血到不同的 div,相同的指令所在。当我将鼠标悬停在一个图表上时,它会在两个图表上绘制圆圈。有没有办法控制它?有人可以向我解释一下,示波器像这样流血是怎么发生的?

这里是 d3 指令:

angular.module('MissionControlApp').directive('d3MultiSeriesLine', ['d3', function(d3) {
    return {
        restrict: 'E',
        scope: {
            data: '=',
            keys: '=',
            onClick: '&d3OnClick'
        },
        link: function(scope, ele) {
            var svg = d3.select(ele[0])
                .append("svg")
                .attr("width", "100%");

            // on window resize, re-render d3 canvas
            window.onresize = function() {
                return scope.$apply();
            };
            scope.$watch(function(){
                    return angular.element(window)[0].innerWidth;
                }, function(){
                    return scope.render(scope.data);
                }
            );

            // watch for data changes and re-render
            scope.$watch("data", function(newVals) {
                if(!newVals) return;
                return scope.render(newVals);
            }, true);

            scope.render = function (data) {
                if(!data) return;

                svg.selectAll("*").remove();

                // setup variables
                var width, height;
                var margin = {top: 5, right: 10, bottom: 20, left: 30};

                width = d3.select(ele[0])[0][0].offsetWidth - margin.left - margin.right;
                height = 300 - margin.top - margin.bottom;

                // set the height based on the calculations above
                svg.attr('height', height + margin.top + margin.bottom);

                var parseDate = d3.time.format('%Y-%m-%dT%H:%M:%S.%LZ').parse;
                var dateFormat = d3.time.format("%d %b");

                var x = d3.time.scale()
                    .range([0, width]);

                var y = d3.scale.linear()
                    .range([height, 0]);

                var color = d3.scale.category10();

                var xAxis = d3.svg.axis()
                    .scale(x)
                    .orient("bottom")
                    .ticks(6)
                    .tickFormat(dateFormat);

                var yAxis = d3.svg.axis()
                    .scale(y)
                    .orient("left")
                    .ticks(9);

                var line = d3.svg.line()
                    .interpolate("basis")
                    .x(function (d) { return x(d.date); })
                    .y(function (d) { return y(d.value); });

                color.domain(scope.keys);

                data.forEach(function (d) { d.date = parseDate(d.createdOn) });

                x.domain(d3.extent(data, function(d) { return d.date; }));

                var lineData = color.domain().map(function(name){
                    return {
                        name: name,
                        values: data.map(function (d) {
                            return {date: parseDate(d.createdOn), value: +d[name]};
                        })
                    }
                });

                var maxValue = d3.max(lineData, function(c) {
                    return d3.max(c.values, function(v) {
                        return v.value;
                    });
                });

                y.domain([0, maxValue + 20]);

                // Add the X Axis
                svg.append("g")
                    .attr("class", "axisMain")
                    .attr("transform", "translate(" + margin.left + "," + (height + margin.top) + ")")
                    .call(xAxis);

                // Add the Y Axis
                svg.append("g")
                    .attr("class", "axisMain")
                    .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
                    .call(yAxis);

                var svgLine = svg.selectAll(".city")
                    .data(lineData)
                    .enter().append("g")
                    .attr("class", "city");

                svgLine.append("path")
                    .attr("class", "line")
                    .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
                    .attr("d", function(d) { return line(d.values); })
                    .style("stroke", function(d) { return color(d.name); })
                    .attr("stroke-width", 1.5)
                    .attr("fill", "none")
                    .style("opacity", 0)
                    .transition()
                    .duration(1500)
                    .style("opacity", 1);

                svgLine.append("text")
                    .datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
                    .attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.value) + ")"; })
                    .attr("x", margin.left)
                    .attr("y", -2)
                    .attr("text-anchor", "end")
                    .attr("dy", ".35em")
                    .text(function(d) { return d.name; });

                var mouseG = svg.append("g")
                    .attr("class", "mouse-over-effects");

                mouseG.append("path") // this is the black vertical line to follow mouse
                    .attr("class", "mouse-line")
                    .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
                    .style("stroke", "grey")
                    .style("stroke-width", "1px")
                    .style("stroke-dasharray", "3,3")
                    .style("opacity", "0");

                var lines = document.getElementsByClassName('line');

                var mousePerLine = mouseG.selectAll('.mouse-per-line')
                    .data(lineData)
                    .enter()
                    .append("g")
                    .attr("class", "mouse-per-line");

                mousePerLine.append("circle")
                    .attr("r", 6)
                    .style("stroke", function(d) { return color(d.name); })
                    .style("fill", "none")
                    .style("stroke-width", "1px")
                    .style("opacity", "0");

                mousePerLine.append("text")
                    .attr("transform", "translate(10,-6)");

                mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas
                    .attr('width', width) // can't catch mouse events on a g element
                    .attr('height', height)
                    .attr('fill', 'none')
                    .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
                    .attr('pointer-events', 'all')
                    .on('mouseout', function() { // on mouse out hide line, circles and text
                        d3.select(".mouse-line")
                            .style("opacity", "0");
                        d3.selectAll(".mouse-per-line circle")
                            .style("opacity", "0");
                        d3.selectAll(".mouse-per-line text")
                            .style("opacity", "0");
                    })
                    .on('mouseover', function() { // on mouse in show line, circles and text
                        d3.select(".mouse-line")
                            .style("opacity", "1");
                        d3.selectAll(".mouse-per-line circle")
                            .style("opacity", "1");
                        d3.selectAll(".mouse-per-line text")
                            .style("opacity", "1");
                    })
                    .on('mousemove', function() { // mouse moving over canvas
                        var mouse = d3.mouse(this);
                        d3.select(".mouse-line")
                            .attr("d", function() {
                                var d = "M" + mouse[0] + "," + height;
                                d += " " + mouse[0] + "," + 0;
                                return d;
                            });

                        d3.selectAll(".mouse-per-line")
                            .attr("transform", function(d, i) {
                                console.log(width/mouse[0]);
                                var xDate = x.invert(mouse[0]),
                                    bisect = d3.bisector(function(d) { return d.date; }).right;
                                bisect(d.values, xDate);

                                var beginning = 0,
                                    end = lines[i].getTotalLength();

                                var pos;
                                var target;
                                while (true) {
                                    target = Math.floor((beginning + end) / 2);
                                    pos = lines[i].getPointAtLength(target);
                                    if ((target === end || target === beginning) && pos.x !== mouse[0]) {
                                        break;
                                    }
                                    if (pos.x > mouse[0]) end = target;
                                    else if (pos.x < mouse[0]) beginning = target;
                                    else break; //position found
                                }

                                d3.select(this).select('text')
                                    .text(y.invert(pos.y).toFixed(2));

                                return "translate(" + (mouse[0]+margin.left) + "," + (pos.y + margin.top) +")";
                            });
                    });
            };
        }
    };
}]);

这是 HTML 代码:

<div class="row">
    <div class="panel-group" id="p1">
        <div class="panel panel-default">
            <div class="panel-heading" data-toggle="collapse" data-parent="#p1" data-target="#collapseP1">
                <h4 class="panel-title">Views / Views on Sheet</h4>
            </div>
            <div id="collapseP1" class="panel-collapse collapse in">
                <div class="panel-body">
                    <d3-multi-series-line data="vm.d3ViewStatsData" keys="vm.ViewKeys" d3-on-click="vm.d3OnClick(item)"></d3-multi-series-line>
                </div>
            </div>
        </div>
    </div>
</div>
<div class="row">
    <div class="panel-group" id="p2">
        <div class="panel panel-default">
            <div class="panel-heading" data-toggle="collapse" data-parent="#p2" data-target="#collapseP2">
                <h4 class="panel-title">Schedules / Schedules on Sheet</h4>
            </div>
            <div id="collapseP2" class="panel-collapse collapse in">
                <div class="panel-body">
                    <d3-multi-series-line data="vm.d3ViewStatsData" keys="vm.ScheduleKeys" d3-on-click="vm.d3OnClick(item)"></d3-multi-series-line>
                </div>
            </div>
        </div>
    </div>
</div>

【问题讨论】:

    标签: javascript angularjs twitter-bootstrap d3.js


    【解决方案1】:

    这是我的一些旧代码:)

    当我最初写这篇文章时,我假设文档中有一个图表。那里有几行在整个文档中全局选择/查找内容。像这样的行:

    var lines = document.getElementsByClassName('line');
    

    d3.selectAll(".mouse-per-line")
    

    不是基于您原来的elem[0] 的选择,因此不限于您的指令。

    简单的解决方法是将所有 d3.select... 替换为 svg.select... 并将行替换为以下内容:

    var lines = [];
    svg.selectAll('line').each(function(){
      lines.append(this);
    });
    

    【讨论】:

    • 马克,感谢您在 GitHub 上分享它。是的,我从 Mike 的 Blocks 页面获得了其中的一部分。我最喜欢你的多系列叠加层,所以我用它开始。我会看看你提出的改变并让你知道。你所说的关于选择的内容非常有道理。
    • 好的,你的建议很有效。唯一的变化是我们需要选择所有具有 line 类的对象,而不是所有行,所以 svg.selectAll(".line") 其他一切都很好!
    猜你喜欢
    • 2013-07-27
    • 2017-12-24
    • 1970-01-01
    • 1970-01-01
    • 2016-03-09
    • 1970-01-01
    • 2014-01-20
    • 2013-07-31
    • 1970-01-01
    相关资源
    最近更新 更多