View Javadoc
1   package org.opentrafficsim.draw.graphs;
2   
3   import org.djunits.value.vdouble.scalar.Duration;
4   import org.djutils.exceptions.Throw;
5   import org.jfree.chart.JFreeChart;
6   import org.jfree.chart.axis.ValueAxis;
7   import org.jfree.chart.event.AxisChangeEvent;
8   import org.jfree.chart.event.AxisChangeListener;
9   import org.jfree.chart.plot.XYPlot;
10  import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
11  
12  /**
13   * Plot that allows hard bounds to be set, with upper and lower bound independent. Manual zooming and auto ranges are bounded
14   * within the bounds.
15   * <p>
16   * Copyright (c) 2013-2023 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
17   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
18   * </p>
19   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
20   * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
21   * @author <a href="https://dittlab.tudelft.nl">Wouter Schakel</a>
22   */
23  public abstract class AbstractBoundedPlot extends AbstractPlot
24  {
25  
26      /** Lower bound of domain axis. */
27      private Double lowerDomainBound = null;
28  
29      /** Upper bound of domain axis. */
30      private Double upperDomainBound = null;
31  
32      /** Lower bound of range axis. */
33      private Double lowerRangeBound = null;
34  
35      /** Upper bound of range axis. */
36      private Double upperRangeBound = null;
37  
38      /**
39       * Constructor.
40       * @param simulator OtsSimulatorInterface; simulator
41       * @param caption String; caption
42       * @param updateInterval Duration; regular update interval (simulation time)
43       * @param delay Duration; amount of time that chart runs behind simulation to prevent gaps in the charted data
44       */
45      public AbstractBoundedPlot(final OtsSimulatorInterface simulator, final String caption, final Duration updateInterval,
46              final Duration delay)
47      {
48          super(simulator, caption, updateInterval, delay);
49      }
50  
51      /** {@inheritDoc} */
52      @Override
53      protected void setChart(final JFreeChart chart)
54      {
55          Throw.when(!(chart.getPlot() instanceof XYPlot), IllegalArgumentException.class,
56                  "AbstractBoundedPlot can only work with XYPlot.");
57  
58          super.setChart(chart);
59  
60          XYPlot xyPlot = chart.getXYPlot();
61          xyPlot.getDomainAxis().addChangeListener(new AxisChangeListener()
62          {
63              /** Whether to listen, this prevents a stack overflow. */
64              private boolean listen = true;
65  
66              /** {@inheritDoc} */
67              @SuppressWarnings("synthetic-access")
68              @Override
69              public void axisChanged(final AxisChangeEvent event)
70              {
71                  if (!this.listen)
72                  {
73                      return;
74                  }
75                  this.listen = false;
76                  constrainAxis(xyPlot.getDomainAxis(), AbstractBoundedPlot.this.lowerDomainBound,
77                          AbstractBoundedPlot.this.upperDomainBound);
78                  this.listen = true;
79              }
80          });
81          xyPlot.getRangeAxis().addChangeListener(new AxisChangeListener()
82          {
83              /** Whether to listen, this prevents a stack overflow. */
84              private boolean listen = true;
85  
86              /** {@inheritDoc} */
87              @SuppressWarnings("synthetic-access")
88              @Override
89              public void axisChanged(final AxisChangeEvent event)
90              {
91                  if (!this.listen)
92                  {
93                      return;
94                  }
95                  this.listen = false;
96                  constrainAxis(xyPlot.getRangeAxis(), AbstractBoundedPlot.this.lowerRangeBound,
97                          AbstractBoundedPlot.this.upperRangeBound);
98                  this.listen = true;
99              }
100         });
101     }
102 
103     /**
104      * Sets the lower domain bound.
105      * @param bound Double; use {@code null} to disable bound
106      */
107     public void setLowerDomainBound(final Double bound)
108     {
109         this.lowerDomainBound = bound;
110         constrainAxis(getChart().getXYPlot().getDomainAxis(), this.lowerDomainBound, this.upperDomainBound);
111     }
112 
113     /**
114      * Sets the upper domain bound.
115      * @param bound Double; use {@code null} to disable bound
116      */
117     public void setUpperDomainBound(final Double bound)
118     {
119         this.upperDomainBound = bound;
120         constrainAxis(getChart().getXYPlot().getDomainAxis(), this.lowerDomainBound, this.upperDomainBound);
121     }
122 
123     /**
124      * Sets the lower range bound.
125      * @param bound Double; use {@code null} to disable bound
126      */
127     public void setLowerRangeBound(final Double bound)
128     {
129         this.lowerRangeBound = bound;
130         constrainAxis(getChart().getXYPlot().getRangeAxis(), this.lowerRangeBound, this.upperRangeBound);
131     }
132 
133     /**
134      * Sets the upper range bound.
135      * @param bound Double; use {@code null} to disable bound
136      */
137     public void setUpperRangeBound(final Double bound)
138     {
139         this.upperRangeBound = bound;
140         constrainAxis(getChart().getXYPlot().getRangeAxis(), this.lowerRangeBound, this.upperRangeBound);
141     }
142 
143     /**
144      * Constrains axis.
145      * @param axis ValueAxis; axis
146      * @param min Double; minimum value, use {@code null} to apply no bound
147      * @param max Double; maximum value, use {@code null} to apply no bound
148      */
149     private void constrainAxis(final ValueAxis axis, final Double min, final Double max)
150     {
151         double xLow = axis.getLowerBound();
152         double xUpp = axis.getUpperBound();
153         if (min != null && max != null && xUpp - xLow > max - min)
154         {
155             axis.setLowerBound(min);
156             axis.setUpperBound(max);
157         }
158         else if (min != null && xLow < min)
159         {
160             axis.setLowerBound(min);
161             axis.setUpperBound(xUpp + (min - xLow));
162         }
163         else if (max != null && xUpp > max)
164         {
165             axis.setLowerBound(xLow - (xUpp - max));
166             axis.setUpperBound(max);
167         }
168     }
169 
170     /** {@inheritDoc} */
171     @Override
172     public void setAutoBoundDomain(final XYPlot plot)
173     {
174         if (this.lowerDomainBound != null)
175         {
176             plot.getDomainAxis().setLowerBound(this.lowerDomainBound);
177         }
178         if (this.upperDomainBound != null)
179         {
180             plot.getDomainAxis().setUpperBound(this.upperDomainBound);
181         }
182     }
183 
184     /** {@inheritDoc} */
185     @Override
186     public void setAutoBoundRange(final XYPlot plot)
187     {
188         if (this.lowerRangeBound != null)
189         {
190             plot.getRangeAxis().setLowerBound(this.lowerRangeBound);
191         }
192         if (this.upperRangeBound != null)
193         {
194             plot.getRangeAxis().setUpperBound(this.upperRangeBound);
195         }
196     }
197 
198 }