Downloads

Java Timeline
Saturday, 21 June 2008 01:00

Description: A simple Java timeline component
License: GNU/GPL
Download: click here

After searching the web for a timeline implementation in java with no success, I decided to create my own. It might not be the cream of the crop and maybe there are some nasty bugs but nevertheless I would like to share it with you. It is basically a modified JTable and looks like this:

You can download the timeline here.

The sourcecode of the timeline:

 
/*
 * A simple java timeline component
 * Copyright (C) 2008  Michael Netter
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 */
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
 
public class TimeLine extends JTable{
  private final int ROWDISTANCE = 5;    // the distance between the rows
  private final int DATEROWHEIGHT = 20;   // height of the date row
  private ImageTableModel itm;      // table model
  private int maxRowHeight = 20;      // maximum row height
  private boolean autoSizeRowHeight;    // resize row height automatically
  /**
   * Generates a new TimeLine object
   * @param rows  - number of rows of the default grid
   * @param columns - number of columns of the default grid
   * @param autoSizeRowHeight  - auto adjust row height to the max row height
   * @param autoSizeColumnWidth - auto adjust column width
   */
  public TimeLine(int rows, int columns, boolean autoSizeRowHeight){
    this.autoSizeRowHeight = autoSizeRowHeight;
    setTimeLineLookAndFeel();
    itm = initModel();
    itm.initGrid(rows, columns);
    setModel(itm);  // set the table model
  }
  /**
   * Adds a new column to the table
   * @param date - The column name as java.util.Date
   * @param columndata - The row values for this column. 
   * Object[0] contains the cell text, object[1] contains the ImageIcon
   */
  public void addColumn(java.util.Date date, ArrayList<Object[]> columndata){
    itm.addColumn(convertDate(date), columndata);
    // adjust row height
    if(autoSizeRowHeight){
      calcMaxRowHeight(columndata);      
    }    
  }
 
  /**
   * Adds a new column to the table
   * @param columnName - The column name
   * @param columndata - The row values for this column. 
   * Object[0] contains the cell text, object[1] contains the ImageIcon
   */
  public void addColumn(String columnName, ArrayList<Object[]> columndata){
    itm.addColumn(columnName, columndata);
    // adjust row height
    if(autoSizeRowHeight){
      calcMaxRowHeight(columndata);      
    }    
  }
 
  /**
   * Utility function to set the scroll pane
   */
  public int getRowCount(){
    return itm.getRowCount();
  }
 
  /**
   * Utility function to set the scroll pane
   */
  public int getColumnCount(){
    return itm.getColumnCount();
  }
 
  /**
   * @return ImageTableModel - a new ImageTableModel
   */
  private ImageTableModel initModel(){
    return new ImageTableModel();
  }
  /**
   * Set some JTable properties to make it 
   * look more like a timeline
   */
  private void setTimeLineLookAndFeel(){
    this.getTableHeader().setReorderingAllowed(false);
    this.setCellSelectionEnabled(true); 
    this.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    this.setIntercellSpacing(new Dimension(0,0));
    this.setShowGrid(false);    
    this.setTableHeader(null);
    this.setDefaultRenderer(Object.class, new MyCellRenderer(this)); 
    this.addMouseMotionListener(new MyMouseMotionAdapter(this));
  }
  /**
   * Converts a java.util.Date object to
   * a readable format
   * @param date - a java.util.Date object
   * @return String - Date as a String
   */
  private String convertDate(java.util.Date date){
    SimpleDateFormat df = new SimpleDateFormat("dd.MM.yyyy");        
    return df.format(date);
  }
  /**
   * Calculates the maximum row height
   * @param list - the column list
   */
  private void calcMaxRowHeight(ArrayList<Object[]> list){
    int max = 0;
    for(int i=0;i<list.size();i++){
      int labelheight = 0;
      int imageheight = 0;
      if(list.get(i) != null){
        if(list.get(i)[0] != null){
          if(list.get(i)[0] instanceof String){
            labelheight = new JLabel((String)list.get(i)[0]).getPreferredSize().height;
          }
        }
        if(list.get(i)[1] != null){
          if(list.get(i)[1] instanceof ImageIcon){
            imageheight = ((ImageIcon)list.get(i)[1]).getIconHeight();
          }
        }
      }
      int height = labelheight + imageheight + ROWDISTANCE; 
      if(maxRowHeight < height){
        maxRowHeight = height;
      }      
    }
  }
  @Override
  public void paintComponent(Graphics g){
    super.paintComponent(g);
    g.setColor( Color.BLACK );
    int y = getRowHeight(0)*(itm.getRowCount()-1) - 1;    
    g.drawLine(0, y, getSize().width, y);
  }
  @Override
  public Component prepareRenderer(TableCellRenderer renderer, int row, int column){
    Component c = super.prepareRenderer(renderer, row, column);  
    if(row == itm.getRowCount()-1){
      c.setBackground(java.awt.SystemColor.control);
    }
    else if( !this.isCellSelected(row, column)){
      c.setBackground(column % 2 != 0 ? new Color(241, 245, 250) : null);
    }    
    return c;
  }  
 
  @Override
  /**
   * makes the date column look like a table header
   */ 
  public void changeSelection(int row, int column, boolean toggle, boolean extend){
    Object o = getValueAt(row, column); 
    if (o != null &amp;&amp; row != itm.getRowCount()-1){
      super.changeSelection(row, column, toggle, extend);    
    }    
  }
 
  /**
   *   MouseMotionAdapter for this table
   *   Used to set the mouse cursor only
   */
  class MyMouseMotionAdapter extends MouseMotionAdapter{
    private TimeLine theTimeLine;
    public MyMouseMotionAdapter(TimeLine table){
      this.theTimeLine = table;
    }    
    public void mouseMoved(MouseEvent me) {
      int row = rowAtPoint( me.getPoint() );
      if(row < theTimeLine.itm.getRowCount()-1){
        theTimeLine.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      }
      else{
        theTimeLine.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
      }
    }    
  }
 
  /**
   *  Cell renderer for this table
   */
  class MyCellRenderer extends DefaultTableCellRenderer{
    private TimeLine theTimeLine;
    public MyCellRenderer(TimeLine timeline){
      this.theTimeLine = timeline;
      setHorizontalAlignment( CENTER );
      setVerticalTextPosition(JLabel.BOTTOM);
      setHorizontalTextPosition(JLabel.CENTER);      
    }
    public void setValue(Object value) {
      Object[] a = (Object[])value;
      if(value == null){      // empty cell
        setIcon(null);
        super.setValue(value);        
      }
      else if (a[1] instanceof Icon) {    // normal cell with text and image
        setIcon((Icon) a[1]);
        setText((String)a[0]);
        this.setAlignmentY(JLabel.BOTTOM_ALIGNMENT);
        theTimeLine.setRowHeight(maxRowHeight);
        theTimeLine.setRowHeight(itm.getRowCount()-1,DATEROWHEIGHT);
        //this.setBorder(new RoundBorder(Color.red,Color.green));
      } 
      else if(a[1] == null &amp;&amp; a[0] instanceof String){
        setText((String)a[0]);
        setIcon(null);
      }
      else {
        setIcon(null);
        super.setValue(value);
      }
    }
  }
 
 
  /**
   * The table model for this timeline   *
   */
  class ImageTableModel extends AbstractTableModel{
    private ArrayList<String> columnnames; // holds the column names
    private ArrayList<ArrayList<Object[]>> data; // holds the table data
    private int maxRowCount;  
    private int columnCursor; // points on the current column
    public ImageTableModel(){
      columnnames = new ArrayList<String>();
      data = new ArrayList<ArrayList<Object[]>>();
      maxRowCount = 0;
      columnCursor = 0;
    }
    public Object getValueAt(int row, int column){
      if (data.get(column).size()-1<row){
        return null;
      }
      else{
        return data.get(column).get(row);
      }      
    } 
    public int getRowCount(){
      return maxRowCount;
    } 
    public int getColumnCount(){
      return columnnames.size();
    }
    public String getColumnName( int columnIndex ){
      return columnnames.get(columnIndex);
    }
    /**
     * Adds a new column to the table
     * @param columnName - The column name
     * @param columndata - The row values for this column.
     */
    public void addColumn(String columnName, ArrayList<Object[]> columndata){
      if(columnCursor >= columnnames.size()){
        columnnames.add(columnName);
        data.add(rotateFillList(columnName,columndata));
      }
      else{
        columnnames.set(columnCursor, columnName);
        data.set(columnCursor, rotateFillList(columnName,columndata));
      }
      SwingUtilities.invokeLater (new Runnable(){    // fixes a nasty java vector bug
        public void run ()  {
          fireTableStructureChanged();
          fireTableDataChanged();  
        }
      });
      columnCursor++;
    }
    public void initGrid(int rows, int columns){
      for(int i=0;i<columns;i++){
        ArrayList<Object[]> newdata = new ArrayList<Object[]>();
        for(int j=0;j<rows;j++){
          newdata.add(null);
        }
        columnnames.add(String.valueOf(i));
        data.add(newdata);
        maxRowCount = rows;
      }
      SwingUtilities.invokeLater (new Runnable(){    // fixes a nasty java vector bug
        public void run ()  {
          fireTableStructureChanged();
          fireTableDataChanged();  
        }
      });
    }
    /**
     * Rotates the list. If list.size() is smaller than
     * maxRowCount the list if filled with null values
     * This generates the bottom up effect
     * @param columnName - The column name
     * @param list
     * @return list
     */
    private ArrayList<Object[]> rotateFillList(String columnName, ArrayList<Object[]> list){
      list.add(0,new Object[]{columnName,null});  // set column name to be on the bottom 
      if(maxRowCount < list.size()){
        // adjust all rows to the new maxRowCount
        maxRowCount = list.size();
        for(int i=0;i<data.size();i++){
          int diff = maxRowCount - data.get(i).size();
          for(int j=0;j<diff;j++){
            data.get(i).add(0,null);
          }
        }        
      }
      else {  // fill with null values
        int diff = maxRowCount - list.size();
        for(int i=0;i<diff;i++){
          list.add(null);
        }
      }
      ArrayList<Object[]> rotatedList = new ArrayList<Object[]>();
      for(int i= list.size()-1;i>=0;i--){    // rotate list
        rotatedList.add(list.get(i));
      }
      return rotatedList;
    }
  }  
}
 

 

 
Comments (1)
1 Tuesday, 05 May 2009 08:37
Dima
Create!
It should be useful!

Add your comment

Your name:
Comment:
  The word for verification. Lowercase letters only with no spaces.
Word verification: