Sue Hernandez's SharePoint Blog

SharePoint and Related Stuff

MOSS – Graphical Organizational Chart

MOSS Organizational Chart using Google API

MOSS Organizational Chart using Google API

  

UPDATE:  Added capability to scroll up and down through the hierarchy.   

UPDATE:  I have uploaded a document that contains the full source code
(sorry, wordpress won’t allow me to upload a ZIP file). 
Get It Here:  
Org Chart Code     

We are going to create a simple Org Chart using the Profile Manager and Google’s Org Chart API (http://code.google.com/apis/visualization/documentation/gallery/orgchart.html).    

I recommend starting with the WSP Builder from CodePlex. This add-in to Visual Studio helps you make features quickly by compiling the wsp for you, without you having to create the ddf or manifest files. Using the WSP Builder Template, choose New –> Project –> WSPBuilder –> WSPBuilder Project.    

You will first need to add references in your project to SharePoint and Microsoft.Office.Server, which can typically be found in the C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\ISAPI directory.    

Next, right-click on the WebPart Project Name, and select Add –> New Item. Choose WSPBuilder –> Web Part Feature. Name the Web Part feature, and set your scope. For the purposes of this excercise, we’ll use the Site scope, which sets the feature at the Site Collection level. This will create the appropriate files and folders for you under the 12 directory    

OrgChartPic1    

Let’s start by setting 2 private variables for the class      

  private int orgChartIndex = 0;
  private int levelsToReturn = 3;   

 

The orgChartIndex is used to keep track of which item you’re on, so that you can populate Google’s array items properly.  The levelsToReturn is how many levels deep of hierarchy you wish to display.  

Next, let’s set the Render Method. The Render method simply calls the PopulateOrg method.
UPDATE:  Updated this code to include the “up and down” feature 

        protected override void Render(HtmlTextWriter writer)
        {
            try
            {
                if (Page.IsPostBack)
                {
     		  // This is used to determine if you're navigating up or down the tree
                    //writer.Write(this.Page.Request["__EVENTARGUMENT"]);
                    string loginName = System.Web.HttpUtility.UrlDecode(this.Page.Request["__EVENTARGUMENT"]);
                    if (!string.IsNullOrEmpty(loginName))
                    {
                        if (loginName.StartsWith("UP:"))
                        {
                            writer.Write(PopulateOrg(loginName.Substring(3), true));
                        }
                        else if (loginName.StartsWith("DN:"))
                        {
                            writer.Write(PopulateOrg(loginName.Substring(3), false));
                        }
                    }
                }
                else
                {
                    writer.Write(PopulateOrg(null, false));
                }
            }
            catch (Exception ex)
            {
                writer.Write(ex.Message + " " + ex.StackTrace);
            }
        }   

 

The PopulateOrg method is where we figure out at which level to start listing the tree at. For my example, I’ve started 1 level up – with the logged in user’s Manager. It then populates all of the direct reports down the tree for 3 levels total, which is what we specified in the variable at the top. 

This code assumes that you have the Manager field in the user’s Profile populated. If you have a custom field, just replace PropertyConstants.Manager with the name of your custom field.

UPDATE:  This was modified to use the “Up and Down” feature.

        private string PopulateOrg(string overrideString, bool isUp)
        {
            try
            {
                string s = "";

                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    // Open current site
                    using (SPSite secureSite = new SPSite(SPContext.Current.Site.ID))
                    {
                        ServerContext siteContext = ServerContext.GetContext(secureSite);
                        UserProfileManager profManager = new UserProfileManager(siteContext);

                        bool runUser = true;
                        if (!string.IsNullOrEmpty(overrideString))
                        {
                            if (profManager.UserExists(overrideString))
                            {
                                runUser = false;
                                UserProfile clickedUsersProfile = profManager.GetUserProfile(overrideString);

                                if (isUp)
                                {
                                    string clickedManagersLogin = GetUserProfileProperty(clickedUsersProfile, PropertyConstants.Manager);

                                    if (!string.IsNullOrEmpty(clickedManagersLogin))
                                    {
                                        if (profManager.UserExists(clickedManagersLogin))
                                        {
                                            UserProfile clickedManagersProfile = profManager.GetUserProfile(clickedManagersLogin);
                                            s = OutputManager(clickedManagersProfile);
                                        }
                                    }
                                    else
                                    {
                                        s = OutputManager(clickedUsersProfile);
                                    }
                                }
                                else
                                {
                                    s = OutputManager(clickedUsersProfile);
                                }
                            }
                            else
                            {
                                runUser = true;
                            }
                        }

                        if (runUser)
                        {
                            string currentUserLogin = SPContext.Current.Web.CurrentUser.LoginName;
                            if (profManager.UserExists(currentUserLogin))
                            {
                                UserProfile currentUsersProfile = profManager.GetUserProfile(currentUserLogin);
                                string managersLogin = GetUserProfileProperty(currentUsersProfile, PropertyConstants.Manager);

                                if (!string.IsNullOrEmpty(managersLogin))
                                {
                                    if (profManager.UserExists(managersLogin))
                                    {
                                        UserProfile managersProfile = profManager.GetUserProfile(managersLogin);
                                        s = OutputManager(managersProfile);
                                    }
                                    else
                                    {
                                        s = OutputManager(currentUsersProfile);
                                    }
                                }
                                else
                                {
                                    s = OutputManager(currentUsersProfile);
                                }
                            }
                        }
                    }
                });

                return s;
            }
            catch (Exception ex)
            {
                return ex.ToString();
            }
        }

     

The OutputManager method is where we start populating the html and script necessary to set up the org chart. NOTE – use https://www.google.com/jsapi if you are running this code on a secure site.
UPDATE:  Don’t use https, rather upload the jsapi into your document library and reference that.

     

  // ******************************************************************
  // ******************************************************************
  //
  // Some code in this section will need to be replaced with your
  // specific information
  //
  // ******************************************************************
  // ******************************************************************

        private string OutputManager(UserProfile profile)
        {
            StringBuilder sb = new StringBuilder();

            sb.Append("\n\n <p><div id='google_org_chart_div_ZoomInfo'>Zoom Level is 100%</div></p>");
            sb.Append("\n\n <p><div style='height=300px;width=1000px;overflow:auto'><div style='zoom=1' id='google_org_chart_div'></div></div></p>");

            sb.Append("\n<a href='javascript:ZoomIn()'>Zoom In</a>");
            sb.Append("&nbsp;&nbsp;<a href='javascript:ZoomOut()'>Zoom Out</a>");

            sb.Append("\n\n<script type='text/javascript' src='http://www.google.com/jsapi'></script>");

            // If you are running HTTPS then download the jsapi from google and put it into a library on your site
            //sb.Append("\n\n<script type='text/javascript' src='https://YOUR-URL-HERE/Shared%20Documents/jsapi'></script>");

            sb.Append("\n<script type='text/javascript'> ");
            sb.Append("\n var zoom = 1; ");
            sb.Append("\n function ZoomOut() { ");
            sb.Append("\n    zoom = zoom - .1; ");
            sb.Append("\n    if (zoom < .10) { zoom = .10; } ");
            sb.Append("\n    var div = document.getElementById('google_org_chart_div'); ");
            sb.Append("\n    var infoDiv = document.getElementById('google_org_chart_div_ZoomInfo'); ");
            sb.Append("\n    infoDiv.innerText = 'Zoom Level is ' + Math.round(zoom*100).toString() + '%'; ");
            sb.Append("\n    if(div.style.zoom) { div.style.zoom = zoom; } ");
            sb.Append("\n } ");
            sb.Append("\n function ZoomIn() { ");
            sb.Append("\n    zoom = zoom + .10; ");
            sb.Append("\n    if (zoom > 2) { zoom = 2; } ");
            sb.Append("\n    var div = document.getElementById('google_org_chart_div'); ");
            sb.Append("\n    var infoDiv = document.getElementById('google_org_chart_div_ZoomInfo'); ");
            sb.Append("\n    infoDiv.innerText = 'Zoom Level is ' + Math.round(zoom*100).toString() + '%'; ");
            sb.Append("\n    if(div.style.zoom) { div.style.zoom = zoom; } ");
            sb.Append("\n } ");
            sb.Append("\n google.load('visualization', '1', {packages:['orgchart']}); ");
            sb.Append("\n google.setOnLoadCallback(drawChart); ");
            sb.Append("\n function drawChart() { ");
            sb.Append("\n    var data = new google.visualization.DataTable(); ");
            sb.Append("\n    data.addColumn('string', 'Name'); ");
            sb.Append("\n    data.addColumn('string', 'Manager'); ");
            sb.Append("\n    data.addColumn('string', 'ToolTip'); ");

            string managersName = GetUserProfileProperty(profile, PropertyConstants.PreferredName);
            managersName = managersName.Replace("'", "");

            sb.Append("\n    data.addRows(1); ");
            sb.Append("\n    data.setCell(0, 0, '" + managersName + "', '" + GetBoxHTML(profile) + "'); ");
            sb.Append("\n    data.setCell(0, 2, '" + managersName + "'); ");

            orgChartIndex = 0;
            foreach (UserProfile directReport in profile.GetDirectReports())
            {
                sb.Append(OutputDirectReport(directReport, managersName, 2));
            }

            sb.Append("\n    var orgchart = new google.visualization.OrgChart(document.getElementById('google_org_chart_div')); ");
            sb.Append("\n    orgchart.draw(data, {allowCollapse:true,allowHtml:true,size:'small'}); ");
            sb.Append("\n } ");
            sb.Append("\n </script>");
            return sb.ToString();
        }

    

        private static string GetUserProfileProperty(UserProfile profile, string propertyName)
        {
            if (profile[propertyName] != null)
            {
                if (profile[propertyName].Value != null)
                {
                    try
                    {
                        return profile[propertyName].Value.ToString();
                    }
                    catch { return string.Empty; }
                }
            }

            return string.Empty;
        }

    

This also calls the OutputDirectReport function, which is a recursive function that gets the strings for the Google data element

    

        private string OutputDirectReport(UserProfile directReport, string managersName, int level)
        {
            orgChartIndex++;

            string s = string.Empty;
            string directReportsName = GetUserProfileProperty(directReport, PropertyConstants.PreferredName);
            directReportsName = directReportsName.Replace("'", "");

            s += "\n    data.addRows(1); ";
            s += "\n    data.setCell(" + orgChartIndex.ToString() + ", 0, '" + directReportsName + "', '" + GetBoxHTML(directReport) + "'); "; // Name and other
            s += "\n    data.setCell(" + orgChartIndex.ToString() + ", 1, '" + managersName + "'); ";       // Manager
            s += "\n    data.setCell(" + orgChartIndex.ToString() + ", 2, '" + directReportsName + "'); ";  // Tooltip

            foreach (UserProfile dirReport in directReport.GetDirectReports())
            {
                if (level < levelsToReturn)
                {
                    s += OutputDirectReport(dirReport, directReportsName, level + 1);
                }
            }

            return s;
        }
What goes inside the box of the org chart node can be HTML code if that’s what you have specified, which we did above. Therefore, we’re calling a method to get the information we want to put in the node in the GetBoxHTML method.
  // ******************************************************************
  // ******************************************************************
  //
  // Some code in this section will need to be replaced with your
  // specific information
  //
  // ******************************************************************
  // ******************************************************************

        private string GetBoxHTML(UserProfile directReport)
        {
            string directReportsName = GetUserProfileProperty(directReport, PropertyConstants.PreferredName);
            directReportsName = directReportsName.Replace("'", "");

            string directReportsLoginName = GetUserProfileProperty(directReport, PropertyConstants.AccountName);

            // If this is the logged in user, highlight it in red.
            if (directReportsLoginName.ToLower() == SPContext.Current.Web.CurrentUser.LoginName.ToLower())
            {
                directReportsName = "<font color=\"red\">" + directReportsName + "</font>";
            }

            string directReportsEmail = GetUserProfileProperty(directReport, PropertyConstants.WorkEmail);
            string directReportsPhone = GetUserProfileProperty(directReport, PropertyConstants.WorkPhone);

            // Replace here...
            string postBackHTMLUp = "<a  id=\"" + orgChartIndex.ToString() + "_Up\" href=\"javascript:" + Page.ClientScript.GetPostBackEventReference(this, "UP:" + directReportsLoginName.Replace("'", "").Replace(@"YOUR-DOMAIN-HERE\", @"YOUR-DOMAIN-HERE\\")) + "\">";
            postBackHTMLUp += "<img src=\"/_layouts/images/ReportsUp.gif\" border=\"0\"></a>";
            postBackHTMLUp = postBackHTMLUp.Replace("'", "\\'");

            // ... and here...
            string postBackHTMLDn = "<a  id=\"" + orgChartIndex.ToString() + "_Dn\" href=\"javascript:" + Page.ClientScript.GetPostBackEventReference(this, "DN:" + directReportsLoginName.Replace("'", "").Replace(@"YOUR-DOMAIN-HERE\", @"YOUR-DOMAIN-HERE\\")) + "\">";
            postBackHTMLDn += "<img src=\"/_layouts/images/ReportsDown.gif\" border=\"0\"></a>";
            postBackHTMLDn = postBackHTMLDn.Replace("'", "\\'");

            // ... and here.
            return string.Format("<b>{4}{5}&nbsp;&nbsp;<a href=\"https://mysite.YOUR-MYSITE-URL-HERE/Person.aspx?accountname={0}\">{1}</a></b><br/><a href=\"mailto:{2}\">{2}</a><br/>{3}", System.Web.HttpUtility.UrlEncode(directReportsLoginName.Replace("'", "")), directReportsName, directReportsEmail, directReportsPhone, postBackHTMLUp, postBackHTMLDn);
        }
Install this solution using the WSP Builder add-in, if you’re coding on your SharePoint Server, or build the WSP file and move that over to your server, and install it using the STSADM.exe tool
We reference a helper method here which simply outputs the property in a string format, checking for nulls and empty strings.
Advertisements

9 responses to “MOSS – Graphical Organizational Chart

  1. Pingback: SharePoint 2007 – Org Chart Web Part Options « SharePoint Sherpa

  2. Walid October 28, 2009 at 4:57 pm

    Its a very interresting Post !!!!!!!!

  3. Mark November 13, 2009 at 3:40 pm

    Hi Sue,
    I wanted to display an clickable chart of site hierarchy and your post tipped me off to the Google library.

    Thanks a lot!

    Mark

  4. vishal April 14, 2010 at 2:33 pm

    I liked your approach for creating the orgchart.Can i have the source code for the above explained project.I really need it.
    If you have your code with the sarepoint list as a source for people and groups that would be gr8.
    Thanks in Advance !!

    • Susan Hernandez April 14, 2010 at 4:23 pm

      Vishal:
      I will have to check my source code and clean it to make sure I have no client information in it. As for where it gets its data from, it pulls it from the SharePoint User Profile. You could get it instead from a SharePoint List, but that might be tricky setting up.

      Finally, I use a plug-in called WSP Builder to build my projects, so if you want to easily build your WSP and deploy, you will need that plugin from CodePlex.

      I’ll get back to you as soon as I can when I have the final source code, hopefully today.

  5. Susan Hernandez April 14, 2010 at 6:17 pm

    I HAVE UPDATED THE CODE to include the “Up and Down” links. I have also added a Word document with the source code.

    Please simply read the relatively short license agreement.

    Thanks.

  6. Prathibha November 26, 2010 at 10:00 am

    Hi,

    Currently I am getting a Manager and Subordinates deails in an org chart for A USER Selected (i.e 3 layer only).

    1. As per my requirement, I should be able to expand from Manager lever to the further UP and from Subordinates level tothe further DOWN.

    2. I would like to have this expand collapse event trigger on image click.Currently it is happening on doubleclickin each box in the org chart.

    3. I would like to highlight the User Selected.

    Please let me know in case there are any suggestios on this.

    Thanks and Regards,
    Prathibha

  7. Andy January 8, 2012 at 10:59 pm

    HI Susan,

    Just a suggestion why dont you put your project on Codeplex its will be much more safe and people are able to find all the data about your Project.

    • Susan Hernandez January 10, 2012 at 10:02 pm

      Hi Andy
      To me this is an “Old” and unpolished application…however since it continues to be popular I’ll think about doing that! Thanks for dropping me a comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: