/*
* RomRaider Open-Source Tuning, Logging and Reflashing
* Copyright (C) 2006-2016 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.tab.dyno;
import static com.romraider.Settings.COMMA;
import com.romraider.logger.ecu.ui.handler.graph.SpringUtilities;
import com.romraider.logger.ecu.ui.tab.CircleDrawer;
import com.romraider.logger.ecu.ui.tab.XYTrendline;
import static com.romraider.Settings.SEMICOLON;
import static com.romraider.util.ParamChecker.checkNotNull;
import jamlab.Polyfit;
import static java.awt.Color.BLACK;
import static java.awt.Color.BLUE;
import static java.awt.Color.GREEN;
import static java.awt.Color.RED;
import static java.awt.Color.WHITE;
import static java.awt.Color.YELLOW;
import static org.jfree.chart.ChartFactory.createScatterPlot;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.annotations.XYAnnotation;
import org.jfree.chart.annotations.XYDrawableAnnotation;
import org.jfree.chart.annotations.XYPointerAnnotation;
import org.jfree.chart.annotations.XYTextAnnotation;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.NumberAxis;
import static org.jfree.chart.plot.PlotOrientation.VERTICAL;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.TextAnchor;
import javax.swing.JPanel;
import javax.swing.SpringLayout;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
public final class DynoChartPanel extends JPanel {
private static final long serialVersionUID = -6577979878171615665L;
private static final Color DARK_GREY = new Color(80, 80, 80);
private static final Color LIGHT_GREY = new Color(110, 110, 110);
private static final String START_PROMPT = "Accelerate using WOT when ready!!";
private static final String ET_PROMPT_I = "Accelerate for 1/4 mile when ready!!";
private static final String ET_PROMPT_M = "Accelerate for 402 meters when ready!!";
private final XYSeries data = new XYSeries("Raw HP"); // series for HorsePower/RPM
private final XYSeries data1 = new XYSeries("Raw TQ"); // series for Torque/RPM
private final XYSeries logRpm = new XYSeries("Logger RPM"); // series for raw sample time/RPM
private final XYTrendline rpmTrend = new XYTrendline(logRpm);
private final XYSeries hpRef = new XYSeries("HP Ref"); // series for reference HP/RPM
private final XYSeries tqRef = new XYSeries("TQ Ref"); // series for reference TQ/RPM
private final String labelX;
private String labelY1 = null;
private String labelY2 = null;
private StandardXYItemRenderer rendererY1 = new StandardXYItemRenderer();
private StandardXYItemRenderer rendererY2 = new StandardXYItemRenderer();
private NumberAxis hpAxis = new NumberAxis("pwr");
private NumberAxis tqAxis = new NumberAxis("tq");
private XYPlot plot;
private final CircleDrawer cd = new CircleDrawer(RED, new BasicStroke(1.0f), null);
private final CircleDrawer cdGreen = new CircleDrawer(GREEN, new BasicStroke(1.0f), null);
private XYAnnotation bestHp;
private XYAnnotation bestTq;
private final XYPointerAnnotation hpPointer = new XYPointerAnnotation(
"Max HP", 1, 1, 3.0 * Math.PI / 6.0);
private final XYPointerAnnotation tqPointer = new XYPointerAnnotation(
"Max TQ", 1, 1, 3.0 * Math.PI / 6.0);
private final XYTextAnnotation refStat = new XYTextAnnotation(" ", 0, 0);
public DynoChartPanel(String labelX, String labelY1, String labelY2) {
super(new SpringLayout());
checkNotNull(labelX, labelY1, labelY2);
this.labelX = labelX;
this.labelY1 = labelY1;
this.labelY2 = labelY2;
addChart();
}
public void quietUpdate(boolean notify) {
data.setNotify(notify);
data1.setNotify(notify);
}
public synchronized void addRawData(double x, double y) {
logRpm.add(x, y);
}
public synchronized void addData(double x, double y) {
data.add(x, y);
}
public synchronized void addData(double x, double y1, double y2) {
data.add(x, y1);
data1.add(x, y2);
}
public synchronized void setRefTrace(double x, double y1, double y2) {
hpRef.add(x, y1);
tqRef.add(x, y2);
}
public void updateRefTrace(String[] line) {
if (hpRef.getItemCount() > 0) {
refStat.setText("Reference: " + line[2] + " (" +
String.format("%1.2f", Double.parseDouble(line[3])) + "; " +
String.format("%1.2f", Double.parseDouble(line[4])) + "; " +
String.format("%1.2f", Double.parseDouble(line[5])) + "; " +
String.format("%1.2f", Double.parseDouble(line[6])) + ")");
refStat.setX(plot.getDomainAxis().getLowerBound() + 10);
refStat.setY(hpAxis.getUpperBound());
plot.addAnnotation(refStat);
}
}
public int getPwrTqCount() {
return (int) data.getItemCount();
}
public String getPwrTq(int x) {
String dataSet = data.getX(x) + COMMA + data.getY(x) + COMMA + data1.getY(x);
return dataSet;
}
public void clearRefTrace() {
refStat.setText(" ");
plot.removeAnnotation(refStat);
hpRef.clear();
tqRef.clear();
}
public void clear() {
logRpm.clear();
rpmTrend.clear();
clearGraph();
}
public long getTimeSample(int index) {
return logRpm.getX(index).longValue();
}
public int getSampleCount() {
clearGraph();
return logRpm.getItemCount();
}
public void clearGraph() {
data.clear();
data1.clear();
rendererY1.removeAnnotation(bestHp);
rendererY2.removeAnnotation(bestTq);
rendererY1.removeAnnotation(hpPointer);
rendererY2.removeAnnotation(tqPointer);
hpAxis.setAutoRange(true);
tqAxis.setAutoRange(true);
plot.clearAnnotations();
}
public double[] getRpmCoeff(int order) {
rpmTrend.update(order);
return getPolynomialCoefficients(rpmTrend);
}
public void interpolate(double[] results, String[] resultStrings) {
hpAxis.setAutoRange(true);
tqAxis.setAutoRange(true);
double rangeMin = Math.min(tqAxis.getLowerBound(), hpAxis.getLowerBound());
double yMin = Math.round(rangeMin);
double ySpace = (hpAxis.getUpperBound() - hpAxis.getLowerBound()) / 25;
double xMin = ((plot.getDomainAxis().getUpperBound() - plot.getDomainAxis().getLowerBound()) / 7) + plot.getDomainAxis().getLowerBound();
hpAxis.setRange(Math.round(rangeMin), Math.round(hpAxis.getUpperBound() + ySpace));
tqAxis.setRange(Math.round(rangeMin), Math.round(tqAxis.getUpperBound() + ySpace));
bestHp = new XYDrawableAnnotation(results[1], results[0], 10, 10, cd);
hpPointer.setX(results[1]);
hpPointer.setY(results[0]);
hpPointer.setArrowPaint(BLUE);
hpPointer.setTipRadius(7.0);
hpPointer.setBaseRadius(30.0);
hpPointer.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
hpPointer.setPaint(BLUE);
bestTq = new XYDrawableAnnotation(results[3], results[2], 10, 10, cd);
tqPointer.setX(results[3]);
tqPointer.setY(results[2]);
tqPointer.setArrowPaint(YELLOW);
tqPointer.setTipRadius(7.0);
tqPointer.setBaseRadius(30.0);
tqPointer.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
tqPointer.setPaint(YELLOW);
final XYTextAnnotation dynoResults = new XYTextAnnotation(resultStrings[1], xMin, yMin + (ySpace * 5));
dynoResults.setPaint(RED);
dynoResults.setTextAnchor(TextAnchor.BOTTOM_LEFT);
dynoResults.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 14));
final XYTextAnnotation carText = new XYTextAnnotation(resultStrings[0], xMin, yMin + (ySpace * 4));
carText.setPaint(RED);
carText.setTextAnchor(TextAnchor.BOTTOM_LEFT);
carText.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
final XYTextAnnotation stat1 = new XYTextAnnotation(resultStrings[2], xMin, yMin + (ySpace * 3));
stat1.setPaint(RED);
stat1.setTextAnchor(TextAnchor.BOTTOM_LEFT);
stat1.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
final XYTextAnnotation stat2 = new XYTextAnnotation(resultStrings[3], xMin, yMin + ySpace * 2);
stat2.setPaint(RED);
stat2.setTextAnchor(TextAnchor.BOTTOM_LEFT);
stat2.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
final XYTextAnnotation stat3 = new XYTextAnnotation(resultStrings[4], xMin, yMin + ySpace);
stat3.setPaint(RED);
stat3.setTextAnchor(TextAnchor.BOTTOM_LEFT);
stat3.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
final XYTextAnnotation stat4 = new XYTextAnnotation(resultStrings[5], xMin, yMin);
stat4.setPaint(RED);
stat4.setTextAnchor(TextAnchor.BOTTOM_LEFT);
stat4.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
if (!refStat.equals(" ")) {
refStat.setX(plot.getDomainAxis().getLowerBound() + 10);
refStat.setY(hpAxis.getUpperBound());
plot.addAnnotation(refStat);
}
rendererY1.addAnnotation(bestHp);
rendererY2.addAnnotation(bestTq);
rendererY1.addAnnotation(hpPointer);
rendererY2.addAnnotation(tqPointer);
plot.addAnnotation(dynoResults);
plot.addAnnotation(carText);
plot.addAnnotation(stat1);
plot.addAnnotation(stat2);
plot.addAnnotation(stat3);
plot.addAnnotation(stat4);
}
public void updateEtResults(String carInfo, double[] etResults, String units) {
String s60Text = "60 ft";
String s330Text = "330 ft";
String s660Text = "1/2 track";
String s1000Text = "1,000 ft";
String s1320Text = "1/4 mile";
String zTo60Text = "60 mph";
if (units.equalsIgnoreCase("km/h")) {
s60Text = "18.3m";
s330Text = "100m";
s1000Text = "305m";
s1320Text = "402m";
zTo60Text = "97 km/h";
}
hpAxis.setLabel("Vehicle Speed (" + units + ")");
String[] car = carInfo.split(SEMICOLON);
car[0] = "LANE 1: " + car[0].substring(0, car[0].length() - 3) + " - ET: " + String.format("%1.3f", etResults[8]) + "\" / " + String.format("%1.2f", etResults[9]) + " " + units;
double ySpace = hpAxis.getUpperBound() / 25;
double xMin = ((plot.getDomainAxis().getUpperBound() - plot.getDomainAxis().getLowerBound()) / 7) + plot.getDomainAxis().getLowerBound();
tqAxis.setRange(hpAxis.getLowerBound(), hpAxis.getUpperBound());
final XYAnnotation s60Marker = new XYDrawableAnnotation(etResults[0], etResults[1], 10, 10, cd);
final XYTextAnnotation s60Label = new XYTextAnnotation(s60Text, etResults[0], (etResults[1] + ySpace));
s60Label.setPaint(RED);
s60Label.setTextAnchor(TextAnchor.TOP_RIGHT);
s60Label.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
final XYTextAnnotation s60Time = new XYTextAnnotation(String.format("%1.3f", etResults[0]) + "\" / " + String.format("%1.2f", etResults[1]), etResults[0], (etResults[1] - ySpace));
s60Time.setPaint(RED);
s60Time.setTextAnchor(TextAnchor.BOTTOM_LEFT);
s60Time.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
final XYAnnotation s330Marker = new XYDrawableAnnotation(etResults[2], etResults[3], 10, 10, cd);
final XYTextAnnotation s330Label = new XYTextAnnotation(s330Text, etResults[2], (etResults[3] + ySpace));
s330Label.setPaint(RED);
s330Label.setTextAnchor(TextAnchor.TOP_RIGHT);
s330Label.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
final XYTextAnnotation s330Time = new XYTextAnnotation(String.format("%1.3f", etResults[2]) + "\" / " + String.format("%1.2f", etResults[3]), etResults[2], (etResults[3] - ySpace));
s330Time.setPaint(RED);
s330Time.setTextAnchor(TextAnchor.BOTTOM_LEFT);
s330Time.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
final XYAnnotation s660Marker = new XYDrawableAnnotation(etResults[4], etResults[5], 10, 10, cd);
final XYTextAnnotation s660Label = new XYTextAnnotation(s660Text, etResults[4], (etResults[5] + ySpace));
s660Label.setPaint(RED);
s660Label.setTextAnchor(TextAnchor.TOP_RIGHT);
s660Label.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
final XYTextAnnotation s660Time = new XYTextAnnotation(String.format("%1.3f", etResults[4]) + "\" / " + String.format("%1.2f", etResults[5]), etResults[4], (etResults[5] - ySpace));
s660Time.setPaint(RED);
s660Time.setTextAnchor(TextAnchor.BOTTOM_LEFT);
s660Time.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
final XYAnnotation s1000Marker = new XYDrawableAnnotation(etResults[6], etResults[7], 10, 10, cd);
final XYTextAnnotation s1000Label = new XYTextAnnotation(s1000Text, etResults[6], (etResults[7] + ySpace));
s1000Label.setPaint(RED);
s1000Label.setTextAnchor(TextAnchor.TOP_RIGHT);
s1000Label.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
final XYTextAnnotation s1000Time = new XYTextAnnotation(String.format("%1.3f", etResults[6]) + "\" / " + String.format("%1.2f", etResults[7]), etResults[6], (etResults[7] - ySpace));
s1000Time.setPaint(RED);
s1000Time.setTextAnchor(TextAnchor.BOTTOM_LEFT);
s1000Time.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
final XYAnnotation s1320Marker = new XYDrawableAnnotation(etResults[8], etResults[9], 10, 10, cd);
final XYTextAnnotation s1320Label = new XYTextAnnotation(s1320Text, etResults[8], (etResults[9] - ySpace));
s1320Label.setPaint(RED);
s1320Label.setTextAnchor(TextAnchor.BOTTOM_CENTER);
s1320Label.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
final XYTextAnnotation s1320Time = new XYTextAnnotation(String.format("%1.3f", etResults[8]) + "\" / " + String.format("%1.2f", etResults[9]), (etResults[8] - 0.2), etResults[9]);
s1320Time.setPaint(RED);
s1320Time.setTextAnchor(TextAnchor.CENTER_RIGHT);
s1320Time.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
final XYTextAnnotation carText = new XYTextAnnotation(car[0], (plot.getDomainAxis().getUpperBound() - 0.2), (hpAxis.getLowerBound() + ySpace));
carText.setPaint(RED);
carText.setTextAnchor(TextAnchor.BOTTOM_RIGHT);
carText.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
final XYAnnotation zTo60Marker = new XYDrawableAnnotation(etResults[10], etResults[11], 10, 10, cdGreen);
final XYTextAnnotation zTo60Label = new XYTextAnnotation(zTo60Text, etResults[10], (etResults[11] + ySpace));
zTo60Label.setPaint(GREEN);
zTo60Label.setTextAnchor(TextAnchor.TOP_RIGHT);
zTo60Label.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
final XYTextAnnotation zTo60Time = new XYTextAnnotation((String.format("%1.3f", etResults[10]) + "\""), etResults[10], (etResults[11] - ySpace));
zTo60Time.setPaint(GREEN);
zTo60Time.setTextAnchor(TextAnchor.BOTTOM_LEFT);
zTo60Time.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 10));
plot.addAnnotation(s60Marker);
plot.addAnnotation(s60Label);
plot.addAnnotation(s60Time);
plot.addAnnotation(s330Marker);
plot.addAnnotation(s330Label);
plot.addAnnotation(s330Time);
plot.addAnnotation(s660Marker);
plot.addAnnotation(s660Label);
plot.addAnnotation(s660Time);
plot.addAnnotation(s1000Marker);
plot.addAnnotation(s1000Label);
plot.addAnnotation(s1000Time);
plot.addAnnotation(s1320Marker);
plot.addAnnotation(s1320Label);
plot.addAnnotation(s1320Time);
plot.addAnnotation(carText);
plot.addAnnotation(zTo60Marker);
plot.addAnnotation(zTo60Label);
plot.addAnnotation(zTo60Time);
}
public double[] getPolynomialCoefficients(XYTrendline trendSeries) {
Polyfit fit = trendSeries.getPolyFit();
return fit.getPolynomialCoefficients();
}
private void addChart() {
ChartPanel chartPanel = new ChartPanel(createChart(), false, true, true, true, true);
chartPanel.setMinimumSize(new Dimension(400, 300));
chartPanel.setPreferredSize(new Dimension(500, 400));
add(chartPanel);
SpringUtilities.makeCompactGrid(this, 1, 1, 2, 2, 2, 2);
}
private JFreeChart createChart() {
JFreeChart chart = createScatterPlot(null, labelX, labelY1, null, VERTICAL, false, true, false);
chart.setBackgroundPaint(BLACK);
configurePlot(chart);
addSeries1(chart, 0, data, BLUE);
addSeries2(chart, 1, data1, YELLOW);
addRef(chart, 2, hpRef, BLUE);
addRef(chart, 3, tqRef, YELLOW);
return chart;
}
private void configurePlot(JFreeChart chart) {
plot = chart.getXYPlot();
plot.setOutlinePaint(DARK_GREY);
plot.setBackgroundPaint(BLACK);
// X axis settings
plot.setDomainAxisLocation(AxisLocation.BOTTOM_OR_RIGHT);
plot.getDomainAxis().setLabelPaint(WHITE);
plot.getDomainAxis().setTickLabelPaint(LIGHT_GREY);
plot.setDomainGridlinePaint(DARK_GREY);
// Y1 axis (left) settings
hpAxis.setLabel(labelY1);
hpAxis.setLabelPaint(BLUE);
hpAxis.setTickLabelPaint(LIGHT_GREY);
hpAxis.setAutoRangeIncludesZero(false);
hpAxis.setAutoRange(true);
plot.setRangeAxis(0, hpAxis);
plot.setRangeAxisLocation(0, AxisLocation.TOP_OR_LEFT);
plot.mapDatasetToRangeAxis(0, 0);
plot.mapDatasetToRangeAxis(2, 0);
// Y2 axis (right) settings
tqAxis.setLabel(labelY2);
tqAxis.setLabelPaint(YELLOW);
tqAxis.setTickLabelPaint(LIGHT_GREY);
tqAxis.setAutoRangeIncludesZero(false);
tqAxis.setAutoRange(true);
plot.setRangeAxis(1, tqAxis);
plot.setRangeAxisLocation(1, AxisLocation.BOTTOM_OR_RIGHT);
plot.mapDatasetToRangeAxis(1, 1);
plot.mapDatasetToRangeAxis(3, 1);
plot.setRangeGridlinePaint(DARK_GREY);
refStat.setPaint(WHITE);
refStat.setTextAnchor(TextAnchor.TOP_LEFT);
refStat.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
}
public void setET() {
clear();
plot.getDomainAxis().setLabel("Time (seconds)");
hpAxis.setLabel("Vehicle Speed");
tqAxis.setLabel(" ");
}
public void setDyno() {
clear();
plot.getDomainAxis().setLabel("Engine Speed (RPM)");
hpAxis.setLabel("Calculated Wheel Power");
tqAxis.setLabel("Calculated Engine Torque");
}
public void startPrompt(String select) {
String startPrompt = START_PROMPT;
if (select.equalsIgnoreCase("mph")) startPrompt = ET_PROMPT_I;
if (select.equalsIgnoreCase("km/h")) startPrompt = ET_PROMPT_M;
final double x = ((plot.getDomainAxis().getUpperBound() - plot.getDomainAxis().getLowerBound()) / 2) + plot.getDomainAxis().getLowerBound();
final double y = ((hpAxis.getUpperBound() - hpAxis.getLowerBound()) / 2) + hpAxis.getLowerBound();
final XYTextAnnotation startMessage = new XYTextAnnotation(startPrompt, x, y);
startMessage.setPaint(GREEN);
startMessage.setTextAnchor(TextAnchor.BOTTOM_CENTER);
startMessage.setFont(new Font("Arial", Font.BOLD, 20));
plot.addAnnotation(startMessage);
}
public void clearPrompt() {
plot.clearAnnotations();
}
private void addSeries1(JFreeChart chart, int index, XYSeries series, Color color) {
XYDataset dataset = new XYSeriesCollection(series);
XYPlot plot = chart.getXYPlot();
plot.setDataset(index, dataset);
plot.setRenderer(index, buildTrendLineRendererY1(color));
}
private void addSeries2(JFreeChart chart, int index, XYSeries series, Color color) {
XYDataset dataset = new XYSeriesCollection(series);
XYPlot plot = chart.getXYPlot();
plot.setDataset(index, dataset);
plot.setRenderer(index, buildTrendLineRendererY2(color));
}
private void addRef(JFreeChart chart, int index, XYSeries series, Color color) {
XYDataset dataset = new XYSeriesCollection(series);
XYPlot plot = chart.getXYPlot();
plot.setDataset(index, dataset);
plot.setRenderer(index, buildTrendLineRenderer(color));
}
private StandardXYItemRenderer buildTrendLineRenderer(Color color) {
StandardXYItemRenderer renderer = new StandardXYItemRenderer();
renderer.setSeriesPaint(0, color);
float dash[] = {2.0f};
renderer.setSeriesStroke(0, new BasicStroke(0.8f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f));
return renderer;
}
private StandardXYItemRenderer buildTrendLineRendererY1(Color color) {
rendererY1.setSeriesPaint(0, color);
rendererY1.setSeriesStroke(0, new BasicStroke(1.6f));
return rendererY1;
}
private StandardXYItemRenderer buildTrendLineRendererY2(Color color) {
rendererY2.setSeriesPaint(0, color);
rendererY2.setSeriesStroke(0, new BasicStroke(1.6f));
return rendererY2;
}
}