Added new chart-type. The ScatterChart.

This commit is contained in:
Philipp Jahoda 2014-06-03 22:00:40 +02:00
parent b64211610f
commit 213987e864
9 changed files with 562 additions and 2 deletions

View file

@ -30,6 +30,7 @@
<activity android:name="MultiLineChartActivity"></activity>
<activity android:name="BarChartActivityMultiDataset"></activity>
<activity android:name="DrawChartActivity"></activity>
<activity android:name="ScatterChartActivity"></activity>
</application>
</manifest>

View file

@ -72,6 +72,13 @@
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:text="View on GitHub" />
<Button
android:id="@+id/button9"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:text="ScatterChart" />
</LinearLayout>
</ScrollView>

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.github.mikephil.charting.charts.ScatterChart
android:id="@+id/chart1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/seekBar1" />
<SeekBar
android:id="@+id/seekBar2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_margin="8dp"
android:layout_toLeftOf="@+id/tvYMax"
android:layout_marginRight="5dp"
android:max="2000"
android:paddingBottom="12dp" />
<SeekBar
android:id="@+id/seekBar1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/seekBar2"
android:layout_margin="8dp"
android:layout_marginBottom="35dp"
android:layout_toLeftOf="@+id/tvXMax"
android:layout_marginRight="5dp"
android:max="500"
android:paddingBottom="12dp" />
<TextView
android:id="@+id/tvXMax"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/seekBar1"
android:layout_alignParentRight="true"
android:text="500"
android:layout_marginBottom="15dp"
android:layout_marginRight="10dp"
android:gravity="right"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/tvYMax"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/seekBar2"
android:layout_alignParentRight="true"
android:text="500"
android:layout_marginBottom="15dp"
android:layout_marginRight="10dp"
android:gravity="right"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/actionToggleRound"
android:title="Toggle Round">
</item>
<item
android:id="@+id/actionToggleValues"
android:title="Toggle Values">
</item>
<item
android:id="@+id/actionToggleHighlight"
android:title="Toggle Highlight">
</item>
<item
android:id="@+id/actionToggleStartzero"
android:title="Toggle StartZero">
</item>
<item
android:id="@+id/actionToggleAdjustXLegend"
android:title="Toggle AdjustXLegend">
</item>
<item
android:id="@+id/actionToggleFilter"
android:title="Toggle filter">
</item>
<item
android:id="@+id/actionSave"
android:title="Save to Gallery">
</item>
</menu>

View file

@ -25,6 +25,7 @@ public class MainActivity extends Activity implements OnClickListener {
Button btn6 = (Button) findViewById(R.id.button6);
Button btn7 = (Button) findViewById(R.id.button7);
Button btn8 = (Button) findViewById(R.id.button8);
Button btn9 = (Button) findViewById(R.id.button9);
btn1.setOnClickListener(this);
btn2.setOnClickListener(this);
btn3.setOnClickListener(this);
@ -33,6 +34,7 @@ public class MainActivity extends Activity implements OnClickListener {
btn6.setOnClickListener(this);
btn7.setOnClickListener(this);
btn8.setOnClickListener(this);
btn9.setOnClickListener(this);
}
@Override
@ -73,6 +75,10 @@ public class MainActivity extends Activity implements OnClickListener {
i = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/PhilJay/MPAndroidChart"));
startActivity(i);
break;
case R.id.button9:
i = new Intent(this, ScatterChartActivity.class);
startActivity(i);
break;
}
}
}

View file

@ -0,0 +1,218 @@
package com.example.mpchartexample;
import android.app.Activity;
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 com.github.mikephil.charting.charts.ScatterChart;
import com.github.mikephil.charting.data.ChartData;
import com.github.mikephil.charting.data.DataSet;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.filter.Approximator.ApproximatorType;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
import com.github.mikephil.charting.utils.ColorTemplate;
import com.github.mikephil.charting.utils.Highlight;
import java.util.ArrayList;
public class ScatterChartActivity extends Activity implements OnSeekBarChangeListener,
OnChartValueSelectedListener {
private ScatterChart 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_scatterchart);
tvX = (TextView) findViewById(R.id.tvXMax);
tvY = (TextView) findViewById(R.id.tvYMax);
mSeekBarX = (SeekBar) findViewById(R.id.seekBar1);
mSeekBarX.setOnSeekBarChangeListener(this);
mSeekBarY = (SeekBar) findViewById(R.id.seekBar2);
mSeekBarY.setOnSeekBarChangeListener(this);
// create a color template for one dataset with only one color
ColorTemplate ct = new ColorTemplate();
ct.addColorsForDataSets(new int[] {
R.color.colorful_1, R.color.colorful_2, R.color.colorful_3
}, this);
mChart = (ScatterChart) findViewById(R.id.chart1);
mChart.setOnChartValueSelectedListener(this);
mChart.setColorTemplate(ct);
// mChart.setDrawFilled(true);
// mChart.setRoundedYLegend(false);
// mChart.setStartAtZero(true);
// mChart.setSpacePercent(20, 10);
mChart.setYLegendCount(6);
mChart.setTouchEnabled(true);
mChart.setHighlightEnabled(true);
// highlight index 2 and 6 in dataset 0
// mChart.highlightValues(new Highlight[] {new Highlight(2, 0), new
// Highlight(6, 0)});
mChart.setDragEnabled(true);
mChart.setMaxVisibleValueCount(300);
mSeekBarX.setProgress(45);
mSeekBarY.setProgress(100);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.scatter, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.actionToggleValues: {
if (mChart.isDrawYValuesEnabled())
mChart.setDrawYValues(false);
else
mChart.setDrawYValues(true);
mChart.invalidate();
break;
}
case R.id.actionToggleHighlight: {
if (mChart.isHighlightEnabled())
mChart.setHighlightEnabled(false);
else
mChart.setHighlightEnabled(true);
mChart.invalidate();
break;
}
case R.id.actionToggleStartzero: {
if (mChart.isStartAtZeroEnabled())
mChart.setStartAtZero(false);
else
mChart.setStartAtZero(true);
mChart.invalidate();
break;
}
case R.id.actionToggleAdjustXLegend: {
if (mChart.isAdjustXLegendEnabled())
mChart.setAdjustXLegend(false);
else
mChart.setAdjustXLegend(true);
mChart.invalidate();
break;
}
case R.id.actionToggleFilter:
if (mChart.isFilterSet()) {
mChart.setFilter(ApproximatorType.NONE, 0);
} else {
mChart.setFilter(ApproximatorType.DOUGLAS_PEUCKER, 2);
}
mChart.invalidate();
break;
case R.id.actionSave: {
// mChart.saveToGallery("title"+System.currentTimeMillis());
mChart.saveToPath("title" + System.currentTimeMillis(), "");
break;
}
}
return true;
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
ArrayList<String> xVals = new ArrayList<String>();
for (int i = 0; i < mSeekBarX.getProgress(); i++) {
xVals.add((i) + "");
}
ArrayList<Entry> yVals1 = new ArrayList<Entry>();
ArrayList<Entry> yVals2 = new ArrayList<Entry>();
ArrayList<Entry> yVals3 = new ArrayList<Entry>();
for (int i = 0; i < mSeekBarX.getProgress(); i++) {
float mult = (mSeekBarY.getProgress() + 1);
float val = (float) (Math.random() * mult * 0.1) + 3;// + (float)
// ((mult *
// 0.1) / 10);
yVals1.add(new Entry(val, i));
}
for (int i = 0; i < mSeekBarX.getProgress(); i++) {
float mult = (mSeekBarY.getProgress() + 1);
float val = (float) (Math.random() * mult * 0.1) + 3;// + (float)
// ((mult *
// 0.1) / 10);
yVals2.add(new Entry(val, i));
}
for (int i = 0; i < mSeekBarX.getProgress(); i++) {
float mult = (mSeekBarY.getProgress() + 1);
float val = (float) (Math.random() * mult * 0.1) + 3;// + (float)
// ((mult *
// 0.1) / 10);
yVals3.add(new Entry(val, i));
}
tvX.setText("" + (mSeekBarX.getProgress() + 1));
tvY.setText("" + (mSeekBarY.getProgress() / 10));
// create a dataset and give it a type (0)
DataSet set1 = new DataSet(yVals1, 0);
DataSet set2 = new DataSet(yVals2, 1);
DataSet set3 = new DataSet(yVals3, 2);
ArrayList<DataSet> dataSets = new ArrayList<DataSet>();
dataSets.add(set1); // add the datasets
dataSets.add(set2);
dataSets.add(set3);
// create a data object with the datasets
ChartData data = new ChartData(xVals, dataSets);
mChart.setData(data);
mChart.invalidate();
}
@Override
public void onValuesSelected(Entry[] values, Highlight[] highlights) {
Log.i("VALS SELECTED",
"Value: " + values[0].getVal() + ", xIndex: " + highlights[0].getXIndex()
+ ", DataSet index: " + highlights[0].getDataSetIndex());
}
@Override
public void onNothingSelected() {
// TODO Auto-generated method stub
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
}

View file

@ -976,7 +976,7 @@ public abstract class BarLineChartBase extends Chart {
Log.i(LOG_TAG, "touchindex x: " + xTouchVal + ", touchindex y: " + yTouchVal);
// touch out of chart
if (this instanceof LineChart && (xTouchVal < 0 || xTouchVal > mDeltaX))
if ((this instanceof LineChart || this instanceof ScatterChart) && (xTouchVal < 0 || xTouchVal > mDeltaX))
return null;
if (this instanceof BarChart && (xTouchVal < 0 || xTouchVal > mDeltaX + 1))
return null;
@ -985,7 +985,7 @@ public abstract class BarLineChartBase extends Chart {
int dataSetIndex = 0; // index of the DataSet inside the ChartData
// object
if (this instanceof LineChart) {
if (this instanceof LineChart || this instanceof ScatterChart) {
// check if we are more than half of a x-value or not
if (xTouchVal - base > 0.5) {

View file

@ -0,0 +1,235 @@
package com.github.mikephil.charting.charts;
import android.content.Context;
import android.graphics.Path;
import android.util.AttributeSet;
import com.github.mikephil.charting.data.DataSet;
import com.github.mikephil.charting.data.Entry;
import java.util.ArrayList;
public class ScatterChart extends BarLineChartBase {
/** enum that defines the shape that is drawn where the values are */
public enum ScatterShape {
CROSS, TRIANGLE, CIRCLE, SQUARE, CUSTOM
}
/**
* custom path object the user can provide that is drawn where the values
* are at
*/
private Path mCustomScatterPath = null;
/** the shape that is drawn onto the chart where the values are at */
private ScatterShape mScatterShape = ScatterShape.SQUARE;
/** the size the scattershape will have, in screen pixels */
private float mShapeSize = 12f;
public ScatterChart(Context context) {
super(context);
}
public ScatterChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScatterChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void drawData() {
ArrayList<DataSet> dataSets = mData.getDataSets();
for (int i = 0; i < mData.getDataSetCount(); i++) {
DataSet dataSet = dataSets.get(i);
ArrayList<Entry> entries = dataSet.getYVals();
float[] pos = generateTransformedValues(entries, 0f);
// Get the colors for the DataSet at the current index. If the index
// is out of bounds, reuse DataSet colors.
ArrayList<Integer> colors = mCt.getDataSetColors(i % mCt.getColors().size());
for (int j = 0; j < pos.length; j += 2) {
// Set the color for the currently drawn value. If the index is
// out of bounds, reuse colors.
mRenderPaint.setColor(colors.get(j % colors.size()));
if (isOffContentRight(pos[j]))
break;
float shapeHalf = mShapeSize / 2f;
// make sure the lines don't do shitty things outside bounds
if (j != 0 && isOffContentLeft(pos[j - 1])
&& isOffContentTop(pos[j + 1])
&& isOffContentBottom(pos[j + 1]))
continue;
if (mScatterShape == ScatterShape.SQUARE) {
mDrawCanvas.drawRect(pos[j] - shapeHalf, pos[j + 1] - shapeHalf, pos[j] + shapeHalf, pos[j + 1]
+ shapeHalf, mRenderPaint);
} else if (mScatterShape == ScatterShape.CIRCLE) {
mDrawCanvas.drawCircle(pos[j], pos[j + 1], mShapeSize / 2f, mRenderPaint);
} else if (mScatterShape == ScatterShape.CROSS) {
mDrawCanvas.drawLine(pos[j] - shapeHalf, pos[j + 1], pos[j] + shapeHalf,
pos[j + 1], mRenderPaint);
mDrawCanvas.drawLine(pos[j], pos[j + 1] - shapeHalf, pos[j], pos[j + 1]
+ shapeHalf, mRenderPaint);
} else if (mScatterShape == ScatterShape.TRIANGLE) {
Path tri = new Path();
tri.moveTo(pos[j], pos[j + 1] - shapeHalf);
tri.lineTo(pos[j] + shapeHalf, pos[j + 1] + shapeHalf);
tri.lineTo(pos[j] - shapeHalf, pos[j + 1] + shapeHalf);
tri.close();
mDrawCanvas.drawPath(tri, mRenderPaint);
} else if (mScatterShape == ScatterShape.CUSTOM) {
if (mCustomScatterPath == null)
return;
// transform the provided custom path
transformPath(mCustomScatterPath);
mDrawCanvas.drawPath(mCustomScatterPath, mRenderPaint);
}
}
}
}
@Override
protected void drawValues() {
// if values are drawn
if (mDrawYValues && mData.getYValCount() < mMaxVisibleCount * mScaleX) {
ArrayList<DataSet> dataSets = mData.getDataSets();
for (int i = 0; i < mData.getDataSetCount(); i++) {
DataSet dataSet = dataSets.get(i);
ArrayList<Entry> entries = dataSet.getYVals();
float[] positions = generateTransformedValues(entries, 0f);
for (int j = 0; j < positions.length; j += 2) {
if (isOffContentRight(positions[j]))
break;
if (isOffContentLeft(positions[j]) || isOffContentTop(positions[j + 1])
|| isOffContentBottom(positions[j + 1]))
continue;
float val = entries.get(j / 2).getVal();
if (mDrawUnitInChart) {
mDrawCanvas.drawText(mFormatValue.format(val) + mUnit, positions[j],
positions[j + 1] - mShapeSize, mValuePaint);
} else {
mDrawCanvas.drawText(mFormatValue.format(val), positions[j],
positions[j + 1] - mShapeSize,
mValuePaint);
}
}
}
}
}
@Override
protected void drawHighlights() {
// if there are values to highlight and highlighnting is enabled, do it
if (mHighlightEnabled && valuesToHighlight()) {
for (int i = 0; i < mIndicesToHightlight.length; i++) {
DataSet set = getDataSetByIndex(mIndicesToHightlight[i].getDataSetIndex());
int xIndex = mIndicesToHightlight[i].getXIndex(); // get the
// x-position
float y = set.getYValForXIndex(xIndex); // get the y-position
float[] pts = new float[] {
xIndex, mYChartMax, xIndex, mYChartMin, 0, y, mDeltaX, y
};
transformPointArray(pts);
// draw the highlight lines
mDrawCanvas.drawLines(pts, mHighlightPaint);
}
}
}
@Override
protected void drawAdditional() {
// TODO Auto-generated method stub
}
/**
* Sets the size the drawn scattershape will have. This only applies for non
* cusom shapes. Default 12f
*
* @param size
*/
public void setScatterShapeSize(float size) {
mShapeSize = size;
}
/**
* returns the currently set scatter shape size
*
* @return
*/
public float getScatterShapeSize() {
return mShapeSize;
}
/**
* Set the shape that is drawn on the position where the values are at. If
* "CUSTOM" is chosen, you need to call setCustomScatterShape(...) and
* provide a path object that is drawn.
*
* @param shape
*/
public void setScatterShape(ScatterShape shape) {
mScatterShape = shape;
}
/**
* returns the currently set scatter shape
*
* @return
*/
public ScatterShape getScatterShape() {
return mScatterShape;
}
/**
* Sets a path object as the shape to be drawn where the values are at. Do
* not forget to call setScatterShape(ScatterShape.CUSTOM).
*
* @param shape
*/
public void setCustomScatterShape(Path shape) {
mCustomScatterPath = shape;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB