protected int graphValue(DatedEvent ev, int index)
        {
            // the Server Availability graph requires interpolated (stepped) line;
            // others require peaks where the data actually is:
            if(ev == null)
            {		// no data at this point.
                switch(m_graphMode)
                {
                    case GRAPH_MODE_STEPPED:
                        ev = m_lastEvent;	// may be null too
                        break;
                }
            }

            int value;
            if(ev == null || (index+1) > ev.nValues())
            {
                value = m_yZeroMark;
            }
            else
            {
                double evValue = ev.values(m_format)[index];
                if(m_format == Project.FORMAT_EARTHQUAKES_STRONGEST && evValue == 0.0d)
                {
                    evValue = 0.5d; // quakes with unknown magnitude will be small bumps on the graph
                }
                value = m_yZeroMark - (int)(evValue * m_valueFactor);
            }
            m_lastEvent = ev;
            return value;
        }
        private void rebuildGraphTab(ArrayList selectedTracksRows)
        {
            Cursor.Current = Cursors.WaitCursor;

            makeTrackpointsDS(selectedTracksRows);

            double maxElev = -1000.0d;		// feet
            double maxSpeed = -1000.0d;		// miles per hour

            SortedList events = new SortedList();
            DataTable table = m_trackpointsDS.Tables["trackpoints"];
            foreach(DataRow row in table.Rows)
            {
                /*
                 * this is what the columns are:
                myDataRow["id"];
                myDataRow["trackid"];
                myDataRow["wptidx"];
                myDataRow["name"];
                myDataRow["time"] = new SortableDateTime(wpt.DateTime);
                myDataRow["location"] = wpt.Location.ToStringWithElev();

                myDataRow["speed"] = new Speed();
                myDataRow["heading"] = string;
                myDataRow["leg"] = Distance;
                myDataRow["track"] = string;
                myDataRow["source"] = string;
                myDataRow["displayed"] = string;
                */

                DateTime time = ((SortableDateTime)row["time"]).DateTime;
                long id = (long)((int)row["id"]);

                double elev = 0.0d;		// feet
                try
                {
                    elev = Math.Round((Double)row["elevation"] / Distance.METERS_PER_FOOT);	// * factor;
                }
                catch {}

                double speed = 0.0d;	// miles per hour
                try
                {
                    speed = Math.Round(((Speed)row["speed"]).Meters / Distance.METERS_PER_MILE);	// * factor;
                }
                catch {}

                maxElev = Math.Max(elev, maxElev);
                maxSpeed = Math.Max(speed, maxSpeed);

                string url = "";
                string properties = (url.Length == 0) ? "" : "w";

                long[] ids = new long[2];
                ids[0] = id;
                ids[1] = id;
                double[] values = new double[2];
                values[0] = elev;
                values[1] = speed;
                string[] sValues = new string[2];
                sValues[0] = properties;
                sValues[1] = "";

                DatedEvent ev = new DatedEvent(time, values, sValues, ids, 2);

                while(true)
                {
                    try
                    {
                        events.Add(time, ev);
                        break;
                    }
                    catch
                    {
                        time = time.AddTicks(1);
                    }
                }
            }

            if(maxElev > 0.0d && maxSpeed > 0.0d)
            {
                // make speed graph match the elevation:
                double speedFactor = 0.6d * maxElev / maxSpeed;
                foreach(DatedEvent ev in events.Values)
                {
                    double[] values = ev.values(Project.FORMAT_TRACK_ELEVATION);
                    values[1] = Math.Round(values[1] * speedFactor);
                }
            }

            string selHint = "Click into grey area to see selected interval";
            StringById dZoomById = new StringById(zoomById);
            StringById dDescrById = new StringById(descrById);

            double rounder = 200.0d;
            if(this.m_elevMax > 10000.0d)
            {
                rounder = 2000.0d;
            }
            else if(this.m_elevMax > 1000.0d)
            {
                rounder = 1000.0d;
            }
            else if(this.m_elevMax <= 50.0d)
            {
                this.m_elevMax = 50.0d;
                rounder = 100.0d;
            }
            double graphGridMaxValue = Math.Ceiling(this.m_elevMax / (Distance.METERS_PER_FOOT * rounder)) * rounder;
            int steps = (int) (graphGridMaxValue / rounder);
            graphByTimeControl.MaxValueY = graphGridMaxValue;
            graphByTimeControl.MinTimeMargin = 1 * 60 * 1000;	// ms - 1 min
            graphByTimeControl.StepY = graphGridMaxValue / steps;

            graphByTimeControl.MarginLeft = 45;
            graphByTimeControl.MarginRight = 30;

            graphByTimeControl.initialHint = "Click on graph to see details. Use arrow keys. Drag mouse to select time interval, click into it to zoom.";

            graphByTimeControl.init(this, "", "", selHint, dDescrById, dZoomById, dBrowseById, new MethodInvoker(showSelected));
            graphByTimeControl.setGraphData(events, Project.FORMAT_TRACK_ELEVATION, 2, false);

            graphByTimeControl.resetLegends();
            graphByTimeControl.setLegend(0, "elevation");
            graphByTimeControl.setLegend(1, "speed");

            m_selectedTracksRows = selectedTracksRows;

            Cursor.Current = Cursors.Default;
        }