]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/gui/profile/ProfileChart.java
17ea32b2a4c004328b1ce1afcaf592b97423780c
[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; minValue--;}
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 (selectedPoint >= 0)
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                                         if (_data.hasData(selectedPoint))
162                                         {
163                                                 g.setColor(currentColour);
164                                                 value = _data.getData(selectedPoint);
165                                                 y = (int) (yScaleFactor * (value - minValue));
166                                                 g.fillRect(BORDER_WIDTH + x, height-BORDER_WIDTH - y, barWidth, y);
167                                         }
168                                 }
169                         }
170                         catch (NullPointerException npe) { // ignore, probably due to data being changed
171                         }
172                         // Draw numbers on top of the graph to mark scale
173                         if (lineScale > 1)
174                         {
175                                 int textHeight = g.getFontMetrics().getHeight();
176                                 scaleValue = (int) (minValue / lineScale + 1) * lineScale;
177                                 y = 0;
178                                 g.setColor(currentColour);
179                                 while (scaleValue < maxValue)
180                                 {
181                                         y = height - BORDER_WIDTH - (int) (yScaleFactor * (scaleValue - minValue));
182                                         // Limit y so String isn't above border
183                                         if (y < (BORDER_WIDTH + textHeight)) {
184                                                 y = BORDER_WIDTH + textHeight;
185                                         }
186                                         g.drawString(""+scaleValue, BORDER_WIDTH + 5, y);
187                                         scaleValue += lineScale;
188                                 }
189                         }
190                         // Paint label on top
191                         paintChildren(g);
192                 }
193         }
194
195
196         /**
197          * Paint the background for the chart
198          * @param inG graphics object
199          * @param inColourScheme colour scheme
200          */
201         private void paintBackground(Graphics inG, ColourScheme inColourScheme)
202         {
203                 final int width = getWidth();
204                 final int height = getHeight();
205                 // Get colours
206                 final Color borderColour = inColourScheme.getColour(ColourScheme.IDX_BORDERS);
207                 final Color backgroundColour = inColourScheme.getColour(ColourScheme.IDX_BACKGROUND);
208                 // background
209                 inG.setColor(backgroundColour);
210                 inG.fillRect(0, 0, width, height);
211                 if (width < 2*BORDER_WIDTH || height < 2*BORDER_WIDTH) return;
212                 // Display message if no data to be displayed
213                 if (_track == null || _track.getNumPoints() <= 0)
214                 {
215                         inG.setColor(COLOR_NODATA_TEXT);
216                         inG.drawString(I18nManager.getText("display.nodata"), 50, height/2);
217                 }
218                 else {
219                         inG.setColor(borderColour);
220                         inG.drawRect(BORDER_WIDTH, BORDER_WIDTH + _label.getHeight(),
221                                 width - 2*BORDER_WIDTH, height-2*BORDER_WIDTH-_label.getHeight());
222                 }
223         }
224
225         /**
226          * Make the popup menu for right-clicking the chart
227          */
228         private void makePopup()
229         {
230                 _popup = new JPopupMenu();
231                 JMenuItem altItem = new JMenuItem(I18nManager.getText("fieldname.altitude"));
232                 altItem.addActionListener(new ActionListener() {
233                         public void actionPerformed(ActionEvent e)
234                         {
235                                 changeView(ChartType.ALTITUDE);
236                         }});
237                 _popup.add(altItem);
238                 JMenuItem speedItem = new JMenuItem(I18nManager.getText("fieldname.speed"));
239                 speedItem.addActionListener(new ActionListener() {
240                         public void actionPerformed(ActionEvent e)
241                         {
242                                 changeView(ChartType.SPEED);
243                         }});
244                 _popup.add(speedItem);
245         }
246
247         /**
248          * Work out the scale for the horizontal lines
249          * @param inMin min value of data
250          * @param inMax max value of data
251          * @return scale separation, or -1 for no scale
252          */
253         private int getLineScale(double inMin, double inMax)
254         {
255                 if ((inMax - inMin) < 5 || inMax < 0) {
256                         return -1;
257                 }
258                 int numScales = LINE_SCALES.length;
259                 for (int i=0; i<numScales; i++)
260                 {
261                         int scale = LINE_SCALES[i];
262                         int numLines = (int)(inMax / scale) - (int)(inMin / scale);
263                         // Check for too many lines
264                         if (numLines > 10) return -1;
265                         // If more than 1 line then use this scale
266                         if (numLines > 1) return scale;
267                 }
268                 // no suitable scale found so just use minimum
269                 return LINE_SCALES[numScales-1];
270         }
271
272
273         /**
274          * Method to inform map that data has changed
275          */
276         public void dataUpdated(byte inUpdateType)
277         {
278                 _data.init();
279                 repaint();
280         }
281
282         /**
283          * React to click on profile display
284          */
285         public void mouseClicked(MouseEvent e)
286         {
287                 if (_track == null || _track.getNumPoints() < 1) {return;}
288                 // left clicks
289                 if (!e.isMetaDown())
290                 {
291                         int xClick = e.getX();
292                         int yClick = e.getY();
293                         // Check click is within main area (not in border)
294                         if (xClick > BORDER_WIDTH && yClick > BORDER_WIDTH && xClick < (getWidth() - BORDER_WIDTH)
295                                 && yClick < (getHeight() - BORDER_WIDTH))
296                         {
297                                 // work out which data point is nearest and select it
298                                 int pointNum = (int) ((e.getX() - BORDER_WIDTH) / _xScaleFactor);
299                                 // If shift clicked, then extend selection
300                                 if (e.isShiftDown()) {
301                                         _trackInfo.extendSelection(pointNum);
302                                 }
303                                 else {
304                                         _trackInfo.selectPoint(pointNum);
305                                 }
306                         }
307                 }
308                 else {
309                         // right clicks
310                         _popup.show(this, e.getX(), e.getY());
311                 }
312         }
313
314         /**
315          * Called by clicking on popup menu to change the view
316          * @param inType selected chart type
317          */
318         private void changeView(ChartType inType)
319         {
320                 if (inType == ChartType.ALTITUDE && !(_data instanceof AltitudeData))
321                 {
322                         _data = new AltitudeData(_track);
323                 }
324                 else if (inType == ChartType.SPEED && !(_data instanceof SpeedData)) {
325                         _data = new SpeedData(_track);
326                 }
327                 _data.init();
328                 repaint();
329         }
330
331         /**
332          * mouse enter events ignored
333          */
334         public void mouseEntered(MouseEvent e)
335         {}
336
337         /**
338          * mouse exit events ignored
339          */
340         public void mouseExited(MouseEvent e)
341         {}
342
343         /**
344          * ignore mouse pressed for now too
345          */
346         public void mousePressed(MouseEvent e)
347         {}
348
349         /**
350          * and also ignore mouse released
351          */
352         public void mouseReleased(MouseEvent e)
353         {}
354 }