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://github.com/peter-knoppers">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 scheduler
40       * @param caption caption
41       * @param updateInterval regular update interval (simulation time)
42       * @param delay 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      @Override
51      protected void setChart(final JFreeChart chart)
52      {
53          Throw.when(!(chart.getPlot() instanceof XYPlot), IllegalArgumentException.class,
54                  "AbstractBoundedPlot can only work with XYPlot.");
55  
56          super.setChart(chart);
57  
58          XYPlot xyPlot = chart.getXYPlot();
59          xyPlot.getDomainAxis().addChangeListener(new AxisChangeListener()
60          {
61              /** Whether to listen, this prevents a stack overflow. */
62              private boolean listen = true;
63  
64              @SuppressWarnings("synthetic-access")
65              @Override
66              public void axisChanged(final AxisChangeEvent event)
67              {
68                  if (!this.listen)
69                  {
70                      return;
71                  }
72                  this.listen = false;
73                  constrainAxis(xyPlot.getDomainAxis(), AbstractBoundedPlot.this.lowerDomainBound,
74                          AbstractBoundedPlot.this.upperDomainBound);
75                  this.listen = true;
76              }
77          });
78          xyPlot.getRangeAxis().addChangeListener(new AxisChangeListener()
79          {
80              /** Whether to listen, this prevents a stack overflow. */
81              private boolean listen = true;
82  
83              @SuppressWarnings("synthetic-access")
84              @Override
85              public void axisChanged(final AxisChangeEvent event)
86              {
87                  if (!this.listen)
88                  {
89                      return;
90                  }
91                  this.listen = false;
92                  constrainAxis(xyPlot.getRangeAxis(), AbstractBoundedPlot.this.lowerRangeBound,
93                          AbstractBoundedPlot.this.upperRangeBound);
94                  this.listen = true;
95              }
96          });
97      }
98  
99      /**
100      * Sets the lower domain bound.
101      * @param bound use {@code null} to disable bound
102      */
103     public void setLowerDomainBound(final Double bound)
104     {
105         this.lowerDomainBound = bound;
106         constrainAxis(getChart().getXYPlot().getDomainAxis(), this.lowerDomainBound, this.upperDomainBound);
107     }
108 
109     /**
110      * Sets the upper domain bound.
111      * @param bound use {@code null} to disable bound
112      */
113     public void setUpperDomainBound(final Double bound)
114     {
115         this.upperDomainBound = bound;
116         constrainAxis(getChart().getXYPlot().getDomainAxis(), this.lowerDomainBound, this.upperDomainBound);
117     }
118 
119     /**
120      * Sets the lower range bound.
121      * @param bound use {@code null} to disable bound
122      */
123     public void setLowerRangeBound(final Double bound)
124     {
125         this.lowerRangeBound = bound;
126         constrainAxis(getChart().getXYPlot().getRangeAxis(), this.lowerRangeBound, this.upperRangeBound);
127     }
128 
129     /**
130      * Sets the upper range bound.
131      * @param bound use {@code null} to disable bound
132      */
133     public void setUpperRangeBound(final Double bound)
134     {
135         this.upperRangeBound = bound;
136         constrainAxis(getChart().getXYPlot().getRangeAxis(), this.lowerRangeBound, this.upperRangeBound);
137     }
138 
139     /**
140      * Constrains axis.
141      * @param axis axis
142      * @param min minimum value, use {@code null} to apply no bound
143      * @param max maximum value, use {@code null} to apply no bound
144      */
145     private void constrainAxis(final ValueAxis axis, final Double min, final Double max)
146     {
147         double xLow = axis.getLowerBound();
148         double xUpp = axis.getUpperBound();
149         if (min != null && max != null && xUpp - xLow > max - min)
150         {
151             axis.setLowerBound(min);
152             axis.setUpperBound(max);
153         }
154         else if (min != null && xLow < min)
155         {
156             axis.setLowerBound(min);
157             axis.setUpperBound(xUpp + (min - xLow));
158         }
159         else if (max != null && xUpp > max)
160         {
161             axis.setLowerBound(xLow - (xUpp - max));
162             axis.setUpperBound(max);
163         }
164     }
165 
166     @Override
167     public void setAutoBoundDomain(final XYPlot plot)
168     {
169         if (this.lowerDomainBound != null)
170         {
171             plot.getDomainAxis().setLowerBound(this.lowerDomainBound);
172         }
173         if (this.upperDomainBound != null)
174         {
175             plot.getDomainAxis().setUpperBound(this.upperDomainBound);
176         }
177     }
178 
179     @Override
180     public void setAutoBoundRange(final XYPlot plot)
181     {
182         if (this.lowerRangeBound != null)
183         {
184             plot.getRangeAxis().setLowerBound(this.lowerRangeBound);
185         }
186         if (this.upperRangeBound != null)
187         {
188             plot.getRangeAxis().setUpperBound(this.upperRangeBound);
189         }
190     }
191 
192 }