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}