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