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 
17 package com.androidplot.xy;
18 
19 import android.graphics.Canvas;
20 import android.util.Log;
21 import android.util.Pair;
22 import com.androidplot.Plot;
23 import com.androidplot.PlotListener;
24 
25 import java.util.LinkedList;
26 import java.util.List;
27 import java.util.NoSuchElementException;
28 import java.util.concurrent.locks.ReentrantReadWriteLock;
29 
30 
31 /**
32  * A convenience class used to create instances of XYPlot generated from Lists of Numbers.
33  */
34 public class SimpleXYSeries implements XYSeries, PlotListener {
35 
36     private static final String TAG = SimpleXYSeries.class.getName();
37 
38     @Override
onBeforeDraw(Plot source, Canvas canvas)39     public void onBeforeDraw(Plot source, Canvas canvas) {
40         lock.readLock().lock();
41     }
42 
43     @Override
onAfterDraw(Plot source, Canvas canvas)44     public void onAfterDraw(Plot source, Canvas canvas) {
45         lock.readLock().unlock();
46     }
47 
48     public enum ArrayFormat {
49         Y_VALS_ONLY,
50         XY_VALS_INTERLEAVED
51     }
52 
53     private volatile LinkedList<Number> xVals = new LinkedList<Number>();
54     private volatile LinkedList<Number> yVals = new LinkedList<Number>();
55     private volatile String title = null;
56     private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
57 
58 
SimpleXYSeries(String title)59     public SimpleXYSeries(String title) {
60         this.title = title;
61     }
62     /**
63      * Generates an XYSeries instance from the List of numbers passed in.  This is a convenience class
64      * and should only be used for static data models; it is not suitable for representing dynamically
65      * changing data.
66      *
67      * @param model  A List of Number elements comprising the data model.
68      * @param format Format of the model.  A format of Y_VALS_ONLY means that the array only contains y-values.
69      *               For this format x values are autogenerated using values of 0 through n-1 where n is the size of the model.
70      * @param title  Title of the series
71      */
SimpleXYSeries(List<? extends Number> model, ArrayFormat format, String title)72     public SimpleXYSeries(List<? extends Number> model, ArrayFormat format, String title) {
73         this(title);
74         setModel(model, format);
75     }
76 
SimpleXYSeries(List<? extends Number> xVals, List<? extends Number> yVals, String title)77     public SimpleXYSeries(List<? extends Number> xVals, List<? extends Number> yVals, String title) {
78         this(title);
79         if(xVals == null || yVals == null) {
80             throw new IllegalArgumentException("Neither the xVals nor the yVals parameters may be null.");
81         }
82 
83         if(xVals.size() != yVals.size()) {
84             throw new IllegalArgumentException("xVals and yVals List parameters must be of the same size.");
85         }
86 
87         this.xVals.addAll(xVals);
88         this.yVals.addAll(yVals);
89     }
90 
91     /**
92      * Use index value as xVal, instead of explicit, user provided xVals.
93      */
useImplicitXVals()94     public void useImplicitXVals() {
95         lock.writeLock().lock();
96         try {
97             xVals = null;
98         } finally {
99             lock.writeLock().unlock();
100         }
101     }
102 
103     /**
104      * Use the provided list of Numbers as yVals and their corresponding indexes as xVals.
105      * @param model A List of Number elements comprising the data model.
106      * @param format Format of the model.  A format of Y_VALS_ONLY means that the array only contains y-values.
107      *               For this format x values are autogenerated using values of 0 through n-1 where n is the size of the model.
108      */
setModel(List<? extends Number> model, ArrayFormat format)109     public void setModel(List<? extends Number> model, ArrayFormat format) {
110 
111         lock.writeLock().lock();
112         try {
113             // empty the current values:
114             //xVals.clear();
115             xVals = null;
116             yVals.clear();
117 
118             // make sure the new model has data:
119             if (model == null || model.size() == 0) {
120                 return;
121             }
122 
123             switch (format) {
124 
125                 // array containing only y-vals. assume x = index:
126                 case Y_VALS_ONLY:
127                     for(Number n : model) {
128                         yVals.add(n);
129                     }
130                     /*for (int i = 0; i < model.size(); i++) {
131                         //xVals.add(i);
132                         yVals.add(model.get(i));
133                     }*/
134                     break;
135 
136                 // xy interleaved array:
137                 case XY_VALS_INTERLEAVED:
138                     if (xVals == null) {
139                         xVals = new LinkedList<Number>();
140                     }
141                     if (model.size() % 2 != 0) {
142                         throw new IndexOutOfBoundsException("Cannot auto-generate series from odd-sized xy List.");
143                     }
144                     // always need an x and y array so init them now:
145                     int sz = model.size() / 2;
146                     for (int i = 0, j = 0; i < sz; i++, j += 2) {
147                         xVals.add(model.get(j));
148                         yVals.add(model.get(j + 1));
149                     }
150                     break;
151                 default:
152                     throw new IllegalArgumentException("Unexpected enum value: " + format);
153             }
154         } finally {
155             lock.writeLock().unlock();
156         }
157     }
158 
159     /**
160      * Sets individual x value based on index
161      * @param value
162      * @param index
163      */
setX(Number value, int index)164     public void setX(Number value, int index) {
165         lock.writeLock().lock();
166         try {
167             xVals.set(index, value);
168         } finally {
169             lock.writeLock().unlock();
170         }
171     }
172 
173     /**
174      * Sets individual y value based on index
175      * @param value
176      * @param index
177      */
setY(Number value, int index)178     public void setY(Number value, int index) {
179         lock.writeLock().lock();
180         try {
181             yVals.set(index, value);
182         } finally {
183             lock.writeLock().unlock();
184         }
185     }
186 
187     /**
188      * Sets xy values based on index
189      * @param xVal
190      * @param yVal
191      * @param index
192      */
setXY(Number xVal, Number yVal, int index)193     public void setXY(Number xVal, Number yVal, int index) {
194         lock.writeLock().lock();
195         try {
196             yVals.set(index, yVal);
197             xVals.set(index, xVal);
198         } finally {lock.writeLock().unlock();}
199     }
200 
addFirst(Number x, Number y)201     public void addFirst(Number x, Number y) {
202         lock.writeLock().lock();
203         try {
204             if (xVals != null) {
205                 xVals.addFirst(x);
206             }
207             yVals.addFirst(y);
208         } finally {
209             lock.writeLock().unlock();
210         }
211     }
212 
213     /**
214      *
215      * @return Pair<Number, Number> with first equal to x-val and second equal to y-val.
216      */
removeFirst()217     public Pair<Number, Number> removeFirst() {
218         lock.writeLock().lock();
219         try {
220             if (size() <= 0) {
221                 throw new NoSuchElementException();
222             }
223             return new Pair<Number, Number>(xVals != null ? xVals.removeFirst() : 0, yVals.removeFirst());
224         } finally {
225             lock.writeLock().unlock();
226         }
227     }
228 
addLast(Number x, Number y)229     public void addLast(Number x, Number y) {
230         lock.writeLock().lock();
231         try {
232             if (xVals != null) {
233                 xVals.addLast(x);
234             }
235             yVals.addLast(y);
236         } finally {
237             lock.writeLock().unlock();
238         }
239     }
240 
241     /**
242      *
243      * @return Pair<Number, Number> with first equal to x-val and second equal to y-val.
244      */
removeLast()245     public Pair<Number, Number> removeLast() {
246         lock.writeLock().lock();
247         try {
248             if (size() <= 0) {
249                 throw new NoSuchElementException();
250             }
251             return new Pair<Number, Number>(xVals != null ? xVals.removeLast() : yVals.size() - 1, yVals.removeLast());
252         } finally {
253             lock.writeLock().unlock();
254         }
255     }
256 
257     @Override
getTitle()258     public String getTitle() {
259         return title;
260     }
261 
setTitle(String title)262     public void setTitle(String title) {
263         lock.writeLock().lock();
264         try {
265             this.title = title;
266         } finally {lock.writeLock().unlock();}
267     }
268 
269     @Override
size()270     public int size() {
271         return yVals != null ? yVals.size() : 0;
272     }
273 
274     @Override
getX(int index)275     public Number getX(int index) {
276         return xVals != null ? xVals.get(index) : index;
277     }
278 
279     @Override
getY(int index)280     public Number getY(int index) {
281         return yVals.get(index);
282     }
283 }
284