001    /*
002     * This file is part of McIDAS-V
003     *
004     * Copyright 2007-2013
005     * Space Science and Engineering Center (SSEC)
006     * University of Wisconsin - Madison
007     * 1225 W. Dayton Street, Madison, WI 53706, USA
008     * https://www.ssec.wisc.edu/mcidas
009     * 
010     * All Rights Reserved
011     * 
012     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013     * some McIDAS-V source code is based on IDV and VisAD source code.  
014     * 
015     * McIDAS-V is free software; you can redistribute it and/or modify
016     * it under the terms of the GNU Lesser Public License as published by
017     * the Free Software Foundation; either version 3 of the License, or
018     * (at your option) any later version.
019     * 
020     * McIDAS-V is distributed in the hope that it will be useful,
021     * but WITHOUT ANY WARRANTY; without even the implied warranty of
022     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023     * GNU Lesser Public License for more details.
024     * 
025     * You should have received a copy of the GNU Lesser Public License
026     * along with this program.  If not, see http://www.gnu.org/licenses.
027     */
028    
029    package edu.wisc.ssec.mcidasv.control.cyclone;
030    
031    import java.awt.BorderLayout;
032    import java.awt.Dimension;
033    import java.awt.Insets;
034    import java.awt.event.ActionEvent;
035    import java.awt.event.ActionListener;
036    import java.util.ArrayList;
037    import java.util.Hashtable;
038    import java.util.List;
039    import java.util.Vector;
040    
041    import javax.swing.BorderFactory;
042    import javax.swing.JButton;
043    import javax.swing.JCheckBox;
044    import javax.swing.JComponent;
045    import javax.swing.JLabel;
046    import javax.swing.JList;
047    import javax.swing.JPanel;
048    import javax.swing.JScrollPane;
049    import javax.swing.ListSelectionModel;
050    import javax.swing.event.ListSelectionEvent;
051    import javax.swing.event.ListSelectionListener;
052    
053    import ucar.unidata.data.storm.StormDataSource;
054    import ucar.unidata.data.storm.StormParam;
055    import ucar.unidata.data.storm.StormTrack;
056    import ucar.unidata.data.storm.StormTrackPoint;
057    import ucar.unidata.data.storm.Way;
058    import ucar.unidata.idv.control.chart.LineState;
059    import ucar.unidata.idv.control.chart.TimeSeriesChart;
060    import ucar.unidata.util.GuiUtils;
061    import ucar.unidata.util.Misc;
062    import ucar.unidata.util.Range;
063    import ucar.unidata.util.TwoFacedObject;
064    import visad.DateTime;
065    import visad.Real;
066    import visad.RealType;
067    import visad.Unit;
068    
069    /**
070     * 
071     * @author Unidata Development Team
072     * @version $Revision$
073     */
074    
075    public class StormTrackChart {
076    
077            /** _more_ */
078            public static final int MODE_FORECASTTIME = 0;
079    
080            /** _more_ */
081            public static final int MODE_FORECASTHOUR = 1;
082    
083            /** _more_ */
084            private StormDisplayState stormDisplayState;
085    
086            /** _more_ */
087            private int mode;
088    
089            /** _more_ */
090            private TimeSeriesChart timeSeries;
091    
092            /** _more_ */
093            private JPanel chartLeft;
094    
095            /** _more_ */
096            private JPanel chartTop;
097    
098            /** _more_ */
099            private boolean madeChart = false;
100    
101            /** _more_ */
102            private List<DateTime> forecastTimes;
103    
104            /** _more_ */
105            private List<Integer> forecastHours;
106    
107            /** _more_ */
108            private List<StormParam> chartParams = new ArrayList<StormParam>();
109    
110            /** _more_ */
111            private List<Way> chartWays = new ArrayList();
112    
113            /** _more_ */
114            private boolean chartDifference = false;
115    
116            /** _more_ */
117            private JList chartTimeBox;
118    
119            /** _more_ */
120            private boolean ignoreChartTimeChanges = false;
121    
122            /** _more_ */
123            private JComponent contents;
124    
125            /** _more_ */
126            private String name;
127    
128            /**
129             * _more_
130             */
131            public StormTrackChart() {
132            }
133    
134            /**
135             * _more_
136             * 
137             * @param stormDisplayState
138             *            _more_
139             * @param name
140             *            _more_
141             * 
142             */
143            public StormTrackChart(StormDisplayState stormDisplayState, String name) {
144                    this(stormDisplayState, name, MODE_FORECASTTIME);
145            }
146    
147            /**
148             * _more_
149             * 
150             * @param stormDisplayState
151             *            _more_
152             * @param name
153             *            _more_
154             * @param mode
155             *            _more_
156             */
157            public StormTrackChart(StormDisplayState stormDisplayState, String name,
158                            int mode) {
159                    this.stormDisplayState = stormDisplayState;
160                    this.name = name;
161                    this.mode = mode;
162            }
163    
164            /**
165             * _more_
166             * 
167             * @return _more_
168             */
169            protected boolean isHourly() {
170                    return mode == MODE_FORECASTHOUR;
171            }
172    
173            /**
174             * _more_
175             */
176            public void deactivate() {
177                    madeChart = false;
178                    contents = null;
179            }
180    
181            /**
182             * _more_
183             * 
184             * @return _more_
185             */
186            protected JComponent getContents() {
187                    if (contents == null) {
188                            contents = doMakeContents();
189                    }
190                    return contents;
191            }
192    
193            /**
194             * _more_
195             * 
196             * @return _more_
197             */
198            private JComponent doMakeContents() {
199                    chartTop = new JPanel(new BorderLayout());
200                    chartLeft = new JPanel(new BorderLayout());
201                    JComponent chartComp = GuiUtils.leftCenter(chartLeft, GuiUtils
202                                    .topCenter(chartTop, getChart().getContents()));
203                    return chartComp;
204            }
205    
206            /**
207             * Get the chart
208             * 
209             * @return The chart_
210             */
211            public TimeSeriesChart getChart() {
212                    if (timeSeries == null) {
213                            timeSeries = new TimeSeriesChart(stormDisplayState
214                                            .getStormTrackControl(), "Track Charts") {
215                                    protected void makeInitialChart() {
216                                    }
217                            };
218                            // TODO make this work:
219                            // timeSeries.setAlwaysShowFirstChart(false);
220                            timeSeries.showAnimationTime(true);
221                            timeSeries.setDateFormat("MM/dd HH:mm z");
222                    }
223                    return timeSeries;
224            }
225    
226            /**
227             * _more_
228             * 
229             * @param time
230             *            _more_
231             */
232            protected void timeChanged(Real time) {
233                    getChart().timeChanged();
234            }
235    
236            /**
237             * _more_
238             * 
239             * @return _more_
240             */
241            private List<DateTime> findForecastTimes() {
242                    List<DateTime> forecastTimes = new ArrayList<DateTime>();
243                    Hashtable seenForecastTime = new Hashtable();
244                    for (StormTrack track : stormDisplayState.getTrackCollection()
245                                    .getTracks()) {
246                            if (track.getWay().isObservation()) {
247                                    continue;
248                            }
249                            if (!chartWays.contains(track.getWay())) {
250                                    continue;
251                            }
252                            DateTime dttm = track.getStartTime();
253                            if (seenForecastTime.get(dttm) == null) {
254                                    seenForecastTime.put(dttm, dttm);
255                                    forecastTimes.add(dttm);
256                            }
257                    }
258                    return (List<DateTime>) Misc.sort(forecastTimes);
259            }
260    
261            /**
262             * _more_
263             * 
264             * @return _more_
265             */
266            private List<Integer> findForecastHours() {
267                    List<Integer> forecastHours = new ArrayList<Integer>();
268                    Hashtable seenForecastHour = new Hashtable();
269                    Hashtable seen = new Hashtable();
270                    for (StormTrack track : stormDisplayState.getTrackCollection()
271                                    .getTracks()) {
272                            if (track.getWay().isObservation()) {
273                                    continue;
274                            }
275                            if (!chartWays.contains(track.getWay())) {
276                                    continue;
277                            }
278                            for (StormTrackPoint stormTrackPoint : track.getTrackPoints()) {
279                                    Integer forecastHour = new Integer(stormTrackPoint
280                                                    .getForecastHour());
281                                    if (seenForecastHour.get(forecastHour) == null) {
282                                            seenForecastHour.put(forecastHour, forecastHour);
283                                            forecastHours.add(forecastHour);
284                                    }
285                            }
286                    }
287                    return (List<Integer>) Misc.sort(forecastHours);
288            }
289    
290            /**
291             * _more_
292             */
293            protected void createChart() {
294    
295                    if (madeChart) {
296                            return;
297                    }
298                    madeChart = true;
299                    final JCheckBox chartDiffCbx = new JCheckBox("Use Difference",
300                                    chartDifference);
301                    chartDiffCbx.addActionListener(new ActionListener() {
302                            public void actionPerformed(ActionEvent ae) {
303                                    chartDifference = chartDiffCbx.isSelected();
304                                    updateChart();
305                            }
306                    });
307    
308                    chartTimeBox = new JList();
309                    chartTimeBox.setVisibleRowCount(3);
310                    chartTimeBox
311                                    .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
312                    chartTimeBox.addListSelectionListener(new ListSelectionListener() {
313                            public void valueChanged(ListSelectionEvent e) {
314                                    if (ignoreChartTimeChanges) {
315                                            return;
316                                    }
317    
318                                    updateChart();
319                            }
320                    });
321    
322                    List<StormParam> params = getStormTrackParams();
323    
324                    Insets inset = new Insets(3, 7, 7, 0);
325                    List chartComps = new ArrayList();
326    
327                    List<Way> ways = Misc.sort(stormDisplayState.getTrackCollection()
328                                    .getWayList());
329                    List wayComps = new ArrayList();
330                    for (Way way : ways) {
331                            final Way theWay = way;
332                            if (way.isObservation() && !chartWays.contains(way)) {
333                                    chartWays.add(way);
334                            }
335                            final JCheckBox cbx = new JCheckBox(way.toString(), chartWays
336                                            .contains(theWay));
337                            cbx.addActionListener(new ActionListener() {
338                                    public void actionPerformed(ActionEvent ae) {
339                                            if (cbx.isSelected()) {
340                                                    addChartWay(theWay);
341                                            } else {
342                                                    removeChartWay(theWay);
343                                            }
344                                    }
345                            });
346                            if (!way.isObservation()) {
347                                    wayComps.add(cbx);
348                            } else {
349                                    wayComps.add(0, cbx);
350                            }
351                    }
352    
353                    chartComps.add(new JLabel(stormDisplayState.getStormTrackControl()
354                                    .getWaysName()
355                                    + ":"));
356                    JComponent chartWayComp = GuiUtils.vbox(wayComps);
357                    if (wayComps.size() > 6) {
358                            chartWayComp = makeScroller(chartWayComp, 100, 150);
359                    }
360                    chartComps.add(GuiUtils.inset(chartWayComp, inset));
361                    chartComps.add(GuiUtils.lLabel((isHourly() ? "Forecast Hour:"
362                                    : "Forecast Time:")));
363                    JScrollPane sp = new JScrollPane(chartTimeBox);
364                    chartComps.add(GuiUtils.inset(sp, inset));
365    
366                    List paramComps = new ArrayList();
367                    for (StormParam param : params) {
368                            // if (param.getIsChartParam() == false) {
369                            // continue;
370                            // }
371                            final StormParam theParam = param;
372                            boolean useChartParam = chartParams.contains(theParam);
373                            final JCheckBox cbx = new JCheckBox(param.toString(), useChartParam);
374                            cbx.addActionListener(new ActionListener() {
375                                    public void actionPerformed(ActionEvent ae) {
376                                            if (cbx.isSelected()) {
377                                                    addChartParam(theParam);
378                                            } else {
379                                                    removeChartParam(theParam);
380                                            }
381                                    }
382                            });
383                            paramComps.add(cbx);
384                    }
385                    chartComps.add(new JLabel("Parameters:"));
386                    JComponent paramComp = GuiUtils.vbox(paramComps);
387                    if (paramComps.size() > 6) {
388                            paramComp = makeScroller(paramComp, 100, 150);
389                    }
390                    chartComps.add(GuiUtils.inset(paramComp, inset));
391                    chartComps.add(chartDiffCbx);
392    
393                    JButton removeBtn = GuiUtils.makeButton("Remove Chart", this,
394                                    "removeChart");
395                    chartComps.add(GuiUtils.filler(5, 10));
396                    chartComps.add(removeBtn);
397    
398                    // JComponent top = GuiUtils.left(GuiUtils.hbox(
399                    // GuiUtils.label("Forecast Time: ", chartTimeBox),
400                    // chartDiffCbx));
401                    // top = GuiUtils.inset(top,5);
402                    // chartTop.add(BorderLayout.NORTH, top);
403                    JComponent left = GuiUtils.doLayout(chartComps, 1, GuiUtils.WT_N,
404                                    new double[] { 0, 1, 0, 1, 0 });
405                    chartLeft.add(BorderLayout.CENTER, GuiUtils.inset(left, 5));
406    
407                    chartLeft.invalidate();
408                    chartLeft.validate();
409                    chartLeft.repaint();
410    
411            }
412    
413            /**
414             * _more_
415             * 
416             * @return _more_
417             */
418            protected List getStormTrackParams() {
419                    // Get the types from the first forecast track
420                    List<StormParam> params = stormDisplayState.getStormChartParams();
421                    if ((params == null) || (params.size() == 0)) {
422                            for (StormTrack track : stormDisplayState.getTrackCollection()
423                                            .getTracks()) {
424                                    if (track == null) {
425                                            continue;
426                                    }
427                                    if (!track.isObservation()) {
428                                            params = track.getParams();
429                                            break;
430                                    }
431                            }
432    
433                            // If we didn't get any from the forecast track use the obs track
434                            if (params.size() == 0) {
435                                    StormTrack obsTrack = stormDisplayState.getTrackCollection()
436                                                    .getObsTrack();
437                                    if (obsTrack != null) {
438                                            params = obsTrack.getParams();
439                                    }
440                            }
441                    }
442                    return params;
443    
444            }
445    
446            /**
447             * _more_
448             */
449            public void removeChart() {
450                    if (GuiUtils.askOkCancel("Remove Chart",
451                                    "Do you want to remove this chart?")) {
452                            stormDisplayState.removeChart(this);
453    
454                    }
455    
456            }
457    
458            /**
459             * _more_
460             * 
461             * @param comp
462             *            _more_
463             * @param width
464             *            _more_
465             * @param height
466             *            _more_
467             * 
468             * @return _more_
469             */
470            JScrollPane makeScroller(JComponent comp, int width, int height) {
471                    JScrollPane scroller = GuiUtils.makeScrollPane(comp, width, height);
472                    scroller.setBorder(BorderFactory.createLoweredBevelBorder());
473                    scroller.setPreferredSize(new Dimension(width, height));
474                    scroller.setMinimumSize(new Dimension(width, height));
475                    return scroller;
476            }
477    
478            /**
479             * _more_
480             * 
481             * @param stormTrack
482             *            _more_
483             * @param param
484             *            _more_
485             * 
486             * @return _more_
487             */
488            protected LineState makeLine(StormTrack stormTrack, StormParam param) {
489                    List<Real> values = new ArrayList<Real>();
490                    List<DateTime> times = stormTrack.getTrackTimes();
491                    List<StormTrackPoint> trackPoints = stormTrack.getTrackPoints();
492                    double min = 0;
493                    double max = 0;
494                    Unit unit = null;
495                    for (int pointIdx = 0; pointIdx < times.size(); pointIdx++) {
496                            Real value = trackPoints.get(pointIdx).getAttribute(param);
497                            if (value == null) {
498                                    continue;
499                            }
500                            if (unit == null) {
501                                    unit = ((RealType) value.getType()).getDefaultUnit();
502                            }
503                            values.add(value);
504                            double dvalue = value.getValue();
505                            // System.err.print(","+dvalue);
506                            if ((pointIdx == 0) || (dvalue > max)) {
507                                    max = dvalue;
508                            }
509                            if ((pointIdx == 0) || (dvalue < min)) {
510                                    min = dvalue;
511                            }
512                    }
513                    if (values.size() == 0) {
514                            return null;
515                    }
516                    // System.err.println("");
517                    String paramLabel = param.toString();
518                    String label = stormTrack.getWay().toString(); // +":" + paramLabel;
519                    LineState lineState = new LineState();
520                    lineState.setRangeIncludesZero(true);
521                    if (stormTrack.getWay().isObservation()) {
522                            lineState.setWidth(2);
523                    } else {
524                            lineState.setWidth(1);
525                    }
526                    lineState.setRange(new Range(min, max));
527                    lineState.setChartName(paramLabel);
528                    lineState.setAxisLabel("[" + unit + "]");
529    
530                    // System.err.println (param + " " +
531                    // StormDataSource.TYPE_STORMCATEGORY);
532                    if (Misc.equals(param, StormDataSource.PARAM_STORMCATEGORY)) {
533                            // lineState.setShape(LineState.LINETYPE_BAR);
534                            lineState.setLineType(LineState.LINETYPE_BAR);
535                            lineState.setLineType(LineState.LINETYPE_AREA);
536                    } else {
537                            lineState.setLineType(LineState.LINETYPE_SHAPES_AND_LINES);
538                            lineState.setShape(LineState.SHAPE_LARGEPOINT);
539                    }
540    
541                    lineState.setColor(stormDisplayState.getWayDisplayState(
542                                    stormTrack.getWay()).getColor());
543                    lineState.setName(label);
544                    lineState.setTrack(times, values);
545                    return lineState;
546    
547            }
548    
549            /**
550             * _more_
551             * 
552             * 
553             * @param param
554             *            _more_
555             */
556            private void removeChartParam(StormParam param) {
557                    while (chartParams.contains(param)) {
558                            chartParams.remove(param);
559                    }
560                    updateChart();
561            }
562    
563            /**
564             * _more_
565             * 
566             * 
567             * @param param
568             *            _more_
569             */
570            private void addChartParam(StormParam param) {
571                    if (!chartParams.contains(param)) {
572                            chartParams.add(param);
573                    }
574                    updateChart();
575            }
576    
577            /**
578             * _more_
579             * 
580             * @param way
581             *            _more_
582             */
583            private void removeChartWay(Way way) {
584                    while (chartWays.contains(way)) {
585                            chartWays.remove(way);
586                    }
587                    updateChart();
588            }
589    
590            /**
591             * _more_
592             * 
593             * @param way
594             *            _more_
595             */
596            private void addChartWay(Way way) {
597                    if (!chartWays.contains(way)) {
598                            chartWays.add(way);
599                    }
600                    updateChart();
601            }
602    
603            /**
604             * _more_
605             */
606            private void getSelectedTimes() {
607                    if (isHourly()) {
608                            Object[] selects = chartTimeBox.getSelectedValues();
609                            if (forecastHours == null) {
610                                    forecastHours = new ArrayList();
611                            }
612                            for (int i = 0; i < selects.length; i++) {
613                                    Object select = selects[i];
614                                    TwoFacedObject tfo = (TwoFacedObject) select;
615                                    Integer hour = (Integer) tfo.getId();
616                                    forecastHours.add(hour);
617                            }
618    
619                    } else {
620                            forecastTimes = (List<DateTime>) Misc.toList(chartTimeBox
621                                            .getSelectedValues());
622    
623                    }
624            }
625    
626            /**
627             * _more_
628             */
629            protected void updateChart() {
630    
631                    try {
632                            if (!madeChart) {
633                                    createChart();
634                            }
635                            ignoreChartTimeChanges = true;
636    
637                            if (isHourly()) {
638                                    List<Integer> fHours = findForecastHours();
639                                    List<TwoFacedObject> tfos = new ArrayList<TwoFacedObject>();
640                                    for (Integer i : fHours) {
641                                            tfos.add(new TwoFacedObject(i + "H", i));
642                                    }
643    
644                                    Object[] selected = chartTimeBox.getSelectedValues();
645                                    chartTimeBox.setListData(new Vector(tfos));
646                                    GuiUtils.setSelectedItems(chartTimeBox, Misc.toList(selected));
647    
648                                    if ((selected == null) && (tfos.size() > 0)) {
649                                            TwoFacedObject selected0 = tfos.get(0);
650                                            forecastHours.add((Integer) selected0.getId());
651                                    }
652    
653                                    // chartTimeBox.setSelectedItem(selected);
654                            } else {
655                                    List<DateTime> fTimes = findForecastTimes();
656    
657                                    Object[] selected = chartTimeBox.getSelectedValues();
658    
659                                    chartTimeBox.setListData(new Vector(fTimes));
660                                    GuiUtils.setSelectedItems(chartTimeBox, Misc.toList(selected));
661    
662                                    if ((selected == null) && (fTimes.size() > 0)) {
663                                            DateTime dt0 = fTimes.get(0);
664                                            forecastTimes.add(dt0);
665                                    }
666    
667                                    // chartTimeBox.setSelectedItem(forecastTime);
668                            }
669                            ignoreChartTimeChanges = false;
670                            getSelectedTimes();
671                            chartTimeBox.repaint();
672                            Hashtable<Way, List> wayToTracks = new Hashtable<Way, List>();
673                            for (Way way : chartWays) {
674                                    wayToTracks.put(way, new ArrayList<Way>());
675                            }
676    
677                            List<StormTrack> tracksToUse = new ArrayList<StormTrack>();
678                            for (StormTrack track : stormDisplayState.getTrackCollection()
679                                            .getTracks()) {
680                                    List trackList = wayToTracks.get(track.getWay());
681                                    if (trackList == null) {
682                                            continue;
683                                    }
684                                    if (track.getWay().isObservation()) {
685                                            tracksToUse.add(track);
686                                    } else {
687                                            trackList.add(track);
688                                    }
689                            }
690    
691                            StormTrack obsTrack = stormDisplayState.getTrackCollection()
692                                            .getObsTrack();
693    
694                            if (isHourly()) {
695                                    for (Way way : chartWays) {
696                                            List<StormTrack> tracksFromWay = wayToTracks.get(way);
697    
698                                            for (Integer fHour : forecastHours) {
699                                                    List<StormTrackPoint> points = new ArrayList<StormTrackPoint>();
700                                                    for (StormTrack track : tracksFromWay) {
701                                                            StormTrackPoint stp = track
702                                                                            .findPointWithForecastHour(fHour.intValue());
703                                                            if (stp != null) {
704                                                                    // TODO: What time do we use???
705                                                                    points.add(stp);
706                                                            }
707    
708                                                    }
709                                                    if (points.size() > 0) {
710                                                            points = (List<StormTrackPoint>) Misc.sort(points);
711                                                            tracksToUse.add(new StormTrack(stormDisplayState
712                                                                            .getStormInfo(), way, points, null));
713                                                    }
714                                            }
715                                    }
716    
717                            } else {
718                                    for (Way way : chartWays) {
719                                            List<StormTrack> tracksFromWay = wayToTracks.get(way);
720                                            for (StormTrack track : tracksFromWay) {
721                                                    for (DateTime fTime : forecastTimes) {
722                                                            if (Misc.equals(fTime, track.getStartTime())) {
723                                                                    tracksToUse.add(track);
724                                                            }
725                                                    }
726                                            }
727                                    }
728                            }
729    
730                            List<LineState> lines = new ArrayList<LineState>();
731    
732                            for (StormParam param : chartParams) {
733                                    Hashtable seenWays = new Hashtable();
734                                    List<LineState> linesForParam = new ArrayList<LineState>();
735                                    for (StormTrack track : tracksToUse) {
736                                            LineState lineState = null;
737                                            if (chartDifference && param.getCanDoDifference()) {
738                                                    if (track.getWay().isObservation()) {
739                                                            continue;
740                                                    }
741                                                    track = StormDataSource.difference(obsTrack, track,
742                                                                    param);
743                                                    if (track != null) {
744                                                            lineState = makeLine(track, param);
745                                                    }
746                                            } else {
747                                                    lineState = makeLine(track, param);
748                                            }
749                                            if (lineState == null) {
750                                                    continue;
751                                            }
752                                            // Only add it if there are values
753                                            if (lineState.getRange().getMin() == lineState.getRange()
754                                                            .getMin()) {
755                                                    if (seenWays.get(track.getWay()) != null) {
756                                                            lineState.setVisibleInLegend(false);
757                                                    } else {
758                                                            seenWays.put(track.getWay(), "");
759                                                    }
760    
761                                                    linesForParam.add(lineState);
762                                            }
763                                    }
764                                    double max = Double.NEGATIVE_INFINITY;
765                                    double min = Double.POSITIVE_INFINITY;
766                                    ;
767                                    for (LineState lineState : linesForParam) {
768                                            Range r = lineState.getRange();
769                                            min = Math.min(min, r.getMin());
770                                            max = Math.max(max, r.getMax());
771                                    }
772                                    // System.err.println(param + " min/max:" + min + "/" + max);
773                                    boolean first = true;
774                                    for (LineState lineState : linesForParam) {
775                                            lineState.setAxisVisible(first);
776                                            first = false;
777                                            lineState.setRange(new Range(min, max));
778                                    }
779                                    lines.addAll(linesForParam);
780                            }
781                            getChart().setTracks(lines);
782                    } catch (Exception exc) {
783                            stormDisplayState.getStormTrackControl().logException(
784                                            "Updating chart", exc);
785                    }
786    
787            }
788    
789            /**
790             * Set the ChartParams property.
791             * 
792             * @param value
793             *            The new value for ChartParams
794             */
795            public void setChartParams(List<StormParam> value) {
796                    chartParams = value;
797            }
798    
799            /**
800             * Get the ChartParams property.
801             * 
802             * @return The ChartParams
803             */
804            public List<StormParam> getChartParams() {
805                    return chartParams;
806            }
807    
808            /**
809             * Set the ChartWays property.
810             * 
811             * @param value
812             *            The new value for ChartWays
813             */
814            public void setChartWays(List<Way> value) {
815                    chartWays = value;
816            }
817    
818            /**
819             * Get the ChartWays property.
820             * 
821             * @return The ChartWays
822             */
823            public List<Way> getChartWays() {
824                    return chartWays;
825            }
826    
827            /**
828             * Set the ChartForecastTime property.
829             * 
830             * @param value
831             *            The new value for ChartForecastTime
832             */
833            public void setForecastTime(DateTime value) {
834                    forecastTimes = (List<DateTime>) Misc.newList(value);
835            }
836    
837            /**
838             * _more_
839             * 
840             * @param value
841             *            _more_
842             */
843            public void setForecastTimes(List<DateTime> value) {
844                    forecastTimes = value;
845            }
846    
847            /**
848             * _more_
849             * 
850             * @return _more_
851             */
852            public List<DateTime> getForecastTimes() {
853                    return forecastTimes;
854            }
855    
856            /**
857             * Set the ChartDifference property.
858             * 
859             * @param value
860             *            The new value for ChartDifference
861             */
862            public void setChartDifference(boolean value) {
863                    chartDifference = value;
864            }
865    
866            /**
867             * Get the ChartDifference property.
868             * 
869             * @return The ChartDifference
870             */
871            public boolean getChartDifference() {
872                    return chartDifference;
873            }
874    
875            /**
876             * Set the Name property.
877             * 
878             * @param value
879             *            The new value for Name
880             */
881            public void setName(String value) {
882                    name = value;
883            }
884    
885            /**
886             * Get the Name property.
887             * 
888             * @return The Name
889             */
890            public String getName() {
891                    return name;
892            }
893    
894            /**
895             * Set the StormDisplayState property.
896             * 
897             * @param value
898             *            The new value for StormDisplayState
899             */
900            public void setStormDisplayState(StormDisplayState value) {
901                    stormDisplayState = value;
902            }
903    
904            /**
905             * Get the StormDisplayState property.
906             * 
907             * @return The StormDisplayState
908             */
909            public StormDisplayState getStormDisplayState() {
910                    return stormDisplayState;
911            }
912    
913            /**
914             * Set the Mode property.
915             * 
916             * @param value
917             *            The new value for Mode
918             */
919            public void setMode(int value) {
920                    mode = value;
921            }
922    
923            /**
924             * Get the Mode property.
925             * 
926             * @return The Mode
927             */
928            public int getMode() {
929                    return mode;
930            }
931    
932            /**
933             * Set the ForecastHour property.
934             * 
935             * @param value
936             *            The new value for ForecastHour
937             */
938            public void setForecastHour(int value) {
939                    forecastHours = (List<Integer>) Misc.newList(new Integer(value));
940            }
941    
942            /**
943             * _more_
944             * 
945             * @param value
946             *            _more_
947             */
948            public void setForecastHours(List<Integer> value) {
949                    forecastHours = value;
950            }
951    
952            /**
953             * _more_
954             * 
955             * @return _more_
956             */
957            public List<Integer> getForecastHours() {
958                    return forecastHours;
959            }
960    }