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.Collections;
027import java.util.HashSet;
028import java.util.Map;
029import java.util.Map.Entry;
030import java.util.Set;
031import java.util.concurrent.ConcurrentHashMap;
032import java.util.function.Consumer;
033
034//import org.semanticweb.owlapi.model.OWLClassExpression;
035
036import com.google.common.base.Function;
037import com.google.common.base.Predicate;
038import com.google.common.collect.Collections2;
039import com.google.common.collect.Iterables;
040import com.google.common.collect.Maps;
041import com.google.common.collect.Sets;
042
043import conexp.fx.core.collections.Pair;
044import conexp.fx.core.collections.relation.RelationEvent;
045import conexp.fx.core.collections.relation.RelationEventHandler;
046import conexp.fx.core.context.Concept;
047import conexp.fx.core.layout.AdditiveConceptLayout;
048import conexp.fx.core.layout.ConceptLayout;
049import conexp.fx.core.layout.ConceptMovement;
050import conexp.fx.core.layout.LayoutEvolution;
051import conexp.fx.core.math.Points;
052import conexp.fx.core.util.Constants;
053//import conexp.fx.core.util.OWLUtil;
054import conexp.fx.gui.ConExpFX;
055import conexp.fx.gui.dataset.FCADataset;
056import conexp.fx.gui.graph.option.AnimationSpeed;
057import conexp.fx.gui.graph.option.AttributeLabelText;
058import conexp.fx.gui.graph.option.EdgeHighlight;
059import conexp.fx.gui.graph.option.EdgeStroke;
060import conexp.fx.gui.graph.option.GraphTransformation;
061import conexp.fx.gui.graph.option.ObjectLabelText;
062import conexp.fx.gui.graph.option.VertexHighlight;
063import conexp.fx.gui.graph.option.VertexRadius;
064import conexp.fx.gui.util.FXControls;
065import conexp.fx.gui.util.NumberPropertyTransition;
066import conexp.fx.gui.util.SearchBox;
067import de.tudresden.inf.tcs.fcalib.Implication;
068import javafx.animation.FillTransition;
069import javafx.animation.KeyFrame;
070import javafx.animation.KeyValue;
071import javafx.animation.StrokeTransition;
072import javafx.animation.Timeline;
073import javafx.beans.binding.DoubleBinding;
074import javafx.beans.binding.ObjectBinding;
075import javafx.beans.binding.StringBinding;
076import javafx.beans.property.SimpleDoubleProperty;
077import javafx.beans.property.SimpleObjectProperty;
078import javafx.beans.property.SimpleStringProperty;
079import javafx.beans.value.ChangeListener;
080import javafx.beans.value.ObservableValue;
081import javafx.collections.ListChangeListener;
082import javafx.collections.MapChangeListener;
083import javafx.collections.SetChangeListener;
084import javafx.event.ActionEvent;
085import javafx.event.EventHandler;
086import javafx.geometry.BoundingBox;
087import javafx.geometry.Insets;
088import javafx.geometry.Point2D;
089import javafx.geometry.Point3D;
090import javafx.scene.control.Button;
091import javafx.scene.control.CheckBox;
092import javafx.scene.control.ChoiceBox;
093import javafx.scene.control.Slider;
094import javafx.scene.control.SliderBuilder;
095import javafx.scene.control.Toggle;
096import javafx.scene.control.ToggleButton;
097import javafx.scene.control.ToggleGroup;
098import javafx.scene.control.ToolBar;
099import javafx.scene.image.Image;
100import javafx.scene.image.ImageViewBuilder;
101import javafx.scene.input.MouseButton;
102import javafx.scene.input.MouseEvent;
103import javafx.scene.input.ScrollEvent;
104import javafx.scene.layout.HBox;
105import javafx.scene.layout.Priority;
106import javafx.scene.layout.StackPane;
107import javafx.scene.layout.VBox;
108import javafx.scene.paint.Color;
109import javafx.scene.paint.CycleMethod;
110import javafx.scene.paint.RadialGradient;
111import javafx.scene.paint.Stop;
112import javafx.scene.shape.Circle;
113import javafx.scene.shape.ClosePath;
114import javafx.scene.shape.LineTo;
115import javafx.scene.shape.MoveTo;
116import javafx.scene.shape.Path;
117import javafx.scene.shape.RectangleBuilder;
118import javafx.scene.shape.StrokeType;
119import javafx.scene.text.Text;
120import javafx.util.Duration;
121import jfxtras.scene.control.ListSpinner;
122
123@SuppressWarnings("deprecation")
124public final class ConceptGraph<G, M> extends Graph<Concept<G, M>, Circle> {
125
126  public static final Color COLOR_CONCEPT      = Color.valueOf("#FFE206");
127  public static final Color COLOR_INTERVAL     = Color.valueOf("#8100BC");
128  public static final Color COLOR_LOWER        = Color.valueOf("#1EA266");
129  public static final Color COLOR_UPPER        = Color.valueOf("#DD3558");
130  public static final Color COLOR_UNCOMPARABLE = Color.valueOf("#EEEEEE");
131
132  protected final class ConceptVertex extends Vertex {
133
134    private final class DragHandler implements EventHandler<MouseEvent> {
135
136      private DragHandler() {}
137
138      private double          startX;
139      private double          startY;
140      private ConceptMovement movement;
141
142      public final void handle(final MouseEvent event) {
143        event.consume();
144        if (event.getEventType().equals(MouseEvent.MOUSE_PRESSED))
145          dragStart(event);
146        else if (event.getEventType().equals(MouseEvent.MOUSE_RELEASED))
147          dragDone(event);
148        else
149          drag(event);
150      }
151
152      private final void dragStart(final MouseEvent event) {
153        controller.drag();
154        startX = event.getX();
155        startY = event.getY();
156        movement = event.getButton().equals(MouseButton.PRIMARY) ? ConceptMovement.INTENT_CHAIN_SEEDS
157            : ConceptMovement.LABEL_CHAIN_SEEDS;
158        showQualityChart();
159      }
160
161      private final void drag(final MouseEvent event) {
162        layout.move(element, movement, getTarget(event).subtract(position.getValue()));
163      }
164
165      private final void dragDone(final MouseEvent event) {
166        disposeQualityChart();
167        controller.dragDone();
168        layout.invalidate();
169      }
170
171      private final void showQualityChart() {
172        if (!controlBox.conflictChart.selectedProperty().get())
173          return;
174        qualityEvolution = fca.qualityChart(element, movement);
175        qualityChartListener = new SetChangeListener<LayoutEvolution<G, M>.Value>() {
176
177          public void onChanged(final SetChangeListener.Change<? extends LayoutEvolution<G, M>.Value> change) {
178            if (change.wasAdded())
179              new Tile(change.getElementAdded(), node.translateXProperty().get(), node.translateYProperty().get());
180          }
181        };
182        qualityEvolution.values.addListener(qualityChartListener);
183      }
184
185      private final void disposeQualityChart() {
186        if (qualityEvolution == null)
187          return;
188        qualityEvolution.values.removeListener(qualityChartListener);
189        qualityEvolution = null;
190        controller.clearBack();
191      }
192
193      private final Point3D getTarget(final MouseEvent event) {
194        final Point2D p;
195        if (event.getSource().equals(node))
196          p = node.localToParent(event.getX(), event.getY());
197        else {
198          final StackPane content = (StackPane) event.getSource();
199          final Point2D nodeP = node.localToParent(0, 0);
200          final Point2D contentP = content.localToParent(startX, startY);
201          final double dx = nodeP.getX() - contentP.getX();
202          final double dy = nodeP.getY() - contentP.getY();
203          p = content.localToParent(event.getX(), event.getY()).add(dx, dy);
204        }
205        return controller.config.get().toContent(p);
206      }
207    }
208
209    private final class HighlightHandler implements EventHandler<MouseEvent> {
210
211      private HighlightHandler() {}
212
213      public final synchronized void handle(final MouseEvent event) {
214//        System.out.println(layout.getPosition(element));
215        event.consume();
216        if (!toolBar.highlight.isSelected() || controller.isDragging())
217          return;
218        if (event.getEventType().equals(MouseEvent.MOUSE_ENTERED)) {
219          highlight();
220          if (controlBox.attributeLabelText.getSelectionModel().getSelectedItem().equals(AttributeLabelText.NONE)) {
221            final Consumer<Concept<G, M>> f = c -> {
222              synchronized (layout.lattice.attributeConcepts) {
223                for (Entry<M, Concept<G, M>> e : layout.lattice.attributeConcepts.entrySet())
224                  if (e.getValue().equals(c)) {
225                    new AttributeLabel(e.getKey(), c);
226                    break;
227                  }
228              }
229            };
230            final VertexHighlight vertexHighlightOption =
231                controlBox.vertexHighlight.getSelectionModel().selectedItemProperty().get();
232            switch (vertexHighlightOption) {
233            case CONCEPT:
234              f.accept(element);
235              break;
236            case UPPER_NEIGHBORS:
237              f.accept(element);
238              layout.lattice.upperNeighbors(element).forEach(f::accept);
239              break;
240            case LOWER_NEIGHBORS:
241              f.accept(element);
242              layout.lattice.lowerNeighbors(element).forEach(f::accept);
243              break;
244            case NEIGHBORS:
245              f.accept(element);
246              layout.lattice.upperNeighbors(element).forEach(f::accept);
247              layout.lattice.lowerNeighbors(element).forEach(f::accept);
248              break;
249            case FILTER:
250              f.accept(element);
251              layout.lattice.filter(element).forEach(f::accept);
252              break;
253            case IDEAL:
254              f.accept(element);
255              layout.lattice.ideal(element).forEach(f::accept);
256              break;
257            case FILTER_IDEAL:
258              f.accept(element);
259              layout.lattice.filter(element).forEach(f::accept);
260              layout.lattice.ideal(element).forEach(f::accept);
261              break;
262            }
263          }
264          if (controlBox.objectLabelText.getSelectionModel().getSelectedItem().equals(ObjectLabelText.NONE)) {
265            final Consumer<Concept<G, M>> f = c -> {
266              synchronized (layout.lattice.objectConcepts) {
267                for (Entry<G, Concept<G, M>> e : layout.lattice.objectConcepts.entrySet())
268                  if (e.getValue().equals(c)) {
269                    new ObjectLabel(e.getKey(), c);
270                    break;
271                  }
272              }
273            };
274            final VertexHighlight vertexHighlightOption =
275                controlBox.vertexHighlight.getSelectionModel().selectedItemProperty().get();
276            switch (vertexHighlightOption) {
277            case CONCEPT:
278              f.accept(element);
279              break;
280            case UPPER_NEIGHBORS:
281              f.accept(element);
282              layout.lattice.upperNeighbors(element).forEach(f::accept);
283              break;
284            case LOWER_NEIGHBORS:
285              f.accept(element);
286              layout.lattice.lowerNeighbors(element).forEach(f::accept);
287              break;
288            case NEIGHBORS:
289              f.accept(element);
290              layout.lattice.upperNeighbors(element).forEach(f::accept);
291              layout.lattice.lowerNeighbors(element).forEach(f::accept);
292              break;
293            case FILTER:
294              f.accept(element);
295              layout.lattice.filter(element).forEach(f::accept);
296              break;
297            case IDEAL:
298              f.accept(element);
299              layout.lattice.ideal(element).forEach(f::accept);
300              break;
301            case FILTER_IDEAL:
302              f.accept(element);
303              layout.lattice.filter(element).forEach(f::accept);
304              layout.lattice.ideal(element).forEach(f::accept);
305              break;
306            }
307          }
308          layout.invalidate();
309        } else if (event.getEventType().equals(MouseEvent.MOUSE_EXITED)) {
310          fca.contextWidget.dehighlight();
311          ConceptGraph.this.highlight(false, highlightRequests.dehighlight());
312          if (controlBox.attributeLabelText.getSelectionModel().getSelectedItem().equals(AttributeLabelText.NONE)) {
313            synchronized (attributeLabels) {
314              for (AttributeLabel u : attributeLabels.values())
315                u.dispose();
316            }
317          }
318          if (controlBox.objectLabelText.getSelectionModel().getSelectedItem().equals(ObjectLabelText.NONE)) {
319            synchronized (objectLabels) {
320              for (ObjectLabel l : objectLabels.values())
321                l.dispose();
322            }
323          }
324          layout.invalidate();
325        }
326      }
327    }
328
329    private final EventHandler<MouseEvent>                 dragHandler      = new DragHandler();
330    private final EventHandler<MouseEvent>                 highlightHandler = new HighlightHandler();
331    private SetChangeListener<LayoutEvolution<G, M>.Value> qualityChartListener;
332
333    @SuppressWarnings("incomplete-switch")
334    private ConceptVertex(final Concept<G, M> concept, final Double layoutX, final Double layoutY) {
335      super(concept, new Circle(), layout.getOrAddPosition(concept), layoutX, layoutY);
336      init();
337      node.setFill(COLOR_DEFAULT);
338      node.setStrokeType(StrokeType.OUTSIDE);
339      node.setStroke(Color.BLACK);
340      node.setStrokeWidth(1d);
341      node.addEventHandler(MouseEvent.MOUSE_PRESSED, dragHandler);
342      node.addEventHandler(MouseEvent.MOUSE_DRAGGED, dragHandler);
343      node.addEventHandler(MouseEvent.MOUSE_RELEASED, dragHandler);
344      node.addEventHandler(MouseEvent.MOUSE_ENTERED, highlightHandler);
345      node.addEventHandler(MouseEvent.MOUSE_EXITED, highlightHandler);
346      upperLabels.addListener(new ListChangeListener<UpperLabel>() {
347
348        public void onChanged(ListChangeListener.Change<? extends UpperLabel> change) {
349          while (change.next())
350            if (change.wasRemoved())
351              for (UpperLabel l : change.getRemoved()) {
352                l.content.removeEventHandler(MouseEvent.MOUSE_PRESSED, dragHandler);
353                l.content.removeEventHandler(MouseEvent.MOUSE_DRAGGED, dragHandler);
354                l.content.removeEventHandler(MouseEvent.MOUSE_RELEASED, dragHandler);
355                l.content.removeEventHandler(MouseEvent.MOUSE_ENTERED, highlightHandler);
356                l.content.removeEventHandler(MouseEvent.MOUSE_EXITED, highlightHandler);
357                l.shift.unbind();
358              }
359            else if (change.wasAdded())
360              for (UpperLabel l : change.getAddedSubList()) {
361                l.content.addEventHandler(MouseEvent.MOUSE_PRESSED, dragHandler);
362                l.content.addEventHandler(MouseEvent.MOUSE_DRAGGED, dragHandler);
363                l.content.addEventHandler(MouseEvent.MOUSE_RELEASED, dragHandler);
364                l.content.addEventHandler(MouseEvent.MOUSE_ENTERED, highlightHandler);
365                l.content.addEventHandler(MouseEvent.MOUSE_EXITED, highlightHandler);
366                l.shift.bind(new ObjectBinding<Point2D>() {
367
368                  {
369                    bind(node.radiusProperty());
370                  }
371
372                  protected Point2D computeValue() {
373                    return new Point2D(0, -node.radiusProperty().get() - 1d);
374                  }
375                });
376              }
377        }
378      });
379      lowerLabels.addListener(new ListChangeListener<LowerLabel>() {
380
381        public void onChanged(ListChangeListener.Change<? extends LowerLabel> change) {
382          while (change.next())
383            if (change.wasRemoved())
384              for (LowerLabel l : change.getRemoved()) {
385                l.content.removeEventHandler(MouseEvent.MOUSE_PRESSED, dragHandler);
386                l.content.removeEventHandler(MouseEvent.MOUSE_DRAGGED, dragHandler);
387                l.content.removeEventHandler(MouseEvent.MOUSE_RELEASED, dragHandler);
388                l.content.removeEventHandler(MouseEvent.MOUSE_ENTERED, highlightHandler);
389                l.content.removeEventHandler(MouseEvent.MOUSE_EXITED, highlightHandler);
390                l.shift.unbind();
391              }
392            else if (change.wasAdded())
393              for (LowerLabel l : change.getAddedSubList()) {
394                l.content.addEventHandler(MouseEvent.MOUSE_PRESSED, dragHandler);
395                l.content.addEventHandler(MouseEvent.MOUSE_DRAGGED, dragHandler);
396                l.content.addEventHandler(MouseEvent.MOUSE_RELEASED, dragHandler);
397                l.content.addEventHandler(MouseEvent.MOUSE_ENTERED, highlightHandler);
398                l.content.addEventHandler(MouseEvent.MOUSE_EXITED, highlightHandler);
399                l.shift.bind(new ObjectBinding<Point2D>() {
400
401                  {
402                    bind(node.radiusProperty());
403                  }
404
405                  protected Point2D computeValue() {
406                    return new Point2D(0, node.radiusProperty().get() + 1d);
407                  }
408                });
409              }
410        }
411      });
412      switch (controlBox.objectLabelText.getSelectionModel().selectedItemProperty().get()) {
413      case EXTENT_SIZE:
414      case EXTENT_PERCENTAGE:
415      case OBJECT_LABELS_SIZE:
416      case OBJECT_LABELS_PERCENTAGE:
417        new ObjectLabel(concept);
418      }
419      switch (controlBox.attributeLabelText.getSelectionModel().selectedItemProperty().get()) {
420      case INTENT_SIZE:
421      case INTENT_PERCENTAGE:
422      case ATTRIBUTE_LABELS_SIZE:
423      case ATTRIBUTE_LABELS_PERCENTAGE:
424        new AttributeLabel(concept);
425      }
426    }
427
428    protected final void init() {
429      node.radiusProperty().bind(new DoubleBinding() {
430
431        {
432          bind(element.intent(), controlBox.vertexRadius.getSelectionModel().selectedItemProperty());
433        }
434
435        public final double computeValue() {
436          return controlBox.vertexRadius.getSelectionModel().selectedItemProperty().get().get(
437              fca.context,
438              fca.lattice,
439              element);
440        }
441      });
442    }
443
444    @SuppressWarnings("incomplete-switch")
445    private final void highlight() {
446//      node.toFront();
447//      for (AttributeLabel l : upperLabels)
448//        l.content.toFront();
449//      for (ObjectLabel l : lowerLabels)
450//        l.content.toFront();
451      final VertexHighlight vertexHighlightOption =
452          controlBox.vertexHighlight.getSelectionModel().selectedItemProperty().get();
453      if (vertexHighlightOption.equals(VertexHighlight.NONE))
454        return;
455      fca.contextWidget.highlight(element);
456      switch (vertexHighlightOption) {
457      case CONCEPT:
458        ConceptGraph.this.highlight(true, highlightRequests.concept(element));
459        break;
460      case UPPER_NEIGHBORS:
461        ConceptGraph.this
462            .highlight(true, highlightRequests.concept(element), highlightRequests.upperNeighbors(element));
463        break;
464      case LOWER_NEIGHBORS:
465        ConceptGraph.this
466            .highlight(true, highlightRequests.concept(element), highlightRequests.lowerNeighbors(element));
467        break;
468      case NEIGHBORS:
469        ConceptGraph.this.highlight(
470            true,
471            highlightRequests.concept(element),
472            highlightRequests.upperNeighbors(element),
473            highlightRequests.lowerNeighbors(element));
474        break;
475      case FILTER:
476        ConceptGraph.this.highlight(true, highlightRequests.concept(element), highlightRequests.strictFilter(element));
477        break;
478      case IDEAL:
479        ConceptGraph.this.highlight(true, highlightRequests.concept(element), highlightRequests.strictIdeal(element));
480        break;
481      case FILTER_IDEAL:
482        ConceptGraph.this.highlight(
483            true,
484            highlightRequests.concept(element),
485            highlightRequests.strictFilter(element),
486            highlightRequests.strictIdeal(element));
487        break;
488      }
489    }
490
491    protected final void dispose() {
492      node.radiusProperty().unbind();
493    }
494  }
495
496  protected final class ConceptEdge extends Edge {
497
498    private ConceptEdge(final Pair<Concept<G, M>, Concept<G, M>> concepts) {
499      super(concepts);
500      line.strokeWidthProperty().bind(new DoubleBinding() {
501
502        {
503          bind(
504              concepts.first().intent(),
505              concepts.second().intent(),
506              controlBox.edgeStroke.getSelectionModel().selectedItemProperty());
507        }
508
509        protected final double computeValue() {
510          return controlBox.edgeStroke.getSelectionModel().selectedItemProperty().get().get(
511              fca.context,
512              fca.lattice,
513              concepts);
514        }
515      });
516    }
517  }
518
519  protected final class ObjectLabel extends LowerLabel {
520
521    private ObjectLabel(final G object, final Concept<G, M> concept) {
522      super(new ObjectBinding<Concept<G, M>>() {
523
524        private final G       g = object;
525        private Concept<G, M> c = concept;
526
527        {
528          layout.lattice.objectConcepts.addListener(new MapChangeListener<G, Concept<G, M>>() {
529
530            public final void onChanged(final MapChangeListener.Change<? extends G, ? extends Concept<G, M>> change) {
531              if (change.wasRemoved() && change.wasAdded() && change.getKey().equals(g)) {
532                c = change.getValueAdded();
533                invalidate();
534              }
535            }
536          });
537        }
538
539        protected final Concept<G, M> computeValue() {
540          return c;
541        }
542      }, new SimpleStringProperty(object.toString()), true);
543      text.styleProperty().bind(controlBox.textSizeBinding);
544      synchronized (objectLabels) {
545        final ObjectLabel old = objectLabels.put(object, this);
546        if (old != null)
547          old.dispose();
548      }
549    }
550
551    private ObjectLabel(final Concept<G, M> concept) {
552      super(new SimpleObjectProperty<Concept<G, M>>(concept), new StringBinding() {
553
554        {
555          bind(concept.intent(), controlBox.objectLabelText.getSelectionModel().selectedItemProperty());
556        }
557
558        protected final String computeValue() {
559          return controlBox.objectLabelText.getSelectionModel().selectedItemProperty().get().get(
560              fca.context,
561              fca.lattice,
562              concept);
563        }
564      }, true);
565      text.styleProperty().bind(controlBox.textSizeBinding);
566    }
567
568    protected final void dispose() {
569      synchronized (objectLabels) {
570        objectLabels.values().remove(this);
571      }
572      super.dispose();
573    }
574  }
575
576  protected final class AttributeLabel extends UpperLabel {
577
578    private AttributeLabel(final M attribute, final Concept<G, M> concept) {
579      super(new ObjectBinding<Concept<G, M>>() {
580
581        private final M       m = attribute;
582        private Concept<G, M> c = concept;
583
584        {
585          layout.lattice.attributeConcepts.addListener(new MapChangeListener<M, Concept<G, M>>() {
586
587            public final void onChanged(final MapChangeListener.Change<? extends M, ? extends Concept<G, M>> change) {
588              if (change.wasRemoved() && change.wasAdded() && change.getKey().equals(m)) {
589                c = change.getValueAdded();
590                invalidate();
591              }
592            }
593          });
594        }
595
596        protected final Concept<G, M> computeValue() {
597          return c;
598        }
599      },
600          // TODO: the type check for OWLClassExpression is currently a workaround, and will be removed in the future.
601          new SimpleStringProperty(
602//              attribute instanceof OWLClassExpression ? OWLUtil.toString((OWLClassExpression) attribute) : 
603                attribute.toString()),
604          true);
605      text.styleProperty().bind(controlBox.textSizeBinding);
606      synchronized (attributeLabels) {
607        final AttributeLabel old = attributeLabels.put(attribute, this);
608        if (old != null)
609          old.dispose();
610      }
611    }
612
613    private AttributeLabel(final Concept<G, M> concept) {
614      super(new SimpleObjectProperty<Concept<G, M>>(concept), new StringBinding() {
615
616        {
617          bind(concept.intent(), controlBox.attributeLabelText.getSelectionModel().selectedItemProperty());
618        }
619
620        protected final String computeValue() {
621          return controlBox.attributeLabelText.getSelectionModel().selectedItemProperty().get().get(
622              fca.context,
623              fca.lattice,
624              concept);
625        }
626      }, true);
627      text.styleProperty().bind(controlBox.textSizeBinding);
628    }
629
630    protected final void dispose() {
631      synchronized (attributeLabels) {
632        attributeLabels.values().remove(this);
633      }
634      super.dispose();
635    }
636  }
637
638  private final class CFXControlBox {
639
640    private final VBox                                  content            = new VBox();
641    private final ChoiceBox<AdditiveConceptLayout.Type> layoutType         =
642        FXControls.newChoiceBox(AdditiveConceptLayout.Type.HYBRID, AdditiveConceptLayout.Type.values());
643    private final ChoiceBox<AnimationSpeed>             animationSpeed     =
644        FXControls.newChoiceBox(AnimationSpeed.DEFAULT, AnimationSpeed.values());
645    private final ListSpinner<Integer>                  labelTextSize      =
646        FXControls.newListSpinner(12, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 24);
647    private final StringBinding                         textSizeBinding    = new StringBinding() {
648
649                                                                             {
650                                                                               bind(labelTextSize.valueProperty());
651                                                                             }
652
653                                                                             protected final String computeValue() {
654                                                                               return "-fx-font-size: "
655                                                                                   + labelTextSize.valueProperty().get()
656                                                                                   + ";";
657                                                                             }
658                                                                           };
659    private final ChoiceBox<ObjectLabelText>            objectLabelText    =
660        FXControls.newChoiceBox(ObjectLabelText.OBJECT_LABELS, ObjectLabelText.values());
661    private final ChoiceBox<AttributeLabelText>         attributeLabelText =
662        FXControls.newChoiceBox(AttributeLabelText.ATTRIBUTE_LABELS, AttributeLabelText.values());
663    private final ChoiceBox<VertexRadius>               vertexRadius       =
664        FXControls.newChoiceBox(VertexRadius.NORMAL, VertexRadius.values());
665    private final ChoiceBox<EdgeStroke>                 edgeStroke         =
666        FXControls.newChoiceBox(EdgeStroke.SMALL, EdgeStroke.values());
667    private final ChoiceBox<VertexHighlight>            vertexHighlight    =
668        FXControls.newChoiceBox(VertexHighlight.FILTER_IDEAL, VertexHighlight.values());
669    private final Slider                                generations        = SliderBuilder
670        .create()
671        .min(0)
672        .max(16)
673        .value(Constants.GENERATIONS)
674        .majorTickUnit(1)
675        .minorTickCount(0)
676        .snapToTicks(true)
677        .build();
678    private final Slider                                population         = SliderBuilder
679        .create()
680        .min(1)
681        .max(64)
682        .value(Constants.POPULATION)
683        .majorTickUnit(1)
684        .minorTickCount(0)
685        .snapToTicks(true)
686        .build();
687    private final CheckBox                              conflictChart      = new CheckBox("Conflict Chart");
688    private final CheckBox                              voronoiChart       = new CheckBox("Voronoi Chart");
689    private final CheckBox                              hideBottom         = new CheckBox("Hide Bottom Concept");
690    private final CheckBox                              hideTop            = new CheckBox("Hide Top Concept");
691
692    private CFXControlBox() {
693      createContent();
694      createListeners();
695    }
696
697    private final void createContent() {
698      final double inset = 4d;
699      content.setPadding(new Insets(inset));
700      content.setSpacing(1d);
701      content.getChildren().add(RectangleBuilder.create().height(inset).build());
702      content.getChildren().add(new Text("Layout Type:"));
703      content.getChildren().add(layoutType);
704      content.getChildren().add(RectangleBuilder.create().height(inset).build());
705      content.getChildren().add(new Text("Animation Speed:"));
706      content.getChildren().add(animationSpeed);
707      content.getChildren().add(RectangleBuilder.create().height(inset).build());
708      content.getChildren().add(new Text("Label Text Size:"));
709      content.getChildren().add(labelTextSize);
710      content.getChildren().add(RectangleBuilder.create().height(inset).build());
711      content.getChildren().add(new Text("Object Label Text:"));
712      content.getChildren().add(objectLabelText);
713      content.getChildren().add(RectangleBuilder.create().height(inset).build());
714      content.getChildren().add(new Text("Attribute Label Text:"));
715      content.getChildren().add(attributeLabelText);
716      content.getChildren().add(RectangleBuilder.create().height(inset).build());
717      content.getChildren().add(new Text("Concept Vertex Radius:"));
718      content.getChildren().add(vertexRadius);
719      content.getChildren().add(RectangleBuilder.create().height(inset).build());
720      content.getChildren().add(new Text("Concept Edge Stroke:"));
721      content.getChildren().add(edgeStroke);
722      content.getChildren().add(RectangleBuilder.create().height(inset).build());
723      content.getChildren().add(new Text("Concept Vertex Highlight:"));
724      content.getChildren().add(vertexHighlight);
725      content.getChildren().add(RectangleBuilder.create().height(4d * inset).build());
726      content.getChildren().add(FXControls.newText(new StringBinding() {
727
728        {
729          bind(generations.valueProperty());
730        }
731
732        protected String computeValue() {
733          return "Generations: " + (int) generations.valueProperty().get();
734        }
735      }));
736      content.getChildren().add(generations);
737      content.getChildren().add(RectangleBuilder.create().height(inset).build());
738      content.getChildren().add(FXControls.newText(new StringBinding() {
739
740        {
741          bind(population.valueProperty());
742        }
743
744        protected String computeValue() {
745          return "Population: " + (int) population.valueProperty().get();
746        }
747      }));
748      content.getChildren().add(population);
749      content.getChildren().add(RectangleBuilder.create().height(4d * inset).build());
750      content.getChildren().add(conflictChart);
751      content.getChildren().add(RectangleBuilder.create().height(inset).build());
752      content.getChildren().add(voronoiChart);
753      content.getChildren().add(RectangleBuilder.create().height(inset).build());
754      content.getChildren().add(hideBottom);
755      content.getChildren().add(RectangleBuilder.create().height(inset).build());
756      content.getChildren().add(hideTop);
757    }
758
759    private final void createListeners() {
760      front.addEventHandler(MouseEvent.MOUSE_MOVED, new EventHandler<MouseEvent>() {
761
762        public final void handle(final MouseEvent event) {
763          final boolean isShown = getRight() != null;
764          final boolean shouldShow = event.getX() > front.widthProperty().get() - 50;
765          if (isShown == shouldShow)
766            return;
767          if (!shouldShow)
768            setRight(null);
769          else
770            setRight(content);
771        }
772      });
773      ConceptGraph.this.setOnMouseExited(e -> setRight(null));
774      // TODO
775//      layout.bindType(layoutType.valueProperty());
776      animationSpeed.valueProperty().addListener(new ChangeListener<AnimationSpeed>() {
777
778        public final void changed(
779            final ObservableValue<? extends AnimationSpeed> observable,
780            final AnimationSpeed oldValue,
781            final AnimationSpeed newValue) {
782          speed = newValue;
783        }
784      });
785      objectLabelText.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<ObjectLabelText>() {
786
787        public final void changed(
788            final ObservableValue<? extends ObjectLabelText> observable,
789            final ObjectLabelText oldValue,
790            final ObjectLabelText newValue) {
791          final boolean wasNoneOrObjectLabels =
792              oldValue == ObjectLabelText.NONE || oldValue == ObjectLabelText.OBJECT_LABELS;
793          switch (newValue) {
794          case NONE:
795            synchronized (objectLabels) {
796              for (ObjectLabel l : objectLabels.values())
797                l.dispose();
798            }
799            synchronized (vertices) {
800              for (Vertex cv : vertices.values())
801                synchronized (cv.lowerLabels) {
802                  for (LowerLabel l : cv.lowerLabels)
803                    l.dispose();
804                }
805            }
806            break;
807          case OBJECT_LABELS:
808            synchronized (vertices) {
809              for (Vertex cv : vertices.values())
810                synchronized (cv.lowerLabels) {
811                  for (LowerLabel l : cv.lowerLabels)
812                    l.dispose();
813                }
814            }
815            synchronized (layout.lattice.objectConcepts) {
816              for (Entry<G, Concept<G, M>> e : layout.lattice.objectConcepts.entrySet())
817                new ObjectLabel(e.getKey(), e.getValue());
818            }
819            break;
820          case EXTENT_SIZE:
821          case EXTENT_PERCENTAGE:
822          case OBJECT_LABELS_SIZE:
823          case OBJECT_LABELS_PERCENTAGE:
824            if (wasNoneOrObjectLabels) {
825              synchronized (objectLabels) {
826                for (ObjectLabel l : objectLabels.values())
827                  l.dispose();
828              }
829              for (Concept<G, M> c : layout.lattice.rowHeads())
830                new ObjectLabel(c);
831            }
832            break;
833          }
834          layout.invalidate();
835        }
836      });
837      attributeLabelText.getSelectionModel().selectedItemProperty().addListener(
838          new ChangeListener<AttributeLabelText>() {
839
840            public final void changed(
841                final ObservableValue<? extends AttributeLabelText> observable,
842                final AttributeLabelText oldValue,
843                final AttributeLabelText newValue) {
844              final boolean wasNoneOrAttributeLabels =
845                  oldValue == AttributeLabelText.NONE || oldValue == AttributeLabelText.ATTRIBUTE_LABELS;
846              switch (newValue) {
847              case NONE:
848                synchronized (attributeLabels) {
849                  for (AttributeLabel u : attributeLabels.values())
850                    u.dispose();
851                }
852                synchronized (vertices) {
853                  for (Vertex cv : vertices.values())
854                    synchronized (cv.upperLabels) {
855                      for (UpperLabel u : cv.upperLabels)
856                        u.dispose();
857                    }
858                }
859                break;
860              case ATTRIBUTE_LABELS:
861                synchronized (vertices) {
862                  for (Vertex cv : vertices.values())
863                    synchronized (cv.upperLabels) {
864                      for (UpperLabel u : cv.upperLabels)
865                        u.dispose();
866                    }
867                }
868                synchronized (layout.lattice.attributeConcepts) {
869                  for (Entry<M, Concept<G, M>> e : layout.lattice.attributeConcepts.entrySet())
870                    new AttributeLabel(e.getKey(), e.getValue());
871                }
872                break;
873              case SEED_LABELS:
874                synchronized (vertices) {
875                  for (Vertex cv : vertices.values())
876                    synchronized (cv.upperLabels) {
877                      for (UpperLabel u : cv.upperLabels)
878                        u.dispose();
879                    }
880                }
881                synchronized (layout.lattice.attributeConcepts) {
882                  for (Entry<M, Concept<G, M>> e : layout.lattice.attributeConcepts.entrySet())
883                    // TODO
884                    // if (layout.seedsM.containsKey(e.getKey()))
885                    new AttributeLabel(e.getKey(), e.getValue());
886                }
887                break;
888              case INTENT_SIZE:
889              case INTENT_PERCENTAGE:
890              case ATTRIBUTE_LABELS_SIZE:
891              case ATTRIBUTE_LABELS_PERCENTAGE:
892                if (wasNoneOrAttributeLabels) {
893                  synchronized (attributeLabels) {
894                    for (AttributeLabel u : attributeLabels.values())
895                      u.dispose();
896                  }
897                  for (Concept<G, M> c : layout.lattice.rowHeads())
898                    new AttributeLabel(c);
899                }
900                break;
901              }
902              layout.invalidate();
903            }
904          });
905      controller.showVoronoi.bind(voronoiChart.selectedProperty());
906      hideBottom.selectedProperty().addListener(new ChangeListener<Boolean>() {
907
908        public void
909            changed(final ObservableValue<? extends Boolean> observable, final Boolean wasHidden, Boolean hide) {
910          if (hide == wasHidden)
911            return;
912          layout.invalidate();
913          if (hide) {
914//                vertices.get(layout.bottomConcept.get()).node.opacityProperty().bind(zero);
915//            vertices.get(fca.context.selection.bottomConcept()).node.opacityProperty().set(0.05d);
916//            .bind(new SimpleDoubleProperty(0.05d));
917            for (Edge e : upperEdges(fca.context.selection.bottomConcept())) {
918              e.line.opacityProperty().unbind();
919              e.line.opacityProperty().bind(new SimpleDoubleProperty(0.05d));
920            }
921          } else {
922//                vertices.get(layout.bottomConcept.get()).node.opacityProperty().unbind();
923//                vertices.get(layout.bottomConcept.get()).node.opacityProperty().set(1d);
924//            vertices.get(fca.context.selection.bottomConcept()).node.opacityProperty().set(1d);
925            for (Edge e : upperEdges(fca.context.selection.bottomConcept())) {
926              e.line.opacityProperty().unbind();
927              e.line.opacityProperty().bind(e.opacity);
928            }
929          }
930        }
931      });
932      hideTop.selectedProperty().addListener(new ChangeListener<Boolean>() {
933
934        public void
935            changed(final ObservableValue<? extends Boolean> observable, final Boolean wasHidden, Boolean hide) {
936          if (hide == wasHidden)
937            return;
938          layout.invalidate();
939          if (hide) {
940//                vertices.get(layout.bottomConcept.get()).node.opacityProperty().bind(zero);
941            for (Edge e : lowerEdges(fca.context.selection.topConcept())) {
942              e.line.opacityProperty().unbind();
943              e.line.opacityProperty().bind(new SimpleDoubleProperty(0.05d));
944            }
945          } else {
946//                vertices.get(layout.bottomConcept.get()).node.opacityProperty().unbind();
947//                vertices.get(layout.bottomConcept.get()).node.opacityProperty().set(1d);
948            for (Edge e : lowerEdges(fca.context.selection.topConcept())) {
949              e.line.opacityProperty().unbind();
950              e.line.opacityProperty().bind(e.opacity);
951            }
952          }
953        }
954      });
955    }
956  }
957
958  private final class CFXToolBar {
959
960    private final Button       relayout  = new Button();
961    private final Button       adjust    = new Button();
962    private final ToggleButton highlight = new ToggleButton();
963    private final ToggleButton labels    = new ToggleButton();
964
965    private CFXToolBar() {
966      final ToolBar toolBar = new ToolBar();
967//      final Button exportButton = ButtonBuilder.create().graphic(
968//          ImageViewBuilder.create().image(
969//              new Image(ConExpFX.class.getResourceAsStream("image/16x16/briefcase.png"))).build()).onAction(
970//          new EventHandler<ActionEvent>() {
971//
972//            @Override
973//            public final void handle(final ActionEvent event) {
974//              fca.exportTeX();
975//            }
976//          }).minHeight(
977//          24).build();
978      toolBar.getItems().addAll(
979          createTransformationBox(),
980          createLayoutBox(),
981          createShowBox(),
982          // exportButton,
983          createSpace(),
984          createSearchBox());
985      relayout.setGraphic(
986          ImageViewBuilder
987              .create()
988              .image(new Image(ConExpFX.class.getResourceAsStream("image/16x16/refresh.png")))
989              .build());
990      adjust.setGraphic(
991          ImageViewBuilder
992              .create()
993              .image(new Image(ConExpFX.class.getResourceAsStream("image/16x16/process.png")))
994              .build());
995      highlight.setGraphic(
996          ImageViewBuilder
997              .create()
998              .image(new Image(ConExpFX.class.getResourceAsStream("image/16x16/flag.png")))
999              .build());
1000      labels.setGraphic(
1001          ImageViewBuilder
1002              .create()
1003              .image(new Image(ConExpFX.class.getResourceAsStream("image/16x16/comments.png")))
1004              .build());
1005      setTop(toolBar);
1006    }
1007
1008    private final HBox createTransformationBox() {
1009      final ToggleGroup transformationToggleGroup = new ToggleGroup();
1010      final ToggleButton transformation2DButton = new ToggleButton();
1011      final ToggleButton transformation3DButton = new ToggleButton();// 2\u00bdD
1012      final ToggleButton transformationXYButton = new ToggleButton();
1013      final ToggleButton transformationPolarButton = new ToggleButton();
1014//      final ToggleButton transformationCircularButton = new ToggleButton();
1015      transformation2DButton.setGraphic(
1016          ImageViewBuilder
1017              .create()
1018              .image(new Image(ConExpFX.class.getResourceAsStream("image/16x16/wired.png")))
1019              .build());
1020      transformation3DButton.setGraphic(
1021          ImageViewBuilder
1022              .create()
1023              .image(new Image(ConExpFX.class.getResourceAsStream("image/16x16/wired.png")))
1024              .build());
1025      transformationXYButton.setGraphic(
1026          ImageViewBuilder
1027              .create()
1028              .image(new Image(ConExpFX.class.getResourceAsStream("image/16x16/chart.png")))
1029              .build());
1030      transformationPolarButton.setGraphic(
1031          ImageViewBuilder
1032              .create()
1033              .image(new Image(ConExpFX.class.getResourceAsStream("image/16x16/target.png")))
1034              .build());
1035//      transformationCircularButton.setGraphic(ImageViewBuilder
1036//          .create()
1037//          .image(new Image(ConExpFX.class.getResourceAsStream("image/16x16/chart_pie.png")))
1038//          .build());
1039//      transformation2DButton.setOnAction(new EventHandler<ActionEvent>() {
1040//
1041//        public final void handle(final ActionEvent event) {
1042//          transformation2DButton.toFront();
1043//        }
1044//      });
1045//      transformation3DButton.setOnAction(new EventHandler<ActionEvent>() {
1046//
1047//        public final void handle(final ActionEvent event) {
1048//          transformation3DButton.toFront();
1049//        }
1050//      });
1051//      transformationXYButton.setOnAction(new EventHandler<ActionEvent>() {
1052//
1053//        public final void handle(final ActionEvent event) {
1054//          transformationXYButton.toFront();
1055//        }
1056//      });
1057//      transformationPolarButton.setOnAction(new EventHandler<ActionEvent>() {
1058//
1059//        public final void handle(final ActionEvent event) {
1060//          transformationPolarButton.toFront();
1061//        }
1062//      });
1063//      transformationCircularButton.setOnAction(new EventHandler<ActionEvent>() {
1064//
1065//        public final void handle(final ActionEvent event) {
1066//          transformationCircularButton.toFront();
1067//        }
1068//      });
1069      transformation2DButton.setUserData(GraphTransformation.GRAPH_2D);
1070      transformation3DButton.setUserData(GraphTransformation.GRAPH_3D);
1071      transformationXYButton.setUserData(GraphTransformation.XY);
1072      transformationPolarButton.setUserData(GraphTransformation.POLAR);
1073//      transformationCircularButton.setUserData(GraphTransformation.CIRCULAR);
1074      transformation2DButton.setToggleGroup(transformationToggleGroup);
1075      transformation3DButton.setToggleGroup(transformationToggleGroup);
1076      transformationXYButton.setToggleGroup(transformationToggleGroup);
1077      transformationPolarButton.setToggleGroup(transformationToggleGroup);
1078//      transformationCircularButton.setToggleGroup(transformationToggleGroup);
1079      transformationToggleGroup.selectToggle(transformation2DButton);
1080      transformation.bind(new ObjectBinding<GraphTransformation>() {
1081
1082        private GraphTransformation lastValue = GraphTransformation.GRAPH_3D;
1083
1084        {
1085          bind(transformationToggleGroup.selectedToggleProperty());
1086        }
1087
1088        protected final GraphTransformation computeValue() {
1089          final Toggle toggle = transformationToggleGroup.selectedToggleProperty().get();
1090          if (toggle != null) {
1091            lastValue = (GraphTransformation) ((ToggleButton) toggle).getUserData();
1092            switch (lastValue) {
1093            case GRAPH_2D:
1094              layout.deleteZ();
1095            case GRAPH_3D:
1096              controlBox.conflictChart.setDisable(false);
1097              // if (conceptEdgeStrokeChoiceBox.getValue() == ConceptEdgeStrokeOption.NONE)
1098              // conceptEdgeStrokeChoiceBox.setValue(ConceptEdgeStrokeOption.NORMAL);
1099              break;
1100            case XY:
1101            case POLAR:
1102              controlBox.conflictChart.selectedProperty().set(false);
1103              controlBox.conflictChart.setDisable(true);
1104              // conceptEdgeStrokeChoiceBox.setValue(ConceptEdgeStrokeOption.NONE);
1105              // conceptVertexRadiusChoiceBox.setValue(ConceptVertexRadiusOption.BY_INTENT);
1106              break;
1107            case CIRCULAR:
1108//              new CircularGraph<G, M>().show(fca.lattice);
1109            }
1110            return lastValue;
1111          } else {
1112            transformationToggleGroup.selectToggle(
1113                lastValue == GraphTransformation.GRAPH_3D ? transformation3DButton
1114                    : lastValue == GraphTransformation.XY ? transformationXYButton : transformationPolarButton);
1115            return lastValue;
1116          }
1117        }
1118      });
1119//      transformation.addListener((observable, oldTransformation, newTransformation) -> {
1120//        Platform.runLater(() -> fca.relayout(1, 4));
1121//      });
1122      final HBox transformationBox = new HBox();
1123      transformationBox.setPadding(new Insets(0d));
1124      transformation2DButton.setStyle("-fx-background-radius: 5 0 0 5, 5 0 0 5, 4 0 0 4, 3 0 0 3;");
1125      transformation3DButton.setStyle("-fx-background-radius: 0, 0, 0, 0;");
1126      transformationXYButton.setStyle("-fx-background-radius: 0, 0, 0, 0;");
1127      transformationPolarButton
1128          // .setStyle("-fx-background-radius: 0, 0, 0, 0;");
1129          // transformationCircularButton
1130          .setStyle("-fx-background-radius: 0 5 5 0, 0 5 5 0, 0 4 4 0, 0 3 3 0;");
1131      transformation2DButton.setMinHeight(24);
1132      transformation3DButton.setMinHeight(24);
1133      transformationXYButton.setMinHeight(24);
1134      transformationPolarButton.setMinHeight(24);
1135//      transformationCircularButton.setMinHeight(24);
1136      transformationBox
1137          .getChildren()
1138          .addAll(transformation2DButton, transformation3DButton, transformationXYButton, transformationPolarButton
1139//          ,transformationCircularButton
1140      );
1141      return transformationBox;
1142    }
1143
1144    private final HBox createLayoutBox() {
1145      relayout.setOnAction(new EventHandler<ActionEvent>() {
1146
1147        public final void handle(final ActionEvent event) {
1148//          relayout.toFront();
1149          fca.relayout((int) controlBox.generations.getValue(), (int) controlBox.population.getValue());
1150        }
1151      });
1152      adjust.setOnAction(new EventHandler<ActionEvent>() {
1153
1154        public final void handle(final ActionEvent event) {
1155//          adjust.toFront();
1156          fca.refine((int) controlBox.generations.getValue());
1157        }
1158      });
1159      relayout.setMinHeight(24);
1160      adjust.setMinHeight(24);
1161      relayout.setStyle("-fx-background-radius: 5 0 0 5, 5 0 0 5, 4 0 0 4, 3 0 0 3;");
1162      adjust.setStyle("-fx-background-radius: 0 5 5 0, 0 5 5 0, 0 4 4 0, 0 3 3 0;");
1163      final HBox layoutBox = new HBox();
1164      layoutBox.setPadding(new Insets(0d));
1165      layoutBox.getChildren().addAll(relayout, adjust);
1166      return layoutBox;
1167    }
1168
1169    private final HBox createShowBox() {
1170      highlight.setSelected(false);
1171      highlight.setMinHeight(24);
1172      labels.setSelected(true);
1173      labels.selectedProperty().addListener(new ChangeListener<Boolean>() {
1174
1175        public final void changed(
1176            final ObservableValue<? extends Boolean> observable,
1177            final Boolean wasSelected,
1178            final Boolean isSelected) {
1179          if (isSelected) {
1180            controlBox.objectLabelText.getSelectionModel().select(ObjectLabelText.OBJECT_LABELS);
1181            controlBox.attributeLabelText.getSelectionModel().select(AttributeLabelText.ATTRIBUTE_LABELS);
1182          } else {
1183            controlBox.objectLabelText.getSelectionModel().select(ObjectLabelText.NONE);
1184            controlBox.attributeLabelText.getSelectionModel().select(AttributeLabelText.NONE);
1185          }
1186        }
1187      });
1188      labels.setMinHeight(24);
1189      highlight.setStyle("-fx-background-radius: 5 0 0 5, 5 0 0 5, 4 0 0 4, 3 0 0 3;");
1190      labels.setStyle("-fx-background-radius: 0 5 5 0, 0 5 5 0, 0 4 4 0, 0 3 3 0;");
1191      final HBox showBox = new HBox();
1192      showBox.setPadding(new Insets(0d));
1193      showBox.getChildren().addAll(highlight, labels);
1194      return showBox;
1195    }
1196
1197    private final HBox createSpace() {
1198      final HBox space = new HBox();
1199      HBox.setHgrow(space, Priority.ALWAYS);
1200      return space;
1201    }
1202
1203    private final SearchBox createSearchBox() {
1204      final SearchBox searchBox = new SearchBox();
1205      searchBox.textBox.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() {
1206
1207        public final void handle(final MouseEvent event) {
1208          ConceptGraph.this.requestFocus();
1209        }
1210      });
1211      searchBox.textBox.textProperty().addListener(new ChangeListener<String>() {
1212
1213        public final void
1214            changed(final ObservableValue<? extends String> observable, final String oldValue, final String newValue) {
1215          if (newValue.equals(""))
1216            highlight(false, highlightRequests.dehighlight());
1217          else {
1218            final Set<Concept<G, M>> concepts = new HashSet<Concept<G, M>>();
1219            for (G g : fca.context.selection.rowHeads())
1220              if (g.toString().toLowerCase().contains(newValue.toLowerCase()))
1221                concepts.add(fca.lattice.objectConcepts.get(g));
1222            for (M m : fca.context.selection.colHeads())
1223              if (m.toString().toLowerCase().contains(newValue.toLowerCase()))
1224                concepts.add(fca.lattice.attributeConcepts.get(m));
1225            highlight(
1226                true,
1227                Iterables
1228                    .concat(Iterables.transform(concepts, new Function<Concept<G, M>, Iterable<HighlightRequest>>() {
1229
1230                      public final Iterable<HighlightRequest> apply(final Concept<G, M> concept) {
1231                        return highlightRequests.concept(concept);
1232                      }
1233                    })));
1234          }
1235        }
1236      });
1237      return searchBox;
1238    }
1239  }
1240
1241  private final FCADataset<G, M>       fca;
1242  private final ConceptLayout<G, M, ?> layout;
1243  private final CFXControlBox          controlBox;
1244  private final CFXToolBar             toolBar;
1245  private final Map<G, ObjectLabel>    objectLabels      = new ConcurrentHashMap<G, ObjectLabel>();
1246  private final Map<M, AttributeLabel> attributeLabels   = new ConcurrentHashMap<M, AttributeLabel>();
1247  private LayoutEvolution<G, M>        qualityEvolution  = null;
1248  public final HighlightRequests       highlightRequests = new HighlightRequests();
1249  private boolean                      dontHighlight     = false;
1250//  private TransitionTimer              highlightTimer    =
1251//      new TransitionTimer(Duration.seconds(1), new EventHandler<ActionEvent>() {
1252//
1253//        public final void handle(final ActionEvent event) {
1254//          dontHighlight = false;
1255//        }
1256//      });
1257  public boolean                       highlightLock     = false;
1258
1259  public ConceptGraph(final FCADataset<G, M> fcaInstance, final ConceptLayout<G, M, ?> layout) {
1260    super(layout);
1261    this.fca = fcaInstance;
1262    this.layout = layout;
1263    this.controlBox = new CFXControlBox();
1264    this.toolBar = new CFXToolBar();
1265    this.initLayoutListeners();
1266    this.initMouseListeners();
1267    this.textSize.bind(this.controlBox.labelTextSize.valueProperty());
1268  }
1269
1270  public ConceptGraph(final FCADataset<G, M> fcaInstance) {
1271    this(fcaInstance, fcaInstance.layout);
1272  }
1273
1274  private final void initLayoutListeners() {
1275    initVertexListeners();
1276    initEdgeListeners();
1277    initLabelListeners();
1278    layout.lattice.addEventHandler(new RelationEventHandler<Concept<G, M>, Concept<G, M>>() {
1279
1280      public final void handle(final RelationEvent<Concept<G, M>, Concept<G, M>> event) {
1281//        Platform.runLater(new Runnable() {
1282//
1283//          public final void run() {
1284        controller.graphLock = true;
1285        front.getChildren().clear();
1286        // edges.clear();
1287        // vertices.clear();
1288        // addEdges.clear();
1289        // addVertices.clear();
1290        // deleteVertices.clear();
1291        // deleteEdges.clear();
1292        // pendingVertices.clear();
1293        // pendingEdges.clear();
1294        controller.polarBottom = null;
1295        controller.graphLock = false;
1296//          }
1297//        });
1298      }
1299    }, RelationEvent.ROWS_CLEARED);
1300  }
1301
1302  private final void initVertexListeners() {
1303    layout.lattice.addEventHandler(new RelationEventHandler<Concept<G, M>, Concept<G, M>>() {
1304
1305      public final void handle(final RelationEvent<Concept<G, M>, Concept<G, M>> event) {
1306//        synchronized (layout.generators) {
1307        for (final Concept<G, M> concept : event.getRows())
1308          if (layout.generators.containsKey(concept)) {
1309            synchronized (layout.generators) {
1310              final Concept<G, M> generator = layout.generators.remove(concept);
1311              final Circle gContent = vertices.get(generator).node;
1312              new ConceptVertex(concept, gContent.translateXProperty().get(), gContent.translateYProperty().get());
1313            }
1314          } else
1315            new ConceptVertex(concept, null, null);
1316//        }
1317      }
1318    }, RelationEvent.ROWS_ADDED);
1319    layout.lattice.addEventHandler(new RelationEventHandler<Concept<G, M>, Concept<G, M>>() {
1320
1321      public final void handle(final RelationEvent<Concept<G, M>, Concept<G, M>> event) {
1322//        synchronized (layout.generators) {
1323        for (Concept<G, M> concept : event.getRows())
1324          if (layout.generators.containsKey(concept)) {
1325            synchronized (layout.generators) {
1326              final Concept<G, M> generator = layout.generators.remove(concept);
1327              controller.disposeVertex(concept, generator);
1328            }
1329          } else
1330            controller.disposeVertex(concept, null);
1331//        }
1332      }
1333    }, RelationEvent.ROWS_REMOVED);
1334  }
1335
1336  private final void initEdgeListeners() {
1337    layout.lattice.addEventHandler(new RelationEventHandler<Concept<G, M>, Concept<G, M>>() {
1338
1339      public final void handle(final RelationEvent<Concept<G, M>, Concept<G, M>> event) {
1340        for (final Pair<Concept<G, M>, Concept<G, M>> concepts : event.getEntries())
1341          new ConceptEdge(concepts);
1342      }
1343    }, RelationEvent.ENTRIES_ADDED);
1344    layout.lattice.addEventHandler(new RelationEventHandler<Concept<G, M>, Concept<G, M>>() {
1345
1346      public final void handle(final RelationEvent<Concept<G, M>, Concept<G, M>> event) {
1347        for (final Pair<Concept<G, M>, Concept<G, M>> concepts : event.getEntries())
1348          controller.disposeEdge(concepts);
1349      }
1350    }, RelationEvent.ENTRIES_REMOVED);
1351  }
1352
1353  private final void initLabelListeners() {
1354    layout.lattice.objectConcepts.addListener(new MapChangeListener<G, Concept<G, M>>() {
1355
1356      public final void onChanged(final MapChangeListener.Change<? extends G, ? extends Concept<G, M>> change) {
1357        if (!controlBox.objectLabelText.getSelectionModel().getSelectedItem().equals(ObjectLabelText.OBJECT_LABELS))
1358          return;
1359        if (change.wasRemoved() && change.wasAdded())
1360          return;
1361        if (change.wasAdded())
1362          new ObjectLabel(change.getKey(), change.getValueAdded());
1363        else if (change.wasRemoved())
1364          objectLabels.get(change.getKey()).dispose();
1365      }
1366    });
1367    layout.lattice.attributeConcepts.addListener(new MapChangeListener<M, Concept<G, M>>() {
1368
1369      public final void onChanged(final MapChangeListener.Change<? extends M, ? extends Concept<G, M>> change) {
1370        if (!controlBox.attributeLabelText.getSelectionModel().getSelectedItem().equals(
1371            AttributeLabelText.ATTRIBUTE_LABELS))
1372          return;
1373        if (change.wasRemoved() && change.wasAdded())
1374          return;
1375        if (change.wasAdded())
1376          new AttributeLabel(change.getKey(), change.getValueAdded());
1377        else if (change.wasRemoved())
1378          attributeLabels.get(change.getKey()).dispose();
1379      }
1380    });
1381  }
1382
1383  private final void initMouseListeners() {
1384    front.addEventHandler(ScrollEvent.SCROLL, new EventHandler<ScrollEvent>() {
1385
1386      public final void handle(final ScrollEvent event) {
1387        if (transformation.get().equals(GraphTransformation.GRAPH_3D))
1388          layout.rotate(event.getDeltaX() / (80d * Math.PI));
1389//          layout.rotate((event.getDeltaX() == 0 ? event.getDeltaY() : event.getDeltaX()) / (80d * Math.PI));
1390      }
1391    });
1392    final EventHandler<MouseEvent> lariatSelectionHandler = new EventHandler<MouseEvent>() {
1393
1394      private Path path = null;
1395
1396      public final void handle(final MouseEvent event) {
1397        if (!event.getButton().equals(MouseButton.PRIMARY))
1398          return;
1399        if (event.getEventType().equals(MouseEvent.MOUSE_PRESSED)) {
1400          path = new Path(new MoveTo(event.getX(), event.getY()));
1401          path.getStrokeDashArray().addAll(2d, 4d);
1402          front.getChildren().add(path);
1403        } else if (event.getEventType().equals(MouseEvent.MOUSE_RELEASED)) {
1404          if (path != null && path.getElements().size() > 1) {
1405            path.getElements().add(new ClosePath());
1406            path.setFill(Color.RED);
1407            final Set<Concept<G, M>> concepts =
1408                new HashSet<Concept<G, M>>(Maps.filterValues(vertices, new Predicate<Vertex>() {
1409
1410                  public final boolean apply(final Vertex v) {
1411                    return path.contains(v.node.translateXProperty().get(), v.node.translateYProperty().get());
1412                  }
1413                }).keySet());
1414            final Concept<G, M> supremum = layout.lattice.supremum(concepts);
1415            final Concept<G, M> infimum = layout.lattice.infimum(concepts);
1416            highlight(
1417                true,
1418                Iterables.concat(
1419                    highlightRequests.filter(supremum),
1420                    highlightRequests.ideal(infimum),
1421                    highlightRequests.interval(infimum, supremum)),
1422                Iterables
1423                    .concat(Iterables.transform(concepts, new Function<Concept<G, M>, Iterable<HighlightRequest>>() {
1424
1425                      public final Iterable<HighlightRequest> apply(final Concept<G, M> concept) {
1426                        return highlightRequests.concept(concept);
1427                      }
1428                    })));
1429          }
1430          front.getChildren().remove(path);
1431          path = null;
1432        } else if (event.getEventType().equals(MouseEvent.MOUSE_DRAGGED))
1433          path.getElements().add(new LineTo(event.getX(), event.getY()));
1434      }
1435    };
1436    front.addEventHandler(MouseEvent.MOUSE_PRESSED, lariatSelectionHandler);
1437    front.addEventHandler(MouseEvent.MOUSE_DRAGGED, lariatSelectionHandler);
1438    front.addEventHandler(MouseEvent.MOUSE_RELEASED, lariatSelectionHandler);
1439  }
1440
1441  protected final BoundingBox getContentBoundingBox() {
1442    if (fca == null || layout == null || controlBox == null)
1443      return new BoundingBox(0, 0, 0, 0, 0, 0);
1444    return layout.getCurrentBoundingBox(controlBox.hideBottom.isSelected(), controlBox.hideTop.isSelected());
1445  }
1446
1447  protected final void initPolarBottom(final Config c, final Timeline t) {
1448    synchronized (vertices) {
1449      controller.polarBottom = vertices.get(fca.context.selection.bottomConcept());
1450    }
1451    // controller.bottom = Iterables.find(vertices.values(), new Predicate<ConceptVertex>()
1452//      {
1453//        public final boolean apply(final ConceptVertex v)
1454//        {
1455//          return lowerEdges(v.element).isEmpty();
1456//        }
1457//      });
1458    if (controller.polarBottom != null) {
1459      controller.polarBottom.node.radiusProperty().unbind();
1460      for (Edge e : upperEdges(controller.polarBottom.element))
1461        e.unbindStart();
1462      t.getKeyFrames().add(new KeyFrame(Duration.ONE, new EventHandler<ActionEvent>() {
1463
1464        public final void handle(final ActionEvent event) {
1465          controller.polarBottom.node.fillProperty().set(
1466              new RadialGradient(
1467                  0d,
1468                  0d,
1469                  0.5d,
1470                  0.5d,
1471                  0.5d,
1472                  true,
1473                  CycleMethod.NO_CYCLE,
1474                  new Stop[] { new Stop(0d, Color.TRANSPARENT), new Stop(0.95d, Color.WHITE),
1475                      new Stop(0.975d, Color.DODGERBLUE), new Stop(1d, Color.BLACK) }));
1476          controller.polarBottom.node.toBack();
1477        }
1478      }));
1479    }
1480  }
1481
1482  protected final void drawPolarBottom(final Config c, final Timeline t) {
1483    t.getKeyFrames().add(
1484        new KeyFrame(
1485            speed.frameSize,
1486            controller.fadeZ(controller.polarBottom.node, new Point3D(0, 0, 0)),
1487            new KeyValue(controller.polarBottom.node.translateXProperty(), c.x0),
1488            new KeyValue(controller.polarBottom.node.translateYProperty(), c.y0),
1489            new KeyValue(controller.polarBottom.node.radiusProperty(), 0.9d * Math.min(c.x0, c.y0) * zoom.get())));
1490    for (Edge e : upperEdges(controller.polarBottom.element)) {
1491      final Point3D p = c.iso.apply(e.upper.position.getValue());
1492      final Point2D q = Points.projectOnCircle(0, 0, c.box.getHeight(), p.getX(), p.getY());
1493      t.getKeyFrames().add(
1494          new KeyFrame(
1495              speed.frameSize,
1496              new KeyValue(e.line.startXProperty(), c.x0 + c.f * q.getX()),
1497              new KeyValue(e.line.startYProperty(), c.y0 + c.f * q.getY())));
1498    }
1499  }
1500
1501  protected final void resetPolarBottom(final Config c, final Timeline t) {
1502    final Point3D q = c.toPane(controller.polarBottom.position.getValue());
1503    t.getKeyFrames().add(
1504        new KeyFrame(
1505            speed.frameSize,
1506            controller.translateX(controller.polarBottom.node, q),
1507            controller.translateY(controller.polarBottom.node, q)));
1508    for (final Edge e : upperEdges(controller.polarBottom.element)) {
1509      t.getKeyFrames().add(
1510          new KeyFrame(
1511              speed.frameSize,
1512              new KeyValue(e.line.startXProperty(), q.getX()),
1513              new KeyValue(e.line.startYProperty(), q.getY())));
1514    }
1515    t.getKeyFrames().add(new KeyFrame(Duration.ONE, new EventHandler<ActionEvent>() {
1516
1517      public final void handle(final ActionEvent event) {
1518        new NumberPropertyTransition(
1519            speed.frameSize,
1520            controller.polarBottom.node.radiusProperty(),
1521            5d,
1522            new EventHandler<ActionEvent>() {
1523
1524              public final void handle(final ActionEvent event) {
1525                controller.polarBottom.init();
1526                for (final Edge e : upperEdges(controller.polarBottom.element))
1527                  e.bindStart();
1528                controller.polarBottom.node.fillProperty().set(COLOR_DEFAULT);
1529                controller.polarBottom.node.toFront();
1530                controller.polarBottom = null;
1531              }
1532            }).play();
1533      }
1534    }));
1535  }
1536
1537  @SafeVarargs
1538  public final void highlight(final boolean fadeComplement, final Iterable<HighlightRequest>... requests) {
1539    if (!controller.graphLock && !dontHighlight && toolBar.highlight.isSelected()) {
1540      final Timeline t = new Timeline();
1541      for (Iterable<HighlightRequest> it : requests)
1542        for (final HighlightRequest r : it)
1543          buildTimeline(t, r, true);
1544      if (fadeComplement)
1545        buildTimeline(t, constructComplementHighlightRequest(requests), false);
1546      t.play();
1547    }
1548  }
1549
1550  @SuppressWarnings("incomplete-switch")
1551  private final void buildTimeline(final Timeline t, final HighlightRequest r, final boolean toFront) {
1552    if (toFront)
1553      synchronized (vertices) {
1554        for (final Concept<G, M> c : r.elements) {
1555          final Vertex v = vertices.get(c);
1556          if (v != null) {
1557            Iterable<Edge> edges = null;
1558            switch (r.edgeHighlightOption) {
1559            case CONTAINS_BOTH:
1560              edges = Collections2.filter(upperEdges(v.element), new Predicate<Edge>() {
1561
1562                public final boolean apply(final Edge c) {
1563                  return r.elements.contains(c.elements.second());
1564                }
1565              });
1566              break;
1567            case CONTAINS_ONE:
1568              edges = Iterables.concat(upperEdges(v.element), lowerEdges(v.element));
1569              break;
1570            case CONTAINS_LOWER:
1571              edges = upperEdges(v.element);
1572              break;
1573            case CONTAINS_UPPER:
1574              edges = lowerEdges(v.element);
1575              break;
1576            }
1577            if (edges != null)
1578              for (final Edge e : edges)
1579                t.getKeyFrames().add(new KeyFrame(Duration.ONE, new EventHandler<ActionEvent>() {
1580
1581                  public final void handle(final ActionEvent event) {
1582                    // e.line.toFront();
1583                    if (!e.line.opacityProperty().isBound())
1584                      e.line.opacityProperty().set(1d);
1585                    new StrokeTransition(speed.frameSize, e.line, (Color) e.line.getStroke(), r.edgeColor).play();
1586                  };
1587                }));
1588            if (!v.equals(controller.polarBottom))
1589              t.getKeyFrames().add(new KeyFrame(Duration.ONE, new EventHandler<ActionEvent>() {
1590
1591                public final void handle(final ActionEvent event) {
1592                  // v.node.toFront();
1593                  if (!threeDimensions() && !v.node.opacityProperty().isBound())
1594                    v.node.opacityProperty().set(1d);
1595                  new FillTransition(
1596                      speed.frameSize,
1597                      v.node,
1598                      v.node.getFill() instanceof Color ? (Color) v.node.getFill() : Color.TRANSPARENT,
1599                      r.vertexColor).play();
1600                };
1601              }));
1602            for (final UpperLabel l : v.upperLabels)
1603              t.getKeyFrames().add(new KeyFrame(Duration.ONE, new EventHandler<ActionEvent>() {
1604
1605                public void handle(ActionEvent event) {
1606                  // l.content.toFront();
1607                  if (!threeDimensions())
1608                    l.content.opacityProperty().set(1d);
1609                  new FillTransition(speed.frameSize, l.back, (Color) l.back.getFill(), r.attributeLabelBackColor)
1610                      .play();
1611                  new FillTransition(speed.frameSize, l.text, (Color) l.text.getFill(), r.attributeLabelTextColor)
1612                      .play();
1613                };
1614              }));
1615            for (final LowerLabel l : v.lowerLabels)
1616              t.getKeyFrames().add(new KeyFrame(Duration.ONE, new EventHandler<ActionEvent>() {
1617
1618                public void handle(ActionEvent event) {
1619                  // l.content.toFront();
1620                  if (!threeDimensions())
1621                    l.content.opacityProperty().set(1d);
1622                  new FillTransition(speed.frameSize, l.back, (Color) l.back.getFill(), r.objectLabelBackColor).play();
1623                  new FillTransition(speed.frameSize, l.text, (Color) l.text.getFill(), r.objectLabelTextColor).play();
1624                };
1625              }));
1626          }
1627        }
1628      }
1629    else
1630      synchronized (vertices) {
1631        for (final Concept<G, M> c : r.elements) {
1632          final Vertex v = vertices.get(c);
1633          if (v != null) {
1634            for (final UpperLabel l : v.upperLabels)
1635              t.getKeyFrames().add(new KeyFrame(Duration.ONE, new EventHandler<ActionEvent>() {
1636
1637                public void handle(ActionEvent event) {
1638                  // l.content.toBack();
1639                  if (!threeDimensions())
1640                    l.content.opacityProperty().set(0.2d);
1641                  new FillTransition(speed.frameSize, l.back, (Color) l.back.getFill(), r.attributeLabelBackColor)
1642                      .play();
1643                  new FillTransition(speed.frameSize, l.text, (Color) l.text.getFill(), r.attributeLabelTextColor)
1644                      .play();
1645                };
1646              }));
1647            for (final LowerLabel l : v.lowerLabels)
1648              t.getKeyFrames().add(new KeyFrame(Duration.ONE, new EventHandler<ActionEvent>() {
1649
1650                public void handle(ActionEvent event) {
1651                  // l.content.toBack();
1652                  if (!threeDimensions())
1653                    l.content.opacityProperty().set(0.2d);
1654                  new FillTransition(speed.frameSize, l.back, (Color) l.back.getFill(), r.objectLabelBackColor).play();
1655                  new FillTransition(speed.frameSize, l.text, (Color) l.text.getFill(), r.objectLabelTextColor).play();
1656                };
1657              }));
1658            if (!v.equals(controller.polarBottom))
1659              t.getKeyFrames().add(new KeyFrame(Duration.ONE, new EventHandler<ActionEvent>() {
1660
1661                public final void handle(final ActionEvent event) {
1662                  // v.node.toBack();
1663                  if (!threeDimensions() && !v.node.opacityProperty().isBound())
1664                    v.node.opacityProperty().set(0.2d);
1665                  new FillTransition(
1666                      speed.frameSize,
1667                      v.node,
1668                      v.node.getFill() instanceof Color ? (Color) v.node.getFill() : Color.TRANSPARENT,
1669                      r.vertexColor).play();
1670                };
1671              }));
1672            Iterable<Edge> edges = null;
1673            switch (r.edgeHighlightOption) {
1674            case CONTAINS_BOTH:
1675              edges = Collections2.filter(upperEdges(v.element), new Predicate<Edge>() {
1676
1677                public final boolean apply(final Edge c) {
1678                  return r.elements.contains(c.elements.second());
1679                }
1680              });
1681              break;
1682            case CONTAINS_ONE:
1683              edges = Iterables.concat(upperEdges(v.element), lowerEdges(v.element));
1684              break;
1685            case CONTAINS_LOWER:
1686              edges = upperEdges(v.element);
1687              break;
1688            case CONTAINS_UPPER:
1689              edges = lowerEdges(v.element);
1690              break;
1691            }
1692            if (edges != null)
1693              for (final Edge e : edges)
1694                t.getKeyFrames().add(new KeyFrame(Duration.ONE, new EventHandler<ActionEvent>() {
1695
1696                  public final void handle(final ActionEvent event) {
1697                    // e.line.toBack();
1698                    if (!e.line.opacityProperty().isBound())
1699                      e.line.opacityProperty().set(0.2d);
1700                    new StrokeTransition(speed.frameSize, e.line, (Color) e.line.getStroke(), r.edgeColor).play();
1701                  };
1702                }));
1703          }
1704        }
1705      }
1706  }
1707
1708  private final HighlightRequest
1709      constructComplementHighlightRequest(@SuppressWarnings("unchecked") final Iterable<HighlightRequest>... requests) {
1710    return new HighlightRequest(
1711        Sets.newHashSet(
1712            layout.lattice.complement(
1713                Sets.newHashSet(
1714                    Iterables.concat(
1715                        Iterables.transform(
1716                            Iterables.concat(Arrays.asList(requests)),
1717                            new Function<HighlightRequest, Iterable<Concept<G, M>>>() {
1718
1719                              public final Iterable<Concept<G, M>> apply(final HighlightRequest request) {
1720                                return request.elements;
1721                              }
1722                            }))))),
1723        EdgeHighlight.CONTAINS_ONE,
1724        COLOR_UNCOMPARABLE,
1725        COLOR_UNCOMPARABLE,
1726        COLOR_UNCOMPARABLE,
1727        Color.WHITE,
1728        COLOR_UNCOMPARABLE,
1729        Color.WHITE);
1730  }
1731
1732  public final class HighlightRequests {
1733
1734    private HighlightRequests() {}
1735
1736    public final Iterable<HighlightRequest> dehighlight() {
1737      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1738        return Collections.emptySet();
1739      return Collections.<HighlightRequest> singleton(
1740          new HighlightRequest(
1741              vertices.keySet(),
1742              EdgeHighlight.CONTAINS_UPPER,
1743              COLOR_DEFAULT,
1744              Color.BLACK,
1745              COLOR_LABEL_DEFAULT,
1746              Color.BLACK,
1747              COLOR_LABEL_DEFAULT,
1748              Color.BLACK));
1749    }
1750
1751    public final Iterable<HighlightRequest> concept(final Concept<G, M> concept) {
1752      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1753        return Collections.emptySet();
1754      return Collections.<HighlightRequest> singleton(
1755          new HighlightRequest(
1756              Collections.singleton(concept),
1757              EdgeHighlight.CONTAINS_ONE,
1758              COLOR_CONCEPT,
1759              COLOR_CONCEPT,
1760              COLOR_CONCEPT,
1761              Color.BLACK,
1762              COLOR_CONCEPT,
1763              Color.BLACK));
1764    }
1765
1766    @SuppressWarnings("unchecked")
1767    public final Iterable<HighlightRequest> implication(final Implication<M> implication) {
1768      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1769        return Collections.emptySet();
1770      final Set<Concept<G, M>> conceptsP = new HashSet<Concept<G, M>>();
1771      final Set<Concept<G, M>> conceptsC = new HashSet<Concept<G, M>>();
1772      final Set<Concept<G, M>> concepts = new HashSet<Concept<G, M>>();
1773      final Set<Concept<G, M>> inner = new HashSet<Concept<G, M>>();
1774      for (M m : implication.getPremise())
1775        conceptsP.add(fca.context.attributeConcept(m));
1776      for (M m : implication.getConclusion())
1777        conceptsC.add(fca.context.attributeConcept(m));
1778      for (G g : fca.context.colAnd(implication.getPremise()))
1779        concepts.add(fca.context.objectConcept(g));
1780//      final Collection<Concept<G, M>> lower =
1781//          Collections3.union(Collections2.transform(
1782//              conceptsC,
1783//              new Function<Concept<G, M>, Collection<Concept<G, M>>>() {
1784//
1785//                @Override
1786//                public Collection<Concept<G, M>> apply(Concept<G, M> mm) {
1787//                  return Collections3.difference(fca.lattice.ideal(mm), Collections.singleton(mm));
1788//                }
1789//              }));
1790//      final Collection<Concept<G, M>> upper =
1791//          Collections3.union(Collections2.transform(
1792//              conceptsP,
1793//              new Function<Concept<G, M>, Collection<Concept<G, M>>>() {
1794//
1795//                @Override
1796//                public Collection<Concept<G, M>> apply(Concept<G, M> mm) {
1797//                  return Collections3.difference(fca.lattice.filter(mm), Collections.singleton(mm));
1798//                }
1799//              }));
1800//      inner.addAll(Collections3.intersection(lower, upper));
1801      return Sets.<HighlightRequest> newHashSet(
1802          new HighlightRequest(
1803              fca.lattice.ideal(fca.lattice.supremum(concepts)),
1804              EdgeHighlight.CONTAINS_BOTH,
1805              COLOR_CONCEPT,
1806              COLOR_CONCEPT,
1807              COLOR_CONCEPT,
1808              Color.WHITE,
1809              COLOR_CONCEPT,
1810              Color.WHITE),
1811          new HighlightRequest(
1812              conceptsP,
1813              EdgeHighlight.CONTAINS_LOWER,
1814              COLOR_UPPER,
1815              COLOR_UPPER,
1816              COLOR_UPPER,
1817              Color.BLACK,
1818              COLOR_UPPER,
1819              Color.BLACK),
1820          new HighlightRequest(
1821              conceptsC,
1822              EdgeHighlight.CONTAINS_UPPER,
1823              COLOR_LOWER,
1824              COLOR_LOWER,
1825              COLOR_LOWER,
1826              Color.BLACK,
1827              COLOR_LOWER,
1828              Color.BLACK),
1829          new HighlightRequest(
1830              inner,
1831              EdgeHighlight.CONTAINS_BOTH,
1832              COLOR_INTERVAL,
1833              COLOR_INTERVAL,
1834              COLOR_INTERVAL,
1835              Color.WHITE,
1836              COLOR_INTERVAL,
1837              Color.WHITE));
1838    }
1839
1840    public final Iterable<HighlightRequest> upperNeighbors(final Concept<G, M> concept) {
1841      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1842        return Collections.emptySet();
1843      return Collections.<HighlightRequest> singleton(
1844          new HighlightRequest(
1845              layout.lattice.upperNeighbors(concept),
1846              EdgeHighlight.CONTAINS_LOWER,
1847              COLOR_UPPER,
1848              COLOR_UPPER,
1849              COLOR_LABEL_DEFAULT,
1850              Color.BLACK,
1851              COLOR_UPPER,
1852              Color.WHITE));
1853    }
1854
1855    public final Iterable<HighlightRequest> lowerNeighbors(final Concept<G, M> concept) {
1856      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1857        return Collections.emptySet();
1858      return Collections.<HighlightRequest> singleton(
1859          new HighlightRequest(
1860              layout.lattice.lowerNeighbors(concept),
1861              EdgeHighlight.CONTAINS_UPPER,
1862              COLOR_LOWER,
1863              COLOR_LOWER,
1864              COLOR_LOWER,
1865              Color.WHITE,
1866              COLOR_LABEL_DEFAULT,
1867              Color.BLACK));
1868    }
1869
1870    public final Iterable<HighlightRequest> filter(final Concept<G, M> concept) {
1871      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1872        return Collections.emptySet();
1873      return Collections.<HighlightRequest> singleton(
1874          new HighlightRequest(
1875              layout.lattice.filter(concept),
1876              EdgeHighlight.CONTAINS_LOWER,
1877              COLOR_UPPER,
1878              COLOR_UPPER,
1879              COLOR_LABEL_DEFAULT,
1880              Color.BLACK,
1881              COLOR_UPPER,
1882              Color.WHITE));
1883    }
1884
1885    public final Iterable<HighlightRequest> ideal(final Concept<G, M> concept) {
1886      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1887        return Collections.emptySet();
1888      return Collections.<HighlightRequest> singleton(
1889          new HighlightRequest(
1890              layout.lattice.ideal(concept),
1891              EdgeHighlight.CONTAINS_UPPER,
1892              COLOR_LOWER,
1893              COLOR_LOWER,
1894              COLOR_LOWER,
1895              Color.WHITE,
1896              COLOR_LABEL_DEFAULT,
1897              Color.BLACK));
1898    }
1899
1900    public final Iterable<HighlightRequest> strictFilter(final Concept<G, M> concept) {
1901      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1902        return Collections.emptySet();
1903      return Collections.<HighlightRequest> singleton(
1904          new HighlightRequest(
1905              Sets.difference(layout.lattice.filter(concept), Collections.singleton(concept)),
1906              EdgeHighlight.CONTAINS_LOWER,
1907              COLOR_UPPER,
1908              COLOR_UPPER,
1909              COLOR_LABEL_DEFAULT,
1910              Color.BLACK,
1911              COLOR_UPPER,
1912              Color.WHITE));
1913    }
1914
1915    public final Iterable<HighlightRequest> strictIdeal(final Concept<G, M> concept) {
1916      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1917        return Collections.emptySet();
1918      return Collections.<HighlightRequest> singleton(
1919          new HighlightRequest(
1920              Sets.difference(layout.lattice.ideal(concept), Collections.singleton(concept)),
1921              EdgeHighlight.CONTAINS_UPPER,
1922              COLOR_LOWER,
1923              COLOR_LOWER,
1924              COLOR_LOWER,
1925              Color.WHITE,
1926              COLOR_LABEL_DEFAULT,
1927              Color.BLACK));
1928    }
1929
1930    public final Iterable<HighlightRequest>
1931        interval(final Concept<G, M> lowerConcept, final Concept<G, M> upperConcept) {
1932      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1933        return Collections.emptySet();
1934      return Collections.<HighlightRequest> singleton(
1935          new HighlightRequest(
1936              layout.lattice.interval(lowerConcept, upperConcept),
1937              EdgeHighlight.CONTAINS_BOTH,
1938              COLOR_INTERVAL,
1939              COLOR_INTERVAL,
1940              COLOR_INTERVAL,
1941              Color.WHITE,
1942              COLOR_INTERVAL,
1943              Color.WHITE));
1944    }
1945
1946    public final Iterable<HighlightRequest> object(final G object) {
1947      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1948        return Collections.emptySet();
1949      return filter(fca.context.selection.objectConcept(object));
1950    }
1951
1952    public final Iterable<HighlightRequest> attribute(final M attribute) {
1953      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1954        return Collections.emptySet();
1955      return ideal(fca.context.selection.attributeConcept(attribute));
1956    }
1957
1958    public final Iterable<HighlightRequest> incidence(final G object, final M attribute) {
1959      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1960        return Collections.emptySet();
1961      return interval(fca.context.selection.objectConcept(object), fca.context.selection.attributeConcept(attribute));
1962    }
1963
1964    public final Iterable<HighlightRequest> nonIncidence(final G object, final M attribute) {
1965      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1966        return Collections.emptySet();
1967      return Iterables.concat(
1968          filter(fca.context.selection.objectConcept(object)),
1969          ideal(fca.context.selection.attributeConcept(attribute)));
1970    }
1971
1972    public final Iterable<HighlightRequest> downArrow(final G object, final M attribute) {
1973      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1974        return Collections.emptySet();
1975      final Concept<G, M> objectConcept = fca.context.selection.objectConcept(object);
1976      final Concept<G, M> attributeConcept = fca.context.selection.attributeConcept(attribute);
1977      final Concept<G, M> uniqueLowerNeighborOfObjectConcept = fca.lattice.infimum(objectConcept, attributeConcept);
1978      return Iterables.concat(
1979          filter(objectConcept),
1980          ideal(attributeConcept),
1981          concept(objectConcept),
1982          concept(uniqueLowerNeighborOfObjectConcept));
1983    }
1984
1985    public final Iterable<HighlightRequest> upArrow(final G object, final M attribute) {
1986      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
1987        return Collections.emptySet();
1988      final Concept<G, M> objectConcept = fca.context.selection.objectConcept(object);
1989      final Concept<G, M> attributeConcept = fca.context.selection.attributeConcept(attribute);
1990      final Concept<G, M> uniqueUpperNeighborOfAttributeConcept = fca.lattice.supremum(objectConcept, attributeConcept);
1991      return Iterables.concat(
1992          filter(objectConcept),
1993          ideal(attributeConcept),
1994          concept(attributeConcept),
1995          concept(uniqueUpperNeighborOfAttributeConcept));
1996    }
1997
1998    @SuppressWarnings("unchecked")
1999    public final Iterable<HighlightRequest> bothArrow(final G object, final M attribute) {
2000      if (controller.graphLock || dontHighlight || !toolBar.highlight.isSelected())
2001        return Collections.emptySet();
2002      final Concept<G, M> objectConcept = fca.context.selection.objectConcept(object);
2003      final Concept<G, M> attributeConcept = fca.context.selection.attributeConcept(attribute);
2004      final Concept<G, M> uniqueUpperNeighborOfAttributeConcept = fca.lattice.supremum(objectConcept, attributeConcept);
2005      final Concept<G, M> uniqueLowerNeighborOfObjectConcept = fca.lattice.infimum(objectConcept, attributeConcept);
2006      return Iterables.concat(
2007          filter(objectConcept),
2008          ideal(attributeConcept),
2009          concept(objectConcept),
2010          concept(attributeConcept),
2011          concept(uniqueLowerNeighborOfObjectConcept),
2012          concept(uniqueUpperNeighborOfAttributeConcept));
2013    }
2014  }
2015
2016  public final boolean threeDimensions() {
2017    return transformation.get().equals(GraphTransformation.GRAPH_3D);
2018  }
2019
2020  public final boolean polar() {
2021    return transformation.get().equals(GraphTransformation.POLAR);
2022  }
2023
2024  @Override
2025  public void removeContent() {
2026    super.removeContent();
2027    objectLabels.clear();
2028    attributeLabels.clear();
2029  }
2030}