AbstractPlot.java

  1. package org.opentrafficsim.draw.graphs;

  2. import java.awt.Color;
  3. import java.awt.Font;
  4. import java.awt.Graphics2D;
  5. import java.awt.geom.AffineTransform;
  6. import java.awt.geom.Rectangle2D;
  7. import java.awt.image.BufferedImage;
  8. import java.io.IOException;
  9. import java.util.LinkedHashSet;
  10. import java.util.Set;
  11. import java.util.UUID;

  12. import org.djunits.value.vdouble.scalar.Duration;
  13. import org.djunits.value.vdouble.scalar.Time;
  14. import org.djutils.event.EventType;
  15. import org.jfree.chart.ChartUtils;
  16. import org.jfree.chart.JFreeChart;
  17. import org.jfree.chart.plot.XYPlot;
  18. import org.jfree.chart.title.TextTitle;
  19. import org.jfree.data.general.Dataset;
  20. import org.jfree.data.general.DatasetChangeEvent;
  21. import org.jfree.data.general.DatasetChangeListener;
  22. import org.jfree.data.general.DatasetGroup;
  23. import org.opentrafficsim.base.Identifiable;
  24. import org.opentrafficsim.core.dsol.OTSSimulatorInterface;

  25. import nl.tudelft.simulation.dsol.SimRuntimeException;
  26. import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
  27. import nl.tudelft.simulation.dsol.simtime.SimTimeDoubleUnit;

  28. /**
  29.  * Super class of all plots. This schedules regular updates, creates menus and deals with listeners. There are a number of
  30.  * delegate methods for sub classes to implement.
  31.  * <p>
  32.  * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  33.  * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  34.  * <p>
  35.  * @version $Revision$, $LastChangedDate$, by $Author$, initial version 4 okt. 2018 <br>
  36.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  37.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  38.  * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  39.  */
  40. public abstract class AbstractPlot implements Identifiable, Dataset
  41. {

  42.     /**
  43.      * The (regular, not timed) event type for pub/sub indicating the addition of a graph. Not used internally.<br>
  44.      * Payload: String graph caption (not an array, just a String)
  45.      */
  46.     public static final EventType GRAPH_ADD_EVENT = new EventType("GRAPH.ADD");

  47.     /**
  48.      * The (regular, not timed) event type for pub/sub indicating the removal of a graph. Not used internally.<br>
  49.      * Payload: String Graph caption (not an array, just a String)
  50.      */
  51.     public static final EventType GRAPH_REMOVE_EVENT = new EventType("GRAPH.REMOVE");

  52.     /** Initial upper bound for the time scale. */
  53.     public static final Time DEFAULT_INITIAL_UPPER_TIME_BOUND = Time.instantiateSI(300.0);

  54.     /** Simulator. */
  55.     private final OTSSimulatorInterface simulator;

  56.     /** Unique ID of the chart. */
  57.     private final String id = UUID.randomUUID().toString();

  58.     /** Caption. */
  59.     private final String caption;

  60.     /** The chart, so we can export it. */
  61.     private JFreeChart chart;

  62.     /** List of parties interested in changes of this plot. */
  63.     private Set<DatasetChangeListener> listeners = new LinkedHashSet<>();

  64.     /** Delay so critical future events have occurred, e.g. GTU's next move's to extend trajectories. */
  65.     private final Duration delay;

  66.     /** Time of last data update. */
  67.     private Time updateTime;

  68.     /** Number of updates. */
  69.     private int updates = 0;

  70.     /** Update interval. */
  71.     private Duration updateInterval;

  72.     /** Event of next update. */
  73.     private SimEventInterface<SimTimeDoubleUnit> updateEvent;

  74.     /**
  75.      * Constructor.
  76.      * @param simulator OTSSimulatorInterface; simulator
  77.      * @param caption String; caption
  78.      * @param updateInterval Duration; regular update interval (simulation time)
  79.      * @param delay Duration; amount of time that chart runs behind simulation to prevent gaps in the charted data
  80.      */
  81.     public AbstractPlot(final OTSSimulatorInterface simulator, final String caption, final Duration updateInterval,
  82.             final Duration delay)
  83.     {
  84.         this.simulator = simulator;
  85.         this.caption = caption;
  86.         this.updateInterval = updateInterval;
  87.         this.delay = delay;
  88.         this.updates = (int) (simulator.getSimulatorTime().si / updateInterval.si); // when creating plot during simulation
  89.         update(); // start redraw chain
  90.     }

  91.     /**
  92.      * Sets the chart and adds menus and listeners.
  93.      * @param chart JFreeChart; chart
  94.      */
  95.     @SuppressWarnings("methodlength")
  96.     protected void setChart(final JFreeChart chart)
  97.     {
  98.         this.chart = chart;

  99.         // make title somewhat smaller
  100.         chart.setTitle(new TextTitle(chart.getTitle().getText(), new Font("SansSerif", java.awt.Font.BOLD, 16)));

  101.         // default colors and zoom behavior
  102.         chart.getPlot().setBackgroundPaint(Color.LIGHT_GRAY);
  103.         chart.setBackgroundPaint(Color.WHITE);
  104.         if (chart.getPlot() instanceof XYPlot)
  105.         {
  106.             chart.getXYPlot().setDomainGridlinePaint(Color.WHITE);
  107.             chart.getXYPlot().setRangeGridlinePaint(Color.WHITE);
  108.         }

  109.     }

  110.     /**
  111.      * Returns the chart as a byte array representing a png image.
  112.      * @param width int; width
  113.      * @param height int; height
  114.      * @param fontSize double; font size (16 is the original on screen size)
  115.      * @return byte[]; the chart as a byte array representing a png image
  116.      * @throws IOException on IO exception
  117.      */
  118.     public byte[] encodeAsPng(final int width, final int height, final double fontSize) throws IOException
  119.     {
  120.         // to double the font size, we halve the base dimensions
  121.         // JFreeChart will the assign more area (relatively) to the fixed actual font size
  122.         double baseWidth = width / (fontSize / 16);
  123.         double baseHeight = height / (fontSize / 16);
  124.         // this code is from ChartUtils.writeScaledChartAsPNG
  125.         BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
  126.         Graphics2D g2 = image.createGraphics();
  127.         // to compensate for the base dimensions which are not w x h, we scale the drawing
  128.         AffineTransform saved = g2.getTransform();
  129.         g2.transform(AffineTransform.getScaleInstance(width / baseWidth, height / baseHeight));
  130.         getChart().draw(g2, new Rectangle2D.Double(0, 0, baseWidth, baseHeight), null, null);
  131.         g2.setTransform(saved);
  132.         g2.dispose();
  133.         return ChartUtils.encodeAsPNG(image);
  134.     }

  135.     /** {@inheritDoc} */
  136.     @Override
  137.     public final DatasetGroup getGroup()
  138.     {
  139.         return null; // not used
  140.     }

  141.     /** {@inheritDoc} */
  142.     @Override
  143.     public final void setGroup(final DatasetGroup group)
  144.     {
  145.         // not used
  146.     }

  147.     /**
  148.      * Overridable; activates auto bounds on domain axis from user input. This class does not force the use of {@code XYPlot}s,
  149.      * but the auto bounds command comes from the {@code ChartPanel} that this class creates. In case the used plot is a
  150.      * {@code XYPlot}, this method is then invoked. Sub classes with auto domain bounds that work with an {@code XYPlot} should
  151.      * implement this. The method is not abstract as the use of {@code XYPlot} is not obligated.
  152.      * @param plot XYPlot; plot
  153.      */
  154.     public void setAutoBoundDomain(final XYPlot plot)
  155.     {
  156.         //
  157.     }

  158.     /**
  159.      * Overridable; activates auto bounds on range axis from user input. This class does not force the use of {@code XYPlot}s,
  160.      * but the auto bounds command comes from the {@code ChartPanel} that this class creates. In case the used plot is a
  161.      * {@code XYPlot}, this method is then invoked. Sub classes with auto range bounds that work with an {@code XYPlot} should
  162.      * implement this. The method is not abstract as the use of {@code XYPlot} is not obligated.
  163.      * @param plot XYPlot; plot
  164.      */
  165.     public void setAutoBoundRange(final XYPlot plot)
  166.     {
  167.         //
  168.     }

  169.     /**
  170.      * Return the graph type for transceiver.
  171.      * @return GraphType; the graph type.
  172.      */
  173.     public abstract GraphType getGraphType();

  174.     /**
  175.      * Returns the status label when the mouse is over the given location.
  176.      * @param domainValue double; domain value (x-axis)
  177.      * @param rangeValue double; range value (y-axis)
  178.      * @return String; status label when the mouse is over the given location
  179.      */
  180.     public abstract String getStatusLabel(double domainValue, double rangeValue);

  181.     /**
  182.      * Increase the simulated time span.
  183.      * @param time Time; time to increase to
  184.      */
  185.     protected abstract void increaseTime(Time time);

  186.     /**
  187.      * Notify all change listeners.
  188.      */
  189.     public final void notifyPlotChange()
  190.     {
  191.         DatasetChangeEvent event = new DatasetChangeEvent(this, this);
  192.         for (DatasetChangeListener dcl : this.listeners)
  193.         {
  194.             dcl.datasetChanged(event);
  195.         }
  196.     }

  197.     /**
  198.      * Returns the chart.
  199.      * @return JFreeChart; chart
  200.      */
  201.     public final JFreeChart getChart()
  202.     {
  203.         return this.chart;
  204.     }

  205.     /** {@inheritDoc} */
  206.     @Override
  207.     public final String getId()
  208.     {
  209.         return this.id;
  210.     }

  211.     /** {@inheritDoc} */
  212.     @Override
  213.     public final void addChangeListener(final DatasetChangeListener listener)
  214.     {
  215.         this.listeners.add(listener);
  216.     }

  217.     /** {@inheritDoc} */
  218.     @Override
  219.     public final void removeChangeListener(final DatasetChangeListener listener)
  220.     {
  221.         this.listeners.remove(listener);
  222.     }

  223.     /**
  224.      * Retrieve the simulator.
  225.      * @return OTSSimulatorInterface; the simulator
  226.      */
  227.     public OTSSimulatorInterface getSimulator()
  228.     {
  229.         return this.simulator;
  230.     }

  231.     /**
  232.      * Sets a new update interval.
  233.      * @param interval Duration; update interval
  234.      */
  235.     public final void setUpdateInterval(final Duration interval)
  236.     {
  237.         if (this.updateEvent != null)
  238.         {
  239.             this.simulator.cancelEvent(this.updateEvent);
  240.         }
  241.         this.updates = (int) (this.simulator.getSimulatorTime().si / interval.si);
  242.         this.updateInterval = interval;
  243.         this.updateTime = Time.instantiateSI(this.updates * this.updateInterval.si);
  244.         scheduleNextUpdateEvent();
  245.     }

  246.     /**
  247.      * Returns time until which data should be plotted.
  248.      * @return Time; time until which data should be plotted
  249.      */
  250.     public final Time getUpdateTime()
  251.     {
  252.         return this.updateTime;
  253.     }

  254.     /**
  255.      * Redraws the plot and schedules the next update.
  256.      */
  257.     protected void update()
  258.     {
  259.         // TODO: next event may be scheduled in the past if the simulator is running fast during these few calls
  260.         this.updateTime = this.simulator.getSimulatorTime();
  261.         increaseTime(this.updateTime.minus(this.delay));
  262.         notifyPlotChange();
  263.         scheduleNextUpdateEvent();
  264.     }

  265.     /**
  266.      * Schedules the next update event.
  267.      */
  268.     private void scheduleNextUpdateEvent()
  269.     {
  270.         try
  271.         {
  272.             this.updates++;
  273.             // events are scheduled slightly later, so all influencing movements have occurred
  274.             this.updateEvent = this.simulator.scheduleEventAbs(Time.instantiateSI(this.updateInterval.si * this.updates
  275.                 + this.delay.si), this, this, "update", null);
  276.         }
  277.         catch (SimRuntimeException exception)
  278.         {
  279.             throw new RuntimeException("Unexpected exception while updating plot.", exception);
  280.         }
  281.     }

  282.     /**
  283.      * Retrieve the caption.
  284.      * @return String; the caption of the plot
  285.      */
  286.     public String getCaption()
  287.     {
  288.         return this.caption;
  289.     }

  290. }