Как сделать линию с закругленными (гладкими) углами с AndroidPlot

У меня небольшая проблема с построением графика. На картинке ниже - это то, что я уже сделал.


График должен представлять фактическую силу сигнала в доступных сетях Wi-Fi. Это простая XYPlot здесь данные представлены с помощью SimpleXYSeries (значения динамически создаются).

Вот небольшой фрагмент кода (только для примера):

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

Пример на рисунке представляет собой динамическое моделирование изменений дБ. Все работает, я думаю, правильно, но я хочу достичь линии с "закругленными" углами (см. Рисунок, чтобы понять, что я имею в виду).

Я уже пытался настроить LineFormatter:

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

Но это не сработало, как ожидалось.

Enter image description here

Примечание. Приложение Wifi Analyzer имеет аналогичный граф, и его график имеет округленные углы, которые я хочу. Это выглядит так:

Enter image description here

Ответ 1

Вы можете использовать метод Path.cubicTo(). Он рисует линию, используя алгоритм кубического сплайна, который дает эффект сглаживания, который вы хотите.

Ознакомьтесь с ответом на аналогичный вопрос здесь, где парень говорит о кубических сплайнах. Существует короткий алгоритм, показывающий, как вычислять входные параметры для метода Path.cubicTo(). Вы можете играть с разделителями для достижения требуемой гладкости. Например, на рисунке ниже я разделил 5 вместо 3. Надеюсь, это поможет.

Example of a polylyne drawn using Path.cubicTo() method

Я потратил некоторое время и реализовал класс SplineLineAndPointFormatter, который делает материал, который вам нужен в библиотеке androidplot. Он использует ту же технику. Вот как выглядит приложение приложений androidplot. Вам просто нужно использовать его вместо LineAndPointFormatter.

Androidplot example with SplineLineAndPointFormatter

Вот пример кода и класс, который я написал.

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

Вот класс, делающий магию. Он основан на версии 0.6.1 библиотеки androidplot.

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 - Я предполагаю, что вы используете только несколько точек для рисования графиков сигналов. Все приложения графика/диаграммы пытаются подключить точки с прямыми линиями, а затем отобразится ваша диаграмма. Поэтому, если вы используете только три точки, ваш график будет выглядеть как треугольник! Если вы хотите, чтобы ваш график был изогнутым, вам нужно добавить больше очков. Затем он выходит как кривая.

2 - Или вы можете найти любую библиотеку, которая может рисовать график sin, например GraphView Library. Затем попробуйте сделать эту функцию:

Enter image description here

Так выглядит:

Enter image description here

Затем переведите его на (a,0), поэтому результат будет выглядеть так, как вы хотите.

3 - И еще один способ, вы можете использовать встроенный Math.sin в Java:

Выберите, например, 1000 точек в диапазоне от a до b и вычислите значение выше функции для каждой точки и, наконец, создайте path и покажите их на холсте.

Вы можете использовать quadTo (float x1, float y1, float x2, float y2), которые упрощают рисование quad curves для вас. В документации указано:

Добавьте квадратичную безье от последней точки, приближаясь к контрольной точке (x1, y1) и заканчивая на (x2, y2). Если вызов moveTo() не был сделан для этот контур, первая точка автоматически устанавливается на (0,0).

Параметры

x1 Координата x контрольной точки на квадратичной кривой
y1 У-координата контрольной точки на квадратичной кривой
x2 Координата x конечной точки на квадратичной кривой
y2 У-координата конечной точки на квадратичной кривой

Наконец, я добавляю простой класс, который расширяет 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);
    }
}

Результат должен выглядеть так:

Enter image description here

Вы можете добавить дополнительные методы в класс и изменить его для повышения производительности!

Ответ 3

В Androidplot всегда был плавный рендеринг линии: BezierLineAndPointRenderer, который, как и вышеприведенные реализации, использует Android, встроенный в программы рисования Bezier cubTo (...) и quadTo (...). Проблема в том, что использование Beziers для рисования гладких линий таким образом создает ложную линию, которая перерегулирует фактические контрольные точки в разных количествах, что вы можете увидеть, если внимательно посмотрите на изображение выше.

Решение состоит в использовании сплайновой интерполяции Catmull-Rom, которая теперь наконец поддерживается Androidplot. Подробности здесь: http://androidplot.com/smooth-curves-and-androidplot/

Ответ 4

Просто используйте ChartFactory.getCubeLineChartView вместо ChartFactory.getLineChartView, используя движок achart

Ответ 5

попробуйте следующее:

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

большая часть найденной информации здесь