Click here to Skip to main content
15,885,244 members
Articles / Multimedia / GDI+

Internal Supply Chain, Visibility via 200 plus 3D Chart Reports - Part II

Rate me:
Please Sign up or sign in to vote.
5.00/5 (15 votes)
8 Oct 2009CPOL4 min read 53.7K   5.3K   46  
This article focuses on internal supply chain management systems visibility via chart reports, and provides assessment apparatus to manage and monitor activities spawned during business processes, hence paves the way for timely and precise business decisions.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms.DataVisualization.Charting;
using System.Collections;

namespace System.Windows.Forms.DataVisualization.Charting.Utilities
{
	/// <summary>
    /// ChartDataTableHelper is a utility that includes functions to render a table that displays
    /// data that is present on the chart.  This utility mainly focuses on how to add a data table 
    /// to your chart to view all of its points. 
	/// </summary>
	public class ChartDataTableHelper
	{
		#region Members
		protected System.Windows.Forms.DataVisualization.Charting.Chart ChartObj = null;
		protected ArrayList ChartAreas = null;
		protected bool AddTableTotals = false;
		protected System.Drawing.Color tableColor = Color.White;
		protected System.Drawing.Color borderColor = Color.Black;
		protected bool enabled = true;
		protected bool Initialized = false;

		#endregion

		#region Properies

		/// <summary>
		/// Enables or Disables the painting of the Data Table.
		/// </summary>
		public bool Enabled
		{
			get
			{
				return Enabled;
			}
			set
			{
				enabled = value;
			}
		}

		/// <summary>
		/// Sets or gets the Chart object.
		/// </summary>
		public System.Windows.Forms.DataVisualization.Charting.Chart Chart
		{
			get
			{
				return ChartObj;
			}
			set
			{
				ChartObj = value;
			}
		}


		/// <summary>
		/// Sets or gets the Table Color that will be painted.
		/// </summary>
		public System.Drawing.Color TableColor
		{
			get
			{
				return tableColor;
			}
			set
			{
				tableColor = value;
			}
		}

		/// <summary>
		/// Sets or gets the Table Border Color that will be painted.
		/// </summary>
		public System.Drawing.Color BorderColor
		{
			get
			{
				return borderColor;
			}
			set
			{
				borderColor = value;
			}
		}

		#endregion

		#region Constructors
		/// <summary>
		/// Construct a ChartDataTableHelper instance.
		/// </summary>
		public ChartDataTableHelper()
		{
			ChartObj = null;
			ChartAreas = new ArrayList();
		}

		/// <summary>
		/// Construct a ChartDataTableHelper instance and Initialize all ChartAreas with a table.
		/// </summary>
		public ChartDataTableHelper(System.Windows.Forms.DataVisualization.Charting.Chart chartObj)
		{
			ChartAreas = new ArrayList();
			Initialize(chartObj);
		}


		/// <summary>
		/// Construct a ChartDataTableHelper instance and Initialize the specified ChartArea with a table.
		/// </summary>
		public ChartDataTableHelper(System.Windows.Forms.DataVisualization.Charting.Chart chartObj, string chartAreaName)
		{
			ChartAreas = new ArrayList();
			Initialize(chartObj, chartAreaName);

		}

		/// <summary>
		/// Construct a ChartDataTableHelper instance, Initialize the specified ChartArea with a table and
		/// set a boolean to show or hide total columns.
		/// </summary>
		public ChartDataTableHelper(System.Windows.Forms.DataVisualization.Charting.Chart chartObj, string chartAreaName, bool addTableTotals)
		{
			ChartAreas = new ArrayList();
			Initialize(chartObj, chartAreaName, addTableTotals);

		}

		#endregion 

		#region Initialization Methods
		/// <summary>
		/// Initialize all ChartAreas with a table.
		/// </summary>
		public void Initialize(System.Windows.Forms.DataVisualization.Charting.Chart chartObj)
		{
			ChartObj = chartObj;

			foreach(ChartArea area in ChartObj.ChartAreas)
			{
				AddDataTable(area.Name);
			}

            if (!Initialized)
                ChartObj.PostPaint += new EventHandler<ChartPaintEventArgs>(Chart_PostPaint);
		
			Initialized = true;
		}



		/// <summary>
		/// Initialize all ChartAreas with a table and
		/// set a boolean to show or hide total columns.
		/// </summary>
		public void Initialize(System.Windows.Forms.DataVisualization.Charting.Chart chartObj, bool addTableTotals)
		{
			AddTableTotals = addTableTotals;
			Initialize(chartObj);
		}

		/// <summary>
		/// Initialize the specified ChartArea with a table.
		/// </summary>
		public void Initialize(System.Windows.Forms.DataVisualization.Charting.Chart chartObj, string chartAreaName)
		{
			ChartObj = chartObj;
			
			AddDataTable(chartAreaName);

			if(!Initialized)
				ChartObj.PostPaint +=new EventHandler<ChartPaintEventArgs>(Chart_PostPaint);
		
			Initialized = true;
		}

		/// <summary>
		/// Initialize the specified ChartArea with a table and
		/// set a boolean to show or hide total columns.
		/// </summary>
		public void Initialize(System.Windows.Forms.DataVisualization.Charting.Chart chartObj, string chartAreaName, bool addTableTotals)
		{
			ChartObj = chartObj;
			AddTableTotals = addTableTotals;
			
			AddDataTable(chartAreaName);

			if(!Initialized)
				ChartObj.PostPaint +=new EventHandler<ChartPaintEventArgs>(Chart_PostPaint);
		
			Initialized = true;
		}

		/// <summary>
		/// Initialize the specified ChartArea with a table.
		/// </summary>
		public void AddDataTable(string chartAreaName)
		{
			if(ChartObj == null || ChartAreas.IndexOf(chartAreaName) >= 0)
				return;

			// add this chart area to the list of chart areas that need to 
			// have a data table attached
			ChartAreas.Add(chartAreaName);

			int Row = 0;


			if(AddTableTotals)
			{
				// create a dummy series that will not be shown but is used for 
				// showing the totals in the data table
				ChartObj.Series.Add("DUMMY");
				ChartObj.Series["DUMMY"].ChartArea = chartAreaName;
				ChartObj.Series["DUMMY"].Enabled = false;
				ChartObj.Series["DUMMY"].Color = Color.Gainsboro;
			}


			// for each of the series that are attached to this 
			// named chart area, create a custom axis label.
			// All tables lines will be drawn on a paint event.
			// ****************************************************
			// NOTE: ALL SERIES MUST HAVE THE SAME NUMBER OF POINTS!
			// ****************************************************
			foreach(Series ser in ChartObj.Series)
			{
				if(chartAreaName == ser.ChartArea)
				{
					if(AddTableTotals)
					{
						// shadows must be turned off otherwise
						// they will still show up for the transparent points
						ser.ShadowOffset = 0;

						// adjust the series values to ensure they are not
						// indexed and they are sorted... plus adding each point
						// to make the dummy series data 
						AdjustXValues(ser);
					}


					Row++;
					double From = 0.0;
					double To = 0.0;
					bool firstPoint = true;

					double YValueTotal = 0;

					foreach(DataPoint dp in ser.Points)
					{
						if(AddTableTotals)
							YValueTotal += dp.YValues[0];

						if(firstPoint && dp.XValue == 0)
							From = 0.5;
						else if(firstPoint)
							From = dp.XValue - 0.5;

						if(firstPoint)
						{
							ChartObj.ChartAreas[chartAreaName].AxisX.Minimum = From;
							ChartObj.ChartAreas[chartAreaName].AxisX.MajorGrid.Interval = 1;
							ChartObj.ChartAreas[chartAreaName].AxisX.MajorTickMark.Interval = 1;
							ChartObj.ChartAreas[chartAreaName].AxisX.LabelStyle.Interval = 1;
							ChartObj.ChartAreas[chartAreaName].AxisX.MajorGrid.IntervalOffset = 0.5;
							ChartObj.ChartAreas[chartAreaName].AxisX.MajorTickMark.IntervalOffset = 0.5;
							ChartObj.ChartAreas[chartAreaName].AxisX.LabelStyle.IntervalOffset = 0.5;
						
							ChartObj.ChartAreas[chartAreaName].CursorX.IntervalOffset = 0.5;
						}

						To = From + 1;

						ChartObj.ChartAreas[chartAreaName].AxisX.CustomLabels.Add(
							From, To, 
							" ",     // space used as a placeholder
							Row, LabelMarkStyle.None, GridTickTypes.None
							);

						firstPoint = false;
						From += 1;
					}

					if(AddTableTotals)
						ser.Points[ser.Points.Count-1].YValues[0] = YValueTotal;
					
					ChartObj.ChartAreas[chartAreaName].AxisX.Maximum = To;

				}
			}

			if(AddTableTotals)
				AdjustYMaximum(chartAreaName);

		}

		/// <summary>
		/// With the addition of Totals, the chart will try to set the maximum
		/// values according to these totals.  This will cause some series to be
		/// barely visible.  Since the points are transparent this is a poor behavior.
		/// This method will find and explicitly set the YAxis maximum to something
		/// a little more with the user expectations.
		/// </summary>
		private void AdjustYMaximum(string chartAreaName)
		{
			double MaxYValue = 0;

			// find the max YValue from all points in all series
			foreach(Series ser in ChartObj.Series)
			{
				if(chartAreaName == ser.ChartArea && ser.Enabled)
				{
					// check agains all points except the last point
					// which is the totals column
					for(int index = 0; index < ser.Points.Count-1; index++)
					{
						DataPoint pt = ser.Points[index];
						if(pt.YValues[0] > MaxYValue)
							MaxYValue = pt.YValues[0];
					}		
				}
			}

			double LogValue = (int)(Math.Log10(MaxYValue)) + 1;
			double NewMaxYValue = Math.Pow(10, LogValue);
			double ratio = MaxYValue / NewMaxYValue;
			double divisor = 1;

			if(ratio <= 0.1)
				divisor = 10;
			else if(ratio < 0.2)
				divisor = 5;
			else if(ratio < 0.25)
				divisor = 4;
			else if(ratio < 0.4)
				divisor = 2.5;
			else if(ratio < 0.5)
				divisor = 2;
			else if(ratio < 0.8)
				divisor = 1.25;

			ChartObj.ChartAreas[chartAreaName].AxisY.Maximum = NewMaxYValue / divisor;
			ChartObj.ChartAreas[chartAreaName].AxisY.RoundAxisValues();

		}


		/// <summary>
		/// A cleanup method that ensures the XValues are sorted accordingly and set explicitly.  
		/// It will also create the totals for the DUMMY series.
		/// </summary>
		private void AdjustXValues(System.Windows.Forms.DataVisualization.Charting.Series series)
		{
			bool AddDummyPoints = true;

			if(series.Name == "DUMMY")
				return;
			else if(ChartObj.Series["DUMMY"].Points.Count > 0)
				AddDummyPoints = false;

			// sort the series
			series.Sort(PointSortOrder.Ascending, "X");

			bool IsIndexed = false;

			if(series.IsXValueIndexed)
				IsIndexed = true;
			else
			{
				bool IsFirstPoint = true;
				bool IsLastPointZero = false;

				// the series X values must be set and greater than zero
				foreach(DataPoint pt in series.Points)
				{
					if(pt.XValue == 0 && !IsFirstPoint && IsLastPointZero)
					{
						IsIndexed = true;
						break;
					}
					else if (pt.XValue == 0 && IsFirstPoint)
						IsLastPointZero = true;

					IsFirstPoint = false;
				}
			}

			if(IsIndexed)
			{
				series.IsXValueIndexed = false;
				int XValue = 0;
				
				foreach(DataPoint pt in series.Points)
					pt.XValue = ++XValue;
			}

			series.Points.AddXY(series.Points[series.Points.Count-1].XValue + 1, 0);
			series.Points[series.Points.Count-1].AxisLabel = "Total";
			series.Points[series.Points.Count-1].Color = Color.Transparent;
			series.Points[series.Points.Count-1].BorderColor = Color.Transparent;

			int index = 0;
			foreach(DataPoint pt in series.Points)
			{
				if(AddDummyPoints)
				{
					ChartObj.Series["DUMMY"].Points.AddXY(pt.XValue, pt.YValues[0]);
				}
				else
				{
					ChartObj.Series["DUMMY"].Points[index].YValues[0] += pt.YValues[0];
				}

				index++;
			}
		}


		#endregion 

		#region Remove Table 

		public void RemoveDataTable(string chartAreaName)
		{
			if(ChartObj.ChartAreas.IndexOf(chartAreaName) >= 0)
				ChartObj.ChartAreas[chartAreaName].AxisX.CustomLabels.Clear();

			if(ChartAreas.IndexOf(chartAreaName) >= 0)
			{
				ChartAreas.RemoveAt(ChartAreas.IndexOf(chartAreaName));
			}
		}

		#endregion

		#region Paint Event Handling

		/// <summary>
		/// Chart Paint event handler.
		/// </summary>
		private void Chart_PostPaint(object sender, System.Windows.Forms.DataVisualization.Charting.ChartPaintEventArgs e)
		{
            if( e.ChartElement is ChartArea )
			{
                ChartArea area = (ChartArea)e.ChartElement;
				// call the paint method.
				if(ChartAreas.IndexOf(area.Name) >= 0 && enabled)
				{
					PaintDataTable(e);
				}
			}
		
		}	

		/// <summary>
		/// This method does all the work for the painting of the data table.
		/// </summary>
		private void PaintDataTable(System.Windows.Forms.DataVisualization.Charting.ChartPaintEventArgs e)
		{
			ChartArea area = (ChartArea) e.ChartElement; 

			// get the rect of the chart area
			RectangleF rect = e.ChartGraphics.GetAbsoluteRectangle( area.Position.ToRectangleF() );

			// get the inner plot position
			ElementPosition elemPos = area.InnerPlotPosition;
			
			// find the coordinates of the inner plot position
			float x = rect.X + (rect.Width / 100 * elemPos.X);
			float y = rect.Y + (rect.Height / 100 * elemPos.Y);
			float ChartAreaBottomY = rect.Y + rect.Height;
			
			// offset the bottom by the width+1 of the scrollbar if it is visible
			if(area.AxisX.ScrollBar.IsVisible && !area.AxisX.ScrollBar.IsPositionedInside)
				ChartAreaBottomY -= ((float)area.AxisX.ScrollBar.Size+1);

			float width = (rect.Width / 100 * elemPos.Width);
			float height = (rect.Height / 100 * elemPos.Height);

			// find the height of the font that will be used
			Font axisFont = area.AxisX.LabelStyle.Font;
			string testString = "ForFontHeight";
			SizeF axisFontSize = e.ChartGraphics.Graphics.MeasureString(testString, axisFont);

			// find the height of the font that will be used
			Font titleFont = area.AxisX.TitleFont;
			testString = area.AxisX.Title;
            SizeF titleFontSize = e.ChartGraphics.Graphics.MeasureString(testString, titleFont);

			int seriesCount = 0;

			// for each series that is attached to the chart area,
			// draw some boxes around the labels in the color provided
			for(int i = e.Chart.Series.Count-1; i >= 0; i--)
			{
				if(area.Name == e.Chart.Series[i].ChartArea)
				{
					seriesCount++;
				}
			}

			// now, if a box was actually drawn, then draw 
			// the verticle lines to separate the columns of the table.
			if(seriesCount > 0)
			{
				for(int i = 0; i < e.Chart.Series.Count; i++)
				{
					if(area.Name == e.Chart.Series[i].ChartArea)
					{
						double min = area.AxisX.Minimum;
						double max = area.AxisX.Maximum;

						// modify the min value for the current axis view
						if(area.AxisX.ScaleView.Position-1 > min)
							min = area.AxisX.ScaleView.Position-1;

						// modify the max value for the currect axis view
						if( (area.AxisX.ScaleView.Position + area.AxisX.ScaleView.Size + 0.5) < max)
							max = area.AxisX.ScaleView.Position + area.AxisX.ScaleView.Size + 0.5;


						// find the starting point that will be display.
						// this is dependent on the current axis view.
						// this sample assumes the same number of points in each
						// series so always take from the zeroth series
						int pointIndex = 0;
						foreach(DataPoint pt in ChartObj.Series[0].Points)
						{
							if(pt.XValue > min)
								break;

							pointIndex++;
						}

						bool TableLegendDrawn = false;

						for(double AxisValue = min; AxisValue < max; AxisValue++)
						{
							float pixelX = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.X,AxisValue);
							float nextPixelX = (float)e.ChartGraphics.GetPositionFromAxis("Default",AxisName.X,AxisValue+1);
							float pixelY = ChartAreaBottomY - titleFontSize.Height - (seriesCount * axisFontSize.Height);

							PointF point1 = PointF.Empty;
							PointF point2 = PointF.Empty;

							// Set Maximum and minimum points
							point1.X = pixelX;
							point1.Y = 0;

							// Convert relative coordinates to absolute coordinates.
							point1 = e.ChartGraphics.GetAbsolutePoint(point1);
							point2.X = point1.X;
							point2.Y = ChartAreaBottomY - titleFontSize.Height;
							point1.Y = pixelY;

							// Draw connection line
							e.ChartGraphics.Graphics.DrawLine(new Pen(borderColor), point1,point2);


							point2.X = nextPixelX;
							point2.Y = 0;
							point2 = e.ChartGraphics.GetAbsolutePoint(point2);

							StringFormat format = new StringFormat();
							format.Alignment = StringAlignment.Center;
							format.LineAlignment = StringAlignment.Center;

							// for each series draw one value in the column
							int row = 0;
							foreach(Series ser in ChartObj.Series)
							{
								if(area.Name == ser.ChartArea)
								{
									if(!TableLegendDrawn)
									{
										// draw the series color box 
										e.ChartGraphics.Graphics.FillRectangle(new SolidBrush(ser.Color), 
											x-10, row*(axisFont.Height)+(point1.Y), 10, axisFontSize.Height);

										e.ChartGraphics.Graphics.DrawRectangle(new Pen(borderColor), 
											x-10, row*(axisFont.Height)+(point1.Y), 10, axisFontSize.Height);

										e.ChartGraphics.Graphics.FillRectangle(new SolidBrush(tableColor),
											x, 
											row*(axisFont.Height)+(point1.Y),
											width, 
											axisFontSize.Height); 

										e.ChartGraphics.Graphics.DrawRectangle(new Pen(borderColor), 
											x, 
											row*(axisFont.Height)+(point1.Y),
											width, 
											axisFontSize.Height); 

									}
									
									if(pointIndex < ser.Points.Count)
									{
										string label = ser.Points[pointIndex].YValues[0].ToString();
										RectangleF textRect = new RectangleF(point1.X, row*(axisFont.Height)+(point1.Y+1), point2.X-point1.X, axisFont.Height);
										e.ChartGraphics.Graphics.DrawString(label, axisFont, new SolidBrush(area.AxisX.LabelStyle.ForeColor), textRect, format);
									}

									row++;

								}
							}

							TableLegendDrawn = true;
						
							pointIndex++;
						}

						// do this only once so break!
						break;
					}
				}
			}
		}


		#endregion


	}
}

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
Pakistan Pakistan
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.
This is a Organisation

33 members

Comments and Discussions