Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# AsBuiltReport.Chart — Copilot Instructions

## Architecture Overview

This project is a **hybrid C# + PowerShell module**. The C# layer compiles into platform-specific DLLs; the PowerShell layer loads the right DLL at import time and exposes cmdlets.

```
Sources/ ← C# library (ScottPlot + SkiaSharp, netstandard2.0)
Chart.cs ← Static base class; all chart settings live here as static props
PieChart.cs / BarChart.cs / StackedBarChart.cs / SignalChart.cs ← Chart implementations
PowerShell/ ← One PSCmdlet class per chart type (New-*Chart cmdlets)
Enums/Enums.cs ← All shared enums (BasicColors, Formats, ColorPalettes, etc.)

AsBuiltReport.Chart/ ← PowerShell module
AsBuiltReport.Chart.psm1 ← Loads the correct DLL based on PSEdition + OS + architecture
AsBuiltReport.Chart.psd1 ← Module manifest; exports: New-PieChart, New-BarChart,
New-StackedBarChart, New-SingleStackedBarChart, New-SignalChart
Src/Assemblies/ ← Pre-compiled DLLs, organized by platform:
Core/linux-x64/
Core/windows-x64/
Core/mac-osx/osx-arm64/ and osx-x64/
Desktop/windows-x64/ ← Windows PowerShell 5.1 (PSEdition Desktop)

Tests/
AsBuiltReport.Chart.Functions.Tests.ps1 ← Pester v5 tests
Invoke-Tests.ps1 ← Test runner script
```

## Build & Test Commands

### Build the C# library (must rebuild after any Sources/ change)

```bash
# Linux
dotnet publish ./Sources -c Release -r linux-x64
cp Sources/bin/Release/netstandard2.0/linux-x64/publish/* AsBuiltReport.Chart/Src/Assemblies/Core/linux-x64/

# Windows (PowerShell 7)
dotnet publish ./Sources -c Release -r win-x64
Copy-Item ./Sources/bin/Release/netstandard2.0/win-x64/publish/* ./AsBuiltReport.Chart/Src/Assemblies/Core/windows-x64 -Recurse

# Windows PowerShell 5.1 (Desktop)
Copy-Item ./Sources/bin/Release/netstandard2.0/win-x64/publish/* ./AsBuiltReport.Chart/Src/Assemblies/Desktop/windows-x64 -Recurse

# macOS (Apple Silicon)
dotnet publish ./Sources -c Release -r osx-arm64
Copy-Item ./Sources/bin/Release/netstandard2.0/osx-arm64/publish/* ./AsBuiltReport.Chart/Src/Assemblies/Core/mac-osx/osx-arm64 -Recurse
```

### Run all Pester tests

```powershell
.\Tests\Invoke-Tests.ps1
```

### Run a single test (by name filter)

```powershell
$cfg = New-PesterConfiguration
$cfg.Run.Path = '.\Tests'
$cfg.Filter.FullName = '*New-PieChart*'
Invoke-Pester -Configuration $cfg
```

### Lint (PSScriptAnalyzer)

```powershell
Invoke-ScriptAnalyzer -Path . -Recurse -Settings .\.github\workflows\PSScriptAnalyzerSettings.psd1
```

Excluded rules: `PSUseToExportFieldsInManifest`, `PSReviewUnusedParameter`, `PSUseDeclaredVarsMoreThanAssignments`, `PSAvoidGlobalVars`.

## Key Conventions

### Adding a new chart type

Every new chart type requires changes in both layers:

1. **C# side** (`Sources/`): Add `<NewType>.cs` (chart logic) and `PowerShell/<NewType>Pwsh.cs` (the PSCmdlet class).
2. **PowerShell side**: Add the exported function name to `FunctionsToExport` in `AsBuiltReport.Chart.psd1`.
3. **Tests**: Add a `Context '<New-XChart>'` block in `Tests/AsBuiltReport.Chart.Functions.Tests.ps1`.

### Chart static-state pattern

`Chart` properties are **static**. Every `ProcessRecord()` override **must** call `Chart.Reset()` first to clear state from the previous invocation before setting new property values.

### Cross-platform path construction

Use `[System.IO.Path]::DirectorySeparatorChar` (not hardcoded `/` or `\`) when building paths in `.psm1` or C# code. The `.psm1` uses the format string pattern:

```powershell
"$PSScriptRoot{0}Src{0}Assemblies{0}..." -f [System.IO.Path]::DirectorySeparatorChar
```

### StackedBarChart Values layout

`-Values` accepts a **jagged array**: one inner `double[]` per bar/label. Wrapping a single array requires the unary comma operator to prevent flattening:

```powershell
New-StackedBarChart -Values @(,[double[]]@(1, 2)) -Labels @('A') -LegendCategories @('X','Y') ...
```

### OutputFolderPath validation

The `ValidatePath` custom attribute (defined in `PieChartPwsh.cs`) validates that the directory exists. Pass `-OutputFolderPath $TestDrive` in tests to use Pester's temp path.

### Chart cmdlet return value

All `New-*Chart` cmdlets return a `System.IO.FileSystemInfo` object (the saved file), not a string path. Tests assert `Should -BeOfType 'System.IO.FileSystemInfo'`.

### Branching

- Target PRs against the **`dev`** branch.
- Follow [Keep a Changelog](https://keepachangelog.com/) when updating `CHANGELOG.md`.
- Follow the [PowerShell Best Practices and Style Guide](https://github.com/PoshCode/PowerShellPracticeAndStyle).
- Use PascalCasing for all public member, type, and namespace names.
69 changes: 37 additions & 32 deletions .github/workflows/Release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Publish PowerShell Module

on:
release:
types: [published]
types: [ published ]

jobs:
publish-to-psgallery:
Expand All @@ -18,23 +18,28 @@ jobs:
- name: Build the library for MacOS X64
run: dotnet publish ./Sources -c Release -r osx-x64
- name: Copy the library for MacOS X64
run: copy ./Sources/bin/Release/netstandard2.0/osx-x64/publish/*.* ./AsBuiltReport.Chart/Src/Assemblies/Core/mac-osx/osx-x64
run: copy ./Sources/bin/Release/netstandard2.0/osx-x64/publish/*.*
./AsBuiltReport.Chart/Src/Assemblies/Core/mac-osx/osx-x64
Comment on lines 20 to +22
- name: Build the library for MacOS Arm64
run: dotnet publish ./Sources -c Release -r osx-arm64
- name: Copy the library for MacOS Arm64
run: copy ./Sources/bin/Release/netstandard2.0/osx-arm64/publish/*.* ./AsBuiltReport.Chart/Src/Assemblies/Core/mac-osx/osx-arm64
run: copy ./Sources/bin/Release/netstandard2.0/osx-arm64/publish/*.*
./AsBuiltReport.Chart/Src/Assemblies/Core/mac-osx/osx-arm64
Comment on lines 25 to +27
- name: Build the library for Linux
run: dotnet publish ./Sources -c Release -r linux-x64
- name: Copy the library for Linux
run: copy ./Sources/bin/Release/netstandard2.0/linux-x64/publish/*.* ./AsBuiltReport.Chart/Src/Assemblies/Core/linux-x64
run: copy ./Sources/bin/Release/netstandard2.0/linux-x64/publish/*.*
./AsBuiltReport.Chart/Src/Assemblies/Core/linux-x64
Comment on lines 30 to +32
- name: Build the library for Windows
run: dotnet publish ./Sources -c Release -r win-x64
- name: Copy the library for Windows
run: copy ./Sources/bin/Release/netstandard2.0/win-x64/publish/*.* ./AsBuiltReport.Chart/Src/Assemblies/Core/windows-x64
run: copy ./Sources/bin/Release/netstandard2.0/win-x64/publish/*.*
./AsBuiltReport.Chart/Src/Assemblies/Core/windows-x64
Comment on lines 35 to +37
- name: Build the library for Windows PowerShell
run: dotnet publish ./Sources -c Release -r win-x64
- name: Copy the library for Windows PowerShell
run: copy ./Sources/bin/Release/netstandard2.0/win-x64/publish/*.* ./AsBuiltReport.Chart/Src/Assemblies/Desktop/windows-x64
run: copy ./Sources/bin/Release/netstandard2.0/win-x64/publish/*.*
./AsBuiltReport.Chart/Src/Assemblies/Desktop/windows-x64
Comment on lines 40 to +42
- name: Run tests
run: dotnet test --no-build --verbosity normal ./Sources
- name: Set PSRepository to Trusted for PowerShell Gallery
Expand All @@ -49,30 +54,30 @@ jobs:
shell: pwsh
run: |
Publish-Module -Path .\AsBuiltReport.Chart\ -NuGetApiKey ${{ secrets.PSGALLERY_API_KEY }} -Verbose
tweet:
needs: publish-to-psgallery
runs-on: ubuntu-latest
steps:
- uses: Eomm/why-don-t-you-tweet@v2
# We don't want to tweet if the repository is not a public one
if: ${{ !github.event.repository.private }}
with:
# GitHub event payload
# https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#release
tweet-message: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #AsBuiltReport #PowerShell"
env:
TWITTER_CONSUMER_API_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
TWITTER_CONSUMER_API_SECRET: ${{ secrets.TWITTER_CONSUMER_API_SECRET }}
TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
bsky-post:
needs: publish-to-psgallery
runs-on: ubuntu-latest
steps:
- uses: zentered/bluesky-post-action@v0.3.0
with:
post: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #AsBuiltReport #PowerShell"
env:
BSKY_IDENTIFIER: ${{ secrets.BSKY_IDENTIFIER }}
BSKY_PASSWORD: ${{ secrets.BSKY_PASSWORD }}
# tweet:
# needs: publish-to-psgallery
# runs-on: ubuntu-latest
# steps:
# - uses: Eomm/why-don-t-you-tweet@v2
# # We don't want to tweet if the repository is not a public one
# if: ${{ !github.event.repository.private }}
# with:
# # GitHub event payload
# # https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#release
# tweet-message: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #AsBuiltReport #PowerShell"
# env:
# TWITTER_CONSUMER_API_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
# TWITTER_CONSUMER_API_SECRET: ${{ secrets.TWITTER_CONSUMER_API_SECRET }}
# TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
# TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
# bsky-post:
# needs: publish-to-psgallery
# runs-on: ubuntu-latest
# steps:
# - uses: zentered/bluesky-post-action@v0.3.0
# with:
# post: "[New Release] ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}! Check out what's new! ${{ github.event.release.html_url }} #AsBuiltReport #PowerShell"
# env:
# BSKY_IDENTIFIER: ${{ secrets.BSKY_IDENTIFIER }}
# BSKY_PASSWORD: ${{ secrets.BSKY_PASSWORD }}

4 changes: 2 additions & 2 deletions AsBuiltReport.Chart/AsBuiltReport.Chart.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
RootModule = 'AsBuiltReport.Chart.psm1'

# Version number of this module.
ModuleVersion = '0.3.1'
ModuleVersion = '0.3.2'

# Supported PSEditions
# CompatiblePSEditions = @()
Expand Down Expand Up @@ -69,7 +69,7 @@
# NestedModules = @()

# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = 'New-PieChart', 'New-BarChart', 'New-StackedBarChart', 'New-SignalChart'
FunctionsToExport = 'New-PieChart', 'New-BarChart', 'New-StackedBarChart', 'New-SignalChart', 'New-SingleStackedBarChart'

# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
# CmdletsToExport = '*'
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.3.2] - 2026-05-05

### Added

- Add New-SingleStackedBarChart cmdlet and update module exports
- Add pester test to validate the functionality of the New-SingleStackedBarChart cmdlet

### Changed

- Update module version to 0.3.2

## [0.3.1] - 2026-04-23

### Changed
Expand Down
95 changes: 95 additions & 0 deletions Examples/Example10.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<#
.SYNOPSIS
Example 10 - Basic Single Stacked Bar Chart

.DESCRIPTION
This example demonstrates how to create a Single Stacked Bar Chart using the
AsBuiltReport.Chart module.

A Single Stacked Bar Chart displays one bar divided into stacked segments, each representing
a percentage contribution to the total. It is ideal for visualising how a whole is broken
down into parts — for example, storage capacity usage across different categories.

The chart displays a vSAN datastore capacity breakdown as a percentage of total raw capacity:
- VM Data : 55 %
- Snapshots : 15 %
- Deduplication : 10 %
- Overhead : 12 %
- Free : 8 %
#>

[CmdletBinding()]
param (
[System.IO.DirectoryInfo] $Path = (Get-Location).Path,
[string] $Format = 'png'
)

<#
Starting with PowerShell v3, modules are auto-imported when needed. Importing the module here
ensures clarity and avoids ambiguity.
#>

# Import-Module AsBuiltReport.Chart -Force -Verbose:$false

<#
Since the chart output is a file, specify the output folder path using $OutputFolderPath.
#>

$OutputFolderPath = Resolve-Path $Path

<#
Define the data to be displayed in the chart.

For a Single Stacked Bar Chart:
- $Values is a flat double array. Each element is the percentage value of one segment.
- $Label is a single string that labels the one bar on the category axis.
- $LegendCategories is an array of strings, one name per segment, shown in the legend.

In this example, the segment labels rendered inside the bar include the '%' suffix
because `-ValueSuffix '%'` is specified when the chart is created.
The value axis (Y-axis for Vertical, X-axis for Horizontal) is also formatted as percentages.
#>

$ChartTitle = 'vSAN Datastore - Capacity Breakdown (%)'
$Label = 'Total'
$LegendCategories = @('VM Data', 'Snapshots', 'Deduplication', 'Overhead', 'Free')
$Values = [double[]]@(55, 15, 10, 12, 8)

<#
The New-SingleStackedBarChart cmdlet generates the Single Stacked Bar Chart image.

-Title : Sets the chart title displayed at the top of the image.
-Values : Flat double array — one value per segment, expressed as a percentage.
-Label : String label for the single bar on the category axis.
-LegendCategories : Array of segment names shown in the legend.
-EnableLegend : Enables the legend on the chart.
-LegendOrientation : Sets legend layout direction (Vertical or Horizontal).
-LegendAlignment : Positions the legend (e.g. LowerCenter, UpperRight).
-LegendFontSize : Sets the legend text font size in points.
-LabelFontSize : Sets the font size of the axis and segment labels.
-AreaOrientation : Vertical (default) renders the bar growing upward; Horizontal grows right.
-Width : Output image width in pixels.
-Height : Output image height in pixels.
-Format : Output file format (e.g. png, jpg, svg).
-OutputFolderPath : Directory where the generated chart file will be saved.
-Filename : Name of the output file (without extension).
-ValueSuffix : Appends a suffix to the segment labels and value axis labels (e.g. '%').
#>

New-SingleStackedBarChart `
-Title $ChartTitle `
-Values $Values `
-Label $Label `
-LegendCategories $LegendCategories `
-EnableLegend `
-LegendOrientation Horizontal `
-LegendAlignment LowerCenter `
-LegendFontSize 12 `
-LabelFontSize 13 `
-AreaOrientation Vertical `
-Width 400 `
-Height 500 `
-Format $Format `
-OutputFolderPath $OutputFolderPath `
-Filename 'Example10-SingleStackedBarChart' `
-ValueSuffix '%'
Loading
Loading