package com.aerodynelabs.habtk.charts; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.jfree.chart.annotations.AbstractXYAnnotation; import org.jfree.chart.annotations.XYTextAnnotation; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.NumberTickUnit; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.plot.CrosshairState; import org.jfree.chart.plot.Plot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.PlotRenderingInfo; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.AbstractXYItemRenderer; import org.jfree.chart.renderer.xy.XYItemRenderer; import org.jfree.chart.renderer.xy.XYItemRendererState; import org.jfree.data.xy.XYDataset; import org.jfree.ui.Layer; import org.jfree.ui.RectangleEdge; import com.aerodynelabs.habtk.atmosphere.AtmosphereProfile; @SuppressWarnings("serial") public class SkewTLogPPlot extends TemperaturePlot { private static final Color isobar = Color.black; private static final Color isotherm = Color.magenta; private static final Color mixing = Color.green; private static final Color dry = Color.orange; private static final Color wet = Color.orange; private AtmosphereSeriesCollection dataset; public SkewTLogPPlot() { super(); dataset = new AtmosphereSeriesCollection(AtmosphereSeriesCollection.DOMAIN_PRESSURE, AtmosphereSeriesCollection.RANGE_TEMPDEWPT); PressureAxis pAxis = new PressureAxis("Pressure (hPa)"); NumberAxis tAxis = new NumberAxis("Temperature (\u00b0C)"); tAxis.setTickUnit(new NumberTickUnit(10, new DecimalFormat("#"), 0)); tAxis.setRange(-50.0, 50.0); setDomainGridlinePaint(isobar); setDomainGridlineStroke(new BasicStroke(1.0f)); setRangeGridlinePaint(isotherm); setRangeGridlineStroke(new BasicStroke(1.0f)); setDataset(dataset); setRenderer(new SkewTRenderer()); setOrientation(PlotOrientation.HORIZONTAL); setDomainAxis(pAxis); setRangeAxis(tAxis); createDryAdiabats(); createWetAdiabats(); createMixingLines(); } private void createWetAdiabats() { final float dash[] = {10.0f, 10.0f}; Stroke stroke = new BasicStroke(0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f); XYItemRenderer r = getRenderer(); double step = 500; double x = -40; while(x < 50) { ArrayList<Point2D.Double> path = new ArrayList<Point2D.Double>(); double T = x + 273.15; for(double h = 0; h <= 15000; h += step) { double p = 105000.0 * Math.pow((1 - 2.5577 * Math.pow(10, -5) * h), 5.35588); // pressure (Pa) path.add(new Point2D.Double(p / 100.0, T - 273.15)); double e = 6.11 * Math.pow(10.0, (7.5 * (T - 273.15)) / (237.7 + (T - 273.15)) ); // saturated vapor pressure double w = 621.97 * e / ((p / 100) - e); // saturated mixing ratio (g/kg) w = w / 1000; double t = 9.8076 * (1 + ( (2230000 * w) / (287 * T) )) / (1007 + ( (Math.pow(2230000, 2)*w*0.6220) / (287*Math.pow(T, 2)) )); T = T - t*step; } r.addAnnotation(new SkewTPathAnnotation(path, stroke, wet), Layer.BACKGROUND); if(x > 0) { x += 5; } else { x += 10; } } } private void createDryAdiabats() { Stroke stroke = new BasicStroke(0.5f); XYItemRenderer r = getRenderer(); for(double x = -40; x <= 90; x += 10) { ArrayList<Point2D.Double> path = new ArrayList<Point2D.Double>(); for(double h = 0; h <= 20000; h += 500) { double p = 105000.0 * Math.pow((1 - 2.5577 * Math.pow(10, -5) * h), 5.35588); double t = x - (9.8 * (h / 1000.0)); path.add(new Point2D.Double(p / 100.0, t)); } r.addAnnotation(new SkewTPathAnnotation(path, stroke, dry), Layer.BACKGROUND); } } private void createMixingLines() { final float dash[] = {10.0f, 10.0f}; Stroke stroke = new BasicStroke(0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f); Paint paint = mixing; XYItemRenderer r = getRenderer(); double y = 950; final double rot = -Math.PI / 3; // 0.1 r.addAnnotation(new SkewTLineAnnotation(1050, -40.9, 100, -60.8378, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t01 = new XYTextAnnotation("0.1", y, -40); t01.setRotationAngle(rot); r.addAnnotation(t01); // 0.2 r.addAnnotation(new SkewTLineAnnotation(1050, -34.137, 100, -55.3946, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t02 = new XYTextAnnotation("0.2", y, -34); t02.setRotationAngle(rot); r.addAnnotation(t02); // 0.4 r.addAnnotation(new SkewTLineAnnotation(1050, -26.8943, 100, -49.6071, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t04 = new XYTextAnnotation("0.4", y, -26); t04.setRotationAngle(rot); r.addAnnotation(t04); // 0.6 r.addAnnotation(new SkewTLineAnnotation(1050, -22.4151, 100, -46.0493, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t06 = new XYTextAnnotation("0.6", y, -21); t06.setRotationAngle(rot); r.addAnnotation(t06); // 1g/kg r.addAnnotation(new SkewTLineAnnotation(1050, -14.4964, 100, -41.3729, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t1 = new XYTextAnnotation("1.0", y, -14.0); t1.setRotationAngle(rot); r.addAnnotation(t1); // 2g/kg r.addAnnotation(new SkewTLineAnnotation(1050, -7.93414, 100, -34.6573, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t2 = new XYTextAnnotation("2.0", y, -7); t2.setRotationAngle(rot); r.addAnnotation(t2); // 3 r.addAnnotation(new SkewTLineAnnotation(1050, -2.62, 100, -30.5186, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t3 = new XYTextAnnotation("3.0", y, -2.0); t3.setRotationAngle(rot); r.addAnnotation(t3); // 4g/kg r.addAnnotation(new SkewTLineAnnotation(1050, 1.2955, 100, -27.4833, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t4 = new XYTextAnnotation("4.0", y, 2.0); t4.setRotationAngle(rot); r.addAnnotation(t4); // 5 r.addAnnotation(new SkewTLineAnnotation(1050, 4.41855, 100, -25.0709, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t5 = new XYTextAnnotation("5.0", y, 5.0); t5.setRotationAngle(rot); r.addAnnotation(t5); // 6 r.addAnnotation(new SkewTLineAnnotation(1050, 7.02728, 100, -23.0616, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t6 = new XYTextAnnotation("6.0", y, 7.5); t6.setRotationAngle(rot); r.addAnnotation(t6); // 8g/kg r.addAnnotation(new SkewTLineAnnotation(1050, 11.2498, 100, -19.8204, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t8 = new XYTextAnnotation("8.0", y, 11.75); t8.setRotationAngle(rot); r.addAnnotation(t8); // 10 r.addAnnotation(new SkewTLineAnnotation(1050, 14.6159, 100, -17.2465, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t10 = new XYTextAnnotation("10", y, 15.0); t10.setRotationAngle(rot); r.addAnnotation(t10); // 15 r.addAnnotation(new SkewTLineAnnotation(1050, 20.9365, 100, -12.4366, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t15 = new XYTextAnnotation("15", y, 21.4); t15.setRotationAngle(rot); r.addAnnotation(t15); // 20 r.addAnnotation(new SkewTLineAnnotation(1050, 25.5789, 100, -8.92309, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t20 = new XYTextAnnotation("20", y, 26.1); t20.setRotationAngle(rot); r.addAnnotation(t20); // 30 r.addAnnotation(new SkewTLineAnnotation(1050, 32.3335, 100, -3.83991, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t30 = new XYTextAnnotation("30", y, 32.8); t30.setRotationAngle(rot); r.addAnnotation(t30); // 40 r.addAnnotation(new SkewTLineAnnotation(1050, 37.2617, 100, -0.152649, stroke, paint), Layer.BACKGROUND); XYTextAnnotation t40 = new XYTextAnnotation("40", y, 37.8); t40.setRotationAngle(rot); r.addAnnotation(t40); } public void setProfile(AtmosphereProfile profile) { dataset.setProfile(profile); } public AtmosphereProfile getProfile() { return dataset.getProfile(); } @SuppressWarnings("rawtypes") @Override protected void drawRangeGridlines(Graphics2D g2, Rectangle2D area, List ticks) { Stroke gridStroke = getRangeGridlineStroke(); Paint gridPaint = getRangeGridlinePaint(); NumberAxis axis = (NumberAxis) getRangeAxis(); if(axis == null) return; for(int value = -100; value <= 50; value += 10) getRenderer().drawRangeLine(g2, this, axis, area, value, gridPaint, gridStroke); } protected Line2D.Double transformLine(double ix1, double iy1, double ix2, double iy2, XYPlot plot, Rectangle2D plotArea, ValueAxis domainAxis, ValueAxis rangeAxis) { PlotOrientation orientation = plot.getOrientation(); RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( plot.getDomainAxisLocation(), orientation); RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( plot.getRangeAxisLocation(), orientation); double y1 = domainAxis.valueToJava2D(ix1, plotArea, domainEdge); double y2 = domainAxis.valueToJava2D(ix2, plotArea, domainEdge); double x1 = rangeAxis.valueToJava2D(iy1, plotArea, rangeEdge); x1 += plotArea.getMaxY() - y1; double x2 = rangeAxis.valueToJava2D(iy2, plotArea, rangeEdge); x2 += plotArea.getMaxY() - y2; return new Line2D.Double(x1, y1, x2, y2); } class SkewTPathAnnotation extends AbstractXYAnnotation { ArrayList<Point2D.Double> path; Stroke stroke; Paint paint; public SkewTPathAnnotation(ArrayList<Point2D.Double> path, Stroke stroke, Paint paint) { this.path = path; this.stroke = stroke; this.paint = paint; } @Override public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, ValueAxis domainAxis, ValueAxis rangeAxis, int rendererIndex, PlotRenderingInfo info) { g2.setStroke(stroke); g2.setPaint(paint); Iterator<Point2D.Double> itr = path.iterator(); Point2D.Double c = null, p = null; if(itr.hasNext()) c = itr.next(); while(itr.hasNext()) { p = c; c = itr.next(); g2.draw(transformLine(p.x, p.y, c.x, c.y, plot, dataArea, domainAxis, rangeAxis)); } } } class SkewTLineAnnotation extends AbstractXYAnnotation { double x1, y1, x2, y2; Stroke stroke; Paint paint; public SkewTLineAnnotation(double x1, double y1, double x2, double y2, Stroke stroke, Paint paint) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; this.stroke = stroke; this.paint = paint; } @Override public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, ValueAxis domainAxis, ValueAxis rangeAxis, int rendererIndex, PlotRenderingInfo info) { g2.setStroke(stroke); g2.setPaint(paint); g2.draw(transformLine(x1, y1, x2, y2, plot, dataArea, domainAxis, rangeAxis)); } } class SkewTRenderer extends AbstractXYItemRenderer { public SkewTRenderer() { super(); } @Override public void drawRangeLine(Graphics2D g2, XYPlot plot, ValueAxis axis, Rectangle2D dataArea, double value, Paint paint, Stroke stroke) { g2.setPaint(paint); g2.setStroke(stroke); double x0 = axis.valueToJava2D(value, dataArea, plot.getRangeAxisEdge()); double y0 = dataArea.getMaxY(); double y1 = dataArea.getMinY(); double x1 = x0 + (y0 - y1); g2.drawLine((int)(x0 + 0.5), (int)(y0 + 0.5), (int)(x1 + 0.5), (int)(y1 + 0.5)); } @Override public void drawItem( Graphics2D g2, XYItemRendererState state, Rectangle2D plotArea, PlotRenderingInfo info, XYPlot plot, ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, int series, int item, CrosshairState crosshairState, int pass) { if(pass > 0) return; AtmosphereSeriesCollection data = (AtmosphereSeriesCollection) dataset; Paint seriesPaint = null; if(series == 0) { seriesPaint = Color.RED; } else { seriesPaint = Color.BLUE; } Stroke seriesStroke = getItemStroke(series, item); g2.setPaint(seriesPaint); g2.setStroke(seriesStroke); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); double alt = data.getXValue(series, item); double temp = data.getYValue(series, item); RectangleEdge domainLocation = plot.getDomainAxisEdge(); RectangleEdge rangeLocation = plot.getRangeAxisEdge(); if(item > 0) { double y1 = domainAxis.valueToJava2D(alt, plotArea, domainLocation); double x1 = rangeAxis.valueToJava2D(temp, plotArea, rangeLocation); x1 += plotArea.getMaxY() - y1; double pAlt = data.getXValue(series, item - 1); double pTemp = data.getYValue(series, item - 1); double y2 = domainAxis.valueToJava2D(pAlt, plotArea, domainLocation); double x2 = rangeAxis.valueToJava2D(pTemp, plotArea, rangeLocation); x2 += plotArea.getMaxY() - y2; g2.drawLine((int)(x1 + 0.5), (int)(y1 + 0.5), (int)(x2 + 0.5), (int)(y2 + 0.5)); } } } }