package com.billkuker.rocketry.motorsim.visual;\r
\r
+import java.awt.BasicStroke;\r
import java.awt.BorderLayout;\r
import java.awt.Color;\r
import java.awt.Font;\r
+import java.awt.Stroke;\r
import java.lang.reflect.InvocationTargetException;\r
import java.lang.reflect.Method;\r
import java.util.Collection;\r
import org.jfree.chart.plot.ValueMarker;\r
import org.jfree.data.xy.XYSeries;\r
import org.jfree.data.xy.XYSeriesCollection;\r
+import org.jfree.ui.RectangleAnchor;\r
import org.jfree.ui.RectangleInsets;\r
import org.jfree.ui.TextAnchor;\r
import org.jscience.physics.amount.Amount;\r
\r
-import com.billkuker.rocketry.motorsim.Burn;\r
import com.billkuker.rocketry.motorsim.RocketScience;\r
import com.billkuker.rocketry.motorsim.grain.CoredCylindricalGrain;\r
\r
public class Chart<X extends Quantity, Y extends Quantity> extends JPanel {\r
private static final long serialVersionUID = 1L;\r
- private static Logger log = Logger.getLogger(Burn.class);\r
+ private static Logger log = Logger.getLogger(Chart.class);\r
+ \r
+ private final Stroke dashed =new BasicStroke(1, 1, 1, 1, new float[]{2,4}, 0);\r
\r
private static ThreadFactory tf = new ThreadFactory() {\r
public Thread newThread(Runnable r) {\r
};\r
private static ExecutorService fast = Executors.newFixedThreadPool(2, tf);\r
private static ExecutorService slow = Executors.newFixedThreadPool(2, tf);\r
- private volatile boolean stop = false;\r
+\r
\r
public class IntervalDomain implements Iterable<Amount<X>> {\r
\r
add(new ChartPanel(chart));\r
}\r
\r
- private Marker marker;\r
+ public void showMax(){\r
+ markMax = true;\r
+ }\r
+ \r
+ public void showAverage(){\r
+ markAverage = true;\r
+ }\r
+ private boolean markMax = false, markAverage = false;\r
+ private Marker focusMarkerX, focusMarkerY;\r
+ private void markMax(){\r
+ if ( !markMax && !markAverage )\r
+ return;\r
+ if ( dataset.getSeriesCount() != 1 ){\r
+ return;\r
+ }\r
+ final XYSeries s = dataset.getSeries(0);\r
+\r
+ double max = 0;\r
+ double accum = 0;\r
+ for ( int i = 0; i < s.getItemCount(); i++){\r
+ if ( s.getY(i).doubleValue() > max ){\r
+ max = s.getY(i).doubleValue();\r
+ }\r
+ if ( i > 0 ){\r
+ double dx = s.getX(i).doubleValue() - s.getX(i-1).doubleValue();\r
+ accum += s.getY(i).doubleValue() * dx;\r
+ }\r
+ }\r
+ double average = accum / (s.getX(s.getItemCount()-1).doubleValue() - s.getX(0).doubleValue());\r
+ \r
+ if (markMax) {\r
+ Marker marker = new ValueMarker(average);\r
+ marker.setStroke(dashed);\r
+ marker.setPaint(Color.BLACK);\r
+ marker.setLabelFont(new Font(Font.DIALOG, Font.BOLD, 10));\r
+ marker.setLabel("Average: "\r
+ + RocketScience.ammountToRoundedString(Amount.valueOf(\r
+ average, yUnit)));\r
+ marker.setLabelTextAnchor(TextAnchor.TOP_LEFT);\r
+ marker.setLabelOffset(new RectangleInsets(0, 5, 0, 0));\r
+ chart.getXYPlot().addRangeMarker(marker);\r
+ }\r
+\r
+ if (markAverage) {\r
+ Marker marker = new ValueMarker(max);\r
+ marker.setStroke(dashed);\r
+ marker.setPaint(Color.BLACK);\r
+ marker.setLabelFont(new Font(Font.DIALOG, Font.BOLD, 10));\r
+ marker.setLabel("Max: "\r
+ + RocketScience.ammountToRoundedString(Amount.valueOf(max,\r
+ yUnit)));\r
+ marker.setLabelTextAnchor(TextAnchor.TOP_LEFT);\r
+ marker.setLabelOffset(new RectangleInsets(0, 5, 0, 0));\r
+ chart.getXYPlot().addRangeMarker(marker);\r
+ }\r
+\r
+ }\r
\r
public void mark(Amount<X> m) {\r
- if (marker != null)\r
- chart.getXYPlot().removeDomainMarker(marker);\r
+ if (focusMarkerX != null)\r
+ chart.getXYPlot().removeDomainMarker(focusMarkerX);\r
+ if (focusMarkerY != null)\r
+ chart.getXYPlot().removeRangeMarker(focusMarkerY);\r
+ \r
if (m != null) {\r
- marker = new ValueMarker(m.doubleValue(xUnit));\r
- marker.setPaint(Color.blue);\r
- marker.setAlpha(0.8f);\r
+ focusMarkerX = new ValueMarker(m.doubleValue(xUnit));\r
+ focusMarkerX.setPaint(Color.blue);\r
+ focusMarkerX.setAlpha(0.8f);\r
\r
- Amount<Y> val = getNear(m);\r
- if ( val != null )\r
- marker.setLabel(RocketScience.ammountToRoundedString(val));\r
- \r
- marker.setLabelTextAnchor(TextAnchor.TOP_LEFT);\r
- marker.setLabelOffset(new RectangleInsets(0,-5,0,0));\r
+ chart.getXYPlot().addDomainMarker(focusMarkerX);\r
\r
- marker.setLabelFont(new Font(Font.DIALOG, Font.BOLD, 12));\r
- chart.getXYPlot().addDomainMarker(marker);\r
+ Amount<Y> val = getNear(m);\r
+ if ( val != null ){\r
+ focusMarkerY = new ValueMarker(val.doubleValue(yUnit));\r
+ focusMarkerY.setPaint(Color.BLUE);\r
+ focusMarkerY.setLabelAnchor(RectangleAnchor.TOP_RIGHT);\r
+ focusMarkerY.setLabelTextAnchor(TextAnchor.TOP_RIGHT);\r
+ focusMarkerY.setLabelPaint(Color.BLUE);\r
+ focusMarkerY.setLabelFont(new Font(Font.DIALOG, Font.BOLD, 10));\r
+ focusMarkerY.setLabelOffset(new RectangleInsets(0,5,0,0));\r
+ chart.getXYPlot().addRangeMarker(focusMarkerY);\r
+ focusMarkerY.setLabel(RocketScience.ammountToRoundedString(val));\r
+ }\r
}\r
}\r
\r
}\r
}\r
}\r
+ \r
+ private void drawDone(){\r
+ markMax();\r
+ }\r
\r
+ private volatile boolean stop = false;\r
+ private volatile int lastSkipStepShown;\r
public void setDomain(final Iterable<Amount<X>> d) {\r
+ chart.getXYPlot().clearDomainMarkers();\r
+ chart.getXYPlot().clearRangeMarkers();\r
+ lastSkipStepShown = Integer.MAX_VALUE;\r
stop = true;\r
fill(d, 100);\r
fast.submit(new Thread() {\r
fill(d, 10);\r
slow.submit(new Thread() {\r
public void run() {\r
- if (!stop)\r
+ if (!stop){\r
fill(d, 1);\r
+ }\r
}\r
});\r
}\r
}\r
\r
@SuppressWarnings("unchecked")\r
- private synchronized void fill(Iterable<Amount<X>> d, int skip) {\r
- log.debug(f.getName() + " " + skip + " Start");\r
+ private synchronized void fill(Iterable<Amount<X>> d, final int requestedSkip) {\r
+ log.debug(f.getName() + " " + requestedSkip + " Start");\r
stop = false;\r
int sz = 0;\r
+ int calculatedSkip = requestedSkip;\r
if (d instanceof Collection) {\r
sz = ((Collection<Amount<X>>) d).size();\r
int sk2 = sz / 200;\r
- if (skip < sk2)\r
- skip = sk2;\r
+ if (calculatedSkip < sk2)\r
+ calculatedSkip = sk2;\r
}\r
// series.clear();\r
int cnt = 0;\r
Amount<X> last = null;\r
for (Amount<X> ax : d) {\r
if (stop) {\r
- log.debug(f.getName() + " " + skip + " Abort");\r
+ log.debug(f.getName() + " " + calculatedSkip + " Abort");\r
return;\r
}\r
last = ax;\r
- if (cnt % skip == 0) {\r
+ if (cnt % calculatedSkip == 0) {\r
Amount<Y> y = (Amount<Y>) f.invoke(source, ax);\r
newSeries.add(ax.doubleValue(xUnit), y.doubleValue(yUnit));\r
}\r
SwingUtilities.invokeLater(new Thread() {\r
@Override\r
public void run() {\r
- dataset.removeAllSeries();\r
- dataset.addSeries(newSeries);\r
- log.debug(f.getName() + " Replaced");\r
+ if ( requestedSkip < lastSkipStepShown ){\r
+ lastSkipStepShown = requestedSkip;\r
+ dataset.removeAllSeries();\r
+ dataset.addSeries(newSeries);\r
+ log.debug(f.getName() + " Replaced with " + requestedSkip);\r
+ }\r
+ if ( requestedSkip == 1 ){\r
+ drawDone();\r
+ }\r
}\r
});\r
} catch (IllegalArgumentException e) {\r
// TODO Auto-generated catch block\r
e.printStackTrace();\r
}\r
- log.debug(f.getName() + " " + skip + " Done");\r
+ log.debug(f.getName() + " " + calculatedSkip + " Done");\r
}\r
\r
public void show() {\r