Add graphing.
[fw/altos] / ao-tools / altosui / AltosGraphTime.java
diff --git a/ao-tools/altosui/AltosGraphTime.java b/ao-tools/altosui/AltosGraphTime.java
new file mode 100644 (file)
index 0000000..c0f99c5
--- /dev/null
@@ -0,0 +1,222 @@
+
+// Copyright (c) 2010 Anthony Towns
+// GPL v2 or later
+
+package altosui;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.jfree.chart.ChartUtilities;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.AxisLocation;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.labels.StandardXYToolTipGenerator;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.plot.ValueMarker;
+import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
+import org.jfree.chart.renderer.xy.XYItemRenderer;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.jfree.ui.RectangleAnchor;
+import org.jfree.ui.TextAnchor;
+
+import altosui.AltosDataPoint;
+import altosui.AltosGraph;
+
+class AltosGraphTime extends AltosGraph {
+    static interface Element {
+        void attachGraph(AltosGraphTime g);
+        void gotTimeData(double time, AltosDataPoint d);
+        void addToPlot(AltosGraphTime g, XYPlot plot);
+    }
+
+    static class TimeAxis implements Element {
+        private int axis;
+        private Color color;
+        private String label;
+        private AxisLocation locn;
+        private double min_y = Double.NaN;
+
+        public TimeAxis(int axis, String label, Color color, AxisLocation locn)
+        {
+            this.axis = axis;
+            this.color = color;
+            this.label = label;
+            this.locn = locn;
+        }
+
+        public void setLowerBound(double min_y) {
+            this.min_y = min_y;
+        }
+
+        public void attachGraph(AltosGraphTime g) { return; }
+        public void gotTimeData(double time, AltosDataPoint d) { return; }
+
+        public void addToPlot(AltosGraphTime g, XYPlot plot) {
+            NumberAxis numAxis = new NumberAxis(label);
+            if (!Double.isNaN(min_y))
+                numAxis.setLowerBound(min_y);
+            plot.setRangeAxis(axis, numAxis);
+            plot.setRangeAxisLocation(axis, locn);
+            numAxis.setLabelPaint(color);
+            numAxis.setTickLabelPaint(color);
+            numAxis.setAutoRangeIncludesZero(false);
+        }
+    }
+
+    abstract static class TimeSeries implements Element {
+        protected XYSeries series;
+        private String axisName;
+        private Color color;
+
+        public TimeSeries(String axisName, String label, Color color) {
+            this.series = new XYSeries(label);
+            this.axisName = axisName;
+            this.color = color;
+        }
+
+        public void attachGraph(AltosGraphTime g) {
+            g.setAxis(this, axisName, color);
+        }
+        abstract public void gotTimeData(double time, AltosDataPoint d);
+
+        public void addToPlot(AltosGraphTime g, XYPlot plot) {
+            XYSeriesCollection dataset = new XYSeriesCollection();
+            dataset.addSeries(this.series);
+
+            XYItemRenderer renderer = new StandardXYItemRenderer();
+            renderer.setSeriesPaint(0, color);
+
+            int dataNum = g.getDataNum(this);
+            int axisNum = g.getAxisNum(this);
+
+            plot.setDataset(dataNum, dataset);
+            plot.mapDatasetToRangeAxis(dataNum, axisNum);
+            plot.setRenderer(dataNum, renderer);
+        }
+    }
+
+    static class StateMarker implements Element {
+        private double val = Double.NaN;
+        private String name;
+        private int state;
+
+        StateMarker(int state, String name) {
+            this.state = state;
+            this.name = name;
+        }
+
+        public void attachGraph(AltosGraphTime g) { return; }
+        public void gotTimeData(double time, AltosDataPoint d) {
+            if (Double.isNaN(val) || time < val) {
+                if (d.state() == state) {
+                    val = time;
+                }
+            }
+        }
+
+        public void addToPlot(AltosGraphTime g, XYPlot plot) {
+            if (Double.isNaN(val))
+                return;
+
+            ValueMarker m = new ValueMarker(val);
+            m.setLabel(name);
+            m.setLabelAnchor(RectangleAnchor.TOP_RIGHT);
+            m.setLabelTextAnchor(TextAnchor.TOP_LEFT);
+            plot.addDomainMarker(m);
+        }
+    }
+
+    private String title;
+    private ArrayList<Element> elements;
+    private HashMap<String,Integer> axes;
+    private HashMap<Element,Integer> datasets;
+    private ArrayList<Integer> datasetAxis;
+
+    public AltosGraphTime(String title) {
+        this.filename = title.toLowerCase().replaceAll("[^a-z0-9]","_")+".png";
+        this.title = title;
+        this.elements = new ArrayList<Element>();
+        this.axes = new HashMap<String,Integer>();
+        this.datasets = new HashMap<Element,Integer>();
+        this.datasetAxis = new ArrayList<Integer>();
+    }
+
+    public AltosGraphTime addElement(Element e) {
+        e.attachGraph(this);
+        elements.add(e);
+        return this;
+    }
+
+    public void setAxis(Element ds, String axisName, Color color) {
+        Integer axisNum = axes.get(axisName);
+        int dsNum = datasetAxis.size();
+        if (axisNum == null) {
+            axisNum = newAxis(axisName, color);
+        }
+        datasets.put(ds, dsNum);
+        datasetAxis.add(axisNum);
+    }
+
+    public int getAxisNum(Element ds) {
+        return datasetAxis.get( datasets.get(ds) ).intValue();
+    }
+    public int getDataNum(Element ds) {
+        return datasets.get(ds).intValue();
+    }
+
+    private Integer newAxis(String name, Color color) {
+        int cnt = axes.size();
+        AxisLocation locn = AxisLocation.BOTTOM_OR_LEFT;
+        if (cnt > 0) {
+            locn = AxisLocation.TOP_OR_RIGHT;
+        }
+        Integer res = new Integer(cnt);
+        axes.put(name, res);
+        this.addElement(new TimeAxis(cnt, name, color, locn));
+        return res;
+    }
+
+    public void addData(AltosDataPoint d) {
+        double time = d.time();
+        for (Element e : elements) {
+            e.gotTimeData(time, d);
+        }
+    }
+
+    public JFreeChart createChart() {
+        NumberAxis xAxis = new NumberAxis("Time (s)");
+        xAxis.setAutoRangeIncludesZero(false);
+        XYItemRenderer renderer = new XYLineAndShapeRenderer(true, false);
+        XYPlot plot = new XYPlot();
+        plot.setDomainAxis(xAxis);
+        plot.setRenderer(renderer);
+        plot.setOrientation(PlotOrientation.VERTICAL);
+
+        renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator());
+        JFreeChart chart = new JFreeChart(title, JFreeChart.DEFAULT_TITLE_FONT,
+                                plot, true);
+        ChartUtilities.applyCurrentTheme(chart);
+
+        plot.setDomainPannable(true);
+        plot.setRangePannable(true);
+   
+        for (Element e : elements) {
+            e.addToPlot(this, plot);
+        }
+
+        return chart;
+    }
+
+    public void toPNG() throws java.io.IOException {
+        if (axes.size() > 1) {
+            toPNG(800, 500);
+        } else {
+            toPNG(300, 500);
+        }
+    }
+}