package com.github.mikephil.charting.renderer; import android.graphics.Canvas; import android.graphics.DashPathEffect; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Path; import android.graphics.Typeface; import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.components.LegendEntry; import com.github.mikephil.charting.data.ChartData; import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; import com.github.mikephil.charting.interfaces.datasets.ICandleDataSet; import com.github.mikephil.charting.interfaces.datasets.IDataSet; import com.github.mikephil.charting.interfaces.datasets.IPieDataSet; import com.github.mikephil.charting.utils.ColorTemplate; import com.github.mikephil.charting.utils.FSize; import com.github.mikephil.charting.utils.Utils; import com.github.mikephil.charting.utils.ViewPortHandler; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class LegendRenderer extends Renderer { /** * paint for the legend labels */ protected Paint mLegendLabelPaint; /** * paint used for the legend forms */ protected Paint mLegendFormPaint; /** * the legend object this renderer renders */ protected Legend mLegend; public LegendRenderer(ViewPortHandler viewPortHandler, Legend legend) { super(viewPortHandler); this.mLegend = legend; mLegendLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mLegendLabelPaint.setTextSize(Utils.convertDpToPixel(9f)); mLegendLabelPaint.setTextAlign(Align.LEFT); mLegendFormPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mLegendFormPaint.setStyle(Paint.Style.FILL); } /** * Returns the Paint object used for drawing the Legend labels. * * @return */ public Paint getLabelPaint() { return mLegendLabelPaint; } /** * Returns the Paint object used for drawing the Legend forms. * * @return */ public Paint getFormPaint() { return mLegendFormPaint; } protected List<LegendEntry> computedEntries = new ArrayList<>(16); /** * Prepares the legend and calculates all needed forms, labels and colors. * * @param data */ public void computeLegend(ChartData<?> data) { if (!mLegend.isLegendCustom()) { computedEntries.clear(); // loop for building up the colors and labels used in the legend for (int i = 0; i < data.getDataSetCount(); i++) { IDataSet dataSet = data.getDataSetByIndex(i); List<Integer> clrs = dataSet.getColors(); int entryCount = dataSet.getEntryCount(); // if we have a barchart with stacked bars if (dataSet instanceof IBarDataSet && ((IBarDataSet) dataSet).isStacked()) { IBarDataSet bds = (IBarDataSet) dataSet; String[] sLabels = bds.getStackLabels(); for (int j = 0; j < clrs.size() && j < bds.getStackSize(); j++) { computedEntries.add(new LegendEntry( sLabels[j % sLabels.length], dataSet.getForm(), dataSet.getFormSize(), dataSet.getFormLineWidth(), dataSet.getFormLineDashEffect(), clrs.get(j) )); } if (bds.getLabel() != null) { // add the legend description label computedEntries.add(new LegendEntry( dataSet.getLabel(), Legend.LegendForm.NONE, Float.NaN, Float.NaN, null, ColorTemplate.COLOR_NONE )); } } else if (dataSet instanceof IPieDataSet) { IPieDataSet pds = (IPieDataSet) dataSet; for (int j = 0; j < clrs.size() && j < entryCount; j++) { computedEntries.add(new LegendEntry( pds.getEntryForIndex(j).getLabel(), dataSet.getForm(), dataSet.getFormSize(), dataSet.getFormLineWidth(), dataSet.getFormLineDashEffect(), clrs.get(j) )); } if (pds.getLabel() != null) { // add the legend description label computedEntries.add(new LegendEntry( dataSet.getLabel(), Legend.LegendForm.NONE, Float.NaN, Float.NaN, null, ColorTemplate.COLOR_NONE )); } } else if (dataSet instanceof ICandleDataSet && ((ICandleDataSet) dataSet).getDecreasingColor() != ColorTemplate.COLOR_NONE) { int decreasingColor = ((ICandleDataSet) dataSet).getDecreasingColor(); int increasingColor = ((ICandleDataSet) dataSet).getIncreasingColor(); computedEntries.add(new LegendEntry( null, dataSet.getForm(), dataSet.getFormSize(), dataSet.getFormLineWidth(), dataSet.getFormLineDashEffect(), decreasingColor )); computedEntries.add(new LegendEntry( dataSet.getLabel(), dataSet.getForm(), dataSet.getFormSize(), dataSet.getFormLineWidth(), dataSet.getFormLineDashEffect(), increasingColor )); } else { // all others for (int j = 0; j < clrs.size() && j < entryCount; j++) { String label; // if multiple colors are set for a DataSet, group them if (j < clrs.size() - 1 && j < entryCount - 1) { label = null; } else { // add label to the last entry label = data.getDataSetByIndex(i).getLabel(); } computedEntries.add(new LegendEntry( label, dataSet.getForm(), dataSet.getFormSize(), dataSet.getFormLineWidth(), dataSet.getFormLineDashEffect(), clrs.get(j) )); } } } if (mLegend.getExtraEntries() != null) { Collections.addAll(computedEntries, mLegend.getExtraEntries()); } mLegend.setEntries(computedEntries); } Typeface tf = mLegend.getTypeface(); if (tf != null) mLegendLabelPaint.setTypeface(tf); mLegendLabelPaint.setTextSize(mLegend.getTextSize()); mLegendLabelPaint.setColor(mLegend.getTextColor()); // calculate all dimensions of the mLegend mLegend.calculateDimensions(mLegendLabelPaint, mViewPortHandler); } protected Paint.FontMetrics legendFontMetrics = new Paint.FontMetrics(); public void renderLegend(Canvas c) { if (!mLegend.isEnabled()) return; Typeface tf = mLegend.getTypeface(); if (tf != null) mLegendLabelPaint.setTypeface(tf); mLegendLabelPaint.setTextSize(mLegend.getTextSize()); mLegendLabelPaint.setColor(mLegend.getTextColor()); float labelLineHeight = Utils.getLineHeight(mLegendLabelPaint, legendFontMetrics); float labelLineSpacing = Utils.getLineSpacing(mLegendLabelPaint, legendFontMetrics) + Utils.convertDpToPixel(mLegend.getYEntrySpace()); float formYOffset = labelLineHeight - Utils.calcTextHeight(mLegendLabelPaint, "ABC") / 2.f; LegendEntry[] entries = mLegend.getEntries(); float formToTextSpace = Utils.convertDpToPixel(mLegend.getFormToTextSpace()); float xEntrySpace = Utils.convertDpToPixel(mLegend.getXEntrySpace()); Legend.LegendOrientation orientation = mLegend.getOrientation(); Legend.LegendHorizontalAlignment horizontalAlignment = mLegend.getHorizontalAlignment(); Legend.LegendVerticalAlignment verticalAlignment = mLegend.getVerticalAlignment(); Legend.LegendDirection direction = mLegend.getDirection(); float defaultFormSize = Utils.convertDpToPixel(mLegend.getFormSize()); // space between the entries float stackSpace = Utils.convertDpToPixel(mLegend.getStackSpace()); float yoffset = mLegend.getYOffset(); float xoffset = mLegend.getXOffset(); float originPosX = 0.f; switch (horizontalAlignment) { case LEFT: if (orientation == Legend.LegendOrientation.VERTICAL) originPosX = xoffset; else originPosX = mViewPortHandler.contentLeft() + xoffset; if (direction == Legend.LegendDirection.RIGHT_TO_LEFT) originPosX += mLegend.mNeededWidth; break; case RIGHT: if (orientation == Legend.LegendOrientation.VERTICAL) originPosX = mViewPortHandler.getChartWidth() - xoffset; else originPosX = mViewPortHandler.contentRight() - xoffset; if (direction == Legend.LegendDirection.LEFT_TO_RIGHT) originPosX -= mLegend.mNeededWidth; break; case CENTER: if (orientation == Legend.LegendOrientation.VERTICAL) originPosX = mViewPortHandler.getChartWidth() / 2.f; else originPosX = mViewPortHandler.contentLeft() + mViewPortHandler.contentWidth() / 2.f; originPosX += (direction == Legend.LegendDirection.LEFT_TO_RIGHT ? +xoffset : -xoffset); // Horizontally layed out legends do the center offset on a line basis, // So here we offset the vertical ones only. if (orientation == Legend.LegendOrientation.VERTICAL) { originPosX += (direction == Legend.LegendDirection.LEFT_TO_RIGHT ? -mLegend.mNeededWidth / 2.0 + xoffset : mLegend.mNeededWidth / 2.0 - xoffset); } break; } switch (orientation) { case HORIZONTAL: { List<FSize> calculatedLineSizes = mLegend.getCalculatedLineSizes(); List<FSize> calculatedLabelSizes = mLegend.getCalculatedLabelSizes(); List<Boolean> calculatedLabelBreakPoints = mLegend.getCalculatedLabelBreakPoints(); float posX = originPosX; float posY = 0.f; switch (verticalAlignment) { case TOP: posY = yoffset; break; case BOTTOM: posY = mViewPortHandler.getChartHeight() - yoffset - mLegend.mNeededHeight; break; case CENTER: posY = (mViewPortHandler.getChartHeight() - mLegend.mNeededHeight) / 2.f + yoffset; break; } int lineIndex = 0; for (int i = 0, count = entries.length; i < count; i++) { LegendEntry e = entries[i]; boolean drawingForm = e.form != Legend.LegendForm.NONE; float formSize = Float.isNaN(e.formSize) ? defaultFormSize : Utils.convertDpToPixel(e.formSize); if (i < calculatedLabelBreakPoints.size() && calculatedLabelBreakPoints.get(i)) { posX = originPosX; posY += labelLineHeight + labelLineSpacing; } if (posX == originPosX && horizontalAlignment == Legend.LegendHorizontalAlignment.CENTER && lineIndex < calculatedLineSizes.size()) { posX += (direction == Legend.LegendDirection.RIGHT_TO_LEFT ? calculatedLineSizes.get(lineIndex).width : -calculatedLineSizes.get(lineIndex).width) / 2.f; lineIndex++; } boolean isStacked = e.label == null; // grouped forms have null labels if (drawingForm) { if (direction == Legend.LegendDirection.RIGHT_TO_LEFT) posX -= formSize; drawForm(c, posX, posY + formYOffset, e, mLegend); if (direction == Legend.LegendDirection.LEFT_TO_RIGHT) posX += formSize; } if (!isStacked) { if (drawingForm) posX += direction == Legend.LegendDirection.RIGHT_TO_LEFT ? -formToTextSpace : formToTextSpace; if (direction == Legend.LegendDirection.RIGHT_TO_LEFT) posX -= calculatedLabelSizes.get(i).width; drawLabel(c, posX, posY + labelLineHeight, e.label); if (direction == Legend.LegendDirection.LEFT_TO_RIGHT) posX += calculatedLabelSizes.get(i).width; posX += direction == Legend.LegendDirection.RIGHT_TO_LEFT ? -xEntrySpace : xEntrySpace; } else posX += direction == Legend.LegendDirection.RIGHT_TO_LEFT ? -stackSpace : stackSpace; } break; } case VERTICAL: { // contains the stacked legend size in pixels float stack = 0f; boolean wasStacked = false; float posY = 0.f; switch (verticalAlignment) { case TOP: posY = (horizontalAlignment == Legend.LegendHorizontalAlignment.CENTER ? 0.f : mViewPortHandler.contentTop()); posY += yoffset; break; case BOTTOM: posY = (horizontalAlignment == Legend.LegendHorizontalAlignment.CENTER ? mViewPortHandler.getChartHeight() : mViewPortHandler.contentBottom()); posY -= mLegend.mNeededHeight + yoffset; break; case CENTER: posY = mViewPortHandler.getChartHeight() / 2.f - mLegend.mNeededHeight / 2.f + mLegend.getYOffset(); break; } for (int i = 0; i < entries.length; i++) { LegendEntry e = entries[i]; boolean drawingForm = e.form != Legend.LegendForm.NONE; float formSize = Float.isNaN(e.formSize) ? defaultFormSize : Utils.convertDpToPixel(e.formSize); float posX = originPosX; if (drawingForm) { if (direction == Legend.LegendDirection.LEFT_TO_RIGHT) posX += stack; else posX -= formSize - stack; drawForm(c, posX, posY + formYOffset, e, mLegend); if (direction == Legend.LegendDirection.LEFT_TO_RIGHT) posX += formSize; } if (e.label != null) { if (drawingForm && !wasStacked) posX += direction == Legend.LegendDirection.LEFT_TO_RIGHT ? formToTextSpace : -formToTextSpace; else if (wasStacked) posX = originPosX; if (direction == Legend.LegendDirection.RIGHT_TO_LEFT) posX -= Utils.calcTextWidth(mLegendLabelPaint, e.label); if (!wasStacked) { drawLabel(c, posX, posY + labelLineHeight, e.label); } else { posY += labelLineHeight + labelLineSpacing; drawLabel(c, posX, posY + labelLineHeight, e.label); } // make a step down posY += labelLineHeight + labelLineSpacing; stack = 0f; } else { stack += formSize + stackSpace; wasStacked = true; } } break; } } } private Path mLineFormPath = new Path(); /** * Draws the Legend-form at the given position with the color at the given * index. * * @param c canvas to draw with * @param x position * @param y position * @param entry the entry to render * @param legend the legend context */ protected void drawForm( Canvas c, float x, float y, LegendEntry entry, Legend legend) { if (entry.formColor == ColorTemplate.COLOR_SKIP || entry.formColor == ColorTemplate.COLOR_NONE || entry.formColor == 0) return; int restoreCount = c.save(); Legend.LegendForm form = entry.form; if (form == Legend.LegendForm.DEFAULT) form = legend.getForm(); mLegendFormPaint.setColor(entry.formColor); final float formSize = Utils.convertDpToPixel( Float.isNaN(entry.formSize) ? legend.getFormSize() : entry.formSize); final float half = formSize / 2f; switch (form) { case NONE: // Do nothing break; case EMPTY: // Do not draw, but keep space for the form break; case DEFAULT: case CIRCLE: mLegendFormPaint.setStyle(Paint.Style.FILL); c.drawCircle(x + half, y, half, mLegendFormPaint); break; case SQUARE: mLegendFormPaint.setStyle(Paint.Style.FILL); c.drawRect(x, y - half, x + formSize, y + half, mLegendFormPaint); break; case LINE: { final float formLineWidth = Utils.convertDpToPixel( Float.isNaN(entry.formLineWidth) ? legend.getFormLineWidth() : entry.formLineWidth); final DashPathEffect formLineDashEffect = entry.formLineDashEffect == null ? legend.getFormLineDashEffect() : entry.formLineDashEffect; mLegendFormPaint.setStyle(Paint.Style.STROKE); mLegendFormPaint.setStrokeWidth(formLineWidth); mLegendFormPaint.setPathEffect(formLineDashEffect); mLineFormPath.reset(); mLineFormPath.moveTo(x, y); mLineFormPath.lineTo(x + formSize, y); c.drawPath(mLineFormPath, mLegendFormPaint); } break; } c.restoreToCount(restoreCount); } /** * Draws the provided label at the given position. * * @param c canvas to draw with * @param x * @param y * @param label the label to draw */ protected void drawLabel(Canvas c, float x, float y, String label) { c.drawText(label, x, y, mLegendLabelPaint); } }