package timeflow.vis.calendar;
import timeflow.data.time.*;
import timeflow.model.Display;
import timeflow.vis.*;
import java.util.*;
import java.awt.*;
import java.text.*;
public class Grid {
TimeUnit rowUnit, columnUnit;
RoughTime startRow, endRow;
Interval interval;
HashMap<Long, GridCell> cells;
int[] screenGridX;
int cellHeight=80, cellWidth, numCols, numRows;
Rectangle bounds=new Rectangle();
int dy;
static final DateFormat dayOfWeek=new SimpleDateFormat("EEE");
static final DateFormat month=new SimpleDateFormat("MMM d");
static final String[] day={"SUN", "MON", "TUES", "WED", "THURS", "FRI", "SAT"};
Grid(TimeUnit rowUnit, TimeUnit columnUnit, Interval interval)
{
this.rowUnit=rowUnit;
this.columnUnit=columnUnit;
numCols=columnUnit.numUnitsIn(rowUnit);
setInterval(interval);
}
public void setDY(int dy)
{
this.dy=dy;
}
public int getCalendarHeight()
{
return bounds.height+bounds.y+20;
}
public RoughTime getTime(int x, int y)
{
y+=dy;
if (!bounds.contains(x,y))
return null;
// find grid coordinates.
int gridX=(x-bounds.x)/cellWidth;
int gridY=(y-bounds.y)/cellHeight;
return startRow.plus(rowUnit, gridY).plus(columnUnit, gridX);
}
public double getScrollFraction()
{
double x= (getFirstDrawnTime().getTime()-startRow.getTime())/(double)
(endRow.getTime()-startRow.getTime());
if (x<0)
return 0;
if (x>1)
return 1;
return x;
}
public RoughTime getFirstDrawnTime()
{
int gridY=(dy-bounds.y)/cellHeight;
return startRow.plus(rowUnit, gridY);
}
private Point getGridCorner(long timestamp)
{
int diff=(int)columnUnit.difference(timestamp, startRow.getTime());
int gridX=diff%numCols;
int gridY=diff/numCols;
return new Point(gridX, gridY);
}
public Rectangle getCell(long timestamp)
{
Point p=getGridCorner(timestamp);
return new Rectangle(bounds.x+p.x*cellWidth, bounds.y+p.y*cellHeight-dy,
cellWidth, cellHeight);
}
void setInterval(Interval interval)
{
this.interval=interval;
startRow=rowUnit.roundDown(interval.start);
endRow=rowUnit.roundDown(interval.end);
numRows=1+(int)(rowUnit.difference(endRow.getTime(), startRow.getTime()));
// the next line fixes a problem with multi-century data sets.
// it works, but there's probably a better way to do this :-)
if (numRows>50 && rowUnit.getRoughSize()>=TimeUnit.YEAR.getRoughSize())
numRows++;
}
void makeCells(java.util.List<VisualAct> visualActs)
{
cells=new HashMap<Long, GridCell>();
for (VisualAct v: visualActs)
{
if (v.getStart()==null)
continue;
long timestamp=v.getStart().getTime();
RoughTime timeKey=columnUnit.roundDown(timestamp);
GridCell cell=cells.get(timeKey.getTime());
if (cell==null)
{
cell=new GridCell(timeKey);
cells.put(timeKey.getTime(), cell);
Point p=getGridCorner(timestamp);
cell.gridX=p.x;
cell.gridY=p.y;
}
cell.visualActs.add(v);
}
}
void render(Graphics2D g, Display display, Rectangle screenBounds, CalendarVisuals visuals,
Collection<Mouseover> objectLocations)
{
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int left=110, right=20;
int padY=50;
boolean shouldLabel=visuals.drawStyle==CalendarVisuals.DrawStyle.LABEL;
cellWidth=(screenBounds.width-left-right)/numCols;
boolean fitTight=visuals.fitStyle==CalendarVisuals.FitStyle.TIGHT;
int idealHeight= fitTight ? 12 : Display.CALENDAR_CELL_HEIGHT;
cellHeight=Math.max(idealHeight, (screenBounds.height-padY-10)/numRows);
this.bounds.setBounds(left, padY, numCols*cellWidth, numRows*cellHeight);
g.setColor(new Color(240,240,240));//Color.white);
g.fill(screenBounds);
g.setColor(new Color(245,245,245));
g.drawRect(bounds.x, bounds.y-dy, bounds.width, bounds.height);
g.setFont(display.bold());
// draw vertical grid lines.
Color gridColor=new Color(220,220,220);
for (int i=0; i<=numCols; i++)
{
int x=bounds.x+i*cellWidth;
g.setColor(gridColor);
g.drawLine(x,bounds.y-dy,x,bounds.y+bounds.height-dy);
if (rowUnit==TimeUnit.WEEK && i<7)
{
g.setColor(Color.gray);
g.drawString(day[i], x, bounds.y-dy-6);
}
}
// horizontal grid lines.
RoughTime labelTime=startRow.copy();
int lastLabelY=-100;
int lastYear=-1000000;
FontMetrics fm=display.boldFontMetrics();
int skipped=0;
for (int i=0; i<numRows; i++)
{
int y=bounds.y+i*cellHeight;
if (y-dy>-50)
{
if (skipped>0)
{
rowUnit.addTo(labelTime, skipped);
skipped=0;
}
g.setColor(gridColor);
g.drawLine(bounds.x,y-dy,bounds.x+bounds.width,y-dy);
if (y-lastLabelY>30 || lastLabelY<0)
{
String label=null;
if (rowUnit==TimeUnit.WEEK)
{
int year=TimeUtils.cal(labelTime.getTime()).get(Calendar.YEAR);
if (year!=lastYear)
label=labelTime.format();
else
label=month.format(labelTime.toDate());
lastYear=year;
}
else
label=labelTime.format();
g.setColor(Color.gray);
g.drawString(label, bounds.x-fm.stringWidth(label)-15, y+15-dy);
lastLabelY=y;
}
if (y-dy>screenBounds.height)
break;
// now draw, in gray, the labels for each of the boxes.
if (!fitTight)
{
RoughTime gridLabel=labelTime.copy();
int labelH=13;
for (int j=0; j<numCols; j++)
{
g.setColor(Color.gray);
g.setFont(display.bold());
String label=columnUnit.format(gridLabel.toDate());
g.drawString(label, bounds.x+j*cellWidth+3, y-dy+labelH);
columnUnit.addTo(gridLabel);
}
}
rowUnit.addTo(labelTime);
}
else
skipped++;
}
// draw a frame around the whole thing.
g.setColor(Color.darkGray);
g.drawRect(bounds.x, bounds.y-dy, bounds.width, bounds.height);
// draw backgrounds
for (GridCell cell: cells.values())
{
// are any visible?
boolean visible=false;
for (VisualAct v: cell.visualActs)
{
if (v.isVisible())
{
visible=true;
break;
}
}
int cx=bounds.x+cell.gridX*cellWidth;
int cy=bounds.y+cell.gridY*cellHeight-dy;
if (cy<screenBounds.y-50 || cy>screenBounds.y+screenBounds.height+50)
continue;
// label top of cell.
int labelH=0;
g.setColor(new Color(240,240,240));
if (visible)
{
g.setColor(Color.white);
g.fillRect(cx+1,cy+1,cellWidth-1,cellHeight-1);
}
if (cellHeight>42)
{
labelH=13;
g.setColor(Color.darkGray);
g.setFont(display.bold());
String label=columnUnit.format(cell.time.toDate());
g.drawString(label, cx+3, cy+labelH);
}
}
// draw items.
int mx=10, my=shouldLabel ? 18 : 10;
for (GridCell cell: cells.values())
{
int cx=bounds.x+cell.gridX*cellWidth;
int cy=bounds.y+cell.gridY*cellHeight-dy;
if (cy<screenBounds.y-50 || cy>screenBounds.y+screenBounds.height+50)
continue;
// label top of cell.
int labelH=cellHeight>42 ? 13 : 0;
// now draw the items in the cell.
// old, non-aggregation code:
// START AGGREGATION CODE
ArrayList<VisualAct> visibleActs=new ArrayList<VisualAct>();
for (VisualAct v: cell.visualActs)
if (v.isVisible())
visibleActs.add(v);
Iterator<VisualAct> vacts=
VisualActFactory.makeEmFit(visuals.model, visibleActs, new Rectangle(cx, cy, cellWidth, cellHeight)).iterator();
// END AGGREGATION CODE
int leftX=6;
int cdx=leftX;
int topDotY=Math.min(labelH+16,cellHeight/2);
int cdy=topDotY;
while (vacts.hasNext())
{
VisualAct v=vacts.next();
if (!v.isVisible())
continue;
// set x,y, room to right.
int x=cx+cdx;
int y=cy+cdy;
int space=cellWidth-20;
v.setX(x);
v.setY(y);
v.setSpaceToRight(space);
Mouseover o=v.draw(g, new Rectangle(cx+1,cy+labelH+1,cellWidth-2, cellHeight-2-labelH),
bounds, display, shouldLabel, false);
if (o!=null)
objectLocations.add(o);
// go to next location. if we're labeling, we do this vertically.
// otherwise, left-to-right, then top-to-bottom.
if (shouldLabel)
{
cdy+=my;
if (cdy>cellHeight-2-my)
{
g.drawString("...", x,y+my);
break;
}
}
else
{
cdx+=mx;
if (cdx>cellWidth-mx/2-2 && vacts.hasNext())
{
cdx=leftX;
cdy+=my;
if (cdy>cellHeight-my/2)
{
break;
}
}
}
}
}
}
}