1 package com.billkuker.rocketry.motorsim.visual;
\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
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
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
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
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
45 private static ThreadFactory tf = new ThreadFactory() {
\r
46 public Thread newThread(Runnable r) {
\r
47 Thread t = new Thread(r);
\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
56 public class IntervalDomain implements Iterable<Amount<X>> {
\r
58 Amount<X> low, high, delta;
\r
61 public IntervalDomain(Amount<X> low, Amount<X> high) {
\r
64 delta = high.minus(low).divide(steps);
\r
67 public IntervalDomain(Amount<X> low, Amount<X> high, int steps) {
\r
71 delta = high.minus(low).divide(steps);
\r
74 public Iterator<Amount<X>> iterator() {
\r
75 return new Iterator<Amount<X>>() {
\r
76 Amount<X> current = low;
\r
78 public boolean hasNext() {
\r
79 return current.isLessThan(high.plus(delta));
\r
82 public Amount<X> next() {
\r
83 Amount<X> ret = current;
\r
84 current = current.plus(delta);
\r
88 public final void remove() {
\r
89 throw new UnsupportedOperationException(
\r
90 "Chart domain iterators are not modifiable.");
\r
97 XYSeriesCollection dataset = new XYSeriesCollection();
\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
111 this.source = source;
\r
114 this.xUnit = RocketScience.UnitPreference.getUnitPreference()
\r
115 .getPreferredUnit(xUnit);
\r
116 this.yUnit = RocketScience.UnitPreference.getUnitPreference()
\r
117 .getPreferredUnit(yUnit);
\r
119 chart = ChartFactory.createXYLineChart(method.substring(0, 1)
\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
129 add(new ChartPanel(chart));
\r
132 private Marker marker;
\r
134 public void mark(Amount<X> m) {
\r
135 if (marker != null)
\r
136 chart.getXYPlot().removeDomainMarker(marker);
\r
138 marker = new ValueMarker(m.doubleValue(xUnit));
\r
139 marker.setPaint(Color.blue);
\r
140 marker.setAlpha(0.8f);
\r
142 Amount<Y> val = getNear(m);
\r
144 marker.setLabel(RocketScience.approx(val));
\r
146 marker.setLabelTextAnchor(TextAnchor.TOP_LEFT);
\r
147 marker.setLabelOffset(new RectangleInsets(0,-5,0,0));
\r
149 marker.setLabelFont(new Font(Font.DIALOG, Font.BOLD, 12));
\r
150 chart.getXYPlot().addDomainMarker(marker);
\r
155 * Get the Y value at or near a given X
\r
156 * For display use only!
\r
161 private Amount<Y> getNear(final Amount<X> ax){
\r
162 if ( dataset.getSeriesCount() != 1 )
\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
169 if ( s.getX(idx).doubleValue() < x ){
\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
187 return Amount.valueOf( y, yUnit);
\r
192 public void setDomain(final Iterable<Amount<X>> d) {
\r
195 fast.submit(new Thread() {
\r
196 public void run() {
\r
199 slow.submit(new Thread() {
\r
200 public void run() {
\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
214 if (d instanceof Collection) {
\r
215 sz = ((Collection<Amount<X>>) d).size();
\r
216 int sk2 = sz / 200;
\r
223 final XYSeries newSeries = new XYSeries(f.getName());
\r
225 Amount<X> last = null;
\r
226 for (Amount<X> ax : d) {
\r
228 log.debug(f.getName() + " " + skip + " Abort");
\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
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
242 public void run() {
\r
243 dataset.removeAllSeries();
\r
244 dataset.addSeries(newSeries);
\r
245 log.debug(f.getName() + " Replaced");
\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
258 log.debug(f.getName() + " " + skip + " Done");
\r
261 public void show() {
\r
263 private static final long serialVersionUID = 1L;
\r
265 setContentPane(Chart.this);
\r
267 setDefaultCloseOperation(DISPOSE_ON_CLOSE);
\r
269 }.setVisible(true);
\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
278 Chart<Length, Area> c = new Chart<Length, Area>(SI.MILLIMETER,
\r
279 SI.MILLIMETER.pow(2).asType(Area.class), g, "surfaceArea");
\r
281 c.setDomain(c.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), g
\r
286 Chart<Length, Volume> v = new Chart<Length, Volume>(SI.MILLIMETER,
\r
287 SI.MILLIMETER.pow(3).asType(Volume.class), g, "volume");
\r
289 v.setDomain(c.new IntervalDomain(Amount.valueOf(0, SI.CENTIMETER), g
\r