EchoTex_Payroll/HRM.BO/Organogram/OrganisationChart.cs
2024-10-14 10:01:49 +06:00

510 lines
18 KiB
C#

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
/// <summary>
/// the employee data
/// </summary>
private OrgData.OrgDetailsDataTable _dtOrg;
/// <summary>
/// A list of the employee positions.
/// </summary>
//private List<EmployeePos> EmpPositions;
// A Dictionary of all employee data
private SortedDictionary<string, ChartRecord> _employeeChartData;
public SortedDictionary<string, ChartRecord> 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;
}
}
/// <summary>
/// The main function used to create the image
/// </summary>
/// <param name="Width"></param>
/// <param name="Height"></param>
/// <param name="BossEmployeeID">The ID of the Boss from which to start building the chart</param>
/// <param name="ImageType"></param>
/// <returns>A stream of the image. can be shown or saved to disk.</returns>
public Stream GenerateOrgChart(int defaultWidth, int defaultHeight, int width, int height, string bossEmployeeID, ImageFormat imageType)
{
MemoryStream Result = new MemoryStream();
_employeeChartData = new SortedDictionary<string, ChartRecord>();
//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;
}
/// <summary>
/// This overloaded method can be used to return the image using it's default calculated size, without resizing
/// </summary>
/// <param name="BossEmployeeID"></param>
/// <param name="ImageType"></param>
/// <returns></returns>
public Stream GenerateOrgChart(int defaultWidth, int defaultHeight, string bossEmployeeID, ImageFormat imageType)
{
return GenerateOrgChart(defaultWidth, defaultHeight, -1, -1, bossEmployeeID, imageType);
}
private XmlDocument _orgTree;
/// <summary>
///
/// </summary>
/// <param name="employeeNode"></param>
/// <param name="y"></param>
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;
}
/// <summary>
///
/// </summary>
/// <param name="currentNode"></param>
/// <returns></returns>
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;
}
/// <summary>
///
/// </summary>
/// <param name="currentNode"></param>
/// <returns></returns>
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;
}
/// <summary>
///
/// </summary>
/// <param name="currentNode"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Draws the actual chart image.
/// </summary>
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));
}
}
/// <summary>
///
/// </summary>
/// <param name="actualWidth"></param>
/// <param name="actualHeight"></param>
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;
}
}
}
}