using System; using System.IO; using System.Xml; using System.Drawing; using System.Drawing.Imaging; using System.Drawing.Drawing2D; using System.Collections.Generic; using Payroll.BO.Organogram; namespace Payroll.BO { class OrganisationChart : IDisposable { private Graphics _gr; private int _imgWidth = 0; private int _imgHeight = 0; public OrganisationChart(OrgData.OrgDetailsDataTable OrganizationalDataTable) { _dtOrg = OrganizationalDataTable; } #region Properties /// /// the employee data /// private OrgData.OrgDetailsDataTable _dtOrg; /// /// A list of the employee positions. /// //private List EmpPositions; // A Dictionary of all employee data private SortedDictionary _employeeChartData; public SortedDictionary EmployeeData { get { return _employeeChartData; } set { _employeeChartData = value; } } private Color _boxFillColor = Color.AliceBlue; public Color BoxFillColor { get { return _boxFillColor; } set { _boxFillColor = value; } } private int _boxWidth = 130; public int BoxWidth { get { return _boxWidth; } set { _boxWidth = value; } } private int _boxHeight = 40; public int BoxHeight { get { return _boxHeight; } set { _boxHeight = value; } } private int _margin = 20; public int Margin { get { return _margin; } set { _margin = value; } } private int _horizontalSpace = 5; public int HorizontalSpace { get { return _horizontalSpace; } set { _horizontalSpace = value; } } private int _verticalSpace = 20; public int VerticalSpace { get { return _verticalSpace; } set { _verticalSpace = value; } } private int _fontSize = 6; public int FontSize { get { return _fontSize; } set { _fontSize = value; } } private Color _lineColor = Color.Black; public Color LineColor { get { return _lineColor; } set { _lineColor = value; } } private float _lineWidth = 2; public float LineWidth { get { return _lineWidth; } set { _lineWidth = value; } } private Color _bgColor = Color.White; public Color BGColor { get { return _bgColor; } set { _bgColor = value; } } private Color _fontColor = Color.Black; public Color FontColor { get { return _fontColor; } set { _fontColor = value; } } private string _selID = string.Empty; public string SelectedEmployeeID { set { _selID = value; } } private Color _selFontColor = Color.Black; public Color SelectedFontColor { get { return _selFontColor; } set { _selFontColor = value; } } private Color _selBackColor = Color.LightBlue; public Color SelectedBakcColor { get { return _selBackColor; } set { _selBackColor = value; } } private string _selFont = "verdana"; public string SelectedFont { get { return _selFont; } set { _selFont = value; } } private int _selFontSize = 7; public int SelectedFontSize { get { return _selFontSize; } set { _selFontSize = value; } } private StringAlignment _horizontalAlignment = StringAlignment.Center; public StringAlignment HorizontalAlignment { get { return _horizontalAlignment; } set { _horizontalAlignment = value; } } private StringAlignment _verticalAlignment = StringAlignment.Near; public StringAlignment VerticalAlignment { get { return _verticalAlignment; } set { _verticalAlignment = value; } } #endregion Properties public ChartRecord GetRecord(int scale, Point p) { foreach (ChartRecord cr in this.EmployeeData.Values) { Rectangle r = cr.CurrentLocation(scale); if (p.X >= r.Left && p.X <= r.Right && p.Y >= r.Top && p.Y <= r.Bottom) return cr; } return null; } public void Dispose() { _dtOrg = null; if (_gr != null) { _gr.Dispose(); _gr = null; } } /// /// The main function used to create the image /// /// /// /// The ID of the Boss from which to start building the chart /// /// A stream of the image. can be shown or saved to disk. public Stream GenerateOrgChart(int defaultWidth, int defaultHeight, int width, int height, string bossEmployeeID, ImageFormat imageType) { MemoryStream Result = new MemoryStream(); _employeeChartData = new SortedDictionary(); //reset image size _imgHeight = 0; _imgWidth = 0; _orgTree = null; _orgTree = new XmlDocument(); XmlNode RootNode = _orgTree.CreateNode(XmlNodeType.Element, "RootNode", ""); XmlAttribute Att = _orgTree.CreateAttribute("EmployeeID"); Att.InnerText = bossEmployeeID; RootNode.Attributes.Append(Att); _orgTree.AppendChild(RootNode); BuildTree(RootNode, 0); Bitmap bmp = new Bitmap(_imgWidth, _imgHeight); _gr = Graphics.FromImage(bmp); _gr.Clear(_bgColor); DrawChart(RootNode); //if caller does not care about size, use original calculated size if (width < 0) { width = _imgWidth; } if (height < 0) { height = _imgHeight; } double gR = 0; double wR = (double)defaultWidth / (double)width; double hR = (double)defaultHeight / (double)height; if (wR > hR) gR = wR; else gR = hR; if (gR > 1) { width = Convert.ToInt32((double)width * gR); height = Convert.ToInt32((double)height * gR); } Bitmap ResizedBMP = new Bitmap(bmp, new Size(width, height)); //after resize - change the coordinates of the list, in order return the proper coordinates for each employee CalculateImageMapData(width, height); ResizedBMP.Save(Result, imageType); ResizedBMP.Dispose(); bmp.Dispose(); _gr.Dispose(); return Result; } /// /// This overloaded method can be used to return the image using it's default calculated size, without resizing /// /// /// /// public Stream GenerateOrgChart(int defaultWidth, int defaultHeight, string bossEmployeeID, ImageFormat imageType) { return GenerateOrgChart(defaultWidth, defaultHeight, -1, -1, bossEmployeeID, imageType); } private XmlDocument _orgTree; /// /// /// /// /// private void BuildTree(XmlNode employeeNode, int y) { //has employees System.Data.DataRow[] oRows = _dtOrg.Select(string.Format("BossEmployeeID='{0}'", employeeNode.Attributes["EmployeeID"].InnerText)); if (oRows == null) return; foreach (OrgData.OrgDetailsRow EmployeeRow in oRows) { //for each employee call this function again XmlNode NewEmployeeNode = _orgTree.CreateElement("Node"); XmlAttribute Att = _orgTree.CreateAttribute("EmployeeID"); Att.InnerText = EmployeeRow.EmployeeID; NewEmployeeNode.Attributes.Append(Att); employeeNode.AppendChild(NewEmployeeNode); BuildTree(NewEmployeeNode, y + 1); } //build employee data after checking for employees we can add the current employee ChartRecord currentEmp = new ChartRecord(); currentEmp.EmployeeData = ((OrgData.OrgDetailsRow)_dtOrg.Select(string.Format("EmployeeID='{0}'", employeeNode.Attributes["EmployeeID"].Value))[0]); currentEmp.EmployeeCount = employeeNode.ChildNodes.Count; int[] resultsArr = new int[] {GetXPosByOwnChildren(employeeNode), GetXPosByParentPreviousSibling(employeeNode), GetXPosByPreviousSibling(employeeNode), _margin }; Array.Sort(resultsArr); int startX = resultsArr[3]; int startY = (y * (_boxHeight + _verticalSpace)) + _margin; int width = _boxWidth; int height = _boxHeight; //update the coordinates of this box into the matrix, for later calculations Rectangle drawRect = new Rectangle(startX, startY, width, height); //update the image size if (_imgWidth < (startX + width + _margin)) { _imgWidth = startX + width + _margin; } if (_imgHeight < (startY + height + _margin)) { _imgHeight = startY + height + _margin; } currentEmp.EmployeePos = drawRect; _employeeChartData.Add(currentEmp.EmployeeData.EmployeeID, currentEmp); currentEmp.Dispose(); currentEmp = null; } /// /// /// /// /// private int GetXPosByPreviousSibling(XmlNode currentNode) { int result = -1; XmlNode prevSibling = currentNode.PreviousSibling; if (prevSibling != null) { if (prevSibling.HasChildNodes) { result = _employeeChartData[prevSibling.LastChild.Attributes["EmployeeID"].Value].EmployeePos.X + _boxWidth + _horizontalSpace; } else { result = _employeeChartData[prevSibling.Attributes["EmployeeID"].Value].EmployeePos.X + _boxWidth + _horizontalSpace; } } return result; } /// /// /// /// /// private int GetXPosByOwnChildren(XmlNode currentNode) { int result = -1; if (currentNode.HasChildNodes) { result = (((_employeeChartData[currentNode.LastChild.Attributes["EmployeeID"].Value].EmployeePos.X + _boxWidth) - _employeeChartData[currentNode.FirstChild.Attributes["EmployeeID"].Value].EmployeePos.X) / 2) - (_boxWidth / 2) + _employeeChartData[currentNode.FirstChild.Attributes["EmployeeID"].Value].EmployeePos.X; } return result; } /// /// /// /// /// private int GetXPosByParentPreviousSibling(XmlNode currentNode) { int result = -1; XmlNode parentPrevSibling = currentNode.ParentNode.PreviousSibling; if (parentPrevSibling != null) { if (parentPrevSibling.HasChildNodes) { result = _employeeChartData[parentPrevSibling.LastChild.Attributes["EmployeeID"].Value].EmployeePos.X + _boxWidth + _horizontalSpace; } else { result = _employeeChartData[parentPrevSibling.Attributes["EmployeeID"].Value].EmployeePos.X + _boxWidth + _horizontalSpace; } } else //ParentPrevSibling == null { if (currentNode.ParentNode.Name != "RootNode" && currentNode.ParentNode.Name != "#document") { result = GetXPosByParentPreviousSibling(currentNode.ParentNode); } } return result; } /// /// Draws the actual chart image. /// private void DrawChart(XmlNode node) { // Create font and brush. Font drawFont = new Font("verdana", _fontSize); SolidBrush drawBrush = new SolidBrush(_fontColor); Pen boxPen = new Pen(_lineColor, _lineWidth); StringFormat drawFormat = new StringFormat(); drawFormat.Alignment = this.VerticalAlignment;//StringAlignment.Near; drawFormat.LineAlignment = this.HorizontalAlignment;//StringAlignment.Center; foreach (OrgData.OrgDetailsRow EmployeeRow in _dtOrg.Select(string.Format("BossEmployeeID='{0}'", node.Attributes["EmployeeID"].Value))) { DrawChart(node.SelectSingleNode(string.Format("Node[@EmployeeID='{0}']", EmployeeRow.EmployeeID))); } string empID = node.Attributes["EmployeeID"].Value; //// Create string to draw. //string drawString = _employeeChartData[empID].EmployeeData.EmployeeID + // Environment.NewLine + // _employeeChartData[empID].EmployeeData.EmployeeTitle; string drawString = _employeeChartData[empID].EmployeeData.EmployeeTitle; if (!string.IsNullOrEmpty(_selID) && _selID == empID) { drawFont = new Font(_selFont, _selFontSize); drawBrush = new SolidBrush(_selFontColor); _gr.DrawRectangle(boxPen, _employeeChartData[empID].EmployeePos); _gr.FillRectangle(new SolidBrush(_selBackColor), _employeeChartData[empID].EmployeePos); _gr.DrawString(drawString, drawFont, drawBrush, _employeeChartData[empID].EmployeePos, drawFormat); } else { drawFormat.Alignment = StringAlignment.Center; drawFormat.LineAlignment = StringAlignment.Near; _gr.DrawRectangle(boxPen, _employeeChartData[empID].EmployeePos); _gr.FillRectangle(new SolidBrush(_boxFillColor), _employeeChartData[empID].EmployeePos); _gr.DrawString(drawString, drawFont, drawBrush, _employeeChartData[empID].EmployeePos, drawFormat); } //draw connecting lines if (node.Name != "RootNode") { //all but the top box should have lines growing out of their top _gr.DrawLine(boxPen, _employeeChartData[empID].EmployeePos.Left + (_boxWidth / 2), _employeeChartData[empID].EmployeePos.Top, _employeeChartData[empID].EmployeePos.Left + (_boxWidth / 2), _employeeChartData[empID].EmployeePos.Top - (_verticalSpace / 2)); } if (node.HasChildNodes) { //all employees which have employees should have lines coming from bottom down _gr.DrawLine(boxPen, _employeeChartData[empID].EmployeePos.Left + (_boxWidth / 2), _employeeChartData[empID].EmployeePos.Top + _boxHeight, _employeeChartData[empID].EmployeePos.Left + (_boxWidth / 2), _employeeChartData[empID].EmployeePos.Top + _boxHeight + (_verticalSpace / 2)); } if (node.PreviousSibling != null) { //the prev employee has the same boss - connect the 2 employees _gr.DrawLine(boxPen, _employeeChartData[node.PreviousSibling.Attributes["EmployeeID"].Value].EmployeePos.Left + (_boxWidth / 2) - (_lineWidth / 2), _employeeChartData[node.PreviousSibling.Attributes["EmployeeID"].Value].EmployeePos.Top - (_verticalSpace / 2), _employeeChartData[node.Attributes["EmployeeID"].Value].EmployeePos.Left + (_boxWidth / 2) + (_lineWidth / 2), _employeeChartData[node.Attributes["EmployeeID"].Value].EmployeePos.Top - (_verticalSpace / 2)); } } /// /// /// /// /// private void CalculateImageMapData(double actualWidth, double actualHeight) { double percentageChangeX = actualWidth / _imgWidth; double PercentageChangeY = actualHeight / _imgHeight; foreach (ChartRecord cr in _employeeChartData.Values) { Rectangle resizedRectangle = new Rectangle( (int)(cr.EmployeePos.X * percentageChangeX), (int)(cr.EmployeePos.Y * PercentageChangeY), (int)(BoxWidth * percentageChangeX), (int)(BoxHeight * PercentageChangeY)); cr.EmployeePos = resizedRectangle; } } } }