Reports are one of those things that everyone has done at some point in time using a variety of methods to include, Excel, XML, CSV, HTML, etc.… A lot of times, while the data is one thing to present, there are times when we need to just present a snapshot of information, usually in the form of a chart of some kind. This is where we usually will dig into Excel and its COM object to take the data and create a quick chart to show the data. But doing this can be slow as working with COM objects are notoriously slow (they are much faster with PowerShell V5), so another approach might be better instead. Enter the .Net Chart Controls which gives us a way to take the data that we have and quickly create a chart such as a pie chart or a line chart and present the data with little to no effort on the system.

What Kind of Charts are Available?

There are a little over 30 possible charts at your disposal ranging from a pie chart to a bar chart or even a bubble chart if you wanted one.

Some of these charts like the Pie and Doughnut have a lot in common and you can use the same type of approach with the data to quickly present information while working with a Line chart might require a little more work to ensure the data is presented accurately and also provides support for multiple “Series” of data which allows for you to provide a comparison between different points of time for your data (useful in column or bar charts). For a better look at each chart type and the expectations associate with each chart (such as number of series allowed), the following link has the information to look at: https://msdn.microsoft.com/en-us/library/dd489233.aspx

What is a Series of Data?

A series of data can be looked at as different captures of data that will be applied to a chart. One example is that you can track the current capacity (Series1) of a hard drive as well as its current drive usage (Series2) over the course of several months and see how the current drive usage changes during the course of the time. Given, the capacity may not change at all if it is a physical drive, but may change if the drive is a virtual drive or SAN attached. Something like this would make for a good line chart.

Another example would be to track the memory (or CPU) utilization of several processes. Here you would take a reading at the beginning (Series1) and then wait maybe a minute or so and take another reading (Series2). From these two samples, you can then display the results as a Bar chart or a Column chart to get an idea of the differences in values, if there happen to be differences.

Where do I begin?

Glad you asked. If you are running PowerShell V3+ then you are good to go and have everything already installed, but if you happen to be running PowerShell 2.0, then odds are you might need to download and install the required bits for the Microsoft Chart Controls for Microsoft .NET Framework 3.5 here.

Let’s Build a Pie Chart!

Building a Pie chart is pretty simply as we only require a single series of data which will consist of a label for the data and its value. In this case we are going to chart out our processes by their WorkingSet (WS) property to see what our top 10 memory hogs are.

$Processes = Get-Process | Sort-Object WS -Descending | Select-Object -First 10

Now we need to do a few other things before we start diving into the world of chart controls. First off I am going to define a couple of helper functions that will assist in some areas.

Edit (10/02/2016): Using a hashtable originally was probably a bad idea when you consider multiple same named processes (or anything that is used more than once with the same name) as it one will just overwrite the next meaning the last one will win. I have since taken out the ability to create the hashtable and used a different approach.

#region Helper Functions Function Invoke-SaveDialog { $FileTypes = [enum]::GetNames('System.Windows.Forms.DataVisualization.Charting.ChartImageFormat')| ForEach { $_.Insert(0,'*.') } $SaveFileDlg = New-Object System.Windows.Forms.SaveFileDialog $SaveFileDlg.DefaultExt='PNG' $SaveFileDlg.Filter="Image Files ($($FileTypes))|$($FileTypes)|All Files (*.*)|*.*" $return = $SaveFileDlg.ShowDialog() If ($Return -eq 'OK') { [pscustomobject]@{ FileName = $SaveFileDlg.FileName Extension = $SaveFileDlg.FileName -replace '.*\.(.*)','$1' } } } #endregion Helper Functions

Next up is loading the required types to work with the chart controls as well as the windows forms.

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

If you are still using the old way ([void][Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms.DataVisualization”)) then you should look at using Add-Type instead.

Next up is to set create our Chart, ChartArea and Series objects as well as making it easier to find all of our available charts by saving the Enum to a variable.

$Chart = New-object System.Windows.Forms.DataVisualization.Charting.Chart $ChartArea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea $Series = New-Object -TypeName System.Windows.Forms.DataVisualization.Charting.Series $ChartTypes = [System.Windows.Forms.DataVisualization.Charting.SeriesChartType]

Now picking a chart is as simple as using $ChartTypes:

$Series.ChartType = $ChartTypes::Pie

With this, we have defined our series as being a Pie chart (default is a line chart). What is interesting here is that we are not defining our chart type where you would have expected it to be at (in the $Chart) but instead define it within the Series object. Now we do end up placing the Series object within the chart and then the Chart within the ChartArea. Note:I don’t actually need a ChartArea with a Pie chart, but am including this for the sake of covering all of the pieces of the chart build.

$Chart.Series.Add($Series) $Chart.ChartAreas.Add($ChartArea)

You can almost visually see how these all stack within one another.

I want to ensure that the Name (X axis) is the Name of the process and that the WS (Y axis) is the WS property which holds the data value. This way, when we apply it to the series, the pie chart control will understand how to present the data.

Note that I am using the DataBindXY method to load my data. The first item in the method parameter has to be the X value which is my label and the Y axis is the corresponding data. Because I am using PowerShell V4+, I can get away with just specifying the property name and it will automatically unroll the values of each property.

$Chart.Series['Series1'].Points.DataBindXY($Process.Name, $Process.WS)

The ‘Series1’ is a default name for the series (you can name it something else if you wish) and any subsequent series added will be Series2,3,4 and so forth if left at the default names.

With the data added for our pie chart, I can now work to make some adjustments to the size of the chart as well as its position and background color.

$Chart.Width = 700 $Chart.Height = 400 $Chart.Left = 10 $Chart.Top = 10 $Chart.BackColor = [System.Drawing.Color]::White $Chart.BorderColor = 'Black' $Chart.BorderDashStyle = 'Solid'

All good charts should have a title, right? How else would we know what the chart might be about if a title is not there to tell us what is going on. With that in mind, we will add a title that gives a brief description about what is being displayed.

$ChartTitle = New-Object System.Windows.Forms.DataVisualization.Charting.Title $ChartTitle.Text = 'Top 5 Processes by Working Set Memory' $Font = New-Object System.Drawing.Font @('Microsoft Sans Serif','12', [System.Drawing.FontStyle]::Bold) $ChartTitle.Font =$Font $Chart.Titles.Add($ChartTitle)

Typically, if I want to add a legend along with a pie chart, I will avoid having anything on the actual chart itself and leave the description for each piece to be in the legend. This is just a personal preference, but if you want, you can certainly have both. With that in mind, I will show two alternative approaches for the chart display with and without the legend.

Using a Legend

As I am using a legend here, I want to avoid any data from being displayed on the chart itself, so I will make sure to disable the pie chart styles.

$Chart.Series[‘Series1’][‘PieLabelStyle’] = ‘Disabled’

The next step is to set up my legend so it displays useful information.

$Legend = New-Object System.Windows.Forms.DataVisualization.Charting.Legend $Legend.IsEquallySpacedItems = $True $Legend.BorderColor = 'Black' $Chart.Legends.Add($Legend) $chart.Series["Series1"].LegendText = "#VALX (#VALY)"

And now I have my configurations completed for including a legend with my chart. Note that the VALX will display the values of the X axis while the VALY displays the Y value. So in this case I will have the Process name as VALX and the Working Set (WS) memory as VALY in the parentheses.

Avoiding a Legend

Ok, so adding a legend wasn’t really in the cards and we just want to show the chart, but at the still time have the items labeled so we know what the pieces of the pie mean. Simple enough, we will just add some more configurations to add the data point labels.

$Chart.Series['Series1']['PieLineColor'] = 'Black' $Chart.Series['Series1']['PieLabelStyle'] = 'Outside' $Chart.Series['Series1'].Label = "#VALX (#VALY)"

Now we are set! All that is really left to do is display the results of our work. But before we do that, we need to define a WinForm object that will host the chart object and properly display our work.

#region Windows Form to Display Chart $AnchorAll = [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right -bor [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left $Form = New-Object Windows.Forms.Form $Form.Width = 740 $Form.Height = 490 $Form.controls.add($Chart) $Chart.Anchor = $AnchorAll # add a save button $SaveButton = New-Object Windows.Forms.Button $SaveButton.Text = "Save" $SaveButton.Top = 420 $SaveButton.Left = 600 $SaveButton.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right # [enum]::GetNames('System.Windows.Forms.DataVisualization.Charting.ChartImageFormat') $SaveButton.add_click({ $Result = Invoke-SaveDialog If ($Result) { $Chart.SaveImage($Result.FileName, $Result.Extension) } }) $Form.controls.add($SaveButton) $Form.Add_Shown({$Form.Activate()}) [void]$Form.ShowDialog() #endregion Windows Form to Display Chart

The result is a chart that we can display to people with the added bonus of being able to save it via a save button.

With Legend

Without Legend

A 3D Touch

If you want to give this a little better look by making the chart 3D, then you can add the following code to your chart configuration to make it a little more eye popping. And yes, we finally managed to sneak in some use of the $ChartArea in this demo.

$ChartArea.Area3DStyle.Enable3D=$True $ChartArea.Area3DStyle.Inclination = 50

And just like that, instant 3D chart!

Saving a File

But what if I wanted to save a file instead? That’s fine, we can completely skip the process of creating the WinForm and instead make use of the builtin SaveImage method and supplying the file name as well as the extension of the file to save the image as a specific file type.

We can find the supported values here:

[enum]::GetValues([System.Windows.Forms.DataVisualization.Charting.ChartImageFormat])

Now we can save the chart using the code below:

$Chart.SaveImage('C:\temp\chart.jpeg', 'jpeg')

Where is My Cool Function at?

Yea, so about that function. I decided instead of just building a function to display a pie chart, that I would instead work on and build a module that would allow you to use a variety of charts instead! Stay tuned to https://github.com/proxb/PoshCharts (look at the Dev branch) and you will soon see a working module that not only does pie charts like shown today, but others such as a bar or line chart! Being that this is still in development, I don’t really have any help put together…yet. But as soon as this is more polished I will be updating this blog post (and posting another blog) so you can check it out! And as always, if anyone wants to dive in and help with this, then fork the repo and submit some Pull Requests and I will work to get them merged.