/*
* Natural Family Planning for J2ME
* Copyright (C) 2007 Krzysztof Rzymkowski <rzymek@users.sourceforge.net>
*
* 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.
*
*/
package net.sf.nfp.mini.view;
import java.util.Vector;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import net.sf.log.mobile.Log;
import net.sf.nfp.mini.data.MucusRegistry;
import net.sf.nfp.mini.data.Observation;
import net.sf.nfp.mini.misc.Color;
import net.sf.nfp.mini.misc.Interval;
import net.sf.nfp.mini.misc.Transformation;
import net.sf.nfp.mini.misc.Utils;
public class GraphView extends PropagatingCanvas {
private static final int DISTURBED_CROSS_COLOR = 0x800000;
private static final int DOT_RADIUS = 2;
private static final int BACKGROUND_COLORS[] = { 0x222222, 0x666666, 0xffffff, 0x666666 };
private int margin;
private Font font;
private Transformation tx;
private Transformation ty;
private Transformation ity;
private int offset;
private int selectedDay;
private final int height;
private final int detailsHeight;
private int titleColor = Color.WHITE;
private String title = "";
private Vector observations;
private int backgroundStripeHeight;
public static class AdditionalInfo {
public static final byte TEMP_LINE = 1;
public static final byte DAY_LINE = 2;
public static final byte DAY = 3;
public static final byte INTERVAL = 4;
public int a;
public int b;
public int color;
public byte type;
};
public Vector/*AdditionalInfo*/ additional = new Vector();
public GraphView() {
offset = 6 * DOT_RADIUS;
selectedDay = 0;
showNotify();
font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
margin = font.stringWidth("00.0");
detailsHeight = font.getHeight() + 2;
Log.log("font.getHeight()="+font.getHeight());
Log.log("detailsHeight="+detailsHeight );
height = getHeight() - detailsHeight;
tx = new Transformation(0, (getWidth() - margin) / 6, margin,
getWidth());
ty = new Transformation(Observation.MIN_TEMP, Observation.MAX_TEMP,
height, 0);
ity = new Transformation(0, height, Observation.MAX_TEMP,
Observation.MIN_TEMP);
backgroundStripeHeight = Math.abs(ty.get(Observation.MIN_TEMP)
- ty.get(Observation.MIN_TEMP + 10));
}
protected void safePaint(Graphics g) {
Log.log("GraphView.safePaint()");
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.BLACK);
g.setFont(font);
drawBackground(g);
drawAdditionalInfo(g);
drawData(g);
drawDetails(g);
drawTemperatureLabels(g);
drawTitle(g);
}
private void drawAdditionalInfo(Graphics g) {
for(int i=0;i<additional.size();++i) {
AdditionalInfo info = (AdditionalInfo) additional.elementAt(i);
g.setColor(info.color);
int x,y;
switch(info.type) {
case AdditionalInfo.DAY:
x = tx.get(info.a);
y = ty.get(info.b);
drawDot(g, x, y, DOT_RADIUS + 4);
break;
case AdditionalInfo.DAY_LINE:
x = tx.get(info.a);
g.drawLine(x, 0, x, height);
break;
case AdditionalInfo.INTERVAL:
drawInterval(g, new Interval(info.a, info.b), info.color, false);
break;
case AdditionalInfo.TEMP_LINE:
y = ty.get(info.a);
g.drawLine(margin, y, getWidth(), y);
break;
}
}
}
private void drawTitle(Graphics g) {
Log.log("GraphView.drawTitle()");
int x = getWidth() - font.stringWidth(title) - 2;
int width = font.stringWidth(title) + 2;
int height = font.getHeight() + 2;
g.setColor(titleColor);
g.fillRect(x, 0, width, height);
g.setColor(Color.BLACK);
g.drawRoundRect(x, 0, width, height, 10, 10);
g.drawString(title, x + 1, 1, 20);
}
private void drawData(Graphics g) {
if(observations == null)
return;
int prevX = 0;
int prevY = 0;
boolean drawLine = false;
boolean lastIsBleeding = true;
for (int day = 0; day < observations.size(); day++) {
Observation observation = (Observation) observations.elementAt(day);
if(observation == null) {
if(lastIsBleeding)
drawInterval(g, new Interval(day, day+1), 0xff6060, true);
continue;
}
if(observation.isBleeding()) {
drawInterval(g, new Interval(day, day+1), 0x800000, true);
if(day == selectedDay)
drawInterval(g, new Interval(day, day+1), 0xff0000, true);
continue;
}else lastIsBleeding = false;
if(!observation.hasTemperature())
continue;
int x = tx.get(day) + offset;
int y = ty.get(observation.getTemperature());
g.setColor(Color.BLACK);
if (drawLine)
g.drawLine(prevX + DOT_RADIUS, prevY, x - DOT_RADIUS, y);
if (day == selectedDay) {
g.setColor(Color.BLACK);
drawDot(g, x, y, DOT_RADIUS + 2);
}
setMucusColor(g, observation);
drawDot(g, x, y);
g.setColor(Color.BLACK);
fillDot(g, x, y);
if(observation.isDisturbed()) {
//draw a red cross
g.setColor(DISTURBED_CROSS_COLOR);
int r = DOT_RADIUS+2;
g.drawLine(x - r, y - r, x + r, y + r);
g.drawLine(x - r, y + r, x + r, y - r);
}
prevX = x;
prevY = y;
drawLine = true;
}
}
private void setMucusColor(Graphics g, Observation observation) {
g.setColor(MucusRegistry.MUCUS_COLORS[observation.getMucus().getCategory()]);
}
private static void drawDot(Graphics g, int x, int y) {
drawDot(g, x, y, DOT_RADIUS);
}
private static void drawDot(Graphics g, int x, int y, int radius) {
g.fillArc(x - radius, y - radius, 2 * radius, 2 * radius, 0, 360);
}
private static void fillDot(Graphics g, int x, int y) {
g.drawArc(x - DOT_RADIUS, y - DOT_RADIUS, 2 * DOT_RADIUS,
2 * DOT_RADIUS, 0, 360);
}
private void drawTemperatureLabels(Graphics g) {
Log.log("GraphView.drawTemperatureLabels()");
g.setColor(Color.WHITE);
g.fillRect(0, 0, margin, height);
g.setColor(0);
g.drawLine(margin, 0, margin, height);
final int tempStart = Observation.MIN_TEMP + 10;
final int tempEnd = Observation.MAX_TEMP - 10;
final int step = 10;
int prevY = Integer.MAX_VALUE;
for (int temp = tempStart; temp <= tempEnd; temp += step) {
int y = ty.get(temp - step/2);
if(prevY - font.getHeight() < y)
continue;
String tempString = Utils.formatTemperature(temp)
.substring(0, "00.0".length());
g.drawString(tempString, 0, y, Graphics.BOTTOM | Graphics.LEFT);
prevY = y;
}
}
private void drawDetails(Graphics g) {
g.setColor(Color.BLACK);
g.drawLine(0, height-1, getWidth(), height-1);
if (observations ==null || observations.isEmpty()) {
return;
} else {
try {
Observation observation = (Observation) observations.elementAt(selectedDay);
if(observation == null)
return;
if(observation.isBleeding())
g.setColor(Color.RED);
else
setMucusColor(g, observation);
g.fillRect(0, height, getWidth(), detailsHeight);
g.setColor(Color.BLACK);
String detailString = Utils.zeroPadded(selectedDay + 1, 2) + ": "
+ observation;
Log.log("GraphView.drawDetails()");
g.drawString(detailString, 0, getHeight() - 1, Graphics.BOTTOM
| Graphics.LEFT);
}catch(Exception ex){
ex.printStackTrace();
return;
}
}
}
private void drawBackground(Graphics g) {
Log.log("GraphView.drawBackground()");
drawInterval(g, Interval.MAX, Color.WHITE, false);
}
private void drawInterval(Graphics g, Interval interval, int color, boolean mini) {
if (interval == null)
return;
int colorIndex = 0;
int boxStart;
int boxWidth;
if (Interval.MAX.equals(interval)) {
boxStart = margin;
boxWidth = getWidth() - margin;
} else {
boxStart = tx.get(interval.getStart());
boxWidth = tx.get(interval.getEnd()) - boxStart;
boxStart += offset;
}
int tempStart = Observation.MIN_TEMP;
int tempEnd = Observation.MAX_TEMP;
if (mini) {
tempStart += 40;
tempEnd -= 40;
colorIndex = 4 % BACKGROUND_COLORS.length;
}
final int step = 10;
for (int tempStep = tempStart; tempStep <= tempEnd; tempStep += step) {
colorIndex = (colorIndex + 1) % BACKGROUND_COLORS.length;
int c = BACKGROUND_COLORS[colorIndex];
c = Color.blend(new Color(c), new Color(color)).getRGB();
g.setColor(c);
g.fillRect(boxStart, ty.get(tempStep + step), boxWidth, backgroundStripeHeight);
}
}
/**
* @param direction one of GraphView.DIRECTION_LEFT or GraphView.DIRECTION_RIGHT
*/
public void selectNextDot(int direction) {
if(observations == null)
return;
int next = selectedDay + direction;
while (0 <= next && next < observations.size()
&& observations.elementAt(next) == null)
next += direction;
if (0 <= next && next < observations.size())
selectedDay = next;
setSelectedDay(selectedDay);
}
public void setSelectedDay(int day) {
selectedDay = day;
while (tx.get(selectedDay + DOT_RADIUS) + offset > getWidth())
offset -= 3 * DOT_RADIUS;
while (tx.get(selectedDay - DOT_RADIUS) + offset < margin)
offset += 3 * DOT_RADIUS;
repaint();
}
public void setObservations(Vector observations) {
this.observations = observations;
}
public void setTitle(String title) {
//#ifndef midp1.0
super.setTitle(title);
//#endif
this.title = title;
}
public Observation getSelectedObservation() {
if(observations == null || selectedDay > observations.size())
return null;
return (Observation) observations.elementAt(selectedDay);
}
protected void showNotify() {
//#ifndef midp1.0
setFullScreenMode(true);
//#endif
}
}