/*
* TangHuLuPlot.java
*
* Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
*
* This file is part of BEAST.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership and licensing.
*
* BEAST is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* BEAST is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BEAST; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
package dr.app.gui.chart;
import dr.stats.CredibleSetAnalysis;
import dr.stats.FrequencyCounter;
import dr.stats.Variate;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Create a string of bubbles to visualise the joint probability
* between two integer/categorical random variables.
* The size of circle is proportional to the joint probability,
* the blue coloured circle is in the credible set of
* given a probability threshold default to 0.95,
* the red is in the set not in the credible set.
* The tile background is coloured if there is any circle,
* in case the very small circle wouldn't be missed out.
* The coloured background can also show the area of
* the credible set and non-credible set.
*
* TangHuLu is a traditional Chinese snack of candied fruit
* normally sugar-coated hawthorns on a stick.
*
* @author Walter Xie
*/
public class TangHuLuPlot extends ScatterPlot {
private Paint circlePaint = new Color(124, 164, 221);
private Paint incrediblePaint = new Color(232, 114, 103);
private Paint tileShade = new Color(222, 229, 235);
private Paint incredTileShade = new Color(235, 225, 230);
// set min circle size to avoid the circle too small to see
private final double MIN_CIRCLE_SIZE = 5;
protected List<XY> uniqueXYList; // todo duplicate to xData yData, but it is used for key of FrequencyCounter
protected FrequencyCounter<XY> xyFC;
protected double credProb = 0.95;
public TangHuLuPlot(List<Double> xData, List<Double> yData) {
super(xData, yData);
}
public void setPrThreshold(double credProb){
this.credProb = credProb;
}
/**
* Set data
*/
public void setData(List<Double> xData, List<Double> yData) {
if (xData.size() != yData.size())
throw new IllegalArgumentException("xData and yData must have the same size ! " +
xData.size() + " != " + yData.size());
List<XY> xyList = new ArrayList<XY>();
for (int i = 0; i < xData.size(); i++) {
Double x = xData.get(i);
Double y = yData.get(i);
// if (x > 0 && y == 0)
// System.out.println(i + " : " + x + " " + y);
xyList.add(new XY(x,y));
}
// find unique pairs
xyFC = new FrequencyCounter<XY>(xyList, false);
List<Double> xUnique = new ArrayList<Double>();
List<Double> yUnique = new ArrayList<Double>();
uniqueXYList = new ArrayList<XY>();
for (XY xy : xyFC.uniqueValues()) {
xUnique.add(xy.x);
yUnique.add(xy.y);
uniqueXYList.add(xy); // store counts for circle size
}
Variate.D xd = new Variate.D(xUnique);
Variate.D yd = new Variate.D(yUnique);
setData(xd, yd);
}
/**
* Paint data series.
* The maximum circle size is the smaller major tick space in x or y
* multiplying with <code>xScale</code> or <code>yScale</code>.
*/
protected void paintData(Graphics2D g2, Variate.N xData, Variate.N yData) {
float x, y;
// remove ?
markBounds = new java.util.Vector<Rectangle2D>();
// todo do we need selectedPoints here?
Set<Integer> selectedPoints = getSelectedPoints();
int n = xData.getCount();
double maxDiameter = MIN_CIRCLE_SIZE * 2;
if (n > 1) {
double xGap = Math.abs(xAxis.getMajorTickSpacing() * xScale);
double yGap = Math.abs(yAxis.getMajorTickSpacing() * yScale);
// take the smaller gap to fit in circles
double maxCS = Math.min(xGap, yGap);
if (maxCS > maxDiameter)
maxDiameter = maxCS;
}
// if (xyFC != null) {
for (int i = 0; i < n; i++) {
x = (float) transformX(((Number) xData.get(i)).doubleValue());
y = (float) transformY(((Number) yData.get(i)).doubleValue());
XY xy = uniqueXYList.get(i);
// probability is proportional to area not diameter
double diameter = maxDiameter * Math.sqrt(xyFC.getFreqScaledMaxTo1(xy));
CredibleSetAnalysis credibleSetAnalysis = xyFC.getCredibleSetAnalysis(credProb);
Set incredibleSet = credibleSetAnalysis.getIncredibleSet();
// background tiles
Paint currentPaint = tileShade;
if (incredibleSet.contains(xy))
currentPaint = incredTileShade;
setMarkStyle(SQUARE_MARK, maxDiameter, new BasicStroke(1), currentPaint, currentPaint);
drawMark(g2, x + (float) maxDiameter/2, y + (float) maxDiameter/2, null);
// circles
currentPaint = circlePaint;
if (incredibleSet.contains(xy))
currentPaint = incrediblePaint;
if (selectedPoints.contains(i)) {
setHilightedMarkStyle(new BasicStroke(1), currentPaint, currentPaint);
drawMarkHilighted(g2, x, y);
} else {
setMarkStyle(CIRCLE_MARK, diameter, new BasicStroke(1),
currentPaint, currentPaint);
// if (colours != null && colours.size() == n)
// drawMark(g2, x, y, colours.get(i));
// else
drawMark(g2, x, y, null);
}
}
// }
}
}