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://tudelft.nl/staff/p.knoppers-1">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 String; caption
  47.      * @param scheduler PlotScheduler; scheduler.
  48.      * @param dataPool ContourDataSource; data pool
  49.      * @param paintScale BoundsPaintScale; paint scale
  50.      * @param legendStep Z; increment between color legend entries
  51.      * @param legendFormat String; format string for the captions in the color legend
  52.      * @param valueFormat String; 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 String; caption
  74.      * @param scheduler PlotScheduler; scheduler.
  75.      * @param dataPool ContourDataSource; data pool
  76.      * @param legendStep Z; increment between color legend entries
  77.      * @param legendFormat String; format string for the captions in the color legend
  78.      * @param minValue Z; minimum value
  79.      * @param maxValue Z; maximum value
  80.      * @param valueFormat String; 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 Number; minimum value
  91.      * @param maxValue Number; maximum value
  92.      * @return BoundsPaintScale; 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 JFreeChart; 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 double; 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 double; 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 double; 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 double; 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 boolean; 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 ContourDataSource; data pool for subclasses
  177.      */
  178.     public final ContourDataSource getDataPool()
  179.     {
  180.         return this.dataPool;
  181.     }

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

  188.     /** {@inheritDoc} */
  189.     @Override
  190.     public final Number getX(final int series, final int item)
  191.     {
  192.         return getXValue(series, item);
  193.     }

  194.     /** {@inheritDoc} */
  195.     @Override
  196.     public final double getXValue(final int series, final int item)
  197.     {
  198.         return this.dataPool.getAxisValue(Dimension.TIME, item);
  199.     }

  200.     /** {@inheritDoc} */
  201.     @Override
  202.     public final Number getY(final int series, final int item)
  203.     {
  204.         return getYValue(series, item);
  205.     }

  206.     /** {@inheritDoc} */
  207.     @Override
  208.     public final double getYValue(final int series, final int item)
  209.     {
  210.         return this.dataPool.getAxisValue(Dimension.DISTANCE, item);
  211.     }

  212.     /** {@inheritDoc} */
  213.     @Override
  214.     public final Number getZ(final int series, final int item)
  215.     {
  216.         return getZValue(series, item);
  217.     }

  218.     /** {@inheritDoc} */
  219.     @Override
  220.     public final Comparable<String> getSeriesKey(final int series)
  221.     {
  222.         return getCaption();
  223.     }

  224.     /** {@inheritDoc} */
  225.     @SuppressWarnings("rawtypes")
  226.     @Override
  227.     public final int indexOf(final Comparable seriesKey)
  228.     {
  229.         return 0;
  230.     }

  231.     /** {@inheritDoc} */
  232.     @Override
  233.     public final DomainOrder getDomainOrder()
  234.     {
  235.         return DomainOrder.ASCENDING;
  236.     }

  237.     /** {@inheritDoc} */
  238.     @Override
  239.     public final double getZValue(final int series, final int item)
  240.     {
  241.         // default 1 series
  242.         return getValue(item, this.dataPool.getGranularity(Dimension.DISTANCE), this.dataPool.getGranularity(Dimension.TIME));
  243.     }

  244.     /** {@inheritDoc} */
  245.     @Override
  246.     public final int getSeriesCount()
  247.     {
  248.         return 1; // default
  249.     }

  250.     /** {@inheritDoc} */
  251.     @Override
  252.     public int getRangeBinCount()
  253.     {
  254.         return this.dataPool.getBinCount(Dimension.DISTANCE);
  255.     }

  256.     /**
  257.      * Returns the status label when the mouse is over the given location.
  258.      * @param domainValue double; domain value (x-axis)
  259.      * @param rangeValue double; range value (y-axis)
  260.      * @return String; status label when the mouse is over the given location
  261.      */
  262.     @Override
  263.     public final String getStatusLabel(final double domainValue, final double rangeValue)
  264.     {
  265.         if (this.dataPool == null)
  266.         {
  267.             return String.format("time %.0fs, distance %.0fm", domainValue, rangeValue);
  268.         }
  269.         int i = this.dataPool.getAxisBin(Dimension.DISTANCE, rangeValue);
  270.         int j = this.dataPool.getAxisBin(Dimension.TIME, domainValue);
  271.         int item = j * this.dataPool.getBinCount(Dimension.DISTANCE) + i;
  272.         double zValue = scale(
  273.                 getValue(item, this.dataPool.getGranularity(Dimension.DISTANCE), this.dataPool.getGranularity(Dimension.TIME)));
  274.         return String.format("time %.0fs, distance %.0fm, " + this.valueFormat, domainValue, rangeValue, zValue);
  275.     }

  276.     /** {@inheritDoc} */
  277.     @Override
  278.     protected final void increaseTime(final Time time)
  279.     {
  280.         if (this.dataPool != null) // dataPool is null at construction
  281.         {
  282.             this.dataPool.increaseTime(time);
  283.         }
  284.     }

  285.     /**
  286.      * Obtain value for cell from the data pool.
  287.      * @param item int; item number
  288.      * @param cellLength double; cell length
  289.      * @param cellSpan double; cell duration
  290.      * @return double; value for cell from the data pool
  291.      */
  292.     protected abstract double getValue(int item, double cellLength, double cellSpan);

  293.     /**
  294.      * Scale the value from SI to the desired unit for users.
  295.      * @param si double; SI value
  296.      * @return double; scaled value
  297.      */
  298.     protected abstract double scale(double si);

  299.     /**
  300.      * Returns the contour data type for use in a {@code ContourDataSource}.
  301.      * @return CountorDataType; contour data type
  302.      */
  303.     protected abstract ContourDataType<Z, ?> getContourDataType();

  304.     /**
  305.      * Returns the block renderer.
  306.      * @return block renderer
  307.      */
  308.     public XyInterpolatedBlockRenderer getBlockRenderer()
  309.     {
  310.         return this.blockRenderer;
  311.     }

  312.     @Override
  313.     public final void actionPerformed(final ActionEvent actionEvent)
  314.     {
  315.         String command = actionEvent.getActionCommand();
  316.         if (command.equalsIgnoreCase("setSpaceGranularity"))
  317.         {
  318.             // The source field is abused to contain the granularity
  319.             double granularity = (double) actionEvent.getSource();
  320.             setSpaceGranularity(granularity);
  321.         }
  322.         else if (command.equalsIgnoreCase("setTimeGranularity"))
  323.         {
  324.             // The source field is abused to contain the granularity
  325.             double granularity = (double) actionEvent.getSource();
  326.             setTimeGranularity(granularity);
  327.         }
  328.         else
  329.         {
  330.             throw new RuntimeException("Unhandled ActionEvent (actionCommand is " + actionEvent.getActionCommand() + "\")");
  331.         }
  332.     }

  333. }