- How to create and use Adaptive Cards - Thu, Oct 5 2023
- Using AWS Lambda functions with Docker containers - Fri, May 12 2023
- A Go AWS SDK example - Fri, Nov 11 2022
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:
- Set up the initial framework.
- Format the chart, i.e., font and alignment.
- Create the chart area.
- Define the chart area.
- Style the chart.
- 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
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'
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 } } }
wonderful post , thank you
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
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
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.
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?
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.
Sorry the block got above the text 🙂
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
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:
So just call your first column “name” and it works.
Hope this helps.
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
$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.
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
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
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".
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.
Or you can just do this:
@"
'$pass'
"@
'40'
Which will work fine 🙂
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:
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 🙂
Hey Graham beer! Any plans to make it work on PowerShell 7? Great function!
Does anyone know what properties to set to ensure a Pie or Doughnut chart starts plotting at 0 degrees/12 o'clock?