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