Building a Chart Using PowerShell and Chart Controls

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.

image

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.

image

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.

image

$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

pie-legend

Without Legend

pie

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

3d-pie

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.

image

We can find the supported values here:

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

image

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.

This entry was posted in powershell and tagged , , , . Bookmark the permalink.

6 Responses to Building a Chart Using PowerShell and Chart Controls

  1. jshewbridge says:

    The Out-LineChart.ps1 .EXAMPLE code cites “import-csv .\TrendReporting\data.csv”, but I don’t see that file in GitHub. Can you post its contents?

    Thanks.

  2. Dirk says:

    Hi Boe,
    Chad Miller created a module to use MS Chart Controls via PowerShell quite some time ago: http://sev17.com/2009/07/08/powershell-charting-with-ms-chart-controls/. Maybe you can use it and make it better.

  3. Max Kozlov says:

    Charts are cool, but way you get your data is not nice 🙂
    iexplore for example have several processes when you open several pages, all with great memory pressure and your hash show only last(smallest from top 10) process !
    thus for chart correctness data binding must looks like

    $Chart.Series[‘Series1’].Points.DataBindXY($Processes.Name, $Processes.WS)

    • Boe Prox says:

      Good catch! Naturally in a hash table there would be no process of the same name :). I’m going to update the blog to use that approach vs. messing with a hash table.

  4. Justin Marshall says:

    Hi Boe,

    Nice article. i’ve built a powershell function that i use for analyzing IIS response times, one is a scatter chart showing response time (logarithmic scale) on the y axis and time on the x axis and the other is a histogram of response time vs occurences. One struggle i have is that the number of datapoints can be quite large so i scale the point size dynamically. however when the points become small (50k+ points) they are almost indistinguishable from each other (shape) in the legend. Looking at the .net documentation the formatting of these markers needs to be handled by an event during the rendering process however i’m unable to get this to work. it seems like the event is either not called or if it does, it doesn’t affect the output. Have you encountered this problem? how did you work around it?

  5. A reader says:

    This is beautiful. I’ve used the chart controls in ASP.NET before but I never thought about using them from powershell. It’s a good way to generate pretty email reports.
    Thanks for the tip!

Leave a comment