package com.github.mikephil.charting.charts; import android.content.Context; import android.graphics.Matrix; import android.graphics.PointF; import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; import com.github.mikephil.charting.components.Legend.LegendPosition; import com.github.mikephil.charting.components.XAxis.XAxisPosition; import com.github.mikephil.charting.components.YAxis.AxisDependency; import com.github.mikephil.charting.data.BarDataSet; import com.github.mikephil.charting.data.BarEntry; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.highlight.Highlight; import com.github.mikephil.charting.highlight.HorizontalBarHighlighter; import com.github.mikephil.charting.renderer.HorizontalBarChartRenderer; import com.github.mikephil.charting.renderer.XAxisRendererHorizontalBarChart; import com.github.mikephil.charting.renderer.YAxisRendererHorizontalBarChart; import com.github.mikephil.charting.utils.TransformerHorizontalBarChart; import com.github.mikephil.charting.utils.Utils; /** * BarChart with horizontal bar orientation. In this implementation, x- and y-axis are switched, meaning the YAxis class * represents the horizontal values and the XAxis class represents the vertical values. * * @author Philipp Jahoda */ public class HorizontalBarChart extends BarChart { public HorizontalBarChart(Context context) { super(context); } public HorizontalBarChart(Context context, AttributeSet attrs) { super(context, attrs); } public HorizontalBarChart(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void init() { super.init(); mLeftAxisTransformer = new TransformerHorizontalBarChart(mViewPortHandler); mRightAxisTransformer = new TransformerHorizontalBarChart(mViewPortHandler); mRenderer = new HorizontalBarChartRenderer(this, mAnimator, mViewPortHandler); mHighlighter = new HorizontalBarHighlighter(this); mAxisRendererLeft = new YAxisRendererHorizontalBarChart(mViewPortHandler, mAxisLeft, mLeftAxisTransformer); mAxisRendererRight = new YAxisRendererHorizontalBarChart(mViewPortHandler, mAxisRight, mRightAxisTransformer); mXAxisRenderer = new XAxisRendererHorizontalBarChart(mViewPortHandler, mXAxis, mLeftAxisTransformer, this); } @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) { // 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; 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()); } } // offsets for y-labels if (mAxisLeft.needsOffset()) { offsetTop += mAxisLeft.getRequiredHeightSpace(mAxisRendererLeft.getPaintAxisLabels()); } if (mAxisRight.needsOffset()) { offsetBottom += mAxisRight.getRequiredHeightSpace(mAxisRendererRight.getPaintAxisLabels()); } float xlabelwidth = mXAxis.mLabelWidth; if (mXAxis.isEnabled()) { // offsets for x-labels if (mXAxis.getPosition() == XAxisPosition.BOTTOM) { offsetLeft += xlabelwidth; } else if (mXAxis.getPosition() == XAxisPosition.TOP) { offsetRight += xlabelwidth; } else if (mXAxis.getPosition() == XAxisPosition.BOTH_SIDED) { offsetLeft += xlabelwidth; offsetRight += xlabelwidth; } } offsetTop += getExtraTopOffset(); offsetRight += getExtraRightOffset(); offsetBottom += getExtraBottomOffset(); offsetLeft += getExtraLeftOffset(); float minOffset = Utils.convertDpToPixel(mMinOffset); mViewPortHandler.restrainViewPort( Math.max(minOffset, offsetLeft), Math.max(minOffset, offsetTop), Math.max(minOffset, offsetRight), Math.max(minOffset, offsetBottom)); if (mLogEnabled) { Log.i(LOG_TAG, "offsetLeft: " + offsetLeft + ", offsetTop: " + offsetTop + ", offsetRight: " + offsetRight + ", offsetBottom: " + offsetBottom); Log.i(LOG_TAG, "Content: " + mViewPortHandler.getContentRect().toString()); } prepareOffsetMatrix(); prepareValuePxMatrix(); } @Override protected void prepareValuePxMatrix() { mRightAxisTransformer.prepareMatrixValuePx(mAxisRight.mAxisMinimum, mAxisRight.mAxisRange, mDeltaX, mXChartMin); mLeftAxisTransformer.prepareMatrixValuePx(mAxisLeft.mAxisMinimum, mAxisLeft.mAxisRange, mDeltaX, mXChartMin); } @Override protected void calcModulus() { float[] values = new float[9]; mViewPortHandler.getMatrixTouch().getValues(values); mXAxis.mAxisLabelModulus = (int) Math.ceil((mData.getXValCount() * mXAxis.mLabelHeight) / (mViewPortHandler.contentHeight() * values[Matrix.MSCALE_Y])); if (mXAxis.mAxisLabelModulus < 1) mXAxis.mAxisLabelModulus = 1; } @Override public RectF getBarBounds(BarEntry e) { BarDataSet set = mData.getDataSetForEntry(e); if (set == null) return null; float barspace = set.getBarSpace(); float y = e.getVal(); float x = e.getXIndex(); float spaceHalf = barspace / 2f; float top = x - 0.5f + spaceHalf; float bottom = x + 0.5f - spaceHalf; float left = y >= 0 ? y : 0; float right = y <= 0 ? y : 0; RectF bounds = new RectF(left, top, right, bottom); getTransformer(set.getAxisDependency()).rectValueToPixel(bounds); return bounds; } @Override public PointF getPosition(Entry e, AxisDependency axis) { if (e == null) return null; float[] vals = new float[] { e.getVal(), e.getXIndex() }; getTransformer(axis).pointValuesToPixel(vals); return new PointF(vals[0], vals[1]); } /** * Returns the Highlight object (contains x-index and DataSet index) of the selected value at the given touch point * inside the BarChart. * * @param x * @param y * @return */ @Override public Highlight getHighlightByTouchPoint(float x, float y) { if (mDataNotSet || mData == null) { Log.e(LOG_TAG, "Can't select by touch. No data set."); return null; } else return mHighlighter.getHighlight(y, x); // switch x and y } /** * Returns the lowest x-index (value on the x-axis) that is still visible on the chart. * * @return */ @Override public int getLowestVisibleXIndex() { float step = mData.getDataSetCount(); float div = (step <= 1) ? 1 : step + mData.getGroupSpace(); float[] pts = new float[] { mViewPortHandler.contentLeft(), mViewPortHandler.contentBottom() }; getTransformer(AxisDependency.LEFT).pixelsToValue(pts); return (int) (((pts[1] <= 0) ? 0 : ((pts[1])) / div) + 1); } /** * Returns the highest x-index (value on the x-axis) that is still visible on the chart. * * @return */ @Override public int getHighestVisibleXIndex() { float step = mData.getDataSetCount(); float div = (step <= 1) ? 1 : step + mData.getGroupSpace(); float[] pts = new float[] { mViewPortHandler.contentLeft(), mViewPortHandler.contentTop() }; getTransformer(AxisDependency.LEFT).pixelsToValue(pts); return (int) ((pts[1] >= getXChartMax()) ? getXChartMax() / div : (pts[1] / div)); } }