The idea to display information as a pie chart came about when I needed to write a summary email about a task I had done with PowerShell. I had the results captured in a CSV file and was about to hit send, when I thought: how quick and easy would it be to interpret this information as a visual chart?
Latest posts by Graham Beer (see all)

Opening a CSV file, going to the bottom to look at the totals of successful and failed or enabled and disabled settings might only take a minute or so. But how much easier is it to view this information as a pie chart?

So I had the idea, how could I do this in PowerShell? Working with PowerShell 5.1, I have the full capabilities of .NET, so you can be sure someone has done this already, be it in C#, VB.NET, or even PowerShell. Initially I wanted to create a solution in Windows Presentation Foundation (WPF), but this wasn't easy. However, I found a way using the Winforms assembly System.Windows.Forms.DataVisualization.

This class exposes all of the properties, methods and events of the Chart Windows control.

This is the first line in the MSDN documentation.

Constructing the pie chart

To start building the chart, I needed to use the namespace System.Windows.Forms.DataVisualization.Charting. The MSDN documentation provides this information. To start working with the assembly, we need to add it to the session with the Add-Type cmdlet:

Add-Type -AssemblyName System.Windows.Forms.DataVisualization

The simplest way to know which properties you can work with is to use Get-Member. Initialize a new instance of the Chart class and pipe it to Get-Member using the -MemberType parameter to look at the properties:

[System.Windows.Forms.DataVisualization.Charting.Chart]::new() | Get-Member -MemberType Properties

By going through the properties and cross-referencing them against the MSDN documentation, you can build up what you need to create the framework of the pie chart. Here are the steps to build the pie chart:

  1. Set up the initial framework.
  2. Format the chart, i.e., font and alignment.
  3. Create the chart area.
  4. Define the chart area.
  5. Style the chart.
  6. Build the function.

The function's Begin block will contain the pie chart's construction. The Process block serves to collate and format the data for the pie chart to use. Within the Process block, there are two If statements: one for collecting the numeric values passed to the function and one for the other labels.

To correctly discern whether the property is numeric, the function parses each property through the double type keyword and if so, captures the property name to a ValueProperty. For the labels, it parses each property and checks whether it has a name property. If so, it sets a LabelProperty variable.

It populates the two array lists by using the InputObject, which is dot notation with the ValueProperty and LabelProperty variables. The function has an option to set these values manually, or you can let the function work it out automatically.

The End block of the function populates the data-bind points of X and Y to the pie chart we constructed in the Begin block. Two parameter switches are variable, either to save the pie chart or display it to the screen.

Using the function to create pie charts

Now that we have our Out-PieChart function, how do we use it? The function relies on correctly formatting the data passed. This means passing properties with values and labels meant to work together.

Time for an example. I'm collecting the top five processes running:

Get-Process |
	Select-Object -First 5 name, pm |
	Out-PieChart -PieChartTitle "Top 5 Windows processes running" -DisplayToScreen
Top five windows processes running

Top five windows processes running

We could also use the function's 3D switch to save the status of services on the machine to a file:

Get-Service |
	Group-Object -Property Status -NoElement |
	Out-PieChart -PieChartTitle "Service Status" -Pie3D -saveImage C:\tmp\PieChart\'Service status.png'

 

Service status

Service status

Summary

The function can take all sorts of information to display it as a pie chart, and even data you have collated in CSV files. Taking data and quickly displaying it as a pie chart aids readability by generating a clear visual. I've added the function below, and the code is up on my GitHub for you to use.

Subscribe to 4sysops newsletter!

Function Out-PieChart {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline)]
        [psobject] $inputObject,
        [Parameter()]
        [string] $PieChartTitle,
        [Parameter()]
        [int] $ChartWidth = 800,
        [Parameter()]
        [int] $ChartHeight = 400,
        [Parameter()]
        [string[]] $NameProperty,
        [Parameter()]
        [string] $ValueProperty,
        [Parameter()]
        [switch] $Pie3D,
        [Parameter()]
        [switch] $DisplayToScreen,
        [Parameter()]
        [string] $saveImage
    )
    begin {
        Add-Type -AssemblyName System.Windows.Forms.DataVisualization
        # Frame
        $Chart = [System.Windows.Forms.DataVisualization.Charting.Chart]@{
            Width       = $ChartWidth
            Height      = $ChartHeight
            BackColor   = 'White'
            BorderColor = 'Black'
        }
        # Body
        $null = $Chart.Titles.Add($PieChartTitle)
        $Chart.Titles[0].Font = "segoeuilight,20pt"
        $Chart.Titles[0].Alignment = "TopCenter"
        # Create Chart Area
        $ChartArea = [System.Windows.Forms.DataVisualization.Charting.ChartArea]::new()
        $ChartArea.Area3DStyle.Enable3D = $Pie3D.ToBool()
        $ChartArea.Area3DStyle.Inclination = 50
        $Chart.ChartAreas.Add($ChartArea)
        # Define Chart Area
        $null = $Chart.Series.Add("Data")
        $Chart.Series["Data"].ChartType = [System.Windows.Forms.DataVisualization.Charting.SeriesChartType]::Pie
        # Chart style
        $Chart.Series["Data"]["PieLabelStyle"] = "Outside"
        $Chart.Series["Data"]["PieLineColor"] = "Black"
        $Chart.Series["Data"]["PieDrawingStyle"] = "Concave"

        $chart.Series["Data"].IsValueShownAsLabel = $true
        $chart.series["Data"].Label = "#PERCENT\n#VALX"
        # Set ArrayList
        $XColumn = [System.Collections.ArrayList]::new()
        $yColumn = [System.Collections.ArrayList]::new()
    }
    process {
        if (-not $valueProperty) {
            $numericProperties = foreach ($property in $inputObject.PSObject.Properties) {
                if ([Double]::TryParse($property.Value, [Ref]$null)) {
                    $property.Name
                }
            }
            if (@($numericProperties).Count -eq 1) {
                $valueProperty = $numericProperties
            }
            else {
                throw 'Unable to automatically determine properties to graph'
            }
        }
        if (-not $LabelProperty) {
            if ($inputObject.PSObject.Properties.Count -eq 2) {
                $LabelProperty = $inputObject.Properties.Name -ne $valueProperty
            }
            elseif ($inputObject.PSObject.Properties.Item('Name')) {
                $LabelProperty = 'Name'
            }
            else {
                throw 'Cannot convert Data'
            }
        }
        # Bind chart columns
        $null = $yColumn.Add($InputObject.$valueProperty)
        $null = $xColumn.Add($inputObject.$LabelProperty)
    }
    end {
        # Add data to chart
        $Chart.Series["Data"].Points.DataBindXY($xColumn, $yColumn)
        # Save file
        if ($psboundparameters.ContainsKey('saveImage')) {
            try{
                if (Test-Path (Split-Path $saveImage -Parent)) {
                    $SaveImage = $pscmdlet.GetUnresolvedProviderPathFromPSPath($saveImage)
                    $Chart.SaveImage($saveImage, "png")
                } else {
                    throw 'Invalid path, the parent directory must exist'
                }
            } catch {
                throw
            }
        }
        # Display Chart to screen
        if ($DisplayToScreen.ToBool()) {
            $Form = [Windows.Forms.Form]@{
                Width           = 800
                Height          = 450
                AutoSize        = $true
                FormBorderStyle = "FixedDialog"
                MaximizeBox     = $false
                MinimizeBox     = $false
                KeyPreview      = $true
            }
            $Form.controls.add($Chart)
            $Chart.Anchor = 'Bottom, Right, Top, Left'
            $Form.Add_KeyDown({
                if ($_.KeyCode -eq "Escape") { $Form.Close() }
            })
            $Form.Add_Shown( {$Form.Activate()})
            $Form.ShowDialog() | Out-Null
        }
    }
}
avatar
20 Comments
  1. Nagi M Mickael 4 years ago

    wonderful post , thank you 

  2. Christian Lindenberg 4 years ago

    Hi Graham,

    that really Looks awesome! This is exactly what I was searching for. But i don't understand your function. 🙁

    What do I have to do in order to Output a custom object to a pie Chart? 

    $obj = New-Object PSObject
    Add-Member -InputObject $obj -MemberType NoteProperty -Name "Property1" -Value "Value1"
    Add-Member -InputObject $obj -MemberType NoteProperty -Name "Property2" -Value "Value2"

    $obj | Out-PieChart -PieChartTitle "Custom PS Object" -DisplayToScreen

    –> I get this error every time: "Unable to automatically determine properties to graph"

    Thank you very much!

    Christian

  3. Author
    Graham Beer (Rank 2) 4 years ago

    Hi Christian

    The problem you have is what you are trying to convert,

    Property1 Property2
    ——— ———
    Value1 Value2

    There are no values.

    Take a look at something like this for an example:

    @’
    Value1, 200
    Value2, 300
    ‘@ | ConvertFrom-Csv -Header Property1, Property2 |
    Out-PieChart -LabelProperty Property1 -ValueProperty Property2 -PieChartTitle “Custom PS Object” -DisplayToScreen

    Note how I can provide my own label with “LabelProperty” and the “ValueProperty”. So the “Property1” has two labels of Value1 and Value2, with corresponding values of 200 and 300.
    Have a play with this and give me a shout if I can help further.

    Thanks

    Graham

    avatar
  4. Matthew 3 years ago

    Awesome function, Graham!  Thanks very much for writing this.  I implemented it with a quick modification to the section on displaying to the screen.   Your original code did not change the window size when the user specified a different chart width/height, but this modification does.

            # Display Chart to screen
            if ($DisplayToScreen.ToBool()) {
                $Form = [Windows.Forms.Form]@{
                    Width           = $ChartWidth
                    Height          = $ChartHeight + 30
                    AutoSize        = $true
                    FormBorderStyle = "FixedDialog"
                    MaximizeBox     = $false
                    MinimizeBox     = $false
                    KeyPreview      = $true
                }
                $Form.controls.add($Chart)
                $Chart.Anchor = 'Bottom, Right, Top, Left'
                $Form.Add_KeyDown({
                    if ($_.KeyCode -eq "Escape") { $Form.Close() }
                })
                $Form.Add_Shown( {$Form.Activate()})
                $Form.ShowDialog() | Out-Null
            }
     
    
    avatar
  5. Brooke 3 years ago

    I have a table in variable $aa with two columns…

    Column A | Column B

    Client1 | 11

    Client2 | 20

    Client3 | 2

    (Both columns show up as type strings in get-member)

    I use the function like $aa | Out-PieChart -PieChartTitle "Top Clients and Quantity" -DisplayToScreen

    I get the error "Cannot Convert Data"

     

    Is that because it cannot convert Column2 into an integer?  How do I get around that?

    • Leos Marek (Rank 4) 3 years ago
      if (-not $valueProperty) {
                  $numericProperties = foreach ($property in $inputObject.PSObject.Properties) {
                      if ([Double]::TryParse($property.Value, [Ref]$null)) {
                          $property.Name
                      }
                  }
                  if (@($numericProperties).Count -eq 1) {
                      $valueProperty = $numericProperties
                  }
                  else {
                      throw 'Unable to automatically determine properties to graph'
                  }

      I dont think it can work with two string objects. Even the example posted by Graham few comments above gives the same error: Cannot convert Data. If you check the function code, there is a block which is determining a property that is numeric, by trying to Double it. 

       

    • Leos Marek (Rank 4) 3 years ago

      Sorry the block got above the text 🙂

  6. Author
    Graham Beer (Rank 2) 3 years ago

    I had to update the code a little while ago, discovered a few bugs.

    Find the updated code here, https://github.com/Graham-Beer/PSCharts/blob/master/Out-PieChart.ps1

  7. Author
    Graham Beer (Rank 2) 3 years ago

    The issue appears to be the first column needs to be called “Name”.
    The part of the code processing this are lines 85 to 95:

    if (-not $LabelProperty) {
                if ($inputObject.PSObject.Properties.Count -eq 2) {
                    $LabelProperty = $inputObject.PSObject.Properties.Name -ne $valueProperty
                }
                elseif ($inputObject.PSObject.Properties.Item('Name')) {
                    $LabelProperty = 'Name'
                }
                else {
                    throw 'Cannot convert Data'
                }
            }
    

    So just call your first column “name” and it works.

    @'
    Name, amount
    Client1, 11
    Client2, 20
    Client3, 2
    '@ | ConvertFrom-Csv | select Name, amount | Out-PieChart -PieChartTitle "Top Clients and Quantity" -DisplayToScreen
    

    Hope this helps.

  8. Neha 3 years ago

    can you please tell me why isnt working

    $pass = 33
    $fail = 60

    @'
    Name, amount
    Client1, '$pass'
    Client2, '$fail'
    '@ | ConvertFrom-Csv |  select Name, amount | Out-PieChart  -PieChartTitle "Custom PS Object" -DisplayToScreen

     

    Error: Unable to automatically determine properties to graph.

    and i can not fix values, need in a variables

    avatar
    • Leos Marek (Rank 4) 3 years ago

      $pass = 33
      $fail = 60

      @'
      Name, amount
      Client1, {0}
      Client2, {1}
      '@ -f $pass,$fail

      Its because the @@ is just a text field. It does not evaluate variables. You have to use text formatting to replace the variables with values.

  9. Author
    Graham Beer (Rank 2) 3 years ago

    Make sure you are using double quotes, ", to allow the variable to be expanded.

    i.e. 

    $pass = 40

    Write-Output '$pass'
    $pass

    Write-Output "$pass"

    40

  10. Leos Marek (Rank 4) 3 years ago

    Hi Graham,

    while this is correct for similar commands (where you dont even need to put any quotes), but it does not work if you define the string like

    @'

    '@

    There you need to use the formatting. However it works if the field is defined as

    @"

    "@

    L

  11. Author
    Graham Beer (Rank 2) 3 years ago

    Hi Leos

    The here-string does require double quotes to expand the variable. Try this:

    $pass = 40

    @'

    $pass

    '@

     

    @"

    $pass

    "@

     

    You'll see that the single quotes returns '$pass' while the double quotes returns "40".

    • Leos Marek (Rank 4) 3 years ago

      Yes I agree on that 🙂 If single quote is used (for example because you need to put something with " inside the string) it can be handled with the formatting system.

  12. Author
    Graham Beer (Rank 2) 3 years ago

    Or you can just do this:

    @"

    '$pass'

    "@

    '40'

    Which will work fine 🙂

    • Leos Marek (Rank 4) 3 years ago

      Right. I remember now why I needed the formatting. I needed to create a text field and output that to a ps1 file. The file required to have a variable $result and the value was from another variable earlier in the script:

      $a = 40
      @'
      $result = {0}
      '@ -f $a
      
      
      $result = 40

      if I would put " quotes in the string, it would also try to enumerate $result variable which I needed to have as a text (to generate another script). 

      This is why I got it fixed in my head 🙂

  13. Hey Graham beer! Any plans to make it work on PowerShell 7? Great function!

  14. schoemandre (Rank 1) 3 years ago

    Does anyone know what properties to set to ensure a Pie or Doughnut chart starts plotting at 0 degrees/12 o'clock?

Leave a reply

Your email address will not be published.

*

© 4sysops 2006 - 2023

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account