简体   繁体   English

如何使用 AndroidPlot 绘制圆角(平滑)线

[英]How to make line with rounded (smooth) corners with AndroidPlot

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).该图应表示可用 Wi-Fi.network(s) 的实际信号强度。 It's a simple XYPlot here data are represented with SimpleXYSeries (values are dynamically created).这是一个简单的XYPlot ,这里的数据用SimpleXYSeries表示(值是动态创建的)。

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.图中的例子是dB变化的动态模拟。 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:我已经尝试自定义 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.注意: Wifi Analyzer应用程序有一个类似的图形,它的图形有我想要的圆角。 It looks like this:它看起来像这样:

在此处输入图像描述

You can use Path.cubicTo() method. 您可以使用Path.cubicTo()方法。 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. 有一个简短的算法显示如何计算Path.cubicTo()方法的输入参数。 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. 例如,在下面的图片中,我除以5而不是3.希望这会有所帮助。

使用Path.cubicTo()方法绘制的聚烯烃的示例

I have spent some time and implemented a SplineLineAndPointFormatter class, which does the stuff you need in androidplot library. 我花了一些时间并实现了一个SplineLineAndPointFormatter类,它可以在androidplot库中完成你需要的东西。 It uses same technics. 它使用相同的技术。 Here is how androidplot example applications looks like. 以下是androidplot示例应用程序的外观。 You just need to use it instead of LineAndPointFormatter . 您只需要使用它而不是LineAndPointFormatter

使用SplineLineAndPointFormatter的Androidplot示例

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. 它基于androidplot库的0.6.1版本。

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);
        }
    }
}

1- I guess that you only use a few points to draw graphs of signals. 1-我猜你只用几个点来绘制信号图。 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 . 2-或者你可以找到任何可以绘制sin图的库,例如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. 然后将其翻译为(a,0) ,因此结果看起来像你想要的。

3- And another way, you can use built in Math.sin in Java: 3-另一种方式,你可以在Java中使用内置的Math.sin

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. 选择例如范围ab 1000点并计算每个点的上述函数的值,最后创建path并在画布中显示它们。

You can use quadTo (float x1, float y1, float x2, float y2) that simplify drawing quad curves for you. 您可以使用quadTo(float x1,float y1,float x2,float y2)来简化绘制quad curves The documentation says: 文件说:

Add a quadratic bezier from the last point, approaching control point (x1,y1), and ending at (x2,y2). 从最后一个点添加二次贝塞尔曲线,接近控制点(x1,y1),并以(x2,y2)结束。 If no moveTo() call has been made for this contour, the first point is automatically set to (0,0). 如果没有对此轮廓进行moveTo()调用,则第一个点自动设置为(0,0)。

Parameters 参数

x1 The x-coordinate of the control point on a quadratic curve x1二次曲线上控制点的x坐标
y1 The y-coordinate of the control point on a quadratic curve y1二次曲线上控制点的y坐标
x2 The x-coordinate of the end point on a quadratic curve x2二次曲线上终点的x坐标
y2 The y-coordinate of the end point on a quadratic curve y2二次曲线上终点的y坐标

Finally, I add a simple class that extends View and can draw a curve that looks like what you want: 最后,我添加了一个扩展View的简单类,可以绘制一个看起来像你想要的曲线:

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! 您可以向该类添加更多方法并进行更改以提高性能!

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(...) . 在Androidplot中总是有一个平滑的线条渲染器:BezierLineAndPointRenderer,它与上面的实现类似,使用Android内置的Bezier绘图例程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. 解决方案是使用Catmull-Rom样条插值,现在最终由Androidplot支持。 Details here: http://androidplot.com/smooth-curves-and-androidplot/ 详情请访问: http//androidplot.com/smooth-curves-and-androidplot/

使用ChartFactory.getCubeLineChartView代替ChartFactory.getLineChartView使用achart引擎

In some simple cases, this could help:在一些简单的情况下,这可能会有所帮助:

mPaint.pathEffect = CornerPathEffect(radius)

even in combination with即使结合

path.lineTo(x,y)

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 大多数信息在这里找到

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM