PieChartView.java 11 KB

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