File: ProfileChart.java

package info (click to toggle)
gpsprune 10-1
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 2,220 kB
  • ctags: 3,013
  • sloc: java: 22,662; sh: 23; makefile: 16; python: 15
file content (351 lines) | stat: -rw-r--r-- 10,054 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
package tim.prune.gui.profile;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;

import tim.prune.I18nManager;
import tim.prune.config.ColourScheme;
import tim.prune.config.Config;
import tim.prune.data.TrackInfo;
import tim.prune.gui.GenericDisplay;

/**
 * Chart component for the profile display
 */
public class ProfileChart extends GenericDisplay implements MouseListener
{
	/** Current scale factor in x direction*/
	private double _xScaleFactor = 0.0;
	/** Data to show on chart */
	private ProfileData _data = null;
	/** Label for chart type, units */
	private JLabel _label = null;
	/** Right-click popup menu */
	private JPopupMenu _popup = null;

	/** Possible scales to use */
	private static final int[] LINE_SCALES = {10000, 5000, 2000, 1000, 500, 200, 100, 50, 10, 5};
	/** Border width around black line */
	private static final int BORDER_WIDTH = 6;
	/** Minimum size for profile chart in pixels */
	private static final Dimension MINIMUM_SIZE = new Dimension(200, 110);
	/** Colour to use for text if no data found */
	private static final Color COLOR_NODATA_TEXT = Color.GRAY;
	/** Chart type */
	private static enum ChartType {ALTITUDE, SPEED};


	/**
	 * Constructor
	 * @param inTrackInfo Track info object
	 */
	public ProfileChart(TrackInfo inTrackInfo)
	{
		super(inTrackInfo);
		_data = new AltitudeData(inTrackInfo.getTrack());
		addMouseListener(this);
		setLayout(new FlowLayout(FlowLayout.LEFT));
		_label = new JLabel("Altitude");
		add(_label);
		makePopup();
	}


	/**
	 * Override minimum size method to restrict slider
	 */
	public Dimension getMinimumSize()
	{
		return MINIMUM_SIZE;
	}

	/**
	 * Override paint method to draw map
	 * @param g Graphics object
	 */
	public void paint(Graphics g)
	{
		super.paint(g);
		ColourScheme colourScheme = Config.getColourScheme();
		paintBackground(g, colourScheme);
		if (_track != null && _track.getNumPoints() > 0)
		{
			_data.init();
			_label.setText(_data.getLabel());
			int width = getWidth();
			int height = getHeight();

			// Set up colours
			final Color barColour = colourScheme.getColour(ColourScheme.IDX_POINT);
			final Color rangeColour = colourScheme.getColour(ColourScheme.IDX_SELECTION);
			final Color currentColour = colourScheme.getColour(ColourScheme.IDX_PRIMARY);
			final Color secondColour = colourScheme.getColour(ColourScheme.IDX_SECONDARY);
			final Color lineColour = colourScheme.getColour(ColourScheme.IDX_LINES);

			// message if no data for the current field in track
			if (!_data.hasData())
			{
				g.setColor(lineColour);
				g.drawString(I18nManager.getText(_data.getNoDataKey()), 50, (height+_label.getHeight())/2);
				paintChildren(g);
				return;
			}

			// Find minimum and maximum values to plot
			double minValue = _data.getMinValue();
			double maxValue = _data.getMaxValue();
			if (maxValue <= minValue) {maxValue = minValue + 1;}

			final int numPoints = _track.getNumPoints();
			_xScaleFactor = 1.0 * (width - 2 * BORDER_WIDTH - 1) / numPoints;
			int usableHeight = height - 2 * BORDER_WIDTH - _label.getHeight();
			double yScaleFactor = 1.0 * usableHeight / (maxValue - minValue);
			int barWidth = (int) (_xScaleFactor + 1.0);
			int selectedPoint = _trackInfo.getSelection().getCurrentPointIndex();
			// selection start, end
			int selectionStart = -1, selectionEnd = -1;
			if (_trackInfo.getSelection().hasRangeSelected()) {
				selectionStart = _trackInfo.getSelection().getStart();
				selectionEnd = _trackInfo.getSelection().getEnd();
			}

			// horizontal lines for scale - set to round numbers eg 500
			int lineScale = getLineScale(minValue, maxValue);
			int scaleValue = (int) (minValue/lineScale + 1) * lineScale;
			int x = 0, y = 0;
			double value = 0.0;
			if (lineScale > 1)
			{
				g.setColor(lineColour);
				while (scaleValue < maxValue)
				{
					y = height - BORDER_WIDTH - (int) (yScaleFactor * (scaleValue - minValue));
					g.drawLine(BORDER_WIDTH + 1, y, width - BORDER_WIDTH - 1, y);
					scaleValue += lineScale;
				}
			}

			try
			{
				// loop through points
				g.setColor(barColour);
				for (int p = 0; p < numPoints; p++)
				{
					x = (int) (_xScaleFactor * p) + 1;
					if (p == selectionStart)
						g.setColor(rangeColour);
					else if (p == (selectionEnd+1))
						g.setColor(barColour);
					if (_data.hasData(p))
					{
						value = _data.getData(p);
						y = (int) (yScaleFactor * (value - minValue));
						g.fillRect(BORDER_WIDTH+x, height-BORDER_WIDTH - y, barWidth, y);
					}
				}
				// current point (make sure it's drawn last)
				if (_data.hasData(selectedPoint))
				{
					x = (int) (_xScaleFactor * selectedPoint) + 1;
					g.setColor(secondColour);
					g.fillRect(BORDER_WIDTH + x, height-usableHeight-BORDER_WIDTH+1, barWidth, usableHeight-2);
					g.setColor(currentColour);
					value = _data.getData(selectedPoint);
					y = (int) (yScaleFactor * (value - minValue));
					g.fillRect(BORDER_WIDTH + x, height-BORDER_WIDTH - y, barWidth, y);
				}
			}
			catch (NullPointerException npe) { // ignore, probably due to data being changed
			}
			// Draw numbers on top of the graph to mark scale
			if (lineScale > 1)
			{
				int textHeight = g.getFontMetrics().getHeight();
				scaleValue = (int) (minValue / lineScale + 1) * lineScale;
				y = 0;
				g.setColor(currentColour);
				while (scaleValue < maxValue)
				{
					y = height - BORDER_WIDTH - (int) (yScaleFactor * (scaleValue - minValue));
					// Limit y so String isn't above border
					if (y < (BORDER_WIDTH + textHeight)) {
						y = BORDER_WIDTH + textHeight;
					}
					g.drawString(""+scaleValue, BORDER_WIDTH + 5, y);
					scaleValue += lineScale;
				}
			}
			// Paint label on top
			paintChildren(g);
		}
	}


	/**
	 * Paint the background for the chart
	 * @param inG graphics object
	 * @param inColourScheme colour scheme
	 */
	private void paintBackground(Graphics inG, ColourScheme inColourScheme)
	{
		final int width = getWidth();
		final int height = getHeight();
		// Get colours
		final Color borderColour = inColourScheme.getColour(ColourScheme.IDX_BORDERS);
		final Color backgroundColour = inColourScheme.getColour(ColourScheme.IDX_BACKGROUND);
		// background
		inG.setColor(backgroundColour);
		inG.fillRect(0, 0, width, height);
		if (width < 2*BORDER_WIDTH || height < 2*BORDER_WIDTH) return;
		// Display message if no data to be displayed
		if (_track == null || _track.getNumPoints() <= 0)
		{
			inG.setColor(COLOR_NODATA_TEXT);
			inG.drawString(I18nManager.getText("display.nodata"), 50, height/2);
		}
		else {
			inG.setColor(borderColour);
			inG.drawRect(BORDER_WIDTH, BORDER_WIDTH + _label.getHeight(),
				width - 2*BORDER_WIDTH, height-2*BORDER_WIDTH-_label.getHeight());
		}
	}

	/**
	 * Make the popup menu for right-clicking the chart
	 */
	private void makePopup()
	{
		_popup = new JPopupMenu();
		JMenuItem altItem = new JMenuItem(I18nManager.getText("fieldname.altitude"));
		altItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e)
			{
				changeView(ChartType.ALTITUDE);
			}});
		_popup.add(altItem);
		JMenuItem speedItem = new JMenuItem(I18nManager.getText("fieldname.speed"));
		speedItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e)
			{
				changeView(ChartType.SPEED);
			}});
		_popup.add(speedItem);
	}

	/**
	 * Work out the scale for the horizontal lines
	 * @param inMin min value of data
	 * @param inMax max value of data
	 * @return scale separation, or -1 for no scale
	 */
	private int getLineScale(double inMin, double inMax)
	{
		if ((inMax - inMin) < 5 || inMax < 0) {
			return -1;
		}
		int numScales = LINE_SCALES.length;
		for (int i=0; i<numScales; i++)
		{
			int scale = LINE_SCALES[i];
			int numLines = (int)(inMax / scale) - (int)(inMin / scale);
			// Check for too many lines
			if (numLines > 10) return -1;
			// If more than 1 line then use this scale
			if (numLines > 1) return scale;
		}
		// no suitable scale found so just use minimum
		return LINE_SCALES[numScales-1];
	}


	/**
	 * Method to inform map that data has changed
	 */
	public void dataUpdated(byte inUpdateType)
	{
		_data.init();
		repaint();
	}

	/**
	 * React to click on profile display
	 */
	public void mouseClicked(MouseEvent e)
	{
		if (_track == null || _track.getNumPoints() < 1) {return;}
		// left clicks
		if (!e.isMetaDown())
		{
			int xClick = e.getX();
			int yClick = e.getY();
			// Check click is within main area (not in border)
			if (xClick > BORDER_WIDTH && yClick > BORDER_WIDTH && xClick < (getWidth() - BORDER_WIDTH)
				&& yClick < (getHeight() - BORDER_WIDTH))
			{
				// work out which data point is nearest and select it
				int pointNum = (int) ((e.getX() - BORDER_WIDTH) / _xScaleFactor);
				// If shift clicked, then extend selection
				if (e.isShiftDown()) {
					_trackInfo.extendSelection(pointNum);
				}
				else {
					_trackInfo.selectPoint(pointNum);
				}
			}
		}
		else {
			// right clicks
			_popup.show(this, e.getX(), e.getY());
		}
	}

	/**
	 * Called by clicking on popup menu to change the view
	 * @param inType selected chart type
	 */
	private void changeView(ChartType inType)
	{
		if (inType == ChartType.ALTITUDE && !(_data instanceof AltitudeData))
		{
			_data = new AltitudeData(_track);
		}
		else if (inType == ChartType.SPEED && !(_data instanceof SpeedData)) {
			_data = new SpeedData(_track);
		}
		_data.init();
		repaint();
	}

	/**
	 * mouse enter events ignored
	 */
	public void mouseEntered(MouseEvent e)
	{}

	/**
	 * mouse exit events ignored
	 */
	public void mouseExited(MouseEvent e)
	{}

	/**
	 * ignore mouse pressed for now too
	 */
	public void mousePressed(MouseEvent e)
	{}

	/**
	 * and also ignore mouse released
	 */
	public void mouseReleased(MouseEvent e)
	{}
}