X-Git-Url: http://gitweb.fperrin.net/?a=blobdiff_plain;f=tim%2Fprune%2Fgui%2Fprofile%2FProfileChart.java;fp=tim%2Fprune%2Fgui%2Fprofile%2FProfileChart.java;h=d989ebfdfb99f889ec853a3c38d040304fe02183;hb=c0387c124840c9407e040600fda88f3c3e8f6aa6;hp=0000000000000000000000000000000000000000;hpb=1ee49ae3c8ef3aa2e63eadd458531e5f8bd4f92c;p=GpsPrune.git diff --git a/tim/prune/gui/profile/ProfileChart.java b/tim/prune/gui/profile/ProfileChart.java new file mode 100644 index 0000000..d989ebf --- /dev/null +++ b/tim/prune/gui/profile/ProfileChart.java @@ -0,0 +1,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 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) + {} +}