package com.handstudio.android.hzgrapherlib.graphview;
import java.util.Calendar;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import com.handstudio.android.hzgrapherlib.canvas.GraphCanvasWrapper;
import com.handstudio.android.hzgrapherlib.error.ErrorCode;
import com.handstudio.android.hzgrapherlib.error.ErrorDetector;
import com.handstudio.android.hzgrapherlib.util.EuclidLine;
import com.handstudio.android.hzgrapherlib.util.EuclidPoint;
import com.handstudio.android.hzgrapherlib.vo.GraphNameBox;
import com.handstudio.android.hzgrapherlib.vo.bubblegraph.BubbleGraph;
import com.handstudio.android.hzgrapherlib.vo.bubblegraph.BubbleGraphVO;
public class BubbleGraphView extends SurfaceView implements Callback
{
public static final String TAG = "BUBBLE_GRAPH_VIEW";
private BubbleGraphVO mBubbleGraphVO = null;
private DrawThread mDrawThread = null;
private Context mContext = null;
public BubbleGraphView ( Context ctx , BubbleGraphVO vo )
{
super( ctx );
mBubbleGraphVO = vo;
initView ( ctx , vo );
}
@Override
public void surfaceDestroyed ( SurfaceHolder holder )
{
if ( mDrawThread != null )
{
mDrawThread.setRunFlag(false);
mDrawThread = null;
}
}
@Override
public void surfaceCreated ( SurfaceHolder holder )
{
Log.i(TAG, "SurfaceCreated!");
if ( mDrawThread == null )
{
mDrawThread = new DrawThread ( holder , mBubbleGraphVO , this.mContext );
mDrawThread.start();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
public void setBubbleGraphVO ( BubbleGraphVO vo ) { mBubbleGraphVO = vo; }
private void initView ( Context ctx , BubbleGraphVO vo )
{
ErrorCode ec = ErrorDetector.checkGraphObject(vo);
ec.printError();
SurfaceHolder holder = this.getHolder();
holder.addCallback(this);
mContext = ctx;
}
private class DrawThread extends Thread
{
private class CircleAnim
{
public float mCurrent = 0.0f;
public float mMax = 0.0f;
}
private Context mCtx = null;
private SurfaceHolder mHolder = null;
private BubbleGraphVO mVO = null;
private boolean mIsRun = true;
private CircleAnim[][] mCircleAnim = null;
private EuclidPoint[][][] mCircleOuterPoint = null;
private Paint mPaintBubble = null;
private Paint mPaintAxisLine = null;
private Paint mPaintGuideLine = null;
private Paint mPaintAxisMarker = null;
private Paint mPaintAxisText = null;
public DrawThread ( SurfaceHolder holder , BubbleGraphVO vo , Context ctx )
{
mCtx = ctx;
mHolder = holder;
mVO = vo;
}
@Override
public void run ()
{
Canvas canvas = null;
GraphCanvasWrapper gcw = null;
float horizontalThreshold = 0.0f;
initCircleAnimation ();
initPaints ();
int width = getWidth();
int height = getHeight();
//chart length
int chartXLength = width - (mVO.getPaddingLeft() + mVO.getPaddingRight());
int chartYLength = height - (mVO.getPaddingBottom() + mVO.getPaddingTop());
initClosePoints ( chartXLength , chartYLength );
long startTick = Calendar.getInstance().getTimeInMillis();
Bitmap bitBackground = null;
Bitmap bitTemp = null;
if ( mVO.getGraphBG() != -1 )
{
bitTemp = BitmapFactory.decodeResource ( mCtx.getResources(), mVO.getGraphBG() );
bitBackground = Bitmap.createScaledBitmap( bitTemp , width , height , true);
}
while ( true )
{
if ( mIsRun == false ) { break; }
canvas = mHolder.lockCanvas();
if ( canvas == null ) { continue; }
gcw = new GraphCanvasWrapper ( canvas , width , height , mVO.getPaddingLeft() , mVO.getPaddingBottom() );
try
{
synchronized ( mHolder )
{
if ( bitBackground == null ) { canvas.drawColor(Color.WHITE); }
else { canvas.drawBitmap(bitBackground, 0, 0 , null); }
drawBaseline ( gcw , chartXLength , chartYLength );
if ( mVO.isAnimationShow() == true )
{
drawGraphWithAnimation ( gcw , chartXLength , chartYLength , horizontalThreshold );
}
else
{
drawGraphWithoutAnimation ( gcw , chartXLength , chartYLength );
}
drawAxisMark ( gcw , chartXLength , chartYLength );
drawAxisValue ( gcw , chartXLength , chartYLength );
drawAxisLine ( gcw , chartXLength , chartYLength );
drawGraphName ( gcw.getCanvas() , width , height );
}
}
finally
{
if ( canvas != null )
{
mHolder.unlockCanvasAndPost(canvas);
}
}
long difference = Calendar.getInstance().getTimeInMillis() - startTick;
horizontalThreshold = ((float)mVO.get(0).getCoordinateArr().length /
(float)mVO.getAnimationDuration())*(float)difference;
}
}
public void setRunFlag ( boolean isRun ) { mIsRun = isRun; }
private void initClosePoints ( int width , int height )
{
int graphCount = mVO.size();
int legendCount = mVO.getLegendArr().length;
mCircleOuterPoint = new EuclidPoint[graphCount][][];
float perX = (float)width/(float)mVO.getLegendArr().length;
float maxValue = mVO.getMaxCoordinate();
float minValue = mVO.getMinCoordinate();
int i; int j;
for ( i = 0 ; i < graphCount ; i++ )
{
mCircleOuterPoint[i] = new EuclidPoint[legendCount][];
float[] coordsArr = mVO.get(i).getCoordinateArr();
float[] sizeArr = mVO.get(i).getSizeArr();
for ( j = 0 ; j < legendCount ; j++ )
{
mCircleOuterPoint[i][j] = new EuclidPoint[2];
mCircleOuterPoint[i][j][0] = null;
mCircleOuterPoint[i][j][1] = null;
float rad = getPixelFromCircleRadius ( sizeArr[j] , width );
float circleX = (float)j*perX;
float circleY = (coordsArr[j]-minValue)*(float)height / (maxValue-minValue);
if ( j > 0 )
{
float prevX = (float)(j-1) * perX;
float prevY = (float)(coordsArr[j-1]-minValue)*(float)height / (maxValue-minValue);
EuclidPoint pt =
new EuclidLine ( new EuclidPoint ( circleX , circleY ) ,
new EuclidPoint ( prevX , prevY ) ).getPointOfLine(true, rad);
mCircleOuterPoint[i][j][0] = new EuclidPoint ( pt.getX() , pt.getY() );
}
if ( j < legendCount - 1 )
{
float nextX = (float)(j+1) * perX;
float nextY = (coordsArr[j+1]-minValue)*(float)height / (maxValue-minValue);
EuclidPoint pt =
new EuclidLine ( new EuclidPoint ( circleX , circleY ) ,
new EuclidPoint ( nextX , nextY ) ).getPointOfLine(false, rad);
mCircleOuterPoint[i][j][1] = new EuclidPoint ( pt.getX() , pt.getY() );
}
}
if ( i == 0 )
{
for ( j = 0 ; j < legendCount ; j++ )
{
String expr = "";
EuclidPoint ptPrev = mCircleOuterPoint[i][j][0];
EuclidPoint ptNext = mCircleOuterPoint[i][j][1];
if ( ptPrev == null ) { expr += "PREV:NULL\n"; }
else
{
expr += "PREV:X:" + Float.toString(ptPrev.getX()) + "/" + "Y:" + Float.toString(ptPrev.getY()) + "\n";
}
if ( ptNext == null ) { expr += "NEXT:NULL\n"; }
else
{
expr += "NEXT:X:" + Float.toString(ptNext.getX()) + "/" + "Y:" + Float.toString(ptNext.getY()) + "\n";
}
Log.i(TAG , expr);
}
}
}
}
private void initCircleAnimation ()
{
mCircleAnim = new CircleAnim [mVO.size()][];
int i; int j;
for ( i = 0 ; i < mCircleAnim.length ; i++ )
{
mCircleAnim[i] = new CircleAnim[mVO.get(i).getSizeArr().length];
for ( j = 0 ; j < mCircleAnim[i].length ; j++ )
{
mCircleAnim[i][j] = new CircleAnim ();
mCircleAnim[i][j].mCurrent = 0.0f;
mCircleAnim[i][j].mMax = mVO.get(i).getSizeArr()[j];
}
}
}
private void initPaints ()
{
mPaintBubble = new Paint ();
mPaintBubble.setFlags(Paint.ANTI_ALIAS_FLAG);
mPaintBubble.setAntiAlias(true);
mPaintBubble.setColor(Color.GRAY);
mPaintBubble.setStrokeWidth(3.0f);
mPaintBubble.setAlpha((int)(256*(1-50)));
mPaintAxisLine = new Paint ();
mPaintAxisLine.setFlags(Paint.ANTI_ALIAS_FLAG);
mPaintAxisLine.setAntiAlias(true);
mPaintAxisLine.setColor(Color.GRAY);
mPaintAxisLine.setStrokeWidth(3.0f);
mPaintGuideLine = new Paint ();
mPaintGuideLine.setFlags(Paint.ANTI_ALIAS_FLAG);
mPaintGuideLine.setPathEffect(new DashPathEffect(new float[] {5,10}, 0));
mPaintGuideLine.setColor(Color.rgb(210, 210, 210));
mPaintGuideLine.setStrokeWidth(3.0f);
mPaintAxisMarker = new Paint ();
mPaintAxisMarker.setFlags(Paint.ANTI_ALIAS_FLAG);
mPaintAxisMarker.setColor(Color.GRAY);
mPaintAxisMarker.setAntiAlias(true);
mPaintAxisMarker.setStrokeWidth(3.0f);
mPaintAxisText = new Paint ();
mPaintAxisText.setFlags(Paint.ANTI_ALIAS_FLAG);
mPaintAxisText.setAntiAlias(true);
mPaintAxisText.setColor(Color.rgb(30, 30, 30));
}
private boolean drawGraphWithAnimation ( GraphCanvasWrapper gcw , int width , int height , float threshold )
{
int i; int j;
for ( i = 0 ; i < mVO.size() ; i++ )
{
BubbleGraph bg = mVO.get(i);
float[] coordsArr = bg.getCoordinateArr();
float maxValue = mVO.getMaxCoordinate();
float minValue = mVO.getMinCoordinate();
mPaintBubble.setColor(bg.getColor());
mPaintBubble.setAlpha(150);
float perX = (float)width/(float)coordsArr.length;
int curIdx = (int)threshold;
// draw animated lines
if ( curIdx < coordsArr.length-1 && mVO.isLineShow() == true )
{
gcw.drawLine ( curIdx*perX ,
(coordsArr[curIdx]-minValue)*height / (maxValue-minValue) ,
perX*threshold ,
(bg.getCoordinateOfFloatIndex(threshold)-minValue)*height / (maxValue-minValue) , mPaintBubble );
}
if ( curIdx < coordsArr.length )
{
float rad = getPixelFromCircleRadius ( mCircleAnim[i][curIdx].mCurrent , width );
gcw.drawCircle ( curIdx*perX , (coordsArr[curIdx]-minValue)*height / (maxValue-minValue) ,
rad , mPaintBubble );
if ( mCircleAnim[i][curIdx].mCurrent <= mCircleAnim[i][curIdx].mMax )
{
mCircleAnim[i][curIdx].mCurrent += 1.0f;
}
}
int loopLimit = curIdx;
if ( curIdx >= coordsArr.length ) { loopLimit = coordsArr.length; }
// draw prev animated line
for ( j = 0 ; j < loopLimit ; j++ )
{
if ( loopLimit == coordsArr.length && j < coordsArr.length-1 )
{
EuclidPoint ptPrev = mCircleOuterPoint[i][j][1];
EuclidPoint ptNext = mCircleOuterPoint[i][j+1][0];
if ( ptPrev != null && ptNext != null )
{
gcw.drawLine( ptPrev.getX() , ptPrev.getY() , ptNext.getX() , ptNext.getY() , mPaintBubble );
}
}
else if ( j < coordsArr.length-1 && mVO.isLineShow() == true )
{
gcw.drawLine( j*perX , (coordsArr[j]-minValue)*height / (maxValue-minValue) ,
(j+1)*perX , (coordsArr[j+1]-minValue)*height / (maxValue-minValue) , mPaintBubble );
}
if ( j < coordsArr.length )
{
float rad = getPixelFromCircleRadius ( mCircleAnim[i][j].mCurrent , width );
gcw.drawCircle ( j*perX , (coordsArr[j]-minValue)*height / (maxValue-minValue) , rad , mPaintBubble );
if ( mCircleAnim[i][j].mCurrent <= mCircleAnim[i][j].mMax )
{
mCircleAnim[i][j].mCurrent += 1.0f;
}
}
}
}
return false;
}
private void drawGraphWithoutAnimation ( GraphCanvasWrapper gcw , int width , int height )
{
int i; int j;
for ( i = 0 ; i < mVO.size() ; i++ )
{
BubbleGraph bg = mVO.get(i);
float[] coordsArr = bg.getCoordinateArr();
float[] sizeArr = bg.getSizeArr();
float maxValue = mVO.getMaxCoordinate();
float minValue = mVO.getMinCoordinate();
mPaintBubble.setColor(bg.getColor());
mPaintBubble.setAlpha(150);
float perX = (float)width/(float)coordsArr.length;
if ( coordsArr.length != sizeArr.length ) { continue; }
for ( j = 0 ; j < coordsArr.length ; j++ )
{
float rad = getPixelFromCircleRadius ( sizeArr[j] , width );
float circleX = j*perX;
float circleY = (coordsArr[j]-minValue)*height / (maxValue-minValue);
gcw.drawCircle ( circleX , circleY , rad , mPaintBubble );
if ( j < coordsArr.length - 1 )
{
EuclidPoint ptPrev = mCircleOuterPoint[i][j][1];
EuclidPoint ptNext = mCircleOuterPoint[i][j+1][0];
if ( ptPrev != null && ptNext != null )
{
gcw.drawLine( ptPrev.getX() , ptPrev.getY() , ptNext.getX() , ptNext.getY() , mPaintBubble );
}
}
}
}
}
private float getPixelFromCircleRadius ( float rad , int width )
{
float ret = 0.0f;
ret = ((rad*((float)width/(float)mVO.get(0).getCoordinateArr().length)) / mVO.getMaxSize())/(float)2;
return ret;
}
private void drawBaseline ( GraphCanvasWrapper gcw , int width , int height )
{
int i;
int totalSize = mVO.get(0).getCoordinateArr().length;
float yPos = 0.0f;
float perY = (float)height/(float)totalSize;
for ( i = 0 ; i < totalSize ; i++ )
{
yPos += perY;
gcw.drawLine(0.0f, yPos , width, yPos , mPaintGuideLine);
}
}
private void drawGraphName(Canvas canvas, int width , int height)
{
GraphNameBox gnb = mVO.getGraphNameBox();
if(gnb != null)
{
int nameboxWidth = 0;
int nameboxHeight = 0;
int nameboxIconWidth = gnb.getNameboxIconWidth();
int nameboxIconHeight = gnb.getNameboxIconHeight();
int nameboxMarginTop = gnb.getNameboxMarginTop();
int nameboxMarginRight = gnb.getNameboxMarginRight();
int nameboxPadding = gnb.getNameboxPadding();
int nameboxTextIconMargin = gnb.getNameboxIconMargin();
int nameboxIconMargin = gnb.getNameboxIconMargin();
int nameboxTextSize = gnb.getNameboxTextSize();
int maxTextWidth = 0;
int maxTextHeight = 0;
Paint nameRextPaint = new Paint();
nameRextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
nameRextPaint.setAntiAlias(true); //text anti alias
nameRextPaint.setFilterBitmap(true); // bitmap anti alias
nameRextPaint.setColor(Color.BLUE);
nameRextPaint.setStrokeWidth(3);
nameRextPaint.setStyle(Style.STROKE);
Paint pIcon = new Paint();
pIcon.setFlags(Paint.ANTI_ALIAS_FLAG);
pIcon.setAntiAlias(true); //text anti alias
pIcon.setFilterBitmap(true); // bitmap anti alias
pIcon.setColor(Color.BLUE);
pIcon.setStrokeWidth(3);
pIcon.setStyle(Style.FILL_AND_STROKE);
Paint pNameText = new Paint();
pNameText.setFlags(Paint.ANTI_ALIAS_FLAG);
pNameText.setAntiAlias(true); //text anti alias
pNameText.setTextSize(nameboxTextSize);
pNameText.setColor(Color.BLACK);
int graphSize = mVO.size();
for (int i = 0; i < graphSize; i++)
{
String text = mVO.get(i).getName();
Rect rect = new Rect();
pNameText.getTextBounds(text, 0, text.length(), rect);
if(rect.width() > maxTextWidth){
maxTextWidth = rect.width();
maxTextHeight = rect.height();
}
mVO.get(i).getName();
}
mVO.get(0).getName();
nameboxWidth = 1 * maxTextWidth + nameboxTextIconMargin + nameboxIconWidth;
int maxCellHight = maxTextHeight;
if(nameboxIconHeight > maxTextHeight){
maxCellHight = nameboxIconHeight;
}
nameboxHeight = graphSize * maxCellHight + (graphSize-1) * nameboxIconMargin;
canvas.drawRect(width - (nameboxMarginRight + nameboxWidth) - nameboxPadding*2,
nameboxMarginTop, width - nameboxMarginRight, nameboxMarginTop + nameboxHeight + nameboxPadding*2, nameRextPaint);
for (int i = 0; i < graphSize; i++)
{
pIcon.setColor(mVO.get(i).getColor());
canvas.drawRect(width - (nameboxMarginRight + nameboxWidth) - nameboxPadding,
nameboxMarginTop + (maxCellHight * i) + nameboxPadding + (nameboxIconMargin * i),
width - (nameboxMarginRight + maxTextWidth) - nameboxPadding - nameboxTextIconMargin,
nameboxMarginTop + maxCellHight * (i+1) + nameboxPadding + nameboxIconMargin * i, pIcon);
String text = mVO.get(i).getName();
canvas.drawText(text, width - (nameboxMarginRight + maxTextWidth) - nameboxPadding,
nameboxMarginTop + maxTextHeight/2 + maxCellHight * i + maxCellHight/2 + nameboxPadding + nameboxIconMargin * i, pNameText);
}
}
}
private void drawAxisMark ( GraphCanvasWrapper gcw , int width , int height )
{
int i;
int totalSizeX = mVO.get(0).getCoordinateArr().length;
float perX = (float)width / (float)totalSizeX;
// draw X axis mark first
for ( i = 0 ; i < totalSizeX ; i++ )
{
gcw.drawLine( (float)(i)*perX , -10.0f , (float)(i)*perX , 0.0f , mPaintAxisMarker );
}
int totalSizeY = mVO.getTotalCountOfItem() / mVO.size();
float perY = (float)height / (float)totalSizeY;
// draw Y axis mark
for ( i = 0 ; i <= totalSizeY ; i++ )
{
gcw.drawLine ( -10.0f , (float)(i)*perY , 0.0f , (float)(i)*perY , mPaintAxisMarker );
}
}
private void drawAxisValue ( GraphCanvasWrapper gcw , int width , int height )
{
int i;
int totalSizeX = mVO.getLegendArr().length;
float perX = (float)width / (float)totalSizeX;
// draw X axis text first
for ( i = 0 ; i < mVO.getLegendArr().length ; i++ )
{
String text = mVO.getLegendArr()[i];
mPaintAxisText.measureText(text);
mPaintAxisText.setTextSize(20);
Rect rect = new Rect ();
mPaintAxisText.getTextBounds(text, 0, text.length(), rect);
gcw.drawText(text, i*perX - (rect.width()/2) , -(20+rect.height()) , mPaintAxisText);
}
int totalSizeY = mVO.getTotalCountOfItem() / mVO.size();
float perY = (float)height / (float)totalSizeY;
float max = mVO.getMaxCoordinate();
float min = mVO.getMinCoordinate();
float valuePerY = (max-min)/totalSizeY;
float cur = min;
// draw Y axis mark
for ( i = 0 ; i <= totalSizeY ; i++ )
{
String text = String.format("%.1f", cur);
mPaintAxisText.measureText(text);
mPaintAxisText.setTextSize(20);
Rect rect = new Rect ();
mPaintAxisText.getTextBounds(text, 0, text.length(), rect);
gcw.drawText(text, -(20+rect.width()) , i*perY - (rect.height()/2) , mPaintAxisText);
cur += valuePerY;
}
}
private void drawAxisLine ( GraphCanvasWrapper gcw , int width , int height )
{
gcw.drawLine(0.0f, 0.0f, width, 0.0f, mPaintAxisLine);
gcw.drawLine(0.0f, 0.0f, 0.0f, height, mPaintAxisLine);
}
}
}