diff --git a/hellocharts-library/src/lecho/lib/hellocharts/model/PieChartData.java b/hellocharts-library/src/lecho/lib/hellocharts/model/PieChartData.java index b26ae86f..6d6a011c 100644 --- a/hellocharts-library/src/lecho/lib/hellocharts/model/PieChartData.java +++ b/hellocharts-library/src/lecho/lib/hellocharts/model/PieChartData.java @@ -18,15 +18,17 @@ public class PieChartData extends AbstractChartData { public static final int DEFAULT_CENTER_TEXT1_SIZE_SP = 42; public static final int DEFAULT_CENTER_TEXT2_SIZE_SP = 16; public static final float DEFAULT_CENTER_CIRCLE_SCALE = 0.6f; + private static final int DEFAULT_SLICE_SPACING_DP = 2; private PieChartValueFormatter formatter = new SimplePieChartValueFormatter(); private boolean hasLabels = false; private boolean hasLabelsOnlyForSelected = false; private boolean hasLabelsOutside = false; + private int slicesSpacing = DEFAULT_SLICE_SPACING_DP; private boolean hasCenterCircle = false; - private int centerCircleColor = Color.WHITE; - private float centerCircleScale = 0.6f; + private int centerCircleColor = Color.TRANSPARENT; + private float centerCircleScale = DEFAULT_CENTER_CIRCLE_SCALE; private int centerText1Color = Color.BLACK; private int centerText1FontSize = DEFAULT_CENTER_TEXT1_SIZE_SP; @@ -161,64 +163,72 @@ public boolean hasLabelsOutside() { * should also change chart fill ration using {@link PieChartView#setCircleFillRatio(float)}. This flag is used only * if you also set hasLabels or hasLabelsOnlyForSelected flags. */ - public void setHasLabelsOutside(boolean hasLabelsOutside) { + public PieChartData setHasLabelsOutside(boolean hasLabelsOutside) { this.hasLabelsOutside = hasLabelsOutside; + return this; } public boolean hasCenterCircle() { return hasCenterCircle; } - public void setHasCenterCircle(boolean hasCenterCircle) { + public PieChartData setHasCenterCircle(boolean hasCenterCircle) { this.hasCenterCircle = hasCenterCircle; + return this; } public int getCenterCircleColor() { return centerCircleColor; } - public void setCenterCircleColor(int centerCircleColor) { + public PieChartData setCenterCircleColor(int centerCircleColor) { this.centerCircleColor = centerCircleColor; + return this; } public float getCenterCircleScale() { return centerCircleScale; } - public void setCenterCircleScale(float centerCircleScale) { + public PieChartData setCenterCircleScale(float centerCircleScale) { this.centerCircleScale = centerCircleScale; + return this; } public int getCenterText1Color() { return centerText1Color; } - public void setCenterText1Color(int centerText1Color) { + public PieChartData setCenterText1Color(int centerText1Color) { this.centerText1Color = centerText1Color; + return this; } public int getCenterText1FontSize() { return centerText1FontSize; } - public void setCenterText1FontSize(int centerText1FontSize) { + public PieChartData setCenterText1FontSize(int centerText1FontSize) { this.centerText1FontSize = centerText1FontSize; + return this; } public Typeface getCenterText1Typeface() { return centerText1Typeface; } - public void setCenterText1Typeface(Typeface text1Typeface) { + public PieChartData setCenterText1Typeface(Typeface text1Typeface) { this.centerText1Typeface = text1Typeface; + return this; } public String getCenterText1() { return centerText1; } - public void setCenterText1(String centerText1) { + public PieChartData setCenterText1(String centerText1) { this.centerText1 = centerText1; + return this; } public String getCenterText2() { @@ -228,34 +238,47 @@ public String getCenterText2() { /** * Note that centerText2 will be drawn only if centerText1 is not empty/null. */ - public void setCenterText2(String centerText2) { + public PieChartData setCenterText2(String centerText2) { this.centerText2 = centerText2; + return this; } public int getCenterText2Color() { return centerText2Color; } - public void setCenterText2Color(int centerText2Color) { + public PieChartData setCenterText2Color(int centerText2Color) { this.centerText2Color = centerText2Color; + return this; } public int getCenterText2FontSize() { return centerText2FontSize; } - public void setCenterText2FontSize(int centerText2FontSize) { + public PieChartData setCenterText2FontSize(int centerText2FontSize) { this.centerText2FontSize = centerText2FontSize; + return this; } public Typeface getCenterText2Typeface() { return centerText2Typeface; } - public void setCenterText2Typeface(Typeface text2Typeface) { + public PieChartData setCenterText2Typeface(Typeface text2Typeface) { this.centerText2Typeface = text2Typeface; + return this; } + public int getSlicesSpacing() { + return slicesSpacing; + } + + public PieChartData setSlicesSpacing(int sliceSpacing) { + this.slicesSpacing = sliceSpacing; + return this; + } + public PieChartValueFormatter getFormatter() { return formatter; } @@ -264,7 +287,7 @@ public PieChartData setFormatter(PieChartValueFormatter formatter) { if (null != formatter) { this.formatter = formatter; } - return this; + return this; } public static PieChartData generateDummyData() { diff --git a/hellocharts-library/src/lecho/lib/hellocharts/model/SliceValue.java b/hellocharts-library/src/lecho/lib/hellocharts/model/SliceValue.java index 20042a61..1a180e04 100644 --- a/hellocharts-library/src/lecho/lib/hellocharts/model/SliceValue.java +++ b/hellocharts-library/src/lecho/lib/hellocharts/model/SliceValue.java @@ -27,6 +27,7 @@ public class SliceValue { /** Darken color used to draw label background and give touch feedback. */ private int darkenColor = ChartUtils.DEFAULT_DARKEN_COLOR; + @Deprecated /** Spacing between this slice and its neighbors. */ private int sliceSpacing = DEFAULT_SLICE_SPACING_DP; @@ -104,10 +105,12 @@ public int getDarkenColor() { return darkenColor; } + @Deprecated public int getSliceSpacing() { return sliceSpacing; } + @Deprecated public SliceValue setSliceSpacing(int sliceSpacing) { this.sliceSpacing = sliceSpacing; return this; diff --git a/hellocharts-library/src/lecho/lib/hellocharts/renderer/PieChartRenderer.java b/hellocharts-library/src/lecho/lib/hellocharts/renderer/PieChartRenderer.java index 32f8bd53..44793b5e 100644 --- a/hellocharts-library/src/lecho/lib/hellocharts/renderer/PieChartRenderer.java +++ b/hellocharts-library/src/lecho/lib/hellocharts/renderer/PieChartRenderer.java @@ -1,11 +1,15 @@ package lecho.lib.hellocharts.renderer; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Paint.FontMetricsInt; import android.graphics.PointF; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.text.TextUtils; @@ -25,220 +29,263 @@ * types. */ public class PieChartRenderer extends AbstractChartRenderer { - private static final float MAX_WIDTH_HEIGHT = 100f; - private static final int DEFAULT_START_ROTATION = 45; - private int rotation = DEFAULT_START_ROTATION; - private static final float DEFAULT_LABEL_INSIDE_RADIUS_FACTOR = 0.7f; - private static final float DEFAULT_LABEL_OUTSIDE_RADIUS_FACTOR = 1.0f; - private static final int DEFAULT_TOUCH_ADDITIONAL_DP = 8; - private static final int MODE_DRAW = 0; - private static final int MODE_HIGHLIGHT = 1; - private PieChartDataProvider dataProvider; - private Paint slicePaint = new Paint(); - private float maxSum; - private RectF originCircleOval = new RectF(); - private RectF drawCircleOval = new RectF(); - private PointF sliceVector = new PointF(); - private int touchAdditional; - private float circleFillRatio = 1.0f; - - // Center circle related attributes - private boolean hasCenterCircle; - private float centerCircleScale; - private Paint centerCirclePaint = new Paint(); - // Text1 - private Paint centerCircleText1Paint = new Paint(); - private FontMetricsInt centerCircleText1FontMetrics = new FontMetricsInt(); - // Text2 - private Paint centerCircleText2Paint = new Paint(); - private FontMetricsInt centerCircleText2FontMetrics = new FontMetricsInt(); - - private boolean hasLabelsOutside; - private boolean hasLabels; - private boolean hasLabelsOnlyForSelected; - private PieChartValueFormatter valueFormatter; - private Viewport tempMaximumViewport = new Viewport(); - - public PieChartRenderer(Context context, Chart chart, PieChartDataProvider dataProvider) { - super(context, chart); - this.dataProvider = dataProvider; - touchAdditional = ChartUtils.dp2px(density, DEFAULT_TOUCH_ADDITIONAL_DP); - - slicePaint.setAntiAlias(true); - slicePaint.setStyle(Paint.Style.FILL); - - centerCirclePaint.setAntiAlias(true); - centerCirclePaint.setStyle(Paint.Style.FILL); - - centerCircleText1Paint.setAntiAlias(true); - centerCircleText1Paint.setTextAlign(Align.CENTER); - - centerCircleText2Paint.setAntiAlias(true); - centerCircleText2Paint.setTextAlign(Align.CENTER); - } - - @Override - public void onChartSizeChanged() { - calculateCircleOval(); - } - - @Override - public void onChartDataChanged() { - super.onChartDataChanged(); - final PieChartData data = dataProvider.getPieChartData(); - hasLabelsOutside = data.hasLabelsOutside(); - hasLabels = data.hasLabels(); - hasLabelsOnlyForSelected = data.hasLabelsOnlyForSelected(); - valueFormatter = data.getFormatter(); - hasCenterCircle = data.hasCenterCircle(); - centerCircleScale = data.getCenterCircleScale(); - centerCirclePaint.setColor(data.getCenterCircleColor()); - if (null != data.getCenterText1Typeface()) { - centerCircleText1Paint.setTypeface(data.getCenterText1Typeface()); - } - centerCircleText1Paint.setTextSize(ChartUtils.sp2px(scaledDensity, data.getCenterText1FontSize())); - centerCircleText1Paint.setColor(data.getCenterText1Color()); - centerCircleText1Paint.getFontMetricsInt(centerCircleText1FontMetrics); - if (null != data.getCenterText2Typeface()) { - centerCircleText2Paint.setTypeface(data.getCenterText2Typeface()); - } - centerCircleText2Paint.setTextSize(ChartUtils.sp2px(scaledDensity, data.getCenterText2FontSize())); - centerCircleText2Paint.setColor(data.getCenterText2Color()); - centerCircleText2Paint.getFontMetricsInt(centerCircleText2FontMetrics); - - onChartViewportChanged(); - } - - @Override - public void onChartViewportChanged() { - if (isViewportCalculationEnabled) { - calculateMaxViewport(); - computator.setMaxViewport(tempMaximumViewport); - computator.setCurrentViewport(computator.getMaximumViewport()); - } - } - - @Override - public void draw(Canvas canvas) { - drawSlices(canvas, MODE_DRAW); + private static final float MAX_WIDTH_HEIGHT = 100f; + private static final int DEFAULT_START_ROTATION = 45; + private int rotation = DEFAULT_START_ROTATION; + private static final float DEFAULT_LABEL_INSIDE_RADIUS_FACTOR = 0.7f; + private static final float DEFAULT_LABEL_OUTSIDE_RADIUS_FACTOR = 1.0f; + private static final int DEFAULT_TOUCH_ADDITIONAL_DP = 8; + private static final int MODE_DRAW = 0; + private static final int MODE_HIGHLIGHT = 1; + private PieChartDataProvider dataProvider; + private Paint slicePaint = new Paint(); + private float maxSum; + private RectF originCircleOval = new RectF(); + private RectF drawCircleOval = new RectF(); + private PointF sliceVector = new PointF(); + private int touchAdditional; + private float circleFillRatio = 1.0f; + + // Center circle related attributes + private boolean hasCenterCircle; + private float centerCircleScale; + private Paint centerCirclePaint = new Paint(); + // Text1 + private Paint centerCircleText1Paint = new Paint(); + private FontMetricsInt centerCircleText1FontMetrics = new FontMetricsInt(); + // Text2 + private Paint centerCircleText2Paint = new Paint(); + private FontMetricsInt centerCircleText2FontMetrics = new FontMetricsInt(); + // Separation lines + private Paint separationLinesPaint = new Paint(); + + private boolean hasLabelsOutside; + private boolean hasLabels; + private boolean hasLabelsOnlyForSelected; + private PieChartValueFormatter valueFormatter; + private Viewport tempMaximumViewport = new Viewport(); + + private Bitmap softwareBitmap; + private Canvas softwareCanvas = new Canvas(); + + public PieChartRenderer(Context context, Chart chart, PieChartDataProvider dataProvider) { + super(context, chart); + this.dataProvider = dataProvider; + touchAdditional = ChartUtils.dp2px(density, DEFAULT_TOUCH_ADDITIONAL_DP); + + slicePaint.setAntiAlias(true); + slicePaint.setStyle(Paint.Style.FILL); + + centerCirclePaint.setAntiAlias(true); + centerCirclePaint.setStyle(Paint.Style.FILL); + centerCirclePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); + + centerCircleText1Paint.setAntiAlias(true); + centerCircleText1Paint.setTextAlign(Align.CENTER); + + centerCircleText2Paint.setAntiAlias(true); + centerCircleText2Paint.setTextAlign(Align.CENTER); + + separationLinesPaint.setAntiAlias(true); + separationLinesPaint.setStyle(Paint.Style.STROKE); + separationLinesPaint.setStrokeCap(Paint.Cap.ROUND); + separationLinesPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + separationLinesPaint.setColor(Color.TRANSPARENT); + } + + @Override + public void onChartSizeChanged() { + calculateCircleOval(); + + if (computator.getChartWidth() > 0 && computator.getChartHeight() > 0) { + softwareBitmap = Bitmap.createBitmap(computator.getChartWidth(), computator.getChartHeight(), + Bitmap.Config.ARGB_8888); + softwareCanvas.setBitmap(softwareBitmap); + } + } + + @Override + public void onChartDataChanged() { + super.onChartDataChanged(); + final PieChartData data = dataProvider.getPieChartData(); + hasLabelsOutside = data.hasLabelsOutside(); + hasLabels = data.hasLabels(); + hasLabelsOnlyForSelected = data.hasLabelsOnlyForSelected(); + valueFormatter = data.getFormatter(); + hasCenterCircle = data.hasCenterCircle(); + centerCircleScale = data.getCenterCircleScale(); + centerCirclePaint.setColor(data.getCenterCircleColor()); + if (null != data.getCenterText1Typeface()) { + centerCircleText1Paint.setTypeface(data.getCenterText1Typeface()); + } + centerCircleText1Paint.setTextSize(ChartUtils.sp2px(scaledDensity, data.getCenterText1FontSize())); + centerCircleText1Paint.setColor(data.getCenterText1Color()); + centerCircleText1Paint.getFontMetricsInt(centerCircleText1FontMetrics); + if (null != data.getCenterText2Typeface()) { + centerCircleText2Paint.setTypeface(data.getCenterText2Typeface()); + } + centerCircleText2Paint.setTextSize(ChartUtils.sp2px(scaledDensity, data.getCenterText2FontSize())); + centerCircleText2Paint.setColor(data.getCenterText2Color()); + centerCircleText2Paint.getFontMetricsInt(centerCircleText2FontMetrics); + + onChartViewportChanged(); + } + + @Override + public void onChartViewportChanged() { + if (isViewportCalculationEnabled) { + calculateMaxViewport(); + computator.setMaxViewport(tempMaximumViewport); + computator.setCurrentViewport(computator.getMaximumViewport()); + } + } + + @Override + public void draw(Canvas canvas) { + // softwareBitmap can be null if chart is rendered in layout editor. In that case use default canvas and not + // softwareCanvas. + final Canvas drawCanvas; + if (null != softwareBitmap) { + drawCanvas = softwareCanvas; + drawCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + } else { + drawCanvas = canvas; + } + + drawSlices(drawCanvas); + drawSeparationLines(drawCanvas); if (hasCenterCircle) { - drawCenterCircle(canvas); + drawCenterCircle(drawCanvas); } - drawLabels(canvas, MODE_DRAW); + drawLabels(drawCanvas); - if (isTouched()) { - //Redraw in highlight mode:( - drawSlices(canvas, MODE_HIGHLIGHT); - if (hasCenterCircle) { - drawCenterCircle(canvas); + if (null != softwareBitmap) { + canvas.drawBitmap(softwareBitmap, 0, 0, null); + } + } + + @Override + public void drawUnclipped(Canvas canvas) { + } + + @Override + public boolean checkTouch(float touchX, float touchY) { + selectedValue.clear(); + final PieChartData data = dataProvider.getPieChartData(); + final float centerX = originCircleOval.centerX(); + final float centerY = originCircleOval.centerY(); + final float circleRadius = originCircleOval.width() / 2f; + + sliceVector.set(touchX - centerX, touchY - centerY); + // Check if touch is on circle area, if not return false; + if (sliceVector.length() > circleRadius + touchAdditional) { + return false; + } + // Check if touch is not in center circle, if yes return false; + if (data.hasCenterCircle() && sliceVector.length() < circleRadius * data.getCenterCircleScale()) { + return false; + } + + // Get touchAngle and align touch 0 degrees with chart 0 degrees, that why I subtracting start angle, + // adding 360 + // and modulo 360 translates i.e -20 degrees to 340 degrees. + final float touchAngle = (pointToAngle(touchX, touchY, centerX, centerY) - rotation + 360f) % 360f; + final float sliceScale = 360f / maxSum; + float lastAngle = 0f; // No start angle here, see above + int sliceIndex = 0; + for (SliceValue sliceValue : data.getValues()) { + final float angle = Math.abs(sliceValue.getValue()) * sliceScale; + if (touchAngle >= lastAngle) { + selectedValue.set(sliceIndex, sliceIndex, SelectedValueType.NONE); } - drawLabels(canvas, MODE_HIGHLIGHT); + lastAngle += angle; + ++sliceIndex; + } + return isTouched(); + } + + /** + * Draw center circle with text if {@link PieChartData#hasCenterCircle()} is set true. + */ + private void drawCenterCircle(Canvas canvas) { + final PieChartData data = dataProvider.getPieChartData(); + final float circleRadius = originCircleOval.width() / 2f; + final float centerRadius = circleRadius * data.getCenterCircleScale(); + final float centerX = originCircleOval.centerX(); + final float centerY = originCircleOval.centerY(); + + canvas.drawCircle(centerX, centerY, centerRadius, centerCirclePaint); + + // Draw center text1 and text2 if not empty. + if (!TextUtils.isEmpty(data.getCenterText1())) { + + final int text1Height = Math.abs(centerCircleText1FontMetrics.ascent); + + if (!TextUtils.isEmpty(data.getCenterText2())) { + // Draw text 2 only if text 1 is not empty. + final int text2Height = Math.abs(centerCircleText2FontMetrics.ascent); + canvas.drawText(data.getCenterText1(), centerX, centerY - text1Height * 0.2f, centerCircleText1Paint); + canvas.drawText(data.getCenterText2(), centerX, centerY + text2Height, centerCircleText2Paint); + } else { + canvas.drawText(data.getCenterText1(), centerX, centerY + text1Height / 4, centerCircleText1Paint); + } + } + } + + /** + * Draw all slices for this PieChart, if mode == {@link #MODE_HIGHLIGHT} currently selected slices will be redrawn + * and + * highlighted. + * + * @param canvas + */ + private void drawSlices(Canvas canvas) { + final PieChartData data = dataProvider.getPieChartData(); + final float sliceScale = 360f / maxSum; + float lastAngle = rotation; + int sliceIndex = 0; + for (SliceValue sliceValue : data.getValues()) { + final float angle = Math.abs(sliceValue.getValue()) * sliceScale; + if (isTouched() && selectedValue.getFirstIndex() == sliceIndex) { + drawSlice(canvas, sliceValue, lastAngle, angle, MODE_HIGHLIGHT); + }else{ + drawSlice(canvas, sliceValue, lastAngle, angle, MODE_DRAW); + } + lastAngle += angle; + ++sliceIndex; + } + } + + private void drawSeparationLines(Canvas canvas) { + final PieChartData data = dataProvider.getPieChartData(); + final float sliceScale = 360f / maxSum; + float lastAngle = rotation; + final float circleRadius = originCircleOval.width() / 2f; + final int sliceSpacing = ChartUtils.dp2px(density, data.getSlicesSpacing()); + separationLinesPaint.setStrokeWidth(sliceSpacing); + for (SliceValue sliceValue : data.getValues()) { + final float angle = Math.abs(sliceValue.getValue()) * sliceScale; + + sliceVector.set((float) (Math.cos(Math.toRadians(lastAngle))), + (float) (Math.sin(Math.toRadians(lastAngle)))); + normalizeVector(sliceVector); + + float x1 = sliceVector.x * (circleRadius + touchAdditional) + originCircleOval.centerX(); + float y1 = sliceVector.y * (circleRadius + touchAdditional) + originCircleOval.centerY(); + canvas.drawLine(originCircleOval.centerX(), originCircleOval.centerY(), x1, y1, separationLinesPaint); + + lastAngle += angle; } - } - - @Override - public void drawUnclipped(Canvas canvas) { - } - - @Override - public boolean checkTouch(float touchX, float touchY) { - selectedValue.clear(); - final PieChartData data = dataProvider.getPieChartData(); - final float centerX = originCircleOval.centerX(); - final float centerY = originCircleOval.centerY(); - final float circleRadius = originCircleOval.width() / 2f; - - sliceVector.set(touchX - centerX, touchY - centerY); - // Check if touch is on circle area, if not return false; - if (sliceVector.length() > circleRadius + touchAdditional) { - return false; - } - // Check if touch is not in center circle, if yes return false; - if (data.hasCenterCircle() && sliceVector.length() < circleRadius * data.getCenterCircleScale()) { - return false; - } - - // Get touchAngle and align touch 0 degrees with chart 0 degrees, that why I subtracting start angle, - // adding 360 - // and modulo 360 translates i.e -20 degrees to 340 degrees. - final float touchAngle = (pointToAngle(touchX, touchY, centerX, centerY) - rotation + 360f) % 360f; - final float sliceScale = 360f / maxSum; - float lastAngle = 0f; // No start angle here, see above - int sliceIndex = 0; - for (SliceValue sliceValue : data.getValues()) { - final float angle = Math.abs(sliceValue.getValue()) * sliceScale; - if (touchAngle >= lastAngle) { - selectedValue.set(sliceIndex, sliceIndex, SelectedValueType.NONE); - } - lastAngle += angle; - ++sliceIndex; - } - return isTouched(); - } - - /** - * Draw center circle with text if {@link PieChartData#hasCenterCircle()} is set true. - */ - private void drawCenterCircle(Canvas canvas) { - final PieChartData data = dataProvider.getPieChartData(); - final float circleRadius = originCircleOval.width() / 2f; - final float centerRadius = circleRadius * data.getCenterCircleScale(); - final float centerX = originCircleOval.centerX(); - final float centerY = originCircleOval.centerY(); - - canvas.drawCircle(centerX, centerY, centerRadius, centerCirclePaint); - - // Draw center text1 and text2 if not empty. - if (!TextUtils.isEmpty(data.getCenterText1())) { - - final int text1Height = Math.abs(centerCircleText1FontMetrics.ascent); - - if (!TextUtils.isEmpty(data.getCenterText2())) { - // Draw text 2 only if text 1 is not empty. - final int text2Height = Math.abs(centerCircleText2FontMetrics.ascent); - canvas.drawText(data.getCenterText1(), centerX, centerY - text1Height * 0.2f, centerCircleText1Paint); - canvas.drawText(data.getCenterText2(), centerX, centerY + text2Height, centerCircleText2Paint); - } else { - canvas.drawText(data.getCenterText1(), centerX, centerY + text1Height / 4, centerCircleText1Paint); - } - } - } - - /** - * Draw all slices for this PieChart, if mode == {@link #MODE_HIGHLIGHT} currently selected slices will be redrawn - * and - * highlighted. - * - * @param canvas - * @param mode - */ - private void drawSlices(Canvas canvas, int mode) { - final PieChartData data = dataProvider.getPieChartData(); - final float sliceScale = 360f / maxSum; - float lastAngle = rotation; - int sliceIndex = 0; - for (SliceValue sliceValue : data.getValues()) { - final float angle = Math.abs(sliceValue.getValue()) * sliceScale; - if (MODE_DRAW == mode) { - drawSlice(canvas, sliceValue, lastAngle, angle, mode); - } else if (MODE_HIGHLIGHT == mode) { - highlightSlice(canvas, sliceValue, lastAngle, angle, sliceIndex); - } else { - throw new IllegalStateException("Cannot process slice in mode: " + mode); - } - lastAngle += angle; - ++sliceIndex; - } - } - - public void drawLabels(Canvas canvas, int mode){ + } + + public void drawLabels(Canvas canvas) { final PieChartData data = dataProvider.getPieChartData(); final float sliceScale = 360f / maxSum; float lastAngle = rotation; for (SliceValue sliceValue : data.getValues()) { final float angle = Math.abs(sliceValue.getValue()) * sliceScale; - if (MODE_HIGHLIGHT == mode) { + if (isTouched()) { if (hasLabels || hasLabelsOnlyForSelected) { drawLabel(canvas, sliceValue, lastAngle, angle); } @@ -251,215 +298,204 @@ public void drawLabels(Canvas canvas, int mode){ } } - /** - * Method draws single slice from lastAngle to lastAngle+angle, if mode = {@link #MODE_HIGHLIGHT} slice will be - * darken - * and will have bigger radius. - */ - private void drawSlice(Canvas canvas, SliceValue sliceValue, float lastAngle, float angle, int mode) { - sliceVector.set((float) (Math.cos(Math.toRadians(lastAngle + angle / 2))), - (float) (Math.sin(Math.toRadians(lastAngle + angle / 2)))); - normalizeVector(sliceVector); - - drawCircleOval.set(originCircleOval); - final int sliceSpacing = ChartUtils.dp2px(density, sliceValue.getSliceSpacing()); - drawCircleOval.inset(sliceSpacing, sliceSpacing); - drawCircleOval.offset((float) (sliceVector.x * sliceSpacing), (float) (sliceVector.y * sliceSpacing)); - if (MODE_HIGHLIGHT == mode) { - // Add additional touch feedback by setting bigger radius for that slice and darken color. - drawCircleOval.inset(-touchAdditional, -touchAdditional); - slicePaint.setColor(sliceValue.getDarkenColor()); - canvas.drawArc(drawCircleOval, lastAngle, angle, true, slicePaint); - } else { - slicePaint.setColor(sliceValue.getColor()); - canvas.drawArc(drawCircleOval, lastAngle, angle, true, slicePaint); - } - } - - private void highlightSlice(Canvas canvas, SliceValue sliceValue, float lastAngle, float angle, int sliceIndex) { - if (selectedValue.getFirstIndex() != sliceIndex) { - return; - } - drawSlice(canvas, sliceValue, lastAngle, angle, MODE_HIGHLIGHT); - } - - private void drawLabel(Canvas canvas, SliceValue sliceValue, float lastAngle, float angle) { + /** + * Method draws single slice from lastAngle to lastAngle+angle, if mode = {@link #MODE_HIGHLIGHT} slice will be + * darken + * and will have bigger radius. + */ + private void drawSlice(Canvas canvas, SliceValue sliceValue, float lastAngle, float angle, int mode) { + sliceVector.set((float) (Math.cos(Math.toRadians(lastAngle + angle / 2))), + (float) (Math.sin(Math.toRadians(lastAngle + angle / 2)))); + normalizeVector(sliceVector); + drawCircleOval.set(originCircleOval); + if (MODE_HIGHLIGHT == mode) { + // Add additional touch feedback by setting bigger radius for that slice and darken color. + drawCircleOval.inset(-touchAdditional, -touchAdditional); + slicePaint.setColor(sliceValue.getDarkenColor()); + canvas.drawArc(drawCircleOval, lastAngle, angle, true, slicePaint); + } else { + slicePaint.setColor(sliceValue.getColor()); + canvas.drawArc(drawCircleOval, lastAngle, angle, true, slicePaint); + } + } + + private void drawLabel(Canvas canvas, SliceValue sliceValue, float lastAngle, float angle) { sliceVector.set((float) (Math.cos(Math.toRadians(lastAngle + angle / 2))), (float) (Math.sin(Math.toRadians(lastAngle + angle / 2)))); normalizeVector(sliceVector); - final int numChars = valueFormatter.formatChartValue(labelBuffer, sliceValue); - - if (numChars == 0) { - // No need to draw empty label - return; - } - - final float labelWidth = labelPaint.measureText(labelBuffer, labelBuffer.length - numChars, numChars); - final int labelHeight = Math.abs(fontMetrics.ascent); - - final float centerX = originCircleOval.centerX(); - final float centerY = originCircleOval.centerY(); - final float circleRadius = originCircleOval.width() / 2f; - final float labelRadius; - - if (hasLabelsOutside) { - labelRadius = circleRadius * DEFAULT_LABEL_OUTSIDE_RADIUS_FACTOR; - } else { - if (hasCenterCircle) { - labelRadius = circleRadius - (circleRadius - (circleRadius * centerCircleScale)) / 2; - } else { - labelRadius = circleRadius * DEFAULT_LABEL_INSIDE_RADIUS_FACTOR; - } - } - - final float rawX = labelRadius * sliceVector.x + centerX; - final float rawY = labelRadius * sliceVector.y + centerY; - - float left; - float right; - float top; - float bottom; - - if (hasLabelsOutside) { - if (rawX > centerX) { - // Right half. - left = rawX + labelMargin; - right = rawX + labelWidth + labelMargin * 3; - } else { - left = rawX - labelWidth - labelMargin * 3; - right = rawX - labelMargin; - } - - if (rawY > centerY) { - // Lower half. - top = rawY + labelMargin; - bottom = rawY + labelHeight + labelMargin * 3; - } else { - top = rawY - labelHeight - labelMargin * 3; - bottom = rawY - labelMargin; - } - } else { - left = rawX - labelWidth / 2 - labelMargin; - right = rawX + labelWidth / 2 + labelMargin; - top = rawY - labelHeight / 2 - labelMargin; - bottom = rawY + labelHeight / 2 + labelMargin; - } - - labelBackgroundRect.set(left, top, right, bottom); - drawLabelTextAndBackground(canvas, labelBuffer, labelBuffer.length - numChars, numChars, - sliceValue.getDarkenColor()); - } - - private void normalizeVector(PointF point) { - final float abs = point.length(); - point.set(point.x / abs, point.y / abs); - } - - /** - * Calculates angle of touched point. - */ - private float pointToAngle(float x, float y, float centerX, float centerY) { - double diffX = x - centerX; - double diffY = y - centerY; - // Pass -diffX to get clockwise degrees order. - double radian = Math.atan2(-diffX, diffY); - - float angle = ((float) Math.toDegrees(radian) + 360) % 360; - // Add 90 because atan2 returns 0 degrees at 6 o'clock. - angle += 90f; - return angle; - } - - /** - * Calculates rectangle(square) that will constraint chart circle. - */ - private void calculateCircleOval() { - Rect contentRect = computator.getContentRectMinusAllMargins(); - final float circleRadius = Math.min(contentRect.width() / 2f, contentRect.height() / 2f); - final float centerX = contentRect.centerX(); - final float centerY = contentRect.centerY(); - final float left = centerX - circleRadius + touchAdditional; - final float top = centerY - circleRadius + touchAdditional; - final float right = centerX + circleRadius - touchAdditional; - final float bottom = centerY + circleRadius - touchAdditional; - originCircleOval.set(left, top, right, bottom); - final float inest = 0.5f * originCircleOval.width() * (1.0f - circleFillRatio); - originCircleOval.inset(inest, inest); - } - - /** - * Viewport is not really important for PieChart, this kind of chart doesn't relay on viewport but uses pixels - * coordinates instead. This method also calculates sum of all SliceValues. - */ - private void calculateMaxViewport() { - tempMaximumViewport.set(0, MAX_WIDTH_HEIGHT, MAX_WIDTH_HEIGHT, 0); - maxSum = 0.0f; - for (SliceValue sliceValue : dataProvider.getPieChartData().getValues()) { - maxSum += Math.abs(sliceValue.getValue()); - } - } - - public RectF getCircleOval() { - return originCircleOval; - } - - public void setCircleOval(RectF orginCircleOval) { - this.originCircleOval = orginCircleOval; - } - - public int getChartRotation() { - return rotation; - } - - public void setChartRotation(int rotation) { - rotation = (rotation % 360 + 360) % 360; - this.rotation = rotation; - } - - /** - * Returns SliceValue that is under given angle, selectedValue (if not null) will be hold slice index. - */ - public SliceValue getValueForAngle(int angle, SelectedValue selectedValue) { - final PieChartData data = dataProvider.getPieChartData(); - final float touchAngle = (angle - rotation + 360f) % 360f; - final float sliceScale = 360f / maxSum; - float lastAngle = 0f; - int sliceIndex = 0; - for (SliceValue sliceValue : data.getValues()) { - final float tempAngle = Math.abs(sliceValue.getValue()) * sliceScale; - if (touchAngle >= lastAngle) { - if (null != selectedValue) { - selectedValue.set(sliceIndex, sliceIndex, SelectedValueType.NONE); - } - return sliceValue; - } - lastAngle += tempAngle; - ++sliceIndex; - } - return null; - } - - /** - * @see #setCircleFillRatio(float) - */ - public float getCircleFillRatio() { - return this.circleFillRatio; - } - - /** - * Set how much of view area should be taken by chart circle. Value should be between 0 and 1. Default is 1 so - * circle will have radius equals min(View.width, View.height). - */ - public void setCircleFillRatio(float fillRatio) { - if (fillRatio < 0) { - fillRatio = 0; - } else if (fillRatio > 1) { - fillRatio = 1; - } - - this.circleFillRatio = fillRatio; - calculateCircleOval(); - } + final int numChars = valueFormatter.formatChartValue(labelBuffer, sliceValue); + + if (numChars == 0) { + // No need to draw empty label + return; + } + + final float labelWidth = labelPaint.measureText(labelBuffer, labelBuffer.length - numChars, numChars); + final int labelHeight = Math.abs(fontMetrics.ascent); + + final float centerX = originCircleOval.centerX(); + final float centerY = originCircleOval.centerY(); + final float circleRadius = originCircleOval.width() / 2f; + final float labelRadius; + + if (hasLabelsOutside) { + labelRadius = circleRadius * DEFAULT_LABEL_OUTSIDE_RADIUS_FACTOR; + } else { + if (hasCenterCircle) { + labelRadius = circleRadius - (circleRadius - (circleRadius * centerCircleScale)) / 2; + } else { + labelRadius = circleRadius * DEFAULT_LABEL_INSIDE_RADIUS_FACTOR; + } + } + + final float rawX = labelRadius * sliceVector.x + centerX; + final float rawY = labelRadius * sliceVector.y + centerY; + + float left; + float right; + float top; + float bottom; + + if (hasLabelsOutside) { + if (rawX > centerX) { + // Right half. + left = rawX + labelMargin; + right = rawX + labelWidth + labelMargin * 3; + } else { + left = rawX - labelWidth - labelMargin * 3; + right = rawX - labelMargin; + } + + if (rawY > centerY) { + // Lower half. + top = rawY + labelMargin; + bottom = rawY + labelHeight + labelMargin * 3; + } else { + top = rawY - labelHeight - labelMargin * 3; + bottom = rawY - labelMargin; + } + } else { + left = rawX - labelWidth / 2 - labelMargin; + right = rawX + labelWidth / 2 + labelMargin; + top = rawY - labelHeight / 2 - labelMargin; + bottom = rawY + labelHeight / 2 + labelMargin; + } + + labelBackgroundRect.set(left, top, right, bottom); + drawLabelTextAndBackground(canvas, labelBuffer, labelBuffer.length - numChars, numChars, + sliceValue.getDarkenColor()); + } + + private void normalizeVector(PointF point) { + final float abs = point.length(); + point.set(point.x / abs, point.y / abs); + } + + /** + * Calculates angle of touched point. + */ + private float pointToAngle(float x, float y, float centerX, float centerY) { + double diffX = x - centerX; + double diffY = y - centerY; + // Pass -diffX to get clockwise degrees order. + double radian = Math.atan2(-diffX, diffY); + + float angle = ((float) Math.toDegrees(radian) + 360) % 360; + // Add 90 because atan2 returns 0 degrees at 6 o'clock. + angle += 90f; + return angle; + } + + /** + * Calculates rectangle(square) that will constraint chart circle. + */ + private void calculateCircleOval() { + Rect contentRect = computator.getContentRectMinusAllMargins(); + final float circleRadius = Math.min(contentRect.width() / 2f, contentRect.height() / 2f); + final float centerX = contentRect.centerX(); + final float centerY = contentRect.centerY(); + final float left = centerX - circleRadius + touchAdditional; + final float top = centerY - circleRadius + touchAdditional; + final float right = centerX + circleRadius - touchAdditional; + final float bottom = centerY + circleRadius - touchAdditional; + originCircleOval.set(left, top, right, bottom); + final float inest = 0.5f * originCircleOval.width() * (1.0f - circleFillRatio); + originCircleOval.inset(inest, inest); + } + + /** + * Viewport is not really important for PieChart, this kind of chart doesn't relay on viewport but uses pixels + * coordinates instead. This method also calculates sum of all SliceValues. + */ + private void calculateMaxViewport() { + tempMaximumViewport.set(0, MAX_WIDTH_HEIGHT, MAX_WIDTH_HEIGHT, 0); + maxSum = 0.0f; + for (SliceValue sliceValue : dataProvider.getPieChartData().getValues()) { + maxSum += Math.abs(sliceValue.getValue()); + } + } + + public RectF getCircleOval() { + return originCircleOval; + } + + public void setCircleOval(RectF orginCircleOval) { + this.originCircleOval = orginCircleOval; + } + + public int getChartRotation() { + return rotation; + } + + public void setChartRotation(int rotation) { + rotation = (rotation % 360 + 360) % 360; + this.rotation = rotation; + } + + /** + * Returns SliceValue that is under given angle, selectedValue (if not null) will be hold slice index. + */ + public SliceValue getValueForAngle(int angle, SelectedValue selectedValue) { + final PieChartData data = dataProvider.getPieChartData(); + final float touchAngle = (angle - rotation + 360f) % 360f; + final float sliceScale = 360f / maxSum; + float lastAngle = 0f; + int sliceIndex = 0; + for (SliceValue sliceValue : data.getValues()) { + final float tempAngle = Math.abs(sliceValue.getValue()) * sliceScale; + if (touchAngle >= lastAngle) { + if (null != selectedValue) { + selectedValue.set(sliceIndex, sliceIndex, SelectedValueType.NONE); + } + return sliceValue; + } + lastAngle += tempAngle; + ++sliceIndex; + } + return null; + } + + /** + * @see #setCircleFillRatio(float) + */ + public float getCircleFillRatio() { + return this.circleFillRatio; + } + + /** + * Set how much of view area should be taken by chart circle. Value should be between 0 and 1. Default is 1 so + * circle will have radius equals min(View.width, View.height). + */ + public void setCircleFillRatio(float fillRatio) { + if (fillRatio < 0) { + fillRatio = 0; + } else if (fillRatio > 1) { + fillRatio = 1; + } + + this.circleFillRatio = fillRatio; + calculateCircleOval(); + } } diff --git a/hellocharts-samples/res/menu/pie_chart.xml b/hellocharts-samples/res/menu/pie_chart.xml index d6e393e7..2b847533 100644 --- a/hellocharts-samples/res/menu/pie_chart.xml +++ b/hellocharts-samples/res/menu/pie_chart.xml @@ -11,10 +11,6 @@ android:id="@+id/action_explode" android:title="Explode/Implode chart" app:showAsAction="never"/> - <item - android:id="@+id/action_single_arc_separation" - android:title="Toggle single arc separation" - app:showAsAction="never"/> <item android:id="@+id/action_center_circle" android:title="Center circle" diff --git a/hellocharts-samples/src/lecho/lib/hellocharts/samples/PieChartActivity.java b/hellocharts-samples/src/lecho/lib/hellocharts/samples/PieChartActivity.java index 02455ec6..ed1794a4 100644 --- a/hellocharts-samples/src/lecho/lib/hellocharts/samples/PieChartActivity.java +++ b/hellocharts-samples/src/lecho/lib/hellocharts/samples/PieChartActivity.java @@ -46,8 +46,7 @@ public static class PlaceholderFragment extends Fragment { private boolean hasCenterCircle = false; private boolean hasCenterText1 = false; private boolean hasCenterText2 = false; - private boolean isExploaded = false; - private boolean hasArcSeparated = false; + private boolean isExploded = false; private boolean hasLabelForSelected = false; public PlaceholderFragment() { @@ -84,10 +83,6 @@ public boolean onOptionsItemSelected(MenuItem item) { explodeChart(); return true; } - if (id == R.id.action_single_arc_separation) { - separateSingleArc(); - return true; - } if (id == R.id.action_center_circle) { hasCenterCircle = !hasCenterCircle; if (!hasCenterCircle) { @@ -151,8 +146,7 @@ private void reset() { hasCenterCircle = false; hasCenterText1 = false; hasCenterText2 = false; - isExploaded = false; - hasArcSeparated = false; + isExploded = false; hasLabelForSelected = false; } @@ -162,15 +156,6 @@ private void generateData() { List<SliceValue> values = new ArrayList<SliceValue>(); for (int i = 0; i < numValues; ++i) { SliceValue sliceValue = new SliceValue((float) Math.random() * 30 + 15, ChartUtils.pickColor()); - - if (isExploaded) { - sliceValue.setSliceSpacing(24); - } - - if (hasArcSeparated && i == 0) { - sliceValue.setSliceSpacing(32); - } - values.add(sliceValue); } @@ -180,7 +165,11 @@ private void generateData() { data.setHasLabelsOutside(hasLabelsOutside); data.setHasCenterCircle(hasCenterCircle); - if (hasCenterText1) { + if (isExploded) { + data.setSlicesSpacing(24); + } + + if (hasCenterText1) { data.setCenterText1("Hello!"); // Get roboto-italic font. @@ -206,19 +195,11 @@ private void generateData() { } private void explodeChart() { - isExploaded = !isExploaded; + isExploded = !isExploded; generateData(); } - private void separateSingleArc() { - hasArcSeparated = !hasArcSeparated; - if (hasArcSeparated) { - isExploaded = false; - } - generateData(); - } - private void toggleLabelsOutside() { // has labels have to be true:P hasLabelsOutside = !hasLabelsOutside;