Work on line approximation

This commit is contained in:
Philipp Jahoda 2016-06-07 21:48:26 +02:00
parent 1ff1c8fcfa
commit 42ceaa9aa2
4 changed files with 129 additions and 268 deletions

View file

@ -18,8 +18,6 @@ import android.view.MotionEvent;
import com.github.mikephil.charting.components.XAxis.XAxisPosition;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.components.YAxis.AxisDependency;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.data.BarLineScatterCandleBubbleData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.highlight.ChartHighlighter;

View file

@ -28,10 +28,10 @@ import com.github.mikephil.charting.renderer.CombinedChartRenderer;
public class CombinedChart extends BarLineChartBase<CombinedData> implements LineDataProvider,
BarDataProvider, ScatterDataProvider, CandleDataProvider, BubbleDataProvider {
/**
* flag that enables or disables the highlighting arrow
*/
private boolean mDrawHighlightArrow = false;
// /**
// * flag that enables or disables the highlighting arrow
// */
// private boolean mDrawHighlightArrow = false;
/**
* if set to true, all values are drawn above their bars, instead of below
@ -167,14 +167,14 @@ public class CombinedChart extends BarLineChartBase<CombinedData> implements Lin
return mDrawValueAboveBar;
}
/**
* set this to true to draw the highlightning arrow
*
* @param enabled
*/
public void setDrawHighlightArrow(boolean enabled) {
mDrawHighlightArrow = enabled;
}
// /**
// * set this to true to draw the highlightning arrow
// *
// * @param enabled
// */
// public void setDrawHighlightArrow(boolean enabled) {
// mDrawHighlightArrow = enabled;
// }
/**
* If set to true, all values are drawn above their bars, instead of below

View file

@ -1,282 +1,102 @@
package com.github.mikephil.charting.data.filter;
import com.github.mikephil.charting.data.Entry;
import android.annotation.TargetApi;
import android.os.Build;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
/**
* Implemented according to Wiki-Pseudocode {@link}
* http://en.wikipedia.org/wiki/Ramer<EFBFBD>Douglas<EFBFBD>Peucker_algorithm
*
*
* @author Philipp Baldauf & Phliipp Jahoda
*/
public class Approximator {
/** the type of filtering algorithm to use */
private ApproximatorType mType = ApproximatorType.DOUGLAS_PEUCKER;
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public float[] reduceWithDouglasPeucker(float[] points, float tolerance) {
/** the tolerance to be filtered with */
private double mTolerance = 0;
int greatestIndex = 0;
float greatestDistance = 0f;
private float mScaleRatio = 1f;
private float mDeltaRatio = 1f;
Line line = new Line(points[0], points[1], points[points.length - 2], points[points.length - 1]);
/**
* array that contains "true" on all indices that will be kept after
* filtering
*/
private boolean[] keep;
for (int i = 2; i < points.length - 2; i += 2) {
/** enums for the different types of filtering algorithms */
public enum ApproximatorType {
NONE, DOUGLAS_PEUCKER
float distance = line.distance(points[i], points[i + 1]);
if (distance > greatestDistance) {
greatestDistance = distance;
greatestIndex = i;
}
}
if (greatestDistance > tolerance) {
float[] reduced1 = reduceWithDouglasPeucker(Arrays.copyOfRange(points, 0, greatestIndex + 2), tolerance);
float[] reduced2 = reduceWithDouglasPeucker(Arrays.copyOfRange(points, greatestIndex, points.length),
tolerance);
float[] result1 = reduced1;
float[] result2 = Arrays.copyOfRange(reduced2, 2, reduced2.length);
return concat(result1, result2);
} else {
return line.getPoints();
}
}
/**
* Initializes the approximator with type NONE
*/
public Approximator() {
this.mType = ApproximatorType.NONE;
}
/**
* Initializes the approximator with the given type and tolerance. If
* toleranec <= 0, no filtering will be done.
*
* @param type
*/
public Approximator(ApproximatorType type, double tolerance) {
setup(type, tolerance);
}
/**
* sets type and tolerance, if tolerance <= 0, no filtering will be done
*
* @param type
* @param tolerance
*/
public void setup(ApproximatorType type, double tolerance) {
mType = type;
mTolerance = tolerance;
}
/**
* sets the tolerance for the Approximator. When using the
* Douglas-Peucker-Algorithm, the tolerance is an angle in degrees, that
* will trigger the filtering.
*/
public void setTolerance(double tolerance) {
mTolerance = tolerance;
}
/**
* Sets the filtering algorithm that should be used.
*
* @param type
*/
public void setType(ApproximatorType type) {
this.mType = type;
}
/**
* Sets the ratios for x- and y-axis, as well as the ratio of the scale
* levels
*
* @param deltaRatio
* @param scaleRatio
*/
public void setRatios(float deltaRatio, float scaleRatio) {
mDeltaRatio = deltaRatio;
mScaleRatio = scaleRatio;
}
/**
* Filters according to type. Uses the pre set set tolerance
*
* @param points the points to filter
* Combine arrays.
*
* @param arrays
* @return
*/
public List<Entry> filter(List<Entry> points) {
return filter(points, mTolerance);
float[] concat(float[]... arrays) {
int length = 0;
for (float[] array : arrays) {
length += array.length;
}
float[] result = new float[length];
int pos = 0;
for (float[] array : arrays) {
for (float element : array) {
result[pos] = element;
pos++;
}
}
return result;
}
/**
* Filters according to type.
*
* @param points the points to filter
* @param tolerance the angle in degrees that will trigger the filtering
* @return
*/
public List<Entry> filter(List<Entry> points, double tolerance) {
private class Line {
if (tolerance <= 0)
private float[] points;
private float sxey;
private float exsy;
private float dx;
private float dy;
private float length;
public Line(float x1, float y1, float x2, float y2) {
dx = x1 - x2;
dy = y1 - y2;
sxey = x1 * y2;
exsy = x2 * y1;
length = (float) Math.sqrt(dx * dx + dy * dy);
points = new float[]{x1, y1, x2, y2};
}
float distance(float x, float y) {
return Math.abs(dy * x - dx * y + sxey - exsy) / length;
}
public float[] getPoints() {
return points;
keep = new boolean[points.size()];
switch (mType) {
case DOUGLAS_PEUCKER:
return reduceWithDouglasPeuker(points, tolerance);
case NONE:
return points;
default:
return points;
}
}
/**
* uses the douglas peuker algorithm to reduce the given List of
* entries
*
* @param entries
* @param epsilon
* @return
*/
private List<Entry> reduceWithDouglasPeuker(List<Entry> entries, double epsilon) {
// if a shape has 2 or less points it cannot be reduced
if (epsilon <= 0 || entries.size() < 3) {
return entries;
}
// first and last always stay
keep[0] = true;
keep[entries.size() - 1] = true;
// first and last entry are entry point to recursion
algorithmDouglasPeucker(entries, epsilon, 0, entries.size() - 1);
// create a new array with series, only take the kept ones
List<Entry> reducedEntries = new ArrayList<Entry>();
for (int i = 0; i < entries.size(); i++) {
if (keep[i]) {
Entry curEntry = entries.get(i);
reducedEntries.add(new Entry(curEntry.getY(), curEntry.getX()));
}
}
return reducedEntries;
}
/**
* apply the Douglas-Peucker-Reduction to an List of Entry with a given
* epsilon (tolerance)
*
* @param entries
* @param epsilon as y-value
* @param start
* @param end
*/
private void algorithmDouglasPeucker(List<Entry> entries, double epsilon, int start,
int end) {
if (end <= start + 1) {
// recursion finished
return;
}
// find the greatest distance between start and endpoint
int maxDistIndex = 0;
double distMax = 0;
Entry firstEntry = entries.get(start);
Entry lastEntry = entries.get(end);
for (int i = start + 1; i < end; i++) {
double dist = calcAngleBetweenLines(firstEntry, lastEntry, firstEntry, entries.get(i));
// keep the point with the greatest distance
if (dist > distMax) {
distMax = dist;
maxDistIndex = i;
}
}
// Log.i("maxangle", "" + distMax);
if (distMax > epsilon) {
// keep max dist point
keep[maxDistIndex] = true;
// recursive call
algorithmDouglasPeucker(entries, epsilon, start, maxDistIndex);
algorithmDouglasPeucker(entries, epsilon, maxDistIndex, end);
} // else don't keep the point...
}
/**
* calculate the distance between a line between two entries and an entry
* (point)
*
* @param startEntry line startpoint
* @param endEntry line endpoint
* @param entryPoint the point to which the distance is measured from the
* line
* @return
*/
public double calcPointToLineDistance(Entry startEntry, Entry endEntry, Entry entryPoint) {
float xDiffEndStart = (float) endEntry.getX() - (float) startEntry.getX();
float xDiffEntryStart = (float) entryPoint.getX() - (float) startEntry.getX();
double normalLength = Math.sqrt((xDiffEndStart)
* (xDiffEndStart)
+ (endEntry.getY() - startEntry.getY())
* (endEntry.getY() - startEntry.getY()));
return Math.abs((xDiffEntryStart)
* (endEntry.getY() - startEntry.getY())
- (entryPoint.getY() - startEntry.getY())
* (xDiffEndStart))
/ normalLength;
}
/**
* Calculates the angle between two given lines. The provided Entry objects
* mark the starting and end points of the lines.
*
* @param start1
* @param end1
* @param start2
* @param end2
* @return
*/
public double calcAngleBetweenLines(Entry start1, Entry end1, Entry start2, Entry end2) {
double angle1 = calcAngleWithRatios(start1, end1);
double angle2 = calcAngleWithRatios(start2, end2);
return Math.abs(angle1 - angle2);
}
/**
* calculates the angle between two Entries (points) in the chart taking
* ratios into consideration
*
* @param p1
* @param p2
* @return
*/
public double calcAngleWithRatios(Entry p1, Entry p2) {
float dx = p2.getX() * mDeltaRatio - p1.getX() * mDeltaRatio;
float dy = p2.getY() * mScaleRatio - p1.getY() * mScaleRatio;
double angle = Math.atan2(dy, dx) * 180.0 / Math.PI;
return angle;
}
/**
* calculates the angle between two Entries (points) in the chart
*
* @param p1
* @param p2
* @return
*/
public double calcAngle(Entry p1, Entry p2) {
float dx = p2.getX() - p1.getX();
float dy = p2.getY() - p1.getY();
double angle = Math.atan2(dy, dx) * 180.0 / Math.PI;
return angle;
}
}

View file

@ -0,0 +1,43 @@
package com.github.mikephil.charting.test;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.filter.Approximator;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static junit.framework.Assert.assertEquals;
/**
* Created by philipp on 07/06/16.
*/
public class ApproximatorTest {
@Test
public void testApproximation() {
float[] points = new float[]{
10, 20,
20, 30,
25, 25,
30, 28,
31, 31,
33, 33,
40, 40,
44, 40,
48, 23,
50, 20,
55, 20,
60, 25};
assertEquals(24, points.length);
Approximator a = new Approximator();
float[] reduced = a.reduceWithDouglasPeucker(points, 2);
assertEquals(18, reduced.length);
}
}