【问题标题】:How to interrupt or chain a transition based on the active transition如何根据活动转换中断或链接转换
【发布时间】:2014-12-09 20:40:35
【问题描述】:

我有一个带有一些过滤器控件作为复选框的 SVG 圆圈图。圆圈有一个进入过渡(过滤器被选中 - 进入屏幕)、一个退出过渡(过滤器未被选中 - 离开屏幕)和一个更新过渡(过滤器悬停在 - 突出显示元素)。

由于我使用相同的控件来进入/退出/突出显示(即复选框),因此每当我取消选中过滤器然后将鼠标移开时,突出显示转换会取消退出转换并且圆圈会停留在任何位置它即将退出。

有没有办法链接高亮过渡,这样它就不会取消退出过渡?有没有办法标记转换,以便我可以根据活动转换的标签链接或中断?我不认为我可以使用transition.eachtransition.transition 方法,因为退出转换选择的元素可能与突出显示转换选择的元素不同(例如,用户单击过滤器1 然后将鼠标悬停在过滤器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>

【问题讨论】:

  • 在 D3 中,安排新的过渡总是会取消现有的过渡。但是,您可以检查元素上是否已经存在转换(检查数据中的 .__transition__ 属性),如果是这种情况,则不要安排新的转换。
  • @LarsKotthoff 我之前实际上已经尝试过,但没有成功。在某些情况下,mouseleave 上的突出显示转换不会中断mouseenter 上的突出显示转换,并且圆圈将保持突出显示。这是因为我无法检查 哪个 转换处于活动状态(即,如果前一个正在突出显示,但如果它正在进入/退出,我想中断转换)。
  • 嗯,在给定的时间只能有一个过渡活动,所以我不完全确定你在说什么。你的意思是你有几个过渡同时工作?
  • @LarsKotthoff 我对问题进行了编辑,以更明确地说明最终目标(我还更新了 sn-p 以包含输入转换和更好的过滤器可视化)。
  • 啊,我明白你的意思了。我会存储一个属性,其中包含表示当前转换类型的数据,您可以检查一下是否应该稍后取消它。

标签: javascript svg d3.js transition


【解决方案1】:

更新:有一个新的“命名转换”d3 API 是proposed。我可以test this out 并且效果很好!比下面的hack简单多了!


我能够找到一个解决方案,但它非常复杂;(希望有人会看看这个并发布一个更简单的解决方案 =)我还打开了一个 d3 issue 以提供更多详细信息。

重申一下,最终目标是

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

这种理想的行为可以在here 看到。我基本上用.call(transitionHandler) 替换了标准的.transition() 调用。 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] 对象并使用该标签来决定是中断转换还是链接它。

【讨论】:

    猜你喜欢
    • 2023-03-11
    • 2013-07-20
    • 2016-11-22
    • 1970-01-01
    • 2019-04-11
    • 1970-01-01
    • 1970-01-01
    • 2019-08-03
    • 2023-03-18
    相关资源
    最近更新 更多