AbstractContourPlot.java

  1. package org.opentrafficsim.draw.graphs;

  2. import java.awt.Color;
  3. import java.awt.event.ActionEvent;
  4. import java.awt.event.ActionListener;

  5. import org.djunits.value.vdouble.scalar.Time;
  6. import org.djutils.exceptions.Throw;
  7. import org.jfree.chart.JFreeChart;
  8. import org.jfree.chart.LegendItem;
  9. import org.jfree.chart.LegendItemCollection;
  10. import org.jfree.chart.axis.NumberAxis;
  11. import org.jfree.chart.plot.XYPlot;
  12. import org.jfree.data.DomainOrder;
  13. import org.opentrafficsim.draw.BoundsPaintScale;
  14. import org.opentrafficsim.draw.graphs.ContourDataSource.ContourDataType;
  15. import org.opentrafficsim.draw.graphs.ContourDataSource.Dimension;

  16. /**
  17.  * Class for contour plots. The data that is plotted is stored in a {@code ContourDataSource}, which may be shared among several
  18.  * contour plots along the same path. This abstract class takes care of the interactions between the plot and the data pool. Sub
  19.  * classes only need to specify a few plot specific variables and functionalities.
  20.  * <p>
  21.  * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  22.  * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  23.  * </p>
  24.  * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  25.  * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
  26.  * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  27.  * @param <Z> z-value type
  28.  */
  29. public abstract class AbstractContourPlot<Z extends Number> extends AbstractSamplerPlot
  30.         implements XyInterpolatedDataset, ActionListener
  31. {

  32.     /** Color scale for the graph. */
  33.     private final BoundsPaintScale paintScale;

  34.     /** Difference of successive values in the legend. */
  35.     private final Z legendStep;

  36.     /** Format string used to create the captions in the legend. */
  37.     private final String legendFormat;

  38.     /** Format string used to create status label (under the mouse). */
  39.     private final String valueFormat;

  40.     /** Data pool. */
  41.     private final ContourDataSource dataPool;

  42.     /** Block renderer in chart. */
  43.     private XyInterpolatedBlockRenderer blockRenderer = null;

  44.     /**
  45.      * Constructor with specified paint scale.
  46.      * @param caption caption
  47.      * @param scheduler scheduler.
  48.      * @param dataPool data pool
  49.      * @param paintScale paint scale
  50.      * @param legendStep increment between color legend entries
  51.      * @param legendFormat format string for the captions in the color legend
  52.      * @param valueFormat format string used to create status label (under the mouse)
  53.      */
  54.     public AbstractContourPlot(final String caption, final PlotScheduler scheduler, final ContourDataSource dataPool,
  55.             final BoundsPaintScale paintScale, final Z legendStep, final String legendFormat, final String valueFormat)
  56.     {
  57.         super(caption, dataPool.getUpdateInterval(), scheduler, dataPool.getSamplerData(), dataPool.getPath(),
  58.                 dataPool.getDelay());
  59.         dataPool.registerContourPlot(this);
  60.         this.dataPool = dataPool;
  61.         this.paintScale = paintScale;
  62.         this.legendStep = legendStep;
  63.         this.legendFormat = legendFormat;
  64.         this.valueFormat = valueFormat;
  65.         this.blockRenderer = new XyInterpolatedBlockRenderer(this);
  66.         this.blockRenderer.setPaintScale(this.paintScale);
  67.         this.blockRenderer.setBlockHeight(dataPool.getGranularity(Dimension.DISTANCE));
  68.         this.blockRenderer.setBlockWidth(dataPool.getGranularity(Dimension.TIME));
  69.         setChart(createChart());
  70.     }

  71.     /**
  72.      * Constructor with default paint scale.
  73.      * @param caption caption
  74.      * @param scheduler scheduler.
  75.      * @param dataPool data pool
  76.      * @param legendStep increment between color legend entries
  77.      * @param legendFormat format string for the captions in the color legend
  78.      * @param minValue minimum value
  79.      * @param maxValue maximum value
  80.      * @param valueFormat format string used to create status label (under the mouse)
  81.      */
  82.     @SuppressWarnings("parameternumber")
  83.     public AbstractContourPlot(final String caption, final PlotScheduler scheduler, final ContourDataSource dataPool,
  84.             final Z legendStep, final String legendFormat, final Z minValue, final Z maxValue, final String valueFormat)
  85.     {
  86.         this(caption, scheduler, dataPool, createPaintScale(minValue, maxValue), legendStep, legendFormat, valueFormat);
  87.     }

  88.     /**
  89.      * Creates a default paint scale from red, via yellow to green.
  90.      * @param minValue minimum value
  91.      * @param maxValue maximum value
  92.      * @return default paint scale
  93.      */
  94.     private static BoundsPaintScale createPaintScale(final Number minValue, final Number maxValue)
  95.     {
  96.         Throw.when(minValue.doubleValue() >= maxValue.doubleValue(), IllegalArgumentException.class,
  97.                 "Minimum value %s is below or equal to maxumum value %s.", minValue, maxValue);
  98.         double[] boundaries =
  99.                 {minValue.doubleValue(), (minValue.doubleValue() + maxValue.doubleValue()) / 2.0, maxValue.doubleValue()};
  100.         Color[] colorValues = {Color.RED, Color.YELLOW, Color.GREEN};
  101.         return new BoundsPaintScale(boundaries, colorValues);
  102.     }

  103.     /**
  104.      * Create a chart.
  105.      * @return chart
  106.      */
  107.     private JFreeChart createChart()
  108.     {
  109.         NumberAxis xAxis = new NumberAxis("Time [s] \u2192");
  110.         NumberAxis yAxis = new NumberAxis("Distance [m] \u2192");
  111.         XYPlot plot = new XYPlot(this, xAxis, yAxis, this.blockRenderer);
  112.         LegendItemCollection legend = new LegendItemCollection();
  113.         for (int i = 0;; i++)
  114.         {
  115.             double value = this.paintScale.getLowerBound() + i * this.legendStep.doubleValue();
  116.             if (value > this.paintScale.getUpperBound() + 1e-6)
  117.             {
  118.                 break;
  119.             }
  120.             legend.add(new LegendItem(String.format(this.legendFormat, scale(value)), this.paintScale.getPaint(value)));
  121.         }
  122.         legend.add(new LegendItem("No data", Color.BLACK));
  123.         plot.setFixedLegendItems(legend);
  124.         final JFreeChart chart = new JFreeChart(getCaption(), plot);
  125.         return chart;
  126.     }

  127.     /**
  128.      * Returns the time granularity, just for information.
  129.      * @return time granularity
  130.      */
  131.     public double getTimeGranularity()
  132.     {
  133.         return this.dataPool.getGranularity(Dimension.TIME);
  134.     }

  135.     /**
  136.      * Returns the space granularity, just for information.
  137.      * @return space granularity
  138.      */
  139.     public double getSpaceGranularity()
  140.     {
  141.         return this.dataPool.getGranularity(Dimension.DISTANCE);
  142.     }

  143.     /**
  144.      * Sets the correct space granularity radio button to selected. This is done from a {@code DataPool} to keep multiple plots
  145.      * consistent.
  146.      * @param granularity space granularity
  147.      */
  148.     public final void setSpaceGranularity(final double granularity)
  149.     {
  150.         this.blockRenderer.setBlockHeight(granularity);
  151.         this.dataPool.spaceAxis.setGranularity(granularity); // XXX: AV added 16-5-2020
  152.     }

  153.     /**
  154.      * Sets the correct time granularity radio button to selected. This is done from a {@code DataPool} to keep multiple plots
  155.      * consistent.
  156.      * @param granularity time granularity
  157.      */
  158.     public final void setTimeGranularity(final double granularity)
  159.     {
  160.         this.blockRenderer.setBlockWidth(granularity);
  161.         this.dataPool.timeAxis.setGranularity(granularity); // XXX: AV added 16-5-2020
  162.     }

  163.     /**
  164.      * Sets the check box for interpolated rendering and block renderer setting. This is done from a {@code DataPool} to keep
  165.      * multiple plots consistent.
  166.      * @param interpolate selected or not
  167.      */
  168.     public final void setInterpolation(final boolean interpolate)
  169.     {
  170.         this.blockRenderer.setInterpolate(interpolate);
  171.         this.dataPool.timeAxis.setInterpolate(interpolate); // XXX: AV added 16-5-2020
  172.         this.dataPool.spaceAxis.setInterpolate(interpolate); // XXX: AV added 16-5-2020
  173.     }

  174.     /**
  175.      * Returns the data pool for sub classes.
  176.      * @return data pool for subclasses
  177.      */
  178.     public final ContourDataSource getDataPool()
  179.     {
  180.         return this.dataPool;
  181.     }

  182.     @Override
  183.     public final int getItemCount(final int series)
  184.     {
  185.         return this.dataPool.getBinCount(Dimension.DISTANCE) * this.dataPool.getBinCount(Dimension.TIME);
  186.     }

  187.     @Override
  188.     public final Number getX(final int series, final int item)
  189.     {
  190.         return getXValue(series, item);
  191.     }

  192.     @Override
  193.     public final double getXValue(final int series, final int item)
  194.     {
  195.         return this.dataPool.getAxisValue(Dimension.TIME, item);
  196.     }

  197.     @Override
  198.     public final Number getY(final int series, final int item)
  199.     {
  200.         return getYValue(series, item);
  201.     }

  202.     @Override
  203.     public final double getYValue(final int series, final int item)
  204.     {
  205.         return this.dataPool.getAxisValue(Dimension.DISTANCE, item);
  206.     }

  207.     @Override
  208.     public final Number getZ(final int series, final int item)
  209.     {
  210.         return getZValue(series, item);
  211.     }

  212.     @Override
  213.     public final Comparable<String> getSeriesKey(final int series)
  214.     {
  215.         return getCaption();
  216.     }

  217.     @SuppressWarnings("rawtypes")
  218.     @Override
  219.     public final int indexOf(final Comparable seriesKey)
  220.     {
  221.         return 0;
  222.     }

  223.     @Override
  224.     public final DomainOrder getDomainOrder()
  225.     {
  226.         return DomainOrder.ASCENDING;
  227.     }

  228.     @Override
  229.     public final double getZValue(final int series, final int item)
  230.     {
  231.         // default 1 series
  232.         return getValue(item, this.dataPool.getGranularity(Dimension.DISTANCE), this.dataPool.getGranularity(Dimension.TIME));
  233.     }

  234.     @Override
  235.     public final int getSeriesCount()
  236.     {
  237.         return 1; // default
  238.     }

  239.     @Override
  240.     public int getRangeBinCount()
  241.     {
  242.         return this.dataPool.getBinCount(Dimension.DISTANCE);
  243.     }

  244.     /**
  245.      * Returns the status label when the mouse is over the given location.
  246.      * @param domainValue domain value (x-axis)
  247.      * @param rangeValue range value (y-axis)
  248.      * @return status label when the mouse is over the given location
  249.      */
  250.     @Override
  251.     public final String getStatusLabel(final double domainValue, final double rangeValue)
  252.     {
  253.         if (this.dataPool == null)
  254.         {
  255.             return String.format("time %.0fs, distance %.0fm", domainValue, rangeValue);
  256.         }
  257.         int i = this.dataPool.getAxisBin(Dimension.DISTANCE, rangeValue);
  258.         int j = this.dataPool.getAxisBin(Dimension.TIME, domainValue);
  259.         int item = j * this.dataPool.getBinCount(Dimension.DISTANCE) + i;
  260.         double zValue = scale(
  261.                 getValue(item, this.dataPool.getGranularity(Dimension.DISTANCE), this.dataPool.getGranularity(Dimension.TIME)));
  262.         return String.format("time %.0fs, distance %.0fm, " + this.valueFormat, domainValue, rangeValue, zValue);
  263.     }

  264.     @Override
  265.     protected final void increaseTime(final Time time)
  266.     {
  267.         if (this.dataPool != null) // dataPool is null at construction
  268.         {
  269.             this.dataPool.increaseTime(time);
  270.         }
  271.     }

  272.     /**
  273.      * Obtain value for cell from the data pool.
  274.      * @param item item number
  275.      * @param cellLength cell length
  276.      * @param cellSpan cell duration
  277.      * @return value for cell from the data pool
  278.      */
  279.     protected abstract double getValue(int item, double cellLength, double cellSpan);

  280.     /**
  281.      * Scale the value from SI to the desired unit for users.
  282.      * @param si SI value
  283.      * @return scaled value
  284.      */
  285.     protected abstract double scale(double si);

  286.     /**
  287.      * Returns the contour data type for use in a {@code ContourDataSource}.
  288.      * @return contour data type
  289.      */
  290.     protected abstract ContourDataType<Z, ?> getContourDataType();

  291.     /**
  292.      * Returns the block renderer.
  293.      * @return block renderer
  294.      */
  295.     public XyInterpolatedBlockRenderer getBlockRenderer()
  296.     {
  297.         return this.blockRenderer;
  298.     }

  299.     @Override
  300.     public final void actionPerformed(final ActionEvent actionEvent)
  301.     {
  302.         String command = actionEvent.getActionCommand();
  303.         if (command.equalsIgnoreCase("setSpaceGranularity"))
  304.         {
  305.             // The source field is abused to contain the granularity
  306.             double granularity = (double) actionEvent.getSource();
  307.             setSpaceGranularity(granularity);
  308.         }
  309.         else if (command.equalsIgnoreCase("setTimeGranularity"))
  310.         {
  311.             // The source field is abused to contain the granularity
  312.             double granularity = (double) actionEvent.getSource();
  313.             setTimeGranularity(granularity);
  314.         }
  315.         else
  316.         {
  317.             throw new RuntimeException("Unhandled ActionEvent (actionCommand is " + actionEvent.getActionCommand() + "\")");
  318.         }
  319.     }

  320. }