123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825 |
- package com.github.mikephil.charting.components;
- import android.graphics.DashPathEffect;
- import android.graphics.Paint;
- 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.List;
- /**
- * Class representing the legend of the chart. The legend will contain one entry
- * per color and DataSet. Multiple colors in one DataSet are grouped together.
- * The legend object is NOT available before setting data to the chart.
- *
- * @author Philipp Jahoda
- */
- public class Legend extends ComponentBase {
- public enum LegendForm {
- /**
- * Avoid drawing a form
- */
- NONE,
- /**
- * Do not draw the a form, but leave space for it
- */
- EMPTY,
- /**
- * Use default (default dataset's form to the legend's form)
- */
- DEFAULT,
- /**
- * Draw a square
- */
- SQUARE,
- /**
- * Draw a circle
- */
- CIRCLE,
- /**
- * Draw a horizontal line
- */
- LINE
- }
- public enum LegendHorizontalAlignment {
- LEFT, CENTER, RIGHT
- }
- public enum LegendVerticalAlignment {
- TOP, CENTER, BOTTOM
- }
- public enum LegendOrientation {
- HORIZONTAL, VERTICAL
- }
- public enum LegendDirection {
- LEFT_TO_RIGHT, RIGHT_TO_LEFT
- }
- /**
- * The legend entries array
- */
- private LegendEntry[] mEntries = new LegendEntry[]{};
- /**
- * Entries that will be appended to the end of the auto calculated entries after calculating the legend.
- * (if the legend has already been calculated, you will need to call notifyDataSetChanged() to let the changes take effect)
- */
- private LegendEntry[] mExtraEntries;
- /**
- * Are the legend labels/colors a custom value or auto calculated? If false,
- * then it's auto, if true, then custom. default false (automatic legend)
- */
- private boolean mIsLegendCustom = false;
- private LegendHorizontalAlignment mHorizontalAlignment = LegendHorizontalAlignment.LEFT;
- private LegendVerticalAlignment mVerticalAlignment = LegendVerticalAlignment.BOTTOM;
- private LegendOrientation mOrientation = LegendOrientation.HORIZONTAL;
- private boolean mDrawInside = false;
- /**
- * the text direction for the legend
- */
- private LegendDirection mDirection = LegendDirection.LEFT_TO_RIGHT;
- /**
- * the shape/form the legend colors are drawn in
- */
- private LegendForm mShape = LegendForm.SQUARE;
- /**
- * the size of the legend forms/shapes
- */
- private float mFormSize = 8f;
- /**
- * the size of the legend forms/shapes
- */
- private float mFormLineWidth = 3f;
- /**
- * Line dash path effect used for shapes that consist of lines.
- */
- private DashPathEffect mFormLineDashEffect = null;
- /**
- * the space between the legend entries on a horizontal axis, default 6f
- */
- private float mXEntrySpace = 6f;
- /**
- * the space between the legend entries on a vertical axis, default 5f
- */
- private float mYEntrySpace = 0f;
- /**
- * the space between the legend entries on a vertical axis, default 2f
- * private float mYEntrySpace = 2f; /** the space between the form and the
- * actual label/text
- */
- private float mFormToTextSpace = 5f;
- /**
- * the space that should be left between stacked forms
- */
- private float mStackSpace = 3f;
- /**
- * the maximum relative size out of the whole chart view in percent
- */
- private float mMaxSizePercent = 0.95f;
- /**
- * default constructor
- */
- public Legend() {
- this.mTextSize = Utils.convertDpToPixel(10f);
- this.mXOffset = Utils.convertDpToPixel(5f);
- this.mYOffset = Utils.convertDpToPixel(3f); // 2
- }
- /**
- * Constructor. Provide entries for the legend.
- *
- * @param entries
- */
- public Legend(LegendEntry[] entries) {
- this();
- if (entries == null) {
- throw new IllegalArgumentException("entries array is NULL");
- }
- this.mEntries = entries;
- }
- /**
- * This method sets the automatically computed colors for the legend. Use setCustom(...) to set custom colors.
- *
- * @param entries
- */
- public void setEntries(List<LegendEntry> entries) {
- mEntries = entries.toArray(new LegendEntry[entries.size()]);
- }
- public LegendEntry[] getEntries() {
- return mEntries;
- }
- /**
- * returns the maximum length in pixels across all legend labels + formsize
- * + formtotextspace
- *
- * @param p the paint object used for rendering the text
- * @return
- */
- public float getMaximumEntryWidth(Paint p) {
- float max = 0f;
- float maxFormSize = 0f;
- float formToTextSpace = Utils.convertDpToPixel(mFormToTextSpace);
- for (LegendEntry entry : mEntries) {
- final float formSize = Utils.convertDpToPixel(
- Float.isNaN(entry.formSize)
- ? mFormSize : entry.formSize);
- if (formSize > maxFormSize)
- maxFormSize = formSize;
- String label = entry.label;
- if (label == null) continue;
- float length = (float) Utils.calcTextWidth(p, label);
- if (length > max)
- max = length;
- }
- return max + maxFormSize + formToTextSpace;
- }
- /**
- * returns the maximum height in pixels across all legend labels
- *
- * @param p the paint object used for rendering the text
- * @return
- */
- public float getMaximumEntryHeight(Paint p) {
- float max = 0f;
- for (LegendEntry entry : mEntries) {
- String label = entry.label;
- if (label == null) continue;
- float length = (float) Utils.calcTextHeight(p, label);
- if (length > max)
- max = length;
- }
- return max;
- }
- public LegendEntry[] getExtraEntries() {
- return mExtraEntries;
- }
- public void setExtra(List<LegendEntry> entries) {
- mExtraEntries = entries.toArray(new LegendEntry[entries.size()]);
- }
- public void setExtra(LegendEntry[] entries) {
- if (entries == null)
- entries = new LegendEntry[]{};
- mExtraEntries = entries;
- }
- /**
- * Entries that will be appended to the end of the auto calculated
- * entries after calculating the legend.
- * (if the legend has already been calculated, you will need to call notifyDataSetChanged()
- * to let the changes take effect)
- */
- public void setExtra(int[] colors, String[] labels) {
- List<LegendEntry> entries = new ArrayList<>();
- for (int i = 0; i < Math.min(colors.length, labels.length); i++) {
- final LegendEntry entry = new LegendEntry();
- entry.formColor = colors[i];
- entry.label = labels[i];
- if (entry.formColor == ColorTemplate.COLOR_SKIP ||
- entry.formColor == 0)
- entry.form = LegendForm.NONE;
- else if (entry.formColor == ColorTemplate.COLOR_NONE)
- entry.form = LegendForm.EMPTY;
- entries.add(entry);
- }
- mExtraEntries = entries.toArray(new LegendEntry[entries.size()]);
- }
- /**
- * Sets a custom legend's entries array.
- * * A null label will start a group.
- * This will disable the feature that automatically calculates the legend
- * entries from the datasets.
- * Call resetCustom() to re-enable automatic calculation (and then
- * notifyDataSetChanged() is needed to auto-calculate the legend again)
- */
- public void setCustom(LegendEntry[] entries) {
- mEntries = entries;
- mIsLegendCustom = true;
- }
- /**
- * Sets a custom legend's entries array.
- * * A null label will start a group.
- * This will disable the feature that automatically calculates the legend
- * entries from the datasets.
- * Call resetCustom() to re-enable automatic calculation (and then
- * notifyDataSetChanged() is needed to auto-calculate the legend again)
- */
- public void setCustom(List<LegendEntry> entries) {
- mEntries = entries.toArray(new LegendEntry[entries.size()]);
- mIsLegendCustom = true;
- }
- /**
- * Calling this will disable the custom legend entries (set by
- * setCustom(...)). Instead, the entries will again be calculated
- * automatically (after notifyDataSetChanged() is called).
- */
- public void resetCustom() {
- mIsLegendCustom = false;
- }
- /**
- * @return true if a custom legend entries has been set default
- * false (automatic legend)
- */
- public boolean isLegendCustom() {
- return mIsLegendCustom;
- }
- /**
- * returns the horizontal alignment of the legend
- *
- * @return
- */
- public LegendHorizontalAlignment getHorizontalAlignment() {
- return mHorizontalAlignment;
- }
- /**
- * sets the horizontal alignment of the legend
- *
- * @param value
- */
- public void setHorizontalAlignment(LegendHorizontalAlignment value) {
- mHorizontalAlignment = value;
- }
- /**
- * returns the vertical alignment of the legend
- *
- * @return
- */
- public LegendVerticalAlignment getVerticalAlignment() {
- return mVerticalAlignment;
- }
- /**
- * sets the vertical alignment of the legend
- *
- * @param value
- */
- public void setVerticalAlignment(LegendVerticalAlignment value) {
- mVerticalAlignment = value;
- }
- /**
- * returns the orientation of the legend
- *
- * @return
- */
- public LegendOrientation getOrientation() {
- return mOrientation;
- }
- /**
- * sets the orientation of the legend
- *
- * @param value
- */
- public void setOrientation(LegendOrientation value) {
- mOrientation = value;
- }
- /**
- * returns whether the legend will draw inside the chart or outside
- *
- * @return
- */
- public boolean isDrawInsideEnabled() {
- return mDrawInside;
- }
- /**
- * sets whether the legend will draw inside the chart or outside
- *
- * @param value
- */
- public void setDrawInside(boolean value) {
- mDrawInside = value;
- }
- /**
- * returns the text direction of the legend
- *
- * @return
- */
- public LegendDirection getDirection() {
- return mDirection;
- }
- /**
- * sets the text direction of the legend
- *
- * @param pos
- */
- public void setDirection(LegendDirection pos) {
- mDirection = pos;
- }
- /**
- * returns the current form/shape that is set for the legend
- *
- * @return
- */
- public LegendForm getForm() {
- return mShape;
- }
- /**
- * sets the form/shape of the legend forms
- *
- * @param shape
- */
- public void setForm(LegendForm shape) {
- mShape = shape;
- }
- /**
- * sets the size in dp of the legend forms, default 8f
- *
- * @param size
- */
- public void setFormSize(float size) {
- mFormSize = size;
- }
- /**
- * returns the size in dp of the legend forms
- *
- * @return
- */
- public float getFormSize() {
- return mFormSize;
- }
- /**
- * sets the line width in dp for forms that consist of lines, default 3f
- *
- * @param size
- */
- public void setFormLineWidth(float size) {
- mFormLineWidth = size;
- }
- /**
- * returns the line width in dp for drawing forms that consist of lines
- *
- * @return
- */
- public float getFormLineWidth() {
- return mFormLineWidth;
- }
- /**
- * Sets the line dash path effect used for shapes that consist of lines.
- *
- * @param dashPathEffect
- */
- public void setFormLineDashEffect(DashPathEffect dashPathEffect) {
- mFormLineDashEffect = dashPathEffect;
- }
- /**
- * @return The line dash path effect used for shapes that consist of lines.
- */
- public DashPathEffect getFormLineDashEffect() {
- return mFormLineDashEffect;
- }
- /**
- * returns the space between the legend entries on a horizontal axis in
- * pixels
- *
- * @return
- */
- public float getXEntrySpace() {
- return mXEntrySpace;
- }
- /**
- * sets the space between the legend entries on a horizontal axis in pixels,
- * converts to dp internally
- *
- * @param space
- */
- public void setXEntrySpace(float space) {
- mXEntrySpace = space;
- }
- /**
- * returns the space between the legend entries on a vertical axis in pixels
- *
- * @return
- */
- public float getYEntrySpace() {
- return mYEntrySpace;
- }
- /**
- * sets the space between the legend entries on a vertical axis in pixels,
- * converts to dp internally
- *
- * @param space
- */
- public void setYEntrySpace(float space) {
- mYEntrySpace = space;
- }
- /**
- * returns the space between the form and the actual label/text
- *
- * @return
- */
- public float getFormToTextSpace() {
- return mFormToTextSpace;
- }
- /**
- * sets the space between the form and the actual label/text, converts to dp
- * internally
- *
- * @param space
- */
- public void setFormToTextSpace(float space) {
- this.mFormToTextSpace = space;
- }
- /**
- * returns the space that is left out between stacked forms (with no label)
- *
- * @return
- */
- public float getStackSpace() {
- return mStackSpace;
- }
- /**
- * sets the space that is left out between stacked forms (with no label)
- *
- * @param space
- */
- public void setStackSpace(float space) {
- mStackSpace = space;
- }
- /**
- * the total width of the legend (needed width space)
- */
- public float mNeededWidth = 0f;
- /**
- * the total height of the legend (needed height space)
- */
- public float mNeededHeight = 0f;
- public float mTextHeightMax = 0f;
- public float mTextWidthMax = 0f;
- /**
- * flag that indicates if word wrapping is enabled
- */
- private boolean mWordWrapEnabled = false;
- /**
- * Should the legend word wrap? / this is currently supported only for:
- * BelowChartLeft, BelowChartRight, BelowChartCenter. / note that word
- * wrapping a legend takes a toll on performance. / you may want to set
- * maxSizePercent when word wrapping, to set the point where the text wraps.
- * / default: false
- *
- * @param enabled
- */
- public void setWordWrapEnabled(boolean enabled) {
- mWordWrapEnabled = enabled;
- }
- /**
- * If this is set, then word wrapping the legend is enabled. This means the
- * legend will not be cut off if too long.
- *
- * @return
- */
- public boolean isWordWrapEnabled() {
- return mWordWrapEnabled;
- }
- /**
- * The maximum relative size out of the whole chart view. / If the legend is
- * to the right/left of the chart, then this affects the width of the
- * legend. / If the legend is to the top/bottom of the chart, then this
- * affects the height of the legend. / If the legend is the center of the
- * piechart, then this defines the size of the rectangular bounds out of the
- * size of the "hole". / default: 0.95f (95%)
- *
- * @return
- */
- public float getMaxSizePercent() {
- return mMaxSizePercent;
- }
- /**
- * The maximum relative size out of the whole chart view. / If
- * the legend is to the right/left of the chart, then this affects the width
- * of the legend. / If the legend is to the top/bottom of the chart, then
- * this affects the height of the legend. / default: 0.95f (95%)
- *
- * @param maxSize
- */
- public void setMaxSizePercent(float maxSize) {
- mMaxSizePercent = maxSize;
- }
- private List<FSize> mCalculatedLabelSizes = new ArrayList<>(16);
- private List<Boolean> mCalculatedLabelBreakPoints = new ArrayList<>(16);
- private List<FSize> mCalculatedLineSizes = new ArrayList<>(16);
- public List<FSize> getCalculatedLabelSizes() {
- return mCalculatedLabelSizes;
- }
- public List<Boolean> getCalculatedLabelBreakPoints() {
- return mCalculatedLabelBreakPoints;
- }
- public List<FSize> getCalculatedLineSizes() {
- return mCalculatedLineSizes;
- }
- /**
- * Calculates the dimensions of the Legend. This includes the maximum width
- * and height of a single entry, as well as the total width and height of
- * the Legend.
- *
- * @param labelpaint
- */
- public void calculateDimensions(Paint labelpaint, ViewPortHandler viewPortHandler) {
- float defaultFormSize = Utils.convertDpToPixel(mFormSize);
- float stackSpace = Utils.convertDpToPixel(mStackSpace);
- float formToTextSpace = Utils.convertDpToPixel(mFormToTextSpace);
- float xEntrySpace = Utils.convertDpToPixel(mXEntrySpace);
- float yEntrySpace = Utils.convertDpToPixel(mYEntrySpace);
- boolean wordWrapEnabled = mWordWrapEnabled;
- LegendEntry[] entries = mEntries;
- int entryCount = entries.length;
- mTextWidthMax = getMaximumEntryWidth(labelpaint);
- mTextHeightMax = getMaximumEntryHeight(labelpaint);
- switch (mOrientation) {
- case VERTICAL: {
- float maxWidth = 0f, maxHeight = 0f, width = 0f;
- float labelLineHeight = Utils.getLineHeight(labelpaint);
- boolean wasStacked = false;
- for (int i = 0; i < entryCount; i++) {
- LegendEntry e = entries[i];
- boolean drawingForm = e.form != LegendForm.NONE;
- float formSize = Float.isNaN(e.formSize)
- ? defaultFormSize
- : Utils.convertDpToPixel(e.formSize);
- String label = e.label;
- if (!wasStacked)
- width = 0.f;
- if (drawingForm) {
- if (wasStacked)
- width += stackSpace;
- width += formSize;
- }
- // grouped forms have null labels
- if (label != null) {
- // make a step to the left
- if (drawingForm && !wasStacked)
- width += formToTextSpace;
- else if (wasStacked) {
- maxWidth = Math.max(maxWidth, width);
- maxHeight += labelLineHeight + yEntrySpace;
- width = 0.f;
- wasStacked = false;
- }
- width += Utils.calcTextWidth(labelpaint, label);
- maxHeight += labelLineHeight + yEntrySpace;
- } else {
- wasStacked = true;
- width += formSize;
- if (i < entryCount - 1)
- width += stackSpace;
- }
- maxWidth = Math.max(maxWidth, width);
- }
- mNeededWidth = maxWidth;
- mNeededHeight = maxHeight;
- break;
- }
- case HORIZONTAL: {
- float labelLineHeight = Utils.getLineHeight(labelpaint);
- float labelLineSpacing = Utils.getLineSpacing(labelpaint) + yEntrySpace;
- float contentWidth = viewPortHandler.contentWidth() * mMaxSizePercent;
- // Start calculating layout
- float maxLineWidth = 0.f;
- float currentLineWidth = 0.f;
- float requiredWidth = 0.f;
- int stackedStartIndex = -1;
- mCalculatedLabelBreakPoints.clear();
- mCalculatedLabelSizes.clear();
- mCalculatedLineSizes.clear();
- for (int i = 0; i < entryCount; i++) {
- LegendEntry e = entries[i];
- boolean drawingForm = e.form != LegendForm.NONE;
- float formSize = Float.isNaN(e.formSize)
- ? defaultFormSize
- : Utils.convertDpToPixel(e.formSize);
- String label = e.label;
- mCalculatedLabelBreakPoints.add(false);
- if (stackedStartIndex == -1) {
- // we are not stacking, so required width is for this label
- // only
- requiredWidth = 0.f;
- } else {
- // add the spacing appropriate for stacked labels/forms
- requiredWidth += stackSpace;
- }
- // grouped forms have null labels
- if (label != null) {
- mCalculatedLabelSizes.add(Utils.calcTextSize(labelpaint, label));
- requiredWidth += drawingForm ? formToTextSpace + formSize : 0.f;
- requiredWidth += mCalculatedLabelSizes.get(i).width;
- } else {
- mCalculatedLabelSizes.add(FSize.getInstance(0.f, 0.f));
- requiredWidth += drawingForm ? formSize : 0.f;
- if (stackedStartIndex == -1) {
- // mark this index as we might want to break here later
- stackedStartIndex = i;
- }
- }
- if (label != null || i == entryCount - 1) {
- float requiredSpacing = currentLineWidth == 0.f ? 0.f : xEntrySpace;
- if (!wordWrapEnabled // No word wrapping, it must fit.
- // The line is empty, it must fit
- || currentLineWidth == 0.f
- // It simply fits
- || (contentWidth - currentLineWidth >=
- requiredSpacing + requiredWidth)) {
- // Expand current line
- currentLineWidth += requiredSpacing + requiredWidth;
- } else { // It doesn't fit, we need to wrap a line
- // Add current line size to array
- mCalculatedLineSizes.add(FSize.getInstance(currentLineWidth, labelLineHeight));
- maxLineWidth = Math.max(maxLineWidth, currentLineWidth);
- // Start a new line
- mCalculatedLabelBreakPoints.set(
- stackedStartIndex > -1 ? stackedStartIndex
- : i, true);
- currentLineWidth = requiredWidth;
- }
- if (i == entryCount - 1) {
- // Add last line size to array
- mCalculatedLineSizes.add(FSize.getInstance(currentLineWidth, labelLineHeight));
- maxLineWidth = Math.max(maxLineWidth, currentLineWidth);
- }
- }
- stackedStartIndex = label != null ? -1 : stackedStartIndex;
- }
- mNeededWidth = maxLineWidth;
- mNeededHeight = labelLineHeight
- * (float) (mCalculatedLineSizes.size())
- + labelLineSpacing *
- (float) (mCalculatedLineSizes.size() == 0
- ? 0
- : (mCalculatedLineSizes.size() - 1));
- break;
- }
- }
- mNeededHeight += mYOffset;
- mNeededWidth += mXOffset;
- }
- }
|