diff src/org/dancres/blitz/tools/dash/graph/Chart.java @ 0:3dc0c5604566

Initial checkin of blitz 2.0 fcs - no installer yet.
author Dan Creswell <dan.creswell@gmail.com>
date Sat, 21 Mar 2009 11:00:06 +0000
parents
children 48228766ed4c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/dancres/blitz/tools/dash/graph/Chart.java	Sat Mar 21 11:00:06 2009 +0000
@@ -0,0 +1,563 @@
+package org.dancres.blitz.tools.dash.graph;
+
+import org.dancres.blitz.tools.dash.ColorScheme;
+
+import java.lang.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.text.*;
+import javax.swing.*;
+
+public class Chart extends JPanel
+    implements ChartItemEnabler
+{
+    
+    private class Data
+    {
+        double  points[];
+        String  label;
+        boolean isVisible=true;
+        double [] pointFactor;
+        String [] tags;
+        int [] date;
+        
+        public int getSize() { return points.length;}
+        public double getPoint(int p) { return points[p];}
+        public double getFactor(int f) { return pointFactor[f];}
+        public String getTag(int f) { return tags[f];}
+    }
+    private static final Color [] chartColor={ColorScheme.READ,
+                                              ColorScheme.WRITE,
+                                              ColorScheme.TAKE};
+    
+    //constraints
+    private static final int MAX_DATA_SETS=chartColor.length;
+    
+    private int dsCounter=0;
+    private Data [] dataSet= new Data[MAX_DATA_SETS];
+    private int labelEvery=1;
+    //
+    //identifier mappings
+    private static final int SOLID=0;
+    private static final int OUTLINE=1;
+    private static NumberFormat  formatter = NumberFormat.getInstance();
+    
+    private IdDrawer [] idDrawer= new IdDrawer[MAX_DATA_SETS];
+    
+    private int maxSize=0;
+    private double maxValue=0.0;
+    private double minValue=0.0;
+    private int maxId; //index to largest dataset
+    private int startAt=0; //for drawing to endAt
+    private int endAt=-1; //reset in zoom
+    private int [] xTagPosition;
+    
+    private Color axisColor=Color.black;
+    private Color gridColor=Color.gray;
+    private int yOffset=25;
+    private boolean dirtyCache;
+    private Dimension minSize=new Dimension(200,200);
+    private Dimension currentSize=null;
+    private Rectangle chartRect= new Rectangle();
+    private Rectangle zoomRect;
+    private boolean  isShown=false; //at least one dataset visiable
+    //public methods
+    public Chart()
+    {
+        idDrawer[0]=new SqDrawer(SOLID);
+        idDrawer[1]=new XDrawer();
+        idDrawer[2]=new RoundRectDrawer(SOLID);
+        
+        
+    }
+    synchronized public void addData(final String name,final double [] data,
+                                     final String [] tags)
+        throws ChartException
+    {
+        addData(name,data,tags,null);
+    }
+    synchronized public void addData(final String name,final double [] data,
+                                     final String [] tags,final int [] dates)
+        throws ChartException
+    {
+        if(dsCounter==MAX_DATA_SETS)
+            throw new ChartException("Maximum number of data sets exceeded!");
+        
+        setDataAt(dsCounter++,name,data,tags,dates);
+    }
+    public void setDataAt(int index,final String name,final double [] data,
+                          final String [] tags,final int [] dates)
+        throws ChartException
+    {
+        dirtyCache=true; //flag that calcFactors must be called internally
+        endAt=-1;
+        
+        Data d= new Data();
+        d.points=data;
+        d.label=name;
+        d.tags=tags;
+        if(dates!=null)
+            d.date=dates;
+        else
+            {
+                //set to defaults
+                int np=data.length;
+                d.date= new int[np];
+                for(int i=0;i<np;i++)
+                    d.date[i]=i;
+            }
+        d.pointFactor= new double[ tags.length ];
+        dataSet[ index ]=d;
+    }
+    public void setLabelEvery(int every) {labelEvery=every;}
+    public static int getMaxSupportedDataSets() {return MAX_DATA_SETS;}
+    public Dimension getMinimumSize()
+    {
+        return minSize;
+    }
+    public Dimension getPreferredSize()
+    {
+        if(currentSize==null)
+            currentSize= getSize();
+        
+        return currentSize;
+    }
+    public void setSize(Dimension d)
+    {
+        super.setSize(d);
+        currentSize=d;
+    }
+    
+    public void print(final Graphics g,final int yoffset)
+    {
+        int ty=yOffset;
+        yOffset=yoffset;
+        super.print(g);
+        yOffset=ty;
+    }
+    public void enableData(final String name,final boolean yesno)
+    {
+        for( int i=0;i<dsCounter;i++)
+            {
+                if(dataSet[i].label.compareTo(name)==0)
+                    {
+                        dataSet[i].isVisible=yesno;
+                        dirtyCache=true;
+                        repaint();
+                        return;
+                    }
+            }
+    }
+    public void paint(Graphics g)
+    {
+        Dimension dim=getSize();
+        g.setColor(Color.white);
+        g.fillRect(0,0,dim.width,dim.height);
+        
+        boolean isPrinting=g instanceof PrintGraphics;
+        
+        if(dirtyCache)
+            {
+                calcFactors();
+                dirtyCache=false;
+            }
+        g.setFont( new Font("Dialog",Font.PLAIN,10) );
+        
+        if(dim.height<50) //to small
+            {
+                return;
+            }
+        int xoff=45;
+        int yoff=yOffset; //set in print(), default==25
+        int wid=dim.width-(2*xoff);
+        int hi=dim.height-(2*yoff);
+        int baseY=15;
+        int legy=baseY; //title pos
+        int legx=xoff;
+        
+        FontMetrics fm=g.getFontMetrics();
+        
+        isShown=false;
+        boolean firstchart=true;
+        int ns=dsCounter;
+        double prevx=0;//last place vert grid line drawn
+        double lastLabelPos=-1000;//make sure first label is always drawn
+        //Draw dataset titles, then calc size of graphRect
+        for(int i=0;i<ns;i++)
+            {
+                if(dataSet[i].isVisible)
+                    {
+                        g.setColor( chartColor[i] );
+                
+                        String dslabel=dataSet[i].label;
+                        int strWidth=fm.stringWidth(dslabel);
+                        if((strWidth+legx)>(wid-5) && firstchart==false) //CR
+                            {
+                                legx=xoff;legy+=15;
+                            }
+                        firstchart=false;
+                        //draw identifier
+                        idDrawer[i].draw(g,legx-2,legy-7);
+                        //draw label
+                        g.drawString(dslabel,legx+5,legy);
+                
+                        legx+=strWidth+12;
+                    }
+            }
+        //now calc the drawable area for the chart
+        // draw zoomable area
+        yoff=legy+15;
+        hi-=(legy-baseY)+5;
+        
+        if(zoomRect!=null)
+            {
+                g.setColor(Color.lightGray);
+                g.fillRect(zoomRect.x,yoff,zoomRect.width,hi);
+            }
+        //border
+        g.setColor(axisColor);
+        g.drawRect(xoff,yoff,wid,hi);
+        //cache for zooming
+        chartRect.setBounds(xoff,yoff,wid,hi);
+        //draw the points
+        for(int i=0;i<ns;i++)
+            {
+                if(dataSet[i].isVisible)
+                    {
+                        g.setColor( chartColor[i] );
+                        int labelWidth=fm.stringWidth(dataSet[i].getTag(0));
+                
+                        isShown=true;
+                        int npoints=dataSet[i].getSize();
+                        double lastx=0;//=xoff;
+                        double lasty=0;//yoff+hi;
+                        int labelCounter=labelEvery; //make sure first label is drawn
+                
+                        for(int j=startAt;j<npoints && j<=endAt;j++)
+                            {
+                                double val=dataSet[i].getPoint(j);
+                                double xpos=xoff+(wid*dataSet[i].getFactor(j));
+                                double yfactor=
+                                    (val-minValue)/(maxValue-minValue);
+                                double ypos=yoff+(hi*(1-yfactor));
+                    
+                                if(lastx==0 && lasty==0)
+                                    {
+                                        //set to current
+                                        lastx=xpos;lasty=ypos;
+                                    }
+                                if(xpos>(prevx+25))
+                                    {
+                                        g.setColor(gridColor);
+                                        g.drawLine((int)xpos,(int)yoff,
+                                                   (int)xpos,(int)yoff+hi+2);
+                                        g.setColor(axisColor);
+                                        if(labelCounter%labelEvery==0 &&
+                                           xpos>(lastLabelPos+labelWidth+10))
+                                            {
+                                                xTagPosition[j]=(int)xpos-5;
+                                                g.drawString(dataSet[i].getTag(j),xTagPosition[j],(int)yoff+hi+20);
+                                                lastLabelPos=xpos;
+                                            }
+                                        else
+                                            {
+                                                xTagPosition[j]=-1; //invalid
+                                            }
+                                        g.setColor(chartColor[i]);
+                                        prevx=xpos;
+                                    }
+                    
+                                g.drawLine((int)lastx,(int)lasty,(int)xpos,
+                                           (int)ypos);
+                                //draw identifier
+                                //g.drawRect((int)xpos-1,(int)ypos-1,2,2);
+                                idDrawer[i].draw(g,(int)xpos-2,(int)ypos-2);
+                                lastx=xpos;
+                                lasty=ypos;
+                                labelCounter++;
+                            }
+                
+                    }
+            
+            }
+        if(isShown)
+            {
+            
+            
+                double inc=(maxValue-minValue)/4;//number of labels (could be set by client)
+                double label=maxValue;
+                double y=yoff;
+        
+                //System.out.println("maxValue="+maxValue+"minValue="+minValue+" inc="+inc);
+        
+                String str=null;
+                String lastStr=null;
+            
+                //count number of labels to display
+                //added version 1.1
+                java.util.ArrayList labels=new java.util.ArrayList();
+            
+                for(int yaxis=0;yaxis<5;yaxis++)
+                    {
+                
+                        str=""+(int)label;//formatter.format(label);
+                        int strLen=str.length();
+                        for(int i=strLen;i<7;i++){
+                            str=" "+str;
+                        }
+                        if(lastStr==null || lastStr.equals(str)==false){
+                            labels.add(str);
+                        }
+                
+                        lastStr=str;
+                
+                        label-=inc;
+                    }
+                int nLabels=labels.size();
+                        
+                for(int yaxis=0;yaxis<nLabels;yaxis++)
+                    {
+                        g.setColor( axisColor );
+                        str=labels.get(yaxis).toString();
+                        g.drawString( str,xoff-45,(int)y+6);
+                        g.setColor( gridColor );
+                        if(yaxis<nLabels-1){
+                            g.drawLine(xoff-3,(int)y,xoff+wid,(int)y);  
+                        }
+                
+                        y+=(hi/(nLabels-1));
+                
+                    }
+            
+            }
+        else
+            {
+                g.drawString("no data",legx,legy);
+            }
+    }
+    
+    //Private impl
+    synchronized private void calcFactors()
+    {
+        int tsize=0;
+        double maxdate=0;
+        double mindate=Double.MAX_VALUE;
+        //reset MaxValue
+        maxValue=0.0;
+        maxSize=0;
+        minValue=Double.MAX_VALUE;
+        int endpos;
+        int startpos;
+        //find the largest dataset, and max dates
+        for(int i=0;i<dsCounter;i++)
+            {
+                if(dataSet[i].isVisible)
+                    {
+                        tsize=dataSet[i].getSize();
+                        if(tsize>maxSize)
+                            {
+                                maxSize=tsize;
+                                maxId=i;
+                            }
+                        endpos= tsize-1;
+                        if(endAt!=-1 && endAt<tsize)
+                            endpos=endAt;
+                
+                        startpos=startAt;
+                        if(startpos>=tsize)
+                            startpos=endpos;
+                
+                        int md=dataSet[ i ].date[ endpos ];
+                        int mind=dataSet[ i ].date[ startpos ];
+                        maxdate=md>maxdate?md:maxdate;
+                        mindate=mind<mindate?mind:mindate;
+                    }
+            }
+        if(endAt==-1)//first time
+            {
+                endAt=maxSize;
+            }
+        //allocate xTagPos here - NOT in paint()
+        xTagPosition= new int[maxSize];
+        //calculate the factors
+        for( int i=0;i<dsCounter;i++)
+            {
+                if(dataSet[i].isVisible)
+                    {
+                        int npoints=dataSet[i].getSize();
+                        for(int j=startAt;j<npoints && j<=endAt;j++)
+                            {
+                                double date=dataSet[i].date[j];
+                                dataSet[i].pointFactor[j]=
+                                    (date-mindate)/(maxdate-mindate);
+                                double val=dataSet[i].getPoint(j);
+                                maxValue=maxValue>val ? maxValue : val;
+                                minValue=val<minValue?val:minValue;
+                            }
+                    }
+            }
+        maxValue=adjustMax(maxValue);
+        minValue=adjustMin(minValue);
+    
+    }
+    private double adjustMax(double d)
+    {
+        double x=d;
+        if(x>0)
+            x+=0.05;
+        else
+            x-=0.05;
+        
+        return Math.rint(x*10)/10;
+    }
+    private double adjustMin(double d)
+    {
+        double x=d;
+        x-=0.05;
+        return Math.rint(x*10)/10;
+    }
+    //private impl classes internal double-dispatch
+    private interface IdDrawer
+    {
+        public void draw(final Graphics g,final int xpos,final int ypos);
+    }
+    
+    private class SqDrawer
+        implements IdDrawer
+    {
+        private int drawMode;
+        
+        public SqDrawer(final int mode)
+        {
+            drawMode=mode;
+        }
+        public void draw(final Graphics g,final int xpos,final int ypos)
+        {
+            if(drawMode==SOLID)
+                g.fillRect(xpos+1,ypos,4,4);
+            else
+                g.drawRect(xpos,ypos,4,4);
+        }
+    }
+    private class RoundRectDrawer
+        implements IdDrawer
+    {
+        private int drawMode;
+        
+        public RoundRectDrawer(final int mode)
+        {
+            drawMode=mode;
+        }
+        public void draw(final Graphics g,final int xpos,final int ypos)
+        {
+            if(drawMode==SOLID)
+                g.fillRoundRect(xpos+1,ypos,4,4,4,4);
+            else
+                g.drawRoundRect(xpos+1,ypos,4,4,4,4);
+        }
+    }
+    private class XDrawer
+        implements IdDrawer
+    {
+        public XDrawer()
+        {
+        }
+        public void draw(final Graphics g,final int xpos,final int ypos)
+        {
+            int os=2;
+            
+            g.drawLine(xpos-2+os,ypos-2+os,xpos+2+os,ypos+2+os);
+            g.drawLine(xpos+2+os,ypos-2+os,xpos-2+os,ypos+2+os);
+        }
+    }
+    //zoomer
+    private int dragStartXPos;
+    private int dragStartYPos;
+    private int lastXPos;
+    //private int lastYPos;
+    private boolean isDragging=false;
+    
+    private void setZoomEndPos(int xend)
+    {
+        Dimension d=getSize();
+        int s=dragStartXPos;
+        int e=xend;
+        if(s>e) //right to lseft highlight
+            {
+                s=xend;
+                e=dragStartXPos;
+            }
+        int oldstart=startAt;
+        int oldend=endAt;
+        calcNewStartAndEnd(s,e);
+        if(startAt<endAt)
+            {
+                calcFactors(); //recalculate the factors
+            }
+        else
+            {
+                startAt=oldstart;
+                endAt=oldend;
+            }
+        //
+        /*
+          double startFactor=(double)s/(double)d.width;
+          double endFactor=(double)e/(double)d.width;
+          int oldstart=startAt;
+          int oldend=endAt;
+          startAt+=(int)(endAt*startFactor);
+          endAt=(int)(endAt*endFactor);
+          if(startAt<endAt)
+          {
+          calcFactors(); //recalculate the factors
+          }
+          else
+          {
+          startAt=oldstart;
+          endAt=oldend;
+          }
+        */
+        repaint();
+    }
+    //calculate startAt, endAt based xTagPositions[]
+    private void calcNewStartAndEnd(int xStart,int xEnd)
+    {
+        //TEST
+        //System.out.println("xStart="+xStart+" End="+xEnd);
+        
+        int lowerBound=0;
+        int upperBound=0;
+        int xLower=0;
+        int xHigher=0;
+        int lowestXBound=Integer.MAX_VALUE;
+        int highestXBound=Integer.MAX_VALUE;
+        
+        int size=xTagPosition.length;
+        for(int i=0;i<size;i++)
+            {
+                if(xTagPosition[i]!=-1)
+                    {
+                
+                        xLower=xStart-xTagPosition[i];
+                        xLower=Math.abs(xLower);
+                        if(xLower<lowestXBound)
+                            {
+                                lowestXBound=xLower;
+                                lowerBound=i;
+                            }
+                        xHigher=xEnd-xTagPosition[i];
+                        xHigher=Math.abs(xHigher);
+                        if(xHigher<highestXBound)
+                            {
+                                highestXBound=xHigher;
+                                upperBound=i;
+                            }
+                    }
+            }
+        startAt=lowerBound;
+        endAt=upperBound;
+        
+    }
+    
+}
+