Skip to content

Commit 28e5b31

Browse files
authored
Pie legend widget (halfhp#54)
Adds legend support to PieChart and refactors legend functionality into abstract class LegendWidget. Also updates docs / unit tests.
1 parent 9d61567 commit 28e5b31

File tree

17 files changed

+618
-311
lines changed

17 files changed

+618
-311
lines changed

androidplot-core/src/main/java/com/androidplot/pie/PieChart.java

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,18 @@ public class PieChart extends Plot<Segment, SegmentFormatter, PieRenderer,
3737
private static final int DEFAULT_PIE_WIDGET_Y_OFFSET_DP = 0;
3838
private static final int DEFAULT_PIE_WIDGET_X_OFFSET_DP = 0;
3939

40-
private static final int DEFAULT_PADDING_DP = 5;
40+
private static final int DEFAULT_LEGEND_WIDGET_H_DP = 30;
41+
private static final int DEFAULT_LEGEND_WIDGET_ICON_SIZE_DP = 18;
42+
private static final int DEFAULT_LEGEND_WIDGET_Y_OFFSET_DP = 0;
43+
private static final int DEFAULT_LEGEND_WIDGET_X_OFFSET_DP = 40;
4144

42-
public void setPie(PieWidget pie) {
43-
this.pie = pie;
44-
}
45+
private static final int DEFAULT_PADDING_DP = 5;
4546

4647
@SuppressWarnings("FieldCanBeLocal")
4748
private PieWidget pie;
4849

50+
private PieLegendWidget legend;
51+
4952
@Override
5053
protected SegmentRegistry getRegistryInstance() {
5154
return new SegmentRegistry();
@@ -81,6 +84,28 @@ protected void onPreInit() {
8184
VerticalPositioning.ABSOLUTE_FROM_CENTER,
8285
Anchor.CENTER);
8386

87+
legend = new PieLegendWidget(
88+
getLayoutManager(),
89+
this,
90+
new Size(
91+
PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_H_DP),
92+
SizeMode.ABSOLUTE, 0.5f, SizeMode.RELATIVE),
93+
new DynamicTableModel(0, 1),
94+
new Size(
95+
PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_ICON_SIZE_DP),
96+
SizeMode.ABSOLUTE,
97+
PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_ICON_SIZE_DP),
98+
SizeMode.ABSOLUTE));
99+
100+
legend.position(
101+
PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_X_OFFSET_DP),
102+
HorizontalPositioning.ABSOLUTE_FROM_RIGHT,
103+
PixelUtils.dpToPix(DEFAULT_LEGEND_WIDGET_Y_OFFSET_DP),
104+
VerticalPositioning.ABSOLUTE_FROM_BOTTOM,
105+
Anchor.RIGHT_BOTTOM);
106+
107+
legend.setVisible(false);
108+
84109
final float padding = PixelUtils.dpToPix(DEFAULT_PADDING_DP);
85110
pie.setPadding(padding, padding, padding, padding);
86111
}
@@ -93,6 +118,10 @@ protected void processAttrs(TypedArray attrs) {
93118
R.styleable.pie_PieChart_pieBorderColor, R.styleable.pie_PieChart_pieBorderThickness);
94119
}
95120

121+
public void setPie(PieWidget pie) {
122+
this.pie = pie;
123+
}
124+
96125
public PieWidget getPie() {
97126
return pie;
98127
}
@@ -104,4 +133,12 @@ public void addSegment(Segment segment, SegmentFormatter formatter) {
104133
public void removeSegment(Segment segment) {
105134
removeSeries(segment);
106135
}
136+
137+
public PieLegendWidget getLegend() {
138+
return legend;
139+
}
140+
141+
public void setLegend(PieLegendWidget legend) {
142+
this.legend = legend;
143+
}
107144
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.androidplot.pie;
2+
3+
4+
import android.support.annotation.NonNull;
5+
6+
import com.androidplot.ui.widget.LegendItem;
7+
8+
/**
9+
* An item in a {@link PieLegendWidget} corresponding to a {@link Segment} in a {@link PieChart}.
10+
*/
11+
public class PieLegendItem implements LegendItem {
12+
13+
public SegmentFormatter formatter;
14+
15+
public Segment segment;
16+
17+
public PieLegendItem(@NonNull Segment segment, @NonNull SegmentFormatter formatter) {
18+
this.segment = segment;
19+
this.formatter = formatter;
20+
}
21+
22+
@Override
23+
public String getTitle() {
24+
return segment.getTitle();
25+
}
26+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.androidplot.pie;
2+
3+
import android.graphics.Canvas;
4+
import android.graphics.RectF;
5+
import android.support.annotation.NonNull;
6+
7+
import com.androidplot.ui.LayoutManager;
8+
import com.androidplot.ui.SeriesBundle;
9+
import com.androidplot.ui.Size;
10+
import com.androidplot.ui.TableModel;
11+
import com.androidplot.ui.widget.LegendWidget;
12+
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
16+
public class PieLegendWidget extends LegendWidget<PieLegendItem> {
17+
18+
private PieChart pieChart;
19+
20+
public PieLegendWidget(LayoutManager layoutManager, PieChart pieChart,
21+
Size widgetSize,
22+
TableModel tableModel,
23+
Size iconSize) {
24+
super(tableModel, layoutManager, widgetSize, iconSize);
25+
this.pieChart = pieChart;
26+
}
27+
28+
@Override
29+
protected void drawIcon(@NonNull Canvas canvas, @NonNull RectF iconRect, @NonNull PieLegendItem item) {
30+
canvas.drawRect(iconRect, item.formatter.getFillPaint());
31+
}
32+
33+
@Override
34+
protected List<PieLegendItem> getLegendItems() {
35+
final List<PieLegendItem> legendItems = new ArrayList<>();
36+
for(SeriesBundle<Segment, SegmentFormatter> item : pieChart.getRegistry().getLegendEnabledItems()) {
37+
legendItems.add(new PieLegendItem(item.getSeries(), item.getFormatter()));
38+
}
39+
return legendItems;
40+
}
41+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import java.util.List;
2424

2525
/**
26-
* A stack of series to be rendered. The stack order is immutable but individual elements may be
26+
* A stack of series to be rendered. The stack order is immutable but individual elements may be
2727
* manipulated via the public methods of {@link RenderStack.StackElement}.
2828
*/
2929
public class RenderStack<SeriesType extends Series, FormatterType extends Formatter> {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.androidplot.ui.widget;
2+
3+
/**
4+
* An item to be displayed by {@link LegendWidget}.
5+
*/
6+
public interface LegendItem {
7+
8+
/**
9+
*
10+
* @return The user facing label for this item.
11+
*/
12+
String getTitle();
13+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.androidplot.ui.widget;
2+
3+
import java.util.List;
4+
5+
6+
public interface LegendItemOrganizer<LegendItemT extends LegendItem> {
7+
8+
void organize(List<LegendItemT> items);
9+
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package com.androidplot.ui.widget;
2+
3+
import android.graphics.Canvas;
4+
import android.graphics.Color;
5+
import android.graphics.Paint;
6+
import android.graphics.RectF;
7+
import android.support.annotation.NonNull;
8+
import android.support.annotation.Nullable;
9+
10+
import com.androidplot.exception.PlotRenderException;
11+
import com.androidplot.ui.LayoutManager;
12+
import com.androidplot.ui.Size;
13+
import com.androidplot.ui.TableModel;
14+
import com.androidplot.util.FontUtils;
15+
import com.androidplot.util.PixelUtils;
16+
17+
import java.util.Collections;
18+
import java.util.Comparator;
19+
import java.util.Iterator;
20+
import java.util.List;
21+
22+
/**
23+
* Provides core functionality for displaying a legend widget within a {@link com.androidplot.Plot}.
24+
* @param <ItemT>
25+
*/
26+
public abstract class LegendWidget<ItemT extends LegendItem> extends Widget {
27+
28+
private static final float DEFAULT_TEXT_SIZE_DP = 20;
29+
30+
private TableModel tableModel;
31+
private Size iconSize;
32+
33+
private Paint textPaint;
34+
private Paint iconBackgroundPaint;
35+
private Paint iconBorderPaint;
36+
37+
private boolean drawIconBackgroundEnabled = true;
38+
private boolean drawIconBorderEnabled = true;
39+
40+
private Comparator<ItemT> legendItemComparator;
41+
42+
{
43+
textPaint = new Paint();
44+
textPaint.setColor(Color.LTGRAY);
45+
textPaint.setTextSize(PixelUtils.spToPix(DEFAULT_TEXT_SIZE_DP));
46+
textPaint.setAntiAlias(true);
47+
48+
iconBackgroundPaint = new Paint();
49+
iconBackgroundPaint.setColor(Color.BLACK);
50+
51+
iconBorderPaint = new Paint();
52+
iconBorderPaint.setColor(Color.TRANSPARENT);
53+
iconBorderPaint.setStyle(Paint.Style.STROKE);
54+
}
55+
56+
57+
public LegendWidget(@NonNull TableModel tableModel, @NonNull LayoutManager layoutManager,
58+
@NonNull Size size, @NonNull Size iconSize) {
59+
super(layoutManager, size);
60+
setTableModel(tableModel);
61+
this.iconSize = iconSize;
62+
}
63+
64+
@Override
65+
protected void doOnDraw(Canvas canvas, RectF widgetRect) throws PlotRenderException {
66+
final List<ItemT> items = getLegendItems();
67+
if(legendItemComparator != null) {
68+
Collections.sort(items, legendItemComparator);
69+
}
70+
final Iterator<RectF> cellRectIterator = tableModel.getIterator(widgetRect, items.size());
71+
for(ItemT item : items) {
72+
final RectF cellRect = cellRectIterator.next();
73+
final RectF iconRect = getIconRect(cellRect);
74+
beginDrawingCell(canvas, iconRect);
75+
drawItem(canvas, iconRect, item);
76+
finishDrawingCell(canvas, cellRect, iconRect, item);
77+
}
78+
}
79+
80+
protected void drawItem(@NonNull Canvas canvas, @NonNull RectF iconRect, @NonNull ItemT item) {
81+
drawIcon(canvas, iconRect, item);
82+
}
83+
84+
/**
85+
* Draw the icon representing the legend item
86+
* @param canvas
87+
* @param iconRect The space to be occupied by the icon.
88+
* @param item
89+
*/
90+
protected abstract void drawIcon(@NonNull Canvas canvas, @NonNull RectF iconRect, @NonNull ItemT item);
91+
92+
/**
93+
*
94+
* @return The list of legend items to be drawn. This is used to calculate table dimensions etc.
95+
*/
96+
protected abstract List<ItemT> getLegendItems();
97+
98+
private RectF getIconRect(RectF cellRect) {
99+
float cellRectCenterY = cellRect.top + (cellRect.height()/2);
100+
RectF iconRect = iconSize.getRectF(cellRect);
101+
102+
// center the icon rect vertically
103+
float centeredIconOriginY = cellRectCenterY - (iconRect.height()/2);
104+
iconRect.offsetTo(cellRect.left + 1, centeredIconOriginY);
105+
return iconRect;
106+
}
107+
108+
/**
109+
* Done at the start of rendering a new cell. Whatever is drawn here will be beneath the rest
110+
* of the cell content; typically used to draw backgrounds.
111+
* @param canvas
112+
* @param iconRect
113+
*/
114+
protected void beginDrawingCell(Canvas canvas, RectF iconRect) {
115+
116+
if(drawIconBackgroundEnabled && iconBackgroundPaint != null) {
117+
canvas.drawRect(iconRect, iconBackgroundPaint);
118+
}
119+
}
120+
121+
/**
122+
* Done at the end of rendering a new cell. Whatever is drawn here will be on top of
123+
* the rest of the cell content; typically used to draw borders and text.
124+
* @param canvas
125+
* @param cellRect
126+
* @param iconRect
127+
* @param legendItem
128+
*/
129+
protected void finishDrawingCell(Canvas canvas, RectF cellRect, RectF iconRect, LegendItem legendItem) {
130+
131+
if(drawIconBorderEnabled && iconBorderPaint != null) {
132+
canvas.drawRect(iconRect, iconBorderPaint);
133+
}
134+
135+
float centeredTextOriginY = getRectCenterY(cellRect) + (FontUtils.getFontHeight(textPaint)/2);
136+
137+
if (textPaint.getTextAlign().equals(Paint.Align.RIGHT)) {
138+
canvas.drawText(legendItem.getTitle(), iconRect.left - 2, centeredTextOriginY, textPaint);
139+
} else {
140+
canvas.drawText(legendItem.getTitle(), iconRect.right + 2, centeredTextOriginY, textPaint);
141+
}
142+
}
143+
144+
protected static float getRectCenterY(RectF cellRect) {
145+
return cellRect.top + (cellRect.height()/2);
146+
}
147+
148+
public synchronized void setTableModel(TableModel tableModel) {
149+
this.tableModel = tableModel;
150+
}
151+
152+
public Paint getTextPaint() {
153+
return textPaint;
154+
}
155+
156+
public void setTextPaint(Paint textPaint) {
157+
this.textPaint = textPaint;
158+
}
159+
160+
public boolean isDrawIconBackgroundEnabled() {
161+
return drawIconBackgroundEnabled;
162+
}
163+
164+
public void setDrawIconBackgroundEnabled(boolean drawIconBackgroundEnabled) {
165+
this.drawIconBackgroundEnabled = drawIconBackgroundEnabled;
166+
}
167+
168+
public boolean isDrawIconBorderEnabled() {
169+
return drawIconBorderEnabled;
170+
}
171+
172+
public void setDrawIconBorderEnabled(boolean drawIconBorderEnabled) {
173+
this.drawIconBorderEnabled = drawIconBorderEnabled;
174+
}
175+
176+
public Size getIconSize() {
177+
return iconSize;
178+
}
179+
180+
public void setIconSize(Size iconSize) {
181+
this.iconSize = iconSize;
182+
}
183+
184+
public Comparator<ItemT> getLegendItemComparator() {
185+
return legendItemComparator;
186+
}
187+
188+
/**
189+
* Set a scheme for sorting the display order or legend items. By default no sorting is applied
190+
* and {@link com.androidplot.Series} items typically appear in the order which the series was
191+
* added to the {@link com.androidplot.Plot}.
192+
* @param legendItemComparator
193+
*/
194+
public void setLegendItemComparator(Comparator<ItemT> legendItemComparator) {
195+
this.legendItemComparator = legendItemComparator;
196+
}
197+
}

0 commit comments

Comments
 (0)