如何根据活动转换中断或链接转换

时间:2014-10-14 22:07:25

标签: javascript svg d3.js transition

我有一个SVG圆圈图,其中一些过滤器控件作为复选框。圆圈有一个输入过渡(过滤器被检查 - 进入屏幕),退出过渡(过滤器未选中 - 离开屏幕),以及更新过渡(过滤器悬停在 - 高亮显示元素)。

由于我使用相同的控件进入/退出/突出显示(即复选框),每当我取消选中过滤器然后移开鼠标时,突出显示过渡会取消退出过渡并且圆圈会在任何位置四处移动它正在走出去。

有没有办法链接高亮转换,以便它不会取消退出转换?有没有办法标记转换,以便我可以根据活动转换的标签链接或中断?我不认为我可以使用transition.eachtransition.transition方法,因为退出转换选择的元素可能与高亮转换选择的元素不同(例如,用户点击filter1然后将鼠标悬停在过滤器2)。

明确地说,最终目标是

  • 如果进入/退出活动转换
    • 如果后续转换是进入/退出,请中断并应用
    • 如果后续转换突出显示,则链
  • 如果突出显示活动转换
    • 如果突出显示后续转换,请中断并应用
    • 如果后续转换是进入/退出,则链

这可归结为

  • 如果后续转换与活动转换相同,则中断并应用
  • 如果后续转换与活动转换不同,则链

将鼠标悬停在某个按钮上会突出显示各个圆圈,然后单击它会将其删除。尝试单击并移开鼠标以查看问题。

// Setup container
var container = d3.selectAll('#svgContainer');
var width = 640,
    height = 480;
var nShapes = 50;

// Create random data
var filteredData,
    data = d3.range(nShapes).map(function(d, i) {
        return {
            id: i,
            x: Math.floor(Math.random() * width),
            y: Math.floor(Math.random() * height),
            r: Math.floor(Math.random() * width / 15),
            red: Math.floor(Math.random() * 100),
            blue: Math.floor(Math.random() * 100)
        };
    });

// Create filter functions
var filters = {
    red: function(d) {return d.red > 50;},
    blue: function(d) {return d.blue > 50;}
};

// Create SVG
var svg = container.append('svg')
    .attr({
        width: width,
        height: height
    });

// Hook up hover handlers for filters
$(".filter").hover(function(e) {return onHover(e, this.dataset['filter']);});

// Hook up click handlers for filters
$(".filter").change(function(e) {
    filterData();
    draw();
});

// Filter data and draw the canvas
filterData();
draw();





/**
 * Recalcualte filtered data
 */
function filterData() {
    filteredData = data;
    
    $('.filter').each(function(idx, el) {
        var filterName = this.dataset['filter'];
        var filteredOut = !$(this).find("input").prop('checked');
        if (filteredOut) filteredData = filteredData.filter(function(d) {
            return !filters[filterName](d);
        });
    });
}

/**
 * Hover handler
 */
function onHover(event, filterName) {
    var isHovering = (event.type == "mouseenter");
    svg.selectAll('.shape')
        .filter(filters[filterName])
        .transition().duration(100)
        .attr({
            r: function (d) {return isHovering ? 1.5 * d.r : d.r;}
        })
        .style({
            opacity: isHovering ? 0.5 : 1.0
        });
}

/**
 * Draw function
 */
function draw() {
    var duration = 750;
    var shapes = svg.selectAll('.shape').data(filteredData, function(d) {
        return d.id;
    });
    shapes.enter().append('circle') // apply to enter selection only
        .attr('class', 'shape')
        .attr({
            cx: 0,
            cy: 0,
            r: 0
        })
        .style('fill', 'white');

    shapes.transition().duration(duration) // apply to enter + update selection
        .delay(function(d) {return (d.id / nShapes) * duration;})
        .attr({
            cx: function(d) {return d.x;},
            cy: function(d) {return d.y;},
            r: function(d) {return d.r;}
        })
        .style({
            fill: function(d) {
                if (filters.red(d) && filters.blue(d)) return "purple";
                if (filters.red(d)) return "red";
                if (filters.blue(d)) return "blue";
                return "green";
            }
        });

    shapes.exit().transition().duration(duration) // apply to exit selection only
        .delay(function(d) {return (d.id / nShapes) * duration;})
        .attr({
            cx: 0,
            cy: 0,
            r: 0
        })
        .style('fill', 'white')
        .remove();
}
svg {
    border: 1px solid black;
}
.shape {
    fill-opacity: 0.9;
}
label {
    margin-right: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<label id="red" class="filter" data-filter="red">
    <input type="checkbox" checked />Red Filter</label>
<label id="blue" class="filter" data-filter="blue">
    <input type="checkbox" checked />Blue Filter</label>
<div id="svgContainer"></div>

1 个答案:

答案 0 :(得分:0)

更新:有一个新的“命名转换”d3 API为proposed。我能够test this out并且效果很好!比下面的黑客简单得多!


我能够找到一个解决方案,但它很复杂;(希望有人会看一下这个并发布一个更简单的解决方案=)我还打开了一个d3 issue,其中包含更多细节。

重申一下,最终目标是

  
      
  • 如果后续转换与上一次转换相同,则中断并应用
  •   
  • 如果后续转换与上一次转换不同,则链
  •   

可以看到here这种理想的行为。我基本上用.transition()替换标准.call(transitionHandler)来电。 transitionHandler函数看起来像

/**
 * Transition handler
 * @param selection the selection object
 * @param label the transition namespace
 * @param duration the transition duration
 * @param apply a function that applies transition attributes/styles
 * @param remove true if removing the element at end of transition
 */
 function trans(selection, label, duration, apply, remove) {
    selection.each(function(d) {
        // Create new transition if no current transition/selection
        if (!this.__transition__ || !transitionSelections[d.id]) {
            transitionSelections[d.id] = d3.select(this).transition();
            var newTransId = transitionSelections[d.id].id;
            this.__transition__[newTransId].namespace = label;
        }
        else {
            var transId = this.__transition__.active;
            var transition = this.__transition__[transId];

            // If the transition hasn't started yet, grab the next one
            if (!transition) {
                var transIds = Object.keys(this.__transition__)
                    .filter(function(el) {
                        // Only keep transition IDs (and not 'active' or 'count' keys)
                        return !isNaN(el);
                    })
                    .sort(function(a,b) {
                        // Convert to numbers and sort
                        return +a - +b;
                    });

                // Find the next transition
                var idx = d3.bisect(transIds, transId);

                // Sometimes, the transition object doesn't exist yet?!
                if (idx == transIds.length) transition = null;
                else {
                    transId = transIds[idx];
                    transition = this.__transition__[transId];
                }
            }

            // If the label matches (or the object doesn't exist yet), interrupt and start new transition
            if (!transition || transition.namespace == label) {
                transitionSelections[d.id] = d3.select(this).transition();
                var newTransId = transitionSelections[d.id].id;
                this.__transition__[newTransId].namespace = label;
            }
            // Otherwise chain it
            else {
                transitionSelections[d.id] = transitionSelections[d.id].transition();
                var newTransId = transitionSelections[d.id].id;
                this.__transition__[newTransId].namespace = label;
            }
        }

        // Apply transition attributes
        // FIXME: linear easing for debugging only
        transitionSelections[d.id].ease('linear').duration(duration).call(apply).each("end", function(d) {
            if(remove) d3.select(this).remove();
        });
    });
}

我使用transitionSelections作为字典来跟踪最新的选择转换,以便我可以在需要时链接转换。我还将namespace属性添加到__transition__[id]对象,并使用该标签来决定是中断转换还是链接它。