PieChartView.java 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. package hr.tacitus.graphs;
  2. import android.content.Context;
  3. import android.content.res.TypedArray;
  4. import android.graphics.Canvas;
  5. import android.graphics.Color;
  6. import android.graphics.Paint;
  7. import android.graphics.RectF;
  8. import android.support.annotation.Nullable;
  9. import android.text.Layout;
  10. import android.text.StaticLayout;
  11. import android.text.TextPaint;
  12. import android.util.AttributeSet;
  13. import android.view.View;
  14. import java.util.ArrayList;
  15. import java.util.List;
  16. /**
  17. * Created by Bero on 7.7.2017..
  18. */
  19. public class PieChartView extends View {
  20. class PieChartItem {
  21. float value;
  22. // color for this pie chart item
  23. Paint paint;
  24. // text
  25. String description;
  26. // cached value
  27. int descriptionWidth;
  28. float getAngle() {
  29. return value * 3.6f;
  30. }
  31. }
  32. private static float MAX_LEGEND_WIDTH_PERCENTAGE = 35f;
  33. private static float MAX_LEGEND_WIDTH_PX = 300;
  34. private static float LEGEND_PADDING_HORIZONTAL = 20f;
  35. private static float LEGEND_PADDING_TOP = 20f;
  36. private float titleTextSizeSp = 16.f;
  37. private boolean showLegend = true;
  38. private float legendTextSizeSp = 10.f;
  39. private float legendSquareSize = 20f;
  40. private float legendWidth;
  41. private float chartPadding = 64.f;
  42. private float chartTextSizeSp = 10.f;
  43. private String title = "This is a title";
  44. private List<Integer> colors;
  45. private TextPaint titlePaint;
  46. private TextPaint valuePaint;
  47. private TextPaint legendDescriptionPaint;
  48. private StaticLayout titleTextLayout;
  49. private RectF titleRect;
  50. private RectF legendRect;
  51. private RectF pieChartRect;
  52. private List<PieChartItem> items = new ArrayList<>();
  53. private boolean readyToDraw = false;
  54. public PieChartView(Context context) {
  55. super(context);
  56. init(null);
  57. }
  58. public PieChartView(Context context, AttributeSet attrs) {
  59. super(context, attrs);
  60. init(attrs);
  61. }
  62. public PieChartView(Context context, AttributeSet attrs, int defStyle) {
  63. super(context, attrs, defStyle);
  64. init(attrs);
  65. }
  66. private void readAttributes(AttributeSet attributeSet) {
  67. TypedArray a = getContext().obtainStyledAttributes(attributeSet, R.styleable.PieChartView);
  68. if (a != null) {
  69. try {
  70. title = a.getString(R.styleable.PieChartView_title);
  71. showLegend = a.getBoolean(R.styleable.PieChartView_showLegend, true);
  72. } finally {
  73. a.recycle();
  74. }
  75. }
  76. }
  77. @Nullable
  78. private RectF getLegendRect() {
  79. if (showLegend) {
  80. float left = titleRect.right - legendWidth;
  81. float right = titleRect.right;
  82. float top = titleRect.bottom;
  83. float bottom = getBottom();
  84. return new RectF(left, top, right, bottom);
  85. }
  86. return null;
  87. }
  88. private RectF getPieChartRect() {
  89. float left = titleRect.left + chartPadding;
  90. float top = titleRect.top + chartPadding;
  91. float right = titleRect.right - chartPadding;
  92. if (showLegend)
  93. right -= legendRect.width();
  94. float bottom = getBottom() - getPaddingLeft() - chartPadding;
  95. RectF rectF = new RectF(left, top, right, bottom);
  96. // width must be equal to height
  97. if (rectF.width() != rectF.height()) {
  98. float width = Math.min(rectF.width(), rectF.height());
  99. left = rectF.centerX() - width / 2;
  100. right = left + width;
  101. top = rectF.centerY() - width / 2;
  102. bottom = top + width;
  103. rectF = new RectF(left, top, right, bottom);
  104. }
  105. return rectF;
  106. }
  107. private RectF getTitleRect() {
  108. return new RectF(getPaddingLeft(), getPaddingTop(),
  109. getWidth() - getPaddingLeft() - getPaddingRight(), 32.f);
  110. }
  111. private void initTitleText() {
  112. // int width = (int) titlePaint.measureText(title);
  113. titleTextLayout = new StaticLayout(title, titlePaint, (int) titleRect.width(),
  114. Layout.Alignment.ALIGN_CENTER, 1.0f, 0, false);
  115. }
  116. private void init(AttributeSet attributeSet) {
  117. if (attributeSet != null)
  118. readAttributes(attributeSet);
  119. titlePaint = new TextPaint();
  120. titlePaint.setAntiAlias(true);
  121. titlePaint.setTextSize(Utils.spsToPixels(titleTextSizeSp, getResources()));
  122. titlePaint.setColor(Color.BLACK);
  123. valuePaint = new TextPaint();
  124. valuePaint.setAntiAlias(true);
  125. valuePaint.setTextSize(Utils.spsToPixels(chartTextSizeSp, getResources()));
  126. valuePaint.setColor(Color.BLACK);
  127. legendDescriptionPaint = new TextPaint();
  128. legendDescriptionPaint.setAntiAlias(true);
  129. legendDescriptionPaint.setTextSize(Utils.spsToPixels(legendTextSizeSp, getResources()));
  130. }
  131. private void initRectangles() {
  132. titleRect = getTitleRect();
  133. if (showLegend) {
  134. updateLegendWidth();
  135. }
  136. legendRect = getLegendRect();
  137. pieChartRect = getPieChartRect();
  138. }
  139. private void drawTitle(Canvas canvas) {
  140. if (titleTextLayout != null)
  141. titleTextLayout.draw(canvas);
  142. }
  143. private void drawChart(Canvas canvas) {
  144. float startAngle = -90.f;
  145. for (PieChartItem item : items) {
  146. canvas.drawArc(pieChartRect, startAngle, item.getAngle(), true, item.paint);
  147. startAngle += item.getAngle();
  148. }
  149. startAngle = 90.f;
  150. for (PieChartItem item : items) {
  151. float angleMiddle = startAngle - item.getAngle() / 2;
  152. float r = pieChartRect.width() / 2;
  153. float x = (float) Math.cos(Math.toRadians(angleMiddle)) * r;
  154. float y = (float) Math.sin(Math.toRadians(angleMiddle)) * r;
  155. String text = Double.toString(item.value);
  156. // Depending on quadrant, adjust text.
  157. float textWidth = valuePaint.measureText(text);
  158. float left = x + pieChartRect.centerX();
  159. float top = pieChartRect.centerY() - y;
  160. // RectF textRect = new RectF(left, top, left + textWidth, top + valuePaint.getTextSize());
  161. canvas.drawText(Integer.toHexString(item.paint.getColor()), left, top, valuePaint);
  162. startAngle -= item.getAngle();
  163. }
  164. }
  165. private void drawLegend(Canvas canvas) {
  166. Paint p = new Paint();
  167. p.setColor(Color.RED);
  168. canvas.drawRect(legendRect, p);
  169. float left = legendRect.left + LEGEND_PADDING_HORIZONTAL;
  170. float top = legendRect.top + LEGEND_PADDING_TOP;
  171. // Draw color explanations
  172. for (int i = 0; i < items.size(); ++i) {
  173. PieChartItem item = items.get(i);
  174. canvas.drawRect(new RectF(left, top, left + legendSquareSize, top + legendSquareSize), item.paint);
  175. // canvas.drawText(Double.toString(item.value), left + 30f, top + 20f, valuePaint);
  176. StaticLayout staticLayout = new StaticLayout(item.description,
  177. valuePaint, Utils.textWidth(item.description, valuePaint, true),
  178. Layout.Alignment.ALIGN_NORMAL, 0, 0, false);
  179. canvas.save();
  180. canvas.translate(left + (legendSquareSize * 1.5f), top);
  181. staticLayout.draw(canvas);
  182. canvas.restore();
  183. top += legendSquareSize * 1.5f;
  184. }
  185. }
  186. @Override
  187. public void onDraw(Canvas canvas) {
  188. if (!readyToDraw)
  189. return;
  190. drawTitle(canvas);
  191. drawChart(canvas);
  192. if (showLegend)
  193. drawLegend(canvas);
  194. }
  195. @Override
  196. public void onSizeChanged(int xNew, int yNew, int xOld, int yOld) {
  197. super.onSizeChanged(xNew, yNew, xOld, yOld);
  198. initRectangles();
  199. initTitleText();
  200. readyToDraw = true;
  201. }
  202. private void initColors(int count) {
  203. colors = new ArrayList<>();
  204. // 3 sets of 2 main colors - rb, rg, bg
  205. int maxValue = 220;
  206. int perSetGain = (int) (maxValue / (Math.ceil(count / 6.) + 1));
  207. float r = maxValue, g = 0, b = 0;
  208. for (int i = 0; i < count; ++i) {
  209. int color = Color.rgb((int) r, (int) g, (int) b);
  210. colors.add(color);
  211. r = Color.green(color);
  212. g = Color.blue(color);
  213. b = Color.red(color);
  214. if (i != 0 && (i + 1) % 3 == 0) {
  215. if (i == 2) {
  216. g = maxValue;
  217. } else {
  218. r -= perSetGain;
  219. }
  220. }
  221. }
  222. }
  223. private void createItems(float[] values) {
  224. for (int i = 0; i < values.length; ++i) {
  225. PieChartItem pieChartItem = new PieChartItem();
  226. pieChartItem.value = values[i];
  227. pieChartItem.paint = new Paint(Paint.ANTI_ALIAS_FLAG);
  228. pieChartItem.paint.setColor(colors.get(i));
  229. pieChartItem.description = Double.toString(values[i]);
  230. pieChartItem.descriptionWidth = Utils.textWidth(pieChartItem.description, legendDescriptionPaint, true);
  231. items.add(pieChartItem);
  232. }
  233. }
  234. private float getMaxLegendWidth() {
  235. float width = getWidth();
  236. float maxPercWidth = width * MAX_LEGEND_WIDTH_PERCENTAGE;
  237. return Math.min(maxPercWidth, MAX_LEGEND_WIDTH_PX);
  238. }
  239. /**
  240. * Calculates required legend width.
  241. */
  242. private void updateLegendWidth() {
  243. float maxWidth = getMaxLegendWidth();
  244. legendWidth = 0;
  245. for (PieChartItem item : items) {
  246. float width = item.descriptionWidth + 2 * LEGEND_PADDING_HORIZONTAL + legendSquareSize * 1.5f;
  247. if (width > legendWidth)
  248. legendWidth = width;
  249. if (legendWidth >= maxWidth) {
  250. legendWidth = maxWidth;
  251. break;
  252. }
  253. }
  254. }
  255. public void setValues(float[] values) {
  256. items.clear();
  257. initColors(values.length);
  258. createItems(values);
  259. initRectangles();
  260. invalidate();
  261. }
  262. }