601bf859c92f9074e8cd501195ebe980445b9411
[debian/openrocket] / android / src / net / sf / openrocket / android / simulation / SimulationChart.java
1 /**\r
2  * Copyright (C) 2009, 2010 SC 4ViewSoft SRL\r
3  *  \r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *  \r
8  *      http://www.apache.org/licenses/LICENSE-2.0\r
9  *  \r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 package net.sf.openrocket.android.simulation;\r
17 \r
18 import java.io.Serializable;\r
19 import java.util.ArrayList;\r
20 import java.util.List;\r
21 \r
22 import net.sf.openrocket.android.util.AndroidLogWrapper;\r
23 import net.sf.openrocket.document.OpenRocketDocument;\r
24 import net.sf.openrocket.document.Simulation;\r
25 import net.sf.openrocket.simulation.FlightDataBranch;\r
26 import net.sf.openrocket.simulation.FlightDataType;\r
27 import net.sf.openrocket.simulation.FlightEvent;\r
28 import net.sf.openrocket.unit.Unit;\r
29 \r
30 import org.achartengine.chart.LineChart;\r
31 import org.achartengine.chart.PointStyle;\r
32 import org.achartengine.chart.XYChart;\r
33 import org.achartengine.model.XYMultipleSeriesDataset;\r
34 import org.achartengine.model.XYSeries;\r
35 import org.achartengine.renderer.XYMultipleSeriesRenderer;\r
36 import org.achartengine.renderer.XYSeriesRenderer;\r
37 \r
38 import android.graphics.Color;\r
39 import android.graphics.Paint.Align;\r
40 \r
41 /**\r
42  * This is really a flyweight object so we can serialize the\r
43  * values behind a simulation chart.  Since OpenRocketDocument, FlightDataBranch,\r
44  * FlightDataType, Unit and all the other underlying types are not serializable,\r
45  * we have to resort to persisting just the bare minimum of information.\r
46  * \r
47  * This also means without further changes to FlightDataType, we cannot actually\r
48  * restore the displayed series.\r
49  * \r
50  * TODO make FlightDataBranch serializable or at least reconstructable from\r
51  * from some the name.\r
52  * \r
53  */\r
54 public class SimulationChart implements Serializable {\r
55         \r
56         private final int simulationIndex;\r
57         private transient FlightDataType series1;\r
58         private transient FlightDataType series2;\r
59         private transient List<FlightEvent> events;\r
60         \r
61         // Define 4 different colors and point styles to use for the series.\r
62         // For now only 2 series are supported though.\r
63         private final static int[] colors = new int[] { Color.BLUE, Color.YELLOW, Color.GREEN, Color.RED };\r
64         private final static PointStyle[] styles = new PointStyle[] { PointStyle.CIRCLE, PointStyle.DIAMOND,\r
65                 PointStyle.TRIANGLE, PointStyle.SQUARE };\r
66 \r
67         public SimulationChart(int simulationIndex) {\r
68                 super();\r
69                 this.simulationIndex = simulationIndex;\r
70         }\r
71 \r
72         private static String formatFlightDataTypeAxisLabel( FlightDataType fdt ) {\r
73                 return fdt.getName() + " (" + fdt.getUnitGroup().getDefaultUnit().toString() + ")";\r
74         }\r
75 \r
76         public void setSeries1(FlightDataType series1) {\r
77                 this.series1 = series1;\r
78         }\r
79 \r
80         public FlightDataType getSeries1() {\r
81                 return series1;\r
82         }\r
83 \r
84         public void setSeries2(FlightDataType series2) {\r
85                 this.series2 = series2;\r
86         }\r
87 \r
88         public FlightDataType getSeries2() {\r
89                 return series2;\r
90         }\r
91 \r
92         public void setEvents( List<FlightEvent> events ) {\r
93                 this.events = events;\r
94         }\r
95         \r
96         public List<FlightEvent> getEvents() {\r
97                 return events;\r
98         }\r
99 \r
100         public FlightDataBranch getFlightDataBranch( OpenRocketDocument rocketDocument ) {\r
101                 Simulation sim = rocketDocument.getSimulation(simulationIndex);\r
102                 FlightDataBranch flightDataBranch = sim.getSimulatedData().getBranch(0);\r
103                 return flightDataBranch;\r
104         }\r
105         /**\r
106          * Executes the chart demo.\r
107          * \r
108          * @param context the context\r
109          * @return the built intent\r
110          */\r
111         public XYChart buildChart(OpenRocketDocument rocketDocument) {\r
112 \r
113                 Simulation sim = rocketDocument.getSimulation(simulationIndex);\r
114                 FlightDataBranch flightDataBranch = sim.getSimulatedData().getBranch(0);\r
115                 FlightDataType time = FlightDataType.TYPE_TIME;\r
116                 if (series1== null) {\r
117                         series1 = flightDataBranch.getTypes()[1];\r
118                 }\r
119                 if (series2== null) {\r
120                         series2 = flightDataBranch.getTypes()[2];\r
121                 }\r
122                 \r
123                 if ( events == null ) {\r
124                         events = new ArrayList<FlightEvent>();\r
125                         for ( FlightEvent event : flightDataBranch.getEvents() ) {\r
126                                 events.add(event);\r
127                         }\r
128                 }\r
129 \r
130                 /*\r
131                  * TODO -\r
132                  * Figure out why you can pan all over the place even where there are no visible points.\r
133                  */\r
134                 int seriesCount = 2;\r
135                 // if the same series is selected twice, only plot it once.\r
136                 if ( series1 == series2 ) {\r
137                         seriesCount = 1;\r
138                 }\r
139 \r
140                 XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(seriesCount);\r
141 \r
142                 renderer.setAxisTitleTextSize(16);\r
143                 renderer.setChartTitleTextSize(20);\r
144                 renderer.setLabelsTextSize(15);\r
145                 renderer.setLegendTextSize(15);\r
146                 renderer.setPointSize(5f);\r
147                 renderer.setXLabels(10);\r
148                 renderer.setYLabels(10);\r
149                 renderer.setShowGrid(true);\r
150                 renderer.setZoomButtonsVisible(true);\r
151                 renderer.setChartTitle(sim.getName());\r
152                 renderer.setShowCustomTextGrid(true);\r
153                 renderer.setXLabelsAlign(Align.RIGHT);\r
154                 renderer.setXLabelsAngle(90);  // rotate right\r
155                 for( FlightEvent event : events ) {\r
156                         renderer.addXTextLabel(event.getTime(), event.getType().toString());\r
157                 }\r
158 \r
159                 renderer.setMargins(new int[] { 50, 30, 0, 20 });\r
160                 {\r
161                         for (int i = 0; i < seriesCount; i++) {\r
162                                 XYSeriesRenderer r = new XYSeriesRenderer();\r
163                                 r.setColor(colors[i]);\r
164                                 r.setPointStyle(styles[i]);\r
165                                 r.setFillPoints(true);\r
166                                 renderer.addSeriesRenderer(r);\r
167                                 // setting the YAximMin to 0 locks the origins.\r
168                                 renderer.setYAxisMin(0.0, i);\r
169                         }\r
170                 }\r
171 \r
172                 renderer.setXTitle(formatFlightDataTypeAxisLabel(time));\r
173                 renderer.setXLabelsAlign(Align.RIGHT);\r
174 \r
175                 renderer.setYTitle(formatFlightDataTypeAxisLabel(series1),0);\r
176                 renderer.setYLabelsAlign(Align.RIGHT,0);\r
177 \r
178                 if ( seriesCount > 1 ) {\r
179                         renderer.setYTitle(formatFlightDataTypeAxisLabel(series2), 1);\r
180                         renderer.setYAxisAlign(Align.RIGHT, 1);\r
181                         renderer.setYLabelsAlign(Align.LEFT, 1);\r
182                 }\r
183 \r
184                 renderer.setAxesColor(Color.LTGRAY);\r
185                 renderer.setLabelsColor(Color.LTGRAY);\r
186 \r
187                 XYMultipleSeriesDataset dataset = new XYMultipleSeriesDataset();\r
188 \r
189                 List<Double> timevalues = flightDataBranch.get(time);\r
190                 List<Double> series1values = new ArrayList<Double>( flightDataBranch.get(series1).size() );\r
191                 {\r
192                         Unit u = series1.getUnitGroup().getDefaultUnit();\r
193                         for( Double d: flightDataBranch.get(series1) ) {\r
194                                 series1values.add( u.toUnit(d));\r
195                         }\r
196                 }\r
197 \r
198                 // compute the axis limits using timevalues and series1values.\r
199                 double xmin = 0;\r
200                 double ymin = 0;\r
201                 renderer.setXAxisMin(xmin);\r
202                 renderer.setYAxisMin(ymin);\r
203 \r
204                 double ymax = computeMaxValueWithPadding( series1values );\r
205                 double xmax = Math.ceil( timevalues.get( timevalues.size()-1));\r
206                 \r
207                 AndroidLogWrapper.d(SimulationChart.class,"ymax = " + ymax);\r
208                 renderer.setXAxisMax(xmax);\r
209                 renderer.setYAxisMax(ymax);\r
210 \r
211                 // These configurations don't really work well just now.\r
212                 //renderer.setPanLimits(new double[] { xmin, xmax, ymin, ymax });\r
213                 //renderer.setZoomLimits(new double[] { xmin, xmax, ymin, ymax });\r
214 \r
215                 // Add first series\r
216                 addXYSeries(dataset, series1.getName(), timevalues, series1values, 0);\r
217 \r
218                 if ( seriesCount > 1 ) {\r
219                         // Add second series\r
220                         List<Double> series2values = new ArrayList<Double>( flightDataBranch.get(series2).size() );\r
221                         {\r
222                                 Unit u = series2.getUnitGroup().getDefaultUnit();\r
223                                 for( Double d: flightDataBranch.get(series2) ) {\r
224                                         series2values.add( u.toUnit(d));\r
225                                 }\r
226                         }\r
227 \r
228                         addXYSeries(dataset, series2.getName(), timevalues, series2values, 1);\r
229                 }\r
230                 XYChart chart = new LineChart(dataset, renderer);\r
231                 \r
232                 return chart;\r
233         }\r
234 \r
235         private static void addXYSeries(XYMultipleSeriesDataset dataset, String titles, List<Double> xValues, List<Double> yValues, int scale) {\r
236                 XYSeries series = new XYSeries(titles, scale);\r
237                 int datasize = xValues.size();\r
238                 for( int i = 0; i<datasize; i++ ) {\r
239                         series.add(xValues.get(i), yValues.get(i));\r
240                 }\r
241                 dataset.addSeries(series);\r
242 \r
243         }\r
244 \r
245         private static double computeMaxValueWithPadding( List<Double> list ) {\r
246                 double max = list.get(0);\r
247                 for( double v : list ) {\r
248                         if ( v > max ) {\r
249                                 max = v;\r
250                         }\r
251                 }\r
252                 if ( max <= 0 ) return 1.0;\r
253 \r
254                 // Do something stupid.\r
255                 // return:\r
256                 //  10 if max <= 10\r
257                 //  next 10 if 10 < max < 1000\r
258                 //  next 100 if 1000 < max < 10,000\r
259                 //  next 1000 if max >= 10,000\r
260                 double numdigits = Math.floor(Math.log10(max));\r
261                 \r
262                 if ( numdigits <= 1.0 ) {\r
263                         return 10.0;\r
264                 } else if ( numdigits <= 3.0 ) {\r
265                         return 10.0 * ( Math.ceil( max/10.0));\r
266                 } else if ( numdigits <= 4.0 ) {\r
267                         return 100.0 * ( Math.ceil( max/ 100.0) );\r
268                 } else {\r
269                         return 1000.0 * ( Math.ceil( max / 1000.0 ));\r
270                 }\r
271                 \r
272         }\r
273         \r
274 }\r