Skip to content
ChartForgeX

ChartForgeX in PowerShell

Charts, service maps, dashboards and animated stories from the objects your scripts already have, in Windows PowerShell 5.1 and PowerShell 7. Every script on this page ran as shown against the published module, next to the files it wrote and the console output it printed.

ChartForgeX commands
77
ImagePlayground
3.3.0
Browsers needed
0
Install-Module -Name ImagePlayground -Scope CurrentUser
service-health.pngwritten by the script below
Service health dashboard written by the script below: three metric cards, a services table, a checklist and an Orders latency chart against a 250 ms target.
PS> ./service-health.ps1
Name     Status  Requests P95
----     ------  -------- ---
Orders   Warning 15402    318
Search   Healthy 30117    146
Identity Healthy 21980    112
Gateway  Healthy 48210    84

A service health dashboard from four objects

Metric cards, a status table, a checklist and a latency chart composed into one grid. The same grid writes a PNG for chat and email and an HTML page for a browser.

PowerShellservice-health.ps1
# Service data from a monitoring API, a CSV export or Get-Counter.
$services = @'
Name,Status,Requests,P95
Gateway,Healthy,48210,84
Identity,Healthy,21980,112
Orders,Warning,15402,318
Search,Healthy,30117,146
'@ | ConvertFrom-Csv
$week = 182, 190, 201, 262, 310, 298, 214   # Orders p95, last seven days
$theme = 'Dark'

$total = ($services | Measure-Object Requests -Sum).Sum
$slowest = $services | Sort-Object { [int] $_.P95 } | Select-Object -Last 1
$healthy = @($services | Where-Object Status -eq 'Healthy').Count

# -PassThru returns the chart so the dashboard can embed it.
$latency = New-ImageChart -Title "$($slowest.Name) latency" -Subtitle 'Last seven days' `
    -XLabel Wed, Thu, Fri, Sat, Sun, Mon, Tue -FilePath latency.png `
    -Width 932 -Height 320 -Theme $theme -ShowGrid -PassThru `
    -Options (New-ImageChartOptions -ShowLegend -LegendPosition Bottom -TickCount 4) {
    New-ImageChartLine -Name "$($slowest.Name) p95 (ms)" -Value $week -Marker Circle
    New-ImageChartLine -Name 'Target' -Value (@(250) * $week.Count) -Color '#eeb640'
}

# Pipe the rows in and pick columns the way Format-Table does.
$table = $services | New-ImageTableBlock -Title Services -StatusColumn Status `
    -Theme $theme -Width 616 -Height 270 -Property Name, Status,
    @{ Name = 'Requests'; Expression = { [int] $_.Requests }; FormatString = 'N0'; Alignment = 'Right' },
    @{ Name = 'P95'; Expression = { "$($_.P95) ms" }; Alignment = 'Right' }

$checks = @{
    Title  = 'Checks'
    Item   = 'TLS expiry', 'Backups', 'Queue depth'
    Value  = '41 days', 'OK', '1,204'
    Status = 'Positive', 'Positive', 'Warning'
    Theme  = $theme; Width = 300; Height = 270
}

$card = @{ Width = 300; Height = 170; Theme = $theme }
$grid = @{
    Title = 'Service health'; Subtitle = 'Week 41'; Columns = 3
    Gap = 16; Padding = 24; AdaptiveRowHeights = $true; Theme = $theme
}
$dashboard = New-ImageVisualGrid @grid -ContentDefinition {
    New-ImageMetricCard -Label Requests -Value $total -Format N0 -Status Positive `
        -Trend '+8% vs last week' -MiniValues 98, 101, 104, 110, 116 @card
    New-ImageMetricCard -Label 'Slowest p95' -Value ([int] $slowest.P95) -Unit ms `
        -Caption $slowest.Name -Status Warning -MiniValues $week @card
    New-ImageMetricCard -Label Healthy -Value "$healthy of $($services.Count)" `
        -Caption services -Status Positive @card
    New-ImageVisualGridItem -Block $table -ColumnSpan 2
    New-ImageListBlock @checks
    New-ImageVisualGridItem -Chart $latency -ColumnSpan 3
}

# One dashboard, two outputs: PNG for chat and email, HTML for a browser.
foreach ($file in 'service-health.png', 'service-health.html') {
    $dashboard | New-ImageVisualStory -FilePath $file
}

$services | Sort-Object { [int] $_.P95 } -Descending | Format-Table -AutoSize

Charts from pipeline objects

Each chart starts from rows a script already has: Import-Csv, Group-Object or a hashtable. The console block under every chart is what the script printed.

Trend against a target

Two series, a target line and release markers. One definition renders PNG and SVG.

Trend against a target: latency-trend.png as written by latency-trend.ps1.
PowerShelllatency-trend.ps1
# Fourteen days of p95 latency, oldest first.
$p95 = 182, 175, 190, 188, 201, 240, 262, 310, 298, 214, 205, 198,
    190, 186
$target = 250

$chart = @{
    Title    = 'Orders API latency'
    Subtitle = 'p95 against the 250 ms target'
    XLabel   = 1..$p95.Count | ForEach-Object { "D$_" }
    YTitle   = 'p95 latency (ms)'
    Width    = 760
    Height   = 380
    ShowGrid = $true
    Options  = New-ImageChartOptions -ShowLegend -TickCount 5
}
$series = {
    New-ImageChartLine -Name 'Orders API' -Value $p95 -Smooth -Marker Circle
    New-ImageChartLine -Name 'Target' -Value (@($target) * $p95.Count) -Color '#c78404'
}
$events = {
    New-ImageChartAnnotation -X 6 -Y 0 -Text 'Release 4.2'
    New-ImageChartAnnotation -X 10 -Y 0 -Text 'Index fix'
}

# One definition, two formats: the extension picks the renderer.
foreach ($file in 'latency-trend.png', 'latency-trend.svg') {
    New-ImageChart $series -FilePath $file @chart -AnnotationsDefinition $events
}

$above = 1..$p95.Count | Where-Object { $p95[$_ - 1] -gt $target }
"Days above target: $($above -join ', ')"
"Peak: $(($p95 | Measure-Object -Maximum).Maximum) ms"
PS> ./latency-trend.ps1
Days above target: 7, 8, 9
Peak: 310 ms

Compliance by ring

Group-Object turns machine rows into percentages; progress bars show them.

Compliance by ring: patch-compliance.png as written by patch-compliance.ps1.
PowerShellpatch-compliance.ps1
# One row per deployment ring, as a WSUS or Intune export gives it.
$rings = @'
Ring,Machines,UpToDate
Servers,48,46
Workstations,412,367
Laptops,236,175
'@ | ConvertFrom-Csv | Select-Object Ring, Machines, UpToDate,
    @{ Name = 'Compliant'; Expression = {
        [int] (100 * $_.UpToDate / $_.Machines) } }

$options = New-ImageChartOptions -ProgressMaximum 100 `
    -ShowDataLabels -NoProgressHandles -NoLegend
New-ImageChart -Title 'Patch compliance' -Options $options `
    -FilePath patch-compliance.png -Width 560 -Height 260 {
    foreach ($ring in $rings) {
        New-ImageChartProgress $ring.Ring $ring.Compliant
    }
}

$rings | Format-Table -AutoSize
PS> ./patch-compliance.ps1
Ring         Machines UpToDate Compliant
----         -------- -------- ---------
Servers      48       46              96
Workstations 412      367             89
Laptops      236      175             74

Storage by department

Share sizes summed per department and drawn as a labelled treemap.

Storage by department: storage-treemap.png as written by storage-treemap.ps1.
PowerShellstorage-treemap.ps1
# Share sizes, as a storage report or a Get-ChildItem sum gives.
$shares = @'
Share,Department,SizeGB
\\fs01\eng-builds,Engineering,640
\\fs01\eng-source,Engineering,412
\\fs02\eng-media,Engineering,188
\\fs01\sales,Sales,655
\\fs02\finance,Finance,412
\\fs02\marketing,Marketing,318
\\fs02\hr,HR,96
'@ | ConvertFrom-Csv

$departments = $shares | Group-Object Department | ForEach-Object {
    [pscustomobject]@{
        Department = $_.Name
        Shares     = $_.Count
        SizeGB     = [int] ($_.Group |
            Measure-Object SizeGB -Sum).Sum
    }
} | Sort-Object SizeGB -Descending

New-ImageChart {
    foreach ($d in $departments) {
        New-ImageChartTreemap -Name $d.Department -Value $d.SizeGB
    }
} -Options (New-ImageChartOptions -ShowDataLabels -NoLegend) `
    -FilePath storage-treemap.png -Width 640 -Height 320

$departments | Format-Table -AutoSize
PS> ./storage-treemap.ps1
Department  Shares SizeGB
----------  ------ ------
Engineering      3   1240
Sales            1    655
Finance          1    412
Marketing        1    318
HR               1     96

Month-over-month cost bridge

A waterfall from an ordered hashtable: the keys become labels, the values become steps.

Month-over-month cost bridge: cost-bridge.png as written by cost-bridge.ps1.
PowerShellcost-bridge.ps1
# Last month's bill, then each change that moved it.
$steps = [ordered]@{
    September  = 4200
    Compute    = 380
    Reserved   = -250
    Storage    = 610
    Licences   = -140
}

New-ImageChart {
    New-ImageChartWaterfall -Name 'Cloud cost (EUR)' `
        -Value @($steps.Values) -Labels @($steps.Keys)
} -ShowGrid -FilePath cost-bridge.png -Width 640 -Height 340 `
    -Options (New-ImageChartOptions -ShowDataLabels -NoLegend)

$steps.GetEnumerator() | Format-Table -AutoSize `
    @{ Name = 'Step'; Expression = 'Key' },
    @{ Name = 'EUR'; Expression = 'Value' }
"October: {0:N0} EUR" -f ($steps.Values | Measure-Object -Sum).Sum
PS> ./cost-bridge.ps1
Step       EUR
----       ---
September 4200
Compute    380
Reserved  -250
Storage    610
Licences  -140

October: 4,800 EUR

Stacked bars from a CSV

Titles, category labels and the bar layout are parameters, so a CSV export becomes a stacked chart in one command.

Stacked bars from a CSV: builds.png as written by stacked-builds.ps1.
PowerShellstacked-builds.ps1
$builds = @'
Day,Passed,Failed
Mon,41,3
Tue,38,6
Wed,45,2
Thu,52,4
Fri,47,1
'@ | ConvertFrom-Csv

# Titles, category labels and the bar layout are all parameters.
$chart = @{
    Title    = 'Builds by result'
    Subtitle = 'Week 41, main branch'
    XLabel   = $builds.Day
    Width    = 640
    Height   = 360
    ShowGrid = $true
    FilePath = 'builds.png'
    Options  = New-ImageChartOptions -BarLayout Stacked -ShowLegend `
        -LegendPosition Bottom -Palette '#11776f', '#d64545'
}
New-ImageChart @chart {
    New-ImageChartBar -Name Passed -Value $builds.Passed
    New-ImageChartBar -Name Failed -Value $builds.Failed
}

$builds | Format-Table -AutoSize
PS> ./stacked-builds.ps1
Day Passed Failed
--- ------ ------
Mon 41     3
Tue 38     6
Wed 45     2
Thu 52     4
Fri 47     1

Maps, org charts and motion

Topology and organization charts take nodes and links from data. Positions, statuses and labels come from columns, not from a drawing tool.

Service map from a dependency list

Tiers become columns and callers become edges. The same map writes a PNG and an interactive HTML page with pan, zoom and node details.

Service map from a dependency list: service-map.png as written by service-map.ps1.
PowerShellservice-map.ps1
# Who calls whom, from a CMDB export or a config file.
$services = @'
Id,Name,Kind,Tier,Status,Callers,Latency
gateway,API gateway,Gateway,0,Healthy,,
identity,Identity,Service,1,Healthy,gateway,
orders,Orders,Service,1,Warning,gateway,
search,Search,Service,1,Healthy,gateway,
sql,Orders DB,Database,2,Warning,orders,41 ms
events,Events,Queue,2,Healthy,orders search,
'@ | ConvertFrom-Csv

$map = {
    # -Layer puts each tier in its own column; the layout does the rest.
    foreach ($s in $services) {
        New-ImageTopologyNode -Id $s.Id -Label $s.Name -Kind $s.Kind `
            -Status $s.Status -Layer $s.Tier -Width 180 -Height 64
    }
    foreach ($s in $services) {
        foreach ($caller in (-split $s.Callers) -ne '') {
            New-ImageTopologyEdge $caller $s.Id $s.Latency `
                -Direction Forward -Status $s.Status
        }
    }
}

$view = @{
    TopologyDefinition   = $map
    Title                = 'Orders platform'
    Subtitle             = 'Database latency above target'
    Layout               = 'Layered'
    Direction            = 'LeftToRight'
    Width                = 900
    Height               = 420
    FitContentToViewport = $true
}
New-ImageTopology @view -FilePath service-map.png
New-ImageTopology @view -FilePath service-map.html -InteractiveHtml

$services | Format-Table Name, Kind, Status, Callers -AutoSize
PS> ./service-map.ps1
Name        Kind     Status  Callers
----        ----     ------  -------
API gateway Gateway  Healthy
Identity    Service  Healthy gateway
Orders      Service  Warning gateway
Search      Service  Healthy gateway
Orders DB   Database Warning orders
Events      Queue    Healthy orders search

Organization chart from directory data

Manager links become the hierarchy; disabled accounts are flagged for review.

Organization chart from directory data: org-chart.png as written by org-chart.ps1.
PowerShellorg-chart.ps1
# The shape Get-ADUser -Properties Title, Manager returns.
$users = @'
SamAccountName,Name,Title,Manager,Enabled
avery,Avery Stone,Head of IT,,True
morgan,Morgan Lee,Infrastructure lead,avery,True
sam,Sam Park,Service desk lead,avery,True
alex,Alex Kim,Network engineer,morgan,True
jordan,Jordan Bell,Cloud engineer,morgan,False
casey,Casey Ward,Analyst,sam,True
robin,Robin Shaw,Analyst,sam,True
'@ | ConvertFrom-Csv

$members = {
    foreach ($user in $users) {
        $member = @{
            Id     = $user.SamAccountName
            Name   = $user.Name
            Role   = $user.Title
            Status = if ($user.Enabled -eq 'True') { 'Healthy' }
                     else { 'Warning' }
        }
        if ($user.Manager) { $member.ParentId = $user.Manager }
        New-ImageOrganizationMember @member
    }
}
New-ImageOrganizationChart $members -TeamLabel 'IT department' `
    -Subtitle 'Disabled accounts flagged' -NoTeamNode `
    -FilePath org-chart.png -Width 960 -Height 420

$users | Format-Table Name, Title, Manager, Enabled -AutoSize
PS> ./org-chart.ps1
Name        Title               Manager Enabled
----        -----               ------- -------
Avery Stone Head of IT                  True
Morgan Lee  Infrastructure lead avery   True
Sam Park    Service desk lead   avery   True
Alex Kim    Network engineer    morgan  True
Jordan Bell Cloud engineer      morgan  False
Casey Ward  Analyst             sam     True
Robin Shaw  Analyst             sam     True

Animated request flow

A scenario walks a request across the map. SVG animates in the browser; GIF works in chat.

Animated request flow: request-flow.svg as written by request-flow.ps1.
PowerShellrequest-flow.ps1
$nodes = @(
    New-ImageTopologyNode browser Browser -Kind Person -X 0 -Y 0
    New-ImageTopologyNode api 'Orders API' -Kind Service -X 260 -Y 0
    New-ImageTopologyNode db 'Orders DB' -Kind Database -X 520 `
        -Y 0 -Status Warning
)
$arrow = @{ Direction = 'Forward' }
$edges = @(
    New-ImageTopologyEdge browser api -Id call @arrow
    New-ImageTopologyEdge api db -Id query @arrow
)

# A scenario names the edges a request travels, in order.
$scenario = New-ImageTopologyScenario order 'Place an order' `
    -StepDefinition {
        New-ImageTopologyScenarioStep call Edge -Label 'POST order'
        New-ImageTopologyScenarioStep query Edge -Label 'Save order'
    }
$motion = New-ImageTopologyMotion -ScenarioId order `
    -DurationSeconds 3 -FramesPerSecond 10

$flow = @{
    Node = $nodes; Edge = $edges; Scenario = $scenario
    Motion = $motion; Layout = 'Manual'
    Width = 720; Height = 160; NoLegend = $true
    FitContentToViewport = $true
}
# SVG animates in the browser; .gif writes the same motion for chat.
New-ImageTopology @flow -FilePath request-flow.svg

$scenario.Steps | Format-Table Id, Kind, Label -AutoSize
PS> ./request-flow.ps1
Id    Kind Label
--    ---- -----
call  Edge POST order
query Edge Save order

Stories and canvases

Replay a terminal session for documentation, or compose a fixed-size card for link previews, from the same objects as the report.

Animated terminal session

Commands and output replayed as animated SVG, HTML or GIF for documentation and release notes.

Animated terminal session: console-story.svg as written by console-story.ps1.
PowerShellconsole-story.ps1
# Rows from your own command. The story replays them; it never
# runs the commands it shows.
$health = @'
Name,Status,P95
Gateway,Running,84 ms
Orders,Degraded,318 ms
Search,Running,146 ms
'@ | ConvertFrom-Csv

$terminal = @{
    Title = 'pwsh'; WorkingDirectory = 'C:\Ops'; Theme = 'Dark'
    WindowStyle = 'WindowsTerminal'; Width = 640
}
$story = New-ImageConsoleStory @terminal -PassThru -Content {
    New-ImageConsoleStoryCommand 'Get-ServiceHealth -Env Prod'
    $health | New-ImageConsoleStoryTable -Property Name, Status, P95
    New-ImageConsoleStoryBlankLine
    New-ImageConsoleStoryCommand 'Restart-OrdersPool -WhatIf'
    New-ImageConsoleStoryOutput 'What if: restart 3 Orders nodes' `
        -Style Warning
    New-ImageConsoleStoryOutput 'Health check passed' -Style Success
}

# SVG and HTML replay in a browser; PNG keeps the final frame.
# .gif and .apng write the same animation for chat tools.
foreach ($ext in 'svg', 'html', 'png') {
    $story | Export-ImageConsoleStory -Path "console-story.$ext"
}

Get-Item console-story.* | Format-Table Name,
    @{ Name = 'KB'; Expression = { [int] ($_.Length / 1KB) } }
PS> ./console-story.ps1
Name               KB
----               --
console-story.html 17
console-story.png  69
console-story.svg  16

Link preview card

A 1200 x 630 canvas of text and info tiles, built from the same objects as the report.

Link preview card: social-card.png as written by social-card.ps1.
PowerShellsocial-card.ps1
$rings = @'
Ring,Compliant,Missing
Servers,96,2
Workstations,89,45
Laptops,74,61
'@ | ConvertFrom-Csv

# SocialPreview is 1200 x 630, the size link previews expect.
New-ImageCanvas -Preset SocialPreview -Title 'Patch compliance' `
    -LayerDefinition {
    $text = @{ X = 72; Width = 1056 }
    New-ImageCanvasText @text -Y 72 -Text 'Patch compliance' `
        -FontSize 58 -Emphasized
    New-ImageCanvasText @text -Y 154 -FontSize 26 `
        -Text 'October cumulative update, three rings'
    $x = 72
    foreach ($ring in $rings) {
        $tile = @{
            X = $x; Y = 260; Width = 336; Height = 176
            Icon = $ring.Ring.Substring(0, 2).ToUpper()
            Label = $ring.Ring; Value = "$($ring.Compliant)%"
            Detail = "$($ring.Missing) machines need updates"
            Progress = $ring.Compliant / 100
            Accent = if ([int] $ring.Compliant -ge 90) { '#37c9b8' }
                     else { '#eeb640' }
        }
        New-ImageCanvasInfoTile @tile
        $x += 360
    }
} -FilePath social-card.png

$rings | Format-Table -AutoSize
PS> ./social-card.ps1
Ring         Compliant Missing
----         --------- -------
Servers      96        2
Workstations 89        45
Laptops      74        61

Desktop wallpapers with PowerBGInfo

PowerBGInfo is a separate Evotec module built on the same engine. Its scripts put machine details, charts, tables and diagrams on Windows desktop and lock-screen wallpapers, and ChartForgeX draws the whole image.

See the wallpapers and build one yourself

Admin workstation wallpaper rendered by PowerBGInfo: health tiles, a privileged sign-in chart and the allowed admin path as a topology diagram. Admin workstation wallpaper rendered by PowerBGInfo, dark theme: health tiles, a privileged sign-in chart and the allowed admin path as a topology diagram.

Every command built on ChartForgeX

ImagePlayground 3.3.0 exports 132 commands for Windows PowerShell 5.1 and PowerShell 7+. These 77 render through ChartForgeX; each name opens its reference page with parameters and examples.

Charts 34

Line, area, bar, donut, radar, heatmap, treemap, waterfall, word cloud and more, as PNG, SVG, JPEG or HTML.

Topology and org charts 12

Service maps, groups, ports, node details, geographic layout, scenarios, route motion and organization charts.

Dashboards and canvases 10

Metric cards, tables, lists and timelines in one grid, plus fixed-size social cards and wallpapers.

Stories, motion and export 21

Animated terminal sessions, source-to-result stories, motion cues, watermarks and visual artifacts.

Coverage and roadmap

ChartForgeX ships features first; ImagePlayground exposes them as commands. This table describes ImagePlayground 3.3.0: rows marked available work from commands alone, and partial rows work with the limits noted, often through -ChartScript, which hands a script the ChartForgeX chart and its .NET API.

  • Available works in ImagePlayground 3.3.0
  • Partial works with the limits noted
  • Planned in ChartForgeX, not yet a command

Charts

Topology and org charts

Dashboards and canvases

Stories and output

Mermaid and Markdown

  • Planned

    Mermaid diagrams rendered by ChartForgeX.Mermaid

    ChartForgeX.Mermaid is not part of the module yet.

  • Planned

    ChartForgeX Markup fences: charts and diagrams from Markdown

    ChartForgeX.Markup is not part of the module yet.

Request a command or vote on the roadmap