]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/function/charts/Charter.java
Version 7, February 2009
[GpsPrune.git] / tim / prune / function / charts / Charter.java
1 package tim.prune.function.charts;
2
3 import java.awt.BorderLayout;
4 import java.awt.FlowLayout;
5 import java.awt.GridLayout;
6 import java.awt.event.ActionEvent;
7 import java.awt.event.ActionListener;
8 import java.io.File;
9 import java.io.FileWriter;
10 import java.io.IOException;
11 import java.io.OutputStreamWriter;
12
13 import javax.swing.BorderFactory;
14 import javax.swing.BoxLayout;
15 import javax.swing.ButtonGroup;
16 import javax.swing.JButton;
17 import javax.swing.JCheckBox;
18 import javax.swing.JDialog;
19 import javax.swing.JFileChooser;
20 import javax.swing.JLabel;
21 import javax.swing.JOptionPane;
22 import javax.swing.JPanel;
23 import javax.swing.JRadioButton;
24 import javax.swing.JTextField;
25 import javax.swing.SwingConstants;
26
27 import tim.prune.App;
28 import tim.prune.Config;
29 import tim.prune.ExternalTools;
30 import tim.prune.GenericFunction;
31 import tim.prune.I18nManager;
32 import tim.prune.data.Altitude;
33 import tim.prune.data.DataPoint;
34 import tim.prune.data.Distance;
35 import tim.prune.data.Field;
36 import tim.prune.data.Timestamp;
37 import tim.prune.data.Track;
38 import tim.prune.data.Distance.Units;
39 import tim.prune.load.GenericFileFilter;
40
41 /**
42  * Class to manage the generation of charts using gnuplot
43  */
44 public class Charter extends GenericFunction
45 {
46         /** dialog object, cached */
47         private JDialog _dialog = null;
48         /** radio button for distance axis */
49         private JRadioButton _distanceRadio = null;
50         /** radio button for time axis */
51         private JRadioButton _timeRadio = null;
52         /** array of checkboxes for specifying y axes */
53         private JCheckBox[] _yAxesBoxes = null;
54         /** radio button for svg output */
55         private JRadioButton _svgRadio = null;
56         /** file chooser for saving svg file */
57         private JFileChooser _fileChooser = null;
58         /** text field for svg width */
59         private JTextField _svgWidthField = null;
60         /** text field for svg height */
61         private JTextField _svgHeightField = null;
62
63         /** Default dimensions of Svg file */
64         private static final String DEFAULT_SVG_WIDTH  = "800";
65         private static final String DEFAULT_SVG_HEIGHT = "400";
66
67
68         /**
69          * Constructor from superclass
70          * @param inApp app object
71          */
72         public Charter(App inApp)
73         {
74                 super(inApp);
75         }
76
77         /**
78          * @return key for function name
79          */
80         public String getNameKey()
81         {
82                 return "function.charts";
83         }
84
85         /**
86          * Show the dialog
87          */
88         public void begin()
89         {
90                 // Make dialog window
91                 if (_dialog == null)
92                 {
93                         _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
94                         _dialog.setLocationRelativeTo(_parentFrame);
95                         _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
96                         _dialog.getContentPane().add(makeDialogComponents());
97                         _dialog.pack();
98                 }
99                 if (setupDialog(_app.getTrackInfo().getTrack())) {
100                         _dialog.setVisible(true);
101                 }
102                 else {
103                         _app.showErrorMessage(getNameKey(), "dialog.charts.needaltitudeortimes");
104                 }
105         }
106
107
108         /**
109          * Make the dialog components
110          * @return panel containing gui elements
111          */
112         private JPanel makeDialogComponents()
113         {
114                 JPanel dialogPanel = new JPanel();
115                 dialogPanel.setLayout(new BorderLayout());
116
117                 JPanel mainPanel = new JPanel();
118                 mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
119                 // x axis choice
120                 JPanel axisPanel = new JPanel();
121                 axisPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("dialog.charts.xaxis")));
122                 _distanceRadio = new JRadioButton(I18nManager.getText("fieldname.distance"));
123                 _distanceRadio.setSelected(true);
124                 _timeRadio = new JRadioButton(I18nManager.getText("fieldname.time"));
125                 ButtonGroup axisGroup = new ButtonGroup();
126                 axisGroup.add(_distanceRadio); axisGroup.add(_timeRadio);
127                 axisPanel.add(_distanceRadio); axisPanel.add(_timeRadio);
128                 mainPanel.add(axisPanel);
129
130                 // y axis choices
131                 JPanel yPanel = new JPanel();
132                 yPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("dialog.charts.yaxis")));
133                 _yAxesBoxes = new JCheckBox[4]; // dist altitude speed vertspeed (time not available on y axis)
134                 _yAxesBoxes[0] = new JCheckBox(I18nManager.getText("fieldname.distance"));
135                 _yAxesBoxes[1] = new JCheckBox(I18nManager.getText("fieldname.altitude"));
136                 _yAxesBoxes[1].setSelected(true);
137                 _yAxesBoxes[2] = new JCheckBox(I18nManager.getText("fieldname.speed"));
138                 _yAxesBoxes[3] = new JCheckBox(I18nManager.getText("fieldname.verticalspeed"));
139                 for (int i=0; i<4; i++) {
140                         yPanel.add(_yAxesBoxes[i]);
141                 }
142                 mainPanel.add(yPanel);
143
144                 // Add validation to prevent choosing invalid (ie dist/dist) combinations
145                 ActionListener xAxisListener = new ActionListener() {
146                         public void actionPerformed(ActionEvent e) {
147                                 enableYbox(0, _timeRadio.isSelected());
148                         }
149                 };
150                 _timeRadio.addActionListener(xAxisListener);
151                 _distanceRadio.addActionListener(xAxisListener);
152
153                 // output buttons
154                 JPanel outputPanel = new JPanel();
155                 outputPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("dialog.charts.output")));
156                 outputPanel.setLayout(new BorderLayout());
157                 JPanel radiosPanel = new JPanel();
158                 JRadioButton screenRadio = new JRadioButton(I18nManager.getText("dialog.charts.screen"));
159                 screenRadio.setSelected(true);
160                 _svgRadio = new JRadioButton(I18nManager.getText("dialog.charts.svg"));
161                 ButtonGroup outputGroup = new ButtonGroup();
162                 outputGroup.add(screenRadio); outputGroup.add(_svgRadio);
163                 radiosPanel.add(screenRadio); radiosPanel.add(_svgRadio);
164                 outputPanel.add(radiosPanel, BorderLayout.NORTH);
165                 // panel for svg width, height
166                 JPanel sizePanel = new JPanel();
167                 sizePanel.setLayout(new GridLayout(2, 2, 10, 1));
168                 JLabel widthLabel = new JLabel(I18nManager.getText("dialog.charts.svgwidth"));
169                 widthLabel.setHorizontalAlignment(SwingConstants.RIGHT);
170                 sizePanel.add(widthLabel);
171                 _svgWidthField = new JTextField(DEFAULT_SVG_WIDTH, 5);
172                 sizePanel.add(_svgWidthField);
173                 JLabel heightLabel = new JLabel(I18nManager.getText("dialog.charts.svgheight"));
174                 heightLabel.setHorizontalAlignment(SwingConstants.RIGHT);
175                 sizePanel.add(heightLabel);
176                 _svgHeightField = new JTextField(DEFAULT_SVG_HEIGHT, 5);
177                 sizePanel.add(_svgHeightField);
178
179                 outputPanel.add(sizePanel, BorderLayout.EAST);
180                 mainPanel.add(outputPanel);
181                 dialogPanel.add(mainPanel, BorderLayout.CENTER);
182
183                 // button panel on bottom
184                 JPanel buttonPanel = new JPanel();
185                 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
186                 // Gnuplot button
187                 JButton gnuplotButton = new JButton(I18nManager.getText("button.gnuplotpath"));
188                 gnuplotButton.addActionListener(new ActionListener() {
189                         public void actionPerformed(ActionEvent e) {
190                                 setGnuplotPath();
191                         }
192                 });
193                 buttonPanel.add(gnuplotButton);
194                 // Cancel button
195                 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
196                 cancelButton.addActionListener(new ActionListener() {
197                         public void actionPerformed(ActionEvent e) {
198                                 _dialog.setVisible(false);
199                         }
200                 });
201                 buttonPanel.add(cancelButton);
202                 // ok button
203                 JButton okButton = new JButton(I18nManager.getText("button.ok"));
204                 okButton.addActionListener(new ActionListener() {
205                         public void actionPerformed(ActionEvent e) {
206                                 showChart(_app.getTrackInfo().getTrack());
207                                 _dialog.setVisible(false);
208                         }
209                 });
210                 buttonPanel.add(okButton);
211                 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
212                 return dialogPanel;
213         }
214
215
216         /**
217          * Set up the dialog according to the track contents
218          * @param inTrack track object
219          * @return true if it's all ok
220          */
221         private boolean setupDialog(Track inTrack)
222         {
223                 boolean hasTimes = inTrack.hasData(Field.TIMESTAMP);
224                 boolean hasAltitudes = inTrack.getAltitudeRange().hasRange();
225                 _timeRadio.setEnabled(hasTimes);
226
227                 // Add checks to prevent choosing unavailable combinations
228                 if (!hasTimes) {
229                         _distanceRadio.setSelected(true);
230                 }
231                 enableYbox(0, !_distanceRadio.isSelected());
232                 enableYbox(1, hasAltitudes);
233                 enableYbox(2, hasTimes);
234                 enableYbox(3, hasTimes && hasAltitudes);
235                 return (hasTimes || hasAltitudes);
236         }
237
238
239         /**
240          * Enable or disable the given y axis checkbox
241          * @param inIndex index of checkbox
242          * @param inFlag true to enable
243          */
244         private void enableYbox(int inIndex, boolean inFlag)
245         {
246                 _yAxesBoxes[inIndex].setEnabled(inFlag);
247                 if (!inFlag) {
248                         _yAxesBoxes[inIndex].setSelected(inFlag);
249                 }
250         }
251
252         /**
253          * Show the chart for the specified track
254          * @param inTrack track object containing data
255          */
256         private void showChart(Track inTrack)
257         {
258                 int numCharts = 0;
259                 for (int i=0; i<_yAxesBoxes.length; i++) {
260                         if (_yAxesBoxes[i].isSelected()) {
261                                 numCharts++;
262                         }
263                 }
264                 // Select default chart if none selected
265                 if (numCharts == 0) {
266                         _yAxesBoxes[1].setSelected(true);
267                         numCharts = 1;
268                 }
269                 int[] heights = getHeights(numCharts);
270
271                 boolean showSvg = _svgRadio.isSelected();
272                 File svgFile = null;
273                 if (showSvg) {
274                         svgFile = selectSvgFile();
275                         if (svgFile == null) {showSvg = false;}
276                 }
277                 OutputStreamWriter writer = null;
278                 try
279                 {
280                         Process process = Runtime.getRuntime().exec(Config.getGnuplotPath() + " -persist");
281                         writer = new OutputStreamWriter(process.getOutputStream());
282                         if (showSvg)
283                         {
284                                 writer.write("set terminal svg size " + getSvgValue(_svgWidthField, DEFAULT_SVG_WIDTH) + " "
285                                         + getSvgValue(_svgHeightField, DEFAULT_SVG_HEIGHT) + "\n");
286                                 writer.write("set out '" + svgFile.getAbsolutePath() + "'\n");
287                         }
288                         if (numCharts > 1) {
289                                 writer.write("set multiplot layout " + numCharts + ",1\n");
290                         }
291                         // Loop over possible charts
292                         int chartNum = 0;
293                         for (int c=0; c<_yAxesBoxes.length; c++)
294                         {
295                                 if (_yAxesBoxes[c].isSelected())
296                                 {
297                                         writer.write("set size 1," + (0.01*heights[chartNum*2+1]) + "\n");
298                                         writer.write("set origin 0," + (0.01*heights[chartNum*2]) + "\n");
299                                         writeChart(writer, inTrack, _distanceRadio.isSelected(), c);
300                                         chartNum++;
301                                 }
302                         }
303                         // Close multiplot if open
304                         if (numCharts > 1) {
305                                 writer.write("unset multiplot\n");
306                         }
307                 }
308                 catch (Exception e) {
309                         _app.showErrorMessageNoLookup(getNameKey(), e.getMessage());
310                 }
311                 finally {
312                         try {
313                                 // Close writer
314                                 if (writer != null) writer.close();
315                         }
316                         catch (Exception e) {} // ignore
317                 }
318         }
319
320
321         /**
322          * Parse the given text field's value and return as string
323          * @param inField text field to read from
324          * @param inDefault default value if not valid
325          * @return value of svg dimension as string
326          */
327         private static String getSvgValue(JTextField inField, String inDefault)
328         {
329                 int value = 0;
330                 try {
331                         value = Integer.parseInt(inField.getText());
332                 }
333                 catch (Exception e) {} // ignore, value stays zero
334                 if (value > 0) {
335                         return "" + value;
336                 }
337                 return inDefault;
338         }
339
340
341         /**
342          * Write out the selected chart to the given Writer object
343          * @param inWriter writer object
344          * @param inTrack Track containing data
345          * @param inDistance true if x axis is distance
346          * @param inYaxis index of y axis
347          * @throws IOException if writing error occurred
348          */
349         private static void writeChart(OutputStreamWriter inWriter, Track inTrack, boolean inDistance, int inYaxis)
350         throws IOException
351         {
352                 ChartSeries xValues = null, yValues = null;
353                 ChartSeries distValues = getDistanceValues(inTrack);
354                 // Choose x values according to axis
355                 if (inDistance) {
356                         xValues = distValues;
357                 }
358                 else {
359                         xValues = getTimeValues(inTrack);
360                 }
361                 // Choose y values according to axis
362                 switch (inYaxis)
363                 {
364                 case 0: // y axis is distance
365                         yValues = distValues;
366                         break;
367                 case 1: // y axis is altitude
368                         yValues = getAltitudeValues(inTrack);
369                         break;
370                 case 2: // y axis is speed
371                         yValues = getSpeedValues(inTrack);
372                         break;
373                 case 3: // y axis is vertical speed
374                         yValues = getVertSpeedValues(inTrack);
375                         break;
376                 }
377                 // Make a temporary data file for the output (one per subchart)
378                 File tempFile = File.createTempFile("prunedata", null);
379                 tempFile.deleteOnExit();
380                 // write out values for x and y to temporary file
381                 FileWriter tempFileWriter = null;
382                 try {
383                         tempFileWriter = new FileWriter(tempFile);
384                         tempFileWriter.write("# Temporary data file for Prune charts\n\n");
385                         for (int i=0; i<inTrack.getNumPoints(); i++) {
386                                 if (xValues.hasData(i) && yValues.hasData(i)) {
387                                         tempFileWriter.write("" + xValues.getData(i) + ", " + yValues.getData(i) + "\n");
388                                 }
389                         }
390                 }
391                 catch (IOException ioe) { // rethrow
392                         throw ioe;
393                 }
394                 finally {
395                         try {
396                                 tempFileWriter.close();
397                         }
398                         catch (Exception e) {}
399                 }
400
401                 // Set x axis label
402                 if (inDistance) {
403                         inWriter.write("set xlabel '" + I18nManager.getText("fieldname.distance") + " (" + getUnitsLabel("units.kilometres.short", "units.miles.short") + ")'\n");
404                 }
405                 else {
406                         inWriter.write("set xlabel '" + I18nManager.getText("fieldname.time") + " (" + I18nManager.getText("units.hours") + ")'\n");
407                 }
408
409                 // set other labels and plot chart
410                 String chartTitle = null;
411                 switch (inYaxis)
412                 {
413                 case 0: // y axis is distance
414                         inWriter.write("set ylabel '" + I18nManager.getText("fieldname.distance") + " (" + getUnitsLabel("units.kilometres.short", "units.miles.short") + ")'\n");
415                         chartTitle = I18nManager.getText("fieldname.distance");
416                         break;
417                 case 1: // y axis is altitude
418                         inWriter.write("set ylabel '" + I18nManager.getText("fieldname.altitude") + " (" + getUnitsLabel("units.metres.short", "units.feet.short") + ")'\n");
419                         chartTitle = I18nManager.getText("fieldname.altitude");
420                         break;
421                 case 2: // y axis is speed
422                         inWriter.write("set ylabel '" + I18nManager.getText("fieldname.speed") + " (" + getUnitsLabel("units.kmh", "units.mph") + ")'\n");
423                         chartTitle = I18nManager.getText("fieldname.speed");
424                         break;
425                 case 3: // y axis is vertical speed
426                         inWriter.write("set ylabel '" + I18nManager.getText("fieldname.verticalspeed") + " (" + getUnitsLabel("units.metrespersec", "units.feetpersec") + ")'\n");
427                         chartTitle = I18nManager.getText("fieldname.verticalspeed");
428                         break;
429                 }
430                 inWriter.write("set style fill solid 0.5 border -1\n");
431                 inWriter.write("plot '" + tempFile.getAbsolutePath() + "' title '" + chartTitle + "' with filledcurve y1=0 lt rgb \"#009000\"\n");
432         }
433
434         /**
435          * Get the units label for the given keys
436          * @param inMetric key if metric
437          * @param inImperial key if imperial
438          * @return display label with appropriate text
439          */
440         private static String getUnitsLabel(String inMetric, String inImperial)
441         {
442                 String key = Config.getUseMetricUnits()?inMetric:inImperial;
443                 return I18nManager.getText(key);
444         }
445
446
447         /**
448          * Calculate the distance values for each point in the given track
449          * @param inTrack track object
450          * @return distance values in a ChartSeries object
451          */
452         private static ChartSeries getDistanceValues(Track inTrack)
453         {
454                 // Calculate distances and fill in in values array
455                 ChartSeries values = new ChartSeries(inTrack.getNumPoints());
456                 double totalRads = 0;
457                 DataPoint prevPoint = null, currPoint = null;
458                 for (int i=0; i<inTrack.getNumPoints(); i++)
459                 {
460                         currPoint = inTrack.getPoint(i);
461                         if (prevPoint != null && !currPoint.isWaypoint() && !currPoint.getSegmentStart())
462                         {
463                                 totalRads += DataPoint.calculateRadiansBetween(prevPoint, currPoint);
464                         }
465                         if (Config.getUseMetricUnits()) {
466                                 values.setData(i, Distance.convertRadiansToDistance(totalRads, Units.KILOMETRES));
467                         } else {
468                                 values.setData(i, Distance.convertRadiansToDistance(totalRads, Units.MILES));
469                         }
470                         prevPoint = currPoint;
471                 }
472                 return values;
473         }
474
475         /**
476          * Calculate the time values for each point in the given track
477          * @param inTrack track object
478          * @return time values in a ChartSeries object
479          */
480         private static ChartSeries getTimeValues(Track inTrack)
481         {
482                 // Calculate times and fill in in values array
483                 ChartSeries values = new ChartSeries(inTrack.getNumPoints());
484                 double seconds = 0.0;
485                 Timestamp prevTimestamp = null;
486                 DataPoint currPoint = null;
487                 for (int i=0; i<inTrack.getNumPoints(); i++)
488                 {
489                         currPoint = inTrack.getPoint(i);
490                         if (currPoint.hasTimestamp())
491                         {
492                                 if (!currPoint.getSegmentStart() && prevTimestamp != null) {
493                                         seconds += (currPoint.getTimestamp().getSecondsSince(prevTimestamp));
494                                 }
495                                 values.setData(i, seconds / 60.0 / 60.0);
496                                 prevTimestamp = currPoint.getTimestamp();
497                         }
498                 }
499                 return values;
500         }
501
502         /**
503          * Calculate the altitude values for each point in the given track
504          * @param inTrack track object
505          * @return altitude values in a ChartSeries object
506          */
507         private static ChartSeries getAltitudeValues(Track inTrack)
508         {
509                 ChartSeries values = new ChartSeries(inTrack.getNumPoints());
510                 Altitude.Format altFormat = Config.getUseMetricUnits()?Altitude.Format.METRES:Altitude.Format.FEET;
511                 for (int i=0; i<inTrack.getNumPoints(); i++) {
512                         if (inTrack.getPoint(i).hasAltitude()) {
513                                 values.setData(i, inTrack.getPoint(i).getAltitude().getValue(altFormat));
514                         }
515                 }
516                 return values;
517         }
518
519         /**
520          * Calculate the speed values for each point in the given track
521          * @param inTrack track object
522          * @return speed values in a ChartSeries object
523          */
524         private static ChartSeries getSpeedValues(Track inTrack)
525         {
526                 // Calculate speeds and fill in in values array
527                 ChartSeries values = new ChartSeries(inTrack.getNumPoints());
528                 DataPoint prevPoint = null, currPoint = null, nextPoint = null;
529                 DataPoint[] points = getDataPoints(inTrack, false);
530                 // Loop over collected points
531                 for (int i=1; i<(points.length-1); i++)
532                 {
533                         prevPoint = points[i-1];
534                         currPoint = points[i];
535                         nextPoint = points[i+1];
536                         if (prevPoint != null && currPoint != null && nextPoint != null
537                                 && nextPoint.getTimestamp().isAfter(currPoint.getTimestamp())
538                                 && currPoint.getTimestamp().isAfter(prevPoint.getTimestamp()))
539                         {
540                                 // Calculate average speed between prevPoint and nextPoint
541                                 double rads = DataPoint.calculateRadiansBetween(prevPoint, currPoint)
542                                         + DataPoint.calculateRadiansBetween(currPoint, nextPoint);
543                                 double time = nextPoint.getTimestamp().getSecondsSince(prevPoint.getTimestamp()) / 60.0 / 60.0;
544                                 // Convert to distance and pass to chartseries
545                                 if (Config.getUseMetricUnits()) {
546                                         values.setData(i, Distance.convertRadiansToDistance(rads, Units.KILOMETRES) / time);
547                                 } else {
548                                         values.setData(i, Distance.convertRadiansToDistance(rads, Units.MILES) / time);
549                                 }
550                         }
551                 }
552                 return values;
553         }
554
555         /**
556          * Calculate the vertical speed values for each point in the given track
557          * @param inTrack track object
558          * @return vertical speed values in a ChartSeries object
559          */
560         private static ChartSeries getVertSpeedValues(Track inTrack)
561         {
562                 // Calculate speeds and fill in in values array
563                 ChartSeries values = new ChartSeries(inTrack.getNumPoints());
564                 Altitude.Format altFormat = Config.getUseMetricUnits()?Altitude.Format.METRES:Altitude.Format.FEET;
565                 DataPoint prevPoint = null, currPoint = null, nextPoint = null;
566                 DataPoint[] points = getDataPoints(inTrack, true); // require that points have altitudes too
567                 // Loop over collected points
568                 for (int i=1; i<(points.length-1); i++)
569                 {
570                         prevPoint = points[i-1];
571                         currPoint = points[i];
572                         nextPoint = points[i+1];
573                         if (prevPoint != null && currPoint != null && nextPoint != null
574                                 && nextPoint.getTimestamp().isAfter(currPoint.getTimestamp())
575                                 && currPoint.getTimestamp().isAfter(prevPoint.getTimestamp()))
576                         {
577                                 // Calculate average vertical speed between prevPoint and nextPoint
578                                 double vspeed = (nextPoint.getAltitude().getValue(altFormat) - prevPoint.getAltitude().getValue(altFormat))
579                                  * 1.0 / nextPoint.getTimestamp().getSecondsSince(prevPoint.getTimestamp());
580                                 values.setData(i, vspeed);
581                         }
582                 }
583                 return values;
584         }
585
586
587         /**
588          * Get an array of DataPoints with data for the charts
589          * @param inTrack track object containing points
590          * @param inRequireAltitudes true if only points with altitudes are considered
591          * @return array of points with contiguous non-null elements (<= size) with timestamps
592          */
593         private static DataPoint[] getDataPoints(Track inTrack, boolean inRequireAltitudes)
594         {
595                 DataPoint[] points = new DataPoint[inTrack.getNumPoints()];
596                 DataPoint currPoint = null;
597                 int pointNum = 0;
598                 // Loop over all points
599                 for (int i=0; i<inTrack.getNumPoints(); i++)
600                 {
601                         currPoint = inTrack.getPoint(i);
602                         if (currPoint != null && !currPoint.isWaypoint() && currPoint.hasTimestamp()
603                                 && (!inRequireAltitudes || currPoint.hasAltitude()))
604                         {
605                                 points[pointNum] = currPoint;
606                                 pointNum++;
607                         }
608                 }
609                 // Any elements at the end of the array will stay null
610                 // Also note, chronological order is not checked
611                 return points;
612         }
613
614
615         /**
616          * Select a file to write for the SVG output
617          * @return
618          */
619         private File selectSvgFile()
620         {
621                 if (_fileChooser == null)
622                 {
623                         _fileChooser = new JFileChooser();
624                         _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
625                         _fileChooser.setFileFilter(new GenericFileFilter("filetype.svg", new String[] {"svg"}));
626                         _fileChooser.setAcceptAllFileFilterUsed(false);
627                         // start from directory in config which should be set
628                         File configDir = Config.getWorkingDirectory();
629                         if (configDir != null) {_fileChooser.setCurrentDirectory(configDir);}
630                 }
631                 boolean chooseAgain = true;
632                 while (chooseAgain)
633                 {
634                         chooseAgain = false;
635                         if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
636                         {
637                                 // OK pressed and file chosen
638                                 File file = _fileChooser.getSelectedFile();
639                                 // Check file extension
640                                 if (!file.getName().toLowerCase().endsWith(".svg")) {
641                                         file = new File(file.getAbsolutePath() + ".svg");
642                                 }
643                                 // Check if file exists and if necessary prompt for overwrite
644                                 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
645                                 if (!file.exists() || (file.canWrite() && JOptionPane.showOptionDialog(_parentFrame,
646                                                 I18nManager.getText("dialog.save.overwrite.text"),
647                                                 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
648                                                 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
649                                         == JOptionPane.YES_OPTION))
650                                 {
651                                         return file;
652                                 }
653                                 else {
654                                         chooseAgain = true;
655                                 }
656                         }
657                 }
658                 // Cancel pressed so no file selected
659                 return null;
660         }
661
662
663         /**
664          * @param inNumCharts number of charts to draw
665          * @return array of ints describing position and height of each subchart
666          */
667         private static int[] getHeights(int inNumCharts)
668         {
669                 if (inNumCharts <= 1) {return new int[] {0, 100};}
670                 if (inNumCharts == 2) {return new int[] {25, 75, 0, 25};}
671                 if (inNumCharts == 3) {return new int[] {40, 60, 20, 20, 0, 20};}
672                 return new int[] {54, 46, 36, 18, 18, 18, 0, 18};
673         }
674
675         /**
676          * Prompt the user to set/edit the path to gnuplot
677          */
678         private void setGnuplotPath()
679         {
680                 String currPath = Config.getGnuplotPath();
681                 Object path = JOptionPane.showInputDialog(_dialog,
682                         I18nManager.getText("dialog.charts.gnuplotpath"),
683                         I18nManager.getText(getNameKey()),
684                         JOptionPane.QUESTION_MESSAGE, null, null, "" + currPath);
685                 if (path != null)
686                 {
687                         String pathString = path.toString().trim();
688                         if (!pathString.equals("") && !pathString.equals(currPath)) {
689                                 Config.setGnuplotPath(pathString);
690                                 // warn if gnuplot still not found
691                                 if (!ExternalTools.isGnuplotInstalled()) {
692                                         _app.showErrorMessage(getNameKey(), "dialog.charts.gnuplotnotfound");
693                                 }
694                         }
695                 }
696         }
697 }