From 8d5ddc8b9e014912ccbe61f258d53d582ac5f7d5 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 21 Dec 2025 11:13:21 +0100 Subject: [PATCH] Kotlin Legend --- .../mikephil/charting/components/Legend.java | 760 ------------------ .../mikephil/charting/components/Legend.kt | 614 ++++++++++++++ .../{LegendEntry.java => LegendEntry.kt} | 54 +- .../charting/renderer/LegendRenderer.kt | 60 +- 4 files changed, 680 insertions(+), 808 deletions(-) delete mode 100644 MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.java create mode 100644 MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.kt rename MPChartLib/src/main/java/com/github/mikephil/charting/components/{LegendEntry.java => LegendEntry.kt} (55%) diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.java b/MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.java deleted file mode 100644 index 85adf9eec8..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.java +++ /dev/null @@ -1,760 +0,0 @@ -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.UtilsKtKt; -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 = UtilsKtKt.convertDpToPixel(10f); - this.mXOffset = UtilsKtKt.convertDpToPixel(5f); - this.mYOffset = UtilsKtKt.convertDpToPixel(3f); // 2 - } - - /** - * Constructor. Provide entries for the legend. - */ - 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. - */ - public void setEntries(List 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 - */ - public float getMaximumEntryWidth(Paint p) { - - float max = 0f; - float maxFormSize = 0f; - float formToTextSpace = UtilsKtKt.convertDpToPixel(mFormToTextSpace); - - for (LegendEntry entry : mEntries) { - final float formSize = UtilsKtKt.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 - */ - 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 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 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 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 - */ - public LegendHorizontalAlignment getHorizontalAlignment() { - return mHorizontalAlignment; - } - - /** - * sets the horizontal alignment of the legend - */ - public void setHorizontalAlignment(LegendHorizontalAlignment value) { - mHorizontalAlignment = value; - } - - /** - * returns the vertical alignment of the legend - */ - public LegendVerticalAlignment getVerticalAlignment() { - return mVerticalAlignment; - } - - /** - * sets the vertical alignment of the legend - */ - public void setVerticalAlignment(LegendVerticalAlignment value) { - mVerticalAlignment = value; - } - - /** - * returns the orientation of the legend - */ - public LegendOrientation getOrientation() { - return mOrientation; - } - - /** - * sets the orientation of the legend - */ - public void setOrientation(LegendOrientation value) { - mOrientation = value; - } - - /** - * returns whether the legend will draw inside the chart or outside - */ - public boolean isDrawInsideEnabled() { - return mDrawInside; - } - - /** - * sets whether the legend will draw inside the chart or outside - */ - public void setDrawInside(boolean value) { - mDrawInside = value; - } - - /** - * returns the text direction of the legend - */ - public LegendDirection getDirection() { - return mDirection; - } - - /** - * sets the text direction of the legend - */ - public void setDirection(LegendDirection pos) { - mDirection = pos; - } - - /** - * returns the current form/shape that is set for the legend - */ - public LegendForm getForm() { - return mShape; - } - - /** - * sets the form/shape of the legend forms - */ - public void setForm(LegendForm shape) { - mShape = shape; - } - - /** - * sets the size in dp of the legend forms, default 8f - */ - public void setFormSize(float size) { - mFormSize = size; - } - - /** - * returns the size in dp of the legend forms - */ - public float getFormSize() { - return mFormSize; - } - - /** - * sets the line width in dp for forms that consist of lines, default 3f - */ - public void setFormLineWidth(float size) { - mFormLineWidth = size; - } - - /** - * returns the line width in dp for drawing forms that consist of lines - */ - public float getFormLineWidth() { - return mFormLineWidth; - } - - /** - * Sets the line dash path effect used for shapes that consist of lines. - */ - 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 - */ - public float getXEntrySpace() { - return mXEntrySpace; - } - - /** - * sets the space between the legend entries on a horizontal axis in pixels, - * converts to dp internally - */ - public void setXEntrySpace(float space) { - mXEntrySpace = space; - } - - /** - * returns the space between the legend entries on a vertical axis in pixels - */ - public float getYEntrySpace() { - return mYEntrySpace; - } - - /** - * sets the space between the legend entries on a vertical axis in pixels, - * converts to dp internally - */ - public void setYEntrySpace(float space) { - mYEntrySpace = space; - } - - /** - * returns the space between the form and the actual label/text - */ - public float getFormToTextSpace() { - return mFormToTextSpace; - } - - /** - * sets the space between the form and the actual label/text, converts to dp - * internally - */ - public void setFormToTextSpace(float space) { - this.mFormToTextSpace = space; - } - - /** - * returns the space that is left out between stacked forms (with no label) - */ - public float getStackSpace() { - return mStackSpace; - } - - /** - * sets the space that is left out between stacked forms (with no label) - */ - 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 - */ - 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. - */ - 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%) - */ - 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%) - */ - public void setMaxSizePercent(float maxSize) { - mMaxSizePercent = maxSize; - } - - private List mCalculatedLabelSizes = new ArrayList<>(16); - private List mCalculatedLabelBreakPoints = new ArrayList<>(16); - private List mCalculatedLineSizes = new ArrayList<>(16); - - public List getCalculatedLabelSizes() { - return mCalculatedLabelSizes; - } - - public List getCalculatedLabelBreakPoints() { - return mCalculatedLabelBreakPoints; - } - - public List 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. - */ - public void calculateDimensions(Paint labelpaint, ViewPortHandler viewPortHandler) { - - float defaultFormSize = UtilsKtKt.convertDpToPixel(mFormSize); - float stackSpace = UtilsKtKt.convertDpToPixel(mStackSpace); - float formToTextSpace = UtilsKtKt.convertDpToPixel(mFormToTextSpace); - float xEntrySpace = UtilsKtKt.convertDpToPixel(mXEntrySpace); - float yEntrySpace = UtilsKtKt.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 - : UtilsKtKt.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 - : UtilsKtKt.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.isEmpty() - ? 0 - : (mCalculatedLineSizes.size() - 1)); - - break; - } - } - - mNeededHeight += mYOffset; - mNeededWidth += mXOffset; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.kt new file mode 100644 index 0000000000..a8596b1326 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.kt @@ -0,0 +1,614 @@ +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 com.github.mikephil.charting.utils.convertDpToPixel +import java.lang.Float +import kotlin.Array +import kotlin.Boolean +import kotlin.IntArray +import kotlin.String +import kotlin.arrayOf +import kotlin.collections.ArrayList +import kotlin.collections.MutableList +import kotlin.collections.toTypedArray +import kotlin.math.max +import kotlin.math.min +import kotlin.requireNotNull + +/** + * 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. + */ +class Legend() : ComponentBase() { + enum class 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 + } + + enum class LegendHorizontalAlignment { + LEFT, CENTER, RIGHT + } + + enum class LegendVerticalAlignment { + TOP, CENTER, BOTTOM + } + + enum class LegendOrientation { + HORIZONTAL, VERTICAL + } + + enum class LegendDirection { + LEFT_TO_RIGHT, RIGHT_TO_LEFT + } + + /** + * The legend entries array + */ + var entries: Array = arrayOf() + private set + + /** + * 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) + */ + var extraEntries: Array = arrayOf() + private set + + /** + * @return true if a custom legend entries has been set default + * false (automatic legend) + */ + /** + * 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) + */ + var isLegendCustom: Boolean = false + private set + + /** + * returns the horizontal alignment of the legend + */ + /** + * sets the horizontal alignment of the legend + */ + var horizontalAlignment: LegendHorizontalAlignment? = LegendHorizontalAlignment.LEFT + /** + * returns the vertical alignment of the legend + */ + /** + * sets the vertical alignment of the legend + */ + var verticalAlignment: LegendVerticalAlignment? = LegendVerticalAlignment.BOTTOM + /** + * returns the orientation of the legend + */ + /** + * sets the orientation of the legend + */ + var orientation: LegendOrientation = LegendOrientation.HORIZONTAL + + /** + * returns whether the legend will draw inside the chart or outside + */ + var isDrawInsideEnabled: Boolean = false + private set + + /** + * returns the text direction of the legend + */ + /** + * sets the text direction of the legend + */ + /** + * the text direction for the legend + */ + var direction: LegendDirection? = LegendDirection.LEFT_TO_RIGHT + + /** + * returns the current form/shape that is set for the legend + */ + /** + * sets the form/shape of the legend forms + */ + /** + * the shape/form the legend colors are drawn in + */ + var form: LegendForm? = LegendForm.SQUARE + + /** + * returns the size in dp of the legend forms + */ + /** + * sets the size in dp of the legend forms, default 8f + */ + /** + * the size of the legend forms/shapes + */ + var formSize: kotlin.Float = 8f + + /** + * returns the line width in dp for drawing forms that consist of lines + */ + /** + * sets the line width in dp for forms that consist of lines, default 3f + */ + /** + * the size of the legend forms/shapes + */ + var formLineWidth: kotlin.Float = 3f + + /** + * @return The line dash path effect used for shapes that consist of lines. + */ + /** + * Sets the line dash path effect used for shapes that consist of lines. + */ + /** + * Line dash path effect used for shapes that consist of lines. + */ + var formLineDashEffect: DashPathEffect? = null + + /** + * returns the space between the legend entries on a horizontal axis in + * pixels + */ + /** + * sets the space between the legend entries on a horizontal axis in pixels, + * converts to dp internally + */ + /** + * the space between the legend entries on a horizontal axis, default 6f + */ + var xEntrySpace: kotlin.Float = 6f + + /** + * returns the space between the legend entries on a vertical axis in pixels + */ + /** + * sets the space between the legend entries on a vertical axis in pixels, + * converts to dp internally + */ + /** + * the space between the legend entries on a vertical axis, default 5f + */ + var yEntrySpace: kotlin.Float = 0f + + /** + * returns the space between the form and the actual label/text + */ + /** + * sets the space between the form and the actual label/text, converts to dp + * internally + */ + /** + * 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 + */ + var formToTextSpace: kotlin.Float = 5f + + /** + * returns the space that is left out between stacked forms (with no label) + */ + /** + * sets the space that is left out between stacked forms (with no label) + */ + /** + * the space that should be left between stacked forms + */ + var stackSpace: kotlin.Float = 3f + + /** + * 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%) + */ + /** + * 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%) + */ + /** + * the maximum relative size out of the whole chart view in percent + */ + var maxSizePercent: kotlin.Float = 0.95f + + /** + * Constructor. Provide entries for the legend. + */ + constructor(entries: Array) : this() { + requireNotNull(entries) { "entries array is NULL" } + + this.entries = entries + } + + /** + * This method sets the automatically computed colors for the legend. Use setCustom(...) to set custom colors. + */ + fun setEntries(entries: MutableList) { + this.entries = entries.toTypedArray() + } + + /** + * returns the maximum length in pixels across all legend labels + formsize + * + formtotextspace + * + * @param p the paint object used for rendering the text + */ + fun getMaximumEntryWidth(p: Paint): kotlin.Float { + var max = 0f + var maxFormSize = 0f + val formToTextSpace = formToTextSpace.convertDpToPixel() + + for (entry in this.entries) { + val formSize = (if (Float.isNaN(entry.formSize)) + this.formSize + else + entry.formSize).convertDpToPixel() + if (formSize > maxFormSize) maxFormSize = formSize + + val label = entry.label + if (label == null) continue + + val length = Utils.calcTextWidth(p, label).toFloat() + + 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 + */ + fun getMaximumEntryHeight(p: Paint): kotlin.Float { + var max = 0f + + for (entry in this.entries) { + val label = entry.label + if (label == null) continue + + val length = Utils.calcTextHeight(p, label).toFloat() + + if (length > max) max = length + } + + return max + } + + fun setExtra(entries: MutableList) { + this.extraEntries = entries.toTypedArray() + } + + fun setExtra(entries: Array) { + this.extraEntries = 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) + */ + fun setExtra(colors: IntArray, labels: Array) { + val entries: MutableList = ArrayList() + + for (i in 0..() + } + + /** + * 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) + */ + fun setCustom(entries: Array) { + this.entries = entries + this.isLegendCustom = 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) + */ + fun setCustom(entries: MutableList) { + this.entries = entries.toTypedArray() + this.isLegendCustom = true + } + + /** + * Calling this will disable the custom legend entries (set by + * setCustom(...)). Instead, the entries will again be calculated + * automatically (after notifyDataSetChanged() is called). + */ + fun resetCustom() { + this.isLegendCustom = false + } + + /** + * sets whether the legend will draw inside the chart or outside + */ + fun setDrawInside(value: Boolean) { + this.isDrawInsideEnabled = value + } + + /** + * the total width of the legend (needed width space) + */ + @JvmField + var mNeededWidth: kotlin.Float = 0f + + /** + * the total height of the legend (needed height space) + */ + @JvmField + var mNeededHeight: kotlin.Float = 0f + + @JvmField + var mTextHeightMax: kotlin.Float = 0f + + var mTextWidthMax: kotlin.Float = 0f + + /** + * If this is set, then word wrapping the legend is enabled. This means the + * legend will not be cut off if too long. + */ + /** + * 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 + */ + /** + * flag that indicates if word wrapping is enabled + */ + var isWordWrapEnabled: Boolean = false + + val calculatedLabelSizes: MutableList = ArrayList(16) + val calculatedLabelBreakPoints: MutableList = ArrayList(16) + val calculatedLineSizes: MutableList = ArrayList(16) + + /** + * default constructor + */ + init { + this.mTextSize = 10f.convertDpToPixel() + this.mXOffset = 5f.convertDpToPixel() + this.mYOffset = 3f.convertDpToPixel() // 2 + } + + /** + * 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. + */ + fun calculateDimensions(labelpaint: Paint, viewPortHandler: ViewPortHandler) { + val defaultFormSize = formSize.convertDpToPixel() + val stackSpace = stackSpace.convertDpToPixel() + val formToTextSpace = formToTextSpace.convertDpToPixel() + val xEntrySpace = xEntrySpace.convertDpToPixel() + val yEntrySpace = yEntrySpace.convertDpToPixel() + val wordWrapEnabled = this.isWordWrapEnabled + val entries = this.entries + val entryCount = entries.size + + mTextWidthMax = getMaximumEntryWidth(labelpaint) + mTextHeightMax = getMaximumEntryHeight(labelpaint) + + when (this.orientation) { + LegendOrientation.VERTICAL -> { + var maxWidth = 0f + var maxHeight = 0f + var width = 0f + val labelLineHeight = Utils.getLineHeight(labelpaint) + var wasStacked = false + + var i = 0 + while (i < entryCount) { + val e = entries[i] + val drawingForm = e.form != LegendForm.NONE + val formSize = if (Float.isNaN(e.formSize)) + defaultFormSize + else + e.formSize.convertDpToPixel() + val label = e.label + + if (!wasStacked) width = 0f + + 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 = max(maxWidth, width) + maxHeight += labelLineHeight + yEntrySpace + width = 0f + wasStacked = false + } + + width += Utils.calcTextWidth(labelpaint, label).toFloat() + + maxHeight += labelLineHeight + yEntrySpace + } else { + wasStacked = true + width += formSize + if (i < entryCount - 1) width += stackSpace + } + + maxWidth = max(maxWidth, width) + i++ + } + + mNeededWidth = maxWidth + mNeededHeight = maxHeight + } + + LegendOrientation.HORIZONTAL -> { + val labelLineHeight = Utils.getLineHeight(labelpaint) + val labelLineSpacing = Utils.getLineSpacing(labelpaint) + yEntrySpace + val contentWidth = viewPortHandler.contentWidth() * this.maxSizePercent + + // Start calculating layout + var maxLineWidth = 0f + var currentLineWidth = 0f + var requiredWidth = 0f + var stackedStartIndex = -1 + + calculatedLabelBreakPoints.clear() + calculatedLabelSizes.clear() + calculatedLineSizes.clear() + + var i = 0 + while (i < entryCount) { + val e = entries[i] + val drawingForm = e.form != LegendForm.NONE + val formSize = if (Float.isNaN(e.formSize)) + defaultFormSize + else + e.formSize.convertDpToPixel() + val label = e.label + + calculatedLabelBreakPoints.add(false) + + if (stackedStartIndex == -1) { + // we are not stacking, so required width is for this label + // only + requiredWidth = 0f + } else { + // add the spacing appropriate for stacked labels/forms + requiredWidth += stackSpace + } + + // grouped forms have null labels + if (label != null) { + calculatedLabelSizes.add(Utils.calcTextSize(labelpaint, label)) + requiredWidth += if (drawingForm) formToTextSpace + formSize else 0f + requiredWidth += calculatedLabelSizes.get(i)!!.width + } else { + calculatedLabelSizes.add(FSize.getInstance(0f, 0f)) + requiredWidth += if (drawingForm) formSize else 0f + + if (stackedStartIndex == -1) { + // mark this index as we might want to break here later + stackedStartIndex = i + } + } + + if (label != null || i == entryCount - 1) { + val requiredSpacing = if (currentLineWidth == 0f) 0f else xEntrySpace + + if (!wordWrapEnabled // No word wrapping, it must fit. + // The line is empty, it must fit + || currentLineWidth == 0f // 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 + + calculatedLineSizes.add(FSize.getInstance(currentLineWidth, labelLineHeight)) + maxLineWidth = max(maxLineWidth, currentLineWidth) + + // Start a new line + calculatedLabelBreakPoints.set( + if (stackedStartIndex > -1) + stackedStartIndex + else + i, true + ) + currentLineWidth = requiredWidth + } + + if (i == entryCount - 1) { + // Add last line size to array + calculatedLineSizes.add(FSize.getInstance(currentLineWidth, labelLineHeight)) + maxLineWidth = max(maxLineWidth, currentLineWidth) + } + } + + stackedStartIndex = if (label != null) -1 else stackedStartIndex + i++ + } + + mNeededWidth = maxLineWidth + mNeededHeight = (labelLineHeight + * (calculatedLineSizes.size).toFloat() + + labelLineSpacing * (if (calculatedLineSizes.isEmpty()) + 0 + else + (calculatedLineSizes.size - 1)).toFloat()) + } + } + + mNeededHeight += mYOffset + mNeededWidth += mXOffset + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/LegendEntry.java b/MPChartLib/src/main/java/com/github/mikephil/charting/components/LegendEntry.kt similarity index 55% rename from MPChartLib/src/main/java/com/github/mikephil/charting/components/LegendEntry.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/components/LegendEntry.kt index 3acec0f461..c435a1d2c3 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/LegendEntry.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/LegendEntry.kt @@ -1,14 +1,12 @@ -package com.github.mikephil.charting.components; +package com.github.mikephil.charting.components +import android.graphics.DashPathEffect +import com.github.mikephil.charting.components.Legend.LegendForm +import com.github.mikephil.charting.utils.ColorTemplate -import android.graphics.DashPathEffect; -import com.github.mikephil.charting.utils.ColorTemplate; - -public class LegendEntry { - public LegendEntry() { - - } +class LegendEntry { + constructor() /** * @@ -19,26 +17,27 @@ public LegendEntry() { * @param formLineDashEffect Set to nil to use the legend's default. * @param formColor The color for drawing the form. */ - public LegendEntry(String label, - Legend.LegendForm form, - float formSize, - float formLineWidth, - DashPathEffect formLineDashEffect, - int formColor) - { - this.label = label; - this.form = form; - this.formSize = formSize; - this.formLineWidth = formLineWidth; - this.formLineDashEffect = formLineDashEffect; - this.formColor = formColor; + constructor( + label: String?, + form: LegendForm?, + formSize: Float, + formLineWidth: Float, + formLineDashEffect: DashPathEffect?, + formColor: Int + ) { + this.label = label + this.form = form + this.formSize = formSize + this.formLineWidth = formLineWidth + this.formLineDashEffect = formLineDashEffect + this.formColor = formColor } /** * The legend entry text. * A `null` label will start a group. */ - public String label; + var label: String? = null /** * The form to draw for this entry. @@ -47,32 +46,31 @@ public LegendEntry(String label, * `EMPTY` will avoid drawing a form, but keep its space. * `DEFAULT` will use the Legend's default. */ - public Legend.LegendForm form = Legend.LegendForm.DEFAULT; + var form: LegendForm? = LegendForm.DEFAULT /** * Form size will be considered except for when .None is used * * Set as NaN to use the legend's default */ - public float formSize = Float.NaN; + var formSize: Float = Float.NaN /** * Line width used for shapes that consist of lines. * * Set as NaN to use the legend's default */ - public float formLineWidth = Float.NaN; + var formLineWidth: Float = Float.NaN /** * Line dash path effect used for shapes that consist of lines. * * Set to null to use the legend's default */ - public DashPathEffect formLineDashEffect = null; + var formLineDashEffect: DashPathEffect? = null /** * The color for drawing the form */ - public int formColor = ColorTemplate.COLOR_NONE; - + var formColor: Int = ColorTemplate.COLOR_NONE } \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LegendRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LegendRenderer.kt index ac0b65448b..cbfbeceea0 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LegendRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LegendRenderer.kt @@ -4,6 +4,7 @@ import android.graphics.Canvas import android.graphics.Paint import android.graphics.Paint.Align import android.graphics.Path +import android.util.Log import androidx.core.graphics.withSave import com.github.mikephil.charting.components.Legend import com.github.mikephil.charting.components.Legend.LegendDirection @@ -227,7 +228,7 @@ open class LegendRenderer( val yOffset = legend.yOffset val xOffset = legend.xOffset - var originPosX: Float + var originPosX: Float = 0f when (horizontalAlignment) { LegendHorizontalAlignment.LEFT -> { @@ -265,6 +266,8 @@ open class LegendRenderer( legend.mNeededWidth / 2.0 - xOffset).toFloat() } } + + null -> Log.e("Chart", "Legend horizontalAlignment is null") } when (orientation) { @@ -279,6 +282,10 @@ open class LegendRenderer( LegendVerticalAlignment.TOP -> yOffset LegendVerticalAlignment.BOTTOM -> viewPortHandler.chartHeight - yOffset - legend.mNeededHeight LegendVerticalAlignment.CENTER -> (viewPortHandler.chartHeight - legend.mNeededHeight) / 2f + yOffset + else -> { + Log.w("Chart", "Legend verticalAlignment not set"); + 0f + } } var lineIndex = 0 @@ -290,16 +297,18 @@ open class LegendRenderer( val drawingForm = e.form != LegendForm.NONE val formSize = if (java.lang.Float.isNaN(e.formSize)) defaultFormSize else e.formSize.convertDpToPixel() - if (i < calculatedLabelBreakPoints.size && calculatedLabelBreakPoints[i]) { + if (i < calculatedLabelBreakPoints.size && calculatedLabelBreakPoints[i] == true) { posX = originPosX posY += labelLineHeight + labelLineSpacing } if (posX == originPosX && horizontalAlignment == LegendHorizontalAlignment.CENTER && lineIndex < calculatedLineSizes.size) { - posX += (if (direction == LegendDirection.RIGHT_TO_LEFT) - calculatedLineSizes[lineIndex].width - else - -calculatedLineSizes[lineIndex].width) / 2f + calculatedLineSizes[lineIndex]?.let { fSize -> + posX += (if (direction == LegendDirection.RIGHT_TO_LEFT) + fSize.width + else + -fSize.width) / 2f + } lineIndex++ } @@ -316,11 +325,14 @@ open class LegendRenderer( if (!isStacked) { if (drawingForm) posX += if (direction == LegendDirection.RIGHT_TO_LEFT) -formToTextSpace else formToTextSpace - if (direction == LegendDirection.RIGHT_TO_LEFT) posX -= calculatedLabelSizes[i].width + if (direction == LegendDirection.RIGHT_TO_LEFT) + posX -= calculatedLabelSizes[i]?.width ?: 0f - drawLabel(canvas, posX, posY + labelLineHeight, e.label) + if (e.label != null) + drawLabel(canvas, posX, posY + labelLineHeight, e.label!!) - if (direction == LegendDirection.LEFT_TO_RIGHT) posX += calculatedLabelSizes[i].width + if (direction == LegendDirection.LEFT_TO_RIGHT) + posX += calculatedLabelSizes[i]?.width ?: 0f posX += if (direction == LegendDirection.RIGHT_TO_LEFT) -xEntrySpace else xEntrySpace } else posX += if (direction == LegendDirection.RIGHT_TO_LEFT) -stackSpace else stackSpace @@ -332,7 +344,7 @@ open class LegendRenderer( // contains the stacked legend size in pixels var stack = 0f var wasStacked = false - var posY: Float + var posY: Float = 0f when (verticalAlignment) { LegendVerticalAlignment.TOP -> { @@ -354,6 +366,8 @@ open class LegendRenderer( LegendVerticalAlignment.CENTER -> posY = (viewPortHandler.chartHeight / 2f - legend.mNeededHeight / 2f + legend.yOffset) + + null -> Log.w("Chart", "Legend verticalAlignment is null"); } var i = 0 @@ -365,8 +379,10 @@ open class LegendRenderer( var posX = originPosX if (drawingForm) { - if (direction == LegendDirection.LEFT_TO_RIGHT) posX += stack - else posX -= formSize - stack + if (direction == LegendDirection.LEFT_TO_RIGHT) + posX += stack + else + posX -= formSize - stack drawForm(canvas, posX, posY + formYOffset, e, legend) @@ -382,12 +398,13 @@ open class LegendRenderer( if (direction == LegendDirection.RIGHT_TO_LEFT) posX -= Utils.calcTextWidth(labelPaint, e.label).toFloat() - if (!wasStacked) { - drawLabel(canvas, posX, posY + labelLineHeight, e.label) - } else { - posY += labelLineHeight + labelLineSpacing - drawLabel(canvas, posX, posY + labelLineHeight, e.label) - } + if (e.label != null) + if (!wasStacked) { + drawLabel(canvas, posX, posY + labelLineHeight, e.label!!) + } else { + posY += labelLineHeight + labelLineSpacing + drawLabel(canvas, posX, posY + labelLineHeight, e.label!!) + } // make a step down posY += labelLineHeight + labelLineSpacing @@ -447,8 +464,8 @@ open class LegendRenderer( val half = formSize / 2f when (form) { - LegendForm.NONE -> {} - LegendForm.EMPTY -> {} + LegendForm.NONE -> Unit + LegendForm.EMPTY -> Unit LegendForm.DEFAULT, LegendForm.CIRCLE -> { formPaint.style = Paint.Style.FILL canvas.drawCircle(x + half, y, half, formPaint) @@ -479,6 +496,9 @@ open class LegendRenderer( mLineFormPath.lineTo(x + formSize, y) canvas.drawPath(mLineFormPath, formPaint) } + + null -> Log.w( "Chart", "Legend form is null") + } }