diff --git a/MPChartExample/src/main/AndroidManifest.xml b/MPChartExample/src/main/AndroidManifest.xml index 14eeed1810..d357bdf1b7 100644 --- a/MPChartExample/src/main/AndroidManifest.xml +++ b/MPChartExample/src/main/AndroidManifest.xml @@ -25,6 +25,7 @@ + diff --git a/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/PieChartRoundedActivity.java b/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/PieChartRoundedActivity.java new file mode 100644 index 0000000000..6c3ddd216a --- /dev/null +++ b/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/PieChartRoundedActivity.java @@ -0,0 +1,334 @@ + +package com.xxmassdeveloper.mpchartexample; + +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.Bundle; +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; +import android.text.style.RelativeSizeSpan; +import android.text.style.StyleSpan; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.WindowManager; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; + +import com.github.mikephil.charting.animation.Easing; +import com.github.mikephil.charting.charts.PieChart; +import com.github.mikephil.charting.components.Legend; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.PieData; +import com.github.mikephil.charting.data.PieDataSet; +import com.github.mikephil.charting.data.PieEntry; +import com.github.mikephil.charting.formatter.PercentFormatter; +import com.github.mikephil.charting.highlight.Highlight; +import com.github.mikephil.charting.interfaces.datasets.IDataSet; +import com.github.mikephil.charting.listener.OnChartValueSelectedListener; +import com.github.mikephil.charting.renderer.PieChartRenderer; +import com.github.mikephil.charting.utils.ColorTemplate; +import com.github.mikephil.charting.utils.MPPointF; +import com.xxmassdeveloper.mpchartexample.notimportant.DemoBase; + +import java.util.ArrayList; + +import androidx.core.content.ContextCompat; + +public class PieChartRoundedActivity extends DemoBase implements OnSeekBarChangeListener, + OnChartValueSelectedListener { + + private PieChart chart; + private SeekBar seekBarX, seekBarY; + private TextView tvX, tvY; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + setContentView(R.layout.activity_piechart); + + setTitle("PieChartActivity"); + + tvX = findViewById(R.id.tvXMax); + tvY = findViewById(R.id.tvYMax); + + seekBarX = findViewById(R.id.seekBarX); + seekBarY = findViewById(R.id.seekBarY); + + seekBarX.setOnSeekBarChangeListener(this); + seekBarY.setOnSeekBarChangeListener(this); + + chart = findViewById(R.id.chart1); + chart.setUsePercentValues(true); + chart.getDescription().setEnabled(false); + chart.setExtraOffsets(5, 10, 5, 5); + + chart.setDragDecelerationFrictionCoef(0.95f); + + chart.setCenterTextTypeface(tfLight); + chart.setCenterText(generateCenterSpannableText()); + + chart.setDrawHoleEnabled(true); + chart.setHoleColor(Color.TRANSPARENT); + + chart.setTransparentCircleColor(Color.TRANSPARENT); + chart.setTransparentCircleAlpha(110); + + chart.setHoleRadius(50f); + + chart.setTransparentCircleRadius(0f); + + chart.setDrawCenterText(true); + + chart.setRotationAngle(0); + // enable rotation of the chart by touch + chart.setRotationEnabled(true); + chart.setHighlightPerTapEnabled(true); + + // chart.setUnit(" €"); + // chart.setDrawUnitsInChart(true); + + // add a selection listener + chart.setOnChartValueSelectedListener(this); + + seekBarX.setProgress(4); + seekBarY.setProgress(10); + + chart.animateY(1400, Easing.EaseInOutQuad); + // chart.spin(2000, 0, 360); + + Legend l = chart.getLegend(); + l.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); + l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); + l.setOrientation(Legend.LegendOrientation.VERTICAL); + l.setDrawInside(false); + l.setXEntrySpace(7f); + l.setYEntrySpace(0f); + l.setYOffset(0f); + + // entry label styling + chart.setEntryLabelColor(Color.WHITE); + chart.setEntryLabelTypeface(tfRegular); + chart.setEntryLabelTextSize(12f); + } + + private void setData(int count, float range) { + ArrayList entries = new ArrayList<>(); + Double[] sampleValues = DataTools.Companion.getValues(100); + + // NOTE: The order of the entries when being added to the entries array determines their position around the center of + // the chart. + for (int i = 0; i < count ; i++) { + entries.add(new PieEntry((float) ((sampleValues[i].floatValue() * range) + range / 5), + parties[i % parties.length], + getResources().getDrawable(R.drawable.star))); + } + + PieDataSet dataSet = new PieDataSet(entries, "Election Results"); + + dataSet.setDrawIcons(false); + + dataSet.setSliceSpace(3f); + dataSet.setIconsOffset(new MPPointF(0, 40)); + dataSet.setSelectionShift(5f); + + // add a lot of colors + + ArrayList colors = new ArrayList<>(); + + for (int c : ColorTemplate.VORDIPLOM_COLORS) + colors.add(c); + + for (int c : ColorTemplate.JOYFUL_COLORS) + colors.add(c); + + for (int c : ColorTemplate.COLORFUL_COLORS) + colors.add(c); + + for (int c : ColorTemplate.LIBERTY_COLORS) + colors.add(c); + + for (int c : ColorTemplate.PASTEL_COLORS) + colors.add(c); + + colors.add(ColorTemplate.getHoloBlue()); + + dataSet.setColors(colors); + //dataSet.setSelectionShift(0f); + + PieData data = new PieData(dataSet); + data.setValueFormatter(new PercentFormatter()); + data.setValueTextSize(11f); + data.setValueTextColor(Color.WHITE); + data.setValueTypeface(tfLight); + chart.setData(data); + + // undo all highlights + chart.highlightValues(null); + + PieChartRenderer renderer =(PieChartRenderer) chart.getRenderer(); + renderer.setRoundedCornerRadius(30f); + dataSet.setSliceSpace(renderer.getRoundedCornerRadius()/2); + + chart.invalidate(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.pie, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + + switch (item.getItemId()) { + case R.id.viewGithub: { + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/PieChartActivity.java")); + startActivity(i); + break; + } + case R.id.actionToggleValues: { + for (IDataSet set : chart.getData().getDataSets()) + set.setDrawValues(!set.isDrawValuesEnabled()); + + chart.invalidate(); + break; + } + case R.id.actionToggleIcons: { + for (IDataSet set : chart.getData().getDataSets()) + set.setDrawIcons(!set.isDrawIconsEnabled()); + + chart.invalidate(); + break; + } + case R.id.actionToggleHole: { + if (chart.isDrawHoleEnabled()) + chart.setDrawHoleEnabled(false); + else + chart.setDrawHoleEnabled(true); + chart.invalidate(); + break; + } + case R.id.actionToggleMinAngles: { + if (chart.getMinAngleForSlices() == 0f) + chart.setMinAngleForSlices(36f); + else + chart.setMinAngleForSlices(0f); + chart.notifyDataSetChanged(); + chart.invalidate(); + break; + } + case R.id.actionToggleCurvedSlices: { + boolean toSet = !chart.isDrawRoundedSlicesEnabled() || !chart.isDrawHoleEnabled(); + chart.setDrawRoundedSlices(toSet); + if (toSet && !chart.isDrawHoleEnabled()) { + chart.setDrawHoleEnabled(true); + } + if (toSet && chart.isDrawSlicesUnderHoleEnabled()) { + chart.setDrawSlicesUnderHole(false); + } + chart.invalidate(); + break; + } + case R.id.actionDrawCenter: { + if (chart.isDrawCenterTextEnabled()) + chart.setDrawCenterText(false); + else + chart.setDrawCenterText(true); + chart.invalidate(); + break; + } + case R.id.actionToggleXValues: { + + chart.setDrawEntryLabels(!chart.isDrawEntryLabelsEnabled()); + chart.invalidate(); + break; + } + case R.id.actionTogglePercent: + chart.setUsePercentValues(!chart.isUsePercentValuesEnabled()); + chart.invalidate(); + break; + case R.id.animateX: { + chart.animateX(1400); + break; + } + case R.id.animateY: { + chart.animateY(1400); + break; + } + case R.id.animateXY: { + chart.animateXY(1400, 1400); + break; + } + case R.id.actionToggleSpin: { + chart.spin(1000, chart.getRotationAngle(), chart.getRotationAngle() + 360, Easing.EaseInOutCubic); + break; + } + case R.id.actionSave: { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery(); + } else { + requestStoragePermission(chart); + } + break; + } + } + return true; + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + + tvX.setText(String.valueOf(seekBarX.getProgress())); + tvY.setText(String.valueOf(seekBarY.getProgress())); + + setData(seekBarX.getProgress(), seekBarY.getProgress()); + } + + @Override + protected void saveToGallery() { + saveToGallery(chart, "PieChartActivity"); + } + + private SpannableString generateCenterSpannableText() { + + SpannableString s = new SpannableString("MPAndroidChart\ndeveloped by Philipp Jahoda"); + s.setSpan(new RelativeSizeSpan(1.7f), 0, 14, 0); + s.setSpan(new StyleSpan(Typeface.NORMAL), 14, s.length() - 15, 0); + s.setSpan(new ForegroundColorSpan(Color.GRAY), 14, s.length() - 15, 0); + s.setSpan(new RelativeSizeSpan(.8f), 14, s.length() - 15, 0); + s.setSpan(new StyleSpan(Typeface.ITALIC), s.length() - 14, s.length(), 0); + s.setSpan(new ForegroundColorSpan(ColorTemplate.getHoloBlue()), s.length() - 14, s.length(), 0); + return s; + } + + @Override + public void onValueSelected(Entry e, Highlight h) { + + if (e == null) + return; + Log.i("VAL SELECTED", + "Value: " + e.getY() + ", index: " + h.getX() + + ", DataSet index: " + h.getDataSetIndex()); + } + + @Override + public void onNothingSelected() { + Log.i("PieChart", "nothing selected"); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) {} + + @Override + public void onStopTrackingTouch(SeekBar seekBar) {} +} diff --git a/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/notimportant/MainActivity.kt b/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/notimportant/MainActivity.kt index 602f5df198..2d9f2da5da 100644 --- a/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/notimportant/MainActivity.kt +++ b/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/notimportant/MainActivity.kt @@ -91,6 +91,7 @@ class MainActivity : AppCompatActivity(), OnItemClickListener { add(19, ContentItem("Pie Charts")) add(20, ContentItem("Basic", "Simple pie chart.", PieChartActivity::class.java)) + add(20, ContentItem("Basic", "Rounded pie chart.", PieChartRoundedActivity::class.java)) add(21, ContentItem("Value Lines", "Stylish lines drawn outward from slices.", PiePolylineChartActivity::class.java)) add(22, ContentItem("Half Pie", "180° (half) pie chart.", HalfPieChartActivity::class.java)) add(23, ContentItem("Specific positions", "This demonstrates how to pass a list of specific positions for lines and labels on x and y axis", SpecificPositionsLineChartActivity::class.java)) diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.java index b557b333dd..653eb4e296 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.java @@ -45,6 +45,11 @@ public class PieChartRenderer extends DataRenderer { protected Paint mTransparentCirclePaint; protected Paint mValueLinePaint; + /** + * paint object used for drawing the rounded corner slice + */ + protected Paint mRoundedCornerPaint; + /** * paint object for the text that can be displayed in the center of the * chart @@ -68,6 +73,20 @@ public class PieChartRenderer extends DataRenderer { protected Canvas mBitmapCanvas; + /** + * Setter for the rounded corner slice paint object + */ + public void setRoundedCornerRadius(float radius){ + mRoundedCornerPaint.setStrokeWidth(radius); + } + + /** + * Getter for the rounded corner slice paint object + */ + public float getRoundedCornerRadius(){ + return mRoundedCornerPaint.getStrokeWidth(); + } + public PieChartRenderer(PieChart chart, ChartAnimator animator, ViewPortHandler viewPortHandler) { super(animator, viewPortHandler); @@ -97,6 +116,10 @@ public PieChartRenderer(PieChart chart, ChartAnimator animator, mValueLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mValueLinePaint.setStyle(Style.STROKE); + + mRoundedCornerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mRoundedCornerPaint.setStyle(Style.STROKE); + mRoundedCornerPaint.setAntiAlias(true); } public Paint getPaintHole() { @@ -245,6 +268,11 @@ protected void drawDataSet(Canvas c, IPieDataSet dataSet) { final float sliceSpace = visibleAngleCount <= 1 ? 0.f : getSliceSpace(dataSet); + if (getRoundedCornerRadius()>0) { + mRoundedCornerPaint.setStrokeCap(Paint.Cap.ROUND); + mRoundedCornerPaint.setStrokeJoin(Paint.Join.ROUND); + } + for (int j = 0; j < entryCount; j++) { float sliceAngle = drawAngles[j]; @@ -268,6 +296,9 @@ protected void drawDataSet(Canvas c, IPieDataSet dataSet) { mRenderPaint.setColor(dataSet.getColor(j)); + // Set current data set color to paint object + mRoundedCornerPaint.setColor(dataSet.getColor(j)); + final float sliceSpaceAngleOuter = visibleAngleCount == 1 ? 0.f : sliceSpace / (Utils.FDEG2RAD * radius); @@ -398,6 +429,11 @@ protected void drawDataSet(Canvas c, IPieDataSet dataSet) { mBitmapCanvas.drawPath(mPathBuffer, mRenderPaint); + // Draw rounded corner path with paint object slice with the given radius + if (getRoundedCornerRadius()>0) { + mBitmapCanvas.drawPath(mPathBuffer, mRoundedCornerPaint); + } + angle += sliceAngle * phaseX; } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RoundedBarChartRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RoundedBarChartRenderer.java new file mode 100644 index 0000000000..e06228c6df --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RoundedBarChartRenderer.java @@ -0,0 +1,346 @@ +package com.github.mikephil.charting.renderer; + +import android.graphics.Canvas; +import android.graphics.LinearGradient; +import android.graphics.Path; +import android.graphics.RectF; +import android.graphics.Shader; + +import com.github.mikephil.charting.animation.ChartAnimator; +import com.github.mikephil.charting.buffer.BarBuffer; +import com.github.mikephil.charting.data.BarData; +import com.github.mikephil.charting.data.BarEntry; +import com.github.mikephil.charting.highlight.Highlight; +import com.github.mikephil.charting.highlight.Range; +import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider; +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; +import com.github.mikephil.charting.utils.Transformer; +import com.github.mikephil.charting.utils.Utils; +import com.github.mikephil.charting.utils.ViewPortHandler; + +/** @noinspection unused*/ +public class RoundedBarChartRenderer extends BarChartRenderer { + + public RoundedBarChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) { + super(chart, animator, viewPortHandler); + } + + private final RectF mBarShadowRectBuffer = new RectF(); + private final float mRadius = 20f; + private float roundedShadowRadius = 0f; + private float roundedPositiveDataSetRadius = 0f; + private float roundedNegativeDataSetRadius = 0f; + + public void setRoundedNegativeDataSetRadius(float roundedNegativeDataSet) { + roundedNegativeDataSetRadius = roundedNegativeDataSet; + } + + public void setRoundedShadowRadius(float roundedShadow) { + roundedShadowRadius = roundedShadow; + } + + public void setRoundedPositiveDataSetRadius(float roundedPositiveDataSet) { + roundedPositiveDataSetRadius = roundedPositiveDataSet; + } + + @Override + protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) { + initBuffers(); + Transformer trans = mChart.getTransformer(dataSet.getAxisDependency()); + mBarBorderPaint.setColor(dataSet.getBarBorderColor()); + mBarBorderPaint.setStrokeWidth(Utils.convertDpToPixel(dataSet.getBarBorderWidth())); + mShadowPaint.setColor(dataSet.getBarShadowColor()); + boolean drawBorder = dataSet.getBarBorderWidth() > 0f; + float phaseX = mAnimator.getPhaseX(); + float phaseY = mAnimator.getPhaseY(); + + if (mChart.isDrawBarShadowEnabled()) { + mShadowPaint.setColor(dataSet.getBarShadowColor()); + BarData barData = mChart.getBarData(); + float barWidth = barData.getBarWidth(); + float barWidthHalf = barWidth / 2.0f; + float x; + int i = 0; + double count = Math.min((double) (int) (double) ((float) dataSet.getEntryCount() * phaseX), dataSet.getEntryCount()); + while (i < count) { + BarEntry e = dataSet.getEntryForIndex(i); + x = e.getX(); + mBarShadowRectBuffer.left = x - barWidthHalf; + mBarShadowRectBuffer.right = x + barWidthHalf; + trans.rectValueToPixel(mBarShadowRectBuffer); + if (!mViewPortHandler.isInBoundsLeft(mBarShadowRectBuffer.right)) { + i++; + continue; + } + if (!mViewPortHandler.isInBoundsRight(mBarShadowRectBuffer.left)) { + break; + } + mBarShadowRectBuffer.top = mViewPortHandler.contentTop(); + mBarShadowRectBuffer.bottom = mViewPortHandler.contentBottom(); + + + if (roundedShadowRadius > 0) { + c.drawRoundRect(mBarRect, roundedShadowRadius, roundedShadowRadius, mShadowPaint); + } else { + c.drawRect(mBarShadowRectBuffer, mShadowPaint); + } + i++; + } + } + + BarBuffer buffer = mBarBuffers[index]; + buffer.setPhases(phaseX, phaseY); + buffer.setDataSet(index); + buffer.setInverted(mChart.isInverted(dataSet.getAxisDependency())); + buffer.setBarWidth(mChart.getBarData().getBarWidth()); + buffer.feed(dataSet); + trans.pointValuesToPixel(buffer.buffer); + + // if multiple colors has been assigned to Bar Chart + if (dataSet.getColors().size() > 1) { + + for (int j = 0; j < buffer.size(); j += 4) { + + if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) { + continue; + } + + if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) { + break; + } + + if (mChart.isDrawBarShadowEnabled()) { + if (roundedShadowRadius > 0) { + c.drawRoundRect(new RectF(buffer.buffer[j], mViewPortHandler.contentTop(), + buffer.buffer[j + 2], + mViewPortHandler.contentBottom()), roundedShadowRadius, roundedShadowRadius, mShadowPaint); + } else { + c.drawRect(buffer.buffer[j], mViewPortHandler.contentTop(), + buffer.buffer[j + 2], + mViewPortHandler.contentBottom(), mShadowPaint); + } + } + + // Set the color for the currently drawn value. If the index + mRenderPaint.setColor(dataSet.getColor(j / 4)); + + if (roundedPositiveDataSetRadius > 0) { + c.drawRoundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3]), roundedPositiveDataSetRadius, roundedPositiveDataSetRadius, mRenderPaint); + } else { + c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3], mRenderPaint); + } + } + } else { + + mRenderPaint.setColor(dataSet.getColor()); + + for (int j = 0; j < buffer.size(); j += 4) { + + if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) { + continue; + } + + if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) { + break; + } + + if (mChart.isDrawBarShadowEnabled()) { + if (roundedShadowRadius > 0) { + c.drawRoundRect(new RectF(buffer.buffer[j], mViewPortHandler.contentTop(), + buffer.buffer[j + 2], + mViewPortHandler.contentBottom()), roundedShadowRadius, roundedShadowRadius, mShadowPaint); + } else { + c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3], mRenderPaint); + } + } + + if (roundedPositiveDataSetRadius > 0) { + c.drawRoundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3]), roundedPositiveDataSetRadius, roundedPositiveDataSetRadius, mRenderPaint); + } else { + c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3], mRenderPaint); + } + } + } + + + boolean isSingleColor = dataSet.getColors().size() == 1; + if (isSingleColor) { + mRenderPaint.setColor(dataSet.getColor(index)); + } + + int j = 0; + while (j < buffer.size()) { + + if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) { + j += 4; + continue; + } + + if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) { + break; + } + + if (!isSingleColor) { + mRenderPaint.setColor(dataSet.getColor(j / 4)); + } + + mRenderPaint.setShader(new LinearGradient( + buffer.buffer[j], + buffer.buffer[j + 3], + buffer.buffer[j], + buffer.buffer[j + 1], + dataSet.getColor(j / 4), + dataSet.getColor(j / 4), + Shader.TileMode.MIRROR)); + + mRenderPaint.setShader(new LinearGradient( + buffer.buffer[j], + buffer.buffer[j + 3], + buffer.buffer[j], + buffer.buffer[j + 1], + dataSet.getColor(j / 4), + dataSet.getColor(j / 4), + Shader.TileMode.MIRROR)); + + + if ((dataSet.getEntryForIndex(j / 4).getY() < 0 && roundedNegativeDataSetRadius > 0)) { + Path path2 = roundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3]), roundedNegativeDataSetRadius, roundedNegativeDataSetRadius, true, true, true, true); + c.drawPath(path2, mRenderPaint); + } else if ((dataSet.getEntryForIndex(j / 4).getY() > 0 && roundedPositiveDataSetRadius > 0)) { + Path path2 = roundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3]), roundedPositiveDataSetRadius, roundedPositiveDataSetRadius, true, true, true, true); + c.drawPath(path2, mRenderPaint); + } else { + c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3], mRenderPaint); + } + + j += 4; + } + + } + + @Override + public void drawHighlighted(Canvas c, Highlight[] indices) { + BarData barData = mChart.getBarData(); + + for (Highlight high : indices) { + + IBarDataSet set = barData.getDataSetByIndex(high.getDataSetIndex()); + + if (set == null || !set.isHighlightEnabled()) { + continue; + } + + BarEntry e = set.getEntryForXValue(high.getX(), high.getY()); + + if (!isInBoundsX(e, set)) { + continue; + } + + Transformer trans = mChart.getTransformer(set.getAxisDependency()); + + mHighlightPaint.setColor(set.getHighLightColor()); + mHighlightPaint.setAlpha(set.getHighLightAlpha()); + + boolean isStack = high.getStackIndex() >= 0 && e.isStacked(); + + final float y1; + final float y2; + + if (isStack) { + + if (mChart.isHighlightFullBarEnabled()) { + + y1 = e.getPositiveSum(); + y2 = -e.getNegativeSum(); + + } else { + + Range range = e.getRanges()[high.getStackIndex()]; + + y1 = range.from; + y2 = range.to; + } + + } else { + y1 = e.getY(); + y2 = 0.f; + } + + prepareBarHighlight(e.getX(), y1, y2, barData.getBarWidth() / 2f, trans); + + setHighlightDrawPos(high, mBarRect); + + Path path2 = roundRect(new RectF(mBarRect.left, mBarRect.top, mBarRect.right, + mBarRect.bottom), mRadius, mRadius, true, true, true, true); + + c.drawPath(path2, mHighlightPaint); + } + } + + private Path roundRect(RectF rect, float rx, float ry, boolean tl, boolean tr, boolean br, boolean bl) { + float top = rect.top; + float left = rect.left; + float right = rect.right; + float bottom = rect.bottom; + Path path = new Path(); + if (rx < 0) { + rx = 0; + } + if (ry < 0) { + ry = 0; + } + float width = right - left; + float height = bottom - top; + if (rx > width / 2) { + rx = width / 2; + } + if (ry > height / 2) { + ry = height / 2; + } + float widthMinusCorners = (width - (2 * rx)); + float heightMinusCorners = (height - (2 * ry)); + + path.moveTo(right, top + ry); + if (tr) { + path.rQuadTo(0, -ry, -rx, -ry);//top-right corner + } else { + path.rLineTo(0, -ry); + path.rLineTo(-rx, 0); + } + path.rLineTo(-widthMinusCorners, 0); + if (tl) { + path.rQuadTo(-rx, 0, -rx, ry); //top-left corner + } else { + path.rLineTo(-rx, 0); + path.rLineTo(0, ry); + } + path.rLineTo(0, heightMinusCorners); + + if (bl) { + path.rQuadTo(0, ry, rx, ry);//bottom-left corner + } else { + path.rLineTo(0, ry); + path.rLineTo(rx, 0); + } + + path.rLineTo(widthMinusCorners, 0); + if (br) + path.rQuadTo(rx, 0, rx, -ry); //bottom-right corner + else { + path.rLineTo(rx, 0); + path.rLineTo(0, -ry); + } + + path.rLineTo(0, -heightMinusCorners); + path.close(); + return path; + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RoundedHorizontalBarChartRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RoundedHorizontalBarChartRenderer.java new file mode 100644 index 0000000000..7921480cb9 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RoundedHorizontalBarChartRenderer.java @@ -0,0 +1,264 @@ +package com.github.mikephil.charting.renderer; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; + +import com.github.mikephil.charting.animation.ChartAnimator; +import com.github.mikephil.charting.buffer.BarBuffer; +import com.github.mikephil.charting.data.BarData; +import com.github.mikephil.charting.data.BarEntry; +import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider; +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; +import com.github.mikephil.charting.utils.Transformer; +import com.github.mikephil.charting.utils.Utils; +import com.github.mikephil.charting.utils.ViewPortHandler; + +/** @noinspection unused*/ +public class RoundedHorizontalBarChartRenderer extends HorizontalBarChartRenderer { + + private final RectF mBarShadowRectBuffer = new RectF(); + private float roundedShadowRadius = 0f; + private float roundedPositiveDataSetRadius = 0f; + private float roundedNegativeDataSetRadius = 0f; + + public void setRoundedNegativeDataSetRadius(float roundedNegativeDataSet) { + roundedNegativeDataSetRadius = roundedNegativeDataSet; + } + + public void setRoundedShadowRadius(float roundedShadow) { + roundedShadowRadius = roundedShadow; + } + + public void setRoundedPositiveDataSetRadius(float roundedPositiveDataSet) { + roundedPositiveDataSetRadius = roundedPositiveDataSet; + } + + public RoundedHorizontalBarChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) { + super(chart, animator, viewPortHandler); + + mValuePaint.setTextAlign(Paint.Align.LEFT); + } + + @Override + protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) { + initBuffers(); + Transformer trans = mChart.getTransformer(dataSet.getAxisDependency()); + mBarBorderPaint.setColor(dataSet.getBarBorderColor()); + mBarBorderPaint.setStrokeWidth(Utils.convertDpToPixel(dataSet.getBarBorderWidth())); + mShadowPaint.setColor(dataSet.getBarShadowColor()); + boolean drawBorder = dataSet.getBarBorderWidth() > 0f; + float phaseX = mAnimator.getPhaseX(); + float phaseY = mAnimator.getPhaseY(); + + if (mChart.isDrawBarShadowEnabled()) { + mShadowPaint.setColor(dataSet.getBarShadowColor()); + BarData barData = mChart.getBarData(); + float barWidth = barData.getBarWidth(); + float barWidthHalf = barWidth / 2.0f; + float x; + int i = 0; + double count = Math.min((double) (int) (double) ((float) dataSet.getEntryCount() * phaseX), dataSet.getEntryCount()); + while (i < count) { + BarEntry e = dataSet.getEntryForIndex(i); + x = e.getX(); + mBarShadowRectBuffer.top = x - barWidthHalf; + mBarShadowRectBuffer.bottom = x + barWidthHalf; + trans.rectValueToPixel(mBarShadowRectBuffer); + if (!mViewPortHandler.isInBoundsTop(mBarShadowRectBuffer.bottom)) { + i++; + continue; + } + if (!mViewPortHandler.isInBoundsBottom(mBarShadowRectBuffer.top)) { + break; + } + mBarShadowRectBuffer.left = mViewPortHandler.contentLeft(); + mBarShadowRectBuffer.right = mViewPortHandler.contentRight(); + + + if (roundedShadowRadius > 0) { + c.drawRoundRect(mBarRect, roundedShadowRadius, roundedShadowRadius, mShadowPaint); + } else { + c.drawRect(mBarShadowRectBuffer, mShadowPaint); + } + i++; + } + } + + BarBuffer buffer = mBarBuffers[index]; + buffer.setPhases(phaseX, phaseY); + buffer.setDataSet(index); + buffer.setInverted(mChart.isInverted(dataSet.getAxisDependency())); + buffer.setBarWidth(mChart.getBarData().getBarWidth()); + buffer.feed(dataSet); + trans.pointValuesToPixel(buffer.buffer); + + // if multiple colors has been assigned to Bar Chart + if (dataSet.getColors().size() > 1) { + + for (int j = 0; j < buffer.size(); j += 4) { + + if (!mViewPortHandler.isInBoundsTop(buffer.buffer[j + 3])) { + continue; + } + + if (!mViewPortHandler.isInBoundsBottom(buffer.buffer[j + 1])) { + break; + } + + if (mChart.isDrawBarShadowEnabled()) { + if (roundedShadowRadius > 0) { + c.drawRoundRect(new RectF(buffer.buffer[j], mViewPortHandler.contentTop(), + buffer.buffer[j + 2], + mViewPortHandler.contentBottom()), roundedShadowRadius, roundedShadowRadius, mShadowPaint); + } else { + c.drawRect(buffer.buffer[j], mViewPortHandler.contentTop(), + buffer.buffer[j + 2], + mViewPortHandler.contentBottom(), mShadowPaint); + } + } + + // Set the color for the currently drawn value. If the index + mRenderPaint.setColor(dataSet.getColor(j / 4)); + + if (roundedPositiveDataSetRadius > 0) { + c.drawRoundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3]), roundedPositiveDataSetRadius, roundedPositiveDataSetRadius, mRenderPaint); + } else { + c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3], mRenderPaint); + } + } + } else { + + mRenderPaint.setColor(dataSet.getColor()); + + for (int j = 0; j < buffer.size(); j += 4) { + + if (!mViewPortHandler.isInBoundsTop(buffer.buffer[j + 3])) { + continue; + } + + if (!mViewPortHandler.isInBoundsBottom(buffer.buffer[j + 1])) { + break; + } + + if (mChart.isDrawBarShadowEnabled()) { + if (roundedShadowRadius > 0) { + c.drawRoundRect(new RectF(buffer.buffer[j], mViewPortHandler.contentTop(), + buffer.buffer[j + 2], + mViewPortHandler.contentBottom()), roundedShadowRadius, roundedShadowRadius, mShadowPaint); + } else { + c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3], mRenderPaint); + } + } + + if (roundedPositiveDataSetRadius > 0) { + c.drawRoundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3]), roundedPositiveDataSetRadius, roundedPositiveDataSetRadius, mRenderPaint); + } else { + c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3], mRenderPaint); + } + } + } + + + boolean isSingleColor = dataSet.getColors().size() == 1; + if (isSingleColor) { + mRenderPaint.setColor(dataSet.getColor(index)); + } + + int j = 0; + while (j < buffer.size()) { + + if (!mViewPortHandler.isInBoundsTop(buffer.buffer[j + 3])) { + j += 4; + continue; + } + + if (!mViewPortHandler.isInBoundsBottom(buffer.buffer[j + 1])) { + break; + } + + if (!isSingleColor) { + mRenderPaint.setColor(dataSet.getColor(j / 4)); + } + + if ((dataSet.getEntryForIndex(j / 4).getY() < 0 && roundedNegativeDataSetRadius > 0)) { + Path path2 = roundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3]), roundedNegativeDataSetRadius, roundedNegativeDataSetRadius, true, true, true, true); + c.drawPath(path2, mRenderPaint); + } else if ((dataSet.getEntryForIndex(j / 4).getY() > 0 && roundedPositiveDataSetRadius > 0)) { + Path path2 = roundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3]), roundedPositiveDataSetRadius, roundedPositiveDataSetRadius, true, true, true, true); + c.drawPath(path2, mRenderPaint); + } else { + c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], + buffer.buffer[j + 3], mRenderPaint); + } + j += 4; + } + } + + private Path roundRect(RectF rect, float rx, float ry, boolean tl, boolean tr, boolean br, boolean bl) { + float top = rect.top; + float left = rect.left; + float right = rect.right; + float bottom = rect.bottom; + Path path = new Path(); + if (rx < 0) { + rx = 0; + } + if (ry < 0) { + ry = 0; + } + float width = right - left; + float height = bottom - top; + if (rx > width / 2) { + rx = width / 2; + } + if (ry > height / 2) { + ry = height / 2; + } + float widthMinusCorners = (width - (2 * rx)); + float heightMinusCorners = (height - (2 * ry)); + + path.moveTo(right, top + ry); + if (tr) { + path.rQuadTo(0, -ry, -rx, -ry);//top-right corner + } else { + path.rLineTo(0, -ry); + path.rLineTo(-rx, 0); + } + path.rLineTo(-widthMinusCorners, 0); + if (tl) { + path.rQuadTo(-rx, 0, -rx, ry); //top-left corner + } else { + path.rLineTo(-rx, 0); + path.rLineTo(0, ry); + } + path.rLineTo(0, heightMinusCorners); + + if (bl) { + path.rQuadTo(0, ry, rx, ry);//bottom-left corner + } else { + path.rLineTo(0, ry); + path.rLineTo(rx, 0); + } + + path.rLineTo(widthMinusCorners, 0); + if (br) + path.rQuadTo(rx, 0, rx, -ry); //bottom-right corner + else { + path.rLineTo(rx, 0); + path.rLineTo(0, -ry); + } + + path.rLineTo(0, -heightMinusCorners); + path.close(); + return path; + } +} diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-20-PieChartRoundedActivity.png b/screenshotsToCompare9/StartTest_smokeTestStart-20-PieChartRoundedActivity.png new file mode 100644 index 0000000000..336bb7e788 Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-20-PieChartRoundedActivity.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-39-PieChartActivity.png b/screenshotsToCompare9/StartTest_smokeTestStart-39-PieChartActivity.png new file mode 100644 index 0000000000..c99634fa55 Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-39-PieChartActivity.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart.png b/screenshotsToCompare9/StartTest_smokeTestStart.png index 06af3ead24..0ba82ed4c4 100644 Binary files a/screenshotsToCompare9/StartTest_smokeTestStart.png and b/screenshotsToCompare9/StartTest_smokeTestStart.png differ