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