]> gitweb.fperrin.net Git - GpsPrune.git/blobdiff - tim/prune/gui/profile/ProfileChart.java
Version 10, May 2010
[GpsPrune.git] / tim / prune / gui / profile / ProfileChart.java
diff --git a/tim/prune/gui/profile/ProfileChart.java b/tim/prune/gui/profile/ProfileChart.java
new file mode 100644 (file)
index 0000000..d989ebf
--- /dev/null
@@ -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<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)
+       {}
+}