package timeflow.app.ui.filter;
|
|
import javax.swing.*;
|
|
import timeflow.data.time.Interval;
|
import timeflow.model.Display;
|
|
|
import java.awt.*;
|
import java.awt.event.MouseAdapter;
|
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseMotionAdapter;
|
import java.text.*;
|
|
|
public class BabyHistogram extends JPanel {
|
private int[] buckets;
|
private double[] x;
|
private double min, max;
|
private int numDefined;
|
private int maxBucket;
|
private double value;
|
private static final DecimalFormat df=new DecimalFormat("###,###,###,###.##");
|
|
|
Point mouseHit=new Point();
|
Point mouse=new Point(-1,0);
|
enum Modify {START, END, POSITION, NONE};
|
Modify change=Modify.NONE;
|
Rectangle startRect=new Rectangle(-1,-1,0,0);
|
Rectangle endRect=new Rectangle(-1,-1,0,0);
|
Rectangle positionRect=new Rectangle(-1,-1,0,0);
|
Color sidePlain=Color.orange;
|
Color sideMouse=new Color(230,100,0);
|
double relLow=0, relHigh=1, originalLow, originalHigh;
|
|
public void setRelRange(double relLow, double relHigh)
|
{
|
this.relLow=Math.max(0,relLow);
|
this.relHigh=Math.min(1,relHigh);
|
repaint();
|
}
|
|
public void setTrueRange(double low, double high)
|
{
|
double span=max-min;
|
if (span<=0) // nothing much to do...
|
return;
|
|
setRelRange((low-min)/span, (high-min)/span);
|
}
|
|
public boolean isEverything()
|
{
|
return relLow==0 && relHigh==1;
|
}
|
|
public double getLow()
|
{
|
return abs(relLow);
|
}
|
|
public double getHigh()
|
{
|
return abs(relHigh);
|
}
|
|
private double abs(double x)
|
{
|
return x*max+(1-x)*min;
|
}
|
|
public BabyHistogram(final Runnable changeAction)
|
{
|
|
addMouseListener(new MouseAdapter() {
|
|
@Override
|
public void mousePressed(MouseEvent e) {
|
int mx=e.getX();
|
int my=e.getY();
|
int lx=lowX(), hx=highX();
|
|
int ox=0;
|
if (Math.abs(mx-lx)<Math.abs(mx-hx))
|
{
|
change=Modify.START;
|
ox=lx;
|
}
|
else
|
{
|
change=Modify.END;
|
ox=hx;
|
}
|
mouseHit.setLocation(ox,my);
|
mouse.setLocation(mx,my);
|
originalLow=relLow;
|
originalHigh=relHigh;
|
repaint();
|
}
|
|
@Override
|
public void mouseReleased(MouseEvent e) {
|
change=Modify.NONE;
|
repaint();
|
}});
|
addMouseMotionListener(new MouseMotionAdapter() {
|
|
@Override
|
public void mouseDragged(MouseEvent e) {
|
|
if (change==Modify.NONE)
|
return;
|
mouse.setLocation(e.getX(), e.getY());
|
int mouseDiff=mouse.x-mouseHit.x;
|
double relDiff=mouseDiff/(double)getSize().width;
|
switch (change)
|
{
|
case POSITION:
|
relLow=originalLow+relDiff;
|
relHigh=originalHigh+relDiff;
|
|
break;
|
case START: relLow=originalLow+relDiff;
|
break;
|
case END: relHigh=originalHigh+relDiff;
|
}
|
relLow=Math.max(0, relLow);
|
relHigh=Math.min(1, relHigh);
|
changeAction.run();
|
repaint();
|
}
|
});
|
|
}
|
|
public void setData(double[] x)
|
{
|
relLow=0;
|
relHigh=1;
|
|
this.x=x;
|
int n=x.length;
|
|
// do some quick checks on the data.
|
boolean positive=true;
|
min=Double.NaN;
|
max=Double.NaN;
|
numDefined=0;
|
for (int i=0; i<n; i++)
|
{
|
double m=x[i];
|
if (!Double.isNaN(m))
|
{
|
numDefined++;
|
positive &= m>0;
|
if (Double.isNaN(min))
|
{
|
min=m;
|
max=m;
|
value=m;
|
}
|
else
|
{
|
min=Math.min(m, min);
|
max=Math.max(m, max);
|
}
|
}
|
}
|
|
if (numDefined==0)
|
return;
|
if (min==max)
|
{
|
buckets=new int[1];
|
buckets[0]=numDefined;
|
maxBucket=numDefined;
|
return;
|
}
|
int numBuckets=(int)Math.min(50, 2*Math.sqrt(numDefined));
|
buckets=new int[numBuckets];
|
maxBucket=0;
|
for (int i=0; i<n; i++)
|
{
|
if (!Double.isNaN(x[i]))
|
{
|
int b=(int)((numBuckets-1)*(x[i]-min)/(max-min));
|
buckets[b]++;
|
maxBucket=Math.max(maxBucket, buckets[b]);
|
}
|
}
|
}
|
|
public void paintComponent(Graphics g)
|
{
|
int w=getSize().width;
|
int h=getSize().height;
|
g.setColor(Color.white);
|
g.fillRect(0,0,w,h);
|
|
if (x==null)
|
{
|
say(g, "No data");
|
return;
|
}
|
|
if (x.length==0)
|
{
|
say(g, "No values");
|
return;
|
}
|
|
if (numDefined==0)
|
{
|
say(g, "No defined values");
|
return;
|
}
|
|
int n=buckets.length;
|
if (n==1)
|
{
|
say(g, "All defined vals = "+df.format(value));
|
return;
|
}
|
|
// wow, if we got here we really have a histogram and not a degenerate mess!
|
|
Color bar=Display.barColor;
|
g.setColor(bar);
|
for (int i=0; i<n; i++)
|
{
|
int x1=(i*w)/n;
|
int x2=((i+1)*w)/n;
|
int y1=h-(buckets[i]*h)/maxBucket;
|
if (buckets[i]>0 && y1>h-2)
|
y1=h-2;
|
g.fillRect(x1,y1,x2-x1-1,h-y1);
|
}
|
|
// now draw thumb.
|
|
int thumb1=lowX();
|
int thumb2=highX();
|
g.setColor(Color.black);
|
g.drawLine(thumb1,0,thumb1,h);
|
g.drawLine(thumb2,0,thumb2,h);
|
g.setColor(new Color(235,235,235,160));
|
g.fillRect(0,0,thumb1,h);
|
g.fillRect(thumb2+1,0,w-thumb2-1,h);
|
g.setColor(Color.lightGray);
|
g.drawRect(0,0,w-1,h-1);
|
}
|
|
int lowX()
|
{
|
return (int)((getSize().width-1)*relLow);
|
}
|
|
int highX()
|
{
|
return (int)((getSize().width-1)*relHigh);
|
}
|
|
void say(Graphics g, String s)
|
{
|
g.setColor(Color.gray);
|
g.drawString(s,5,getSize().height-5);
|
}
|
|
public Dimension getPreferredSize()
|
{
|
return new Dimension(200,60);
|
}
|
}
|