1/*
2 * Copyright 2012 AndroidPlot.com
3 *
4 *    Licensed under the Apache License, Version 2.0 (the "License");
5 *    you may not use this file except in compliance with the License.
6 *    You may obtain a copy of the License at
7 *
8 *        http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *    Unless required by applicable law or agreed to in writing, software
11 *    distributed under the License is distributed on an "AS IS" BASIS,
12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *    See the License for the specific language governing permissions and
14 *    limitations under the License.
15 */
16
17package com.androidplot.xy;
18
19import android.graphics.*;
20import android.util.Pair;
21import com.androidplot.exception.PlotRenderException;
22import com.androidplot.util.ValPixConverter;
23
24import java.util.ArrayList;
25import java.util.List;
26
27/**
28 * Renders a point as a line with the vertices marked.  Requires 2 or more points to
29 * be rendered.
30 */
31public class LineAndPointRenderer<FormatterType extends LineAndPointFormatter> extends XYSeriesRenderer<FormatterType> {
32
33    public LineAndPointRenderer(XYPlot plot) {
34        super(plot);
35    }
36
37    @Override
38    public void onRender(Canvas canvas, RectF plotArea) throws PlotRenderException {
39
40
41        List<XYSeries> seriesList = getPlot().getSeriesListForRenderer(this.getClass());
42        if (seriesList != null) {
43            for (XYSeries series : seriesList) {
44                //synchronized(series) {
45                    drawSeries(canvas, plotArea, series, getFormatter(series));
46                //}
47            }
48        }
49    }
50
51    @Override
52    public void doDrawLegendIcon(Canvas canvas, RectF rect, LineAndPointFormatter formatter) {
53        // horizontal icon:
54        float centerY = rect.centerY();
55        float centerX = rect.centerX();
56
57        if(formatter.getFillPaint() != null) {
58            canvas.drawRect(rect, formatter.getFillPaint());
59        }
60        if(formatter.getLinePaint() != null) {
61            canvas.drawLine(rect.left, rect.bottom, rect.right, rect.top, formatter.getLinePaint());
62        }
63
64        if(formatter.getVertexPaint() != null) {
65            canvas.drawPoint(centerX, centerY, formatter.getVertexPaint());
66        }
67    }
68
69    /**
70     * This method exists for StepRenderer to override without having to duplicate any
71     * additional code.
72     */
73    protected void appendToPath(Path path, PointF thisPoint, PointF lastPoint) {
74
75        path.lineTo(thisPoint.x, thisPoint.y);
76    }
77
78
79    protected void drawSeries(Canvas canvas, RectF plotArea, XYSeries series, LineAndPointFormatter formatter) {
80        PointF thisPoint;
81        PointF lastPoint = null;
82        PointF firstPoint = null;
83        Paint  linePaint = formatter.getLinePaint();
84
85        //PointF lastDrawn = null;
86        Path path = null;
87        ArrayList<Pair<PointF, Integer>> points = new ArrayList<Pair<PointF, Integer>>(series.size());
88        for (int i = 0; i < series.size(); i++) {
89            Number y = series.getY(i);
90            Number x = series.getX(i);
91
92            if (y != null && x != null) {
93                thisPoint = ValPixConverter.valToPix(
94                        x,
95                        y,
96                        plotArea,
97                        getPlot().getCalculatedMinX(),
98                        getPlot().getCalculatedMaxX(),
99                        getPlot().getCalculatedMinY(),
100                        getPlot().getCalculatedMaxY());
101                points.add(new Pair<PointF, Integer>(thisPoint, i));
102                //appendToPath(path, thisPoint, lastPoint);
103            } else {
104                thisPoint = null;
105            }
106
107            if(linePaint != null && thisPoint != null) {
108
109                // record the first point of the new Path
110                if(firstPoint == null) {
111                    path = new Path();
112                    firstPoint = thisPoint;
113                    // create our first point at the bottom/x position so filling
114                    // will look good
115                    path.moveTo(firstPoint.x, firstPoint.y);
116                }
117
118                if(lastPoint != null) {
119                    appendToPath(path, thisPoint, lastPoint);
120                }
121
122                lastPoint = thisPoint;
123            } else {
124                if(lastPoint != null) {
125                    renderPath(canvas, plotArea, path, firstPoint, lastPoint, formatter);
126                }
127                firstPoint = null;
128                lastPoint = null;
129            }
130        }
131        if(linePaint != null && firstPoint != null) {
132            renderPath(canvas, plotArea, path, firstPoint, lastPoint, formatter);
133        }
134
135        // TODO: benchmark this against drawPoints(float[]);
136        Paint vertexPaint = formatter.getVertexPaint();
137        PointLabelFormatter plf = formatter.getPointLabelFormatter();
138        if (vertexPaint != null || plf != null) {
139            for (Pair<PointF, Integer> p : points) {
140            	PointLabeler pointLabeler = formatter.getPointLabeler();
141
142                // if vertexPaint is available, draw vertex:
143                if(vertexPaint != null) {
144                    canvas.drawPoint(p.first.x, p.first.y, formatter.getVertexPaint());
145                }
146
147                // if textPaint and pointLabeler are available, draw point's text label:
148                if(plf != null && pointLabeler != null) {
149                    canvas.drawText(pointLabeler.getLabel(series, p.second), p.first.x + plf.hOffset, p.first.y + plf.vOffset, plf.getTextPaint());
150                }
151            }
152        }
153    }
154
155    protected void renderPath(Canvas canvas, RectF plotArea, Path path, PointF firstPoint, PointF lastPoint, LineAndPointFormatter formatter) {
156        Path outlinePath = new Path(path);
157
158        // determine how to close the path for filling purposes:
159        // We always need to calculate this path because it is also used for
160        // masking off for region highlighting.
161        switch (formatter.getFillDirection()) {
162            case BOTTOM:
163                path.lineTo(lastPoint.x, plotArea.bottom);
164                path.lineTo(firstPoint.x, plotArea.bottom);
165                path.close();
166                break;
167            case TOP:
168                path.lineTo(lastPoint.x, plotArea.top);
169                path.lineTo(firstPoint.x, plotArea.top);
170                path.close();
171                break;
172            case RANGE_ORIGIN:
173                float originPix = ValPixConverter.valToPix(
174                        getPlot().getRangeOrigin().doubleValue(),
175                        getPlot().getCalculatedMinY().doubleValue(),
176                        getPlot().getCalculatedMaxY().doubleValue(),
177                        plotArea.height(),
178                        true);
179                originPix += plotArea.top;
180
181                path.lineTo(lastPoint.x, originPix);
182                path.lineTo(firstPoint.x, originPix);
183                path.close();
184                break;
185            default:
186                throw new UnsupportedOperationException("Fill direction not yet implemented: " + formatter.getFillDirection());
187        }
188
189        if (formatter.getFillPaint() != null) {
190            canvas.drawPath(path, formatter.getFillPaint());
191        }
192
193
194        //}
195
196        // draw any visible regions on top of the base region:
197        double minX = getPlot().getCalculatedMinX().doubleValue();
198        double maxX = getPlot().getCalculatedMaxX().doubleValue();
199        double minY = getPlot().getCalculatedMinY().doubleValue();
200        double maxY = getPlot().getCalculatedMaxY().doubleValue();
201
202        // draw each region:
203        for (RectRegion r : RectRegion.regionsWithin(formatter.getRegions().elements(), minX, maxX, minY, maxY)) {
204            XYRegionFormatter f = formatter.getRegionFormatter(r);
205            RectF regionRect = r.getRectF(plotArea, minX, maxX, minY, maxY);
206            if (regionRect != null) {
207                try {
208                canvas.save(Canvas.ALL_SAVE_FLAG);
209                canvas.clipPath(path);
210                canvas.drawRect(regionRect, f.getPaint());
211                } finally {
212                    canvas.restore();
213                }
214            }
215        }
216
217        // finally we draw the outline path on top of everything else:
218        if(formatter.getLinePaint() != null) {
219            canvas.drawPath(outlinePath, formatter.getLinePaint());
220        }
221
222        path.rewind();
223    }
224}
225