2 * Copyright (C) 2009, 2010 SC 4ViewSoft SRL
\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
8 * http://www.apache.org/licenses/LICENSE-2.0
\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
16 package net.sf.openrocket.android.simulation;
\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
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
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
40 import android.graphics.Color;
\r
41 import android.graphics.Paint.Align;
\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
49 * This also means without further changes to FlightDataType, we cannot actually
\r
50 * restore the displayed series.
\r
52 * TODO make FlightDataBranch serializable or at least reconstructable from
\r
53 * from some the name.
\r
56 public class SimulationChart implements Serializable {
\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
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
69 public SimulationChart(int simulationIndex) {
\r
71 this.simulationIndex = simulationIndex;
\r
74 private static String formatFlightDataTypeAxisLabel( FlightDataType fdt ) {
\r
75 return fdt.getName() + " (" + fdt.getUnitGroup().getDefaultUnit().toString() + ")";
\r
78 public void setSeries1(FlightDataType series1) {
\r
79 this.series1 = series1;
\r
82 public void setSeries2(FlightDataType series2) {
\r
83 this.series2 = series2;
\r
86 public void setEvents( Map<Double,String> events ) {
\r
87 this.events = events;
\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
96 * Executes the chart demo.
\r
98 * @param context the context
\r
99 * @return the built intent
\r
101 public XYChart buildChart(OpenRocketDocument rocketDocument) {
\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
109 if (series2== null) {
\r
110 series2 = flightDataBranch.getTypes()[2];
\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
122 * Figure out why you can pan all over the place even where there are no visible points.
\r
124 int seriesCount = 2;
\r
125 // if the same series is selected twice, only plot it once.
\r
126 if ( series1 == series2 ) {
\r
130 XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(seriesCount);
\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
149 renderer.setMargins(new int[] { 50, 30, 0, 20 });
\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
162 renderer.setXTitle(formatFlightDataTypeAxisLabel(time));
\r
163 renderer.setXLabelsAlign(Align.RIGHT);
\r
165 renderer.setYTitle(formatFlightDataTypeAxisLabel(series1),0);
\r
166 renderer.setYLabelsAlign(Align.RIGHT,0);
\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
174 renderer.setAxesColor(Color.LTGRAY);
\r
175 renderer.setLabelsColor(Color.LTGRAY);
\r
177 XYMultipleSeriesDataset dataset = new XYMultipleSeriesDataset();
\r
179 List<Double> timevalues = flightDataBranch.get(time);
\r
180 List<Double> series1values = new ArrayList<Double>( flightDataBranch.get(series1).size() );
\r
182 Unit u = series1.getUnitGroup().getDefaultUnit();
\r
183 for( Double d: flightDataBranch.get(series1) ) {
\r
184 series1values.add( u.toUnit(d));
\r
188 // compute the axis limits using timevalues and series1values.
\r
191 renderer.setXAxisMin(xmin);
\r
192 renderer.setYAxisMin(ymin);
\r
194 double ymax = computeMaxValueWithPadding( series1values );
\r
195 double xmax = Math.ceil( timevalues.get( timevalues.size()-1));
\r
197 AndroidLogWrapper.d(SimulationChart.class,"ymax = " + ymax);
\r
198 renderer.setXAxisMax(xmax);
\r
199 renderer.setYAxisMax(ymax);
\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
205 // Add first series
\r
206 addXYSeries(dataset, series1.getName(), timevalues, series1values, 0);
\r
208 if ( seriesCount > 1 ) {
\r
209 // Add second series
\r
210 List<Double> series2values = new ArrayList<Double>( flightDataBranch.get(series2).size() );
\r
212 Unit u = series2.getUnitGroup().getDefaultUnit();
\r
213 for( Double d: flightDataBranch.get(series2) ) {
\r
214 series2values.add( u.toUnit(d));
\r
218 addXYSeries(dataset, series2.getName(), timevalues, series2values, 1);
\r
220 XYChart chart = new LineChart(dataset, renderer);
\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
231 dataset.addSeries(series);
\r
235 private static double computeMaxValueWithPadding( List<Double> list ) {
\r
236 double max = list.get(0);
\r
237 for( double v : list ) {
\r
242 if ( max <= 0 ) return 1.0;
\r
244 // Do something stupid.
\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
252 if ( numdigits <= 1.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
259 return 1000.0 * ( Math.ceil( max / 1000.0 ));
\r