package com.handstudio.android.hzgrapherlib.graphview; import java.util.WeakHashMap; 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.Matrix; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.PointF; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; import com.handstudio.android.hzgrapherlib.error.ErrorCode; import com.handstudio.android.hzgrapherlib.error.ErrorDetector; import com.handstudio.android.hzgrapherlib.util.Converter; import com.handstudio.android.hzgrapherlib.vo.GraphNameBox; import com.handstudio.android.hzgrapherlib.vo.radargraph.RadarGraphVO; public class RadarGraphView extends SurfaceView implements Callback{ public static final String TAG = "RadarGraphView"; private SurfaceHolder mHolder; private DrawThread mDrawThread; private RadarGraphVO mRadarGraphVO = null; public static final float MAX_VALUE = 100; private int fieldCount = 8; private int baselineCount = 5; //Constructor public RadarGraphView(Context context, RadarGraphVO vo) { super(context); mRadarGraphVO = vo; initView(context, vo); } private void initView(Context context, RadarGraphVO vo) { ErrorCode ec = ErrorDetector.checkGraphObject(vo); ec.printError(); mHolder = getHolder(); mHolder.addCallback(this); fieldCount = vo.getArrGraph().get(0).getCoordinateArr().length; baselineCount = vo.getMaxValue() / vo.getIncrement(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { if(mDrawThread == null){ mDrawThread = new DrawThread(mHolder, getContext()); mDrawThread.start(); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { if(mDrawThread != null){ mDrawThread.setRunFlag(false); mDrawThread = null; } } private static final Object touchLock = new Object(); // touch synchronize @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); if(mDrawThread == null ){ return false; } if(action == MotionEvent.ACTION_DOWN){ synchronized (touchLock) { mDrawThread.isDirty = true; } return true; }else if(action == MotionEvent.ACTION_MOVE){ synchronized (touchLock) { mDrawThread.isDirty = true; } return true; }else if(action == MotionEvent.ACTION_UP){ synchronized (touchLock) { mDrawThread.isDirty = true; } return true; } return super.onTouchEvent(event); } class DrawThread extends Thread{ SurfaceHolder mHolder; Context mCtx; boolean isRun = true; boolean isDirty = true; Matrix matrix = new Matrix(); int height = getHeight(); int width = getWidth(); //graph length int xLength = width - (mRadarGraphVO.getPaddingLeft() + mRadarGraphVO.getPaddingRight() + mRadarGraphVO.getMarginRight()); int yLength = height - (mRadarGraphVO.getPaddingBottom() + mRadarGraphVO.getPaddingTop() + mRadarGraphVO.getMarginTop()); //chart length int chartXLength = width - (mRadarGraphVO.getPaddingLeft() + mRadarGraphVO.getPaddingRight()); int chartYLength = height - (mRadarGraphVO.getPaddingBottom() + mRadarGraphVO.getPaddingTop()); float chartSize = 0; //chartCenter PointF chartCenter = new PointF(width/2, height/2); Paint pGraphColor = new Paint(); Paint pGraphRegionColor = new Paint(); Paint pCircle = new Paint(); Paint pCrossLine = new Paint(); Paint pBaseLine = new Paint(); Paint pMarkText = new Paint(); //animation float anim = 0.0f; boolean isAnimation = false; boolean isDrawRegion = false; long animStartTime = -1; WeakHashMap<Integer, Bitmap> arrIcon = new WeakHashMap<Integer, Bitmap>(); Bitmap bg = null; public DrawThread(SurfaceHolder holder, Context context) { mHolder = holder; mCtx = context; int size = mRadarGraphVO.getArrGraph().size(); for (int i = 0; i < size; i++) { int bitmapResource = mRadarGraphVO.getArrGraph().get(i).getBitmapResource(); if(bitmapResource != -1){ arrIcon.put(i, BitmapFactory.decodeResource(getResources(), bitmapResource)); }else{ if(arrIcon.get(i) != null){ arrIcon.remove(i); } } } int bgResource = mRadarGraphVO.getGraphBG(); if(bgResource != -1){ Bitmap tempBg = BitmapFactory.decodeResource(getResources(), bgResource); bg = Bitmap.createScaledBitmap(tempBg, width, height, true); tempBg.recycle(); } getChartSize(); } private void getChartSize() { if(chartXLength <= chartYLength){ chartSize = chartXLength/2; }else{ chartSize = chartYLength/2; } } public void setRunFlag(boolean bool){ isRun = bool; } @Override public void run() { Canvas canvas = null; Log.e(TAG,"height = " + height); Log.e(TAG,"width = " + width); setPaint(); isAnimation(); isDrawRegion(); animStartTime = System.currentTimeMillis(); while(isRun){ //draw only on dirty mode if(!isDirty){ try { Thread.sleep(100); } catch (InterruptedException e1) { e1.printStackTrace(); } continue; } canvas = mHolder.lockCanvas(); try { Thread.sleep(0000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } calcTimePass(); synchronized(mHolder){ synchronized (touchLock) { try { //bg color canvas.drawColor(Color.WHITE); if(bg != null){ canvas.drawBitmap(bg, 0, 0, null); } //TODO draw cross line drawCrossLine(canvas); //TODO x coord dot line drawBaseLine(canvas); drawBaseLineText(canvas); // //TODO draw outline //TODO draw text drawLegendText(canvas); //Graph drawGraphRegion(canvas); drawGraph(canvas); drawGraphName(canvas); // isDirty = false; } catch (Exception e) { e.printStackTrace(); } finally { if(canvas != null){ mHolder.unlockCanvasAndPost(canvas); } } } } } } private void calcTimePass(){ if(isAnimation){ long curTime = System.currentTimeMillis(); long gapTime = curTime - animStartTime; long animDuration = mRadarGraphVO.getAnimation().getDuration(); if(gapTime >= animDuration){ gapTime = animDuration; isDirty = false; } anim = mRadarGraphVO.getArrGraph().get(0).getCoordinateArr().length * (float)gapTime/(float)animDuration; // anim = anim+0.01f; }else{ isDirty = false; } // Log.e(TAG,"curTime = " + curTime + " , animStartTime = " + animStartTime); // Log.e(TAG,"anim = " + anim + " , gapTime = " + gapTime); } private void drawGraphName(Canvas canvas) { GraphNameBox gnb = mRadarGraphVO.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 = mRadarGraphVO.getArrGraph().size(); for (int i = 0; i < graphSize; i++) { String text = mRadarGraphVO.getArrGraph().get(i).getName(); Rect rect = new Rect(); pNameText.getTextBounds(text, 0, text.length(), rect); if(rect.width() > maxTextWidth){ maxTextWidth = rect.width(); maxTextHeight = rect.height(); } mRadarGraphVO.getArrGraph().get(i).getName(); } mRadarGraphVO.getArrGraph().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(mRadarGraphVO.getArrGraph().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 = mRadarGraphVO.getArrGraph().get(i).getName(); canvas.drawText(text, width - (nameboxMarginRight + maxTextWidth) - nameboxPadding, nameboxMarginTop + maxTextHeight/2 + maxCellHight * i + maxCellHight/2 + nameboxPadding + nameboxIconMargin * i, pNameText); } } } /** * check graph line animation */ private void isAnimation() { if(mRadarGraphVO.getAnimation() != null){ isAnimation = true; }else{ isAnimation = false; } } /** * check graph line region animation */ private void isDrawRegion() { if(mRadarGraphVO.isDrawRegion()){ isDrawRegion = true; }else{ isDrawRegion = false; } } private void drawCrossLine(Canvas canvas) { PointF dot = new PointF(chartCenter.x + 00, chartCenter.y - chartSize); for (int i = 0; i < fieldCount; i++) { float radAngle = (float) (Converter.DegreeToRadian(360/fieldCount * (i))); // use radian PointF rotateDot = getRotatePoint(dot, radAngle); canvas.drawLine(chartCenter.x, chartCenter.y, rotateDot.x, rotateDot.y, pCrossLine); } // canvas.drawCircle(chartCenter.x, chartCenter.y, 4, pCircle); } /** * calc rotated point * @param dot * @param radAngle */ private PointF getRotatePoint(PointF dot, float radAngle) { PointF rotateDot = new PointF(); rotateDot.x = (float) (chartCenter.x + (dot.x-chartCenter.x)*Math.cos(radAngle) - (dot.y-chartCenter.y)*Math.sin(radAngle)); rotateDot.y = (float) (chartCenter.y + (dot.x-chartCenter.x)*Math.sin(radAngle) + (dot.y-chartCenter.y)*Math.cos(radAngle)); return rotateDot; } private void drawBaseLine(Canvas canvas) { PointF dot[] = { new PointF(chartCenter.x + 00, chartCenter.y - chartSize / baselineCount * 1), new PointF(chartCenter.x + 00, chartCenter.y - chartSize / baselineCount * 2), new PointF(chartCenter.x + 00, chartCenter.y - chartSize / baselineCount * 3), new PointF(chartCenter.x + 00, chartCenter.y - chartSize / baselineCount * 4), new PointF(chartCenter.x + 00, chartCenter.y - chartSize / baselineCount * 5)}; Path linePath[] = new Path[baselineCount]; for (int i = 0; i <= fieldCount; i++) { float radAngle = (float) (Converter.DegreeToRadian(360/fieldCount * (i))); // use radian for (int j = 0; j < baselineCount; j++) { PointF rotateDot = getRotatePoint(dot[j], radAngle); if(i == 0){ linePath[j] = new Path(); linePath[j].moveTo(rotateDot.x, rotateDot.y); }else{ linePath[j].lineTo(rotateDot.x, rotateDot.y); } canvas.drawPath(linePath[j], pBaseLine); } } } private void drawBaseLineText(Canvas canvas) { PointF dot[] = { new PointF(chartCenter.x + 00, chartCenter.y - chartSize / baselineCount * 1), new PointF(chartCenter.x + 00, chartCenter.y - chartSize / baselineCount * 2), new PointF(chartCenter.x + 00, chartCenter.y - chartSize / baselineCount * 3), new PointF(chartCenter.x + 00, chartCenter.y - chartSize / baselineCount * 4), new PointF(chartCenter.x + 00, chartCenter.y - chartSize / baselineCount * 5)}; for (int i = 0; i < baselineCount; i++) { PointF rotateDot = dot[i]; String mark = Integer.toString(mRadarGraphVO.getIncrement() * (i+1)); pMarkText.measureText(mark); pMarkText.setTextSize(20); Rect rect = new Rect(); pMarkText.getTextBounds(mark, 0, mark.length(), rect); canvas.drawText(mark, rotateDot.x -(rect.width() + 20), rotateDot.y + rect.height()/2, pMarkText); } } /** * set graph line color */ private void setPaint() { pGraphColor = new Paint(); pGraphColor.setFlags(Paint.ANTI_ALIAS_FLAG); pGraphColor.setAntiAlias(true); //text anti alias pGraphColor.setFilterBitmap(true); // bitmap anti alias pGraphColor.setColor(Color.BLUE); pGraphColor.setStrokeWidth(3); pGraphColor.setStyle(Style.STROKE); pGraphRegionColor = new Paint(); pGraphRegionColor.setFlags(Paint.ANTI_ALIAS_FLAG); pGraphRegionColor.setAntiAlias(true); //text anti alias pGraphRegionColor.setFilterBitmap(true); // bitmap anti alias pGraphRegionColor.setColor(Color.BLUE); pGraphRegionColor.setAlpha(127); pGraphRegionColor.setStrokeWidth(1); pGraphRegionColor.setStyle(Style.FILL_AND_STROKE); pCircle = new Paint(); pCircle.setFlags(Paint.ANTI_ALIAS_FLAG); pCircle.setAntiAlias(true); //text anti alias pCircle.setFilterBitmap(true); // bitmap anti alias pCircle.setColor(Color.BLUE); pCircle.setStrokeWidth(3); pCircle.setStyle(Style.FILL_AND_STROKE); pCrossLine = new Paint(); pCrossLine.setFlags(Paint.ANTI_ALIAS_FLAG); pCrossLine.setAntiAlias(true); //text anti alias pCrossLine.setFilterBitmap(true); // bitmap anti alias pCrossLine.setColor(Color.GRAY); pCrossLine.setStrokeWidth(3); pBaseLine = new Paint(); pBaseLine.setFlags(Paint.ANTI_ALIAS_FLAG); pBaseLine.setAntiAlias(true); //text anti alias pBaseLine.setFilterBitmap(true); // bitmap anti alias pBaseLine.setColor(0xffcccccc); pBaseLine.setStrokeWidth(3); pBaseLine.setStyle(Style.STROKE); pBaseLine.setPathEffect(new DashPathEffect(new float[] {10,5}, 0)); pMarkText = new Paint(); pMarkText.setFlags(Paint.ANTI_ALIAS_FLAG); pMarkText.setAntiAlias(true); //text anti alias pMarkText.setColor(Color.BLACK); } /** * draw Graph Region */ private void drawGraphRegion(Canvas canvas) { if(isDrawRegion){ if (isAnimation){ drawGraphRegionWithAnimation(canvas); }else{ drawGraphRegionWithoutAnimation(canvas); } } } /** * draw Graph */ private void drawGraph(Canvas canvas) { if (isAnimation){ drawGraphWithAnimation(canvas); }else{ drawGraphWithoutAnimation(canvas); } } /** * draw graph region without animation */ private void drawGraphRegionWithoutAnimation(Canvas canvas) { for (int i = 0; i < mRadarGraphVO.getArrGraph().size(); i++) { float[] graph = mRadarGraphVO.getArrGraph().get(i).getCoordinateArr(); pGraphRegionColor.setColor(mRadarGraphVO.getArrGraph().get(i).getColor()); pGraphRegionColor.setAlpha(127); Path lineRegionPath = new Path(); for (int j = 0; j < fieldCount; j++) { PointF dot = new PointF(chartCenter.x + 00, chartCenter.y - graph[j % fieldCount] / MAX_VALUE * chartSize); float radAngle = (float) (Converter.DegreeToRadian(360/fieldCount * (j % fieldCount))); // use radian PointF rotateDot = getRotatePoint(dot, radAngle); if (j == 0) { lineRegionPath.moveTo(rotateDot.x, rotateDot.y); } else { lineRegionPath.lineTo(rotateDot.x, rotateDot.y); } } canvas.drawPath(lineRegionPath, pGraphRegionColor); } } /** * draw graph without animation */ private void drawGraphWithoutAnimation(Canvas canvas) { for (int i = 0; i < mRadarGraphVO.getArrGraph().size(); i++) { float[] graph = mRadarGraphVO.getArrGraph().get(i).getCoordinateArr(); pGraphColor.setColor(mRadarGraphVO.getArrGraph().get(i).getColor()); pCircle.setColor(mRadarGraphVO.getArrGraph().get(i).getColor()); Bitmap icon = arrIcon.get(i); Path linePath = new Path(); PointF first = new PointF(); for (int j = 0; j <= fieldCount; j++) { PointF dot = new PointF(chartCenter.x + 00, chartCenter.y - graph[j % fieldCount] / MAX_VALUE * chartSize); float radAngle = (float) (Converter.DegreeToRadian(360/fieldCount * (j % fieldCount))); // use radian PointF rotateDot = getRotatePoint(dot, radAngle); if(j< fieldCount){ if(icon == null){ canvas.drawCircle(rotateDot.x, rotateDot.y, 4, pCircle); }else{ canvas.drawBitmap(icon, rotateDot.x - icon.getWidth()/2, rotateDot.y - icon.getHeight()/2, null); } } if (j == 0) { first.x = rotateDot.x; first.y = rotateDot.y; linePath.moveTo(rotateDot.x, rotateDot.y); }else{ linePath.lineTo(rotateDot.x, rotateDot.y); } } canvas.drawPath(linePath, pGraphColor); } } /** * draw graph region with animation */ private void drawGraphRegionWithAnimation(Canvas canvas) { //for draw animation float prev_x = 0; float prev_y = 0; float next_x = 0; float next_y = 0; int value = 0; float mode = 0; for (int i = 0; i < mRadarGraphVO.getArrGraph().size(); i++) { float[] graph = mRadarGraphVO.getArrGraph().get(i).getCoordinateArr(); pGraphRegionColor.setColor(mRadarGraphVO.getArrGraph().get(i).getColor()); pGraphRegionColor.setAlpha(127); Path lineRegionPath = new Path(); value = (int) (anim/1); mode = anim %1; // Log.e("", "value = " + value + "\t ,mode = " + mode); for (int j = 0; j <= value+1; j++) { PointF dot = new PointF(chartCenter.x + 00, chartCenter.y - graph[j % fieldCount] / MAX_VALUE * chartSize); float radAngle = (float) (Converter.DegreeToRadian(360/fieldCount * (j % fieldCount))); // use radian PointF rotateDot = getRotatePoint(dot, radAngle); if (j == 0) { lineRegionPath.moveTo(rotateDot.x, rotateDot.y); } else { if( j > value){ next_x = rotateDot.x - prev_x; next_y = rotateDot.y - prev_y; lineRegionPath.lineTo(prev_x + next_x * mode, prev_y + next_y * mode); lineRegionPath.lineTo(chartCenter.x, chartCenter.y); }else{ lineRegionPath.lineTo(rotateDot.x, rotateDot.y); } } prev_x = rotateDot.x; prev_y = rotateDot.y; } canvas.drawPath(lineRegionPath, pGraphRegionColor); } } /** * draw graph with animation */ private void drawGraphWithAnimation(Canvas canvas) { //for draw animation float prev_x = 0; float prev_y = 0; float next_x = 0; float next_y = 0; float value = 0; float mode = 0; for (int i = 0; i < mRadarGraphVO.getArrGraph().size(); i++) { float[] graph = mRadarGraphVO.getArrGraph().get(i).getCoordinateArr(); pGraphColor.setColor(mRadarGraphVO.getArrGraph().get(i).getColor()); pCircle.setColor(mRadarGraphVO.getArrGraph().get(i).getColor()); Bitmap icon = arrIcon.get(i); Path linePath = new Path(); PointF first = new PointF(); value = anim/1; mode = anim %1; for (int j = 0; j < value+1; j++) { PointF dot = new PointF(chartCenter.x + 00, chartCenter.y - graph[j % fieldCount] / MAX_VALUE * chartSize); float radAngle = (float) (Converter.DegreeToRadian(360/fieldCount * (j % fieldCount))); // use radian PointF rotateDot = getRotatePoint(dot, radAngle); if(j< fieldCount){ if(icon == null){ canvas.drawCircle(rotateDot.x, rotateDot.y, 4, pCircle); }else{ canvas.drawBitmap(icon, rotateDot.x - icon.getWidth()/2, rotateDot.y - icon.getHeight()/2, null); } } if (j == 0) { first.x = rotateDot.x; first.y = rotateDot.y; linePath.moveTo(rotateDot.x, rotateDot.y); }else{ if( j > value){ next_x = rotateDot.x - prev_x; next_y = rotateDot.y - prev_y; linePath.lineTo(prev_x + next_x * mode, prev_y + next_y * mode); }else{ linePath.lineTo(rotateDot.x, rotateDot.y); } } prev_x = rotateDot.x; prev_y = rotateDot.y; } canvas.drawPath(linePath, pGraphColor); } } /** * draw Legend Text */ private void drawLegendText(Canvas canvas) { for (int j = 0; j < fieldCount; j++) { PointF textPoint = new PointF(chartCenter.x + 00, chartCenter.y - chartSize); float radAngle = (float) (Converter.DegreeToRadian(360/fieldCount * (j))); // use radian PointF rotateDot = getRotatePoint(textPoint, radAngle); String legend = mRadarGraphVO.getLegendArr()[j]; pMarkText.measureText(legend); pMarkText.setTextSize(20); Rect rect = new Rect(); pMarkText.getTextBounds(legend, 0, legend.length(), rect); if(radAngle >= Converter.DegreeToRadian(180)){ canvas.drawText(legend, rotateDot.x -(rect.width() + 20), rotateDot.y + rect.height(), pMarkText); }else{ canvas.drawText(legend, rotateDot.x + (20), rotateDot.y - rect.height()/2, pMarkText); } } } } }