package org.chesmapper.view.cluster;
import java.awt.Color;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import javax.vecmath.Vector3f;
import org.chesmapper.map.dataInterface.ClusterData;
import org.chesmapper.map.dataInterface.CompoundData;
import org.chesmapper.map.dataInterface.CompoundGroupWithProperties;
import org.chesmapper.map.dataInterface.CompoundProperty;
import org.chesmapper.map.dataInterface.NominalProperty;
import org.chesmapper.map.dataInterface.NumericProperty;
import org.chesmapper.map.dataInterface.SubstructureSmartsType;
import org.chesmapper.view.cluster.Compound.DisplayName;
import org.chesmapper.view.gui.View;
import org.chesmapper.view.gui.ViewControler.HighlightSorting;
import org.mg.javalib.gui.DoubleNameListCellRenderer.DoubleNameElement;
import org.mg.javalib.util.ArrayUtil;
import org.mg.javalib.util.CountedSet;
import org.mg.javalib.util.ObjectUtil;
import org.mg.javalib.util.Vector3fUtil;
public class Cluster extends ZoomableCompoundGroup implements CompoundGroupWithProperties, DoubleNameElement,
Comparable<Cluster>
{
private ClusterData clusterData;
HashMap<String, List<Compound>> compoundsOrderedByPropterty = new HashMap<String, List<Compound>>();
private boolean watched;
private CompoundProperty highlightProp;
private HighlightSorting highlightSorting;
private boolean showLabel = false;
public Cluster(org.chesmapper.map.dataInterface.ClusterData clusterData)
{
this.clusterData = clusterData;
List<Compound> c = new ArrayList<Compound>();
int count = 0;
for (CompoundData d : clusterData.getCompounds())
c.add(new Compound(clusterData.getCompoundClusterIndices().get(count++), d));
setCompounds(c);
displayName.name = getName() + " (#" + getNumCompounds() + ")";
displayName.compareIndex = clusterData.getOrigIndex();
if (View.instance != null) // for export without graphics
update();
}
public void setFilter(CompoundFilter filter)
{
super.setFilter(filter);
List<Integer> origIndices = new ArrayList<Integer>();
if (filter != null)
for (Compound c : getCompounds())
if (filter.accept(c))
origIndices.add(c.getOrigIndex());
clusterData.setFilter(filter == null ? null : origIndices);
updateDisplayName();
}
boolean alignedCompoundsCalibrated = false;
public void updatePositions()
{
// the actual compound position that is stored in compound is never changed (depends only on scaling)
// this method moves all compounds to the cluster position
// recalculate non-superimposed diameter
update();
if (!clusterData.isAligned())
{
// the compounds have not been aligned
// the compounds may have a center != 0, calibrate to 0
for (Compound m : getCompounds())
m.moveTo(new Vector3f(0f, 0f, 0f));
}
else
{
// the compounds are aligned, cannot calibrate to 0, this would brake the alignment
// however, the compound center may have an offset, calculate and remove
if (!alignedCompoundsCalibrated)
{
Vector3f[] origCenters = new Vector3f[getCompounds().size()];
for (int i = 0; i < origCenters.length; i++)
origCenters[i] = getCompounds().get(i).origCenter;
Vector3f center = Vector3fUtil.center(origCenters);
for (int i = 0; i < origCenters.length; i++)
getCompounds().get(i).origCenter.sub(center);
alignedCompoundsCalibrated = true;
}
for (Compound m : getCompounds())
m.moveTo(m.origCenter);
}
// translate compounds to the cluster position
View.instance.setAtomCoordRelative(getCenter(true), getBitSet());
}
private DisplayName displayName = new DisplayName();
private Color highlightColorText;
public String getName()
{
return clusterData.getName();
}
@Override
public String toString()
{
return getName();
}
public String toStringWithValue()
{
return displayName.toString(false, null);
}
@Override
public String getFirstName()
{
return displayName.name;
}
@Override
public String getSecondName()
{
if (ObjectUtil.equals(displayName.valDisplay, displayName.name))
return null;
else
return displayName.valDisplay;
}
public String getSummaryStringValue(CompoundProperty property, boolean html)
{
return clusterData.getSummaryStringValue(property, html);
}
public CountedSet<String> getNominalSummary(NominalProperty p)
{
return clusterData.getNominalSummary(p);
}
public String getAlignAlgorithm()
{
return clusterData.getAlignAlgorithm();
}
protected void update()
{
if (getOrigSize() != getNumCompounds())
displayName.name = getName() + " (#" + getNumCompounds() + "/" + getOrigSize() + ")";
else
displayName.name = getName() + " (#" + getNumCompounds() + ")";
super.update();
}
public List<Compound> getCompoundsInOrder(final CompoundProperty property, HighlightSorting sorting)
{
String key = property + "_" + sorting;
if (!compoundsOrderedByPropterty.containsKey(key))
{
List<Compound> c = new ArrayList<Compound>();
for (Compound compound : getCompounds())
c.add(compound);
final HighlightSorting finalSorting;
if (sorting == HighlightSorting.Median)
finalSorting = HighlightSorting.Max;
else
finalSorting = sorting;
Collections.sort(c, new Comparator<Compound>()
{
@Override
public int compare(Compound o1, Compound o2)
{
int res;
if (o1 == null)
{
if (o2 == null)
res = 0;
else
res = 1;
}
else if (o2 == null)
res = -1;
else if (property instanceof NumericProperty)
{
Double d1 = o1.getDoubleValue((NumericProperty) property);
Double d2 = o2.getDoubleValue((NumericProperty) property);
if (d1 == null)
{
if (d2 == null)
res = 0;
else
res = 1;
}
else if (d2 == null)
res = -1;
else
res = d1.compareTo(d2);
}
else
res = (o1.getStringValue((NominalProperty) property) + "").compareTo(o2
.getStringValue((NominalProperty) property) + "");
return (finalSorting == HighlightSorting.Max ? -1 : 1) * res;
}
});
if (sorting == HighlightSorting.Median)
{
// Settings.LOGGER.warn("max order: ");
// for (Compound mm : m)
// Settings.LOGGER.warn(mm.getStringValue(property) + " ");
// Settings.LOGGER.warn();
/**
* median sorting:
* - first order by max to compute median
* - create a dist-to-median array, sort compounds according to that array
*/
Compound medianCompound = c.get(c.size() / 2);
// Settings.LOGGER.warn(medianCompound.getStringValue(property));
double distToMedian[] = new double[c.size()];
if (property instanceof NumericProperty)
{
Double med = medianCompound.getDoubleValue((NumericProperty) property);
for (int i = 0; i < distToMedian.length; i++)
{
Double d = c.get(i).getDoubleValue((NumericProperty) property);
if (med == null)
{
if (d == null)
distToMedian[i] = 0;
else
distToMedian[i] = Double.MAX_VALUE;
}
else if (d == null)
distToMedian[i] = Double.MAX_VALUE;
else
distToMedian[i] = Math.abs(med - d);
}
}
else
{
String medStr = medianCompound.getStringValue((NominalProperty) property);
for (int i = 0; i < distToMedian.length; i++)
distToMedian[i] = Math.abs((c.get(i).getStringValue((NominalProperty) property) + "")
.compareTo(medStr + ""));
}
int order[] = ArrayUtil.getOrdering(distToMedian, true);
Compound a[] = new Compound[c.size()];
Compound s[] = ArrayUtil.sortAccordingToOrdering(order, c.toArray(a));
c = ArrayUtil.toList(s);
// Settings.LOGGER.warn("med order: ");
// for (Compound mm : m)
// Settings.LOGGER.warn(mm.getStringValue(property) + " ");
// Settings.LOGGER.warn();
}
compoundsOrderedByPropterty.put(key, c);
}
// Settings.LOGGER.warn("in order: ");
// for (Compound m : order.get(key))
// Settings.LOGGER.warn(m.getCompoundOrigIndex() + " ");
// Settings.LOGGER.warn("");
return compoundsOrderedByPropterty.get(key);
}
public String getSubstructureSmarts(SubstructureSmartsType type)
{
return clusterData.getSubstructureSmarts(type);
}
public void removeWithJmolIndices(int[] compoundJmolIndices)
{
System.out.println("to remove from cluster: " + ArrayUtil.toString(compoundJmolIndices));
List<Compound> toDel = new ArrayList<Compound>();
int[] toDelIndex = new int[compoundJmolIndices.length];
int count = 0;
for (int i : compoundJmolIndices)
{
Compound c = getCompoundWithJmolIndex(i);
toDel.add(c);
toDelIndex[count++] = getIndex(c);
}
BitSet bs = new BitSet();
for (Compound m : toDel)
{
bs.or(m.getBitSet());
getCompounds().remove(m);
}
View.instance.hide(bs);
compoundsOrderedByPropterty.clear();
clusterData.remove(toDelIndex);
update();
}
public boolean isWatched()
{
return watched;
}
public void setWatched(boolean watched)
{
this.watched = watched;
}
public void setHighlighProperty(CompoundProperty highlightProp, Color highlightColorText)
{
this.highlightProp = highlightProp;
this.highlightColorText = highlightColorText;
updateDisplayName();
}
private void updateDisplayName()
{
displayName.valDisplay = null;
displayName.valCompare = null;
if (highlightProp != null)
{
if (highlightProp instanceof NumericProperty)
displayName.valCompare = new Double[] { getDoubleValue((NumericProperty) highlightProp) };
else
{
String mode = getNominalSummary((NominalProperty) highlightProp).getMode(false);
String domain[] = ((NominalProperty) highlightProp).getDomain();
boolean invertSecondBinaryVal = false;
if (domain.length == 2 && ArrayUtil.indexOf(domain, mode) == 1)
invertSecondBinaryVal = true;
CountedSet<String> set = getNominalSummary((NominalProperty) highlightProp);
/**
* Clusters with nominal feature values should be sorted as follows:
* 1. according to the mode (the most common feature value)
* 2. within equal modes, according to how pure the cluster is with respect to the mode (ratio of compounds with this feature value)
* 3. within equal ratios, according to size (and therefore according to number of compounds with this feature value)
* 4. within equal size, according to cluster index
*/
displayName.valCompare = new Comparable[] { mode,
(invertSecondBinaryVal ? 1 : -1) * (set.getMaxCount(false) / (double) (set.getSum(false))),
(invertSecondBinaryVal ? 1 : -1) * set.getSum(false) };
}
displayName.valDisplay = getFormattedValue(highlightProp);
}
}
public CompoundProperty getHighlightProperty()
{
return highlightProp;
}
public void setHighlightSorting(HighlightSorting highlightSorting)
{
this.highlightSorting = highlightSorting;
}
public HighlightSorting getHighlightSorting()
{
return highlightSorting;
}
public String[] getStringValues(NominalProperty property, Compound excludeCompound)
{
return getStringValues(property, excludeCompound, false);
}
public String[] getStringValues(NominalProperty property, Compound excludeCompound, boolean formatted)
{
List<String> l = new ArrayList<String>();
for (Compound c : getCompounds())
if (c != excludeCompound && c.getStringValue(property) != null)
l.add(formatted ? c.getFormattedValue(property) : c.getStringValue(property));
String v[] = new String[l.size()];
return l.toArray(v);
}
public Double[] getDoubleValues(NumericProperty property)
{
Double v[] = new Double[getCompounds().size()];
for (int i = 0; i < v.length; i++)
v[i] = getCompounds().get(i).getDoubleValue(property);
return v;
}
public void setShowLabel(boolean showLabel)
{
this.showLabel = showLabel;
}
public boolean isShowLabel()
{
return showLabel;
}
public int numMissingValues(CompoundProperty p)
{
return clusterData.numMissingValues(p);
}
public boolean containsNotClusteredCompounds()
{
return clusterData.containsNotClusteredCompounds();
}
@Override
public Double getDoubleValue(NumericProperty p)
{
return clusterData.getDoubleValue(p);
}
@Override
public String getFormattedValue(CompoundProperty p)
{
return clusterData.getFormattedValue(p);
}
public Color getHighlightColorText()
{
return highlightColorText;
}
@Override
public int compareTo(Cluster m)
{
return displayName.compareTo(m.displayName);
}
}