• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2013 AndroidPlot.com
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16 
17 package com.androidplot.pie;
18 
19 import android.graphics.*;
20 
21 import com.androidplot.exception.PlotRenderException;
22 import com.androidplot.ui.SeriesRenderer;
23 
24 import java.util.Set;
25 
26 public class PieRenderer extends SeriesRenderer<PieChart, Segment, SegmentFormatter> {
27 
28     // starting angle to use when drawing the first radial line of the first segment.
29     @SuppressWarnings("FieldCanBeLocal")
30     private float startDeg = 0;
31     private float endDeg = 360;
32 
33     // TODO: express donut in units other than px.
34     private float donutSize = 0.5f;
35     private DonutMode donutMode = DonutMode.PERCENT;
36 
37     public enum DonutMode {
38         PERCENT,
39         DP,
40         PIXELS
41     }
42 
PieRenderer(PieChart plot)43     public PieRenderer(PieChart plot) {
44         super(plot);
45     }
46 
getRadius(RectF rect)47     public float getRadius(RectF rect) {
48     	return  rect.width() < rect.height() ? rect.width() / 2 : rect.height() / 2;
49     }
50 
51     @Override
onRender(Canvas canvas, RectF plotArea)52     public void onRender(Canvas canvas, RectF plotArea) throws PlotRenderException {
53 
54         float radius = getRadius(plotArea);
55         PointF origin = new PointF(plotArea.centerX(), plotArea.centerY());
56 
57         double[] values = getValues();
58         double scale = calculateScale(values);
59         float offset = startDeg;
60         Set<Segment> segments = getPlot().getSeriesSet();
61 
62         //PointF lastRadial = calculateLineEnd(origin, radius, offset);
63 
64         RectF rec = new RectF(origin.x - radius, origin.y - radius, origin.x + radius, origin.y + radius);
65 
66         int i = 0;
67         for (Segment segment : segments) {
68             float lastOffset = offset;
69             float sweep = (float) (scale * (values[i]) * 360);
70             offset += sweep;
71             //PointF radial = calculateLineEnd(origin, radius, offset);
72             drawSegment(canvas, rec, segment, getPlot().getFormatter(segment, PieRenderer.class),
73                     radius, lastOffset, sweep);
74             //lastRadial = radial;
75             i++;
76         }
77     }
78 
drawSegment(Canvas canvas, RectF bounds, Segment seg, SegmentFormatter f, float rad, float startAngle, float sweep)79     protected void drawSegment(Canvas canvas, RectF bounds, Segment seg, SegmentFormatter f,
80                                float rad, float startAngle, float sweep) {
81         canvas.save();
82 
83         float cx = bounds.centerX();
84         float cy = bounds.centerY();
85 
86         float donutSizePx;
87         switch(donutMode) {
88             case PERCENT:
89                 donutSizePx = donutSize * rad;
90                 break;
91             case PIXELS:
92                 donutSizePx = (donutSize > 0)?donutSize:(rad + donutSize);
93                 break;
94             default:
95                 throw new UnsupportedOperationException("Not yet implemented.");
96         }
97 
98         // vertices of the first radial:
99         PointF r1Outer = calculateLineEnd(cx, cy, rad, startAngle);
100         PointF r1Inner = calculateLineEnd(cx, cy, donutSizePx, startAngle);
101 
102         // vertices of the second radial:
103         PointF r2Outer = calculateLineEnd(cx, cy, rad, startAngle + sweep);
104         PointF r2Inner = calculateLineEnd(cx, cy, donutSizePx, startAngle + sweep);
105 
106         Path clip = new Path();
107 
108         //float outerStroke = f.getOuterEdgePaint().getStrokeWidth();
109         //float halfOuterStroke = outerStroke / 2;
110 
111         // leave plenty of room on the outside for stroked borders;
112         // necessary because the clipping border is ugly
113         // and cannot be easily anti aliased.  Really we only care about masking off the
114         // radial edges.
115         clip.arcTo(new RectF(bounds.left - rad,
116                 bounds.top - rad,
117                 bounds.right + rad,
118                 bounds.bottom + rad),
119                 startAngle, sweep);
120         clip.lineTo(cx, cy);
121         clip.close();
122         canvas.clipPath(clip);
123 
124         Path p = new Path();
125         p.arcTo(bounds, startAngle, sweep);
126         p.lineTo(r2Inner.x, r2Inner.y);
127 
128         // sweep back to original angle:
129         p.arcTo(new RectF(
130                 cx - donutSizePx,
131                 cy - donutSizePx,
132                 cx + donutSizePx,
133                 cy + donutSizePx),
134                 startAngle + sweep, -sweep);
135 
136         p.close();
137 
138         // fill segment:
139         canvas.drawPath(p, f.getFillPaint());
140 
141         // draw radial lines
142         canvas.drawLine(r1Inner.x, r1Inner.y, r1Outer.x, r1Outer.y, f.getRadialEdgePaint());
143         canvas.drawLine(r2Inner.x, r2Inner.y, r2Outer.x, r2Outer.y, f.getRadialEdgePaint());
144 
145         // draw inner line:
146         canvas.drawCircle(cx, cy, donutSizePx, f.getInnerEdgePaint());
147 
148         // draw outer line:
149         canvas.drawCircle(cx, cy, rad, f.getOuterEdgePaint());
150         canvas.restore();
151 
152         PointF labelOrigin = calculateLineEnd(cx, cy,
153                 (rad-((rad- donutSizePx)/2)), startAngle + (sweep/2));
154 
155         // TODO: move segment labelling outside the segment drawing loop
156         // TODO: so that the labels will not be clipped by the edge of the next
157         // TODO: segment being drawn.
158         drawSegmentLabel(canvas, labelOrigin, seg, f);
159     }
160 
drawSegmentLabel(Canvas canvas, PointF origin, Segment seg, SegmentFormatter f)161     protected void drawSegmentLabel(Canvas canvas, PointF origin,
162                                     Segment seg, SegmentFormatter f) {
163         canvas.drawText(seg.getTitle(), origin.x, origin.y, f.getLabelPaint());
164 
165     }
166 
167     @Override
doDrawLegendIcon(Canvas canvas, RectF rect, SegmentFormatter formatter)168     protected void doDrawLegendIcon(Canvas canvas, RectF rect, SegmentFormatter formatter) {
169         throw new UnsupportedOperationException("Not yet implemented.");
170     }
171 
172     /**
173      * Determines how many counts there are per cent of whatever the
174      * pie chart is displaying as a fraction, 1 being 100%.
175      */
calculateScale(double[] values)176     private double calculateScale(double[] values) {
177         double total = 0;
178         for (int i = 0; i < values.length; i++) {
179 			total += values[i];
180 		}
181 
182         return (1d / total);
183     }
184 
getValues()185 	private double[] getValues() {
186 		Set<Segment> segments = getPlot().getSeriesSet();
187 		double[] result = new double[segments.size()];
188 		int i = 0;
189 		for (Segment seg : getPlot().getSeriesSet()) {
190 			result[i] = seg.getValue().doubleValue();
191 			i++;
192 		}
193 		return result;
194 	}
195 
calculateLineEnd(float x, float y, float rad, float deg)196     private PointF calculateLineEnd(float x, float y, float rad, float deg) {
197         return calculateLineEnd(new PointF(x, y), rad, deg);
198     }
199 
calculateLineEnd(PointF origin, float rad, float deg)200     private PointF calculateLineEnd(PointF origin, float rad, float deg) {
201 
202         double radians = deg * Math.PI / 180F;
203         double x = rad * Math.cos(radians);
204         double y = rad * Math.sin(radians);
205 
206         // convert to screen space:
207         return new PointF(origin.x + (float) x, origin.y + (float) y);
208     }
209 
setDonutSize(float size, DonutMode mode)210     public void setDonutSize(float size, DonutMode mode) {
211         switch(mode) {
212             case PERCENT:
213                 if(size < 0 || size > 1) {
214                     throw new IllegalArgumentException(
215                             "Size parameter must be between 0 and 1 when operating in PERCENT mode.");
216                 }
217                 break;
218             case PIXELS:
219             	break;
220             default:
221                 throw new UnsupportedOperationException("Not yet implemented.");
222         }
223         donutMode = mode;
224         donutSize = size;
225     }
226 
setStartDeg(float deg)227     public void setStartDeg(float deg) {
228         startDeg = deg;
229     }
230 
setEndDeg(float deg)231     public void setEndDeg(float deg) {
232         endDeg = deg;
233     }
234 }
235