/*
* RomRaider Open-Source Tuning, Logging and Reflashing
* Copyright (C) 2006-2012 RomRaider.com
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.romraider.logger.ecu.ui.handler.graph;
import com.romraider.logger.ecu.comms.query.Response;
import com.romraider.logger.ecu.definition.ConvertorUpdateListener;
import com.romraider.logger.ecu.definition.LoggerData;
import com.romraider.logger.ecu.ui.handler.DataUpdateHandler;
import static com.romraider.logger.ecu.ui.handler.graph.SpringUtilities.makeCompactGrid;
import static java.awt.BorderLayout.CENTER;
import static java.awt.BorderLayout.NORTH;
import static java.awt.Color.BLACK;
import static java.awt.Color.WHITE;
import static java.util.Collections.synchronizedMap;
import static javax.swing.JComponent.WHEN_IN_FOCUSED_WINDOW;
import static javax.swing.KeyStroke.getKeyStroke;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import static org.jfree.chart.plot.PlotOrientation.VERTICAL;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import javax.swing.AbstractAction;
import javax.swing.JCheckBox;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.SpringLayout;
import javax.swing.SwingUtilities;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.Map;
public final class GraphUpdateHandler implements DataUpdateHandler, ConvertorUpdateListener {
private static final Color DARK_GREY = new Color(80, 80, 80);
private static final Color LIGHT_GREY = new Color(110, 110, 110);
private final Map<LoggerData, ChartPanel> chartMap = synchronizedMap(new HashMap<LoggerData, ChartPanel>());
private final Map<LoggerData, XYSeries> seriesMap = synchronizedMap(new HashMap<LoggerData, XYSeries>());
private final Map<LoggerData, Integer> datasetIndexes = synchronizedMap(new HashMap<LoggerData, Integer>());
private final JPanel graphPanel;
private long startTime = System.currentTimeMillis();
private boolean combinedChart = false;
private boolean paused = false;
private long pauseStartTime = System.currentTimeMillis();
private ChartPanel combinedChartPanel = null;
private int counter = 0;
public GraphUpdateHandler(final JPanel panel) {
this.graphPanel = new JPanel(new SpringLayout());
final JCheckBox combinedCheckbox = new JCheckBox("Combine Graphs", combinedChart);
combinedCheckbox.addActionListener(new CombinedActionListener(combinedCheckbox));
JToggleButton playPauseButton = new JToggleButton("Pause Graphs");
playPauseButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
paused = !paused;
if (paused) {
pauseStartTime = System.currentTimeMillis();
} else {
startTime = startTime + (System.currentTimeMillis() - pauseStartTime);
}
}
});
panel.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(getKeyStroke("F12"), "toggleCombineGraphs");
panel.getActionMap().put("toggleCombineGraphs", new AbstractAction() {
private static final long serialVersionUID = 1540427179539775534L;
public void actionPerformed(ActionEvent e) {
combinedCheckbox.doClick();
}
});
JPanel controlPanel = new JPanel();
controlPanel.add(combinedCheckbox);
controlPanel.add(playPauseButton);
panel.add(controlPanel, NORTH);
panel.add(this.graphPanel, CENTER);
}
public synchronized void registerData(LoggerData loggerData) {
// add to charts
registerSeries(loggerData);
if (combinedChart) {
addToCombined(loggerData);
layoutForCombined();
} else {
addToPanel(loggerData);
layoutForPanel();
}
graphPanel.updateUI();
}
private synchronized void addToPanel(LoggerData loggerData) {
XYSeries series = seriesMap.get(loggerData);
ChartPanel chartPanel = new ChartPanel(createXYLineChart(loggerData, new XYSeriesCollection(series), false), false, true, true, true, true);
chartPanel.setMinimumSize(new Dimension(600, 200));
chartPanel.setMaximumSize(new Dimension(10000, 200));
chartPanel.setPreferredSize(new Dimension(600, 200));
chartMap.put(loggerData, chartPanel);
graphPanel.add(chartPanel);
}
private void layoutForPanel() {
makeCompactGrid(graphPanel, seriesMap.size(), 1, 2, 2, 2, 2);
}
private synchronized void addToCombined(LoggerData loggerData) {
if (combinedChartPanel == null) {
combinedChartPanel = new ChartPanel(createXYLineChart(loggerData, null, true), false, true, true, true, true);
LegendTitle legendTitle = new LegendTitle(combinedChartPanel.getChart().getXYPlot());
legendTitle.setItemPaint(WHITE);
combinedChartPanel.getChart().addLegend(legendTitle);
combinedChartPanel.setMinimumSize(new Dimension(500, 400));
combinedChartPanel.setPreferredSize(new Dimension(500, 400));
graphPanel.add(combinedChartPanel);
}
XYPlot plot = combinedChartPanel.getChart().getXYPlot();
plot.setDataset(counter, new XYSeriesCollection(seriesMap.get(loggerData)));
plot.setRenderer(counter, new StandardXYItemRenderer());
datasetIndexes.put(loggerData, counter++);
}
private void layoutForCombined() {
makeCompactGrid(graphPanel, 1, 1, 2, 2, 2, 2);
}
public synchronized void handleDataUpdate(final Response response) {
for (final LoggerData loggerData : response.getData()) {
final XYSeries series = seriesMap.get(loggerData);
if (series != null && !paused) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
series.add((response.getTimestamp() - startTime) / 1000.0, response.getDataValue(loggerData));
}
});
}
}
}
public synchronized void deregisterData(LoggerData loggerData) {
seriesMap.remove(loggerData);
if (combinedChart) {
removeFromCombined(loggerData);
} else {
removeFromPanel(loggerData);
layoutForPanel();
}
graphPanel.updateUI();
}
private void removeFromCombined(LoggerData loggerData) {
// remove from charts
if (datasetIndexes.containsKey(loggerData)) {
combinedChartPanel.getChart().getXYPlot().setDataset(datasetIndexes.get(loggerData), null);
}
datasetIndexes.remove(loggerData);
chartMap.remove(loggerData);
if (datasetIndexes.isEmpty()) {
graphPanel.remove(combinedChartPanel);
combinedChartPanel = null;
}
}
private void removeFromPanel(LoggerData loggerData) {
// remove from charts
graphPanel.remove(chartMap.get(loggerData));
datasetIndexes.remove(loggerData);
chartMap.remove(loggerData);
}
public synchronized void cleanUp() {
}
public synchronized void reset() {
for (XYSeries series : seriesMap.values()) {
series.clear();
}
}
public synchronized void notifyConvertorUpdate(LoggerData updatedLoggerData) {
if (chartMap.containsKey(updatedLoggerData)) {
seriesMap.get(updatedLoggerData).clear();
JFreeChart chart = chartMap.get(updatedLoggerData).getChart();
chart.getXYPlot().getRangeAxis().setLabel(buildRangeAxisTitle(updatedLoggerData));
}
}
private void registerSeries(LoggerData loggerData) {
final XYSeries series = new XYSeries(loggerData.getName());
series.setMaximumItemCount(200);
seriesMap.put(loggerData, series);
}
private JFreeChart createXYLineChart(LoggerData loggerData, XYDataset dataset, boolean combined) {
String title = combined ? "Combined Data" : loggerData.getName();
String rangeAxisTitle = combined ? "Data" : buildRangeAxisTitle(loggerData);
JFreeChart chart = ChartFactory.createXYLineChart(title, "Time (sec)", rangeAxisTitle, dataset, VERTICAL, false, true, false);
chart.setBackgroundPaint(BLACK);
chart.getTitle().setPaint(WHITE);
XYPlot plot = chart.getXYPlot();
plot.setBackgroundPaint(BLACK);
plot.getDomainAxis().setLabelPaint(WHITE);
plot.getRangeAxis().setLabelPaint(WHITE);
plot.getDomainAxis().setTickLabelPaint(LIGHT_GREY);
plot.getRangeAxis().setTickLabelPaint(LIGHT_GREY);
plot.setDomainGridlinePaint(DARK_GREY);
plot.setRangeGridlinePaint(DARK_GREY);
plot.setOutlinePaint(DARK_GREY);
return chart;
}
private String buildRangeAxisTitle(LoggerData loggerData) {
return loggerData.getName() + " (" + loggerData.getSelectedConvertor().getUnits() + ")";
}
private final class CombinedActionListener implements ActionListener {
private final JCheckBox combinedCheckbox;
private CombinedActionListener(JCheckBox combinedCheckbox) {
this.combinedCheckbox = combinedCheckbox;
}
public void actionPerformed(ActionEvent actionEvent) {
combinedChart = combinedCheckbox.isSelected();
if (combinedChart) {
removeAllFromPanel();
addAllToCombined();
layoutForCombined();
} else {
removeAllFromCombined();
addAllToPanel();
layoutForPanel();
}
graphPanel.updateUI();
}
private void addAllToCombined() {
for (LoggerData loggerData : seriesMap.keySet()) {
addToCombined(loggerData);
}
}
private void removeAllFromPanel() {
for (LoggerData loggerData : seriesMap.keySet()) {
removeFromPanel(loggerData);
}
}
private void addAllToPanel() {
for (LoggerData loggerData : seriesMap.keySet()) {
addToPanel(loggerData);
}
}
private void removeAllFromCombined() {
for (LoggerData loggerData : seriesMap.keySet()) {
removeFromCombined(loggerData);
}
}
}
}