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}