It’s been close to a year since I published my Out-SquarifiedTreemap function (and blogged about it) after spending a number of weeks prior working on it to ensure that everything was accurate when used and also trying to figure out a good algorithm to handling the sizes of each square as well as the heatmap on it.

Fast forward those 11 months and I haven’t really done anything with it other than adding some code examples for those who might have been interested on the GitHub page. I use the code in the real world but I admit that the output is rather hard to read unless you use the tooltip when you hover over each square to see what the data actually is. Fortunately another individual by the name of Aaron Nelson (Blog | Twitter) who is a Data Platform MVP happened to see this and hit me up to ask about adding some features to it. Naturally I couldn’t resist the ideas for improvement and proceeded to implement a couple of I feel were much needed updates to the function.

Label for Each Square

Honestly, this one was way overdue to implement. Having the squares with their various sizes and colors for the heatmap gave a great view into how things shaped up, not having an ‘at a view’ glance due to no labels made the UI a little painful to read unless you hover over each square for its respective tooltip.

It took a little bit of time to figure out how I wanted to do this. I wanted something that would scale appropriately depending on the size of the square. That meant that I didn’t want to worry about the font size at all and didn’t want it to look like it was constrained to its own text box. The Label control jumped out at me because doesn’t show up as though it is in a box and looking like it was just typed in. This works nicely until you try to use this in a Canvas layout. My testing was done using a Grid layout control which makes scaling of the text simple whereas a Canvas is the lowest form of a layout as the user has complete control of where the child controls can be placed and sized with pretty much no constraints at all.

This was a problem. I didn’t want a label that didn’t scale properly…or at all for that matter. Enter the ViewBox control which allows me to place the label inside of it and lets me scale treat the Label as though it was in the Grid by scaling the text based on the size of each square it is in. Perfect! Using these controls, I can then create the control and then just size it as the same size as my rectangle that shows off each item and place it within the same coordinates as the rectangle so it appears like it is the same thing. The last thing that I needed to do was to turn off the hit detection on the Viewbox and Label so only the Rectangle would be detected when clicked on (which plays a major role in the –PassThru support). The code that handles the creation of the Label and Viewbox is here:

Function New-ViewBox { Param( $Width, $Height, $Text ) $Label = new-object System.Windows.Controls.Label $Viewbox = new-object System.Windows.Controls.Viewbox $DropShadow = New-Object System.Windows.Media.Effects.DropShadowEffect $DropShadow.Opacity = 5 $DropShadow.BlurRadius = 5 $DropShadow.Color = 'Black' $DropShadow.ShadowDepth = 0 $Viewbox.Stretch = 'Uniform' $Viewbox.Width = $Width $Viewbox.Height = $Height $Label.IsHitTestVisible = $False $Label.FontFamily = 'Calibri' $Label.FontWeight = 'Bold' $Label.Foreground = 'White' $Label.Content = $Text $Label.Effect = $DropShadow $Viewbox.AddChild($Label) Return $Viewbox }

I can call it in the same way that I called my New-Rectangle function and place it in the same location on the canvas.

If ($ShowLabel) { $Viewbox = New-ViewBox -Width $_.Width -Height $_.Height -Text $_.$ShowLabel [void]$Canvas.Children.Add($Viewbox) [System.Windows.Controls.Canvas]::SetLeft($Viewbox,$_.Coordinate.X) [System.Windows.Controls.Canvas]::SetTop($Viewbox,$_.Coordinate.Y) }

Ah yes, the –ShowLabel parameter is what will make up whether the label is actually shown as well as what type of label will be displayed on the UI. Currently, there are 3 possible values that you can use: LabelProperty, DataProperty and HeatmapProperty. You might notice that these are named after the same parameters that already exist in the function. They are directly related and will display the respective label on the UI when used.

$Tooltip = { @" Process Name <PID>: $($This.LabelProperty) <$($This.ObjectData.Id)> WorkingSet Memory(MB): $([math]::Round(($This.DataProperty/1MB),2)) "@ } Get-Process | Sort-Object -prop WS -Descending | Select -First 8 | Out-SquarifiedTreeMap -Tooltip $Tooltip -LabelProperty ProcessName -DataProperty WS -HeatmapProperty WS -Width 600 -Height 400 -ShowLabel LabelProperty

Looks like Chrome is definitely a memory hog based on the UI being displayed. As you can see, the text does some nice scaling based on the size of each rectangle. Naturally,the smaller the box the smaller the text, so at that point you will want to rely on the tooltip for those cases.

Providing PassThru Support

Next up is providing PassThru support so that if you wanted to do more than just view the pretty UI, you can specify the –PassThru parameter and then pick an item that you want to do more with so that it will pass through the original object that was sent to the UI. Pretty handy if you wanted to stop the process with the most memory utilization or needed to do more with some files or folders or anything else that you can think of! Setting this up meant that I had to specify a command to run when one of the rectangles has been clicked on. One problem that I ran into was that I am already tracking when I click on the mouse button to drag the UI around with the mouse. This gets thrown out of the window when using –PassThru and if you did want to move the window around, you need to be holding down on one of the CTRL buttons before attempting to click and move it.

Some of the code that sets all of this up is below:

#region TabControl event handler [System.Windows.RoutedEventHandler]$Global:RectangleKeyDownChangeHandler = { Write-Verbose "[KeyDwnhandler-CTRLKeyDown] $($Script:KeyDown)" If ($Script:KeyDown) { Try { Write-Verbose "[KEYUP] DragMove" $Window.DragMove() } Catch {Write-Warning $_} } } $Window.AddHandler([System.Windows.Shapes.Rectangle]::MouseLeftButtonUpEvent, $RectangleKeyDownChangeHandler) [System.Windows.RoutedEventHandler]$Global:RectangleKeyUpChangeHandler = { If ($Script:IsPassThru -AND -NOT $Script:KeyDown) { If ($_.OriginalSource -is [System.Windows.Shapes.Rectangle]) { $Source = $_.OriginalSource $Script:Result = $DataHash.TreeMapData | Where { $_.Tag -eq $Source.Tag } | Select-Object -ExpandProperty ObjectData $Window.Close() } } } $Window.AddHandler([System.Windows.Shapes.Rectangle]::MouseLeftButtonUpEvent, $RectangleKeyUpChangeHandler) #endregion TabControl event handler

This code sets up the event handlers for each rectangle by making use of the bubble up eventing so that regardless of which rectangle is clicked, the handler will fire and proceed with the appropriate actions.

To handle the output when you click the UI, I have a piece of code at the end that runs after the UI itself is closed. This ensures that if there was a selection picked, that it would actually send the object onto the next command.

#Show UI Write-Verbose "[END] Show UI" [void]$Window.ShowDialog() Write-Verbose "[END] UI Close" If ($IsPassThru) { Write-Verbose "Output Object" $Result }

I’ll make use of the same code earlier but now I will add the –PassThru piece so I could attempt to stop a process that was appearing to be hogging up my much needed memory.

$Tooltip = { @" Process Name <PID>: $($This.LabelProperty) <$($This.ObjectData.Id)> WorkingSet Memory(MB): $([math]::Round(($This.DataProperty/1MB),2)) "@ } Get-Process | Sort-Object -prop WS -Descending | Select -First 8 | Out-SquarifiedTreeMap -Tooltip $Tooltip -LabelProperty ProcessName -DataProperty WS -HeatmapProperty WS -Width 600 -Height 400 ` -PassThru -ShowLabel LabelProperty | Stop-Process –WhatIf

So with that, there are a few new things that have been added to my function that will hopefully make using this UI a little more useful than previously.

As stated earlier, you can find this function over at GitHub: https://github.com/proxb/SquarifiedTreemap

And as always, feel free to let me know of new features that you would like to see or go ahead and submit a Pull Request and let me see what you have in mind!