Rufen Sie am Ende eines Übergangs einen Rückruf auf

98

Ich muss eine FadeOut-Methode (ähnlich wie jQuery) mit D3.js erstellen . Was ich tun muss, ist die Deckkraft mit auf 0 zu setzen transition().

d3.select("#myid").transition().style("opacity", "0");

Das Problem ist, dass ich einen Rückruf benötige, um zu erkennen, wann der Übergang abgeschlossen ist. Wie kann ich einen Rückruf implementieren?

Tony
quelle

Antworten:

143

Sie möchten auf das "End" -Ereignis des Übergangs warten.

// d3 v5
d3.select("#myid").transition().style("opacity","0").on("end", myCallback);

// old way
d3.select("#myid").transition().style("opacity","0").each("end", myCallback);
  • Diese Demo verwendet das "Ende" -Ereignis, um viele Übergänge der Reihe nach zu verketten.
  • Das mit D3 gelieferte Donut-Beispiel verwendet dies auch, um mehrere Übergänge miteinander zu verketten.
  • Hier ist meine eigene Demo , die den Stil der Elemente zu Beginn und am Ende des Übergangs ändert.

Aus der Dokumentation für transition.each([type],listener):

Wenn der Typ angegeben ist, wird ein Listener für Übergangsereignisse hinzugefügt, der sowohl "Start" - als auch "Ende" -Ereignisse unterstützt. Der Listener wird für jedes einzelne Element im Übergang aufgerufen, auch wenn der Übergang eine konstante Verzögerung und Dauer aufweist. Das Startereignis kann verwendet werden, um eine sofortige Änderung auszulösen, wenn jedes Element mit dem Übergang beginnt. Das Endereignis kann verwendet werden, um mehrstufige Übergänge einzuleiten, indem das aktuelle Element ausgewählt thisund ein neuer Übergang abgeleitet wird. Während des Endereignisses erstellte Übergänge erben die aktuelle Übergangs-ID und überschreiben daher keinen neueren Übergang, der zuvor geplant wurde.

Weitere Informationen finden Sie in diesem Forenthread zum Thema .

Beachten Sie schließlich, dass Sie die Elemente verwenden können, wenn Sie sie nur nach dem Ausblenden (nach Abschluss des Übergangs) entfernen möchten transition.remove().

Phrogz
quelle
7
Vielen Dank. Dies ist eine großartige Bibliothek, aber es ist nicht so einfach, die wichtigen Informationen in der Dokumentation zu finden.
Tony
9
Mein Problem bei dieser Art, am Ende des Übergangs fortzufahren, ist, dass Ihre Funktion N-mal ausgeführt wird (für N Elemente in der Menge der Übergangselemente). Dies ist manchmal alles andere als ideal.
Steven Lu
2
Ich habe das gleiche Problem. Ich wünschte, es würde die Funktion einmal nach dem letzten Entfernen
ausführen
1
Wie führen Sie einen Rückruf erst durch, nachdem alle Übergänge für a abgeschlossen wurden d3.selectAll()(stattdessen nach Abschluss jedes Elements)? Mit anderen Worten, ich möchte nur eine Funktion zurückrufen, sobald alle Elemente den Übergang abgeschlossen haben.
hobbes3
Hallo, der erste Link zum Stapel- / Gruppenbalkendiagramm verweist auf ein Observable-Notizbuch, das weder einen .eachEreignis-Listener noch das "end"Ereignis verwendet. Es scheint keine Übergänge zu "verketten". Der zweite Link zeigt auf einen Github, der für mich nicht geladen wird.
Die rote Erbse
65

Mike Bostocks Lösung für v3 mit einem kleinen Update:

  function endall(transition, callback) { 
    if (typeof callback !== "function") throw new Error("Wrong callback in endall");
    if (transition.size() === 0) { callback() }
    var n = 0; 
    transition 
        .each(function() { ++n; }) 
        .each("end", function() { if (!--n) callback.apply(this, arguments); }); 
  } 

  d3.selectAll("g").transition().call(endall, function() { console.log("all done") });
kashesandr
quelle
5
Wenn die Auswahl keine Elemente enthält, wird der Rückruf niemals ausgelöst. Eine Möglichkeit, dies zu beheben, istif (transition.size() === 0) { callback(); }
Hughes
1
if (! callback) callback = function () {}; Warum nicht sofort zurückkehren oder eine Ausnahme auslösen? Ein ungültiger Rückruf macht den ganzen Zweck dieser Rutine zunichte. Warum sollte man ihn wie einen blinden Uhrmacher durchgehen? :)
Prizma
1
@kashesandr man kann einfach nichts tun, da der Benutzer den gleichen Effekt hat: (kein Rückruf am Ende des Übergangs) function endall(transition, callback){ if(!callback) return; // ... } oder, da es mit Sicherheit ein Fehler ist, diese Funktion ohne Rückruf aufzurufen, eine Ausnahme auslöst Ich denke, dieser Fall muss nicht zu kompliziert sein. Ausnahme function endall(transition, callback){ if(!callback) throw "Missing callback argument!"; // .. }
Prizma
1
Wenn wir also separate Übergänge enter()und exit()Übergänge haben und warten möchten, bis alle drei abgeschlossen sind, müssen wir Code in den Rückruf einfügen, um sicherzustellen, dass er dreimal aufgerufen wurde, oder? D3 ist so chaotisch! Ich wünschte, ich hätte eine andere Bibliothek ausgewählt.
Michael Scheper
1
Ich sollte hinzufügen, ich erkenne, dass Ihre Antwort einige der Probleme löst, mit denen ich mich befasst habe, und ich kann eine Dienstprogrammfunktion schreiben, um sie anzuwenden. Ich habe jedoch keine elegante Möglichkeit gefunden, sie anzuwenden und dennoch zusätzliche Anpassungen für jeden Übergang zuzulassen, insbesondere wenn die Übergänge für neue und alte Daten unterschiedlich sind. Ich bin mir sicher, dass ich mir etwas einfallen lassen werde, aber "Rufen Sie diesen Rückruf auf, wenn alle diese Übergänge abgeschlossen sind" scheint ein Anwendungsfall zu sein, der sofort in einer Bibliothek unterstützt werden sollte, die so ausgereift ist wie D3. Es scheint also, dass ich die falsche Bibliothek gewählt habe - nicht wirklich D3s Schuld. Wie auch immer, danke für deine Hilfe.
Michael Scheper
44

In d3 v4.0 gibt es jetzt eine Möglichkeit, Ereignishandler explizit an Übergänge anzuhängen:

https://github.com/d3/d3-transition#transition_on

Um Code auszuführen, wenn ein Übergang abgeschlossen ist, benötigen Sie lediglich:

d3.select("#myid").transition().style("opacity", "0").on("end", myCallback);
ericsoco
quelle
Wunderschönen. Event-Handler sind eklig.
KFunk
Es gibt auch transition.remove()( Link ), der einen allgemeinen Anwendungsfall für den Übergang eines Elements aus der Ansicht behandelt: `" Entfernt für jedes ausgewählte Element das Element, wenn der Übergang endet, solange das Element keine anderen aktiven oder ausstehenden Übergänge aufweist Element hat andere aktive oder ausstehende Übergänge, tut nichts. "
Brichins
9
Es sieht so aus, als würde dies als PER-Element bezeichnet, auf das der Übergang angewendet wird, was nach meinem Verständnis nicht die Frage ist.
Taylor C. White
10

Ein etwas anderer Ansatz, der auch funktioniert, wenn viele Übergänge mit vielen Elementen gleichzeitig ausgeführt werden:

var transitions = 0;

d3.select("#myid").transition().style("opacity","0").each( "start", function() {
        transitions++;
    }).each( "end", function() {
        if( --transitions === 0 ) {
            callbackWhenAllIsDone();
        }
    });
Jesper Wir
quelle
Danke, das hat bei mir gut funktioniert. Ich habe versucht, die Ausrichtung der x-Achsenbeschriftung nach dem Laden eines diskreten Balkendiagramms automatisch anzupassen. Die Anpassung kann vor dem Laden nicht wirksam werden, und dies stellte einen Ereignis-Hook bereit, über den ich dies tun konnte.
Whitestryder
6

Das Folgende ist eine andere Version von Mike Bostocks Lösung und inspiriert von @hughes 'Kommentar zu @ kashesandrs Antwort. Es macht einen einzelnen Rückruf am transitionEnde.

Gegeben eine dropFunktion ...

function drop(n, args, callback) {
    for (var i = 0; i < args.length - n; ++i) args[i] = args[i + n];
    args.length = args.length - n;
    callback.apply(this, args);
}

... wir können d3so erweitern:

d3.transition.prototype.end = function(callback, delayIfEmpty) {
    var f = callback, 
        delay = delayIfEmpty,
        transition = this;

    drop(2, arguments, function() {
        var args = arguments;
        if (!transition.size() && (delay || delay === 0)) { // if empty
            d3.timer(function() {
                f.apply(transition, args);
                return true;
            }, typeof(delay) === "number" ? delay : 0);
        } else {                                            // else Mike Bostock's routine
            var n = 0; 
            transition.each(function() { ++n; }) 
                .each("end", function() { 
                    if (!--n) f.apply(transition, args); 
                });
        }
    });

    return transition;
}

Als JSFiddle .

Verwendung transition.end(callback[, delayIfEmpty[, arguments...]]):

transition.end(function() {
    console.log("all done");
});

... oder mit einer optionalen Verzögerung, wenn transitionleer:

transition.end(function() {
    console.log("all done");
}, 1000);

... oder mit optionalen callbackArgumenten:

transition.end(function(x) {
    console.log("all done " + x);
}, 1000, "with callback arguments");

d3.transition.endwendet das übergebene callbackauch mit einem Leerzeichen an, transition wenn die Anzahl der Millisekunden angegeben ist oder wenn das zweite Argument wahr ist. Dadurch werden auch alle zusätzlichen Argumente an callback(und nur diese Argumente) weitergeleitet. Wichtig ist, dass dies nicht standardmäßig gilt, callbackwenn if transitionleer ist, was in einem solchen Fall wahrscheinlich eine sicherere Annahme ist.

milos
quelle
Das ist schön, ich mag es.
Kashsandr
1
Danke @kashesandr. Dies wurde in der Tat von Ihrer Antwort inspiriert!
Milos
Ich glaube nicht, dass wir eine Drop-Funktion oder die Übergabe von Argumenten benötigen, da der gleiche Effekt durch eine Wrapper-Funktion oder durch die Verwendung von bind erzielt werden kann. Ansonsten denke ich, dass es eine großartige Lösung ist +1
Ahmed Masud
Klappt wunderbar !
Benoît Sauvère
Siehe diese Antwort, .end () wurde jetzt offiziell hinzugefügt - stackoverflow.com/a/57796240/228369
chrismarx
5

Ab D3 v5.8.0 + gibt es jetzt eine offizielle Möglichkeit, dies mit zu tun transition.end. Die Dokumente sind hier:

https://github.com/d3/d3-transition#transition_end

Ein Arbeitsbeispiel von Bostock finden Sie hier:

https://observablehq.com/@d3/transition-end

Und die Grundidee ist, dass .end()der Übergang nur durch Anhängen ein Versprechen zurückgibt , das sich erst auflöst, wenn alle Elemente übergangen sind:

 await d3.selectAll("circle").transition()
      .duration(1000)
      .ease(d3.easeBounce)
      .attr("fill", "yellow")
      .attr("cx", r)
    .end();

Weitere Informationen finden Sie in den Versionshinweisen zur Version:

https://github.com/d3/d3/releases/tag/v5.8.0

Chrismarx
quelle
1
Dies ist eine sehr schöne Art, mit Dingen umzugehen. Ich sage nur, für diejenigen unter Ihnen wie mich, die nicht alle v5 kennen und genau dies implementieren möchten, können Sie die neue Übergangsbibliothek mit <script src = " d3js.org/d3-transition.v1 importieren .min.js "> </ script >
DGill
0

Die Lösung von Mike Bostock wurde verbessert, indem kashesandr + Argumente an die Rückruffunktion übergeben wurden:

function d3_transition_endall(transition, callback, arguments) {
    if (!callback) callback = function(){};
    if (transition.size() === 0) {
        callback(arguments);
    }

    var n = 0;
    transition
        .each(function() {
            ++n;
        })
        .each("end", function() {
            if (!--n) callback.apply(this, arguments);
    });
}

function callback_function(arguments) {
        console.log("all done");
        console.log(arguments);
}

d3.selectAll("g").transition()
    .call(d3_transition_endall, callback_function, "some arguments");
int_ua
quelle
-2

Tatsächlich gibt es noch eine Möglichkeit, dies mithilfe von Timern zu tun.

var timer = null,
    timerFunc = function () {
      doSomethingAfterTransitionEnds();
    };

transition
  .each("end", function() {
    clearTimeout(timer);
    timer = setTimeout(timerFunc, 100);
  });
ifadey
quelle
-2

Ich habe ein ähnliches Problem gelöst, indem ich mithilfe einer Variablen eine Dauer für Übergänge festgelegt habe. Dann habe ich setTimeout()die nächste Funktion aufgerufen. In meinem Fall wollte ich eine leichte Überlappung zwischen dem Übergang und dem nächsten Anruf, wie Sie in meinem Beispiel sehen werden:

var transitionDuration = 400;

selectedItems.transition().duration(transitionDuration).style("opacity", .5);

setTimeout(function () {
  sortControl.forceSort();
}, (transitionDuration * 0.75)); 
Brett
quelle