Click here to Skip to main content
15,886,736 members
Articles / Desktop Programming / Windows Forms

Reputationator - CP Narcissists Rejoice! Part 1 of 4

Rate me:
Please Sign up or sign in to vote.
4.93/5 (35 votes)
30 Aug 2011CPOL22 min read 58.7K   882   41  
Keep more detailed track of your Codeproject reputation points.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using System.Windows.Controls.DataVisualization;
using System.Windows.Controls.DataVisualization.Charting;

using ReputationLib;

namespace RepWPF
{
	//////////////////////////////////////////////////////////////////////////////////////
	public class RepChart
	{
		/// <summary>
		/// Contains the colors used for each chart series. By default, we use 
		/// CodeProject's colors.
		/// </summary>
		public static Dictionary<RepCategory, Color> m_seriesColors = new Dictionary<RepCategory, Color>();

		/// <summary>
		/// The font used for the chart title
		/// </summary>
		//protected Font m_titleFont = new Font("Arial", 12, FontStyle.Bold | FontStyle.Italic, GraphicsUnit.Pixel);

		/// <summary>
		/// The DataVisualization.Charting.Chart object for this RepChart
		/// </summary>
		public Chart ChartObj { get; private set; }

		/// <summary>
		/// Event handler for the "No data" event
		/// </summary>
		public event NoDataEventHandler NoDataEvent = delegate { };

		protected DateTime m_dateFrom;
		protected DateTime m_dateTo;
		public    string   TitlePrefix {get; set; }

		public bool ShowTrendLine { get; set; }

		//--------------------------------------------------------------------------------
		/// <summary>
		/// Constructor - sets default values for many of the chart's properties, and 
		/// creates several objects necessary to bring a given chart to life.
		/// </summary>
		/// <param name="name"></param>
		public RepChart(string titlePrefix, ChartNames chartObjName)
		{
			this.ShowTrendLine = false;
			this.TitlePrefix = titlePrefix;
			InitSeriesColors();
			this.ChartObj = new Chart();
			this.ChartObj.Name = chartObjName.ToString();
			this.ChartObj.Tag = this;
			this.ChartObj.TabIndex = 1;
			this.ChartObj.Series.Clear();
		}

		//--------------------------------------------------------------------------------
		/// <summary>
		/// Sets the title for the chart object. By default, the chart title is the text 
		/// that appears in the chart category combo box on the form. To make it more 
		/// meaningful, use this method to describe the represented time period.
		/// </summary>
		/// <param name="period">A string that represents data added to the name of the 
		/// chart that indicates the covered date period. You should set the string to 
		/// whatever is needed to make sense such as putting the word "from", or "for" 
		/// BEFORE the actual time period. </param>
		protected void SetTitle(string period)
		{
			string title = this.ChartObj.Name;
			if (!string.IsNullOrEmpty(period))
			{
				title = string.Format("{0} {1}", this.ChartObj.Name, period);
			}
			this.ChartObj.Title = title;
		}

		//--------------------------------------------------------------------------------
		/// <summary>
		/// The base PopulateChart method. Using this base instance with throw a 
		/// NotImplementedException.
		/// </summary>
		/// <param name="scraper"></param>
		/// <param name="comboTimePeriod"></param>
		/// <param name="dateFrom"></param>
		/// <param name="dateTo"></param>
		/// <param name="categories"></param>
		public virtual void PopulateChart(string comboTimePeriod, DateTime dateFrom, DateTime dateTo, DisplayCategories categories)
		{
			this.ChartObj.Series.Clear();
		}

		//--------------------------------------------------------------------------------
		/// <summary>
		/// sets the colors used by the various series. This base version uss colors 
		/// similar to what are used on the CodeProject web site.  If you want to set 
		/// different colors, override this method.
		/// </summary>
		protected virtual void InitSeriesColors()
		{
			if (m_seriesColors.Count == 0)
			{
				m_seriesColors.Add(RepCategory.Author,      Colors.DarkRed);
				m_seriesColors.Add(RepCategory.Authority,   Colors.DarkGreen);
				m_seriesColors.Add(RepCategory.Debator,     Colors.Magenta);
				m_seriesColors.Add(RepCategory.Editor,      Colors.Goldenrod);
				m_seriesColors.Add(RepCategory.Enquirer,    Colors.Purple);
				m_seriesColors.Add(RepCategory.Organiser,   Colors.Olive);
				m_seriesColors.Add(RepCategory.Participant, Colors.LightSteelBlue);
				m_seriesColors.Add(RepCategory.Total,       Colors.Orange);
			}
		}

		//--------------------------------------------------------------------------------
		/// <summary>
		/// Retrieves a list of reputation items based on the specified time period 
		/// category, the reputation category, and the from/to dates.
		/// </summary>
		/// <param name="repItems">The list of reputation items to process</param>
		/// <param name="comboTimePeriod">The selected time perio (current week, current year, etc)</param>
		/// <param name="category">The reputation category to pull items for</param>
		/// <param name="dateFrom">The starting date of the specified time period</param>
		/// <param name="dateTo">The ending date of the specified time period</param>
		/// <returns></returns>
		protected virtual List<RepItem> GetSeriesList(RepItemCollection repItems, string comboTimePeriod, RepCategory category, DateTime dateFrom, DateTime dateTo)
		{
			// exception throws
			// we have to have reputation items
			if (repItems.Count <= 0)
			{
				throw new Exception(string.Format("No reputation items found. ({0} / {1})", comboTimePeriod, category.ToString()));
			}
			// there are no points associated with the Unknown reputaion category
			if (category == RepCategory.Unknown)
			{
				throw new Exception(string.Format("Invalid reputation cateory. ({0} / {1})", comboTimePeriod, category.ToString()));
			}
			// if this is for a specific time period, the dateFrom parameter must be 
			// valid (ticks > 0)
			if (comboTimePeriod == "Specified Period" && dateFrom.Ticks == 0)
			{
				throw new Exception(string.Format("Invalid start date (dateFrom) used for retrieving series data. ({0} / {1})", comboTimePeriod, category.ToString()));
			}
			// the "dateTo" must be set (ticks > 0) - we don't actually check to make 
			// sure it's >= the from date unless this is for a "Specified Period"
			if (dateTo.Ticks == 0)
			{
				throw new Exception(string.Format("Invalid end date (dateTo) used for retrieving series data. ({0} / {1})", comboTimePeriod, category.ToString()));
			}
			// so we can avoid a smart-ass programmer trying something like setting the 
			// dateTo parameter to a value that would result in a dateFrom being less 
			// than the lowest possible date (ticks = 0), we provide one last sanity 
			// check before proceeding, making sure that the dateFrom is really set to 
			// 0 ticks (but only if we're not doing  a "specified period"
			if (comboTimePeriod != "Specified Period" && dateFrom.Ticks > 0)
			{
				dateFrom.AddTicks(dateFrom.Ticks * -1);
			}

			// when we calculate the dateFrom, we do a Math.Max on the resulting ticks 
			// to mack sure we're not going to generate an invalid date
			switch (comboTimePeriod)
			{
				case "Current Week":
					dateFrom = dateFrom.AddTicks(Math.Max(0, dateTo.WeekStartDate().Ticks));
					break;

				case "Current Month":
					dateFrom = dateFrom.AddTicks(Math.Max(0, (dateTo.AddDays((dateTo.Day - 1) * -1).Ticks)));
					break;

				case "Current Year":
					dateFrom = dateFrom.AddTicks(Math.Max(0, (dateTo.AddDays((dateTo.DayOfYear - 1) * -1).Ticks)));
					break;

				case "Last 7 days":
					dateFrom = dateFrom.AddTicks(Math.Max(0, (dateTo.AddDays(-7)).Ticks));
					break;

				case "Last 30 days":
					dateFrom = dateFrom.AddTicks(Math.Max(0, (dateTo.AddDays(-30)).Ticks));
					break;

				case "Last 365 days":
					dateFrom = dateFrom.AddTicks(Math.Max(0, (dateTo.AddDays(-365)).Ticks));
					break;

				case "Specified Period":
					if (dateTo < dateFrom)
					{
						throw new Exception(string.Format("End date (dateTo) cannot be ealier than start date (dateFrom). ({0} / {1})", comboTimePeriod, category.ToString()));
					}
					break;
			}

			m_dateTo = dateTo;
			m_dateFrom = dateFrom;

			// now that we have our from/to dates we can retrieve/return the specified items
			List<RepItem> list = null;
			list = (from item in repItems
					where ((item.Category == category) && (item.TimeScraped.Between(dateFrom, dateTo, true)))
					select item).ToList();
			return list;
		}

		//--------------------------------------------------------------------------------
		/// <summary>
		/// determines the highest value to be represented on the chart, and essentially 
		/// allows that value to be the next highest possible delta (if the actual 
		/// highest value on the chart is 950, the highest value shown will be 1000.
		/// </summary>
		/// <param name="maxY"></param>
		/// <returns></returns>
		protected virtual int NormalizeMaxY(int maxY)
		{
			int thousands = (int)Math.Ceiling(maxY / 1000d);
			if (thousands > 100)
			{
				thousands += (50 - (thousands % 50));
			}
			else if (thousands > 10)
			{
				thousands += (5 - (thousands % 5));
			}
			maxY = 1000 * thousands;
			return maxY;
		}

		//--------------------------------------------------------------------------------
		/// <summary>
		/// Determines the Y-Axis interval based on the max possible value on the Y-Axis.
		/// </summary>
		/// <param name="maxY"></param>
		/// <returns></returns>
		protected virtual int NormalizeInterval(int maxY)
		{
			int interval = 0;
			if (maxY > 100000)
			{
				interval = 50000;
			}
			else if (maxY > 10000)
			{
				interval = 10000;
			}
			else if (maxY > 1000)
			{
				interval = 1000;
			}
			else
			{
				interval = 100;
			}
			return interval;
		}

		//--------------------------------------------------------------------------------
		/// <summary>
		/// Raises the No Data event that signifies that the resulting list of repitems 
		/// is empty, and no chart can be rendered.
		/// </summary>
		protected void RaiseNoDataEvent()
		{
			NoDataEvent(this, null);
		}

		//--------------------------------------------------------------------------------
		/// <summary>
		/// Calculates the trend line based on the specified parent series, and creates a 
		/// series for the chart.
		/// </summary>
		/// <param name="series"></param>
		/// <param name="categoryCount"></param>
		protected void CalculateTrendLine(List<RepItem> data, int categoryCount)
		{
			if (this.ShowTrendLine)
			{
				LineSeries series = new LineSeries();
				List<RepItem> trendItems = new List<RepItem>();
				int pointCount = data.Count;

				// create a points array for the Regress method
				double[] points = new double[pointCount];
				for (int i = 0; i < pointCount; i++)
				{
					points[i] = (this.TitlePrefix == "Daily Changes") ? data[i].ChangeValue : data[i].Value;
				}
				double a = 0;
				double b = 0;
				//calculation the regression results
				Globals.Regress(points, ref a, ref b);
				for (int i = 0; i < pointCount; i++)
				{
					trendItems.Add(data[i].Clone());
					double yield = (a + b) * i;
					if (this.TitlePrefix == "Accumulated By Day")
					{
						trendItems[i].ChangeValue = Convert.ToInt32(yield);
					}
					else
					{
						trendItems[i].Value = Convert.ToInt32(yield);
					}
				}


				SolidColorBrush brush = new SolidColorBrush((categoryCount > 1) ? m_seriesColors[trendItems[0].Category] : Colors.Black);
				MakeDataLine(trendItems, trendItems[0].Category.ToString(), "TimeScraped", (this.TitlePrefix == "Accumulated By Day") ? "ChangeValue" : "Value", brush); 

			}
		}


		//--------------------------------------------------------------------------------
		protected void MakeChartAxes()
		{
			TimeSpan span = this.m_dateTo - this.m_dateFrom;
			string format = "{0:MMM yyyy}";
			DateTimeIntervalType intervalType = DateTimeIntervalType.Months;
			if (span.TotalDays <= 7)
			{
				format = "{0:ddd}";
				intervalType = DateTimeIntervalType.Days;
			}
			else if (span.TotalDays <= 30)
			{
				format = "{0:MMM dd}";
				intervalType = DateTimeIntervalType.Weeks;
			}

			Style xLabelStyle = new Style(typeof(AxisLabel));
			xLabelStyle.Setters.Add(new Setter() { Property = AxisLabel.StringFormatProperty, Value = format });

			LinearAxis yAxis = new LinearAxis()
			{
				Name          = "ChartYAxis",
				Orientation   = AxisOrientation.Y,
				Location      = AxisLocation.Left,
				ShowGridLines = true,
			};
			CategoryAxis xCategory = new CategoryAxis()
			{
				Name           = "ChartXCategoryAxis",
				Orientation    = AxisOrientation.X,
				Location       = AxisLocation.Bottom,
				ShowGridLines  = false,
				AxisLabelStyle = xLabelStyle,
			};
			DateTimeAxis xAxis = new DateTimeAxis()
			{
				Name           = "ChartXAxis",
				Orientation    = AxisOrientation.X,
				Location       = AxisLocation.Bottom,
				ShowGridLines  = false,
				Interval       = 1,
				AxisLabelStyle = xLabelStyle,
				IntervalType   = intervalType 
			};

			this.ChartObj.Axes.Clear();
			this.ChartObj.Axes.Add(yAxis);
			this.ChartObj.Axes.Add(xAxis);
			this.ChartObj.Axes.Add(xCategory);
		}

		//--------------------------------------------------------------------------------
		/// <summary>
		/// Makes a column series for the specifed data.
		/// </summary>
		/// <param name="data"></param>
		/// <param name="seriesTitle"></param>
		/// <param name="xProperty"></param>
		/// <param name="yProperty"></param>
		/// <param name="colorBrush"></param>
		protected void MakeColumn(List<RepItem> data, string seriesTitle, string xProperty, string yProperty, Brush colorBrush)
		{
			ColumnSeries series = new ColumnSeries()
			{
				ItemsSource = data,
				Title = seriesTitle,
				DependentValueBinding = new System.Windows.Data.Binding(yProperty),
				IndependentValueBinding = new System.Windows.Data.Binding(xProperty)
			};

			this.ChartObj.Series.Add(series);

			try
			{
				Style columnStyle  = new Style(typeof(ColumnDataPoint));
				SetStyleProperty(columnStyle, ColumnDataPoint.BackgroundProperty, colorBrush);
				SetStyleProperty(columnStyle, ColumnDataPoint.BorderThicknessProperty, new Thickness(0));
				ControlTemplate template = Application.Current.Resources["ColumnSeriesTemplate"] as ControlTemplate;
				if (template != null)
				{
					SetStyleProperty(columnStyle, ColumnDataPoint.TemplateProperty, template);
				}
				series.DataPointStyle = columnStyle;
				series.TransitionDuration = new TimeSpan(0);
			}
			catch (Exception ex)
			{
				if (ex != null) { }
			}
		}

		//--------------------------------------------------------------------------------
		/// <summary>
		/// Sets tthe specified property to the specified value, and adds that setter to 
		/// the specified style.
		/// </summary>
		/// <param name="style"></param>
		/// <param name="property"></param>
		/// <param name="value"></param>
		private void SetStyleProperty(Style style, DependencyProperty property, object value)
		{
			Setter setter = new Setter(property, value);
			style.Setters.Add(setter);
		}

		//--------------------------------------------------------------------------------
		/// <summary>
		/// Sets the series styles for regular data lines.
		/// </summary>
		/// <param name="series">The series to set the styles on</param>
		/// <param name="seriesTitle">The series title (as it would appear in the legend)</param>
		/// <param name="xProperty">The data property used for the x-axis</param>
		/// <param name="yProperty">The data property used for the y-axis</param>
		/// <param name="colorBrush">The color of the line</param>
		protected void MakeDataLine(List<RepItem> data, string seriesTitle, string xProperty, string yProperty, Brush colorBrush)
		{
			LineSeries series  = new LineSeries()
			{
				ItemsSource             = data,
				Title                   = seriesTitle,
				DependentValueBinding   = new System.Windows.Data.Binding(yProperty),
				IndependentValueBinding = new System.Windows.Data.Binding(xProperty)
			};

			this.ChartObj.Series.Add(series);

			try
			{
				Style style = new Style(typeof(LineDataPoint));
				SetStyleProperty(style, LineDataPoint.BackgroundProperty, colorBrush);
				string templateName = (this.ShowTrendLine) ? "TrendLineSeriesTemplate" : "LineSeriesTemplate";
				ControlTemplate template = Application.Current.Resources[templateName] as ControlTemplate;
				if (template != null)
				{
					SetStyleProperty(style, ColumnDataPoint.TemplateProperty, template);
				}
				series.DataPointStyle = style;
				series.TransitionDuration = new TimeSpan(0);
			}
			catch (Exception ex)
			{
				if (ex != null) {}
			}
		}

		//--------------------------------------------------------------------------------
		/// <summary>
		/// Creates a pie chart series.
		/// </summary>
		/// <param name="data"></param>
		/// <param name="seriesTitle"></param>
		/// <param name="yProperty"></param>
		protected void MakePie(List<RepItem> data, string seriesTitle, string yProperty)
		{
			DisplayCategories categories = new DisplayCategories();
			foreach(RepItem item in data)
			{
				categories.Add(item.Category);
			}
			System.Collections.ObjectModel.Collection<ResourceDictionary> palette = MakePalette(categories);
			//this.ChartObj.Palette = palette;

			// add our custom color palette
			PieSeries series = new PieSeries()
			{
				ItemsSource             = data,
				Title                   = seriesTitle,
				DependentValueBinding   = new System.Windows.Data.Binding(yProperty),
				IndependentValueBinding = new System.Windows.Data.Binding("Category"),
				TransitionDuration      = new TimeSpan(0),
			};

			Style style = new Style(typeof(PieDataPoint));
			ControlTemplate template = Application.Current.Resources["PieDataTemplate"] as ControlTemplate;
			if (template != null)
			{
			    SetStyleProperty(style, ColumnDataPoint.TemplateProperty, template);
			}
			series.DataPointStyle = style;

			Style legendStyle = new Style(typeof(LegendItem));
			ControlTemplate legendTemplate = Application.Current.Resources["PieLegendTemplate"] as ControlTemplate;
			if (legendTemplate != null)
			{
				SetStyleProperty(legendStyle, LegendItem.TemplateProperty, legendTemplate);
			}
			series.LegendItemStyle = legendStyle;
			series.Palette = palette;

			this.ChartObj.Series.Add(series);
		}

		//--------------------------------------------------------------------------------
		/// <summary>
		/// Default MakePalette method (for line and column charts). The pie chart has an 
		/// overloaded version of this method.
		/// </summary>
		/// <param name="list"></param>
		/// <returns></returns>
		protected System.Collections.ObjectModel.Collection<ResourceDictionary> MakePalette(DisplayCategories list)
		{
			// allocate the collection
			System.Collections.ObjectModel.Collection<ResourceDictionary> palette = new System.Collections.ObjectModel.Collection<ResourceDictionary>();
			// cycle through the specified categories
			foreach (RepCategory item in list)
			{
				// create a resource dictonary
				ResourceDictionary rd    = new ResourceDictionary();
				// create a style based on Control (as opposed to PieDataPoint)
				Style              style = new Style(typeof(Control));
				// get ready to create the appropriate brush
				SolidColorBrush    brush = null;
				switch (item)
				{
					case RepCategory.Author      : brush = new SolidColorBrush(Colors.DarkRed);        break;
					case RepCategory.Authority   : brush = new SolidColorBrush(Colors.DarkGreen);      break;
					case RepCategory.Debator     : brush = new SolidColorBrush(Colors.Magenta);        break;
					case RepCategory.Editor      : brush = new SolidColorBrush(Colors.Goldenrod);      break;
					case RepCategory.Enquirer    : brush = new SolidColorBrush(Colors.Purple);         break;
					case RepCategory.Organiser   : brush = new SolidColorBrush(Colors.Olive);          break;
					case RepCategory.Participant : brush = new SolidColorBrush(Colors.LightSteelBlue); break;
				}
				// set the background for the style to be the brush we created
				style.Setters.Add(new Setter() 
				{ 
					Property = Control.BackgroundProperty, 
					Value = brush 
				});
				// add the style to the resouorce dictionary
				rd.Add("DataPointStyle", style);
				//add the resource dictionary to the palettte
				palette.Add(rd);
			}
			// return the palette
			return palette;
			// what a pain in the ass, but wait until you see the axis code for column and line charts...
		}


	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Paddedwall Software
United States United States
I've been paid as a programmer since 1982 with experience in Pascal, and C++ (both self-taught), and began writing Windows programs in 1991 using Visual C++ and MFC. In the 2nd half of 2007, I started writing C# Windows Forms and ASP.Net applications, and have since done WPF, Silverlight, WCF, web services, and Windows services.

My weakest point is that my moments of clarity are too brief to hold a meaningful conversation that requires more than 30 seconds to complete. Thankfully, grunts of agreement are all that is required to conduct most discussions without committing to any particular belief system.

Comments and Discussions