How to make line with rounded (smooth) corners with AndroidPlot

南楼画角 提交于 2019-12-28 05:35:36

问题


I have a small problem with ploting my graph. On a picture below is what I have already done.


The graph should represent the actual signal strength of available Wi-Fi network(s). It's a simple XYPlot here data are represented with SimpleXYSeries (values are dynamically created).

Here is a little snippet of code (only for example):

plot = (XYPlot) findViewById(R.id.simplexyPlot);
series1 = new SimpleXYSeries(Arrays.asList(series1Numbers),
SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Link 1");
f1 = new LineAndPointFormatter(color.getColor(), null,
Color.argb(60, color.getRed(), color.getGreen(), color.getBlue()), null);
plot.addSeries(series1, f1);

The example in the picture is a dynamic simulation of dB changes. Everything works, I guess, correctly, but what I want to achieve is to have line with "rounded" corners (see the picture to see what I mean).

I already tried to customize LineFormatter:

f1.getFillPaint().setStrokeJoin(Join.ROUND);
f1.getFillPaint().setStrokeWidth(8);

But this didn't work as expected.

Note: The Wifi Analyzer application has a similar graph and its graph has the rounded corners I want. It looks like this:


回答1:


You can use Path.cubicTo() method. It draws a line using cubic spline algorithm which results in the smoothing effect you want.

Checkout the answer to a similar question here, where a guy is talking about cubic splines. There is a short algorithm showing how to calculate input parameters for Path.cubicTo() method. You can play with divider values to achieve required smoothness. For example, in the picture below I divided by 5 instead of 3. Hope this helps.

I have spent some time and implemented a SplineLineAndPointFormatter class, which does the stuff you need in androidplot library. It uses same technics. Here is how androidplot example applications looks like. You just need to use it instead of LineAndPointFormatter.

Here is code example and the class I wrote.

f1 = new SplineLineAndPointFormatter(color.getColor(), null, 
      Color.argb(60, color.getRed(), color.getGreen(), color.getBlue()), null);
plot.addSeries(series1, f1);

Here is the class doing the magic. It is based on version 0.6.1 of androidplot library.

package com.androidplot.xy;

import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;

import com.androidplot.ui.SeriesRenderer;
import com.androidplot.util.ValPixConverter;

public class SplineLineAndPointFormatter extends LineAndPointFormatter {

    public SplineLineAndPointFormatter() { }

    public SplineLineAndPointFormatter(Integer lineColor, Integer vertexColor, Integer fillColor) {
        super(lineColor, vertexColor, fillColor, null);
    }

    public SplineLineAndPointFormatter(Integer lineColor, Integer vertexColor, Integer fillColor, FillDirection fillDir) {
        super(lineColor, vertexColor, fillColor, null, fillDir);
    }

    @Override
    public Class<? extends SeriesRenderer> getRendererClass() {
        return SplineLineAndPointRenderer.class;
    }

    @Override
    public SeriesRenderer getRendererInstance(XYPlot plot) {
        return new SplineLineAndPointRenderer(plot);
    }

    public static class SplineLineAndPointRenderer extends LineAndPointRenderer<BezierLineAndPointFormatter> {

        static class Point {
            public float x, y, dx, dy;
            public Point(PointF pf) { x = pf.x; y = pf.y; }
        }

        private Point prev, point, next;
        private int pointsCounter;

        public SplineLineAndPointRenderer(XYPlot plot) {
            super(plot);
        }

        @Override
        protected void appendToPath(Path path, final PointF thisPoint, PointF lastPoint) {
            pointsCounter--;

            if (point == null) {
                point = new Point(thisPoint);
                point.dx = ((point.x - prev.x) / 5);
                point.dy = ((point.y - prev.y) / 5);
                return;

            } else if (next == null) {
                next = new Point(thisPoint);
            } else {
                prev = point;
                point = next;
                next = new Point(thisPoint);
            }

            point.dx = ((next.x - prev.x) / 5);
            point.dy = ((next.y - prev.y) / 5);
            path.cubicTo(prev.x + prev.dx, prev.y + prev.dy, point.x - point.dx, point.y - point.dy, point.x, point.y);

            if (pointsCounter == 1) { // last point
                next.dx = ((next.x - point.x) / 5);
                next.dy = ((next.y - point.y) / 5);
                path.cubicTo(point.x + point.dx, point.y + point.dy, next.x - next.dx, next.y - next.dy, next.x, next.y);
            }

        }

        @Override
        protected void drawSeries(Canvas canvas, RectF plotArea, XYSeries series, LineAndPointFormatter formatter) {

            Number y = series.getY(0);
            Number x = series.getX(0);
            if (x == null || y == null) throw new IllegalArgumentException("no null values in xyseries permitted");

            XYPlot p = getPlot();
            PointF thisPoint = ValPixConverter.valToPix(x, y, plotArea,
                    p.getCalculatedMinX(), p.getCalculatedMaxX(), p.getCalculatedMinY(), p.getCalculatedMaxY());

            prev = new Point(thisPoint);
            point = next = null;
            pointsCounter = series.size();

            super.drawSeries(canvas, plotArea, series, formatter);
        }
    }
}



回答2:


1- I guess that you only use a few points to draw graphs of signals. All graph/chart applications try to connect points with direct lines and then your chart will be shown. So if you only use three points, your graph will looks like a triangle! If you want your graph to be curved, you have to add more points. Then it comes out like a curve.

2- Or you can find any library that can draw sin graph, for example GraphView Library. Then try to draw this function:

So it looks like to this:

Then translate it to (a,0), so result seems like what you want.

3- And another way, you can use built in Math.sin in Java:

Chose for example 1000 point in range a to b and compute value of above function for each point and finally create a path and show them in a canvas.

You can use quadTo (float x1, float y1, float x2, float y2) that simplify drawing quad curves for you. The documentation says:

Add a quadratic bezier from the last point, approaching control point (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for this contour, the first point is automatically set to (0,0).

Parameters

x1 The x-coordinate of the control point on a quadratic curve
y1 The y-coordinate of the control point on a quadratic curve
x2 The x-coordinate of the end point on a quadratic curve
y2 The y-coordinate of the end point on a quadratic curve

Finally, I add a simple class that extends View and can draw a curve that looks like what you want:

public class SinWave extends View {

    private float first_X = 50;
    private float first_Y = 230;
    private float end_X = 100;
    private float end_Y = 230;
    private float Max = 50;

    public SinWave(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint() {
            {
                setStyle(Paint.Style.STROKE);
                setStrokeCap(Paint.Cap.ROUND);
                setStrokeWidth(0.7f);
                setAntiAlias(true);
                setColor(0xFFFF00FF);
            }
        };
        final Path path = new Path();
        path.moveTo(first_X, first_Y);
        path.quadTo((first_X + end_X)/2, Max, end_X, end_Y);
        canvas.drawPath(path, paint);
    }
}

The result must look like this:

You can add more methods to the class and change it to increase performance!




回答3:


There's always been a smooth line renderer in Androidplot: BezierLineAndPointRenderer, which like the implementations above uses Android's built in Bezier drawing routines cubicTo(...) & quadTo(...). The problem is that using Beziers to draw smooth lines in this way creates a false line that overshoots the actual control points by varying amounts, which you can see happening if you look closely at the image above.

The solution is to use the Catmull-Rom spline interpolation, which is now finally supported by Androidplot. Details here: http://androidplot.com/smooth-curves-and-androidplot/




回答4:


Just use ChartFactory.getCubeLineChartView instead of ChartFactory.getLineChartView using achart engine




回答5:


try this:

symbol = new Path();
paint = new Paint();
paint.setAntiAlias(true);      
paint.setStrokeWidth(2);
paint.setColor(-7829368);  
paint.setStrokeJoin(Paint.Join.ROUND);    // set the join to round you want
paint.setStrokeCap(Paint.Cap.ROUND);      // set the paint cap to round too
paint.setPathEffect(new CornerPathEffect(10) );
paint.setStyle(Paint.Style.STROKE);       

symbol.moveTo(50.0F, 230.0F);         
symbol.lineTo(75.0F, 100.0F);
symbol.lineTo(100.0F, 230.0F);

most of the info found here



来源:https://stackoverflow.com/questions/18014834/how-to-make-line-with-rounded-smooth-corners-with-androidplot

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!