1 package org.opentrafficsim.graphs;
2
3 import java.awt.BorderLayout;
4 import java.awt.Color;
5 import java.awt.event.ActionEvent;
6 import java.awt.event.ActionListener;
7 import java.io.Serializable;
8 import java.rmi.RemoteException;
9 import java.text.NumberFormat;
10 import java.text.ParseException;
11 import java.util.ArrayList;
12 import java.util.HashSet;
13 import java.util.List;
14 import java.util.Locale;
15 import java.util.Set;
16
17 import javax.swing.ButtonGroup;
18 import javax.swing.JFrame;
19 import javax.swing.JLabel;
20 import javax.swing.JMenu;
21 import javax.swing.JPopupMenu;
22 import javax.swing.JRadioButtonMenuItem;
23 import javax.swing.SwingConstants;
24 import javax.swing.event.EventListenerList;
25
26 import org.djunits.unit.LengthUnit;
27 import org.djunits.unit.TimeUnit;
28 import org.djunits.value.StorageType;
29 import org.djunits.value.ValueException;
30 import org.djunits.value.vdouble.scalar.DoubleScalar;
31 import org.djunits.value.vdouble.scalar.Length;
32 import org.djunits.value.vdouble.scalar.Time;
33 import org.djunits.value.vdouble.vector.LengthVector;
34 import org.jfree.chart.ChartPanel;
35 import org.jfree.chart.JFreeChart;
36 import org.jfree.chart.LegendItem;
37 import org.jfree.chart.LegendItemCollection;
38 import org.jfree.chart.axis.NumberAxis;
39 import org.jfree.chart.event.PlotChangeEvent;
40 import org.jfree.chart.plot.XYPlot;
41 import org.jfree.chart.renderer.xy.XYBlockRenderer;
42 import org.jfree.data.DomainOrder;
43 import org.jfree.data.general.DatasetChangeEvent;
44 import org.jfree.data.general.DatasetChangeListener;
45 import org.jfree.data.general.DatasetGroup;
46 import org.jfree.data.xy.XYZDataset;
47 import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
48 import org.opentrafficsim.core.gtu.GTUException;
49 import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
50 import org.opentrafficsim.road.network.lane.Lane;
51 import org.opentrafficsim.simulationengine.OTSSimulationException;
52
53 import nl.tudelft.simulation.event.EventInterface;
54 import nl.tudelft.simulation.event.EventListenerInterface;
55 import nl.tudelft.simulation.event.TimedEvent;
56
57
58
59
60
61
62
63
64
65
66
67
68 public abstract class ContourPlot extends JFrame
69 implements ActionListener, XYZDataset, MultipleViewerChart, LaneBasedGTUSampler, EventListenerInterface, Serializable
70 {
71
72 private static final long serialVersionUID = 20140716L;
73
74
75 private final String caption;
76
77
78 private final ContinuousColorPaintScale paintScale;
79
80
81 @SuppressWarnings("visibilitymodifier")
82 protected final Axis xAxis;
83
84
85 @SuppressWarnings("visibilitymodifier")
86 protected final Axis yAxis;
87
88
89 private final double legendStep;
90
91
92 private final String legendFormat;
93
94
95 protected static final double[] STANDARDTIMEGRANULARITIES = { 1, 2, 5, 10, 20, 30, 60, 120, 300, 600 };
96
97
98 protected static final int STANDARDINITIALTIMEGRANULARITYINDEX = 3;
99
100
101 protected static final double[] STANDARDDISTANCEGRANULARITIES = { 10, 20, 50, 100, 200, 500, 1000 };
102
103
104 protected static final int STANDARDINITIALDISTANCEGRANULARITYINDEX = 3;
105
106
107 protected static final Time INITIALLOWERTIMEBOUND = new Time(0, TimeUnit.SECOND);
108
109
110 protected static final Time INITIALUPPERTIMEBOUND = new Time(300, TimeUnit.SECOND);
111
112
113 private final ArrayList<Lane> path;
114
115
116 private final LengthVector cumulativeLengths;
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131 public ContourPlot(final String caption, final Axis xAxis, final List<Lane> path, final double redValue,
132 final double yellowValue, final double greenValue, final String valueFormat, final String legendFormat,
133 final double legendStep) throws OTSSimulationException
134 {
135 this.caption = caption;
136 this.path = new ArrayList<Lane>(path);
137 double[] endLengths = new double[path.size()];
138 double cumulativeLength = 0;
139 LengthVector lengths = null;
140 for (int i = 0; i < path.size(); i++)
141 {
142 Lane lane = path.get(i);
143 lane.addListener(this, Lane.GTU_ADD_EVENT, true);
144 lane.addListener(this, Lane.GTU_REMOVE_EVENT, true);
145 try
146 {
147
148 for (LaneBasedGTU gtu : lane.getGtuList())
149 {
150 notify(new TimedEvent<OTSSimTimeDouble>(Lane.GTU_ADD_EVENT, lane, new Object[] { gtu.getId(), gtu },
151 gtu.getSimulator().getSimulatorTime()));
152 }
153 }
154 catch (RemoteException exception)
155 {
156 exception.printStackTrace();
157 }
158 cumulativeLength += lane.getLength().getSI();
159 endLengths[i] = cumulativeLength;
160 }
161 try
162 {
163 lengths = new LengthVector(endLengths, LengthUnit.SI, StorageType.DENSE);
164 }
165 catch (ValueException exception)
166 {
167 exception.printStackTrace();
168 }
169 this.cumulativeLengths = lengths;
170 this.xAxis = xAxis;
171 this.yAxis = new Axis(new Length(0, LengthUnit.METER), getCumulativeLength(-1), STANDARDDISTANCEGRANULARITIES,
172 STANDARDDISTANCEGRANULARITIES[STANDARDINITIALDISTANCEGRANULARITYINDEX], "", "Distance", "%.0fm");
173 this.legendStep = legendStep;
174 this.legendFormat = legendFormat;
175 extendXRange(xAxis.getMaximumValue());
176 double[] boundaries = { redValue, yellowValue, greenValue };
177 final Color[] colorValues = { Color.RED, Color.YELLOW, Color.GREEN };
178 this.paintScale = new ContinuousColorPaintScale(valueFormat, boundaries, colorValues);
179 createChart(this);
180 reGraph();
181 }
182
183
184 private Set<LaneBasedGTU> gtusOfInterest = new HashSet<>();
185
186
187 @Override
188 @SuppressWarnings("checkstyle:designforextension")
189 public void notify(final EventInterface event) throws RemoteException
190 {
191 LaneBasedGTU gtu;
192 if (event.getType().equals(Lane.GTU_ADD_EVENT))
193 {
194 Object[] content = (Object[]) event.getContent();
195 gtu = (LaneBasedGTU) content[1];
196 if (!this.gtusOfInterest.contains(gtu))
197 {
198 this.gtusOfInterest.add(gtu);
199 gtu.addListener(this, LaneBasedGTU.MOVE_EVENT);
200 }
201 }
202 else if (event.getType().equals(Lane.GTU_REMOVE_EVENT))
203 {
204 Object[] content = (Object[]) event.getContent();
205 gtu = (LaneBasedGTU) content[1];
206 boolean interest = false;
207 for (Lane lane : gtu.getLanes().keySet())
208 {
209 if (this.path.contains(lane))
210 {
211 interest = true;
212 }
213 }
214 if (!interest)
215 {
216 this.gtusOfInterest.remove(gtu);
217 gtu.removeListener(this, LaneBasedGTU.MOVE_EVENT);
218 }
219 }
220 else if (event.getType().equals(LaneBasedGTU.MOVE_EVENT))
221 {
222 Object[] content = (Object[]) event.getContent();
223 Lane lane = (Lane) content[6];
224 gtu = (LaneBasedGTU) event.getSource();
225 addData(gtu, lane);
226 }
227 }
228
229
230
231
232
233
234 public final Length getCumulativeLength(final int index)
235 {
236 int useIndex = -1 == index ? this.cumulativeLengths.size() - 1 : index;
237 try
238 {
239 return new Length(this.cumulativeLengths.get(useIndex));
240 }
241 catch (ValueException exception)
242 {
243 exception.printStackTrace();
244 }
245 return null;
246 }
247
248
249
250
251
252
253
254
255
256
257 private JMenu buildMenu(final String menuName, final String format, final String commandPrefix, final double[] values,
258 final double currentValue)
259 {
260 final JMenu result = new JMenu(menuName);
261
262 final ButtonGroup group = new ButtonGroup();
263 for (double value : values)
264 {
265 final JRadioButtonMenuItem item = new JRadioButtonMenuItem(String.format(format, value));
266 item.setSelected(value == currentValue);
267 item.setActionCommand(commandPrefix + String.format(Locale.US, " %f", value));
268 item.addActionListener(this);
269 result.add(item);
270 group.add(item);
271 }
272 return result;
273 }
274
275
276
277
278
279
280 private JFreeChart createChart(final JFrame container)
281 {
282 final JLabel statusLabel = new JLabel(" ", SwingConstants.CENTER);
283 container.add(statusLabel, BorderLayout.SOUTH);
284 final NumberAxis xAxis1 = new NumberAxis("\u2192 " + "time [s]");
285 xAxis1.setLowerMargin(0.0);
286 xAxis1.setUpperMargin(0.0);
287 final NumberAxis yAxis1 = new NumberAxis("\u2192 " + "Distance [m]");
288 yAxis1.setAutoRangeIncludesZero(false);
289 yAxis1.setLowerMargin(0.0);
290 yAxis1.setUpperMargin(0.0);
291 yAxis1.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
292 XYBlockRenderer renderer = new XYBlockRenderer();
293 renderer.setPaintScale(this.paintScale);
294 final XYPlot plot = new XYPlot(this, xAxis1, yAxis1, renderer);
295 final LegendItemCollection legend = new LegendItemCollection();
296 for (int i = 0;; i++)
297 {
298 double value = this.paintScale.getLowerBound() + i * this.legendStep;
299 if (value > this.paintScale.getUpperBound())
300 {
301 break;
302 }
303 legend.add(new LegendItem(String.format(this.legendFormat, value), this.paintScale.getPaint(value)));
304 }
305 legend.add(new LegendItem("No data", Color.BLACK));
306 plot.setFixedLegendItems(legend);
307 plot.setBackgroundPaint(Color.lightGray);
308 plot.setDomainGridlinePaint(Color.white);
309 plot.setRangeGridlinePaint(Color.white);
310 final JFreeChart chart = new JFreeChart(this.caption, plot);
311 FixCaption.fixCaption(chart);
312 chart.setBackgroundPaint(Color.white);
313 final ChartPanel cp = new ChartPanel(chart);
314 final PointerHandler ph = new PointerHandler()
315 {
316
317 @Override
318 void updateHint(final double domainValue, final double rangeValue)
319 {
320 if (Double.isNaN(domainValue))
321 {
322 statusLabel.setText(" ");
323 return;
324 }
325
326 XYZDataset dataset = (XYZDataset) plot.getDataset();
327 String value = "";
328 double roundedTime = domainValue;
329 double roundedDistance = rangeValue;
330 for (int item = dataset.getItemCount(0); --item >= 0;)
331 {
332 double x = dataset.getXValue(0, item);
333 if (x + ContourPlot.this.xAxis.getCurrentGranularity() / 2 < domainValue
334 || x - ContourPlot.this.xAxis.getCurrentGranularity() / 2 >= domainValue)
335 {
336 continue;
337 }
338 double y = dataset.getYValue(0, item);
339 if (y + ContourPlot.this.yAxis.getCurrentGranularity() / 2 < rangeValue
340 || y - ContourPlot.this.yAxis.getCurrentGranularity() / 2 >= rangeValue)
341 {
342 continue;
343 }
344 roundedTime = x;
345 roundedDistance = y;
346 double valueUnderMouse = dataset.getZValue(0, item);
347
348 if (Double.isNaN(valueUnderMouse))
349 {
350 break;
351 }
352 String format = ((ContinuousColorPaintScale) (((XYBlockRenderer) (plot.getRenderer(0))).getPaintScale()))
353 .getFormat();
354 value = String.format(format, valueUnderMouse);
355 }
356 statusLabel.setText(String.format("time %.0fs, distance %.0fm, %s", roundedTime, roundedDistance, value));
357 }
358
359 };
360 cp.addMouseMotionListener(ph);
361 cp.addMouseListener(ph);
362 container.add(cp, BorderLayout.CENTER);
363 cp.setMouseWheelEnabled(true);
364 JPopupMenu popupMenu = cp.getPopupMenu();
365 popupMenu.add(new JPopupMenu.Separator());
366 popupMenu.add(StandAloneChartWindow.createMenuItem(this));
367 popupMenu.insert(buildMenu("Distance granularity", "%.0f m", "setDistanceGranularity", this.yAxis.getGranularities(),
368 this.yAxis.getCurrentGranularity()), 0);
369 popupMenu.insert(buildMenu("Time granularity", "%.0f s", "setTimeGranularity", this.xAxis.getGranularities(),
370 this.xAxis.getCurrentGranularity()), 1);
371 return chart;
372 }
373
374
375 @Override
376 public final void actionPerformed(final ActionEvent actionEvent)
377 {
378 final String command = actionEvent.getActionCommand();
379
380 String[] fields = command.split("[ ]");
381 if (fields.length == 2)
382 {
383 final NumberFormat nf = NumberFormat.getInstance(Locale.US);
384 double value;
385 try
386 {
387 value = nf.parse(fields[1]).doubleValue();
388 }
389 catch (ParseException e)
390 {
391 throw new RuntimeException("Bad value: " + fields[1]);
392 }
393 if (fields[0].equalsIgnoreCase("setDistanceGranularity"))
394 {
395 this.getYAxis().setCurrentGranularity(value);
396 clearCachedValues();
397 }
398 else if (fields[0].equalsIgnoreCase("setTimeGranularity"))
399 {
400 this.getXAxis().setCurrentGranularity(value);
401 clearCachedValues();
402 }
403 else
404 {
405 throw new RuntimeException("Unknown ActionEvent");
406 }
407 reGraph();
408 }
409 else
410 {
411 throw new RuntimeException("Unknown ActionEvent: " + command);
412 }
413 }
414
415
416
417
418 public final void reGraph()
419 {
420 for (DatasetChangeListener dcl : this.listenerList.getListeners(DatasetChangeListener.class))
421 {
422 if (dcl instanceof XYPlot)
423 {
424 final XYPlot plot = (XYPlot) dcl;
425 plot.notifyListeners(new PlotChangeEvent(plot));
426 final XYBlockRenderer blockRenderer = (XYBlockRenderer) plot.getRenderer();
427 blockRenderer.setBlockHeight(this.getYAxis().getCurrentGranularity());
428 blockRenderer.setBlockWidth(this.getXAxis().getCurrentGranularity());
429
430 }
431 }
432 notifyListeners(new DatasetChangeEvent(this, null));
433 }
434
435
436
437
438
439 private void notifyListeners(final DatasetChangeEvent event)
440 {
441 for (DatasetChangeListener dcl : this.listenerList.getListeners(DatasetChangeListener.class))
442 {
443 dcl.datasetChanged(event);
444 }
445 }
446
447
448 private transient EventListenerList listenerList = new EventListenerList();
449
450
451 @Override
452 public final int getSeriesCount()
453 {
454 return 1;
455 }
456
457
458 private int cachedYAxisBins = -1;
459
460
461
462
463
464 protected final int yAxisBins()
465 {
466 if (this.cachedYAxisBins >= 0)
467 {
468 return this.cachedYAxisBins;
469 }
470 this.cachedYAxisBins = this.getYAxis().getAggregatedBinCount();
471 return this.cachedYAxisBins;
472 }
473
474
475
476
477
478
479
480 protected final int yAxisBin(final int item)
481 {
482 int maxItem = getItemCount(0);
483 if (item < 0 || item >= maxItem)
484 {
485 throw new RuntimeException("yAxisBin: item out of range (value is " + item + "), valid range is 0.." + maxItem);
486 }
487 return item % yAxisBins();
488 }
489
490
491
492
493
494
495
496 protected final int xAxisBin(final int item)
497 {
498 int maxItem = getItemCount(0);
499 if (item < 0 || item >= maxItem)
500 {
501 throw new RuntimeException("xAxisBin: item out of range (value is " + item + "), valid range is 0.." + maxItem);
502 }
503 return item / yAxisBins();
504 }
505
506
507 private int cachedXAxisBins = -1;
508
509
510
511
512
513 protected final int xAxisBins()
514 {
515 if (this.cachedXAxisBins >= 0)
516 {
517 return this.cachedXAxisBins;
518 }
519 this.cachedXAxisBins = this.getXAxis().getAggregatedBinCount();
520 return this.cachedXAxisBins;
521 }
522
523
524 private int cachedItemCount = -1;
525
526
527 @Override
528 public final int getItemCount(final int series)
529 {
530 if (this.cachedItemCount >= 0)
531 {
532 return this.cachedItemCount;
533 }
534 this.cachedItemCount = yAxisBins() * xAxisBins();
535 return this.cachedItemCount;
536 }
537
538
539 @Override
540 public final Number getX(final int series, final int item)
541 {
542 return getXValue(series, item);
543 }
544
545
546 @Override
547 public final double getXValue(final int series, final int item)
548 {
549 double result = this.getXAxis().getValue(xAxisBin(item));
550
551
552 return result;
553 }
554
555
556 @Override
557 public final Number getY(final int series, final int item)
558 {
559 return getYValue(series, item);
560 }
561
562
563 @Override
564 public final double getYValue(final int series, final int item)
565 {
566 return this.getYAxis().getValue(yAxisBin(item));
567 }
568
569
570 @Override
571 public final Number getZ(final int series, final int item)
572 {
573 return getZValue(series, item);
574 }
575
576
577 @Override
578 public final void addChangeListener(final DatasetChangeListener listener)
579 {
580 this.listenerList.add(DatasetChangeListener.class, listener);
581 }
582
583
584 @Override
585 public final void removeChangeListener(final DatasetChangeListener listener)
586 {
587 this.listenerList.remove(DatasetChangeListener.class, listener);
588 }
589
590
591 @Override
592 public final DatasetGroup getGroup()
593 {
594 return null;
595 }
596
597
598 @Override
599 public void setGroup(final DatasetGroup group)
600 {
601
602 }
603
604
605 @SuppressWarnings("rawtypes")
606 @Override
607 public final int indexOf(final Comparable seriesKey)
608 {
609 return 0;
610 }
611
612
613 @Override
614 public final DomainOrder getDomainOrder()
615 {
616 return DomainOrder.ASCENDING;
617 }
618
619
620
621
622 private void clearCachedValues()
623 {
624 this.cachedItemCount = -1;
625 this.cachedXAxisBins = -1;
626 this.cachedYAxisBins = -1;
627 }
628
629
630
631
632
633
634 protected final void addData(final LaneBasedGTU gtu, final Lane lane)
635 {
636
637
638
639 double lengthOffset = 0;
640 int index = this.path.indexOf(lane);
641 if (index >= 0)
642 {
643 if (index > 0)
644 {
645 try
646 {
647 lengthOffset = this.cumulativeLengths.getSI(index - 1);
648 }
649 catch (ValueException exception)
650 {
651
652 System.err.println("ContourPlot: GTU " + gtu.getId() + " on lane " + lane.toString() + " caused exception "
653 + exception.getMessage());
654 }
655 }
656 }
657 else
658 {
659
660 System.err.println("ContouryPlot: GTU " + gtu.getId() + " is not registered on lane " + lane.toString());
661 }
662
663 try
664 {
665 final Time fromTime = gtu.getOperationalPlan().getStartTime();
666 if (gtu.position(lane, gtu.getRear(), fromTime).getSI() < 0 && lengthOffset > 0)
667 {
668 return;
669 }
670 final Time toTime = gtu.getOperationalPlan().getEndTime();
671 if (toTime.getSI() > this.getXAxis().getMaximumValue().getSI())
672 {
673 extendXRange(toTime);
674 clearCachedValues();
675 this.getXAxis().adjustMaximumValue(toTime);
676 }
677 if (toTime.le(fromTime))
678 {
679 return;
680 }
681
682 final double relativeFromDistance = (gtu.position(lane, gtu.getRear(), fromTime).getSI() + lengthOffset)
683 / this.getYAxis().getGranularities()[0];
684 final double relativeToDistance =
685 (gtu.position(lane, gtu.getRear(), toTime).getSI() + lengthOffset) / this.getYAxis().getGranularities()[0];
686 double relativeFromTime =
687 (fromTime.getSI() - this.getXAxis().getMinimumValue().getSI()) / this.getXAxis().getGranularities()[0];
688 final double relativeToTime =
689 (toTime.getSI() - this.getXAxis().getMinimumValue().getSI()) / this.getXAxis().getGranularities()[0];
690 final int fromTimeBin = (int) Math.floor(relativeFromTime);
691 final int toTimeBin = (int) Math.floor(relativeToTime) + 1;
692 double relativeMeanSpeed = (relativeToDistance - relativeFromDistance) / (relativeToTime - relativeFromTime);
693
694
695 double acceleration = gtu.getAcceleration().getSI();
696 for (int timeBin = fromTimeBin; timeBin < toTimeBin; timeBin++)
697 {
698 if (timeBin < 0)
699 {
700 continue;
701 }
702 double binEndTime = timeBin + 1;
703 if (binEndTime > relativeToTime)
704 {
705 binEndTime = relativeToTime;
706 }
707 if (binEndTime <= relativeFromTime)
708 {
709 continue;
710 }
711 double binDistanceStart = (gtu
712 .position(lane, gtu.getRear(),
713 new Time(relativeFromTime * this.getXAxis().getGranularities()[0], TimeUnit.SECOND))
714 .getSI() - this.getYAxis().getMinimumValue().getSI() + lengthOffset)
715 / this.getYAxis().getGranularities()[0];
716 double binDistanceEnd = (gtu
717 .position(lane, gtu.getRear(),
718 new Time(binEndTime * this.getXAxis().getGranularities()[0], TimeUnit.SECOND))
719 .getSI() - this.getYAxis().getMinimumValue().getSI() + lengthOffset)
720 / this.getYAxis().getGranularities()[0];
721
722
723 for (int distanceBin = (int) Math.floor(binDistanceStart); distanceBin <= binDistanceEnd; distanceBin++)
724 {
725 double relativeDuration = 1;
726 if (relativeFromTime > timeBin)
727 {
728 relativeDuration -= relativeFromTime - timeBin;
729 }
730 if (distanceBin == (int) Math.floor(binDistanceEnd))
731 {
732
733 if (binEndTime < timeBin + 1)
734 {
735 relativeDuration -= timeBin + 1 - binEndTime;
736 }
737 }
738 else
739 {
740
741
742
743 double timeToBinBoundary = (distanceBin + 1 - binDistanceStart) / relativeMeanSpeed;
744 double endTime = relativeFromTime + timeToBinBoundary;
745 relativeDuration -= timeBin + 1 - endTime;
746 }
747 final double duration = relativeDuration * this.getXAxis().getGranularities()[0];
748 final double distance = duration * relativeMeanSpeed * this.getYAxis().getGranularities()[0];
749
750
751
752
753 incrementBinData(timeBin, distanceBin, duration, distance, acceleration);
754 relativeFromTime += relativeDuration;
755 binDistanceStart = distanceBin + 1;
756 }
757 relativeFromTime = timeBin + 1;
758 }
759 }
760 catch (GTUException exception)
761 {
762
763 System.err.println("ContourPlot: GTU " + gtu.getId() + " on lane " + lane.toString() + " caused exception "
764 + exception.getMessage());
765 }
766 }
767
768
769
770
771
772
773 public abstract void extendXRange(DoubleScalar<?> newUpperLimit);
774
775
776
777
778
779
780
781
782
783 public abstract void incrementBinData(int timeBin, int distanceBin, double duration, double distanceCovered,
784 double acceleration);
785
786
787 @Override
788 public final double getZValue(final int series, final int item)
789 {
790 final int timeBinGroup = xAxisBin(item);
791 final int distanceBinGroup = yAxisBin(item);
792
793
794 final int timeGroupSize = (int) (this.getXAxis().getCurrentGranularity() / this.getXAxis().getGranularities()[0]);
795 final int firstTimeBin = timeBinGroup * timeGroupSize;
796 final int distanceGroupSize = (int) (this.getYAxis().getCurrentGranularity() / this.getYAxis().getGranularities()[0]);
797 final int firstDistanceBin = distanceBinGroup * distanceGroupSize;
798 final int endTimeBin = Math.min(firstTimeBin + timeGroupSize, this.getXAxis().getBinCount());
799 final int endDistanceBin = Math.min(firstDistanceBin + distanceGroupSize, this.getYAxis().getBinCount());
800 return computeZValue(firstTimeBin, endTimeBin, firstDistanceBin, endDistanceBin);
801 }
802
803
804
805
806
807
808
809
810
811 public abstract double computeZValue(int firstTimeBin, int endTimeBin, int firstDistanceBin, int endDistanceBin);
812
813
814
815
816
817 public final Axis getXAxis()
818 {
819 return this.xAxis;
820 }
821
822
823
824
825
826 public final Axis getYAxis()
827 {
828 return this.yAxis;
829 }
830
831
832 @Override
833 public final JFrame addViewer()
834 {
835 JFrame result = new JFrame(this.caption);
836 JFreeChart newChart = createChart(result);
837 newChart.setTitle((String) null);
838 addChangeListener(newChart.getPlot());
839 reGraph();
840 return result;
841 }
842
843 }