From a964b8dafcc8d8b4b9e2b3320b27bc6928f030bf Mon Sep 17 00:00:00 2001 From: Daniel Cohen Gindi Date: Sun, 10 Apr 2016 20:02:55 +0300 Subject: [PATCH] Exploded the Legend-Position enum to support more combinations --- .../charting/charts/BarLineChartBase.java | 141 ++++-- .../charting/charts/HorizontalBarChart.java | 43 +- .../charting/charts/PieRadarChartBase.java | 200 ++++---- .../mikephil/charting/components/Legend.java | 447 +++++++++++++----- .../charting/renderer/LegendRenderer.java | 192 ++++---- 5 files changed, 637 insertions(+), 386 deletions(-) diff --git a/MPChartLib/src/com/github/mikephil/charting/charts/BarLineChartBase.java b/MPChartLib/src/com/github/mikephil/charting/charts/BarLineChartBase.java index 1f66c92e5..9bba9d588 100644 --- a/MPChartLib/src/com/github/mikephil/charting/charts/BarLineChartBase.java +++ b/MPChartLib/src/com/github/mikephil/charting/charts/BarLineChartBase.java @@ -10,10 +10,12 @@ import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.PointF; +import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; +import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.components.Legend.LegendPosition; import com.github.mikephil.charting.components.XAxis.XAxisPosition; import com.github.mikephil.charting.components.YAxis; @@ -299,9 +301,13 @@ protected void prepareValuePxMatrix() { Log.i(LOG_TAG, "Preparing Value-Px Matrix, xmin: " + mXAxis.mAxisMinimum + ", xmax: " + mXAxis.mAxisMaximum + ", xdelta: " + mXAxis.mAxisRange); - mRightAxisTransformer.prepareMatrixValuePx(mXAxis.mAxisMinimum, mXAxis.mAxisRange, mAxisRight.mAxisRange, + mRightAxisTransformer.prepareMatrixValuePx(mXAxis.mAxisMinimum, + mXAxis.mAxisRange, + mAxisRight.mAxisRange, mAxisRight.mAxisMinimum); - mLeftAxisTransformer.prepareMatrixValuePx(mXAxis.mAxisMinimum, mXAxis.mAxisRange, mAxisLeft.mAxisRange, + mLeftAxisTransformer.prepareMatrixValuePx(mXAxis.mAxisMinimum, + mXAxis.mAxisRange, + mAxisLeft.mAxisRange, mAxisLeft.mAxisMinimum); } @@ -355,6 +361,90 @@ protected void calcMinMax() { .RIGHT)); } + protected void calculateLegendOffsets(RectF offsets) { + + offsets.left = 0.f; + offsets.right = 0.f; + offsets.top = 0.f; + offsets.bottom = 0.f; + + // setup offsets for legend + if (mLegend != null && mLegend.isEnabled() && !mLegend.isDrawInsideEnabled()) { + switch (mLegend.getOrientation()) { + case VERTICAL: + + switch (mLegend.getHorizontalAlignment()) { + case LEFT: + offsets.left += Math.min(mLegend.mNeededWidth, + mViewPortHandler.getChartWidth() * mLegend.getMaxSizePercent()) + + mLegend.getXOffset(); + break; + + case RIGHT: + offsets.right += Math.min(mLegend.mNeededWidth, + mViewPortHandler.getChartWidth() * mLegend.getMaxSizePercent()) + + mLegend.getXOffset(); + break; + + case CENTER: + + switch (mLegend.getVerticalAlignment()) { + case TOP: + offsets.top += Math.min(mLegend.mNeededHeight, + mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()) + + mLegend.getYOffset(); + + if (getXAxis().isEnabled() && getXAxis().isDrawLabelsEnabled()) + offsets.top += getXAxis().mLabelRotatedHeight; + break; + + case BOTTOM: + offsets.bottom += Math.min(mLegend.mNeededHeight, + mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()) + + mLegend.getYOffset(); + + if (getXAxis().isEnabled() && getXAxis().isDrawLabelsEnabled()) + offsets.bottom += getXAxis().mLabelRotatedHeight; + break; + + default: + break; + } + } + + break; + + case HORIZONTAL: + + switch (mLegend.getVerticalAlignment()) { + case TOP: + offsets.top += Math.min(mLegend.mNeededHeight, + mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()) + + mLegend.getYOffset(); + + if (getXAxis().isEnabled() && getXAxis().isDrawLabelsEnabled()) + offsets.top += getXAxis().mLabelRotatedHeight; + break; + + case BOTTOM: + offsets.bottom += Math.min(mLegend.mNeededHeight, + mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()) + + mLegend.getYOffset(); + + if (getXAxis().isEnabled() && getXAxis().isDrawLabelsEnabled()) + offsets.bottom += getXAxis().mLabelRotatedHeight; + break; + + default: + break; + } + break; + } + } + } + + private RectF mOffsetsBuffer = new RectF(); + @Override public void calculateOffsets() { @@ -362,49 +452,12 @@ public void calculateOffsets() { float offsetLeft = 0f, offsetRight = 0f, offsetTop = 0f, offsetBottom = 0f; - // setup offsets for legend - if (mLegend != null && mLegend.isEnabled()) { - - if (mLegend.getPosition() == LegendPosition.RIGHT_OF_CHART - || mLegend.getPosition() == LegendPosition.RIGHT_OF_CHART_CENTER) { - - offsetRight += Math.min(mLegend.mNeededWidth, mViewPortHandler.getChartWidth() - * mLegend.getMaxSizePercent()) - + mLegend.getXOffset() * 2f; - - } else if (mLegend.getPosition() == LegendPosition.LEFT_OF_CHART - || mLegend.getPosition() == LegendPosition.LEFT_OF_CHART_CENTER) { - - offsetLeft += Math.min(mLegend.mNeededWidth, mViewPortHandler.getChartWidth() - * mLegend.getMaxSizePercent()) - + mLegend.getXOffset() * 2f; + calculateLegendOffsets(mOffsetsBuffer); - } else if (mLegend.getPosition() == LegendPosition.BELOW_CHART_LEFT - || mLegend.getPosition() == LegendPosition.BELOW_CHART_RIGHT - || mLegend.getPosition() == LegendPosition.BELOW_CHART_CENTER) { - - // It's possible that we do not need this offset anymore as it - // is available through the extraOffsets, but changing it can mean - // changing default visibility for existing apps. - float yOffset = mLegend.mTextHeightMax; - - offsetBottom += Math.min(mLegend.mNeededHeight + yOffset, - mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()); - - } else if (mLegend.getPosition() == LegendPosition.ABOVE_CHART_LEFT - || mLegend.getPosition() == LegendPosition.ABOVE_CHART_RIGHT - || mLegend.getPosition() == LegendPosition.ABOVE_CHART_CENTER) { - - // It's possible that we do not need this offset anymore as it - // is available through the extraOffsets, but changing it can mean - // changing default visibility for existing apps. - float yOffset = mLegend.mTextHeightMax; - - offsetTop += Math.min(mLegend.mNeededHeight + yOffset, - mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()); - - } - } + offsetLeft += mOffsetsBuffer.left; + offsetTop += mOffsetsBuffer.top; + offsetRight += mOffsetsBuffer.right; + offsetBottom += mOffsetsBuffer.bottom; // offsets for y-labels if (mAxisLeft.needsOffset()) { diff --git a/MPChartLib/src/com/github/mikephil/charting/charts/HorizontalBarChart.java b/MPChartLib/src/com/github/mikephil/charting/charts/HorizontalBarChart.java index 2ff098b1a..593ce6c7f 100644 --- a/MPChartLib/src/com/github/mikephil/charting/charts/HorizontalBarChart.java +++ b/MPChartLib/src/com/github/mikephil/charting/charts/HorizontalBarChart.java @@ -56,48 +56,19 @@ protected void init() { mXAxisRenderer = new XAxisRendererHorizontalBarChart(mViewPortHandler, mXAxis, mLeftAxisTransformer, this); } + private RectF mOffsetsBuffer = new RectF(); + @Override public void calculateOffsets() { float offsetLeft = 0f, offsetRight = 0f, offsetTop = 0f, offsetBottom = 0f; - // setup offsets for legend - if (mLegend != null && mLegend.isEnabled()) { - - if (mLegend.getPosition() == LegendPosition.RIGHT_OF_CHART || mLegend.getPosition() == LegendPosition.RIGHT_OF_CHART_CENTER) { - - offsetRight += Math.min(mLegend.mNeededWidth, mViewPortHandler.getChartWidth() * mLegend.getMaxSizePercent()) - + mLegend.getXOffset() * 2f; - - } else if (mLegend.getPosition() == LegendPosition.LEFT_OF_CHART - || mLegend.getPosition() == LegendPosition.LEFT_OF_CHART_CENTER) { - - offsetLeft += Math.min(mLegend.mNeededWidth, mViewPortHandler.getChartWidth() * mLegend.getMaxSizePercent()) - + mLegend.getXOffset() * 2f; - - } else if (mLegend.getPosition() == LegendPosition.BELOW_CHART_LEFT - || mLegend.getPosition() == LegendPosition.BELOW_CHART_RIGHT - || mLegend.getPosition() == LegendPosition.BELOW_CHART_CENTER) { + calculateLegendOffsets(mOffsetsBuffer); - // It's possible that we do not need this offset anymore as it - // is available through the extraOffsets, but changing it can mean - // changing default visibility for existing apps. - float yOffset = mLegend.mTextHeightMax; - - offsetBottom += Math.min(mLegend.mNeededHeight + yOffset, mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()); - - } else if (mLegend.getPosition() == LegendPosition.ABOVE_CHART_LEFT - || mLegend.getPosition() == LegendPosition.ABOVE_CHART_RIGHT - || mLegend.getPosition() == LegendPosition.ABOVE_CHART_CENTER) { - - // It's possible that we do not need this offset anymore as it - // is available through the extraOffsets, but changing it can mean - // changing default visibility for existing apps. - float yOffset = mLegend.mTextHeightMax * 2.f; - - offsetTop += Math.min(mLegend.mNeededHeight + yOffset, mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()); - } - } + offsetLeft += mOffsetsBuffer.left; + offsetTop += mOffsetsBuffer.top; + offsetRight += mOffsetsBuffer.right; + offsetBottom += mOffsetsBuffer.bottom; // offsets for y-labels if (mAxisLeft.needsOffset()) { diff --git a/MPChartLib/src/com/github/mikephil/charting/charts/PieRadarChartBase.java b/MPChartLib/src/com/github/mikephil/charting/charts/PieRadarChartBase.java index a7cb8f73e..5a938872c 100644 --- a/MPChartLib/src/com/github/mikephil/charting/charts/PieRadarChartBase.java +++ b/MPChartLib/src/com/github/mikephil/charting/charts/PieRadarChartBase.java @@ -13,6 +13,7 @@ import android.view.MotionEvent; import com.github.mikephil.charting.animation.Easing; +import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.components.Legend.LegendPosition; import com.github.mikephil.charting.components.XAxis; import com.github.mikephil.charting.data.ChartData; @@ -103,119 +104,118 @@ public void calculateOffsets() { float legendLeft = 0f, legendRight = 0f, legendBottom = 0f, legendTop = 0f; - if (mLegend != null && mLegend.isEnabled()) { - - float fullLegendWidth = Math.min(mLegend.mNeededWidth, - mViewPortHandler.getChartWidth() * mLegend.getMaxSizePercent()) + - mLegend.getFormSize() + mLegend.getFormToTextSpace(); - - if (mLegend.getPosition() == LegendPosition.RIGHT_OF_CHART_CENTER) { - - // this is the space between the legend and the chart - float spacing = Utils.convertDpToPixel(13f); - - legendRight = fullLegendWidth + spacing; - - } else if (mLegend.getPosition() == LegendPosition.RIGHT_OF_CHART) { - - // this is the space between the legend and the chart - float spacing = Utils.convertDpToPixel(8f); - - float legendWidth = fullLegendWidth + spacing; - - float legendHeight = mLegend.mNeededHeight + mLegend.mTextHeightMax; - - PointF c = getCenter(); - - PointF bottomRight = new PointF(getWidth() - legendWidth + 15, legendHeight + 15); - float distLegend = distanceToCenter(bottomRight.x, bottomRight.y); - - PointF reference = getPosition(c, getRadius(), - getAngleForPoint(bottomRight.x, bottomRight.y)); - - float distReference = distanceToCenter(reference.x, reference.y); - float min = Utils.convertDpToPixel(5f); - - if (distLegend < distReference) { - - float diff = distReference - distLegend; - legendRight = min + diff; - } - - if (bottomRight.y >= c.y && getHeight() - legendWidth > getWidth()) { - legendRight = legendWidth; - } - - } else if (mLegend.getPosition() == LegendPosition.LEFT_OF_CHART_CENTER) { - - // this is the space between the legend and the chart - float spacing = Utils.convertDpToPixel(13f); + if (mLegend != null && mLegend.isEnabled() && !mLegend.isDrawInsideEnabled()) { - legendLeft = fullLegendWidth + spacing; - - } else if (mLegend.getPosition() == LegendPosition.LEFT_OF_CHART) { - - // this is the space between the legend and the chart - float spacing = Utils.convertDpToPixel(8f); - - float legendWidth = fullLegendWidth + spacing; - - float legendHeight = mLegend.mNeededHeight + mLegend.mTextHeightMax; - - PointF c = getCenter(); - - PointF bottomLeft = new PointF(legendWidth - 15, legendHeight + 15); - float distLegend = distanceToCenter(bottomLeft.x, bottomLeft.y); - - PointF reference = getPosition(c, getRadius(), - getAngleForPoint(bottomLeft.x, bottomLeft.y)); - - float distReference = distanceToCenter(reference.x, reference.y); - float min = Utils.convertDpToPixel(5f); - - if (distLegend < distReference) { - - float diff = distReference - distLegend; - legendLeft = min + diff; - } + float fullLegendWidth = Math.min(mLegend.mNeededWidth, + mViewPortHandler.getChartWidth() * mLegend.getMaxSizePercent()) + + mLegend.getFormSize() + mLegend.getFormToTextSpace(); - if (bottomLeft.y >= c.y && getHeight() - legendWidth > getWidth()) { - legendLeft = legendWidth; + switch (mLegend.getOrientation()) { + case VERTICAL: + { + float xLegendOffset = 0.f; + + if (mLegend.getHorizontalAlignment() == Legend.LegendHorizontalAlignment.LEFT + || mLegend.getHorizontalAlignment() == Legend.LegendHorizontalAlignment.RIGHT) { + if (mLegend.getVerticalAlignment() == Legend.LegendVerticalAlignment.CENTER) { + // this is the space between the legend and the chart + final float spacing = Utils.convertDpToPixel(13f); + + xLegendOffset = fullLegendWidth + spacing; + + } else { + // this is the space between the legend and the chart + float spacing = Utils.convertDpToPixel(8f); + + float legendWidth = fullLegendWidth + spacing; + float legendHeight = mLegend.mNeededHeight + mLegend.mTextHeightMax; + + PointF c = getCenter(); + + float bottomX = mLegend.getHorizontalAlignment() == + Legend.LegendHorizontalAlignment.RIGHT + ? getWidth() - legendWidth + 15.f + : legendWidth - 15.f; + float bottomY = legendHeight + 15.f; + float distLegend = distanceToCenter(bottomX, bottomY); + + PointF reference = getPosition(c, getRadius(), + getAngleForPoint(bottomX, bottomY)); + + float distReference = distanceToCenter(reference.x, reference.y); + float minOffset = Utils.convertDpToPixel(5f); + + if (bottomY >= c.y && getHeight() - legendWidth > getWidth()) { + xLegendOffset = legendWidth; + } else if (distLegend < distReference) { + + float diff = distReference - distLegend; + xLegendOffset = minOffset + diff; + } + } + } + + switch (mLegend.getHorizontalAlignment()) { + case LEFT: + legendLeft = xLegendOffset; + break; + + case RIGHT: + legendRight = xLegendOffset; + break; + + case CENTER: + switch (mLegend.getVerticalAlignment()) { + case TOP: + legendTop = Math.min(mLegend.mNeededHeight, + mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()); + break; + case BOTTOM: + legendBottom = Math.min(mLegend.mNeededHeight, + mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()); + break; + } + break; + } } - - } else if (mLegend.getPosition() == LegendPosition.BELOW_CHART_LEFT - || mLegend.getPosition() == LegendPosition.BELOW_CHART_RIGHT - || mLegend.getPosition() == LegendPosition.BELOW_CHART_CENTER) { - - // It's possible that we do not need this offset anymore as it - // is available through the extraOffsets, but changing it can mean - // changing default visibility for existing apps. - float yOffset = getRequiredLegendOffset(); - - legendBottom = Math.min(mLegend.mNeededHeight + yOffset, mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()); - - } else if (mLegend.getPosition() == LegendPosition.ABOVE_CHART_LEFT - || mLegend.getPosition() == LegendPosition.ABOVE_CHART_RIGHT - || mLegend.getPosition() == LegendPosition.ABOVE_CHART_CENTER) { - - // It's possible that we do not need this offset anymore as it - // is available through the extraOffsets, but changing it can mean - // changing default visibility for existing apps. - float yOffset = getRequiredLegendOffset(); - - legendTop = Math.min(mLegend.mNeededHeight + yOffset, mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()); - + break; + + case HORIZONTAL: + float yLegendOffset = 0.f; + + if (mLegend.getVerticalAlignment() == Legend.LegendVerticalAlignment.TOP || + mLegend.getVerticalAlignment() == Legend.LegendVerticalAlignment.BOTTOM) { + + // It's possible that we do not need this offset anymore as it + // is available through the extraOffsets, but changing it can mean + // changing default visibility for existing apps. + float yOffset = getRequiredLegendOffset(); + + yLegendOffset = Math.min(mLegend.mNeededHeight + yOffset, + mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()); + + switch (mLegend.getVerticalAlignment()) { + case TOP: + legendTop = yLegendOffset; + break; + case BOTTOM: + legendBottom = yLegendOffset; + break; + } + } + break; } legendLeft += getRequiredBaseOffset(); legendRight += getRequiredBaseOffset(); legendTop += getRequiredBaseOffset(); + legendBottom += getRequiredBaseOffset(); } float minOffset = Utils.convertDpToPixel(mMinOffset); if (this instanceof RadarChart) { - XAxis x = ((RadarChart) this).getXAxis(); + XAxis x = this.getXAxis(); if (x.isEnabled() && x.isDrawLabelsEnabled()) { minOffset = Math.max(minOffset, x.mLabelRotatedWidth); diff --git a/MPChartLib/src/com/github/mikephil/charting/components/Legend.java b/MPChartLib/src/com/github/mikephil/charting/components/Legend.java index 0d1cd4d23..77f1710f2 100644 --- a/MPChartLib/src/com/github/mikephil/charting/components/Legend.java +++ b/MPChartLib/src/com/github/mikephil/charting/components/Legend.java @@ -20,6 +20,9 @@ */ public class Legend extends ComponentBase { + /** + * This property is deprecated - Use `position`, `horizontalAlignment`, `verticalAlignment`, `orientation`, `drawInside`, `direction`. + */ public enum LegendPosition { RIGHT_OF_CHART, RIGHT_OF_CHART_CENTER, RIGHT_OF_CHART_INSIDE, LEFT_OF_CHART, LEFT_OF_CHART_CENTER, LEFT_OF_CHART_INSIDE, @@ -32,6 +35,21 @@ public enum LegendForm { SQUARE, CIRCLE, 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 } @@ -63,8 +81,10 @@ public enum LegendDirection { */ private boolean mIsLegendCustom = false; - /** the position relative to the chart the legend is drawn on */ - private LegendPosition mPosition = LegendPosition.BELOW_CHART_LEFT; + 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; @@ -351,16 +371,175 @@ public boolean isLegendCustom() { * @return */ public LegendPosition getPosition() { - return mPosition; + + if (mOrientation == LegendOrientation.VERTICAL + && mHorizontalAlignment == LegendHorizontalAlignment.CENTER + && mVerticalAlignment == LegendVerticalAlignment.CENTER) { + return LegendPosition.PIECHART_CENTER; + } + else if (mOrientation == LegendOrientation.HORIZONTAL) { + if (mVerticalAlignment == LegendVerticalAlignment.TOP) + return mHorizontalAlignment == LegendHorizontalAlignment.LEFT + ? LegendPosition.ABOVE_CHART_LEFT + : (mHorizontalAlignment == LegendHorizontalAlignment.RIGHT + ? LegendPosition.ABOVE_CHART_RIGHT + : LegendPosition.ABOVE_CHART_CENTER); + else + return mHorizontalAlignment == LegendHorizontalAlignment.LEFT + ? LegendPosition.BELOW_CHART_LEFT + : (mHorizontalAlignment == LegendHorizontalAlignment.RIGHT + ? LegendPosition.BELOW_CHART_RIGHT + : LegendPosition.BELOW_CHART_CENTER); + } + else { + if (mHorizontalAlignment == LegendHorizontalAlignment.LEFT) + return mVerticalAlignment == LegendVerticalAlignment.TOP && mDrawInside + ? LegendPosition.LEFT_OF_CHART_INSIDE + : (mVerticalAlignment == LegendVerticalAlignment.CENTER + ? LegendPosition.LEFT_OF_CHART_CENTER + : LegendPosition.LEFT_OF_CHART); + else + return mVerticalAlignment == LegendVerticalAlignment.TOP && mDrawInside + ? LegendPosition.RIGHT_OF_CHART_INSIDE + : (mVerticalAlignment == LegendVerticalAlignment.CENTER + ? LegendPosition.RIGHT_OF_CHART_CENTER + : LegendPosition.RIGHT_OF_CHART); + } } /** * sets the position of the legend relative to the whole chart * - * @param pos + * @param newValue + */ + public void setPosition(LegendPosition newValue) { + + switch (newValue) { + case LEFT_OF_CHART: + case LEFT_OF_CHART_INSIDE: + case LEFT_OF_CHART_CENTER: + mHorizontalAlignment = LegendHorizontalAlignment.LEFT; + mVerticalAlignment = newValue == LegendPosition.LEFT_OF_CHART_CENTER + ? LegendVerticalAlignment.CENTER + : LegendVerticalAlignment.TOP; + mOrientation = LegendOrientation.VERTICAL; + break; + + case RIGHT_OF_CHART: + case RIGHT_OF_CHART_INSIDE: + case RIGHT_OF_CHART_CENTER: + mHorizontalAlignment = LegendHorizontalAlignment.RIGHT; + mVerticalAlignment = newValue == LegendPosition.RIGHT_OF_CHART_CENTER + ? LegendVerticalAlignment.CENTER + : LegendVerticalAlignment.TOP; + mOrientation = LegendOrientation.VERTICAL; + break; + + case ABOVE_CHART_LEFT: + case ABOVE_CHART_CENTER: + case ABOVE_CHART_RIGHT: + mHorizontalAlignment = newValue == LegendPosition.ABOVE_CHART_LEFT + ? LegendHorizontalAlignment.LEFT + : (newValue == LegendPosition.ABOVE_CHART_RIGHT + ? LegendHorizontalAlignment.RIGHT + : LegendHorizontalAlignment.CENTER); + mVerticalAlignment = LegendVerticalAlignment.TOP; + mOrientation = LegendOrientation.HORIZONTAL; + break; + + case BELOW_CHART_LEFT: + case BELOW_CHART_CENTER: + case BELOW_CHART_RIGHT: + mHorizontalAlignment = newValue == LegendPosition.BELOW_CHART_LEFT + ? LegendHorizontalAlignment.LEFT + : (newValue == LegendPosition.BELOW_CHART_RIGHT + ? LegendHorizontalAlignment.RIGHT + : LegendHorizontalAlignment.CENTER); + mVerticalAlignment = LegendVerticalAlignment.BOTTOM; + mOrientation = LegendOrientation.HORIZONTAL; + break; + + case PIECHART_CENTER: + mHorizontalAlignment = LegendHorizontalAlignment.CENTER; + mVerticalAlignment = LegendVerticalAlignment.CENTER; + mOrientation = LegendOrientation.VERTICAL; + break; + } + + mDrawInside = newValue == LegendPosition.LEFT_OF_CHART_INSIDE + || newValue == LegendPosition.RIGHT_OF_CHART_INSIDE; + } + + /** + * 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 void setPosition(LegendPosition pos) { - mPosition = pos; + 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; } /** @@ -605,9 +784,7 @@ public float getMaxSizePercent() { * 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%) + * this affects the height of the legend. / default: 0.95f (95%) * * @param maxSize */ @@ -640,132 +817,168 @@ public FSize[] getCalculatedLineSizes() { */ public void calculateDimensions(Paint labelpaint, ViewPortHandler viewPortHandler) { - if (mPosition == LegendPosition.RIGHT_OF_CHART - || mPosition == LegendPosition.RIGHT_OF_CHART_CENTER - || mPosition == LegendPosition.LEFT_OF_CHART - || mPosition == LegendPosition.LEFT_OF_CHART_CENTER - || mPosition == LegendPosition.PIECHART_CENTER) { - mNeededWidth = getMaximumEntryWidth(labelpaint); - mNeededHeight = getFullHeight(labelpaint); - mTextWidthMax = mNeededWidth; - mTextHeightMax = getMaximumEntryHeight(labelpaint); - - } else if (mPosition == LegendPosition.BELOW_CHART_LEFT - || mPosition == LegendPosition.BELOW_CHART_RIGHT - || mPosition == LegendPosition.BELOW_CHART_CENTER - || mPosition == LegendPosition.ABOVE_CHART_LEFT - || mPosition == LegendPosition.ABOVE_CHART_RIGHT - || mPosition == LegendPosition.ABOVE_CHART_CENTER) { - - int labelCount = mLabels.length; - float labelLineHeight = Utils.getLineHeight(labelpaint); - float labelLineSpacing = Utils.getLineSpacing(labelpaint) + mYEntrySpace; - float contentWidth = viewPortHandler.contentWidth(); - - // Prepare arrays for calculated layout - ArrayList calculatedLabelSizes = new ArrayList(labelCount); - ArrayList calculatedLabelBreakPoints = new ArrayList(labelCount); - ArrayList calculatedLineSizes = new ArrayList(); - - // Start calculating layout - float maxLineWidth = 0.f; - float currentLineWidth = 0.f; - float requiredWidth = 0.f; - int stackedStartIndex = -1; - - for (int i = 0; i < labelCount; i++) { - - boolean drawingForm = mColors[i] != ColorTemplate.COLOR_SKIP; - - calculatedLabelBreakPoints.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 += mStackSpace; - } + mTextWidthMax = getMaximumEntryWidth(labelpaint); + mTextHeightMax = getMaximumEntryHeight(labelpaint); - // grouped forms have null labels - if (mLabels[i] != null) { + switch (mOrientation) { + case VERTICAL: { - calculatedLabelSizes.add(Utils.calcTextSize(labelpaint, mLabels[i])); - requiredWidth += drawingForm ? mFormToTextSpace + mFormSize : 0.f; - requiredWidth += calculatedLabelSizes.get(i).width; - } else { + float maxWidth = 0f, maxHeight = 0f, width = 0f; + float labelLineHeight = Utils.getLineHeight(labelpaint); + final int count = mLabels.length; + boolean wasStacked = false; - calculatedLabelSizes.add(new FSize(0.f, 0.f)); - requiredWidth += drawingForm ? mFormSize : 0.f; + for (int i = 0; i < count; i++) { - if (stackedStartIndex == -1) { - // mark this index as we might want to break here later - stackedStartIndex = i; + boolean drawingForm = mColors[i] != ColorTemplate.COLOR_SKIP; + + if (!wasStacked) + width = 0.f; + + if (drawingForm) { + if (wasStacked) + width += mStackSpace; + width += mFormSize; } + + // grouped forms have null labels + if (mLabels[i] != null) { + + // make a step to the left + if (drawingForm && !wasStacked) + width += mFormToTextSpace; + else if (wasStacked) { + maxWidth = Math.max(maxWidth, width); + maxHeight += labelLineHeight + mYEntrySpace; + width = 0.f; + wasStacked = false; + } + + width += Utils.calcTextWidth(labelpaint, mLabels[i]); + + if (i < count - 1) + maxHeight += labelLineHeight + mYEntrySpace; + } + else { + wasStacked = true; + width += mFormSize; + if (i < count - 1) + width += mStackSpace; + } + + maxWidth = Math.max(maxWidth, width); } - if (mLabels[i] != null || i == labelCount - 1) { + mNeededWidth = maxWidth; + mNeededHeight = maxHeight; + + break; + } + case HORIZONTAL: { + + int labelCount = mLabels.length; + float labelLineHeight = Utils.getLineHeight(labelpaint); + float labelLineSpacing = Utils.getLineSpacing(labelpaint) + mYEntrySpace; + float contentWidth = viewPortHandler.contentWidth() * mMaxSizePercent; + + // Prepare arrays for calculated layout + ArrayList calculatedLabelSizes = new ArrayList(labelCount); + ArrayList calculatedLabelBreakPoints = new ArrayList(labelCount); + ArrayList calculatedLineSizes = new ArrayList(); - float requiredSpacing = currentLineWidth == 0.f ? 0.f : mXEntrySpace; + // Start calculating layout + float maxLineWidth = 0.f; + float currentLineWidth = 0.f; + float requiredWidth = 0.f; + int stackedStartIndex = -1; - if (!mWordWrapEnabled || // No word wrapping, it must fit. - currentLineWidth == 0.f || // The line is empty, it - // must fit. - (contentWidth - currentLineWidth >= requiredSpacing + requiredWidth)) // It - // simply - // fits - { - // Expand current line - currentLineWidth += requiredSpacing + requiredWidth; + for (int i = 0; i < labelCount; i++) { - } else { // It doesn't fit, we need to wrap a line + boolean drawingForm = mColors[i] != ColorTemplate.COLOR_SKIP; - // Add current line size to array - calculatedLineSizes.add(new FSize(currentLineWidth, labelLineHeight)); - maxLineWidth = Math.max(maxLineWidth, currentLineWidth); + calculatedLabelBreakPoints.add(false); - // Start a new line - calculatedLabelBreakPoints.set(stackedStartIndex > -1 ? stackedStartIndex - : i, true); - currentLineWidth = requiredWidth; + 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 += mStackSpace; + } + + // grouped forms have null labels + if (mLabels[i] != null) { + + calculatedLabelSizes.add(Utils.calcTextSize(labelpaint, mLabels[i])); + requiredWidth += drawingForm ? mFormToTextSpace + mFormSize : 0.f; + requiredWidth += calculatedLabelSizes.get(i).width; } + else { - if (i == labelCount - 1) { - // Add last line size to array - calculatedLineSizes.add(new FSize(currentLineWidth, labelLineHeight)); - maxLineWidth = Math.max(maxLineWidth, currentLineWidth); + calculatedLabelSizes.add(new FSize(0.f, 0.f)); + requiredWidth += drawingForm ? mFormSize : 0.f; + + if (stackedStartIndex == -1) { + // mark this index as we might want to break here later + stackedStartIndex = i; + } + } + + if (mLabels[i] != null || i == labelCount - 1) { + + float requiredSpacing = currentLineWidth == 0.f ? 0.f : mXEntrySpace; + + if (!mWordWrapEnabled // 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 + calculatedLineSizes.add(new FSize(currentLineWidth, labelLineHeight)); + maxLineWidth = Math.max(maxLineWidth, currentLineWidth); + + // Start a new line + calculatedLabelBreakPoints.set( + stackedStartIndex > -1 ? stackedStartIndex + : i, true); + currentLineWidth = requiredWidth; + } + + if (i == labelCount - 1) { + // Add last line size to array + calculatedLineSizes.add(new FSize(currentLineWidth, labelLineHeight)); + maxLineWidth = Math.max(maxLineWidth, currentLineWidth); + } } + + stackedStartIndex = mLabels[i] != null ? -1 : stackedStartIndex; } - stackedStartIndex = mLabels[i] != null ? -1 : stackedStartIndex; + mCalculatedLabelSizes = calculatedLabelSizes.toArray( + new FSize[calculatedLabelSizes.size()]); + mCalculatedLabelBreakPoints = calculatedLabelBreakPoints + .toArray(new Boolean[calculatedLabelBreakPoints.size()]); + mCalculatedLineSizes = calculatedLineSizes + .toArray(new FSize[calculatedLineSizes.size()]); + + mNeededWidth = maxLineWidth; + mNeededHeight = labelLineHeight + * (float) (mCalculatedLineSizes.length) + + labelLineSpacing * + (float) (mCalculatedLineSizes.length == 0 + ? 0 + : (mCalculatedLineSizes.length - 1)); + + break; } - - mCalculatedLabelSizes = calculatedLabelSizes.toArray( - new FSize[calculatedLabelSizes.size()]); - mCalculatedLabelBreakPoints = calculatedLabelBreakPoints - .toArray(new Boolean[calculatedLabelBreakPoints.size()]); - mCalculatedLineSizes = calculatedLineSizes - .toArray(new FSize[calculatedLineSizes.size()]); - - mTextWidthMax = getMaximumEntryWidth(labelpaint); - mTextHeightMax = getMaximumEntryHeight(labelpaint); - mNeededWidth = maxLineWidth; - mNeededHeight = labelLineHeight - * (float) (mCalculatedLineSizes.length) - + labelLineSpacing * - (float) (mCalculatedLineSizes.length == 0 - ? 0 - : (mCalculatedLineSizes.length - 1)); - - } else { - /* RIGHT_OF_CHART_INSIDE, LEFT_OF_CHART_INSIDE */ - - mNeededWidth = getFullWidth(labelpaint); - mNeededHeight = getMaximumEntryHeight(labelpaint); - mTextWidthMax = getMaximumEntryWidth(labelpaint); - mTextHeightMax = mNeededHeight; } } } diff --git a/MPChartLib/src/com/github/mikephil/charting/renderer/LegendRenderer.java b/MPChartLib/src/com/github/mikephil/charting/renderer/LegendRenderer.java index 9a24030dc..f005a1f2a 100644 --- a/MPChartLib/src/com/github/mikephil/charting/renderer/LegendRenderer.java +++ b/MPChartLib/src/com/github/mikephil/charting/renderer/LegendRenderer.java @@ -195,57 +195,89 @@ public void renderLegend(Canvas c) { float formToTextSpace = mLegend.getFormToTextSpace(); float xEntrySpace = mLegend.getXEntrySpace(); + Legend.LegendOrientation orientation = mLegend.getOrientation(); + Legend.LegendHorizontalAlignment horizontalAlignment = mLegend.getHorizontalAlignment(); + Legend.LegendVerticalAlignment verticalAlignment = mLegend.getVerticalAlignment(); Legend.LegendDirection direction = mLegend.getDirection(); float formSize = mLegend.getFormSize(); // space between the entries float stackSpace = mLegend.getStackSpace(); - float posX, posY; - float yoffset = mLegend.getYOffset(); float xoffset = mLegend.getXOffset(); + float originPosX = 0.f; - Legend.LegendPosition legendPosition = mLegend.getPosition(); + switch (horizontalAlignment) { + case LEFT: - switch (legendPosition) { - case BELOW_CHART_LEFT: - case BELOW_CHART_RIGHT: - case BELOW_CHART_CENTER: - case ABOVE_CHART_LEFT: - case ABOVE_CHART_RIGHT: - case ABOVE_CHART_CENTER: { - float contentWidth = mViewPortHandler.contentWidth(); + if (orientation == Legend.LegendOrientation.VERTICAL) + originPosX = xoffset; + else + originPosX = mViewPortHandler.contentLeft() + xoffset; - float originPosX; + if (direction == Legend.LegendDirection.RIGHT_TO_LEFT) + originPosX += mLegend.mNeededWidth; - if (legendPosition == Legend.LegendPosition.BELOW_CHART_LEFT - || legendPosition == Legend.LegendPosition.ABOVE_CHART_LEFT) { - originPosX = mViewPortHandler.contentLeft() + xoffset; + break; + + case RIGHT: - if (direction == Legend.LegendDirection.RIGHT_TO_LEFT) - originPosX += mLegend.mNeededWidth; - } else if (legendPosition == Legend.LegendPosition.BELOW_CHART_RIGHT - || legendPosition == Legend.LegendPosition.ABOVE_CHART_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; - } else // BELOW_CHART_CENTER || ABOVE_CHART_CENTER - originPosX = mViewPortHandler.contentLeft() + contentWidth / 2.f + 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: { FSize[] calculatedLineSizes = mLegend.getCalculatedLineSizes(); FSize[] calculatedLabelSizes = mLegend.getCalculatedLabelSizes(); Boolean[] calculatedLabelBreakPoints = mLegend.getCalculatedLabelBreakPoints(); - posX = originPosX; + float posX = originPosX; + float posY = 0.f; + + switch (verticalAlignment) { + case TOP: + posY = yoffset; + break; - if (legendPosition == Legend.LegendPosition.ABOVE_CHART_LEFT || - legendPosition == Legend.LegendPosition.ABOVE_CHART_RIGHT || - legendPosition == Legend.LegendPosition.ABOVE_CHART_CENTER) { - posY = yoffset; - } else { - posY = mViewPortHandler.getChartHeight() - yoffset - mLegend.mNeededHeight; + case BOTTOM: + posY = mViewPortHandler.getChartHeight() - yoffset - mLegend.mNeededHeight; + break; + + case CENTER: + posY = (mViewPortHandler.getChartHeight() - mLegend.mNeededHeight) / 2.f + yoffset; + break; } int lineIndex = 0; @@ -257,10 +289,11 @@ public void renderLegend(Canvas c) { } if (posX == originPosX && - (legendPosition == Legend.LegendPosition.BELOW_CHART_CENTER || - legendPosition == Legend.LegendPosition.ABOVE_CHART_CENTER) && + horizontalAlignment == Legend.LegendHorizontalAlignment.CENTER && lineIndex < calculatedLineSizes.length) { - posX += (direction == Legend.LegendDirection.RIGHT_TO_LEFT ? calculatedLineSizes[lineIndex].width : -calculatedLineSizes[lineIndex].width) / 2.f; + posX += (direction == Legend.LegendDirection.RIGHT_TO_LEFT + ? calculatedLineSizes[lineIndex].width + : -calculatedLineSizes[lineIndex].width) / 2.f; lineIndex++; } @@ -294,91 +327,70 @@ public void renderLegend(Canvas c) { posX += direction == Legend.LegendDirection.RIGHT_TO_LEFT ? -stackSpace : stackSpace; } + break; } - break; - case PIECHART_CENTER: - case RIGHT_OF_CHART: - case RIGHT_OF_CHART_CENTER: - case RIGHT_OF_CHART_INSIDE: - case LEFT_OF_CHART: - case LEFT_OF_CHART_CENTER: - case LEFT_OF_CHART_INSIDE: { + case VERTICAL: { // contains the stacked legend size in pixels float stack = 0f; boolean wasStacked = false; - - if (legendPosition == Legend.LegendPosition.PIECHART_CENTER) { - posX = mViewPortHandler.getChartWidth() / 2f - + (direction == Legend.LegendDirection.LEFT_TO_RIGHT ? -mLegend.mTextWidthMax / 2f - : mLegend.mTextWidthMax / 2f); - posY = mViewPortHandler.getChartHeight() / 2f - mLegend.mNeededHeight / 2f - + mLegend.getYOffset(); - } else { - boolean isRightAligned = legendPosition == Legend.LegendPosition.RIGHT_OF_CHART - || - legendPosition == Legend.LegendPosition.RIGHT_OF_CHART_CENTER || - legendPosition == Legend.LegendPosition.RIGHT_OF_CHART_INSIDE; - - if (isRightAligned) { - posX = mViewPortHandler.getChartWidth() - xoffset; - if (direction == Legend.LegendDirection.LEFT_TO_RIGHT) - posX -= mLegend.mTextWidthMax; - } else { - posX = xoffset; - if (direction == Legend.LegendDirection.RIGHT_TO_LEFT) - posX += mLegend.mTextWidthMax; - } - - if (legendPosition == Legend.LegendPosition.RIGHT_OF_CHART || - legendPosition == Legend.LegendPosition.LEFT_OF_CHART) { - posY = mViewPortHandler.contentTop() + yoffset; - } else if (legendPosition == Legend.LegendPosition.RIGHT_OF_CHART_CENTER || - legendPosition == Legend.LegendPosition.LEFT_OF_CHART_CENTER) { - posY = mViewPortHandler.getChartHeight() / 2f - mLegend.mNeededHeight / 2f; - } else /* - * if (legendPosition == - * Legend.LegendPosition.RIGHT_OF_CHART_INSIDE || - * legendPosition == - * Legend.LegendPosition.LEFT_OF_CHART_INSIDE) - */ { - posY = mViewPortHandler.contentTop() + yoffset; - } + 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 < labels.length; i++) { Boolean drawingForm = colors[i] != ColorTemplate.COLOR_SKIP; - float x = posX; + float posX = originPosX; if (drawingForm) { if (direction == Legend.LegendDirection.LEFT_TO_RIGHT) - x += stack; + posX += stack; else - x -= formSize - stack; + posX -= formSize - stack; - drawForm(c, x, posY + formYOffset, i, mLegend); + drawForm(c, posX, posY + formYOffset, i, mLegend); if (direction == Legend.LegendDirection.LEFT_TO_RIGHT) - x += formSize; + posX += formSize; } if (labels[i] != null) { if (drawingForm && !wasStacked) - x += direction == Legend.LegendDirection.LEFT_TO_RIGHT ? formToTextSpace + posX += direction == Legend.LegendDirection.LEFT_TO_RIGHT ? formToTextSpace : -formToTextSpace; else if (wasStacked) - x = posX; + posX = originPosX; if (direction == Legend.LegendDirection.RIGHT_TO_LEFT) - x -= Utils.calcTextWidth(mLegendLabelPaint, labels[i]); + posX -= Utils.calcTextWidth(mLegendLabelPaint, labels[i]); if (!wasStacked) { - drawLabel(c, x, posY + labelLineHeight, labels[i]); + drawLabel(c, posX, posY + labelLineHeight, labels[i]); } else { posY += labelLineHeight + labelLineSpacing; - drawLabel(c, x, posY + labelLineHeight, labels[i]); + drawLabel(c, posX, posY + labelLineHeight, labels[i]); } // make a step down @@ -389,8 +401,10 @@ else if (wasStacked) wasStacked = true; } } + + break; + } - break; } }