510 lines
18 KiB
C#
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;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|