package edu.colostate.vchill.ascope; import edu.colostate.vchill.Loader; import edu.colostate.vchill.ViewUtil; import edu.colostate.vchill.chill.ChillMomentFieldScale; import edu.colostate.vchill.data.Ray; import edu.colostate.vchill.gui.ViewWindow; import javax.swing.*; import java.awt.*; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.image.BufferedImage; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; /** * A window to plot data AScope style. * <p/> * Methods are synchronized on this.formatter to prevent conflict with * synchronized JPanel methods * * @author Jochen Deyke * @author jpont * @version 2009-06-26 */ public class ViewAScopeWindow extends ViewWindow { /** * */ private static final long serialVersionUID = 420048517751841846L; public static final String TYPE_NOT_SET = "<none>"; private static final Config aconf = Config.getInstance(); private static final Color bg = Color.BLACK; private static final Color fg = Color.WHITE; private static final Color color1 = Color.GREEN; private static final Color color2 = Color.RED; private final SimpleDateFormat formatter = new SimpleDateFormat("EEE d MMM yy"); /** * Used to draw data onto */ private BufferedImage dataBuffer; /** * Z, V, etc */ private String type2 = null; /** * Data of last ray plotted */ private Ray ray1; private Ray ray2; private double[] data1; private double[] data2; /** * {date, UTC} */ private String[] time = {"Date", "Time"}; private double azimuth; private double elevation; private int numGates; private double gateWidth; /** * Constructor for the ViewPlotWindow object * * @param type The datatype to display (eg Z, V, ...) */ public ViewAScopeWindow(final String type) { super(); this.type = type; this.type2 = null; //set by WindowManager this.formatter.setTimeZone(TimeZone.getTimeZone("UTC")); //Set the layout to add components to the left as they are added. this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); this.dataBuffer = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); //Swing setup. setBackground(bg); setForeground(bg); setDoubleBuffered(false); this.addComponentListener(new ComponentAdapter() { @Override public void componentResized(final ComponentEvent ce) { wm.resizeAScopes(parent.getWidth(), parent.getHeight()); } }); } @Override public void paintComponent(final Graphics g) { if (g == null) return; super.paintComponent(g); g.drawImage(this.dataBuffer, 0, 0, this); if (aconf.isDrawClickRangeEnabled()) { int clickX = (int) (aconf.getClickRange() * this.getPlotWidth() / config.getPlotRange()); if (clickX > this.getPlotWidth()) return; //don't draw over annotation g.setColor(fg); g.drawLine(clickX, 0, clickX, this.getHeight()); int clickY = this.getPlotHeight() - (int) (this.getPlotHeight() * aconf.getClickY()); g.drawLine(0, clickY, this.getPlotWidth(), clickY); } } /** * @param ray1 the primary Ray to plot * @param ray2 the secondary Ray to plot, or * null if secondary plot is disabled */ public void plot(final Ray ray1, final Ray ray2) { synchronized (this.formatter) { if (ray1 == null) return; //no data to plot; boolean secondaryEnabled = ray2 != null; if (secondaryEnabled) { this.ray2 = ray2; this.data2 = this.ray2.getData(); } this.ray1 = ray1; this.data1 = this.ray1.getData(); Date date = new Date(this.ray1.getDate()); this.formatter.applyPattern("EEE d MMM yy"); this.time[0] = this.formatter.format(date); this.formatter.applyPattern("HH:mm:ss 'UTC'"); this.time[1] = this.formatter.format(date); this.azimuth = this.ray1.getStartAzimuth(); this.elevation = this.ray1.getStartElevation(); this.numGates = this.ray1.getNumberOfGates(); this.gateWidth = this.ray1.getGateWidth(); this.plotData(secondaryEnabled); } } public void plotData(final boolean secondaryEnabled) { synchronized (this.formatter) { ChillMomentFieldScale scale1 = sm.getScale(this.type); ChillMomentFieldScale scale2 = sm.getScale(this.type2); Graphics2D g = this.dataBuffer.createGraphics(); double stepSize; int x1, x2, y1, y2; double x1Data, x2Data; int offset; this.clearScreen(); if (secondaryEnabled) { stepSize = (config.getPlotRange() * this.data2.length) / (this.getPlotWidth() * this.gateWidth * this.numGates); offset = (int) ((this.ray2.getStartRange() * 1e-6) / config.getPlotRange() * this.getPlotWidth()); //mm -> km x2 = 0; g.setColor(color2); for (x1 = x2++; x2 < this.getPlotWidth(); x1 = x2++) { try { x1Data = this.data2[(int) (x1 * stepSize)]; y1 = this.getPlotHeight() - (int) (this.getPlotHeight() * (x1Data - scale2.getMin()) / (scale2.getMax() - scale2.getMin())); x2Data = this.data2[(int) (x2 * stepSize)]; y2 = this.getPlotHeight() - (int) (this.getPlotHeight() * (x2Data - scale2.getMin()) / (scale2.getMax() - scale2.getMin())); if (Double.isNaN(x1Data)) { if (!Double.isNaN(x2Data)) { g.drawLine(x2 + offset, y2, x2 + offset, y2); } } else if (Double.isNaN(x2Data)) { if (!Double.isNaN(x1Data)) { g.drawLine(x1 + offset, y1, x1 + offset, y1); } } else g.drawLine(x1 + offset, y1, x2 + offset, y2); } catch (ArrayIndexOutOfBoundsException aioobe) { break; //no more data to plot } } } stepSize = (config.getPlotRange() * this.data1.length) / (this.getPlotWidth() * this.gateWidth * this.numGates); offset = (int) ((this.ray1.getStartRange() * 1e-6) / config.getPlotRange() * this.getPlotWidth()); //mm -> km x2 = 0; g.setColor(color1); for (x1 = x2++; x2 < this.getPlotWidth(); x1 = x2++) { try { x1Data = this.data1[(int) (x1 * stepSize)]; y1 = this.getPlotHeight() - (int) (this.getPlotHeight() * (x1Data - scale1.getMin()) / (scale1.getMax() - scale1.getMin())); x2Data = this.data1[(int) (x2 * stepSize)]; y2 = this.getPlotHeight() - (int) (this.getPlotHeight() * (x2Data - scale1.getMin()) / (scale1.getMax() - scale1.getMin())); if (Double.isNaN(x1Data)) { if (!Double.isNaN(x2Data)) { g.drawLine(x2 + offset, y2, x2 + offset, y2); } } else if (Double.isNaN(x2Data)) { if (!Double.isNaN(x1Data)) { g.drawLine(x1 + offset, y1, x1 + offset, y1); } } else g.drawLine(x1 + offset, y1, x2 + offset, y2); } catch (ArrayIndexOutOfBoundsException aioobe) { break; //no more data to plot } } this.plotAnnotation(); } } public void plotAnnotation() { synchronized (this.formatter) { ChillMomentFieldScale scale1 = sm.getScale(this.type); ChillMomentFieldScale scale2 = sm.getScale(this.type2); Graphics2D g = this.dataBuffer.createGraphics(); g.setColor(color1); int x = this.getPlotWidth() + 5; int y; int incr = 12 * this.getPlotHeight() / 240; //primary scale g.drawString(ViewUtil.format(scale1.getMax()), x, 12); g.drawString(ViewUtil.format((scale1.getMin() + 3 * scale1.getMax()) / 4.0), x, 12 + (this.getPlotHeight() - 14) / 4); g.drawString(ViewUtil.format((scale1.getMin() + scale1.getMax()) / 2.0), x, 12 + (this.getPlotHeight() - 14) / 2); g.drawString(ViewUtil.format((3 * scale1.getMin() + scale1.getMax()) / 4.0), x, 12 + (this.getPlotHeight() - 14) * 3 / 4); g.drawString(ViewUtil.format(scale1.getMin()), x, 12 + this.getPlotHeight() - 14); y = this.getPlotHeight() - (int) (this.getPlotHeight() * scale1.getMin() / (scale1.getMin() - scale1.getMax())); g.drawLine(0, y, this.getPlotWidth(), y); //secondary scale if (this.type2 != null) { g.setColor(color2); x += 40; g.drawString(ViewUtil.format(scale2.getMax()), x, 12); g.drawString(ViewUtil.format((scale2.getMin() + 3 * scale2.getMax()) / 4.0), x, 12 + (this.getPlotHeight() - 14) / 4); g.drawString(ViewUtil.format((scale2.getMin() + scale2.getMax()) / 2.0), x, 12 + (this.getPlotHeight() - 14) / 2); g.drawString(ViewUtil.format((3 * scale2.getMin() + scale2.getMax()) / 4.0), x, 12 + (this.getPlotHeight() - 14) * 3 / 4); g.drawString(ViewUtil.format(scale2.getMin()), x, 12 + this.getPlotHeight() - 14); y = this.getPlotHeight() - (int) (this.getPlotHeight() * scale2.getMin() / (scale2.getMin() - scale2.getMax())); g.drawLine(0, y, this.getPlotWidth(), y); } g.setColor(fg); x = 2; y = this.getHeight() - 2; //range scale if (this.ray1 != null) { g.drawString(ViewUtil.format(0), x, y); g.drawString(ViewUtil.format(config.getPlotRange() / 2.0), this.getPlotWidth() / 2, y); g.drawString(ViewUtil.format(config.getPlotRange()), this.getPlotWidth(), y); } //sidebar x = this.getPlotWidth() + 10; y = incr * 5 / 2; g.drawString("Type:", x, y += incr); g.setColor(color1); g.drawString(this.type, x + 40, y); if (this.type2 != null) { g.setColor(color2); g.drawString(this.type2, x + 80, y); } y += incr * 3; //leave room for scale g.setColor(fg); g.drawString(this.time[0], x, y += incr); //date g.drawString(this.time[1], x, y += incr); //utc y += incr * 3; //leave room for scale g.drawString("Azimuth: " + ViewUtil.format(this.azimuth) + "\u00b0", x, y += incr); g.drawString("Elevation: " + ViewUtil.format(this.elevation) + "\u00b0", x, y += incr); g.drawString("Range: " + ViewUtil.format(aconf.getClickRange()) + "km", x, y += incr); y += incr * 5 / 2; //leave room for scale g.drawString("Value: ", x, y += incr); if (this.ray1 != null) { g.setColor(color1); int index = (int) ((aconf.getClickRange() - this.ray1.getStartRange() * 1e-6) / this.gateWidth); if (index > this.numGates - 1) index = this.numGates - 1; if (index < 0) index = 0; g.drawString(Double.isNaN(this.data1[index]) ? "N/A" : ViewUtil.format(this.data1[index]), x + 40, y); if (this.ray2 != null && this.type2 != null) { g.setColor(color2); g.drawString(Double.isNaN(this.data2[index]) ? "N/A" : ViewUtil.format(this.data2[index]), x + 80, y); } } g.setColor(fg); g.drawString("Marker: ", x, y += incr); g.setColor(color1); g.drawString(ViewUtil.format(aconf.getClickY() * (scale1.getMax() - scale1.getMin()) + scale1.getMin()), x + 40, y); if (this.type2 != null) { g.setColor(color2); g.drawString(ViewUtil.format(aconf.getClickY() * (scale2.getMax() - scale2.getMin()) + scale2.getMin()), x + 80, y); } } } public void clearScreen() { Graphics2D g = this.dataBuffer.createGraphics(); g.setColor(bg); g.fillRect(0, 0, this.dataBuffer.getWidth(), this.dataBuffer.getHeight()); } public void clearAnnotation() { Graphics2D g = this.dataBuffer.createGraphics(); g.setColor(bg); g.fillRect(this.getPlotWidth(), 0, this.dataBuffer.getWidth(), this.dataBuffer.getHeight()); } /** * @return the width to be used for plotting data */ public int getPlotWidth() { return super.getWidth() * 4 / 5; } /** * @return the height to be used for plotting data */ public int getPlotHeight() { return super.getHeight() - 12; } @Override public void setType(final String type) { synchronized (this.formatter) { super.setType(type); this.clearScreen(); this.plotAnnotation(); this.repaint(this.getVisibleRect()); } } /** * @param type the secondary type to be plotted, or * <code>null</code> to disable the secondary plot */ public void setSecondary(final String type) { synchronized (this.formatter) { this.type2 = sm.getScale(type) == null ? null : type; wm.calculateOpenWindows(); this.clearScreen(); this.plotAnnotation(); this.repaint(this.getVisibleRect()); } } /** * @return the secondary type being plotted, or * null if secondary plot is disabled */ public String getSecondary() { synchronized (this.formatter) { return this.type2; } } /** * @return a BufferedImage containing the current plot and annotation */ @Override public BufferedImage getBufferedImage() { synchronized (this.formatter) { BufferedImage imageBuffer = new BufferedImage(this.dataBuffer.getWidth(), this.dataBuffer.getHeight(), BufferedImage.TYPE_INT_RGB); this.paintComponent(imageBuffer.createGraphics()); return imageBuffer; } } public void markData(double x, final double y) { synchronized (this.formatter) { if (x > this.getPlotWidth()) x = this.getPlotWidth(); //outside plot area double range = x * config.getPlotRange() / this.getPlotWidth(); wm.setClickRay(this.azimuth, this.elevation, range, (int) x, (int) y); wm.setAScopeClickY((this.getPlotHeight() - y) / getPlotHeight()); } } /** * @param km the distance from the radar in km to draw the click line */ public void setClickRange(final double km) { synchronized (this.formatter) { aconf.setClickRange(km); this.clearAnnotation(); this.plotAnnotation(); this.repaint(this.getVisibleRect()); } } public void setClickY(final double fraction) { synchronized (this.formatter) { aconf.setClickY(fraction); } } public void enableClickRangeLine(final boolean shouldClickRangeLineBeDrawn) { synchronized (this.formatter) { aconf.setDrawClickRangeEnabled(shouldClickRangeLineBeDrawn); } } @Override public void setSizes(final int width, final int height) { synchronized (this.formatter) { super.setSizes(width, height); this.dataBuffer = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB); this.clearScreen(); this.plotAnnotation(); this.plot(ray1, ray2); this.repaint(this.getVisibleRect()); } } @Override public void setParent(final JInternalFrame parent) { super.setParent(parent); parent.setFrameIcon(new ImageIcon(Loader.getResource("icons/ascope.png"))); } @Override public String getStyle() { return "AScope"; } }