/****************************************************************************************
* Copyright (c) 2014 Michael Goldbach <michael@wildplot.com> *
* *
* 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 3 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, see <http://www.gnu.org/licenses/>. *
****************************************************************************************/
package com.wildplot.android.rendering;
import com.wildplot.android.rendering.graphics.wrapper.FontMetricsWrap;
import com.wildplot.android.rendering.graphics.wrapper.GraphicsWrap;
import com.wildplot.android.rendering.graphics.wrapper.RectangleWrap;
import com.wildplot.android.rendering.interfaces.Drawable;
import java.text.DecimalFormat;
/**
* This Class represents a Drawable x-axis
*
*
*/
public class YAxis implements Drawable {
private boolean mHasNumbersRotated = false;
private float maxTextWidth = 0;
private boolean isIntegerNumbering = false;
private boolean isOnRightSide = false;
private boolean isLog = false;
public boolean hasVariableLimits = true;
private boolean isAutoTic = false;
/**
* the offset of the x-axis to prevent drawing numbering on x-axis
*/
private double yOffset = 0;
/**
* offset to move axis left or right
*/
private double xOffset = 0;
private String name = "Y";
private boolean mHasName = false;
/**
* Format that is used to print numbers under markers
*/
private DecimalFormat df = new DecimalFormat( "##0.0#" );
private DecimalFormat dfScience = new DecimalFormat( "0.0###E0" );
private DecimalFormat dfInteger = new DecimalFormat( "#.#" );
private boolean isScientific = false;
/**
* the PlotSheet object the x-axis is drawn onto
*/
private PlotSheet plotSheet;
/**
* the start of x-axis marker, used for relative alignment of further marks
*/
private double ticStart = 0;
/**
* the space between two marks
*/
private double tic = 1;
/**
* the space between two minor marks
*/
private double minorTic= 0.5;
/**
* the estimated size between two mayor tics in auto tic mode
*/
private float pixelDistance = 25;
/**
* the estimated size between two minor tics in auto tic mode
*/
private float minorPixelDistance = 25;
/**
* start of drawn x-axis
*/
private double start = 0;
/**
* end of drawn x-axis
*/
private double end = 100;
/**
* true if the marker should be drawn into the direction above the axis
*/
private boolean markOnLeft = true;
/**
* true if the marker should be drawn into the direction under the axis
*/
private boolean markOnRight = true;
/**
* length of a marker in pixel, length is only for one side
*/
private float markerLength = 5;
private boolean isOnFrame = false;
/**
* Constructor for an Y-axis object
* @param plotSheet the sheet the axis will be drawn onto
* @param ticStart the start of the axis markers used for relative alignment of other markers
* @param tic the space between two markers
*/
public YAxis(PlotSheet plotSheet, double ticStart, double tic, double minorTic ) {
this.plotSheet = plotSheet;
this.ticStart = ticStart;
this.tic = tic;
this.minorTic=minorTic;
this.pixelDistance = (int)Math.round((plotSheet.getyRange()[1] - plotSheet.getyRange()[0])/tic);
}
/**
* Constructor for an Y-axis object this instance uses autocalculation of tics with a given pixeldistance
* @param plotSheet the sheet the axis will be drawn onto
* @param ticStart the start of the axis markers used for relative alignment of other markers
*/
public YAxis(PlotSheet plotSheet, double ticStart, float pixelDistance , float minorPixelDistance) {
this.plotSheet = plotSheet;
this.ticStart = ticStart;
this.pixelDistance = pixelDistance;
this.minorPixelDistance = minorPixelDistance;
this.isAutoTic = true;
}
/*
* (non-Javadoc)
* @see rendering.Drawable#paint(java.awt.Graphics)
*/
public void paint(GraphicsWrap g) {
RectangleWrap field = g.getClipBounds();
if(this.hasVariableLimits){
start = plotSheet.getyRange()[0];
end = plotSheet.getyRange()[1];
}
if(this.isOnFrame)
//xOffset = plotSheet.getxRange()[0];
if(this.isAutoTic) {
this.tic = plotSheet.ticsCalcY(pixelDistance, field);
double quotient = (pixelDistance*1.0)/(minorPixelDistance*1.0);
this.minorTic = (this.tic / Math.round(quotient));
}
else {
this.pixelDistance = Math.abs(plotSheet.yToGraphic(0, field) - plotSheet.yToGraphic(tic, field));
this.minorPixelDistance = Math.abs(plotSheet.yToGraphic(0, field) - plotSheet.yToGraphic(minorTic, field));
}
if(this.tic < 1e-2 || this.tic > 1e2)
this.isScientific = true;
//vertikale Linie
float[] coordStart = plotSheet.toGraphicPoint(xOffset, start, field);
float[] coordEnd = plotSheet.toGraphicPoint(xOffset, end, field);
if(!this.isOnFrame)
g.drawLine(coordStart[0], coordStart[1], coordEnd[0], coordEnd[1]);
drawMarkers(g);
drawMinorMarkers(g);
}
/**
* draw markers on the axis
* @param g graphic object used for drawing
*/
private void drawMarkers(GraphicsWrap g) {
RectangleWrap field = g.getClipBounds();
float cleanSpace = 17; // space in pixel that will be unmarked on the end of the axis for arrow and description
float tics = (int)((this.ticStart - this.start)/tic);
double leftStart = this.ticStart - this.tic*tics;
double logStart = 0;
if(this.isLog) {
logStart = Math.ceil(Math.log10(this.start));
leftStart = Math.pow(10, logStart++);
}
double currentY = leftStart;
while(currentY <= this.end) {
if((!this.isOnFrame &&plotSheet.yToGraphic(currentY, field) >= plotSheet.yToGraphic(this.end, field) +cleanSpace
&& plotSheet.yToGraphic(currentY, field) <= plotSheet.yToGraphic(this.start, field) - cleanSpace
&& plotSheet.yToGraphic(currentY, field) <= field.y + field.height - cleanSpace
&& plotSheet.yToGraphic(currentY, field) >= field.y + cleanSpace) ||
(this.isOnFrame && currentY <= this.plotSheet.getyRange()[1] &&
currentY >= this.plotSheet.getyRange()[0])){
if(this.markOnRight ) {
drawRightMarker(g, field, currentY);
}
if(this.markOnLeft ) {
drawLeftMarker(g, field, currentY);
}
if(!(Math.abs(currentY) < yOffset+0.001 && !this.isOnFrame)) {
if(isOnRightSide)
drawNumberingOnRightSide(g,field,currentY);
else
drawNumbering(g, field, currentY);
}
}
if(this.isLog) {
currentY = Math.pow(10, logStart++);
}else {
currentY += this.tic;
}
}
FontMetricsWrap fm = g.getFontMetrics( g.getFont() );
float width = fm.stringWidth(this.name);
//arrow
float[] arowheadPos = {plotSheet.xToGraphic(xOffset, field), (plotSheet.getyRange()[1] >= this.end)? plotSheet.yToGraphic( this.end, field): plotSheet.yToGraphic(plotSheet.getyRange()[1], field) };
if(!this.isOnFrame) {
g.drawLine(arowheadPos[0]-1, arowheadPos[1]+1, arowheadPos[0]-3, arowheadPos[1]+6);
g.drawLine(arowheadPos[0]+1, arowheadPos[1]+1, arowheadPos[0]+3, arowheadPos[1]+6);
if(mHasName)
g.drawString(this.name, arowheadPos[0]-13-width, arowheadPos[1] + 10);
} else {
// AffineTransform fontAT = new AffineTransform();
// Font oldFont = g.getFont();
// fontAT.rotate(-Math.PI/2.0);
// Font newFont = oldFont.deriveFont(fontAT);
// g.setFont(newFont);
float spacerValue = maxTextWidth;
if(mHasNumbersRotated)
spacerValue = g.getFontMetrics().getHeight();
g.save();
if(isOnRightSide) {
float[] middlePosition = {plotSheet.xToGraphic(xOffset, field), plotSheet.yToGraphic(0, field)};
g.rotate(90, middlePosition[0]+spacerValue*1.4f, field.height / 2 - width / 2);
if(mHasName)
g.drawString(this.name, middlePosition[0]+spacerValue*1.4f, field.height / 2 - width / 2);
}else {
float[] middlePosition = {plotSheet.xToGraphic(xOffset, field), plotSheet.yToGraphic(0, field)};
g.rotate(-90, middlePosition[0]-spacerValue*1.4f, field.height / 2 + width / 2);
if(mHasName)
g.drawString(this.name, middlePosition[0]-spacerValue*1.4f, field.height / 2 + width / 2);
}
g.restore();
// g.setFont(oldFont);
}
//axis name
}
/**
* draw number left to a marker
* @param g graphic object used for drawing
* @param field bounds of plot
* @param y position of number
*/
private void drawNumbering(GraphicsWrap g, RectangleWrap field, double y) {
if(this.tic < 1 && Math.abs(ticStart-y) < this.tic*this.tic)
y = ticStart;
float[] coordStart = plotSheet.toGraphicPoint(xOffset, y, field);
FontMetricsWrap fm = g.getFontMetrics( g.getFont() );
float fontHeight = fm.getHeight(true);
String font = df.format(y);
float width = fm.stringWidth(font);
if(this.isScientific && !isIntegerNumbering){
font = dfScience.format(y);
width = fm.stringWidth(font);
}else if(isIntegerNumbering){
font = dfInteger.format(y);
width = fm.stringWidth(font);
}
g.save();
if(mHasNumbersRotated) {
float[] middlePosition = {plotSheet.xToGraphic(xOffset, field), plotSheet.yToGraphic(y, field)};
g.rotate(-90, middlePosition[0]-width*0.1f, middlePosition[1]+ width / 2.0f);
g.drawString(font, middlePosition[0]-width*0.1f, middlePosition[1]+ width / 2.0f );
}else
g.drawString(font, Math.round(coordStart[0]-width*1.1f), Math.round(coordStart[1] + fontHeight*0.4f) );
g.restore();
if(width > maxTextWidth)
maxTextWidth = width;
//g.drawString(df.format(y), coordStart[0]-33, coordStart[1]+4);
}
/**
* draw number left to a marker
* @param g graphic object used for drawing
* @param field bounds of plot
* @param y position of number
*/
private void drawNumberingOnRightSide(GraphicsWrap g, RectangleWrap field, double y) {
if(this.tic < 1 && Math.abs(ticStart-y) < this.tic*this.tic)
y = ticStart;
float[] coordStart = plotSheet.toGraphicPoint(xOffset, y, field);
FontMetricsWrap fm = g.getFontMetrics( g.getFont() );
float fontHeight = fm.getHeight(true);
String font = df.format(y);
float width = fm.stringWidth(font);
g.save();
if(this.isScientific && !isIntegerNumbering){
font = dfScience.format(y);
width = fm.stringWidth(font);
}else if(isIntegerNumbering){
font = dfInteger.format(y);
width = fm.stringWidth(font);
}
if(mHasNumbersRotated) {
float[] middlePosition = {plotSheet.xToGraphic(xOffset, field), plotSheet.yToGraphic(y, field)};
g.rotate(90, middlePosition[0]+width*0.1f, middlePosition[1]- width / 2.0f);
g.drawString(font, middlePosition[0]+width*0.1f, middlePosition[1]- width / 2.0f );
}else
g.drawString(font, Math.round(coordStart[0] + width * 0.1f), Math.round(coordStart[1] + fontHeight * 0.4f));
g.restore();
if(width > maxTextWidth)
maxTextWidth = width;
//g.drawString(df.format(y), coordStart[0]-33, coordStart[1]+4);
}
/**
* draws an left marker
* @param g graphic object used for drawing
* @param field bounds of plot
* @param y position of marker
*/
private void drawLeftMarker(GraphicsWrap g, RectangleWrap field, double y){
float[] coordStart = plotSheet.toGraphicPoint(xOffset, y, field);
float[] coordEnd = {coordStart[0] - this.markerLength, coordStart[1]};
g.drawLine(coordStart[0], coordStart[1], coordEnd[0], coordEnd[1]);
}
/**
* draws an right marker
* @param g graphic object used for drawing
* @param field bounds of plot
* @param y position of marker
*/
private void drawRightMarker(GraphicsWrap g, RectangleWrap field, double y){
float[] coordStart = plotSheet.toGraphicPoint(xOffset, y, field);
float[] coordEnd = {coordStart[0] + this.markerLength+1, coordStart[1]};
g.drawLine(coordStart[0], coordStart[1], coordEnd[0], coordEnd[1]);
}
/**
* get the offset of this axis
* @return the offset of this axis
*/
public double getxOffset() {
return xOffset;
}
/**
* set the offset of this axis
* @param yOffset new offset
*/
public void setxOffset(double yOffset) {
this.xOffset = yOffset;
}
/**
* set offset back to zero for normal axis behavior
*/
public void unsetxOffset() {
this.xOffset = 0;
}
/**
* set the y coordinate where the x axis goes through this axis
* @param yOffset offset of the x-axis
*/
public void setIntersectionOffset(double yOffset) {
this.yOffset = yOffset;
}
/**
* set the axis to draw on the border between outer frame and plot
*/
public void setOnFrame() {
this.isOnFrame = true;
xOffset = plotSheet.getxRange()[0];
markOnLeft = false;
}
/**
* unset the axis to draw on the border between outer frame and plot
*/
public void unsetOnFrame() {
this.isOnFrame = false;
xOffset = 0;
markOnLeft = true;
markOnRight = true;
isOnRightSide = false;
}
public void setOnRightSideFrame() {
this.isOnFrame = true;
xOffset = plotSheet.getxRange()[1];
markOnRight = false;
isOnRightSide = true;
}
/*
* (non-Javadoc)
* @see rendering.Drawable#isOnFrame()
*/
public boolean isOnFrame() {
return isOnFrame;
}
/**
* set name of axis
* @param name name of axis
*/
public void setName(String name) {
this.name = name;
if(name.equals(""))
mHasName = false;
else
mHasName = true;
}
private void drawMinorMarkers(GraphicsWrap g) {
RectangleWrap field = g.getClipBounds();
int cleanSpace = 17; // space in pixel that will be unmarked on the end of the axis for arrow and description
int tics = (int)((this.ticStart - this.start)/tic);
double leftStart = this.ticStart - this.tic*tics;
int factor = 1;
double logStart = 0;
if(this.isLog) {
logStart = Math.floor(Math.log10(this.start));
leftStart = Math.pow(10, logStart) * factor++;
}
double currentY = leftStart;
while(currentY <= this.end) {
if((!this.isOnFrame &&plotSheet.yToGraphic(currentY, field) >= plotSheet.yToGraphic(this.end, field) +cleanSpace
&& plotSheet.yToGraphic(currentY, field) <= plotSheet.yToGraphic(this.start, field) - cleanSpace
&& plotSheet.yToGraphic(currentY, field) <= field.y + field.height - cleanSpace
&& plotSheet.yToGraphic(currentY, field) >= field.y + cleanSpace) ||
(this.isOnFrame && currentY <= this.plotSheet.getyRange()[1] &&
currentY >= this.plotSheet.getyRange()[0])){
if(this.markOnRight ) {
drawRightMinorMarker(g, field, currentY);
}
if(this.markOnLeft ) {
drawLeftMinorMarker(g, field, currentY);
}
}
if(this.isLog) {
if(factor == 10)
{
factor = 1;
logStart++;
}
currentY = Math.pow(10, logStart)*factor++;
}else {
currentY += minorTic;
}
}
}
// /**
// * caculates minor tics
// * @param g graphic object used for drawing
// * @return returns the new minor tics in percent
// */
//
// private double calcMinorTics(Graphics g)
// {
// double percentMinorTic=this.minorTic/this.tic;
// return percentMinorTic;
// }
/**
* draws an left minor marker
* @param g graphic object used for drawing
* @param field bounds of plot
* @param y position of marker
*/
private void drawLeftMinorMarker(GraphicsWrap g, RectangleWrap field, double y){
float[] coordStart = plotSheet.toGraphicPoint(xOffset, y, field);
float[] coordEnd = {(float) (coordStart[0] - 0.5*this.markerLength), coordStart[1]};
g.drawLine(coordStart[0], coordStart[1], coordEnd[0], coordEnd[1]);
}
/**
* draws an right minor marker
* @param g graphic object used for drawing
* @param field bounds of plot
* @param y position of marker
*/
private void drawRightMinorMarker(GraphicsWrap g, RectangleWrap field, double y){
float[] coordStart = plotSheet.toGraphicPoint(xOffset, y, field);
float[] coordEnd = {(float) (coordStart[0] + 0.5*this.markerLength+1), coordStart[1]};
g.drawLine(coordStart[0], coordStart[1], coordEnd[0], coordEnd[1]);
}
public void setIntegerNumbering(boolean isIntegerNumbering){
this.isIntegerNumbering = isIntegerNumbering;
}
public void setLog(){
this.isLog = true;
}
public void unsetLog(){
this.isLog = false;
}
@Override
public void abortAndReset() {
// TODO Auto-generated method stub
}
@Override
public boolean isClusterable() {
return true;
}
@Override
public boolean isCritical() {
return true;
}
public void setHasNumbersRotated(){
mHasNumbersRotated = true;
}
public void unsetHasNumbersRotated(){
mHasNumbersRotated = false;
}
}