1 package org.opentrafficsim.draw.graphs;
2
3 import org.djunits.value.vdouble.scalar.Duration;
4 import org.djunits.value.vdouble.scalar.Length;
5 import org.djunits.value.vdouble.scalar.Time;
6 import org.jfree.chart.JFreeChart;
7 import org.jfree.chart.event.AxisChangeEvent;
8 import org.jfree.chart.event.AxisChangeListener;
9 import org.jfree.chart.plot.XYPlot;
10
11 /**
12 * Plots with space-time. This class adds some zoom control, where a user can manually select a zoom range, or the plot
13 * automatically zooms over the entire space range, and either the entire or some most recent fixed period in time.
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 AbstractSpaceTimePlot extends AbstractBoundedPlot
23 {
24
25 /** Initial end time of plot. */
26 private final Time initialEnd;
27
28 /** Whether to update the axes. */
29 private boolean autoBoundAxes = true;
30
31 /** Whether to disable auto bounds on the axes on any change on the axes. */
32 private boolean virtualAutoBounds = false;
33
34 /** Fixed domain range. */
35 private Double fixedDomainRange = null;
36
37 /**
38 * Constructor.
39 * @param caption caption
40 * @param updateInterval regular update interval (simulation time)
41 * @param scheduler scheduler.
42 * @param delay amount of time that chart runs behind simulation to prevent gaps in the charted data
43 * @param initialEnd initial end time of plots, will be expanded if simulation time exceeds it
44 */
45 public AbstractSpaceTimePlot(final String caption, final Duration updateInterval, final PlotScheduler scheduler,
46 final Duration delay, final Time initialEnd)
47 {
48 super(scheduler, caption, updateInterval, delay);
49 this.initialEnd = initialEnd;
50 }
51
52 @Override
53 protected void setChart(final JFreeChart chart)
54 {
55 super.setChart(chart);
56 XYPlot xyPlot = chart.getXYPlot();
57 setLowerRangeBound(0.0);
58 setUpperRangeBound(getEndLocation().si);
59 setLowerDomainBound(0.0);
60 setUpperDomainBound(this.initialEnd.si);
61 setAutoBounds(xyPlot);
62 // axis listeners to enable/disable auto zoom
63 xyPlot.getDomainAxis().addChangeListener(new AxisChangeListener()
64 {
65 @SuppressWarnings("synthetic-access")
66 @Override
67 public void axisChanged(final AxisChangeEvent event)
68 {
69 if (!AbstractSpaceTimePlot.this.virtualAutoBounds)
70 {
71 // the axis was changed, but not by a command from this class, auto bounds should be disabled
72 AbstractSpaceTimePlot.this.autoBoundAxes = false;
73 }
74 }
75 });
76 }
77
78 @Override
79 protected void update()
80 {
81 if (getUpdateTime() != null && this.initialEnd != null)
82 {
83 setUpperDomainBound(Math.max(getUpdateTime().si, this.initialEnd.si));
84 }
85 if (this.autoBoundAxes && getChart() != null) // null during construction
86 {
87 setAutoBounds(getChart().getXYPlot());
88 }
89 super.update();
90 }
91
92 /**
93 * Update the fixed-ness of the domain range.
94 * @param fixed if true; the domain range will not update when new data becomes available; if false; the domain range will
95 * update to show newly available data
96 */
97 public void updateFixedDomainRange(final boolean fixed)
98 {
99 this.fixedDomainRange = fixed ? getChart().getXYPlot().getDomainAxis().getRange().getLength() : null;
100 notifyPlotChange();
101 }
102
103 /**
104 * Sets the auto bounds without deactivating auto bounds through the axis change listener. This is used to initialize the
105 * plot, and to update the plot when time is increased.
106 * @param plot plot with default zoom-all bounds set
107 */
108 private void setAutoBounds(final XYPlot plot)
109 {
110 // disables the axis change listener from registering a user input that is actually an update of bounds as the time
111 // increases
112 this.virtualAutoBounds = true;
113 if (this.fixedDomainRange != null && getUpdateTime().si > 0.0)
114 {
115 plot.getDomainAxis().setRange(Math.max(getUpdateTime().si - this.fixedDomainRange, 0.0), getUpdateTime().si);
116 }
117 else
118 {
119 super.setAutoBoundDomain(plot); // super to skip setting autoBoundAxes = true
120 }
121 super.setAutoBoundRange(plot); // super to skip setting autoBoundAxes = true
122 this.virtualAutoBounds = false;
123 }
124
125 /** {@inheritDoc} This implementation overrides to enable it's own form of auto bounds. */
126 @Override
127 public final void setAutoBoundDomain(final XYPlot plot)
128 {
129 super.setAutoBoundDomain(plot);
130 this.autoBoundAxes = true;
131 }
132
133 /** {@inheritDoc} This implementation overrides to enable it's own form of auto bounds. */
134 @Override
135 public final void setAutoBoundRange(final XYPlot plot)
136 {
137 super.setAutoBoundRange(plot);
138 this.autoBoundAxes = true;
139 }
140
141 /**
142 * Returns the total path length.
143 * @return total path length
144 */
145 protected abstract Length getEndLocation();
146
147 }