diff --git a/MPChartExample/src/com/xxmassdeveloper/mpchartexample/HorizontalBarNegativeChartActivity.java b/MPChartExample/src/com/xxmassdeveloper/mpchartexample/HorizontalBarNegativeChartActivity.java new file mode 100644 index 0000000000..c3de5fa68f --- /dev/null +++ b/MPChartExample/src/com/xxmassdeveloper/mpchartexample/HorizontalBarNegativeChartActivity.java @@ -0,0 +1,288 @@ + +package com.xxmassdeveloper.mpchartexample; + +import android.annotation.SuppressLint; +import android.graphics.RectF; +import android.os.Bundle; +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 android.widget.Toast; + +import com.github.mikephil.charting.charts.HorizontalBarChart; +import com.github.mikephil.charting.components.Legend; +import com.github.mikephil.charting.components.XAxis; +import com.github.mikephil.charting.components.XAxis.XAxisPosition; +import com.github.mikephil.charting.components.YAxis; +import com.github.mikephil.charting.data.BarData; +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.interfaces.datasets.IBarDataSet; +import com.github.mikephil.charting.listener.OnChartValueSelectedListener; +import com.github.mikephil.charting.utils.MPPointF; +import com.xxmassdeveloper.mpchartexample.notimportant.DemoBase; + +import java.util.ArrayList; +import java.util.List; + +public class HorizontalBarNegativeChartActivity extends DemoBase implements OnSeekBarChangeListener, + OnChartValueSelectedListener { + + protected HorizontalBarChart mChart; + private SeekBar mSeekBarX, mSeekBarY; + 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_horizontalbarchart); + + tvX = findViewById(R.id.tvXMax); + tvY = (TextView) findViewById(R.id.tvYMax); + + mSeekBarX = (SeekBar) findViewById(R.id.seekBar1); + mSeekBarY = (SeekBar) findViewById(R.id.seekBar2); + + mChart = (HorizontalBarChart) findViewById(R.id.chart1); + mChart.setOnChartValueSelectedListener(this); + // mChart.setHighlightEnabled(false); + + mChart.setDrawBarShadow(false); + + mChart.setDrawValueAboveBar(true); + + mChart.getDescription().setEnabled(false); + + // if more than 60 entries are displayed in the chart, no values will be + // drawn + mChart.setMaxVisibleValueCount(60); + + // scaling can now only be done on x- and y-axis separately + mChart.setPinchZoom(false); + + // draw shadows for each bar that show the maximum value + // mChart.setDrawBarShadow(true); + + mChart.setDrawGridBackground(false); + + XAxis xl = mChart.getXAxis(); + xl.setPosition(XAxisPosition.BOTTOM); + xl.setTypeface(mTfLight); + xl.setDrawAxisLine(true); + xl.setDrawGridLines(false); + xl.setGranularity(10f); + + YAxis yl = mChart.getAxisLeft(); + yl.setTypeface(mTfLight); + yl.setDrawAxisLine(true); + yl.setDrawGridLines(true); + yl.setDrawZeroLine(true); // draw a zero line +// yl.setInverted(true); + + YAxis yr = mChart.getAxisRight(); + yr.setTypeface(mTfLight); + yr.setDrawAxisLine(true); + yr.setDrawGridLines(false); +// yr.setInverted(true); + + setData(12, 50); + mChart.setFitBars(true); + mChart.animateY(2500); + + // setting data + mSeekBarY.setProgress(50); + mSeekBarX.setProgress(12); + + mSeekBarY.setOnSeekBarChangeListener(this); + mSeekBarX.setOnSeekBarChangeListener(this); + + Legend l = mChart.getLegend(); + l.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM); + l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.LEFT); + l.setOrientation(Legend.LegendOrientation.HORIZONTAL); + l.setDrawInside(false); + l.setFormSize(8f); + l.setXEntrySpace(4f); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.bar, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + + switch (item.getItemId()) { + case R.id.actionToggleValues: { + List sets = mChart.getData() + .getDataSets(); + + for (IBarDataSet iSet : sets) { + + IBarDataSet set = (BarDataSet) iSet; + set.setDrawValues(!set.isDrawValuesEnabled()); + } + + mChart.invalidate(); + break; + } + case R.id.actionToggleIcons: { + List sets = mChart.getData() + .getDataSets(); + + for (IBarDataSet iSet : sets) { + + IBarDataSet set = (BarDataSet) iSet; + set.setDrawIcons(!set.isDrawIconsEnabled()); + } + + mChart.invalidate(); + break; + } + case R.id.actionToggleHighlight: { + if(mChart.getData() != null) { + mChart.getData().setHighlightEnabled(!mChart.getData().isHighlightEnabled()); + mChart.invalidate(); + } + break; + } + case R.id.actionTogglePinch: { + if (mChart.isPinchZoomEnabled()) + mChart.setPinchZoom(false); + else + mChart.setPinchZoom(true); + + mChart.invalidate(); + break; + } + case R.id.actionToggleAutoScaleMinMax: { + mChart.setAutoScaleMinMaxEnabled(!mChart.isAutoScaleMinMaxEnabled()); + mChart.notifyDataSetChanged(); + break; + } + case R.id.actionToggleBarBorders: { + for (IBarDataSet set : mChart.getData().getDataSets()) + ((BarDataSet)set).setBarBorderWidth(set.getBarBorderWidth() == 1.f ? 0.f : 1.f); + + mChart.invalidate(); + break; + } + case R.id.animateX: { + mChart.animateX(3000); + break; + } + case R.id.animateY: { + mChart.animateY(3000); + break; + } + case R.id.animateXY: { + + mChart.animateXY(3000, 3000); + break; + } + case R.id.actionSave: { + if (mChart.saveToGallery("title" + System.currentTimeMillis(), 50)) { + Toast.makeText(getApplicationContext(), "Saving SUCCESSFUL!", + Toast.LENGTH_SHORT).show(); + } else + Toast.makeText(getApplicationContext(), "Saving FAILED!", Toast.LENGTH_SHORT) + .show(); + break; + } + } + return true; + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + + tvX.setText("" + (mSeekBarX.getProgress() + 1)); + tvY.setText("" + (mSeekBarY.getProgress())); + + setData(mSeekBarX.getProgress() + 1, mSeekBarY.getProgress()); + mChart.setFitBars(true); + mChart.invalidate(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // TODO Auto-generated method stub + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // TODO Auto-generated method stub + + } + + private void setData(int count, float range) { + + float barWidth = 9f; + float spaceForBar = 10f; + ArrayList yVals1 = new ArrayList(); + + for (int i = 0; i < count; i++) { + float val = (float) (Math.random() * range - range / 2); + yVals1.add(new BarEntry(i * spaceForBar, val, + getResources().getDrawable(R.drawable.star))); + } + + BarDataSet set1; + + if (mChart.getData() != null && + mChart.getData().getDataSetCount() > 0) { + set1 = (BarDataSet)mChart.getData().getDataSetByIndex(0); + set1.setValues(yVals1); + mChart.getData().notifyDataChanged(); + mChart.notifyDataSetChanged(); + } else { + set1 = new BarDataSet(yVals1, "DataSet 1"); + + set1.setDrawIcons(false); + + ArrayList dataSets = new ArrayList(); + dataSets.add(set1); + + BarData data = new BarData(dataSets); + data.setValueTextSize(10f); + data.setValueTypeface(mTfLight); + data.setBarWidth(barWidth); + mChart.setData(data); + } + } + + protected RectF mOnValueSelectedRectF = new RectF(); + @SuppressLint("NewApi") + @Override + public void onValueSelected(Entry e, Highlight h) { + + if (e == null) + return; + + RectF bounds = mOnValueSelectedRectF; + mChart.getBarBounds((BarEntry) e, bounds); + + MPPointF position = mChart.getPosition(e, mChart.getData().getDataSetByIndex(h.getDataSetIndex()) + .getAxisDependency()); + + Log.i("bounds", bounds.toString()); + Log.i("position", position.toString()); + + MPPointF.recycleInstance(position); + } + + @Override + public void onNothingSelected() { + }; +} diff --git a/MPChartExample/src/main/AndroidManifest.xml b/MPChartExample/src/main/AndroidManifest.xml index 28c55b89b2..99334e601a 100644 --- a/MPChartExample/src/main/AndroidManifest.xml +++ b/MPChartExample/src/main/AndroidManifest.xml @@ -24,6 +24,7 @@ + diff --git a/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/PiePolylineChartActivity.java b/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/PiePolylineChartActivity.java index b276806c7d..dd3bd575da 100644 --- a/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/PiePolylineChartActivity.java +++ b/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/PiePolylineChartActivity.java @@ -156,7 +156,6 @@ private void setData(int count, float range) { dataSet.setValueLinePart1OffsetPercentage(80.f); dataSet.setValueLinePart1Length(0.2f); dataSet.setValueLinePart2Length(0.4f); - //dataSet.setUsingSliceColorAsValueLineColor(true); //dataSet.setXValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE); dataSet.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE); diff --git a/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/notimportant/MainActivity.java b/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/notimportant/MainActivity.java index 67749e742f..88e5dc8d8b 100644 --- a/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/notimportant/MainActivity.java +++ b/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/notimportant/MainActivity.java @@ -26,6 +26,7 @@ import com.xxmassdeveloper.mpchartexample.FilledLineActivity; import com.xxmassdeveloper.mpchartexample.HalfPieChartActivity; import com.xxmassdeveloper.mpchartexample.HorizontalBarChartActivity; +import com.xxmassdeveloper.mpchartexample.HorizontalBarNegativeChartActivity; import com.xxmassdeveloper.mpchartexample.InvertedLineChartActivity; import com.xxmassdeveloper.mpchartexample.LineChartActivity1; import com.xxmassdeveloper.mpchartexample.LineChartActivity2; @@ -87,40 +88,41 @@ protected void onCreate(Bundle savedInstanceState) { objects.add(13, new ContentItem("Horizontal", "Render bar chart horizontally.")); objects.add(14, new ContentItem("Stacked", "Stacked bar chart.")); objects.add(15, new ContentItem("Negative", "Positive and negative values with unique colors.")); - objects.add(16, new ContentItem("Stacked 2", "Stacked bar chart with negative values.")); - objects.add(17, new ContentItem("Sine", "Sine function in bar chart format.")); + objects.add(16, new ContentItem("Negative Horizontal", "demonstrates how to create a HorizontalBarChart with positive and negative values.")); + objects.add(17, new ContentItem("Stacked 2", "Stacked bar chart with negative values.")); + objects.add(18, new ContentItem("Sine", "Sine function in bar chart format.")); //// - objects.add(18, new ContentItem("Pie Charts")); + objects.add(19, new ContentItem("Pie Charts")); - objects.add(19, new ContentItem("Basic", "Simple pie chart.")); - objects.add(20, new ContentItem("Value Lines", "Stylish lines drawn outward from slices.")); - objects.add(21, new ContentItem("Half Pie", "180° (half) pie chart.")); + objects.add(20, new ContentItem("Basic", "Simple pie chart.")); + objects.add(21, new ContentItem("Value Lines", "Stylish lines drawn outward from slices.")); + objects.add(22, new ContentItem("Half Pie", "180° (half) pie chart.")); //// - objects.add(22, new ContentItem("Other Charts")); + objects.add(23, new ContentItem("Other Charts")); - objects.add(23, new ContentItem("Combined Chart", "Bar and line chart together.")); - objects.add(24, new ContentItem("Scatter Plot", "Simple scatter plot.")); - objects.add(25, new ContentItem("Bubble Chart", "Simple bubble chart.")); - objects.add(26, new ContentItem("Candlestick", "Simple financial chart.")); - objects.add(27, new ContentItem("Radar Chart", "Simple web chart.")); + objects.add(24, new ContentItem("Combined Chart", "Bar and line chart together.")); + objects.add(25, new ContentItem("Scatter Plot", "Simple scatter plot.")); + objects.add(26, new ContentItem("Bubble Chart", "Simple bubble chart.")); + objects.add(27, new ContentItem("Candlestick", "Simple financial chart.")); + objects.add(28, new ContentItem("Radar Chart", "Simple web chart.")); //// - objects.add(28, new ContentItem("Scrolling Charts")); + objects.add(29, new ContentItem("Scrolling Charts")); - objects.add(29, new ContentItem("Multiple", "Various types of charts as fragments.")); - objects.add(30, new ContentItem("View Pager", "Swipe through different charts.")); - objects.add(31, new ContentItem("Tall Bar Chart", "Bars bigger than your screen!")); - objects.add(32, new ContentItem("Many Bar Charts", "More bars than your screen can handle!")); + objects.add(30, new ContentItem("Multiple", "Various types of charts as fragments.")); + objects.add(31, new ContentItem("View Pager", "Swipe through different charts.")); + objects.add(32, new ContentItem("Tall Bar Chart", "Bars bigger than your screen!")); + objects.add(33, new ContentItem("Many Bar Charts", "More bars than your screen can handle!")); //// - objects.add(33, new ContentItem("Even More Line Charts")); + objects.add(34, new ContentItem("Even More Line Charts")); - objects.add(34, new ContentItem("Dynamic", "Build a line chart by adding points and sets.")); - objects.add(35, new ContentItem("Realtime", "Add data points in realtime.")); - objects.add(36, new ContentItem("Hourly", "Uses the current time to add a data point for each hour.")); - //objects.add(37, new ContentItem("Realm.io Examples", "See more examples that use Realm.io mobile database.")); + objects.add(35, new ContentItem("Dynamic", "Build a line chart by adding points and sets.")); + objects.add(36, new ContentItem("Realtime", "Add data points in realtime.")); + objects.add(37, new ContentItem("Hourly", "Uses the current time to add a data point for each hour.")); + //objects.add(38, new ContentItem("Realm.io Examples", "See more examples that use Realm.io mobile database.")); MyAdapter adapter = new MyAdapter(this, objects); @@ -179,57 +181,60 @@ public void onItemClick(AdapterView av, View v, int pos, long arg3) { i = new Intent(this, BarChartPositiveNegative.class); break; case 16: - i = new Intent(this, StackedBarActivityNegative.class); + i = new Intent(this, HorizontalBarNegativeChartActivity.class); break; case 17: + i = new Intent(this, StackedBarActivityNegative.class); + break; + case 18: i = new Intent(this, BarChartActivitySinus.class); break; - case 19: + case 20: i = new Intent(this, PieChartActivity.class); break; - case 20: + case 21: i = new Intent(this, PiePolylineChartActivity.class); break; - case 21: + case 22: i = new Intent(this, HalfPieChartActivity.class); break; - case 23: + case 24: i = new Intent(this, CombinedChartActivity.class); break; - case 24: + case 25: i = new Intent(this, ScatterChartActivity.class); break; - case 25: + case 26: i = new Intent(this, BubbleChartActivity.class); break; - case 26: + case 27: i = new Intent(this, CandleStickChartActivity.class); break; - case 27: + case 28: i = new Intent(this, RadarChartActivity.class); break; - case 29: + case 30: i = new Intent(this, ListViewMultiChartActivity.class); break; - case 30: + case 31: i = new Intent(this, SimpleChartDemo.class); break; - case 31: + case 32: i = new Intent(this, ScrollViewActivity.class); break; - case 32: + case 33: i = new Intent(this, ListViewBarChartActivity.class); break; - case 34: + case 35: i = new Intent(this, DynamicalAddingActivity.class); break; - case 35: + case 36: i = new Intent(this, RealtimeLineChartActivity.class); break; - case 36: + case 37: i = new Intent(this, LineChartTime.class); break; - /*case 37: + /*case 38: i = new Intent(this, RealmMainActivity.class); break;*/ } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarLineChartBase.java b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarLineChartBase.java index 71f8a2d8a8..0926dff244 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarLineChartBase.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarLineChartBase.java @@ -100,6 +100,8 @@ public abstract class BarLineChartBase= mData.getDataSetCount()) { highlightValue(null, callListener); } else { - highlightValue(new Highlight(x, y, dataSetIndex), callListener); + highlightValue(new Highlight(x, y, dataSetIndex, dataIndex), callListener); } } + /** + * Highlights any y-value at the given x-value in the given DataSet. + * Provide -1 as the dataSetIndex to undo all highlighting. + * @param x The x-value to highlight + * @param y The y-value to highlight. Supply `NaN` for "any" + * @param dataSetIndex The dataset index to search in + * @param callListener Should the listener be called for this change + */ + public void highlightValue(float x, float y, int dataSetIndex, boolean callListener) { + highlightValue(x, y, dataSetIndex, -1, callListener); + } + /** * Highlights the values represented by the provided Highlight object * This method *will not* call the listener. @@ -1162,6 +1227,15 @@ public void setNoDataTextTypeface(Typeface tf) { mInfoPaint.setTypeface(tf); } + /** + * alignment of the no data text + * + * @param align + */ + public void setNoDataTextAlignment(Align align) { + mInfoPaint.setTextAlign(align); + } + /** * Set this to false to disable all gestures and touches on the chart, * default: true diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/HorizontalBarChart.java b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/HorizontalBarChart.java index e4ec309d9f..9aac1ce97c 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/HorizontalBarChart.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/HorizontalBarChart.java @@ -60,6 +60,84 @@ protected void init() { private RectF mOffsetsBuffer = new RectF(); + protected void calculateLegendOffsets(RectF offsets) { + + offsets.left = 0.f; + offsets.right = 0.f; + offsets.top = 0.f; + offsets.bottom = 0.f; + + if (mLegend == null || !mLegend.isEnabled() || mLegend.isDrawInsideEnabled()) + return; + + 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(); + break; + + case BOTTOM: + offsets.bottom += Math.min(mLegend.mNeededHeight, + mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()) + + mLegend.getYOffset(); + break; + + default: + break; + } + } + + break; + + case HORIZONTAL: + + switch (mLegend.getVerticalAlignment()) { + case TOP: + offsets.top += Math.min(mLegend.mNeededHeight, + mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()) + + mLegend.getYOffset(); + + if (mAxisLeft.isEnabled() && mAxisLeft.isDrawLabelsEnabled()) + offsets.top += mAxisLeft.getRequiredHeightSpace( + mAxisRendererLeft.getPaintAxisLabels()); + break; + + case BOTTOM: + offsets.bottom += Math.min(mLegend.mNeededHeight, + mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()) + + mLegend.getYOffset(); + + if (mAxisRight.isEnabled() && mAxisRight.isDrawLabelsEnabled()) + offsets.bottom += mAxisRight.getRequiredHeightSpace( + mAxisRendererRight.getPaintAxisLabels()); + break; + + default: + break; + } + break; + } + } + @Override public void calculateOffsets() { diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/RadarChart.java b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/RadarChart.java index 3c9aec0dde..8c0885395d 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/RadarChart.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/RadarChart.java @@ -84,6 +84,7 @@ protected void init() { super.init(); mYAxis = new YAxis(AxisDependency.LEFT); + mYAxis.setLabelXOffset(10f); mWebLineWidth = Utils.convertDpToPixel(1.5f); mInnerWebLineWidth = Utils.convertDpToPixel(0.75f); diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/AxisBase.java b/MPChartLib/src/main/java/com/github/mikephil/charting/components/AxisBase.java index c1f02828be..96f706a3a1 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/AxisBase.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/AxisBase.java @@ -151,6 +151,39 @@ public abstract class AxisBase extends ComponentBase { */ public float mAxisRange = 0f; + private int mAxisMinLabels = 2; + private int mAxisMaxLabels = 25; + + /** + * The minumum number of labels on the axis + */ + public int getAxisMinLabels() { + return mAxisMinLabels; + } + + /** + * The minumum number of labels on the axis + */ + public void setAxisMinLabels(int labels) { + if (labels > 0) + mAxisMinLabels = labels; + } + + /** + * The maximum number of labels on the axis + */ + public int getAxisMaxLabels() { + return mAxisMaxLabels; + } + + /** + * The maximum number of labels on the axis + */ + public void setAxisMaxLabels(int labels) { + if (labels > 0) + mAxisMaxLabels = labels; + } + /** * default constructor */ @@ -314,10 +347,10 @@ public boolean isDrawLabelsEnabled() { */ public void setLabelCount(int count) { - if (count > 25) - count = 25; - if (count < 2) - count = 2; + if (count > getAxisMaxLabels()) + count = getAxisMaxLabels(); + if (count < getAxisMinLabels()) + count = getAxisMinLabels(); mLabelCount = count; mForceLabels = false; 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 index b785098881..708129259b 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.java @@ -703,8 +703,7 @@ else if (wasStacked) { width += Utils.calcTextWidth(labelpaint, label); - if (i < entryCount - 1) - maxHeight += labelLineHeight + yEntrySpace; + maxHeight += labelLineHeight + yEntrySpace; } else { wasStacked = true; width += formSize; diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/YAxis.java b/MPChartLib/src/main/java/com/github/mikephil/charting/components/YAxis.java index 030603f55a..d2071ec5a8 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/YAxis.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/YAxis.java @@ -73,6 +73,11 @@ public class YAxis extends AxisBase { */ private YAxisLabelPosition mPosition = YAxisLabelPosition.OUTSIDE_CHART; + /** + * the horizontal offset of the y-label + */ + private float mXLabelOffset = 0.0f; + /** * enum for the position of the y-labels relative to the chart */ @@ -174,6 +179,22 @@ public void setPosition(YAxisLabelPosition pos) { mPosition = pos; } + /** + * returns the horizontal offset of the y-label + */ + public float getLabelXOffset() { + return mXLabelOffset; + } + + /** + * sets the horizontal offset of the y-label + * + * @param xOffset + */ + public void setLabelXOffset(float xOffset) { + mXLabelOffset = xOffset; + } + /** * returns true if drawing the top y-axis label entry is enabled * @@ -406,6 +427,26 @@ public void calculate(float dataMin, float dataMax) { float min = dataMin; float max = dataMax; + // Make sure max is greater than min + // Discussion: https://github.com/danielgindi/Charts/pull/3650#discussion_r221409991 + if (min > max) + { + if (mCustomAxisMax && mCustomAxisMin) + { + float t = min; + min = max; + max = t; + } + else if (mCustomAxisMax) + { + min = max < 0f ? max * 1.5f : max * 0.5f; + } + else if (mCustomAxisMin) + { + max = min < 0f ? min * 0.5f : min * 1.5f; + } + } + float range = Math.abs(max - min); // in case all values are equal diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarDataSet.java index 496f4046f8..7b7ee5f916 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarDataSet.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarDataSet.java @@ -38,9 +38,7 @@ public class BarDataSet extends BarLineScatterCandleBubbleDataSet impl /** * array of labels used to describe the different values of the stacked bars */ - private String[] mStackLabels = new String[]{ - "Stack" - }; + private String[] mStackLabels = new String[]{}; public BarDataSet(List yVals, String label) { super(yVals, label); @@ -54,8 +52,8 @@ public BarDataSet(List yVals, String label) { @Override public DataSet copy() { List entries = new ArrayList(); - for (int i = 0; i < mValues.size(); i++) { - entries.add(mValues.get(i).copy()); + for (int i = 0; i < mEntries.size(); i++) { + entries.add(mEntries.get(i).copy()); } BarDataSet copied = new BarDataSet(entries, getLabel()); copy(copied); diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleDataSet.java index 1f88272dd9..9ef87fb2d5 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleDataSet.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleDataSet.java @@ -42,8 +42,8 @@ protected void calcMinMax(BubbleEntry e) { @Override public DataSet copy() { List entries = new ArrayList(); - for (int i = 0; i < mValues.size(); i++) { - entries.add(mValues.get(i).copy()); + for (int i = 0; i < mEntries.size(); i++) { + entries.add(mEntries.get(i).copy()); } BubbleDataSet copied = new BubbleDataSet(entries, getLabel()); copy(copied); diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleDataSet.java index c7f8362803..dcd5b76cea 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleDataSet.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleDataSet.java @@ -80,8 +80,8 @@ public CandleDataSet(List yVals, String label) { @Override public DataSet copy() { List entries = new ArrayList(); - for (int i = 0; i < mValues.size(); i++) { - entries.add(mValues.get(i).copy()); + for (int i = 0; i < mEntries.size(); i++) { + entries.add(mEntries.get(i).copy()); } CandleDataSet copied = new CandleDataSet(entries, getLabel()); copy(copied); diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.java index 9bd460290d..95d439a71d 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.java @@ -399,7 +399,7 @@ public boolean removeDataSet(T d) { // if a DataSet was removed if (removed) { - calcMinMax(); + notifyDataChanged(); } return removed; @@ -526,7 +526,7 @@ public boolean removeEntry(Entry e, int dataSetIndex) { boolean removed = set.removeEntry(e); if (removed) { - calcMinMax(); + notifyDataChanged(); } return removed; diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.java index 39625b30f9..0b36aa3bef 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.java @@ -3,6 +3,7 @@ import android.util.Log; +import com.github.mikephil.charting.components.YAxis; import com.github.mikephil.charting.highlight.Highlight; import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet; @@ -91,18 +92,26 @@ public void calcMinMax() { if (data.getXMin() < mXMin) mXMin = data.getXMin(); - if (data.mLeftAxisMax > mLeftAxisMax) - mLeftAxisMax = data.mLeftAxisMax; - - if (data.mLeftAxisMin < mLeftAxisMin) - mLeftAxisMin = data.mLeftAxisMin; - - if (data.mRightAxisMax > mRightAxisMax) - mRightAxisMax = data.mRightAxisMax; - - if (data.mRightAxisMin < mRightAxisMin) - mRightAxisMin = data.mRightAxisMin; - + for (IBarLineScatterCandleBubbleDataSet dataset : sets) { + if (dataset.getAxisDependency() == YAxis.AxisDependency.LEFT) { + if (dataset.getYMax() > mLeftAxisMax) { + mLeftAxisMax = dataset.getYMax(); + } + + if (dataset.getYMin() < mLeftAxisMin) { + mLeftAxisMin = dataset.getYMin(); + } + } + else { + if (dataset.getYMax() > mRightAxisMax) { + mRightAxisMax = dataset.getYMax(); + } + + if (dataset.getYMin() < mRightAxisMin) { + mRightAxisMin = dataset.getYMin(); + } + } + } } } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/DataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/DataSet.java index 3c69d9c58f..fda07efef2 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/DataSet.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/DataSet.java @@ -17,7 +17,7 @@ public abstract class DataSet extends BaseDataSet { /** * the entries that this DataSet represents / holds together */ - protected List mValues = null; + protected List mEntries; /** * maximum y-value in the value array @@ -45,15 +45,15 @@ public abstract class DataSet extends BaseDataSet { * label that describes the DataSet can be specified. The label can also be * used to retrieve the DataSet from a ChartData object. * - * @param values + * @param entries * @param label */ - public DataSet(List values, String label) { + public DataSet(List entries, String label) { super(label); - this.mValues = values; + this.mEntries = entries; - if (mValues == null) - mValues = new ArrayList(); + if (mEntries == null) + mEntries = new ArrayList(); calcMinMax(); } @@ -61,35 +61,36 @@ public DataSet(List values, String label) { @Override public void calcMinMax() { - if (mValues == null || mValues.isEmpty()) - return; - mYMax = -Float.MAX_VALUE; mYMin = Float.MAX_VALUE; mXMax = -Float.MAX_VALUE; mXMin = Float.MAX_VALUE; - for (T e : mValues) { + if (mEntries == null || mEntries.isEmpty()) + return; + + for (T e : mEntries) { calcMinMax(e); } } @Override public void calcMinMaxY(float fromX, float toX) { - - if (mValues == null || mValues.isEmpty()) - return; - mYMax = -Float.MAX_VALUE; mYMin = Float.MAX_VALUE; + + if (mEntries == null || mEntries.isEmpty()) + return; int indexFrom = getEntryIndex(fromX, Float.NaN, Rounding.DOWN); int indexTo = getEntryIndex(toX, Float.NaN, Rounding.UP); + if (indexTo < indexFrom) return; + for (int i = indexFrom; i <= indexTo; i++) { // only recalculate y - calcMinMaxY(mValues.get(i)); + calcMinMaxY(mEntries.get(i)); } } @@ -128,25 +129,47 @@ protected void calcMinMaxY(T e) { @Override public int getEntryCount() { - return mValues.size(); + return mEntries.size(); } /** - * Returns the array of entries that this DataSet represents. + * This method is deprecated. + * Use getEntries() instead. * * @return */ + @Deprecated public List getValues() { - return mValues; + return mEntries; } /** - * Sets the array of entries that this DataSet represents, and calls notifyDataSetChanged() + * Returns the array of entries that this DataSet represents. * * @return */ + public List getEntries() { + return mEntries; + } + + /** + * This method is deprecated. + * Use setEntries(...) instead. + * + * @param values + */ + @Deprecated public void setValues(List values) { - mValues = values; + setEntries(values); + } + + /** + * Sets the array of entries that this DataSet represents, and calls notifyDataSetChanged() + * + * @return + */ + public void setEntries(List entries) { + mEntries = entries; notifyDataSetChanged(); } @@ -169,8 +192,8 @@ protected void copy(DataSet dataSet) { public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append(toSimpleString()); - for (int i = 0; i < mValues.size(); i++) { - buffer.append(mValues.get(i).toString() + " "); + for (int i = 0; i < mEntries.size(); i++) { + buffer.append(mEntries.get(i).toString() + " "); } return buffer.toString(); } @@ -183,7 +206,7 @@ public String toString() { */ public String toSimpleString() { StringBuffer buffer = new StringBuffer(); - buffer.append("DataSet, label: " + (getLabel() == null ? "" : getLabel()) + ", entries: " + mValues.size() + + buffer.append("DataSet, label: " + (getLabel() == null ? "" : getLabel()) + ", entries: " + mEntries.size() + "\n"); return buffer.toString(); } @@ -214,23 +237,23 @@ public void addEntryOrdered(T e) { if (e == null) return; - if (mValues == null) { - mValues = new ArrayList(); + if (mEntries == null) { + mEntries = new ArrayList(); } calcMinMax(e); - if (mValues.size() > 0 && mValues.get(mValues.size() - 1).getX() > e.getX()) { + if (mEntries.size() > 0 && mEntries.get(mEntries.size() - 1).getX() > e.getX()) { int closestIndex = getEntryIndex(e.getX(), e.getY(), Rounding.UP); - mValues.add(closestIndex, e); + mEntries.add(closestIndex, e); } else { - mValues.add(e); + mEntries.add(e); } } @Override public void clear() { - mValues.clear(); + mEntries.clear(); notifyDataSetChanged(); } @@ -240,9 +263,9 @@ public boolean addEntry(T e) { if (e == null) return false; - List values = getValues(); + List values = getEntries(); if (values == null) { - values = new ArrayList(); + values = new ArrayList<>(); } calcMinMax(e); @@ -257,11 +280,11 @@ public boolean removeEntry(T e) { if (e == null) return false; - if (mValues == null) + if (mEntries == null) return false; // remove the entry - boolean removed = mValues.remove(e); + boolean removed = mEntries.remove(e); if (removed) { calcMinMax(); @@ -272,7 +295,7 @@ public boolean removeEntry(T e) { @Override public int getEntryIndex(Entry e) { - return mValues.indexOf(e); + return mEntries.indexOf(e); } @Override @@ -280,7 +303,7 @@ public T getEntryForXValue(float xValue, float closestToY, Rounding rounding) { int index = getEntryIndex(xValue, closestToY, rounding); if (index > -1) - return mValues.get(index); + return mEntries.get(index); return null; } @@ -291,24 +314,24 @@ public T getEntryForXValue(float xValue, float closestToY) { @Override public T getEntryForIndex(int index) { - return mValues.get(index); + return mEntries.get(index); } @Override public int getEntryIndex(float xValue, float closestToY, Rounding rounding) { - if (mValues == null || mValues.isEmpty()) + if (mEntries == null || mEntries.isEmpty()) return -1; int low = 0; - int high = mValues.size() - 1; + int high = mEntries.size() - 1; int closest = high; while (low < high) { int m = (low + high) / 2; - final float d1 = mValues.get(m).getX() - xValue, - d2 = mValues.get(m + 1).getX() - xValue, + final float d1 = mEntries.get(m).getX() - xValue, + d2 = mEntries.get(m + 1).getX() - xValue, ad1 = Math.abs(d1), ad2 = Math.abs(d2); if (ad2 < ad1) { @@ -335,10 +358,10 @@ public int getEntryIndex(float xValue, float closestToY, Rounding rounding) { } if (closest != -1) { - float closestXValue = mValues.get(closest).getX(); + float closestXValue = mEntries.get(closest).getX(); if (rounding == Rounding.UP) { // If rounding up, and found x-value is lower than specified x, and we can go upper... - if (closestXValue < xValue && closest < mValues.size() - 1) { + if (closestXValue < xValue && closest < mEntries.size() - 1) { ++closest; } } else if (rounding == Rounding.DOWN) { @@ -350,23 +373,23 @@ public int getEntryIndex(float xValue, float closestToY, Rounding rounding) { // Search by closest to y-value if (!Float.isNaN(closestToY)) { - while (closest > 0 && mValues.get(closest - 1).getX() == closestXValue) + while (closest > 0 && mEntries.get(closest - 1).getX() == closestXValue) closest -= 1; - float closestYValue = mValues.get(closest).getY(); + float closestYValue = mEntries.get(closest).getY(); int closestYIndex = closest; while (true) { closest += 1; - if (closest >= mValues.size()) + if (closest >= mEntries.size()) break; - final Entry value = mValues.get(closest); + final Entry value = mEntries.get(closest); if (value.getX() != closestXValue) break; - if (Math.abs(value.getY() - closestToY) < Math.abs(closestYValue - closestToY)) { + if (Math.abs(value.getY() - closestToY) <= Math.abs(closestYValue - closestToY)) { closestYValue = closestToY; closestYIndex = closest; } @@ -385,22 +408,22 @@ public List getEntriesForXValue(float xValue) { List entries = new ArrayList(); int low = 0; - int high = mValues.size() - 1; + int high = mEntries.size() - 1; while (low <= high) { int m = (high + low) / 2; - T entry = mValues.get(m); + T entry = mEntries.get(m); // if we have a match if (xValue == entry.getX()) { - while (m > 0 && mValues.get(m - 1).getX() == xValue) + while (m > 0 && mEntries.get(m - 1).getX() == xValue) m--; - high = mValues.size(); + high = mEntries.size(); // loop over all "equal" entries for (; m < high; m++) { - entry = mValues.get(m); + entry = mEntries.get(m); if (entry.getX() == xValue) { entries.add(entry); } else { diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineDataSet.java index c1018d1fb4..10d1837ecd 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineDataSet.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineDataSet.java @@ -85,8 +85,8 @@ public LineDataSet(List yVals, String label) { @Override public DataSet copy() { List entries = new ArrayList(); - for (int i = 0; i < mValues.size(); i++) { - entries.add(mValues.get(i).copy()); + for (int i = 0; i < mEntries.size(); i++) { + entries.add(mEntries.get(i).copy()); } LineDataSet copied = new LineDataSet(entries, getLabel()); copy(copied); diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieData.java index db7972a3fb..423ce19b16 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieData.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieData.java @@ -1,6 +1,8 @@ package com.github.mikephil.charting.data; +import android.util.Log; + import com.github.mikephil.charting.highlight.Highlight; import com.github.mikephil.charting.interfaces.datasets.IPieDataSet; @@ -46,6 +48,18 @@ public IPieDataSet getDataSet() { return mDataSets.get(0); } + @Override + public List getDataSets() { + List dataSets = super.getDataSets(); + + if (dataSets.size() < 1) { + Log.e("MPAndroidChart", + "Found multiple data sets while pie chart only allows one"); + } + + return dataSets; + } + /** * The PieData object can only have one DataSet. Use getDataSet() method instead. * diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieDataSet.java index e592399513..38a5d0b89b 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieDataSet.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieDataSet.java @@ -3,6 +3,7 @@ import com.github.mikephil.charting.interfaces.datasets.IPieDataSet; import com.github.mikephil.charting.utils.Utils; +import android.support.annotation.Nullable; import java.util.ArrayList; import java.util.List; @@ -22,13 +23,14 @@ public class PieDataSet extends DataSet implements IPieDataSet { private ValuePosition mXValuePosition = ValuePosition.INSIDE_SLICE; private ValuePosition mYValuePosition = ValuePosition.INSIDE_SLICE; - private boolean mUsingSliceColorAsValueLineColor = false; private int mValueLineColor = 0xff000000; + private boolean mUseValueColorForLine = false; private float mValueLineWidth = 1.0f; private float mValueLinePart1OffsetPercentage = 75.f; private float mValueLinePart1Length = 0.3f; private float mValueLinePart2Length = 0.4f; private boolean mValueLineVariableLength = true; + private Integer mHighlightColor = null; public PieDataSet(List yVals, String label) { super(yVals, label); @@ -38,8 +40,8 @@ public PieDataSet(List yVals, String label) { @Override public DataSet copy() { List entries = new ArrayList<>(); - for (int i = 0; i < mValues.size(); i++) { - entries.add(mValues.get(i).copy()); + for (int i = 0; i < mEntries.size(); i++) { + entries.add(mEntries.get(i).copy()); } PieDataSet copied = new PieDataSet(entries, getLabel()); copy(copied); @@ -135,15 +137,23 @@ public void setYValuePosition(ValuePosition yValuePosition) { } /** - * When valuePosition is OutsideSlice, use slice colors as line color if true + * This method is deprecated. + * Use isUseValueColorForLineEnabled() instead. */ - @Override + @Deprecated public boolean isUsingSliceColorAsValueLineColor() { - return mUsingSliceColorAsValueLineColor; + return isUseValueColorForLineEnabled(); } - public void setUsingSliceColorAsValueLineColor(boolean usingSliceColorAsValueLineColor) { - this.mUsingSliceColorAsValueLineColor = usingSliceColorAsValueLineColor; + /** + * This method is deprecated. + * Use setUseValueColorForLine(...) instead. + * + * @param enabled + */ + @Deprecated + public void setUsingSliceColorAsValueLineColor(boolean enabled) { + setUseValueColorForLine(enabled); } /** @@ -158,6 +168,17 @@ public void setValueLineColor(int valueLineColor) { this.mValueLineColor = valueLineColor; } + @Override + public boolean isUseValueColorForLineEnabled() + { + return mUseValueColorForLine; + } + + public void setUseValueColorForLine(boolean enabled) + { + mUseValueColorForLine = enabled; + } + /** * When valuePosition is OutsideSlice, indicates line width */ @@ -218,6 +239,21 @@ public void setValueLineVariableLength(boolean valueLineVariableLength) { this.mValueLineVariableLength = valueLineVariableLength; } + /** Gets the color for the highlighted sector */ + @Override + @Nullable + public Integer getHighlightColor() + { + return mHighlightColor; + } + + /** Sets the color for the highlighted sector (null for using entry color) */ + public void setHighlightColor(@Nullable Integer color) + { + this.mHighlightColor = color; + } + + public enum ValuePosition { INSIDE_SLICE, OUTSIDE_SLICE diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarDataSet.java index 09c94b417d..8a9740b6b5 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarDataSet.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarDataSet.java @@ -102,8 +102,8 @@ public void setHighlightCircleStrokeWidth(float strokeWidth) { @Override public DataSet copy() { List entries = new ArrayList(); - for (int i = 0; i < mValues.size(); i++) { - entries.add(mValues.get(i).copy()); + for (int i = 0; i < mEntries.size(); i++) { + entries.add(mEntries.get(i).copy()); } RadarDataSet copied = new RadarDataSet(entries, getLabel()); copy(copied); diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterDataSet.java index d234c751a0..85ed808160 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterDataSet.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterDataSet.java @@ -48,8 +48,8 @@ public ScatterDataSet(List yVals, String label) { @Override public DataSet copy() { List entries = new ArrayList(); - for (int i = 0; i < mValues.size(); i++) { - entries.add(mValues.get(i).copy()); + for (int i = 0; i < mEntries.size(); i++) { + entries.add(mEntries.get(i).copy()); } ScatterDataSet copied = new ScatterDataSet(entries, getLabel()); copy(copied); diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/filter/ApproximatorN.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/filter/ApproximatorN.java new file mode 100644 index 0000000000..9351341c76 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/filter/ApproximatorN.java @@ -0,0 +1,146 @@ + +package com.github.mikephil.charting.data.filter; + +import java.util.ArrayList; + +/** + * Implemented according to modified Douglas Peucker {@link} + * http://psimpl.sourceforge.net/douglas-peucker.html + */ +public class ApproximatorN +{ + public float[] reduceWithDouglasPeucker(float[] points, float resultCount) { + + int pointCount = points.length / 2; + + // if a shape has 2 or less points it cannot be reduced + if (resultCount <= 2 || resultCount >= pointCount) + return points; + + boolean[] keep = new boolean[pointCount]; + + // first and last always stay + keep[0] = true; + keep[pointCount - 1] = true; + + int currentStoredPoints = 2; + + ArrayList queue = new ArrayList<>(); + Line line = new Line(0, pointCount - 1, points); + queue.add(line); + + do { + line = queue.remove(queue.size() - 1); + + // store the key + keep[line.index] = true; + + // check point count tolerance + currentStoredPoints += 1; + + if (currentStoredPoints == resultCount) + break; + + // split the polyline at the key and recurse + Line left = new Line(line.start, line.index, points); + if (left.index > 0) { + int insertionIndex = insertionIndex(left, queue); + queue.add(insertionIndex, left); + } + + Line right = new Line(line.index, line.end, points); + if (right.index > 0) { + int insertionIndex = insertionIndex(right, queue); + queue.add(insertionIndex, right); + } + } while (queue.isEmpty()); + + float[] reducedEntries = new float[currentStoredPoints * 2]; + + for (int i = 0, i2 = 0, r2 = 0; i < currentStoredPoints; i++, r2 += 2) { + if (keep[i]) { + reducedEntries[i2++] = points[r2]; + reducedEntries[i2++] = points[r2 + 1]; + } + } + + return reducedEntries; + } + + private static float distanceToLine( + float ptX, float ptY, float[] + fromLinePoint1, float[] fromLinePoint2) { + float dx = fromLinePoint2[0] - fromLinePoint1[0]; + float dy = fromLinePoint2[1] - fromLinePoint1[1]; + + float dividend = Math.abs( + dy * ptX - + dx * ptY - + fromLinePoint1[0] * fromLinePoint2[1] + + fromLinePoint2[0] * fromLinePoint1[1]); + double divisor = Math.sqrt(dx * dx + dy * dy); + + return (float)(dividend / divisor); + } + + private static class Line { + int start; + int end; + + float distance = 0; + int index = 0; + + Line(int start, int end, float[] points) { + this.start = start; + this.end = end; + + float[] startPoint = new float[]{points[start * 2], points[start * 2 + 1]}; + float[] endPoint = new float[]{points[end * 2], points[end * 2 + 1]}; + + if (end <= start + 1) return; + + for (int i = start + 1, i2 = i * 2; i < end; i++, i2 += 2) { + float distance = distanceToLine( + points[i2], points[i2 + 1], + startPoint, endPoint); + + if (distance > this.distance) { + this.index = i; + this.distance = distance; + } + } + } + + boolean equals(final Line rhs) { + return (start == rhs.start) && (end == rhs.end) && index == rhs.index; + } + + boolean lessThan(final Line rhs) { + return distance < rhs.distance; + } + } + + private static int insertionIndex(Line line, ArrayList queue) { + int min = 0; + int max = queue.size(); + + while (!queue.isEmpty()) { + int midIndex = min + (max - min) / 2; + Line midLine = queue.get(midIndex); + + if (midLine.equals(line)) { + return midIndex; + } + else if (line.lessThan(midLine)) { + // perform search in left half + max = midIndex; + } + else { + // perform search in right half + min = midIndex + 1; + } + } + + return min; + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/Highlight.java b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/Highlight.java index 032698d5e5..62307cbeaf 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/Highlight.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/Highlight.java @@ -60,10 +60,18 @@ public class Highlight { */ private float mDrawY; + public Highlight(float x, float y, int dataSetIndex, int dataIndex) { + this.mX = x; + this.mY = y; + this.mDataSetIndex = dataSetIndex; + this.mDataIndex = dataIndex; + } + public Highlight(float x, float y, int dataSetIndex) { this.mX = x; this.mY = y; this.mDataSetIndex = dataSetIndex; + this.mDataIndex = -1; } public Highlight(float x, int dataSetIndex, int stackIndex) { diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IPieDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IPieDataSet.java index 1698ef786b..3720394f25 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IPieDataSet.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IPieDataSet.java @@ -1,5 +1,7 @@ package com.github.mikephil.charting.interfaces.datasets; +import android.support.annotation.Nullable; + import com.github.mikephil.charting.data.PieDataSet; import com.github.mikephil.charting.data.PieEntry; @@ -36,14 +38,14 @@ public interface IPieDataSet extends IDataSet { PieDataSet.ValuePosition getYValuePosition(); /** - * When valuePosition is OutsideSlice, use slice colors as line color if true + * When valuePosition is OutsideSlice, indicates line color * */ - boolean isUsingSliceColorAsValueLineColor(); + int getValueLineColor(); /** - * When valuePosition is OutsideSlice, indicates line color + * When valuePosition is OutsideSlice and enabled, line will have the same color as the slice * */ - int getValueLineColor(); + boolean isUseValueColorForLineEnabled(); /** * When valuePosition is OutsideSlice, indicates line width @@ -70,5 +72,11 @@ public interface IPieDataSet extends IDataSet { * */ boolean isValueLineVariableLength(); + /** + * Gets the color for the highlighted sector + * */ + @Nullable + Integer getHighlightColor(); + } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/BarLineChartTouchListener.java b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/BarLineChartTouchListener.java index 53ab12a369..5685d32fa0 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/BarLineChartTouchListener.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/BarLineChartTouchListener.java @@ -580,12 +580,19 @@ public boolean onDoubleTap(MotionEvent e) { MPPointF trans = getTrans(e.getX(), e.getY()); - mChart.zoom(mChart.isScaleXEnabled() ? 1.4f : 1f, mChart.isScaleYEnabled() ? 1.4f : 1f, trans.x, trans.y); + float scaleX = mChart.isScaleXEnabled() ? 1.4f : 1f; + float scaleY = mChart.isScaleYEnabled() ? 1.4f : 1f; + + mChart.zoom(scaleX, scaleY, trans.x, trans.y); if (mChart.isLogEnabled()) Log.i("BarlineChartTouch", "Double-Tap, Zooming In, x: " + trans.x + ", y: " + trans.y); + if (l != null) { + l.onChartScale(e, scaleX, scaleY); + } + MPPointF.recycleInstance(trans); } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartGestureListener.java b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartGestureListener.java index a17cdde941..da0c5ed180 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartGestureListener.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartGestureListener.java @@ -57,7 +57,7 @@ public interface OnChartGestureListener { void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY); /** - * Callbacks when the chart is scaled / zoomed via pinch zoom gesture. + * Callbacks when the chart is scaled / zoomed via pinch zoom / double-tap gesture. * * @param me * @param scaleX scalefactor on the x-axis diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/AxisRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/AxisRenderer.java index 90528a1359..72ea2d17c8 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/AxisRenderer.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/AxisRenderer.java @@ -174,9 +174,12 @@ protected void computeAxisValues(float min, float max) { double intervalMagnitude = Utils.roundToNextSignificant(Math.pow(10, (int) Math.log10(interval))); int intervalSigDigit = (int) (interval / intervalMagnitude); if (intervalSigDigit > 5) { - // Use one order of magnitude higher, to avoid intervals like 0.9 or - // 90 - interval = Math.floor(10 * intervalMagnitude); + // Use one order of magnitude higher, to avoid intervals like 0.9 or 90 + // if it's 0.0 after floor(), we use the old value + interval = Math.floor(10.0 * intervalMagnitude) == 0.0 + ? interval + : Math.floor(10.0 * intervalMagnitude); + } int n = mAxis.isCenterAxisLabelsEnabled() ? 1 : 0; @@ -214,11 +217,14 @@ protected void computeAxisValues(float min, float max) { double f; int i; - if (interval != 0.0) { + if (interval != 0.0 && last != first) { for (f = first; f <= last; f += interval) { ++n; } } + else if (last == first && n == 0) { + n = 1; + } mAxis.mEntryCount = n; diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BubbleChartRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BubbleChartRenderer.java index 57b81c1d9c..be141c46a0 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BubbleChartRenderer.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BubbleChartRenderer.java @@ -108,7 +108,7 @@ protected void drawDataSet(Canvas c, IBubbleDataSet dataSet) { if (!mViewPortHandler.isInBoundsRight(pointBuffer[0] - shapeHalf)) break; - final int color = dataSet.getColor((int) entry.getX()); + final int color = dataSet.getColor(j); mRenderPaint.setColor(color); c.drawCircle(pointBuffer[0], pointBuffer[1], shapeHalf, mRenderPaint); diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/HorizontalBarChartRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/HorizontalBarChartRenderer.java index 7607abdd92..b692a1f90b 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/HorizontalBarChartRenderer.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/HorizontalBarChartRenderer.java @@ -200,7 +200,8 @@ public void drawValues(Canvas c) { // calculate the correct offset depending on the draw position of the value float valueTextWidth = Utils.calcTextWidth(mValuePaint, formattedValue); posOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextWidth + valueOffsetPlus)); - negOffset = (drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus) : valueOffsetPlus); + negOffset = (drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus) : valueOffsetPlus) + - (buffer.buffer[j + 2] - buffer.buffer[j]); if (isInverted) { posOffset = -posOffset - valueTextWidth; diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LegendRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LegendRenderer.java index 85597db6a1..5d49580561 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LegendRenderer.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LegendRenderer.java @@ -90,6 +90,7 @@ public void computeLegend(ChartData data) { for (int i = 0; i < data.getDataSetCount(); i++) { IDataSet dataSet = data.getDataSetByIndex(i); + if (dataSet == null) continue; List clrs = dataSet.getColors(); int entryCount = dataSet.getEntryCount(); @@ -100,10 +101,19 @@ public void computeLegend(ChartData data) { IBarDataSet bds = (IBarDataSet) dataSet; String[] sLabels = bds.getStackLabels(); - for (int j = 0; j < clrs.size() && j < bds.getStackSize(); j++) { + int minEntries = Math.min(clrs.size(), bds.getStackSize()); + + for (int j = 0; j < minEntries; j++) { + String label; + if (sLabels.length > 0) { + int labelIndex = j % minEntries; + label = labelIndex < sLabels.length ? sLabels[labelIndex] : null; + } else { + label = null; + } computedEntries.add(new LegendEntry( - sLabels[j % sLabels.length], + label, dataSet.getForm(), dataSet.getFormSize(), dataSet.getFormLineWidth(), diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LineChartRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LineChartRenderer.java index ead9d6d701..849bc4a7aa 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LineChartRenderer.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LineChartRenderer.java @@ -323,10 +323,14 @@ protected void drawLinear(Canvas c, ILineDataSet dataSet) { // more than 1 color if (dataSet.getColors().size() > 1) { - if (mLineBuffer.length <= pointsPerEntryPair * 2) - mLineBuffer = new float[pointsPerEntryPair * 4]; + int numberOfFloats = pointsPerEntryPair * 2; - for (int j = mXBounds.min; j <= mXBounds.range + mXBounds.min; j++) { + if (mLineBuffer.length <= numberOfFloats) + mLineBuffer = new float[numberOfFloats * 2]; + + int max = mXBounds.min + mXBounds.range; + + for (int j = mXBounds.min; j < max; j++) { Entry e = dataSet.getEntryForIndex(j); if (e == null) continue; @@ -357,16 +361,26 @@ protected void drawLinear(Canvas c, ILineDataSet dataSet) { mLineBuffer[3] = mLineBuffer[1]; } + // Determine the start and end coordinates of the line, and make sure they differ. + float firstCoordinateX = mLineBuffer[0]; + float firstCoordinateY = mLineBuffer[1]; + float lastCoordinateX = mLineBuffer[numberOfFloats - 2]; + float lastCoordinateY = mLineBuffer[numberOfFloats - 1]; + + if (firstCoordinateX == lastCoordinateX && + firstCoordinateY == lastCoordinateY) + continue; + trans.pointValuesToPixel(mLineBuffer); - if (!mViewPortHandler.isInBoundsRight(mLineBuffer[0])) + if (!mViewPortHandler.isInBoundsRight(firstCoordinateX)) break; // make sure the lines don't do shitty things outside // bounds - if (!mViewPortHandler.isInBoundsLeft(mLineBuffer[2]) - || (!mViewPortHandler.isInBoundsTop(mLineBuffer[1]) && !mViewPortHandler - .isInBoundsBottom(mLineBuffer[3]))) + if (!mViewPortHandler.isInBoundsLeft(lastCoordinateX) || + !mViewPortHandler.isInBoundsTop(lastCoordinateY) || + !mViewPortHandler.isInBoundsBottom(firstCoordinateY)) continue; // get the color that is set for this line-segment 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 c9f653e808..f427ffe5d3 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 @@ -258,7 +258,7 @@ protected void drawDataSet(Canvas c, IPieDataSet dataSet) { } // Don't draw if it's highlighted, unless the chart uses rounded slices - if (mChart.needsHighlight(j) && !drawRoundedSlices) { + if (dataSet.isHighlightEnabled() && mChart.needsHighlight(j) && !drawRoundedSlices) { angle += sliceAngle * phaseX; continue; } @@ -468,7 +468,9 @@ public void drawValues(Canvas c) { int entryCount = dataSet.getEntryCount(); - mValueLinePaint.setColor(dataSet.getValueLineColor()); + boolean isUseValueColorForLineEnabled = dataSet.isUseValueColorForLineEnabled(); + int valueLineColor = dataSet.getValueLineColor(); + mValueLinePaint.setStrokeWidth(Utils.convertDpToPixel(dataSet.getValueLineWidth())); final float sliceSpace = getSliceSpace(dataSet); @@ -565,12 +567,15 @@ public void drawValues(Canvas c) { labelPty = pt2y; } - if (dataSet.getValueLineColor() != ColorTemplate.COLOR_NONE) { + int lineColor = ColorTemplate.COLOR_NONE; - if (dataSet.isUsingSliceColorAsValueLineColor()) { - mValueLinePaint.setColor(dataSet.getColor(j)); - } + if (isUseValueColorForLineEnabled) + lineColor = dataSet.getColor(j); + else if (valueLineColor != ColorTemplate.COLOR_NONE) + lineColor = valueLineColor; + if (lineColor != ColorTemplate.COLOR_NONE) { + mValueLinePaint.setColor(lineColor); c.drawLine(pt0x, pt0y, pt1x, pt1y, mValueLinePaint); c.drawLine(pt1x, pt1y, pt2x, pt2y, mValueLinePaint); } @@ -825,8 +830,7 @@ public void drawHighlighted(Canvas c, Highlight[] indices) { continue; IPieDataSet set = mChart.getData() - .getDataSetByIndex(indices[i] - .getDataSetIndex()); + .getDataSetByIndex(indices[i].getDataSetIndex()); if (set == null || !set.isHighlightEnabled()) continue; @@ -857,7 +861,10 @@ public void drawHighlighted(Canvas c, Highlight[] indices) { final boolean accountForSliceSpacing = sliceSpace > 0.f && sliceAngle <= 180.f; - mRenderPaint.setColor(set.getColor(index)); + Integer highlightColor = set.getHighlightColor(); + if (highlightColor == null) + highlightColor = set.getColor(index); + mRenderPaint.setColor(highlightColor); final float sliceSpaceAngleOuter = visibleAngleCount == 1 ? 0.f : diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRenderer.java index a2bf679777..53cca7ee03 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRenderer.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRenderer.java @@ -119,12 +119,17 @@ protected void drawYLabels(Canvas c, float fixedPosition, float[] positions, flo ? mYAxis.mEntryCount : (mYAxis.mEntryCount - 1); + float xOffset = mYAxis.getLabelXOffset(); + // draw for (int i = from; i < to; i++) { String text = mYAxis.getFormattedLabel(i); - c.drawText(text, fixedPosition, positions[i * 2 + 1] + offset, mAxisLabelPaint); + c.drawText(text, + fixedPosition + xOffset, + positions[i * 2 + 1] + offset, + mAxisLabelPaint); } } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererHorizontalBarChart.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererHorizontalBarChart.java index 71275b03c3..fedf8054a1 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererHorizontalBarChart.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererHorizontalBarChart.java @@ -142,11 +142,16 @@ protected void drawYLabels(Canvas c, float fixedPosition, float[] positions, flo ? mYAxis.mEntryCount : (mYAxis.mEntryCount - 1); + float xOffset = mYAxis.getLabelXOffset(); + for (int i = from; i < to; i++) { String text = mYAxis.getFormattedLabel(i); - c.drawText(text, positions[i * 2], fixedPosition - offset, mAxisLabelPaint); + c.drawText(text, + positions[i * 2], + fixedPosition - offset + xOffset, + mAxisLabelPaint); } } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererRadarChart.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererRadarChart.java index ee7392e928..f7b1ad9e87 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererRadarChart.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererRadarChart.java @@ -52,9 +52,11 @@ protected void computeAxisValues(float min, float max) { double intervalMagnitude = Utils.roundToNextSignificant(Math.pow(10, (int) Math.log10(interval))); int intervalSigDigit = (int) (interval / intervalMagnitude); if (intervalSigDigit > 5) { - // Use one order of magnitude higher, to avoid intervals like 0.9 or - // 90 - interval = Math.floor(10 * intervalMagnitude); + // Use one order of magnitude higher, to avoid intervals like 0.9 or 90 + // if it's 0.0 after floor(), we use the old value + interval = Math.floor(10.0 * intervalMagnitude) == 0.0 + ? interval + : Math.floor(10.0 * intervalMagnitude); } boolean centeringEnabled = mAxis.isCenterAxisLabelsEnabled(); @@ -161,6 +163,8 @@ public void renderAxisLabels(Canvas c) { ? mYAxis.mEntryCount : (mYAxis.mEntryCount - 1); + float xOffset = mYAxis.getLabelXOffset(); + for (int j = from; j < to; j++) { float r = (mYAxis.mEntries[j] - mYAxis.mAxisMinimum) * factor; @@ -169,7 +173,7 @@ public void renderAxisLabels(Canvas c) { String label = mYAxis.getFormattedLabel(j); - c.drawText(label, pOut.x + 10, pOut.y, mAxisLabelPaint); + c.drawText(label, pOut.x + xOffset, pOut.y, mAxisLabelPaint); } MPPointF.recycleInstance(center); MPPointF.recycleInstance(pOut);