X-Git-Url: http://gitweb.fperrin.net/?a=blobdiff_plain;f=tim%2Fprune%2Fgui%2Fprofile%2FProfileChart.java;h=466fda2175de2418569297c0722c6cfb1a2fd3c0;hb=1a735a99408fd3b0c5ac4fe7b2fdbdbb23d38f40;hp=770a211ea777535d900bb43bfed109c047b771a5;hpb=4d5796d02a15808311c09448d79e6e7d1de9d636;p=GpsPrune.git diff --git a/tim/prune/gui/profile/ProfileChart.java b/tim/prune/gui/profile/ProfileChart.java index 770a211..466fda2 100644 --- a/tim/prune/gui/profile/ProfileChart.java +++ b/tim/prune/gui/profile/ProfileChart.java @@ -4,10 +4,13 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; 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; @@ -15,6 +18,8 @@ import javax.swing.JPopupMenu; import tim.prune.I18nManager; import tim.prune.config.ColourScheme; import tim.prune.config.Config; +import tim.prune.data.Field; +import tim.prune.data.FieldList; import tim.prune.data.TrackInfo; import tim.prune.gui.GenericDisplay; @@ -23,6 +28,121 @@ import tim.prune.gui.GenericDisplay; */ public class ProfileChart extends GenericDisplay implements MouseListener { + /** Inner class to handle popup menu clicks */ + class MenuClicker implements ActionListener + { + private Field _field = null; + MenuClicker(Field inField) {_field = inField;} + /** React to menu click by changing the field */ + public void actionPerformed(ActionEvent arg0) { + changeView(_field); + } + } + + /** Inner class to remember a single index */ + class PointIndex + { + public int index = -1; + public boolean hasValue = false; + public PointIndex() + { + index = -1; + hasValue = false; + } + /** Set a single value */ + public void set(int inValue) + { + index = inValue; + hasValue = (inValue != -1); + } + /** Add an index to the minimum calculation */ + public void setMin(PointIndex other) + { + if (!other.hasValue) {return;} + if (!hasValue) { + index = other.index; + hasValue = other.hasValue; + } + else { + index = Math.min(index, other.index); + } + } + /** Add an index to the maximum calculation */ + public void setMax(PointIndex other) + { + if (!other.hasValue) {return;} + if (!hasValue) { + index = other.index; + hasValue = other.hasValue; + } + else { + index = Math.max(index, other.index); + } + } + /** @return true if two Indexes are equal */ + public boolean equals(PointIndex other) + { + if (!hasValue || !other.hasValue) { + return hasValue == other.hasValue; + } + return index == other.index; + } + } + + /** Inner class to remember previous chart parameters */ + class ChartParameters + { + public PointIndex selectedPoint = new PointIndex(); + public PointIndex rangeStart = new PointIndex(), rangeEnd = new PointIndex(); + public void clear() + { + selectedPoint.hasValue = false; + rangeStart.hasValue = false; + rangeEnd.hasValue = false; + } + /** Get the minimum index which has changed between two sets of parameters */ + public int getMinChangedIndex(ChartParameters other) + { + PointIndex minIndex = new PointIndex(); + if (!selectedPoint.equals(other.selectedPoint)) { + minIndex.setMin(selectedPoint); + minIndex.setMin(other.selectedPoint); + } + if (!rangeStart.equals(other.rangeStart)) { + minIndex.setMin(rangeStart); + minIndex.setMin(other.rangeStart); + } + if (!rangeEnd.equals(other.rangeEnd)) { + minIndex.setMin(rangeEnd); + minIndex.setMin(other.rangeEnd); + } + return minIndex.index; + } + /** Get the maximum index which has changed between two sets of parameters */ + public int getMaxChangedIndex(ChartParameters other) + { + PointIndex maxIndex = new PointIndex(); + if (!selectedPoint.equals(other.selectedPoint)) { + maxIndex.setMax(selectedPoint); + maxIndex.setMax(other.selectedPoint); + } + if (!rangeStart.equals(other.rangeStart)) { + maxIndex.setMax(rangeStart); + maxIndex.setMax(other.rangeStart); + } + if (!rangeEnd.equals(other.rangeEnd)) { + maxIndex.setMax(rangeEnd); + maxIndex.setMax(other.rangeEnd); + } + return maxIndex.index; + } + /** @return true if the parameters are completely empty (cleared) */ + public boolean isEmpty() + { + return !selectedPoint.hasValue && !rangeStart.hasValue && !rangeEnd.hasValue; + } + } + /** Current scale factor in x direction*/ private double _xScaleFactor = 0.0; /** Data to show on chart */ @@ -31,6 +151,8 @@ public class ProfileChart extends GenericDisplay implements MouseListener private JLabel _label = null; /** Right-click popup menu */ private JPopupMenu _popup = null; + /** Parameters last time chart was drawn */ + private ChartParameters _previousParameters = new ChartParameters(); /** Possible scales to use */ private static final int[] LINE_SCALES = {10000, 5000, 2000, 1000, 500, 200, 100, 50, 10, 5, 2, 1}; @@ -40,8 +162,6 @@ public class ProfileChart extends GenericDisplay implements MouseListener 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, VERT_SPEED}; /** @@ -75,144 +195,151 @@ public class ProfileChart extends GenericDisplay implements MouseListener public void paint(Graphics g) { super.paint(g); + // Set antialiasing depending on Config + ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, + Config.getConfigBoolean(Config.KEY_ANTIALIAS) ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF); ColourScheme colourScheme = Config.getColourScheme(); paintBackground(g, colourScheme); - if (_track != null && _track.getNumPoints() > 0) + + if (_track == null || _track.getNumPoints() <= 0) { - _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; - } + return; + } - // Find minimum and maximum values to plot - double minValue = _data.getMinValue(); - double maxValue = _data.getMaxValue(); - if (maxValue <= minValue) {maxValue = minValue + 1; minValue--;} - - 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(); - } + _label.setText(_data.getLabel()); + int width = getWidth(); + int height = getHeight(); - // horizontal lines for scale - set to round numbers eg 500 - int lineScale = getLineScale(minValue, maxValue); - int scaleValue = (int) (minValue/lineScale + 1) * lineScale; - if (minValue < 0.0) {scaleValue -= lineScale;} - int x = 0, y = 0; - final int zeroY = height - BORDER_WIDTH - (int) (yScaleFactor * (0.0 - minValue)); + // 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); - double value = 0.0; + // message if no data for the current field in track + if (!_data.hasData()) + { g.setColor(lineColour); - if (lineScale >= 1) - { - while (scaleValue < maxValue) - { - y = height - BORDER_WIDTH - (int) (yScaleFactor * (scaleValue - minValue)); - g.drawLine(BORDER_WIDTH + 1, y, width - BORDER_WIDTH - 1, y); - scaleValue += lineScale; - } - } - else if (minValue < 0.0) + 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; minValue--;} + + 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(); + } + + int y = 0; + double value = 0.0; + // horizontal lines for scale - set to round numbers eg 500 + final int lineScale = getLineScale(minValue, maxValue); + double scaleValue = Math.ceil(minValue/lineScale) * lineScale; + final int zeroY = height - BORDER_WIDTH - (int) (yScaleFactor * (0.0 - minValue)); + + g.setColor(lineColour); + if (lineScale >= 1) + { + while (scaleValue < maxValue) { - // just draw zero line - y = zeroY; + y = height - BORDER_WIDTH - (int) (yScaleFactor * (scaleValue - minValue)); g.drawLine(BORDER_WIDTH + 1, y, width - BORDER_WIDTH - 1, y); + scaleValue += lineScale; } + } + else if (minValue < 0.0) + { + // just draw zero line + y = zeroY; + g.drawLine(BORDER_WIDTH + 1, y, width - BORDER_WIDTH - 1, y); + } - try + try + { + // loop through points + g.setColor(barColour); + for (int p = 0; p < numPoints; p++) { - // 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); - // Normal case is the minimum value greater than zero - if (minValue >= 0) - { - y = (int) (yScaleFactor * (value - minValue)); - g.fillRect(BORDER_WIDTH+x, height-BORDER_WIDTH - y, barWidth, y); - } - else if (value >= 0.0) { - // Bar upwards from the zero line - y = height-BORDER_WIDTH - (int) (yScaleFactor * (value - minValue)); - g.fillRect(BORDER_WIDTH+x, y, barWidth, zeroY - y); - } - else { - // Bar downwards from the zero line - int barHeight = (int) (yScaleFactor * value); - g.fillRect(BORDER_WIDTH+x, zeroY, barWidth, -barHeight); - } - } - } - // current point (make sure it's drawn last) - if (selectedPoint >= 0) + if (p == selectionStart) + g.setColor(rangeColour); + else if (p == (selectionEnd+1)) + g.setColor(barColour); + + final int x = (int) (_xScaleFactor * p) + 1; + if (_data.hasData(p)) { - x = (int) (_xScaleFactor * selectedPoint) + 1; - g.setColor(secondColour); - g.fillRect(BORDER_WIDTH + x, height-usableHeight-BORDER_WIDTH+1, barWidth, usableHeight-2); - if (_data.hasData(selectedPoint)) + value = _data.getData(p); + // Normal case is the minimum value greater than zero + if (minValue >= 0) { - g.setColor(currentColour); - value = _data.getData(selectedPoint); y = (int) (yScaleFactor * (value - minValue)); - g.fillRect(BORDER_WIDTH + x, height-BORDER_WIDTH - y, barWidth, y); + g.fillRect(BORDER_WIDTH+x, height-BORDER_WIDTH - y, barWidth, y); + } + else if (value >= 0.0) { + // Bar upwards from the zero line + y = height-BORDER_WIDTH - (int) (yScaleFactor * (value - minValue)); + g.fillRect(BORDER_WIDTH+x, y, barWidth, zeroY - y); + } + else { + // Bar downwards from the zero line + int barHeight = (int) (yScaleFactor * value); + g.fillRect(BORDER_WIDTH+x, zeroY, barWidth, -barHeight); } } } - catch (NullPointerException npe) { // ignore, probably due to data being changed - } - // Draw numbers on top of the graph to mark scale - if (lineScale >= 1) + + // current point (make sure it's drawn last) + if (selectedPoint >= 0) { - int textHeight = g.getFontMetrics().getHeight(); - scaleValue = (int) (minValue / lineScale + 1) * lineScale; - if (minValue < 0.0) {scaleValue -= lineScale;} - y = 0; - g.setColor(currentColour); - while (scaleValue < maxValue) + final int sel_x = (int) (_xScaleFactor * selectedPoint) + 1; + g.setColor(secondColour); + g.fillRect(BORDER_WIDTH + sel_x, height-usableHeight-BORDER_WIDTH+1, barWidth, usableHeight-2); + if (_data.hasData(selectedPoint)) { - 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; + g.setColor(currentColour); + value = _data.getData(selectedPoint); + y = (int) (yScaleFactor * (value - minValue)); + g.fillRect(BORDER_WIDTH + sel_x, height-BORDER_WIDTH - y, barWidth, y); } } - // Paint label on top - paintChildren(g); } + 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; + if (minValue < 0.0) {scaleValue -= 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(""+(int)scaleValue, BORDER_WIDTH + 5, y); + scaleValue += lineScale; + } + } + // Paint label on top + paintChildren(g); } @@ -238,7 +365,8 @@ public class ProfileChart extends GenericDisplay implements MouseListener inG.setColor(COLOR_NODATA_TEXT); inG.drawString(I18nManager.getText("display.nodata"), 50, height/2); } - else { + else + { inG.setColor(borderColour); inG.drawRect(BORDER_WIDTH, BORDER_WIDTH + _label.getHeight(), width - 2*BORDER_WIDTH, height-2*BORDER_WIDTH-_label.getHeight()); @@ -248,30 +376,45 @@ public class ProfileChart extends GenericDisplay implements MouseListener /** * Make the popup menu for right-clicking the chart */ - private void makePopup() + private synchronized 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); + changeView(Field.ALTITUDE); }}); _popup.add(altItem); JMenuItem speedItem = new JMenuItem(I18nManager.getText("fieldname.speed")); speedItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - changeView(ChartType.SPEED); + changeView(Field.SPEED); }}); _popup.add(speedItem); JMenuItem vertSpeedItem = new JMenuItem(I18nManager.getText("fieldname.verticalspeed")); vertSpeedItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - changeView(ChartType.VERT_SPEED); + changeView(Field.VERTICAL_SPEED); }}); _popup.add(vertSpeedItem); + // Go through track's master field list, see if any other fields to list + boolean addSeparator = true; + FieldList fields = _track.getFieldList(); + for (int i=0; i 0) { + makePopup(); + } + if (inUpdateType == SELECTION_CHANGED) { + triggerPartialRepaint(); + } + else + { + repaint(); } - repaint(); + } + + /** + * For performance reasons, only repaint the part of the graphics affected by + * the change in selection + */ + private void triggerPartialRepaint() + { + ChartParameters currentParameters = new ChartParameters(); + currentParameters.selectedPoint.set(_trackInfo.getSelection().getCurrentPointIndex()); + if (_trackInfo.getSelection().hasRangeSelected()) + { + currentParameters.rangeStart.set(_trackInfo.getSelection().getStart()); + currentParameters.rangeEnd.set(_trackInfo.getSelection().getEnd()); + } + + int minPointIndex = currentParameters.getMinChangedIndex(_previousParameters); + minPointIndex = Math.max(minPointIndex, 0); + int maxPointIndex = currentParameters.getMaxChangedIndex(_previousParameters); + if (maxPointIndex < minPointIndex) { + maxPointIndex = _trackInfo.getTrack().getNumPoints() - 1; + } + // System.out.println("Redraw from index: " + minPointIndex + " to " + maxPointIndex); + _previousParameters = currentParameters; + final int region_x = (int) (_xScaleFactor * minPointIndex) + BORDER_WIDTH; + final int region_width = (int) (_xScaleFactor * (maxPointIndex-minPointIndex+2)) + 2; + repaint(region_x, 0, region_width, getHeight()); } /** @@ -346,19 +528,31 @@ public class ProfileChart extends GenericDisplay implements MouseListener /** * Called by clicking on popup menu to change the view - * @param inType selected chart type + * @param inField field to show */ - private void changeView(ChartType inType) + private void changeView(Field inField) { - if (inType == ChartType.ALTITUDE && !(_data instanceof AltitudeData)) + if (inField == Field.ALTITUDE) { - _data = new AltitudeData(_track); + if (!(_data instanceof AltitudeData)) { + _data = new AltitudeData(_track); + } + } + else if (inField == Field.SPEED) { + if (!(_data instanceof SpeedData)) { + _data = new SpeedData(_track); + } } - else if (inType == ChartType.SPEED && !(_data instanceof SpeedData)) { - _data = new SpeedData(_track); + else if (inField == Field.VERTICAL_SPEED) { + if (!(_data instanceof VerticalSpeedData)) { + _data = new VerticalSpeedData(_track); + } } - else if (inType == ChartType.VERT_SPEED && !(_data instanceof VerticalSpeedData)) { - _data = new VerticalSpeedData(_track); + else + { + if (!(_data instanceof ArbitraryData) || ((ArbitraryData)_data).getField() != inField) { + _data = new ArbitraryData(_track, inField); + } } _data.init(Config.getUnitSet()); repaint();