Work on line approximation
This commit is contained in:
parent
1ff1c8fcfa
commit
42ceaa9aa2
4 changed files with 129 additions and 268 deletions
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue