package p2pp;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Plotter {
private Map<String, String> options = new HashMap<String, String>();
private List<String> labels = new ArrayList<String>();
private String line = null;
private String fittedLine = null;
private List<double[]> data = new ArrayList<double[]>();
private int nbOfPlots;
private String[] titles;
private String description;
private File dataFile;
private File scriptFile;
private final String defaultType = "linespoints";
public Plotter(String description, String... titles) {
this(description, new File("plot"), titles);
}
public Plotter(String description, File path, String... titles) {
this.nbOfPlots = titles.length;
this.titles = titles;
this.description = description;
long time = System.currentTimeMillis();
this.dataFile = new File(path, description + "-" + time + "-data.data");
this.scriptFile = new File(path, description + "-" + time + "-script.p");
this.set("xtics", "auto");
this.set("ytics", "auto");
this.set("datafile missing", "NaN", true);
// Set output to pdf.
this.set("terminal", "pdf");
this.set("output", description + "-" + time + "-plot.pdf", true);
}
public void set(String name, String value) {
set(name, value, false);
}
public void set(String name, String value, boolean wrap) {
value = wrap ? "\"" + value + "\"" : value;
options.put(name, value);
}
public void setTitle(String title) {
set("title", title, true);
}
public void setAxisLabels(String xlabel, String ylabel) {
set("xlabel", xlabel, true);
set("ylabel", ylabel, true);
}
public void setLabel(String label, double x, double y) {
labels.add("label \"" + label + "\" at " + x + ", " + y);
}
public void setLegendPlacing(String x, String y) {
set("key", x + " " + y);
}
public void setLegendPlacing(double x, double y) {
set("key", x + ", " + y);
}
public void setYMin(double min) {
set("yrange", "[" + min + ":*]");
}
public void setXRange(double start, double end) {
set("xrange", "[" + start + ":" + end + "]");
}
public void setXMin(double min) {
set("xrange", "[" + min + ":*]");
}
public void doFitWithLine() {
fittedLine = "fitted(x)";
}
public void line(double a, double b, String title) {
line = a + "*x+" + b + " title '" + title + "'";
}
public void horizontalLine(double y, String title) {
line = y + " title '" + title + "'";
}
public <T extends Number> void put(List<T> vector) {
double[] v = new double[vector.size()];
for(int i = 0; i < vector.size(); i ++)
v[i] = vector.get(i).doubleValue();
put(v);
}
// Add data.
public void put(double... vector) {
if(vector.length > nbOfPlots)
throw new IllegalArgumentException(
"The length of the vector must be less or egual to the given size!");
else if(vector.length < nbOfPlots) {
double[] norm = new double[nbOfPlots];
System.arraycopy(vector, 0, norm, 0, vector.length);
for(int i = vector.length; i < norm.length; i++)
norm[i] = Double.NaN;
vector = norm;
}
data.add(vector);
}
/**
* Executes gnuplot to create a graph. The method createPlot
* must be run first.
* @param path Path to the gnuplot executable file.
* @return Returns gnuplots exit value.
*/
public int runGnuplot(String path) throws IOException {
Process gnuplot = Runtime.getRuntime().exec(path);
OutputStream stdin = gnuplot.getOutputStream();
String cmd = "cd \"" + scriptFile.getAbsoluteFile().
getParent().replace('\\', '/') + "\"\n";
stdin.write(cmd.getBytes());
stdin.flush();
cmd = "load \"" + scriptFile.getName() + "\"\n";
stdin.write(cmd.getBytes());
stdin.flush();
cmd = "exit\n";
stdin.write(cmd.getBytes());
stdin.flush();
stdin.close();
try {
gnuplot.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
return gnuplot.exitValue();
}
/**
* Called to create all the necessary files so the data can be plotted
* in a 2 dimensional graph.
* The types argument specifies which type of graph to be used for each
* data set. If none are given the default is 'linespoints'. If the types
* array is smaller that the data sets, the last item is used for the
* remaining sets.
* @param types The types to be used for each data set. E.g. points, lines or
* linespoints (see gnuplot manual for details).
*/
public void createPlot(String... types) throws IOException {
writeData(dataFile);
writeOptions(scriptFile, dataFile.getName(),
normilizeArray(types, titles.length));
}
private String[] normilizeArray(String[] strs, int length) {
if(strs == null || strs.length == 0)
strs = new String[] {defaultType};
String[] result = new String[length];
int len = strs.length > length ? length : strs.length;
System.arraycopy(strs, 0, result, 0, len);
for(int i = len; i < length; i++)
result[i] = strs[len - 1];
return result;
}
private void writeOptions(File file, String data, String[] types)
throws IOException {
BufferedWriter out = new BufferedWriter(new FileWriter(file));
out.write("# Plot script for " + description + ".\n");
out.write("# Used with " + data + " data file.\n");
out.write("# Run with the following commands:\n");
out.write("# $> gnuplot\n");
out.write("# $gnuplot> load '" + file.getName() + "'\n");
for(String name : options.keySet())
out.write("set " + name + " " + options.get(name) + "\n");
for(String label : labels)
out.write("set " + label + "\n");
if(fittedLine != null) {
out.write(fittedLine + "=a*x\n");
out.write("fit " + fittedLine + " \"" + data + "\" using 1:2 via a\n");
}
out.write("plot ");
for(int i = 1; i < types.length; i++) {
out.write(plot(data, titles[i], types[i], "1:" + (i + 1)));
if(i < types.length - 1)
out.write(", ");
}
if(line != null)
out.write(", " + line);
if(fittedLine != null)
out.write(", " + fittedLine + " title 'Average'");
out.write("\n");
out.close();
}
private String plot(String data, String title, String type, String using) {
return "\"" + data + "\" using " + using + " title '" +
title + "' with " + type;
}
private void writeData(File file) throws IOException {
BufferedWriter out = new BufferedWriter(new FileWriter(file));
out.write("# Plot data for " + description + ".\n");
out.write("# ");
String space = " ";
int[] spaces = new int[nbOfPlots];
for(int i = 0; i < titles.length; i++) {
out.write(titles[i] + space);
spaces[i] = titles[i].length() + space.length();
}
out.write("\n");
DecimalFormatSymbols dfs = new DecimalFormatSymbols();
dfs.setDecimalSeparator('.');
// 5 decimal precision.
DecimalFormat dec = new DecimalFormat("#.#####", dfs);
for(double[] vector : data) {
for(int i = 0; i < vector.length; i++) {
String value = dec.format(vector[i]);
int in = spaces[i] - value.length();
String indent = "";
for(int j = 0; j < in; j++)
indent += " ";
out.write(value + indent);
}
out.write("\n");
}
out.close();
}
}