]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/gui/profile/ProfileChart.java
Version 10, May 2010
[GpsPrune.git] / tim / prune / gui / profile / ProfileChart.java
1 package tim.prune.gui.profile;
2
3 import java.awt.Color;
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;
14
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;
20
21 /**
22  * Chart component for the profile display
23  */
24 public class ProfileChart extends GenericDisplay implements MouseListener
25 {
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;
34
35         /** Possible scales to use */
36         private static final int[] LINE_SCALES = {10000, 5000, 2000, 1000, 500, 200, 100, 50, 10, 5};
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;
43         /** Chart type */
44         private static enum ChartType {ALTITUDE, SPEED};
45
46
47         /**
48          * Constructor
49          * @param inTrackInfo Track info object
50          */
51         public ProfileChart(TrackInfo inTrackInfo)
52         {
53                 super(inTrackInfo);
54                 _data = new AltitudeData(inTrackInfo.getTrack());
55                 addMouseListener(this);
56                 setLayout(new FlowLayout(FlowLayout.LEFT));
57                 _label = new JLabel("Altitude");
58                 add(_label);
59                 makePopup();
60         }
61
62
63         /**
64          * Override minimum size method to restrict slider
65          */
66         public Dimension getMinimumSize()
67         {
68                 return MINIMUM_SIZE;
69         }
70
71         /**
72          * Override paint method to draw map
73          * @param g Graphics object
74          */
75         public void paint(Graphics g)
76         {
77                 super.paint(g);
78                 ColourScheme colourScheme = Config.getColourScheme();
79                 paintBackground(g, colourScheme);
80                 if (_track != null && _track.getNumPoints() > 0)
81                 {
82                         _data.init();
83                         _label.setText(_data.getLabel());
84                         int width = getWidth();
85                         int height = getHeight();
86
87                         // Set up colours
88                         final Color barColour = colourScheme.getColour(ColourScheme.IDX_POINT);
89                         final Color rangeColour = colourScheme.getColour(ColourScheme.IDX_SELECTION);
90                         final Color currentColour = colourScheme.getColour(ColourScheme.IDX_PRIMARY);
91                         final Color secondColour = colourScheme.getColour(ColourScheme.IDX_SECONDARY);
92                         final Color lineColour = colourScheme.getColour(ColourScheme.IDX_LINES);
93
94                         // message if no data for the current field in track
95                         if (!_data.hasData())
96                         {
97                                 g.setColor(lineColour);
98                                 g.drawString(I18nManager.getText(_data.getNoDataKey()), 50, (height+_label.getHeight())/2);
99                                 paintChildren(g);
100                                 return;
101                         }
102
103                         // Find minimum and maximum values to plot
104                         double minValue = _data.getMinValue();
105                         double maxValue = _data.getMaxValue();
106                         if (maxValue <= minValue) {maxValue = minValue + 1;}
107
108                         final int numPoints = _track.getNumPoints();
109                         _xScaleFactor = 1.0 * (width - 2 * BORDER_WIDTH - 1) / numPoints;
110                         int usableHeight = height - 2 * BORDER_WIDTH - _label.getHeight();
111                         double yScaleFactor = 1.0 * usableHeight / (maxValue - minValue);
112                         int barWidth = (int) (_xScaleFactor + 1.0);
113                         int selectedPoint = _trackInfo.getSelection().getCurrentPointIndex();
114                         // selection start, end
115                         int selectionStart = -1, selectionEnd = -1;
116                         if (_trackInfo.getSelection().hasRangeSelected()) {
117                                 selectionStart = _trackInfo.getSelection().getStart();
118                                 selectionEnd = _trackInfo.getSelection().getEnd();
119                         }
120
121                         // horizontal lines for scale - set to round numbers eg 500
122                         int lineScale = getLineScale(minValue, maxValue);
123                         int scaleValue = (int) (minValue/lineScale + 1) * lineScale;
124                         int x = 0, y = 0;
125                         double value = 0.0;
126                         if (lineScale > 1)
127                         {
128                                 g.setColor(lineColour);
129                                 while (scaleValue < maxValue)
130                                 {
131                                         y = height - BORDER_WIDTH - (int) (yScaleFactor * (scaleValue - minValue));
132                                         g.drawLine(BORDER_WIDTH + 1, y, width - BORDER_WIDTH - 1, y);
133                                         scaleValue += lineScale;
134                                 }
135                         }
136
137                         try
138                         {
139                                 // loop through points
140                                 g.setColor(barColour);
141                                 for (int p = 0; p < numPoints; p++)
142                                 {
143                                         x = (int) (_xScaleFactor * p) + 1;
144                                         if (p == selectionStart)
145                                                 g.setColor(rangeColour);
146                                         else if (p == (selectionEnd+1))
147                                                 g.setColor(barColour);
148                                         if (_data.hasData(p))
149                                         {
150                                                 value = _data.getData(p);
151                                                 y = (int) (yScaleFactor * (value - minValue));
152                                                 g.fillRect(BORDER_WIDTH+x, height-BORDER_WIDTH - y, barWidth, y);
153                                         }
154                                 }
155                                 // current point (make sure it's drawn last)
156                                 if (_data.hasData(selectedPoint))
157                                 {
158                                         x = (int) (_xScaleFactor * selectedPoint) + 1;
159                                         g.setColor(secondColour);
160                                         g.fillRect(BORDER_WIDTH + x, height-usableHeight-BORDER_WIDTH+1, barWidth, usableHeight-2);
161                                         g.setColor(currentColour);
162                                         value = _data.getData(selectedPoint);
163                                         y = (int) (yScaleFactor * (value - minValue));
164                                         g.fillRect(BORDER_WIDTH + x, height-BORDER_WIDTH - y, barWidth, y);
165                                 }
166                         }
167                         catch (NullPointerException npe) { // ignore, probably due to data being changed
168                         }
169                         // Draw numbers on top of the graph to mark scale
170                         if (lineScale > 1)
171                         {
172                                 int textHeight = g.getFontMetrics().getHeight();
173                                 scaleValue = (int) (minValue / lineScale + 1) * lineScale;
174                                 y = 0;
175                                 g.setColor(currentColour);
176                                 while (scaleValue < maxValue)
177                                 {
178                                         y = height - BORDER_WIDTH - (int) (yScaleFactor * (scaleValue - minValue));
179                                         // Limit y so String isn't above border
180                                         if (y < (BORDER_WIDTH + textHeight)) {
181                                                 y = BORDER_WIDTH + textHeight;
182                                         }
183                                         g.drawString(""+scaleValue, BORDER_WIDTH + 5, y);
184                                         scaleValue += lineScale;
185                                 }
186                         }
187                         // Paint label on top
188                         paintChildren(g);
189                 }
190         }
191
192
193         /**
194          * Paint the background for the chart
195          * @param inG graphics object
196          * @param inColourScheme colour scheme
197          */
198         private void paintBackground(Graphics inG, ColourScheme inColourScheme)
199         {
200                 final int width = getWidth();
201                 final int height = getHeight();
202                 // Get colours
203                 final Color borderColour = inColourScheme.getColour(ColourScheme.IDX_BORDERS);
204                 final Color backgroundColour = inColourScheme.getColour(ColourScheme.IDX_BACKGROUND);
205                 // background
206                 inG.setColor(backgroundColour);
207                 inG.fillRect(0, 0, width, height);
208                 if (width < 2*BORDER_WIDTH || height < 2*BORDER_WIDTH) return;
209                 // Display message if no data to be displayed
210                 if (_track == null || _track.getNumPoints() <= 0)
211                 {
212                         inG.setColor(COLOR_NODATA_TEXT);
213                         inG.drawString(I18nManager.getText("display.nodata"), 50, height/2);
214                 }
215                 else {
216                         inG.setColor(borderColour);
217                         inG.drawRect(BORDER_WIDTH, BORDER_WIDTH + _label.getHeight(),
218                                 width - 2*BORDER_WIDTH, height-2*BORDER_WIDTH-_label.getHeight());
219                 }
220         }
221
222         /**
223          * Make the popup menu for right-clicking the chart
224          */
225         private void makePopup()
226         {
227                 _popup = new JPopupMenu();
228                 JMenuItem altItem = new JMenuItem(I18nManager.getText("fieldname.altitude"));
229                 altItem.addActionListener(new ActionListener() {
230                         public void actionPerformed(ActionEvent e)
231                         {
232                                 changeView(ChartType.ALTITUDE);
233                         }});
234                 _popup.add(altItem);
235                 JMenuItem speedItem = new JMenuItem(I18nManager.getText("fieldname.speed"));
236                 speedItem.addActionListener(new ActionListener() {
237                         public void actionPerformed(ActionEvent e)
238                         {
239                                 changeView(ChartType.SPEED);
240                         }});
241                 _popup.add(speedItem);
242         }
243
244         /**
245          * Work out the scale for the horizontal lines
246          * @param inMin min value of data
247          * @param inMax max value of data
248          * @return scale separation, or -1 for no scale
249          */
250         private int getLineScale(double inMin, double inMax)
251         {
252                 if ((inMax - inMin) < 5 || inMax < 0) {
253                         return -1;
254                 }
255                 int numScales = LINE_SCALES.length;
256                 for (int i=0; i<numScales; i++)
257                 {
258                         int scale = LINE_SCALES[i];
259                         int numLines = (int)(inMax / scale) - (int)(inMin / scale);
260                         // Check for too many lines
261                         if (numLines > 10) return -1;
262                         // If more than 1 line then use this scale
263                         if (numLines > 1) return scale;
264                 }
265                 // no suitable scale found so just use minimum
266                 return LINE_SCALES[numScales-1];
267         }
268
269
270         /**
271          * Method to inform map that data has changed
272          */
273         public void dataUpdated(byte inUpdateType)
274         {
275                 _data.init();
276                 repaint();
277         }
278
279         /**
280          * React to click on profile display
281          */
282         public void mouseClicked(MouseEvent e)
283         {
284                 if (_track == null || _track.getNumPoints() < 1) {return;}
285                 // left clicks
286                 if (!e.isMetaDown())
287                 {
288                         int xClick = e.getX();
289                         int yClick = e.getY();
290                         // Check click is within main area (not in border)
291                         if (xClick > BORDER_WIDTH && yClick > BORDER_WIDTH && xClick < (getWidth() - BORDER_WIDTH)
292                                 && yClick < (getHeight() - BORDER_WIDTH))
293                         {
294                                 // work out which data point is nearest and select it
295                                 int pointNum = (int) ((e.getX() - BORDER_WIDTH) / _xScaleFactor);
296                                 // If shift clicked, then extend selection
297                                 if (e.isShiftDown()) {
298                                         _trackInfo.extendSelection(pointNum);
299                                 }
300                                 else {
301                                         _trackInfo.selectPoint(pointNum);
302                                 }
303                         }
304                 }
305                 else {
306                         // right clicks
307                         _popup.show(this, e.getX(), e.getY());
308                 }
309         }
310
311         /**
312          * Called by clicking on popup menu to change the view
313          * @param inType selected chart type
314          */
315         private void changeView(ChartType inType)
316         {
317                 if (inType == ChartType.ALTITUDE && !(_data instanceof AltitudeData))
318                 {
319                         _data = new AltitudeData(_track);
320                 }
321                 else if (inType == ChartType.SPEED && !(_data instanceof SpeedData)) {
322                         _data = new SpeedData(_track);
323                 }
324                 _data.init();
325                 repaint();
326         }
327
328         /**
329          * mouse enter events ignored
330          */
331         public void mouseEntered(MouseEvent e)
332         {}
333
334         /**
335          * mouse exit events ignored
336          */
337         public void mouseExited(MouseEvent e)
338         {}
339
340         /**
341          * ignore mouse pressed for now too
342          */
343         public void mousePressed(MouseEvent e)
344         {}
345
346         /**
347          * and also ignore mouse released
348          */
349         public void mouseReleased(MouseEvent e)
350         {}
351 }