An XYPlot
renders one or more XYSeries onto an instance of XYGraphWidget. It also includes two instances
of TextLabelWidget
used to display an optional title for the domain and range axis, and an instance
of XYLegendWidget which by default will automatically display legend items for each XYSeries
added to the plot.
XYSeries
is the interface Androidplot uses to retrieve your numeric data. You may either create your own
implementation of XYSeries
or use SimpleXYSeries
if you don't have tight performance requirements or
if your numeric data is easily accessed as an array or list of values.
As a convenience, Androidplot provides an all-purpose implementation of the XYSeries
interface called
SimpleXYSeries
. SimpleXYSeries
is used to wrap your raw data with an implementation of the XYSeries
interface.
You can supply your data in several ways:
As a list of y-vals (x = i for each supplied y-val):
Number[] yVals = {1, 4, 2, 8, 4, 16, 8, 32, 16, 64};
XYSeries series1 = new SimpleXYSeries(
Arrays.asList(yVals), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "my series");
An interleaved list of x/y value pairs (x[0] = 1, y[0] = 4, x[1] = 2, y[1] = 8, ...):
Number[] yVals = {1, 4, 2, 8, 4, 16, 8, 32, 16, 64};
XYSeries series1 = new SimpleXYSeries(
Arrays.asList(yVals), SimpleXYSeries.ArrayFormat.XY_VALS_INTERLEAVED, "my series");
Separate lists of x-vals and y-vals:
Number[] xVals = {1, 4, 2, 8, 4, 16, 8, 32, 16, 64};
Number[] yVals = {5, 2, 10, 5, 20, 10, 40, 20, 80, 40};
XYSeries series = new SimpleXYSeries(xVals, yVals, "my series");
Keep in mind that SimpleXYSeries
is designed to be easy to use for a broad number of applications; it's not
optimized for any specific scenario; if you are dynamically displaying data that needs to be refreshed more than several
times a second, consider building your own implementation of XYSeries
designed for your app's
specific needs.
XYGraphWidget
encapsulates XYPlot's graphing functionality. Given an instance of XYPlot
, a reference
to XYGraphWidget
can be retrieve via XYPlot.getGraph()
.
By default, Androidplot will analyze all XYSeries
instances registered with the XYPlot
, determine the
min/max values for domain and range and adjust the XYPlot
boundaries to match those values. If your
plot contains dynamic data, especially if your plot can periodically contain either no series data
or data with no resolution on one or both axis (all identical values for either x or y) then you may
want to manually set your XYPlot
's domain and range boundaries.
To set your plot's boundaries use:
XYPlot.setDomainBoundaries(Number value, BoundaryMode mode)
XYPlot.setRangeBoundaries(Number value, BoundaryMode mode)
Note that the value argument is only used when setting BoundaryMode.FIXED
. For all other
modes, pass in null.
Androidplot provides four boundary modes:
The plot's boundaries on the specified axis are fixed to user defined values.
The plot's boundaries auto adjust to the min/max values for the defined axis.
The plot's boundaries automatically increase to the max value encountered by the plot. The initial
determines the starting boundaries from which the BoundaryMode.GROW
behavior will be based.
The plot's boundaries automatically shrink to the min value encountered by the plot. The initial determines the starting boundaries from which the shrink behavior will be based.
These are the horizontal lines drawn on a graph. These lines are configured via:
XYPlot.setDomainStep(StepMode mode, Number value)
XYPlot.setRangeStep(StepMode, Number value)
Androidplot provides these step modes:
When using StepMode.SUBDIVIDE
, the graph is subdivided into the specified number of sections.
StepMode.INCREMENT_BY_VALUE
instructs Androidplot draw grid lines at the specified interval. This
is the most commonly used modes as is produces an easy to read result.
StepMode.INCREMENT_BY_PIXELS
behaves identically to StepMode.INCREMENT_BY_VALUE
except that
the increment quantity is expressed in pixels.
Androidplot supports labeling domain values on either or both the top and bottom graph edges and range values on either or both the left and right graph edges. Most default styles show labels only on the left and bottom edges.
Insets are used to control where line labels are drawn in relation to the graph space. The Insets instance
can be obtained via XYPlot.getGraph().getLineLabelInsets()
. For example, to
move the range labels on the left of the graph further to the left by 5dp:
xml
ap:lineLabelInsetLeft="-5dp"
java
plot.getGraph().getLineLabelInsets().setLeft(PixelUtils.dpToPix(-5));
Androidplot provides methods for enabling / disabling axis labels along all edges of the graph. By default, only the left and bottom edge labels are enabled. To enable labels on the right edge:
xml
ap:lineLabels="left|bottom|right"
java
plot.getGraph().setLineLabelEdges(
XYGraphWidget.Edge.BOTTOM,
XYGraphWidget.Edge.LEFT,
XYGraphWidget.Edge.RIGHT);
Once the edge has been enabled, text formatting can be controlled by enabling a custom formatter for the desired edge:
plot.getGraph().getLineLabelStyle(XYGraphWidget.Edge.RIGHT).setFormat(new Format() {
@Override
public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
// obj contains the raw Number value representing the position of the label being drawn.
// customize the labeling however you want here:
int i = Math.round(((Number) obj).floatValue());
return toAppendTo.append(i + " thingies");
}
@Override
public Object parseObject(String source, ParsePosition pos) {
// unused
return null;
}
});
You're likely also going to need to normalize your series data in order to get it to display properly. To help simplify
this step, Androidplot provides NormedXYSeries
which can be used to wrap any XYSeries
to provide the normalized representation of it's data.
There's a full reference implementation
of a dual scale plot using a custom Formatter
and NormedXYSeries
in the DemoApp.
Androidplot allows you to configure the interval at which labels are rendered for domain and range lines:
XYPlot.getGraph().setLinesPerDomainLabel(int interval)
XYPlot.getGraph().setLinesPerRangeLabel(int interval)
The styling of the line labels drawn on each edge of the graph is controlled by it's associated style. this style contains params that control:
- Paint used to draw labels (determines, color, font size, etc.)
- Format used to draw text (can be NumberFormat, etc.)
- Rotation of the label text
To get the style used to draw the left edge (range) labels:
plot.getGraph().getLineLabelStyle(XYGraphWidget.Edge.LEFT);
If you need to implement special rendering behavior for your line labels, such as drawing graphics, symbols, etc. you can create a custom renderer by extending LineLabelRenderer and injecting it into the graph:
plot.getGraph().setLineLabelRenderer(XYGraphWidget.Edge.LEFT, customLineLabelRenderer);
f(x) plot example source provides an example of this kind of customization.
You can enable pan/zoom behavior on any instance of XYPlot using the PanZoom class like this:
PanZoom.attach(plot);
The default behavior is to enable horizontal and vertical panning an to zoom using uniform scaling.
If you want to override this behavior use the three argument form of PanZoom.attach(Plot)
. For example,
to enable pan and zoom (stretch) on the horizontal axis only:
PanZoom.attach(plot, PanZoom.Pan.HORIZONTAL, PanZoom.Zoom.STRETCH_HORIZONTAL);
Pan and zoom operations abide by your plot's defined outer limits limits. If no such limits have been set then the plot will pan and zoom on both axes infinitely. To set the plot's outer limits:
// cap pan/zoom limits for panning and zooming to a 100x100 space:
plot.getOuterLimits().set(0, 100, 0, 100);
The PanZoom
class provides convenience methods for saving and restoring state from your Activity
:
// save the current pan/zoom state
@Override
public void onSaveInstanceState(Bundle bundle) {
bundle.putSerializable("pan-zoom-state", panZoom.getState());
}
// restore the previously saved pan/zoom state
@Override
public void onRestoreInstanceState(Bundle bundle) {
PanZoom.State state = (PanZoom.State) bundle.getSerializable("pan-zoom-state");
panZoom.setState(state);
plot.redraw();
}
For a more detailed look at pan & zoom behavior, check out the Touch Zoom Example source code.
There are several renderers available for XYPlots:
- LineAndPointRenderer
- BarRenderer
- CandlestickRenderer
When you add a new series to your plot, you tell Androidplot how to render it by passing
in a subclass of XYSeriesFormatter
that corresponds to the desired renderer. For example, to use
LineAndPointRenderer
, you'd register your series with an instance of LineAndPointFormatter
:
LineAndPointFormatter format = new LineAndPointFormatter();
plot.addSeries(series, format);
If you need special behavior not provided by an existing renderer you can create
your own by either extending XYSeriesRenderer
or one of the above implementations. You'll also need
to create a matching implementation of XYSeriesFormatter
that returns your renderer's class
from it's getRendererClass()
method.
LineAndPointRenderer
is the go-to renderer when it comes to XYSeries
. It provides the most robust
feature set of any XYSeriesRenderer
and has been the most carefully optimized for performance. Having
said that, LineAndPointRenderer
isn't always the best tool for the job. Androidplot includes two
additional variations of LineAndPointRenderer
:
- FastLineAndPointRenderer - intended for use in apps displaying large amounts of dynamic data where fast refresh rates are important.
- AdvancedLineAndPointRenderer - provides capabilities for dynamically coloring individual line segments, etc. See the ECGExample source in the demo app for a complete example.
Most implementations of XYSeriesRenderer
support labeling rendered points with text. This functionality
is activated by setting the PointLabelFormatter
property in the associated XYSeriesFormatter
. For
example, to enable point labels for a LineAndPointFormatter
:
// create a new PointLabelFormatter
with a text size of 12sp and a color of red:
PointLabelFormatter plf = new PointLabelFormatter();
plf.getTextPaint().setTextSize(PixelUtils.spToPix(12));
plf.getTextPaint().setColor(Color.RED);
lineAndPointFormatter.setPointLabelFormatter(plf);
By default this will enable labels for all points using a string representation of the yVal of each
point. This behavior can be customized by setting a custom instance of PointLabeler
on the XYSeriesFormatter
:
formatter.setPointLabeler(new PointLabeler() {
@Override
public String getLabel(XYSeries series, int index) {
// draw labels on even indexes only:
if(index % 2 == 0) {
return "Y=" + series.getY(index).doubleValue();
}
return null;
}
});
See the barcharts documentation.
See the candlestick documentation
Smooth lines can be created by applying the Catmull-Rom interpolator to your series' Format.
By default, Androidplot will automatically produce a legend for your XYPlot
. See the legend doc
for usage details.
Androidplot provides the Widget.setRotation(Widget.Rotation)
method for controlling the orientation
of Widgets. For example, if you wanted to create a bar graph where the bars extended across the screen
from left to right:
xml
ap:graphRotation="ninety_degrees"
java
plot.getGraph().setRotation(Widget.Rotation.NINETY_DEGREES);
Here are a few suggestions to improve performance when plotting dynamic data:
- Create your own implementation of
XYSeries
to work with your data in it's rawest form. - Use
FastLineAndPointRenderer
instead ofLineAndPointRenderer
:
plot.addSeries(azimuthHistorySeries,
new FastLineAndPointRenderer.Formatter(
Color.rgb(100, 100, 200), null, null, null));
- Consider averaging or subsampling very large datasets before rendering. If you have the time and
inclination, the LTTB algorithm is
particularly well suited for downsampling
XYSeries
data. - If possible, avoid rendering vertices (points).
- Disable anti-aliasing on your
XYSeriesFormatter
's paint values:
LineAndPointFormatter format = new LineAndPointFormatter(...);
format.getLinePaint().setAntiAlias(false);
Because the coordinate system used by your XYSeries
data is almost always different than the screen
coordinate system upon which the data is rendered, you'll often need to convert from one system to
the other. XYPlot
provides convenience methods for this purpose:
// x
float screenX = ...
Number x = plot.screenToSeriesX(screenX);
// y
float screenY = ...
Number y = plot.screenToSeriesY(screenY);
// x and y
PointF screenCoords = ...
XYCoords xy = plot.screenToSeries(screenCoords)
// x
Number x = ...
float screenX = plot.seriesToScreenX(x);
// y
Number y = ...
float screenY = plot.seriesToScreenY(y);
// x and y
XYCoords xy = ...
PointF screenCoords = plot.series.Screen(xy);
Explore Advanced XYPlot Topics