001package conexp.fx.gui.graph;
002
003/*
004 * #%L
005 * Concept Explorer FX
006 * %%
007 * Copyright (C) 2010 - 2023 Francesco Kriegel
008 * %%
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as
011 * published by the Free Software Foundation, either version 3 of the
012 * License, or (at your option) any later version.
013 * 
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 * 
019 * You should have received a copy of the GNU General Public
020 * License along with this program.  If not, see
021 * <http://www.gnu.org/licenses/gpl-3.0.html>.
022 * #L%
023 */
024
025import java.util.Arrays;
026import java.util.Collection;
027import java.util.HashSet;
028import java.util.LinkedList;
029import java.util.Map;
030import java.util.Queue;
031import java.util.Set;
032import java.util.concurrent.ConcurrentHashMap;
033import java.util.concurrent.ConcurrentLinkedQueue;
034import java.util.stream.Stream;
035
036import com.google.common.base.Function;
037import com.google.common.base.Predicate;
038import com.google.common.base.Predicates;
039import com.google.common.collect.Collections2;
040import com.google.common.collect.Maps;
041
042import be.humphreys.simplevoronoi.GraphEdge;
043import conexp.fx.core.collections.Collections3;
044import conexp.fx.core.collections.Pair;
045import conexp.fx.core.layout.LayoutEvolution;
046import conexp.fx.core.math.GuavaIsomorphism;
047import conexp.fx.core.math.VoronoiGenerator;
048import conexp.fx.gui.graph.option.AnimationSpeed;
049import conexp.fx.gui.graph.option.EdgeHighlight;
050import conexp.fx.gui.graph.option.GraphTransformation;
051import conexp.fx.gui.graph.transformation.PolarTransformation;
052import conexp.fx.gui.graph.transformation.RotationXY;
053import conexp.fx.gui.util.LaTeX;
054import conexp.fx.gui.util.Platform2;
055import conexp.fx.gui.util.SynchronizedPane;
056import conexp.fx.gui.util.TransitionTimer;
057import javafx.animation.FadeTransitionBuilder;
058import javafx.animation.KeyFrame;
059import javafx.animation.KeyValue;
060import javafx.animation.Timeline;
061import javafx.animation.Transition;
062import javafx.beans.InvalidationListener;
063import javafx.beans.Observable;
064import javafx.beans.binding.DoubleBinding;
065import javafx.beans.binding.FloatBinding;
066import javafx.beans.binding.IntegerBinding;
067import javafx.beans.binding.ObjectBinding;
068import javafx.beans.property.BooleanProperty;
069import javafx.beans.property.DoubleProperty;
070import javafx.beans.property.IntegerProperty;
071import javafx.beans.property.ObjectProperty;
072import javafx.beans.property.SimpleBooleanProperty;
073import javafx.beans.property.SimpleDoubleProperty;
074import javafx.beans.property.SimpleIntegerProperty;
075import javafx.beans.property.SimpleObjectProperty;
076import javafx.beans.value.ChangeListener;
077import javafx.beans.value.ObservableValue;
078import javafx.collections.FXCollections;
079import javafx.collections.ObservableList;
080import javafx.event.ActionEvent;
081import javafx.event.EventHandler;
082import javafx.geometry.BoundingBox;
083import javafx.geometry.Point2D;
084import javafx.geometry.Point3D;
085import javafx.geometry.Rectangle2D;
086import javafx.scene.Node;
087import javafx.scene.control.LabelBuilder;
088import javafx.scene.image.ImageView;
089import javafx.scene.input.MouseButton;
090import javafx.scene.input.MouseEvent;
091import javafx.scene.input.ScrollEvent;
092import javafx.scene.layout.BorderPane;
093import javafx.scene.layout.StackPane;
094import javafx.scene.layout.StackPaneBuilder;
095import javafx.scene.paint.Color;
096import javafx.scene.shape.Line;
097import javafx.scene.shape.Rectangle;
098import javafx.scene.shape.StrokeType;
099import javafx.scene.text.Text;
100import javafx.util.Duration;
101
102@SuppressWarnings("deprecation")
103public abstract class Graph<T, N extends Node> extends BorderPane {
104
105  public static final Color COLOR_DEFAULT       = Color.valueOf("#0576B0");
106  public static final Color COLOR_LABEL_DEFAULT = Color.valueOf("#EEEEEE");
107
108  protected abstract class Vertex {
109
110    protected final T                          element;
111    protected final N                          node;
112    protected final ObservableValue<Point3D>   position;
113    protected final ObservableList<UpperLabel> upperLabels     =
114        FXCollections.observableList(new LinkedList<UpperLabel>());
115    protected final ObservableList<LowerLabel> lowerLabels     =
116        FXCollections.observableList(new LinkedList<LowerLabel>());
117    protected final Set<Pair<Vertex, Edge>>    pendingVertices = new HashSet<Pair<Vertex, Edge>>();
118
119    protected Vertex(
120        final T element,
121        final N node,
122        final ObservableValue<Point3D> position,
123        final Double layoutX,
124        final Double layoutY) {
125      super();
126      this.element = element;
127      this.node = node;
128      this.position = position;
129      this.node.translateXProperty().set(layoutX == null ? front.getWidth() / 2d : layoutX);
130      this.node.translateYProperty().set(layoutY == null ? front.getHeight() / 2d : layoutY);
131      synchronized (vertices) {
132        vertices.put(element, this);
133      }
134      synchronized (controller.addVertices) {
135        controller.addVertices.offer(this);
136      }
137    }
138
139    protected abstract void init();
140
141    protected abstract void dispose();
142  }
143
144  protected abstract class Edge {
145
146    protected Vertex           lower;
147    protected Vertex           upper;
148    protected final Pair<T, T> elements;
149    protected final Line       line          = new Line();
150    protected DoubleBinding    opacity;
151    private boolean            isInitialized = false;
152    protected boolean          isDisposing   = false;
153
154    protected Edge(final Pair<T, T> elements) {
155      super();
156      this.elements = elements;
157      this.line.setStrokeWidth(2);
158      synchronized (edges) {
159        edges.put(elements, this);
160      }
161      synchronized (controller.addEdges) {
162        controller.addEdges.offer(this);
163      }
164    }
165
166    protected void initialize() throws NullPointerException {
167      if (isInitialized)
168        return;
169      synchronized (vertices) {
170        lower = vertices.get(elements.x());
171        if (lower == null)
172          throw new NullPointerException();
173        upper = vertices.get(elements.y());
174        if (upper == null)
175          throw new NullPointerException();
176      }
177      bindStart();
178      bindEnd();
179      opacity = new DoubleBinding() {
180
181        {
182          bind(lower.node.opacityProperty(), upper.node.opacityProperty());
183        }
184
185        protected final double computeValue() {
186          return (lower.node.opacityProperty().get() + upper.node.opacityProperty().get()) / 2d;
187        }
188      };
189      isInitialized = true;
190    }
191
192    protected void dispose() {
193      line.strokeWidthProperty().unbind();
194      line.opacityProperty().unbind();
195      unbindStart();
196      unbindEnd();
197    }
198
199    protected final void bindOpacity() {
200      line.opacityProperty().bind(opacity);
201    }
202
203    protected final void bindStart() {
204      line.startXProperty().bind(lower.node.translateXProperty());
205      line.startYProperty().bind(lower.node.translateYProperty());
206    }
207
208    protected final void bindEnd() {
209      line.endXProperty().bind(upper.node.translateXProperty());
210      line.endYProperty().bind(upper.node.translateYProperty());
211    }
212
213    protected final void unbindStart() {
214      line.startXProperty().unbind();
215      line.startYProperty().unbind();
216    }
217
218    protected final void unbindEnd() {
219      line.endXProperty().unbind();
220      line.endYProperty().unbind();
221    }
222  }
223
224  protected abstract class Label {
225
226    protected final ObservableValue<T>      element;
227    protected final ObjectBinding<Vertex>   vertex;
228    protected final ImageView               tex           = new ImageView();
229    protected final Text                    text          = new Text();
230    protected final boolean                 showLaTeX;
231    protected final Rectangle               back          = new Rectangle();
232    protected final StackPane               content       = StackPaneBuilder.create().children(back).build();
233    protected final IntegerProperty         index         = new SimpleIntegerProperty(0);
234    protected final ObjectProperty<Point2D> shift         = new SimpleObjectProperty<Point2D>(new Point2D(0, 0));
235    protected boolean                       isInitialized = false;
236
237    protected Label(final ObservableValue<T> element, final ObservableValue<String> string, final boolean showLaTeX) {
238      super();
239      this.showLaTeX = showLaTeX;
240      if (showLaTeX) {
241        content.getChildren().add(LabelBuilder.create().graphic(tex).build());
242      } else {
243        content.getChildren().add(text);
244      }
245      this.element = element;
246      this.vertex = new ObjectBinding<Vertex>() {
247
248        {
249          bind(element);
250        }
251
252        protected final Vertex computeValue() {
253          synchronized (vertices) {
254            return vertices.get(element.getValue());
255          }
256        }
257      };
258      this.rebind(null, vertex.get());
259      this.vertex.addListener(new ChangeListener<Vertex>() {
260
261        public final void
262            changed(final ObservableValue<? extends Vertex> observable, final Vertex from, final Vertex to) {
263          rebind(from, to);
264        }
265      });
266      if (showLaTeX) {
267        this.tex.imageProperty().bind(LaTeX.toFXImageBinding(string, new FloatBinding() {
268
269          {
270            bind(zoom, textSize);
271          }
272
273          @Override
274          protected final float computeValue() {
275            return (float) (zoom.get() * textSize.get());
276          }
277        }));
278      } else {
279        this.text.textProperty().bind(string);
280      }
281      this.createContent();
282      synchronized (controller.addLabels) {
283        controller.addLabels.offer(this);
284      }
285    }
286
287    protected abstract void rebind(final Vertex from, final Vertex to);
288
289    private void createContent() {
290      back.setFill(COLOR_LABEL_DEFAULT);
291      back.setStrokeType(StrokeType.OUTSIDE);
292      back.setStroke(Color.BLACK);
293      back.setStrokeWidth(0.25d);
294      back.setOpacity(0.8d);
295      if (showLaTeX) {
296        back.widthProperty().bind(new DoubleBinding() {
297
298          {
299            bind(tex.boundsInLocalProperty());
300          }
301
302          protected double computeValue() {
303            return tex.boundsInLocalProperty().getValue().getWidth() + 4;
304          }
305        });
306        back.heightProperty().bind(new DoubleBinding() {
307
308          {
309            bind(tex.boundsInLocalProperty());
310          }
311
312          protected double computeValue() {
313            return tex.boundsInLocalProperty().getValue().getHeight();
314          }
315        });
316      } else {
317        back.widthProperty().bind(new DoubleBinding() {
318
319          {
320            bind(text.boundsInLocalProperty());
321          }
322
323          protected double computeValue() {
324            return text.boundsInLocalProperty().getValue().getWidth() + 4;
325          }
326        });
327        back.heightProperty().bind(new DoubleBinding() {
328
329          {
330            bind(text.boundsInLocalProperty());
331          }
332
333          protected double computeValue() {
334            return text.boundsInLocalProperty().getValue().getHeight();
335          }
336        });
337      }
338    }
339
340    protected void dispose() {
341      index.unbind();
342      synchronized (controller.removeLabels) {
343        controller.removeLabels.add(this);
344      }
345    }
346  }
347
348  protected abstract class LowerLabel extends Label {
349
350    protected LowerLabel(ObservableValue<T> element, ObservableValue<String> string, final boolean showLaTeX) {
351      super(element, string, showLaTeX);
352    }
353
354    protected void rebind(final Vertex from, final Vertex to) {
355      if (from != null)
356        synchronized (from.lowerLabels) {
357          from.lowerLabels.remove(this);
358        }
359      synchronized (to.lowerLabels) {
360        to.lowerLabels.add(this);
361      }
362      index.bind(new IntegerBinding() {
363
364        {
365          bind(to.lowerLabels);
366        }
367
368        protected int computeValue() {
369          synchronized (to.lowerLabels) {
370            return to.lowerLabels.indexOf(LowerLabel.this);
371          }
372        }
373      });
374    }
375
376    protected void dispose() {
377      super.dispose();
378      final Vertex v = vertex.get();
379      synchronized (v.lowerLabels) {
380        v.lowerLabels.remove(this);
381      }
382    }
383  }
384
385  protected abstract class UpperLabel extends Label {
386
387    protected UpperLabel(ObservableValue<T> element, ObservableValue<String> string, final boolean showLaTeX) {
388      super(element, string, showLaTeX);
389    }
390
391    protected void rebind(final Vertex from, final Vertex to) {
392      if (from != null)
393        synchronized (from.upperLabels) {
394          from.upperLabels.remove(this);
395        }
396      synchronized (to.upperLabels) {
397        to.upperLabels.add(this);
398      }
399      index.bind(new IntegerBinding() {
400
401        {
402          bind(to.upperLabels);
403        }
404
405        protected int computeValue() {
406          synchronized (to.upperLabels) {
407            return -to.upperLabels.indexOf(UpperLabel.this) - 1;
408          }
409        }
410      });
411    }
412
413    protected void dispose() {
414      super.dispose();
415      final Vertex v = vertex.get();
416      synchronized (v.upperLabels) {
417        v.upperLabels.remove(this);
418      }
419    }
420  }
421
422  protected class Tile extends Rectangle {
423
424    protected final Rectangle2D rect;
425
426    protected Tile(final LayoutEvolution<?, ?>.Value value, final Double initialX, final Double initialY) {
427      this(value.rectangle);
428      if (value.result > 1d)
429        this.setFill(Color.RED);
430      else
431        this.setFill(Color.rgb((int) (255d * Math.pow(1d - value.result, 0.125d)), 255, 255));
432      if (initialX != null)
433        this.translateXProperty().set(initialX);
434      if (initialY != null)
435        this.translateYProperty().set(initialY);
436    }
437
438    private Tile(final Rectangle2D rect) {
439      super();
440      this.rect = rect;
441      synchronized (controller.addTiles) {
442        controller.addTiles.add(this);
443      }
444    }
445  }
446
447  protected class HighlightRequest {
448
449    protected final Set<T>        elements;
450    protected final EdgeHighlight edgeHighlightOption;
451    protected final Color         vertexColor;
452    protected final Color         edgeColor;
453    protected final Color         objectLabelBackColor;
454    protected final Color         objectLabelTextColor;
455    protected final Color         attributeLabelBackColor;
456    protected final Color         attributeLabelTextColor;
457
458    protected HighlightRequest(
459        final Set<T> elements,
460        final EdgeHighlight edgeHighlightPolicy,
461        final Color vertexColor,
462        final Color edgeColor,
463        final Color objectLabelBackColor,
464        final Color objectLabelTextColor,
465        final Color attributeLabelBackColor,
466        final Color attributeLabelTextColor) {
467      super();
468      this.elements = elements;
469      this.edgeHighlightOption = edgeHighlightPolicy;
470      this.vertexColor = vertexColor;
471      this.edgeColor = edgeColor;
472      this.objectLabelBackColor = objectLabelBackColor;
473      this.objectLabelTextColor = objectLabelTextColor;
474      this.attributeLabelBackColor = attributeLabelBackColor;
475      this.attributeLabelTextColor = attributeLabelTextColor;
476    }
477  }
478
479  protected final class Config {
480
481    public final GuavaIsomorphism<Point3D, Point3D> iso;
482    public final BoundingBox                        box;
483    public final double                             f, x0, y0, w, h;
484
485    protected Config(
486        final GuavaIsomorphism<Point3D, Point3D> iso,
487        final BoundingBox box,
488        final double f,
489        final double x0,
490        final double y0,
491        final double w,
492        final double h) {
493      super();
494      this.iso = iso;
495      this.box = box;
496      this.f = f;
497      this.x0 = x0;
498      this.y0 = y0;
499      this.w = w;
500      this.h = h;
501    }
502
503    protected final Point3D toPane(final Point3D p) {
504      final Point3D q = iso.apply(p);
505      final double z = (q.getZ() - box.getMinZ()) / (box.getMaxZ() - box.getMinZ());
506      return new Point3D(q.getX() * f + x0, q.getY() * f + y0, z);
507    }
508
509    protected final Point3D toContent(final Point3D p) {
510      return iso.invert(new Point3D((p.getX() - x0) / f, (p.getY() - y0) / f, 0));
511    }
512
513    protected final Point3D toContent(final Point2D p) {
514      return iso.invert(new Point3D((p.getX() - x0) / f, (p.getY() - y0) / f, 0));
515    }
516  }
517
518//  protected final class Cleaner {
519//
520////    private final class CleanTask extends TimerTask {
521////
522////      public void run() {
523////        if (Platform.isFxApplicationThread())
524////          clean();
525////        else
526////          Platform.runLater(new Runnable() {
527////
528////            public void run() {
529////              clean();
530////            }
531////          });
532////      }
533////    }
534////
535////    private final Timer timer = new Timer();
536////    private TimerTask   task  = new TimerTask() {
537////
538////      public void run() {}
539////    };
540////
541////    protected final void schedule() {
542////      task.cancel();
543////      timer.purge();
544////      task = new CleanTask();
545////      timer.schedule(task, (int) (10d * speed.frameSize.toMillis()));
546////    }
547//
548//    private Cleaner(final Observable... observable) {
549//      Arrays.asList(observable).forEach(o -> o.addListener(__ -> clean()));
550//    }
551//  }
552
553  public class Controller {
554
555    protected final ObjectBinding<Config> config;
556    private boolean                       isLocked        = false;
557    private boolean                       isDragging      = false;
558    private AnimationSpeed                speedBeforeDrag;
559    protected final BooleanProperty       showVoronoi     = new SimpleBooleanProperty(false);
560//    protected final Cleaner               cleaner         = new Cleaner();
561    protected final Queue<Vertex>         addVertices     = new ConcurrentLinkedQueue<Vertex>();
562    protected final Queue<Edge>           addEdges        = new ConcurrentLinkedQueue<Edge>();
563    protected final Queue<Label>          addLabels       = new ConcurrentLinkedQueue<Label>();
564    protected final Queue<Tile>           addTiles        = new ConcurrentLinkedQueue<Tile>();
565    protected final Queue<Pair<T, T>>     removeVertices  = new ConcurrentLinkedQueue<Pair<T, T>>();
566    protected final Queue<Pair<T, T>>     removeEdges     = new ConcurrentLinkedQueue<Pair<T, T>>();
567    protected final Queue<Label>          removeLabels    = new ConcurrentLinkedQueue<Label>();
568    protected final Set<Vertex>           pendingVertices = Collections3.newConcurrentHashSet();
569    protected final Set<Edge>             pendingEdges    = Collections3.newConcurrentHashSet();
570    public Vertex                         polarBottom     = null;
571
572    protected Controller(final Observable... observable) {
573      super();
574      this.config = new ObjectBinding<Config>() {
575
576        {
577          Arrays.asList(observable).forEach(this::bind);
578          bind(front.widthProperty(), front.heightProperty(), transformation, showVoronoi, zoom, pan);
579        }
580
581        private Config value;
582
583        protected Config computeValue() {
584          if (!isDragging) {
585            final BoundingBox box = getContentBoundingBox();
586            final double w = front.widthProperty().get();
587            final double h = front.heightProperty().get();
588            final GuavaIsomorphism<Point3D, Point3D> iso;
589            final double f, x0, y0;
590            switch (transformation.get()) {
591            case GRAPH_3D:
592            case GRAPH_2D:
593              iso = GuavaIsomorphism.<Point3D> identity();
594              final double widthRatio = box.getWidth() < 0.001d ? Double.MAX_VALUE : w / box.getWidth();
595              final double heightRatio = box.getHeight() < 0.001d ? Double.MAX_VALUE : h / box.getHeight();
596              final double depthRatio = box.getDepth() < 0.001d ? Double.MAX_VALUE : w / box.getDepth();
597              final double paneToLayoutRatio = Math.min(widthRatio, Math.min(heightRatio, depthRatio));
598              f = (0.9d * (paneToLayoutRatio == Double.MAX_VALUE ? 1d : paneToLayoutRatio));
599              x0 = (-box.getMinX() * f + (w - box.getWidth() * f) / 2d);
600              y0 = (-box.getMinY() * f + (h - box.getHeight() * f) / 2d);
601              break;
602            case XY:
603              iso = new RotationXY(new Point3D(0, 0, 0), -Math.PI * 3d / 4d);
604              final Point3D top = iso.apply(new Point3D(box.getMaxX(), box.getMaxY(), 0));
605              final Point3D left = iso.apply(new Point3D(box.getMaxX(), box.getMinY(), 0));
606              final Point3D right = iso.apply(new Point3D(box.getMinX(), box.getMaxY(), 0));
607              final Point3D bot = iso.apply(new Point3D(box.getMinX(), box.getMinY(), 0));
608              final double width = Math.abs(right.getX() - left.getX());
609              final double height = Math.abs(top.getY() - bot.getY());
610              f = 0.9d * Math.min(w / width, h / height);
611              x0 = -left.getX() * f + (w - width * f) / 2d;
612              y0 = -top.getY() * f + (h - height * f) / 2d;
613              break;
614            case POLAR:
615              iso = new PolarTransformation(box.getMinX(), box.getWidth());
616              f = box.getHeight() < 0.001d ? 1d : 0.9d * (Math.min(w, h) / (2d * box.getHeight()));
617              x0 = w / 2d;
618              y0 = h / 2d;
619              break;
620            default:
621              iso = GuavaIsomorphism.<Point3D> identity();
622              f = 1d;
623              x0 = 0d;
624              y0 = 0d;
625              break;
626            }
627            value = new Config(iso, box, f * zoom.get(), x0 + pan.get().getX(), y0 + pan.get().getY(), w, h);
628          } else
629            invalidate();
630          return value;
631        }
632      };
633      config.addListener(new InvalidationListener() {
634
635        public final void invalidated(final Observable observable) {
636          refresh();
637        }
638      });
639      showVoronoi.addListener(new ChangeListener<Boolean>() {
640
641        public final void changed(
642            final ObservableValue<? extends Boolean> observable,
643            final Boolean oldValue,
644            final Boolean newValue) {
645          if (!newValue)
646            Platform2.runOnFXThread(back.getChildren()::clear);
647        }
648      });
649      refresh();
650//      new Timer().schedule(new TimerTask() {
651//
652//        @Override
653//        public void run() {
654//          refresh();
655//        }
656//      }, 0, 2l * (long) speed.frameSize.toMillis());
657    }
658
659    public boolean graphLock = false;
660
661    public final void drag() {
662      isDragging = true;
663      speedBeforeDrag = speed;
664      speed = AnimationSpeed.FASTESTEST;
665    }
666
667    public final void dragDone() {
668      isDragging = false;
669      speed = speedBeforeDrag;
670//      refresh();
671    }
672
673    public final boolean isDragging() {
674      return isDragging;
675    }
676
677    protected final void disposeVertex(final T element, final T to) {
678//      if (element == null || to == null)
679//        throw new NullPointerException();
680      synchronized (removeVertices) {
681        removeVertices.offer(Pair.of(element, to));
682      }
683    }
684
685    protected final void disposeEdge(final Pair<T, T> elements) {
686//      if (elements.first() == null || elements.second() == null)
687//        throw new NullPointerException();
688      synchronized (removeEdges) {
689        removeEdges.offer(elements);
690      }
691    }
692
693    protected final void clearBack() {
694      Platform2.runOnFXThread(back::clear);
695    }
696
697    private final void clean() {
698//      final Collection<Node> circles = Collections2.transform(vertices.values(), new Function<Vertex, Node>() {
699//
700//        public final Node apply(final Vertex v) {
701//          return v.node;
702//        }
703//      });
704//      final Collection<Node> lines = Collections2.transform(edges.values(), new Function<Edge, Node>() {
705//
706//        public final Node apply(final Edge e) {
707//          return e.line;
708//        }
709//      });
710//      final Collection<Node> labels =
711//          Collections3.union(Collections2.transform(vertices.values(), new Function<Vertex, Collection<Node>>() {
712//
713//            public final Collection<Node> apply(final Vertex v) {
714//              return Collections2
715//                  .transform(Collections3.union(v.lowerLabels, v.upperLabels), new Function<Label, Node>() {
716//
717//                public final Node apply(final Label l) {
718//                  return l.content;
719//                }
720//              });
721//            }
722//          }));
723////      Platform2.runOnFXThread(() -> front.getChildren().retainAll(Collections3.union(circles, lines, labels)));
724//      front.getChildren().retainAll(Collections3.union(circles, lines, labels));
725      final Set<Node> children = Collections3.newConcurrentHashSet();
726      synchronized (vertices) {
727        vertices.values().parallelStream().map(v -> v.node).forEach(children::add);
728        vertices.values().parallelStream().map(v -> v.lowerLabels.parallelStream()).reduce(Stream::concat).ifPresent(
729            s -> s.map(l -> l.content).forEach(children::add));
730        vertices.values().parallelStream().map(v -> v.upperLabels.parallelStream()).reduce(Stream::concat).ifPresent(
731            s -> s.map(l -> l.content).forEach(children::add));
732      }
733      synchronized (edges) {
734        edges.values().parallelStream().map(e -> e.line).forEach(children::add);
735      }
736      front.getChildren().retainAll(children);
737    }
738
739    public synchronized final void refresh() {
740      if (isLocked)
741        return;
742      final Timeline t = new Timeline();
743//      if (!isLocked) {
744      final Config c = config.get();
745      removeContent(t);
746      addContent(t);
747      refreshBottom(c, t);
748      refreshVertices(c, t);
749      if (showVoronoi.get())
750        refreshVoronoi(c, t);
751      else
752        refreshTiles(c, t);
753//      } else {
754//        t.getKeyFrames().add(new KeyFrame(speed.frameSize));
755//      }
756      t.setOnFinished(__ -> {
757        clean();
758//        refresh();
759      });
760      Platform2.runOnFXThread(t::play);
761    }
762
763    private final void addContent(final Timeline t) {
764      synchronized (addVertices) {
765        while (!addVertices.isEmpty()) {
766          final Vertex v = addVertices.poll();
767          final Transition fadeIn = fadeIn(v.node);
768          t.getKeyFrames().add(new KeyFrame(Duration.ONE, __ -> {
769            front.add(v.node);
770            fadeIn.play();
771          }));
772        }
773      }
774      synchronized (addEdges) {
775        while (!addEdges.isEmpty()) {
776          final Edge e = addEdges.poll();
777          e.initialize();
778          t.getKeyFrames().add(new KeyFrame(Duration.ONE, __ -> {
779            e.initialize();
780            front.add(e.line);
781            e.line.toBack();
782            fadeIn(e).play();
783          }));
784        }
785      }
786      synchronized (addLabels) {
787        while (!addLabels.isEmpty()) {
788          final Label l = addLabels.poll();
789          l.content.opacityProperty().set(0d);
790          final Transition fadeIn =
791              FadeTransitionBuilder.create().node(l.content).duration(speed.frameSize).toValue(0d).build();
792          final Transition fadeIn2 = fadeIn(l.content);
793          fadeIn.setOnFinished(__ -> {
794            l.content.toFront();
795            l.content.layoutXProperty().set(l.shift.getValue().getX() - l.content.widthProperty().get() / 2d);
796            l.content.layoutYProperty().set(
797                l.shift.getValue().getY() + l.index.doubleValue() * l.content.heightProperty().get());
798            l.content.setVisible(true);
799            l.isInitialized = true;
800            fadeIn2.play();
801          });
802          t.getKeyFrames().add(new KeyFrame(Duration.ZERO, __ -> {
803            l.content.setVisible(false);
804            front.add(l.content);
805            fadeIn.play();
806          }));
807        }
808      }
809    }
810
811    private final void removeContent(final Timeline t) {
812      synchronized (removeVertices) {
813        synchronized (removeEdges) {
814          removeEdges.removeAll(removeVertices);
815          for (Pair<T, T> e = removeEdges.poll(); e != null; e = removeEdges.poll()) {
816            final Edge edge;
817            synchronized (edges) {
818              edge = edges.remove(e);
819            }
820            // TODO
821//            if (edge != null) {
822            edge.isDisposing = true;
823            edge.line.opacityProperty().unbind();
824            synchronized (pendingEdges) {
825              pendingEdges.add(edge);
826            }
827//            } else {
828//              System.err.println(new NullPointerException());
829//            }
830          }
831        }
832        for (Pair<T, T> v = removeVertices.poll(); v != null; v = removeVertices.poll()) {
833          final Vertex vertex;
834          synchronized (vertices) {
835            vertex = vertices.remove(v.first());
836          }
837          synchronized (vertex.lowerLabels) {
838            for (LowerLabel l : vertex.lowerLabels)
839              l.dispose();
840          }
841          synchronized (vertex.upperLabels) {
842            for (UpperLabel u : vertex.upperLabels)
843              u.dispose();
844          }
845          if (v.second() == null)
846            synchronized (pendingVertices) {
847              pendingVertices.add(vertex);
848            }
849          else {
850            final Vertex to;
851            synchronized (vertices) {
852              to = vertices.get(v.second());
853            }
854            final Edge edge;
855            synchronized (edges) {
856              edge = edges.remove(v);
857            }
858//            if (edge != null) {
859            edge.isDisposing = true;
860            edge.line.opacityProperty().unbind();
861            synchronized (to.pendingVertices) {
862              to.pendingVertices.add(Pair.of(vertex, edge));
863            }
864//            }
865          }
866        }
867      }
868      synchronized (pendingEdges) {
869        for (Edge edge : pendingEdges)
870          t.getKeyFrames().add(new KeyFrame(speed.frameSize, dispose(edge), fadeOut(edge.line)));
871      }
872      synchronized (pendingVertices) {
873        for (Vertex vertex : pendingVertices)
874          t.getKeyFrames().add(new KeyFrame(speed.frameSize, dispose(vertex), fadeOut(vertex.node)));
875      }
876      synchronized (removeLabels) {
877        for (Label label : removeLabels)
878          t.getKeyFrames().add(new KeyFrame(speed.frameSize, dispose(label), fadeOut(label.content)));
879      }
880    }
881
882    private final void refreshBottom(final Config c, final Timeline t) {
883      switch (transformation.get()) {
884      case GRAPH_3D:
885      case GRAPH_2D:
886      case XY:
887        if (polarBottom != null)
888          resetPolarBottom(c, t);
889        break;
890      case POLAR:
891        if (polarBottom == null)
892          initPolarBottom(c, t);
893        if (polarBottom != null)
894          drawPolarBottom(c, t);
895        break;
896      }
897    }
898
899//    private final Predicate<Label> isInitialized = new Predicate<Label>()
900//                                                   {
901//                                                     public final boolean apply(final Label label)
902//                                                     {
903//                                                       return label.isInitialized;
904//                                                     };
905//                                                   };
906    private final void refreshVertices(final Config c, final Timeline t) {
907      synchronized (vertices) {
908        for (Vertex v : vertices.values()) {
909          if (!v.equals(polarBottom)) {
910            final Point3D p = c.toPane(v.position.getValue());
911            t.getKeyFrames().add(
912                new KeyFrame(speed.frameSize, translateX(v.node, p), translateY(v.node, p), fadeZ(v.node, p)));
913            synchronized (v.lowerLabels) {
914              for (LowerLabel l : v.lowerLabels)
915                if (l.isInitialized)
916                  t.getKeyFrames().add(
917                      new KeyFrame(
918                          speed.frameSize,
919                          translateX(l.content, p),
920                          translateY(l.content, p),
921                          fadeZ(l.content, p),
922                          layoutX(l),
923                          layoutY(l)));
924                else
925                  t.getKeyFrames().add(
926                      new KeyFrame(
927                          speed.frameSize,
928                          translateX(l.content, p),
929                          translateY(l.content, p),
930                          fadeZ(l.content, p)));
931            }
932            synchronized (v.upperLabels) {
933              for (UpperLabel u : v.upperLabels)
934                if (u.isInitialized)
935                  t.getKeyFrames().add(
936                      new KeyFrame(
937                          speed.frameSize,
938                          translateX(u.content, p),
939                          translateY(u.content, p),
940                          fadeZ(u.content, p),
941                          layoutX(u),
942                          layoutY(u)));
943                else
944                  t.getKeyFrames().add(
945                      new KeyFrame(
946                          speed.frameSize,
947                          translateX(u.content, p),
948                          translateY(u.content, p),
949                          fadeZ(u.content, p)));
950            }
951            synchronized (v.pendingVertices) {
952              for (Pair<Vertex, Edge> pv : v.pendingVertices)
953                t.getKeyFrames().add(
954                    new KeyFrame(
955                        speed.frameSize,
956                        dispose(v, pv),
957                        translateX(pv.first().node, p),
958                        translateY(pv.first().node, p),
959                        fadeZ(pv.first().node, p),
960                        fadeOut(pv.first().node),
961                        fadeOut(pv.second().line)));
962            }
963          }
964        }
965      }
966    }
967
968    private final void refreshTiles(final Config c, final Timeline t) {
969      synchronized (addTiles) {
970        while (!addTiles.isEmpty()) {
971          final Tile tile = addTiles.poll();
972          back.add(tile);
973          t.getKeyFrames().add(
974              new KeyFrame(
975                  speed.frameSize,
976                  new KeyValue(tile.translateXProperty(), c.x0 + c.f * tile.rect.getMinX()),
977                  new KeyValue(tile.translateYProperty(), c.y0 + c.f * tile.rect.getMinY()),
978                  new KeyValue(tile.widthProperty(), c.f * tile.rect.getWidth()),
979                  new KeyValue(tile.heightProperty(), c.f * tile.rect.getHeight())));
980        }
981      }
982      // for (Tile tile : Collections3.getTypedElements(back.getChildren(), Tile.class))
983      // t.getKeyFrames().add(
984      // new KeyFrame(
985      // speed.frameSize,
986      // new KeyValue(tile.translateXProperty(), c.x0 + c.f * tile.rect.getMinX()),
987      // new KeyValue(tile.translateYProperty(), c.y0 + c.f * tile.rect.getMinY()),
988      // new KeyValue(tile.widthProperty(), c.f * tile.rect.getWidth()),
989      // new KeyValue(tile.heightProperty(), c.f * tile.rect.getHeight())));
990    }
991
992    private final void refreshVoronoi(final Config c, final Timeline t) {
993      t.getKeyFrames().add(new KeyFrame(Duration.ONE, __ -> new TransitionTimer(speed.frameSize, ___ -> {
994        back.clear();
995        synchronized (vertices) {
996          for (GraphEdge e : VoronoiGenerator.generate(
997              Collections2.transform(
998                  Collections2.filter(vertices.values(), Predicates.not(Predicates.equalTo(polarBottom))),
999                  new Function<Vertex, Point3D>() {
1000
1001                    public final Point3D apply(final Vertex v) {
1002                      return new Point3D(v.node.translateXProperty().get(), v.node.translateYProperty().get(), 0);
1003                    }
1004                  }),
1005              0,
1006              c.w,
1007              0,
1008              c.h)) {
1009            final Line l = new Line(e.x1, e.y1, e.x2, e.y2);
1010            l.setStroke(Color.RED);
1011            back.add(l);
1012          }
1013        }
1014      }).play()));
1015    }
1016
1017    protected final KeyValue translateX(final Node n, final Point3D p) {
1018      return new KeyValue(n.translateXProperty(), p.getX());
1019    }
1020
1021    protected final KeyValue translateY(final Node n, final Point3D p) {
1022      return new KeyValue(n.translateYProperty(), p.getY());
1023    }
1024
1025    protected final KeyValue fadeZ(final Node n, final Point3D p) {
1026      return new KeyValue(n.opacityProperty(), Math.pow(1d - 0.9d * p.getZ(), 2.5d));
1027    }
1028
1029    private final KeyValue layoutX(final Label label) {
1030      return new KeyValue(
1031          label.content.layoutXProperty(),
1032          label.shift.getValue().getX() - label.content.widthProperty().get() / 2d);
1033    }
1034
1035    private final KeyValue layoutY(final Label label) {
1036      return new KeyValue(
1037          label.content.layoutYProperty(),
1038          label.shift.getValue().getY() + label.index.doubleValue() * label.content.heightProperty().get());
1039    }
1040
1041    protected final Transition fadeIn(final Node n) {
1042      n.opacityProperty().set(0d);
1043      return FadeTransitionBuilder.create().node(n).duration(speed.frameSize).toValue(1d).build();
1044    }
1045
1046    protected final Transition fadeIn(final Edge e) {
1047      e.line.opacityProperty().set(0d);
1048      return FadeTransitionBuilder
1049          .create()
1050          .node(e.line)
1051          .duration(speed.frameSize)
1052          .toValue(e.opacity.get())
1053          .onFinished(__ -> {
1054            if (!e.isDisposing)
1055              e.bindOpacity();
1056          })
1057          .build();
1058    }
1059
1060    protected final KeyValue fadeOut(final Node n) {
1061      return new KeyValue(n.opacityProperty(), 0d);
1062    }
1063
1064    private final EventHandler<ActionEvent> dispose(final Vertex vertex) {
1065      return __ -> {
1066        synchronized (pendingVertices) {
1067          pendingVertices.remove(vertex);
1068        }
1069        vertex.dispose();
1070        front.remove(vertex.node);
1071        synchronized (vertex.pendingVertices) {
1072          if (!vertex.pendingVertices.isEmpty())
1073            synchronized (pendingVertices) {
1074              synchronized (pendingEdges) {
1075                for (Pair<Vertex, Edge> p : vertex.pendingVertices) {
1076                  pendingVertices.add(p.first());
1077                  pendingEdges.add(p.second());
1078                }
1079              }
1080            }
1081        }
1082      };
1083    }
1084
1085    private final EventHandler<ActionEvent> dispose(final Edge edge) {
1086      return __ -> {
1087        synchronized (pendingEdges) {
1088          pendingEdges.remove(edge);
1089        }
1090        edge.dispose();
1091        front.remove(edge.line);
1092      };
1093    }
1094
1095    private final EventHandler<ActionEvent> dispose(final Vertex v, final Pair<Vertex, Edge> pv) {
1096      return __ -> {
1097        synchronized (v.pendingVertices) {
1098          v.pendingVertices.remove(pv);
1099        }
1100        pv.second().dispose();
1101        pv.first().dispose();
1102        front.remove(pv.second().line);
1103        front.remove(pv.first().node);
1104        synchronized (pv.first().pendingVertices) {
1105          if (!pv.first().pendingVertices.isEmpty())
1106            synchronized (pendingVertices) {
1107              synchronized (pendingEdges) {
1108                for (Pair<Vertex, Edge> p : pv.first().pendingVertices) {
1109                  pendingVertices.add(p.first());
1110                  pendingEdges.add(p.second());
1111                }
1112              }
1113            }
1114        }
1115      };
1116    }
1117
1118    private final EventHandler<ActionEvent> dispose(final Label label) {
1119      return __ -> {
1120        synchronized (removeLabels) {
1121          removeLabels.remove(label);
1122        }
1123        front.remove(label.content);
1124      };
1125    }
1126  }
1127
1128  public final Controller                             controller;
1129  protected final Map<T, Vertex>                      vertices       = new ConcurrentHashMap<T, Vertex>();
1130  protected final Map<Pair<T, T>, Edge>               edges          = new ConcurrentHashMap<Pair<T, T>, Edge>();
1131  protected final SynchronizedPane                    front          = new SynchronizedPane();
1132  protected final SynchronizedPane                    back           = new SynchronizedPane();
1133  protected AnimationSpeed                            speed          = AnimationSpeed.DEFAULT;
1134  protected final ObjectProperty<GraphTransformation> transformation =
1135      new SimpleObjectProperty<GraphTransformation>(GraphTransformation.GRAPH_3D);
1136  protected final DoubleProperty                      zoom           = new SimpleDoubleProperty(1d);
1137  protected final IntegerProperty                     textSize       = new SimpleIntegerProperty(12);
1138  protected final ObjectProperty<Point2D>             pan            =
1139      new SimpleObjectProperty<Point2D>(new Point2D(0d, 0d));
1140
1141  protected Graph(final Observable... observable) {
1142    super();
1143    this.controller = new Controller(observable);
1144//    new Cleaner(observable);
1145    this.setCenter(StackPaneBuilder.create().children(back, front).build());
1146    front.addEventHandler(ScrollEvent.SCROLL, new EventHandler<ScrollEvent>() {
1147
1148      public final void handle(final ScrollEvent event) {
1149        if (event.getDeltaY() > 0)
1150          zoom.set(zoom.get() * 1.1d);
1151        else if (event.getDeltaY() < 0)
1152          zoom.set(zoom.get() / 1.1d);
1153      }
1154    });
1155    final EventHandler<MouseEvent> panHandler = new EventHandler<MouseEvent>() {
1156
1157      private double startX, startY;
1158      private double panX, panY;
1159
1160      public final void handle(final MouseEvent event) {
1161        if (!event.getButton().equals(MouseButton.SECONDARY))
1162          return;
1163        if (event.getEventType().equals(MouseEvent.MOUSE_PRESSED)) {
1164          startX = event.getSceneX();
1165          startY = event.getSceneY();
1166          panX = pan.get().getX();
1167          panY = pan.get().getY();
1168        } else if (event.getEventType().equals(MouseEvent.MOUSE_RELEASED)) {
1169          // do nothing
1170        } else if (event.getEventType().equals(MouseEvent.MOUSE_DRAGGED)) {
1171          double deltaX = event.getSceneX() - startX;
1172          double deltaY = event.getSceneY() - startY;
1173          pan.set(new Point2D(panX + deltaX, panY + deltaY));
1174        }
1175      }
1176    };
1177    front.addEventHandler(MouseEvent.MOUSE_PRESSED, panHandler);
1178    front.addEventHandler(MouseEvent.MOUSE_DRAGGED, panHandler);
1179    front.addEventHandler(MouseEvent.MOUSE_RELEASED, panHandler);
1180  }
1181
1182  protected final Collection<Edge> lowerEdges(final T element) {
1183    synchronized (edges) {
1184      return
1185//          new HashSet<Edge>(
1186      Maps.filterKeys(edges, new Predicate<Pair<T, T>>() {
1187
1188        public final boolean apply(final Pair<T, T> p) {
1189          return p.second().equals(element);
1190        }
1191      }).values()
1192//          )
1193      ;
1194    }
1195  }
1196
1197  protected final Collection<Edge> upperEdges(final T element) {
1198    synchronized (edges) {
1199      return
1200//          new HashSet<Edge>(
1201      Maps.filterKeys(edges, new Predicate<Pair<T, T>>() {
1202
1203        public final boolean apply(final Pair<T, T> p) {
1204          return p.first().equals(element);
1205        }
1206      }).values()
1207//              )
1208      ;
1209    }
1210  }
1211
1212  protected abstract BoundingBox getContentBoundingBox();
1213
1214  protected abstract void initPolarBottom(final Config c, final Timeline t);
1215
1216  protected abstract void drawPolarBottom(final Config c, final Timeline t);
1217
1218  protected abstract void resetPolarBottom(final Config c, final Timeline t);
1219
1220  public abstract void
1221      highlight(boolean fadeComplement, @SuppressWarnings("unchecked") Iterable<HighlightRequest>... requests);
1222
1223  public void removeContent() {
1224    front.getChildren().clear();
1225    back.getChildren().clear();
1226    edges.clear();
1227    vertices.clear();
1228//    edges.keySet().forEach(controller::disposeEdge);
1229//    vertices.keySet().forEach(v -> controller.disposeVertex(v, null));
1230//    edges.clear();
1231//    vertices.clear();
1232  }
1233
1234}