create changelog entry
[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                                 switch( event.getType()) {\r
127                                 case LAUNCHROD:\r
128                                 case APOGEE:\r
129                                 case BURNOUT:\r
130                                 case EJECTION_CHARGE:\r
131                                         events.add(event);\r
132                                 default:\r
133                                         break;\r
134                                 }\r
135                         }\r
136                 }\r
137 \r
138                 /*\r
139                  * TODO -\r
140                  * Figure out why you can pan all over the place even where there are no visible points.\r
141                  */\r
142                 int seriesCount = 2;\r
143                 // if the same series is selected twice, only plot it once.\r
144                 if ( series1 == series2 ) {\r
145                         seriesCount = 1;\r
146                 }\r
147 \r
148                 XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(seriesCount);\r
149 \r
150                 renderer.setAxisTitleTextSize(16);\r
151                 renderer.setChartTitleTextSize(20);\r
152                 renderer.setLabelsTextSize(15);\r
153                 renderer.setLegendTextSize(15);\r
154                 renderer.setPointSize(5f);\r
155                 renderer.setXLabels(10);\r
156                 renderer.setYLabels(10);\r
157                 renderer.setShowGrid(true);\r
158                 renderer.setZoomButtonsVisible(true);\r
159                 renderer.setChartTitle(sim.getName());\r
160                 renderer.setShowCustomTextGrid(true);\r
161                 renderer.setXLabelsAlign(Align.RIGHT);\r
162                 renderer.setXLabelsAngle(90);  // rotate right\r
163                 for( FlightEvent event : events ) {\r
164                         renderer.addXTextLabel(event.getTime(), event.getType().toString());\r
165                 }\r
166 \r
167                 renderer.setMargins(new int[] { 50, 30, 0, 20 });\r
168                 {\r
169                         for (int i = 0; i < seriesCount; i++) {\r
170                                 XYSeriesRenderer r = new XYSeriesRenderer();\r
171                                 r.setColor(colors[i]);\r
172                                 r.setPointStyle(styles[i]);\r
173                                 r.setFillPoints(true);\r
174                                 renderer.addSeriesRenderer(r);\r
175                                 // setting the YAximMin to 0 locks the origins.\r
176                                 renderer.setYAxisMin(0.0, i);\r
177                         }\r
178                 }\r
179 \r
180                 renderer.setXTitle(formatFlightDataTypeAxisLabel(time));\r
181                 renderer.setXLabelsAlign(Align.RIGHT);\r
182 \r
183                 renderer.setYTitle(formatFlightDataTypeAxisLabel(series1),0);\r
184                 renderer.setYLabelsAlign(Align.RIGHT,0);\r
185 \r
186                 if ( seriesCount > 1 ) {\r
187                         renderer.setYTitle(formatFlightDataTypeAxisLabel(series2), 1);\r
188                         renderer.setYAxisAlign(Align.RIGHT, 1);\r
189                         renderer.setYLabelsAlign(Align.LEFT, 1);\r
190                 }\r
191 \r
192                 renderer.setAxesColor(Color.LTGRAY);\r
193                 renderer.setLabelsColor(Color.LTGRAY);\r
194 \r
195                 XYMultipleSeriesDataset dataset = new XYMultipleSeriesDataset();\r
196 \r
197                 List<Double> timevalues = flightDataBranch.get(time);\r
198                 List<Double> series1values = new ArrayList<Double>( flightDataBranch.get(series1).size() );\r
199                 {\r
200                         Unit u = series1.getUnitGroup().getDefaultUnit();\r
201                         for( Double d: flightDataBranch.get(series1) ) {\r
202                                 series1values.add( u.toUnit(d));\r
203                         }\r
204                 }\r
205 \r
206                 // compute the axis limits using timevalues and series1values.\r
207                 double xmin = 0;\r
208                 double ymin = 0;\r
209                 renderer.setXAxisMin(xmin);\r
210                 renderer.setYAxisMin(ymin);\r
211 \r
212                 double ymax = computeMaxValueWithPadding( series1values );\r
213                 double xmax = Math.ceil( timevalues.get( timevalues.size()-1));\r
214 \r
215                 AndroidLogWrapper.d(SimulationChart.class,"ymax = " + ymax);\r
216                 renderer.setXAxisMax(xmax);\r
217                 renderer.setYAxisMax(ymax);\r
218 \r
219                 // These configurations don't really work well just now.\r
220                 //renderer.setPanLimits(new double[] { xmin, xmax, ymin, ymax });\r
221                 //renderer.setZoomLimits(new double[] { xmin, xmax, ymin, ymax });\r
222 \r
223                 // Add first series\r
224                 addXYSeries(dataset, series1.getName(), timevalues, series1values, 0);\r
225 \r
226                 if ( seriesCount > 1 ) {\r
227                         // Add second series\r
228                         List<Double> series2values = new ArrayList<Double>( flightDataBranch.get(series2).size() );\r
229                         {\r
230                                 Unit u = series2.getUnitGroup().getDefaultUnit();\r
231                                 for( Double d: flightDataBranch.get(series2) ) {\r
232                                         series2values.add( u.toUnit(d));\r
233                                 }\r
234                         }\r
235 \r
236                         addXYSeries(dataset, series2.getName(), timevalues, series2values, 1);\r
237                 }\r
238                 XYChart chart = new LineChart(dataset, renderer);\r
239 \r
240                 return chart;\r
241         }\r
242 \r
243         private static void addXYSeries(XYMultipleSeriesDataset dataset, String titles, List<Double> xValues, List<Double> yValues, int scale) {\r
244                 XYSeries series = new XYSeries(titles, scale);\r
245                 int datasize = xValues.size();\r
246                 for( int i = 0; i<datasize; i++ ) {\r
247                         series.add(xValues.get(i), yValues.get(i));\r
248                 }\r
249                 dataset.addSeries(series);\r
250 \r
251         }\r
252 \r
253         private static double computeMaxValueWithPadding( List<Double> list ) {\r
254                 double max = list.get(0);\r
255                 for( double v : list ) {\r
256                         if ( v > max ) {\r
257                                 max = v;\r
258                         }\r
259                 }\r
260                 if ( max <= 0 ) return 1.0;\r
261 \r
262                 // Do something stupid.\r
263                 // return:\r
264                 //  10 if max <= 10\r
265                 //  next 10 if 10 < max < 1000\r
266                 //  next 100 if 1000 < max < 10,000\r
267                 //  next 1000 if max >= 10,000\r
268                 double numdigits = Math.floor(Math.log10(max));\r
269 \r
270                 if ( numdigits <= 1.0 ) {\r
271                         return 10.0;\r
272                 } else if ( numdigits <= 3.0 ) {\r
273                         return 10.0 * ( Math.ceil( max/10.0));\r
274                 } else if ( numdigits <= 4.0 ) {\r
275                         return 100.0 * ( Math.ceil( max/ 100.0) );\r
276                 } else {\r
277                         return 1000.0 * ( Math.ceil( max / 1000.0 ));\r
278                 }\r
279 \r
280         }\r
281 \r
282 }\r