Ejemplo n.º 1
0
        /// <summary>
        /// Gets a data track that represents the corresponding data value for each maximum effort.
        /// Example (single data point): 300 watts for 10 minutes:  What was the avg cadence over this max. effort?  HR, etc.
        /// This data series will return the track to answer this question for every point in the max effort.
        /// </summary>
        /// <param name="activity">Activity to analyze.</param>
        /// <param name="pointType">Cadence, power, hr, etc.</param>
        /// <param name="timeTrack">Coded data track describing when the max efforts occurred.
        /// Time track aligns with the max effort period (same as normal mean max time scale)
        /// Values represent max effort start time.
        /// Start time + Elapsed seconds = end time of max effort</param>
        /// <returns></returns>
        internal static INumericTimeDataSeries GetAvgTrack(IActivity activity, Common.TrackType pointType, INumericTimeDataSeries timeTrack)
        {
            if (activity == null || timeTrack == null || timeTrack.Count == 0)
            {
                return(null);
            }

            INumericTimeDataSeries track = new NumericTimeDataSeries();

            foreach (TimeValueEntry <float> point in timeTrack)
            {
                // Start time is relative to activity start time.
                //  Some data tracks don't start at same time as activity
                // point.Elapsed seconds is the period
                // point.Value is the start time relative to activity start
                float value = GetAvgValue(activity, pointType, activity.StartTime.AddSeconds(point.Value), point.ElapsedSeconds);
                if (!float.IsNaN(value))
                {
                    track.Add(timeTrack.EntryDateTime(point), value);
                }
                else if (point.ElapsedSeconds == 0)
                {
                    // This is to address the first point where the period = 0
                    value = GetAvgValue(activity, pointType, activity.StartTime, 1);
                    track.Add(timeTrack.StartTime, value);
                }
            }

            return(track);
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Change ChartType (HR, Power, Cadence, etc.) from menu
        /// </summary>
        /// <param name="sender">menu item that was clicked</param>
        /// <param name="e">This item is not used</param>
        private void bannerMenuItem_Click(object sender, EventArgs e)
        {
            ToolStripMenuItem selected = sender as ToolStripMenuItem;

            for (int i = 0; i < mnuDetail.Items.Count; i++)
            {
                ToolStripMenuItem item = mnuDetail.Items[i] as ToolStripMenuItem;

                if (item != null)
                {
                    if (item != selected)
                    {
                        item.Checked = false;
                    }
                    else
                    {
                        item.Checked = true;
                    }
                }
                else
                {
                    // ToolStrip Separator encountered.  Stop evaluating
                    break;
                }
            }

            bnrReport.Text = selected.Text.ToString();
            ChartType      = (Common.TrackType)Enum.Parse(typeof(Common.TrackType), selected.Tag.ToString());
        }
Ejemplo n.º 3
0
 internal static void InitializeSettings()
 {
     chartLines         = null;
     primaryChart       = Common.TrackType.Power;
     reportChart        = Common.TrackType.Power;
     criticalPowerLines = null;
 }
Ejemplo n.º 4
0
        /// <summary>
        /// Gets the 'critical value' for a particular activity.  For instance, 5 minute power in an given activity.
        /// </summary>
        /// <param name="activity">Activity to analyze</param>
        /// <param name="chartType">Which data track to analyze</param>
        /// <param name="seconds">Seconds representing the period.  For example, 5 minute power would be 300 seconds.</param>
        /// <returns>The critical value requested.  5 minute power might return 350 watts for example.</returns>
        internal static float GetCriticalValue(IActivity activity, Common.TrackType chartType, float seconds)
        {
            float min, criticalValue;
            INumericTimeDataSeries source, mmTrack, timeTrack;
            string id = activity.ReferenceId + chartType + seconds;

            mmTrack = GetMeanMaxTrack(activity, chartType, out timeTrack, false);

            // Return value from cache
            if (criticalCache.ContainsKey(id))
            {
                // Critical cache
                return(criticalCache[id]);
            }
            else if (mmTrack != null)
            {
                // value was cached previously.  saves a little time.
                ITimeValueEntry <float> point = mmTrack.GetInterpolatedValue(mmTrack.StartTime.AddSeconds(seconds));
                if (point != null)
                {
                    criticalValue = point.Value;
                }
                else
                {
                    // Value does not exist for this activity
                    criticalValue = float.NaN;
                }
            }
            else
            {
                // Calculate value
                switch (chartType)
                {
                case Common.TrackType.Cadence:
                    source = activity.CadencePerMinuteTrack;
                    break;

                case Common.TrackType.Power:
                    source = activity.PowerWattsTrack;
                    break;

                default:
                case Common.TrackType.HR:
                    source = activity.HeartRatePerMinuteTrack;
                    break;
                }

                Utilities.Smooth(source, (uint)seconds, out min, out criticalValue);
            }

            // Save to cache
            if (!float.IsNaN(criticalValue) && criticalValue != 0)
            {
                criticalCache.Add(id, criticalValue);
            }

            return(criticalValue);
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Gets the cached track if available, or calculates from scratch if not available.
        /// </summary>
        /// <param name="activity">Activity to calculate</param>
        /// <param name="chartType">Which track to calculate</param>
        /// <param name="create">Cache the track if it doesn't exist.
        /// True will always return a track.
        /// False will return the cached track, or null if not already cached.</param>
        /// <returns>Cached track if available, or empty track if not found.</returns>
        private static INumericTimeDataSeries GetMeanMaxTrack(IActivity activity, Common.TrackType chartType, out INumericTimeDataSeries timeTrack, bool create)
        {
            if (activity == null)
            {
                timeTrack = new NumericTimeDataSeries();
                return(new NumericTimeDataSeries());
            }

            string id = activity.ReferenceId + chartType;

            if (tracks.ContainsKey(id))
            {
                // Returned cached value from memory :)
                timeTrack = tracks[id + "T"];
                return(tracks[id]);
            }
            else if (create)
            {
                // Not in cache, create a new mean-max track :(
                INumericTimeDataSeries track = new NumericTimeDataSeries();
                timeTrack = null;

                switch (chartType)
                {
                case Common.TrackType.HR:
                {
                    track = GetMeanMaxTrack(activity.HeartRatePerMinuteTrack, out timeTrack, activity.StartTime);
                    break;
                }

                case Common.TrackType.Power:
                {
                    track = GetMeanMaxTrack(activity.PowerWattsTrack, out timeTrack, activity.StartTime);
                    break;
                }

                case Common.TrackType.Cadence:
                {
                    track = GetMeanMaxTrack(activity.CadencePerMinuteTrack, out timeTrack, activity.StartTime);
                    break;
                }
                }

                // Add data track and related 'T'ime track to cache for next time
                MeanMaxCache.AddTrack(track, id);
                MeanMaxCache.AddTrack(timeTrack, id + "T");

                return(track);
            }
            else
            {
                // Not previously cached, AND requested not to create a new cached item.
                timeTrack = new NumericTimeDataSeries();
                return(null);
            }
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Add 'track' to 'graph' and apply labels based on 'chartType'
        /// </summary>
        /// <param name="track">Data track</param>
        /// <param name="graph">Which graph to stick the data on</param>
        /// <param name="chartType">This determines the labeling, coloring, etc. (all appearance related)</param>
        internal static void updateZedGraph(Dictionary <CriticalLineDefinition, INumericTimeDataSeries> tracks, ZedGraphControl graph, Common.TrackType chartType)
        {
            GraphPane myPane = graph.GraphPane;

            myPane.XAxis.Title.Text = CommonResources.Text.LabelDate;
            myPane.XAxis.Type       = AxisType.Date;

            Color  mainCurveColor = Common.GetColor(chartType);
            string tag            = string.Empty;

            switch (chartType)
            {
            case Common.TrackType.Cadence:
                myPane.YAxis.Title.Text = CommonResources.Text.LabelCadence + " (" + CommonResources.Text.LabelRPM + ")";
                tag = ColumnDefinition.cadenceID;
                break;

            case Common.TrackType.HR:
                myPane.YAxis.Title.Text = CommonResources.Text.LabelHeartRate + " (" + CommonResources.Text.LabelBPM + ")";
                tag = ColumnDefinition.hrID;
                break;

            case Common.TrackType.Power:
                myPane.YAxis.Title.Text = CommonResources.Text.LabelPower + " (" + CommonResources.Text.LabelWatts + ")";
                tag = ColumnDefinition.powerID;
                break;
            }

            LineItem curve;

            myPane.CurveList.Clear();
            myPane.YAxis.Title.FontSpec.FontColor = mainCurveColor;
            myPane.YAxis.Scale.FontSpec.FontColor = mainCurveColor;

            // Add each critical values chart
            foreach (KeyValuePair <CriticalLineDefinition, INumericTimeDataSeries> track in tracks)
            {
                PointPairList zedTrack = new PointPairList();
                DateTime      itemDate;
                // Assemble associated track data
                foreach (ITimeValueEntry <float> item in track.Value)
                {
                    itemDate = track.Value.EntryDateTime(item);
                    zedTrack.Add(new XDate(itemDate), item.Value);
                }

                // Setup display properties of associated track
                Color color = track.Key.LineColor;
                curve                  = myPane.AddCurve(track.Key.Name, zedTrack, color, SymbolType.None);
                curve.Line.Width       = 1f;
                curve.Line.Fill.Type   = FillType.None;
                curve.Line.IsAntiAlias = true;
                //curve.Line.IsSmooth = true;
                //curve.Line.SmoothTension = .15f;
                curve.Tag = track.Key.ReferenceId;
            }

            if (tracks.Count > 0)
            {
                graph.AxisChange();
            }

            graph.Refresh();
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Get critical value track.  This is the full track of critical values for various activities over a date range until 'now'.
        /// /// </summary>
        /// <param name="activities">The list of activities to consider</param>
        /// <param name="trackType">Which data track to evaluate</param>
        /// <param name="seconds">The critical period, measured in seconds.  Example, for 5 minute power this would be 300.</param>
        /// <returns>Returns a data track of values over a date range.  1 value per day.</returns>
        internal static INumericTimeDataSeries GetCriticalTrack(IEnumerable <IActivity> activities, Common.TrackType trackType, float seconds)
        {
            float criticalValue = 0;
            SortedList <DateTime, float> criticalData = new SortedList <DateTime, float>();
            INumericTimeDataSeries       criticalTrack = new NumericTimeDataSeries();
            float i = 0, count = (activities as List <IActivity>).Count;


            // Populate activity data
            foreach (IActivity activity in activities)
            {
                Progress = (int)(i++ / count * 100f);
                Application.DoEvents();

                // Filter bad data
                if (activity.StartTime.Year != 1)
                {
                    criticalValue = MeanMaxCache.GetCriticalValue(activity, trackType, seconds);
                    if (!float.IsNaN(criticalValue))
                    {
                        DateTime activityDate = activity.StartTime.Add(activity.TimeZoneUtcOffset).Date;

                        if (criticalData.ContainsKey(activityDate))
                        {
                            // Handle days with multiple activities
                            // Update with higher value
                            criticalData[activityDate] = Math.Max(criticalValue, criticalData[activityDate]);
                        }
                        else if (criticalValue != 0)
                        {
                            // Add critical value to data set
                            criticalData.Add(activityDate, criticalValue);
                        }
                    }
                }
            }

            // Construct critical data track
            DateTime firstDate;

            if (criticalData.Count > 0)
            {
                firstDate     = criticalData.Keys[0];
                criticalValue = criticalData[firstDate];
            }
            else
            {
                firstDate = DateTime.Now;
            }
            float value;

            for (DateTime day = firstDate; day < DateTime.Now;)
            {
                // Decay the critical value first, value for 'today'
                //criticalValue = criticalValue - criticalValue / GlobalSettings.Instance.TCc;

                if (criticalData.TryGetValue(day.Date, out value))
                {
                    // TODO: Determine best way to incorporate decay values
                    // Value recorded for today or previous (decayed) value
                    //criticalValue = Math.Max(value, criticalValue);
                    //criticalTrack.Add(day, criticalValue);
                    criticalTrack.Add(day, value);
                }
                else
                {
                    // Decayed value
                    //criticalTrack.Add(day, criticalValue);
                }
                // Next day
                day = day.AddDays(1);
            }

            Progress = -1;

            return(criticalTrack);
        }
Ejemplo n.º 8
0
 /// <summary>
 /// Gets the cached track if available, or calculates from scratch if not available.
 /// </summary>
 /// <param name="activity">Activity to calculate</param>
 /// <param name="chartType">Which track to calculate</param>
 /// <returns>Cached track if available, or empty track if not found.</returns>
 internal static INumericTimeDataSeries GetMeanMaxTrack(IActivity activity, Common.TrackType chartType, out INumericTimeDataSeries timeTrack)
 {
     return(GetMeanMaxTrack(activity, chartType, out timeTrack, true));
 }
Ejemplo n.º 9
0
        /// <summary>
        /// Gets an average value over a specific timeframe in an activity track.
        /// Used to calculate associated values, for example.
        /// </summary>
        /// <param name="activity">Activity to analyze</param>
        /// <param name="pointType">Which data track is data requested from</param>
        /// <param name="start">Start time of requested average period (in UTC time)</param>
        /// <param name="period">Duration of time (in seconds) to evaluate with respect to start time</param>
        /// <returns>The average value of X track, from 'start' to 'period' seconds later.</returns>
        internal static float GetAvgValue(IActivity activity, Common.TrackType pointType, DateTime start, uint period)
        {
            // Check for bad data
            if (activity == null || period < 0)
            {
                return(float.NaN);
            }

            INumericTimeDataSeries track = null;

            switch (pointType)
            {
            case Common.TrackType.Cadence:
                track = activity.CadencePerMinuteTrack;
                break;

            case Common.TrackType.HR:
                track = activity.HeartRatePerMinuteTrack;
                break;

            case Common.TrackType.Power:
                track = activity.PowerWattsTrack;
                break;

            case Common.TrackType.Grade:
                track = new NumericTimeDataSeries(ActivityInfoCache.Instance.GetInfo(activity).SmoothedGradeTrack);
                for (int i = 0; i < track.Count; i++)
                {
                    track.SetValueAt(i, track[i].Value * 100f);
                }
                break;
            }

            // No track data
            if (track == null)
            {
                return(float.NaN);
            }

            // Find average value:  Sum entries every 1 second, then divide it out at the end
            float    sum  = 0;
            DateTime time = start;
            TimeSpan span = TimeSpan.Zero;

            while (time < start.AddSeconds(period))
            {
                // Sum all values (we'll divide it out at the end)
                ITimeValueEntry <float> item = track.GetInterpolatedValue(time);

                // Ignore bad values... they're simply excluded from calculation
                //  and the average is taken from a smaller subset of values
                if (item != null)
                {
                    sum += item.Value;
                    span = span.Add(TimeSpan.FromSeconds(1));
                }

                time = time.AddSeconds(1);
            }

            // Divide sum by period to get the average value
            if (span.TotalSeconds > 0)
            {
                return(sum / (int)span.TotalSeconds);
            }
            else
            {
                // Bad data.  Oops, requested time range didn't exist for the requested track.
                return(float.NaN);
            }
        }
Ejemplo n.º 10
0
        /// <summary>
        /// Add 'track' to 'graph' and apply labels based on 'chartType'
        /// </summary>
        /// <param name="track">Data track</param>
        /// <param name="graph">Which graph to stick the data on</param>
        /// <param name="chartType">This determines the labeling, coloring, etc. (all appearance related)</param>
        internal static void updateZedGraph(INumericTimeDataSeries track, Dictionary <string, INumericTimeDataSeries> assocTracks, ZedGraphControl graph, Common.TrackType chartType)
        {
            GraphPane myPane = graph.GraphPane;

            myPane.XAxis.Title.Text = CommonResources.Text.LabelTime;
            myPane.XAxis.Type       = AxisType.Log;

            Color  mainCurveColor = Common.GetColor(chartType);
            string tag            = string.Empty;

            switch (chartType)
            {
            case Common.TrackType.Cadence:
                myPane.YAxis.Title.Text = CommonResources.Text.LabelCadence + " (" + CommonResources.Text.LabelRPM + ")";
                tag = ColumnDefinition.cadenceID;
                break;

            case Common.TrackType.HR:
                myPane.YAxis.Title.Text = CommonResources.Text.LabelHeartRate + " (" + CommonResources.Text.LabelBPM + ")";
                tag = ColumnDefinition.hrID;
                break;

            case Common.TrackType.Power:
                myPane.YAxis.Title.Text = CommonResources.Text.LabelPower + " (" + CommonResources.Text.LabelWatts + ")";
                tag = ColumnDefinition.powerID;
                break;
            }

            myPane.XAxis.MinorTic.IsOutside = true;

            // Add primary mean max chart
            PointPairList          zedTrack  = new PointPairList();
            INumericTimeDataSeries timeTrack = null;

            if (assocTracks.ContainsKey(timeId))
            {
                timeTrack = assocTracks[timeId];
            }

            foreach (ITimeValueEntry <float> item in track)
            {
                float time = -1;
                if (timeTrack != null)
                {
                    int index = track.IndexOf(item);
                    time = timeTrack[index].Value;
                }

                zedTrack.Add(item.ElapsedSeconds, item.Value, time);
            }

            myPane.CurveList.Clear();
            LineItem curve = myPane.AddCurve("Curve Label", zedTrack, mainCurveColor, SymbolType.None);

            curve.Line.Width       = 1f;
            curve.Line.Fill.Type   = FillType.Solid;
            curve.Line.Fill.Color  = Color.FromArgb(50, mainCurveColor);
            curve.Tag              = tag;
            curve.Line.IsAntiAlias = true;
            myPane.YAxis.Title.FontSpec.FontColor = mainCurveColor;
            myPane.YAxis.Scale.FontSpec.FontColor = mainCurveColor;


            // Add secondary correlation charts
            int yIndex = 1;

            foreach (string id in GlobalSettings.Instance.ChartLines)
            {
                if (assocTracks.ContainsKey(id))
                {
                    zedTrack = new PointPairList();

                    // Assemble associated track data
                    foreach (ITimeValueEntry <float> item in assocTracks[id])
                    {
                        // Include time of occurrance
                        float time  = -1;
                        int   index = assocTracks[id].IndexOf(item);

                        if (timeTrack != null && timeTrack.Count > index)
                        {
                            time = timeTrack[index].Value;
                        }

                        zedTrack.Add(item.ElapsedSeconds, item.Value, time);
                    }

                    // Setup display properties of associated track
                    Color color = ColumnDefinition.GetTrackColor(id);
                    curve                  = myPane.AddCurve(id, zedTrack, color, SymbolType.None);
                    curve.Line.Width       = 1f;
                    curve.Line.Fill.Type   = FillType.None;
                    curve.Line.IsAntiAlias = true;
                    curve.Tag              = id;
                    curve.IsY2Axis         = true;
                    yIndex                 = myPane.Y2AxisList.IndexOfTag(id);

                    if (yIndex != -1)
                    {
                        // Set to existing axis
                        curve.YAxisIndex = yIndex;
                    }
                    else
                    {
                        // Oops... ERROR!
                    }
                }
            }

            if (track.Count > 0)
            {
                graph.AxisChange();
            }

            graph.Refresh();
        }
Ejemplo n.º 11
0
        internal void RefreshPage()
        {
            if (activities == null)
            {
                return;
            }

            INumericTimeDataSeries meanMax     = new NumericTimeDataSeries();
            INumericTimeDataSeries timeTrack   = new NumericTimeDataSeries();
            INumericTimeDataSeries activityMax = new NumericTimeDataSeries();
            Dictionary <string, INumericTimeDataSeries> assocTracks = new Dictionary <string, INumericTimeDataSeries>();
            SortedList <float, float> mmTempList = new SortedList <float, float>();

            meanMax.AllowMultipleAtSameTime = true;

            DateTime start = DateTime.Now.Date;

            foreach (IActivity mmact in activities)
            {
                // Try to pull from memory if available
                activityMax = MeanMaxCache.GetMeanMaxTrack(mmact, ChartType, out timeTrack);
#if DebugOFF
                Utilities.ExportTrack(activityMax, "C:\\STexports\\" + "MeanMax" + ".csv");
                Utilities.ExportTrack(mmact.PowerWattsTrack, "C:\\STexports\\" + "RawPowerTrack" + ".csv");
                Utilities.ExportTrack(mmact.HeartRatePerMinuteTrack, "C:\\STexports\\" + "RawHeartRate" + ".csv");
                Utilities.ExportTrack(mmact.CadencePerMinuteTrack, "C:\\STexports\\" + "RawCadence" + ".csv");
#endif
                int numActivities = (activities as List <IActivity>).Count;
                if (numActivities == 1)
                {
                    // Add timetrack - Note this this only available for single activity analysis
                    assocTracks.Add(timeId, timeTrack);

                    // Add associated chart lines (matching HR, Cadence, etc.)
                    foreach (string id in GlobalSettings.Instance.ChartLines)
                    {
                        Common.TrackType lineType = ColumnDefinition.GetTrackType(id);
                        if (lineType != ChartType)
                        {
                            // Get associated track (HR, Cad, etc.)
                            // This line is similar to the Mean-max chart where the time track is the 'period'
                            //  but the data is the HR, Cad., etc. that occurred at the -same time- as the Max effort occurred
                            //  rather than being a max value in itself.
                            //  The value is the average value during the range of time as opposed to a single entry
                            INumericTimeDataSeries track = MeanMaxCache.GetAvgTrack(mmact, lineType, timeTrack);
#if DebugOFF
                            Utilities.ExportTrack(track, "C:\\STexports\\MM" + ColumnDefinition.GetText(id) + ".csv");
#endif
                            if (!assocTracks.ContainsKey(id) && track != null)
                            {
                                // Store associated tracks for charting
                                assocTracks.Add(id, track);
                            }
                            else
                            {
                                // TODO: Combine multiple tracks for averaging.  Currently it'll only display the first activity track :(
                            }
                        }
                    }
                }

                // Compile all points together (used for multiple activities)
                foreach (TimeValueEntry <float> item in activityMax)
                {
                    /* If(entry not exist in meanMax || current value > existing value)
                     *  {
                     *   Add new MM entry
                     *   Update assoc track values
                     *  }
                     *  else
                     *  {
                     *
                     *  }
                     */

                    // Add to temporary sortedlist
                    if (!mmTempList.ContainsKey(item.ElapsedSeconds))
                    {
                        mmTempList.Add(item.ElapsedSeconds, item.Value);
                    }
                    else if (mmTempList.ContainsKey(item.ElapsedSeconds) && mmTempList[item.ElapsedSeconds] < item.Value)
                    {
                        mmTempList[item.ElapsedSeconds] = item.Value;
                    }
                    //meanMax.Add(start.AddSeconds(item.ElapsedSeconds), item.Value);
                }

                // Copy sorted temporary list to proper numeric time series
                foreach (float seconds in mmTempList.Keys)
                {
                    meanMax.Add(start.AddSeconds(seconds), mmTempList[seconds]);
                }

                // Remove low points (used for multiple activities)
                for (int i = meanMax.Count - 2; i >= 0; i--)
                {
                    if (meanMax[i].Value < meanMax[i + 1].Value)
                    {
                        meanMax.RemoveAt(i);
                    }
                }
            }

            updateZedGraph(meanMax, assocTracks, zedChart, ChartType);
        }