Skip to content

Commit c338235

Browse files
authored
* fixed a bug causing points scrolled off-screen to accumulate and render along the left edge of the graph (halfhp#20)
* fixed a bug that would cause render jitter when extreme zoom levels were applied * fixed a bug that prevented PanZoom from working properly on plots that did not specify outer limits. * added basic implementation of a normalizing xyseries wrapper class * added dual scale xy example * added rotation property to Widget * added graphRotation XML attr to XYPlot
1 parent f55660c commit c338235

29 files changed

+1045
-91
lines changed

androidplot-core/src/main/java/com/androidplot/SeriesRegistry.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ public boolean isEmpty() {
5454
}
5555

5656
public boolean add(SeriesType series, FormatterType formatter) {
57+
if(series == null || formatter == null) {
58+
throw new IllegalArgumentException("Neither series nor formatter param may be null.");
59+
}
5760
return registry.add(newSeriesBundle(series, formatter));
5861
}
5962

@@ -122,4 +125,13 @@ public List<SeriesBundle<SeriesType, FormatterType>> getLegendEnabledItems() {
122125
}
123126
return sfList;
124127
}
128+
129+
public boolean contains(SeriesType series, Class<? extends FormatterType> formatterClass) {
130+
for(BundleType b : registry) {
131+
if(b.getFormatter().getClass() == formatterClass && b.getSeries() == series) {
132+
return true;
133+
}
134+
}
135+
return false;
136+
}
125137
}

androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ public void draw(Canvas canvas) throws PlotRenderException {
8585
drawSpacing(canvas, displayDims.marginatedRect, displayDims.paddedRect, paddingPaint);
8686
}
8787
for (Widget widget : elements()) {
88-
//int canvasState = canvas.save(Canvas.ALL_SAVE_FLAG); // preserve clipping etc
8988
try {
9089
canvas.save(Canvas.ALL_SAVE_FLAG);
9190
PositionMetrics metrics = widget.getPositionMetrics();

androidplot-core/src/main/java/com/androidplot/ui/widget/Widget.java

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ public abstract class Widget implements BoxModelable, Resizable {
4141
private PositionMetrics positionMetrics;
4242
private LayoutManager layoutManager;
4343

44+
private Rotation rotation = Rotation.NONE;
45+
46+
public enum Rotation {
47+
NINETY_DEGREES,
48+
NEGATIVE_NINETY_DEGREES,
49+
ONE_HUNDRED_EIGHTY_DEGREES,
50+
NONE,
51+
}
52+
4453
public Widget(LayoutManager layoutManager, SizeMetric heightMetric, SizeMetric widthMetric) {
4554
this(layoutManager, new Size(heightMetric, widthMetric));
4655
}
@@ -113,7 +122,6 @@ public void onPostInit() {
113122
* @return
114123
*/
115124
public boolean containsPoint(PointF point) {
116-
//return outlineRect != null && outlineRect.contains(point.x, point.y);
117125
return widgetDimensions.canvasRect.contains(point.x, point.y);
118126
}
119127

@@ -344,14 +352,55 @@ public void draw(Canvas canvas) throws PlotRenderException {
344352
if (backgroundPaint != null) {
345353
drawBackground(canvas, widgetDimensions.canvasRect);
346354
}
347-
doOnDraw(canvas, widgetDimensions.paddedRect);
355+
canvas.save();
356+
final RectF paddedRect = applyRotation(canvas, widgetDimensions.paddedRect);
357+
doOnDraw(canvas, paddedRect);
358+
canvas.restore();
348359

349360
if (borderPaint != null) {
350-
drawBorder(canvas, widgetDimensions.paddedRect);
361+
drawBorder(canvas, paddedRect);
351362
}
352363
}
353364
}
354365

366+
protected RectF applyRotation(Canvas canvas, RectF rect) {
367+
float rotationDegs = 0;
368+
final float cx = widgetDimensions.paddedRect.centerX();
369+
final float cy = widgetDimensions.paddedRect.centerY();
370+
final float halfHeight = widgetDimensions.paddedRect.height() / 2;
371+
final float halfWidth = widgetDimensions.paddedRect.width() / 2;
372+
switch (rotation) {
373+
case NINETY_DEGREES:
374+
rotationDegs = 90;
375+
rect = new RectF(
376+
cx - halfHeight,
377+
cy - halfWidth,
378+
cx + halfHeight,
379+
cy + halfWidth);
380+
break;
381+
case NEGATIVE_NINETY_DEGREES:
382+
rotationDegs = -90;
383+
rect = new RectF(
384+
cx - halfHeight,
385+
cy - halfWidth,
386+
cx + halfHeight,
387+
cy + halfWidth);
388+
break;
389+
case ONE_HUNDRED_EIGHTY_DEGREES:
390+
rotationDegs = 180;
391+
// fall through
392+
case NONE:
393+
break;
394+
default:
395+
throw new UnsupportedOperationException("Not yet implemented.");
396+
397+
}
398+
if(rotation != Rotation.NONE) {
399+
canvas.rotate(rotationDegs, cx, cy);
400+
}
401+
return rect;
402+
}
403+
355404
protected void drawBorder(Canvas canvas, RectF paddedRect) {
356405
canvas.drawRect(paddedRect, borderPaint);
357406
}
@@ -405,4 +454,12 @@ public PositionMetrics getPositionMetrics() {
405454
public void setPositionMetrics(PositionMetrics positionMetrics) {
406455
this.positionMetrics = positionMetrics;
407456
}
457+
458+
public Rotation getRotation() {
459+
return rotation;
460+
}
461+
462+
public void setRotation(Rotation rotation) {
463+
this.rotation = rotation;
464+
}
408465
}

androidplot-core/src/main/java/com/androidplot/util/AttrUtils.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,12 @@ public static void configureWidget(TypedArray attrs, Widget widget, int heightSi
188188
}
189189
}
190190

191+
public static void configureWidgetRotation(TypedArray attrs, Widget widget, int rotationAttr) {
192+
if(attrs != null) {
193+
widget.setRotation(getWidgetRotation(attrs, rotationAttr, Widget.Rotation.NONE));
194+
}
195+
}
196+
191197
/**
192198
* Configure a {@link Widget} from xml attrs.
193199
* @param attrs
@@ -245,6 +251,10 @@ private static VerticalPositioning getYLayoutStyle(TypedArray attrs, int attr, V
245251
return VerticalPositioning.values()[attrs.getInt(attr, defaultValue.ordinal())];
246252
}
247253

254+
private static Widget.Rotation getWidgetRotation(TypedArray attrs, int attr, Widget.Rotation defaultValue) {
255+
return Widget.Rotation.values()[attrs.getInt(attr, defaultValue.ordinal())];
256+
}
257+
248258
private static Anchor getAnchorPosition(TypedArray attrs, int attr, Anchor defaultValue) {
249259
return Anchor.values()[attrs.getInt(attr, defaultValue.ordinal())];
250260
}

androidplot-core/src/main/java/com/androidplot/util/SeriesUtils.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,26 @@ public static RectRegion minMax(XYSeries... seriesList) {
3434
return minMax(null, seriesList);
3535
}
3636

37+
public static Region minMaxX(XYSeries... seriesList) {
38+
final Region bounds = new Region();
39+
for (XYSeries series : seriesList) {
40+
for (int i = 0; i < series.size(); i++) {
41+
bounds.union(series.getX(i));
42+
}
43+
}
44+
return bounds;
45+
}
46+
47+
public static Region minMaxY(XYSeries... seriesList) {
48+
final Region bounds = new Region();
49+
for (XYSeries series : seriesList) {
50+
for (int i = 0; i < series.size(); i++) {
51+
bounds.union(series.getY(i));
52+
}
53+
}
54+
return bounds;
55+
}
56+
3757
/**
3858
* @param constraints may be null.
3959
* @param seriesList
@@ -101,7 +121,6 @@ public static RectRegion minMax(XYConstraints constraints, XYSeries... seriesArr
101121
public static Region minMax(Region bounds, List<Number>... lists) {
102122
for (final List<Number> list : lists) {
103123
for (final Number i : list) {
104-
//minMax(bounds, i);
105124
bounds.union(i);
106125
}
107126
}

androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java

Lines changed: 55 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@
2222
import android.graphics.PointF;
2323
import android.graphics.RectF;
2424

25+
import com.androidplot.Plot;
26+
import com.androidplot.PlotListener;
2527
import com.androidplot.Region;
2628
import com.androidplot.exception.PlotRenderException;
2729
import com.androidplot.ui.RenderStack;
2830
import com.androidplot.util.*;
2931

3032
import java.util.ArrayList;
3133
import java.util.List;
34+
import java.util.concurrent.ConcurrentHashMap;
3235

3336
/**
3437
* Renders a point as a line with the vertices marked. Requires 2 or more points to
@@ -41,8 +44,22 @@ public class LineAndPointRenderer<FormatterType extends LineAndPointFormatter> e
4144

4245
private final Path path = new Path();
4346

47+
protected final ConcurrentHashMap<XYSeries, ArrayList<PointF>> pointsCaches
48+
= new ConcurrentHashMap<>(2, 0.75f, 2);
49+
4450
public LineAndPointRenderer(XYPlot plot) {
4551
super(plot);
52+
plot.addListener(new PlotListener() {
53+
@Override
54+
public void onBeforeDraw(Plot source, Canvas canvas) {
55+
cullPointsCache();
56+
}
57+
58+
@Override
59+
public void onAfterDraw(Plot source, Canvas canvas) {
60+
61+
}
62+
});
4663
}
4764

4865
@Override
@@ -77,17 +94,38 @@ protected void appendToPath(Path path, PointF thisPoint, PointF lastPoint) {
7794
path.lineTo(thisPoint.x, thisPoint.y);
7895
}
7996

80-
final ArrayList<PointF> points = new ArrayList<>();
97+
/**
98+
* Retrieves or initializes a list for storing calculated screen-coords to render as points.
99+
* Also handles automatic resizing and culling of unused caches.
100+
* Should only be called once per render cycle.
101+
* @param series
102+
* @return
103+
*/
104+
protected ArrayList<PointF> getPointsCache(XYSeries series) {
105+
ArrayList<PointF> pointsCache = pointsCaches.get(series);
106+
final int seriesSize = series.size();
107+
if(pointsCache == null) {
108+
pointsCache = new ArrayList<>(seriesSize);
109+
pointsCaches.put(series, pointsCache);
110+
}
81111

82-
// avoids needless new allocations of the points array
83-
protected void resizePointsArray(int newSize) {
84-
if(points.size() < newSize) {
85-
while(points.size() < newSize) {
86-
points.add(null);
112+
if(pointsCache.size() < seriesSize) {
113+
while(pointsCache.size() < seriesSize) {
114+
pointsCache.add(null);
87115
}
88-
} else if(points.size() > newSize) {
89-
while(points.size() > newSize) {
90-
points.remove(0);
116+
} else if(pointsCache.size() > seriesSize) {
117+
while(pointsCache.size() > seriesSize) {
118+
pointsCache.remove(0);
119+
}
120+
}
121+
return pointsCache;
122+
}
123+
124+
protected void cullPointsCache() {
125+
for(XYSeries series : pointsCaches.keySet()) {
126+
if(!getPlot().getRegistry().contains(series, LineAndPointFormatter.class)) {
127+
//pointsCaches.put(series, null);
128+
pointsCaches.remove(series);
91129
}
92130
}
93131
}
@@ -96,37 +134,27 @@ protected void drawSeries(Canvas canvas, RectF plotArea, XYSeries series, LineAn
96134
PointF thisPoint;
97135
PointF lastPoint = null;
98136
PointF firstPoint = null;
99-
final int seriesSize = series.size();
100137
path.reset();
101-
resizePointsArray(seriesSize);
138+
final List<PointF> points = getPointsCache(series);
102139

103140
int iStart = 0;
104-
int iEnd = seriesSize;
141+
int iEnd = series.size();
105142
if(SeriesUtils.getXYOrder(series) == OrderedXYSeries.XOrder.ASCENDING) {
106143
final Region iBounds = SeriesUtils.iBounds(series, getPlot().getBounds());
107144
iStart = iBounds.getMin().intValue();
108145
if(iStart > 0) {
109146
iStart--;
110147
}
111-
iEnd = iBounds.getMax().intValue();
112-
if(iEnd < seriesSize - 1) {
148+
iEnd = iBounds.getMax().intValue() + 1;
149+
if(iEnd < series.size() - 1) {
113150
iEnd++;
114151
}
115152
}
116-
final double minX = getPlot().getBounds().getMinX().doubleValue();
117-
final double maxX = getPlot().getBounds().getMaxX().doubleValue();
118153
for (int i = iStart; i < iEnd; i++) {
119154
final Number y = series.getY(i);
120155
final Number x = series.getX(i);
121156
PointF iPoint = points.get(i);
122157

123-
final double dx = x.doubleValue();
124-
if(i > 0 && i < seriesSize - 1) {
125-
if (dx < minX || dx > maxX) {
126-
continue;
127-
}
128-
}
129-
130158
if (y != null && x != null) {
131159
if(iPoint == null) {
132160
iPoint = new PointF();
@@ -187,7 +215,7 @@ protected void drawSeries(Canvas canvas, RectF plotArea, XYSeries series, LineAn
187215
renderPath(canvas, plotArea, path, firstPoint, lastPoint, formatter);
188216
}
189217
}
190-
renderPoints(canvas, plotArea, series, points, formatter);
218+
renderPoints(canvas, plotArea, series, iStart, iEnd, points, formatter);
191219
}
192220

193221
/**
@@ -209,16 +237,15 @@ protected PointF convertPoint(XYCoords coord, RectF plotArea) {
209237
return getPlot().getBounds().transformScreen(coord, plotArea);
210238
}
211239

212-
protected void renderPoints(Canvas canvas, RectF plotArea, XYSeries series, List<PointF> points,
240+
protected void renderPoints(Canvas canvas, RectF plotArea, XYSeries series, int iStart, int iEnd, List<PointF> points,
213241
LineAndPointFormatter formatter) {
214-
//PointLabelFormatter plf = formatter.getPointLabelFormatter();
215242
if (formatter.hasVertexPaint() || formatter.hasPointLabelFormatter()) {
216-
int i = 0;
217243
final Paint vertexPaint = formatter.hasVertexPaint() ? formatter.getVertexPaint() : null;
218244
final boolean hasPointLabelFormatter = formatter.hasPointLabelFormatter();
219245
final PointLabelFormatter plf = hasPointLabelFormatter ? formatter.getPointLabelFormatter() : null;
220246
final PointLabeler pointLabeler = hasPointLabelFormatter ? formatter.getPointLabeler() : null;
221-
for (PointF p : points) {
247+
for(int i = iStart; i < iEnd; i++) {
248+
PointF p = points.get(i);
222249

223250
// if vertexPaint is available, draw vertex:
224251
if (vertexPaint != null) {
@@ -227,11 +254,9 @@ protected void renderPoints(Canvas canvas, RectF plotArea, XYSeries series, List
227254

228255
// if textPaint and pointLabeler are available, draw point's text label:
229256
if (pointLabeler != null) {
230-
//final PointLabelFormatter plf = formatter.getPointLabelFormatter();
231257
canvas.drawText(pointLabeler.getLabel(series, i),
232258
p.x + plf.hOffset, p.y + plf.vOffset, plf.getTextPaint());
233259
}
234-
i++;
235260
}
236261
}
237262
}

0 commit comments

Comments
 (0)