X-Git-Url: http://gitweb.fperrin.net/?p=GpsPrune.git;a=blobdiff_plain;f=src%2Ftim%2Fprune%2Ffunction%2Fcharts%2FCharter.java;fp=src%2Ftim%2Fprune%2Ffunction%2Fcharts%2FCharter.java;h=5b7ae83c29470ae7fa51306446554c10514405d2;hp=0000000000000000000000000000000000000000;hb=ce6f2161b8596f7018d6a76bff79bc9e571f35fd;hpb=2d8cb72e84d5cc1089ce77baf1e34ea3ea2f8465 diff --git a/src/tim/prune/function/charts/Charter.java b/src/tim/prune/function/charts/Charter.java new file mode 100644 index 0000000..5b7ae83 --- /dev/null +++ b/src/tim/prune/function/charts/Charter.java @@ -0,0 +1,620 @@ +package tim.prune.function.charts; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStreamWriter; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.SwingConstants; + +import tim.prune.App; +import tim.prune.ExternalTools; +import tim.prune.GenericFunction; +import tim.prune.I18nManager; +import tim.prune.config.Config; +import tim.prune.data.DataPoint; +import tim.prune.data.Distance; +import tim.prune.data.Field; +import tim.prune.data.Timestamp; +import tim.prune.data.Track; +import tim.prune.gui.profile.SpeedData; +import tim.prune.gui.profile.VerticalSpeedData; +import tim.prune.load.GenericFileFilter; + +/** + * Class to manage the generation of charts using gnuplot + */ +public class Charter extends GenericFunction +{ + /** dialog object, cached */ + private JDialog _dialog = null; + /** radio button for distance axis */ + private JRadioButton _distanceRadio = null; + /** radio button for time axis */ + private JRadioButton _timeRadio = null; + /** array of checkboxes for specifying y axes */ + private JCheckBox[] _yAxesBoxes = null; + /** radio button for svg output */ + private JRadioButton _svgRadio = null; + /** file chooser for saving svg file */ + private JFileChooser _fileChooser = null; + /** text field for svg width */ + private JTextField _svgWidthField = null; + /** text field for svg height */ + private JTextField _svgHeightField = null; + + /** Default dimensions of Svg file */ + private static final String DEFAULT_SVG_WIDTH = "800"; + private static final String DEFAULT_SVG_HEIGHT = "400"; + + + /** + * Constructor from superclass + * @param inApp app object + */ + public Charter(App inApp) + { + super(inApp); + } + + /** + * @return key for function name + */ + public String getNameKey() + { + return "function.charts"; + } + + /** + * Show the dialog + */ + public void begin() + { + // First check if gnuplot is available + if (!ExternalTools.isToolInstalled(ExternalTools.TOOL_GNUPLOT)) + { + _app.showErrorMessage(getNameKey(), "dialog.charts.gnuplotnotfound"); + return; + } + // Make dialog window + if (_dialog == null) + { + _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true); + _dialog.setLocationRelativeTo(_parentFrame); + _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + _dialog.getContentPane().add(makeDialogComponents()); + _dialog.pack(); + } + if (setupDialog(_app.getTrackInfo().getTrack())) { + _dialog.setVisible(true); + } + else { + _app.showErrorMessage(getNameKey(), "dialog.charts.needaltitudeortimes"); + } + } + + + /** + * Make the dialog components + * @return panel containing gui elements + */ + private JPanel makeDialogComponents() + { + JPanel dialogPanel = new JPanel(); + dialogPanel.setLayout(new BorderLayout()); + + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + // x axis choice + JPanel axisPanel = new JPanel(); + axisPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("dialog.charts.xaxis"))); + _distanceRadio = new JRadioButton(I18nManager.getText("fieldname.distance")); + _distanceRadio.setSelected(true); + _timeRadio = new JRadioButton(I18nManager.getText("fieldname.time")); + ButtonGroup axisGroup = new ButtonGroup(); + axisGroup.add(_distanceRadio); axisGroup.add(_timeRadio); + axisPanel.add(_distanceRadio); axisPanel.add(_timeRadio); + mainPanel.add(axisPanel); + + // y axis choices + JPanel yPanel = new JPanel(); + yPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("dialog.charts.yaxis"))); + _yAxesBoxes = new JCheckBox[4]; // dist altitude speed vertspeed (time not available on y axis) + _yAxesBoxes[0] = new JCheckBox(I18nManager.getText("fieldname.distance")); + _yAxesBoxes[1] = new JCheckBox(I18nManager.getText("fieldname.altitude")); + _yAxesBoxes[1].setSelected(true); + _yAxesBoxes[2] = new JCheckBox(I18nManager.getText("fieldname.speed")); + _yAxesBoxes[3] = new JCheckBox(I18nManager.getText("fieldname.verticalspeed")); + for (int i=0; i<4; i++) { + yPanel.add(_yAxesBoxes[i]); + } + mainPanel.add(yPanel); + + // Add validation to prevent choosing invalid (ie dist/dist) combinations + ActionListener xAxisListener = new ActionListener() { + public void actionPerformed(ActionEvent e) { + enableYbox(0, _timeRadio.isSelected()); + } + }; + _timeRadio.addActionListener(xAxisListener); + _distanceRadio.addActionListener(xAxisListener); + + // output buttons + JPanel outputPanel = new JPanel(); + outputPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("dialog.charts.output"))); + outputPanel.setLayout(new BorderLayout()); + JPanel radiosPanel = new JPanel(); + JRadioButton screenRadio = new JRadioButton(I18nManager.getText("dialog.charts.screen")); + screenRadio.setSelected(true); + _svgRadio = new JRadioButton(I18nManager.getText("dialog.charts.svg")); + ButtonGroup outputGroup = new ButtonGroup(); + outputGroup.add(screenRadio); outputGroup.add(_svgRadio); + radiosPanel.add(screenRadio); radiosPanel.add(_svgRadio); + outputPanel.add(radiosPanel, BorderLayout.NORTH); + // panel for svg width, height + JPanel sizePanel = new JPanel(); + sizePanel.setLayout(new GridLayout(2, 2, 10, 1)); + JLabel widthLabel = new JLabel(I18nManager.getText("dialog.charts.svgwidth")); + widthLabel.setHorizontalAlignment(SwingConstants.RIGHT); + sizePanel.add(widthLabel); + _svgWidthField = new JTextField(DEFAULT_SVG_WIDTH, 5); + sizePanel.add(_svgWidthField); + JLabel heightLabel = new JLabel(I18nManager.getText("dialog.charts.svgheight")); + heightLabel.setHorizontalAlignment(SwingConstants.RIGHT); + sizePanel.add(heightLabel); + _svgHeightField = new JTextField(DEFAULT_SVG_HEIGHT, 5); + sizePanel.add(_svgHeightField); + + outputPanel.add(sizePanel, BorderLayout.EAST); + mainPanel.add(outputPanel); + dialogPanel.add(mainPanel, BorderLayout.CENTER); + + // button panel on bottom + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); + // ok button + JButton okButton = new JButton(I18nManager.getText("button.ok")); + okButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + showChart(_app.getTrackInfo().getTrack()); + _dialog.setVisible(false); + } + }); + buttonPanel.add(okButton); + // Cancel button + JButton cancelButton = new JButton(I18nManager.getText("button.cancel")); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + _dialog.setVisible(false); + } + }); + buttonPanel.add(cancelButton); + dialogPanel.add(buttonPanel, BorderLayout.SOUTH); + return dialogPanel; + } + + + /** + * Set up the dialog according to the track contents + * @param inTrack track object + * @return true if it's all ok + */ + private boolean setupDialog(Track inTrack) + { + boolean hasTimes = inTrack.hasData(Field.TIMESTAMP); + boolean hasAltitudes = inTrack.hasAltitudeData(); + _timeRadio.setEnabled(hasTimes); + + // Add checks to prevent choosing unavailable combinations + if (!hasTimes) { + _distanceRadio.setSelected(true); + } + enableYbox(0, !_distanceRadio.isSelected()); + enableYbox(1, hasAltitudes); + enableYbox(2, hasTimes); + enableYbox(3, hasTimes && hasAltitudes); + return (hasTimes || hasAltitudes); + } + + + /** + * Enable or disable the given y axis checkbox + * @param inIndex index of checkbox + * @param inFlag true to enable + */ + private void enableYbox(int inIndex, boolean inFlag) + { + _yAxesBoxes[inIndex].setEnabled(inFlag); + if (!inFlag) { + _yAxesBoxes[inIndex].setSelected(inFlag); + } + } + + /** + * Show the chart for the specified track + * @param inTrack track object containing data + */ + private void showChart(Track inTrack) + { + int numCharts = 0; + for (int i=0; i<_yAxesBoxes.length; i++) { + if (_yAxesBoxes[i].isSelected()) { + numCharts++; + } + } + // Select default chart if none selected + if (numCharts == 0) { + _yAxesBoxes[1].setSelected(true); + numCharts = 1; + } + int[] heights = getHeights(numCharts); + + boolean showSvg = _svgRadio.isSelected(); + File svgFile = null; + if (showSvg) { + svgFile = selectSvgFile(); + if (svgFile == null) {showSvg = false;} + } + OutputStreamWriter writer = null; + try + { + final String gnuplotPath = Config.getConfigString(Config.KEY_GNUPLOT_PATH); + Process process = Runtime.getRuntime().exec(gnuplotPath + " -persist"); + writer = new OutputStreamWriter(process.getOutputStream()); + if (showSvg) + { + writer.write("set terminal svg size " + getSvgValue(_svgWidthField, DEFAULT_SVG_WIDTH) + " " + + getSvgValue(_svgHeightField, DEFAULT_SVG_HEIGHT) + "\n"); + writer.write("set out '" + svgFile.getAbsolutePath() + "'\n"); + } + else { + // For screen output, gnuplot should use the default terminal (windows or x11 or wxt or something) + } + if (numCharts > 1) { + writer.write("set multiplot layout " + numCharts + ",1\n"); + } + // Loop over possible charts + int chartNum = 0; + for (int c=0; c<_yAxesBoxes.length; c++) + { + if (_yAxesBoxes[c].isSelected()) + { + writer.write("set size 1," + (0.01*heights[chartNum*2+1]) + "\n"); + writer.write("set origin 0," + (0.01*heights[chartNum*2]) + "\n"); + writeChart(writer, inTrack, _distanceRadio.isSelected(), c); + chartNum++; + } + } + // Close multiplot if open + if (numCharts > 1) { + writer.write("unset multiplot\n"); + } + } + catch (Exception e) { + _app.showErrorMessageNoLookup(getNameKey(), e.getMessage()); + } + finally { + try { + // Close writer + if (writer != null) writer.close(); + } + catch (Exception e) {} // ignore + } + } + + + /** + * Parse the given text field's value and return as string + * @param inField text field to read from + * @param inDefault default value if not valid + * @return value of svg dimension as string + */ + private static String getSvgValue(JTextField inField, String inDefault) + { + int value = 0; + try { + value = Integer.parseInt(inField.getText()); + } + catch (Exception e) {} // ignore, value stays zero + if (value > 0) { + return "" + value; + } + return inDefault; + } + + + /** + * Write out the selected chart to the given Writer object + * @param inWriter writer object + * @param inTrack Track containing data + * @param inDistance true if x axis is distance + * @param inYaxis index of y axis + * @throws IOException if writing error occurred + */ + private static void writeChart(OutputStreamWriter inWriter, Track inTrack, boolean inDistance, int inYaxis) + throws IOException + { + ChartSeries xValues = null, yValues = null; + ChartSeries distValues = getDistanceValues(inTrack); + // Choose x values according to axis + if (inDistance) { + xValues = distValues; + } + else { + xValues = getTimeValues(inTrack); + } + // Choose y values according to axis + switch (inYaxis) + { + case 0: // y axis is distance + yValues = distValues; + break; + case 1: // y axis is altitude + yValues = getAltitudeValues(inTrack); + break; + case 2: // y axis is speed + yValues = getSpeedValues(inTrack); + break; + case 3: // y axis is vertical speed + yValues = getVertSpeedValues(inTrack); + break; + } + // Make a temporary data file for the output (one per subchart) + File tempFile = File.createTempFile("gpsprunedata", null); + tempFile.deleteOnExit(); + // write out values for x and y to temporary file + FileWriter tempFileWriter = null; + try { + tempFileWriter = new FileWriter(tempFile); + tempFileWriter.write("# Temporary data file for GpsPrune charts\n\n"); + for (int i=0; i