View Javadoc
1   package org.opentrafficsim.demo.lanechange;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Frame;
5   import java.awt.geom.Line2D;
6   import java.lang.reflect.InvocationTargetException;
7   import java.rmi.RemoteException;
8   import java.util.ArrayList;
9   import java.util.Collection;
10  import java.util.HashSet;
11  import java.util.LinkedHashSet;
12  import java.util.Set;
13  
14  import javax.naming.NamingException;
15  import javax.swing.JFrame;
16  import javax.swing.JPanel;
17  import javax.swing.SwingUtilities;
18  import javax.swing.event.EventListenerList;
19  
20  import nl.tudelft.simulation.dsol.SimRuntimeException;
21  import nl.tudelft.simulation.dsol.gui.swing.TablePanel;
22  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
23  
24  import org.djunits.unit.TimeUnit;
25  import org.djunits.unit.UNITS;
26  import org.djunits.value.vdouble.scalar.Acceleration;
27  import org.djunits.value.vdouble.scalar.DoubleScalar;
28  import org.djunits.value.vdouble.scalar.DoubleScalar.Abs;
29  import org.djunits.value.vdouble.scalar.Duration;
30  import org.djunits.value.vdouble.scalar.Length;
31  import org.djunits.value.vdouble.scalar.Speed;
32  import org.djunits.value.vdouble.scalar.Time;
33  import org.jfree.chart.ChartFactory;
34  import org.jfree.chart.ChartPanel;
35  import org.jfree.chart.JFreeChart;
36  import org.jfree.chart.StandardChartTheme;
37  import org.jfree.chart.axis.NumberAxis;
38  import org.jfree.chart.event.PlotChangeEvent;
39  import org.jfree.chart.plot.Plot;
40  import org.jfree.chart.plot.PlotOrientation;
41  import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
42  import org.jfree.data.DomainOrder;
43  import org.jfree.data.general.DatasetChangeListener;
44  import org.jfree.data.general.DatasetGroup;
45  import org.jfree.data.xy.XYDataset;
46  import org.opentrafficsim.core.dsol.OTSModelInterface;
47  import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
48  import org.opentrafficsim.core.geometry.OTSGeometryException;
49  import org.opentrafficsim.core.geometry.OTSPoint3D;
50  import org.opentrafficsim.core.gtu.GTUDirectionality;
51  import org.opentrafficsim.core.gtu.GTUException;
52  import org.opentrafficsim.core.gtu.GTUType;
53  import org.opentrafficsim.core.gtu.behavioralcharacteristics.BehavioralCharacteristics;
54  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
55  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
56  import org.opentrafficsim.core.network.LongitudinalDirectionality;
57  import org.opentrafficsim.core.network.NetworkException;
58  import org.opentrafficsim.core.network.OTSNetwork;
59  import org.opentrafficsim.core.network.OTSNode;
60  import org.opentrafficsim.road.gtu.lane.LaneBasedIndividualGTU;
61  import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
62  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTUSimple;
63  import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedCFLCTacticalPlanner;
64  import org.opentrafficsim.road.gtu.lane.tactical.following.GTUFollowingModelOld;
65  import org.opentrafficsim.road.gtu.lane.tactical.following.IDMOld;
66  import org.opentrafficsim.road.gtu.lane.tactical.following.IDMPlusOld;
67  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.Altruistic;
68  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.Egoistic;
69  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.LaneChangeModel;
70  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.LaneMovementStep;
71  import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
72  import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlanner;
73  import org.opentrafficsim.road.network.factory.LaneFactory;
74  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
75  import org.opentrafficsim.road.network.lane.Lane;
76  import org.opentrafficsim.road.network.lane.LaneType;
77  import org.opentrafficsim.simulationengine.SimpleSimulator;
78  
79  /**
80   * Create a plot that characterizes a lane change graph.
81   * <p>
82   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
83   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
84   * <p>
85   * $LastChangedDate: 2016-08-04 17:07:01 +0200 (Thu, 04 Aug 2016) $, @version $Revision: 2124 $, by $Author: wjschakel $,
86   * initial version 18 nov. 2014 <br>
87   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
88   */
89  public class LaneChangeGraph extends JFrame implements OTSModelInterface, UNITS
90  {
91      /** */
92      private static final long serialVersionUID = 20141118L;
93  
94      /** Standard speed values in km/h. */
95      static final double[] STANDARDSPEEDS = {30, 50, 80, 100, 120};
96  
97      /** The car following model. */
98      private GTUFollowingModelOld carFollowingModel;
99  
100     /** The graphs. */
101     private ChartPanel[][] charts;
102 
103     /** Start of two lane road. */
104     private static final Length LOWERBOUND = new Length(-500, METER);
105 
106     /** Position of reference vehicle on the two lane road. */
107     private static final Length MIDPOINT = new Length(0, METER);
108 
109     /** End of two lane road. */
110     private static final Length UPPERBOUND = new Length(500, METER);
111 
112     /** The JFrame with the lane change graphs. */
113     private static LaneChangeGraph lcs;
114 
115     /** The network. */
116     private OTSNetwork network = new OTSNetwork("network");
117 
118     /**
119      * Create a Lane Change Graph.
120      * @param title String; title text of the window
121      * @param mainPanel JPanel; panel that will (indirectly?) contain the charts
122      */
123     LaneChangeGraph(final String title, final JPanel mainPanel)
124     {
125         super(title);
126         setContentPane(mainPanel);
127         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
128         this.charts = new ChartPanel[2][STANDARDSPEEDS.length];
129     }
130 
131     /**
132      * Main entry point; now Swing thread safe (I hope).
133      * @param args String[]; the command line arguments (not used)
134      * @throws GTUException on error during GTU construction
135      * @throws SimRuntimeException on ???
136      * @throws NetworkException on network inconsistency
137      * @throws NamingException on ???
138      * @throws OTSGeometryException
139      * @throws ParameterException in case of a parameter problem.
140      * @throws OperationalPlanException 
141      */
142     public static void main(final String[] args) throws NamingException, NetworkException, SimRuntimeException,
143         GTUException, OTSGeometryException, ParameterException, OperationalPlanException
144     {
145         try
146         {
147             SwingUtilities.invokeAndWait(new Runnable()
148             {
149 
150                 @Override
151                 public void run()
152                 {
153                     try
154                     {
155                         buildGUI(args);
156                     }
157                     catch (NamingException | NetworkException | SimRuntimeException | GTUException exception)
158                     {
159                         exception.printStackTrace();
160                     }
161                 }
162 
163             });
164         }
165         catch (InvocationTargetException | InterruptedException exception)
166         {
167             exception.printStackTrace();
168         }
169         for (int row = 0; row < lcs.charts.length; row++)
170         {
171             LaneChangeModel laneChangeModel = 0 == row ? new Egoistic() : new Altruistic();
172             for (int index = 0; index < STANDARDSPEEDS.length; index++)
173             {
174                 Speed speed = new Speed(STANDARDSPEEDS[index], KM_PER_HOUR);
175                 // System.out.println("speed " + speed);
176                 double startSpeedDifference = -30; // standardSpeeds[index];
177                 double endSpeedDifference = startSpeedDifference + 60; // 150;
178                 ChartData data = (ChartData) lcs.charts[row][index].getChart().getXYPlot().getDataset();
179                 int beginRightKey = data.addSeries("Begin of no lane change to right");
180                 int endRightKey = data.addSeries("End of no lane change to right");
181                 int beginLeftKey = data.addSeries("Begin of no lane change to left");
182                 int endLeftKey = data.addSeries("End of no lane change to left");
183                 for (double speedDifference = startSpeedDifference; speedDifference <= endSpeedDifference; speedDifference +=
184                     1)
185                 {
186                     Length criticalHeadway =
187                         lcs.findDecisionPoint(LaneChangeGraph.LOWERBOUND, MIDPOINT, speed, new Speed(speedDifference,
188                             KM_PER_HOUR), laneChangeModel, true);
189                     if (null != criticalHeadway)
190                     {
191                         data.addXYPair(beginRightKey, speedDifference, criticalHeadway.getInUnit(METER));
192                     }
193                     criticalHeadway =
194                         lcs.findDecisionPoint(MIDPOINT, LaneChangeGraph.UPPERBOUND, speed, new Speed(speedDifference,
195                             KM_PER_HOUR), laneChangeModel, true);
196                     if (null != criticalHeadway)
197                     {
198                         data.addXYPair(endRightKey, speedDifference, criticalHeadway.getInUnit(METER));
199                     }
200                     criticalHeadway =
201                         lcs.findDecisionPoint(LaneChangeGraph.LOWERBOUND, MIDPOINT, speed, new Speed(speedDifference,
202                             KM_PER_HOUR), laneChangeModel, false);
203                     if (null != criticalHeadway)
204                     {
205                         data.addXYPair(beginLeftKey, speedDifference, criticalHeadway.getInUnit(METER));
206                     }
207                     else
208                     {
209                         lcs.findDecisionPoint(LaneChangeGraph.LOWERBOUND, MIDPOINT, speed, new Speed(speedDifference,
210                             KM_PER_HOUR), laneChangeModel, false);
211                     }
212                     criticalHeadway =
213                         lcs.findDecisionPoint(MIDPOINT, LaneChangeGraph.UPPERBOUND, speed, new Speed(speedDifference,
214                             KM_PER_HOUR), laneChangeModel, false);
215                     if (null != criticalHeadway)
216                     {
217                         data.addXYPair(endLeftKey, speedDifference, criticalHeadway.getInUnit(METER));
218                     }
219                     Plot plot = lcs.charts[row][index].getChart().getPlot();
220                     plot.notifyListeners(new PlotChangeEvent(plot));
221                 }
222             }
223         }
224 
225     }
226 
227     /**
228      * Then execution start point.
229      * @param args String[]; the command line arguments (not used)
230      * @throws NamingException on ???
231      * @throws NetworkException on network inconsistency
232      * @throws SimRuntimeException on ???
233      * @throws GTUException on error during GTU construction
234      */
235     public static void buildGUI(final String[] args) throws NamingException, NetworkException, SimRuntimeException,
236         GTUException
237     {
238         JPanel mainPanel = new JPanel(new BorderLayout());
239         lcs = new LaneChangeGraph("Lane change graphs", mainPanel);
240         TablePanel chartsPanel = new TablePanel(STANDARDSPEEDS.length, 2);
241         mainPanel.add(chartsPanel, BorderLayout.CENTER);
242         for (int index = 0; index < STANDARDSPEEDS.length; index++)
243         {
244             lcs.charts[0][index] =
245                 new ChartPanel(lcs.createChart(String.format("Egoistic reference car at %.0fkm/h", STANDARDSPEEDS[index]),
246                     STANDARDSPEEDS[index]));
247             chartsPanel.setCell(lcs.charts[0][index], index, 0);
248         }
249         for (int index = 0; index < STANDARDSPEEDS.length; index++)
250         {
251             lcs.charts[1][index] =
252                 new ChartPanel(lcs.createChart(String.format("Altruistic reference car at %.0fkm/h", STANDARDSPEEDS[index]),
253                     STANDARDSPEEDS[index]));
254             chartsPanel.setCell(lcs.charts[1][index], index, 1);
255         }
256         lcs.pack();
257         lcs.setExtendedState(Frame.MAXIMIZED_BOTH);
258         lcs.setVisible(true);
259     }
260 
261     /**
262      * Find the headway at which the decision to merge right changes.
263      * @param low minimum headway to consider
264      * @param high maximum headway to consider
265      * @param referenceSpeed Speed; speed of the reference car
266      * @param speedDifference Speed; speed of the other car minus speed of the reference car
267      * @param laneChangeModel LaneChangeModel; the lane change model to apply
268      * @param mergeRight boolean; if true; merge right is tested; if false; merge left is tested
269      * @return Length
270      * @throws NamingException on ???
271      * @throws NetworkException on network inconsistency
272      * @throws SimRuntimeException on ???
273      * @throws GTUException on error during GTU construction
274      * @throws OTSGeometryException
275      * @throws ParameterException in case of a parameter problem.
276      * @throws OperationalPlanException 
277      */
278     private Length findDecisionPoint(Length low, Length high, final Speed referenceSpeed, final Speed speedDifference,
279         final LaneChangeModel laneChangeModel, final boolean mergeRight) throws NamingException, NetworkException,
280         SimRuntimeException, GTUException, OTSGeometryException, ParameterException, OperationalPlanException
281     {
282         // Set up the network
283         GTUType gtuType = new GTUType("car");
284         Set<GTUType> compatibility = new HashSet<>();
285         compatibility.add(gtuType);
286         LaneType laneType = new LaneType("CarLane", compatibility);
287         final Speed speedLimit = new Speed(120, KM_PER_HOUR);
288 
289         Lane[] lanes =
290             LaneFactory.makeMultiLane("Road with two lanes", new OTSNode("From", new OTSPoint3D(LOWERBOUND.getSI(), 0, 0)),
291                 new OTSNode("To", new OTSPoint3D(UPPERBOUND.getSI(), 0, 0)), null, 2, laneType, speedLimit, null,
292                 LongitudinalDirectionality.DIR_PLUS);
293         // Create the reference vehicle
294         Set<DirectedLanePosition> initialLongitudinalPositions = new LinkedHashSet<>(1);
295         initialLongitudinalPositions.add(new DirectedLanePosition(lanes[mergeRight ? 0 : 1], new Length(0, METER),
296             GTUDirectionality.DIR_PLUS));
297 
298         // The reference car only needs a simulator
299         // But that needs a model (which this class implements)
300         SimpleSimulator simpleSimulator =
301             new SimpleSimulator(new Time(0.0, SECOND), new Duration(0.0, SECOND), new Duration(3600.0, SECOND), this);
302         this.carFollowingModel =
303             new IDMPlusOld(new Acceleration(1, METER_PER_SECOND_2), new Acceleration(1.5, METER_PER_SECOND_2), new Length(2,
304                 METER), new Duration(1, SECOND), 1d);
305         this.carFollowingModel =
306             new IDMOld(new Acceleration(1, METER_PER_SECOND_2), new Acceleration(1.5, METER_PER_SECOND_2), new Length(2,
307                 METER), new Duration(1, SECOND), 1d);
308 
309         BehavioralCharacteristics behavioralCharacteristics = new BehavioralCharacteristics();
310         LaneBasedIndividualGTU referenceCar =
311             new LaneBasedIndividualGTU("ReferenceCar", gtuType, new Length(4, METER), new Length(2, METER), new Speed(150,
312                 KM_PER_HOUR), simpleSimulator, this.network);
313         LaneBasedStrategicalPlanner strategicalPlanner =
314             new LaneBasedStrategicalRoutePlanner(behavioralCharacteristics, new LaneBasedCFLCTacticalPlanner(
315                 this.carFollowingModel, laneChangeModel, referenceCar), referenceCar);
316         referenceCar.init(strategicalPlanner, initialLongitudinalPositions, referenceSpeed);
317         Collection<Headway> sameLaneGTUs = new LinkedHashSet<>();
318         sameLaneGTUs.add(new HeadwayGTUSimple(referenceCar.getId(), referenceCar.getGTUType(), Length.ZERO, referenceCar
319             .getLength(), referenceCar.getSpeed(), null));
320         // TODO play with the speed limit
321         // TODO play with the preferredLaneRouteIncentive
322         LaneMovementStep lowResult =
323             computeLaneChange(referenceCar, sameLaneGTUs, speedLimit, laneChangeModel, low, lanes[1], speedDifference,
324                 mergeRight);
325         LaneMovementStep highResult =
326             computeLaneChange(referenceCar, sameLaneGTUs, speedLimit, laneChangeModel, high, lanes[1], speedDifference,
327                 mergeRight);
328         Length mid = null;
329         if (lowResult.getLaneChange() != highResult.getLaneChange())
330         {
331             // Use bisection to home in onto the decision point
332             final double delta = 0.1; // [m]
333             final int stepsNeeded = (int) Math.ceil(Math.log(DoubleScalar.minus(high, low).getSI() / delta) / Math.log(2));
334             for (int step = 0; step < stepsNeeded; step++)
335             {
336                 Length mutableMid = low.plus(high).divideBy(2);
337                 mid = mutableMid;
338                 LaneMovementStep midResult =
339                     computeLaneChange(referenceCar, sameLaneGTUs, speedLimit, laneChangeModel, mid, lanes[1],
340                         speedDifference, mergeRight);
341                 // System.out.println(String.format ("mid %.2fm: %s", mid.getSI(), midResult));
342                 if (midResult.getLaneChange() != lowResult.getLaneChange())
343                 {
344                     high = mid;
345                     highResult = midResult;
346                 }
347                 else
348                 {
349                     low = mid;
350                     lowResult = midResult;
351                 }
352             }
353         }
354         else
355         {
356             // System.out.println("Bisection failed");
357             computeLaneChange(referenceCar, sameLaneGTUs, speedLimit, laneChangeModel, low, lanes[1], speedDifference,
358                 mergeRight);
359         }
360         return mid;
361     }
362 
363     /**
364      * @param referenceCar LaneBasedIndifidualCar&lt;String&gt;; the reference GTU
365      * @param sameLaneGTUs Collection&lt;HeadwayGTU&gt;; the set of GTUs in the same lane as the <cite>referenceCar</cite>
366      * @param speedLimit Speed; the speed limit
367      * @param laneChangeModel LaneChangeModel; the lane change model
368      * @param otherCarPosition Length; the position of the other car
369      * @param otherCarLane Lane; the lane of the other car
370      * @param deltaV Speed; the speed difference
371      * @param mergeRight boolean; if true; merging direction is to the right; if false; merging direction is to the left
372      * @return LaneMovementStep
373      * @throws NamingException on ???
374      * @throws SimRuntimeException on ???
375      * @throws NetworkException on network inconsistency
376      * @throws GTUException on error during GTU construction
377      * @throws OTSGeometryException when the initial position is outside the lane's center line
378      * @throws ParameterException in case of a parameter problem.
379      * @throws OperationalPlanException 
380      */
381     private LaneMovementStep computeLaneChange(final LaneBasedIndividualGTU referenceCar,
382         final Collection<Headway> sameLaneGTUs, final Speed speedLimit, final LaneChangeModel laneChangeModel,
383         final Length otherCarPosition, final Lane otherCarLane, final Speed deltaV, final boolean mergeRight)
384         throws NamingException, NetworkException, SimRuntimeException, GTUException, OTSGeometryException,
385         ParameterException, OperationalPlanException
386     {
387         Set<DirectedLanePosition> initialLongitudinalPositions = new LinkedHashSet<>(1);
388         initialLongitudinalPositions
389             .add(new DirectedLanePosition(otherCarLane, otherCarPosition, GTUDirectionality.DIR_PLUS));
390         BehavioralCharacteristics behavioralCharacteristics = new BehavioralCharacteristics();
391         LaneBasedIndividualGTU otherCar =
392             new LaneBasedIndividualGTU("otherCar", referenceCar.getGTUType(), new Length(4, METER), new Length(2, METER),
393                 new Speed(150, KM_PER_HOUR), referenceCar.getSimulator(), this.network);
394         LaneBasedStrategicalPlanner strategicalPlanner =
395             new LaneBasedStrategicalRoutePlanner(behavioralCharacteristics, new LaneBasedCFLCTacticalPlanner(
396                 this.carFollowingModel, laneChangeModel, otherCar), otherCar);
397         otherCar.init(strategicalPlanner, initialLongitudinalPositions, referenceCar.getSpeed().plus(deltaV));
398         Collection<Headway> preferredLaneGTUs = new LinkedHashSet<>();
399         Collection<Headway> nonPreferredLaneGTUs = new LinkedHashSet<>();
400         Length referenceCarPosition =
401             referenceCar.position(referenceCar.positions(referenceCar.getReference()).keySet().iterator().next(),
402                 referenceCar.getReference());
403         Headway otherHeadwayGTU =
404             new HeadwayGTUSimple(otherCar.getId(), otherCar.getGTUType(), otherCarPosition.minus(referenceCarPosition),
405                 otherCar.getLength(), otherCar.getSpeed(), null);
406         if (mergeRight)
407         {
408             preferredLaneGTUs.add(otherHeadwayGTU);
409         }
410         else
411         {
412             sameLaneGTUs.add(otherHeadwayGTU);
413         }
414         // System.out.println(referenceCar);
415         // System.out.println(otherCar);
416         LaneMovementStep result =
417             laneChangeModel.computeLaneChangeAndAcceleration(referenceCar, sameLaneGTUs, mergeRight ? preferredLaneGTUs
418                 : null, mergeRight ? null : nonPreferredLaneGTUs, speedLimit, new Acceleration(0.3, METER_PER_SECOND_2),
419                 new Acceleration(0.1, METER_PER_SECOND_2), new Acceleration(-0.3, METER_PER_SECOND_2));
420         // System.out.println(result);
421         sameLaneGTUs.remove(otherHeadwayGTU);
422         otherCar.destroy();
423         return result;
424     }
425 
426     /**
427      * @param caption String; the caption for the chart
428      * @param speed double; the speed of the reference vehicle
429      * @return new JFreeChart
430      */
431     private JFreeChart createChart(final String caption, final double speed)
432     {
433         ChartFactory.setChartTheme(new StandardChartTheme("JFree/Shadow", false));
434         ChartData chartData = new ChartData();
435         JFreeChart chartPanel =
436             ChartFactory.createXYLineChart(caption, "", "", chartData, PlotOrientation.VERTICAL, false, false, false);
437         NumberAxis xAxis = new NumberAxis("\u2192 " + "\u0394v (other car speed minus reference car speed) [km/h]");
438         xAxis.setAutoRangeIncludesZero(true);
439         double minimumDifference = -30;
440         xAxis.setRange(minimumDifference, minimumDifference + 60);
441         xAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
442         NumberAxis yAxis = new NumberAxis("\u2192 " + "gross headway (\u0394s) [m]");
443         yAxis.setAutoRangeIncludesZero(true);
444         yAxis.setRange(LOWERBOUND.getSI(), UPPERBOUND.getSI());
445         yAxis.setInverted(true);
446         chartPanel.getXYPlot().setDomainAxis(xAxis);
447         chartPanel.getXYPlot().setRangeAxis(yAxis);
448         final XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) chartPanel.getXYPlot().getRenderer();
449         renderer.setBaseLinesVisible(true);
450         renderer.setBaseShapesVisible(false);
451         renderer.setBaseShape(new Line2D.Float(0, 0, 0, 0));
452         return chartPanel;
453     }
454 
455     /** {@inheritDoc} */
456     @Override
457     public void constructModel(
458         final SimulatorInterface<Abs<TimeUnit>, DoubleScalar.Rel<TimeUnit>, OTSSimTimeDouble> simulator)
459         throws SimRuntimeException, RemoteException
460     {
461         // Do nothing
462     }
463 
464     /** {@inheritDoc} */
465     @Override
466     public final SimulatorInterface<Abs<TimeUnit>, DoubleScalar.Rel<TimeUnit>, OTSSimTimeDouble> getSimulator()
467         throws RemoteException
468     {
469         return null;
470     }
471 
472 }
473 
474 /** */
475 class ChartData implements XYDataset
476 {
477 
478     /** The X values. */
479     private ArrayList<ArrayList<Double>> xValues = new ArrayList<>();
480 
481     /** The Y values. */
482     private ArrayList<ArrayList<Double>> yValues = new ArrayList<>();
483 
484     /** The names of the series. */
485     private ArrayList<String> seriesKeys = new ArrayList<>();
486 
487     /** List of parties interested in changes of this ContourPlot. */
488     private transient EventListenerList listenerList = new EventListenerList();
489 
490     /** Not used internally. */
491     private DatasetGroup datasetGroup = null;
492 
493     /**
494      * Add storage for another series of XY values.
495      * @param seriesName String; the name of the new series
496      * @return int; the index to use to address the new series
497      */
498     public final int addSeries(final String seriesName)
499     {
500         this.xValues.add(new ArrayList<Double>());
501         this.yValues.add(new ArrayList<Double>());
502         this.seriesKeys.add(seriesName);
503         return this.xValues.size() - 1;
504     }
505 
506     /**
507      * Add an XY pair to the data.
508      * @param seriesKey int; key to the data series
509      * @param x double; x value of the pair
510      * @param y double; y value of the pair
511      */
512     public final void addXYPair(final int seriesKey, final double x, final double y)
513     {
514         this.xValues.get(seriesKey).add(x);
515         this.yValues.get(seriesKey).add(y);
516     }
517 
518     /** {@inheritDoc} */
519     @Override
520     public final int getSeriesCount()
521     {
522         return this.seriesKeys.size();
523     }
524 
525     /** {@inheritDoc} */
526     @Override
527     public final Comparable<?> getSeriesKey(final int series)
528     {
529         return this.seriesKeys.get(series);
530     }
531 
532     /** {@inheritDoc} */
533     @Override
534     public final int indexOf(@SuppressWarnings("rawtypes") final Comparable seriesKey)
535     {
536         return this.seriesKeys.indexOf(seriesKey);
537     }
538 
539     /** {@inheritDoc} */
540     @Override
541     public final void addChangeListener(final DatasetChangeListener listener)
542     {
543         this.listenerList.add(DatasetChangeListener.class, listener);
544     }
545 
546     /** {@inheritDoc} */
547     @Override
548     public final void removeChangeListener(final DatasetChangeListener listener)
549     {
550         this.listenerList.remove(DatasetChangeListener.class, listener);
551     }
552 
553     /** {@inheritDoc} */
554     @Override
555     public final DatasetGroup getGroup()
556     {
557         return this.datasetGroup;
558     }
559 
560     /** {@inheritDoc} */
561     @Override
562     public final void setGroup(final DatasetGroup group)
563     {
564         this.datasetGroup = group;
565     }
566 
567     /** {@inheritDoc} */
568     @Override
569     public final DomainOrder getDomainOrder()
570     {
571         return DomainOrder.ASCENDING;
572     }
573 
574     /** {@inheritDoc} */
575     @Override
576     public final int getItemCount(final int series)
577     {
578         return this.xValues.get(series).size();
579     }
580 
581     /** {@inheritDoc} */
582     @Override
583     public final Number getX(final int series, final int item)
584     {
585         return this.xValues.get(series).get(item);
586     }
587 
588     /** {@inheritDoc} */
589     @Override
590     public final double getXValue(final int series, final int item)
591     {
592         return this.xValues.get(series).get(item);
593     }
594 
595     /** {@inheritDoc} */
596     @Override
597     public final Number getY(final int series, final int item)
598     {
599         return this.yValues.get(series).get(item);
600     }
601 
602     /** {@inheritDoc} */
603     @Override
604     public final double getYValue(final int series, final int item)
605     {
606         return this.yValues.get(series).get(item);
607     }
608 
609 }