1 package tim.prune.gui.profile;
4 import java.awt.Dimension;
5 import java.awt.FlowLayout;
6 import java.awt.Graphics;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.awt.event.MouseEvent;
10 import java.awt.event.MouseListener;
11 import javax.swing.JLabel;
12 import javax.swing.JMenuItem;
13 import javax.swing.JPopupMenu;
15 import tim.prune.I18nManager;
16 import tim.prune.config.ColourScheme;
17 import tim.prune.config.Config;
18 import tim.prune.data.TrackInfo;
19 import tim.prune.gui.GenericDisplay;
22 * Chart component for the profile display
24 public class ProfileChart extends GenericDisplay implements MouseListener
26 /** Current scale factor in x direction*/
27 private double _xScaleFactor = 0.0;
28 /** Data to show on chart */
29 private ProfileData _data = null;
30 /** Label for chart type, units */
31 private JLabel _label = null;
32 /** Right-click popup menu */
33 private JPopupMenu _popup = null;
35 /** Possible scales to use */
36 private static final int[] LINE_SCALES = {10000, 5000, 2000, 1000, 500, 200, 100, 50, 10, 5, 2, 1};
37 /** Border width around black line */
38 private static final int BORDER_WIDTH = 6;
39 /** Minimum size for profile chart in pixels */
40 private static final Dimension MINIMUM_SIZE = new Dimension(200, 110);
41 /** Colour to use for text if no data found */
42 private static final Color COLOR_NODATA_TEXT = Color.GRAY;
44 private static enum ChartType {ALTITUDE, SPEED, VERT_SPEED};
49 * @param inTrackInfo Track info object
51 public ProfileChart(TrackInfo inTrackInfo)
54 _data = new AltitudeData(inTrackInfo.getTrack());
55 addMouseListener(this);
56 setLayout(new FlowLayout(FlowLayout.LEFT));
57 _label = new JLabel("Altitude"); // text will be replaced later
64 * Override minimum size method to restrict slider
66 public Dimension getMinimumSize()
72 * Override paint method to draw map
73 * @param g Graphics object
75 public void paint(Graphics g)
78 ColourScheme colourScheme = Config.getColourScheme();
79 paintBackground(g, colourScheme);
80 if (_track != null && _track.getNumPoints() > 0)
82 _label.setText(_data.getLabel());
83 int width = getWidth();
84 int height = getHeight();
87 final Color barColour = colourScheme.getColour(ColourScheme.IDX_POINT);
88 final Color rangeColour = colourScheme.getColour(ColourScheme.IDX_SELECTION);
89 final Color currentColour = colourScheme.getColour(ColourScheme.IDX_PRIMARY);
90 final Color secondColour = colourScheme.getColour(ColourScheme.IDX_SECONDARY);
91 final Color lineColour = colourScheme.getColour(ColourScheme.IDX_LINES);
93 // message if no data for the current field in track
96 g.setColor(lineColour);
97 g.drawString(I18nManager.getText(_data.getNoDataKey()), 50, (height+_label.getHeight())/2);
102 // Find minimum and maximum values to plot
103 double minValue = _data.getMinValue();
104 double maxValue = _data.getMaxValue();
105 if (maxValue <= minValue) {maxValue = minValue + 1; minValue--;}
107 final int numPoints = _track.getNumPoints();
108 _xScaleFactor = 1.0 * (width - 2 * BORDER_WIDTH - 1) / numPoints;
109 int usableHeight = height - 2 * BORDER_WIDTH - _label.getHeight();
110 double yScaleFactor = 1.0 * usableHeight / (maxValue - minValue);
111 int barWidth = (int) (_xScaleFactor + 1.0);
112 int selectedPoint = _trackInfo.getSelection().getCurrentPointIndex();
113 // selection start, end
114 int selectionStart = -1, selectionEnd = -1;
115 if (_trackInfo.getSelection().hasRangeSelected()) {
116 selectionStart = _trackInfo.getSelection().getStart();
117 selectionEnd = _trackInfo.getSelection().getEnd();
120 // horizontal lines for scale - set to round numbers eg 500
121 int lineScale = getLineScale(minValue, maxValue);
122 int scaleValue = (int) (minValue/lineScale + 1) * lineScale;
123 if (minValue < 0.0) {scaleValue -= lineScale;}
125 final int zeroY = height - BORDER_WIDTH - (int) (yScaleFactor * (0.0 - minValue));
128 g.setColor(lineColour);
131 while (scaleValue < maxValue)
133 y = height - BORDER_WIDTH - (int) (yScaleFactor * (scaleValue - minValue));
134 g.drawLine(BORDER_WIDTH + 1, y, width - BORDER_WIDTH - 1, y);
135 scaleValue += lineScale;
138 else if (minValue < 0.0)
140 // just draw zero line
142 g.drawLine(BORDER_WIDTH + 1, y, width - BORDER_WIDTH - 1, y);
147 // loop through points
148 g.setColor(barColour);
149 for (int p = 0; p < numPoints; p++)
151 x = (int) (_xScaleFactor * p) + 1;
152 if (p == selectionStart)
153 g.setColor(rangeColour);
154 else if (p == (selectionEnd+1))
155 g.setColor(barColour);
156 if (_data.hasData(p))
158 value = _data.getData(p);
159 // Normal case is the minimum value greater than zero
162 y = (int) (yScaleFactor * (value - minValue));
163 g.fillRect(BORDER_WIDTH+x, height-BORDER_WIDTH - y, barWidth, y);
165 else if (value >= 0.0) {
166 // Bar upwards from the zero line
167 y = height-BORDER_WIDTH - (int) (yScaleFactor * (value - minValue));
168 g.fillRect(BORDER_WIDTH+x, y, barWidth, zeroY - y);
171 // Bar downwards from the zero line
172 int barHeight = (int) (yScaleFactor * value);
173 g.fillRect(BORDER_WIDTH+x, zeroY, barWidth, -barHeight);
177 // current point (make sure it's drawn last)
178 if (selectedPoint >= 0)
180 x = (int) (_xScaleFactor * selectedPoint) + 1;
181 g.setColor(secondColour);
182 g.fillRect(BORDER_WIDTH + x, height-usableHeight-BORDER_WIDTH+1, barWidth, usableHeight-2);
183 if (_data.hasData(selectedPoint))
185 g.setColor(currentColour);
186 value = _data.getData(selectedPoint);
187 y = (int) (yScaleFactor * (value - minValue));
188 g.fillRect(BORDER_WIDTH + x, height-BORDER_WIDTH - y, barWidth, y);
192 catch (NullPointerException npe) { // ignore, probably due to data being changed
194 // Draw numbers on top of the graph to mark scale
197 int textHeight = g.getFontMetrics().getHeight();
198 scaleValue = (int) (minValue / lineScale + 1) * lineScale;
199 if (minValue < 0.0) {scaleValue -= lineScale;}
201 g.setColor(currentColour);
202 while (scaleValue < maxValue)
204 y = height - BORDER_WIDTH - (int) (yScaleFactor * (scaleValue - minValue));
205 // Limit y so String isn't above border
206 if (y < (BORDER_WIDTH + textHeight)) {
207 y = BORDER_WIDTH + textHeight;
209 g.drawString(""+scaleValue, BORDER_WIDTH + 5, y);
210 scaleValue += lineScale;
213 // Paint label on top
220 * Paint the background for the chart
221 * @param inG graphics object
222 * @param inColourScheme colour scheme
224 private void paintBackground(Graphics inG, ColourScheme inColourScheme)
226 final int width = getWidth();
227 final int height = getHeight();
229 final Color borderColour = inColourScheme.getColour(ColourScheme.IDX_BORDERS);
230 final Color backgroundColour = inColourScheme.getColour(ColourScheme.IDX_BACKGROUND);
232 inG.setColor(backgroundColour);
233 inG.fillRect(0, 0, width, height);
234 if (width < 2*BORDER_WIDTH || height < 2*BORDER_WIDTH) return;
235 // Display message if no data to be displayed
236 if (_track == null || _track.getNumPoints() <= 0)
238 inG.setColor(COLOR_NODATA_TEXT);
239 inG.drawString(I18nManager.getText("display.nodata"), 50, height/2);
242 inG.setColor(borderColour);
243 inG.drawRect(BORDER_WIDTH, BORDER_WIDTH + _label.getHeight(),
244 width - 2*BORDER_WIDTH, height-2*BORDER_WIDTH-_label.getHeight());
249 * Make the popup menu for right-clicking the chart
251 private void makePopup()
253 _popup = new JPopupMenu();
254 JMenuItem altItem = new JMenuItem(I18nManager.getText("fieldname.altitude"));
255 altItem.addActionListener(new ActionListener() {
256 public void actionPerformed(ActionEvent e)
258 changeView(ChartType.ALTITUDE);
261 JMenuItem speedItem = new JMenuItem(I18nManager.getText("fieldname.speed"));
262 speedItem.addActionListener(new ActionListener() {
263 public void actionPerformed(ActionEvent e)
265 changeView(ChartType.SPEED);
267 _popup.add(speedItem);
268 JMenuItem vertSpeedItem = new JMenuItem(I18nManager.getText("fieldname.verticalspeed"));
269 vertSpeedItem.addActionListener(new ActionListener() {
270 public void actionPerformed(ActionEvent e)
272 changeView(ChartType.VERT_SPEED);
274 _popup.add(vertSpeedItem);
278 * Work out the scale for the horizontal lines
279 * @param inMin min value of data
280 * @param inMax max value of data
281 * @return scale separation, or -1 for no scale
283 private int getLineScale(double inMin, double inMax)
285 if ((inMax - inMin) < 2.0) {
288 int numScales = LINE_SCALES.length;
289 for (int i=0; i<numScales; i++)
291 int scale = LINE_SCALES[i];
292 int numLines = (int)(inMax / scale) - (int)(inMin / scale);
293 // Check for too many lines
294 if (numLines > 10) return -1;
295 // If more than 1 line then use this scale
296 if (numLines > 1) return scale;
298 // no suitable scale found so just use minimum
299 return LINE_SCALES[numScales-1];
304 * Method to inform map that data has changed
306 public void dataUpdated(byte inUpdateType)
308 // Try not to recalculate all the values unless necessary
309 if (inUpdateType != SELECTION_CHANGED) {
310 _data.init(Config.getUnitSet());
316 * React to click on profile display
318 public void mouseClicked(MouseEvent e)
320 if (_track == null || _track.getNumPoints() < 1) {return;}
324 int xClick = e.getX();
325 int yClick = e.getY();
326 // Check click is within main area (not in border)
327 if (xClick > BORDER_WIDTH && yClick > BORDER_WIDTH && xClick < (getWidth() - BORDER_WIDTH)
328 && yClick < (getHeight() - BORDER_WIDTH))
330 // work out which data point is nearest and select it
331 int pointNum = (int) ((e.getX() - BORDER_WIDTH) / _xScaleFactor);
332 // If shift clicked, then extend selection
333 if (e.isShiftDown()) {
334 _trackInfo.extendSelection(pointNum);
337 _trackInfo.selectPoint(pointNum);
343 _popup.show(this, e.getX(), e.getY());
348 * Called by clicking on popup menu to change the view
349 * @param inType selected chart type
351 private void changeView(ChartType inType)
353 if (inType == ChartType.ALTITUDE && !(_data instanceof AltitudeData))
355 _data = new AltitudeData(_track);
357 else if (inType == ChartType.SPEED && !(_data instanceof SpeedData)) {
358 _data = new SpeedData(_track);
360 else if (inType == ChartType.VERT_SPEED && !(_data instanceof VerticalSpeedData)) {
361 _data = new VerticalSpeedData(_track);
363 _data.init(Config.getUnitSet());
368 * mouse enter events ignored
370 public void mouseEntered(MouseEvent e)
374 * mouse exit events ignored
376 public void mouseExited(MouseEvent e)
380 * ignore mouse pressed for now too
382 public void mousePressed(MouseEvent e)
386 * and also ignore mouse released
388 public void mouseReleased(MouseEvent e)