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