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