From d0d9c1b23511acabaea00657a7d6cef9115d71f5 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Sun, 3 May 2026 11:42:22 -0400 Subject: [PATCH 1/9] Adjust axis limits for single bar spacing in BarChart --- Sources/BarChart.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/BarChart.cs b/Sources/BarChart.cs index 5002f17..0892033 100644 --- a/Sources/BarChart.cs +++ b/Sources/BarChart.cs @@ -210,16 +210,16 @@ public object Chart(double[] values, string[] labels, string filename = "output" // Value is on the X-axis for horizontal orientation double xMin = Math.Min(0, singleValue) - padding; double xMax = Math.Max(0, singleValue) + padding; - // Use ±0.5 on the category (Y) axis to match bar index spacing for a single bar - myPlot.Axes.SetLimits(xMin, xMax, -0.5, 0.5); + // Use ±2 on the category (Y) axis to match bar index spacing for a single bar + myPlot.Axes.SetLimits(xMin, xMax, -2, 2); } else { // Value is on the Y-axis for vertical orientation double yMin = Math.Min(0, singleValue) - padding; double yMax = Math.Max(0, singleValue) + padding; - // Use ±0.5 on the category (X) axis to match bar index spacing for a single bar - myPlot.Axes.SetLimits(-0.5, 0.5, yMin, yMax); + // Use ±2 on the category (X) axis to match bar index spacing for a single bar + myPlot.Axes.SetLimits(-2, 2, yMin, yMax); } } From 05a0911eea8c5a9bb18987fde32a41cf96486427 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Sun, 3 May 2026 11:42:59 -0400 Subject: [PATCH 2/9] Adjust bar spacing in StackedBarChart for better visibility --- Sources/StackedBarChart.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/StackedBarChart.cs b/Sources/StackedBarChart.cs index 47f4968..7194b40 100755 --- a/Sources/StackedBarChart.cs +++ b/Sources/StackedBarChart.cs @@ -287,14 +287,14 @@ public object Chart(List values, string[] labels, string[] categoryNam // Horizontal bars: X is value, Y is category (bar index) xMin = minVal; xMax = maxVal; - yMin = barIndex - 0.5; - yMax = barIndex + 0.5; + yMin = barIndex - 2; + yMax = barIndex +2; } else { // Vertical bars: X is category (bar index), Y is value - xMin = barIndex - 0.5; - xMax = barIndex + 0.5; + xMin = barIndex - 2; + xMax = barIndex + 2; yMin = minVal; yMax = maxVal; } From 12444f80ae1c326ba651ad6f8ab8a2efdd195c43 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Sun, 3 May 2026 11:43:21 -0400 Subject: [PATCH 3/9] Add Copilot instructions for project architecture and development guidelines --- .github/copilot-instructions.md | 116 ++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..0e1e898 --- /dev/null +++ b/.github/copilot-instructions.md @@ -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-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 `.cs` (chart logic) and `PowerShell/Pwsh.cs` (the PSCmdlet class). +2. **PowerShell side**: Add the exported function name to `FunctionsToExport` in `AsBuiltReport.Chart.psd1`. +3. **Tests**: Add a `Context ''` 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. From b7344a6425810c97eb8958f02d3cf3b250d0edf2 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Sun, 3 May 2026 14:43:52 -0400 Subject: [PATCH 4/9] Bump module version to 0.3.2, add New-SingleStackedBarChart cmdlet, and update exports. Enhance examples and tests for new functionality. --- AsBuiltReport.Chart/AsBuiltReport.Chart.psd1 | 4 +- CHANGELOG.md | 7 + Examples/Example10.ps1 | 94 +++++++ Examples/Example11.ps1 | 120 ++++++++ Sources/AsBuiltReportChart.csproj | 2 +- Sources/Chart.cs | 4 + .../PowerShell/SingleStackedBarChartPwsh.cs | 263 ++++++++++++++++++ Sources/SingleStackedBarChart.cs | 253 +++++++++++++++++ Sources/StackedBarChart.cs | 2 +- Tests/AsBuiltReport.Chart.Functions.Tests.ps1 | 41 +++ 10 files changed, 786 insertions(+), 4 deletions(-) create mode 100644 Examples/Example10.ps1 create mode 100644 Examples/Example11.ps1 create mode 100644 Sources/PowerShell/SingleStackedBarChartPwsh.cs create mode 100644 Sources/SingleStackedBarChart.cs diff --git a/AsBuiltReport.Chart/AsBuiltReport.Chart.psd1 b/AsBuiltReport.Chart/AsBuiltReport.Chart.psd1 index 2421ffe..f37ef90 100644 --- a/AsBuiltReport.Chart/AsBuiltReport.Chart.psd1 +++ b/AsBuiltReport.Chart/AsBuiltReport.Chart.psd1 @@ -12,7 +12,7 @@ RootModule = 'AsBuiltReport.Chart.psm1' # Version number of this module. - ModuleVersion = '0.3.1' + ModuleVersion = '0.3.2' # Supported PSEditions # CompatiblePSEditions = @() @@ -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 = '*' diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b2c543..a0b8af9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ 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] - Unreleased + +### Changed + +- Update module version to 0.3.2 +- Add New-SingleStackedBarChart cmdlet and update module exports + ## [0.3.1] - 2026-04-23 ### Changed diff --git a/Examples/Example10.ps1 b/Examples/Example10.ps1 new file mode 100644 index 0000000..3f05bde --- /dev/null +++ b/Examples/Example10.ps1 @@ -0,0 +1,94 @@ +<# + .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. + + The segment labels rendered inside the bar automatically include the '%' suffix. + 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 '%' diff --git a/Examples/Example11.ps1 b/Examples/Example11.ps1 new file mode 100644 index 0000000..9c440c6 --- /dev/null +++ b/Examples/Example11.ps1 @@ -0,0 +1,120 @@ +<# + .SYNOPSIS + Example 11 - Single Stacked Bar Chart with Advanced Options + + .DESCRIPTION + This example demonstrates how to create a Single Stacked Bar Chart with advanced visual options + using the AsBuiltReport.Chart module. + + Features demonstrated: + - Horizontal bar orientation + - Custom hex color palette + - Bold title with larger font + - Chart border + - Legend with custom font size + + The chart displays a VMware vSphere cluster resource pool CPU entitlement breakdown showing + how the total CPU allocation is distributed across workload pools: + - Production : 45 % + - Development : 20 % + - Testing : 15 % + - Management : 12 % + - Reserve : 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. + + When -AreaOrientation is set to Horizontal: + - The bar grows left-to-right. + - The value axis (X-axis) is formatted as percentages. + - The category axis (Y-axis) shows the $Label string. +#> + +$ChartTitle = 'vSphere Cluster - CPU Entitlement by Resource Pool (%)' +$Label = 'Cluster-Prod' +$LegendCategories = @('Production', 'Development', 'Testing', 'Management', 'Reserve') +$Values = [double[]]@(45, 20, 15, 12, 8) + +<# + A custom hex color palette gives each segment a distinct colour that can be aligned with + corporate branding or infrastructure classification conventions. + Colors are assigned in the same order as $LegendCategories. +#> + +$CustomColors = @('#2E86AB', '#A23B72', '#F18F01', '#C73E1D', '#3B1F2B') + +<# + The New-SingleStackedBarChart cmdlet generates the Single Stacked Bar Chart image. + + -Title : Sets the chart title displayed at the top of the image. + -TitleFontSize : Sets the font size of the title in points. + -TitleFontBold : Renders the title in bold. + -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. + -EnableChartBorder : Draws a border around the entire chart figure. + -ChartBorderColor : Color of the chart border. + -EnableCustomColorPalette : Enables use of the custom color palette. + -CustomColorPalette : Array of hex color strings, one per segment. + -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 ` + -TitleFontSize 16 ` + -TitleFontBold ` + -Values $Values ` + -Label $Label ` + -LegendCategories $LegendCategories ` + -EnableLegend ` + -LegendOrientation Horizontal ` + -LegendAlignment LowerCenter ` + -LegendFontSize 12 ` + -LabelFontSize 13 ` + -AreaOrientation Horizontal ` + -EnableChartBorder ` + -ChartBorderColor DarkBlue ` + -EnableCustomColorPalette ` + -CustomColorPalette $CustomColors ` + -Width 700 ` + -Height 300 ` + -Format $Format ` + -OutputFolderPath $OutputFolderPath ` + -Filename 'Example11-SingleStackedBarChart-Advanced' ` ` + -ValueSuffix '%' diff --git a/Sources/AsBuiltReportChart.csproj b/Sources/AsBuiltReportChart.csproj index ed09cf0..5a0269a 100644 --- a/Sources/AsBuiltReportChart.csproj +++ b/Sources/AsBuiltReportChart.csproj @@ -3,7 +3,7 @@ netstandard2.0 - 0.3.1 + 0.3.2 diff --git a/Sources/Chart.cs b/Sources/Chart.cs index 39535db..4c5834d 100644 --- a/Sources/Chart.cs +++ b/Sources/Chart.cs @@ -337,6 +337,9 @@ public static double AxesMarginsRight public static BasicColors? FigureBackgroundColor { get; set; } public static BasicColors? DataBackgroundColor { get; set; } + // Value axis suffix (SingleStackedBarChart) + public static string ValueSuffix { get; set; } = ""; + // Watermark settings (All Charts) public static bool EnableWatermark { get; set; } public static string WatermarkText { get; set; } = "Confidential"; @@ -427,6 +430,7 @@ internal static void Reset() _axesMarginsRight = 0.05; FigureBackgroundColor = null; DataBackgroundColor = null; + ValueSuffix = ""; EnableWatermark = false; WatermarkText = "Confidential"; WatermarkAlignment = Alignments.MiddleCenter; diff --git a/Sources/PowerShell/SingleStackedBarChartPwsh.cs b/Sources/PowerShell/SingleStackedBarChartPwsh.cs new file mode 100644 index 0000000..fed9e70 --- /dev/null +++ b/Sources/PowerShell/SingleStackedBarChartPwsh.cs @@ -0,0 +1,263 @@ +using System; +using System.IO; +using System.Management.Automation; + +namespace AsBuiltReportChart.PowerShell +{ + [Cmdlet(VerbsCommon.New, "SingleStackedBarChart")] + public class NewSingleStackedBarChartCommand : Cmdlet + { + [Parameter( + Mandatory = false, + HelpMessage = "Provide a filename for the chart. If not provided, a random filename will be generated." + )] + public string Filename { get; set; } = Chart.GenerateToken(8); + + [Parameter(Mandatory = true, HelpMessage = "Provide an array of doubles representing the percentage value of each segment in the single stacked bar.")] + public double[] Values { get; set; } + + [Parameter(Mandatory = true, HelpMessage = "Provide a string for the label of the single bar on the category axis.")] + public string Label { get; set; } + + [Parameter(Mandatory = true, HelpMessage = "Provide an array of strings for the legend categories, one per segment.")] + public string[] LegendCategories { get; set; } + + // Title settings + [Parameter(Mandatory = true, HelpMessage = "Provide a title for the chart.")] + public string Title { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Enable bold font for the chart title.")] + public SwitchParameter TitleFontBold { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Set the font size for the chart title.")] + public int TitleFontSize { get; set; } = 14; + + [Parameter(Mandatory = false, HelpMessage = "Set the font color for the chart title.")] + public BasicColors TitleFontColor { get; set; } = BasicColors.Black; + + [Parameter(Mandatory = false, HelpMessage = "Enable the legend for the chart.")] + public SwitchParameter EnableLegend { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Set the orientation of the legend.")] + public Enums.Orientations LegendOrientation { get; set; } = Enums.Orientations.Horizontal; + + [Parameter(Mandatory = false, HelpMessage = "Set the alignment of the legend.")] + public Enums.Alignments LegendAlignment { get; set; } = Enums.Alignments.LowerCenter; + + [Parameter(Mandatory = false, HelpMessage = "Set the font size for the legend.")] + public int LegendFontSize { get; set; } = 14; + + [Parameter(Mandatory = false, HelpMessage = "Set the font color for the legend.")] + public BasicColors LegendFontColor { get; set; } = BasicColors.Black; + + [Parameter(Mandatory = false, HelpMessage = "Set the border style for the legend.")] + public Enums.BorderStyles LegendBorderStyle { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Set the border size for the legend.")] + public int LegendBorderSize { get; set; } = 1; + + [Parameter(Mandatory = false, HelpMessage = "Set the border color for the legend.")] + public BasicColors LegendBorderColor { get; set; } = BasicColors.Black; + + [Parameter(Mandatory = true, HelpMessage = "Provide a format for the chart output.")] + public Formats Format { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Set the border color for the chart area.")] + public BasicColors ChartBorderColor { get; set; } = BasicColors.Black; + + [Parameter(Mandatory = false, HelpMessage = "Set the border size for the chart area.")] + public int ChartBorderSize { get; set; } = 1; + + [Parameter(Mandatory = false, HelpMessage = "Set the border style for the chart area.")] + public Enums.BorderStyles ChartBorderStyle { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Enable the border for the chart area.")] + public SwitchParameter EnableChartBorder { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Set the color palette for the chart.")] + public Enums.ColorPalettes ColorPalette { get; set; } = Enums.ColorPalettes.Category10; + + [Parameter(Mandatory = false, HelpMessage = "Enable custom color palette for the chart.")] + public SwitchParameter EnableCustomColorPalette { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Invert the custom color palette.")] + public SwitchParameter InvertCustomColorPalette { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Set the custom color palette for the chart.")] + public string[] CustomColorPalette { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Set the font name for the chart.")] + public string FontName { get; set; } = "Arial"; + + // Label Font settings + [Parameter(Mandatory = false, HelpMessage = "Set the font size for the chart labels.")] + public int LabelFontSize { get; set; } = 14; + + [Parameter(Mandatory = false, HelpMessage = "Set the font color for the chart labels.")] + public BasicColors LabelFontColor { get; set; } = BasicColors.Black; + + [Parameter(Mandatory = false, HelpMessage = "Enable bold font for the chart labels.")] + public SwitchParameter LabelBold { get; set; } + + // Axis label text + [Parameter(Mandatory = false, HelpMessage = "Set the label for the Y-axis.")] + public string LabelYAxis { get; set; } = ""; + + [Parameter(Mandatory = false, HelpMessage = "Set the label for the X-axis.")] + public string LabelXAxis { get; set; } = ""; + + // Chart orientation + [Parameter(Mandatory = false, HelpMessage = "Set the orientation of the chart area (Vertical or Horizontal).")] + public Enums.Orientations AreaOrientation { get; set; } = Enums.Orientations.Vertical; + + // Value axis suffix + [Parameter(Mandatory = false, HelpMessage = "Suffix appended to each value label and axis tick (e.g. '%', ' GB', ' TB'). Defaults to no suffix.")] + public string ValueSuffix { get; set; } = ""; + + // Axes margins + [Parameter(Mandatory = false, HelpMessage = "Set the top margin for the chart area axes.")] + public double AxesMarginsTop { get; set; } = 0.2; + + [Parameter(Mandatory = false, HelpMessage = "Set the bottom margin for the chart area axes.")] + public double AxesMarginsDown { get; set; } = 0.05; + + [Parameter(Mandatory = false, HelpMessage = "Set the left margin for the chart area axes.")] + public double AxesMarginsLeft { get; set; } = 0.05; + + [Parameter(Mandatory = false, HelpMessage = "Set the right margin for the chart area axes.")] + public double AxesMarginsRight { get; set; } = 0.05; + + [Parameter(Mandatory = false, HelpMessage = "Background color of the entire figure (canvas).")] + public BasicColors? FigureBackgroundColor { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Background color of the data area inside the axes.")] + public BasicColors? DataBackgroundColor { get; set; } + + // Watermark settings + [Parameter(Mandatory = false, HelpMessage = "Enable a watermark on the chart.")] + public SwitchParameter EnableWatermark { get; set; } + + [Parameter(Mandatory = false, HelpMessage = "Text to display as the watermark. Defaults to 'Confidential'.")] + public string WatermarkText { get; set; } = "Confidential"; + + [Parameter(Mandatory = false, HelpMessage = "Alignment of the watermark text. Defaults to 'MiddleCenter'.")] + public Enums.Alignments WatermarkAlignment { get; set; } = Enums.Alignments.MiddleCenter; + + [Parameter(Mandatory = false, HelpMessage = "Font name for the watermark text.")] + public string WatermarkFontName { get; set; } = "Arial"; + + [Parameter(Mandatory = false, HelpMessage = "Font size for the watermark text in points. Defaults to 24.")] + public int WatermarkFontSize { get; set; } = 24; + + [Parameter(Mandatory = false, HelpMessage = "Color of the watermark text.")] + public BasicColors WatermarkColor { get; set; } = BasicColors.Gray; + + [Parameter(Mandatory = false, HelpMessage = "Opacity of the watermark (0.0 fully transparent to 1.0 fully opaque). Defaults to 0.3.")] + public double WatermarkOpacity { get; set; } = 0.3; + + [Parameter(Mandatory = false, HelpMessage = "Rotation angle of the watermark text in degrees. Defaults to 0.")] + public float WatermarkRotation { get; set; } = 0; + + // Chart size + [Parameter(Mandatory = false, HelpMessage = "Set the width of the chart in pixels.")] + public int Width { get; set; } = 400; + + [Parameter(Mandatory = false, HelpMessage = "Set the height of the chart in pixels.")] + public int Height { get; set; } = 300; + + // Output folder + [Parameter(Mandatory = false, HelpMessage = "Set the output folder path for the chart file.")] + public string OutputFolderPath { get; set; } = Directory.GetCurrentDirectory(); + + protected override void ProcessRecord() + { + Chart.Reset(); + if (Values != null && Label != null && LegendCategories != null) + { + if (EnableLegend) + { + Chart.EnableLegend = EnableLegend; + Chart.LegendOrientation = LegendOrientation; + Chart.LegendAlignment = LegendAlignment; + Chart.LegendFontSize = LegendFontSize; + Chart.LegendFontColor = LegendFontColor; + Chart.LegendBorderStyle = LegendBorderStyle; + Chart.LegendBorderSize = LegendBorderSize; + Chart.LegendBorderColor = LegendBorderColor; + } + + if (EnableChartBorder) + { + Chart.EnableChartBorder = EnableChartBorder; + Chart.ChartBorderColor = ChartBorderColor; + Chart.ChartBorderSize = ChartBorderSize; + Chart.ChartBorderStyle = ChartBorderStyle; + } + + if (EnableCustomColorPalette) + { + if (CustomColorPalette != null && CustomColorPalette.Length > 0) + { + Chart.EnableCustomColorPalette = EnableCustomColorPalette; + Chart.InvertCustomColorPalette = InvertCustomColorPalette; + Chart.CustomColorPalette = CustomColorPalette; + } + else + { + throw new InvalidOperationException("EnableCustomColorPalette requires CustomColorPalette to be set."); + } + } + else + { + Chart.ColorPalette = ColorPalette; + } + + if (Title != null) + { + Chart.Title = Title; + Chart.TitleFontBold = TitleFontBold; + Chart.TitleFontSize = TitleFontSize; + Chart.TitleFontColor = TitleFontColor; + } + + Chart.FontName = FontName; + Chart.LabelFontSize = LabelFontSize; + Chart.LabelFontColor = LabelFontColor; + Chart.LabelBold = LabelBold; + + Chart.LabelXAxis = LabelXAxis; + Chart.LabelYAxis = LabelYAxis; + + Chart.AreaOrientation = AreaOrientation; + Chart.ValueSuffix = ValueSuffix; + + Chart.AxesMarginsTop = AxesMarginsTop; + Chart.AxesMarginsDown = AxesMarginsDown; + Chart.AxesMarginsLeft = AxesMarginsLeft; + Chart.AxesMarginsRight = AxesMarginsRight; + + Chart.FigureBackgroundColor = FigureBackgroundColor; + Chart.DataBackgroundColor = DataBackgroundColor; + + Chart.EnableWatermark = EnableWatermark; + Chart.WatermarkText = WatermarkText; + Chart.WatermarkAlignment = WatermarkAlignment; + Chart.WatermarkFontName = WatermarkFontName; + Chart.WatermarkFontSize = WatermarkFontSize; + Chart.WatermarkColor = WatermarkColor; + Chart.WatermarkOpacity = WatermarkOpacity; + Chart.WatermarkRotation = WatermarkRotation; + + Chart.OutputFolderPath = OutputFolderPath; + Chart.Format = Format; + + SingleStackedBar mySingleStackedBar = new SingleStackedBar(); + WriteObject(mySingleStackedBar.Chart(Values, Label, LegendCategories, Filename, Width, Height)); + } + else + { + WriteObject("Please provide Values, Label and LegendCategories parameters."); + } + } + } +} diff --git a/Sources/SingleStackedBarChart.cs b/Sources/SingleStackedBarChart.cs new file mode 100644 index 0000000..8aa55b8 --- /dev/null +++ b/Sources/SingleStackedBarChart.cs @@ -0,0 +1,253 @@ +using ScottPlot; +using System; +using System.Collections.Generic; +using System.IO; +using AsBuiltReportChart.Enums; +namespace AsBuiltReportChart +{ + internal class SingleStackedBar : Chart + { + static SingleStackedBar() { } + public object Chart(double[] values, string label, string[] categoryNames, string filename = "output", int width = 400, int height = 300) + { + Plot myPlot = new Plot(); + + if (EnableCustomColorPalette) + { + if (_customColorPalette != null && _customColorPalette.Length > 0) + { + myPlot.Add.Palette = colorPalette = new ScottPlot.Palettes.Custom(_customColorPalette); + } + else + { + throw new InvalidOperationException("CustomColorPalette is empty. Please provide valid color values."); + } + } + else + { + if (colorPalette != null) + { + myPlot.Add.Palette = colorPalette; + } + } + + // Validate that values.Length matches categoryNames.Length + if (values.Length != categoryNames.Length) + { + throw new ArgumentException("Error: Values and category names length must be equal."); + } + + // Build the single stacked bar + var bars = new List(); + double nextBarBase = 0; + for (int i = 0; i < values.Length; i++) + { + if (colorPalette != null) + { + bars.Add(new ScottPlot.Bar + { + Position = 0, + Value = nextBarBase + values[i], + ValueBase = nextBarBase, + FillColor = colorPalette.GetColor(i), + Label = $"{values[i]:0.##}{ValueSuffix}", + CenterLabel = true, + }); + nextBarBase += values[i]; + } + } + + var bar = myPlot.Add.Bars(bars); + + // Customize bar segment label style + bar.ValueLabelStyle.FontName = FontName; + bar.ValueLabelStyle.ForeColor = GetDrawingColor(LabelFontColor); + bar.ValueLabelStyle.Bold = LabelBold; + bar.ValueLabelStyle.FontSize = LabelFontSize; + + // Set X-axis label settings — swap axis text based on orientation + if (AreaOrientation == Orientations.Horizontal) + { + myPlot.Axes.Bottom.Label.Text = LabelYAxis; + } + else if (AreaOrientation == Orientations.Vertical) + { + myPlot.Axes.Bottom.Label.Text = LabelXAxis; + } + else + { + myPlot.Axes.Bottom.Label.Text = ""; + } + myPlot.Axes.Bottom.Label.FontSize = LabelFontSize; + myPlot.Axes.Bottom.Label.ForeColor = GetDrawingColor(LabelFontColor); + myPlot.Axes.Bottom.Label.FontName = FontName; + myPlot.Axes.Bottom.Label.Bold = LabelBold; + + myPlot.Axes.Bottom.TickLabelStyle.FontSize = LabelFontSize; + myPlot.Axes.Bottom.TickLabelStyle.ForeColor = GetDrawingColor(LabelFontColor); + myPlot.Axes.Bottom.TickLabelStyle.FontName = FontName; + + // Set Y-axis label settings — swap axis text based on orientation + if (AreaOrientation == Orientations.Horizontal) + { + myPlot.Axes.Left.Label.Text = LabelXAxis; + } + else if (AreaOrientation == Orientations.Vertical) + { + myPlot.Axes.Left.Label.Text = LabelYAxis; + } + else + { + myPlot.Axes.Left.Label.Text = ""; + } + myPlot.Axes.Left.Label.FontSize = LabelFontSize; + myPlot.Axes.Left.Label.ForeColor = GetDrawingColor(LabelFontColor); + myPlot.Axes.Left.Label.FontName = FontName; + myPlot.Axes.Left.Label.Bold = LabelBold; + + myPlot.Axes.Left.TickLabelStyle.FontSize = LabelFontSize; + myPlot.Axes.Left.TickLabelStyle.ForeColor = GetDrawingColor(LabelFontColor); + myPlot.Axes.Left.TickLabelStyle.FontName = FontName; + + // Category tick (single bar label) and value tick generators depend on orientation + var tickGenCategory = new ScottPlot.TickGenerators.NumericManual(); + tickGenCategory.AddMajor(0, label); + + var tickGenValue = new ScottPlot.TickGenerators.NumericAutomatic(); + tickGenValue.LabelFormatter = value => $"{value:0.##}{ValueSuffix}"; + + if (AreaOrientation == Orientations.Vertical) + { + // Vertical: categories on X (bottom), values on Y (left) + myPlot.Axes.Bottom.TickGenerator = tickGenCategory; + myPlot.Axes.Left.TickGenerator = tickGenValue; + } + else + { + // Horizontal: categories on Y (left), values on X (bottom) + myPlot.Axes.Left.TickGenerator = tickGenCategory; + myPlot.Axes.Bottom.TickGenerator = tickGenValue; + } + + // Legend + if (EnableLegend) + { + for (int i = 0; i < categoryNames.Length; i++) + { + if (colorPalette != null) + { + myPlot.Legend.ManualItems.Add(new LegendItem() + { + LabelText = categoryNames[i], + FillColor = colorPalette.GetColor(i) + }); + } + } + + myPlot.ShowLegend(); + + myPlot.Legend.FontName = FontName; + myPlot.Legend.FontSize = LegendFontSize; + myPlot.Legend.FontColor = GetDrawingColor(LegendFontColor); + + myPlot.Legend.OutlineColor = GetDrawingColor(LegendBorderColor); + myPlot.Legend.OutlineWidth = LegendBorderSize; + myPlot.Legend.OutlinePattern = LegendBorderStyleMap[LegendBorderStyle]; + + myPlot.Legend.Orientation = LegendOrientationMap[LegendOrientation]; + myPlot.Legend.Alignment = LegendAlignmentMap[LegendAlignment]; + } + + // Hide unnecessary plot components + myPlot.HideGrid(); + myPlot.Axes.Top.IsVisible = false; + myPlot.Axes.Right.IsVisible = false; + + if (EnableChartBorder) + { + myPlot.FigureBorder = new LineStyle() + { + Color = GetDrawingColor(ChartBorderColor), + Width = ChartBorderSize, + Pattern = ChartBorderStyleMap[ChartBorderStyle], + }; + } + + // Set title properties + if (Title != null) + { + myPlot.Title(Title); + myPlot.Axes.Title.Label.FontSize = TitleFontSize; + myPlot.Axes.Title.Label.ForeColor = GetDrawingColor(TitleFontColor); + myPlot.Axes.Title.Label.Bold = TitleFontBold; + myPlot.Axes.Title.Label.FontName = FontName; + } + + // Set margins + // myPlot.Axes.Margins(left: AxesMarginsLeft, right: AxesMarginsRight, bottom: AxesMarginsDown, top: AxesMarginsTop); + + // Set background colors + if (FigureBackgroundColor.HasValue) + { + myPlot.FigureBackground.Color = GetDrawingColor(FigureBackgroundColor.Value); + } + if (DataBackgroundColor.HasValue) + { + myPlot.DataBackground.Color = GetDrawingColor(DataBackgroundColor.Value); + } + + // Set bar orientation + bar.Horizontal = (AreaOrientation == Orientations.Horizontal); + + // Compute cumulative range and set axis limits + double cumulative = 0; + double minSum = 0; + double maxSum = 0; + + foreach (double v in values) + { + cumulative += v; + if (cumulative < minSum) minSum = cumulative; + if (cumulative > maxSum) maxSum = cumulative; + } + + double minVal = Math.Min(0, minSum); + double maxVal = Math.Max(0, maxSum); + + if (minVal == maxVal) + { + double pad = (minVal == 0) ? 1.0 : Math.Abs(minVal) * 0.1; + minVal -= pad; + maxVal += pad; + } + else + { + double range = maxVal - minVal; + double pad = range * 0.1; + minVal -= pad; + maxVal += pad; + } + + // Single bar at position 0 — swap value/category axes for orientation + double barIndex = 0; + if (AreaOrientation == Orientations.Horizontal) + { + // Horizontal: value axis is X, category axis is Y + myPlot.Axes.SetLimits(minVal, maxVal, barIndex - 2, barIndex + 2); + } + else + { + // Vertical: category axis is X, value axis is Y + myPlot.Axes.SetLimits(barIndex - 2, barIndex + 2, minVal, maxVal); + } + + // Apply watermark if enabled + ApplyWatermark(myPlot); + + // Set filepath + string Filepath = _outputFolderPath ?? Directory.GetCurrentDirectory(); + + return SaveInFormat(myPlot, width, height, Filepath, filename, Format); + } + } +} diff --git a/Sources/StackedBarChart.cs b/Sources/StackedBarChart.cs index 7194b40..0b39196 100755 --- a/Sources/StackedBarChart.cs +++ b/Sources/StackedBarChart.cs @@ -288,7 +288,7 @@ public object Chart(List values, string[] labels, string[] categoryNam xMin = minVal; xMax = maxVal; yMin = barIndex - 2; - yMax = barIndex +2; + yMax = barIndex + 2; } else { diff --git a/Tests/AsBuiltReport.Chart.Functions.Tests.ps1 b/Tests/AsBuiltReport.Chart.Functions.Tests.ps1 index d8d92dc..4d44cc3 100644 --- a/Tests/AsBuiltReport.Chart.Functions.Tests.ps1 +++ b/Tests/AsBuiltReport.Chart.Functions.Tests.ps1 @@ -20,6 +20,9 @@ Describe 'AsBuiltReport.Chart Exported Functions' { It 'Should export New-SignalChart' { Get-Command -Module AsBuiltReport.Chart -Name New-SignalChart | Should -Not -BeNullOrEmpty } + It 'Should export New-SingleStackedBarChart' { + Get-Command -Module AsBuiltReport.Chart -Name New-SingleStackedBarChart | Should -Not -BeNullOrEmpty + } Context 'New-PieChart' { It 'Should run without error with sample input' { @@ -83,6 +86,44 @@ Describe 'AsBuiltReport.Chart Exported Functions' { } } + Context 'New-SingleStackedBarChart' { + It 'Should run without error with sample input' { + { New-SingleStackedBarChart -Title 'Test' -Values @(40, 50, 10) -Label 'Total' -LegendCategories @('A', 'B', 'C') -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw + } + It 'Should return a file path as output' { + $result = New-SingleStackedBarChart -Title 'Test' -Values @(40, 50, 10) -Label 'Total' -LegendCategories @('A', 'B', 'C') -Format 'png' -OutputFolderPath $TestDrive + $result | Should -BeOfType 'System.IO.FileSystemInfo' + Test-Path $result | Should -BeTrue + } + It 'Should throw error for missing mandatory parameters' { + { New-SingleStackedBarChart } | Should -Throw + } + It 'Should throw error for mismatched Values and LegendCategories' { + { New-SingleStackedBarChart -Title 'Test' -Values @(40, 50) -Label 'Total' -LegendCategories @('A') -Format 'png' -OutputFolderPath $TestDrive } | Should -Throw -ExpectedMessage "Error: Values and category names length must be equal." + } + It 'Should run without error with legend enabled' { + { New-SingleStackedBarChart -Title 'Test' -Values @(40, 50, 10) -Label 'Total' -LegendCategories @('A', 'B', 'C') -Format 'png' -OutputFolderPath $TestDrive -EnableLegend -LegendOrientation Horizontal -LegendAlignment LowerCenter } | Should -Not -Throw + } + It 'Should run without error with watermark enabled' { + { New-SingleStackedBarChart -Title 'Test' -Values @(40, 50, 10) -Label 'Total' -LegendCategories @('A', 'B', 'C') -Format 'png' -OutputFolderPath $TestDrive -EnableWatermark } | Should -Not -Throw + } + It 'Should run without error with horizontal orientation' { + { New-SingleStackedBarChart -Title 'Test' -Values @(40, 50, 10) -Label 'Total' -LegendCategories @('A', 'B', 'C') -Format 'png' -OutputFolderPath $TestDrive -AreaOrientation Horizontal } | Should -Not -Throw + } + It 'Should run without error with custom color palette' { + { New-SingleStackedBarChart -Title 'Test' -Values @(40, 50, 10) -Label 'Total' -LegendCategories @('A', 'B', 'C') -Format 'png' -OutputFolderPath $TestDrive -EnableCustomColorPalette -CustomColorPalette @('#4472C4', '#ED7D31', '#A5A5A5') } | Should -Not -Throw + } + It 'Should run without error with no suffix (generic numeric data)' { + { New-SingleStackedBarChart -Title 'Test' -Values @(1.5, 2.3, 0.8) -Label 'Total' -LegendCategories @('A', 'B', 'C') -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw + } + It 'Should run without error with a custom ValueSuffix' { + { New-SingleStackedBarChart -Title 'Test' -Values @(512, 1024, 256) -Label 'Disk' -LegendCategories @('OS', 'Data', 'Swap') -Format 'png' -OutputFolderPath $TestDrive -ValueSuffix ' GB' } | Should -Not -Throw + } + It 'Should run without error with percent suffix' { + { New-SingleStackedBarChart -Title 'Test' -Values @(40, 50, 10) -Label 'Total' -LegendCategories @('A', 'B', 'C') -Format 'png' -OutputFolderPath $TestDrive -ValueSuffix '%' } | Should -Not -Throw + } + } + Context 'New-SignalChart' { It 'Should run without error with a single signal line' { { New-SignalChart -Title 'Test' -Values @(,[double[]]@(1, 2, 3, 4)) -Format 'png' -OutputFolderPath $TestDrive } | Should -Not -Throw From 34e5b21c270c89b0d75819ab549bc8b244afcfd9 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Tue, 5 May 2026 16:48:00 -0400 Subject: [PATCH 5/9] Update CHANGELOG.md for version 0.3.2: add New-SingleStackedBarChart cmdlet and corresponding pester test --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0b8af9..9e9da47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.3.2] - Unreleased +### 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 -- Add New-SingleStackedBarChart cmdlet and update module exports ## [0.3.1] - 2026-04-23 From 74c88f346dee4f2a21d07398bd5cbb620c4fd4d7 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Tue, 5 May 2026 16:53:14 -0400 Subject: [PATCH 6/9] Update CHANGELOG.md for version 0.3.2 release date --- .github/workflows/Release.yml | 69 +++++++++++++++++++---------------- CHANGELOG.md | 2 +- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 06a4ab3..79a0a0b 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -2,7 +2,7 @@ name: Publish PowerShell Module on: release: - types: [published] + types: [ published ] jobs: publish-to-psgallery: @@ -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 - 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 - 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 - 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 - 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 - name: Run tests run: dotnet test --no-build --verbosity normal ./Sources - name: Set PSRepository to Trusted for PowerShell Gallery @@ -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 }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e9da47..82ca8ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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] - Unreleased +## [0.3.2] - 2026-05-05 ### Added From 600c8869e8413c7ae745b814fd57b575d73481ad Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Tue, 5 May 2026 17:03:23 -0400 Subject: [PATCH 7/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Examples/Example11.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/Example11.ps1 b/Examples/Example11.ps1 index 9c440c6..3c9538d 100644 --- a/Examples/Example11.ps1 +++ b/Examples/Example11.ps1 @@ -116,5 +116,5 @@ New-SingleStackedBarChart ` -Height 300 ` -Format $Format ` -OutputFolderPath $OutputFolderPath ` - -Filename 'Example11-SingleStackedBarChart-Advanced' ` ` + -Filename 'Example11-SingleStackedBarChart-Advanced' ` -ValueSuffix '%' From b854a29c5a2070606ca700afd967d07eb4a44c3a Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Tue, 5 May 2026 17:03:52 -0400 Subject: [PATCH 8/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Examples/Example10.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Examples/Example10.ps1 b/Examples/Example10.ps1 index 3f05bde..ec8507c 100644 --- a/Examples/Example10.ps1 +++ b/Examples/Example10.ps1 @@ -45,7 +45,8 @@ $OutputFolderPath = Resolve-Path $Path - $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. - The segment labels rendered inside the bar automatically include the '%' suffix. + 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. #> From 6799af1c7e80e5a14d1c52024190612a7f5a3550 Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Tue, 5 May 2026 17:04:14 -0400 Subject: [PATCH 9/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 0e1e898..2d86f45 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -14,7 +14,7 @@ Sources/ ← C# library (ScottPlot + SkiaSharp, netstandard2.0 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-SignalChart + New-StackedBarChart, New-SingleStackedBarChart, New-SignalChart Src/Assemblies/ ← Pre-compiled DLLs, organized by platform: Core/linux-x64/ Core/windows-x64/