• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 package com.github.mikephil.charting.renderer;
3 
4 import android.graphics.Canvas;
5 import android.graphics.DashPathEffect;
6 import android.graphics.Paint;
7 import android.graphics.Paint.Align;
8 import android.graphics.Path;
9 import android.graphics.Typeface;
10 
11 import com.github.mikephil.charting.components.Legend;
12 import com.github.mikephil.charting.components.LegendEntry;
13 import com.github.mikephil.charting.data.ChartData;
14 import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
15 import com.github.mikephil.charting.interfaces.datasets.ICandleDataSet;
16 import com.github.mikephil.charting.interfaces.datasets.IDataSet;
17 import com.github.mikephil.charting.interfaces.datasets.IPieDataSet;
18 import com.github.mikephil.charting.utils.ColorTemplate;
19 import com.github.mikephil.charting.utils.FSize;
20 import com.github.mikephil.charting.utils.Utils;
21 import com.github.mikephil.charting.utils.ViewPortHandler;
22 
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.List;
26 
27 public class LegendRenderer extends Renderer {
28 
29     /**
30      * paint for the legend labels
31      */
32     protected Paint mLegendLabelPaint;
33 
34     /**
35      * paint used for the legend forms
36      */
37     protected Paint mLegendFormPaint;
38 
39     /**
40      * the legend object this renderer renders
41      */
42     protected Legend mLegend;
43 
LegendRenderer(ViewPortHandler viewPortHandler, Legend legend)44     public LegendRenderer(ViewPortHandler viewPortHandler, Legend legend) {
45         super(viewPortHandler);
46 
47         this.mLegend = legend;
48 
49         mLegendLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
50         mLegendLabelPaint.setTextSize(Utils.convertDpToPixel(9f));
51         mLegendLabelPaint.setTextAlign(Align.LEFT);
52 
53         mLegendFormPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
54         mLegendFormPaint.setStyle(Paint.Style.FILL);
55     }
56 
57     /**
58      * Returns the Paint object used for drawing the Legend labels.
59      *
60      * @return
61      */
getLabelPaint()62     public Paint getLabelPaint() {
63         return mLegendLabelPaint;
64     }
65 
66     /**
67      * Returns the Paint object used for drawing the Legend forms.
68      *
69      * @return
70      */
getFormPaint()71     public Paint getFormPaint() {
72         return mLegendFormPaint;
73     }
74 
75 
76     protected List<LegendEntry> computedEntries = new ArrayList<>(16);
77 
78     /**
79      * Prepares the legend and calculates all needed forms, labels and colors.
80      *
81      * @param data
82      */
computeLegend(ChartData<?> data)83     public void computeLegend(ChartData<?> data) {
84 
85         if (!mLegend.isLegendCustom()) {
86 
87             computedEntries.clear();
88 
89             // loop for building up the colors and labels used in the legend
90             for (int i = 0; i < data.getDataSetCount(); i++) {
91 
92                 IDataSet dataSet = data.getDataSetByIndex(i);
93                 if (dataSet == null) continue;
94 
95                 List<Integer> clrs = dataSet.getColors();
96                 int entryCount = dataSet.getEntryCount();
97 
98                 // if we have a barchart with stacked bars
99                 if (dataSet instanceof IBarDataSet && ((IBarDataSet) dataSet).isStacked()) {
100 
101                     IBarDataSet bds = (IBarDataSet) dataSet;
102                     String[] sLabels = bds.getStackLabels();
103 
104                     int minEntries = Math.min(clrs.size(), bds.getStackSize());
105 
106                     for (int j = 0; j < minEntries; j++) {
107                         String label;
108                         if (sLabels.length > 0) {
109                             int labelIndex = j % minEntries;
110                             label = labelIndex < sLabels.length ? sLabels[labelIndex] : null;
111                         } else {
112                             label = null;
113                         }
114 
115                         computedEntries.add(new LegendEntry(
116                                 label,
117                                 dataSet.getForm(),
118                                 dataSet.getFormSize(),
119                                 dataSet.getFormLineWidth(),
120                                 dataSet.getFormLineDashEffect(),
121                                 clrs.get(j)
122                         ));
123                     }
124 
125                     if (bds.getLabel() != null) {
126                         // add the legend description label
127                         computedEntries.add(new LegendEntry(
128                                 dataSet.getLabel(),
129                                 Legend.LegendForm.NONE,
130                                 Float.NaN,
131                                 Float.NaN,
132                                 null,
133                                 ColorTemplate.COLOR_NONE
134                         ));
135                     }
136 
137                 } else if (dataSet instanceof IPieDataSet) {
138 
139                     IPieDataSet pds = (IPieDataSet) dataSet;
140 
141                     for (int j = 0; j < clrs.size() && j < entryCount; j++) {
142 
143                         computedEntries.add(new LegendEntry(
144                                 pds.getEntryForIndex(j).getLabel(),
145                                 dataSet.getForm(),
146                                 dataSet.getFormSize(),
147                                 dataSet.getFormLineWidth(),
148                                 dataSet.getFormLineDashEffect(),
149                                 clrs.get(j)
150                         ));
151                     }
152 
153                     if (pds.getLabel() != null) {
154                         // add the legend description label
155                         computedEntries.add(new LegendEntry(
156                                 dataSet.getLabel(),
157                                 Legend.LegendForm.NONE,
158                                 Float.NaN,
159                                 Float.NaN,
160                                 null,
161                                 ColorTemplate.COLOR_NONE
162                         ));
163                     }
164 
165                 } else if (dataSet instanceof ICandleDataSet && ((ICandleDataSet) dataSet).getDecreasingColor() !=
166                         ColorTemplate.COLOR_NONE) {
167 
168                     int decreasingColor = ((ICandleDataSet) dataSet).getDecreasingColor();
169                     int increasingColor = ((ICandleDataSet) dataSet).getIncreasingColor();
170 
171                     computedEntries.add(new LegendEntry(
172                             null,
173                             dataSet.getForm(),
174                             dataSet.getFormSize(),
175                             dataSet.getFormLineWidth(),
176                             dataSet.getFormLineDashEffect(),
177                             decreasingColor
178                     ));
179 
180                     computedEntries.add(new LegendEntry(
181                             dataSet.getLabel(),
182                             dataSet.getForm(),
183                             dataSet.getFormSize(),
184                             dataSet.getFormLineWidth(),
185                             dataSet.getFormLineDashEffect(),
186                             increasingColor
187                     ));
188 
189                 } else { // all others
190 
191                     for (int j = 0; j < clrs.size() && j < entryCount; j++) {
192 
193                         String label;
194 
195                         // if multiple colors are set for a DataSet, group them
196                         if (j < clrs.size() - 1 && j < entryCount - 1) {
197                             label = null;
198                         } else { // add label to the last entry
199                             label = data.getDataSetByIndex(i).getLabel();
200                         }
201 
202                         computedEntries.add(new LegendEntry(
203                                 label,
204                                 dataSet.getForm(),
205                                 dataSet.getFormSize(),
206                                 dataSet.getFormLineWidth(),
207                                 dataSet.getFormLineDashEffect(),
208                                 clrs.get(j)
209                         ));
210                     }
211                 }
212             }
213 
214             if (mLegend.getExtraEntries() != null) {
215                 Collections.addAll(computedEntries, mLegend.getExtraEntries());
216             }
217 
218             mLegend.setEntries(computedEntries);
219         }
220 
221         Typeface tf = mLegend.getTypeface();
222 
223         if (tf != null)
224             mLegendLabelPaint.setTypeface(tf);
225 
226         mLegendLabelPaint.setTextSize(mLegend.getTextSize());
227         mLegendLabelPaint.setColor(mLegend.getTextColor());
228 
229         // calculate all dimensions of the mLegend
230         mLegend.calculateDimensions(mLegendLabelPaint, mViewPortHandler);
231     }
232 
233     protected Paint.FontMetrics legendFontMetrics = new Paint.FontMetrics();
234 
235     public void renderLegend(Canvas c) {
236 
237         if (!mLegend.isEnabled())
238             return;
239 
240         Typeface tf = mLegend.getTypeface();
241 
242         if (tf != null)
243             mLegendLabelPaint.setTypeface(tf);
244 
245         mLegendLabelPaint.setTextSize(mLegend.getTextSize());
246         mLegendLabelPaint.setColor(mLegend.getTextColor());
247 
248         float labelLineHeight = Utils.getLineHeight(mLegendLabelPaint, legendFontMetrics);
249         float labelLineSpacing = Utils.getLineSpacing(mLegendLabelPaint, legendFontMetrics)
250                 + Utils.convertDpToPixel(mLegend.getYEntrySpace());
251         float formYOffset = labelLineHeight - Utils.calcTextHeight(mLegendLabelPaint, "ABC") / 2.f;
252 
253         LegendEntry[] entries = mLegend.getEntries();
254 
255         float formToTextSpace = Utils.convertDpToPixel(mLegend.getFormToTextSpace());
256         float xEntrySpace = Utils.convertDpToPixel(mLegend.getXEntrySpace());
257         Legend.LegendOrientation orientation = mLegend.getOrientation();
258         Legend.LegendHorizontalAlignment horizontalAlignment = mLegend.getHorizontalAlignment();
259         Legend.LegendVerticalAlignment verticalAlignment = mLegend.getVerticalAlignment();
260         Legend.LegendDirection direction = mLegend.getDirection();
261         float defaultFormSize = Utils.convertDpToPixel(mLegend.getFormSize());
262 
263         // space between the entries
264         float stackSpace = Utils.convertDpToPixel(mLegend.getStackSpace());
265 
266         float yoffset = mLegend.getYOffset();
267         float xoffset = mLegend.getXOffset();
268         float originPosX = 0.f;
269 
270         switch (horizontalAlignment) {
271             case LEFT:
272 
273                 if (orientation == Legend.LegendOrientation.VERTICAL)
274                     originPosX = xoffset;
275                 else
276                     originPosX = mViewPortHandler.contentLeft() + xoffset;
277 
278                 if (direction == Legend.LegendDirection.RIGHT_TO_LEFT)
279                     originPosX += mLegend.mNeededWidth;
280 
281                 break;
282 
283             case RIGHT:
284 
285                 if (orientation == Legend.LegendOrientation.VERTICAL)
286                     originPosX = mViewPortHandler.getChartWidth() - xoffset;
287                 else
288                     originPosX = mViewPortHandler.contentRight() - xoffset;
289 
290                 if (direction == Legend.LegendDirection.LEFT_TO_RIGHT)
291                     originPosX -= mLegend.mNeededWidth;
292 
293                 break;
294 
295             case CENTER:
296 
297                 if (orientation == Legend.LegendOrientation.VERTICAL)
298                     originPosX = mViewPortHandler.getChartWidth() / 2.f;
299                 else
300                     originPosX = mViewPortHandler.contentLeft()
301                             + mViewPortHandler.contentWidth() / 2.f;
302 
303                 originPosX += (direction == Legend.LegendDirection.LEFT_TO_RIGHT
304                         ? +xoffset
305                         : -xoffset);
306 
307                 // Horizontally layed out legends do the center offset on a line basis,
308                 // So here we offset the vertical ones only.
309                 if (orientation == Legend.LegendOrientation.VERTICAL) {
310                     originPosX += (direction == Legend.LegendDirection.LEFT_TO_RIGHT
311                             ? -mLegend.mNeededWidth / 2.0 + xoffset
312                             : mLegend.mNeededWidth / 2.0 - xoffset);
313                 }
314 
315                 break;
316         }
317 
318         switch (orientation) {
319             case HORIZONTAL: {
320 
321                 List<FSize> calculatedLineSizes = mLegend.getCalculatedLineSizes();
322                 List<FSize> calculatedLabelSizes = mLegend.getCalculatedLabelSizes();
323                 List<Boolean> calculatedLabelBreakPoints = mLegend.getCalculatedLabelBreakPoints();
324 
325                 float posX = originPosX;
326                 float posY = 0.f;
327 
328                 switch (verticalAlignment) {
329                     case TOP:
330                         posY = yoffset;
331                         break;
332 
333                     case BOTTOM:
334                         posY = mViewPortHandler.getChartHeight() - yoffset - mLegend.mNeededHeight;
335                         break;
336 
337                     case CENTER:
338                         posY = (mViewPortHandler.getChartHeight() - mLegend.mNeededHeight) / 2.f + yoffset;
339                         break;
340                 }
341 
342                 int lineIndex = 0;
343 
344                 for (int i = 0, count = entries.length; i < count; i++) {
345 
346                     LegendEntry e = entries[i];
347                     boolean drawingForm = e.form != Legend.LegendForm.NONE;
348                     float formSize = Float.isNaN(e.formSize) ? defaultFormSize : Utils.convertDpToPixel(e.formSize);
349 
350                     if (i < calculatedLabelBreakPoints.size() && calculatedLabelBreakPoints.get(i)) {
351                         posX = originPosX;
352                         posY += labelLineHeight + labelLineSpacing;
353                     }
354 
355                     if (posX == originPosX &&
356                             horizontalAlignment == Legend.LegendHorizontalAlignment.CENTER &&
357                             lineIndex < calculatedLineSizes.size()) {
358                         posX += (direction == Legend.LegendDirection.RIGHT_TO_LEFT
359                                 ? calculatedLineSizes.get(lineIndex).width
360                                 : -calculatedLineSizes.get(lineIndex).width) / 2.f;
361                         lineIndex++;
362                     }
363 
364                     boolean isStacked = e.label == null; // grouped forms have null labels
365 
366                     if (drawingForm) {
367                         if (direction == Legend.LegendDirection.RIGHT_TO_LEFT)
368                             posX -= formSize;
369 
370                         drawForm(c, posX, posY + formYOffset, e, mLegend);
371 
372                         if (direction == Legend.LegendDirection.LEFT_TO_RIGHT)
373                             posX += formSize;
374                     }
375 
376                     if (!isStacked) {
377                         if (drawingForm)
378                             posX += direction == Legend.LegendDirection.RIGHT_TO_LEFT ? -formToTextSpace :
379                                     formToTextSpace;
380 
381                         if (direction == Legend.LegendDirection.RIGHT_TO_LEFT)
382                             posX -= calculatedLabelSizes.get(i).width;
383 
384                         drawLabel(c, posX, posY + labelLineHeight, e.label);
385 
386                         if (direction == Legend.LegendDirection.LEFT_TO_RIGHT)
387                             posX += calculatedLabelSizes.get(i).width;
388 
389                         posX += direction == Legend.LegendDirection.RIGHT_TO_LEFT ? -xEntrySpace : xEntrySpace;
390                     } else
391                         posX += direction == Legend.LegendDirection.RIGHT_TO_LEFT ? -stackSpace : stackSpace;
392                 }
393 
394                 break;
395             }
396 
397             case VERTICAL: {
398                 // contains the stacked legend size in pixels
399                 float stack = 0f;
400                 boolean wasStacked = false;
401                 float posY = 0.f;
402 
403                 switch (verticalAlignment) {
404                     case TOP:
405                         posY = (horizontalAlignment == Legend.LegendHorizontalAlignment.CENTER
406                                 ? 0.f
407                                 : mViewPortHandler.contentTop());
408                         posY += yoffset;
409                         break;
410 
411                     case BOTTOM:
412                         posY = (horizontalAlignment == Legend.LegendHorizontalAlignment.CENTER
413                                 ? mViewPortHandler.getChartHeight()
414                                 : mViewPortHandler.contentBottom());
415                         posY -= mLegend.mNeededHeight + yoffset;
416                         break;
417 
418                     case CENTER:
419                         posY = mViewPortHandler.getChartHeight() / 2.f
420                                 - mLegend.mNeededHeight / 2.f
421                                 + mLegend.getYOffset();
422                         break;
423                 }
424 
425                 for (int i = 0; i < entries.length; i++) {
426 
427                     LegendEntry e = entries[i];
428                     boolean drawingForm = e.form != Legend.LegendForm.NONE;
429                     float formSize = Float.isNaN(e.formSize) ? defaultFormSize : Utils.convertDpToPixel(e.formSize);
430 
431                     float posX = originPosX;
432 
433                     if (drawingForm) {
434                         if (direction == Legend.LegendDirection.LEFT_TO_RIGHT)
435                             posX += stack;
436                         else
437                             posX -= formSize - stack;
438 
439                         drawForm(c, posX, posY + formYOffset, e, mLegend);
440 
441                         if (direction == Legend.LegendDirection.LEFT_TO_RIGHT)
442                             posX += formSize;
443                     }
444 
445                     if (e.label != null) {
446 
447                         if (drawingForm && !wasStacked)
448                             posX += direction == Legend.LegendDirection.LEFT_TO_RIGHT ? formToTextSpace
449                                     : -formToTextSpace;
450                         else if (wasStacked)
451                             posX = originPosX;
452 
453                         if (direction == Legend.LegendDirection.RIGHT_TO_LEFT)
454                             posX -= Utils.calcTextWidth(mLegendLabelPaint, e.label);
455 
456                         if (!wasStacked) {
457                             drawLabel(c, posX, posY + labelLineHeight, e.label);
458                         } else {
459                             posY += labelLineHeight + labelLineSpacing;
460                             drawLabel(c, posX, posY + labelLineHeight, e.label);
461                         }
462 
463                         // make a step down
464                         posY += labelLineHeight + labelLineSpacing;
465                         stack = 0f;
466                     } else {
467                         stack += formSize + stackSpace;
468                         wasStacked = true;
469                     }
470                 }
471 
472                 break;
473 
474             }
475         }
476     }
477 
478     private Path mLineFormPath = new Path();
479 
480     /**
481      * Draws the Legend-form at the given position with the color at the given
482      * index.
483      *
484      * @param c      canvas to draw with
485      * @param x      position
486      * @param y      position
487      * @param entry  the entry to render
488      * @param legend the legend context
489      */
490     protected void drawForm(
491             Canvas c,
492             float x, float y,
493             LegendEntry entry,
494             Legend legend) {
495 
496         if (entry.formColor == ColorTemplate.COLOR_SKIP ||
497                 entry.formColor == ColorTemplate.COLOR_NONE ||
498                 entry.formColor == 0)
499             return;
500 
501         int restoreCount = c.save();
502 
503         Legend.LegendForm form = entry.form;
504         if (form == Legend.LegendForm.DEFAULT)
505             form = legend.getForm();
506 
507         mLegendFormPaint.setColor(entry.formColor);
508 
509         final float formSize = Utils.convertDpToPixel(
510                 Float.isNaN(entry.formSize)
511                         ? legend.getFormSize()
512                         : entry.formSize);
513         final float half = formSize / 2f;
514 
515         switch (form) {
516             case NONE:
517                 // Do nothing
518                 break;
519 
520             case EMPTY:
521                 // Do not draw, but keep space for the form
522                 break;
523 
524             case DEFAULT:
525             case CIRCLE:
526                 mLegendFormPaint.setStyle(Paint.Style.FILL);
527                 c.drawCircle(x + half, y, half, mLegendFormPaint);
528                 break;
529 
530             case SQUARE:
531                 mLegendFormPaint.setStyle(Paint.Style.FILL);
532                 c.drawRect(x, y - half, x + formSize, y + half, mLegendFormPaint);
533                 break;
534 
535             case LINE:
536             {
537                 final float formLineWidth = Utils.convertDpToPixel(
538                         Float.isNaN(entry.formLineWidth)
539                                 ? legend.getFormLineWidth()
540                                 : entry.formLineWidth);
541                 final DashPathEffect formLineDashEffect = entry.formLineDashEffect == null
542                         ? legend.getFormLineDashEffect()
543                         : entry.formLineDashEffect;
544                 mLegendFormPaint.setStyle(Paint.Style.STROKE);
545                 mLegendFormPaint.setStrokeWidth(formLineWidth);
546                 mLegendFormPaint.setPathEffect(formLineDashEffect);
547 
548                 mLineFormPath.reset();
549                 mLineFormPath.moveTo(x, y);
550                 mLineFormPath.lineTo(x + formSize, y);
551                 c.drawPath(mLineFormPath, mLegendFormPaint);
552             }
553                 break;
554         }
555 
556         c.restoreToCount(restoreCount);
557     }
558 
559     /**
560      * Draws the provided label at the given position.
561      *
562      * @param c     canvas to draw with
563      * @param x
564      * @param y
565      * @param label the label to draw
566      */
567     protected void drawLabel(Canvas c, float x, float y, String label) {
568         c.drawText(label, x, y, mLegendLabelPaint);
569     }
570 }
571