package com.code44.finance.graphs.line; import android.graphics.Path; import android.graphics.PointF; import android.util.Pair; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class SmoothPathMaker implements PathMaker { @Override public Path makePath(List<PointF> points) { final Path path = new Path(); final List<List<PointF>> continuousCurves = prepareListOfContinuousCurves(points); for (List<PointF> curve : continuousCurves) { addCurveToPath(path, curve); } return path; } private void addCurveToPath(Path outPath, List<PointF> curve) { List<Pair<PointF, PointF>> bezierControlPoints = Collections.emptyList(); if (curve.size() > 1) { bezierControlPoints = getBezierControlPoints(curve); } for (int i = 0, size = curve.size(); i < size; i++) { final PointF point = curve.get(i); if (i == 0) { outPath.moveTo(point.x, point.y); } else { final Pair<PointF, PointF> bezierControlPointPair = bezierControlPoints.get(i - 1); outPath.cubicTo(bezierControlPointPair.first.x, bezierControlPointPair.first.y, bezierControlPointPair.second.x, bezierControlPointPair.second.y, point.x, point.y); } } } private List<List<PointF>> prepareListOfContinuousCurves(List<PointF> points) { final List<List<PointF>> continuousCurves = new ArrayList<>(); List<PointF> curve = null; for (PointF point : points) { if (point == null) { if (curve != null) { continuousCurves.add(curve); } curve = null; } else { if (curve == null) { curve = new ArrayList<>(); } curve.add(point); } } if (curve != null) { continuousCurves.add(curve); } return continuousCurves; } private List<Pair<PointF, PointF>> getBezierControlPoints(List<PointF> knots) { if (knots == null) { throw new NullPointerException("Knots cannot be null."); } int controlPointsSize = knots.size() - 1; if (controlPointsSize < 1) { throw new IllegalArgumentException("At least two knot points required."); } // Special case: Bezier curve should be a straight line. if (controlPointsSize == 1) { final PointF firstControlPoint = new PointF((2 * knots.get(0).x + knots.get(1).x) / 3, (2 * knots.get(0).y + knots.get(1).y) / 3); final PointF secondControlPoint = new PointF(2 * firstControlPoint.x - knots.get(0).x, 2 * firstControlPoint.y - knots.get(0).y); final List<Pair<PointF, PointF>> bezierControlPoints = new ArrayList<>(); bezierControlPoints.add(Pair.create(firstControlPoint, secondControlPoint)); return bezierControlPoints; } // Calculate first Bezier control points // Right hand side vector float[] rhs = new float[controlPointsSize]; // Set right hand side X values for (int i = 1; i < controlPointsSize - 1; ++i) { rhs[i] = 4 * knots.get(i).x + 2 * knots.get(i + 1).x; } rhs[0] = knots.get(0).x + 2 * knots.get(1).x; rhs[controlPointsSize - 1] = (8 * knots.get(controlPointsSize - 1).x + knots.get(controlPointsSize).x) / 2.0f; // Get first control points X-values float[] x = getFirstControlPoints(rhs); // Set right hand side Y values for (int i = 1; i < controlPointsSize - 1; ++i) { rhs[i] = 4 * knots.get(i).y + 2 * knots.get(i + 1).y; } rhs[0] = knots.get(0).y + 2 * knots.get(1).y; rhs[controlPointsSize - 1] = (8 * knots.get(controlPointsSize - 1).y + knots.get(controlPointsSize).y) / 2.0f; // Get first control points Y-values float[] y = getFirstControlPoints(rhs); // Fill output arrays. //firstControlPoints = new PointF[n]; //secondControlPoints = new PointF[n]; final List<Pair<PointF, PointF>> bezierControlPoints = new ArrayList<>(); for (int i = 0; i < controlPointsSize; ++i) { // First control point final PointF firstControlPoint = new PointF(x[i], y[i]); // Second control point final PointF secondControlPoint; if (i < controlPointsSize - 1) { secondControlPoint = new PointF(2 * knots.get(i + 1).x - x[i + 1], 2 * knots.get(i + 1).y - y[i + 1]); } else { secondControlPoint = new PointF((knots.get(controlPointsSize).x + x[controlPointsSize - 1]) / 2, (knots.get(controlPointsSize).y + y[controlPointsSize - 1]) / 2); } bezierControlPoints.add(Pair.create(firstControlPoint, secondControlPoint)); } return bezierControlPoints; } private float[] getFirstControlPoints(float[] rhs) { int n = rhs.length; float[] x = new float[n]; // Solution vector. float[] tmp = new float[n]; // Temp workspace. float b = 2.0f; x[0] = rhs[0] / b; for (int i = 1; i < n; i++) // Decomposition and forward substitution. { tmp[i] = 1 / b; b = (i < n - 1 ? 4.0f : 3.5f) - tmp[i]; x[i] = (rhs[i] - x[i - 1]) / b; } for (int i = 1; i < n; i++) { x[n - i - 1] -= tmp[n - i] * x[n - i]; // Backsubstitution. } return x; } }