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.
@@ -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!