Skip to content

Problem with skipping points in XYPlot #26

@tsaedek

Description

@tsaedek

As suggested here I used null values in yVals for generating a new series:

XYSeries series1 = new SimpleXYSeries(xVals, yVals, "f(x) = tan(x)");

This seems to throw an NullPointerException in LineAndPointRenderer.java, but the app does not crash (caught somewhere?).

java.lang.NullPointerException: Attempt to read from field 'float android.graphics.PointF.x' on a null object reference
at com.androidplot.xy.LineAndPointRenderer.renderPoints(LineAndPointRenderer.java:252)
at com.androidplot.xy.LineAndPointRenderer.drawSeries(LineAndPointRenderer.java:218)
at com.androidplot.xy.LineAndPointRenderer.onRender(LineAndPointRenderer.java:67)
at com.androidplot.xy.LineAndPointRenderer.onRender(LineAndPointRenderer.java:40)
at com.androidplot.ui.SeriesRenderer.render(SeriesRenderer.java:59)
at com.androidplot.xy.XYGraphWidget.drawData(XYGraphWidget.java:814)
at com.androidplot.xy.XYGraphWidget.doOnDraw(XYGraphWidget.java:505)
at com.androidplot.ui.widget.Widget.draw(Widget.java:357)
at com.androidplot.ui.LayoutManager.draw(LayoutManager.java:108)
at com.androidplot.Plot.renderOnCanvas(Plot.java:822)
at com.androidplot.Plot.onDraw(Plot.java:794)
at android.view.View.draw(View.java:16435)
at android.view.View.buildDrawingCacheImpl(View.java:15700)
at android.view.View.buildDrawingCache(View.java:15554)
at android.view.View.draw(View.java:16182)
at android.view.ViewGroup.drawChild(ViewGroup.java:3735)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3525)
at android.view.View.updateDisplayListIfDirty(View.java:15380)
at android.view.View.draw(View.java:16189)
at android.view.ViewGroup.drawChild(ViewGroup.java:3735)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3525)
at android.view.View.updateDisplayListIfDirty(View.java:15380)
at android.view.View.draw(View.java:16189)
at android.view.ViewGroup.drawChild(ViewGroup.java:3735)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3525)
at android.view.View.updateDisplayListIfDirty(View.java:15380)
at android.view.View.draw(View.java:16189)
at android.view.ViewGroup.drawChild(ViewGroup.java:3735)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3525)
at com.android.internal.policy.PhoneWindow$DecorView.dispatchDraw(PhoneWindow.java:2754)
at android.view.View.draw(View.java:16447)
at com.android.internal.policy.PhoneWindow$DecorView.draw(PhoneWindow.java:2740)
at android.view.View.updateDisplayListIfDirty(View.java:15388)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:286)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:292)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:327)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:3040)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2844)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2456)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1341)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6818)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:894)
at android.view.Choreographer.doCallbacks(Choreographer.java:696)
at android.view.Choreographer.doFrame(Choreographer.java:631)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:880)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:5728)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:679)
at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132)

When rendering just one series, this seems to be no problem at all. But when trying to render a second after such error, the second series is not drawn (even the label of the first one isn't drawn).

For my application I need a plotting library that can plot mathematical functions, so I tried the FXPlotExample from your demoapp folder and modified it to display tan(x).

This is my complete code (modification to the example start at bottom, indicated by comment):

package com.androidplot.demos;

import android.app.*;
import android.graphics.*;
import android.os.*;

import com.androidplot.util.*;
import com.androidplot.xy.*;

import java.text.*;
import java.util.*;

/**
 * A simple XYPlot
 */
public class FXPlotExampleActivity extends Activity {

    private XYPlot plot;

    /**
     * Custom line label renderer that highlights origin labels
     */
    class MyLineLabelRenderer extends XYGraphWidget.LineLabelRenderer {

        @Override
        protected void drawLabel(Canvas canvas, String text, Paint paint,
                float x, float y, boolean isOrigin) {
            if(isOrigin) {
                // make the origin labels red:
                final Paint originPaint = new Paint(paint);
                originPaint.setColor(Color.RED);
                super.drawLabel(canvas, text, originPaint, x, y , isOrigin);
            } else {
                super.drawLabel(canvas, text, paint, x, y , isOrigin);
            }
        }
    }

    /**
     * Draws every other tick label and renders text in gray instead of white.
     */
    class MySecondaryLabelRenderer extends MyLineLabelRenderer {


        @Override
        public void drawLabel(Canvas canvas, XYGraphWidget.LineLabelStyle style,
                Number val, float x, float y, boolean isOrigin) {
            if(val.doubleValue() % 2 == 0) {
                final Paint paint = style.getPaint();
                if(!isOrigin) {
                    paint.setColor(Color.GRAY);
                }
                super.drawLabel(canvas, style, val, x, y, isOrigin);
            }
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fx_plot_example);

        // initialize our XYPlot reference:
        plot = (XYPlot) findViewById(R.id.plot);

        plot.setDomainStep(StepMode.INCREMENT_BY_VAL, 1);
        plot.setRangeStep(StepMode.INCREMENT_BY_VAL, 1);

        plot.centerOnDomainOrigin(0);
        plot.centerOnRangeOrigin(0);

        // create formatters to use for drawing a series using LineAndPointRenderer
        // and configure them from xml:
        LineAndPointFormatter series1Format =
                new LineAndPointFormatter(this, R.xml.line_point_formatter);

        // use our custom renderer to make origin labels red
        plot.getGraph().setLineLabelRenderer(XYGraphWidget.Edge.BOTTOM, new MyLineLabelRenderer());
        plot.getGraph().setLineLabelRenderer(XYGraphWidget.Edge.LEFT, new MyLineLabelRenderer());

        // skip every other line for top and right edge labels
        plot.getGraph().setLineLabelRenderer(XYGraphWidget.Edge.RIGHT, new MySecondaryLabelRenderer());
        plot.getGraph().setLineLabelRenderer(XYGraphWidget.Edge.TOP, new MySecondaryLabelRenderer());

        // don't show decimal places for top and right edge labels
        plot.getGraph().getLineLabelStyle(XYGraphWidget.Edge.TOP).setFormat(new DecimalFormat("0"));
        plot.getGraph().getLineLabelStyle(XYGraphWidget.Edge.RIGHT).setFormat(new DecimalFormat("0"));

        // create a dash effect for domain and range grid lines:
        DashPathEffect dashFx = new DashPathEffect(
                new float[] {PixelUtils.dpToPix(3), PixelUtils.dpToPix(3)}, 0);
        plot.getGraph().getDomainGridLinePaint().setPathEffect(dashFx);
        plot.getGraph().getRangeGridLinePaint().setPathEffect(dashFx);

        //----------------------------------------------------------------------------------------------------------
        /// modifications starting here:
        //----------------------------------------------------------------------------------------------------------
        // add a new series' to the xyplot:
        plot.setRangeBoundaries(-10, 10, BoundaryMode.FIXED);
        plot.addSeries(generateSeries(-5, 5, 300), series1Format);
        plot.addSeries(generateSeries1(-5, 5, 10), series1Format);
    }

    protected XYSeries generateSeries(double minX, double maxX, double resolution) {
        final double range = maxX - minX;
        final double step = range / resolution;
        List<Number> xVals = new ArrayList<>();
        List<Number> yVals = new ArrayList<>();

        double x = minX;
        while (x <= maxX) {
            xVals.add(x);
            double value = fx(x);
            if (value >= -15 && value <= 15)
                yVals.add(value);
            else
                yVals.add(null);
            x +=step;
        }

        return new SimpleXYSeries(xVals, yVals, "f(x) = tan(x)");
    }

    protected XYSeries generateSeries1(double minX, double maxX, double resolution) {
        final double range = maxX - minX;
        final double step = range / resolution;
        List<Number> xVals = new ArrayList<>();
        List<Number> yVals = new ArrayList<>();

        double x = minX;
        while (x <= maxX) {
            xVals.add(x);
            yVals.add(x);
            x +=step;
        }

        return new SimpleXYSeries(xVals, yVals, "g(x) = x");
    }


    protected double fx(double x) {
        return Math.tan(x);
    }
}

For a quick fix I changed the rendering order, so that the "problematic" series is second:

plot.addSeries(generateSeries1(-5, 5, 10), series1Format);
plot.addSeries(generateSeries(-5, 5, 300), series1Format);

Now they are both plotted, but the labels are still missing.

The proper fix would be to check for null in LineAndPointRenderer.java:

@@ -246,6 +246,8 @@ public class LineAndPointRenderer<FormatterType extends LineAndPointFormatter> e
             final PointLabeler pointLabeler = hasPointLabelFormatter ? formatter.getPointLabeler() : null;
             for(int i = iStart; i < iEnd; i++) {
                 PointF p = points.get(i);
+                if (p == null)
+                    continue;
 
                 // if vertexPaint is available, draw vertex:
                 if (vertexPaint != null) {

This way it seems to work as expected.
If this fix does not conflict with any other use cases, please consider to add it to the project!

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions