move gui to another source package
[sw/motorsim] / gui / com / billkuker / rocketry / motorsim / visual / Chart.java
1 package com.billkuker.rocketry.motorsim.visual;\r
2 \r
3 import java.awt.BorderLayout;\r
4 import java.awt.Color;\r
5 import java.awt.Font;\r
6 import java.lang.reflect.InvocationTargetException;\r
7 import java.lang.reflect.Method;\r
8 import java.util.Collection;\r
9 import java.util.Iterator;\r
10 import java.util.concurrent.ExecutorService;\r
11 import java.util.concurrent.Executors;\r
12 import java.util.concurrent.ThreadFactory;\r
13 \r
14 import javax.measure.quantity.Area;\r
15 import javax.measure.quantity.Length;\r
16 import javax.measure.quantity.Quantity;\r
17 import javax.measure.quantity.Volume;\r
18 import javax.measure.unit.SI;\r
19 import javax.measure.unit.Unit;\r
20 import javax.swing.JFrame;\r
21 import javax.swing.JPanel;\r
22 import javax.swing.SwingUtilities;\r
23 \r
24 import org.apache.log4j.Logger;\r
25 import org.jfree.chart.ChartFactory;\r
26 import org.jfree.chart.ChartPanel;\r
27 import org.jfree.chart.JFreeChart;\r
28 import org.jfree.chart.plot.Marker;\r
29 import org.jfree.chart.plot.PlotOrientation;\r
30 import org.jfree.chart.plot.ValueMarker;\r
31 import org.jfree.data.xy.XYSeries;\r
32 import org.jfree.data.xy.XYSeriesCollection;\r
33 import org.jfree.ui.RectangleInsets;\r
34 import org.jfree.ui.TextAnchor;\r
35 import org.jscience.physics.amount.Amount;\r
36 \r
37 import com.billkuker.rocketry.motorsim.Burn;\r
38 import com.billkuker.rocketry.motorsim.RocketScience;\r
39 import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
40 \r
41 public class Chart<X extends Quantity, Y extends Quantity> extends JPanel {\r
42         private static final long serialVersionUID = 1L;\r
43         private static Logger log = Logger.getLogger(Burn.class);\r
44 \r
45         private static ThreadFactory tf = new ThreadFactory() {\r
46                 public Thread newThread(Runnable r) {\r
47                         Thread t = new Thread(r);\r
48                         t.setDaemon(true);\r
49                         return t;\r
50                 }\r
51         };\r
52         private static ExecutorService fast = Executors.newFixedThreadPool(2, tf);\r
53         private static ExecutorService slow = Executors.newFixedThreadPool(2, tf);\r
54         private volatile boolean stop = false;\r
55 \r
56         public class IntervalDomain implements Iterable<Amount<X>> {\r
57 \r
58                 Amount<X> low, high, delta;\r
59                 int steps = 100;\r
60 \r
61                 public IntervalDomain(Amount<X> low, Amount<X> high) {\r
62                         this.low = low;\r
63                         this.high = high;\r
64                         delta = high.minus(low).divide(steps);\r
65                 }\r
66 \r
67                 public IntervalDomain(Amount<X> low, Amount<X> high, int steps) {\r
68                         this.steps = steps;\r
69                         this.low = low;\r
70                         this.high = high;\r
71                         delta = high.minus(low).divide(steps);\r
72                 }\r
73 \r
74                 public Iterator<Amount<X>> iterator() {\r
75                         return new Iterator<Amount<X>>() {\r
76                                 Amount<X> current = low;\r
77 \r
78                                 public boolean hasNext() {\r
79                                         return current.isLessThan(high.plus(delta));\r
80                                 }\r
81 \r
82                                 public Amount<X> next() {\r
83                                         Amount<X> ret = current;\r
84                                         current = current.plus(delta);\r
85                                         return ret;\r
86                                 }\r
87 \r
88                                 public final void remove() {\r
89                                         throw new UnsupportedOperationException(\r
90                                                         "Chart domain iterators are not modifiable.");\r
91                                 }\r
92                         };\r
93                 }\r
94 \r
95         }\r
96 \r
97         XYSeriesCollection dataset = new XYSeriesCollection();\r
98         JFreeChart chart;\r
99 \r
100         Unit<X> xUnit;\r
101         Unit<Y> yUnit;\r
102 \r
103         Object source;\r
104         Method f;\r
105 \r
106         public Chart(Unit<X> xUnit, Unit<Y> yUnit, Object source, String method)\r
107                         throws NoSuchMethodException {\r
108                 super(new BorderLayout());\r
109                 f = source.getClass().getMethod(method, Amount.class);\r
110 \r
111                 this.source = source;\r
112 \r
113 \r
114                 this.xUnit = RocketScience.UnitPreference.getUnitPreference()\r
115                                 .getPreferredUnit(xUnit);\r
116                 this.yUnit = RocketScience.UnitPreference.getUnitPreference()\r
117                                 .getPreferredUnit(yUnit);\r
118 \r
119                 chart = ChartFactory.createXYLineChart(method.substring(0, 1)\r
120                                 .toUpperCase()\r
121                                 + method.substring(1), // Title\r
122                                 this.xUnit.toString(), // x-axis Label\r
123                                 this.yUnit.toString(), // y-axis Label\r
124                                 dataset, PlotOrientation.VERTICAL, // Plot Orientation\r
125                                 false, // Show Legend\r
126                                 true, // Use tool tips\r
127                                 false // Configure chart to generate URLs?\r
128                                 );\r
129                 add(new ChartPanel(chart));\r
130         }\r
131 \r
132         private Marker marker;\r
133 \r
134         public void mark(Amount<X> m) {\r
135                 if (marker != null)\r
136                         chart.getXYPlot().removeDomainMarker(marker);\r
137                 if (m != null) {\r
138                         marker = new ValueMarker(m.doubleValue(xUnit));\r
139                         marker.setPaint(Color.blue);\r
140                         marker.setAlpha(0.8f);\r
141                         \r
142                         Amount<Y> val = getNear(m);\r
143                         if ( val != null )\r
144                                 marker.setLabel(RocketScience.approx(val));\r
145                         \r
146                         marker.setLabelTextAnchor(TextAnchor.TOP_LEFT);\r
147                         marker.setLabelOffset(new RectangleInsets(0,-5,0,0));\r
148                         \r
149                         marker.setLabelFont(new Font(Font.DIALOG, Font.BOLD, 12));\r
150                         chart.getXYPlot().addDomainMarker(marker);\r
151                 }\r
152         }\r
153         \r
154         /**\r
155          * Get the Y value at or near a given X\r
156          * For display use only!\r
157          * \r
158          * @param ax\r
159          * @return\r
160          */\r
161         private Amount<Y> getNear(final Amount<X> ax){\r
162                 if ( dataset.getSeriesCount() != 1 )\r
163                         return null;\r
164                 final XYSeries s = dataset.getSeries(0);\r
165                 final double x = ax.doubleValue(xUnit);\r
166                 int idx = s.getItemCount() / 2;\r
167                 int delta = s.getItemCount() / 4;\r
168                 while(true){\r
169                         if ( s.getX(idx).doubleValue() < x ){\r
170                                 idx += delta;\r
171                         } else {\r
172                                 idx -= delta;\r
173                         }\r
174                         delta = delta / 2;\r
175                         if ( delta < 1 ){\r
176                                 int idxL = idx-1;\r
177                                 int idxH = idx;\r
178                                 final double lowerX = s.getX(idxL).doubleValue();\r
179                                 final double higherX = s.getX(idxH).doubleValue();\r
180                                 final double sampleXDiff = higherX - lowerX;\r
181                                 final double xDiff = x - lowerX;\r
182                                 final double dist = xDiff / sampleXDiff;\r
183                                 final double lowerY = s.getY(idxL).doubleValue();\r
184                                 final double higherY = s.getY(idxH).doubleValue();\r
185                                 final double y = lowerY + dist * (higherY - lowerY);\r
186                                 \r
187                                 return Amount.valueOf( y, yUnit);\r
188                         }\r
189                 }\r
190         }\r
191 \r
192         public void setDomain(final Iterable<Amount<X>> d) {\r
193                 stop = true;\r
194                 fill(d, 100);\r
195                 fast.submit(new Thread() {\r
196                         public void run() {\r
197                                 if (!stop)\r
198                                         fill(d, 10);\r
199                                 slow.submit(new Thread() {\r
200                                         public void run() {\r
201                                                 if (!stop)\r
202                                                         fill(d, 1);\r
203                                         }\r
204                                 });\r
205                         }\r
206                 });\r
207         }\r
208 \r
209         @SuppressWarnings("unchecked")\r
210         private synchronized void fill(Iterable<Amount<X>> d, int skip) {\r
211                 log.debug(f.getName() + " " + skip + " Start");\r
212                 stop = false;\r
213                 int sz = 0;\r
214                 if (d instanceof Collection) {\r
215                         sz = ((Collection<Amount<X>>) d).size();\r
216                         int sk2 = sz / 200;\r
217                         if (skip < sk2)\r
218                                 skip = sk2;\r
219                 }\r
220                 // series.clear();\r
221                 int cnt = 0;\r
222 \r
223                 final XYSeries newSeries = new XYSeries(f.getName());\r
224                 try {\r
225                         Amount<X> last = null;\r
226                         for (Amount<X> ax : d) {\r
227                                 if (stop) {\r
228                                         log.debug(f.getName() + " " + skip + " Abort");\r
229                                         return;\r
230                                 }\r
231                                 last = ax;\r
232                                 if (cnt % skip == 0) {\r
233                                         Amount<Y> y = (Amount<Y>) f.invoke(source, ax);\r
234                                         newSeries.add(ax.doubleValue(xUnit), y.doubleValue(yUnit));\r
235                                 }\r
236                                 cnt++;\r
237                         }\r
238                         Amount<Y> y = (Amount<Y>) f.invoke(source, last);\r
239                         newSeries.add(last.doubleValue(xUnit), y.doubleValue(yUnit));\r
240                         SwingUtilities.invokeLater(new Thread() {\r
241                                 @Override\r
242                                 public void run() {\r
243                                         dataset.removeAllSeries();\r
244                                         dataset.addSeries(newSeries);\r
245                                         log.debug(f.getName() + " Replaced");\r
246                                 }\r
247                         });\r
248                 } catch (IllegalArgumentException e) {\r
249                         // TODO Auto-generated catch block\r
250                         e.printStackTrace();\r
251                 } catch (IllegalAccessException e) {\r
252                         // TODO Auto-generated catch block\r
253                         e.printStackTrace();\r
254                 } catch (InvocationTargetException e) {\r
255                         // TODO Auto-generated catch block\r
256                         e.printStackTrace();\r
257                 }\r
258                 log.debug(f.getName() + " " + skip + " Done");\r
259         }\r
260 \r
261         public void show() {\r
262                 new JFrame() {\r
263                         private static final long serialVersionUID = 1L;\r
264                         {\r
265                                 setContentPane(Chart.this);\r
266                                 setSize(640, 480);\r
267                                 setDefaultCloseOperation(DISPOSE_ON_CLOSE);\r
268                         }\r
269                 }.setVisible(true);\r
270         }\r
271 \r
272         public static void main(String args[]) throws Exception {\r
273                 CoredCylindricalGrain g = new CoredCylindricalGrain();\r
274                 g.setLength(Amount.valueOf(70, SI.MILLIMETER));\r
275                 g.setOD(Amount.valueOf(30, SI.MILLIMETER));\r
276                 g.setID(Amount.valueOf(10, SI.MILLIMETER));\r
277 \r
278                 Chart<Length, Area> c = new Chart<Length, Area>(SI.MILLIMETER,\r
279                                 SI.MILLIMETER.pow(2).asType(Area.class), g, "surfaceArea");\r
280 \r
281                 c.setDomain(c.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), g\r
282                                 .webThickness()));\r
283 \r
284                 c.show();\r
285 \r
286                 Chart<Length, Volume> v = new Chart<Length, Volume>(SI.MILLIMETER,\r
287                                 SI.MILLIMETER.pow(3).asType(Volume.class), g, "volume");\r
288 \r
289                 v.setDomain(c.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), g\r
290                                 .webThickness()));\r
291 \r
292                 v.show();\r
293         }\r
294 \r
295 }\r