/*
This file is part of RouteConverter.
RouteConverter is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
RouteConverter is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with RouteConverter; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Copyright (C) 2007 Christian Pesch. All Rights Reserved.
*/
package slash.navigation.converter.gui.profileview;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartMouseEvent;
import org.jfree.chart.ChartMouseListener;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.XYItemEntity;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.ValueMarker;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeriesCollection;
import slash.navigation.common.UnitSystem;
import slash.navigation.converter.gui.models.*;
import slash.navigation.gui.Application;
import slash.navigation.gui.actions.ActionManager;
import slash.navigation.gui.actions.FrameAction;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.util.ResourceBundle;
import java.util.prefs.Preferences;
import static java.text.MessageFormat.format;
import static java.text.NumberFormat.getIntegerInstance;
import static org.jfree.chart.axis.NumberAxis.createIntegerTickUnits;
import static org.jfree.chart.plot.PlotOrientation.VERTICAL;
import static org.jfree.ui.Layer.FOREGROUND;
import static slash.navigation.converter.gui.profileview.XAxisMode.Distance;
import static slash.navigation.converter.gui.profileview.YAxisMode.Elevation;
/**
* Displays the elevations of a {@link PositionsModel}.
*
* @author Christian Pesch
*/
public class ProfileView implements PositionsSelectionModel {
protected static final Preferences preferences = Preferences.userNodeForPackage(ProfileView.class);
private static final String X_GRID_PREFERENCE = "xGrid";
private static final String Y_GRID_PREFERENCE = "yGrid";
private LazyToolTipChartPanel chartPanel;
private XYPlot plot;
private PositionsModel positionsModel;
private ProfileModel profileModel;
public void initialize(PositionsModel positionsModel, final PositionsSelectionModel positionsSelectionModel,
final UnitSystemModel unitSystemModel, final ProfileModeModel profileModeModel) {
this.positionsModel = positionsModel;
PatchedXYSeries series = new PatchedXYSeries("Profile");
this.profileModel = new ProfileModel(positionsModel, series, unitSystemModel.getUnitSystem(),
profileModeModel.getXAxisMode(), profileModeModel.getYAxisMode());
XYSeriesCollection dataset = new XYSeriesCollection(series);
unitSystemModel.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
setUnitSystem(unitSystemModel.getUnitSystem());
}
});
profileModeModel.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
setProfileMode(profileModeModel.getXAxisMode(), profileModeModel.getYAxisMode());
}
});
JFreeChart chart = createChart(dataset);
plot = createPlot(chart);
ActionManager actionManager = Application.getInstance().getContext().getActionManager();
for (XAxisMode mode : XAxisMode.values())
actionManager.register("show-" + mode.name().toLowerCase(), new ToggleXAxisProfileModeAction(profileModeModel, mode));
for (YAxisMode mode : YAxisMode.values())
actionManager.register("show-" + mode.name().toLowerCase(), new ToggleYAxisProfileModeAction(profileModeModel, mode));
// since JFreeChart is not very nice to extensions - constructors calling protected methods... ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD
LazyToolTipChartPanel.profileModeModel = profileModeModel;
chartPanel = new LazyToolTipChartPanel(chart, false, true, true, true, true);
chartPanel.addChartMouseListener(new ChartMouseListener() {
public void chartMouseClicked(ChartMouseEvent e) {
ChartEntity entity = e.getEntity();
if (!(entity instanceof XYItemEntity))
return;
int row = ((XYItemEntity) entity).getItem();
positionsSelectionModel.setSelectedPositions(new int[]{row}, true);
}
public void chartMouseMoved(ChartMouseEvent e) {
}
});
chartPanel.setMouseWheelEnabled(true);
updateAxis();
}
private static ResourceBundle getBundle() {
return Application.getInstance().getContext().getBundle();
}
private JFreeChart createChart(XYDataset dataset) {
JFreeChart chart = ChartFactory.createXYAreaChart(null, null, null, dataset, VERTICAL, false, true, false);
chart.setBackgroundPaint(new JPanel().getBackground());
return chart;
}
private XYPlot createPlot(JFreeChart chart) {
XYPlot plot = chart.getXYPlot();
plot.setForegroundAlpha(0.65F);
plot.setDomainGridlinesVisible(preferences.getBoolean(X_GRID_PREFERENCE, true));
plot.setRangeGridlinesVisible(preferences.getBoolean(Y_GRID_PREFERENCE, true));
NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
rangeAxis.setAutoRangeIncludesZero(false);
rangeAxis.setStandardTickUnits(createIntegerTickUnits());
Font font = new JLabel().getFont();
rangeAxis.setLabelFont(font);
NumberAxis domainAxis = (NumberAxis) plot.getDomainAxis();
domainAxis.setStandardTickUnits(createIntegerTickUnits());
domainAxis.setLowerMargin(0.0);
domainAxis.setUpperMargin(0.0);
domainAxis.setLabelFont(font);
plot.getRenderer().setBaseToolTipGenerator(null);
return plot;
}
public Component getComponent() {
return chartPanel;
}
private void setUnitSystem(UnitSystem unitSystem) {
profileModel.setUnitSystem(unitSystem);
updateAxis();
}
private void setProfileMode(XAxisMode xAxisMode, YAxisMode yAxisMode) {
profileModel.setProfileMode(xAxisMode, yAxisMode);
updateAxis();
}
private void updateAxis() {
UnitSystem unitSystem = profileModel.getUnitSystem();
YAxisMode yAxisMode = profileModel.getYAxisMode();
String xAxisUnit = yAxisMode.equals(Elevation) ? unitSystem.getElevationName() : unitSystem.getSpeedName();
String xAxisKey = yAxisMode.equals(Elevation) ? "elevation-axis" : "speed-axis";
plot.getRangeAxis().setLabel(format(getBundle().getString(xAxisKey), xAxisUnit));
XAxisMode xAxisMode = profileModel.getXAxisMode();
String yAxisUnit = xAxisMode.equals(Distance) ? unitSystem.getDistanceName() : "s";
String yAxisKey = xAxisMode.equals(Distance) ? "distance-axis" : "time-axis";
plot.getDomainAxis().setLabel(format(getBundle().getString(yAxisKey), yAxisUnit));
chartPanel.setToolTipGenerator(new StandardXYToolTipGenerator(
"{2} " + xAxisUnit + " @ {1} " + yAxisUnit, getIntegerInstance(), getIntegerInstance()) {
public String generateLabelString(XYDataset dataset, int series, int item) {
return super.generateLabelString(dataset, series, item).replaceAll("null", "?");
}
});
}
public void setSelectedPositions(int[] selectPositions, boolean replaceSelection) {
if (replaceSelection)
plot.clearDomainMarkers();
if (profileModel.getXAxisMode().equals(Distance)) {
double[] distances = positionsModel.getRoute().getDistancesFromStart(selectPositions);
for (double distance : distances) {
plot.addDomainMarker(0, new ValueMarker(profileModel.formatDistance(distance)), FOREGROUND, false);
}
} else {
long[] times = positionsModel.getRoute().getTimesFromStart(selectPositions);
for (long time : times) {
plot.addDomainMarker(0, new ValueMarker(profileModel.formatTime(time)), FOREGROUND, false);
}
}
// make sure the protected fireChangeEvent() is called without any side effects
plot.setWeight(plot.getWeight());
}
public void print() {
chartPanel.createChartPrintJob();
}
private static class ToggleXAxisProfileModeAction extends FrameAction {
private final ProfileModeModel profileModeModel;
private final XAxisMode xAxisMode;
public ToggleXAxisProfileModeAction(ProfileModeModel profileModeModel, XAxisMode xAxisMode) {
this.profileModeModel = profileModeModel;
this.xAxisMode = xAxisMode;
}
public void run() {
profileModeModel.setXAxisMode(xAxisMode);
}
}
private static class ToggleYAxisProfileModeAction extends FrameAction {
private final ProfileModeModel profileModeModel;
private final YAxisMode yAxisMode;
public ToggleYAxisProfileModeAction(ProfileModeModel profileModeModel, YAxisMode yAxisMode) {
this.profileModeModel = profileModeModel;
this.yAxisMode = yAxisMode;
}
public void run() {
profileModeModel.setYAxisMode(yAxisMode);
}
}
}