001package conexp.fx.core.layout;
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.HashSet;
026import java.util.Map;
027import java.util.Map.Entry;
028import java.util.Set;
029import java.util.concurrent.ConcurrentHashMap;
030
031import conexp.fx.core.collections.relation.RelationEvent;
032import conexp.fx.core.context.Concept;
033import conexp.fx.core.context.ConceptLattice;
034import javafx.beans.InvalidationListener;
035import javafx.beans.Observable;
036import javafx.beans.binding.Binding;
037import javafx.beans.value.ObservableValue;
038import javafx.geometry.BoundingBox;
039import javafx.geometry.Point3D;
040
041public abstract class ConceptLayout<G, M, P extends ObservableValue<Point3D>> implements Observable {
042
043  protected boolean                              observe          = false;
044  public final ConceptLattice<G, M>              lattice;
045  protected final Map<Concept<G, M>, P>          positionBindings = new ConcurrentHashMap<Concept<G, M>, P>();
046  public final Map<Concept<G, M>, Concept<G, M>> generators       =
047      new ConcurrentHashMap<Concept<G, M>, Concept<G, M>>();
048
049  protected ConceptLayout(final ConceptLattice<G, M> conceptLattice) {
050    super();
051    this.lattice = conceptLattice;
052  }
053
054  public final void observe() {
055    if (this.observe)
056      return;
057    this.observe = true;
058    // TODO synchronized(positionBindings)
059    lattice.addEventHandler(e -> e.getRows().forEach(this::getOrAddPosition), RelationEvent.ROWS_ADDED);
060    lattice.addEventHandler(e -> e.getRows().forEach(this::disposePosition), RelationEvent.ROWS_REMOVED);
061  }
062
063  public final P getOrAddPosition(final Concept<G, M> concept) {
064    synchronized (positionBindings) {
065      return positionBindings.computeIfAbsent(concept, this::newPositionBinding);
066    }
067  }
068
069  protected abstract P newPositionBinding(Concept<G, M> concept);
070
071  public final P getPosition(final Concept<G, M> c) {
072    synchronized (positionBindings) {
073      return positionBindings.get(c);
074    }
075  }
076
077  public final void disposePosition(final Concept<G, M> concept) {
078    synchronized (positionBindings) {
079      final P p = positionBindings.remove(concept);
080      if (p instanceof Binding)
081        ((Binding<Point3D>) p).dispose();
082    }
083  }
084
085  protected final void initializePositionBindings() {
086    for (final Concept<G, M> concept : lattice.rowHeads())
087      getOrAddPosition(concept);
088  }
089
090  public abstract void rotate(double angle);
091
092  public abstract void move(Concept<G, M> concept, ConceptMovement movement, Point3D delta);
093
094  public abstract void deleteZ();
095
096  public final BoundingBox getCurrentBoundingBox(final boolean hideBottom, final boolean hideTop) {
097    synchronized (positionBindings) {
098      double xmin = Double.MAX_VALUE;
099      double xmax = Double.MIN_VALUE;
100      double ymin = Double.MAX_VALUE;
101      double ymax = Double.MIN_VALUE;
102      double zmin = Double.MAX_VALUE;
103      double zmax = Double.MIN_VALUE;
104//    final java.util.function.Predicate<Entry<Concept<G, M>, Point3D>> pred;
105//    if (hideBottom && hideTop)
106//      pred =
107//          e -> !e.getKey().getExtent().containsAll(lattice.context.rowHeads())
108//              && !e.getKey().getIntent().containsAll(lattice.context.colHeads());
109//    else if (hideBottom)
110//      pred =
111//          e -> !e.getKey().getExtent().containsAll(lattice.context.rowHeads())
112//              && !e.getKey().getIntent().containsAll(lattice.context.colHeads());
113//    else if (hideTop)
114//      pred =
115//          e -> !e.getKey().getExtent().containsAll(lattice.context.rowHeads())
116//              && !e.getKey().getIntent().containsAll(lattice.context.colHeads());
117//    else
118//      pred = e -> true;
119//    positions
120//        .entrySet()
121//        .parallelStream()
122//        .filter(pred)
123//        .map(e -> e.getValue())
124//        .reduce(
125//            Pair.of(new double[] { Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE }, new double[] {
126//                Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE }),
127//            (a, p) -> Pair.of(new double[] {}, new double[] {}),
128//            (a, b) -> Pair.of(null, null));
129      for (Entry<Concept<G, M>, P> e : positionBindings.entrySet())
130        if (!hideBottom || !e.getKey().getIntent().containsAll(lattice.context.colHeads()))
131          if (!hideTop || !e.getKey().getExtent().containsAll(lattice.context.rowHeads())) {
132            final Point3D p = e.getValue().getValue();
133            xmin = Math.min(xmin, p.getX());
134            xmax = Math.max(xmax, p.getX());
135            ymin = Math.min(ymin, p.getY());
136            ymax = Math.max(ymax, p.getY());
137            zmin = Math.min(zmin, p.getZ());
138            zmax = Math.max(zmax, p.getZ());
139          }
140      final double dx = xmax - xmin;
141      final double dy = ymax - ymin;
142      final double dz = zmax - zmin;
143      return new BoundingBox(xmin, ymin, zmin, dx, dy, dz);
144    }
145  }
146
147  private final Set<InvalidationListener> listeners = new HashSet<InvalidationListener>();
148
149  public final void invalidate() {
150    synchronized (listeners) {
151      for (InvalidationListener listener : listeners)
152        listener.invalidated(this);
153    }
154  }
155
156  public final void addListener(final InvalidationListener listener) {
157    synchronized (listeners) {
158      listeners.add(listener);
159    }
160  }
161
162  public final void removeListener(final InvalidationListener listener) {
163    synchronized (listeners) {
164      listeners.remove(listener);
165    }
166  }
167
168}